🚧Build a smart contract on the Cudos network: Part 2 — set up a project🕸️

CUDOS
CUDOS
Published in
10 min readNov 23, 2021
Build on Cudos

Introduction

In the previous part, you completed the setup of the CosmWasm environment and the architecture for the CosmWasm contract. Now it is time to proceed to set up a project, and this is what you will learn in this part.

Set up a project

Root folder: CW-plus (all the paths are defined from CW-plus as root)S.

  • Remove all the folders inside /contracts
  • Go to root Cargo.toml and remove all [profile.release.package.<name>]
  • Add a new [profile.release.stake-cw20] with codegen-uints=1 and incremental = false

[workspace]
members = [“packages/*”, “contracts/*”]

[profile.release.package.stake-cw20]
codegen-units = 1
incremental = false

[profile.release]
rpath = false
lto = true
overflow-checks = true
opt-level = 3
debug = false
debug-assertions = false

Here, stake-cw20 is the project name you’ll create in the next step. Here, if you can observe this cargo.toml you’ll find this workspace configuration of cargo.
codegen-units flag controls how many code generation units the crate is split into. It takes an integer greater than 0.

  • Create a new cargo lib stake-cw20 inside a /contracts

cargo new stake-cw20 — lib

  • After creating, go inside stake-cw20/ and update cargo.toml.
  • It defines all the package that is required by this project
  • Copy the lib, dependency and dev-dependency files present inside cw20-base/cargo.toml. After completing all your changes, your cargo.toml will look like this.

[package]
name = “stake-cw20”
version = “0.1.0”
edition = “2018”

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = [“cdylib”, “rlib”]

[features]
backtraces = [“cosmwasm-std/backtraces”]
# use library feature to disable all instantiate/execute/query exports
library = []

[dependencies]
cw0 = { path = “../../packages/cw0”, version = “0.8.0” }
cw2 = { path = “../../packages/cw2”, version = “0.8.0” }
cw20 = { path = “../../packages/cw20”, version = “0.8.0” }
cw-storage-plus = { path = “../../packages/storage-plus”, version = “0.8.0” }
cosmwasm-std = { version = “0.16.0” }
schemars = “0.8.1”
serde = { version = “1.0.103”, default-features = false, features = [“derive”] }
thiserror = { version = “1.0.23” }

[dev-dependencies]
cosmwasm-schema = { version = “0.16.0” }

Smart Contract Development

Create the following folders inside an src folder

  • contracts.rs
  • error.rs
  • msg.rs
  • state.rs
  • lib.rs

Error.rs

Start by defining the error messages on the smart contracts. So, go ahead and update error.rs with the following code:

  • Here you’ll use thiserror, to create custom error messages in Rust.
  • derive macro is an easy way to provide some predefined traits to the enum or struct.
  • You’ll learn more about the errors mentioned below when using them a bit later.

use cosmwasm_std::StdError;
use thiserror::Error;

#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
#[error(“{0}”)]
Std(#[from] StdError),

#[error(“Unauthorized”)]
Unauthorized {},

#[error(“Cannot set to own account”)]
CannotSetOwnAccount {},

#[error(“Invalid zero amount”)]
InvalidZeroAmount {},

#[error(“Allowance is expired”)]
Expired {},

#[error(“No allowance for this account”)]
NoAllowance {},
}

State.rs

After defining the messages, you’ll define the states the contract holds. So, think of states as a database in a typical application where you’ll store all the data.

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

use cosmwasm_std::{Addr, Timestamp, Uint128};
use cw_storage_plus::{Item, Map};

use cw20::{AllowanceResponse, Logo};

#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
#[serde(rename_all = “snake_case”)]
pub struct TokenInfo {
pub name: String,
pub symbol: String,
pub decimals: u8,
pub total_supply: Uint128,
pub minter: Addr,
}

#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
#[serde(rename_all = “snake_case”)]
pub struct StakeHolderInfo {
pub amount: Uint128,
pub rewards: Uint128,
}
pub const TOKEN_INFO: Item<TokenInfo> = Item::new(“token_info”);
pub const LAST_DISTRIBUTE_TIME: Item<Timestamp> = Item::new(“last_distributed_time”);
pub const BALANCES: Map<&Addr, Uint128> = Map::new(“balance”);
pub const ALLOWANCES: Map<(&Addr, &Addr), AllowanceResponse> = Map::new(“allowance”);
pub const STAKE_HOLDERS: Map<&Addr, StakeHolderInfo> = Map::new(“stake_holder”);
pub const TOTAL_STAKE: Item<Uint128> = Item::new(“total_stake”);

use stmt

  • schemars::JsonSchema: This is the easiest way to generate a JSON schema for a given type.
  • serde::{Deserialize, Serialize}:
  • This is one of the most popular libs which gives you derive obj.
  • If you look closely at local cargo.toml(stake-cw20/cargo.toml) you will find
    serde = { version = “1.0.103”, default-features = false, features = [“derive”] }
    This means you’re enabling the use of `derive` in your project.
  • Serialise: You can use the to_string() method to convert the given object into a string. It is similar to Serialization in JS.
  • Deserialize: You can convert the stringified version of an object to an actual object.
  • cosmwasm_std:
  • It is a mandatory library. It is the heart and soul of CosmWasm contracts.
  • It provides all the CosmWasm data structures such as Address, Environments (env), and Dependencies (deps). We will learn more about these in later sections.
  • cw_storage_plus:
  • Another important library is used to provide you with all storage-related data structures.
  • CosmWasm encourages the use of this library as it is explicitly created for blockchain storage.
  • Cw20: These are also Rust contracts that have predefined CW20 specs, which means that they contain some predefined traits, struct, and enums that will increase speed, reduce the bugs, and help us create production-ready CW20 contracts. (more information)

derive stmt

The basic functionality of derive stmt is to provide standard traits implementation to a given type. This helps reduce the code and remove bugs.

States

If you are familiar with Rust, then you’ve figured out structs. If not, please first check the structs. Moving forward, you’ll find Items<> and Maps<> types in a state. Let’s take a look at them in detail:

  • Items:
  • Items are variables that store a single type of value.
  • Simply provide the proper type, as well as a database key not used by any other item.
  • I use const fn to create the Item, allowing it to be defined as a global compile-time constant rather than a function that must be constructed each time, which saves gas and time.
  • In our contract, you need to create three items:
  • TOKEN_INFO: Item<TokenInfo>: It is used to store the name, symbol, decimal, total supply, and minter address of the CW20 token.
  • LAST_DISTRIBUTE_TIME: Item<Timestamp>: It is used to store the timestamp, determining the last known reward distribution time. This helps with distributing rewards promptly.
  • TOTAL_STAKE: Item<Uint128>: Stores the total amount people staked on which you calculate the rewards.
  • Maps:
  • Mappings act as tables that consist of key types and corresponding value type pairs.
  • Same as above, you can use const fn to create the Item, allowing it to be defined as a global compile-time constant rather than a function that must be constructed each time, which saves gas and time.
  • For our contract, create three items:
  • BALANCES: Map<&Addr, Uint128>: Stores the balance of each address holding CW-20 tokens.
  • ALLOWANCES: Map<(&Addr, &Addr), AllowanceResponse>: Uses owner address and spender address as a composite key and in value. Use it to define how much the sender is allowed to send to other addresses.
  • STAKE_HOLDERS: Map<&Addr, StakeHolderInfo>: Uses a simple address as a key, which is the staker address and as a value you store the amount staker staked and current rewards.

That is all inside the state.rs file.

Msg.rs

Here you’ll define the structure of reading and writing only functions of blockchain.

  • Read functions are known as QueryMsg.
  • Write functions are known as ExecuteMsg.
  • And the third type of Msg is a special type of msg used for instantiation known as InstantiateMsg

Here you can name the msg as you wish. Below you can see the convention that developers use to create a CosmWasm contract.

use cosmwasm_std::{StdError, StdResult, Uint128};
use cw0::Expiration;
use cw20::{Cw20Coin};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)]
pub struct InstantiateMsg {
pub name: String,
pub symbol: String,
pub decimals: u8,
pub initial_balances: Vec<Cw20Coin>,
}

impl InstantiateMsg {
pub fn validate(&self) -> StdResult<()> {
// Check name, symbol, decimals
if !is_valid_name(&self.name) {
return Err(StdError::generic_err(
“Name is not in the expected format (3–50 UTF-8 bytes)”,
));
}
if !is_valid_symbol(&self.symbol) {
return Err(StdError::generic_err(
“Ticker symbol is not in expected format [a-zA-Z\\-]{3,12}”,
));
}
if self.decimals > 18 {
return Err(StdError::generic_err(“Decimals must not exceed 18”));
}
Ok(())
}
}

fn is_valid_name(name: &str) -> bool {
let bytes = name.as_bytes();
if bytes.len() < 3 || bytes.len() > 50 {
return false;
}
true
}

fn is_valid_symbol(symbol: &str) -> bool {
let bytes = symbol.as_bytes();
if bytes.len() < 3 || bytes.len() > 12 {
return false;
}
for byte in bytes.iter() {
if (*byte != 45) && (*byte < 65 || *byte > 90) && (*byte < 97 || *byte > 122) {
return false;
}
}
true
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = “snake_case”)]
pub enum QueryMsg {
/// Returns the current balance of the given address, 0 if unset.
/// Return type: BalanceResponse.
Balance {
address: String,
},
Minter {},
/// Returns metadata on the contract — name, decimals, supply, etc.
/// Return type: TokenInfoResponse.
TokenInfo {},
/// Only with “allowance” extension.
/// Returns how much spender can use from owner account, 0 if unset.
/// Return type: AllowanceResponse.
Allowance {
owner: String,
spender: String,
},
/// Only with “enumerable” extension (and “allowances”)
/// Returns all allowances this owner has approved. Supports pagination.
/// Return type: AllAllowancesResponse.
AllAllowances {
owner: String,
start_after: Option<String>,
limit: Option<u32>,
},
/// Only with “enumerable” extension
/// Returns all accounts that have balances. Supports pagination.
/// Return type: AllAccountsResponse.
AllAccounts {
start_after: Option<String>,
limit: Option<u32>,
},
AllStakeHolders {
start_after: Option<String>,
limit: Option<u32>,
},
TotalStake {},
StakeAndRewardOf {
account: String,
},
}
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
#[serde(rename_all = “snake_case”)]
pub enum ExecuteMsg {
/// Transfer is a base message to move tokens to another account without triggering actions
Transfer {
recipient: String,
amount: Uint128,
},
/// Burn is a base message to destroy tokens forever
Burn {
amount: Uint128,
},
/// Only with “approval” extension. Allows spender to access an additional amount tokens
/// from the owner’s (env.sender) account. If expires is Some(), overwrites current allowance
/// expiration with this one.
IncreaseAllowance {
spender: String,
amount: Uint128,
expires: Option<Expiration>,
},
/// Only with “approval” extension. Lowers the spender’s access of tokens
/// from the owner’s (env.sender) account by amount. If expires is Some(), overwrites current
/// allowance expiration with this one.
DecreaseAllowance {
spender: String,
amount: Uint128,
expires: Option<Expiration>,
},
/// Only with “approval” extension. Transfers amount tokens from owner -> recipient
/// if `env.sender` has sufficient pre-approval.
TransferFrom {
owner: String,
recipient: String,
amount: Uint128,
},
/// Only with “approval” extension. Destroys tokens forever
BurnFrom {
owner: String,
amount: Uint128,
},
/// Only with the “mintable” extension. If authorized, create amount new tokens
/// and adds to the recipient balance.
Mint {
recipient: String,
amount: Uint128,
},

CreateStake {
amount: Uint128,
},

RemoveStake {
amount: Uint128,
},

DistributRewards {},

WithdrawRewards {},
}
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
pub struct TotalStakeResponse {
pub total_stake: Uint128,
}
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
pub struct AllStakeHolderResponse {
pub accounts: Vec<String>,
}

InstantiateMsg:

  • It is similar to a constructor, i.e. used to initialise the state of the contract at the time of initialising it.
  • In your contract, it contains five variables and traits implemented on this msg.
  • First, let’s look into Traits. Traits are similar to interfaces with some additional capabilities.
  • If you look here in this contract, you will find a function inside traits:
  • Validate: it is a public function used to validate the name, symbol, and decimal present inside the InstantiateMsg struct.

Five variables:

  • Name: name of the CW20 token.
  • Symbol: symbol of CW20 token similar to $ for USD.
  • Decimals: total number of denominations a currency has.
  • For example, USD has cent as a lower denomination which means decimal is 2.
  • Generally, the currency has 18 decimals.
  • Initial_balances: it is a vector which is a one dimensional array with CW20Coin. If you look into the definition of CW20Coin, you will find that it contains two fields. One is an address, and the other is an amount that is used to give the user some initial balance.

ExecuteMsg:

  • These are write-only functions that require gas to perform computation blockchain.
  • You’ll define an enum that contains all the message names with parameters they need to run that message.

For example

  • To perform a Transfer
  • Define enum Transfer {recipient: String, amount: Uint128,} where you need two parameters. One is the recipient address who is going to receive the transferred amount and the other is the amount that you want the recipient to receive.
  • So if you need to transfer amount x to person B, you need to call Transfer message with {recipient: B, amount: x}

QueryMsg:

  • These are read-only functions that require no gas to perform computation blockchain.
  • Similar to Execute Msg, you’ll define here all the query msg.

Lib.rs

  • Lib.rs is used when you create a rust library. It is similar to the index.js file in js where you enter the file.
  • Pub mod is telling the rust compiler that these files are included inside this rust.
  • If you remove any mod then you will not be able to use that .rs file inside your lib.

pub mod allowances;
pub mod contract;
pub mod enumerable;
mod error;
pub mod msg;
pub mod state;

pub use crate::error::ContractError;

Also, you will find two new mods allowances and enumerable.

  • Allowance.rs: contain all the logic which you need for approving token(s) to the spender.
  • Enumerable.rs: contains logic where you can find a paginated array of all accounts holding CW20.

Instead of creating these two files, you can copy those files directly from here:

In the third part of this series, we will cover the Contract.rs which contains the main business logic and discuss the various functions that enable you to perform some computation and store values inside states.

What can you do right now?

You can get involved right away by joining our incentivised testnet Project Artemis by following the links below:

Notably, you’ll receive rewards based on the tasks you complete as part of the testnet.

P.S. If you’ve already bought your CUDOS tokens, you can make the most of them by staking them on our platform to secure the network and, in return, receive rewards.

About Cudos

The Cudos Network is a layer-one blockchain and layer-two computation and oracle network designed to ensure decentralized, permissionless access to high-performance computing at scale and enables scaling of computing resources to hundreds of thousands of nodes. Once bridged onto Ethereum, Algorand, Polkadot and Cosmos, Cudos will enable scalable computational and layer-two oracles on all of the bridged blockchains.

For more, please visit:
Website, Twitter, Telegram, YouTube, Discord, Medium

--

--

CUDOS
CUDOS
Editor for

CUDOS is powering AI by uniting blockchain and cloud computing to realise the vision of a sustainable, equitable, and democratised Web3.