From ce82615051629858a903be756450d0f2b490adf8 Mon Sep 17 00:00:00 2001 From: Reece Williams Date: Wed, 9 Oct 2024 10:56:22 -0500 Subject: [PATCH 1/5] wip: guide --- .../01-setup/01-system-setup.md | 19 + .../09-ibc-cosmwasm.md | 635 ++++++++++++++++++ .../03-demos/04-cw-validator-reviews.md | 19 +- 3 files changed, 655 insertions(+), 18 deletions(-) create mode 100644 docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md diff --git a/docs/versioned_docs/version-v0.50.x/01-setup/01-system-setup.md b/docs/versioned_docs/version-v0.50.x/01-setup/01-system-setup.md index c8523896..9d74e834 100644 --- a/docs/versioned_docs/version-v0.50.x/01-setup/01-system-setup.md +++ b/docs/versioned_docs/version-v0.50.x/01-setup/01-system-setup.md @@ -120,3 +120,22 @@ sudo apt -y install docker.io git config --global user.email "yourEmail@gmail.com" git config --global user.name "Your Name" ``` + +## CosmWasm + +Some tutorials require CosmWasm (Rust smart contracts) setup. This section is option, unless a tutorial is CosmWasm focused. +CosmWasm requires [Rust](https://www.rust-lang.org/). You must have this installed as the contract will be built locally. + +```bash +# Install rust - https://www.rust-lang.org/tools/install +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# or Update if you have it +rustup update + +# Install other dependencies +rustup target add wasm32-unknown-unknown + +cargo install cargo-generate --features vendored-openssl +cargo install cargo-run-script +``` diff --git a/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md b/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md new file mode 100644 index 00000000..43f268b1 --- /dev/null +++ b/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md @@ -0,0 +1,635 @@ +--- +title: "IBC NameService Contract" +sidebar_label: "IBC Contract (Part 3)" +sidebar_position: 8 +slug: /build/name-service-ibc-contract +--- + +# IBC Name Service Contract + +You will build a new IBC contract with [CosmWasm](https://cosmwasm.com), enabling the same features we just built out in the IBC module. + +## Prerequisites +- [System Setup](../01-setup/01-system-setup.md) +- [Install Spawn](../01-setup/02-install-spawn.md) +- [Rust + CosmWasm](../01-setup/01-system-setup.md#cosmwasm) + +## Setup the Chain + +Build a new blockchain with CosmWasm enabled. THen generate a new module from the template for the reviews. + +```bash +GITHUB_USERNAME=rollchains + +spawn new cwchain \ +--consensus=proof-of-stake \ +--bech32=roll \ +--denom=uroll \ +--bin=rolld \ +--disabled=block-explorer \ +--org=${GITHUB_USERNAME} + + +# move into the chain directory +cd cwchain + +go mod tidy +``` + +Once the chain is started, continue on to the contract building steps + +## CosmWasm Build Contract + +CosmWasm has a template repository that is used to generate new contracts. A minimal contract will be built with the `validator-reviews-contract` name provided on a specific commit. + +```bash +cargo generate --git https://github.com/CosmWasm/cw-template.git \ + --name nameservice-contract \ + -d minimal=true --tag a2a169164324aa1b48ab76dd630f75f504e41d99 +``` + +```bash +code nameservice-contract/ +``` + + + + +`make sh-testnet` + +### Upload Contract to both networks + +Make sure you are in the `cwchain` directory to begin interacting and uploading the contract to the chain. + +```bash +make local-image + +local-ic start self-ibc +``` + + +```bash +RPC_1=http://localhost:26657 +RPC_2=http://localhost:41427 # todo: query from the http://127.0.0.1:8080/info with jq parse jq .logs.chains[1].rpc_address + +CONTRACT_SOURCE=./nameservice-contract/artifacts/nameservice_contract.wasm +rolld tx wasm store $CONTRACT_SOURCE --from=acc0 --gas=auto --gas-adjustment=2.0 --yes --node=$RPC_1 +# rolld q wasm list-code --node=$RPC_1 + +rolld tx wasm store $CONTRACT_SOURCE --from=acc0 --gas=auto --gas-adjustment=2.0 --yes --node=$RPC_2 --chain-id=localchain-2 +# rolld q wasm list-code --node=$RPC_2 +``` + +### Instantiate our Contract on oth chains + +```bash +rolld tx wasm instantiate 1 '{}' --no-admin --from=acc0 --label="ns-1" --gas=auto --gas-adjustment=2.0 --yes --node=$RPC_1 +rolld tx wasm instantiate 1 '{}' --no-admin --from=acc0 --label="ns-1" --gas=auto --gas-adjustment=2.0 --yes --node=$RPC_2 --chain-id=localchain-2 + +rolld q wasm list-contracts-by-creator roll1hj5fveer5cjtn4wd6wstzugjfdxzl0xpg2te87 +rolld q wasm list-contracts-by-creator roll1hj5fveer5cjtn4wd6wstzugjfdxzl0xpg2te87 --node=$RPC_2 + +NSERVICE_CONTRACT=roll14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjczpjh +``` + +### Relayer connect + +```bash +# Import the testnet interaction helper functions +# for local-interchain +curl -s https://raw.githubusercontent.com/strangelove-ventures/interchaintest/main/local-interchain/bash/source.bash > ict_source.bash +source ./ict_source.bash + +API_ADDR="http://localhost:8080" + +# This will take a moment. +ICT_RELAYER_EXEC "$API_ADDR" "localchain-1" "rly tx link localchain-1_localchain-2 --src-port wasm.${NSERVICE_CONTRACT} --dst-port=wasm.${NSERVICE_CONTRACT} --order unordered --version counter-1" +ICT_RELAYER_EXEC "$API_ADDR" "localchain-1" "rly start" +``` + +## Transaction against + +```bash +MESSAGE='{"set_name":{"channel":"channel-1","name":"myname"}}' +rolld tx wasm execute $NSERVICE_CONTRACT "$MESSAGE" --from=acc0 \ + --gas=auto --gas-adjustment=2.0 --yes + + + +``` + +--- + +### Verify data + +```bash +# TODO: rename this to get the name from a wallet +# query on the other sidebar_label +rolld q wasm state smart $NSERVICE_CONTRACT '{"get_wallet":{"channel":"channel-1","name":"roll1hj5fveer5cjtn4wd6wstzugjfdxzl0xpg2te87 "}}' --node=$RPC_2 + +# dump state fropm the other chain +rolld q wasm state all $NSERVICE_CONTRACT --node=$RPC_2 +``` + +## Write a review + +```bash +MESSAGE='{"write_review":{"val_addr":"rollvaloper1hj5fveer5cjtn4wd6wstzugjfdxzl0xpmhf3p6","review":"hi reviewing."}}' +rolld tx wasm execute $NSERVICE_CONTRACT "$MESSAGE" --from=acc0 \ + --gas=auto --gas-adjustment=2.0 --yes +``` + +### Verify the review + +```bash +rolld q wasm state smart $NSERVICE_CONTRACT '{"reviews":{"address":"rollvaloper1hj5fveer5cjtn4wd6wstzugjfdxzl0xpmhf3p6"}}' +``` + +### Write a review for a non-validator + +```bash +MESSAGE='{"write_review":{"val_addr":"NotAValidator","review":"hi this is a review"}}' + +rolld tx wasm execute $NSERVICE_CONTRACT "$MESSAGE" --from=acc0 \ + --gas=auto --gas-adjustment=2.0 --yes +``` + + +---------------------------------------------------------------------------------------------------------------------------------------------------------- + + +### Set State + +The contract state and base structure is set in the state.rs file. There are 2 groups of data that must be managed, validators and the reviews for validators. + +- `Validators` have unique addresses and name stored on the chain. This data will be passed from the Cosmos-SDK to the contract. +- `Reviews` will save a user wallets address and their text reviews for a validator. + +```rust title="src/state.rs" +use std::collections::BTreeMap; + +use cosmwasm_schema::cw_serde; +use cw_storage_plus::{Item, Map}; + +#[cw_serde] +pub struct Validator { + pub address: String, + pub moniker: String, +} +pub const VALIDATORS: Item> = Item::new("validators"); + +// user -> text +pub type Reviews = BTreeMap; + +// validator_address -> reviews +pub const REVIEWS: Map<&str, Reviews> = Map::new("reviews"); +``` + +### Set Inputs + +By default contracts get 3 messages, `InstantiateMsg`, `ExecuteMsg`, and `QueryMsg`. + +- **Instantiate** allows initial contracts setup with a configuration desired. This is not used for us. Keep it empty. +- **Execute** is where the main logic of the contract is. Add a `WriteReview` message to allow users to write reviews. The user must know who they want to write a review for and what it says. +- **Query** is for reading data from the contract. Add 2 queries, one to get all validators available and one to get reviews for a specific validator. + +The `SudoMsg` is a default type not typically used. `Sudo` stands for `Super User DO` where the super user is the chain. **Only** the chain can submit data to this message type. A `SetValidators` message is added to allow the chain to update the validators list within the contract. This is the pass through from the Cosmos-SDK to the contract. + +```rust title="src/msg.rs" +use cosmwasm_schema::{cw_serde, QueryResponses}; + +#[cw_serde] +pub struct InstantiateMsg {} + +#[cw_serde] +pub enum ExecuteMsg { + // highlight-next-line + WriteReview { val_addr: String, review: String }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + // highlight-start + #[returns(Vec)] + Validators {}, + + #[returns(crate::state::Reviews)] + Reviews { address: String }, + // highlight-end +} + +// highlight-start +#[cw_serde] +pub enum SudoMsg { + SetValidators { + validators: Vec, + }, +} +// highlight-end +``` + +### Set new error + +For a better experience, a new error is added to the contract. This will be used when a validator is not found in the list of validators. Users should not be allowed to post reviews for non existent validators. + +```rust title="src/error.rs" +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + // Add any other custom errors you like here. + // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. + // highlight-start + #[error("The validator is not found")] + NoValidatorFound {}, + // highlight-end +} +``` + +### Imports + +The imports required for this next section are provided here. Paste these at the top of the file to get syntax highlighting. + +```rust title="src/contract.rs" +// highlight-start +use crate::state::{Reviews, REVIEWS, VALIDATORS}; +use cosmwasm_std::to_json_binary; +use std::collections::BTreeMap; +// highlight-end +``` + +### Modify Instantiate Message + +Even though no extra data is passed through to the setup method, an empty list of validators is saved to storage. This way if we try to get validators from the contract **before** any have been set by the chain, it returns nothing instead of an error. + +```rust title="src/contract.rs" +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + // highlight-next-line + deps: DepsMut, // removes the underscore + _env: Env, + _info: MessageInfo, + _msg: InstantiateMsg, +) -> Result { + // highlight-start + VALIDATORS.save(deps.storage, &Vec::new())?; + Ok(Response::default()) + // highlight-end +} +``` + +### Add Execute Logic + +When the user sends a message, the contract needs to first check if the validator exist. It does this by loading the validators state and looping through all the validators to see if the one the user requested if in the list. If it is not, it returns to the user that the validator is not found. If it is found then the contract loads all reviews a validator has. If there are none, it creates an empty list of reviews since this will be the first one. The user's review is then added to the list and saved back to storage. + +```rust title="src/contract.rs" +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + // highlight-next-line + deps: DepsMut, // removes the underscore + _env: Env, + _info: MessageInfo, + // highlight-next-line + msg: ExecuteMsg, // removes the underscore +) -> Result { + // highlight-start + match msg { + ExecuteMsg::WriteReview { val_addr, review } => { + let active_validators = VALIDATORS.load(deps.storage)?; + if active_validators.iter().find(|v| v.address == val_addr).is_none() { + return Err(ContractError::NoValidatorFound {}); + } + + // Get current validator reviews if any. If there are none, create a new empty review map. + let mut all_revs: Reviews = match REVIEWS.may_load(deps.storage, &val_addr) { + Ok(Some(rev)) => rev, + _ => BTreeMap::new(), + }; + + // Set this users review for the validator. + all_revs.insert(val_addr.clone(), review); + + // Save the updated reviews + REVIEWS.save(deps.storage, &val_addr, &all_revs)?; + } + } + + Ok(Response::default()) + // highlight-end +} +``` + +### Add Queries + +It is only useful to set reviews if you can also get them back. The first query for `Validators` is just a helper method so users can see who they are allowed to review. The second query is for `Reviews` and takes a validator address as a parameter. This will return all reviews for that validator. + +To get reviews for all validators, a user would need to query `Validators`, then iterate through all the addresses and query `Reviews` for each one. + +```rust title="src/contract.rs" +#[cfg_attr(not(feature = "library"), entry_point)] +// highlight-start +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + // note: ^^ deps & msg are not underscored ^^ + match msg { + QueryMsg::Validators {} => { + let validators = VALIDATORS.load(deps.storage)?; + Ok(to_json_binary(&validators)?) + } + QueryMsg::Reviews { address } => { + let reviews = REVIEWS.load(deps.storage, &address)?; + Ok(to_json_binary(&reviews)?) + } + } +// highlight-end +} +``` + +### Add New Sudo Message Type + +The chain extended portion of this contract is now added. It is where the validator logic is actually set and saved to storage. As the validator set changes (nodes stop, new nodes come online), the chain will update the contract right away. + +```rust title="src/contract.rs" +// highlight-start +// Insert at the bottom of the file +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn sudo(deps: DepsMut, _env: Env, msg: crate::msg::SudoMsg) -> Result { + match msg { + crate::msg::SudoMsg::SetValidators { validators } => { + VALIDATORS.save(deps.storage, &validators)?; + Ok(Response::new()) + } + } +} +// highlight-end +``` + + + +### Build the Contract + +Build the contract to get the cosmwasm wasm binary. This converts it from english programming rust text to 0s and 1s that the chain can understand. The `optimize` script requires docker to be installed and running. Make sure you followed the setup prerequisites at the top of the page and have the docker service or docker desktop installed. + +```bash +# run the build optimizer (from source -> contract wasm binary) +cargo run-script optimize +``` + +The .wasm file is then saved to `./artifacts/validator_NSERVICE_CONTRACT.wasm`. + +## Modify the Module + +The contract is complete but we need to pass the data into the contract from the chain. This is done through the cosmos-sdk reviews module generated earlier. The module will be updated to include the wasm contract and the endblocker will be updated to pass the validator data to the contract. + +### Setup the Keeper + +We must give our code access to other modules on the chain. The wasm module is required to interact with the contract. The staking module is required to get the list of validators. This keeper is the access point for all the specific logic. + +Add the imports, keeper setup, and new keeper output. + +```go title="x/reviews/keeper.go" +import ( + ... + + // highlight-start + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + // highlight-end +) + +type Keeper struct + ... + + // highlight-start + WasmKeeper *wasmkeeper.Keeper + ContractKeeper wasmtypes.ContractOpsKeeper + StakingKeeper *stakingkeeper.Keeper + // highlight-end +} + +func NewKeeper( + ... + // highlight-start + wasmKeeper *wasmkeeper.Keeper, // since wasm may not be created yet. + stakingKeeper *stakingkeeper.Keeper, + // highlight-end + authority string, +) Keeper { + ... + + k := Keeper{ + ... + + // highlight-start + WasmKeeper: wasmKeeper, + ContractKeeper: wasmkeeper.NewDefaultPermissionKeeper(wasmKeeper), + StakingKeeper: stakingKeeper, + // highlight-end + + authority: authority, + } +} +``` + +### 'Fix' keeper_test + + +:::note Warning +Testing wasm requires significantly more setup for the test environment. For now, just add a blank reference here. +::: + +```go title="x/reviews/keeper/keeper_test.go" +func SetupTest(t *testing.T) *testFixture + ... + + // Setup Keeper. + // highlight-next-line + f.k = keeper.NewKeeper(encCfg.Codec, storeService, logger, &wasmkeeper.Keeper{}, f.stakingKeeper, f.govModAddr) +} +``` + +### Dependency Inject (v2) + +Similar to the keeper_test issue, CosmWasm does not have support for Cosmos-SDK v2 depinject. This will be updated in the future. For now, set the keeper to nil and provide Staking reference. You do not need to know what this does. Just resolve the error on the line with a copy paste. + +```go title="x/reviews/depinject.go" +func ProvideModule(in ModuleInputs) ModuleOutputs { + ... + + // highlight-next-line + k := keeper.NewKeeper(in.Cdc, in.StoreService, log.NewLogger(os.Stderr), nil, &in.StakingKeeper, govAddr) +} +``` + +### Fix app.go references + +The main application needs the access to the wasm and staking logic as well. Fix the missing imports and add them in like so. + +```go title="app/app.go" + app.ReviewsKeeper = reviewskeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keys[reviewstypes.StoreKey]), + logger, + // highlight-start + &app.WasmKeeper, + app.StakingKeeper, + // highlight-end + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) +``` + +it is now time to use these modules and write the logic to pass data to the contract from the chain. + +### Module Core Logic + +The CosmWasm contract requires data in a specific format. You can review this back in the `src/state.rs` file. Since the chain only is passing validator data over, we need to convert this into Go code manually. The `Validator` struct is created to match the contract. The CosmWasm contract expects a JSON formatted input. This input is put together with the `Formatted` method on a list of validators. The chain could have just 1 validator, or several hundred. This method will convert them all into the correct format for the list we are to pass. + +```go title="x/reviews/module.go" +// Add this below AppModule struct { + +// highlight-start +type Validator struct { + Address string `json:"address"` + Moniker string `json:"moniker"` +} + +type Validators []Validator + +func (vs Validators) Formatted() string { + output := "" + for _, val := range vs { + output += fmt.Sprintf(`{"address":"%s","moniker":"%s"},`, val.Address, val.Moniker) + } + + // remove the trailing comma from the last output append. + return output[:len(output)-1] +} +// highlight-end +``` + +### Implement the EndBlocker + +To pass data we must first get the data. Using the `GetAllValidators` method from the staking module, all validators are now accessible for the logic to use. Loop through these validators and only add the ones that are bonded (active) to the list of validators. If they are bonded, they are added to the list. + +Once all validators have been processed the `endBlockSudoMsg` gets them into the JSON format required. The format is out of scope but a high level overview +- `SetValidators` in the code becomes `set_validators`, called [snake case](https://simple.wikipedia.org/wiki/Snake_case). +- The `SetValidators` type in rust has the element called `validators` which is an array of `Validator` objects. This is the `validators` array in the JSON. +- Each `Validator` object has an `address` and `moniker` field. These are the `address` and `moniker` fields in the JSON, called from the Formatted() method. + +```go title="x/reviews/module.go" +// Paste this anywhere within the file +// highlight-start +func (am AppModule) EndBlock(ctx context.Context) error { + stakingVals, err := am.keeper.StakingKeeper.GetAllValidators(ctx) + if err != nil { + return err + } + + validators := Validators{} + for _, val := range stakingVals { + // if it is not active, skip it + if !val.IsBonded() { + continue + } + + validators = append(validators, Validator{ + Address: val.OperatorAddress, + Moniker: val.Description.Moniker, + }) + } + + // The first contract created from acc0 + addr := "roll14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjczpjh" + contract := sdk.MustAccAddressFromBech32(addr) + + // SudoMsg format for the contract input. + // example: {"set_validators":{"validators":[{"address":"ADDRESS","moniker": "NAME"}]}} + endBlockSudoMsg := fmt.Sprintf(`{"set_validators":{"validators":[%s]}}`, validators.Formatted()) + fmt.Println("EndBlockSudoMessage Format:", endBlockSudoMsg) + + // When the network first starts up there is no contract to execute against (until uploaded) + // This returns an error but is expected behavior initially. + // You can not return errors in the EndBlocker as it is not a transaction. It will halt the network. + // + // This is why the error is only printed to the logs and not returned. + // + // A more proper solution would set the contract via a SDK message after it is uploaded. + // This is out of scope for this tutorial, but a future challenge for you. + res, err := am.keeper.ContractKeeper.Sudo(sdk.UnwrapSDKContext(ctx), contract, []byte(endBlockSudoMsg)) + if err != nil { + fmt.Println("EndBlockSudoResult", res) + fmt.Println("EndBlockSudoError", err) + } + + return nil +} +// highlight-end +``` + +## Test Deployment + +### Start Testnet + +Begin the CosmWasm testnet with the custom EndBlocker logic. You will see errors every block. This is expected and is explained in the EndBlock code why this is the case. + +```bash +make sh-testnet +``` + +### Upload Contract + +Make sure you are in the `rollchain` directory to begin interacting and uploading the contract to the chain. + +```bash +CONTRACT_SOURCE=./validator-reviews-contract/artifacts/validator_NSERVICE_CONTRACT.wasm +rolld tx wasm store $CONTRACT_SOURCE --from=acc0 --gas=auto --gas-adjustment=2.0 --yes +# rolld q wasm list-code +``` + +### Instantiate our Contract + +```bash +rolld tx wasm instantiate 1 '{}' --no-admin --from=acc0 --label="reviews" \ + --gas=auto --gas-adjustment=2.0 --yes + +rolld q wasm list-contracts-by-creator roll1hj5fveer5cjtn4wd6wstzugjfdxzl0xpg2te87 + +NSERVICE_CONTRACT=roll14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjczpjh +``` + +### Verify data + +```bash +rolld q wasm state smart $NSERVICE_CONTRACT '{"validators":{}}' +``` + +## Write a review + +```bash +MESSAGE='{"write_review":{"val_addr":"rollvaloper1hj5fveer5cjtn4wd6wstzugjfdxzl0xpmhf3p6","review":"hi reviewing."}}' +rolld tx wasm execute $NSERVICE_CONTRACT "$MESSAGE" --from=acc0 \ + --gas=auto --gas-adjustment=2.0 --yes +``` + +### Verify the review + +```bash +rolld q wasm state smart $NSERVICE_CONTRACT '{"reviews":{"address":"rollvaloper1hj5fveer5cjtn4wd6wstzugjfdxzl0xpmhf3p6"}}' +``` + +### Write a review for a non-validator + +```bash +MESSAGE='{"write_review":{"val_addr":"NotAValidator","review":"hi this is a review"}}' + +rolld tx wasm execute $NSERVICE_CONTRACT "$MESSAGE" --from=acc0 \ + --gas=auto --gas-adjustment=2.0 --yes +``` diff --git a/docs/versioned_docs/version-v0.50.x/03-demos/04-cw-validator-reviews.md b/docs/versioned_docs/version-v0.50.x/03-demos/04-cw-validator-reviews.md index e2d1914b..5c97e5df 100644 --- a/docs/versioned_docs/version-v0.50.x/03-demos/04-cw-validator-reviews.md +++ b/docs/versioned_docs/version-v0.50.x/03-demos/04-cw-validator-reviews.md @@ -13,24 +13,7 @@ There are easy ways to get validators in a cosmwasm smart contract. The goal of ## Prerequisites - [System Setup](../01-setup/01-system-setup.md) - [Install Spawn](../01-setup/02-install-spawn.md) - -## CosmWasm Setup - -CosmWasm requires [Rust](https://www.rust-lang.org/). You must have this installed as the contract will be built locally. - -```bash -# Install rust - https://www.rust-lang.org/tools/install -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - -# or Update if you have it -rustup update - -# Install other dependencies -rustup target add wasm32-unknown-unknown - -cargo install cargo-generate --features vendored-openssl -cargo install cargo-run-script -``` +- [Rust + CosmWasm](../01-setup/01-system-setup.md#cosmwasm) ## Setup the Chain From 27d6d273b479376323439f133d18ad83ef153c39 Mon Sep 17 00:00:00 2001 From: Reece Williams Date: Wed, 9 Oct 2024 16:07:15 -0500 Subject: [PATCH 2/5] proper tutorial, needs content --- .../09-ibc-cosmwasm.md | 799 +++++++++--------- simapp/chains/README.md | 1 + 2 files changed, 393 insertions(+), 407 deletions(-) diff --git a/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md b/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md index 43f268b1..a6793dcb 100644 --- a/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md +++ b/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md @@ -40,11 +40,12 @@ Once the chain is started, continue on to the contract building steps ## CosmWasm Build Contract -CosmWasm has a template repository that is used to generate new contracts. A minimal contract will be built with the `validator-reviews-contract` name provided on a specific commit. +CosmWasm has a template repository that is used to generate new contracts. A minimal contract will be built with the `nameservice-contract` name provided on a specific commit. ```bash cargo generate --git https://github.com/CosmWasm/cw-template.git \ --name nameservice-contract \ + --force-git-init \ -d minimal=true --tag a2a169164324aa1b48ab76dd630f75f504e41d99 ``` @@ -55,145 +56,42 @@ code nameservice-contract/ -`make sh-testnet` - -### Upload Contract to both networks - -Make sure you are in the `cwchain` directory to begin interacting and uploading the contract to the chain. - -```bash -make local-image - -local-ic start self-ibc -``` - - -```bash -RPC_1=http://localhost:26657 -RPC_2=http://localhost:41427 # todo: query from the http://127.0.0.1:8080/info with jq parse jq .logs.chains[1].rpc_address - -CONTRACT_SOURCE=./nameservice-contract/artifacts/nameservice_contract.wasm -rolld tx wasm store $CONTRACT_SOURCE --from=acc0 --gas=auto --gas-adjustment=2.0 --yes --node=$RPC_1 -# rolld q wasm list-code --node=$RPC_1 - -rolld tx wasm store $CONTRACT_SOURCE --from=acc0 --gas=auto --gas-adjustment=2.0 --yes --node=$RPC_2 --chain-id=localchain-2 -# rolld q wasm list-code --node=$RPC_2 + +```toml title="Cargo.toml" +[dependencies] +# highlight-start +cosmwasm-schema = "1.5.7" +cosmwasm-std = { version = "1.5.7", features = [ + # "cosmwasm_1_3", + "ibc3" +] } +# highlight-end ``` -### Instantiate our Contract on oth chains - -```bash -rolld tx wasm instantiate 1 '{}' --no-admin --from=acc0 --label="ns-1" --gas=auto --gas-adjustment=2.0 --yes --node=$RPC_1 -rolld tx wasm instantiate 1 '{}' --no-admin --from=acc0 --label="ns-1" --gas=auto --gas-adjustment=2.0 --yes --node=$RPC_2 --chain-id=localchain-2 - -rolld q wasm list-contracts-by-creator roll1hj5fveer5cjtn4wd6wstzugjfdxzl0xpg2te87 -rolld q wasm list-contracts-by-creator roll1hj5fveer5cjtn4wd6wstzugjfdxzl0xpg2te87 --node=$RPC_2 - -NSERVICE_CONTRACT=roll14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjczpjh -``` - -### Relayer connect - -```bash -# Import the testnet interaction helper functions -# for local-interchain -curl -s https://raw.githubusercontent.com/strangelove-ventures/interchaintest/main/local-interchain/bash/source.bash > ict_source.bash -source ./ict_source.bash - -API_ADDR="http://localhost:8080" - -# This will take a moment. -ICT_RELAYER_EXEC "$API_ADDR" "localchain-1" "rly tx link localchain-1_localchain-2 --src-port wasm.${NSERVICE_CONTRACT} --dst-port=wasm.${NSERVICE_CONTRACT} --order unordered --version counter-1" -ICT_RELAYER_EXEC "$API_ADDR" "localchain-1" "rly start" -``` - -## Transaction against - -```bash -MESSAGE='{"set_name":{"channel":"channel-1","name":"myname"}}' -rolld tx wasm execute $NSERVICE_CONTRACT "$MESSAGE" --from=acc0 \ - --gas=auto --gas-adjustment=2.0 --yes - - - -``` - ---- - -### Verify data - -```bash -# TODO: rename this to get the name from a wallet -# query on the other sidebar_label -rolld q wasm state smart $NSERVICE_CONTRACT '{"get_wallet":{"channel":"channel-1","name":"roll1hj5fveer5cjtn4wd6wstzugjfdxzl0xpg2te87 "}}' --node=$RPC_2 - -# dump state fropm the other chain -rolld q wasm state all $NSERVICE_CONTRACT --node=$RPC_2 -``` - -## Write a review - -```bash -MESSAGE='{"write_review":{"val_addr":"rollvaloper1hj5fveer5cjtn4wd6wstzugjfdxzl0xpmhf3p6","review":"hi reviewing."}}' -rolld tx wasm execute $NSERVICE_CONTRACT "$MESSAGE" --from=acc0 \ - --gas=auto --gas-adjustment=2.0 --yes -``` - -### Verify the review - ```bash -rolld q wasm state smart $NSERVICE_CONTRACT '{"reviews":{"address":"rollvaloper1hj5fveer5cjtn4wd6wstzugjfdxzl0xpmhf3p6"}}' +cargo update ``` -### Write a review for a non-validator - -```bash -MESSAGE='{"write_review":{"val_addr":"NotAValidator","review":"hi this is a review"}}' - -rolld tx wasm execute $NSERVICE_CONTRACT "$MESSAGE" --from=acc0 \ - --gas=auto --gas-adjustment=2.0 --yes -``` - - ----------------------------------------------------------------------------------------------------------------------------------------------------------- - - -### Set State - -The contract state and base structure is set in the state.rs file. There are 2 groups of data that must be managed, validators and the reviews for validators. - -- `Validators` have unique addresses and name stored on the chain. This data will be passed from the Cosmos-SDK to the contract. -- `Reviews` will save a user wallets address and their text reviews for a validator. +## Setup State ```rust title="src/state.rs" +// highlight-start use std::collections::BTreeMap; -use cosmwasm_schema::cw_serde; -use cw_storage_plus::{Item, Map}; +use cw_storage_plus::Map; -#[cw_serde] -pub struct Validator { - pub address: String, - pub moniker: String, -} -pub const VALIDATORS: Item> = Item::new("validators"); +// Wallet -> Name +pub type WalletMapping = BTreeMap; -// user -> text -pub type Reviews = BTreeMap; +pub fn new_wallet_mapping() -> WalletMapping { + BTreeMap::new() +} -// validator_address -> reviews -pub const REVIEWS: Map<&str, Reviews> = Map::new("reviews"); +pub const NAME_SERVICE: Map = Map::new("nameservice"); +// highlight-end ``` -### Set Inputs - -By default contracts get 3 messages, `InstantiateMsg`, `ExecuteMsg`, and `QueryMsg`. - -- **Instantiate** allows initial contracts setup with a configuration desired. This is not used for us. Keep it empty. -- **Execute** is where the main logic of the contract is. Add a `WriteReview` message to allow users to write reviews. The user must know who they want to write a review for and what it says. -- **Query** is for reading data from the contract. Add 2 queries, one to get all validators available and one to get reviews for a specific validator. - -The `SudoMsg` is a default type not typically used. `Sudo` stands for `Super User DO` where the super user is the chain. **Only** the chain can submit data to this message type. A `SetValidators` message is added to allow the chain to update the validators list within the contract. This is the pass through from the Cosmos-SDK to the contract. +## Setup Interactions ```rust title="src/msg.rs" use cosmwasm_schema::{cw_serde, QueryResponses}; @@ -204,432 +102,519 @@ pub struct InstantiateMsg {} #[cw_serde] pub enum ExecuteMsg { // highlight-next-line - WriteReview { val_addr: String, review: String }, + SetName { channel: String, name: String }, } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { // highlight-start - #[returns(Vec)] - Validators {}, - - #[returns(crate::state::Reviews)] - Reviews { address: String }, + #[returns(GetNameResponse)] + GetName { channel: String, wallet: String }, // highlight-end } // highlight-start #[cw_serde] -pub enum SudoMsg { - SetValidators { - validators: Vec, - }, +pub enum IbcExecuteMsg { + SetName { wallet: String, name: String }, } -// highlight-end -``` - -### Set new error - -For a better experience, a new error is added to the contract. This will be used when a validator is not found in the list of validators. Users should not be allowed to post reviews for non existent validators. - -```rust title="src/error.rs" -use cosmwasm_std::StdError; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - #[error("Unauthorized")] - Unauthorized {}, - - // Add any other custom errors you like here. - // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. - // highlight-start - #[error("The validator is not found")] - NoValidatorFound {}, - // highlight-end +#[cw_serde] +pub struct GetNameResponse { + pub name: String, } +// highlight-end ``` -### Imports -The imports required for this next section are provided here. Paste these at the top of the file to get syntax highlighting. +## Contract Logic + +Here are all the imports used in this. Replace your files top with these. ```rust title="src/contract.rs" // highlight-start -use crate::state::{Reviews, REVIEWS, VALIDATORS}; -use cosmwasm_std::to_json_binary; -use std::collections::BTreeMap; -// highlight-end -``` +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; -### Modify Instantiate Message +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, IbcExecuteMsg, InstantiateMsg, QueryMsg}; -Even though no extra data is passed through to the setup method, an empty list of validators is saved to storage. This way if we try to get validators from the contract **before** any have been set by the chain, it returns nothing instead of an error. +use cosmwasm_std::{to_json_binary, IbcMsg, IbcTimeout, StdError}; +// highlight-end +``` + ```rust title="src/contract.rs" #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( - // highlight-next-line - deps: DepsMut, // removes the underscore + _deps: DepsMut, _env: Env, _info: MessageInfo, _msg: InstantiateMsg, ) -> Result { - // highlight-start - VALIDATORS.save(deps.storage, &Vec::new())?; - Ok(Response::default()) - // highlight-end + // highlight-next-line + Ok(Response::new().add_attribute("method", "instantiate")) } ``` -### Add Execute Logic - -When the user sends a message, the contract needs to first check if the validator exist. It does this by loading the validators state and looping through all the validators to see if the one the user requested if in the list. If it is not, it returns to the user that the validator is not found. If it is found then the contract loads all reviews a validator has. If there are none, it creates an empty list of reviews since this will be the first one. The user's review is then added to the list and saved back to storage. - + ```rust title="src/contract.rs" #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( - // highlight-next-line - deps: DepsMut, // removes the underscore - _env: Env, - _info: MessageInfo, - // highlight-next-line - msg: ExecuteMsg, // removes the underscore + _deps: DepsMut, + // highlight-start + env: Env, // removes the underscore _ + info: MessageInfo, + msg: ExecuteMsg, + // highlight-end ) -> Result { // highlight-start match msg { - ExecuteMsg::WriteReview { val_addr, review } => { - let active_validators = VALIDATORS.load(deps.storage)?; - if active_validators.iter().find(|v| v.address == val_addr).is_none() { - return Err(ContractError::NoValidatorFound {}); - } - - // Get current validator reviews if any. If there are none, create a new empty review map. - let mut all_revs: Reviews = match REVIEWS.may_load(deps.storage, &val_addr) { - Ok(Some(rev)) => rev, - _ => BTreeMap::new(), - }; - - // Set this users review for the validator. - all_revs.insert(val_addr.clone(), review); - - // Save the updated reviews - REVIEWS.save(deps.storage, &val_addr, &all_revs)?; + ExecuteMsg::SetName { channel, name } => { + Ok(Response::new() + .add_attribute("method", "set_name") + .add_attribute("channel", channel.clone()) + // outbound IBC message, where packet is then received on other chain + .add_message(IbcMsg::SendPacket { + channel_id: channel, + data: to_json_binary(&IbcExecuteMsg::SetName { + name: name, + wallet: info.sender.into_string(), + })?, + // default timeout of two minutes. + timeout: IbcTimeout::with_timestamp(env.block.time.plus_seconds(120)), + })) } } - - Ok(Response::default()) // highlight-end } ``` -### Add Queries - -It is only useful to set reviews if you can also get them back. The first query for `Validators` is just a helper method so users can see who they are allowed to review. The second query is for `Reviews` and takes a validator address as a parameter. This will return all reviews for that validator. - -To get reviews for all validators, a user would need to query `Validators`, then iterate through all the addresses and query `Reviews` for each one. - + ```rust title="src/contract.rs" #[cfg_attr(not(feature = "library"), entry_point)] -// highlight-start pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - // note: ^^ deps & msg are not underscored ^^ + // highlight-start match msg { - QueryMsg::Validators {} => { - let validators = VALIDATORS.load(deps.storage)?; - Ok(to_json_binary(&validators)?) - } - QueryMsg::Reviews { address } => { - let reviews = REVIEWS.load(deps.storage, &address)?; - Ok(to_json_binary(&reviews)?) + QueryMsg::GetName { channel, wallet } => { + crate::state::NAME_SERVICE + .may_load(deps.storage, channel.clone()) + .and_then(|maybe_wallets| match maybe_wallets { + Some(wallets) => match wallets.get(&wallet) { + Some(wallet) => Ok(to_json_binary(&crate::msg::GetNameResponse { + name: wallet.clone(), + })?), + None => Err(StdError::generic_err("No name set for wallet")), + }, + None => Err(StdError::generic_err("Channel not found")), + }) } } -// highlight-end + // highlight-end } ``` -### Add New Sudo Message Type - -The chain extended portion of this contract is now added. It is where the validator logic is actually set and saved to storage. As the validator set changes (nodes stop, new nodes come online), the chain will update the contract right away. - + ```rust title="src/contract.rs" // highlight-start -// Insert at the bottom of the file -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn sudo(deps: DepsMut, _env: Env, msg: crate::msg::SudoMsg) -> Result { - match msg { - crate::msg::SudoMsg::SetValidators { validators } => { - VALIDATORS.save(deps.storage, &validators)?; - Ok(Response::new()) - } - } +/// called on IBC packet receive in other chain +pub fn try_set_name( + deps: DepsMut, + channel: String, + wallet: String, + name: String, +) -> Result { + crate::state::NAME_SERVICE.update(deps.storage, channel, |wallets| -> StdResult<_> { + let mut wallets = wallets.unwrap_or_default(); + wallets.insert(wallet, name.clone()); + Ok(wallets) + })?; + Ok(name) } // highlight-end ``` +## Create Transaction acknowledgement - -### Build the Contract - -Build the contract to get the cosmwasm wasm binary. This converts it from english programming rust text to 0s and 1s that the chain can understand. The `optimize` script requires docker to be installed and running. Make sure you followed the setup prerequisites at the top of the page and have the docker service or docker desktop installed. +Create a new file, `ack.rs`, to handle the IBC ACK messages. ```bash -# run the build optimizer (from source -> contract wasm binary) -cargo run-script optimize +touch src/ack.rs ``` -The .wasm file is then saved to `./artifacts/validator_NSERVICE_CONTRACT.wasm`. +```rust title="src/ack.rs" +// highlight-start +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{to_json_binary, Binary}; + +/// IBC ACK. See: +/// https://github.com/cosmos/cosmos-sdk/blob/f999b1ff05a4db4a338a855713864497bedd4396/proto/ibc/core/channel/v1/channel.proto#L141-L147 +#[cw_serde] +pub enum Ack { + Result(Binary), + Error(String), +} -## Modify the Module +pub fn make_ack_success() -> Binary { + let res = Ack::Result(b"1".into()); + to_json_binary(&res).unwrap() +} -The contract is complete but we need to pass the data into the contract from the chain. This is done through the cosmos-sdk reviews module generated earlier. The module will be updated to include the wasm contract and the endblocker will be updated to pass the validator data to the contract. +pub fn make_ack_fail(err: String) -> Binary { + let res = Ack::Error(err); + to_json_binary(&res).unwrap() +} +// highlight-end +``` -### Setup the Keeper +Add it to the lib.rs so the application can use it. -We must give our code access to other modules on the chain. The wasm module is required to interact with the contract. The staking module is required to get the list of validators. This keeper is the access point for all the specific logic. +```rust title="src/lib.rs" +// highlight-next-line +pub mod ack; +pub mod contract; +... -Add the imports, keeper setup, and new keeper output. +pub use crate::error::ContractError; +``` -```go title="x/reviews/keeper.go" -import ( - ... +## Setup Errors - // highlight-start - wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" - // highlight-end -) +```rust title="src/error.rs" +use cosmwasm_std::StdError; +use thiserror::Error; -type Keeper struct - ... +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + // Add any other custom errors you like here. + // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. // highlight-start - WasmKeeper *wasmkeeper.Keeper - ContractKeeper wasmtypes.ContractOpsKeeper - StakingKeeper *stakingkeeper.Keeper + #[error("only unordered channels are supported")] + OrderedChannel {}, + + #[error("invalid IBC channel version. Got ({actual}), expected ({expected})")] + InvalidVersion { actual: String, expected: String }, // highlight-end } -func NewKeeper( - ... - // highlight-start - wasmKeeper *wasmkeeper.Keeper, // since wasm may not be created yet. - stakingKeeper *stakingkeeper.Keeper, - // highlight-end - authority string, -) Keeper { - ... +// highlight-start +#[derive(Error, Debug)] +pub enum Never {} +// highlight-end +``` - k := Keeper{ - ... - // highlight-start - WasmKeeper: wasmKeeper, - ContractKeeper: wasmkeeper.NewDefaultPermissionKeeper(wasmKeeper), - StakingKeeper: stakingKeeper, - // highlight-end +## IBC Specific Logic - authority: authority, - } -} -``` +Create a new file `ibc.rs`. Add this to the lib.rs -### 'Fix' keeper_test +```bash +touch src/ibc.rs +``` +```rust title="src/lib.rs" +// highlight-next-line +pub mod ibc; +pub mod ack; +pub mod contract; +mod error; +pub mod helpers; +pub mod msg; +pub mod state; -:::note Warning -Testing wasm requires significantly more setup for the test environment. For now, just add a blank reference here. -::: +pub use crate::error::ContractError; +``` -```go title="x/reviews/keeper/keeper_test.go" -func SetupTest(t *testing.T) *testFixture - ... +Now begin working on the IBC logic. - // Setup Keeper. - // highlight-next-line - f.k = keeper.NewKeeper(encCfg.Codec, storeService, logger, &wasmkeeper.Keeper{}, f.stakingKeeper, f.govModAddr) -} -``` + -### Dependency Inject (v2) +```rust title="src/ibc.rs" +// highlight-start +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + from_json, DepsMut, Env, IbcBasicResponse, IbcChannel, IbcChannelCloseMsg, + IbcChannelConnectMsg, IbcChannelOpenMsg, IbcChannelOpenResponse, IbcOrder, IbcPacketAckMsg, + IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, +}; + +use crate::{ + ack::{make_ack_fail, make_ack_success}, + contract::try_set_name, + msg::IbcExecuteMsg, + state::NAME_SERVICE, + ContractError, +}; + +pub const IBC_VERSION: &str = "ns-1"; + +pub fn validate_order_and_version( + channel: &IbcChannel, + counterparty_version: Option<&str>, +) -> Result<(), ContractError> { + // We expect an unordered channel here. Ordered channels have the + // property that if a message is lost the entire channel will stop + // working until you start it again. + if channel.order != IbcOrder::Unordered { + return Err(ContractError::OrderedChannel {}); + } -Similar to the keeper_test issue, CosmWasm does not have support for Cosmos-SDK v2 depinject. This will be updated in the future. For now, set the keeper to nil and provide Staking reference. You do not need to know what this does. Just resolve the error on the line with a copy paste. + if channel.version != IBC_VERSION { + return Err(ContractError::InvalidVersion { + actual: channel.version.to_string(), + expected: IBC_VERSION.to_string(), + }); + } -```go title="x/reviews/depinject.go" -func ProvideModule(in ModuleInputs) ModuleOutputs { - ... + // Make sure that we're talking with a counterparty who speaks the + // same "protocol" as us. + // + // For a connection between chain A and chain B being established + // by chain A, chain B knows counterparty information during + // `OpenTry` and chain A knows counterparty information during + // `OpenAck`. We verify it when we have it but when we don't it's + // alright. + if let Some(counterparty_version) = counterparty_version { + if counterparty_version != IBC_VERSION { + return Err(ContractError::InvalidVersion { + actual: counterparty_version.to_string(), + expected: IBC_VERSION.to_string(), + }); + } + } - // highlight-next-line - k := keeper.NewKeeper(in.Cdc, in.StoreService, log.NewLogger(os.Stderr), nil, &in.StakingKeeper, govAddr) + Ok(()) } +// highlight-end ``` -### Fix app.go references - -The main application needs the access to the wasm and staking logic as well. Fix the missing imports and add them in like so. - -```go title="app/app.go" - app.ReviewsKeeper = reviewskeeper.NewKeeper( - appCodec, - runtime.NewKVStoreService(keys[reviewstypes.StoreKey]), - logger, - // highlight-start - &app.WasmKeeper, - app.StakingKeeper, - // highlight-end - authtypes.NewModuleAddress(govtypes.ModuleName).String(), - ) -``` + -it is now time to use these modules and write the logic to pass data to the contract from the chain. +```rust title="src/ibc.rs" +// highlight-start +/// Handles the `OpenInit` and `OpenTry` parts of the IBC handshake. +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_channel_open( + _deps: DepsMut, + _env: Env, + msg: IbcChannelOpenMsg, +) -> Result { + validate_order_and_version(msg.channel(), msg.counterparty_version())?; + Ok(None) +} -### Module Core Logic +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_channel_close( + deps: DepsMut, + _env: Env, + msg: IbcChannelCloseMsg, +) -> Result { + let channel = msg.channel().endpoint.channel_id.clone(); + NAME_SERVICE.remove(deps.storage, channel.clone()); + Ok(IbcBasicResponse::new() + .add_attribute("method", "ibc_channel_close") + .add_attribute("channel", channel)) +} +// highlight-end +``` -The CosmWasm contract requires data in a specific format. You can review this back in the `src/state.rs` file. Since the chain only is passing validator data over, we need to convert this into Go code manually. The `Validator` struct is created to match the contract. The CosmWasm contract expects a JSON formatted input. This input is put together with the `Formatted` method on a list of validators. The chain could have just 1 validator, or several hundred. This method will convert them all into the correct format for the list we are to pass. + -```go title="x/reviews/module.go" -// Add this below AppModule struct { +```rust title="src/ibc.rs" +// highlight-start +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_channel_connect( + deps: DepsMut, + _env: Env, + msg: IbcChannelConnectMsg, +) -> Result { + validate_order_and_version(msg.channel(), msg.counterparty_version())?; + + let channel = msg.channel().endpoint.channel_id.clone(); + NAME_SERVICE.save( + deps.storage, channel.clone(), &crate::state::new_wallet_mapping(), + )?; + + Ok(IbcBasicResponse::new() + .add_attribute("method", "ibc_channel_connect") + .add_attribute("channel_id", channel)) +} +// highlight-end +``` + + +```rust title="src/ibc.rs" // highlight-start -type Validator struct { - Address string `json:"address"` - Moniker string `json:"moniker"` +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_packet_receive( + deps: DepsMut, + _env: Env, + msg: IbcPacketReceiveMsg, +) -> Result { + // Regardless of if our processing of this packet works we need to + // commit an ACK to the chain. As such, we wrap all handling logic + // in a septate function and on error write out an error ack. + match do_ibc_packet_receive(deps, msg) { + Ok(response) => Ok(response), + Err(error) => Ok(IbcReceiveResponse::new() + .add_attribute("method", "ibc_packet_receive") + .add_attribute("error", error.to_string()) + .set_ack(make_ack_fail(error.to_string()))), + } } -type Validators []Validator +pub fn do_ibc_packet_receive( + deps: DepsMut, + msg: IbcPacketReceiveMsg, +) -> Result { + // The channel this packet is being relayed along on this chain. + let channel = msg.packet.dest.channel_id; + let msg: IbcExecuteMsg = from_json(&msg.packet.data)?; -func (vs Validators) Formatted() string { - output := "" - for _, val := range vs { - output += fmt.Sprintf(`{"address":"%s","moniker":"%s"},`, val.Address, val.Moniker) - } + match msg { + IbcExecuteMsg::SetName { wallet, name } => { + let name = try_set_name(deps, channel, wallet, name)?; - // remove the trailing comma from the last output append. - return output[:len(output)-1] + Ok(IbcReceiveResponse::new() + .add_attribute("method", "execute_increment") + .add_attribute("name", name) + .set_ack(make_ack_success())) + } + } } // highlight-end ``` -### Implement the EndBlocker - -To pass data we must first get the data. Using the `GetAllValidators` method from the staking module, all validators are now accessible for the logic to use. Loop through these validators and only add the ones that are bonded (active) to the list of validators. If they are bonded, they are added to the list. + -Once all validators have been processed the `endBlockSudoMsg` gets them into the JSON format required. The format is out of scope but a high level overview -- `SetValidators` in the code becomes `set_validators`, called [snake case](https://simple.wikipedia.org/wiki/Snake_case). -- The `SetValidators` type in rust has the element called `validators` which is an array of `Validator` objects. This is the `validators` array in the JSON. -- Each `Validator` object has an `address` and `moniker` field. These are the `address` and `moniker` fields in the JSON, called from the Formatted() method. - -```go title="x/reviews/module.go" -// Paste this anywhere within the file +```rust title="src/ibc.rs" // highlight-start -func (am AppModule) EndBlock(ctx context.Context) error { - stakingVals, err := am.keeper.StakingKeeper.GetAllValidators(ctx) - if err != nil { - return err - } - - validators := Validators{} - for _, val := range stakingVals { - // if it is not active, skip it - if !val.IsBonded() { - continue - } - - validators = append(validators, Validator{ - Address: val.OperatorAddress, - Moniker: val.Description.Moniker, - }) - } - - // The first contract created from acc0 - addr := "roll14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjczpjh" - contract := sdk.MustAccAddressFromBech32(addr) - - // SudoMsg format for the contract input. - // example: {"set_validators":{"validators":[{"address":"ADDRESS","moniker": "NAME"}]}} - endBlockSudoMsg := fmt.Sprintf(`{"set_validators":{"validators":[%s]}}`, validators.Formatted()) - fmt.Println("EndBlockSudoMessage Format:", endBlockSudoMsg) - - // When the network first starts up there is no contract to execute against (until uploaded) - // This returns an error but is expected behavior initially. - // You can not return errors in the EndBlocker as it is not a transaction. It will halt the network. - // - // This is why the error is only printed to the logs and not returned. - // - // A more proper solution would set the contract via a SDK message after it is uploaded. - // This is out of scope for this tutorial, but a future challenge for you. - res, err := am.keeper.ContractKeeper.Sudo(sdk.UnwrapSDKContext(ctx), contract, []byte(endBlockSudoMsg)) - if err != nil { - fmt.Println("EndBlockSudoResult", res) - fmt.Println("EndBlockSudoError", err) - } +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_packet_ack( + _deps: DepsMut, + _env: Env, + _ack: IbcPacketAckMsg, +) -> Result { + Ok(IbcBasicResponse::new().add_attribute("method", "ibc_packet_ack")) +} - return nil +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_packet_timeout( + _deps: DepsMut, + _env: Env, + _msg: IbcPacketTimeoutMsg, +) -> Result { + Ok(IbcBasicResponse::new().add_attribute("method", "ibc_packet_timeout")) } // highlight-end ``` -## Test Deployment +## Build Contract From Source + +```bash +cargo-run-script optimize +``` + + +--- + -### Start Testnet +### Upload Contract to both networks -Begin the CosmWasm testnet with the custom EndBlocker logic. You will see errors every block. This is expected and is explained in the EndBlock code why this is the case. +Make sure you are in the `cwchain` directory to begin interacting and uploading the contract to the chain. ```bash -make sh-testnet -``` +# Build docker image, set configs, keys, and install binary +# Error 1 (ignored) codes are okay here if you already have +# the keys and configs setup. If so you only have to `make local-image` +# in future runs :) +make setup-testnet -### Upload Contract +local-ic start self-ibc +``` -Make sure you are in the `rollchain` directory to begin interacting and uploading the contract to the chain. ```bash -CONTRACT_SOURCE=./validator-reviews-contract/artifacts/validator_NSERVICE_CONTRACT.wasm -rolld tx wasm store $CONTRACT_SOURCE --from=acc0 --gas=auto --gas-adjustment=2.0 --yes -# rolld q wasm list-code +RPC_1=`curl http://127.0.0.1:8080/info | jq -r .logs.chains[0].rpc_address` +RPC_2=`curl http://127.0.0.1:8080/info | jq -r .logs.chains[1].rpc_address` +echo "Using RPC_1=$RPC_1 and RPC_2=$RPC_2" + +CONTRACT_SOURCE=./nameservice-contract/artifacts/nameservice_contract.wasm +rolld tx wasm store $CONTRACT_SOURCE --from=acc0 --gas=auto --gas-adjustment=2.0 --yes --node=$RPC_1 +# rolld q wasm list-code --node=$RPC_1 + +rolld tx wasm store $CONTRACT_SOURCE --from=acc0 --gas=auto --gas-adjustment=2.0 --yes --node=$RPC_2 --chain-id=localchain-2 +# rolld q wasm list-code --node=$RPC_2 ``` -### Instantiate our Contract +### Instantiate our Contract on oth chains ```bash -rolld tx wasm instantiate 1 '{}' --no-admin --from=acc0 --label="reviews" \ - --gas=auto --gas-adjustment=2.0 --yes +rolld tx wasm instantiate 1 '{}' --no-admin --from=acc0 --label="ns-1" --gas=auto --gas-adjustment=2.0 --yes --node=$RPC_1 +rolld tx wasm instantiate 1 '{}' --no-admin --from=acc0 --label="ns-1" --gas=auto --gas-adjustment=2.0 --yes --node=$RPC_2 --chain-id=localchain-2 rolld q wasm list-contracts-by-creator roll1hj5fveer5cjtn4wd6wstzugjfdxzl0xpg2te87 +rolld q wasm list-contracts-by-creator roll1hj5fveer5cjtn4wd6wstzugjfdxzl0xpg2te87 --node=$RPC_2 NSERVICE_CONTRACT=roll14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjczpjh ``` -### Verify data +### Relayer connect ```bash -rolld q wasm state smart $NSERVICE_CONTRACT '{"validators":{}}' +# Import the testnet interaction helper functions +# for local-interchain +curl -s https://raw.githubusercontent.com/strangelove-ventures/interchaintest/main/local-interchain/bash/source.bash > ict_source.bash +source ./ict_source.bash + +API_ADDR="http://localhost:8080" + +# This will take a moment. +ICT_RELAYER_EXEC "$API_ADDR" "localchain-1" \ + "rly tx link localchain-1_localchain-2 --src-port wasm.${NSERVICE_CONTRACT} --dst-port=wasm.${NSERVICE_CONTRACT} --order unordered --version ns-1" ``` -## Write a review + +## Verify channels ```bash -MESSAGE='{"write_review":{"val_addr":"rollvaloper1hj5fveer5cjtn4wd6wstzugjfdxzl0xpmhf3p6","review":"hi reviewing."}}' -rolld tx wasm execute $NSERVICE_CONTRACT "$MESSAGE" --from=acc0 \ - --gas=auto --gas-adjustment=2.0 --yes +# app binary +rolld q ibc channel channels + +# relayer +ICT_RELAYER_EXEC "$API_ADDR" "localchain-1" "rly q channels localchain-1" ``` -### Verify the review +## Transaction against ```bash -rolld q wasm state smart $NSERVICE_CONTRACT '{"reviews":{"address":"rollvaloper1hj5fveer5cjtn4wd6wstzugjfdxzl0xpmhf3p6"}}' +# Set the name from chain 1 +MESSAGE='{"set_name":{"channel":"channel-1","name":"myname"}}' +rolld tx wasm execute $NSERVICE_CONTRACT "$MESSAGE" --from=acc0 --gas=auto --gas-adjustment=2.0 --yes + +# This will take a moment +# 'account sequence mismatch' errors are fine. +ICT_RELAYER_EXEC "$API_ADDR" "localchain-1" "rly tx flush" ``` -### Write a review for a non-validator +--- + +### Verify data ```bash -MESSAGE='{"write_review":{"val_addr":"NotAValidator","review":"hi this is a review"}}' +# query the name on chain 2, from chain 1 +rolld q wasm state smart $NSERVICE_CONTRACT '{"get_name":{"channel":"channel-1","wallet":"roll1hj5fveer5cjtn4wd6wstzugjfdxzl0xpg2te87"}}' --node=$RPC_2 -rolld tx wasm execute $NSERVICE_CONTRACT "$MESSAGE" --from=acc0 \ - --gas=auto --gas-adjustment=2.0 --yes +# dump contract state from the other chain +rolld q wasm state all $NSERVICE_CONTRACT --node=$RPC_2 ``` diff --git a/simapp/chains/README.md b/simapp/chains/README.md index 0f5d414a..2aac5006 100644 --- a/simapp/chains/README.md +++ b/simapp/chains/README.md @@ -2,6 +2,7 @@ RUN: - `make testnet` *(full setup: docker image, binary, keys, and ibc testnet start)* +- `spawn local-ic start testnet` *(IBC with CosmosHub)* - `spawn local-ic start testnet` *(Standalone start)* ## Documentation From 87cc79a9b94058f3d59e2451f155a2ebaaa927d1 Mon Sep 17 00:00:00 2001 From: Reece Williams Date: Fri, 11 Oct 2024 19:24:47 -0500 Subject: [PATCH 3/5] content --- .../09-ibc-cosmwasm.md | 111 ++++++++++++------ 1 file changed, 78 insertions(+), 33 deletions(-) diff --git a/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md b/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md index a6793dcb..176270f1 100644 --- a/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md +++ b/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md @@ -7,7 +7,7 @@ slug: /build/name-service-ibc-contract # IBC Name Service Contract -You will build a new IBC contract with [CosmWasm](https://cosmwasm.com), enabling the same features we just built out in the IBC module. +You will build a new IBC contract with [CosmWasm](https://cosmwasm.com), enabling the same features we just built out in the IBC module. While this is a part 3 of the series, it can be done standalone as it requires a new chain. It is a similar concept to the previous parts 1 and 2, but with a smart contract focus instead of a chain. ## Prerequisites - [System Setup](../01-setup/01-system-setup.md) @@ -16,7 +16,7 @@ You will build a new IBC contract with [CosmWasm](https://cosmwasm.com), enablin ## Setup the Chain -Build a new blockchain with CosmWasm enabled. THen generate a new module from the template for the reviews. +Build a new blockchain with CosmWasm enabled. ```bash GITHUB_USERNAME=rollchains @@ -29,18 +29,16 @@ spawn new cwchain \ --disabled=block-explorer \ --org=${GITHUB_USERNAME} - # move into the chain directory cd cwchain +# download latest dependencies go mod tidy ``` -Once the chain is started, continue on to the contract building steps - -## CosmWasm Build Contract +## Build CosmWasm Contract -CosmWasm has a template repository that is used to generate new contracts. A minimal contract will be built with the `nameservice-contract` name provided on a specific commit. +CosmWasm has a template repository that is used to generate new contracts. A minimal contract will be built with the `nameservice-contract` name provided on a [specific commit](https://github.com/CosmWasm/cw-template/commits/a2a169164324aa1b48ab76dd630f75f504e41d99/). ```bash cargo generate --git https://github.com/CosmWasm/cw-template.git \ @@ -49,14 +47,21 @@ cargo generate --git https://github.com/CosmWasm/cw-template.git \ -d minimal=true --tag a2a169164324aa1b48ab76dd630f75f504e41d99 ``` +Open the contract in your code editor now to begin adding the application logic. + +:::note Info +It is useful to install code rust extensions like [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) and [even better toml](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml) for an increased editing experience. +::: + ```bash +# open using vscode in the terminal code nameservice-contract/ ``` - +## Update Contract Dependencies +This version of the CosmWasm template has some outdated versions. Update these in the `Cargo.toml` file and add the "ibc3" capability (for IBC support). - ```toml title="Cargo.toml" [dependencies] # highlight-start @@ -68,31 +73,42 @@ cosmwasm-std = { version = "1.5.7", features = [ # highlight-end ``` +Update your local environment with the dependencies. + ```bash cargo update ``` ## Setup State +This Rust code defines the structure for a name service in a CosmWasm smart contract. It saves a map of all channels (outside chain connections) to a list of wallet address and their associated names. + ```rust title="src/state.rs" // highlight-start use std::collections::BTreeMap; use cw_storage_plus::Map; -// Wallet -> Name +// Pair the wallet address to the name a user provides. pub type WalletMapping = BTreeMap; +/// create a new empty wallet mapping for a channel. +/// useful if a channel is opened and we have no data yet pub fn new_wallet_mapping() -> WalletMapping { BTreeMap::new() } +/// Name Service maps for each channel saved to a storage object pub const NAME_SERVICE: Map = Map::new("nameservice"); // highlight-end ``` ## Setup Interactions +Now that the state is setup, focus on modeling the users interaction with the contract. Users should be able to set a name. This also requires an input for a "channel" since a contract could connect to multiple chains. It could be written in a way that a user could set it to all channels, but for simplicity, we will require a channel to be specified. Just as is set, a user should get the name with the same format: a channel and a wallet address. Then a new message type is added specifically for IBCExecution messages. This is the packet transfered over the network, between chains, and gives the ability to set a name elsewhere on its contract. + +The contract will call the IBCExecuteMsg when a user runs the ExecuteMsg.SendName function. This indirectly generates the packet and submits it for the user. + ```rust title="src/msg.rs" use cosmwasm_schema::{cw_serde, QueryResponses}; @@ -130,7 +146,7 @@ pub struct GetNameResponse { ## Contract Logic -Here are all the imports used in this. Replace your files top with these. +Here are all the imports used in this. Replace your files top. ```rust title="src/contract.rs" // highlight-start @@ -145,7 +161,9 @@ use cosmwasm_std::{to_json_binary, IbcMsg, IbcTimeout, StdError}; // highlight-end ``` - +Instantiate creates a new version of this contract that you control. Rather than being unimplemented, return a basic response saying it was Ok (successful) and add some extra logging metadata. + + ```rust title="src/contract.rs" #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( @@ -159,7 +177,10 @@ pub fn instantiate( } ``` - +The ExecuteMsg::SetName method is allowed to be interacted from anyone. Just like instantiate we return a new Ok response. This time an add_message function is added. This will generate the packet as the user interacts, performing a new action from a previous action. This uses the IbcMsg::SendPacket built in type to create it for the user. Notice the data field includes the IbcExecuteMsg::SetName we defined before. This is transferred to the other version of this contract on another chain and processed. + +If the packet is not picked up by a relayer service provider within a few minutes, the packet will become void and stop attempting execution on the other chain's contract. + ```rust title="src/contract.rs" #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( @@ -192,7 +213,8 @@ pub fn execute( } ``` - +The users name is not set, but it is only useful if you can also get said data. Read from the `NAME_SERVICE` storage Map defined in `state.rs`. Using may load grabs the data if the channel has a name set. If no channel is found (no users have set a name from this chain), it returns an error to the user requesting. If a channel of pairs is found, it loads them and checks if the wallet address requested is set in it. If it is, return what the wallets name is set to. If the user with this wallet did not set a name, return an error. + ```rust title="src/contract.rs" #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { @@ -216,7 +238,8 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { } ``` - +The contract will receive a packet and must run logic to process it. This is called the `try_set_name` method. It updates a given channel to include a new wallet. If the wallet already exists, it will overwrite the name. It then returns the users name back, or an error, for our future IBC logic to handle. + ```rust title="src/contract.rs" // highlight-start /// called on IBC packet receive in other chain @@ -238,7 +261,7 @@ pub fn try_set_name( ## Create Transaction acknowledgement -Create a new file, `ack.rs`, to handle the IBC ACK messages. +Create a new file, `ack.rs`, to handle the IBC ACK (acknowledgement) messages. This just returns back to the user if their interaction was a success or an error. ```bash touch src/ack.rs @@ -269,7 +292,11 @@ pub fn make_ack_fail(err: String) -> Binary { // highlight-end ``` -Add it to the lib.rs so the application can use it. +:::note Note +Rust has a lib.rs file that is the entry point for the Rust library. All files that are used must be mentioned here to have access to them. +::: + +Add the ack logic to the lib.rs so the application can use it. ```rust title="src/lib.rs" // highlight-next-line @@ -282,6 +309,10 @@ pub use crate::error::ContractError; ## Setup Errors +If a relayer or contract try to connect to an unlike protocol, the InvalidVersion error will be returned to the attempted actor. This contract only supports 1 protocol version across networks because it must speak the same "language". If you speak english while another person speaks spanish, your interactions are incompatible. Contracts are like this too. They verify their protocol version in a format like "ics-20" or "ns-1" first to make sure they can communicate. + +OrderedChannel is a type of flow control for network packets, or interactions. This tutorial uses unordered paths so any packet that times out or fails does not block future packets from going through. If a relayer tries to make this an ordered path, the contract returns this error to stop them from doing so. + ```rust title="src/error.rs" use cosmwasm_std::StdError; use thiserror::Error; @@ -297,15 +328,16 @@ pub enum ContractError { // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. // highlight-start - #[error("only unordered channels are supported")] - OrderedChannel {}, - #[error("invalid IBC channel version. Got ({actual}), expected ({expected})")] InvalidVersion { actual: String, expected: String }, + + #[error("only unordered channels are supported")] + OrderedChannel {}, // highlight-end } // highlight-start +// There is an IBC specific error that is never returned. #[derive(Error, Debug)] pub enum Never {} // highlight-end @@ -314,7 +346,7 @@ pub enum Never {} ## IBC Specific Logic -Create a new file `ibc.rs`. Add this to the lib.rs +Create a new file `ibc.rs`. Add this to the lib.rs. This is where our core IBC logic will go. ```bash touch src/ibc.rs @@ -333,9 +365,7 @@ pub mod state; pub use crate::error::ContractError; ``` -Now begin working on the IBC logic. - - +Place the following in the ibc.rs file. Import all the types needed, set the IBC version to "ns-1" to stand for "nameservice-1", and add the basic validation logic for the contract. You must ensure contracts that try to talk together are verified to work together. This is that logic. ```rust title="src/ibc.rs" // highlight-start @@ -397,7 +427,7 @@ pub fn validate_order_and_version( // highlight-end ``` - +The contract verifies data on an attempted open of a new connection. Ensure the contracts talk the same protocol language, and that all the validation basic logic is connect. Then when a channel is closed, clear the data from storage for it. It is very rare you would want to close a channel. ```rust title="src/ibc.rs" // highlight-start @@ -427,7 +457,7 @@ pub fn ibc_channel_close( // highlight-end ``` - +When a successful connection is made, the contract saves a new blank wallet mapping to the channel's unique id. 'channel-0' is the first. All future connections are channel-1, channel-2, etc. This is the first step in the IBC process. The contract is now ready to receive packets once the handler logic is put in place on receive. ```rust title="src/ibc.rs" // highlight-start @@ -451,7 +481,7 @@ pub fn ibc_channel_connect( // highlight-end ``` - +ibc_packet_receive handles incoming packets from already connected networks. The packet is forwarded to this contract and processed in `do_ibc_packet_receive`. It takes the channel and the packet data *(the IbcMsg::SetName sent out from the ExecuteMsg earlier)*, and tries to set the name on a wallet for this channel. If successful, it returns an acknowledgment of success. If not, it returns an acknowledgment of failure. The user will see this in their log event output. ```rust title="src/ibc.rs" // highlight-start @@ -495,7 +525,7 @@ pub fn do_ibc_packet_receive( // highlight-end ``` - +Sometimes after a failed acknowledgement the contract may want to rollback some data or make note of it for future reference. This contract is simple enough so no rollback or refunds are required. We just return a basic response to the user for both the ack or a timeout. Think of this similarly as a NoOp (no operation). ```rust title="src/ibc.rs" // highlight-start @@ -521,20 +551,22 @@ pub fn ibc_packet_timeout( ## Build Contract From Source +The contract can now be compiled from its source into the .wasm file. This is the binary executable that will be uploaded to the chain. + ```bash cargo-run-script optimize ``` - --- -### Upload Contract to both networks +### Start the chains and connect -Make sure you are in the `cwchain` directory to begin interacting and uploading the contract to the chain. +Make sure you are in the `cwchain` directory to begin interacting and uploading the contract to the chain. It is time to start the cosmwasm chain and launch a testnet that connects to itself. The `self-ibc` chain is automatically generated for you on the creation with spawn. It launches 2 of your networks, localchain-1 and localchain-2, and connects them with a relayer operator at startup. ```bash # Build docker image, set configs, keys, and install binary +# # Error 1 (ignored) codes are okay here if you already have # the keys and configs setup. If so you only have to `make local-image` # in future runs :) @@ -543,6 +575,9 @@ make setup-testnet local-ic start self-ibc ``` +### Store the Contract on both chains + +Get the [RPC ](https://www.techtarget.com/searchapparchitecture/definition/Remote-Procedure-Call-RPC) interaction addresses for each network from the local-interchain testnet API. Upload the contract source to both chains using the different RPC addresses. ```bash RPC_1=`curl http://127.0.0.1:8080/info | jq -r .logs.chains[0].rpc_address` @@ -557,7 +592,9 @@ rolld tx wasm store $CONTRACT_SOURCE --from=acc0 --gas=auto --gas-adjustment=2.0 # rolld q wasm list-code --node=$RPC_2 ``` -### Instantiate our Contract on oth chains +### Instantiate our Contract on both chains + +You can now create your contract from the source on each chain. ```bash rolld tx wasm instantiate 1 '{}' --no-admin --from=acc0 --label="ns-1" --gas=auto --gas-adjustment=2.0 --yes --node=$RPC_1 @@ -571,6 +608,8 @@ NSERVICE_CONTRACT=roll14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjczpj ### Relayer connect +The relayer must now connect the contracts together and create an IBC connection, link, between them. Use the Local-Interchain helper methods to connect the contracts across the chains. This command will take a second then show a bunch of logs. `Error context canceled` is fine to see. You will verify they were opened in the next step. + ```bash # Import the testnet interaction helper functions # for local-interchain @@ -587,6 +626,8 @@ ICT_RELAYER_EXEC "$API_ADDR" "localchain-1" \ ## Verify channels +Verify the channels were created. Query either with the application binary of the relayer itself. If you see both a channel-0 and channel-1 in your logs, it was a success. If you only see channel-0 re-run the above relayer exec tx link command. + ```bash # app binary rolld q ibc channel channels @@ -595,7 +636,9 @@ rolld q ibc channel channels ICT_RELAYER_EXEC "$API_ADDR" "localchain-1" "rly q channels localchain-1" ``` -## Transaction against +## Transaction interaction + +Using the ExecuteMsg::SetName method, set a name. This will be transferred to chain 2 behind the scenes. Flushing the relayer will force it to auto pick up pending IBC packets and transfer them across. Not running this may take up to 30 seconds for the relayer to automatically pick it up. ```bash # Set the name from chain 1 @@ -611,6 +654,8 @@ ICT_RELAYER_EXEC "$API_ADDR" "localchain-1" "rly tx flush" ### Verify data +After the packet is sent over the network, processed, and acknowledged *(something that can be done in <30 seconds)*, you can query the data on chain 2. You can also dump all the contract data out to get HEX and BASE64 encoded data for what the contract state storage looks like. + ```bash # query the name on chain 2, from chain 1 rolld q wasm state smart $NSERVICE_CONTRACT '{"get_name":{"channel":"channel-1","wallet":"roll1hj5fveer5cjtn4wd6wstzugjfdxzl0xpg2te87"}}' --node=$RPC_2 From e2167cd7f613eefa6f1ae5f6ea402fa81d5e7df3 Mon Sep 17 00:00:00 2001 From: Reece Williams Date: Fri, 11 Oct 2024 19:26:31 -0500 Subject: [PATCH 4/5] summary --- .../02-build-your-application/09-ibc-cosmwasm.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md b/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md index 176270f1..5b576a03 100644 --- a/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md +++ b/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md @@ -663,3 +663,15 @@ rolld q wasm state smart $NSERVICE_CONTRACT '{"get_name":{"channel":"channel-1", # dump contract state from the other chain rolld q wasm state all $NSERVICE_CONTRACT --node=$RPC_2 ``` + +## Summary + +You just build an IBC protocol using cosmwasm! It allowed you to set a name on another network entirely and securely with IBC. + +## What you Learned + +* Scaffolding an CosmWasm contract +* Adding business logic for an IBC request +* Implementing IBC in a contract +* Connecting two CosmWasm contracts with a custom IBC protocol +* Sending a packet from contract A to contract B From 8971d8db3d0c453a879b7bb170ef04a2b93d800a Mon Sep 17 00:00:00 2001 From: Reece Williams Date: Fri, 11 Oct 2024 19:26:58 -0500 Subject: [PATCH 5/5] fix: using < --- .../02-build-your-application/09-ibc-cosmwasm.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md b/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md index 5b576a03..5a797623 100644 --- a/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md +++ b/docs/versioned_docs/version-v0.50.x/02-build-your-application/09-ibc-cosmwasm.md @@ -654,7 +654,7 @@ ICT_RELAYER_EXEC "$API_ADDR" "localchain-1" "rly tx flush" ### Verify data -After the packet is sent over the network, processed, and acknowledged *(something that can be done in <30 seconds)*, you can query the data on chain 2. You can also dump all the contract data out to get HEX and BASE64 encoded data for what the contract state storage looks like. +After the packet is sent over the network, processed, and acknowledged *(something that can be done in less than 30 seconds)*, you can query the data on chain 2. You can also dump all the contract data out to get HEX and BASE64 encoded data for what the contract state storage looks like. ```bash # query the name on chain 2, from chain 1