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
[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]
[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]
[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]
[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]
[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 complementscosmwasm-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]
[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 ofMultiTest
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
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:
.
├── 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 namedcontract.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 namedmsg.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.
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
use cosmwasm_schema::cw_serde;
Required imports, like cw_serde
annotation.
Instantiation message
This message is passed to instantiate
entry-point.
#[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.
#[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.
#[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.
#[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.
#[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
#[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
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
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.
#[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.
#[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.
#[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.