wormhole/cosmwasm/contracts/ibc-translator/tests/contract_test.rs

371 lines
14 KiB
Rust

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::<Empty>::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<TokenFactoryMsg> = 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<TokenFactoryMsg> = 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);
}