Terra is a decentralized financial infrastructure and blockchain protocol that introduces some unique concepts and theories into the market. The network leverages a native token, stablecoin protocol, oracle system, and smart contracts to bring users programmable money for the internet.
The project offers multiple stablecoin options that provide instant settlement. To accomplish this task, Terra relies on a price-stability algorithm that actively alters the monetary supply of an asset to retain value. In this way, Terra can provide users with lower fees, more stability, seamless cross-border exchanges, and highly responsive financial assets.
Terra has a development-focused agenda. The network allows programmers to build smart contracts in Rust. Additionally, you can add extra functionality to your Dapp through the use of the network’s oracles. Oracles are off-chain sensors that have the ability to communicate data to-and-from the blockchain. Oracles are critical to many blockchain networks, especially when used for price discovery purposes. In this article we will discuss how to create your first smart contract in Terra.
What are we building?
We will create a simple counter app. Our app will have a function to increment the counter state variable, which will enable to understand how the contract persists state. I’m assuming that you have some experience with blockchain development and have a basic understanding of what a smart contract is. If you do not have this knowledge yet. Please feel free to watch checkout courses like 6-Figure Blockchain Developer or Web Development For Blockchain.
Terra Developer Tooling
Terra has official SDKs that developers can use to get started. The following table maps commonly-used Ethereum developer tools to their Terra counterparts.
Terra | Ethereum | |
---|---|---|
Frontend SDK | Terra.js, Terra SDK | Web3.js, Web3py |
Browser Extension | Station CX | MetaMask, MEW |
Local Testnet | LocalTerra | Ganache |
Contract Language | Rust | Solidity, Vyper |
For more info check official documentation.
Requirements
- Docker (opens new window)installed on your machine
- Basic familiarity with the Terra ecosystem (check out our 2min intro video to Terra)
- Basic proficiency with the Rust programming language
Environment Setup
As a smart contract developer, you will need to write, compile, upload, and test your contracts before deploying them to be used on the Columbus mainnet. As this development process can involve manually juggling multiple moving parts over many iterations, it is helpful to first set up a specialized environment to streamline development.
In order to work with Terra Smart Contracts, you should have access to a Terra network that includes the WASM integration. For local development, we are going to use LocalTerra, a package used to setup private terra network.
1. Setup LocalTerra
To use LocalTerra, you should first make sure Docker is installed on your computer by following the Docker get-started tutorial . You will also need to set up and configure Docker Composeon your machine.
git clone --branch v0.5.2 --depth 1 https://github.com/terra-money/localterra
cd localterra
docker-compose up
You should now have a local testnet running on your machine, with the following configurations:
- Node listening on RPC port 26657
- LCD running on port 1317
- Swagger Documentation at http://localhost:3060/swagger
The account with the following mnemonic is the sole validator on the network and has enough funds to get started with smart contracts.
satisfy adjust timber high purchase tuition stool faith fine install that you unaware feed domain license impose boss human eager hat rent enjoy dawn
2. Install Rust
Follow the installation steps on https://www.rust-lang.org/tools/install to setup rust language if you don’t have it already. Check out the Rust Crash Course for getting started with rust.
IMPORTANT: Configure you path variables
In the Rust development environment, all tools are installed to the ~/.cargo/bin directory, and this is where you will find the Rust toolchain, including rustc, cargo, and rustup.
Accordingly, it is customary for Rust developers to include this directory in their PATH environment variable. During installation rustup will attempt to configure the PATH. Because of differences between platforms, command shells, and bugs in rustup, the modifications to PATH may not take effect until the console is restarted, or the user is logged out, or it may not succeed at all.
If, after installation, running rustc --version
in the console fails, this is the most likely reason.
Once you’ll installed Rust and its toolchain (cargo et al.), you’ll need to add the wasm32-unknown-unknown compilation target.
rustup default stable
rustup target add wasm32-unknown-unknown
Then, install cargo-generate, which we will need for bootstrapping new CosmWasm smart contracts via a template.
cargo install cargo-generate --features vendored-openssl
3. Writing your first smart contract
Terra has a template you can use as a starter kit. Lets go ahead and clone it:
cargo generate --git https://github.com/CosmWasm/cw-template.git --branch 0.16 --name counter-dapp
cd counter-dapp
Lets review the project structure of the template
the src folder has 4 rust files: – state.rs: stores state of our contract i.e the data we wish to persist on the blockchain. – contract.rs: stores our contract logic which will get executed when a user performs a query. – error.rs: handles error messages – lib.rs: is used to package our contract as a library
We will start with setting up state of our contract
//src/state.rs
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<State> = Item::new("state");
Here we have declared 2 state variables. pub count
stores the value of count and pub owner
store the address of the contract owner.
State values are initialised in the contract constructor function which will get executed when our contract is deployed.
// src/contract.rs
// version info for migration info
const CONTRACT_NAME: &str = "crates.io:{{project-name}}";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
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()))
}
The constructor sets the value of the count variable and sets the message sender as the owner of contract.
Lets add a function to increment the counter
// src/contract.rs
pub fn try_increment(deps: DepsMut) -> Result<Response, ContractError> {
STATE.update(deps.storage, |mut state| -> Result<_, ContractError> {
state.count += 1;
Ok(state)
})?;
Ok(Response::new().add_attribute("method", "try_increment"))
}
This function increments the state variable by 1 every time its invoked.
Lets also add another function to query the current value of count
// src/contract.rs
fn query_count(deps: Deps) -> StdResult<CountResponse> {
let state = STATE.load(deps.storage)?;
Ok(CountResponse { count: state.count })
}
4. Deploying your contract to LocalTerra
Please ensure the LocalTerra is running as described in step 1. We first need to add a test account. We can use the following command to set it up from a mnemonic:
terrad keys add test1 --recover
satisfy adjust timber high purchase tuition stool faith fine install that you unaware feed domain license impose boss human eager hat rent enjoy dawn
Run the following command to create a WASM build in your directory
cargo run-script optimize
Finally to publish your code to LocalTerra run the following command.
terrad tx wasm store artifacts/counter_dapp.wasm --from test1 --chain-id=localterra --gas=auto --fees=100000uluna --broadcast-mode=block
You should see a similar output:
height: 6
txhash: 83BB9C6FDBA1D2291E068D5CF7DDF7E0BE459C6AF547EC82652C52507CED8A9F
codespace: ""
code: 0
data: ""
rawlog: '[{"msg_index":0,"log":"","events":[{"type":"message","attributes":[{"key":"action","value":"store_code"},{"key":"module","value":"wasm"}]},{"type":"store_code","attributes":[{"key":"sender","value":"terra1dcegyrekltswvyy0xy69ydgxn9x8x32zdtapd8"},{"key":"code_id","value":"1"}]}]}]'
logs:
- msgindex: 0
log: ""
events:
- type: message
attributes:
- key: action
value: store_code
- key: module
value: wasm
- type: store_code
attributes:
- key: sender
value: terra1dcegyrekltswvyy0xy69ydgxn9x8x32zdtapd8
- key: code_id
value: "1"
info: ""
gaswanted: 681907
gasused: 680262
tx: null
timestamp: ""
You will have to manually create the contract after the code has been deployed. This is an additional step in Terra as compared to something like Ethereum where the contract is instantly published and deployed.
Lets initialise the contract with count 0
terrad tx wasm instantiate 1 '{"count":0}' --from test1 --chain-id=localterra --fees=10000uluna --gas=auto --broadcast-mode=block
You should see the a similar message once contract is deployed successfully.
height: 41
txhash: AEF6F2FA570029A5D4C0DA5ACFA4A2B614D5811E29EEE10FF59F821AFECCD399
codespace: ""
code: 0
data: ""
rawlog: '[{"msg_index":0,"log":"","events":[{"type":"instantiate_contract","attributes":[{"key":"owner","value":"terra1dcegyrekltswvyy0xy69ydgxn9x8x32zdtapd8"},{"key":"code_id","value":"1"},{"key":"contract_address","value":"terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5"}]},{"type":"message","attributes":[{"key":"action","value":"instantiate_contract"},{"key":"module","value":"wasm"}]}]}]'
logs:
- msgindex: 0
log: ""
events:
- type: instantiate_contract
attributes:
- key: owner
value: terra1dcegyrekltswvyy0xy69ydgxn9x8x32zdtapd8
- key: code_id
value: "1"
- key: contract_address
value: terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5
- type: message
attributes:
- key: action
value: instantiate_contract
- key: module
value: wasm
info: ""
gaswanted: 120751
gasused: 120170
tx: null
timestamp: ""
Keep note of contract_address
key i.e “terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5”, we’ll need this to interact with our contract.
5. Interacting with our contract
Lets query contract info.
terrad query wasm contract terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5
You should see an output like so
address: terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5
owner: terra1dcegyrekltswvyy0xy69ydgxn9x8x32zdtapd8
codeid: 1
initmsg: eyJjb3VudCI6MH0=
migratable: false
The initmsg
is the value we passed during contract creation which is encoded in base64
format. You can decode base64 data here
Lets increment our counter by calling the increment
function
terrad tx wasm execute terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5 '{"increment":{}}' --from test1 --chain-id=localterra --gas=auto --fees=1000000uluna --broadcast-mode=block
Lets see if the state has changed. We can use the get_count
method to check the value of our count state variable.
terrad query wasm contract-store terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5 '{"get_count":{}}'
You should get the following output:
query_result:
count: 1
Great! you have created your first smart contract in Terra protocol.
Conclusion
We have just explored how the terra blockchain development workflow is setup. You can challenge yourself by creating a CW20 Token which is the terra equivalent of Ethereum’s ERC20 standard. You can find out more in the official CW20 documentation. Also checkout more smart contract examples in the offical terra repository.
Leave a Reply