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 requires an environment variables named RELEASE_VERSION
that is the name of the release as an arbitrary string.
Th project is separated into three workspaces:
platform
- Network- and protocol-agnostic contracts and packagesprotocol
- Network- and protocol-specific contracts and packagestests
- integration tests
In the instructions below this value is stored in WORKSPACE_DIR_NAME.
From the protocols
directory select the directory with the protocol specific definitions and execute
cp -r --update=all packages/currencies/src_by_protocol/<protocol>/<net>/* packages/currencies/src/
The command below builds the protocol contracts if ran from the contract directory, or builds the contracts of the workspace from within which it is ran:
RELEASE_VERSION='dev-release' cargo each run -x -t build -t <protocol> -t <net> --exact -- cargo build --profile "production_nets_release" --lib --locked --target=wasm32-unknown-unknown
One way to avoid having to set those environment variables is to set them in the editor/IDE's configuration.
An example one for VSCode/VSCodium, located at .vscode/settings.json
, is shown here:
{
"rust-analyzer.cargo.extraEnv": {
"RELEASE_VERSION": "local",
},
"terminal.integrated.env.linux": {
"RELEASE_VERSION": "local",
},
"terminal.integrated.env.osx": {
"RELEASE_VERSION": "local",
},
"terminal.integrated.env.windows": {
"RELEASE_VERSION": "local",
},
}
First, the image for building the contracts needs to be built. This happens by running the command shown here:
docker build . -f "Containerfile" -t "wasm-optimizer" --build-arg "rust_ver=1.75"
Do note that the command is an example one and the Rust version, denoted by the
rust_ver
build argument, should match the one set in the rust-toolchain.toml
file, located at the root of the project!
The command shown below builds an optimized and verifiable version of
each set of contracts, depending on their workspace (indicated by
WORKSPACE_DIR_NAME
), the targeted protocol (indicated by PROTOCOL
) and targeted network
(indicated by NET
):
-
export WORKSPACE_DIR_NAME='platform'
OR
export WORKSPACE_DIR_NAME='protocol'
-
export NET='dev' export PROTOCOL='osmosis-osmosis-usdc_axelar' export ARTIFACTS_SUBDIR="$([[ $WORKSPACE_DIR_NAME == 'protocol' ]] && echo $PROTOCOL || echo 'platform')"
mkdir -p "$(pwd)/artifacts/${ARTIFACTS_SUBDIR}/" && \ docker run --rm -v "$(pwd)/platform/:/platform/:ro" \ -v "$(pwd)/${WORKSPACE_DIR_NAME}/:/code/:ro" \ -v "$(pwd)/artifacts/${ARTIFACTS_SUBDIR}/:/artifacts/:rw" \ --env "RELEASE_VERSION=`git describe`-`date -Iminute`" \ --env "features=contract$(if test "${WORKSPACE_DIR_NAME}" = 'protocol'; then echo ",net_${NET}"; fi)$(if test "${WORKSPACE_DIR_NAME}" = 'protocol'; then echo ",${PROTOCOL}"; fi)" \ wasm-optimizer
NOTE: As one might set those environment variables in the settings
of their editor/IDE, those environment variables still must be provided
as arguments to the docker run
command.
Exception to this should be the platform
workspace, as it strives to
be agnostic to the targeted network and protocol.
NOTE: Builds are reproducable as long as all environment variables
passed to the container are the exact same. If it is desired to build
a verification copy of the contracts, one must set the RELEASE_VERSION
environment variable to the one used to build the original instead.
First, install Binaryen
:
pacman -S binaryen
then
for wasm_path in $(find "target/wasm32-unknown-unknown/production_nets_release/" -maxdepth 1 -name "*.wasm" | sort)
do
wasm_name="$(basename "${wasm_path}")"
echo "Optimizing: ${wasm_name}"
#-O4 - worse size but less total function parameters
wasm-opt -Os --inlining-optimizing --signext-lowering -o "../artifacts/${wasm_name}" \
"${wasm_path}"
echo "Finished optimizing: ${wasm_name}"
done
Run the following in a package directory or any workspace.
cargo test --features "net_${NET},${PROTOCOL}" --all-targets
Run the following in the platform
workspace.
./lint.sh
Run the following in the protocol
and tests
workspaces.
./lint.sh "net_${NET},${PROTOCOL}"
RELEASE_VERSION=dev cargo llvm-lines -p <package> --features net_main,osmosis-osmosis-usdc_axelar --profile production_nets_release --target "wasm32-unknown-unknown"
First, install the checker:
cargo install cosmwasm-check
then
cosmwasm-check --available-capabilities "cosmwasm_1_1,cosmwasm_1_2,iterator,neutron,staking,stargate" ../artifacts/*.wasm
Install wasm-tools
:
cargo install wasm-tool
then
wasm-tools demangle ../artifacts/<name>.wasm | wasm-tools print -o ../artifacts/<name>.wat
file="../artifacts/<name>.wat"
for fn_index in $(seq 0 36)
do
fn_signature="type (;${fn_index};) (func"
fn_type=$(grep "${fn_signature}" "${file}")
num=$(grep "(func (" ${file} | grep "(type ${fn_index})" | wc -l)
echo "Fn type ${fn_type} instances = ${num}"
done
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.
The process of deploying a new contract on a live network is presented with the steps below:
nolusd tx wasm store <wasm_file_path> --instantiate-anyof-addresses <addresses_to_instantiate_the_code> --from <store_code_privileged_user_key>
Due to the fact that contract addresses depend on the order in which they are deployed, and because of the dependencies between some of their init messages, the new contract address must be predicted. Тherefor, there is a query provided by the admin
contract:
nolusd q wasm contract-state smart <admin_contract_address> '{"instantiate_address":{"code_id":<code_id_from_the_previous_step>,"protocol":"<protocol>"}}'
Where <protocol
> is a combination of the chosen DEX name and the protocol currency (eg "osmosis-osmosis-usdc_axelar").
On a live network, a new contract can be instantiated through the admin
contract:
nolusd tx wasm execute <admin_contract_address> '{"instantiate":{"code_id":<code_id>,"label":"<label>","message":"<init_msg>","protocol":"<protocol>","expected_address":"<expected_address_received_from_the_previous_step>"}}' --from <network_DEX_admin_key>
Where <label
> can be a combination of the chosen protocol and the contract name (eg osmosis-osmosis-usdc_axelar-leaser
)
If the given expected address matches the real one, the instantiation will be successful.
As mentioned in the sections above, the order in which contracts are deployed is important. So there is a correct way to deploy a new set of Protocol-specific contracts.
- store Leaser code
- store Lease code
- store and instantiate LPP
- store and instantiate Oracle
- store and instantiate Profit
- instantiate Leaser
This can be done manually by following the steps in the section above,
or by using the deploy-contracts-live.sh
:
./scripts/deploy-contracts-live.sh deploy_contracts "<nolus_node_url>" "<nolus_chain_id>" "<nolus_home_dir>" "<network_DEX_admin_key>" "<store_code_privileged_user_key>" "<admin_contract_address>" "<protocol_wasm_artifacts_dir_path>" "<dex_network>" "<dex_name>" "<dex_connection>" "<dex_channel_local>" "<dex_channel_remote>" "<protocol_currency>" "<treasury_contract_address>" "<timealarms_contract_address>" '<protocol_swap_tree_obj>'
The goal is to make the platform to work with the new contracts as well.
nolusd tx wasm execute <admin_contract_address> '{"register_protocol":{"name":"<protocol>","protocol":{"network":"<network>","contracts":{"leaser":"<leaser_contract_address>","lpp":"<lpp_contract_address>","oracle":"<oracle_contract_address>","profit":"<profit_contract_address>"}}}}' --from <network_DEX_admin_key>
Read all protocols:
nolusd q wasm cs smart <admin_contract_address> '{"protocols":{}}'
Read protocol:
nolusd q wasm cs smart <admin_contract_address> '{"protocol":{"protocol":"<protocol_name>"}}'
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 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