๐๏ธ CosmWasm rollup
CosmWasm is a smart contracting platform built for the Cosmos ecosystem by making use of WebAssembly (Wasm) to build smart contracts for Cosmos-SDK. In this tutorial, we will be exploring how to integrate CosmWasm with Celestia's data availability layer using Rollkit.
This tutorial will explore developing with Rollkit, which is still in Alpha stage. If you run into bugs, please write a Github Issue ticket or let us know in our Telegram.
The script for this tutorial is built for Celestia's Blockspacerace testnet. If you choose to use Mocha testnet or Arabica devnet, you will need to modify the script manually.
You can learn more about CosmWasm here.
In this tutorial, we will going over the following:
- Setting up your dependencies for your CosmWasm smart contracts
- Setting up Rollkit on CosmWasm
- Instantiate a local network for your CosmWasm chain connected to Celestia
- Deploying a Rust smart contract to CosmWasm chain
- Interacting with the smart contract
The smart contract we will use for this tutorial is one provided by the CosmWasm team for Nameservice purchasing.
You can check out the contract here.
How to write the Rust smart contract for Nameservice is outside the scope of this tutorial. In the future we will add more tutorials for writing CosmWasm smart contracts for Celestia.
๐ป CosmWasm dependency installationsโ
๐ ๏ธ Environment setupโ
For this tutorial, we will be using curl
and jq
as helpful
tools. You can follow the guide on installing them
here.
๐ Golang dependencyโ
The Golang version used for this tutorial is v1.18+
You can install Golang by following our tutorial here.
๐ฆ Rust installationโ
๐จ Rustupโ
First, before installing Rust, you would need to install rustup
.
On Mac and Linux systems, here are the commands for installing it:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
You will see a note similar to below after installing Rust:
Rust is installed now. Great!
To get started you may need to restart your current shell.
This would reload your PATH environment variable to include
Cargo's bin directory ($HOME/.cargo/bin).
To configure your current shell, run:
source "$HOME/.cargo/env"
If you don't follow the guidance, you won't be able to continue with the tutorial!
After installation, follow the commands here to setup Rust.
rustup default stable
cargo version
rustup target list --installed
rustup target add wasm32-unknown-unknown
Your output should look similar to below:
info: using existing install for 'stable-aarch64-apple-darwin'
info: default toolchain set to 'stable-aarch64-apple-darwin'
stable-aarch64-apple-darwin unchanged - rustc 1.65.0 (897e37553 2022-11-02)
cargo 1.65.0 (4bc8f24d3 2022-10-20)
aarch64-apple-darwin
info: downloading component 'rust-std' for 'wasm32-unknown-unknown'
info: installing component 'rust-std' for 'wasm32-unknown-unknown'
๐ณ Docker installationโ
We will be using Docker later in this tutorial for compiling a smart contract to use a small footprint. We recommend installing Docker on your machine.
Examples on how to install it on Linux are found here. Find the right instructions specific for your OS here.
๐ป Wasmd installationโ
Here, we are going to pull down the wasmd
repository and replace Tendermint
with Rollkit. Rollkit is a drop-in replacement for Tendermint that allows
Cosmos-SDK applications to connect to Celestia's data availability network.
git clone https://github.com/CosmWasm/wasmd.git
cd wasmd
git fetch --tags
git checkout v0.27.0
go mod edit -replace github.com/cosmos/cosmos-sdk=github.com/rollkit/[email protected]
go mod edit -replace github.com/tendermint/tendermint=github.com/celestiaorg/[email protected]
go mod tidy -compat=1.17
go mod download
make install
โจ Celestia nodeโ
You will need a light node running with test tokens on Blockspace race testnet in order to complete this tutorial. Please complete the tutorial here, or start up your node.
๐ Setting up your environment for CosmWasm on Celestiaโ
Now the wasmd
binary is built, we need to setup a local network
that communicates between wasmd
and Rollkit.
๐๏ธ Initializing CosmWasm rollup with a bash scriptโ
We have a handy init.sh
found in this repo
here.
We can copy it over to our directory with the following commands:
# From inside the `wasmd` directory
wget https://raw.githubusercontent.com/rollkit/docs/main/docs/scripts/cosmwasm/init.sh
This copies over our init.sh
script to initialize our
CosmWasm rollup.
You can view the contents of the script to see how we initialize the CosmWasm Rollup.
You can initialize the script with the following command:
bash init.sh
With that, we have kickstarted our wasmd
network!
๐ Optional: see what's inside the scriptโ
You can skip this section, but it is important to know how Rollkit is initializing the cosmwasm rollup.
Here are the contents of the script:
#!/bin/sh
VALIDATOR_NAME=validator1
CHAIN_ID=celeswasm
KEY_NAME=celeswasm-key
TOKEN_AMOUNT="10000000000000000000000000uwasm"
STAKING_AMOUNT=1000000000uwasm
CHAINFLAG="--chain-id ${CHAIN_ID}"
TXFLAG="--chain-id ${CHAIN_ID} --gas-prices 0uwasm --gas auto --gas-adjustment 1.3"
NAMESPACE_ID=$(openssl rand -hex 8)
echo $NAMESPACE_ID
DA_BLOCK_HEIGHT=$(curl https://rpc-mocha.pops.one/block | jq -r '.result.block.header.height')
echo $DA_BLOCK_HEIGHT
rm -rf "$HOME"/.wasmd
wasmd tendermint unsafe-reset-all
wasmd init $VALIDATOR_NAME --chain-id $CHAIN_ID
sed -i'' -e 's/^minimum-gas-prices *= .*/minimum-gas-prices = "0uwasm"/' "$HOME"/.wasmd/config/app.toml
sed -i'' -e '/\[api\]/,+3 s/enable *= .*/enable = true/' "$HOME"/.wasmd/config/app.toml
sed -i'' -e "s/^chain-id *= .*/chain-id = \"$CHAIN_ID\"/" "$HOME"/.wasmd/config/client.toml
sed -i'' -e '/\[rpc\]/,+3 s/laddr *= .*/laddr = "tcp:\/\/0.0.0.0:26657"/' "$HOME"/.wasmd/config/config.toml
sed -i'' -e 's/"time_iota_ms": "1000"/"time_iota_ms": "10"/' "$HOME"/.wasmd/config/genesis.json
sed -i'' -e 's/bond_denom": ".*"/bond_denom": "uwasm"/' "$HOME"/.wasmd/config/genesis.json
sed -i'' -e 's/mint_denom": ".*"/mint_denom": "uwasm"/' "$HOME"/.wasmd/config/genesis.json
wasmd keys add $KEY_NAME --keyring-backend test
wasmd add-genesis-account $KEY_NAME $TOKEN_AMOUNT --keyring-backend test
wasmd gentx $KEY_NAME $STAKING_AMOUNT --chain-id $CHAIN_ID --keyring-backend test
wasmd start --rollkit.aggregator true --rollkit.da_layer celestia --rollkit.da_config='{"base_url":"http://localhost:26659","timeout":60000000000,"fee":6000,"gas_limit":6000000}' --rollkit.namespace_id $NAMESPACE_ID --rollkit.da_start_height $DA_BLOCK_HEIGHT
๐ Contract deployment on CosmWasm with Rollkitโ
๐ค Compile the smart contractโ
In a new terminal instance, we will run the following commands to pull down the Nameservice smart contract and compile it:
git clone https://github.com/InterWasm/cw-contracts
cd cw-contracts
cd contracts/nameservice
cargo wasm
The compiled contract is outputted to:
target/wasm32-unknown-unknown/release/cw_nameservice.wasm
.
๐งช Unit testsโ
If we want to run tests, we can do so with the following command in the
~/cw-contracts/contracts/nameservice
directory:
cargo unit-test
๐๏ธ Optimized smart contractโ
Because we are deploying the compiled smart contract to wasmd
,
we want it to be as small as possible.
The CosmWasm team provides a tool called rust-optimizer
which we need
Docker for in order to compile.
- AMD
- ARM
AMD Machinesโ
Run the following command in the ~/cw-contracts/contracts/nameservice
directory:
sudo docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/rust-optimizer:0.12.6
This will place the optimized Wasm bytecode at artifacts/cw_nameservice.wasm
.
ARM Machinesโ
Run the following command in the ~/cw-contracts/contracts/nameservice
directory:
sudo docker run --platform linux/arm64 --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/rust-optimizer-arm64:0.12.8
This will place the optimized Wasm bytecode at artifacts/cw_nameservice-aarch64.wasm
.
๐ Contract deploymentโ
Let's now deploy our smart contract!
- AMD
- ARM
AMD Machinesโ
Run the following in the ~/cw-contracts/contracts/nameservice
directory:
TX_HASH=$(wasmd tx wasm store artifacts/cw_nameservice.wasm --from celeswasm-key --keyring-backend test --chain-id celeswasm --gas-prices 0uwasm --gas auto --gas-adjustment 1.3 --node http://127.0.0.1:26657 --output json -y | jq -r '.txhash') && echo $TX_HASH
ARM Machinesโ
Run the following in the ~/cw-contracts/contracts/nameservice
directory:
TX_HASH=$(wasmd tx wasm store artifacts/cw_nameservice-aarch64.wasm --from celeswasm-key --keyring-backend test --chain-id celeswasm --gas-prices 0uwasm --gas auto --gas-adjustment 1.3 --node http://127.0.0.1:26657 --output json -y | jq -r '.txhash') && echo $TX_HASH
This will get you the transaction hash for the smart contract deployment. Given we are using Rollkit, there will be a delay on the transaction being included due to Rollkit waiting on Celestia's data availability layer to confirm the block has been included before submitting a new block.
If you run into errors with variables on the previous command,
or commands in the remainder of the tutorial, cross-reference
the variables in the command with the variables in the init.sh
script.
๐ Contract interaction on CosmWasm with Celestiaโ
In the previous steps, we have stored out contract's tx hash in an environment variable for later use.
Because of the longer time periods of submitting transactions via Rollkit due to waiting on Celestia's data availability layer to confirm block inclusion, we will need to query our tx hash directly to get information about it.
๐ Contract queryingโ
Let's start by querying our transaction hash for its code ID:
CODE_ID=$(wasmd query tx --type=hash $TX_HASH --chain-id celeswasm --node http://127.0.0.1:26657 --output json | jq -r '.logs[0].events[-1].attributes[0].value')
echo $CODE_ID
This will give us back the Code ID of the deployed contract.
In our case, since it's the first contract deployed on our local network,
the value is 1
.
Now, we can take a look at the contracts instantiated by this Code ID:
wasmd query wasm list-contract-by-code $CODE_ID --chain-id celeswasm --node http://127.0.0.1:26657 --output json
We get the following output:
{"contracts":[],"pagination":{"next_key":null,"total":"0"}}
๐ Contract instantiationโ
We start instantiating the contract by writing up the following INIT
message
for nameservice contract. Here, we are specifying that purchase_price
of a name
is 100uwasm
and transfer_price
is 999uwasm
.
INIT='{"purchase_price":{"amount":"100","denom":"uwasm"},"transfer_price":{"amount":"999","denom":"uwasm"}}'
wasmd tx wasm instantiate $CODE_ID "$INIT" --from celeswasm-key --keyring-backend test --label "name service" --chain-id celeswasm --gas-prices 0uwasm --gas auto --gas-adjustment 1.3 -y --no-admin --node http://127.0.0.1:26657
๐ Contract interactionโ
Now that we instantiated it, we can interact further with the contract:
wasmd query wasm list-contract-by-code $CODE_ID --chain-id celeswasm --output json --node http://127.0.0.1:26657
CONTRACT=$(wasmd query wasm list-contract-by-code $CODE_ID --chain-id celeswasm --output json --node http://127.0.0.1:26657 | jq -r '.contracts[-1]')
echo $CONTRACT
wasmd query wasm contract --node http://127.0.0.1:26657 $CONTRACT --chain-id celeswasm
wasmd query bank balances --node http://127.0.0.1:26657 $CONTRACT --chain-id celeswasm
This allows us to see the contract address, contract details, and bank balances.
Your output will look similar to below:
{"contracts":["wasm14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s0phg4d"],"pagination":{"next_key":null,"total":"0"}}
wasm14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s0phg4d
address: wasm14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s0phg4d
contract_info:
admin: ""
code_id: "1"
created: null
creator: wasm1y9ceqvnsnm9xtcdmhrjvv4rslgwfzmrzky2c5z
extension: null
ibc_port_id: ""
label: name service
balances: []
pagination:
next_key: null
total: "0"
Now, let's register a name to the contract for our wallet address:
REGISTER='{"register":{"name":"fred"}}'
wasmd tx wasm execute $CONTRACT "$REGISTER" --amount 100uwasm --from celeswasm-key --chain-id celeswasm --gas-prices 0uwasm --gas auto --gas-adjustment 1.3 --node http://127.0.0.1:26657 --keyring-backend test -y
Your output will look similar to below:
DEIP --keyring-backend test -y
gas estimate: 167533
code: 0
codespace: ""
data: ""
events: []
gas_used: "0"
gas_wanted: "0"
height: "0"
info: ""
logs: []
raw_log: '[]'
timestamp: ""
tx: null
txhash: C147257485B72E7FFA5FDB943C94CE951A37817554339586FFD645AD2AA397C3
If you try to register the same name again, you'll see an expected error:
Error: rpc error: code = Unknown desc = rpc error: code = Unknown desc = failed to execute message; message index: 0: Name has been taken (name fred): execute wasm contract failed [CosmWasm/wasmd/x/wasm/keeper/keeper.go:364] With gas wanted: '0' and gas used: '123809' : unknown request
Next, query the owner of the name record:
NAME_QUERY='{"resolve_record": {"name": "fred"}}'
wasmd query wasm contract-state smart $CONTRACT "$NAME_QUERY" --chain-id celeswasm --node http://127.0.0.1:26657 --output json
You'll see the owner's address in a JSON response:
{"data":{"address":"wasm1y9ceqvnsnm9xtcdmhrjvv4rslgwfzmrzky2c5z"}}
With that, we have instantiated and interacted with the CosmWasm nameservice smart contract using Celestia!