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 stableCheck compiler version and update if needed
cargo version # run the following line if the version is lower than 1.55.0+
rustup update stableInstall wasm32 target if it is missed
rustup target add wasm32-unknown-unknownOptionally, 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 wasmdFetch 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 buildPut the compiled file to $PATH
cp build/wasmd ~/.local/bin.cargo_analyzer/-cargotarget directory forrust-analyzertool invscode, it is different from default target dir as build cache from globally installedrustshould not conflict with dockerized instance.cargo_cache/-cargohome directory, it stores allcargodependencies mentioned inCargo.tomlfile.cargo_target/-cargotarget directory for dockerizedrustinstance.wasmd_data/-wasmdhome directory, it stores allwasmdwallets, configuration, and cache
.cargo/- directory storingrustconfiguration andcargoaliases.vscode/- directory storingvscodetools configurations.editorconfig- file storing general editor config.gitignore- file storing a list of files ignored bygitrustfmt.toml- configuration file forrustformatter
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 inrustcontainerdocker_wasmd/- directory storing some scripts needed inwasmdcontainerDockerfile.rust- configuration file forrustcontainerDockerfile.wasmd- configuration file forwasmdcontainer
examples/- directory storing usefulrustscripts 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 configuredockerimages 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 namewalletparameterchain.store_wasm- load and check wasm code to chain, accept wallet namewalletand path to wasmwasmparameterschain.contract.instantiate- instantiate contract, accept wallet namewallet, instantiate messagemsgand optional code IDcode_idparameterschain.contract.execute- execute message on contract, accept wallet namewallet, execute messagemsgand optional contract addresscontractparameterschain.contract.query- query data from a contract, accept query messagemsgand optional contract addresscontractparameters
#[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 git@github.com:SteMak/cosmwasm_template.git
cd cosmwasm_templateBuild Docker images
make setupUpdate 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.unitCheck 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.coverageCheck 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.integrationGenerate contract API
You may want to generate JSON Schema for validating contract API on your client
make code.schemaCreate optimized build
The optimized build is generated longer but is more smaller
Te result is placed in artifacts/ directory
make code.build.optimizeCreate wallet
You need a wallet created for smart contract deployment
make chain.wallet wallet=wallet_nameLoad 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.wasmInstantiate 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 ofCitymetadatapower_level: u8- part ofCitymetadata
Fail conditions:
Unauthorized- caller is not maintainer
Return: void
RegisterPerson
Add new Person providing metadata
Signature:
birthday: Birthday- part ofPersonmetadatanickname: Nickname- part ofPersonmetadataemail: Option<Email>- part ofPersonmetadata
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 ofPersonmetadataemail: Option<Email>- part ofPersonmetadata
Fail conditions:
NotFound- noPersoncreated by caller found
Return: void
RegisterInCity
Register Person in City
Signature:
city_id: u64-Cityidentifier
Fail conditions:
NotFound- noPersoncreated by caller foundNotFound- noCitywith the identifier foundPersonAlreadyRegisteredInCity-Personis already registered in theCity
Return: void
UnregisterFromCity
Unregister Person from City
Signature:
city_id: u64-Cityidentifier
Fail conditions:
NotFound- noPersoncreated by caller foundNotFound- noCitywith the identifier foundNotFound-Personis not registered in theCity
Return: void
BecomeMaintainer
Set caller maintainer
Signature: void
Fail conditions:
AlreadyMaintainer- caller is maintainerNotFound- noPersoncreated by caller foundInconsistentMaintainer-Personnickname is notSuper_Maintainer_887InconsistentMaintainer-Personage 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- noPersoncreated by queried address found
Return:
person: PersonResponse-address: Addr- queried addressbirthday: Birthday- part ofPersonmetadatanickname: Nickname- part ofPersonmetadataemail: Option<Email>- part ofPersonmetadataresident_times: u64- amount ofCitieswherePersonis registered
LookCities
Check Cities list with metadata
Signature:
start_id: u64- startCityidentifierlimit: u64- maximum amount ofCitiesresponded
Fail conditions: void
Return:
cities: Vec<CityResponse>-id: u64-Cityidentifiername: CityName- part ofCitymetadatapower_level: u8- part ofCitymetadatapopulation: u64- amount ofPeopleregistered in theCity
LookPersonCities
Check Cities list with metadata where the Person is registered
Signature:
person: Addr- address of user createdPersonstart_id: u64- startCityidentifierlimit: u64- maximum amount ofCitiesresponded
Fail conditions:
NotFound- noPersoncreated by queried address found
Return:
cities: Vec<CityResponse>-id: u64-Cityidentifiername: CityName- part ofCitymetadatapower_level: u8- part ofCitymetadatapopulation: u64- amount ofPeopleregistered in theCity
LookCityPeople
Check People metadata by city where they are registered
Signature:
city: u64-Cityidentifierstart_id: u64- startPersonidentifierlimit: u64- maximum amount ofPeopleresponded
Fail conditions:
NotFound- noCitywith the identifier found
Return:
people: Vec<PersonResponse>-address: Addr- queried addressbirthday: Birthday- part ofPersonmetadatanickname: Nickname- part ofPersonmetadataemail: Option<Email>- part ofPersonmetadataresident_times: u64- amount ofCitieswherePersonis registered