11#[ cfg( not( feature = "library" ) ) ]
22use cosmwasm_std:: entry_point;
33use cosmwasm_std:: {
4- from_binary, to_binary, Addr , Binary , Deps , DepsMut , Env , IbcMsg , IbcQuery , MessageInfo , Order ,
5- PortIdResponse , Response , StdResult ,
4+ ensure_eq , from_binary, to_binary, Addr , Binary , Deps , DepsMut , Env , IbcMsg , IbcQuery ,
5+ MessageInfo , Order , PortIdResponse , Response , StdResult ,
66} ;
77
88use cw2:: { get_contract_version, set_contract_version} ;
99use cw20:: { Cw20Coin , Cw20ReceiveMsg } ;
10+ use cw_storage_plus:: Bound ;
1011
1112use crate :: amount:: Amount ;
1213use crate :: error:: ContractError ;
1314use crate :: ibc:: Ics20Packet ;
1415use crate :: msg:: {
15- ChannelResponse , ExecuteMsg , InitMsg , ListChannelsResponse , MigrateMsg , PortResponse , QueryMsg ,
16- TransferMsg ,
16+ AllowMsg , AllowedInfo , AllowedResponse , ChannelResponse , ConfigResponse , ExecuteMsg , InitMsg ,
17+ ListAllowedResponse , ListChannelsResponse , MigrateMsg , PortResponse , QueryMsg , TransferMsg ,
1718} ;
18- use crate :: state:: { Config , CHANNEL_INFO , CHANNEL_STATE , CONFIG } ;
19+ use crate :: state:: { AllowInfo , Config , ALLOW_LIST , CHANNEL_INFO , CHANNEL_STATE , CONFIG } ;
1920use cw_utils:: { nonpayable, one_coin} ;
2021
2122// version info for migration info
@@ -32,8 +33,18 @@ pub fn instantiate(
3233 set_contract_version ( deps. storage , CONTRACT_NAME , CONTRACT_VERSION ) ?;
3334 let cfg = Config {
3435 default_timeout : msg. default_timeout ,
36+ gov_contract : deps. api . addr_validate ( & msg. gov_contract ) ?,
3537 } ;
3638 CONFIG . save ( deps. storage , & cfg) ?;
39+
40+ // add all allows
41+ for allowed in msg. allowlist {
42+ let contract = deps. api . addr_validate ( & allowed. contract ) ?;
43+ let info = AllowInfo {
44+ gas_limit : allowed. gas_limit ,
45+ } ;
46+ ALLOW_LIST . save ( deps. storage , & contract, & info) ?;
47+ }
3748 Ok ( Response :: default ( ) )
3849}
3950
@@ -50,6 +61,7 @@ pub fn execute(
5061 let coin = one_coin ( & info) ?;
5162 execute_transfer ( deps, env, msg, Amount :: Native ( coin) , info. sender )
5263 }
64+ ExecuteMsg :: Allow ( allow) => execute_allow ( deps, env, info, allow) ,
5365 }
5466}
5567
@@ -81,11 +93,18 @@ pub fn execute_transfer(
8193 return Err ( ContractError :: NoFunds { } ) ;
8294 }
8395 // ensure the requested channel is registered
84- // FIXME: add a .has method to map to make this faster
85- if CHANNEL_INFO . may_load ( deps. storage , & msg. channel ) ?. is_none ( ) {
96+ if !CHANNEL_INFO . has ( deps. storage , & msg. channel ) {
8697 return Err ( ContractError :: NoSuchChannel { id : msg. channel } ) ;
8798 }
8899
100+ // if cw20 token, ensure it is whitelisted
101+ if let Amount :: Cw20 ( coin) = & amount {
102+ let addr = deps. api . addr_validate ( & coin. address ) ?;
103+ ALLOW_LIST
104+ . may_load ( deps. storage , & addr) ?
105+ . ok_or ( ContractError :: NotOnAllowList ) ?;
106+ } ;
107+
89108 // delta from user is in seconds
90109 let timeout_delta = match msg. timeout {
91110 Some ( t) => t,
@@ -103,7 +122,7 @@ pub fn execute_transfer(
103122 ) ;
104123 packet. validate ( ) ?;
105124
106- // prepare message
125+ // prepare ibc message
107126 let msg = IbcMsg :: SendPacket {
108127 channel_id : msg. channel ,
109128 data : to_binary ( & packet) ?,
@@ -124,6 +143,48 @@ pub fn execute_transfer(
124143 Ok ( res)
125144}
126145
146+ /// The gov contract can allow new contracts, or increase the gas limit on existing contracts.
147+ /// It cannot block or reduce the limit to avoid forcible sticking tokens in the channel.
148+ pub fn execute_allow (
149+ deps : DepsMut ,
150+ _env : Env ,
151+ info : MessageInfo ,
152+ allow : AllowMsg ,
153+ ) -> Result < Response , ContractError > {
154+ let cfg = CONFIG . load ( deps. storage ) ?;
155+ ensure_eq ! ( info. sender, cfg. gov_contract, ContractError :: Unauthorized ) ;
156+
157+ let contract = deps. api . addr_validate ( & allow. contract ) ?;
158+ let set = AllowInfo {
159+ gas_limit : allow. gas_limit ,
160+ } ;
161+ ALLOW_LIST . update ( deps. storage , & contract, |old| {
162+ if let Some ( old) = old {
163+ // we must ensure it increases the limit
164+ match ( old. gas_limit , set. gas_limit ) {
165+ ( None , Some ( _) ) => return Err ( ContractError :: CannotLowerGas ) ,
166+ ( Some ( old) , Some ( new) ) if new < old => return Err ( ContractError :: CannotLowerGas ) ,
167+ _ => { }
168+ } ;
169+ }
170+ Ok ( AllowInfo {
171+ gas_limit : allow. gas_limit ,
172+ } )
173+ } ) ?;
174+
175+ let gas = if let Some ( gas) = allow. gas_limit {
176+ gas. to_string ( )
177+ } else {
178+ "None" . to_string ( )
179+ } ;
180+
181+ let res = Response :: new ( )
182+ . add_attribute ( "action" , "allow" )
183+ . add_attribute ( "contract" , allow. contract )
184+ . add_attribute ( "gas_limit" , gas) ;
185+ Ok ( res)
186+ }
187+
127188#[ cfg_attr( not( feature = "library" ) , entry_point) ]
128189pub fn migrate ( deps : DepsMut , _env : Env , _msg : MigrateMsg ) -> Result < Response , ContractError > {
129190 let version = get_contract_version ( deps. storage ) ?;
@@ -141,6 +202,11 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
141202 QueryMsg :: Port { } => to_binary ( & query_port ( deps) ?) ,
142203 QueryMsg :: ListChannels { } => to_binary ( & query_list ( deps) ?) ,
143204 QueryMsg :: Channel { id } => to_binary ( & query_channel ( deps, id) ?) ,
205+ QueryMsg :: Config { } => to_binary ( & query_config ( deps) ?) ,
206+ QueryMsg :: Allowed { contract } => to_binary ( & query_allowed ( deps, contract) ?) ,
207+ QueryMsg :: ListAllowed { start_after, limit } => {
208+ to_binary ( & list_allowed ( deps, start_after, limit) ?)
209+ }
144210 }
145211}
146212
@@ -183,6 +249,59 @@ pub fn query_channel(deps: Deps, id: String) -> StdResult<ChannelResponse> {
183249 } )
184250}
185251
252+ fn query_config ( deps : Deps ) -> StdResult < ConfigResponse > {
253+ let cfg = CONFIG . load ( deps. storage ) ?;
254+ let res = ConfigResponse {
255+ default_timeout : cfg. default_timeout ,
256+ gov_contract : cfg. gov_contract . into ( ) ,
257+ } ;
258+ Ok ( res)
259+ }
260+
261+ fn query_allowed ( deps : Deps , contract : String ) -> StdResult < AllowedResponse > {
262+ let addr = deps. api . addr_validate ( & contract) ?;
263+ let info = ALLOW_LIST . may_load ( deps. storage , & addr) ?;
264+ let res = match info {
265+ None => AllowedResponse {
266+ is_allowed : false ,
267+ gas_limit : None ,
268+ } ,
269+ Some ( a) => AllowedResponse {
270+ is_allowed : true ,
271+ gas_limit : a. gas_limit ,
272+ } ,
273+ } ;
274+ Ok ( res)
275+ }
276+
277+ // settings for pagination
278+ const MAX_LIMIT : u32 = 30 ;
279+ const DEFAULT_LIMIT : u32 = 10 ;
280+
281+ fn list_allowed (
282+ deps : Deps ,
283+ start_after : Option < String > ,
284+ limit : Option < u32 > ,
285+ ) -> StdResult < ListAllowedResponse > {
286+ let limit = limit. unwrap_or ( DEFAULT_LIMIT ) . min ( MAX_LIMIT ) as usize ;
287+ let start = match start_after {
288+ Some ( x) => Some ( Bound :: exclusive ( deps. api . addr_validate ( & x) ?. into_string ( ) ) ) ,
289+ None => None ,
290+ } ;
291+
292+ let allow = ALLOW_LIST
293+ . range ( deps. storage , start, None , Order :: Ascending )
294+ . take ( limit)
295+ . map ( |item| {
296+ item. map ( |( addr, allow) | AllowedInfo {
297+ contract : addr. into ( ) ,
298+ gas_limit : allow. gas_limit ,
299+ } )
300+ } )
301+ . collect :: < StdResult < _ > > ( ) ?;
302+ Ok ( ListAllowedResponse { allow } )
303+ }
304+
186305#[ cfg( test) ]
187306mod test {
188307 use super :: * ;
@@ -195,7 +314,7 @@ mod test {
195314
196315 #[ test]
197316 fn setup_and_query ( ) {
198- let deps = setup ( & [ "channel-3" , "channel-7" ] ) ;
317+ let deps = setup ( & [ "channel-3" , "channel-7" ] , & [ ] ) ;
199318
200319 let raw_list = query ( deps. as_ref ( ) , mock_env ( ) , QueryMsg :: ListChannels { } ) . unwrap ( ) ;
201320 let list_res: ListChannelsResponse = from_binary ( & raw_list) . unwrap ( ) ;
@@ -230,7 +349,7 @@ mod test {
230349 #[ test]
231350 fn proper_checks_on_execute_native ( ) {
232351 let send_channel = "channel-5" ;
233- let mut deps = setup ( & [ send_channel, "channel-10" ] ) ;
352+ let mut deps = setup ( & [ send_channel, "channel-10" ] , & [ ] ) ;
234353
235354 let mut transfer = TransferMsg {
236355 channel : send_channel. to_string ( ) ,
@@ -242,6 +361,7 @@ mod test {
242361 let msg = ExecuteMsg :: Transfer ( transfer. clone ( ) ) ;
243362 let info = mock_info ( "foobar" , & coins ( 1234567 , "ucosm" ) ) ;
244363 let res = execute ( deps. as_mut ( ) , mock_env ( ) , info, msg) . unwrap ( ) ;
364+ assert_eq ! ( res. messages[ 0 ] . gas_limit, None ) ;
245365 assert_eq ! ( 1 , res. messages. len( ) ) ;
246366 if let CosmosMsg :: Ibc ( IbcMsg :: SendPacket {
247367 channel_id,
@@ -289,9 +409,9 @@ mod test {
289409 #[ test]
290410 fn proper_checks_on_execute_cw20 ( ) {
291411 let send_channel = "channel-15" ;
292- let mut deps = setup ( & [ "channel-3" , send_channel] ) ;
293-
294412 let cw20_addr = "my-token" ;
413+ let mut deps = setup ( & [ "channel-3" , send_channel] , & [ ( cw20_addr, 123456 ) ] ) ;
414+
295415 let transfer = TransferMsg {
296416 channel : send_channel. to_string ( ) ,
297417 remote_address : "foreign-address" . to_string ( ) ,
@@ -307,6 +427,7 @@ mod test {
307427 let info = mock_info ( cw20_addr, & [ ] ) ;
308428 let res = execute ( deps. as_mut ( ) , mock_env ( ) , info, msg. clone ( ) ) . unwrap ( ) ;
309429 assert_eq ! ( 1 , res. messages. len( ) ) ;
430+ assert_eq ! ( res. messages[ 0 ] . gas_limit, None ) ;
310431 if let CosmosMsg :: Ibc ( IbcMsg :: SendPacket {
311432 channel_id,
312433 data,
@@ -330,4 +451,27 @@ mod test {
330451 let err = execute ( deps. as_mut ( ) , mock_env ( ) , info, msg) . unwrap_err ( ) ;
331452 assert_eq ! ( err, ContractError :: Payment ( PaymentError :: NonPayable { } ) ) ;
332453 }
454+
455+ #[ test]
456+ fn execute_cw20_fails_if_not_whitelisted ( ) {
457+ let send_channel = "channel-15" ;
458+ let mut deps = setup ( & [ "channel-3" , send_channel] , & [ ] ) ;
459+
460+ let cw20_addr = "my-token" ;
461+ let transfer = TransferMsg {
462+ channel : send_channel. to_string ( ) ,
463+ remote_address : "foreign-address" . to_string ( ) ,
464+ timeout : Some ( 7777 ) ,
465+ } ;
466+ let msg = ExecuteMsg :: Receive ( Cw20ReceiveMsg {
467+ sender : "my-account" . into ( ) ,
468+ amount : Uint128 :: new ( 888777666 ) ,
469+ msg : to_binary ( & transfer) . unwrap ( ) ,
470+ } ) ;
471+
472+ // works with proper funds
473+ let info = mock_info ( cw20_addr, & [ ] ) ;
474+ let err = execute ( deps. as_mut ( ) , mock_env ( ) , info, msg) . unwrap_err ( ) ;
475+ assert_eq ! ( err, ContractError :: NotOnAllowList ) ;
476+ }
333477}
0 commit comments