use cosmwasm_std::{ testing::{mock_dependencies, mock_env}, to_binary, to_vec, Binary, ContractResult, CosmosMsg::Stargate, Reply, Response, SubMsgResponse, SystemError, SystemResult, Uint128, WasmQuery, }; use cw20::TokenInfoResponse; use cw_token_bridge::msg::{AssetInfo, CompleteTransferResponse, TransferInfoResponse}; use ibc_translator::{ reply::{ contract_addr_to_base58, convert_cw20_to_bank_and_send, handle_complete_transfer_reply, }, state::{CHAIN_TO_CHANNEL_MAP, CURRENT_TRANSFER, CW_DENOMS}, }; use prost::Message; use wormhole_bindings::tokenfactory::{DenomUnit, Metadata, TokenFactoryMsg, TokenMsg}; use wormhole_sdk::Chain; mod test_setup; use test_setup::{default_custom_mock_deps, WORMHOLE_CONTRACT_ADDR, WORMHOLE_USER_ADDR}; #[derive(Clone, PartialEq, Message)] struct MsgExecuteContractResponse { #[prost(bytes, tag = "1")] pub data: ::prost::alloc::vec::Vec, } // Tests // 1. handle_complete_transfer_reply // 1. happy path, GatewayTransfer // 2. happy path, GatewayTransferWithPayload // 2. bad msg result // 3. invalid response data // 4. no repsonse data // 5. invalid reponse data type // 6. no reponse contract // 7. no storage // 8. wrong stored payload // 9. invalid recipient // 10. invalid contract // 2. convert_cw20_to_bank_and_send // 1. happy path // 2. happy path create denom // 3. happy path create denom with empty symbol // 4. failure invalid contract // 5. chain id no channel // 6. bad payload // 3. contract_addr_to_base58 // 1. happy path // 2. bad contract address // TESTS: handle_complete_transfer_reply // 1. Happy path: GatewayTransfer #[test] fn handle_complete_transfer_reply_happy_path() { let mut deps = mock_dependencies(); let env = mock_env(); let msg = Reply { id: 1, result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { events: vec![], data: Some(Binary::from_base64("Cv0BeyJjb250cmFjdCI6Indvcm1ob2xlMXl3NHd2MnpxZzl4a242N3p2cTNhenllMHQ4aDB4OWtneWczZDUzanltMjRneHQ0OXZkeXM2czhoN2EiLCJkZW5vbSI6bnVsbCwicmVjaXBpZW50Ijoic2VpMWRrZHdkdmtueDBxYXY1Y3A1a3c2OG1rbjNyOTltM3N2a3lqZnZrenR3aDk3ZHYybG0wa3NqNnhyYWsiLCJhbW91bnQiOiIxMDAwIiwicmVsYXllciI6InNlaTF2aGttMnF2Nzg0cnVseDh5bHJ1MHpwdnl2dzNtM2N5OXgzeHlmdiIsImZlZSI6IjAifQ==").unwrap()) }) }; let contract_addr = "wormhole1yw4wv2zqg9xkn67zvq3azye0t8h0x9kgyg3d53jym24gxt49vdys6s8h7a".to_string(); let tokenfactory_denom = "factory/cosmos2contract/3QEQyi7iyJHwQ4wfUMLFPB4kRzczMAXCitWh7h6TETDa".to_string(); CW_DENOMS .save(deps.as_mut().storage, contract_addr, &tokenfactory_denom) .unwrap(); let channel = "channel-0".to_string(); CHAIN_TO_CHANNEL_MAP .save(deps.as_mut().storage, 0, &channel) .unwrap(); let transfer_payload = TransferInfoResponse { amount: 0u32.into(), token_address: [0; 32], token_chain: 0, recipient: [0; 32], recipient_chain: 0, fee: 0u32.into(), payload: hex::decode("7B22676174657761795F7472616E73666572223A7B22636861696E223A302C22726563697069656E74223A22633256704D575636637A56745A4731334F486436646D4E7A4F585A344F586B335A4774306357646C4D336C36626A52334D477735626A5130222C22666565223A2230222C226E6F6E6365223A307D7D").unwrap() }; CURRENT_TRANSFER .save(deps.as_mut().storage, &transfer_payload) .unwrap(); // just verifying that we called the convert_cw20_to_bank -- unwrap the result without an error // other tests verify the correctness of this method // wormhole core and token bridge tests verify the correctness of the VAA parameters handle_complete_transfer_reply(deps.as_mut(), env, msg).unwrap(); } // 2. Happy path: GatewayTransferWithPayload #[test] fn handle_complete_transfer_reply_happy_path_with_payload() { let mut deps = mock_dependencies(); let env = mock_env(); let msg = Reply { id: 1, result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { events: vec![], data: Some(Binary::from_base64("Cv0BeyJjb250cmFjdCI6Indvcm1ob2xlMXl3NHd2MnpxZzl4a242N3p2cTNhenllMHQ4aDB4OWtneWczZDUzanltMjRneHQ0OXZkeXM2czhoN2EiLCJkZW5vbSI6bnVsbCwicmVjaXBpZW50Ijoic2VpMWRrZHdkdmtueDBxYXY1Y3A1a3c2OG1rbjNyOTltM3N2a3lqZnZrenR3aDk3ZHYybG0wa3NqNnhyYWsiLCJhbW91bnQiOiIxMDAwIiwicmVsYXllciI6InNlaTF2aGttMnF2Nzg0cnVseDh5bHJ1MHpwdnl2dzNtM2N5OXgzeHlmdiIsImZlZSI6IjAifQ==").unwrap()) }) }; let contract_addr = "wormhole1yw4wv2zqg9xkn67zvq3azye0t8h0x9kgyg3d53jym24gxt49vdys6s8h7a".to_string(); let tokenfactory_denom = "factory/cosmos2contract/3QEQyi7iyJHwQ4wfUMLFPB4kRzczMAXCitWh7h6TETDa".to_string(); CW_DENOMS .save(deps.as_mut().storage, contract_addr, &tokenfactory_denom) .unwrap(); let channel = "channel-0".to_string(); CHAIN_TO_CHANNEL_MAP .save(deps.as_mut().storage, 0, &channel) .unwrap(); let transfer_payload = TransferInfoResponse { amount: 0u32.into(), token_address: [0; 32], token_chain: 0, recipient: [0; 32], recipient_chain: 0, fee: 0u32.into(), payload: hex::decode("7B22676174657761795F7472616E736665725F776974685F7061796C6F6164223A7B22636861696E223A302C22636F6E7472616374223A22633256704D575636637A56745A4731334F486436646D4E7A4F585A344F586B335A4774306357646C4D336C36626A52334D477735626A5130222C227061796C6F6164223A225647567A64464268655778765957513D222C226E6F6E6365223A307D7D").unwrap() }; CURRENT_TRANSFER .save(deps.as_mut().storage, &transfer_payload) .unwrap(); // just verifying that we called the convert_cw20_to_bank -- unwrap the result without an error // other tests verify the correctness of this method // wormhole core and token bridge tests verify the correctness of the VAA parameters handle_complete_transfer_reply(deps.as_mut(), env, msg).unwrap(); } // 3. Failure: msg result is not okay #[test] fn handle_complete_transfer_reply_bad_msg_result() { let mut deps = mock_dependencies(); let env = mock_env(); let msg = Reply { id: 1, result: cosmwasm_std::SubMsgResult::Err("random error".to_string()), }; let err = handle_complete_transfer_reply(deps.as_mut(), env, msg).unwrap_err(); assert_eq!( err.to_string(), "msg result is not okay, we should never get here" ); } // 4. Failure: could not parse reply response_data #[test] fn handle_complete_transfer_reply_invalid_response_data() { let mut deps = mock_dependencies(); let env = mock_env(); let msg = Reply { id: 1, result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { events: vec![], data: Some(Binary::from_base64("eyJiYXNpY19yZWNpcGllbnQiOnsicmVjaXBpZW50IjoiYzJWcE1XVjZjelZ0WkcxM09IZDZkbU56T1haNE9YazNaR3QwY1dkbE0zbDZialIzTUd3NWJqUTAifX0=").unwrap()) }) }; let err = handle_complete_transfer_reply(deps.as_mut(), env, msg).unwrap_err(); assert_eq!( err.to_string(), "failed to parse protobuf reply response_data" ); } // 5. Failure: no data in the parsed response #[test] fn handle_complete_transfer_reply_no_response_data() { let mut deps = mock_dependencies(); let env = mock_env(); let msg = Reply { id: 1, result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { events: vec![], data: Some(Binary::from_base64("").unwrap()), }), }; let err = handle_complete_transfer_reply(deps.as_mut(), env, msg).unwrap_err(); assert_eq!( err.to_string(), "no data in the response, we should never get here" ); } // 6. Failure: could not deserialize response data #[test] fn handle_complete_transfer_reply_invalid_response_data_type() { let mut deps = mock_dependencies(); let env = mock_env(); // encode a payload as protobuf // the payload is NOT a CompleteTransferResponse let execute_reply = MsgExecuteContractResponse { data: to_vec(&AssetInfo::NativeToken { denom: "denomA".to_string(), }) .unwrap(), }; let mut encoded_execute_reply = Vec::::with_capacity(execute_reply.encoded_len()); execute_reply.encode(&mut encoded_execute_reply).unwrap(); let msg = Reply { id: 1, result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { events: vec![], data: Some(encoded_execute_reply.into()), }), }; let err = handle_complete_transfer_reply(deps.as_mut(), env, msg).unwrap_err(); assert_eq!(err.to_string(), "failed to deserialize response data"); } // 7. Failure: no contract in the response #[test] fn handle_complete_transfer_reply_no_response_contract() { let mut deps = mock_dependencies(); let env = mock_env(); // encode a payload as protobuf // the payload is a CompleteTransferResponse let execute_reply = MsgExecuteContractResponse { data: to_vec(&CompleteTransferResponse { contract: None, denom: None, recipient: "fake".to_string(), amount: 1u32.into(), relayer: "fake".to_string(), fee: 0u32.into(), }) .unwrap(), }; let mut encoded_execute_reply = Vec::::with_capacity(execute_reply.encoded_len()); execute_reply.encode(&mut encoded_execute_reply).unwrap(); let msg = Reply { id: 1, result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { events: vec![], data: Some(encoded_execute_reply.into()), }), }; let err = handle_complete_transfer_reply(deps.as_mut(), env, msg).unwrap_err(); assert_eq!( err.to_string(), "no contract in response, we should never get here" ); } // 8. Failure: no current transfer in storage #[test] fn handle_complete_transfer_reply_no_storage() { let mut deps = mock_dependencies(); let env = mock_env(); let msg = Reply { id: 1, result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { events: vec![], data: Some(Binary::from_base64("CvgBeyJjb250cmFjdCI6InNlaTE0MG02eGFnbXcwemVzZWp6aHN2azQ2enByZ3Njcjd0dTk0aDM2cndzdXRjc3hjczRmbWRzOXNldnltIiwiZGVub20iOm51bGwsInJlY2lwaWVudCI6InNlaTFka2R3ZHZrbngwcWF2NWNwNWt3Njhta24zcjk5bTNzdmt5amZ2a3p0d2g5N2R2MmxtMGtzajZ4cmFrIiwiYW1vdW50IjoiMTAwMCIsInJlbGF5ZXIiOiJzZWkxdmhrbTJxdjc4NHJ1bHg4eWxydTB6cHZ5dnczbTNjeTl4M3h5ZnYiLCJmZWUiOiIwIn0=").unwrap()) }) }; let err = handle_complete_transfer_reply(deps.as_mut(), env, msg).unwrap_err(); assert_eq!( err.to_string(), "failed to load current transfer from storage" ); } // 9. Failure: could not deserialize payload3 payload from stored transfer #[test] fn handle_complete_transfer_reply_wrong_stored_payload() { let mut deps = mock_dependencies(); let env = mock_env(); let msg = Reply { id: 1, result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { events: vec![], data: Some(Binary::from_base64("CvgBeyJjb250cmFjdCI6InNlaTE0MG02eGFnbXcwemVzZWp6aHN2azQ2enByZ3Njcjd0dTk0aDM2cndzdXRjc3hjczRmbWRzOXNldnltIiwiZGVub20iOm51bGwsInJlY2lwaWVudCI6InNlaTFka2R3ZHZrbngwcWF2NWNwNWt3Njhta24zcjk5bTNzdmt5amZ2a3p0d2g5N2R2MmxtMGtzajZ4cmFrIiwiYW1vdW50IjoiMTAwMCIsInJlbGF5ZXIiOiJzZWkxdmhrbTJxdjc4NHJ1bHg4eWxydTB6cHZ5dnczbTNjeTl4M3h5ZnYiLCJmZWUiOiIwIn0=").unwrap()) }) }; let bad_transfer_payload = TransferInfoResponse { amount: 0u32.into(), token_address: [0; 32], token_chain: 0, recipient: [0; 32], recipient_chain: 0, fee: 0u32.into(), payload: hex::decode("7b22726563697069656e74223a7b22726563697069656e74223a22633256704d575636637a56745a4731334f486436646d4e7a4f585a344f586b335a4774306357646c4d336c36626a52334d477735626a5130227d7d").unwrap() }; CURRENT_TRANSFER .save(deps.as_mut().storage, &bad_transfer_payload) .unwrap(); let err = handle_complete_transfer_reply(deps.as_mut(), env, msg).unwrap_err(); assert_eq!(err.to_string(), "failed to deserialize transfer payload"); } // 10. Failure: could not convert the recipient (GatewayTransfer) base64 encoded bytes to a utf8 string #[test] fn handle_complete_transfer_reply_invalid_recipient() { let mut deps = mock_dependencies(); let env = mock_env(); let msg = Reply { id: 1, result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { events: vec![], data: Some(Binary::from_base64("CvgBeyJjb250cmFjdCI6InNlaTE0MG02eGFnbXcwemVzZWp6aHN2azQ2enByZ3Njcjd0dTk0aDM2cndzdXRjc3hjczRmbWRzOXNldnltIiwiZGVub20iOm51bGwsInJlY2lwaWVudCI6InNlaTFka2R3ZHZrbngwcWF2NWNwNWt3Njhta24zcjk5bTNzdmt5amZ2a3p0d2g5N2R2MmxtMGtzajZ4cmFrIiwiYW1vdW50IjoiMTAwMCIsInJlbGF5ZXIiOiJzZWkxdmhrbTJxdjc4NHJ1bHg4eWxydTB6cHZ5dnczbTNjeTl4M3h5ZnYiLCJmZWUiOiIwIn0=").unwrap()) }) }; let bad_transfer_payload = TransferInfoResponse { amount: 0u32.into(), token_address: [0; 32], token_chain: 0, recipient: [0; 32], recipient_chain: 0, fee: 0u32.into(), payload: hex::decode("7B22676174657761795F7472616E73666572223A7B22636861696E223A302C22726563697069656E74223A223256704D575636637A56745A4731334F486436646D4E7A4F585A344F586B335A4774306357646C4D336C36626A52334D477735626A5130222C22666565223A2230222C226E6F6E6365223A307D7D").unwrap() }; CURRENT_TRANSFER .save(deps.as_mut().storage, &bad_transfer_payload) .unwrap(); let err = handle_complete_transfer_reply(deps.as_mut(), env, msg).unwrap_err(); assert_eq!( err.to_string(), "failed to convert 2VpMWV6czVtZG13OHd6dmNzOXZ4OXk3ZGt0cWdlM3l6bjR3MGw5bjQ0= to utf8 string" ); } // 11. Failure: could not convert the contract (GatewayTransferWithPayload) base64 encoded bytes to a utf8 string #[test] fn handle_complete_transfer_reply_invalid_contract() { let mut deps = mock_dependencies(); let env = mock_env(); let msg = Reply { id: 1, result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { events: vec![], data: Some(Binary::from_base64("Cv0BeyJjb250cmFjdCI6Indvcm1ob2xlMXl3NHd2MnpxZzl4a242N3p2cTNhenllMHQ4aDB4OWtneWczZDUzanltMjRneHQ0OXZkeXM2czhoN2EiLCJkZW5vbSI6bnVsbCwicmVjaXBpZW50Ijoic2VpMWRrZHdkdmtueDBxYXY1Y3A1a3c2OG1rbjNyOTltM3N2a3lqZnZrenR3aDk3ZHYybG0wa3NqNnhyYWsiLCJhbW91bnQiOiIxMDAwIiwicmVsYXllciI6InNlaTF2aGttMnF2Nzg0cnVseDh5bHJ1MHpwdnl2dzNtM2N5OXgzeHlmdiIsImZlZSI6IjAifQ==").unwrap()) }) }; let bad_transfer_payload = TransferInfoResponse { amount: 0u32.into(), token_address: [0; 32], token_chain: 0, recipient: [0; 32], recipient_chain: 0, fee: 0u32.into(), payload: hex::decode("7B22676174657761795F7472616E736665725F776974685F7061796C6F6164223A7B22636861696E223A302C22636F6E7472616374223A223256704D575636637A56745A4731334F486436646D4E7A4F585A344F586B335A4774306357646C4D336C36626A52334D477735626A5130222C227061796C6F6164223A225647567A64464268655778765957513D222C226E6F6E6365223A307D7D").unwrap() }; CURRENT_TRANSFER .save(deps.as_mut().storage, &bad_transfer_payload) .unwrap(); // just verifying that we called the convert_cw20_to_bank -- unwrap the result without an error // other tests verify the correctness of this method // wormhole core and token bridge tests verify the correctness of the VAA parameters let err = handle_complete_transfer_reply(deps.as_mut(), env, msg).unwrap_err(); assert_eq!( err.to_string(), "failed to convert 2VpMWV6czVtZG13OHd6dmNzOXZ4OXk3ZGt0cWdlM3l6bjR3MGw5bjQ0= to utf8 string" ); } // Test convert_cw20_to_bank_and_send // TESTS: convert_cw20_to_bank // 1. Happy path #[test] fn convert_cw20_to_bank_and_send_happy_path() { let mut deps = default_custom_mock_deps(); let env = mock_env(); let recipient = WORMHOLE_USER_ADDR.to_string(); let amount = 1; let contract_addr = WORMHOLE_CONTRACT_ADDR.to_string(); let tokenfactory_denom = "factory/cosmos2contract/3QEQyi7iyJHwQ4wfUMLFPB4kRzczMAXCitWh7h6TETDa".to_string(); CW_DENOMS .save( deps.as_mut().storage, contract_addr.clone(), &tokenfactory_denom, ) .unwrap(); let chain_id = Chain::Ethereum; let channel = "channel-0".to_string(); CHAIN_TO_CHANNEL_MAP .save(deps.as_mut().storage, chain_id.into(), &channel) .unwrap(); let response = convert_cw20_to_bank_and_send( deps.as_mut(), env, recipient, amount, contract_addr, chain_id.into(), None, ) .unwrap(); // response should have 2 messages: assert_eq!(response.messages.len(), 2); let mut expected_response: Response = Response::new(); expected_response = expected_response.add_message(TokenMsg::MintTokens { denom: tokenfactory_denom, amount, mint_to_address: "cosmos2contract".to_string(), }); expected_response = expected_response.add_message(Stargate { type_url: "/ibc.applications.transfer.v1.MsgTransfer".to_string(), value: Binary::from_base64("Cgh0cmFuc2ZlchIJY2hhbm5lbC0wGkkKRGZhY3RvcnkvY29zbW9zMmNvbnRyYWN0LzNRRVF5aTdpeUpId1E0d2ZVTUxGUEI0a1J6Y3pNQVhDaXRXaDdoNlRFVERhEgExIg9jb3Ntb3MyY29udHJhY3QqL3dvcm1ob2xlMXZoa20ycXY3ODRydWx4OHlscnUwenB2eXZ3M20zY3k5OWU2d3kwOL2irKOC6IugFg==").unwrap(), }); // 1. TokenMsg::MintTokens assert_eq!(response.messages[0].msg, expected_response.messages[0].msg,); // 2. Stargate ibc transfer assert_eq!(response.messages[1].msg, expected_response.messages[1].msg,); } // 2. Happy path + CreateDenom on TokenFactory #[test] fn convert_cw20_to_bank_happy_path_create_denom() { let mut deps = default_custom_mock_deps(); let env = mock_env(); let recipient = WORMHOLE_USER_ADDR.to_string(); let amount = 1; let contract_addr = WORMHOLE_CONTRACT_ADDR.to_string(); let tokenfactory_denom = "factory/cosmos2contract/3QEQyi7iyJHwQ4wfUMLFPB4kRzczMAXCitWh7h6TETDa".to_string(); let subdenom = "3QEQyi7iyJHwQ4wfUMLFPB4kRzczMAXCitWh7h6TETDa".to_string(); let chain_id = Chain::Ethereum; let channel = "channel-0".to_string(); CHAIN_TO_CHANNEL_MAP .save(deps.as_mut().storage, chain_id.into(), &channel) .unwrap(); let token_info_response = TokenInfoResponse { name: "TestCoin".to_string(), symbol: "TEST".to_string(), decimals: 6, total_supply: Uint128::new(10_000_000), }; let token_info_response_copy = token_info_response.clone(); deps.querier.update_wasm(move |q| match q { WasmQuery::Smart { contract_addr: _, msg: _, } => SystemResult::Ok(ContractResult::Ok( to_binary(&token_info_response_copy).unwrap(), )), _ => SystemResult::Err(SystemError::UnsupportedRequest { kind: "wasm".to_string(), }), }); // Populate token factory token's metadata from cw20 token's metadata let tf_description = token_info_response.name.clone() + ", " + token_info_response.symbol.as_str() + ", " + tokenfactory_denom.as_str(); let tf_denom_unit_base = DenomUnit { denom: tokenfactory_denom.clone(), exponent: 0, aliases: vec![], }; let tf_scaled_denom = "wormhole/".to_string() + subdenom.as_str() + "/" + token_info_response.decimals.to_string().as_str(); let tf_denom_unit_scaled = DenomUnit { denom: tf_scaled_denom.clone(), exponent: u32::from(token_info_response.decimals), aliases: vec![], }; let tf_metadata = Metadata { description: Some(tf_description), base: Some(tokenfactory_denom.clone()), denom_units: vec![tf_denom_unit_base, tf_denom_unit_scaled], display: Some(tf_scaled_denom), name: Some(token_info_response.name), symbol: Some(token_info_response.symbol), }; let response = convert_cw20_to_bank_and_send( deps.as_mut(), env, recipient, amount, contract_addr, chain_id.into(), None, ) .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::CreateDenom { subdenom, metadata: Some(tf_metadata), }); expected_response = expected_response.add_message(TokenMsg::MintTokens { denom: tokenfactory_denom, amount, mint_to_address: "cosmos2contract".to_string(), }); expected_response = expected_response.add_message(Stargate { type_url: "/ibc.applications.transfer.v1.MsgTransfer".to_string(), value: Binary::from_base64("Cgh0cmFuc2ZlchIJY2hhbm5lbC0wGkkKRGZhY3RvcnkvY29zbW9zMmNvbnRyYWN0LzNRRVF5aTdpeUpId1E0d2ZVTUxGUEI0a1J6Y3pNQVhDaXRXaDdoNlRFVERhEgExIg9jb3Ntb3MyY29udHJhY3QqL3dvcm1ob2xlMXZoa20ycXY3ODRydWx4OHlscnUwenB2eXZ3M20zY3k5OWU2d3kwOL2irKOC6IugFg==").unwrap(), }); // 1. TokenMsg::CreateDenom assert_eq!(response.messages[0].msg, expected_response.messages[0].msg,); // 2. TokenMsg::MintTokens assert_eq!(response.messages[1].msg, expected_response.messages[1].msg,); // 3. Stargate ibc transfer assert_eq!(response.messages[2].msg, expected_response.messages[2].msg,); } // 3. Happy path + CreateDenom on TokenFactory with empty symbol #[test] fn convert_cw20_to_bank_happy_path_create_denom_empty_symbol() { let mut deps = default_custom_mock_deps(); let env = mock_env(); let recipient = WORMHOLE_USER_ADDR.to_string(); let amount = 1; let contract_addr = WORMHOLE_CONTRACT_ADDR.to_string(); let tokenfactory_denom = "factory/cosmos2contract/3QEQyi7iyJHwQ4wfUMLFPB4kRzczMAXCitWh7h6TETDa".to_string(); let subdenom = "3QEQyi7iyJHwQ4wfUMLFPB4kRzczMAXCitWh7h6TETDa".to_string(); let chain_id = Chain::Ethereum; let channel = "channel-0".to_string(); CHAIN_TO_CHANNEL_MAP .save(deps.as_mut().storage, chain_id.into(), &channel) .unwrap(); let token_info_response = TokenInfoResponse { name: "TestCoin".to_string(), symbol: "".to_string(), decimals: 6, total_supply: Uint128::new(10_000_000), }; let token_info_response_copy = token_info_response.clone(); deps.querier.update_wasm(move |q| match q { WasmQuery::Smart { contract_addr: _, msg: _, } => SystemResult::Ok(ContractResult::Ok( to_binary(&token_info_response_copy).unwrap(), )), _ => SystemResult::Err(SystemError::UnsupportedRequest { kind: "wasm".to_string(), }), }); // Populate token factory token's metadata from cw20 token's metadata let tf_denom_unit_base = DenomUnit { denom: tokenfactory_denom.clone(), exponent: 0, aliases: vec![], }; let tf_scaled_denom = "wormhole/".to_string() + subdenom.as_str() + "/" + token_info_response.decimals.to_string().as_str(); let tf_denom_unit_scaled = DenomUnit { denom: tf_scaled_denom.clone(), exponent: u32::from(token_info_response.decimals), aliases: vec![], }; let tf_description = token_info_response.name.clone() + ", " + tf_scaled_denom.as_str() // CW20 symbol is empty, use tf_scaled_denom + ", " + tokenfactory_denom.as_str(); let tf_metadata = Metadata { description: Some(tf_description), base: Some(tokenfactory_denom.clone()), denom_units: vec![tf_denom_unit_base, tf_denom_unit_scaled], display: Some(tf_scaled_denom.clone()), name: Some(token_info_response.name), symbol: Some(tf_scaled_denom), // CW20 symbol is empty, use tf_scaled_denom }; let response = convert_cw20_to_bank_and_send( deps.as_mut(), env, recipient, amount, contract_addr, chain_id.into(), None, ) .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::CreateDenom { subdenom, metadata: Some(tf_metadata), }); expected_response = expected_response.add_message(TokenMsg::MintTokens { denom: tokenfactory_denom, amount, mint_to_address: "cosmos2contract".to_string(), }); expected_response = expected_response.add_message(Stargate { type_url: "/ibc.applications.transfer.v1.MsgTransfer".to_string(), value: Binary::from_base64("Cgh0cmFuc2ZlchIJY2hhbm5lbC0wGkkKRGZhY3RvcnkvY29zbW9zMmNvbnRyYWN0LzNRRVF5aTdpeUpId1E0d2ZVTUxGUEI0a1J6Y3pNQVhDaXRXaDdoNlRFVERhEgExIg9jb3Ntb3MyY29udHJhY3QqL3dvcm1ob2xlMXZoa20ycXY3ODRydWx4OHlscnUwenB2eXZ3M20zY3k5OWU2d3kwOL2irKOC6IugFg==").unwrap(), }); // 1. TokenMsg::CreateDenom assert_eq!(response.messages[0].msg, expected_response.messages[0].msg,); // 2. TokenMsg::MintTokens assert_eq!(response.messages[1].msg, expected_response.messages[1].msg,); // 3. Stargate ibc transfer assert_eq!(response.messages[2].msg, expected_response.messages[2].msg,); } // 4. Failure: couldn't validate contract address #[test] fn convert_cw20_to_bank_failure_invalid_contract() { let mut deps = default_custom_mock_deps(); let env = mock_env(); let recipient = WORMHOLE_USER_ADDR.to_string(); let amount = 1; let contract_addr = "badContractAddr".to_string(); let chain_id = Chain::Ethereum; let method_err = convert_cw20_to_bank_and_send( deps.as_mut(), env, recipient, amount, contract_addr, chain_id.into(), None, ) .unwrap_err(); assert_eq!( method_err.to_string(), "invalid contract address badContractAddr" ); } // 5. Failure: Chain id doesn't have a channel #[test] fn convert_cw20_to_bank_and_send_chain_id_no_channel() { let mut deps = default_custom_mock_deps(); let env = mock_env(); let recipient = WORMHOLE_USER_ADDR.to_string(); let amount = 1; let contract_addr = WORMHOLE_CONTRACT_ADDR.to_string(); let tokenfactory_denom = "factory/cosmos2contract/3QEQyi7iyJHwQ4wfUMLFPB4kRzczMAXCitWh7h6TETDa".to_string(); CW_DENOMS .save( deps.as_mut().storage, contract_addr.clone(), &tokenfactory_denom, ) .unwrap(); let chain_id = Chain::Ethereum; let method_err = convert_cw20_to_bank_and_send( deps.as_mut(), env, recipient, amount, contract_addr, chain_id.into(), None, ) .unwrap_err(); assert_eq!( method_err.to_string(), "chain id does not have an allowed channel" ); } // 6. Failure: bad payload #[test] fn convert_cw20_to_bank_and_send_bad_payload() { let mut deps = default_custom_mock_deps(); let env = mock_env(); let recipient = WORMHOLE_USER_ADDR.to_string(); let amount = 1; let contract_addr = WORMHOLE_CONTRACT_ADDR.to_string(); let tokenfactory_denom = "factory/cosmos2contract/3QEQyi7iyJHwQ4wfUMLFPB4kRzczMAXCitWh7h6TETDa".to_string(); CW_DENOMS .save( deps.as_mut().storage, contract_addr.clone(), &tokenfactory_denom, ) .unwrap(); let chain_id = Chain::Ethereum; let channel = "channel-0".to_string(); CHAIN_TO_CHANNEL_MAP .save(deps.as_mut().storage, chain_id.into(), &channel) .unwrap(); let method_err = convert_cw20_to_bank_and_send( deps.as_mut(), env, recipient, amount, contract_addr, chain_id.into(), Some( Binary::from_base64("2VpMWV6czVtZG13OHd6dmNzOXZ4OXk3ZGt0cWdlM3l6bjR3MGw5bjQ0").unwrap(), ), ) .unwrap_err(); assert_eq!( method_err.to_string(), "failed to convert 2VpMWV6czVtZG13OHd6dmNzOXZ4OXk3ZGt0cWdlM3l6bjR3MGw5bjQ0= to utf8 string" ); } // 1. happy path #[test] fn contract_addr_to_base58_happy_path() { let deps = default_custom_mock_deps(); let b58_str = contract_addr_to_base58( deps.as_ref(), "wormhole1yw4wv2zqg9xkn67zvq3azye0t8h0x9kgyg3d53jym24gxt49vdys6s8h7a".to_string(), ) .unwrap(); assert_eq!(b58_str, "3QEQyi7iyJHwQ4wfUMLFPB4kRzczMAXCitWh7h6TETDa"); } // 2. bad contract address, could not canonicalize contract address #[test] fn contract_addr_to_base58_bad_contract_address() { let deps = default_custom_mock_deps(); let method_err = contract_addr_to_base58( deps.as_ref(), "wormhole1yw4wv2zqg9xkn67zvq3azye0t8h0x9kgyg3d53jym24gxt49vdys6s8h7".to_string(), ) .unwrap_err(); assert_eq!( method_err.to_string(), "could not canonicalize contract address wormhole1yw4wv2zqg9xkn67zvq3azye0t8h0x9kgyg3d53jym24gxt49vdys6s8h7" ) }