From e0e47485cbdcb770dfa4775ecb76d12d8c5734c2 Mon Sep 17 00:00:00 2001 From: kienn6034 Date: Tue, 2 Jan 2024 11:54:04 +0700 Subject: [PATCH 01/10] fix: typo integration test --- scripts/tests/tokenfactory/tokenfactory.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/tests/tokenfactory/tokenfactory.sh b/scripts/tests/tokenfactory/tokenfactory.sh index 9e2dc6b8..d9c0054a 100755 --- a/scripts/tests/tokenfactory/tokenfactory.sh +++ b/scripts/tests/tokenfactory/tokenfactory.sh @@ -21,7 +21,7 @@ WALLET_3=$($BINARY keys show wallet3 -a --keyring-backend test --home $CHAIN_DIR echo "Creating token denom $TOKEN_DENOM with $WALLET_1 on chain test-1" TX_HASH=$($BINARY tx tokenfactory create-denom $TOKEN_DENOM --from $WALLET_1 --home $CHAIN_DIR/test-1 --chain-id test-1 --node tcp://localhost:16657 --gas "$GAS" --fees "$FEES" --keyring-backend test -o json -y | jq -r '.txhash') sleep 3 -CREATED_RES_DENOM=$($BINARY query tx $TX_HASH -o josn --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 | jq -r '.logs[0].events[5].attributes[1].value') +CREATED_RES_DENOM=$($BINARY query tx $TX_HASH -o json --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 | jq -r '.logs[0].events[5].attributes[1].value') if [ "$CREATED_RES_DENOM" != "factory/$WALLET_1/$TOKEN_DENOM" ]; then echo "ERROR: Tokenfactory creating denom error. Expected result 'factory/$WALLET_1/$TOKEN_DENOM', got '$CREATED_RES_DENOM'" @@ -31,14 +31,14 @@ fi echo "Minting $MINT_AMOUNT units of $TOKEN_DENOM with $WALLET_1 on chain test-1" TX_HASH=$($BINARY tx tokenfactory mint $MINT_AMOUNT$CREATED_RES_DENOM --from $WALLET_1 --home $CHAIN_DIR/test-1 --chain-id test-1 --node tcp://localhost:16657 --gas "$GAS" --fees "$FEES" --keyring-backend test -o json -y | jq -r '.txhash') sleep 3 -MINT_RES=$($BINARY query tx $TX_HASH -o josn --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 | jq -r '.logs[0].events[2].type') +MINT_RES=$($BINARY query tx $TX_HASH -o json --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 | jq -r '.logs[0].events[2].type') if [ "$MINT_RES" != "coinbase" ]; then echo "ERROR: Tokenfactory minting error. Expected result 'coinbase', got '$CREATED_RES_DENOM'" exit 1 fi echo "Querying $TOKEN_DENOM from $WALLET_1 on chain test-1 to validate the amount minted" -BALANCE_RES_AMOUNT=$($BINARY query bank balances $WALLET_1 --denom $CREATED_RES_DENOM --chain-id test-2 --node tcp://localhost:16657 -o json | jq -r '.amount') +BALANCE_RES_AMOUNT=$($BINARY query bank balances $WALLET_1 --denom $CREATED_RES_DENOM --chain-id test-1 --node tcp://localhost:16657 -o json | jq -r '.amount') if [ "$BALANCE_RES_AMOUNT" != $MINT_AMOUNT ]; then echo "ERROR: Tokenfactory minting error. Expected minted balance '$MINT_AMOUNT', got '$BALANCE_RES_AMOUNT'" exit 1 @@ -47,14 +47,14 @@ fi echo "Burning 1 $TOKEN_DENOM from $WALLET_1 on chain test-1" TX_HASH=$($BINARY tx tokenfactory burn 1$CREATED_RES_DENOM --from $WALLET_1 --home $CHAIN_DIR/test-1 --chain-id test-1 --node tcp://localhost:16657 --gas "$GAS" --fees "$FEES" --keyring-backend test -o json -y | jq -r '.txhash') sleep 3 -BURN_RES=$($BINARY query tx $TX_HASH -o josn --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 | jq -r '.logs[0].events[7].type') +BURN_RES=$($BINARY query tx $TX_HASH -o json --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 | jq -r '.logs[0].events[7].type') if [ "$BURN_RES" != "tf_burn" ]; then echo "ERROR: Tokenfactory burning error. Expected result 'tf_burn', got '$BURN_RES'" exit 1 fi echo "Querying $TOKEN_DENOM from $WALLET_1 on chain test-1 to validate the burned amount" -BALANCES_AFTER_BURNING=$($BINARY query bank balances $WALLET_1 --denom $CREATED_RES_DENOM --chain-id test-2 --node tcp://localhost:16657 -o json | jq -r '.amount') +BALANCES_AFTER_BURNING=$($BINARY query bank balances $WALLET_1 --denom $CREATED_RES_DENOM --chain-id test-1 --node tcp://localhost:16657 -o json | jq -r '.amount') if [ "$BALANCES_AFTER_BURNING" != 999999 ]; then echo "ERROR: Tokenfactory minting error. Expected minted balance '999999', got '$BALANCES_AFTER_BURNING'" exit 1 @@ -63,14 +63,14 @@ fi echo "Sending 1 $TOKEN_DENOM from $WALLET_1 to $WALLET_3 on chain test-1" TX_HASH=$($BINARY tx bank send $WALLET_1 $WALLET_3 1$CREATED_RES_DENOM --from $WALLET_1 --home $CHAIN_DIR/test-1 --chain-id test-1 --node tcp://localhost:16657 --gas "$GAS" --fees "$FEES" --keyring-backend test -o json -y | jq -r '.txhash') sleep 3 -SEND_RES_MSG_TYPE=$($BINARY query tx $TX_HASH -o josn --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 | jq -r '.logs[0].events[0].attributes[0].value') +SEND_RES_MSG_TYPE=$($BINARY query tx $TX_HASH -o json --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 | jq -r '.logs[0].events[0].attributes[0].value') if [ "$SEND_RES_MSG_TYPE" != "/cosmos.bank.v1beta1.MsgSend" ]; then echo "ERROR: Sending expected to be '/cosmos.bank.v1beta1.MsgSend' but got '$SEND_RES_MSG_TYPE'" exit 1 fi echo "Querying $TOKEN_DENOM from $WALLET_3 on chain test-1 to validate the funds were received" -BALANCES_RECEIVED=$($BINARY query bank balances $WALLET_3 --denom $CREATED_RES_DENOM --chain-id test-2 --node tcp://localhost:16657 -o json | jq -r '.amount') +BALANCES_RECEIVED=$($BINARY query bank balances $WALLET_3 --denom $CREATED_RES_DENOM --chain-id test-1 --node tcp://localhost:16657 -o json | jq -r '.amount') if [ "$BALANCES_RECEIVED" != 1 ]; then echo "ERROR: Tokenfactory minting error. Expected minted balance '1', got '$BALANCES_RECEIVED'" exit 1 @@ -80,7 +80,7 @@ fi echo "IBC'ing 1 $TOKEN_DENOM from $WALLET_1 chain test-1 to $WALLET_2 chain test-2" TX_HASH=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_2 1$CREATED_RES_DENOM --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 --keyring-backend test --gas "$GAS" --fees "$FEES" --from $WALLET_1 -y -o json | jq -r '.txhash') sleep 3 -IBC_SEND_RES=$($BINARY query tx $TX_HASH -o josn --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 | jq -r '.logs[0].events[0].attributes[0].value') +IBC_SEND_RES=$($BINARY query tx $TX_HASH -o json --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 | jq -r '.logs[0].events[0].attributes[0].value') if [ "$IBC_SEND_RES" != "/ibc.applications.transfer.v1.MsgTransfer" ]; then echo "ERROR: IBC'ing expected type '/ibc.applications.transfer.v1.MsgTransfer' but got '$IBC_SEND_RES'" exit 1 From cd79bdc4186222bab5663d32eab20d376a0345d0 Mon Sep 17 00:00:00 2001 From: kienn6034 Date: Tue, 2 Jan 2024 15:27:25 +0700 Subject: [PATCH 02/10] test: add alliance test --- Makefile | 6 +- scripts/tests/alliance/delegate.sh | 108 +++++++++++++++++++++ scripts/tests/init-test-framework.sh | 3 +- scripts/tests/tokenfactory/tokenfactory.sh | 1 + 4 files changed, 116 insertions(+), 2 deletions(-) create mode 100755 scripts/tests/alliance/delegate.sh diff --git a/Makefile b/Makefile index 8016aef7..e5bf48d0 100644 --- a/Makefile +++ b/Makefile @@ -162,9 +162,13 @@ test-tokenfactory: @echo "Testing tokenfactory..." ./scripts/tests/tokenfactory/tokenfactory.sh +test-alliance: + @echo "Testing alliance..." + ./scripts/tests/alliance/delegate.sh + clean-testing-data: - @echo "Killing terrad and removing previous data" + @echo "Killing migallod and removing previous data" -@pkill $(BINARY) 2>/dev/null -@pkill rly 2>/dev/null -@pkill migalood_new 2>/dev/null diff --git a/scripts/tests/alliance/delegate.sh b/scripts/tests/alliance/delegate.sh new file mode 100755 index 00000000..bd145bf0 --- /dev/null +++ b/scripts/tests/alliance/delegate.sh @@ -0,0 +1,108 @@ +#!/bin/bash + +echo "" +echo "#################################################" +echo "# Alliance: bridge funds and create an alliance #" +echo "#################################################" +echo "" + +BINARY=migalood +CHAIN_DIR=$(pwd)/data + +AMOUNT_TO_DELEGATE=10000000000 +UWHALE_DENOM=uwhale +VAL_WALLET_1=$($BINARY keys show val1 -a --keyring-backend test --home $CHAIN_DIR/test-1) +VAL_WALLET_2=$($BINARY keys show val2 -a --keyring-backend test --home $CHAIN_DIR/test-2) + +echo "Sending tokens from validator wallet on test-1 to validator wallet on test-2" +IBC_TRANSFER=$($BINARY tx ibc-transfer transfer transfer channel-0 $VAL_WALLET_2 $AMOUNT_TO_DELEGATE$UWHALE_DENOM --chain-id test-1 --from $VAL_WALLET_1 --home $CHAIN_DIR/test-1 --fees 60000$UWHALE_DENOM --node tcp://localhost:16657 --keyring-backend test -y -o json | jq -r '.raw_log' ) + +if [[ "$IBC_TRANSFER" == "failed to execute message"* ]]; then + echo "Error: IBC transfer failed, with error: $IBC_TRANSFER" + exit 1 +fi + +ACCOUNT_BALANCE="" +IBC_DENOM="" +while [ "$ACCOUNT_BALANCE" == "" ]; do + IBC_DENOM=$($BINARY q bank balances $VAL_WALLET_2 --chain-id test-2 --node tcp://localhost:26657 -o json | jq -r '.balances[0].denom') + IBC_QUERY=$($BINARY q bank balances $VAL_WALLET_2 --chain-id test-2 --node tcp://localhost:26657 -o json) + + echo $IBC_QUERY + if [ "$IBC_DENOM" != "$UWHALE_DENOM" ]; then + ACCOUNT_BALANCE=$($BINARY q bank balances $VAL_WALLET_2 --chain-id test-2 --node tcp://localhost:26657 -o json | jq -r '.balances[0].amount') + fi + sleep 2 +done + +GOV_ADDRESS=$($BINARY query auth module-account gov --output json | jq .account.base_account.address -r) +echo '{ + "messages": [ + { + "@type": "/alliance.alliance.MsgCreateAlliance", + "authority" : "'"$GOV_ADDRESS"'", + "denom": "'"$IBC_DENOM"'", + "reward_weight": "0.3", + "take_rate": "0.01", + "reward_change_rate": "0.01", + "reward_change_interval": "10s", + "reward_weight_range": { + "min":"0.0001", + "max":"0.3" + } + } + ], + "metadata": "", + "deposit": "25000000000'$UWHALE_DENOM'", + "title": "Create an Alliance!", + "summary": "Source Code Version https://github.com/terra-money/core" +}' > $CHAIN_DIR/create-alliance.json + + +echo "Creating an alliance with the denom $IBC_DENOM" +PROPOSAL_HEIGHT=$($BINARY tx gov submit-proposal $CHAIN_DIR/create-alliance.json --from=$VAL_WALLET_2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 -o json --keyring-backend test --fees 60000$UWHALE_DENOM -y | jq -r '.height') +sleep 3 + + +PROPOSAL_ID=$($BINARY query gov proposals --home $CHAIN_DIR/test-2 --count-total --node tcp://localhost:26657 -o json --output json --chain-id=test-2 | jq .proposals[-1].id -r) + + +VOTE_RES=$($BINARY tx gov vote $PROPOSAL_ID yes --from=$VAL_WALLET_2 --home $CHAIN_DIR/test-2 --keyring-backend=test --fees 60000$UWHALE_DENOM --chain-id=test-2 --node tcp://localhost:26657 -o json -y) +echo "Vote res: $VOTE_RES" + +ALLIANCE="null" +while [ "$ALLIANCE" == "null" ]; do + echo "Waiting for alliance with denom $IBC_DENOM to be created" + ALLIANCE=$($BINARY q alliance alliances --chain-id test-2 --node tcp://localhost:26657 -o json | jq -r '.alliances[0]') + ALLIANCE_QUERY=$($BINARY q alliance alliances --chain-id test-2 --node tcp://localhost:26657 -o json) + echo $ALLIANCE_QUERY + + sleep 2 +done + +echo "Delegating $AMOUNT_TO_DELEGATE to the alliance $IBC_DENOM" +VAL_ADDR=$($BINARY query staking validators --output json | jq .validators[0].operator_address --raw-output) +DELEGATE_RES=$($BINARY tx alliance delegate $VAL_ADDR $AMOUNT_TO_DELEGATE$IBC_DENOM --from=node0 --from=$VAL_WALLET_2 --home $CHAIN_DIR/test-2 --keyring-backend=test --fees 60000$UWHALE_DENOM --chain-id=test-2 -o json -y) +sleep 3 +DELEGATIONS=$($BINARY query alliance delegation $VAL_WALLET_2 $VAL_ADDR $IBC_DENOM --chain-id test-2 --node tcp://localhost:26657 -o json | jq -r '.delegation.balance.amount') +if [[ "$DELEGATIONS" == "0" ]]; then + echo "Error: Alliance delegations expected to be greater than 0" + exit 1 +fi + +echo "Query bank balance after alliance creation" +TOTAL_SUPPLY_BEFORE_ALLIANCE=$($BINARY query bank total --denom $UWHALE_DENOM --height $PROPOSAL_HEIGHT -o json | jq -r '.amount') +TOTAL_SUPPLY_AFTER_ALLIANCE=$($BINARY query bank total --denom $UWHALE_DENOM -o json | jq -r '.amount') +TOTAL_SUPPLY_INCREMENT=$(($TOTAL_SUPPLY_BEFORE_ALLIANCE - $TOTAL_SUPPLY_AFTER_ALLIANCE)) + + +if [ "$TOTAL_SUPPLY_INCREMENT" -gt 100000 ] && [ "$TOTAL_SUPPLY_INCREMENT" -lt 1000000 ]; then + echo "Error: Something went wrong, total supply of $UWHALE_DENOM has increased out of range 100_000 between 1_000_000. current value $TOTAL_SUPPLY_INCREMENT" + exit 1 +fi + +echo "" +echo "#########################################################" +echo "# Success: Alliance bridge funds and create an alliance #" +echo "#########################################################" +echo "" \ No newline at end of file diff --git a/scripts/tests/init-test-framework.sh b/scripts/tests/init-test-framework.sh index f1261723..0b7078dd 100755 --- a/scripts/tests/init-test-framework.sh +++ b/scripts/tests/init-test-framework.sh @@ -137,8 +137,9 @@ update_test_genesis () { update_test_genesis ".app_state[\"staking\"][\"params\"][\"bond_denom\"]=\"$DENOM\"" update_test_genesis ".app_state[\"mint\"][\"params\"][\"mint_denom\"]=\"$DENOM\"" update_test_genesis ".app_state[\"crisis\"][\"constant_fee\"][\"denom\"]=\"$DENOM\"" -update_test_genesis ".app_state[\"gov\"][\"deposit_params\"][\"min_deposit\"][0][\"denom\"]=\"$DENOM\"" +update_test_genesis ".app_state[\"gov\"][\"params\"][\"min_deposit\"][0][\"denom\"]=\"$DENOM\"" update_test_genesis ".app_state[\"tokenfactory\"][\"params\"][\"denom_creation_fee\"][0][\"denom\"]=\"$DENOM\"" +update_test_genesis # Starting the chain diff --git a/scripts/tests/tokenfactory/tokenfactory.sh b/scripts/tests/tokenfactory/tokenfactory.sh index d9c0054a..2d30a3dc 100755 --- a/scripts/tests/tokenfactory/tokenfactory.sh +++ b/scripts/tests/tokenfactory/tokenfactory.sh @@ -92,6 +92,7 @@ while [ "$IBC_RECEIVED_RES_AMOUNT" != "1" ] || [ "${IBC_RECEIVED_RES_DENOM:0:4}" sleep 2 IBC_RECEIVED_RES_AMOUNT=$($BINARY query bank balances $WALLET_2 --chain-id test-2 --node tcp://localhost:26657 -o json | jq -r '.balances[0].amount') IBC_RECEIVED_RES_DENOM=$($BINARY query bank balances $WALLET_2 --chain-id test-2 --node tcp://localhost:26657 -o json | jq -r '.balances[0].denom') + echo "Received:" $IBC_RECEIVED_RES_AMOUNT $IBC_RECEIVED_RES_DENOM done From 8dcd2b80d338801372390e5622d0b585e1e040c4 Mon Sep 17 00:00:00 2001 From: kienn6034 Date: Fri, 5 Jan 2024 17:09:57 +0700 Subject: [PATCH 03/10] test: ibc hooks --- Makefile | 4 + app/app.go | 13 +- 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 | 88 ++++ scripts/tests/ica/delegate.sh | 62 +++ 16 files changed, 833 insertions(+), 6 deletions(-) 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 100644 scripts/tests/ica/delegate.sh diff --git a/Makefile b/Makefile index e5bf48d0..bac4afe5 100644 --- a/Makefile +++ b/Makefile @@ -167,6 +167,10 @@ test-alliance: ./scripts/tests/alliance/delegate.sh +test-ibc-hooks: + @echo "Testing ibc-hooks..." + ./scripts/tests/ibc-hooks/increment.sh + clean-testing-data: @echo "Killing migallod and removing previous data" -@pkill $(BINARY) 2>/dev/null diff --git a/app/app.go b/app/app.go index 168bb870..002cff8b 100644 --- a/app/app.go +++ b/app/app.go @@ -3,6 +3,12 @@ package app import ( "encoding/json" "fmt" + "io" + "net/http" + "os" + "path/filepath" + "sort" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" @@ -44,11 +50,6 @@ import ( solomachine "github.com/cosmos/ibc-go/v7/modules/light-clients/06-solomachine" ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" icq "github.com/strangelove-ventures/async-icq/v7" - "io" - "net/http" - "os" - "path/filepath" - "sort" distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" @@ -670,7 +671,7 @@ func NewMigalooApp( transferStack = router.NewIBCMiddleware( transferStack, &app.RouterKeeper, - 0, + 5, routerkeeper.DefaultForwardTransferPacketTimeoutTimestamp, routerkeeper.DefaultRefundTransferPacketTimeoutTimestamp, ) 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..e34503b7 --- /dev/null +++ b/scripts/tests/ibc-hooks/increment.sh @@ -0,0 +1,88 @@ +#!/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" +TX_HASH=$($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 -y --gas 10000000 --fees 6000000uwhale -o json | jq -r '.txhash') +sleep 3 +CODE_ID=$($BINARY query tx $TX_HASH -o josn --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 | 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) +TX_HASH=$($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 -y --gas 10000000 --fees 6000000uwhale -o json | jq -r '.txhash') +sleep 3 +CONTRACT_ADDRESS=$($BINARY query tx $TX_HASH -o josn --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 | jq -r '.logs[0].events[1].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 --fees 6000000uwhale -y -o json) +echo "IBC Hook response: $IBC_HOOK_RES" +sleep 3 +# ... then send another transfer to increments the count value from 0 to 1, send 1 more uluna 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 --fees 6000000uwhale --node tcp://localhost:16657 --keyring-backend test --from $WALLET_1 -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) + +IBC_RECEIVER_BALANCE=$($BINARY query bank balances $WALLET_1 --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 -o json) +echo "IBC Receiver balance: $IBC_RECEIVER_BALANCE" + +COUNT_RES="" +COUNT_FUNDS_RES="" +while [ "$COUNT_RES" != "1" ] || [ "$COUNT_FUNDS_RES" != "2" ]; do + sleep 3 + # Get count res + 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) + echo "Query response: $RES" + + # Query to assert that the counter value is 1 and the fund send are 2uluna (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 1uluna --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 -y -o json) +# sleep 3 +# IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1uluna --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 -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 1uluna --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 -y -o json) +# sleep 3 +# IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1uluna --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 -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/ica/delegate.sh b/scripts/tests/ica/delegate.sh new file mode 100644 index 00000000..25209230 --- /dev/null +++ b/scripts/tests/ica/delegate.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +echo "" +echo "###########################################" +echo "# ICA Cross Chain Delegation to Validator #" +echo "###########################################" +echo "" + +BINARY=terrad +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) + +echo "Registering ICA on chain test-1" +ICA_REGISTER_RESPONSE=$($BINARY tx interchain-accounts controller register connection-0 --from $WALLET_1 --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 --keyring-backend test -y --gas 10000000) + +ICS_TX_RESULT="Error:" +ICS_TX_ERROR="Error:" +while [[ "$ICS_TX_ERROR" == "$ICS_TX_RESULT"* ]]; do + echo "Waiting for the transaction to be relayed..." + sleep 5 + ICS_TX_RESULT=$($BINARY query interchain-accounts controller interchain-account $WALLET_1 connection-0 --node tcp://localhost:16657 -o json | jq -r '.address') +done + +echo "Sending tokens to ICA on chain test-2" +$BINARY tx bank send $WALLET_2 $ICS_TX_RESULT 10000000uluna --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 --keyring-backend test -y &> /dev/null +sleep 5 +ICS_ACCOUNT_BALANCE=$($BINARY query bank balances $ICS_TX_RESULT --chain-id test-2 --node tcp://localhost:26657 -o json | jq -r '.balances[0].amount') + +if [[ "$ICS_ACCOUNT_BALANCE" != "10000000" ]]; then + echo "Error: ICA Have not received tokens" + exit 1 +fi + +echo "Executing Delegation from test-1 to test-2 via ICA" +VAL_ADDR_1=$(cat $CHAIN_DIR/test-2/config/genesis.json | jq -r '.app_state.genutil.gen_txs[0].body.messages[0].validator_address') + +GENERATED_PACKET=$($BINARY tx interchain-accounts host generate-packet-data '{ + "@type":"/cosmos.staking.v1beta1.MsgDelegate", + "delegator_address": "'"$ICS_TX_RESULT"'", + "validator_address": "'"$VAL_ADDR_1"'", + "amount": { + "denom": "uluna", + "amount": "'"$ICS_ACCOUNT_BALANCE"'" + } +}') + +$BINARY tx interchain-accounts controller send-tx connection-0 $GENERATED_PACKET --from $WALLET_1 --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 --keyring-backend test -y &> /dev/null + +VALIDATOR_DELEGATIONS="" +while [[ "$VALIDATOR_DELEGATIONS" != "$ICS_ACCOUNT_BALANCE" ]]; do + sleep 2 + echo "Waiting for the transaction '/cosmos.bank.v1beta1.MsgDelegate' to be relayed..." + VALIDATOR_DELEGATIONS=$($BINARY query staking delegations-to $VAL_ADDR_1 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 -o json | jq -r '.delegation_responses[-1].balance.amount') +done + +echo "" +echo "####################################################" +echo "# SUCCESS: ICA Cross Chain Delegation to Validator #" +echo "####################################################" +echo "" From dd4259c54f7d73e56cd917fea1c3b981a251ea8c Mon Sep 17 00:00:00 2001 From: kienn6034 Date: Mon, 8 Jan 2024 16:04:21 +0700 Subject: [PATCH 04/10] feat: fix ibc hooks --- app/app.go | 8 ++++---- scripts/tests/ibc-hooks/increment.sh | 13 +++++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/app/app.go b/app/app.go index 002cff8b..e1ddf046 100644 --- a/app/app.go +++ b/app/app.go @@ -297,7 +297,7 @@ type MigalooApp struct { // IBC hooks IBCHooksKeeper *ibchookskeeper.Keeper - TransferStack *ibchooks.IBCMiddleware + TransferStack *ibcporttypes.IBCModule ScopedIBCKeeper capabilitykeeper.ScopedKeeper ScopedICAHostKeeper capabilitykeeper.ScopedKeeper @@ -668,6 +668,7 @@ func NewMigalooApp( var transferStack ibcporttypes.IBCModule transferStack = transfer.NewIBCModule(app.TransferKeeper) transferStack = ibcfee.NewIBCMiddleware(transferStack, app.IBCFeeKeeper) + transferStack = ibchooks.NewIBCMiddleware(transferStack, &app.HooksICS4Wrapper) transferStack = router.NewIBCMiddleware( transferStack, &app.RouterKeeper, @@ -676,8 +677,7 @@ func NewMigalooApp( routerkeeper.DefaultRefundTransferPacketTimeoutTimestamp, ) // Hooks Middleware - hooksTransferStack := ibchooks.NewIBCMiddleware(transferStack, &app.HooksICS4Wrapper) - app.TransferStack = &hooksTransferStack + app.TransferStack = &transferStack // Create Interchain Accounts Stack // SendPacket, since it is originating from the application to core IBC: @@ -707,7 +707,7 @@ func NewMigalooApp( // Create static IBC router, add app routes, then set and seal it ibcRouter := ibcporttypes.NewRouter(). - AddRoute(ibctransfertypes.ModuleName, transferStack). + AddRoute(ibctransfertypes.ModuleName, *app.TransferStack). AddRoute(wasmtypes.ModuleName, wasmStack). AddRoute(icahosttypes.SubModuleName, icaHostStack). AddRoute(icqtypes.ModuleName, icqStack) diff --git a/scripts/tests/ibc-hooks/increment.sh b/scripts/tests/ibc-hooks/increment.sh index e34503b7..1b45814c 100755 --- a/scripts/tests/ibc-hooks/increment.sh +++ b/scripts/tests/ibc-hooks/increment.sh @@ -6,10 +6,10 @@ 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) +export BINARY=migalood +export CHAIN_DIR=$(pwd)/data +export WALLET_1=$($BINARY keys show wallet1 -a --keyring-backend test --home $CHAIN_DIR/test-1) +export 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" @@ -22,8 +22,11 @@ CODE_ID=$($BINARY query tx $TX_HASH -o josn --chain-id test-2 --home $CHAIN_DIR/ echo "Instantiating counter contract" RANDOM_HASH=$(hexdump -vn16 -e'4/4 "%08X" 1 "\n"' /dev/urandom) TX_HASH=$($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 -y --gas 10000000 --fees 6000000uwhale -o json | jq -r '.txhash') + +echo "TX hash: $TX_HASH" sleep 3 CONTRACT_ADDRESS=$($BINARY query tx $TX_HASH -o josn --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 | jq -r '.logs[0].events[1].attributes[0].value') +echo "Contract address: $CONTRACT_ADDRESS" 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 ... @@ -37,6 +40,8 @@ export WALLET_1_WASM_SENDER=$($BINARY q ibchooks wasm-sender channel-0 "$WALLET_ IBC_RECEIVER_BALANCE=$($BINARY query bank balances $WALLET_1 --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 -o json) echo "IBC Receiver balance: $IBC_RECEIVER_BALANCE" +echo "wallet 1 wasm sender: $WALLET_1_WASM_SENDER" + COUNT_RES="" COUNT_FUNDS_RES="" while [ "$COUNT_RES" != "1" ] || [ "$COUNT_FUNDS_RES" != "2" ]; do From 9d35a9ead7c17d97fba002ed27db6cab1f5cd1b6 Mon Sep 17 00:00:00 2001 From: kienn6034 Date: Mon, 8 Jan 2024 23:45:19 +0700 Subject: [PATCH 05/10] chore: fix update test genesis --- scripts/tests/ibc-hooks/increment.sh | 43 ++++++++++++++-------------- scripts/tests/init-test-framework.sh | 4 +-- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/scripts/tests/ibc-hooks/increment.sh b/scripts/tests/ibc-hooks/increment.sh index 1b45814c..9c39fede 100755 --- a/scripts/tests/ibc-hooks/increment.sh +++ b/scripts/tests/ibc-hooks/increment.sh @@ -6,14 +6,15 @@ echo "# IBC Hook call #" echo "#################" echo "" -export BINARY=migalood -export CHAIN_DIR=$(pwd)/data -export WALLET_1=$($BINARY keys show wallet1 -a --keyring-backend test --home $CHAIN_DIR/test-1) -export WALLET_2=$($BINARY keys show wallet2 -a --keyring-backend test --home $CHAIN_DIR/test-2) +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) +DENOM=uwhale # 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" -TX_HASH=$($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 -y --gas 10000000 --fees 6000000uwhale -o json | jq -r '.txhash') +TX_HASH=$($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 -y --gas 10000000 --fees 6000000$DENOM -o json | jq -r '.txhash') sleep 3 CODE_ID=$($BINARY query tx $TX_HASH -o josn --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 | jq -r '.logs[0].events[1].attributes[1].value') @@ -21,7 +22,7 @@ CODE_ID=$($BINARY query tx $TX_HASH -o josn --chain-id test-2 --home $CHAIN_DIR/ # 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) -TX_HASH=$($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 -y --gas 10000000 --fees 6000000uwhale -o json | jq -r '.txhash') +TX_HASH=$($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 -y --gas 10000000 --fees 6000000$DENOM -o json | jq -r '.txhash') echo "TX hash: $TX_HASH" sleep 3 @@ -30,11 +31,11 @@ echo "Contract address: $CONTRACT_ADDRESS" 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 --fees 6000000uwhale -y -o json) +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 --fees 6000000$DENOM -y -o json) echo "IBC Hook response: $IBC_HOOK_RES" sleep 3 # ... then send another transfer to increments the count value from 0 to 1, send 1 more uluna 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 --fees 6000000uwhale --node tcp://localhost:16657 --keyring-backend test --from $WALLET_1 -y -o json) +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 --fees 6000000$DENOM --node tcp://localhost:16657 --keyring-backend test --from $WALLET_1 -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) IBC_RECEIVER_BALANCE=$($BINARY query bank balances $WALLET_1 --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 -o json) @@ -56,20 +57,20 @@ while [ "$COUNT_RES" != "1" ] || [ "$COUNT_FUNDS_RES" != "2" ]; do 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 1uluna --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 -y -o json) -# sleep 3 -# IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1uluna --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 -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) +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 1uluna --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --fees 6000000$DENOM --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 -y -o json) +sleep 3 +IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1uluna --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 6000000$DENOM --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 -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 +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. diff --git a/scripts/tests/init-test-framework.sh b/scripts/tests/init-test-framework.sh index 0b7078dd..9a153e2c 100755 --- a/scripts/tests/init-test-framework.sh +++ b/scripts/tests/init-test-framework.sh @@ -121,7 +121,7 @@ sed -i -e 's#":8080"#":'"$ROSETTA_2"'"#g' $CHAIN_DIR/$CHAINID_2/config/app.toml sed -i -e 's/enabled-unsafe-cors = false/enabled-unsafe-cors = true/g' $CHAIN_DIR/$CHAINID_2/config/app.toml -echo "Chaning genesis.json..." +echo "Changing 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 @@ -134,12 +134,12 @@ update_test_genesis () { jq "$1" $GENESIS_2 > $TMP_GENESIS_2 && mv $TMP_GENESIS_2 $GENESIS_2 } +echo "update test genesis" update_test_genesis ".app_state[\"staking\"][\"params\"][\"bond_denom\"]=\"$DENOM\"" update_test_genesis ".app_state[\"mint\"][\"params\"][\"mint_denom\"]=\"$DENOM\"" update_test_genesis ".app_state[\"crisis\"][\"constant_fee\"][\"denom\"]=\"$DENOM\"" update_test_genesis ".app_state[\"gov\"][\"params\"][\"min_deposit\"][0][\"denom\"]=\"$DENOM\"" update_test_genesis ".app_state[\"tokenfactory\"][\"params\"][\"denom_creation_fee\"][0][\"denom\"]=\"$DENOM\"" -update_test_genesis # Starting the chain From 008f2cd5e04adb49b91b382b6d61cbb051474e66 Mon Sep 17 00:00:00 2001 From: kienn6034 Date: Tue, 9 Jan 2024 01:56:44 +0700 Subject: [PATCH 06/10] fix: ibc callback --- app/app.go | 2 +- scripts/tests/ibc-hooks/increment.sh | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/app.go b/app/app.go index e1ddf046..b1f7db94 100644 --- a/app/app.go +++ b/app/app.go @@ -579,7 +579,7 @@ func NewMigalooApp( appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), - app.IBCFeeKeeper, // ISC4 Wrapper: fee IBC middleware + app.HooksICS4Wrapper, // ISC4 Wrapper: fee IBC middleware app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, app.AccountKeeper, diff --git a/scripts/tests/ibc-hooks/increment.sh b/scripts/tests/ibc-hooks/increment.sh index 9c39fede..930d39f9 100755 --- a/scripts/tests/ibc-hooks/increment.sh +++ b/scripts/tests/ibc-hooks/increment.sh @@ -59,9 +59,11 @@ 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 1uluna --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --fees 6000000$DENOM --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 -y -o json) +IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1$DENOM --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --fees 6000000$DENOM --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 -y -o json) + + sleep 3 -IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1uluna --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 6000000$DENOM --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 -y -o json) +IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1$DENOM --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --fees 6000000$DENOM --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 -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="" @@ -74,9 +76,9 @@ 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 1uluna --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 -y -o json) +# IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1$DENOM --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 -y -o json) # sleep 3 -# IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1uluna --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 -y -o json) +# IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1$DENOM --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 -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="" From d148bc85b22d0efa816306383ea3af136c70e7da Mon Sep 17 00:00:00 2001 From: kienn6034 Date: Tue, 9 Jan 2024 10:07:43 +0700 Subject: [PATCH 07/10] test: ibc callback with timeout --- app/app.go | 6 ++-- scripts/tests/ibc-hooks/increment.sh | 44 ++++++++++++++-------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/app/app.go b/app/app.go index b1f7db94..b1f698da 100644 --- a/app/app.go +++ b/app/app.go @@ -322,7 +322,7 @@ type MigalooApp struct { } func (app *MigalooApp) Close() error { - //TODO implement me + // TODO implement me panic("implement me") } @@ -569,7 +569,7 @@ func NewMigalooApp( // IBC Fee Module keeper app.IBCFeeKeeper = ibcfeekeeper.NewKeeper( appCodec, keys[ibcfeetypes.StoreKey], - app.IBCKeeper.ChannelKeeper, // may be replaced with IBC middleware + app.HooksICS4Wrapper, // may be replaced with IBC middleware app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, ) @@ -579,7 +579,7 @@ func NewMigalooApp( appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), - app.HooksICS4Wrapper, // ISC4 Wrapper: fee IBC middleware + app.IBCFeeKeeper, // ISC4 Wrapper: fee IBC middleware app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, app.AccountKeeper, diff --git a/scripts/tests/ibc-hooks/increment.sh b/scripts/tests/ibc-hooks/increment.sh index 930d39f9..4ae1cad9 100755 --- a/scripts/tests/ibc-hooks/increment.sh +++ b/scripts/tests/ibc-hooks/increment.sh @@ -34,7 +34,7 @@ echo "Executing the IBC Hook to increment the counter" 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 --fees 6000000$DENOM -y -o json) echo "IBC Hook response: $IBC_HOOK_RES" sleep 3 -# ... then send another transfer to increments the count value from 0 to 1, send 1 more uluna to the contract address to validate that it increased the value correctly. +# ... 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 --fees 6000000$DENOM --node tcp://localhost:16657 --keyring-backend test --from $WALLET_1 -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) @@ -51,7 +51,7 @@ while [ "$COUNT_RES" != "1" ] || [ "$COUNT_FUNDS_RES" != "2" ]; do 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) echo "Query response: $RES" - # Query to assert that the counter value is 1 and the fund send are 2uluna (remeber that the first time fund are send to the contract the counter is set to 0 instead of 1) + # 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" @@ -74,23 +74,23 @@ while [ "$COUNT_RES" != "2" ]; do 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 1$DENOM --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 -y -o json) -# sleep 3 -# IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1$DENOM --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 -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 "" +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 1$DENOM --packet-timeout-height="0-1" --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --fees 6000000$DENOM --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 -y -o json) +sleep 3 +IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1$DENOM --packet-timeout-height="0-1" --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --fees 6000000$DENOM --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 -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 "" From 584d3fa0a3dbe221f5de51a995f79889f0985da6 Mon Sep 17 00:00:00 2001 From: kienn6034 Date: Tue, 9 Jan 2024 14:43:56 +0700 Subject: [PATCH 08/10] fix: register ica controller --- Makefile | 3 +++ app/app.go | 1 + scripts/tests/ica/delegate.sh | 20 +++++++++++--------- 3 files changed, 15 insertions(+), 9 deletions(-) mode change 100644 => 100755 scripts/tests/ica/delegate.sh diff --git a/Makefile b/Makefile index bac4afe5..bdbb65c4 100644 --- a/Makefile +++ b/Makefile @@ -166,6 +166,9 @@ test-alliance: @echo "Testing alliance..." ./scripts/tests/alliance/delegate.sh +test-ica: + @echo "Testing ica..." + ./scripts/tests/ica/delegate.sh test-ibc-hooks: @echo "Testing ibc-hooks..." diff --git a/app/app.go b/app/app.go index b1f698da..eaf8cffb 100644 --- a/app/app.go +++ b/app/app.go @@ -707,6 +707,7 @@ func NewMigalooApp( // Create static IBC router, add app routes, then set and seal it ibcRouter := ibcporttypes.NewRouter(). + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). AddRoute(ibctransfertypes.ModuleName, *app.TransferStack). AddRoute(wasmtypes.ModuleName, wasmStack). AddRoute(icahosttypes.SubModuleName, icaHostStack). diff --git a/scripts/tests/ica/delegate.sh b/scripts/tests/ica/delegate.sh old mode 100644 new mode 100755 index 25209230..0879b2e0 --- a/scripts/tests/ica/delegate.sh +++ b/scripts/tests/ica/delegate.sh @@ -6,14 +6,16 @@ echo "# ICA Cross Chain Delegation to Validator #" echo "###########################################" echo "" -BINARY=terrad -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) +export BINARY=migalood +export CHAIN_DIR=$(pwd)/data +export DENOM=uwhale + +export WALLET_1=$($BINARY keys show wallet1 -a --keyring-backend test --home $CHAIN_DIR/test-1) +export WALLET_2=$($BINARY keys show wallet2 -a --keyring-backend test --home $CHAIN_DIR/test-2) echo "Registering ICA on chain test-1" -ICA_REGISTER_RESPONSE=$($BINARY tx interchain-accounts controller register connection-0 --from $WALLET_1 --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 --keyring-backend test -y --gas 10000000) +ICA_REGISTER_RESPONSE=$($BINARY tx interchain-accounts controller register connection-0 --from $WALLET_1 --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 --keyring-backend test --fees 3000000$DENOM -y --gas 10000000) + ICS_TX_RESULT="Error:" ICS_TX_ERROR="Error:" @@ -24,7 +26,7 @@ while [[ "$ICS_TX_ERROR" == "$ICS_TX_RESULT"* ]]; do done echo "Sending tokens to ICA on chain test-2" -$BINARY tx bank send $WALLET_2 $ICS_TX_RESULT 10000000uluna --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 --keyring-backend test -y &> /dev/null +$BINARY tx bank send $WALLET_2 $ICS_TX_RESULT 10000000$DENOM --chain-id test-2 --home $CHAIN_DIR/test-2 --fees 3000000$DENOM --node tcp://localhost:26657 --keyring-backend test -y &> /dev/null sleep 5 ICS_ACCOUNT_BALANCE=$($BINARY query bank balances $ICS_TX_RESULT --chain-id test-2 --node tcp://localhost:26657 -o json | jq -r '.balances[0].amount') @@ -41,12 +43,12 @@ GENERATED_PACKET=$($BINARY tx interchain-accounts host generate-packet-data '{ "delegator_address": "'"$ICS_TX_RESULT"'", "validator_address": "'"$VAL_ADDR_1"'", "amount": { - "denom": "uluna", + "denom": "$DENOM", "amount": "'"$ICS_ACCOUNT_BALANCE"'" } }') -$BINARY tx interchain-accounts controller send-tx connection-0 $GENERATED_PACKET --from $WALLET_1 --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 --keyring-backend test -y &> /dev/null +$BINARY tx interchain-accounts controller send-tx connection-0 $GENERATED_PACKET --from $WALLET_1 --chain-id test-1 --home $CHAIN_DIR/test-1 --fees 3000000$DENOM --node tcp://localhost:16657 --keyring-backend test -y &> /dev/null VALIDATOR_DELEGATIONS="" while [[ "$VALIDATOR_DELEGATIONS" != "$ICS_ACCOUNT_BALANCE" ]]; do From fab40e46bfacc9328428d638a2f94795ab1da30a Mon Sep 17 00:00:00 2001 From: kienn6034 Date: Tue, 9 Jan 2024 15:58:13 +0700 Subject: [PATCH 09/10] test: ica integration test --- app/app.go | 2 +- scripts/tests/ica/delegate.sh | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/app.go b/app/app.go index eaf8cffb..132aa6a7 100644 --- a/app/app.go +++ b/app/app.go @@ -708,9 +708,9 @@ func NewMigalooApp( // Create static IBC router, add app routes, then set and seal it ibcRouter := ibcporttypes.NewRouter(). AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostStack). AddRoute(ibctransfertypes.ModuleName, *app.TransferStack). AddRoute(wasmtypes.ModuleName, wasmStack). - AddRoute(icahosttypes.SubModuleName, icaHostStack). AddRoute(icqtypes.ModuleName, icqStack) app.IBCKeeper.SetRouter(ibcRouter) diff --git a/scripts/tests/ica/delegate.sh b/scripts/tests/ica/delegate.sh index 0879b2e0..77230be9 100755 --- a/scripts/tests/ica/delegate.sh +++ b/scripts/tests/ica/delegate.sh @@ -43,18 +43,23 @@ GENERATED_PACKET=$($BINARY tx interchain-accounts host generate-packet-data '{ "delegator_address": "'"$ICS_TX_RESULT"'", "validator_address": "'"$VAL_ADDR_1"'", "amount": { - "denom": "$DENOM", + "denom": "'"$DENOM"'", "amount": "'"$ICS_ACCOUNT_BALANCE"'" } }') -$BINARY tx interchain-accounts controller send-tx connection-0 $GENERATED_PACKET --from $WALLET_1 --chain-id test-1 --home $CHAIN_DIR/test-1 --fees 3000000$DENOM --node tcp://localhost:16657 --keyring-backend test -y &> /dev/null -VALIDATOR_DELEGATIONS="" +SEND_TX_RESULT=$($BINARY tx interchain-accounts controller send-tx connection-0 $GENERATED_PACKET --from $WALLET_1 --chain-id test-1 --home $CHAIN_DIR/test-1 --fees 3000000$DENOM --node tcp://localhost:16657 --keyring-backend test -y) + +PRE_VALIDATOR_DELEGATIONS="" while [[ "$VALIDATOR_DELEGATIONS" != "$ICS_ACCOUNT_BALANCE" ]]; do sleep 2 echo "Waiting for the transaction '/cosmos.bank.v1beta1.MsgDelegate' to be relayed..." VALIDATOR_DELEGATIONS=$($BINARY query staking delegations-to $VAL_ADDR_1 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 -o json | jq -r '.delegation_responses[-1].balance.amount') + + # log the validator delegations, and ics account balance + echo "Validator Delegations: $VALIDATOR_DELEGATIONS" + echo "ICS Account Balance: $ICS_ACCOUNT_BALANCE" done echo "" From 3666b1516456c5262a4cc5f68367d7fd01c84c9f Mon Sep 17 00:00:00 2001 From: kienn6034 Date: Tue, 9 Jan 2024 16:03:20 +0700 Subject: [PATCH 10/10] chore: update make file --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index bdbb65c4..bcf6f841 100644 --- a/Makefile +++ b/Makefile @@ -151,8 +151,12 @@ ictest-all: ictest-start-cosmos ictest-ibc ### Integration Tests ### ############################################################################### -#./scripts/tests/relayer/interchain-acc-config/rly-init.sh - +integration-test-all: init-test-framework \ + test-ica \ + test-ibc-hooks \ + test-alliance \ + test-tokenfactory + init-test-framework: clean-testing-data install @echo "Initializing both blockchains..." ./scripts/tests/init-test-framework.sh