Developing Terra Smart Contracts
Jan 3, 2022•8 min read
Terra blockchain provides a robust platform for developing smart contracts. In this comprehensive guide, we'll explore how to develop, test, and deploy smart contracts on Terra.
Understanding Terra Smart Contracts
Terra smart contracts are written in Rust and compiled to WebAssembly (Wasm). This provides better performance and type safety compared to other blockchain platforms.
Development Environment Setup
bash# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Install cargo-generate
cargo install cargo-generate
# Install terrain
npm install -g @terra-money/terrain
Creating Your First Contract
rustuse cosmwasm_std::{
entry_point, to_binary, Binary, Deps, DepsMut,
Env, MessageInfo, Response, StdResult
};
#[entry_point]
pub fn instantiate(
_deps: DepsMut,
_env: Env,
_info: MessageInfo,
_msg: InstantiateMsg,
) -> StdResult<Response> {
Ok(Response::default())
}
#[entry_point]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> StdResult<Response> {
match msg {
ExecuteMsg::Transfer { recipient, amount } => {
execute_transfer(deps, env, info, recipient, amount)
}
}
}
Contract State Management
rust#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct State {
pub count: i32,
pub owner: Addr,
}
pub const STATE: Item<State> = Item::new("state");
pub fn save_state(deps: DepsMut, state: &State) -> StdResult<()> {
STATE.save(deps.storage, state)
}
Testing Smart Contracts
Unit Tests
rust#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
#[test]
fn proper_initialization() {
let mut deps = mock_dependencies();
let msg = InstantiateMsg { count: 17 };
let info = mock_info("creator", &[]);
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(0, res.messages.len());
}
}
Integration Tests
rust#[test]
fn integration_test() {
let mut app = App::default();
let code_id = app.store_code(Box::new(ContractWrapper::new(
execute,
instantiate,
query,
)));
let contract_addr = app
.instantiate_contract(
code_id,
Addr::unchecked("owner"),
&InstantiateMsg { count: 0 },
&[],
"Contract",
None,
)
.unwrap();
}
Interacting with Other Contracts
Contract-to-Contract Calls
rust#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct OtherContract {
pub address: String,
}
pub fn execute_cross_contract(
deps: DepsMut,
env: Env,
info: MessageInfo,
other_contract: String,
) -> StdResult<Response> {
let msg = to_binary(&ExecuteMsg::SomeAction {})?;
let sub_msg = SubMsg::new(WasmMsg::Execute {
contract_addr: other_contract,
msg,
funds: vec![],
});
Ok(Response::new().add_submessage(sub_msg))
}
Handling Terra Native Tokens
rustpub fn handle_deposit(
deps: DepsMut,
env: Env,
info: MessageInfo,
) -> StdResult<Response> {
let deposit_amount = info
.funds
.iter()
.find(|c| c.denom == "uluna")
.map(|c| c.amount)
.unwrap_or_default();
STATE.update(deps.storage, |mut state| -> StdResult<_> {
state.total_deposits += deposit_amount;
Ok(state)
})?;
Ok(Response::new()
.add_attribute("action", "deposit")
.add_attribute("amount", deposit_amount))
}
Error Handling and Custom Errors
rust#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),
#[error("Unauthorized")]
Unauthorized {},
#[error("Invalid amount")]
InvalidAmount {},
}
pub fn validate_amount(amount: Uint128) -> Result<(), ContractError> {
if amount.is_zero() {
return Err(ContractError::InvalidAmount {});
}
Ok(())
}
Deployment Process
- Build the Contract:
bashcargo wasm
- Optimize the Wasm Binary:
bashdocker 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:0.12.6
- Deploy using Terrain:
bashterrain deploy contract --network testnet
Best Practices
-
Security Considerations
- Always validate inputs
- Use proper access control
- Handle funds carefully
- Test extensively
-
Gas Optimization
- Minimize storage operations
- Batch operations when possible
- Use appropriate data structures
-
Code Organization
- Separate contract logic
- Use proper error handling
- Document your code
- Follow Rust best practices
Common Patterns
- Owner Pattern
rustpub fn assert_owner(deps: Deps, sender: Addr) -> StdResult<()> {
let owner = STATE.load(deps.storage)?.owner;
if sender != owner {
return Err(StdError::generic_err("Unauthorized"));
}
Ok(())
}
- Pausable Pattern
rustpub fn pause_contract(
deps: DepsMut,
info: MessageInfo,
) -> Result<Response, ContractError> {
assert_owner(deps.as_ref(), info.sender)?;
STATE.update(deps.storage, |mut state| -> StdResult<_> {
state.paused = true;
Ok(state)
})?;
Ok(Response::new().add_attribute("action", "pause"))
}
Conclusion
Developing smart contracts on Terra requires understanding of both Rust and blockchain concepts. Following best practices and proper testing procedures ensures secure and efficient contracts.
Key Points:
- Use proper testing methodologies
- Implement secure patterns
- Optimize for gas usage
- Handle errors appropriately
For more information, visit the Terra Developer Documentation.