Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: removed predefined action and added external contract callbacks #16

Merged
merged 50 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
104040e
feat(owner): generated new contract from template
srdtrk Oct 21, 2023
efb2029
deps(owner): imported local pin of 'cw-ica-controller'
srdtrk Oct 21, 2023
c840ef1
feat(owner): added initialization logic and state
srdtrk Oct 21, 2023
762729b
imp(testing/owner): added state
srdtrk Oct 22, 2023
2ba1bf1
imp(testing/owner): added 'CreateIcaContract' execute message
srdtrk Oct 22, 2023
8d9e176
merge: branch 'main' into feat/remove-predefined-action
srdtrk Oct 22, 2023
3622099
deps(testing/owner): local pin updated
srdtrk Oct 22, 2023
848439e
fix(testing/owner): code_id is a u64
srdtrk Oct 22, 2023
1b2bb76
feat(testing/owner): added initial implementation of instantiate2
srdtrk Oct 24, 2023
d50b931
imp(testing/owner): added basic state queries
srdtrk Oct 24, 2023
f32c6d4
merge: branch 'main' into feat/remove-predefined-action
srdtrk Oct 24, 2023
0e3880c
merge: branch 'main' into feat/remove-predefined-action
srdtrk Oct 25, 2023
3bc4136
deps: ran 'cargo clippy'
srdtrk Oct 25, 2023
7f7e880
deps: using library feature
srdtrk Oct 25, 2023
db57c2a
feat(e2e): refactor types, and create IcaContract type
srdtrk Oct 25, 2023
5cfbb36
imp: added types/owner_state.go
srdtrk Oct 26, 2023
b3a2862
imp(e2e): refactored queries to use generics
srdtrk Oct 26, 2023
9f85e27
style: renamed 'StoreAndInstantiateNewContract' to 'StoreAndInstantia…
srdtrk Oct 26, 2023
802bd23
imp: added basic test helpers
srdtrk Oct 28, 2023
8805f0d
merge: branch 'main' into feat/remove-predefined-action
srdtrk Oct 28, 2023
a44c161
imp: updated ica_owner
srdtrk Nov 1, 2023
dd2abf5
e2e: minor imp
srdtrk Nov 1, 2023
81e1e81
fix(testing/owner): fixed instantiate2
srdtrk Nov 4, 2023
ceb1ad3
imp: owner channel creation test passing now
srdtrk Nov 4, 2023
c0d0431
deps: added cosmos_sdk_proto to deps
srdtrk Nov 4, 2023
ba35d4c
imp: converted ContractError -> StdError for packet.rs
srdtrk Nov 4, 2023
77a854b
feat(testing/owner): implemented SendPredefined action
srdtrk Nov 4, 2023
48f858b
feat: added callback types
srdtrk Nov 5, 2023
97430ca
imp: added callback_address to state
srdtrk Nov 5, 2023
b7356ec
deps: bumped version to 0.2.0
srdtrk Nov 5, 2023
4d60ddf
test: added test migrate
srdtrk Nov 5, 2023
790f358
feat: implemented callback logic
srdtrk Nov 5, 2023
38e5934
imp: added UpdateAdmin and UpdateCallbackAddress
srdtrk Nov 5, 2023
5a7f8b1
deps(testing/owner): updated cw-ica-owner
srdtrk Nov 5, 2023
ade371e
imp: improved callbacks api
srdtrk Nov 5, 2023
442e7c5
imp(testing/owner): added callback handling to owner
srdtrk Nov 5, 2023
236dd6e
imp: improved callback api
srdtrk Nov 5, 2023
b8fa2ac
e2e: added a new e2e owner tests (not passing yet)
srdtrk Nov 5, 2023
f8f91df
imp: improved callback serialization
srdtrk Nov 6, 2023
f5dcf3f
imp(testing/owner): updated callback receiver api
srdtrk Nov 6, 2023
d716407
fix(testing/owner): used wrong address in predefined action
srdtrk Nov 6, 2023
26c9c37
imp: removed SendPredefinedAction
srdtrk Nov 6, 2023
49e799b
feat(e2e): owner predefined action test passing
srdtrk Nov 6, 2023
20cf305
feat(e2e): removed contract predefinedaction test
srdtrk Nov 6, 2023
29afbd3
docs: updated README
srdtrk Nov 7, 2023
ead79c2
style(e2e): renamed a test
srdtrk Nov 7, 2023
9704da9
ci: added owner e2e tests
srdtrk Nov 7, 2023
76a10fa
docs(e2e): readme updated
srdtrk Nov 7, 2023
345c478
ci: fixed workflow
srdtrk Nov 7, 2023
3be7b6a
style: using more idiomatic rust
srdtrk Nov 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,19 @@ jobs:
- TestIcaContractExecutionProto3JsonEncoding
- TestIcaContractExecutionProtobufEncoding
- TestIcaContractTimeoutPacket
- TestOwnerCreateIcaContract
- TestOwnerPredefinedAction
name: ${{ matrix.test }}
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Build Owner Contract with Docker
run: |
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="devcontract_cache_burner",target=/code/contracts/burner/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/rust-optimizer:0.14.0 ./testing/contracts/cw-ica-owner
- name: Install cargo-run-script
uses: actions-rs/cargo@v1
with:
Expand All @@ -52,4 +60,8 @@ jobs:
- name: TestContract
run: |
cd e2e/interchaintest
go test -v . -run TestWithContractTestSuite -testify.m ${{ matrix.test }}
if [[ ${{ matrix.test }} == TestOwner* ]]; then
go test -v . -run TestWithOwnerTestSuite -testify.m ${{ matrix.test }}
else
go test -v . -run TestWithContractTestSuite -testify.m ${{ matrix.test }}
fi
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cw-ica-controller"
version = "0.1.3"
version = "0.2.0"
authors = ["srdtrk <srdtrk@hotmail.com>"]
edition = "2021"
description = "This is a cosmwasm implementation of an interchain accounts controller."
Expand Down
30 changes: 21 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ To create an interchain account, the relayer must start the channel handshake on

### Execute an interchain account transaction

In this contract, the `execute` message is used to commit a packet to be sent to the host chain. This contract has two ways of executing an interchain transaction:
In this contract, the `ExecuteMsg::SendCustomIcaMessages` message is used to commit a packet to be sent to the host chain.

1. `SendCustomIcaMessages`: This message requires the sender to give base64 encoded messages that will be sent to the host chain. The host chain will decode the messages and execute them. The result of the execution is sent back to this contract, and a callback is executed based on the result.
`SendCustomIcaMessages`: This message requires the sender to give base64 encoded messages that will be sent to the host chain. The host chain will decode the messages and execute them. The result of the execution is sent back to this contract, and a callback is executed based on the result.

If the channel is using `proto3json` encoding, then the format that json messages have to take are defined by the cosmos-sdk's json codec. The following is an example of a json message that is submitting a text legacy governance: (In this example, the `proposer` is the address of the interchain account on the host chain)

Expand Down Expand Up @@ -145,11 +145,25 @@ func NewSendCustomIcaMessagesMsg(cdc codec.BinaryCodec, msgs []proto.Message, en
}
```

2. `SendPredefinedAction`: This message sends a 100 stake from the ica account to a user defined address on the host chain. This action is used to demonstrate how you can have a contract that executes a predefined action on the host chain. This is more useful for DAOs or other contracts that need to execute specific actions on the host chain. This message type checks which encoding the channel is using, and sends the appropriate message to the host chain. The host chain then executes the message, and sends the result back to this contract. A callback is then executed based on the result.

### Execute a callback

This contract supports callbacks. See [`src/ibc/relay.rs`](./src/ibc/relay.rs) to learn how to decode whether a transaction was successful or not. Currently, a counter is incremented to record how many transactions were successful and how many failed. This is just a placeholder for more complex logic that can be executed in the callback.
This contract supports external contract callbacks. See [`src/types/callbacks.rs`](./src/types/callbacks.rs) to learn what callbacks are supported.
This contract currently only supports sending callbacks to a single contract. You register the callback contract address during instantiation, or update it later using `ExecuteMsg::UpdateCallbackAddress`.

The callback contract must include the following variant in its `ExecuteMsg` enum:

```rust, ignore
use cosmwasm_schema::cw_serde;
use cw_ica_controller::types::callbacks::IcaControllerCallbackMsg;

#[cw_serde]
pub enum ExecuteMsg {
// ... other variants

/// The callback message from the ICA controller contract.
ReceiveIcaCallback(IcaControllerCallbackMsg),
}
```

### Channel Closing and Reopening

Expand All @@ -165,17 +179,15 @@ In general, the unit tests are for testing the verification functions for the ha

### End to end tests

The end to end tests are for testing the contract's functionality in an environment mimicking production. To see whether or not it can perform the channel handshake, send packets, and execute callbacks. We achieve this by running two local chains, one for the contract, and one for the host chain. The relayer is then used to perform the channel handshake, and send packets. The contract then executes callbacks based on the result of the packet. To learn more about how to run the end to end tests, see the [Readme](./e2e/Readme.md) in the `e2e` directory.
The end to end tests are for testing the contract's functionality in an environment mimicking production. To see whether or not it can perform the channel handshake, send packets, and execute callbacks. We achieve this by running two local chains, one for the contract, and one for the host chain. The relayer is then used to perform the channel handshake, and send packets. The contract then executes callbacks based on the result of the packet. To learn more about the end to end tests, see the [Readme](./e2e/Readme.md) in the `e2e` directory.

## Limitations

This contract is not meant to be used in production. It is meant to be used as a reference implementation for how to build a CosmWasm contract that can communicate with the golang ica/host module. The following are some of the limitations of this contract:

- The contract cannot create multiple interchain accounts. It can only create one.
- ICA channels must be ordered (enforced by golang ica/host module). Due to the semantics of ordered channels in IBC, any timeout will cause the channel to be closed.
- The relayer must start the channel handshake on the contract's chain. This is not possible to do in the contract itself. See e2e tests for an example of how to do this.
- The contract cannot initialize with an empty string as the version. This is due to a limitation of the IBCModule interface provided by ibc-go, see issue [#3942](https://github.com/cosmos/ibc-go/issues/3942). (The contract can be initialized with an empty version string if the chain supports stargate queries, but this is not the case for the end to end tests.)

## Acknowledgements

Much thanks to [Art3mix](https://github.com/Art3miX) for all the helpful discussions and nailing down of the encoding/decoding issues. Also thanks to [0xekez](https://github.com/0xekez) for their work on [cw-ibc-example](https://github.com/0xekez/cw-ibc-example) which was a great reference for CosmWasm IBC endpoints and interchaintest.
Much thanks to [Art3mix](https://github.com/Art3miX) and [CyberHoward](https://github.com/CyberHoward) for all the helpful discussions. Also thanks to [0xekez](https://github.com/0xekez) for their work on [cw-ibc-example](https://github.com/0xekez/cw-ibc-example) which was a great reference for CosmWasm IBC endpoints and interchaintest.
26 changes: 25 additions & 1 deletion e2e/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@

The e2e tests are built using the [interchaintest](https://github.com/strangelove-ventures/interchaintest) library by Strangelove. It runs multiple docker container validators, and lets you test IBC enabled smart contracts.

These end to end tests are designed to run in the ci, but you can also run them locally.

## Running the tests locally

The end to end tests are currently split into two parts:

### ICA Contract Tests

These tests are designed to test the ICA contract itself and its interaction with the relayer.

All contract tests are located in `interchaintest/contract_test.go` file. Currently, there are four tests in this file:

- `TestIcaContractChannelHandshake`
Expand All @@ -30,6 +38,22 @@ Before running the tests, you must have built the optimized contract in the `/ar
cargo run-script optimize
```

### Owner Contract Tests

These tests are designed to test the ICA contract's interaction with external contracts such as callbacks. For this, a mock owner contract is used.

All owner contract tests are located in `interchaintest/owner_test.go` file. Currently, there are two tests in this file:

- `TestOwnerCreateIcaContract`
- `TestOwnerPredefinedAction`

```text
cd interchaintest/
go test -v . -run TestWithOwnerTestSuite -testify.m $TEST_NAME
```

where `$TEST_NAME` is one of the two tests listed above.

## In the CI

The tests are run in the github CI after every push to the `main` branch. See the [github actions workflow](https://github.com/srdtrk/cw-ica-controller/blob/main/.github/workflows/e2e.yml) for more details.
Expand All @@ -38,4 +62,4 @@ For some unknown reason, the timeout test sometimes fails in the CI (I'd say abo

## About the tests

The tests are currently run on wasmd `v0.40.2` and ibc-go `v7.3.0`'s simd which implements json encoding feature for the interchain accounts module.
The tests are currently run on wasmd `v0.41.0` and ibc-go `v7.3.0`'s simd which implements json encoding feature for the interchain accounts module.
52 changes: 21 additions & 31 deletions e2e/interchaintest/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"

icatypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/types"
Expand All @@ -30,11 +31,11 @@ import (
type ContractTestSuite struct {
mysuite.TestSuite

Contract *types.Contract
Contract *types.IcaContract
IcaAddress string
}

// SetupContractAndChannel starts the chains, relayer, creates the user accounts, creates the ibc clients and connections,
// SetupContractTestSuite starts the chains, relayer, creates the user accounts, creates the ibc clients and connections,
// sets up the contract and does the channel handshake for the contract test suite.
func (s *ContractTestSuite) SetupContractTestSuite(ctx context.Context, encoding string) {
s.SetupSuite(ctx, chainSpecs)
Expand All @@ -48,7 +49,7 @@ func (s *ContractTestSuite) SetupContractTestSuite(ctx context.Context, encoding
contractAddr, err := s.ChainA.InstantiateContract(ctx, s.UserA.KeyName(), codeId, instantiateMsg, true, "--gas", "500000")
s.Require().NoError(err)

s.Contract = types.NewContract(contractAddr, codeId, s.ChainA)
s.Contract = types.NewIcaContract(types.NewContract(contractAddr, codeId, s.ChainA))

// Wait for the channel to get set up
err = testutil.WaitForBlocks(ctx, 5, s.ChainA, s.ChainB)
Expand All @@ -58,6 +59,7 @@ func (s *ContractTestSuite) SetupContractTestSuite(ctx context.Context, encoding
s.Require().NoError(err)

s.IcaAddress = contractState.IcaInfo.IcaAddress
s.Contract.SetIcaAddress(s.IcaAddress)
}

func TestWithContractTestSuite(t *testing.T) {
Expand Down Expand Up @@ -135,7 +137,7 @@ func (s *ContractTestSuite) TestIcaRelayerInstantiatedChannelHandshake() {

var err error
// Upload and Instantiate the contract on wasmd:
s.Contract, err = types.StoreAndInstantiateNewContract(ctx, wasmd, wasmdUser.KeyName(), "../../artifacts/cw_ica_controller.wasm")
s.Contract, err = types.StoreAndInstantiateNewIcaContract(ctx, wasmd, wasmdUser.KeyName(), "../../artifacts/cw_ica_controller.wasm")
s.Require().NoError(err)

version := fmt.Sprintf(`{"version":"%s","controller_connection_id":"%s","host_connection_id":"%s","address":"","encoding":"%s","tx_type":"%s"}`, icatypes.Version, s.ChainAConnID, s.ChainBConnID, icatypes.EncodingProtobuf, icatypes.TxTypeSDKMultiMsg)
Expand Down Expand Up @@ -235,7 +237,7 @@ func (s *ContractTestSuite) TestRecoveredIcaContractInstantiatedChannelHandshake
contractAddr, err := wasmd.InstantiateContract(ctx, wasmdUser.KeyName(), codeId, instantiateMsg, true, "--gas", "500000")
s.Require().NoError(err)

s.Contract = types.NewContract(contractAddr, codeId, wasmd)
s.Contract = types.NewIcaContract(types.NewContract(contractAddr, codeId, wasmd))
})

s.Run("TestChannelHandshakeSuccessAfterFail", func() {
Expand Down Expand Up @@ -312,31 +314,11 @@ func (s *ContractTestSuite) IcaContractExecutionTestWithEncoding(encoding string
// sets up the contract and does the channel handshake for the contract test suite.
s.SetupContractTestSuite(ctx, encoding)
wasmd, simd := s.ChainA, s.ChainB
wasmdUser, simdUser := s.UserA, s.UserB
wasmdUser := s.UserA

// Fund the ICA address:
s.FundAddressChainB(ctx, s.IcaAddress)

s.Run(fmt.Sprintf("TestSendPredefinedActionSuccess-%s", encoding), func() {
err := s.Contract.ExecPredefinedAction(ctx, wasmdUser.KeyName(), simdUser.FormattedAddress())
s.Require().NoError(err)

err = testutil.WaitForBlocks(ctx, 6, wasmd, simd)
s.Require().NoError(err)

icaBalance, err := simd.GetBalance(ctx, s.IcaAddress, simd.Config().Denom)
s.Require().NoError(err)
s.Require().Equal(sdkmath.NewInt(1000000000-100), icaBalance)

// Check if contract callbacks were executed:
callbackCounter, err := s.Contract.QueryCallbackCounter(ctx)
s.Require().NoError(err)

s.Require().Equal(uint64(1), callbackCounter.Success)
s.Require().Equal(uint64(0), callbackCounter.Error)
s.Require().Equal(uint64(0), callbackCounter.Timeout)
})

s.Run(fmt.Sprintf("TestSendCustomIcaMessagesSuccess-%s", encoding), func() {
// Send custom ICA messages through the contract:
// Let's create a governance proposal on simd and deposit some funds to it.
Expand Down Expand Up @@ -370,7 +352,7 @@ func (s *ContractTestSuite) IcaContractExecutionTestWithEncoding(encoding string
callbackCounter, err := s.Contract.QueryCallbackCounter(ctx)
s.Require().NoError(err)

s.Require().Equal(uint64(2), callbackCounter.Success)
s.Require().Equal(uint64(1), callbackCounter.Success)
s.Require().Equal(uint64(0), callbackCounter.Error)

// Check if the proposal was created:
Expand All @@ -397,7 +379,7 @@ func (s *ContractTestSuite) IcaContractExecutionTestWithEncoding(encoding string
// Check if contract callbacks were executed:
callbackCounter, err := s.Contract.QueryCallbackCounter(ctx)
s.Require().NoError(err)
s.Require().Equal(uint64(2), callbackCounter.Success)
s.Require().Equal(uint64(1), callbackCounter.Success)
s.Require().Equal(uint64(1), callbackCounter.Error)
s.Require().Equal(uint64(0), callbackCounter.Timeout)
})
Expand All @@ -410,7 +392,7 @@ func (s *ContractTestSuite) TestIcaContractTimeoutPacket() {
// sets up the contract and does the channel handshake for the contract test suite.
s.SetupContractTestSuite(ctx, icatypes.EncodingProto3JSON)
wasmd, simd := s.ChainA, s.ChainB
wasmdUser, simdUser := s.UserA, s.UserB
wasmdUser, _ := s.UserA, s.UserB

// Fund the ICA address:
s.FundAddressChainB(ctx, s.IcaAddress)
Expand Down Expand Up @@ -530,8 +512,16 @@ func (s *ContractTestSuite) TestIcaContractTimeoutPacket() {
s.Require().Equal(uint64(1), callbackCounter.Timeout)
})

s.Run("TestPredefinedActionAfterReopen", func() {
err := s.Contract.ExecPredefinedAction(ctx, wasmdUser.KeyName(), simdUser.FormattedAddress())
s.Run("TestSendCustomIcaMessagesAfterReopen", func() {
// Send custom ICA message through the contract:
sendMsg := &banktypes.MsgSend{
FromAddress: s.IcaAddress,
ToAddress: s.UserB.FormattedAddress(),
Amount: sdk.NewCoins(sdk.NewCoin(simd.Config().Denom, sdkmath.NewInt(100))),
}

// Execute the contract:
err = s.Contract.ExecCustomIcaMessages(ctx, wasmdUser.KeyName(), []proto.Message{sendMsg}, icatypes.EncodingProto3JSON, nil, nil)
s.Require().NoError(err)

err = testutil.WaitForBlocks(ctx, 10, wasmd, simd)
Expand Down
Loading
Loading