use cosmwasm_std::{ coin, testing::{mock_dependencies, mock_env, mock_info}, to_binary, Binary, ContractResult, CosmosMsg, Empty, Event, Reply, ReplyOn, Response, SubMsgResponse, SystemError, SystemResult, Uint128, WasmMsg, WasmQuery, }; use cw_token_bridge::msg::TransferInfoResponse; use ibc_translator::{ contract::{execute, instantiate, migrate, query, reply}, msg::{ChannelResponse, ExecuteMsg, InstantiateMsg, QueryMsg, COMPLETE_TRANSFER_REPLY_ID}, state::{CHAIN_TO_CHANNEL_MAP, CURRENT_TRANSFER, CW_DENOMS, TOKEN_BRIDGE_CONTRACT}, }; use wormhole_bindings::tokenfactory::{TokenFactoryMsg, TokenMsg}; mod test_setup; use test_setup::{ execute_custom_mock_deps, mock_env_custom_contract, WORMHOLE_CONTRACT_ADDR, WORMHOLE_USER_ADDR, }; // TESTS // 1. instantiate // 1. happy path // 2. migrate // 1. happy path // 3. execute // 1. CompleteTransferAndConvert // 2. GatewayConvertAndTransfer // 3. GatewayConvertAndTransferWithPaylod // 4. SubmitUpdateChainToChannelMap // 4. reply // 1. happy path // 2. no id match // 5. query // 1. happy path // TESTS: instantiate // 1. happy path #[test] fn instantiate_happy_path() { let tokenbridge_addr = "faketokenbridge".to_string(); let mut deps = mock_dependencies(); let env = mock_env(); let info = mock_info(WORMHOLE_USER_ADDR, &[]); let msg = InstantiateMsg { token_bridge_contract: tokenbridge_addr.clone(), }; let response = instantiate(deps.as_mut(), env, info, msg).unwrap(); // response should have 2 attributes assert_eq!(response.attributes.len(), 2); assert_eq!(response.attributes[0].key, "action"); assert_eq!(response.attributes[0].value, "instantiate"); assert_eq!(response.attributes[1].key, "owner"); assert_eq!(response.attributes[1].value, WORMHOLE_USER_ADDR); // contract addrs should have been set in storage let saved_tb = TOKEN_BRIDGE_CONTRACT.load(deps.as_mut().storage).unwrap(); assert_eq!(saved_tb, tokenbridge_addr); } // TESTS: migrate // 1. happy path #[test] fn migrate_happy_path() { let mut deps = mock_dependencies(); let env = mock_env(); let msg = Empty {}; let expected_response = Response::::default(); let response = migrate(deps.as_mut(), env, msg).unwrap(); assert_eq!(response, expected_response); } // TESTS: execute // 1. CompleteTransferAndConvert #[test] fn execute_complete_transfer_and_convert() { let mut deps = execute_custom_mock_deps(); let env = mock_env_custom_contract(WORMHOLE_CONTRACT_ADDR); let transfer_info_response = TransferInfoResponse { amount: 1000000u32.into(), token_address: hex::decode("0000000000000000000000009c3c9283d3e44854697cd22d3faa240cfb032889").unwrap().try_into().unwrap(), token_chain: 5, recipient: hex::decode("23aae62840414d69ebc26023d1132f59eef316c82222da4644daaa832ea56349").unwrap().try_into().unwrap(), recipient_chain: 32, fee: 0u32.into(), payload: hex::decode("7b2262617369635f726563697069656e74223a7b22726563697069656e74223a22633256704d575636637a56745a4731334f486436646d4e7a4f585a344f586b335a4774306357646c4d336c36626a52334d477735626a5130227d7d").unwrap(), }; let transfer_info_response_copy = transfer_info_response.clone(); deps.querier.update_wasm(move |q| match q { WasmQuery::Smart { contract_addr: _, msg: _, } => SystemResult::Ok(ContractResult::Ok( to_binary(&transfer_info_response_copy).unwrap(), )), _ => SystemResult::Err(SystemError::UnsupportedRequest { kind: "wasm".to_string(), }), }); let token_bridge_addr = "faketokenbridge".to_string(); TOKEN_BRIDGE_CONTRACT .save(deps.as_mut().storage, &token_bridge_addr) .unwrap(); let info = mock_info(WORMHOLE_USER_ADDR, &[]); let vaa = Binary::from_base64("AAAAAA").unwrap(); let msg = ExecuteMsg::CompleteTransferAndConvert { vaa }; let response = execute(deps.as_mut(), env, info, msg).unwrap(); // response should have 1 message assert_eq!(response.messages.len(), 1); // 1. WasmMsg::Execute (token bridge complete transfer) assert_eq!(response.messages[0].id, COMPLETE_TRANSFER_REPLY_ID); assert_eq!(response.messages[0].reply_on, ReplyOn::Success); assert_eq!( response.messages[0].msg, CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: token_bridge_addr, msg: Binary::from_base64("eyJjb21wbGV0ZV90cmFuc2Zlcl93aXRoX3BheWxvYWQiOnsiZGF0YSI6IkFBQUFBQT09IiwicmVsYXllciI6Indvcm1ob2xlMXZoa20ycXY3ODRydWx4OHlscnUwenB2eXZ3M20zY3k5OWU2d3kwIn19").unwrap(), funds: vec![] }) ); // response should have 2 attributes assert_eq!(response.attributes.len(), 2); assert_eq!(response.attributes[0].key, "action"); assert_eq!( response.attributes[0].value, "complete_transfer_with_payload" ); assert_eq!(response.attributes[1].key, "transfer_payload"); assert_eq!( response.attributes[1].value, Binary::from(transfer_info_response.clone().payload).to_base64() ); // finally, validate that the state was saved into storage let saved_transfer = CURRENT_TRANSFER.load(deps.as_mut().storage).unwrap(); assert_eq!(saved_transfer, transfer_info_response); } // 2. GatewayConvertAndTransfer #[test] fn execute_gateway_convert_and_transfer() { let mut deps = execute_custom_mock_deps(); let token_bridge_addr = "faketokenbridge".to_string(); TOKEN_BRIDGE_CONTRACT .save(deps.as_mut().storage, &token_bridge_addr) .unwrap(); let tokenfactory_denom = "factory/cosmos2contract/3QEQyi7iyJHwQ4wfUMLFPB4kRzczMAXCitWh7h6TETDa".to_string(); CW_DENOMS .save( deps.as_mut().storage, WORMHOLE_CONTRACT_ADDR.to_string(), &tokenfactory_denom, ) .unwrap(); let coin = coin(1, tokenfactory_denom.clone()); let info = mock_info(WORMHOLE_USER_ADDR, &[coin.clone()]); let env = mock_env(); let recipient_chain = 2; let recipient = Binary::from_base64("AAAAAAAAAAAAAAAAjyagAl3Mxs/Aen04dWKAoQ4pWtc=").unwrap(); let fee = Uint128::zero(); let nonce = 0u32; let msg = ExecuteMsg::GatewayConvertAndTransfer { recipient, chain: recipient_chain, fee, nonce, }; let response = execute(deps.as_mut(), env, info, msg).unwrap(); // response should have 3 messages assert_eq!(response.messages.len(), 3); let mut expected_response: Response = Response::new(); expected_response = expected_response.add_message(TokenMsg::BurnTokens { denom: tokenfactory_denom, amount: coin.amount.u128(), burn_from_address: "".to_string(), }); expected_response = expected_response.add_message( CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: WORMHOLE_CONTRACT_ADDR.to_string(), msg: Binary::from_base64("eyJpbmNyZWFzZV9hbGxvd2FuY2UiOnsic3BlbmRlciI6ImZha2V0b2tlbmJyaWRnZSIsImFtb3VudCI6IjEiLCJleHBpcmVzIjpudWxsfX0=").unwrap(), funds: vec![] }) ); expected_response = expected_response.add_message( CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: token_bridge_addr, msg: Binary::from_base64("eyJpbml0aWF0ZV90cmFuc2ZlciI6eyJhc3NldCI6eyJpbmZvIjp7InRva2VuIjp7ImNvbnRyYWN0X2FkZHIiOiJ3b3JtaG9sZTF5dzR3djJ6cWc5eGtuNjd6dnEzYXp5ZTB0OGgweDlrZ3lnM2Q1M2p5bTI0Z3h0NDl2ZHlzNnM4aDdhIn19LCJhbW91bnQiOiIxIn0sInJlY2lwaWVudF9jaGFpbiI6MiwicmVjaXBpZW50IjoiQUFBQUFBQUFBQUFBQUFBQWp5YWdBbDNNeHMvQWVuMDRkV0tBb1E0cFd0Yz0iLCJmZWUiOiIwIiwibm9uY2UiOjB9fQ==").unwrap(), funds: vec![] }) ); // 1. TokenMsg::BurnTokens assert_eq!(response.messages[0].msg, expected_response.messages[0].msg,); // 2. WasmMsg::Execute (increase allowance) assert_eq!(response.messages[1].msg, expected_response.messages[1].msg,); // 3. WasmMsg::Execute (initiate transfer) assert_eq!(response.messages[2].msg, expected_response.messages[2].msg,); } // 3. GatewayConvertAndTransferWithPaylod #[test] fn execute_gateway_convert_and_transfer_with_payload() { let mut deps = execute_custom_mock_deps(); let token_bridge_addr = "faketokenbridge".to_string(); TOKEN_BRIDGE_CONTRACT .save(deps.as_mut().storage, &token_bridge_addr) .unwrap(); let tokenfactory_denom = "factory/cosmos2contract/3QEQyi7iyJHwQ4wfUMLFPB4kRzczMAXCitWh7h6TETDa".to_string(); CW_DENOMS .save( deps.as_mut().storage, WORMHOLE_CONTRACT_ADDR.to_string(), &tokenfactory_denom, ) .unwrap(); let coin = coin(1, tokenfactory_denom.clone()); let info = mock_info(WORMHOLE_USER_ADDR, &[coin.clone()]); let env = mock_env(); let recipient_chain = 2; let recipient = Binary::from_base64("AAAAAAAAAAAAAAAAjyagAl3Mxs/Aen04dWKAoQ4pWtc=").unwrap(); let nonce = 0u32; let msg = ExecuteMsg::GatewayConvertAndTransferWithPayload { contract: recipient, chain: recipient_chain, payload: Binary::default(), nonce, }; let response = execute(deps.as_mut(), env, info, msg).unwrap(); // response should have 3 messages assert_eq!(response.messages.len(), 3); let mut expected_response: Response = Response::new(); expected_response = expected_response.add_message(TokenMsg::BurnTokens { denom: tokenfactory_denom, amount: coin.amount.u128(), burn_from_address: "".to_string(), }); expected_response = expected_response.add_message( CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: WORMHOLE_CONTRACT_ADDR.to_string(), msg: Binary::from_base64("eyJpbmNyZWFzZV9hbGxvd2FuY2UiOnsic3BlbmRlciI6ImZha2V0b2tlbmJyaWRnZSIsImFtb3VudCI6IjEiLCJleHBpcmVzIjpudWxsfX0=").unwrap(), funds: vec![] }) ); expected_response = expected_response.add_message( CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: token_bridge_addr, msg: Binary::from_base64("eyJpbml0aWF0ZV90cmFuc2Zlcl93aXRoX3BheWxvYWQiOnsiYXNzZXQiOnsiaW5mbyI6eyJ0b2tlbiI6eyJjb250cmFjdF9hZGRyIjoid29ybWhvbGUxeXc0d3YyenFnOXhrbjY3enZxM2F6eWUwdDhoMHg5a2d5ZzNkNTNqeW0yNGd4dDQ5dmR5czZzOGg3YSJ9fSwiYW1vdW50IjoiMSJ9LCJyZWNpcGllbnRfY2hhaW4iOjIsInJlY2lwaWVudCI6IkFBQUFBQUFBQUFBQUFBQUFqeWFnQWwzTXhzL0FlbjA0ZFdLQW9RNHBXdGM9IiwiZmVlIjoiMCIsInBheWxvYWQiOiIiLCJub25jZSI6MH19").unwrap(), funds: vec![] }) ); // 1. TokenMsg::BurnTokens assert_eq!(response.messages[0].msg, expected_response.messages[0].msg,); // 2. WasmMsg::Execute (increase allowance) assert_eq!(response.messages[1].msg, expected_response.messages[1].msg,); // 3. WasmMsg::Execute (initiate transfer) assert_eq!(response.messages[2].msg, expected_response.messages[2].msg,); } // 4. SubmitUpdateChainToChannelMap #[test] fn execute_submit_update_chain_to_channel_map() { let mut deps = execute_custom_mock_deps(); let info = mock_info(WORMHOLE_USER_ADDR, &[]); let env = mock_env(); let vaa = Binary::from_base64("AQAAAAAFAI84lwdr/G1Uv36wfJpLtlTsfFexBcSjWGOHXt71h43IJNlDRh+FMX4eIpMdyBlY82LEZPGZDT/VetSupFgR4zYBATLRAqUMGfqBraBAMdI12bRk3aV2auwls+juBOuUe+kXOhYrUIQiltr4JGBVQ+VW3Mt7ykM5nOUq/+xWRBdzEuMAAm448B4M67xvIUOw4BaYUz5q5won0hXLR8w0jocO39bXdxksR+ZKTevfEHglmH0ti0lFduMGznqu3AJ8n9WbytcBA3JCC0Jd5PHeu8cAuAnYTsBdeDng1nHzMqUsU9r/2BCsGouEjrqgYicx5StwuBqjyIT7ede2/3wjKfoxOLMMeQUABNR1TWQhY8LEJDgqetXszpsKhh9xeJp3sTPSNpfKxKa8LHL8e4McoHEwbZ3uBMsqNDVVri1vSHxFkrOaLIYIwqsBAAAAAAAAAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAA4gAAAAAAAAAAAAAAAAAAAAAAAAAEliY1RyYW5zbGF0b3IBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY2hhbm5lbC0xAAs=").unwrap(); let msg = ExecuteMsg::SubmitUpdateChainToChannelMap { vaa }; let response = execute(deps.as_mut(), env, info, msg).unwrap(); // response should have 0 message assert_eq!(response.messages.len(), 0); assert_eq!( response, Response::new().add_event( Event::new("UpdateChainToChannelMap") .add_attribute("chain_id", "Karura".to_string()) .add_attribute("channel_id", "channel-1".to_string()), ) ); } // TESTS: reply // 1. Happy path: REPLY ID matches #[test] fn reply_happy_path() { let mut deps = mock_dependencies(); let env = mock_env(); // for this test we don't build a proper reply. // we're just testing that the handle_complete_transfer_reply method is called when the reply_id is 1 let msg = Reply { id: 1, result: cosmwasm_std::SubMsgResult::Err("random error".to_string()), }; let err = reply(deps.as_mut(), env, msg).unwrap_err(); assert_eq!( err.to_string(), "msg result is not okay, we should never get here" ); } // 2. ID does not match reply -- no op #[test] fn reply_no_id_match() { let mut deps = mock_dependencies(); let env = mock_env(); let msg = Reply { id: 0, result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { events: vec![], data: None, }), }; let err = reply(deps.as_mut(), env, msg).unwrap_err(); assert_eq!(err.to_string(), "unmatched reply id 0"); } // TEST: query // 1. happy path #[test] fn query_query_ibc_channel_happy_path() { let mut deps = mock_dependencies(); let env = mock_env(); let chain_id: u16 = 0; let msg = QueryMsg::IbcChannel { chain_id }; let channel = "channel-0".to_string(); CHAIN_TO_CHANNEL_MAP .save(deps.as_mut().storage, 0, &channel) .unwrap(); let expected_response = to_binary(&ChannelResponse { channel }).unwrap(); let response = query(deps.as_ref(), env, msg).unwrap(); assert_eq!(expected_response, response); }