CosmWasm 101-Instantiate, Execution and Query Messages

Previously in this series, we created a simple CosmWasm contract by defining contract entry points using the cosmwasm-std library. In this post, we will be covering creating Instantiate,Execution and Query messages that are used to interact with contract entry points. Also, we will configure the entry point functions to handle the messages.

Instantiate Message

The initial state of the contract can be initialized using an Instantiate message. Let us create a new filemsg.rs and add a new empty struct for the instantiated message.

use super::*;

// Message for Instantiating Contract
#[cw_serde]
pub struct InstantiateMsg {
    pub counter: u64,
}

For our simple counter contract, we will have the above-mentioned instantiate message, which sets the initial state of the contract with a counter value.

In CosmWasm, the blockchain state is just massive key-value storage. The keys are prefixed with meta information pointing to the contract that owns them. We will start creating states by using cosmological storage abstraction. Add Cosmetic Storage Plus Crate as a dependency in Cargo.toml.

[dependencies]
cosmwasm-std = {version = "1.2.2",features=["iterator"]}
cw-storage-plus = "1.0.1"

Let us create a new filestate.rs and define the contract state structure and store it using Item.

use super::*;

#[cw_serde]
pub struct CwSimpleCounter {
    pub owner: String,
    pub counter: u64,
}

pub const STATE: Item<CwSimpleCounter> = Item::new("COUNTER");

The contract state is stored in the blockchain rather than in contracts and accessed via deps.

Let's implement the instantiate function and initialize the contract state using an instantiate message.

#[entry_point]
pub fn instantiate(
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    msg: InstantiateMsg,
) -> Result<Response, ContractError> {
    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)
        .map_err(ContractError::Std)?;
    let contract_state = CwSimpleCounter {
        owner: info.sender.to_string(),
        counter: msg.counter,
    };
    STATE
        .save(deps.storage, &contract_state)
        .map_err(ContractError::Std)?;

    Ok(Response::new()
        .add_attribute("method", "instantiate")
        .add_attribute("value", msg.counter.to_string()))
}

Setting the name and contract version will be helpful while migrating the contract. Initialize the contract state object by setting the owner and counter, and save it to the storage.

Create an enum for handling errors in contracts.

use super::*;

#[derive(Error, Debug)]
pub enum ContractError {
    #[error("{0}")]
    Std(#[from] StdError),
    #[error("DecodeError {error}")]
    DecodeError { error: String },
}

Execution Message

Execution messages are used to change the state of the contracts. Let us define the execution message in msg.rs.

#[cw_serde]
pub enum ExecuteMsg {
    IncrementCounter {},
    DecrementCounter {},
    UpdateCounter { count: u64 },
    ResetCounter {},
}

Define the execute function, which handles the execution messages.

#[entry_point]
pub fn execute(
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    msg: ExecuteMsg,
) -> Result<Response, ContractError> {
    let mut contract_state = STATE
        .load(deps.as_ref().storage)
        .map_err(ContractError::Std)?;

    if contract_state.owner != info.sender.to_string() {
        return Err(ContractError::Unauthorized {});
    }
    match msg {
        ExecuteMsg::IncrementCounter {} => {
            contract_state.counter.checked_add(1).unwrap();
            STATE.save(deps.storage, &contract_state)?;
            Ok(Response::new()
                .add_attribute("method", "increment")
                .add_attribute("count", contract_state.counter.to_string()))
        }
        ExecuteMsg::DecrementCounter {} => {
            contract_state.counter.checked_sub(1).unwrap();
            STATE.save(deps.storage, &contract_state)?;
            Ok(Response::new()
                .add_attribute("method", "decrement")
                .add_attribute("count", contract_state.counter.to_string()))
        }
        ExecuteMsg::UpdateCounter { count } => {
            contract_state.counter = count;
            STATE.save(deps.storage, &contract_state)?;
            Ok(Response::new()
                .add_attribute("method", "update")
                .add_attribute("count", contract_state.counter.to_string()))
        }
        ExecuteMsg::ResetCounter {} => {
            contract_state.counter = 0;
            STATE.save(deps.storage, &contract_state)?;
            Ok(Response::new()
                .add_attribute("method", "reset")
                .add_attribute("count", contract_state.counter.to_string()))
        }
    }
}

We have defined the execute message that consists increment , decrement,update and reset counter. Based on the message type, the execution will be done.

Query Message

Query messages won't alter the state of the contract but are used just to read the state data.

#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
    #[returns(u64)]
    GetCount {},
}

We are just querying the counter from the state of the contract and not modifying it. Modifying the query function to handle the query messages

#[entry_point]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
    match msg {
        QueryMsg::GetCount {} => {
            let state = STATE.load(deps.storage).unwrap();
            Ok(to_binary(&state.counter).unwrap())
        }
    }
}

Till now, we have defined the contract messages for instantiate, execute, and query and updated the respective contract functions to handle these messages. In the upcoming post, we will focus on writing unit tests for the functionality that we implemented, building the WASM binary, and deploying it in the testnet.

The source code is available on GitHub

Did you find this article valuable?

Support The Missing Semicolon by becoming a sponsor. Any amount is appreciated!