Basic usage of cosmwasm and cw20
2024.3.9
2021.3.30
I participated in HackAtom RU for 2 weeks. I regretted not taking notes about the previous HackAtom V, so I noted the use of cosmwasm and cw20.
Each client of the blockchains using Cosmos SDK
- gaiad: for Cosmos Hub (and the blockchains built by Cosmos SDK)
- wasmd: for CosmWasm
- secretcli: for SecretNetwork
Since different blockchains support different client versions, it is necessary to select a version that matches the testnet to be used during development.
// Install gaiad
$ git clone -b <latest-release-tag> https://github.com/cosmos/gaia.git
$ cd gaia && make install
// Install wasmd
$ git clone -b <latest-release-tag> https://github.com/cosmwasm/wasmd.git
$ cd wasmd && make install && make test
After the update, gaiacli
and wasmcli
have been integrated into gaiad
and wasmd
. Secretcli` remains independent.
Set environment variables for ease of operation later. This example assumes that the Musselnet test net is used.
$ nano ~/.cosmwasm-env
$ source ~/.cosmwasm-env
The settings are as follows:
export MUSSELNET_NODE=(--node https://rpc.musselnet.cosmwasm.com:443)
export MUSSELNET_TXFLAG=($MUSSELNET_NODE --chain-id musselnet-4 --gas-prices 0.025ucosm --gas auto --gas-adjustment 1.3)
Control an account
// Create a new account
$ wasmd keys add fred
// Show the address of the account named "fred"
$ wasmd keys show -a fred
// Details of the account on musselnet-4
// "q" is the shorthand of "query"
$ wasmd q account $(wasmd keys show -a fred) $MUSSELNET_NODE
// Get the balance of the native token on musselnet-4
$ wasmd q bank balances $(wasmd keys show -a fred) $MUSSELNET_NODE
Faucet
Different chains use different methods of faucet, some use curl
, some use REST, and so on.
On Musselnet, the CosmWasm testnet, you can receive a fee token by POSTing JSON with curl
; you must ask for the staking token in discord.
$ JSON=$(jq -n --arg addr $(wasmd keys show -a fred) '{"denom":"umayo","address":$addr}')
// "-H" is the shorthand of "--header"
$ curl -X POST -H "Content-Type: application/json" --data "$JSON" https://faucet.musselnet.cosmwasm.com/credit
Usage of a contract
Compile with optimizer
When deploying a contract to the testnet, it must be compiled using the Optimizer container. Simply running cargo wasm
will not be accepted.
Optimizers are prepared differently for each contract, and this time we used cosmwasm/workspace-optimizer since we used cosmwasm-plus as a base.
The OS/ARCH of the Docker container is linux/amd64, so M1 Mac (with arm64-enabled Docker Desktop RC 2) could not run it with an error (Rosetta 2 is also not supported). Please use Intel CPU or something.
In the case of cosmwasm-plus, the command is as follows
$ 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/workspace-optimizer:0.10.7
If you run it in the root directory, it will compile for all directories under . /contracts/
, it will compile all directories under . /artifacts/
. The rust-optimizer used in cosmwasm-examples has a part to describe what to compile, so it is possible to process only specific contracts, but it does not seem to be possible with this command. It is not possible to do so with this command. It takes a fair amount of time.
Store
After compilation is complete, save the contract on the chain.
$ RES=$(wasmd tx wasm store ./artifacts/CONTRACT.wasm --from fred $MUSSELNET_TXFLAG -y)
gas used: 1234567
$ CODE_ID=$(echo $RES | jq -r '.logs[0].events[0].attributes[-1].value')
CODE_ID
is a number assigned in the order of contract upload.
Deployment is not yet complete at this point. Because the contract has not been initialized, no contract address has been assigned.
Instantiate
Initializes a contract and makes it available for use. The initialized contract is specified by its ID (CODE_ID
).
$ INIT='{}'
$ wasmd tx wasm instantiate $CODE_ID $INIT --label 'hoge' $MUSSELNET_TXFLAG -y
INIT
message must contain a message for the contract. If the message is empty, no value is passed, as shown above. If you want to transfer a native token at initialization, use --amount
. The contents of the label can be specified freely.
Always use the JSON format when passing messages to contracts, not just here. If you use double quotation marks when entering JSON as a string in the terminal, you need to escape them when specifying the string inside JSON. When single quotation marks are used, there is (almost) no problem (because only double quotation marks can be used to identify key and value in JSON).
Once initialized, the contract address can be obtained as follows.
$ CONTRACT=$(wasmd query wasm list-contract-by-code $CODE_ID $MUSSELNET_NODE -o json | jq -r '.contract_infos[0].address')
Execute
$ EXECUTE='{}'
$ wasmd tx wasm execute $CONTRACT $EXECUTE --from fred $MUSSELNET_TXFLAG -y
EXECUTE
message is changed according to the contract's runtime message. Again, use --amount
, etc. as needed for native tokens.
Query
$ QUERY='{}'
$ wasmd query wasm contract-state smart $CONTRACT $QUERY $MUSSELNET_NODE
Since the query does not require gas, the parameters set in MUSSELNET_TXFLAG
and the execution account specified by --from
are not necessary.
CW20トークン発行・操作
Try to mint CW20 token using cw20-base. You can check the require parameters in ./cw20-base/src/msg.rs.
$ git clone https://github.com/CosmWasm/cosmwasm-plus.git && cd cosmwasm-plus
// Compile at the root directory
$ 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/workspace-optimizer:0.10.7
// Store the code
$ RES=$(wasmd tx wasm store ./artifacts/cw20-base.wasm --from fred $MUSSELNET_TXFLAG -y)
gas used: 1234567
$ CODE_ID=$(echo $RES | jq -r '.logs[0].events[0].attributes[-1].value')
// Instantiate
$ INIT=$(jq -n --arg fred $(wasmd keys show -a fred) '{"name":"test","symbol":"TST","decimals":6,"initial_balances":{"address":$fred,"amount":"10000"}}')
$ wasmd tx wasm instantiate $CODE_ID $INIT --label 'test' $MUSSELNET_TXFLAG -y
$ CONTRACT=$(wasmd query wasm list-contract-by-code $CODE_ID $MUSSELNET_NODE -o json | jq -r '.contract_infos[0].address')
// Query fred's balance of the CW20 token
$ QUERY=$(jq -n --arg fred $(wasmd keys show -a fred) '{"balance":{"address":$fred}}')
$ wasmd query wasm contract-state smart $CONTRACT $QUERY $MUSSELNET_NODE
// Execute the contract
$ EXECUTE='{"transfer":{"recipient":"wasmADDRESSOFRECIPIENT","amount":"1000"}}'
$ wasmd tx wasm execute $CONTRACT $EXECUTE --from fred $MUSSELNET_TXFLAG -y
The key in the message is CamelCase, but when input in JSON, snake_case is used. This is probably due to the following sentence.
#[serde(rename_all = "snake_case")]
For CW20 remittance, only amount
is required, while denom
is required for native token remittance, because the CW20 contract is specified and denom
is necessarily fixed.
Note that amount
is declared as Uint128
type on the contract, so it must be passed as a string when given as JSON. Note that the type of decimals
given as a number is u8
.
Executing a contract using CW20
When a native token is transferred to a contract for processing, it can be passed as --amount
, which is processed as info.sent_funds
in the contract. In this case, the transaction is handled as info.sent_funds
in the contract.
However, this is not the case with CW20. If you want to handle an already issued CW20 in another contract, use the CW20 contract's Send
message to throw a message to the other contract.
For example, to use a CW20-ICS20 contract to transfer CW20 to another Cosmos blockchain via an IBC, the following is used.
$ MSG=$(jq -n --arg fred $(gaiad keys show -a fred) '{"channel":"channel-0","remote_address":$fred,"timeout":3600}' | base64)
$ EXECUTE=$(jq -n --arg msg $MSG '{"send":{"contract":"wasmALTERNATIVECONTRACT","amount":"1000","msg":$msg}}')
$ wasmd tx wasm execute $CONTRACT $EXECUTE --from fred $MUSSELNET_TXFLAG -y