From 7e2fd4d0e2db2f9e789064b842a852540fc0fa53 Mon Sep 17 00:00:00 2001 From: Kaku <105181329+kakucodes@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:30:03 -0500 Subject: [PATCH] cosmwasm: add wormchain ibc receiver tests --- cosmwasm/Cargo.lock | 3 + .../wormchain-ibc-receiver/Cargo.toml | 6 + .../wormchain-ibc-receiver/src/lib.rs | 3 + .../src/tests/integration_tests.rs | 362 ++++++++++++++++++ .../wormchain-ibc-receiver/src/tests/mod.rs | 2 + .../src/tests/test_utils.rs | 66 ++++ 6 files changed, 442 insertions(+) create mode 100644 cosmwasm/contracts/wormchain-ibc-receiver/src/tests/integration_tests.rs create mode 100644 cosmwasm/contracts/wormchain-ibc-receiver/src/tests/mod.rs create mode 100644 cosmwasm/contracts/wormchain-ibc-receiver/src/tests/test_utils.rs diff --git a/cosmwasm/Cargo.lock b/cosmwasm/Cargo.lock index 2c3fc20235..1a4f2f1567 100644 --- a/cosmwasm/Cargo.lock +++ b/cosmwasm/Cargo.lock @@ -2597,8 +2597,11 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", + "cw-multi-test", "cw-storage-plus 0.13.4", "semver", + "serde", + "serde-json-wasm 0.4.1", "serde_wormhole", "thiserror", "wormhole-bindings", diff --git a/cosmwasm/contracts/wormchain-ibc-receiver/Cargo.toml b/cosmwasm/contracts/wormchain-ibc-receiver/Cargo.toml index 778fe83edb..58c6be3054 100644 --- a/cosmwasm/contracts/wormchain-ibc-receiver/Cargo.toml +++ b/cosmwasm/contracts/wormchain-ibc-receiver/Cargo.toml @@ -21,3 +21,9 @@ thiserror = "1.0.31" wormhole-bindings = "0.1.0" wormhole-sdk = { workspace = true, features = ["schemars"] } serde_wormhole.workspace = true + +[dev-dependencies] +cw-multi-test = "0.13.2" +serde-json-wasm = "0.4" +wormhole-bindings = { version = "0.1.0", features=["fake"] } +serde = { version = "1.0.137", default-features = false, features = ["derive"] } \ No newline at end of file diff --git a/cosmwasm/contracts/wormchain-ibc-receiver/src/lib.rs b/cosmwasm/contracts/wormchain-ibc-receiver/src/lib.rs index 10d266d8f4..31d3febfdc 100644 --- a/cosmwasm/contracts/wormchain-ibc-receiver/src/lib.rs +++ b/cosmwasm/contracts/wormchain-ibc-receiver/src/lib.rs @@ -3,3 +3,6 @@ pub mod error; pub mod ibc; pub mod msg; pub mod state; + +#[cfg(test)] +pub mod tests; diff --git a/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/integration_tests.rs b/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/integration_tests.rs new file mode 100644 index 0000000000..86210bfe20 --- /dev/null +++ b/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/integration_tests.rs @@ -0,0 +1,362 @@ +use crate::{ + contract::{execute, query}, + msg::{AllChannelChainsResponse, ExecuteMsg, QueryMsg}, + tests::test_utils::{create_gov_vaa_body, create_transfer_vaa_body, sign_vaa_body}, +}; +use anyhow::Error; +use cosmwasm_std::{ + from_binary, + testing::{mock_env, mock_info, MockApi, MockQuerier, MockStorage}, + to_binary, Binary, ContractResult, Deps, DepsMut, Empty, QuerierWrapper, SystemResult, +}; +use wormhole_bindings::{fake::WormholeKeeper, WormholeQuery}; +use wormhole_sdk::{ + ibc_receiver::{Action, GovernancePacket}, + vaa::Body, + Chain, GOVERNANCE_EMITTER, +}; + +#[test] +pub fn add_channel_chain_happy_path() -> anyhow::Result<(), Error> { + let wh = WormholeKeeper::new(); + + let querier: MockQuerier = + MockQuerier::new(&[]).with_custom_handler(|q| match q { + WormholeQuery::VerifyVaa { vaa } => { + match WormholeKeeper::new().verify_vaa(&vaa.0, 0u64) { + Ok(_) => SystemResult::Ok(if let Ok(data) = to_binary(&Empty {}) { + ContractResult::Ok(data) + } else { + ContractResult::Err("Unable to convert to binary".to_string()) + }), + Err(e) => SystemResult::Ok(ContractResult::Err(e.to_string())), + } + } + _ => cosmwasm_std::SystemResult::Ok(cosmwasm_std::ContractResult::Ok( + to_binary(&Empty {}).unwrap(), + )), + }); + + let mut mut_deps = DepsMut { + storage: &mut MockStorage::default(), + api: &MockApi::default(), + querier: QuerierWrapper::new(&querier), + }; + let info = mock_info("sender", &[]); + let env = mock_env(); + + let add_sei_channel_body = create_gov_vaa_body(1, Chain::Sei, *b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00channel-0"); + let (_, add_sei_vaa_binary) = sign_vaa_body(wh.clone(), add_sei_channel_body); + + let submissions = execute( + mut_deps.branch(), + env.clone(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![add_sei_vaa_binary], + }, + ); + + assert!( + submissions.is_ok(), + "A proper UpdateChannelChain gov vaa should be accepted" + ); + + // create a readonly deps to use for querying the state + let empty_mock_querier = MockQuerier::::new(&[]); + let readonly_deps = Deps { + storage: mut_deps.storage, + api: mut_deps.api, + querier: QuerierWrapper::new(&empty_mock_querier), + }; + + let channel_binary = query(readonly_deps, env, QueryMsg::AllChannelChains {})?; + let channel: AllChannelChainsResponse = from_binary(&channel_binary)?; + + assert_eq!(channel.channels_chains.len(), 1); + let channel_entry = channel.channels_chains.first().unwrap(); + assert_eq!( + channel_entry.0, + Binary::from(*b"channel-0"), + "the stored channel for sei should initially be channel-0" + ); + assert_eq!( + channel_entry.1, + Into::::into(Chain::Sei), + "the stored channel should be for sei's chain id" + ); + + Ok(()) +} + +#[test] +pub fn add_channel_chain_happy_path_multiple() -> anyhow::Result<(), Error> { + let wh = WormholeKeeper::new(); + + let querier: MockQuerier = + MockQuerier::new(&[]).with_custom_handler(|q| match q { + WormholeQuery::VerifyVaa { vaa } => { + match WormholeKeeper::new().verify_vaa(&vaa.0, 0u64) { + Ok(_) => SystemResult::Ok(if let Ok(data) = to_binary(&Empty {}) { + ContractResult::Ok(data) + } else { + ContractResult::Err("Unable to convert to binary".to_string()) + }), + Err(e) => SystemResult::Ok(ContractResult::Err(e.to_string())), + } + } + _ => cosmwasm_std::SystemResult::Ok(cosmwasm_std::ContractResult::Ok( + to_binary(&Empty {}).unwrap(), + )), + }); + + let mut mut_deps = DepsMut { + storage: &mut MockStorage::default(), + api: &MockApi::default(), + querier: QuerierWrapper::new(&querier), + }; + let info = mock_info("sender", &[]); + + let add_inj_channel_body = create_gov_vaa_body(2, Chain::Injective, *b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00channel-1"); + let (_, add_inj_vaa_bin) = sign_vaa_body(wh.clone(), add_inj_channel_body); + let add_sei_channel_body = create_gov_vaa_body(3, Chain::Sei, *b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00channel-2"); + let (_, add_sei_vaa_binary) = sign_vaa_body(wh.clone(), add_sei_channel_body); + + // add a channel for injective and update the channel set for sei + let submissions = execute( + mut_deps.branch(), + mock_env(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![add_sei_vaa_binary, add_inj_vaa_bin], + }, + ); + + assert!( + submissions.is_ok(), + "A pair of proper UpdateChannelChain gov vaas should be accepted" + ); + + // create a readonly deps to use for querying the state + let empty_mock_querier = MockQuerier::::new(&[]); + let readonly_deps = Deps { + storage: mut_deps.storage, + api: mut_deps.api, + querier: QuerierWrapper::new(&empty_mock_querier), + }; + + // refetch all the channels that are in state + let channel_binary = query(readonly_deps, mock_env(), QueryMsg::AllChannelChains {})?; + let AllChannelChainsResponse { + channels_chains: mut channels, + }: AllChannelChainsResponse = from_binary(&channel_binary)?; + + channels.sort_by(|(_, a_chain_id), (_, b_chain_id)| a_chain_id.cmp(b_chain_id)); + + assert_eq!(channels.len(), 2); + + let channel_entry = channels.first().unwrap(); + assert_eq!( + channel_entry.0, + Binary::from(*b"channel-1"), + "the stored channel should be channel-1 " + ); + assert_eq!( + channel_entry.1, + Into::::into(Chain::Injective), + "the stored channel should be for injective's chain id" + ); + + let channel_entry = channels.last().unwrap(); + assert_eq!( + channel_entry.0, + Binary::from(*b"channel-2"), + "the stored channel should be channel-2" + ); + assert_eq!( + channel_entry.1, + Into::::into(Chain::Sei), + "the stored channel should be for sei's chain id" + ); + + Ok(()) +} + +#[test] +pub fn reject_invalid_add_channel_chain_vaas() { + let wh = WormholeKeeper::new(); + + let querier: MockQuerier = + MockQuerier::new(&[]).with_custom_handler(|q| match q { + WormholeQuery::VerifyVaa { vaa } => { + match WormholeKeeper::new().verify_vaa(&vaa.0, 0u64) { + Ok(_) => SystemResult::Ok(if let Ok(data) = to_binary(&Empty {}) { + ContractResult::Ok(data) + } else { + ContractResult::Err("Unable to convert to binary".to_string()) + }), + Err(e) => SystemResult::Ok(ContractResult::Err(e.to_string())), + } + } + _ => cosmwasm_std::SystemResult::Ok(cosmwasm_std::ContractResult::Ok( + to_binary(&Empty {}).unwrap(), + )), + }); + + let mut mut_deps = DepsMut { + storage: &mut MockStorage::default(), + api: &MockApi::default(), + querier: QuerierWrapper::new(&querier), + }; + let info = mock_info("sender", &[]); + + let add_channel_body = create_gov_vaa_body(1, Chain::Wormchain, *b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00channel-0"); + let (_, add_vaa_binary) = sign_vaa_body(wh.clone(), add_channel_body); + + let submissions = execute( + mut_deps.branch(), + mock_env(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![add_vaa_binary], + }, + ); + + assert!( + submissions.is_err(), + "Cannot add a channel from Gateway to Gateway" + ); + + let submissions = execute( + mut_deps.branch(), + mock_env(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![Binary::from(vec![0u8; 32])], + }, + ); + + assert!( + submissions.is_err(), + "VAA should be rejected if it cannot be parsed because it's too short" + ); + + let add_channel_body = create_transfer_vaa_body(1); + let (_, add_vaa_binary) = sign_vaa_body(wh.clone(), add_channel_body); + + let submissions = execute( + mut_deps.branch(), + mock_env(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![add_vaa_binary], + }, + ); + + assert!(submissions.is_err(), "Can only execute governance vaas"); + + let add_channel_body = create_gov_vaa_body(1, Chain::Osmosis, *b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00channel-0"); + let (_, add_vaa_binary) = sign_vaa_body(wh.clone(), add_channel_body); + + let submissions = execute( + mut_deps.branch(), + mock_env(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![add_vaa_binary], + }, + ); + + assert!( + submissions.is_ok(), + "Can add a channel from Osmosis to Gateway" + ); + + let add_channel_body: Body = Body { + timestamp: 1u32, + nonce: 1u32, + emitter_chain: Chain::Solana, + emitter_address: GOVERNANCE_EMITTER, + sequence: 1u64, + consistency_level: 0, + payload: GovernancePacket { + chain: Chain::Osmosis, + action: Action::UpdateChannelChain { + channel_id: *b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00channel-0", + chain_id: Chain::CosmosHub, + }, + }, + }; + let (_, add_vaa_binary) = sign_vaa_body(wh.clone(), add_channel_body); + + let submissions = execute( + mut_deps.branch(), + mock_env(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![add_vaa_binary], + }, + ); + + assert!( + submissions.is_err(), + "Cannot add a update a chain besides Gateway" + ); +} + +#[test] +pub fn reject_replayed_add_channel_chain_vaas() { + let wh = WormholeKeeper::new(); + + let querier: MockQuerier = + MockQuerier::new(&[]).with_custom_handler(|q| match q { + WormholeQuery::VerifyVaa { vaa } => { + match WormholeKeeper::new().verify_vaa(&vaa.0, 0u64) { + Ok(_) => SystemResult::Ok(if let Ok(data) = to_binary(&Empty {}) { + ContractResult::Ok(data) + } else { + ContractResult::Err("Unable to convert to binary".to_string()) + }), + Err(e) => SystemResult::Ok(ContractResult::Err(e.to_string())), + } + } + _ => cosmwasm_std::SystemResult::Ok(cosmwasm_std::ContractResult::Ok( + to_binary(&Empty {}).unwrap(), + )), + }); + + let mut mut_deps = DepsMut { + storage: &mut MockStorage::default(), + api: &MockApi::default(), + querier: QuerierWrapper::new(&querier), + }; + let info = mock_info("sender", &[]); + + let add_channel_body = create_gov_vaa_body(1, Chain::Osmosis, *b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00channel-0"); + let (_, add_vaa_binary) = sign_vaa_body(wh.clone(), add_channel_body); + + let submissions = execute( + mut_deps.branch(), + mock_env(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![add_vaa_binary.clone()], + }, + ); + + assert!( + submissions.is_ok(), + "Can add a channel from Osmosis to Gateway" + ); + + let submissions = execute( + mut_deps.branch(), + mock_env(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![add_vaa_binary], + }, + ); + + assert!(submissions.is_err(), "Cannot replay the same VAA"); +} diff --git a/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/mod.rs b/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/mod.rs new file mode 100644 index 0000000000..7e5f6c1060 --- /dev/null +++ b/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/mod.rs @@ -0,0 +1,2 @@ +pub mod integration_tests; +pub mod test_utils; diff --git a/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/test_utils.rs b/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/test_utils.rs new file mode 100644 index 0000000000..702d83bf7c --- /dev/null +++ b/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/test_utils.rs @@ -0,0 +1,66 @@ +use cosmwasm_std::{Binary, Uint256}; +use serde::Serialize; +use wormhole_bindings::fake::WormholeKeeper; +use wormhole_sdk::{ + ibc_receiver::{Action, GovernancePacket}, + token::Message, + vaa::{Body, Header, Vaa}, + Address, Amount, Chain, GOVERNANCE_EMITTER, +}; + +pub fn create_transfer_vaa_body(i: usize) -> Body { + Body { + timestamp: i as u32, + nonce: i as u32, + emitter_chain: (i as u16).into(), + emitter_address: Address([(i as u8); 32]), + sequence: i as u64, + consistency_level: 32, + payload: Message::Transfer { + amount: Amount(Uint256::from(i as u128).to_be_bytes()), + token_address: Address([(i + 1) as u8; 32]), + token_chain: (i as u16).into(), + recipient: Address([i as u8; 32]), + recipient_chain: ((i + 2) as u16).into(), + fee: Amount([0u8; 32]), + }, + } +} + +pub fn create_gov_vaa_body( + i: usize, + chain_id: Chain, + channel_id: [u8; 64], +) -> Body { + Body { + timestamp: i as u32, + nonce: i as u32, + emitter_chain: Chain::Solana, + emitter_address: GOVERNANCE_EMITTER, + sequence: i as u64, + consistency_level: 0, + payload: GovernancePacket { + chain: Chain::Wormchain, + action: Action::UpdateChannelChain { + channel_id, + chain_id, + }, + }, + } +} + +pub fn sign_vaa_body(wh: WormholeKeeper, body: Body

) -> (Vaa

, Binary) { + let data = serde_wormhole::to_vec(&body).unwrap(); + let signatures = WormholeKeeper::new().sign(&data); + + let header = Header { + version: 1, + guardian_set_index: wh.guardian_set_index(), + signatures, + }; + + let v = (header, body).into(); + let data = serde_wormhole::to_vec(&v).map(From::from).unwrap(); + + (v, data) +}