Intro to writing Cosmwasm Smart contract

In this article, we will explore the fundamentals of CosmWasm smart contracts on Oraichain. Through the course of this article, we will understand token contracts, ready to deploy on Oraichain CosmWasm.

Once we are all set with the required requisites, let’s start working on our first token contract on Oraichain.

Setting up the Dev Environment

To understand, how to set the dev environment for writing CosmWasm smart contracts, follow the previous tutorial about setting up dev environment for oraichain .

Create a new CosmWasm Smart Contract

To create a new CosmWasm smart contract we will set up the cw-template with the help of the following command:

cargo generate --git <https://github.com/CosmWasm/cw-template.git> --name first-token

Install the cw20-base

To install cw20-base, navigate to the first-token directory, after setting up the cw-template

cd ./first-token

Then navigate to the Cargo.toml to add the following dependencies under the dependencies section:

# ...

[dependencies]
cw20-base = {  version = "0.13.2", features = ["library"] }
# ...

💡 The CW20 token standard in CosmWasm is similar to your ERC20 token standard for EVM compatible architectures. Similar to your OpenZeppelin library for Ethereum smart contracts, Cosmos has cw-plus GitHub repo for CosmWasm smart contract templates.

Modify the contract files ⚙️

Now that , we have completed the setup , so lets modify the boilerplate code and get our token ready 🪙 . To know what each file is used for you can refer this tutorial .

We will be changing the logic inside the following files :

  • msg.rs

  • lib.rs

  • contract.rs

  • schemas.rs

For this tutorial you can ignore other files .

So let’s begin by editing src/msg.rs file in our code editor , you can paste the following code

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

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct MigrateMsg {}

let’s breakdown one by one what is happening in this code .

  • use schemars::JsonSchema: This line imports the JsonSchema trait from the schemars crate. The JsonSchema trait is used for generating JSON schemas for Rust data structures.

  • use serde::{Deserialize, Serialize};: This line imports the Deserialize and Serialize traits from the serde crate. These traits are used for serializing Rust data structures into JSON and deserializing JSON back into Rust data structures.

  • #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]: This is a Rust attribute macro used for automatically deriving implementations of several traits for the MigrateMsg struct. Specifically:

  • Serialize and Deserialize are derived for serialization and deserialization purposes.

  • Clone is derived to allow creating copies of the struct.

  • Debug is derived to enable debug printing of the struct.

  • PartialEq is derived to enable comparison of instances of the struct for equality.

  • JsonSchema is derived to generate a JSON schema for the struct. This schema can be used for documentation or validation purposes. We will discuss the MigrateMsg struct in further steps .

Edit src/lib.rs

Now open the lib.rs and paste the following code

pub mod contract;
pub mod msg;

💡 in lib.rs we declare the crates we are using , and for this tutorial we are only modifying the contract.rs and msg.rs file .

Open src/contract.rs and edit it

Now paste the following code in src/contract.rs

#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{
    to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult,
};
use cw20_base::ContractError;
use cw20_base::enumerable::{query_all_allowances, query_all_accounts};
use cw20_base::msg::{QueryMsg,ExecuteMsg};

use crate::msg::MigrateMsg;
use cw2::set_contract_version;
use cw20_base::allowances::{
    execute_decrease_allowance, execute_increase_allowance, execute_send_from,
    execute_transfer_from, query_allowance, execute_burn_from,
};
use cw20_base::contract::{
    execute_mint, execute_send, execute_transfer, execute_update_marketing,
    execute_upload_logo, query_balance, query_token_info, query_minter, query_download_logo, query_marketing_info, execute_burn,
};

// version info for migration info
const CONTRACT_NAME: &str = "crates.io:cw20-token";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: cw20_base::msg::InstantiateMsg,
) -> Result<Response, ContractError> {
    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

    /* Execute the instantiate method from cw_20_base as the code from that
    library is already battle tested we do not have to re-write the full
    functionality: <https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw20-base*/>
    Ok(cw20_base::contract::instantiate(deps, env, info, msg)?)
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: ExecuteMsg,
) -> Result<Response, cw20_base::ContractError> {
    match msg {
        ExecuteMsg::Transfer { recipient, amount } => {
            execute_transfer(deps, env, info, recipient, amount)
        }
        ExecuteMsg::Burn { amount } => execute_burn(deps, env, info, amount),
        ExecuteMsg::Send {
            contract,
            amount,
            msg,
        } => execute_send(deps, env, info, contract, amount, msg),
        ExecuteMsg::Mint { recipient, amount } => execute_mint(deps, env, info, recipient, amount),
        ExecuteMsg::IncreaseAllowance {
            spender,
            amount,
            expires,
        } => execute_increase_allowance(deps, env, info, spender, amount, expires),
        ExecuteMsg::DecreaseAllowance {
            spender,
            amount,
            expires,
        } => execute_decrease_allowance(deps, env, info, spender, amount, expires),
        ExecuteMsg::TransferFrom {
            owner,
            recipient,
            amount,
        } => execute_transfer_from(deps, env, info, owner, recipient, amount),
        ExecuteMsg::BurnFrom { owner, amount } => execute_burn_from(deps, env, info, owner, amount),
        ExecuteMsg::SendFrom {
            owner,
            contract,
            amount,
            msg,
        } => execute_send_from(deps, env, info, owner, contract, amount, msg),
        ExecuteMsg::UpdateMarketing {
            project,
            description,
            marketing,
        } => execute_update_marketing(deps, env, info, project, description, marketing),
        ExecuteMsg::UploadLogo(logo) => execute_upload_logo(deps, env, info, logo),
    }
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
    match msg {
        /* Default methods from CW20 Standard with no modifications:
        <https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw20-base> */
        QueryMsg::Balance { address } => to_binary(&query_balance(deps, address)?),
        QueryMsg::TokenInfo {} => to_binary(&query_token_info(deps)?),
        QueryMsg::Minter {} => to_binary(&query_minter(deps)?),
        QueryMsg::Allowance { owner, spender } => {
            to_binary(&query_allowance(deps, owner, spender)?)
        }
        QueryMsg::AllAllowances {
            owner,
            start_after,
            limit,
        } => to_binary(&query_all_allowances(deps, owner, start_after, limit)?),
        QueryMsg::AllAccounts { start_after, limit } => {
            to_binary(&query_all_accounts(deps, start_after, limit)?)
        }
        QueryMsg::MarketingInfo {} => to_binary(&query_marketing_info(deps)?),
        QueryMsg::DownloadLogo {} => to_binary(&query_download_logo(deps)?),
    }
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
    Ok(Response::default())
}

Now let’s break down the above code and understand it’s logic .

  • Since the execution and query logic we are using are from cw20-base crate we are not required to write our own logic for that ,

  • using the use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult,}; we are importing the functionalities of cosmwasm-std library let’s understand each of them :

    • deps - The dependencies, this contains your contract storage, the ability to query other contracts and balances, and some API functionality.

    • env - The environment, contains contract 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) and the funds sent with it a Vec<Coin>.

    • msg - The InstantiateMsg you define in src/msg.rs.

  • Then we are using the use cw2::set_contract_version for setting the contract version in the instantiate function of our contract .

  • We are using the cw-20 utilities in our execute and query functions and calling the needful functions when we need them . For example let us take the code snippet

 ExecuteMsg::Mint { recipient, amount } => execute_mint(deps, env, info, recipient, amount),

In this code snippet we are executing the mint function from the cw20 crate which is taking recipient and amount as arguments , the mint function will mint the specified amount of tokens to the recipient account which is an address .

💡 We will talk of execute and query functions in depth in further tutorials

Instantiating the contract

We have discussed the steps for instantiating the contract and setting up cosmwasm-ide in this tutorial , for now I will directly demonstrate the procedure to interact with our deployed contract .

After following all the steps in this tutorial , you should be getting the similar interface at the cosmwasm-ide .let’s fill them

Untitled

I am filling up the following options , you can toggle between option 1 or option 2 according to your requirements

Untitled

Now let’s interact with our contract , I have queried token balance for my account

Untitled

Hurray !! we are getting the correct response , Now we have successfully created our first token and deployed it on oraichain .

This tutorial was to introduce you to the cw20 token standard and cosmwasm contract , in future tutorials we will dive deep into the practices of writing badass cosmwasm smart contracts . till then stay tuned . Happy Hacking ✌️ .

Last updated