Counter written in CosmWasm

Counter source code

The following code snippets present the content of Cargo.toml, lib.rs, msg.rs, and contract.rs files, respectively. You can just copy and paste the provided content to previously created empty files, temporarily skipping the detailed explanations. However, if you're curious about what happens inside each file, feel free to check the detailed explanations provided for each code snippet.

Cargo.toml

Cargo.toml
[package]
name = "counter"
version = "0.1.0"
edition = "2021"
 
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
[lib]
crate-type = ["cdylib", "rlib"]
 
[features]
# use library feature to disable all instantiate/execute/query exports
library = []
 
[dependencies]
cosmwasm-schema = "2"
cosmwasm-std = { version = "2" }
cw-storage-plus = "2"
schemars = "0.8"
serde = "1.0"
 
[dev-dependencies]
cw-multi-test = { version = "2", features = ["cosmwasm_2_0"] }

Cargo.toml file is a configuration file for a Rust project, in our case for a smart contract written in Rust. Here's a detailed explanation of each section and what it's doing.

[package]

Cargo.toml
[package]
name = "counter"
version = "0.1.0"
edition = "2021"
  • [package] section provides metadata about the Rust crate (smart contract library in our case).
  • name = "counter" specifies the name of the crate, it's named counter like our smart contract.
  • version = "0.1.0" indicates the current version of the package and the counter smart contract.
  • edition = "2021" specifies the Rust edition being used; editions in Rust are sets of language and compiler improvements, with 2021 being one of the latest editions at the time, providing the latest features and enhancements.

[lib]

Cargo.toml
[lib]
crate-type = ["cdylib", "rlib"]
  • [lib] section specifies settings for building the library.
  • crate-type enumerates types of libraries to be produced during compiling.
  • "cdylib" specifies that the package will be compiled as a C-compatible dynamic library; which is required for smart contracts to run on the CosmWasm runtime.
  • "rlib" specifies a Rust library file that can be used as a dependency for other Rust projects, in our case for other smart contracts.

[features]

Cargo.toml
[features]
# use library feature to disable all instantiate/execute/query exports
library = []
  • [features] section defines optional features for the Rust package.
  • library = [] defines a feature named library, which when set, disables exporting smart contract entry-points. Exporting entry points is necessary for interacting with the smart contract on the blockchain. However, when the contract is used as a dependency by other contracts, exporting these entry-points should be disabled to prevent unintended function name clashes.

[dependencies]

Cargo.toml
[dependencies]
cosmwasm-schema = "2"
cosmwasm-std = { version = "2" }
cw-storage-plus = "2"
schemars = "0.8"
serde = "1.0"
  • [dependencies] section lists the libraries that the package depends on.
  • cosmwasm-schema is used for generating JSON schemas from Rust data structures, which is useful for documentation and ensuring compatibility of messages and queries.
  • cosmwasm-std is the standard library for CosmWasm contracts, providing common types and utilities needed for interacting with the CosmWasm runtime.
  • cw-storage-plus is a library that provides advanced storage abstractions and utilities on top of the basic storage capabilities in CosmWasm, making it easier to manage state within contracts.
  • schemars is a library for generating JSON schemas, which complements cosmwasm-schema by providing additional features for schema generation.
  • serde is a widely used serialization library in Rust, allowing easy conversion of Rust data structures to and from formats like JSON, which is crucial for data interchange in smart contracts.

[dev-dependencies]

Cargo.toml
[dev-dependencies]
cw-multi-test = { version = "2", features = ["cosmwasm_2_0"] }
  • [dev-dependencies] section lists dependencies that are only needed for development and testing.
  • cw-multi-test is a name of MultiTest library, and should ALWAYS be placed in [dev-dependencies] section.

Overall, this Cargo.toml file configures a Rust project for a CosmWasm-based smart contract. It sets up the basic package details, specifies how the contract should be compiled, defines dependencies for core functionality and testing, and includes features to enable or disable certain parts of the contract code. This setup ensures the contract can be developed, tested, and deployed effectively on the blockchain within the CosmWasm ecosystem.

lib.rs

lib.rs
pub mod contract;
pub mod msg;

The lib.rs file in a Rust project serves as the main entry point for defining the structure of a library. In the context of our example counter smart contract, the lib.rs file is defining and organizing the modules that make up the contract. Recall the counter project file structure:

counter (directory)
.
├── Cargo.toml
└── src
    ├── contract.rs
    ├── lib.rs
    └── msg.rs

There are two modules in the project: contract.rs and msg.rs. That's why in the lib.rs file there are two declarations:

  • pub mod contract;
    This line declares a public module named contract; tells Rust to include the code from a file named contract.rs located in the same directory and makes the module publicly accessible (pub keyword), which means that other modules or external code can access the entry-points of our smart contract.
  • pub mod msg;
    This line declares a public module named msg; includes the code from a file named msg.rs and also makes this module public which allows other parts of the code (especially our counter smart contract) to access the messages defined here.

Overall, this lib.rs file is setting up the main structure of the smart contract by defining its key components as separate modules. This organization helps in keeping the code clean, modular, and maintainable by separating the core contract logic (contract.rs) from the message and query definitions (msg.rs). This modular approach makes the smart contract easier to understand, extend, and test.

msg.rs

The msg module in file msg.rs typically defines the messages and queries that the smart contract accepts and responds to. Messages are usually structured as Rust enums or structs and define the input and output interfaces of the contract. In our example this includes messages shown below.

msg.rs
use cosmwasm_schema::{cw_serde, QueryResponses};
 
#[cw_serde]
pub enum CounterInitMsg {
    Zero,
    Set(u8),
}
 
#[cw_serde]
pub enum CounterExecMsg {
    Inc,
    Dec,
    Set(u8),
}
 
#[cw_serde]
#[derive(QueryResponses)]
pub enum CounterQueryMsg {
    #[returns(CounterResponse)]
    Value,
}
 
#[cw_serde]
pub struct CounterResponse {
    pub value: u8,
}

Let's take a detailed look at the implementation of these messages.

Imports

msg.rs
use cosmwasm_schema::cw_serde;

Required imports, like cw_serde annotation.

Instantiation message

This message is passed to instantiate entry-point.

msg.rs (CounterInitMsg)
#[cw_serde]
pub enum CounterInitMsg {
    Zero,
    Set(u8),
}

CounterInitMsg enumeration is used to initialize the contract. CounterInitMsg::Zero variant initializes the counter with the zero value, and CounterInitMsg::Set variant initializes the counter with an arbitrary value in range 0 to 255. This message is passed to instantiate entry-point of the counter smart contract.

Execution message

This message is passed to execute entry-point.

msg.rs (CounterExecMsg)
#[cw_serde]
pub enum CounterExecMsg {
    Inc,
    Dec,
    Set(u8),
}

CounterExecMsg enumeration is used to perform various actions within the contract, especially incrementing (the CounterExecMsg::Inc variant), decrementing (the CounterExecMsg::Dec variant) and setting an arbitrary counter value (the CounterExecMsg::Set variant). This message is passed to execute entry-point of the counter smart contract.

Query message

This message is passed to query entry-point.

msg.rs (CounterQueryMsg)
#[cw_serde]
#[derive(QueryResponses)]
pub enum CounterQueryMsg {
    #[returns(CounterResponse)]
    Value,
}

CounterQueryMsg enumeration, with its single variant CounterQueryMsg::Value is used to query the state of the contract, in our case to retrieve the current counter value. This message is passed to query entry-point of the counter smart contract. The #[derive(QueryResponses)] annotation informs the schema generator about the type of the value returned by the query.

Response message

This message is returned from query entry-point and passed to the user.

msg.rs (CounterResponse)
#[cw_serde]
pub struct CounterResponse {
    pub value: u8,
}

CounterResponse struct with a single field value, used to pass the responses (results) from the queries.

Overall, the msg.rs file is basically setting up the contract’s social skills, defining how it interacts with the outside world by initializing, executing actions, and answering questions. Each message type is a different way of communicating with the counter, making it an easy-going, versatile smart contract, ready for action.

contract.rs

Typically, in a smart contract project, the contract module (placed in the contract.rs file) contains the core logic of the contract, including functions (entry-points) for instantiation, execution, querying and migrating. This is where the main functionality of the smart contract is implemented. And this is also the case for our counter smart contract. The full source code is shown below.

contract.rs
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
 
use crate::msg::{CounterExecMsg, CounterInitMsg, CounterQueryMsg, CounterResponse};
use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError};
use cw_storage_plus::Item;
 
const COUNTER: Item<u8> = Item::new("value");
 
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
    deps: DepsMut,
    _env: Env,
    _info: MessageInfo,
    msg: CounterInitMsg,
) -> Result<Response, StdError> {
    COUNTER.save(
        deps.storage,
        &match msg {
            CounterInitMsg::Zero => 0,
            CounterInitMsg::Set(new_value) => new_value,
        },
    )?;
    Ok(Response::default())
}
 
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
    deps: DepsMut,
    _env: Env,
    _info: MessageInfo,
    msg: CounterExecMsg,
) -> Result<Response, StdError> {
    COUNTER.update::<_, StdError>(deps.storage, |old_value| {
        Ok(match msg {
            CounterExecMsg::Inc => old_value.saturating_add(1),
            CounterExecMsg::Dec => old_value.saturating_sub(1),
            CounterExecMsg::Set(new_value) => new_value,
        })
    })?;
    Ok(Response::default())
}
 
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: CounterQueryMsg) -> Result<Binary, StdError> {
    match msg {
        CounterQueryMsg::Value => Ok(to_json_binary(&CounterResponse {
            value: COUNTER.may_load(deps.storage)?.unwrap(),
        })?),
    }
}

Let's take a detailed look at this implementation.

Conditional imports

contract.rs
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;

The import shown above is used for conditional compilation in CosmWasm smart contracts. It ensures that the entry_point annotation is only included when the contract is being compiled as a standalone WASM binary, not as a library. The entry_point annotation is essential for defining the main functions of the smart contract, such as instantiate, execute, and query, which are responsible for interacting with the contract. If you're using this smart contract in another project, this configuration ensures that unnecessary code isn't included when compiling it as a library.

Imports

contract.rs
use crate::msg::{CounterExecMsg, CounterInitMsg, CounterQueryMsg, CounterResponse};
use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError};
use cw_storage_plus::Item;

These are additional imports required to compile the contract. The first use statement imports message structures for handling execution, initialization, and queries related to a counter-based smart contract. The second use imports essential tools and types from the CosmWasm standard library, required for interacting with the blockchain environment, converting data, and accessing dependencies. The third use brings in the Item type, which is needed for storing single values in the contract’s persistent storage.

Storage variable definition

contract.rs
const COUNTER: Item<u8> = Item::new("value");

COUNTER is a storage variable (although declared as const) for a counter, represented as an 8-bit unsigned integer. The "value" string is the key used to store and retrieve the counter value in the contract's persistent storage. This is used for tracking a counter value that can be incremented, decremented, queried, or reset by the smart contract. More details about the Item type can be found in cw-storage-plus documentation.

instantiate entry-point

The instantiate function (entry-point) is called during the instantiation of the smart contract. Depending on the value of the message passed in msg argument, the counter will be initialized with zero or with the value provided in CounterInitMsg::Set variant. Using the COUNTER variable, the initial value is saved in the contract's persistent storage for future use.

contract.rs
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
    deps: DepsMut,
    _env: Env,
    _info: MessageInfo,
    msg: CounterInitMsg,
) -> Result<Response, StdError> {
    COUNTER.save(
        deps.storage,
        &match msg {
            CounterInitMsg::Zero => 0,
            CounterInitMsg::Set(new_value) => new_value,
        },
    )?;
    Ok(Response::default())
}

execute entry-point

The execute function (entry-point) is called whenever the user wants to interact with the contract, especially when the value of the counter should be incremented, decremented or reset. Depending on the msg value passed as an argument, the "old value" of the counter will be incremented by one for CounterExecMsg::Inc variant, decremented by one for CounterExecMsg::Dec variant, or replaced with the new value for CounterExecMsg::Set variant. The new value will be saved in the contract's persistent storage by calling update function of the COUNTER variable.

contract.rs
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
    deps: DepsMut,
    _env: Env,
    _info: MessageInfo,
    msg: CounterExecMsg,
) -> Result<Response, StdError> {
    COUNTER.update::<_, StdError>(deps.storage, |old_value| {
        Ok(match msg {
            CounterExecMsg::Inc => old_value.saturating_add(1),
            CounterExecMsg::Dec => old_value.saturating_sub(1),
            CounterExecMsg::Set(new_value) => new_value,
        })
    })?;
    Ok(Response::default())
}

query entry-point

The query function (entry-point) is called whenever the user asks the counter smart contract for the current value. The counter value is retrieved from the contract's persistent storage, wrapped with CounterResponse type and returned to the user.

contract.rs
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: CounterQueryMsg) -> Result<Binary, StdError> {
    match msg {
        CounterQueryMsg::Value => Ok(to_json_binary(&CounterResponse {
            value: COUNTER.may_load(deps.storage)?.unwrap(),
        })?),
    }
}

What next?

Having the counter smart contract prepared, you can begin writing tests using MultiTest, or you might want to check the Sylvia version of the counter smart contract first.