Smart Contract Development on TOS Network
Welcome to smart contract development on TOS Network! This guide will teach you how to build, deploy, and manage smart contracts using Rust powered by the TAKO runtime. Experience modern Rust development with ultra-low gas costs and native blockchain features.
Why TOS Network for Smart Contracts?
Rust Smart Contracts
- Modern Language: Use Rust’s safety guarantees and performance
- No Runtime Overhead: Compiles to efficient TBPFv3 bytecode
- Native Features: Direct access to VRF, referrals, batch transfers
- Ultra-Low Gas: $0.001 per transaction average
TAKO Runtime
- eBPF-Inspired: Efficient, sandboxed execution
- Fast Compilation: Quick development iteration
- Predictable Costs: Gas costs are transparent and low
- Security First: Memory-safe execution environment
Native Features Integration
- VRF Randomness: Verifiable random numbers for gaming and lotteries
- Referral System: Built-in referral rewards at protocol level
- Batch Transfers: Send to multiple recipients in one transaction
- Privacy Options: Optional encrypted transfers with UNO
Development Environment Setup
Prerequisites
Before starting, ensure you have:
- Rust 1.70+ with
wasm32-unknown-unknowntarget - TOS Platform Tools for compilation
- Git for version control
- TOS CLI for deployment
Install TOS Development Tools
# Install Rust (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
# Add WebAssembly target
rustup target add wasm32-unknown-unknown
# Install TOS Platform Tools
curl -fsSL https://install.tos.network/tools | bash
# Verify installation
tos-tools --versionCreate Development Project
# Create new smart contract project
cargo new --lib my_first_contract
cd my_first_contract
# Project structure
my_first_contract/
├── src/
│ └── lib.rs
├── Cargo.toml
└── README.mdConfigure Cargo.toml
[package]
name = "my_first_contract"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
tako_sdk = "0.1"
[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"Your First Smart Contract
Hello World Contract
Create src/lib.rs:
#![no_std]
#![no_main]
use tako_sdk::*;
#[no_mangle]
pub extern "C" fn entrypoint() -> u64 {
// Log a message (visible in transaction logs)
log("Hello from TOS Network!");
// Return success
SUCCESS
}Build and Deploy
# Build the contract
cargo build --release --target wasm32-unknown-unknown
# Deploy to testnet
tos deploy \
--contract target/wasm32-unknown-unknown/release/my_first_contract.wasm \
--network testnetUnderstanding TAKO Contracts
Contract Structure
#![no_std]
#![no_main]
use tako_sdk::*;
// Contract entrypoint - called for every transaction
#[no_mangle]
pub extern "C" fn entrypoint() -> u64 {
// Get the method being called
let method = get_method();
// Route to appropriate handler
match method.as_str() {
"initialize" => initialize(),
"transfer" => transfer(),
"balance_of" => balance_of(),
_ => {
log("Unknown method");
ERROR_INVALID_METHOD
}
}
}Storage Operations
use tako_sdk::*;
// Store a value
fn store_balance(address: &[u8], amount: u64) -> u64 {
let key = ["balance:", address].concat();
storage_write(&key, &amount.to_le_bytes())
}
// Read a value
fn get_balance(address: &[u8]) -> u64 {
let key = ["balance:", address].concat();
let mut buffer = [0u8; 8];
if storage_read(&key, &mut buffer) == SUCCESS {
u64::from_le_bytes(buffer)
} else {
0
}
}
// Delete a value
fn clear_balance(address: &[u8]) -> u64 {
let key = ["balance:", address].concat();
storage_delete(&key)
}Getting Transaction Context
use tako_sdk::*;
fn get_context() {
// Get transaction sender
let sender = get_caller();
// Get contract address
let contract = get_address();
// Get TOS value sent with transaction
let value = get_value();
// Get current block height
let height = get_block_height();
// Get block timestamp
let timestamp = get_timestamp();
}Token Contract Example
ERC20-Compatible Token
#![no_std]
#![no_main]
use tako_sdk::*;
const DECIMALS: u8 = 8;
const TOTAL_SUPPLY: u64 = 1_000_000_00000000; // 1M tokens
#[no_mangle]
pub extern "C" fn entrypoint() -> u64 {
let method = get_method();
match method.as_str() {
"initialize" => initialize(),
"name" => name(),
"symbol" => symbol(),
"decimals" => decimals(),
"total_supply" => total_supply(),
"balance_of" => balance_of(),
"transfer" => transfer(),
"approve" => approve(),
"allowance" => allowance(),
"transfer_from" => transfer_from(),
_ => ERROR_INVALID_METHOD
}
}
fn initialize() -> u64 {
// Only allow initialization once
let mut initialized = [0u8; 1];
if storage_read(b"initialized", &mut initialized) == SUCCESS {
return ERROR_ALREADY_INITIALIZED;
}
// Set owner and initial supply
let owner = get_caller();
storage_write(b"initialized", &[1]);
storage_write(b"owner", &owner);
// Give all tokens to owner
let key = [b"balance:", &owner[..]].concat();
storage_write(&key, &TOTAL_SUPPLY.to_le_bytes());
// Emit event
emit_event("Transfer", &[&[0u8; 32], &owner, &TOTAL_SUPPLY.to_le_bytes()]);
SUCCESS
}
fn transfer() -> u64 {
let args = get_args();
let to = &args[0..32];
let amount = u64::from_le_bytes(args[32..40].try_into().unwrap());
let from = get_caller();
// Get sender balance
let from_key = [b"balance:", &from[..]].concat();
let mut from_balance_bytes = [0u8; 8];
storage_read(&from_key, &mut from_balance_bytes);
let from_balance = u64::from_le_bytes(from_balance_bytes);
// Check sufficient balance
if from_balance < amount {
return ERROR_INSUFFICIENT_BALANCE;
}
// Get recipient balance
let to_key = [b"balance:", to].concat();
let mut to_balance_bytes = [0u8; 8];
storage_read(&to_key, &mut to_balance_bytes);
let to_balance = u64::from_le_bytes(to_balance_bytes);
// Update balances
storage_write(&from_key, &(from_balance - amount).to_le_bytes());
storage_write(&to_key, &(to_balance + amount).to_le_bytes());
// Emit transfer event
emit_event("Transfer", &[&from, to, &amount.to_le_bytes()]);
SUCCESS
}
fn balance_of() -> u64 {
let args = get_args();
let account = &args[0..32];
let key = [b"balance:", account].concat();
let mut balance_bytes = [0u8; 8];
storage_read(&key, &mut balance_bytes);
// Return balance in result
set_return_data(&balance_bytes);
SUCCESS
}
fn name() -> u64 {
set_return_data(b"My Token");
SUCCESS
}
fn symbol() -> u64 {
set_return_data(b"MTK");
SUCCESS
}
fn decimals() -> u64 {
set_return_data(&[DECIMALS]);
SUCCESS
}
fn total_supply() -> u64 {
set_return_data(&TOTAL_SUPPLY.to_le_bytes());
SUCCESS
}
fn approve() -> u64 {
let args = get_args();
let spender = &args[0..32];
let amount = u64::from_le_bytes(args[32..40].try_into().unwrap());
let owner = get_caller();
// Store allowance
let key = [b"allowance:", &owner[..], b":", spender].concat();
storage_write(&key, &amount.to_le_bytes());
emit_event("Approval", &[&owner, spender, &amount.to_le_bytes()]);
SUCCESS
}
fn allowance() -> u64 {
let args = get_args();
let owner = &args[0..32];
let spender = &args[32..64];
let key = [b"allowance:", owner, b":", spender].concat();
let mut amount_bytes = [0u8; 8];
storage_read(&key, &mut amount_bytes);
set_return_data(&amount_bytes);
SUCCESS
}
fn transfer_from() -> u64 {
let args = get_args();
let from = &args[0..32];
let to = &args[32..64];
let amount = u64::from_le_bytes(args[64..72].try_into().unwrap());
let spender = get_caller();
// Check allowance
let allowance_key = [b"allowance:", from, b":", &spender[..]].concat();
let mut allowance_bytes = [0u8; 8];
storage_read(&allowance_key, &mut allowance_bytes);
let current_allowance = u64::from_le_bytes(allowance_bytes);
if current_allowance < amount {
return ERROR_INSUFFICIENT_ALLOWANCE;
}
// Update allowance
storage_write(&allowance_key, &(current_allowance - amount).to_le_bytes());
// Perform transfer (similar to transfer())
let from_key = [b"balance:", from].concat();
let mut from_balance_bytes = [0u8; 8];
storage_read(&from_key, &mut from_balance_bytes);
let from_balance = u64::from_le_bytes(from_balance_bytes);
if from_balance < amount {
return ERROR_INSUFFICIENT_BALANCE;
}
let to_key = [b"balance:", to].concat();
let mut to_balance_bytes = [0u8; 8];
storage_read(&to_key, &mut to_balance_bytes);
let to_balance = u64::from_le_bytes(to_balance_bytes);
storage_write(&from_key, &(from_balance - amount).to_le_bytes());
storage_write(&to_key, &(to_balance + amount).to_le_bytes());
emit_event("Transfer", &[from, to, &amount.to_le_bytes()]);
SUCCESS
}Using Native Features
VRF Random Numbers
use tako_sdk::*;
fn lottery_draw() -> u64 {
// Request VRF random number
let seed = get_args();
let random = vrf_random(&seed);
// Use random number for lottery
let winner_index = random % get_participant_count();
// ... distribute prize
SUCCESS
}Referral Rewards
use tako_sdk::*;
fn register_with_referral() -> u64 {
let args = get_args();
let referrer = &args[0..32];
let new_user = get_caller();
// Register referral relationship
referral_set(&new_user, referrer);
SUCCESS
}
fn distribute_referral_rewards() -> u64 {
let user = get_caller();
let amount: u64 = 100_00000000; // 100 tokens
// Automatically distribute to referral chain
referral_distribute(&user, amount);
SUCCESS
}Batch Transfers
use tako_sdk::*;
fn airdrop() -> u64 {
let args = get_args();
// Parse recipients and amounts
let count = args[0] as usize;
let mut recipients = Vec::new();
let mut amounts = Vec::new();
let mut offset = 1;
for _ in 0..count {
recipients.push(&args[offset..offset+32]);
offset += 32;
amounts.push(u64::from_le_bytes(args[offset..offset+8].try_into().unwrap()));
offset += 8;
}
// Execute batch transfer
batch_transfer(&recipients, &amounts);
SUCCESS
}Testing Smart Contracts
Unit Testing
#[cfg(test)]
mod tests {
use super::*;
use tako_sdk::testing::*;
#[test]
fn test_transfer() {
// Setup test environment
let mut env = TestEnv::new();
let owner = env.create_account(1000_00000000);
let recipient = env.create_account(0);
// Deploy contract
env.set_caller(owner);
let result = initialize();
assert_eq!(result, SUCCESS);
// Check initial balance
env.set_args(&owner);
balance_of();
let balance = env.get_return_data_u64();
assert_eq!(balance, 1000_00000000);
// Transfer tokens
let transfer_args = [&recipient[..], &100_00000000u64.to_le_bytes()].concat();
env.set_args(&transfer_args);
let result = transfer();
assert_eq!(result, SUCCESS);
// Verify balances
env.set_args(&owner);
balance_of();
assert_eq!(env.get_return_data_u64(), 900_00000000);
env.set_args(&recipient);
balance_of();
assert_eq!(env.get_return_data_u64(), 100_00000000);
}
}Running Tests
# Run all tests
cargo test
# Run specific test
cargo test test_transfer
# Run with output
cargo test -- --nocaptureDeployment
Testnet Deployment
# Build optimized contract
cargo build --release --target wasm32-unknown-unknown
# Deploy to testnet
tos deploy \
--contract target/wasm32-unknown-unknown/release/my_token.wasm \
--network testnet \
--gas-limit 1000000
# Output:
# Contract deployed!
# Address: tos1contract_abc123...
# Transaction: 0xtxhash...
# Gas used: 234,567
# Cost: $0.002Mainnet Deployment
# Pre-deployment checklist
# - All tests passing
# - Security review completed
# - Gas optimization verified
# Deploy to mainnet
tos deploy \
--contract target/wasm32-unknown-unknown/release/my_token.wasm \
--network mainnet \
--gas-limit 2000000 \
--confirmInteracting with Contracts
# Call view function
tos call \
--contract tos1contract_abc123... \
--method balance_of \
--args "tos1user_address..."
# Send transaction
tos send \
--contract tos1contract_abc123... \
--method transfer \
--args "tos1recipient...,1000000000"Gas Costs
| Operation | Approximate Cost |
|---|---|
| Deploy contract | $0.01 - $0.05 |
| Token transfer | $0.001 |
| Storage write | $0.0001 per byte |
| Storage read | Free |
| VRF random | $0.002 |
| Batch transfer (10 recipients) | $0.005 |
Best Practices
Security
// Always validate inputs
fn transfer() -> u64 {
let args = get_args();
if args.len() < 40 {
return ERROR_INVALID_ARGS;
}
let amount = u64::from_le_bytes(args[32..40].try_into().unwrap());
if amount == 0 {
return ERROR_ZERO_AMOUNT;
}
// ... continue
}
// Use checked arithmetic
fn safe_add(a: u64, b: u64) -> Option<u64> {
a.checked_add(b)
}
// Implement access control
fn only_owner() -> bool {
let caller = get_caller();
let mut owner = [0u8; 32];
storage_read(b"owner", &mut owner);
caller == owner
}Gas Optimization
// Cache storage reads
fn optimized_batch() -> u64 {
let from = get_caller();
let from_key = [b"balance:", &from[..]].concat();
// Read balance once
let mut balance_bytes = [0u8; 8];
storage_read(&from_key, &mut balance_bytes);
let mut balance = u64::from_le_bytes(balance_bytes);
// Process all transfers
for (recipient, amount) in transfers {
balance -= amount;
// Update recipient...
}
// Write sender balance once
storage_write(&from_key, &balance.to_le_bytes());
SUCCESS
}Next Steps
Ready to build more advanced contracts? Check out the complete examples and SDK reference.
- Smart Contracts Overview - Full TAKO documentation
- TAKO SDK Reference - Complete API reference
- Contract Examples - ERC20, VRF, Referrals, and more
- Native Features - VRF, Referrals, Batch Transfers
Last updated on