Hugo Peixoto

Setting up an ink! development environment

Published on February 18, 2021

I’m currently working on bringing upgradeability to ink! smart contracts, as part of this grant proposal. This post will contain the steps it took me to get things up and running.

The first thing I did was to stop using asdf for rust. I enjoy using it for things like ruby and nodejs, but using it for rust nightly has not been great. Every time I want to bump to a more recent nightly version I end up having to reinstall all of my rust tools. rustup is the recommended tool anyway, so I decided to give it a try. The rustup installation is kind enough to provide me information on which paths and environment variables it uses, so I was able to configure those before proceeding.

To setup an ink! environment, I followed almost to the letter the setup in their recommended tutorial. There is some nightly vs stable confusion in the instructions. Since I don’t have the stable toolchain installed, my installation commands were as follows:

# apt install clang libclang-dev libz-dev

$ rustup component add rust-src --toolchain nightly
$ rustup target add wasm32-unknown-unknown --toolchain nightly
$ cargo install canvas-node --git --tag v0.1.4 --force --locked
$ cargo install cargo-contract --vers 0.8.0 --force --locked
$ cargo contract new flipper

Later they suggest running cargo +nightly test. Unsure why folks need to specify +nightly here, when the other cargo commands didn’t. I’m able to run cargo test without any issues.

Running cargo contract build outputs a couple of files to target:

After running the local node with canvas --dev --tmp, I tried to access it using It connects to a test node / network by default, but changing it to Local Node worked fine and I was able to deploy and call functions on my contract.

Deploying a contract is a two step operation:

  1. Calling put_code with the wasm blob
  2. Calling contract.instantiate with the code address

This was easier than I expected, although the cargo install steps took forever.

Just like in ethereum, the way that calling contract functions works is by sending a transaction with the deployed contract address and a payload. The wasm blob will pick the right codepath based on data. For example, when I called the flip function on my contract, it sent the data 0xc096a5f3. Looking at the contract metadata (metadata.json), I can find that number under the spec.messages key:

// cat target/metadata.json  | jq '.spec.messages[0]'
  "args": [],
  "docs": [
    " A message that can be called on instantiated contracts.",
  " This one flips the value of the stored `bool` from `true`",
  " to `false` and vice versa."
  "mutates": true,
  "name": [
  "payable": false,
  "returnType": null,
  "selector": "0xc096a5f3"

Using the canvas-ui thing, I can call methods either as RPC calls or as transactions. The next step is to figure out how cross contract calls are made and how storage is handled.