🚧Build a smart contract on the Cudos network: Part 4 — test and try🕸️
Introduction
We explored setting up the CosmWasm environment and the architecture for the CosmWasm contract in the first part of our series. The second part described the process of setting up a project.
In the third, we discussed the main business logic and the functions used to compute and store values within states. In this series’s fourth and final installment, we will perform unit and integration testing, applying what we have learned thus far. Additionally, we will attempt a few functions to assess our comprehension of the entire procedure.
Also in this series:
Build a smart contract on the Cudos network: Part 1: preparation, Part 2: set up a project, Part 3: Main business logic.
Test
Rust provided an excellent functionality where you can do all unit and integration tests inside a project. In this way, you don’t need to set up another environment for testing.
#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, StdError};
…..
}
- Use super::* will import all the libs used in this project
- use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}: here you will get mocks of deps, env, and info which will require for testing
- Let’s go straight to the test
do_instantiate
A. First, create a function that will deploy the contract successfully for us, so you need a function do_instantiate that can deploy a contract for you.
B. Params:
- Deps: the mutable object of deps so you can access storage, amount, and mint
- Addr: to whom you assign an initial balance
- Amount: the amount of CW20 tokens to Addr
- Owner: Owner of the contract and also acts as minter. Minter is only responsible for minting CW20 tokens to other addresses.
C. Here you need an InstantiateMsg.
D. After that, you need to mock the caller who will instantiate the contract, so for that, use mock_info where you put owner as of the first param. If you need to pass some native coin, you must insert an array of Coins as the second parameter.
E. I have used mock_env to mock the blockchain info like height, time, etc.
F. I just call the instantiate function with all the parameters.
G. I assert response length with 0 because Response::default() will give zero length response object
H. Call query_token_info() to confirm data storage on the contract.
I. Call getBalance() to check whether the recipient got the initial balance or not.
J. Call query_minter to verify if the owner of the contract is minter or not.
fn do_instantiate(
mut deps: DepsMut,
addr: &str,
amount: Uint128,
owner: &str,
) -> TokenInfoResponse {
let instantiate_msg = InstantiateMsg {
name: “Auto Gen”.to_string(),
symbol: “AUTO”.to_string(),
decimals: 3,
initial_balances: vec![Cw20Coin {
address: addr.to_string(),
amount,
}],
};
let info = mock_info(owner, &[]);
let env = mock_env();
let res = instantiate(deps.branch(), env, info, instantiate_msg).unwrap();
assert_eq!(0, res.messages.len());let meta = query_token_info(deps.as_ref()).unwrap();
assert_eq!(
meta,
TokenInfoResponse {
name: “Auto Gen”.to_string(),
symbol: “AUTO”.to_string(),
decimals: 3,
total_supply: amount,
}
);
assert_eq!(get_balance(deps.as_ref(), addr), amount);
assert_eq!(query_minter(deps.as_ref()).unwrap(), owner);
meta
}
Can_mint_by_minter
A. can_mint_by_minter test case ensured that only a minter could mint new tokens to other addresses.
B. Let’s go straight into the test cases.
C. To mark any function as a test case in rust, you need to add #[test] on top of that function
D. Generally, developers write all the positive and negative test cases related to a given function inside the same test cases. So you have to segregate test cases using comments(//).
E. First, you have to mock the deps object because it contains the storage, API and querier provided by Blockchain; this is done using mock_depenndencies().
F. Then created a few variables for
i. recipient: who will receive tokens as part of the initial balance
ii. amount: how many tokens the recipient receive as a part of the initial balance
iii. owner: the address who deployed the contract and became minter
iv. winner: address to whom owner will mint tokens
v. prize: the amount of CW20 tokens the owner will mint to the winner
G. First test case: Successful minting of tokens to the winner by owner
i. To call the Mint function, you need to create an ExecuteMsg::Mint object.
ii. mock_info to mock the caller as the owner address.
iii. mock_env to mock blockchain
iv. Use execute method to run the executeMsg created on the first step and do unwrap returns a panic when it receives a None.
v. Check the balance of the Winner using getBalance(). If the balance is equal to the prize, then our Mint function is working correctly.
H. Second test case: Others cannot mint
i. To call the Mint function, you need to create an ExecuteMsg::Mint object.
ii. mock_info to mock others addresses. For that, use “anyone else” as caller
iii. mock_env to mock blockchain.
iv. Use the execute method to run the executeMsg created on the first step and do unwrap_err return an error message.
v. Assert the error message received from function ContractError: Unauthorized
I. Third Test case: throw error as pass zero as an amount
i. To call the Mint function, you need to create an ExecuteMsg::Mint object with an amount as zero.
ii. mock_info to mock owner as caller
iii. mock_env to mock blockchain
iv. Use the execute method to run the executeMsg created on the first step and do unwrap_err return an error message.
v. Assert the error message received from function ContractError::InvalidZeroAmount
#[test]
fn can_mint_by_minter() {
let mut deps = mock_dependencies(&[]);let recipient = String::from(“recipient”);
let amount = Uint128::new(11223344);
let owner = String::from(“asmodat”);
do_instantiate(deps.as_mut(), &recipient, amount, &owner);
let winner = String::from(“lucky”);
let prize = Uint128::new(222_222_222);
let msg = ExecuteMsg::Mint {
recipient: winner.clone(),
amount: prize,
};// successful minting of tokens to winner by owner
let info = mock_info(owner.as_ref(), &[]);
let env = mock_env();
let res = execute(deps.as_mut(), env, info, msg).unwrap();assert_eq!(0, res.messages.len());
assert_eq!(get_balance(deps.as_ref(), recipient), amount);
assert_eq!(get_balance(deps.as_ref(), winner.clone()), prize);// Others cannot mint
let msg = ExecuteMsg::Mint {
recipient: String::from(“lucky”),
amount: Uint128::new(222),
};
let info = mock_info(“anyone else”, &[]);
let env = mock_env();
let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
assert_eq!(err, ContractError::Unauthorized {});// throw error as pass zero as an amount
let msg = ExecuteMsg::Mint {
recipient: winner.clone(),
amount: Uint128::zero(),
};
let info = mock_info(owner.as_ref(), &[]);
let env = mock_env();
let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
assert_eq!(err, ContractError::InvalidZeroAmount {});
}
Homework
After that, I want you guys to try and create a few functions by taking the above code and CW-plus as an example:
- DistributRewards()
- Distribute rewards to all the stakeholders
- Rewards are calculated daily
- Once users call this function, awards are distributed to each stakeholder
- Only minter address can call this function
- WithdrawRewards()
- The caller can withdraw its reward, not stake.
- StakeAndRewardOf(account):
- Display the stake and rewards of a given account
- TotalStake:
- Display the total stake.
What can you do right now?
You can get involved right away by joining our incentivised testnet Project Artemis by following the links below:
- Join Cudos’ Discord server
- Join Cudos’ Telegram community
- Buy CUDOS
- Become a Cudos’ ambassador
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 1 blockchain and layer 2 computation and oracle network designed to ensure decentralised, permissionless access to high-performance computing at scale. It enables the 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 bridged blockchains.
Learn more:
Website, Twitter, Telegram, YouTube, Discord, Medium