Subscribe to our free newsletter

To make sure you won't miss any valuable content we share with our community.

How to Set Up the Dependencies For Running Local Terra: All You Need to Know

In this tutorial-based article, we are going to set up the dependencies for running Local Terra. So, you will be guided through all the installations and commands necessary to start your interaction with local Terra smart contracts. Installing Go, Docker, Terrad, and git cloning local Terra, as well as terra-core repositories, are some of the main steps we are going to take in this tutorial.

Installing the Dependencies

For running local terra the main step is to install all of the dependencies required. The first step is to install go. You need to be careful that the version should be higher than 1.17.5.

The Dependencies for Running Local Terra #1: Go

To install Go, you should find the latest version for your operating system using this link. Then, the next step is to check the version to make sure it meets the requirement of our version (higher than 1.17.5). To do so, enter the following command in the terminal:

go version

Result:

go version go1.18.1 linux/amd64

If you see a result like the above, your installation has been successful. You should also have the Rust programming language installed using the below command (Notice that this command is used for Linux OS) in the terminal:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

For other operating systems, follow the instructions on this link. Now, it is time to git clone the terra-core repository. In the terminal, enter the following command:

git clone https://github.com/terra-money/core terra-core

After that, change the directory to the terra-core folder by entering the below command in the terminal:

cd terra-core

To install the downloaded repository, enter the following in the terminal:

git checkout main make install

And, the most important step is to git clone the local Terra repository. We follow the same steps for this one as well:

git clone --depth 1 https://www.github.com/terra-money/LocalTerra cd LocalTerra

The Dependencies for Running Local Terra #2: Docker

One of the required dependencies for running local Terra is Docker. You should also have docker installed on your operating system. Here we explain the process for Linux:
For the installation guide in other operating systems than Linux, follow the instructions on the Docker.

dependencies for running local Terra

Installation Guide on Linux

This process consists of 5 steps that are described in the followings.

1. Dependencies:

At first, update the system to get the list of available packages and their version numbers. To do so, enter the following command in the terminal:

sudo apt update

To prepare for the installation run these commands in the terminal one after the other:

sudo apt -y install apt-transport-https ca-certificates curl software-properties-common
sudo apt -y remove docker docker-engine docker.io containerd runc

2. Add Docker’s Official GPG Key:

Also run the following commands in the terminal:

curl -fsSL https://download.docker.com/linux/ubuntu/gpg |
sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

3. Add the Docker Repository to Linux:

To add the Docker repository for Linux, enter the following commands in the terminal:

echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/dockerarchive-keyring.gpg] https://download.docker.com/linux/ubuntu bionic stable" |

sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu bionic stable

4. Install the Docker Engine and the Docker Compose:

And finally, It is time to install docker by running these 2 commands in the terminal one after the other.

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io

5. Checking the Installation:

To start with checking the installation, use the command below:

sudo usermod -aG docker
USER newgrp docker

To make sure the installation has been successful, run the following command in the terminal:

docker version

Result:

Client: Docker Engine - Community Version: 20.10.14 API version: 1.41 Go version: go1.16.15 Git commit: a224086 Built: Thu Mar 24 01:47:57 2022 OS/Arch: linux/amd64 Context: default Experimental: true Server: Engine: Version: 20.10.14 API version: 1.41 (minimum version 1.12) Go version: go1.16.15 Git commit: 87a90dc Built: Thu Mar 24 17:15:03 2022 OS/Arch: linux/amd64 Experimental: false containerd: Version: v1.5.11 GitCommit: 3df54a852345ae127d1fa3092b95168e4a88e2f8 runc: Version: 1.0.3 GitCommit: docker-init: Version: 0.19.0 GitCommit: de40ad

After installation for every use, you need to first sign in and run the following commands in the terminal:

sudo usermod -aG docker docker login docker-compose up

Run the above commands in the local Terra directory terminal.
Result:

terrad_1 | 1:47PM INF indexed block height=143 module=txindex terrad_1 | 1:47PM INF Timed out dur=4987.108435 height=144 module=consensus round=0 step=1 terrad_1 | 1:47PM INF received proposal module=consensus proposal={"Type":32,"block_id":{"hash":"F4DD36C8FB8D539F3F28ABB8BB7969A099AEE78B707FAE7F3C7DC8D36BF38B9D","parts": {"hash":"F92FDEA4153F172B082676706A3B35F54A0EF3D6639FA23A3FD88FFBB40ACA6E","total":1}},"height":144,"pol_round":- 1,"round":0,"signature":"nDA4YtVFM/a8TXCLMwTwydkfJSwOoI57zNkkKieyv0xy/ zbnCVIM10cpzh78+0wym1jT3fj16ZqH5OiFE3kaAg==","timestamp":"2022-05- 07T13:47:21.846543194Z"} terrad_1 | 1:47PM INF received complete proposal block hash=F4DD36C8FB8D539F3F28ABB8BB7969A099AEE78B707FAE7F3C7DC8D36BF38B9D height=144 module=consensus terrad_1 | 1:47PM INF finalizing commit of block hash=F4DD36C8FB8D539F3F28ABB8BB7969A099AEE78B707FAE7F3C7DC8D36BF38B9D height=144 module=consensus num_txs=0 root=EFAA4A74DD1E73032E0FF553A37CCDFAD443AF0EE395516EB247948ADAF45A21 terrad_1 | 1:47PM INF minted coins from module account amount=226570495uluna from=mint module=x/bank terrad_1 | 1:47PM INF executed block height=144 module=state num_invalid_txs=0 num_valid_txs=0 terrad_1 | 1:47PM INF commit synced commit=436F6D6D697449447B5B313138203835203538203234362036322032392 03133203136302033372031393920343920393520373020323030203231312032323 12032353320323039203231382031393720313538203134392032313720322039203 13238203232352031362031363420313339203131392032325D3A39307D terrad_1 | 1:47PM INF committed state app_hash=76553AF63E1D0- DA025C7315F46C8D3DDFDD1DAC59E95D9020980E110A48B7716 height=144 module=state num_txs=0 terrad_1 | 1:47PM INF indexed block height=144 module=txindex Now, you should install cargo: To install cargo, run these 3 commands in the terminal separately: cargo install cargo-generate --features vendored-openssl cargo install cargo-run-scrip

For the following command in the terminal, instead of YourProjectName, you can enter any name you want for your project to be created (make sure you run this command in the directory of local Terra you have just git cloned):

cargo generate --git https://github.com/CosmWasm/cw-template.git --name YourProjectName

Result:

Generating template ... [ 1/36] Done: .cargo/config [ 2/36] Done: .cargo [ 3/36] Skipped: .circleci/config.yml [ 4/36] Done: .circleci [ 1/36] Done: .cargo/config [ 2/36] Done: .cargo [ 3/36] Skipped: .circleci/config.yml [ 4/36] Done: .circleci [ 5/36] Done: .editorconfig [ 6/36] Done: .github/workflows/Basic.yml [ 7/36] Done: .github/workflows [ 8/36] Done: .github [ 9/36] Done: .gitignore [10/36] Done: .gitpod.Dockerfile [11/36] Done: .gitpod.yml [ 1/36] Done: .cargo/config [ 2/36] Done: .cargo [ 3/36] Skipped: .circleci/config.yml [ 4/36] Done: .circleci [ 5/36] Done: .editorconfig [ 6/36] Done: .github/workflows/Basic.yml [ 7/36] Done: .github/workflows [ 8/36] Done: .github [ 9/36] Done: .gitignore [10/36] Done: .gitpod.Dockerfile [11/36] Done: .gitpod.yml [12/36] Done: Cargo.lock [13/36] Done: Cargo.toml [14/36] Done: Developing.md [15/36] Done: Importing.md [16/36] Done: LICENSE [17/36] Done: NOTICE [ 1/36] Done: .cargo/config [ 2/36] Done: .cargo [ 3/36] Skipped: .circleci/config.yml [ 4/36] Done: .circleci [ 5/36] Done: .editorconfig [ 6/36] Done: .github/workflows/Basic.yml [ 7/36] Done: .github/workflows [ 8/36] Done: .github [ 9/36] Done: .gitignore [10/36] Done: .gitpod.Dockerfile [11/36] Done: .gitpod.yml [12/36] Done: Cargo.lock [13/36] Done: Cargo.toml [14/36] Done: Developing.md [15/36] Done: Importing.md [16/36] Done: LICENSE [17/36] Done: NOTICE [18/36] Done: Publishing.md [19/36] Done: README.md [20/36] Done: examples/schema.rs [21/36] Done: examples [22/36] Done: rustfmt.toml [23/36] Done: schema/count_response.json [24/36] Done: schema/execute_msg.json [ 1/36] Done: .cargo/config [ 2/36] Done: .cargo [ 3/36] Skipped: .circleci/config.yml [ 4/36] Done: .circleci [ 5/36] Done: .editorconfig [ 6/36] Done: .github/workflows/Basic.yml [ 7/36] Done: .github/workflows [ 8/36] Done: .github [ 9/36] Done: .gitignore [10/36] Done: .gitpod.Dockerfile [11/36] Done: .gitpod.yml [12/36] Done: Cargo.lock [13/36] Done: Cargo.toml [14/36] Done: Developing.md [15/36] Done: Importing.md [16/36] Done: LICENSE [17/36] Done: NOTICE [18/36] Done: Publishing.md [19/36] Done: README.md [20/36] Done: examples/schema.rs [21/36] Done: examples [22/36] Done: rustfmt.toml [23/36] Done: schema/count_response.json [24/36] Done: schema/execute_msg.json [25/36] Done: schema/instantiate_msg.json [26/36] Done: schema/query_msg.json [27/36] Done: schema/state.json [28/36] Done: schema [29/36] Done: src/contract.rs [30/36] Done: src/error.rs [31/36] Done: src/helpers.rs [32/36] Done: src/integration_tests.rs [33/36] Done: src/lib.rs [34/36] Done: src/msg.rs [35/36] Done: src/state.rs [36/36] Done: src Moving generated files into: `/home/mohamad/LocalTerra/my-project/np`... Initializing a fresh Git repository Done! New project created /home/mohamad/LocalTerra/my-project/np

CosmWasm Smart Contracts on Local Terra

In this section, we are going to get familiar with CosmWasm smart contracts written inRust programming language for interacting with local Terra through Python or JavaScript. These smart contracts have different functions that we need to send specific JSON messages to interact with them. In the end, we are going to create the artifacts folder containing the necessary binary files to interact with local Terra.

If you open the YourProjectName folder and in its src folder, the Msg.rs and contract.rs alongside with other Rust script files are found. The Msg.rs contract is related to the 3 kinds of messages we can send to our contract and it is composed of 3 main parts:

    1. InstantiateMsg struct:
This message sets the state in the smart contract meaning an initial state must be given to the smart contract when it is launched.
    2. ExecuteMsg enum:
This is a message that executes an action to the change of state, such as posting a message to the blockchain.
    3. QueryMsg Query Msg:
This message is for querying data from the chain.


use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
     pub count: i32,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
     Increment {},
     Reset { count: i32 },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
     // GetCount returns the current count as a json-encoded number
     GetCount {},
}
// We define a custom struct for each query response
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct CountResponse {
     pub count: i32,
}


On Dependencies for Running Local Terra: What is Inside the Contract.rs File?

Another important Rust file is Contract.rs. In the next part, we are going to refer to the functions of this contract and use them in python. The important functions that we are going to interact with later using the python scripts are instantiated, execute, try increment, try_reset, and query_count. We will later see how we can interact with these functions with the JSON messages sent to them through python codes. As you can see the programming language that these smart contracts are written in is Rust.


use cosmwasm_std::entry_point;
use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};
use cw2::set_contract_version;
use crate::error::ContractError;
use crate::msg::{CountResponse, ExecuteMsg, InstantiateMsg, QueryMsg};
use crate::state::{State, STATE};
// version info for migration info
const CONTRACT_NAME: &str = "crates.io:my-project";
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: InstantiateMsg,
          ) -> Result {
     let state = State {
     count: msg.count,
     owner: info.sender.clone(),
     };

     set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
     STATE.save(deps.storage, &state)?;
     Ok(Response::new()
     .add_attribute("method", "instantiate")
     .add_attribute("owner", info.sender)
     .add_attribute("count", msg.count.to_string()))
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
          deps: DepsMut,
          _env: Env,
          info: MessageInfo,
          msg: ExecuteMsg,
          ) -> Result {
     match msg {
     ExecuteMsg::Increment {} => try_increment(deps),
     ExecuteMsg::Reset { count } => try_reset(deps, info, count),
     }
}

pub fn try_increment(deps: DepsMut) -> Result {
     STATE.update(deps.storage, |mut state| -> Result<_, ContractError> {
          state.count += 1;
          Ok(state)})?;
          Ok(Response::new().add_attribute("method", "try_increment"))
     }
}

pub fn try_reset(deps: DepsMut, info: MessageInfo, count: i32) -> Result {
     STATE.update(deps.storage, |mut state| -> Result<_, ContractError> {
          if info.sender != state.owner {
               return Err(ContractError::Unauthorized {});
          }
          state.count = count;
          Ok(state)
     })?;
     Ok(Response::new().add_attribute("method", "reset"))
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult {
     match msg {
          QueryMsg::GetCount {} => to_binary(&query_count(deps)?),
     }
}

fn query_count(deps: Deps) -> StdResult {
     let state = STATE.load(deps.storage)?;
     Ok(CountResponse { count: state.count })
}

#[cfg(test)]
mod tests {
     use super::*;
     use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
     use cosmwasm_std::{coins, from_binary};
     #[test]
     fn proper_initialization() {
          let mut deps = mock_dependencies(&[]);
          let msg = InstantiateMsg { count: 17 };
          let info = mock_info("creator", &coins(1000, "earth"));
          // we can just call .unwrap() to assert this was a success
          let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
          assert_eq!(0, res.messages.len());
          // it worked, let's query the state
          let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap();
          let value: CountResponse = from_binary(&res).unwrap();
          assert_eq!(17, value.count);
     }
     #[test]
     fn increment() {
          let mut deps = mock_dependencies(&coins(2, "token"));
          let msg = InstantiateMsg { count: 17 };
          let info = mock_info("creator", &coins(2, "token"));
          let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
          // beneficiary can release it
          let info = mock_info("anyone", &coins(2, "token"));
          let msg = ExecuteMsg::Increment {};
          let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap();
          // should increase counter by 1
          let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap();
          let value: CountResponse = from_binary(&res).unwrap();
          assert_eq!(18, value.count);
     }
     #[test]
     fn reset() {
          let mut deps = mock_dependencies(&coins(2, "token"));
          let msg = InstantiateMsg { count: 17 };
          let info = mock_info("creator", &coins(2, "token"));
          let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
          // beneficiary can release it
          let unauth_info = mock_info("anyone", &coins(2, "token"));
          let msg = ExecuteMsg::Reset { count: 5 };
          let res = execute(deps.as_mut(), mock_env(), unauth_info, msg);
          match res {
               Err(ContractError::Unauthorized {}) => {}
                    _ => panic!("Must return unauthorized error"),
          }
          // only the original creator can reset the counter
          let auth_info = mock_info("creator", &coins(2, "token"));
          let msg = ExecuteMsg::Reset { count: 5 };
          let _res = execute(deps.as_mut(), mock_env(), auth_info, msg).unwrap();
          // should now be 5
          let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap();
          let value: CountResponse = from_binary(&res).unwrap();
          assert_eq!(5, value.count);
     }
}


Creating a project folder using cargo

Now, let’s run the following command in the terminal to create binaries and other dependency files so that we can eventually interact with local Terra using python Terra SDK.

cargo run-script optimize

Result:

Running script 'optimize': 'docker run --rm -v "$(pwd)":/code --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry cosmwasm/rustoptimizer:0.12.5 ' Info: RUSTC_WRAPPER=sccache Info: sccache stats before build Compile requests 0 Compile requests executed 0 Cache hits 0 Cache misses 0 Cache timeouts 0 Cache read errors 0 Forced recaches 0 Cache write errors 0 Compilation failures 0 Cache errors 0 Non-cacheable compilations 0 Non-cacheable calls 0 Non-compilation calls 0 Unsupported compiler calls 0 Average cache write 0.000 s Average cache read miss 0.000 s Average cache read hit 0.000 s Failed distributed compilations 0 Cache location Local disk: "/root/.cache/sccache" Cache size 0 bytes Max cache size 10 GiB Building contract in /code ... Finished release [optimized] target(s) in 0.16s Creating intermediate hash for my_project.wasm ... ec1944cdda3c5f8f6968d59b990d6f92800f65d9655a53a45ee29984f6c5d882 ./target/wasm32-unknown-unknown/release/my_project.wasm Optimizing my_project.wasm ... Creating hashes ... fa445512e5b3274ddda6474335d8515c660c5fcdf03b8b2d7f468c12f3e55564 my_project.wasm Info: sccache stats after build Compile requests 0 Compile requests executed 0 Cache hits 0 Cache misses 0 Cache timeouts 0 Cache read errors 0 Forced recaches 0 Cache write errors 0 Compilation failures 0 Cache errors 0 Non-cacheable compilations 0 Non-cacheable calls 0 Non-compilation calls 0 Unsupported compiler calls 0 Average cache write 0.000 s Average cache read miss 0.000 s Average cache read hit 0.000 s Failed distributed compilations 0 Cache location Local disk: "/root/.cache/sccache" Cache size 0 bytes Max cache size 10 GiB done Finished, status of exit status: 0

And in the project name folder, we are going to see that a new folder has been created called artifacts. Now, we are ready to write our python codes to interact with local Terra and use CosmWasm based contracts written in Rust.

Conclusion:

In this tutorial, we have managed to install the dependencies required to run local Terra, such as GO and Rust programming languages, Docker, Cargo, and some Github repositories like terra-core and local-terra. We have also connected to Local Terra network using the docker-compose up command. In the end, we have created our project folder and files using the cargo generate command.
Moreover, we have taken a quick look at some of the important smart contracts written in Rust programming language and got familiar with their functions so that we can interact with them later using our python scripts. We also created the artifacts folder using the cargo command to have everything set up for interacting with the local Terra.

Download this Article in PDF format

Rust learning for CosmWasm smart contracts

Need More Rust Learning?

To fully take advantage of this article, you’d better get familiar with Rust Language applied to CosmWasm smart contracts. And the good news is that we have a complete tutorial for it.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Request a Quote
Blockchain Development

How to Write Terra Smart Contracts: A Complete Tutorial

This article explains the connections between the python codes we wrote earlier (same for JavaScript) and the pre-built functions inside the src folder created with the cargo generate–git command. Reading this article will give you a deeper insight into what actually happens when we instantiate, execute or query a state of Terra smart contracts in python code and how it communicates with the CosmWasm libraries written in Rust. Being familiar with Rust programming language can help you better understand the steps we are going to take to interact with the CosmWasm smart contracts on the Terra network. We have provided a small Rust tutorial to make you more familiar with Rust scripts at CosmWasm Smart Contracts – learning Rust series.

What Are Terra Smart Contracts?

In the blockchain, Terra smart contracts are instances of a singleton object whose internal state is persisted on the blockchain. A singleton is a class that allows only a single instance of itself to be created and gives access to that created instance. Users can query or set the state changes by sending a JSON message. The details of these messages are going to be explained throughout this article. Terra smart contracts are defined by these 3 main functions:
    1. Instantiate(): a constructor which is used during contract instantiation to set the initial state.
    2. Execute(): it is used when a user wants to invoke a method on the smart contract.
    3. Query(): it is used when a user wants to get data out of a smart contract.

Creating the Template to Write Terra Smart Contracts

The 1st step of creating a smart contract is to create the template using the command below: cargo generate --git https://github.com/CosmWasm/cw-template.git --branch 0.16 -- name my-first-contract cd my-first-contract The above cargo command creates a structure and boilerplate for creating Terra smart contract. A sample smart contract has the following template, we should define a struct called state and in it, we should determine the count and the address of the owner of the contract. The below code can also be found in the src folder, state.rs file.

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_std::Addr;
use cw_storage_plus::Item;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct State {
     pub count: i32,
     pub owner: Addr,
}
pub const STATE: Item = Item::new("state");

Serialization & Deserialization

Notice that some data, like owner and count, can only be persisted as bytes, so we need to convert these data from human-readable format to bytes. That is why we use serialization and deserialization. The CosmWasm team has provided the utility crates such as cosmwasm_storage, which automatically provides serialization and deserialization for commonly used types such as Structs and numbers in Rust. Some of the useful traits applied by deriving attributes in #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] are as follows:
    1. Serialize: for providing serialization
    2. Deserialize: for deserialization
    3. Clone: makes the struct copyable
    4. Debug: enables the struct to be printed to string
    5. PartialEq: provides equality comparison
    6. JsonSchema: automatically creates a JSON schema

InstantiateMsg

The instantiateMsg is provided for the users when they want to use MsgInstantiate Contract in their script (Python or JavaScript). This provides the initial state and the configuration of the contract. As opposed to Ethereum, in Terra Smart Contracts, the uploading of a smart contract code and the instantiation of it are considered 2 separate events. The reason for this is to allow different contracts to have the same base code by instantiating. The below code can be found in the src folder and inside the file called msg.rs.

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
     pub count: i32,
}


To create Terra smart contracts, you can send a JSON message like this:

{
     "count": 100
}


Remember in the python scripts (contracts.py) when we sent the instantiateMsg and set the JSON message to {“count”: 15}:

contract_address = instantiate_contract(code_id,{"count": 15})


When the message InstantiateMsg is sent, the below rust script in the src/contract.rs will extract the message and set up the initial state. In this instantiate function, the count is the extracted count from the message and the address is the address of the account that sent the MsgInstantiateContract message.

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
               deps: DepsMut,
               _env: Env,
               info: MessageInfo,
               msg: InstantiateMsg,
          ) -> Result {
     let state = State {
     count: msg.count,
     owner: info.sender.clone(),
     };
     set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
     STATE.save(deps.storage, &state)?;
     Ok(Response::new()
     .add_attribute("method", "instantiate")
     .add_attribute("owner", info.sender)
     .add_attribute("count", msg.count.to_string()))
}


ExecuteMsg

The ExecuteMsg in python or Javascript is the message sent to the execute function in contract.rs through MsgExecuteContract (remember when we imported this in our contract.py). Opposite to what we had in InstantiateMsg, in the ExecuteMsg we have different messages for executing different functionalities(logics). The execute function in the contract.rs file demultiplexes these different messages to their related logic. In the msg.rs file, you will see the definition of the 2 messages Increment and Reset in a public enum.

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
     Increment {},
     Reset { count: i32 },
}


And as you can see, in the contract.rs, Increment and Reset are connected to their appropriate functions in the match msg:

ExecuteMsg::Increment {} => try_increment(deps)
Increment {} => try_increment(deps),

The complete code of execute function goes like this:

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
               deps: DepsMut,
               _env: Env,
               info: MessageInfo,
               msg: ExecuteMsg,
          ) -> Result {
     match msg {
          ExecuteMsg::Increment {} => try_increment(deps),
          ExecuteMsg::Reset { count } => try_reset(deps, info, count),
     }
}


Now, the messages that we send in a python or Javascript code, goes like this: increment message:

{
     "increment": {}
}


reset message:

{
     "reset": {"count": 5}
}

remember the kind of script we wrote in python to send the increment message.

execute = execute_contract(deployer,contract_address,{"increment":{}})


Now, let’s see how try_increment and try_reset functions work in the contract.rs file. In the try_increment function, the mutable state from state.rs is called. Then, it is updated (incremented) to a new number and then the function returns OK with a new state. Finally the execution is finalized as successful by the OK response.

pub fn try_increment(deps: DepsMut) -> Result {
     STATE.update(deps.storage, |mut state| -> Result<_, ContractError> {
          state.count += 1;
          Ok(state)
     })?;
     Ok(Response::new().add_attribute("method", "try_increment"))
}


The try_reset works the same way as try_increment with the difference that it first checks whether the sender of the message is the owner or not.

pub fn try_reset(deps: DepsMut, info: MessageInfo, count: i32) -> Result {
     STATE.update(deps.storage, |mut state| -> Result<_, ContractError> {
          if info.sender != state.owner {
               return Err(ContractError::Unauthorized {});
          }
          state.count = count;
          Ok(state)
     })?;
     Ok(Response::new().add_attribute("method", "reset"))
}


How to Interact with Terra Smart Contracts

In the continuation of this journey, we are going to see how the QueryMsg function is defined in Rust in addition to how we can interact with it using Python or JavaScript. QueryMsg: For the query messages, there are two functionalities required:
    1. A format of receiving defined in the request GetCount {} inside the QueryMsg enum.
    2. And a variable pub count: i32 to respond to the query request which is defined inside the CountResponse struct. The scripts below can be found in the src folder inside the file called msg.rs

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
     // GetCount returns the current count as a json-encoded number
     GetCount {},
}
// Define a custom struct for each query response
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct CountResponse {
     pub count: i32,
}

The following script can be found inside the contract.rs file in the src folder. This script contains 2 functions, one for receiving the query request and two for returning the responding of the state of the contract from the state.rs file. The query function receives the query request and converts the message to binary and passes it to the query_count function which loads the state of the contract from the state.rs and returns the result with the OK response and the message in the { count: state.count } format.


#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult {
     match msg {
          QueryMsg::GetCount {} => to_binary(&query_count(deps)?),
     }
}

fn query_count(deps: Deps) -> StdResult {
     let state = STATE.load(deps.storage)?;
     Ok(CountResponse { count: state.count })
}


The message we send for querying the state of the contract is in this format.

{
     "get_count": {}
}


And the respond we get is like the following:

{
     "count": 5
}

Building Terra Smart Contract

To build the contract, you can use the 2 commands below: cargo test
cargo wasm
The next thing we do is optimize our build by running the following command: cargo run-script optimize To be able to run the above command, you need to make sure you have installed docker.
If you are on arm-64 machine, you should run: docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ cosmwasm/rust-optimizer-arm64:0.12.4 And if you are on windows with docker daemon running on WSL1 run the following: docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ cosmwasm/rust-optimizer-arm64:0.12.4 Any of the above commands if run properly (according to the use cases mentioned), should create a new folder called artifacts which contains the my-first-contract.wasm file. To make JSON Schema files, for serialization, you need to register each of the data structures by export_schema(&schema_for!(), &out_dir); you can see the registration in the examples folder, inside the file called schema.rs:

use std::env::current_dir;
use std::fs::create_dir_all;
use cosmwasm_schema::{export_schema, remove_schemas, schema_for};
use my_first_contract::msg::{CountResponse, HandleMsg, InitMsg, QueryMsg};
use my_first_contract::state::State;
fn main() {
     let mut out_dir = current_dir().unwrap();
     out_dir.push("schema");
     create_dir_all(&out_dir).unwrap();
     remove_schemas(&out_dir).unwrap();
     export_schema(&schema_for!(InstantiateMsg), &out_dir);
     export_schema(&schema_for!(ExecuteMsg), &out_dir);
     export_schema(&schema_for!(QueryMsg), &out_dir);
     export_schema(&schema_for!(State), &out_dir);
     export_schema(&schema_for!(CountResponse), &out_dir);
}

Now, using the following commands, you can create these data structure JSON files inside schema folder: cd examples Cargo schema Result: Finished dev [unoptimized + debuginfo] target(s) in 0.37s Running `/home/mohamad/LocalTerra/my-first-contract.wasm/target/debug/examples/schema` Created /home/mohamad/LocalTerra/my-first-contract.wasm/examples/schema/instantiate_msg.json Created /home/mohamad/LocalTerra/my-first-contract.wasm/examples/schema/execute_msg.json Created /home/mohamad/LocalTerra/my-first-contract.wasm/examples/schema/ query_msg.json Created /home/mohamad/LocalTerra/my-first-contract.wasm/examples/schema/ state.json Created /home/mohamad/LocalTerra/my-first-contract.wasm/examples/schema/ count_response.json Inside the schema folder, you should be able to see all the requested data structures JSON files.

count_response.json
execute_msg.json
instantiate_msg.json
query_msg.json
state.json

Inside the state.json, you can see different sections of the .json file, the most important of which is the requires containing the “count” and the “owner”. { "$schema": "http://json-schema.org/draft-07/schema#", "title": "State", "type": "object", "required": [ "count", "owner" ], "properties": { "count": { "type": "integer", "format": "int32" }, "owner": { "$ref": "#/definitions/Addr" } }, "definitions": { "Addr": { "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Re- ally? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", "type": "string" } } } Besides, you can see 2 required sections inside the execute_msg.json: 1. "increment" 2. "reset" { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", "anyOf": [ { "type": "object", "required": [ "increment" ], "properties": { "increment": { "type": "object" } }, "additionalProperties": false }, { "type": "object", "required": [ "reset" ], "properties": { "reset": { "type": "object", "required": [ "count" ], "properties": { "count": { "type": "integer", "format": "int32" } } } }, "additionalProperties": false } ] }

Summing Up

we dived deep into the details of the local Terra smart contracts written in Rust or in other words the CosmWasm smart contracts. This type of understanding from scratch helps us interact and communicate better with local Terra and understand what actually happens behind the scenes when we send JSON messages with python scripts to instantiate or execute a smart contract or query data from it. Firstly, we showed how to create a template for initiating state. Then, we created a smart contract and learned how to interact with it. Besides, we learned how to use the QueryMsg function and build a Terra smart contract.

Download this Article in PDF format

metaverse

Curious about What We’re Up To?

In Arashtad, we provide custom services in various design and development fields. 3D websites, 3D games, metaverses, and other types of WebGL and 3D applications are just some of our expertise.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Request a Quote
ThreeJS Development