371 lines
14 KiB
Rust
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);
|
|
}
|