The project aims to show CosmWasm
features and highlight important points
- CosmWasm Template
Docker
is the main tool you need:
installation link
git
jq
curl
make
sha3sum
sha3sum
tput
cat
cut
and other commonly known tools used in Makefile
Optionally, following the guide, you can manually install Rust
on your system
Rust
is needed as it is the primary language for CosmWasm
smart contracts development
Install rustup if it is missed
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
It is considered using a stable compiler release
rustup default stable
Check compiler version and update if needed
cargo version # run the following line if the version is lower than 1.55.0+
rustup update stable
Install wasm32
target if it is missed
rustup target add wasm32-unknown-unknown
Optionally, you can manually install Wasmd
on your system following the guide
Wasmd
is the tool for interacting with blockchain and smart contracts
Install go if it is missed
Clone the project
git clone https://github.com/CosmWasm/wasmd.git
cd wasmd
Fetch releases and switch to the latest release compatible release
git fetch --tags
# replace version if the latest release is not compatible
git checkout $(git describe --tags `git rev-list --tags --max-count=1`)
Compile it!
make install
make build
Put the compiled file to $PATH
cp build/wasmd ~/.local/bin
.cargo_analyzer/
-cargo
target directory forrust-analyzer
tool invscode
, it is different from default target dir as build cache from globally installedrust
should not conflict with dockerized instance.cargo_cache/
-cargo
home directory, it stores allcargo
dependencies mentioned inCargo.toml
file.cargo_target/
-cargo
target directory for dockerizedrust
instance.wasmd_data/
-wasmd
home directory, it stores allwasmd
wallets, configuration, and cache
.cargo/
- directory storingrust
configuration andcargo
aliases.vscode/
- directory storingvscode
tools configurations.editorconfig
- file storing general editor config.gitignore
- file storing a list of files ignored bygit
rustfmt.toml
- configuration file forrust
formatter
artifacts/
- directory for results of optimized buildcoverage/
- directory for coverage reportschema/
- directory for JSON contract schema used on frontend
docker_rust/
- directory storing some scripts needed inrust
containerdocker_wasmd/
- directory storing some scripts needed inwasmd
containerDockerfile.rust
- configuration file forrust
containerDockerfile.wasmd
- configuration file forwasmd
container
examples/
- directory storing usefulrust
scripts could be run separately from the whole projectexamples/schema.rs
- script generates JSON schema of the contractsrc/
- directory storing contract source filessrc/contract.rs
- source file storing contract entrypoints, execute/query methods and unit testssrc/error.rs
- source file storing an expanded list of contract errorssrc/lib.rs
- source file storing a list of modules united to the librarysrc/msg.rs
- source file storing execute/query messages structssrc/state.rs
- source file storing storage layout and help functionssrc/utils.rs
- source file storing some structs, types, and general functionstests/
- directory storing test scriptstests/integration.rs
- test file for verifying cross-contract calls and other chain features
Makefile
- file storing all command aliases, detailed description is provided in the corresponding section
setup
- build and configuredocker
images should be run once on project setup
code.build
- build contract code, the output file is not optimized enough for deploying to chain, but it may be used for running integration testscode.build.optimize
- build contract code for deploying to chaincode.test.integration
- run integration tests, a wasm file should be built beforecode.test.unit
- run unit tests provided in contract filecode.test.coverage
- calculate unit tests coverage
chain.wallet
- create and fund wallet, accept wallet namewallet
parameterchain.store_wasm
- load and check wasm code to chain, accept wallet namewallet
and path to wasmwasm
parameterschain.contract.instantiate
- instantiate contract, accept wallet namewallet
, instantiate messagemsg
and optional code IDcode_id
parameterschain.contract.execute
- execute message on contract, accept wallet namewallet
, execute messagemsg
and optional contract addresscontract
parameterschain.contract.query
- query data from a contract, accept query messagemsg
and optional contract addresscontract
parameters
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
Derive implements features for the following structure:
Serialize
- adds a possibility to code the struct into JSON (is needed when other contracts try to get the storage)Deserialize
- adds a possibility to decode the struct from JSONClone
- adds a possibility to create a duplicate of the struct by calling struct_instance.clone()Debug
- adds a possibility to use the structure in assertsPartialEq
- adds a possibility to compare instances of the structJsonSchema
- adds a possibility to create JSON schema of the struct
Custom errors are cool! You may specify your error text with parameters
pub enum CustomError {
#[error("Your access level is {have:?} and {needed:?} is needed")]
Unauthorized { have: u8, needed: u8 },
}
What is going on there?
Ok(to_binary(&query(get_part_param()? + another_part_param)?)?)
Error propagation is a great pattern as errors could be processed in one place, avoiding panic in local functions
There is a useful Result<Ok_Type, Err_Type>
type defined, return values are unwrapped by ?
syntax
?
works like unwrap but does not panic on error propagating it to a higher level
?
may convert error type if the result error type implements Std()
entry, for example:
pub enum CustomError {
#[error("{0}")]
Std(#[from] StdError),
}
There are Addr
and CanonicalAddr
types provided for addresses
Addr
is based on String
type with some validations and may be received as a parameter, caller address info.sender
, etc
CanonicalAddr
is a binary type and it is important to store any addresses in contract storage only wrapped to the type as text representation may be changed in future
String
- it is a terrible idea to store or manipulate addresses wrapped to the String
type
#[cfg_attr(not(feature = "library"), entry_point)]
Contracts has 3 main entrypoints:
Instantiate
- should be called once on contract deployment, base storage layout is defined thereExecute
- base set data method, routes other methodsQuery
- base view data method, routes other methods
As execute
and query
entrypoints are routers, signatures are defined separately in msg.rs
#[serde(rename_all = "snake_case")]
It is considered using snake case in JSON message field names
Item::new("item_key");
Map::new("map_key");
CosmWasm
implements key-value storage API, you should set unique keys manually for each storage instace
Item
and Map
are the main storage types, description with examples on crates.io
Clone the repository
git clone [email protected]:SteMak/cosmwasm_template.git
cd cosmwasm_template
Build Docker
images
make setup
Update contract sources
For example, you may update restrictions in src/contract.rs : execute_become_maintainer
, changing the minimum maintainer age
Check code consistency by running tests
Tests are presented at the end of src/contract.rs
file
make code.test.unit
Check coverage
When code updation is finished, check tests code coverage and update tests to get the wanted coverage level
The coverage report is placed in coverage/
directory
make code.test.coverage
Check integration tests
If your contract may do cross-contract calls, you may want to create and run integration tests
Integration tests work with compiled binary, do not forget to provide the path to wasm in tests/integration.rs
make code.build
make code.test.integration
Generate contract API
You may want to generate JSON Schema for validating contract API on your client
make code.schema
Create optimized build
The optimized build is generated longer but is more smaller
Te result is placed in artifacts/
directory
make code.build.optimize
Create wallet
You need a wallet created for smart contract deployment
make chain.wallet wallet=wallet_name
Load code to chain
There could be several contracts created with the same code, so load code at first
make chain.store_wasm wallet=wallet_name wasm=artifacts/cosmwasm_template.wasm
Instantiate contract
Instantiate contract with JSON message
code_id
parameter may be provided for instantiating custom code
make chain.contract.instantiate wallet=wallet_name msg='{}'
As a contract created, you can interact with it
JSON message should contain the method name field and its value is the method signature
Executing contract
contract
parameter may be provided for calling custom contract
make chain.contract.execute wallet=wallet_name \
msg='{ "register_city": { "name": "Aloha City", "power_level": 7 } }'
Quering contract
wallet
parameter is not needed as query do not waste Gas
contract
parameter may be provided for calling custom contract
make chain.contract.query msg='{ "look_maintainer": {} }'
A maintainer is a contract manager with the highest access level
Anyone is able to become a maintainer if corresponding registered person is 17+ years old and person name is "Super_Maintainer_887"
It is not essential to keep the requirements satisfied after caller became maintainer
There are 2 main object groups: People
and Cities
The contract maintainer is able to create a City
Anyone is able to create and update Person
Anyone is able to register/unregister his Person
in/from any City
Set caller maintainer
Signature: void
Fail conditions: void
Return: void
RegisterCity
Add new City
providing metadata
Signature:
name: CityName
- part ofCity
metadatapower_level: u8
- part ofCity
metadata
Fail conditions:
Unauthorized
- caller is not maintainer
Return: void
RegisterPerson
Add new Person
providing metadata
Signature:
birthday: Birthday
- part ofPerson
metadatanickname: Nickname
- part ofPerson
metadataemail: Option<Email>
- part ofPerson
metadata
Fail conditions:
InconsistentData
-!(1 <= birthday.day <= 366)
InconsistentData
-!(1756 <= birthday.year <= current year)
PersonAlreadyRegistered
- caller already created aPerson
Return: void
UpdatePerson
Update Person
metadata
Signature:
nickname: Nickname
- part ofPerson
metadataemail: Option<Email>
- part ofPerson
metadata
Fail conditions:
NotFound
- noPerson
created by caller found
Return: void
RegisterInCity
Register Person
in City
Signature:
city_id: u64
-City
identifier
Fail conditions:
NotFound
- noPerson
created by caller foundNotFound
- noCity
with the identifier foundPersonAlreadyRegisteredInCity
-Person
is already registered in theCity
Return: void
UnregisterFromCity
Unregister Person
from City
Signature:
city_id: u64
-City
identifier
Fail conditions:
NotFound
- noPerson
created by caller foundNotFound
- noCity
with the identifier foundNotFound
-Person
is not registered in theCity
Return: void
BecomeMaintainer
Set caller maintainer
Signature: void
Fail conditions:
AlreadyMaintainer
- caller is maintainerNotFound
- noPerson
created by caller foundInconsistentMaintainer
-Person
nickname is notSuper_Maintainer_887
InconsistentMaintainer
-Person
age is under17
Return: void
LookMaintainer
Check who is maintainer
Signature: void
Fail conditions: void
Return:
maintainer: Addr
- maintainer address
LookPerson
Check Person
metadata
Signature:
person: Addr
- address of user createdPerson
Fail conditions:
NotFound
- noPerson
created by queried address found
Return:
person: PersonResponse
-address: Addr
- queried addressbirthday: Birthday
- part ofPerson
metadatanickname: Nickname
- part ofPerson
metadataemail: Option<Email>
- part ofPerson
metadataresident_times: u64
- amount ofCities
wherePerson
is registered
LookCities
Check Cities
list with metadata
Signature:
start_id: u64
- startCity
identifierlimit: u64
- maximum amount ofCities
responded
Fail conditions: void
Return:
cities: Vec<CityResponse>
-id: u64
-City
identifiername: CityName
- part ofCity
metadatapower_level: u8
- part ofCity
metadatapopulation: u64
- amount ofPeople
registered in theCity
LookPersonCities
Check Cities
list with metadata where the Person
is registered
Signature:
person: Addr
- address of user createdPerson
start_id: u64
- startCity
identifierlimit: u64
- maximum amount ofCities
responded
Fail conditions:
NotFound
- noPerson
created by queried address found
Return:
cities: Vec<CityResponse>
-id: u64
-City
identifiername: CityName
- part ofCity
metadatapower_level: u8
- part ofCity
metadatapopulation: u64
- amount ofPeople
registered in theCity
LookCityPeople
Check People
metadata by city where they are registered
Signature:
city: u64
-City
identifierstart_id: u64
- startPerson
identifierlimit: u64
- maximum amount ofPeople
responded
Fail conditions:
NotFound
- noCity
with the identifier found
Return:
people: Vec<PersonResponse>
-address: Addr
- queried addressbirthday: Birthday
- part ofPerson
metadatanickname: Nickname
- part ofPerson
metadataemail: Option<Email>
- part ofPerson
metadataresident_times: u64
- amount ofCities
wherePerson
is registered