Implementation of the core business logic as CosmWasm contracts.
-
Install the Rust Toolchain Installer
- follow the instructions on https://rustup.rs/, or
- [preferred] install through your system's package manager, e.g. on ArchLinux use
sudo pacman -S rustup
-
Install the
wasm32
targetrustup target install wasm32-unknown-unknown
-
Install the Rust linter
rustup component add clippy
-
Install a set of handy tools
cargo install cargo-edit cargo-workspaces cargo-expand
The build is controlled with a few environment variables:
RELEASE_VERSION
- an arbitrary string giving the release a nameNET_NAME
- the name of the targeted network, e.g.:dev
,test
,main
A non-optimized version
The command below builds a contract if ran from the contract directory, or builds all contracts if ran from the workspace directory:
RELEASE_VERSION=dev-release NET_NAME=dev cargo build --target=wasm32-unknown-unknown
An optimized version
The command below builds an optimized and verifiable version of all contracts, run from the workspace 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 \
--env RELEASE_VERSION=`git describe`-`date -Iminute` --env NET_NAME=dev cosmwasm/workspace-optimizer:0.12.13
Run the following in a package directory or in the workspace root.
cargo test
Run the following in the workspace root.
./lint.sh
Contract addresses are dependent on the order in which they are deployed in the script.
When adding a new contract, and it needs to be deployed with the genesis:
- Add it to the
scripts/deploy-contracts-genesis.sh
script. - Ensure you preserve the order:
- Your contract is not a dependency:
- Add your initialization logic at the end and fill in the address that you get based on the contract's ID.
- Your contract is a dependency:
-
Find the position corresponding to the contract's position in the dependency tree.
-
Assume the address of the first contract that you pushed down.
-
Shift down the addresses of the following contracts.
In the end, you should be left with one contract for which there won't be an address to assume.
-
After you are done with the address shifting, fill out the address of the contract in the script file, which you get based on the contract's ID.
-
- Your contract is not a dependency:
As mentioned in the section above, contract addresses are dependent on the order in which they are deployed in the script.
When changing the order of deployment, reorder the contracts' addresses accordingly, so the order of the actual addresses is not changed but the contract who owns that address is.
Using the previously installed cargo-edit one can easily upgrade the dependencies.
For more details please refer to
cargo upgrade --help
An example:
cargo upgrade --workspace cw-storage-plus
- Add new key to be used for the deployment:
Running this command will create a new account called "wallet".
nolusd keys add wallet
------------ Example Output-----------------
- name: wallet
type: local
address: nolus1um993zvsdp8upa5qvtspu0jdy66eahlcghm0w6
pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A0MFMuJSqWpofT3GIQchGyL9bADlC5GEWu3QJHGL/XHZ"}'
mnemonic: ""
- The new key needs some tokens for the deployment
When scripts/init-local-network.sh is started it creates two accounts. One of them is the "treasury" account.
To find the address of the treasury account, run the following command:
nolusd keys show -a treasury
> nolus122f36dx292yy72253ufkt2g8rzheml2pkcfckl
Use the treasury address to send tokens to the new "wallet" account.
nolusd query bank total $NODE
nolusd tx bank send $(nolusd keys show -a treasury) $(nolusd keys show -a wallet) 1000000unolus --chain-id nolus-local --keyring-backend test
nolusd query bank balances $(nolusd keys show -a wallet) --chain-id nolus-local
- set environment
export CHAIN_ID="nolus-local"
export TXFLAG="--chain-id ${CHAIN_ID} --gas-prices 0.025unolus --gas auto --gas-adjustment 1.3"
- see how many codes we have now
nolusd query wasm list-code
- now we store the bytecode on chain; you can see the code in the result
RES=$(nolusd tx wasm store artifacts/<contract name>.wasm --from wallet $TXFLAG -y --output json -b block)
- you can also get the code this way
CODE_ID=$(echo $RES | jq -r '.logs[0].events[-1].attributes[0].value')
- no contracts yet, this should return an empty list
nolusd query wasm list-contract-by-code $CODE_ID --output json
- you can also download the wasm from the chain and check that the difference between them is empty
nolusd query wasm code $CODE_ID download.wasm
diff artifacts/<contract name>.wasm download.wasm
We use the cosmjs
library to work with smart contracts via TypeScript.
First of all, make sure you have created a user who has some amount. We will use this user to upload a contract and send it messages. You can use util/ methods in the UAT-test project to create a new user and client or use existing ones. Example: the getUserClient() and getUser1Wallet() methods are helper methods (from UAT-tests/src/util) that do this first step:
let userClient: SigningCosmWasmClient;
let userAccount: AccountData;
userClient = await getUser1Client();
[userAccount] = await (await getUser1Wallet()).getAccounts();
This userAccount address will be transmitted as a sender when we want to send messages to the contract. Тhe essence of this example shows how to deploy the contract and communicate with it:
-
After building the code of the smart contract, we get the .wasm file we need. The first step is to access this file:
import * as fs from "fs"; const wasmBinary: Buffer = fs.readFileSync("./oracle.wasm");
-
Now we can upload a wasm binary:
const customFees = { upload: { amount: [{amount: "2000000", denom: "unolus"}], gas: "2000000", }, init: { amount: [{amount: "500000", denom: "unolus"}], gas: "500000", }, exec: { amount: [{amount: "500000", denom: "unolus"}], gas: "500000", } }; const uploadReceipt = await userClient.upload(userAccount.address, wasmBinary, customFees.upload); const codeId = uploadReceipt.codeId;
-
Then we can instantiate the contract and get its address:
const instatiateMsg = { "base_asset": "ust", "price_feed_period": 60, "feeders_percentage_needed": 50, }; const contract: InstantiateResult = await userClient.instantiate(userAccount.address, codeId, instatiateMsg, "test", customFees.init); contractAddress = contract.contractAddress;
This contractAddress variable is our entry point to the contract. When we send an execute or query message, we give this address to the methods.
-
How to send an execute message:
const addFeederMsg = { "register_feeder": { "feeder_address":"nolus1gzk...." }, }; await userClient.execute(userAccount.address, contractAddress, addFeederMsg, customFees.exec);
-
How to send a query message:
const isFeederMsg = { "is_feeder": { "address":"nolus1gzk...." }, }; await userClient.queryContractSmart(contractAddress, isFeederMsg);
These json messages that we form (including the initial message) depend on what the contract expects to receive in order to provide us with certain functionality. We can check this from the generated json schemas.
Add Rust support by installing rust-analyzer
extension
- Press
Ctrl+Shift+P
- Execute
ext install matklad.rust-analyzer
Add syntax highlighting for TOML files
- Press
Ctrl+Shift+P
- Execute
ext install bungcip.better-toml
Add dependency versions update by installing crates
extension
-
Press
Ctrl+Shift+P
-
Execute
ext install serayuzgur.crates
- Rust Language
- Notable Traits in the Standard Library
- My favourite and all-in-one is Rust Language Cheat Sheet
- Many resources are linked at Learn Rust
- The official book
- Cooking with Rust
- Learning Rust by programming
- Rust by Examples
- Style Guidelines although partially completed
- API Guidelines
- Rust Design Patterns
- A collection of resources to guide programmers to write Idiomatic Rust code
- Node to Rust series
- Advanced concepts like ownership, type conversions, etc. The Rustonomicon
- A nice collection of selected posts