DEV Community

Vu
Vu

Posted on • Updated on

Writing a smart contract with CosmWasm (Part 2)

Smart contract explain

Welcome back to my series. In this section, I will explain how smart contract work. Don't think it is difficult. Keep it easy to imagine.

The smart contract has a state - or what we call storage. We need to interact with the state by query and state.

Image description

Imagine, developing a smart contract same as we are developing a backend application API:

Smart contract API Application Description
State Database Store data for application
Query QUERY Get current data stored in the application
Execute POST, PUT, DELETE Change data stores in application

And an important component is the message, it is the way we tell the smart contract do something.
Done. We have the understanding mechanism of the smart contract. Moving to the next section, we how to create a state of the smart contract.

Create state smart contracts.

Let's start in the src/state.rs and update the file with the following code:

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use cw_storage_plus::Item;

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct State {
    pub counter: i32,
}

pub const STATE: Item<State> = Item::new("state");
Enter fullscreen mode Exit fullscreen mode

Image description

I note something to give a quick overview:

  • JsonSchema allows structs to be serialized and deserialized to and from JSON
  • Deserialize and Serialize provide the serialization described above.
  • Item is a helper provided by storage plus. It effectively means we can store an item in storage. In this case, the STATE variable is an Item that stores a singular State struct.

We just define a schema of the state.

Instantiating a Contract

When contracts are stored on the chain, they must be instantiated.
Instantiating a contract is achieved by a special message.
First, update the src/msg.rs with the code:

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct InstantiateMsg {}
Enter fullscreen mode Exit fullscreen mode

Image description

We don't need parameters when instantiating the contract in the special message InstantiateMsg.
Now let's use this message.

Instantiation

Alright, open the src/contract.rs, this is where logic happens, update the file with the following code:

#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};
use cw2::set_contract_version;

use crate::error::ContractError;
use crate::msg::InstantiateMsg;
use crate::state::{State, STATE};

const CONTRACT_NAME: &str = "crates.io:cw-starter";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
pub const ZERO_CODE: i32 = 0;

#[cfg_attr(not(feature = "library"), 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)?;

    let state = State { counter: 0 };

    STATE.save(deps.storage, &state)?;
    Ok(Response::new().add_attribute("counter", ZERO_CODE.to_string()))
}
Enter fullscreen mode Exit fullscreen mode

Image description

This is explicit for reading. I explain it step by step. We create the first entry point, and pass some parameters to instantiate:

  • deps - The dependencies, this contains your contract storage, the ability to query other contracts and balances, and some API functionality
  • env - The environment, contains information such as its address, block information such as current height and time, as well as some optional transaction info
  • info - Message metadata, contains the sender of the message (Addr).
  • msg - The InstantiateMsg you define in src/msg.rs

The dash _ prefix of params means we don't use it in function and the compiler will not show a warning notification.

Return of this function Result enum with Response struct from cosmwasm_std and enum ContractError. Then we set the contract name and contract version to storage with set_contract_version.

Now this is the main section of instantiate, we create an instance of struct State with a value counter equal to 0 and assign it to the state variable

STATE creates a new item in storage and saves state to storage.
Finally, we warp Response struct has attribute counter and its value is 0 by Ok.

Testing module

Alright, we have implemented the first entry point of our contract! Let's get to testing it!
So let's look at our src/contract.rs file, at the bottom, add the following code:

#[cfg(test)]
mod tests {
    use crate::msg::InstantiateMsg;
    use cosmwasm_std::attr;
    use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};

    use super::{instantiate, ZERO_CODE};

    const ADDR1: &str = "addr1";

    // instantiate with provide admin address
    #[test]
    fn test_instantiate() {
        let mut deps = mock_dependencies();
        let env = mock_env();
        let info = mock_info(ADDR1, &[]);

        let msg = InstantiateMsg {};
        let resp = instantiate(deps.as_mut(), env, info, msg).unwrap();

        assert_eq!(
            resp.attributes,
            vec![attr("counter", ZERO_CODE.to_string())]
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

Image description

We create a module test and unit test function test_instantiate.
First, we need to import mock to simulate parameters (mock_dependencies, mock_env, mock_info) and import the instantiate function and ZERO_CODE constant from above via the super keyword.
asssert_eq! is marco in Rust to compare two values. In this case, we compare response from instantiate with array vector has an element attr("counter", ZERO_CODE.to_string())

Let's test the module. We have two ways:

  1. Open the terminal and run cargo test:
  2. Simply, press Run Test in VS Code: Image description

If your output is like the below, everything is right:

running 1 test
test contract::tests::test_instantiate ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests cw-starter

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Enter fullscreen mode Exit fullscreen mode

It runs 1 test case, and 1 passed.

Sumary

We just created the first entry point of the smart contract is instantiate.
We also created a module testing function.

Thanks to everyone to read the post. Part 3 I introduce how to execute the smart contract. Have a good day!

Top comments (0)