diff --git a/docs/tutorial/platform/05-first-contract.md b/docs/tutorial/platform/05-first-contract.md index 5eb1465..cf7a9f2 100644 --- a/docs/tutorial/platform/05-first-contract.md +++ b/docs/tutorial/platform/05-first-contract.md @@ -3,15 +3,18 @@ title: First contract description: Write your first smart contract --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + # First contract In the hello world, you used an already-made smart contract that implements a name service. It is a [rudimentary one](https://github.com/deus-labs/cw-contracts/blob/v0.11.0/contracts/nameservice/src/contract.rs). This somewhat longer-running exercise intends to build progressively a better nameservice, which could be [this one](https://github.com/public-awesome/names/blob/v1.2.8/contracts/name-minter/src/contract.rs), from the ground up. - +:::info Exercise progression In practice, you will progressively build [this name service](https://github.com/b9lab/cw-my-nameservice). The exercise is built such that you can skip ahead by switching to the appropriate [branch](https://github.com/b9lab/cw-my-nameservice/branches) as mentioned at the top of the page of each exercise section. - +::: It offers two tracks, one local and the other with Docker, so that you can postpone installing the prerequisites. @@ -21,88 +24,87 @@ It was built with Rust 1.80.1 for CosmWasm 2.1.3. It may work with other version Most likely, you will start your CosmWasm project as a Rust project. Use `cargo` to initialize a new one. - - - ```sh + + + ```shell cargo new my-nameservice --lib --edition 2021 ``` - - - ```sh + + + ```shell docker run --rm --interactive --tty \ --volume $(pwd):/root/ --workdir /root \ rust:1.80.1 \ cargo new my-nameservice --lib --edition 2021 ``` - - + + Move into the project directory. -```sh +```shell cd my-nameservice ``` - +:::info Exercise progression At this stage, you should have something similar to the [`initial-cargo`](https://github.com/b9lab/cw-my-nameservice/tree/initial-cargo) branch. - +::: -If you are using VisualStudio Code, feel free to copy the `.vscode` content you see [here]((https://github.com/b9lab/cw-my-nameservice/tree/initial-cargo). +If you are using Visual Studio Code, feel free to copy the `.vscode` content you see [here]((https://github.com/b9lab/cw-my-nameservice/tree/initial-cargo). ## The instantiation message With the base project ready, you can move to your first message. Your smart contract will be instantiated, and the `instantiate` function needs a message. Create it in a new `src/msg.rs` file: - -```rust +```rust title="src/msg.rs" use cosmwasm_schema::cw_serde; #[cw_serde] pub struct InstantiateMsg {} ``` - You use the attribute macro [`cw_serde`](https://docs.cosmwasm.com/core/entrypoints#defining-your-own-messages) in order to make your for-now-empty _instantiate_ message serializable. Make its content available to the Rust project by replacing the sample code in `src/lib.rs` with: - - ```diff-rs - + pub mod msg; - - pub fn add(left: u64, right: u64) -> u64 { - - left + right - - } - - - - #[cfg(test)] - - mod tests { - - ... - - } - ``` - +```rust title="src/lib.rs" +//with-coverage ++ pub mod msg; +//no-coverage-start +- pub fn add(left: u64, right: u64) -> u64 { +- left + right +- } +- +- #[cfg(test)] +- mod tests { +- ... +- } +//no-coverage-end +``` Note that it says `pub` as the message needs to be known outside of the project, including tests. Back in `src/msg.rs` you will notice that `cosmwasm_schema` now appears as an `unresolved import`. You see the same message if you try to build: - - - ```sh + + + ```shell cargo build ``` - - - ```sh + + + ```shell docker run --rm -it \ -v $(pwd):/root/ -w /root \ rust:1.80.1 \ cargo build ``` - - + + Returns: -```txt +```text error[E0432]: unresolved import `cosmwasm_schema` --> src/msg.rs:1:5 | @@ -112,27 +114,28 @@ error[E0432]: unresolved import `cosmwasm_schema` Indeed, you need to add the relevant dependency: - - - ```sh + + + ```shell cargo add cosmwasm-schema@2.1.3 ``` - - - ```sh + + + ```shell docker run --rm -it \ -v $(pwd):/root/ -w /root \ rust:1.80.1 \ cargo add cosmwasm-schema@2.1.3 ``` - - + + - +:::info Exercise progression -At this stage, you should have something similar to the [`instantiation-message`](https://github.com/b9lab/cw-my-nameservice/tree/instantiation-message) branch, with [this](https://github.com/b9lab/cw-my-nameservice/compare/initial-cargo..instantiation-message) as the diff. +At this stage, you should have something similar to the [`instantiation-message`](https://github.com/b9lab/cw-my-nameservice/tree/instantiation-message) branch, +with [this](https://github.com/b9lab/cw-my-nameservice/compare/initial-cargo..instantiation-message) as the diff. - +::: ## The instantiation function @@ -140,22 +143,20 @@ With the message declared, you can move on to the function that will instantiate Create a new file `src/contract.rs` with: - - ```rust - use crate::msg::InstantiateMsg; - use cosmwasm_std::{entry_point, DepsMut, Env, MessageInfo, Response, StdError}; - - #[cfg_attr(not(feature = "library"), entry_point)] - pub fn instantiate( - _: DepsMut, - _: Env, - _: MessageInfo, - _: InstantiateMsg, - ) -> Result { - Ok(Response::default()) - } - ``` - +```rust title="src/contract.rs" +use crate::msg::InstantiateMsg; +use cosmwasm_std::{entry_point, DepsMut, Env, MessageInfo, Response, StdError}; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + _: DepsMut, + _: Env, + _: MessageInfo, + _: InstantiateMsg, +) -> Result { + Ok(Response::default()) +} +``` Note how: @@ -169,242 +170,241 @@ Note how: Also make it available to the Rust project by adding the following line to `src/lib.rs`: - - ```diff-rs - + pub mod contract; - pub mod msg; - ``` - +```rust title="src/lib.rs" +//with-coverage ++ pub mod contract; +pub mod msg; +``` The module is also marked as public because the CosmWasm system needs to be able to call its function(s). Once again, there is a missing dependency: `cosmwasm_std`. Add it: - - - ```sh + + + ```shell cargo add cosmwasm-std@2.1.3 ``` - - - ```sh + + + ```shell docker run --rm -it \ -v $(pwd):/root/ -w /root \ rust:1.80.1 \ cargo add cosmwasm-std@2.1.3 ``` - - + + - +:::info Exercise progression At this stage, you should have something similar to the [`instantiation-function`](https://github.com/b9lab/cw-my-nameservice/tree/instantiation-function) branch, with [this](https://github.com/b9lab/cw-my-nameservice/compare/instantiation-message..instantiation-function) as the diff. - +::: ## Improve error reporting With a view to improving error reporting as you progress, you introduce your own error type. In a new `src/error.rs`, add: - - ```rust - use cosmwasm_std::StdError; - use thiserror::Error; +```rust title="src/error.rs" +use cosmwasm_std::StdError; +use thiserror::Error; - #[derive(Error, Debug)] - pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - } - ``` - +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), +} +``` -Note that it uses the popular [thiserror package](https://docs.rs/thiserror/latest/thiserror). Again, add the following line to `src/lib.rs`. +Note that it uses the popular [thiserror package](https://docs.rs/thiserror/latest/thiserror). +Again, add the following line to `src/lib.rs`. - - ```diff-rs - pub mod contract; - + mod error; - pub mod msg; - ``` - +```rust title="src/lib.rs" +pub mod contract; +//with-coverage ++ mod error; +pub mod msg; +``` Note that it is not `pub` as it only needs to be available within the Rust library project. And don't forget to add the corresponding dependency: - - - ```sh + + + ```shell cargo add thiserror@1.0.63 ``` - - - ```sh + + + ```shell docker run --rm -it \ -v $(pwd):/root/ -w /root \ rust:1.80.1 \ cargo add thiserror@1.0.63 ``` - - + + Now that the new error type has been declared, you can use it in `src/contract.rs`: - - ```diff-rs - - use crate::msg::InstantiateMsg; - - use cosmwasm_std::{entry_point, DepsMut, Env, MessageInfo, Response, StdError}; - + use crate::{error::ContractError, msg::InstantiateMsg}; - + use cosmwasm_std::{entry_point, DepsMut, Env, MessageInfo, Response}; - + - + type ContractResult = Result; - - #[cfg_attr(not(feature = "library"), entry_point)] - pub fn instantiate( - ... - - ) -> Result { - + ) -> ContractResult { - ... - } - ``` - +```diff-rs title="src/contract.rs" +//no-coverage-start +- use crate::msg::InstantiateMsg; +- use cosmwasm_std::{entry_point, DepsMut, Env, MessageInfo, Response, StdError}; +//no-coverage-end +//with-coverage-start ++ use crate::{error::ContractError, msg::InstantiateMsg}; ++ use cosmwasm_std::{entry_point, DepsMut, Env, MessageInfo, Response}; ++ ++ type ContractResult = Result; +//with-coverage-end + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + ... +//no-coverage +- ) -> Result { +//with-coverage ++ ) -> ContractResult { + ... +} +``` Note how: -* It uses the new error type in a new alias type for the oft-used `Result` type. -* It uses the new type as the return of the `instantiate` function. +- It uses the new error type in a new alias type for the oft-used `Result` type. +- It uses the new type as the return of the `instantiate` function. - +:::info Exercise progression At this stage, you should have something similar to the [`improve-error-reporting`](https://github.com/b9lab/cw-my-nameservice/tree/improve-error-reporting) branch, with [this](https://github.com/b9lab/cw-my-nameservice/compare/instantiation-function..improve-error-reporting) as the diff. - +::: ## Compilation to WebAssembly You can already build with the `cargo build` command. How about building to WebAssembly? You need to add the WebAssembly compiling target for that, if it was not yet installed. - - - ```sh + + + ```shell rustup target add wasm32-unknown-unknown ``` - - + + To avoid downloading the `wasm32` target every time, it is good to create a new Docker image that includes it. Create a new file `builder.dockerfile`: - - ```Dockerfile + + ```Dockerfile title="builder.dockerfile" FROM rust:1.80.1 RUN rustup target add wasm32-unknown-unknown ``` - + And build the image to be named `rust-cosmwasm:1.80.1`: - ```sh + ```shell docker build . --file builder.dockerfile --tag rust-cosmwasm:1.80.1 ``` - - + + With the target installed, you can compile to WebAssembly with: - - - ```sh + + + ```shell cargo build --release --target wasm32-unknown-unknown ``` - - - ```sh + + + ```shell docker run --rm -it \ -v $(pwd):/root -w /root \ rust-cosmwasm:1.80.1 \ cargo build --release --target wasm32-unknown-unknown ``` - - + + This `cargo build` command is a bit verbose so it pays to create an alias. The right place for that is in `.cargo/config.toml`. Create the folder and the file: -```sh +```shell mkdir .cargo touch .cargo/config.toml ``` And in it, put: - -```toml +```toml title=".cargo/config.toml" [alias] wasm = "build --release --target wasm32-unknown-unknown" ``` - With this alias defined, you can now use `cargo wasm` instead of writing `cargo build --release --target wasm32-unknown-unknown`. Change your command to: - - - ```sh + + + ```shell cargo wasm ``` - - - ```sh + + + ```shell docker run --rm -it \ -v $(pwd):/root -w /root \ rust-cosmwasm:1.80.1 \ cargo wasm ``` - - + + ## Compilation to CosmWasm While you are working on the configuration, you might as well add some elements necessary to compile to a type amenable to the CosmWasm module, along with flags curated by the CosmWasm team. Add the following lines in `Cargo.toml` below the `[package]` section: - - ```diff - [package] - name = "my-nameservice" - version = "0.1.0" - edition = "2021" - - + # Linkage options. More information: https://doc.rust-lang.org/reference/linkage.html - + [lib] - + crate-type = ["cdylib", "rlib"] - - + [features] - + # Use library feature to disable all instantiate/execute/query exports - + library = [] - - + # Optimizations in release builds. More information: https://doc.rust-lang.org/cargo/reference/profiles.html - + [profile.release] - + opt-level = "z" - + debug = false - + rpath = false - + lto = true - + debug-assertions = false - + codegen-units = 1 - + panic = 'abort' - + incremental = false - + overflow-checks = true - - [dependencies] - ... - ``` - +```toml title="Cargo.toml" +[package] +name = "my-nameservice" +version = "0.1.0" +edition = "2021" + +//with-coverage-start ++ # Linkage options. More information: https://doc.rust-lang.org/reference/linkage.html ++ [lib] ++ crate-type = ["cdylib", "rlib"] + ++ [features] ++ # Use library feature to disable all instantiate/execute/query exports ++ library = [] + ++ # Optimizations in release builds. More information: https://doc.rust-lang.org/cargo/reference/profiles.html ++ [profile.release] ++ opt-level = "z" ++ debug = false ++ rpath = false ++ lto = true ++ debug-assertions = false ++ codegen-units = 1 ++ panic = 'abort' ++ incremental = false ++ overflow-checks = true +//with-coverage-end + +[dependencies] +... +``` You can now build your smart contract, then store and deploy on-chain the generated wasm found in `target/wasm32-unknown-unknown/release/my_nameservice.wasm`. Refer to the hello world for how to do it. - +:::info Exercise progression At this stage, you should have something similar to the [`compilation-elements`](https://github.com/b9lab/cw-my-nameservice/tree/compilation-elements) branch, with [this](https://github.com/b9lab/cw-my-nameservice/compare/improve-error-reporting..compilation-elements) as the diff. - +::: ## Unit testing @@ -412,38 +412,37 @@ You have not written much of a smart contract. However it is still useful to pre In `src/contract.rs`, add this at the end of the file: - - ```rust - #[cfg(test)] - mod tests { - use crate::msg::InstantiateMsg; - use cosmwasm_std::{testing, Addr, Response}; - - #[test] - fn test_instantiate() { - // Arrange - let mut mocked_deps_mut = testing::mock_dependencies(); - let mocked_env = testing::mock_env(); - let mocked_addr = Addr::unchecked("addr"); - let mocked_msg_info = testing::message_info(&mocked_addr, &[]); - - let instantiate_msg = InstantiateMsg {}; - - // Act - let contract_result = super::instantiate( - mocked_deps_mut.as_mut(), - mocked_env, - mocked_msg_info, - instantiate_msg, - ); - - // Assert - assert!(contract_result.is_ok(), "Failed to instantiate"); - assert_eq!(contract_result.unwrap(), Response::default()) - } + +```rust title="src/contract.rs" +#[cfg(test)] +mod tests { + use crate::msg::InstantiateMsg; + use cosmwasm_std::{testing, Addr, Response}; + + #[test] + fn test_instantiate() { + // Arrange + let mut mocked_deps_mut = testing::mock_dependencies(); + let mocked_env = testing::mock_env(); + let mocked_addr = Addr::unchecked("addr"); + let mocked_msg_info = testing::message_info(&mocked_addr, &[]); + + let instantiate_msg = InstantiateMsg {}; + + // Act + let contract_result = super::instantiate( + mocked_deps_mut.as_mut(), + mocked_env, + mocked_msg_info, + instantiate_msg, + ); + + // Assert + assert!(contract_result.is_ok(), "Failed to instantiate"); + assert_eq!(contract_result.unwrap(), Response::default()) } - ``` - +} +``` Note how: @@ -453,25 +452,25 @@ Note how: With the test ready, you can run it with the following command: - - - ```sh + + + ```shell cargo test ``` - - - ```sh + + + ```shell docker run --rm -it \ -v $(pwd):/root/ -w /root \ rust:1.80.1 \ cargo test ``` - - + + Which should print its success in the output: -```txt +```text ... running 1 test test contract::tests::test_instantiate ... ok @@ -480,8 +479,9 @@ test contract::tests::test_instantiate ... ok ## Conclusion - +:::info Exercise progression -At this stage, you should have something similar to the [`first-unit-test`](https://github.com/b9lab/cw-my-nameservice/tree/first-unit-test) branch, with [this](https://github.com/b9lab/cw-my-nameservice/compare/compilation-elements..first-unit-test) as the diff. +At this stage, you should have something similar to the [`first-unit-test`](https://github.com/b9lab/cw-my-nameservice/tree/first-unit-test) branch, +with [this](https://github.com/b9lab/cw-my-nameservice/compare/compilation-elements..first-unit-test) as the diff. - +:::