From 763e9efbbffcb1ebeed3487bbcafbb0e826d0454 Mon Sep 17 00:00:00 2001 From: emidev98 Date: Thu, 2 Nov 2023 12:41:57 +0200 Subject: [PATCH] fix: ibc-hooks transfer stack order and add tests --- .gitignore | 2 + Makefile | 24 ++ app/app.go | 123 +++--- scripts/tests/README.md | 15 + scripts/tests/ibc-hooks/counter/Cargo.toml | 43 ++ scripts/tests/ibc-hooks/counter/README.md | 11 + .../ibc-hooks/counter/artifacts/checksums.txt | 1 + .../artifacts/checksums_intermediate.txt | 1 + .../ibc-hooks/counter/artifacts/counter.wasm | Bin 0 -> 177531 bytes .../tests/ibc-hooks/counter/src/contract.rs | 395 ++++++++++++++++++ scripts/tests/ibc-hooks/counter/src/error.rs | 16 + .../tests/ibc-hooks/counter/src/helpers.rs | 48 +++ .../counter/src/integration_tests.rs | 71 ++++ scripts/tests/ibc-hooks/counter/src/lib.rs | 9 + scripts/tests/ibc-hooks/counter/src/msg.rs | 63 +++ scripts/tests/ibc-hooks/counter/src/state.rs | 14 + scripts/tests/ibc-hooks/increment.sh | 72 ++++ scripts/tests/init-test-framework.sh | 122 ++++++ .../interchain-acc-config/chains/test-1.json | 21 + .../interchain-acc-config/chains/test-2.json | 21 + .../paths/test1-test2.json | 18 + .../relayer/interchain-acc-config/rly-init.sh | 49 +++ 22 files changed, 1072 insertions(+), 67 deletions(-) create mode 100644 scripts/tests/README.md create mode 100644 scripts/tests/ibc-hooks/counter/Cargo.toml create mode 100644 scripts/tests/ibc-hooks/counter/README.md create mode 100644 scripts/tests/ibc-hooks/counter/artifacts/checksums.txt create mode 100644 scripts/tests/ibc-hooks/counter/artifacts/checksums_intermediate.txt create mode 100644 scripts/tests/ibc-hooks/counter/artifacts/counter.wasm create mode 100644 scripts/tests/ibc-hooks/counter/src/contract.rs create mode 100644 scripts/tests/ibc-hooks/counter/src/error.rs create mode 100644 scripts/tests/ibc-hooks/counter/src/helpers.rs create mode 100644 scripts/tests/ibc-hooks/counter/src/integration_tests.rs create mode 100644 scripts/tests/ibc-hooks/counter/src/lib.rs create mode 100644 scripts/tests/ibc-hooks/counter/src/msg.rs create mode 100644 scripts/tests/ibc-hooks/counter/src/state.rs create mode 100755 scripts/tests/ibc-hooks/increment.sh create mode 100755 scripts/tests/init-test-framework.sh create mode 100644 scripts/tests/relayer/interchain-acc-config/chains/test-1.json create mode 100644 scripts/tests/relayer/interchain-acc-config/chains/test-2.json create mode 100644 scripts/tests/relayer/interchain-acc-config/paths/test1-test2.json create mode 100755 scripts/tests/relayer/interchain-acc-config/rly-init.sh diff --git a/.gitignore b/.gitignore index c646b43c..4dd8bbba 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ bin */.DS_Store +scripts/tests/ibc-hooks/counter/target +data \ No newline at end of file diff --git a/Makefile b/Makefile index 1f00ae4c..1ac8c024 100644 --- a/Makefile +++ b/Makefile @@ -92,3 +92,27 @@ install: go.sum build: go build $(BUILD_FLAGS) -o bin/migalood ./cmd/migalood + +integration-test-all: init-test-framework \ + test-relayer \ + test-ibc-hooks + +init-test-framework: clean-testing-data install + @echo "Initializing both blockchains..." + ./scripts/tests/init-test-framework.sh + +test-relayer: + @echo "Testing relayer..." + ./scripts/tests/relayer/interchain-acc-config/rly-init.sh + +test-ibc-hooks: + @echo "Testing ibc hooks..." + ./scripts/tests/ibc-hooks/increment.sh + +clean-testing-data: + @echo "Killing migalood and removing previous data" + -@pkill migalood 2>/dev/null + -@pkill rly 2>/dev/null + -@rm -rf ./data + +.PHONY: all install build integration-test-all init-test-framework test-relayer test-ibc-hooks clean-testing-data \ No newline at end of file diff --git a/app/app.go b/app/app.go index 8b70fba9..0aff0368 100644 --- a/app/app.go +++ b/app/app.go @@ -302,7 +302,7 @@ type MigalooApp struct { // IBC hooks IBCHooksKeeper *ibchookskeeper.Keeper - TransferStack *ibchooks.IBCMiddleware + TransferStack porttypes.Middleware Ics20WasmHooks *ibchooks.WasmHooks HooksICS4Wrapper ibchooks.ICS4Middleware @@ -518,18 +518,6 @@ func NewMigalooApp( AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)). AddRoute(alliancemoduletypes.RouterKey, alliancemodule.NewAllianceProposalHandler(app.AllianceKeeper)) - // RouterKeeper must be created before TransferKeeper - app.RouterKeeper = *routerkeeper.NewKeeper( - appCodec, - app.keys[routertypes.StoreKey], - app.GetSubspace(routertypes.ModuleName), - app.TransferKeeper, - app.IBCKeeper.ChannelKeeper, - app.DistrKeeper, - app.BankKeeper, - app.IBCKeeper.ChannelKeeper, - ) - // Configure the hooks keeper hooksKeeper := ibchookskeeper.NewKeeper( keys[ibchookstypes.StoreKey], @@ -543,38 +531,47 @@ func NewMigalooApp( app.Ics20WasmHooks, ) - // IBC Fee Module keeper - app.IBCFeeKeeper = ibcfeekeeper.NewKeeper( - appCodec, keys[ibcfeetypes.StoreKey], - app.IBCKeeper.ChannelKeeper, // may be replaced with IBC middleware - app.IBCKeeper.ChannelKeeper, - &app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, - ) - // Create Transfer Keepers app.TransferKeeper = ibctransferkeeper.NewKeeper( appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), - app.IBCFeeKeeper, // ISC4 Wrapper: fee IBC middleware + app.HooksICS4Wrapper, app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, scopedTransferKeeper, ) + transferIBCModule := transfer.NewIBCModule(app.TransferKeeper) - app.RouterKeeper.SetTransferKeeper(app.TransferKeeper) + // Hooks Middleware + hooksTransferStack := ibchooks.NewIBCMiddleware(&transferIBCModule, &app.HooksICS4Wrapper) - // ICA Host keeper - app.ICAHostKeeper = icahostkeeper.NewKeeper( - appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), - app.IBCFeeKeeper, // use ics29 fee as ics4Wrapper in middleware stack - app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, - app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), + app.RouterKeeper = *routerkeeper.NewKeeper( + appCodec, + app.keys[routertypes.StoreKey], + app.GetSubspace(routertypes.ModuleName), + app.TransferKeeper, + app.IBCKeeper.ChannelKeeper, + app.DistrKeeper, + app.BankKeeper, + app.IBCKeeper.ChannelKeeper, ) - icaModule := ica.NewAppModule(nil, &app.ICAHostKeeper) + pmfTransferStack := router.NewIBCMiddleware(hooksTransferStack, + &app.RouterKeeper, + 5, + routerkeeper.DefaultForwardTransferPacketTimeoutTimestamp, + routerkeeper.DefaultRefundTransferPacketTimeoutTimestamp, + ) + app.TransferStack = &pmfTransferStack + app.IBCFeeKeeper = ibcfeekeeper.NewKeeper( + appCodec, keys[ibcfeetypes.StoreKey], + app.IBCKeeper.ChannelKeeper, // may be replaced with IBC middleware + app.IBCKeeper.ChannelKeeper, + &app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, + ) app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( appCodec, keys[icacontrollertypes.StoreKey], @@ -585,9 +582,34 @@ func NewMigalooApp( scopedICAControllerKeeper, app.MsgServiceRouter(), ) + app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, + keys[icahosttypes.StoreKey], + app.GetSubspace(icahosttypes.SubModuleName), + app.IBCFeeKeeper, // use ics29 fee as ics4Wrapper in middleware stack + app.IBCKeeper.ChannelKeeper, + &app.IBCKeeper.PortKeeper, + app.AccountKeeper, + scopedICAHostKeeper, + app.MsgServiceRouter(), + ) + icaModule := ica.NewAppModule(nil, &app.ICAHostKeeper) // For wasmd we use the demo controller from https://github.com/cosmos/interchain-accounts but see notes below - app.InterTxKeeper = intertxkeeper.NewKeeper(appCodec, keys[intertxtypes.StoreKey], app.ICAControllerKeeper, scopedInterTxKeeper) + app.InterTxKeeper = intertxkeeper.NewKeeper( + appCodec, + keys[intertxtypes.StoreKey], + app.ICAControllerKeeper, + scopedInterTxKeeper, + ) + + var icaControllerStack porttypes.IBCModule + icaControllerStack = intertx.NewIBCModule(app.InterTxKeeper) + icaControllerStack = icacontroller.NewIBCMiddleware(icaControllerStack, app.ICAControllerKeeper) + icaControllerStack = ibcfee.NewIBCMiddleware(icaControllerStack, app.IBCFeeKeeper) + + icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + icaHostStack := ibcfee.NewIBCMiddleware(icaHostIBCModule, app.IBCFeeKeeper) // create evidence keeper with router evidenceKeeper := evidencekeeper.NewKeeper( @@ -635,39 +657,6 @@ func NewMigalooApp( govRouter.AddRoute(wasm.RouterKey, wasm.NewWasmProposalHandler(app.WasmKeeper, enabledProposals)) } - // Create Transfer Stack - var transferStack porttypes.IBCModule - transferStack = transfer.NewIBCModule(app.TransferKeeper) - transferStack = ibcfee.NewIBCMiddleware(transferStack, app.IBCFeeKeeper) - transferStack = router.NewIBCMiddleware( - transferStack, - &app.RouterKeeper, - 0, - routerkeeper.DefaultForwardTransferPacketTimeoutTimestamp, - routerkeeper.DefaultRefundTransferPacketTimeoutTimestamp, - ) - // Hooks Middleware - hooksTransferStack := ibchooks.NewIBCMiddleware(transferStack, &app.HooksICS4Wrapper) - app.TransferStack = &hooksTransferStack - - // Create Interchain Accounts Stack - // SendPacket, since it is originating from the application to core IBC: - // icaAuthModuleKeeper.SendTx -> icaController.SendPacket -> fee.SendPacket -> channel.SendPacket - - // Note: please do your research before using this in production app, this is a demo and not an officially - // supported IBC team implementation. Do your own research before using it. - var icaControllerStack porttypes.IBCModule - // You will likely want to use your own reviewed and maintained ica auth module - icaControllerStack = intertx.NewIBCModule(app.InterTxKeeper) - icaControllerStack = icacontroller.NewIBCMiddleware(icaControllerStack, app.ICAControllerKeeper) - icaControllerStack = ibcfee.NewIBCMiddleware(icaControllerStack, app.IBCFeeKeeper) - - // RecvPacket, message that originates from core IBC and goes down to app, the flow is: - // channel.RecvPacket -> fee.OnRecvPacket -> icaHost.OnRecvPacket - var icaHostStack porttypes.IBCModule - icaHostStack = icahost.NewIBCModule(app.ICAHostKeeper) - icaHostStack = ibcfee.NewIBCMiddleware(icaHostStack, app.IBCFeeKeeper) - // Create fee enabled wasm ibc Stack var wasmStack porttypes.IBCModule wasmStack = wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCFeeKeeper) @@ -675,11 +664,11 @@ func NewMigalooApp( // Create static IBC router, add app routes, then set and seal it ibcRouter := porttypes.NewRouter(). - AddRoute(ibctransfertypes.ModuleName, transferStack). - AddRoute(wasm.ModuleName, wasmStack). AddRoute(intertxtypes.ModuleName, icaControllerStack). AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). - AddRoute(icahosttypes.SubModuleName, icaHostStack) + AddRoute(icahosttypes.SubModuleName, icaHostStack). + AddRoute(ibctransfertypes.ModuleName, pmfTransferStack). + AddRoute(wasm.ModuleName, wasmStack) app.IBCKeeper.SetRouter(ibcRouter) govConfig := govtypes.DefaultConfig() diff --git a/scripts/tests/README.md b/scripts/tests/README.md new file mode 100644 index 00000000..27948371 --- /dev/null +++ b/scripts/tests/README.md @@ -0,0 +1,15 @@ +# Tests + +This folder contains the integration tests that should run successfully each time a new core release is created. + +At the moment, there are four tests defined that can be run with `make integration-test-all`. The breakdown of each test is as follows: + +- [init-test-framework](./start.sh): build the core and spin up two nodes with their own genesis event and some accounts preloaded with funds. +- [test-relayer](./relayer/): connect the two blockchains with a relayer opening a channel between both of them. + - [test-ica](./ica/delegate.sh): using the relayer, this test creates an interchain account and delegates funds using an interchain message. + - [test-ibc-hooks](./ibc-hooks/increment.sh): deploys a slightly modified [counter contract](./ibc-hooks/counter/) to chain test-2 and submits two requests from chain test-1 to validate that the contract received the funds and executed the wasm code correctly. +- remove-ica-data: removes the data and kills the process when the integration tests are completed. + +## Development process + +This set of tests must run out of the box in Linux-based systems installing [GoLang 1.20](https://go.dev/), [jq](https://stedolan.github.io/jq/), [screen](https://www.geeksforgeeks.org/screen-command-in-linux-with-examples/) and [rly](https://github.com/cosmos/relayer). \ No newline at end of file diff --git a/scripts/tests/ibc-hooks/counter/Cargo.toml b/scripts/tests/ibc-hooks/counter/Cargo.toml new file mode 100644 index 00000000..f164afc0 --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "counter" +description = "Cosmwasm counter dapp, with permissions for testing Osmosis wasmhooks" +version = "0.1.0" +authors = ["osmosis contributors"] +edition = "2021" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[package.metadata.scripts] +optimize = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.12.6 +""" + +[dependencies] +cosmwasm-schema = "1.1.3" +cosmwasm-std = "1.1.3" +cosmwasm-storage = "1.1.3" +cw-storage-plus = "0.16.0" +cw2 = "0.16.0" +schemars = "0.8.10" +serde = { version = "1.0.145", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.31" } + +[dev-dependencies] +cw-multi-test = "0.16.0" diff --git a/scripts/tests/ibc-hooks/counter/README.md b/scripts/tests/ibc-hooks/counter/README.md new file mode 100644 index 00000000..f4394fe8 --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/README.md @@ -0,0 +1,11 @@ +# Counter contract from [Osmosis Labs](https://github.com/osmosis-labs/osmosis/commit/64393a14e18b2562d72a3892eec716197a3716c7) + +This contract is a modification of the standard cosmwasm `counter` contract. +Namely, it tracks a counter, _by sender_. +This is a better way to test wasmhooks. + +This contract tracks any funds sent to it by adding it to the state under the `sender` key. + +This way we can verify that, independently of the sender, the funds will end up under the +`WasmHooksModuleAccount` address when the contract is executed via an IBC send that goes +through the wasmhooks module. diff --git a/scripts/tests/ibc-hooks/counter/artifacts/checksums.txt b/scripts/tests/ibc-hooks/counter/artifacts/checksums.txt new file mode 100644 index 00000000..1f542142 --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/artifacts/checksums.txt @@ -0,0 +1 @@ +c0e7a3b40d9710f6f72322293ba5cd871714008d9accd9a91c0fb08272609054 counter.wasm diff --git a/scripts/tests/ibc-hooks/counter/artifacts/checksums_intermediate.txt b/scripts/tests/ibc-hooks/counter/artifacts/checksums_intermediate.txt new file mode 100644 index 00000000..0394e5fc --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/artifacts/checksums_intermediate.txt @@ -0,0 +1 @@ +bffb3256d4fd5668e497ae5671844ef3dcdf1a5f2594747b894dfc02e243ab0e ./target/wasm32-unknown-unknown/release/counter.wasm diff --git a/scripts/tests/ibc-hooks/counter/artifacts/counter.wasm b/scripts/tests/ibc-hooks/counter/artifacts/counter.wasm new file mode 100644 index 0000000000000000000000000000000000000000..e3e5b53bd76f861b707222cd0cca618845dfe52b GIT binary patch literal 177531 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

T#-=AM8e(J99=S3e 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

$zt3zc2LPj&Gl2xh=We`M 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 Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + let initial_counter = Counter { + count: msg.count, + total_funds: vec![], + owner: info.sender.clone(), + }; + COUNTERS.save(deps.storage, info.sender.clone(), &initial_counter)?; + + Ok(Response::new() + .add_attribute("method", "instantiate") + .add_attribute("owner", info.sender) + .add_attribute("count", msg.count.to_string())) +} + +pub mod utils { + use cosmwasm_std::Addr; + + use super::*; + + pub fn update_counter( + deps: DepsMut, + sender: Addr, + update_counter: &dyn Fn(&Option) -> i32, + update_funds: &dyn Fn(&Option) -> Vec, + ) -> Result { + COUNTERS + .update( + deps.storage, + sender.clone(), + |state| -> Result<_, ContractError> { + match state { + None => Ok(Counter { + count: update_counter(&None), + total_funds: update_funds(&None), + owner: sender, + }), + Some(counter) => Ok(Counter { + count: update_counter(&Some(counter.clone())), + total_funds: update_funds(&Some(counter)), + owner: sender, + }), + } + }, + ) + .map(|_r| true) + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Increment {} => execute::increment(deps, info), + ExecuteMsg::Reset { count } => execute::reset(deps, info, count), + } +} + +pub mod execute { + use super::*; + + pub fn increment(deps: DepsMut, info: MessageInfo) -> Result { + utils::update_counter( + deps, + info.sender, + &|counter| match counter { + None => 0, + Some(counter) => counter.count + 1, + }, + &|counter| match counter { + None => info.funds.clone(), + Some(counter) => naive_add_coins(&info.funds, &counter.total_funds), + }, + )?; + Ok(Response::new().add_attribute("action", "increment")) + } + + pub fn reset(deps: DepsMut, info: MessageInfo, count: i32) -> Result { + utils::update_counter(deps, info.sender, &|_counter| count, &|_counter| vec![])?; + Ok(Response::new().add_attribute("action", "reset")) + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> Result { + match msg { + SudoMsg::IBCLifecycleComplete(IBCLifecycleComplete::IBCAck { + channel: _, + sequence: _, + ack: _, + success, + }) => sudo::receive_ack(deps, env.contract.address, success), + SudoMsg::IBCLifecycleComplete(IBCLifecycleComplete::IBCTimeout { + channel: _, + sequence: _, + }) => sudo::ibc_timeout(deps, env.contract.address), + } +} + +pub mod sudo { + use cosmwasm_std::Addr; + + use super::*; + + pub fn receive_ack( + deps: DepsMut, + contract: Addr, + _success: bool, + ) -> Result { + utils::update_counter( + deps, + contract, + &|counter| match counter { + None => 1, + Some(counter) => counter.count + 1, + }, + &|_counter| vec![], + )?; + Ok(Response::new().add_attribute("action", "ack")) + } + + pub(crate) fn ibc_timeout(deps: DepsMut, contract: Addr) -> Result { + utils::update_counter( + deps, + contract, + &|counter| match counter { + None => 10, + Some(counter) => counter.count + 10, + }, + &|_counter| vec![], + )?; + Ok(Response::new().add_attribute("action", "timeout")) + } +} + +pub fn naive_add_coins(lhs: &Vec, rhs: &Vec) -> Vec { + // This is a naive, inneficient implementation of Vec addition. + // This shouldn't be used in production but serves our purpose for this + // testing contract + let mut coins: HashMap = HashMap::new(); + for coin in lhs { + coins.insert(coin.denom.clone(), coin.amount); + } + + for coin in rhs { + coins + .entry(coin.denom.clone()) + .and_modify(|e| *e += coin.amount) + .or_insert(coin.amount); + } + coins.iter().map(|(d, &a)| Coin::new(a.into(), d)).collect() +} + +#[test] +fn coin_addition() { + let c1 = vec![Coin::new(1, "a"), Coin::new(2, "b")]; + let c2 = vec![Coin::new(7, "a"), Coin::new(2, "c")]; + + let mut sum = naive_add_coins(&c1, &c1); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!(sum, vec![Coin::new(2, "a"), Coin::new(4, "b")]); + + let mut sum = naive_add_coins(&c1, &c2); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!( + sum, + vec![Coin::new(8, "a"), Coin::new(2, "b"), Coin::new(2, "c"),] + ); + + let mut sum = naive_add_coins(&c2, &c2); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!(sum, vec![Coin::new(14, "a"), Coin::new(4, "c"),]); + + let mut sum = naive_add_coins(&c2, &c1); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!( + sum, + vec![Coin::new(8, "a"), Coin::new(2, "b"), Coin::new(2, "c"),] + ); + + let mut sum = naive_add_coins(&vec![], &c2); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!(sum, c2); + + let mut sum = naive_add_coins(&c2, &vec![]); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!(sum, c2); +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetCount { addr } => to_binary(&query::count(deps, addr)?), + QueryMsg::GetTotalFunds { addr } => to_binary(&query::total_funds(deps, addr)?), + } +} + +pub mod query { + use cosmwasm_std::Addr; + + use super::*; + + pub fn count(deps: Deps, addr: Addr) -> StdResult { + let state = COUNTERS.load(deps.storage, addr)?; + Ok(GetCountResponse { count: state.count }) + } + + pub fn total_funds(deps: Deps, addr: Addr) -> StdResult { + let state = COUNTERS.load(deps.storage, addr)?; + Ok(GetTotalFundsResponse { + total_funds: state.total_funds, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::Addr; + use cosmwasm_std::{coins, from_binary}; + + #[test] + fn proper_initialization() { + let mut deps = mock_dependencies(); + + let msg = InstantiateMsg { count: 17 }; + let info = mock_info("creator", &coins(1000, "earth")); + + // we can just call .unwrap() to assert this was a success + let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!(0, res.messages.len()); + + // it worked, let's query the state + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::GetCount { + addr: Addr::unchecked("creator"), + }, + ) + .unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(17, value.count); + } + + #[test] + fn increment() { + let mut deps = mock_dependencies(); + + let msg = InstantiateMsg { count: 17 }; + let info = mock_info("creator", &coins(2, "token")); + let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + let msg = InstantiateMsg { count: 17 }; + let info = mock_info("someone-else", &coins(2, "token")); + let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + let info = mock_info("creator", &coins(2, "token")); + let msg = ExecuteMsg::Increment {}; + let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); + + // should increase counter by 1 + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::GetCount { + addr: Addr::unchecked("creator"), + }, + ) + .unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(18, value.count); + + // Counter for someone else is not incremented + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::GetCount { + addr: Addr::unchecked("someone-else"), + }, + ) + .unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(17, value.count); + } + + #[test] + fn reset() { + let mut deps = mock_dependencies(); + + let msg = InstantiateMsg { count: 17 }; + let info = mock_info("creator", &coins(2, "token")); + let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + // beneficiary can release it + let unauth_info = mock_info("anyone", &coins(2, "token")); + let msg = ExecuteMsg::Reset { count: 7 }; + let _res = execute(deps.as_mut(), mock_env(), unauth_info, msg); + + // should be 7 + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::GetCount { + addr: Addr::unchecked("anyone"), + }, + ) + .unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(7, value.count); + + // only the original creator can reset the counter + let auth_info = mock_info("creator", &coins(2, "token")); + let msg = ExecuteMsg::Reset { count: 5 }; + let _res = execute(deps.as_mut(), mock_env(), auth_info, msg).unwrap(); + + // should now be 5 + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::GetCount { + addr: Addr::unchecked("creator"), + }, + ) + .unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(5, value.count); + } + + #[test] + fn acks() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let get_msg = QueryMsg::GetCount { + addr: Addr::unchecked(env.clone().contract.address), + }; + + // No acks + query(deps.as_ref(), env.clone(), get_msg.clone()).unwrap_err(); + + let msg = SudoMsg::ReceiveAck { + channel: format!("channel-0"), + sequence: 1, + ack: String::new(), + success: true, + }; + let _res = sudo(deps.as_mut(), env.clone(), msg).unwrap(); + + // should increase counter by 1 + let res = query(deps.as_ref(), env.clone(), get_msg.clone()).unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(1, value.count); + + let msg = SudoMsg::ReceiveAck { + channel: format!("channel-0"), + sequence: 1, + ack: String::new(), + success: true, + }; + let _res = sudo(deps.as_mut(), env.clone(), msg).unwrap(); + + // should increase counter by 1 + let res = query(deps.as_ref(), env, get_msg).unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(2, value.count); + } +} diff --git a/scripts/tests/ibc-hooks/counter/src/error.rs b/scripts/tests/ibc-hooks/counter/src/error.rs new file mode 100644 index 00000000..3caf0c5c --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/src/error.rs @@ -0,0 +1,16 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Custom Error val: {val:?}")] + CustomError { val: String }, + // Add any other custom errors you like here. + // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. +} diff --git a/scripts/tests/ibc-hooks/counter/src/helpers.rs b/scripts/tests/ibc-hooks/counter/src/helpers.rs new file mode 100644 index 00000000..c943c136 --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/src/helpers.rs @@ -0,0 +1,48 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{ + to_binary, Addr, Coin, CosmosMsg, CustomQuery, Querier, QuerierWrapper, StdResult, WasmMsg, + WasmQuery, +}; + +use crate::msg::{ExecuteMsg, GetCountResponse, QueryMsg}; + +/// CwTemplateContract is a wrapper around Addr that provides a lot of helpers +/// for working with this. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct CwTemplateContract(pub Addr); + +impl CwTemplateContract { + pub fn addr(&self) -> Addr { + self.0.clone() + } + + pub fn call>(&self, msg: T) -> StdResult { + let msg = to_binary(&msg.into())?; + Ok(WasmMsg::Execute { + contract_addr: self.addr().into(), + msg, + funds: vec![], + } + .into()) + } + + /// Get Count + pub fn count(&self, querier: &Q, addr: Addr) -> StdResult + where + Q: Querier, + T: Into, + CQ: CustomQuery, + { + let msg = QueryMsg::GetCount { addr }; + let query = WasmQuery::Smart { + contract_addr: self.addr().into(), + msg: to_binary(&msg)?, + } + .into(); + let res: GetCountResponse = QuerierWrapper::::new(querier).query(&query)?; + Ok(res) + } +} + diff --git a/scripts/tests/ibc-hooks/counter/src/integration_tests.rs b/scripts/tests/ibc-hooks/counter/src/integration_tests.rs new file mode 100644 index 00000000..4c507846 --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/src/integration_tests.rs @@ -0,0 +1,71 @@ +#[cfg(test)] +mod tests { + use crate::helpers::CwTemplateContract; + use crate::msg::InstantiateMsg; + use cosmwasm_std::{Addr, Coin, Empty, Uint128}; + use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper, Executor}; + + pub fn contract_template() -> Box> { + let contract = ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ); + Box::new(contract) + } + + const USER: &str = "USER"; + const ADMIN: &str = "ADMIN"; + const NATIVE_DENOM: &str = "denom"; + + fn mock_app() -> App { + AppBuilder::new().build(|router, _, storage| { + router + .bank + .init_balance( + storage, + &Addr::unchecked(USER), + vec![Coin { + denom: NATIVE_DENOM.to_string(), + amount: Uint128::new(1), + }], + ) + .unwrap(); + }) + } + + fn proper_instantiate() -> (App, CwTemplateContract) { + let mut app = mock_app(); + let cw_template_id = app.store_code(contract_template()); + + let msg = InstantiateMsg { count: 1i32 }; + let cw_template_contract_addr = app + .instantiate_contract( + cw_template_id, + Addr::unchecked(ADMIN), + &msg, + &[], + "test", + None, + ) + .unwrap(); + + let cw_template_contract = CwTemplateContract(cw_template_contract_addr); + + (app, cw_template_contract) + } + + mod count { + use super::*; + use crate::msg::ExecuteMsg; + + #[test] + fn count() { + let (mut app, cw_template_contract) = proper_instantiate(); + + let msg = ExecuteMsg::Increment {}; + let cosmos_msg = cw_template_contract.call(msg).unwrap(); + app.execute(Addr::unchecked(USER), cosmos_msg).unwrap(); + } + } +} diff --git a/scripts/tests/ibc-hooks/counter/src/lib.rs b/scripts/tests/ibc-hooks/counter/src/lib.rs new file mode 100644 index 00000000..ffd1f6ac --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/src/lib.rs @@ -0,0 +1,9 @@ +#![allow(unused_imports)] +pub mod contract; +mod error; +pub mod helpers; +pub mod integration_tests; +pub mod msg; +pub mod state; + +pub use crate::error::ContractError; diff --git a/scripts/tests/ibc-hooks/counter/src/msg.rs b/scripts/tests/ibc-hooks/counter/src/msg.rs new file mode 100644 index 00000000..037d8c57 --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/src/msg.rs @@ -0,0 +1,63 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Coin}; + +#[cw_serde] +pub struct InstantiateMsg { + pub count: i32, +} + +#[cw_serde] +pub enum ExecuteMsg { + Increment {}, + Reset { count: i32 }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + // GetCount returns the current count as a json-encoded number + #[returns(GetCountResponse)] + GetCount { addr: Addr }, + #[returns(GetTotalFundsResponse)] + GetTotalFunds { addr: Addr }, +} + +// We define a custom struct for each query response +#[cw_serde] +pub struct GetCountResponse { + pub count: i32, +} + +#[cw_serde] +pub struct GetTotalFundsResponse { + pub total_funds: Vec, +} + +#[cw_serde] +#[serde(rename = "ibc_lifecycle_complete")] +pub enum IBCLifecycleComplete { + #[serde(rename = "ibc_ack")] + IBCAck { + /// The source channel (terra side) of the IBC packet + channel: String, + /// The sequence number that the packet was sent with + sequence: u64, + /// String encoded version of the ack as seen by OnAcknowledgementPacket(..) + ack: String, + /// Weather an ack is a success of failure according to the transfer spec + success: bool, + }, + #[serde(rename = "ibc_timeout")] + IBCTimeout { + /// The source channel (terra side) of the IBC packet + channel: String, + /// The sequence number that the packet was sent with + sequence: u64, + }, +} + +#[cw_serde] +pub enum SudoMsg { + #[serde(rename = "ibc_lifecycle_complete")] + IBCLifecycleComplete(IBCLifecycleComplete), +} diff --git a/scripts/tests/ibc-hooks/counter/src/state.rs b/scripts/tests/ibc-hooks/counter/src/state.rs new file mode 100644 index 00000000..4b8002fc --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/src/state.rs @@ -0,0 +1,14 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{Addr, Coin}; +use cw_storage_plus::{Item, Map}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct Counter { + pub count: i32, + pub total_funds: Vec, + pub owner: Addr, +} + +pub const COUNTERS: Map = Map::new("state"); diff --git a/scripts/tests/ibc-hooks/increment.sh b/scripts/tests/ibc-hooks/increment.sh new file mode 100755 index 00000000..0735be65 --- /dev/null +++ b/scripts/tests/ibc-hooks/increment.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +echo "" +echo "#################" +echo "# IBC Hook call #" +echo "#################" +echo "" + +BINARY=migalood +CHAIN_DIR=$(pwd)/data +WALLET_1=$($BINARY keys show wallet1 -a --keyring-backend test --home $CHAIN_DIR/test-1) +WALLET_2=$($BINARY keys show wallet2 -a --keyring-backend test --home $CHAIN_DIR/test-2) + +# Deploy the smart contract on chain to test the callbacks. (find the source code under the following url: `~/scripts/tests/ibc-hooks/counter/src/contract.rs`) +echo "Deploying counter contract" +CODE_ID=$($BINARY tx wasm store $(pwd)/scripts/tests/ibc-hooks/counter/artifacts/counter.wasm --from $WALLET_2 --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 --keyring-backend test --broadcast-mode block -y --gas 10000000 -o json | jq -r '.logs[0].events[1].attributes[1].value') + +# Use Instantiate2 to instantiate the previous smart contract with a random hash to enable multiple instances of the same contract (when needed). +echo "Instantiating counter contract" +RANDOM_HASH=$(hexdump -vn16 -e'4/4 "%08X" 1 "\n"' /dev/urandom) +CONTRACT_ADDRESS=$($BINARY tx wasm instantiate2 $CODE_ID '{"count": 0}' $RANDOM_HASH --no-admin --label="Label with $RANDOM_HASH" --from $WALLET_2 --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 --keyring-backend test --broadcast-mode block -y --gas 10000000 -o json | jq -r '.logs[0].events[0].attributes[0].value') + +echo "Executing the IBC Hook to increment the counter" +# First execute an IBC transfer to create the entry in the smart contract with the sender address ... +IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $CONTRACT_ADDRESS 1uwhale --memo='{"wasm":{"contract": "'"$CONTRACT_ADDRESS"'" ,"msg": {"increment": {}}}}' --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 --keyring-backend test --from $WALLET_1 --broadcast-mode block -y -o json) +# ... then send another transfer to increments the count value from 0 to 1, send 1 more uwhale to the contract address to validate that it increased the value correctly. +IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $CONTRACT_ADDRESS 1uwhale --memo='{"wasm":{"contract": "'"$CONTRACT_ADDRESS"'" ,"msg": {"increment": {}}}}' --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 --keyring-backend test --from $WALLET_1 --broadcast-mode block -y -o json) +export WALLET_1_WASM_SENDER=$($BINARY q ibchooks wasm-sender channel-0 "$WALLET_1" --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657) + +COUNT_RES="" +COUNT_FUNDS_RES="" +while [ "$COUNT_RES" != "1" ] || [ "$COUNT_FUNDS_RES" != "2" ]; do + sleep 3 + # Query to assert that the counter value is 1 and the fund send are 2uwhale (remeber that the first time fund are send to the contract the counter is set to 0 instead of 1) + COUNT_RES=$($BINARY query wasm contract-state smart "$CONTRACT_ADDRESS" '{"get_count": {"addr": "'"$WALLET_1_WASM_SENDER"'"}}' --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 -o json | jq -r '.data.count') + COUNT_FUNDS_RES=$($BINARY query wasm contract-state smart "$CONTRACT_ADDRESS" '{"get_total_funds": {"addr": "'"$WALLET_1_WASM_SENDER"'"}}' --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 -o json | jq -r '.data.total_funds[0].amount') + echo "transaction relayed count: $COUNT_RES and relayed funds: $COUNT_FUNDS_RES" +done + +echo "Executing the IBC Hook to increment the counter on callback" +# Execute an IBC transfer with ibc_callback to test the callback acknowledgement twice. +IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1uwhale --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 --broadcast-mode block -y -o json) +IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1uwhale --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 --broadcast-mode block -y -o json) +export WALLET_2_WASM_SENDER=$($BINARY q ibchooks wasm-sender channel-0 "$WALLET_2" --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657) + +COUNT_RES="" +while [ "$COUNT_RES" != "2" ]; do + sleep 3 + # Query the smart contract to validate that it received the callback twice (notice that the queried addess is the contract address itself). + COUNT_RES=$($BINARY query wasm contract-state smart "$CONTRACT_ADDRESS" '{"get_count": {"addr": "'"$CONTRACT_ADDRESS"'"}}' --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 -o json | jq -r '.data.count') + echo "relayed callback transaction count: $COUNT_RES" +done + +echo "Executing the IBC Hook to increment the counter on callback with timeout" +# Prepare two callback queries but this time with a timeout height that is unreachable (0-1) to test the timeout callback. +IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1uwhale --packet-timeout-height="0-1" --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 --broadcast-mode block -y -o json) +IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1uwhale --packet-timeout-height="0-1" --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 --broadcast-mode block -y -o json) +export WALLET_2_WASM_SENDER=$($BINARY q ibchooks wasm-sender channel-0 "$WALLET_2" --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657) + +COUNT_RES="" +while [ "$COUNT_RES" != "22" ]; do + sleep 3 + # Query the smart contract to validate that it received the timeout callback twice and keep in mind that per each timeout the contract increases 10 counts (notice that the queried addess is the contract address itself). + COUNT_RES=$($BINARY query wasm contract-state smart "$CONTRACT_ADDRESS" '{"get_count": {"addr": "'"$CONTRACT_ADDRESS"'"}}' --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 -o json | jq -r '.data.count') + echo "relayed timeout callback transaction count: $COUNT_RES" +done + +echo "" +echo "##########################" +echo "# SUCCESS: IBC Hook call #" +echo "##########################" +echo "" diff --git a/scripts/tests/init-test-framework.sh b/scripts/tests/init-test-framework.sh new file mode 100755 index 00000000..860bd596 --- /dev/null +++ b/scripts/tests/init-test-framework.sh @@ -0,0 +1,122 @@ +#!/bin/bash + +BINARY=migalood +CHAIN_DIR=$(pwd)/data +CHAINID_1=test-1 +CHAINID_2=test-2 + +VAL_MNEMONIC_1="clock post desk civil pottery foster expand merit dash seminar song memory figure uniform spice circle try happy obvious trash crime hybrid hood cushion" +VAL_MNEMONIC_2="angry twist harsh drastic left brass behave host shove marriage fall update business leg direct reward object ugly security warm tuna model broccoli choice" +WALLET_MNEMONIC_1="banner spread envelope side kite person disagree path silver will brother under couch edit food venture squirrel civil budget number acquire point work mass" +WALLET_MNEMONIC_2="veteran try aware erosion drink dance decade comic dawn museum release episode original list ability owner size tuition surface ceiling depth seminar capable only" +WALLET_MNEMONIC_3="vacuum burst ordinary enact leaf rabbit gather lend left chase park action dish danger green jeans lucky dish mesh language collect acquire waste load" +WALLET_MNEMONIC_4="open attitude harsh casino rent attitude midnight debris describe spare cancel crisp olive ride elite gallery leaf buffalo sheriff filter rotate path begin soldier" +RLY_MNEMONIC_1="alley afraid soup fall idea toss can goose become valve initial strong forward bright dish figure check leopard decide warfare hub unusual join cart" +RLY_MNEMONIC_2="record gift you once hip style during joke field prize dust unique length more pencil transfer quit train device arrive energy sort steak upset" + +P2PPORT_1=16656 +P2PPORT_2=26656 +RPCPORT_1=16657 +RPCPORT_2=26657 +RESTPORT_1=1316 +RESTPORT_2=1317 +ROSETTA_1=8080 +ROSETTA_2=8081 +GRPCPORT_1=8090 +GRPCPORT_2=9090 +GRPCWEB_1=8091 +GRPCWEB_2=9091 + +# Stop if it is already running +if pgrep -x "$BINARY" >/dev/null; then + echo "Terminating $BINARY..." + killall $BINARY +fi + +echo "Removing previous data..." +rm -rf $CHAIN_DIR/$CHAINID_1 &> /dev/null +rm -rf $CHAIN_DIR/$CHAINID_2 &> /dev/null + +# Add directories for both chains, exit if an error occurs +if ! mkdir -p $CHAIN_DIR/$CHAINID_1 2>/dev/null; then + echo "Failed to create chain folder. Aborting..." + exit 1 +fi + +if ! mkdir -p $CHAIN_DIR/$CHAINID_2 2>/dev/null; then + echo "Failed to create chain folder. Aborting..." + exit 1 +fi + +echo "Initializing $CHAINID_1 & $CHAINID_2..." +$BINARY init test --home $CHAIN_DIR/$CHAINID_1 --chain-id=$CHAINID_1 --staking-bond-denom uwhale &> /dev/null +$BINARY init test --home $CHAIN_DIR/$CHAINID_2 --chain-id=$CHAINID_2 --staking-bond-denom uwhale &> /dev/null + +echo "Adding genesis accounts..." +echo $VAL_MNEMONIC_1 | $BINARY keys add val1 --home $CHAIN_DIR/$CHAINID_1 --recover --keyring-backend=test +echo $VAL_MNEMONIC_2 | $BINARY keys add val2 --home $CHAIN_DIR/$CHAINID_2 --recover --keyring-backend=test +echo $WALLET_MNEMONIC_1 | $BINARY keys add wallet1 --home $CHAIN_DIR/$CHAINID_1 --recover --keyring-backend=test +echo $WALLET_MNEMONIC_2 | $BINARY keys add wallet2 --home $CHAIN_DIR/$CHAINID_2 --recover --keyring-backend=test +echo $WALLET_MNEMONIC_3 | $BINARY keys add wallet3 --home $CHAIN_DIR/$CHAINID_1 --recover --keyring-backend=test +echo $WALLET_MNEMONIC_4 | $BINARY keys add wallet4 --home $CHAIN_DIR/$CHAINID_2 --recover --keyring-backend=test +echo $RLY_MNEMONIC_1 | $BINARY keys add rly1 --home $CHAIN_DIR/$CHAINID_1 --recover --keyring-backend=test +echo $RLY_MNEMONIC_2 | $BINARY keys add rly2 --home $CHAIN_DIR/$CHAINID_2 --recover --keyring-backend=test + +VAL1_ADDR=$($BINARY keys show val1 --home $CHAIN_DIR/$CHAINID_1 --keyring-backend test -a) +VAL2_ADDR=$($BINARY keys show val2 --home $CHAIN_DIR/$CHAINID_2 --keyring-backend test -a) +WALLET1_ADDR=$($BINARY keys show wallet1 --home $CHAIN_DIR/$CHAINID_1 --keyring-backend test -a) +WALLET2_ADDR=$($BINARY keys show wallet2 --home $CHAIN_DIR/$CHAINID_2 --keyring-backend test -a) +WALLET3_ADDR=$($BINARY keys show wallet3 --home $CHAIN_DIR/$CHAINID_1 --keyring-backend test -a) +WALLET4_ADDR=$($BINARY keys show wallet4 --home $CHAIN_DIR/$CHAINID_2 --keyring-backend test -a) +RLY1_ADDR=$($BINARY keys show rly1 --home $CHAIN_DIR/$CHAINID_1 --keyring-backend test -a) +RLY2_ADDR=$($BINARY keys show rly2 --home $CHAIN_DIR/$CHAINID_2 --keyring-backend test -a) + +$BINARY add-genesis-account $VAL1_ADDR 1000000000000uwhale --home $CHAIN_DIR/$CHAINID_1 +$BINARY add-genesis-account $VAL2_ADDR 1000000000000uwhale --home $CHAIN_DIR/$CHAINID_2 +$BINARY add-genesis-account $WALLET1_ADDR 1000000000000uwhale --home $CHAIN_DIR/$CHAINID_1 +$BINARY add-genesis-account $WALLET2_ADDR 1000000000000uwhale --home $CHAIN_DIR/$CHAINID_2 +$BINARY add-genesis-account $WALLET3_ADDR 1000000000000uwhale --vesting-amount 10000000000uwhale --vesting-start-time $(date +%s) --vesting-end-time $(($(date '+%s') + 100000023)) --home $CHAIN_DIR/$CHAINID_1 +$BINARY add-genesis-account $WALLET4_ADDR 1000000000000uwhale --vesting-amount 10000000000uwhale --vesting-start-time $(date +%s) --vesting-end-time $(($(date '+%s') + 100000023)) --home $CHAIN_DIR/$CHAINID_2 +$BINARY add-genesis-account $RLY1_ADDR 1000000000000uwhale --home $CHAIN_DIR/$CHAINID_1 +$BINARY add-genesis-account $RLY2_ADDR 1000000000000uwhale --home $CHAIN_DIR/$CHAINID_2 + +echo "Creating and collecting gentx..." +$BINARY gentx val1 7000000000uwhale --home $CHAIN_DIR/$CHAINID_1 --chain-id $CHAINID_1 --keyring-backend test +$BINARY gentx val2 7000000000uwhale --home $CHAIN_DIR/$CHAINID_2 --chain-id $CHAINID_2 --keyring-backend test +$BINARY collect-gentxs --home $CHAIN_DIR/$CHAINID_1 &> /dev/null +$BINARY collect-gentxs --home $CHAIN_DIR/$CHAINID_2 &> /dev/null + +echo "Changing defaults and ports in app.toml and config.toml files..." +sed -i -e 's#"tcp://0.0.0.0:26656"#"tcp://0.0.0.0:'"$P2PPORT_1"'"#g' $CHAIN_DIR/$CHAINID_1/config/config.toml +sed -i -e 's#"tcp://127.0.0.1:26657"#"tcp://0.0.0.0:'"$RPCPORT_1"'"#g' $CHAIN_DIR/$CHAINID_1/config/config.toml +sed -i -e 's/timeout_commit = "5s"/timeout_commit = "1s"/g' $CHAIN_DIR/$CHAINID_1/config/config.toml +sed -i -e 's/timeout_propose = "3s"/timeout_propose = "1s"/g' $CHAIN_DIR/$CHAINID_1/config/config.toml +sed -i -e 's/index_all_keys = false/index_all_keys = true/g' $CHAIN_DIR/$CHAINID_1/config/config.toml +sed -i -e 's/enable = false/enable = true/g' $CHAIN_DIR/$CHAINID_1/config/app.toml +sed -i -e 's/swagger = false/swagger = true/g' $CHAIN_DIR/$CHAINID_1/config/app.toml +sed -i -e 's#"tcp://0.0.0.0:1317"#"tcp://0.0.0.0:'"$RESTPORT_1"'"#g' $CHAIN_DIR/$CHAINID_1/config/app.toml +sed -i -e 's#":8080"#":'"$ROSETTA_1"'"#g' $CHAIN_DIR/$CHAINID_1/config/app.toml + +sed -i -e 's#"tcp://0.0.0.0:26656"#"tcp://0.0.0.0:'"$P2PPORT_2"'"#g' $CHAIN_DIR/$CHAINID_2/config/config.toml +sed -i -e 's#"tcp://127.0.0.1:26657"#"tcp://0.0.0.0:'"$RPCPORT_2"'"#g' $CHAIN_DIR/$CHAINID_2/config/config.toml +sed -i -e 's/timeout_commit = "5s"/timeout_commit = "1s"/g' $CHAIN_DIR/$CHAINID_2/config/config.toml +sed -i -e 's/timeout_propose = "3s"/timeout_propose = "1s"/g' $CHAIN_DIR/$CHAINID_2/config/config.toml +sed -i -e 's/index_all_keys = false/index_all_keys = true/g' $CHAIN_DIR/$CHAINID_2/config/config.toml +sed -i -e 's/enable = false/enable = true/g' $CHAIN_DIR/$CHAINID_2/config/app.toml +sed -i -e 's/swagger = false/swagger = true/g' $CHAIN_DIR/$CHAINID_2/config/app.toml +sed -i -e 's#"tcp://0.0.0.0:1317"#"tcp://0.0.0.0:'"$RESTPORT_2"'"#g' $CHAIN_DIR/$CHAINID_2/config/app.toml +sed -i -e 's#":8080"#":'"$ROSETTA_2"'"#g' $CHAIN_DIR/$CHAINID_2/config/app.toml + +echo "Chaning genesis.json..." +sed -i -e 's/"voting_period": "172800s"/"voting_period": "10s"/g' $CHAIN_DIR/$CHAINID_1/config/genesis.json +sed -i -e 's/"voting_period": "172800s"/"voting_period": "10s"/g' $CHAIN_DIR/$CHAINID_2/config/genesis.json +sed -i -e 's/"reward_delay_time": "604800s"/"reward_delay_time": "0s"/g' $CHAIN_DIR/$CHAINID_1/config/genesis.json +sed -i -e 's/"reward_delay_time": "604800s"/"reward_delay_time": "0s"/g' $CHAIN_DIR/$CHAINID_2/config/genesis.json + +echo "Starting $CHAINID_1 in $CHAIN_DIR..." +echo "Creating log file at $CHAIN_DIR/$CHAINID_1.log" +$BINARY start --log_level trace --log_format json --home $CHAIN_DIR/$CHAINID_1 --pruning=nothing --grpc.address="0.0.0.0:$GRPCPORT_1" --grpc-web.address="0.0.0.0:$GRPCWEB_1" > $CHAIN_DIR/$CHAINID_1.log 2>&1 & + +echo "Starting $CHAINID_2 in $CHAIN_DIR..." +echo "Creating log file at $CHAIN_DIR/$CHAINID_2.log" +$BINARY start --log_level trace --log_format json --home $CHAIN_DIR/$CHAINID_2 --pruning=nothing --grpc.address="0.0.0.0:$GRPCPORT_2" --grpc-web.address="0.0.0.0:$GRPCWEB_2" > $CHAIN_DIR/$CHAINID_2.log 2>&1 & diff --git a/scripts/tests/relayer/interchain-acc-config/chains/test-1.json b/scripts/tests/relayer/interchain-acc-config/chains/test-1.json new file mode 100644 index 00000000..6e763760 --- /dev/null +++ b/scripts/tests/relayer/interchain-acc-config/chains/test-1.json @@ -0,0 +1,21 @@ +{ + "type": "cosmos", + "value": { + "key": "testkey", + "chain-id": "test-1", + "rpc-addr": "http://localhost:16657", + "grpc-addr": "", + "account-prefix": "migaloo", + "keyring-backend": "test", + "default-denom": "uwhale", + "gas-adjustment": 1.5, + "gas-prices": "0.025uwhale", + "coin-type": 118, + "debug": true, + "timeout": "10s", + "output-format": "json", + "sign-mode": "direct", + "trusting-period": "330h", + "gas": 900000 + } +} \ No newline at end of file diff --git a/scripts/tests/relayer/interchain-acc-config/chains/test-2.json b/scripts/tests/relayer/interchain-acc-config/chains/test-2.json new file mode 100644 index 00000000..9098385a --- /dev/null +++ b/scripts/tests/relayer/interchain-acc-config/chains/test-2.json @@ -0,0 +1,21 @@ +{ + "type": "cosmos", + "value": { + "key": "testkey", + "chain-id": "test-2", + "rpc-addr": "http://localhost:26657", + "grpc-addr": "", + "account-prefix": "migaloo", + "keyring-backend": "test", + "default-denom": "uwhale", + "gas-adjustment": 1.5, + "gas-prices": "0.025uwhale", + "coin-type": 118, + "debug": true, + "timeout": "10s", + "output-format": "json", + "sign-mode": "direct", + "trusting-period": "330h", + "gas": 900000 + } +} \ No newline at end of file diff --git a/scripts/tests/relayer/interchain-acc-config/paths/test1-test2.json b/scripts/tests/relayer/interchain-acc-config/paths/test1-test2.json new file mode 100644 index 00000000..144ab176 --- /dev/null +++ b/scripts/tests/relayer/interchain-acc-config/paths/test1-test2.json @@ -0,0 +1,18 @@ +{ + "src": { + "chain-id": "test-1", + "client-id": "", + "connection-id": "", + "channel-id": "" + }, + "dst": { + "chain-id": "test-2", + "client-id": "", + "connection-id": "", + "channel-id": "" + }, + "src-channel-filter": { + "rule": "", + "channel-list": [] + } +} \ No newline at end of file diff --git a/scripts/tests/relayer/interchain-acc-config/rly-init.sh b/scripts/tests/relayer/interchain-acc-config/rly-init.sh new file mode 100755 index 00000000..9eba9c52 --- /dev/null +++ b/scripts/tests/relayer/interchain-acc-config/rly-init.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +echo "" +echo "##################" +echo "# Create relayer #" +echo "##################" +echo "" + +# Configure predefined mnemonic pharses +BINARY=rly +CHAIN_DIR=$(pwd)/data +CHAINID_1=test-1 +CHAINID_2=test-2 +RELAYER_DIR=/relayer +MNEMONIC_1="alley afraid soup fall idea toss can goose become valve initial strong forward bright dish figure check leopard decide warfare hub unusual join cart" +MNEMONIC_2="record gift you once hip style during joke field prize dust unique length more pencil transfer quit train device arrive energy sort steak upset" + +# Ensure rly is installed +if ! [ -x "$(command -v $BINARY)" ]; then + echo "$BINARY is required to run this script..." + echo "You can download at https://github.com/cosmos/relayer" + exit 1 +fi + +echo "Initializing $BINARY..." +$BINARY config init --home $CHAIN_DIR/$RELAYER_DIR + +echo "Adding configurations for both chains..." +$BINARY chains add-dir ./scripts/tests/relayer/interchain-acc-config/chains --home $CHAIN_DIR/$RELAYER_DIR +$BINARY paths add $CHAINID_1 $CHAINID_2 test1-test2 --file ./scripts/tests/relayer/interchain-acc-config/paths/test1-test2.json --home $CHAIN_DIR/$RELAYER_DIR + +echo "Restoring accounts..." +$BINARY keys restore $CHAINID_1 testkey "$MNEMONIC_1" --home $CHAIN_DIR/$RELAYER_DIR +$BINARY keys restore $CHAINID_2 testkey "$MNEMONIC_2" --home $CHAIN_DIR/$RELAYER_DIR + +echo "Creating clients and a connection..." +$BINARY tx connection test1-test2 --home $CHAIN_DIR/$RELAYER_DIR + +echo "Creating a channel..." +$BINARY tx channel test1-test2 --home $CHAIN_DIR/$RELAYER_DIR + +echo "Starting to listen relayer..." +$BINARY start test1-test2 -p events -b 100 --home $CHAIN_DIR/$RELAYER_DIR > $CHAIN_DIR/relayer.log 2>&1 & + +echo "" +echo "############################" +echo "# SUCCESS: Relayer created #" +echo "############################" +echo ""