use cosmwasm_std::{ attr, coin, to_binary, Addr, BalanceResponse as NativeBalanceResponse, BankQuery, Coin, QueryRequest, Uint128, WasmQuery, }; use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse, TokenInfoResponse}; use astroport::asset::{native_asset_info, token_asset_info, AssetInfo}; use astroport::native_coin_wrapper::{Config, Cw20HookMsg, ExecuteMsg, InstantiateMsg, QueryMsg}; use astroport::token::InstantiateMsg as AstroInstantiateMsg; use cw_multi_test::{App, ContractWrapper, Executor}; fn mock_app(owner: Addr, coins: Vec) -> App { App::new(|router, _, storage| { router.bank.init_balance(storage, &owner, coins).unwrap(); }) } fn check_balance(app: &mut App, user: Addr, asset_info: &AssetInfo) -> Uint128 { match asset_info { AssetInfo::Token { contract_addr } => { let res: Result = app.wrap().query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: contract_addr.to_string(), msg: to_binary(&Cw20QueryMsg::Balance { address: user.to_string(), }) .unwrap(), })); res.unwrap().balance } AssetInfo::NativeToken { denom } => { let res: Result = app.wrap().query(&QueryRequest::Bank(BankQuery::Balance { address: user.to_string(), denom: denom.to_string(), })); res.unwrap().amount.amount } } } fn store_astro_code_id(app: &mut App) -> u64 { let astro_token_contract = Box::new(ContractWrapper::new_with_empty( astroport_token::contract::execute, astroport_token::contract::instantiate, astroport_token::contract::query, )); app.store_code(astro_token_contract) } fn create_astro_token(app: &mut App, astro_token_code_id: u64, owner: &Addr) -> Addr { let msg = AstroInstantiateMsg { name: String::from("Astro token"), symbol: String::from("ASTRO"), decimals: 6, initial_balances: vec![], mint: Some(MinterResponse { minter: owner.to_string(), cap: Some(Uint128::new(100000000000)), }), marketing: None, }; app.instantiate_contract( astro_token_code_id, owner.clone(), &msg, &[], String::from("ASTRO"), None, ) .unwrap() } fn mint_some_astro( router: &mut App, owner: Addr, astro_token_instance: Addr, to: &str, amount: Uint128, ) { let res = router .execute_contract( owner.clone(), astro_token_instance.clone(), &cw20::Cw20ExecuteMsg::Mint { recipient: String::from(to), amount, }, &[], ) .unwrap(); assert_eq!(res.events[1].attributes[1], attr("action", "mint")); assert_eq!(res.events[1].attributes[2], attr("to", String::from(to))); assert_eq!(res.events[1].attributes[3], attr("amount", amount)); } fn store_native_wrapper_code(app: &mut App) -> u64 { let contract = Box::new( ContractWrapper::new_with_empty( astroport_native_coin_wrapper::contract::execute, astroport_native_coin_wrapper::contract::instantiate, astroport_native_coin_wrapper::contract::query, ) .with_reply_empty(astroport_native_coin_wrapper::contract::reply), ); app.store_code(contract) } #[test] fn proper_initialization() { let owner = Addr::unchecked("owner"); let mut app = mock_app(owner.clone(), vec![]); let native_wrapper_code_id = store_native_wrapper_code(&mut app); let astro_token_code_id = store_astro_code_id(&mut app); let native_wrapper_instance = app .instantiate_contract( native_wrapper_code_id, Addr::unchecked(owner.clone()), &InstantiateMsg { denom: "ibc/EBD5A24C554198EBAF44979C5B4D2C2D312E6EBAB71962C92F735499C7575839" .to_string(), token_code_id: astro_token_code_id, token_decimals: 15, }, &[], "CW20 native tokens wrapper contract", None, ) .unwrap(); let config_res: Config = app .wrap() .query_wasm_smart(&native_wrapper_instance, &QueryMsg::Config {}) .unwrap(); assert_eq!( "ibc/EBD5A24C554198EBAF44979C5B4D2C2D312E6EBAB71962C92F735499C7575839".to_string(), config_res.denom.to_string() ); assert_eq!("contract1", config_res.token.to_string()); let token_res: TokenInfoResponse = app .wrap() .query_wasm_smart(&config_res.token, &Cw20QueryMsg::TokenInfo {}) .unwrap(); assert_eq!("IBC/EBD5", token_res.symbol.to_string()); assert_eq!( "CW20-wrapped ibc/EBD5A24C554198EBAF44979C5B4D2C2D3", token_res.name.to_string() ); assert_eq!("15", token_res.decimals.to_string()); } #[test] fn check_wrap_and_unwrap() { let owner = Addr::unchecked("owner"); let user1 = Addr::unchecked("user1"); let mut app = mock_app( owner.clone(), vec![ Coin { denom: "ibc/EBD5A24C554198EBA".to_string(), amount: Uint128::new(100_000_000_000u128), }, Coin { denom: "wrapped_coin_1".to_string(), amount: Uint128::new(100_000_000_000u128), }, ], ); // Send native asset to user1 app.send_tokens( owner.clone(), user1.clone(), &[coin(100, "ibc/EBD5A24C554198EBA".to_string())], ) .unwrap(); let native_wrapper_code_id = store_native_wrapper_code(&mut app); let astro_token_code_id = store_astro_code_id(&mut app); let astro_token_addr = create_astro_token(&mut app, astro_token_code_id, &owner); let native_wrapper_instance = app .instantiate_contract( native_wrapper_code_id, Addr::unchecked(owner.clone()), &InstantiateMsg { denom: "ibc/EBD5A24C554198EBA".to_string(), token_code_id: astro_token_code_id, token_decimals: 6, }, &[], "CW20 native tokens wrapper contract", None, ) .unwrap(); let res = app .wrap() .query::(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr: native_wrapper_instance.to_string(), msg: to_binary(&QueryMsg::Config {}).unwrap(), })) .unwrap(); let wrapped_cw20_native_token = token_asset_info(res.token); assert_eq!("contract2", wrapped_cw20_native_token.to_string()); let err = app .execute_contract( Addr::unchecked("user1"), native_wrapper_instance.clone(), &ExecuteMsg::Wrap {}, &[], ) .unwrap_err(); assert_eq!(err.root_cause().to_string(), "No funds sent"); let err = app .execute_contract( Addr::unchecked("owner"), native_wrapper_instance.clone(), &ExecuteMsg::Wrap {}, &[ coin(20, "ibc/EBD5A24C554198EBA"), coin(30, "wrapped_coin_1"), ], ) .unwrap_err(); assert_eq!( err.root_cause().to_string(), "Sent more than one denomination" ); // try to unwrap cw20 tokens to get native tokens let err = app .execute_contract( user1.clone(), Addr::unchecked(wrapped_cw20_native_token.to_string()), &Cw20ExecuteMsg::Send { contract: native_wrapper_instance.to_string(), msg: to_binary(&Cw20HookMsg::Unwrap {}).unwrap(), amount: Uint128::from(10u128), }, &[], ) .unwrap_err(); assert_eq!("Cannot Sub with 0 and 10", err.root_cause().to_string()); // check user1's wrapped cw20 token balance assert_eq!( check_balance(&mut app, user1.clone(), &wrapped_cw20_native_token), Uint128::new(0) ); app.execute_contract( Addr::unchecked("user1"), native_wrapper_instance.clone(), &ExecuteMsg::Wrap {}, &[coin(20, "ibc/EBD5A24C554198EBA")], ) .unwrap(); // check user1's native coin balance assert_eq!( check_balance( &mut app, user1.clone(), &native_asset_info("ibc/EBD5A24C554198EBA".to_string()) ), Uint128::new(80) ); // check user1's wrapped cw20 token balance assert_eq!( check_balance(&mut app, user1.clone(), &wrapped_cw20_native_token), Uint128::new(20) ); // check wrapper's wrapped cw20 token balance assert_eq!( check_balance( &mut app, native_wrapper_instance.clone(), &wrapped_cw20_native_token ), Uint128::new(0) ); // check wrapper's native coin balance assert_eq!( check_balance( &mut app, native_wrapper_instance.clone(), &native_asset_info("ibc/EBD5A24C554198EBA".to_string()) ), Uint128::new(20) ); mint_some_astro( &mut app, owner.clone(), astro_token_addr.clone(), owner.as_str(), Uint128::new(100), ); // try to unwrap cw20 tokens from the other cw20 token. let resp = app .execute_contract( owner.clone(), astro_token_addr.clone(), &Cw20ExecuteMsg::Send { contract: native_wrapper_instance.to_string(), msg: to_binary(&Cw20HookMsg::Unwrap {}).unwrap(), amount: Uint128::from(10u128), }, &[], ) .unwrap_err(); assert_eq!(resp.root_cause().to_string(), "Unauthorized"); // try to unwrap cw20 tokens from our cw20 token. app.execute_contract( user1.clone(), Addr::unchecked(wrapped_cw20_native_token.to_string()), &Cw20ExecuteMsg::Send { contract: native_wrapper_instance.to_string(), msg: to_binary(&Cw20HookMsg::Unwrap {}).unwrap(), amount: Uint128::from(10u128), }, &[], ) .unwrap(); // check user1's balances assert_eq!( check_balance(&mut app, user1.clone(), &wrapped_cw20_native_token), Uint128::new(10) ); assert_eq!( check_balance( &mut app, user1.clone(), &native_asset_info("ibc/EBD5A24C554198EBA".to_string()) ), Uint128::new(90) ); // check wrapper's balances assert_eq!( check_balance( &mut app, native_wrapper_instance.clone(), &wrapped_cw20_native_token ), Uint128::zero() ); assert_eq!( check_balance( &mut app, native_wrapper_instance.clone(), &native_asset_info("ibc/EBD5A24C554198EBA".to_string()) ), Uint128::new(10) ); }