Hello World
As always, the goal of this hello world exercise is to give you a feel of the technology, but also to superficially experience how the concepts you learned map to something more tangible. Don't hesitate to expand the collapsed sections if you want to dive deep on certain points, with a view to better understand the mechanics of it.
These first steps take inspiration from the CosmWasm docs' hello world, and from the Cosmos SDK's basic tutorial. The difference is that after you have downloaded all the packages and dependencies, you no longer need access to other online resources.
It offers two tracks, you can either do all your compilations and running natively on your computer, or achieve the same in Docker. If you choose the Docker path, that allows you to hold off installing anything other than Docker.
What will happen
Here are the steps you are going to complete:
- Build the blockchain code.
- Create a running Cosmos blockchain:
- With a single running node.
- With CosmWasm installed
- Compile a smart contract code.
- Store the code and deploy a smart contract instance.
- Interact with it.
Let's get started.
Install pre-requisites. Either on your computer, or install Docker. Including JQ.
Build Your blockchain code
Clone wasmd, which is the simd of CosmWasm, at v0.53.2.
git clone https://github.com/CosmWasm/wasmd --branch v0.53.2
cd wasmdBuild the blockchain application.
make builddocker build --tag wasmd:0.53.2 .With this done, .build/wasmd or Docker's wasmd:0.53.2 is your blockchain executable.
Prepare your blockchain
After you have compiled your blockchain code, and before you can turn your focus to CosmWasm, you need a running blockchain. This step has you prepare the blockchain application, test keys and a genesis file.
If you have previously used wasmd, you may already have data in your ~/.wasmd folder. It is typically safe to erase the config sub-folder. But if you are a validator on one of the wasmd networks, this exercise is not safe, so stop right there or proceed at your own risks.
The Docker track also uses your local folder, by sharing it as a volume with the flag -v $HOME/.wasmd:/root/.wasmd, so that you can switch to the local track at a later stage. Make space in the ~/.wasmd folder:
rm -rf ~/.wasmd/config \
~/.wasmd/data \
~/.wasmd/wasmInitialize the blockchain application's genesis file and other configuration files.
./build/wasmd init validator-1 \
--chain-id learning-chain-1docker run --rm -it \
-v $HOME/.wasmd:/root/.wasmd \
wasmd:0.53.2 \
wasmd init validator-1 \
--chain-id learning-chain-1If you are curious, you can see what was created in ~/.wasmd.
Then you can add a convenient but low-safety key for Alice, who shall become the validator operator.
./build/wasmd keys add alice \
--keyring-backend testdocker run --rm -it \
-v $HOME/.wasmd:/root/.wasmd \
wasmd:0.53.2 \
wasmd keys add alice \
--keyring-backend testMake a note of the seed phrase so that you can reuse it in a Node.js REPL console.
To become a validator, Alice needs an initial balance in the staking token. Give her one:
./build/wasmd genesis \
add-genesis-account alice 100000000stake \
--keyring-backend testdocker run --rm -it \
-v $HOME/.wasmd:/root/.wasmd \
wasmd:0.53.2 \
wasmd genesis \
add-genesis-account alice 100000000stake \
--keyring-backend testHave Alice create and sign the token-staking transaction that will make Alice an initial validator.
./build/wasmd genesis \
gentx alice 70000000stake \
--keyring-backend test \
--chain-id learning-chain-1docker run --rm -it \
-v $HOME/.wasmd:/root/.wasmd \
wasmd:0.53.2 \
wasmd genesis \
gentx alice 70000000stake \
--keyring-backend test \
--chain-id learning-chain-1The system tells you that a file has been created with a signed transaction in it. Add this signed transaction to the genesis file so that Alice starts indeed as a validator.
./build/wasmd genesis collect-gentxsdocker run --rm -it \
-v $HOME/.wasmd:/root/.wasmd \
wasmd:0.53.2 \
wasmd genesis collect-gentxsThe blockchain preparation is now done, and you need not redo the above steps if you restart this exercise at a later date.
Run your blockchain
If you stopped wasmd earlier, you can come back here and restart wasmd where you left it off.
Run Alice's validating node to start the blockchain system and to interact with it.
./build/wasmd startdocker run --rm -it \
--name val-alice-1 \
-v $HOME/.wasmd:/root/.wasmd \
wasmd:0.53.2 \
wasmd startYour node is now running and ready to receive smart contracts.
Compile your smart contract
With a running chain, you can start interacting with the CosmWasm module. First you are going to download and compile a smart contract.
We will use the same nameservice smart contract of the long running exercise but at the intermediate add-first-library branch. In another terminal, start by cloning it and changing the directory.
git clone https://github.com/b9lab/cw-my-nameservice --branch add-first-library
cd cw-my-nameservice/contracts/nameserviceAt this version of the contract, it compiles with Rust v1.80.1 to the Wasm target.
rustup install 1.80.1
rustup target add wasm32-unknown-unknown --toolchain 1.80.1
RUSTFLAGS='-C link-arg=-s' cargo +1.80.1 wasmdocker run --rm -it \
-v $(pwd):/root \
-w /root \
rust:1.80.1 \
sh -c "rustup target add wasm32-unknown-unknown && \
RUSTFLAGS='-C link-arg=-s' cargo +1.80.1 wasm"The compiled WebAssembly is located relative to the contract dir in ./target/wasm32-unknown-unknown/release/cw_my_nameservice.wasm. The file ends up at about 221 KB in size. In fact, the RUSTFLAGS='-C link-arg=-s' flag is there to reduce its size, always a concern in blockchain. You can remove the flag as a test, and you should see that it then ends up at 1.5 MB in size.
Copy the file to the ~/.wasmd so as to reuse it when storing the code on-chain. Make the folder if it is missing:
mkdir -p $HOME/.wasmd/wasm/code
cp $(pwd)/target/wasm32-unknown-unknown/release/cw_my_nameservice.wasm \
$HOME/.wasmd/wasm/code/cw_my_nameservice.wasmNote that the ~/.wasmd path is also accessible by the Docker container running the blockchain app, which makes it convenient for this exercise.
Store your contract code
With the bytecode of the smart contract ready, you are about to interact with the running chain again. Return to the wasmd directory, in a new shell.
Start with a simple initial CosmWasm query to see what code, if any, has already been stored.
./build/wasmd query wasm list-codedocker exec val-alice-1 \
wasmd query wasm list-codeAs expected, it returns:
code_infos: []
pagination: ...It is time to store your first CosmWasm code with the tx wasm store command. Alice, who owns stake tokens, can do it.
./build/wasmd tx wasm store $HOME/.wasmd/wasm/code/cw_my_nameservice.wasm \
--from alice --keyring-backend test \
--gas-prices 0.25stake --gas auto --gas-adjustment 1.3 \
--chain-id learning-chain-1 \
--yes --output json --broadcast-mode syncdocker exec val-alice-1 \
wasmd tx wasm store /root/.wasmd/wasm/code/cw_my_nameservice.wasm \
--from alice --keyring-backend test \
--gas-prices 0.25stake --gas auto --gas-adjustment 1.3 \
--chain-id learning-chain-1 \
--yes --output json --broadcast-mode syncWith --broadcast-mode sync, the command sends a transaction, but does not wait for it to be confirmed. Instead, you get succinct information, including the transaction hash:
{"height":"0","txhash":"34087EB0B74233E7E3C3AA9CE6EFCB4279130AF1C2BCAE992DD1E1D1775D02ED","codespace":"","code":0,"data":"","raw_log":"","logs":[],"info":"","gas_wanted":"0","gas_used":"0","tx":null,"timestamp":"","events":[]}Make a note of this txhash, such as:
ns_store_txhash=34087EB0B74233E7E3C3AA9CE6EFCB4279130AF1C2BCAE992DD1E1D1775D02EDWith your specific value.
Verify your stored code
The newly stored code has a newly created id that you need to know in order to use it. The authoritative way is to retrieve the code information from the transaction's events itself. The event of interest has the type: "store_code":
./build/wasmd query tx $ns_store_txhash --output json \
| jq '.events[] | select(.type == "store_code")'docker exec val-alice-1 \
wasmd query tx $ns_store_txhash --output json \
| jq '.events[] | select(.type == "store_code")'Which returns something like:
{
"type": "store_code",
"attributes": [
{
"key": "code_checksum",
"value": "98f9924c5fbe94dd6ad24d71f2352593e54aac6aabcfaa9b1bf000f64b33992d",
"index": true
},
{
"key": "code_id",
"value": "1",
"index": true
},
{
"key": "msg_index",
"value": "0",
"index": true
}
]
}There is a code id, predictably at 1, and a code checksum.
The msg_index is here to assist you with identifying which code is which, in the rare case where you store two or more codes in a single transaction.
Make a note of the code id as you will use it during instantiation:
ns_code_id=$(./build/wasmd query tx $ns_store_txhash --output json \
| jq -r '.events[] | select(.type == "store_code") .attributes[] | select(.key == "code_id") .value')ns_code_id=$(docker exec val-alice-1 \
wasmd query tx $ns_store_txhash --output json \
| jq -r '.events[] | select(.type == "store_code") .attributes[] | select(.key == "code_id") .value')Does the code checksum match? Let's check:
sha256sum $HOME/.wasmd/wasm/code/cw_my_nameservice.wasmdocker exec val-alice-1 \
sha256sum /root/.wasmd/wasm/code/cw_my_nameservice.wasmYou should see the same value as the one emitted in the event.
Deploy your contract instance
Now you have your bytecode stored on-chain, but no smart contract has been deployed using this code. You can confirm this with:
./build/wasmd query wasm list-contract-by-code $ns_code_iddocker exec val-alice-1 \
wasmd query wasm list-contract-by-code $ns_code_idThis returns:
contracts: []
pagination: ...Time to instantiate your first CosmWasm smart contract. The constructor requires a specific message:
Which has to be serialized as JSON. The minter is the account that will be allowed to register new names. To make it easy, you pick Alice as the minter. Get her address:
alice=$(./build/wasmd keys show alice \
--keyring-backend test \
--address)alice=$(docker run --rm -i \
-v $HOME/.wasmd:/root/.wasmd \
wasmd:0.53.2 \
wasmd keys show alice \
--keyring-backend test \
--address | tr -d '\r')The next action is to prepare the instantiate message by setting the right value:
ns_init_msg_1='{"minter":"'$alice'"}'With the message ready, you can send the comand to instantiate your first smart contract:
./build/wasmd tx wasm instantiate $ns_code_id "$ns_init_msg_1" \
--label "name service" --no-admin \
--from alice --keyring-backend test \
--chain-id learning-chain-1 \
--gas-prices 0.25stake --gas auto --gas-adjustment 1.3 \
--yesdocker exec val-alice-1 \
wasmd tx wasm instantiate $ns_code_id "$ns_init_msg_1" \
--label "name service" --no-admin \
--from alice --keyring-backend test \
--chain-id learning-chain-1 \
--gas-prices 0.25stake --gas auto --gas-adjustment 1.3 \
--yesNote that the meat of the command is instantiate $ns_code_id "$ns_init_msg_1", which would read as instantiate 1 "{"minter":"wasm1tev6xt4pgrnjpxwmvv7jrl8ph4x47wg5vcd0as"}".
Once again, you get a transaction hash. Make a note of it with your own hash, for instance:
ns_instantiate_txhash_1=9881879B2A7663638D6DA81D6F9ECC7DBF8AA12B105817B06A761749A22764E1Retrieve your contract address
At this stage, what is important is the address at which your contract instance resides. This is the address you will use to interact with your instantiated contract. The authoritative way to get this information is to get it from the events, more precisely at the event of type "instantiate":
./build/wasmd query tx $ns_instantiate_txhash_1 \
--output json | jq '.events[] | select(.type == "store_code") .attributes[] | select(.key == "code_id") .value'docker exec val-alice-1 \
wasmd query tx $ns_instantiate_txhash_1 \
--output json | jq '.events[] | select(.type == "instantiate")'This returns something like:
{
"type": "instantiate",
"attributes": [
{
"key": "_contract_address",
"value": "wasm14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s0phg4d",
"index": true
},
{
"key": "code_id",
"value": "1",
"index": true
},
{
"key": "msg_index",
"value": "0",
"index": true
}
]
}Here too, msg_index assists you when you have more than one instantiation in one transaction. Your smart contract address is wasm14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s0phg4d, which you can retrieve with:
ns_addr1=$(./build/wasmd query tx $ns_instantiate_txhash_1 \
--output json | jq -r '.events[] | select(.type == "instantiate") .attributes[] | select(.key == "_contract_address") .value')ns_addr1=$(docker exec val-alice-1 \
wasmd query tx $ns_instantiate_txhash_1 \
--output json | jq -r '.events[] | select(.type == "instantiate") .attributes[] | select(.key == "_contract_address") .value')Note how the address is much longer than a regular address, like Alice's. With the contract instantiated, you can query a few things about it.
Send a transaction to your contract
The smart contract you just instantiated is made to register names. As your first transaction, you will register the name "queen-of-the-hill" and map it to Alice. What message does the execute function expect? It expects this:
CosmWasm serializes an enum such as ExecuteMsg by prefixing the value proper with the type, here Register. Since you want to register Alice at the name, your register message, with its two fields, is:
ns_register_queen_to_alice='{"register":{"name":"queen-of-the-hill","owner":"'$alice'"}}'As can be seen in the execute_register function, only the minter can send an ExecuteMsg::Register message:
MINTER
.assert_owner(deps.storage, &info.sender)
.map_err(ContractError::from_minter(&info.sender))?;And as you recall from the instantiation message, Alice is the minter. So Alice has to send this transaction. This smart contract does not need funds, but as a vehicle to demonstrate the concept, you attach funds of 100 stake to the call:
./build/wasmd tx wasm execute $ns_addr1 "$ns_register_queen_to_alice" \
--amount 100stake \
--from alice --keyring-backend test \
--chain-id learning-chain-1 \
--gas-prices 0.25stake --gas auto --gas-adjustment 1.3 \
--yesdocker exec val-alice-1 \
wasmd tx wasm execute $ns_addr1 "$ns_register_queen_to_alice" \
--amount 100stake \
--from alice --keyring-backend test \
--chain-id learning-chain-1 \
--gas-prices 0.25stake --gas auto --gas-adjustment 1.3 \
--yesOnce more, make a note of the transaction hash. For instance:
ns_register_queen_to_alice_txhash=7966EBDD3766243FFFFE70D0A360305DE11B0BE77A305470D23D376B65432451Send a query to your contract
Has the name been duly registered, and is there a convenient way to verify? Yes, first create the resolve message so that it follows the expected query type:
This is another enum which again is serialized by prefixing with the snake-case variant:
ns_resolve_queen='{"resolve_record":{"name":"queen-of-the-hill"}}'Then you pass it as a query to the smart contract:
./build/wasmd query wasm contract-state smart $ns_addr1 "$ns_resolve_queen"docker exec val-alice-1 \
wasmd query wasm contract-state smart $ns_addr1 "$ns_resolve_queen"Which returns as expected:
data:
address: wasm1tev6xt4pgrnjpxwmvv7jrl8ph4x47wg5vcd0asCongratulations! You have updated the name service smart contract and confirmed it.
Conclusion
Here is a summary of what you accomplished:
- You compiled a blockchain that supports CosmWasm.
- You initialized and ran it.
- You compiled a smart contract.
- You stored a bytecode on-chain.
- You instantiated a smart contract using your bytecode.
- You had your smart contract save information in its state with the use of a transaction.
- You interrogated your smart contract about its state with the use of a query.
If you did not on the first pass, go back and expand the collapsed sections to learn more.
If you are keen on doing a couple of other hello-world-like exercises before plunging into smart contract writing, try Neutron's Remix IDE's tutorial or WasmKit's tutorial.
If you feel ready to start an exercise that will take you from creating your own smart contract, to testing it and managing it, head to the next section: first contract.