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.
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");
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 {}
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()))
}
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 insrc/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())]
)
}
}
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:
- Open the terminal and run
cargo test
: - Simply, press
Run Test
in VS Code:
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
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)