Smart Contract Structure

Building well-structured smart contracts in Rust.

Advanced⏱️ 55 min📚 Prerequisites: 1

Smart Contract Structure

Smart contracts need clear structure for maintainability and security. Let's build a token contract as an example.

Contract Components

  1. State: Contract storage
  2. Events: Log important actions
  3. Functions: Public and private methods
  4. Initialization: Contract setup

Token Contract Example

RUST
struct TokenContract {
    name: String,
    symbol: String,
    total_supply: u64,
    balances: HashMap<String, u64>,
    owner: String,
}

impl TokenContract {
    fn new(name: String, symbol: String, initial_supply: u64) -> Self {
        let mut contract = TokenContract {
            name,
            symbol,
            total_supply: initial_supply,
            balances: HashMap::new(),
            owner: String::from("deployer"),
        };
        
        // Give all tokens to deployer
        contract.balances.insert(contract.owner.clone(), initial_supply);
        contract
    }
    
    fn transfer(&mut self, from: String, to: String, amount: u64) -> Result<(), String> {
        // Check balance
        let balance = self.balances.get(&from).copied().unwrap_or(0);
        if balance < amount {
            return Err(String::from("Insufficient balance"));
        }
        
        // Transfer
        *self.balances.get_mut(&from).unwrap() -= amount;
        *self.balances.entry(to).or_insert(0) += amount;
        
        Ok(())
    }
}

Best Practices

  • Access control: Check permissions
  • Input validation: Validate all inputs
  • Error handling: Return clear errors
  • Events: Emit events for important actions
  • Gas optimization: Minimize storage operations

Security Considerations

  • Reentrancy: Prevent recursive calls
  • Overflow: Use checked arithmetic
  • Access control: Verify caller permissions
  • Input validation: Sanitize all inputs

Code Examples

Token Contract

Simple token contract implementation

RUST
use std::collections::HashMap;
struct TokenContract {
    name: String,
    symbol: String,
    total_supply: u64,
    balances: HashMap<String, u64>,
}
impl TokenContract {
    fn new(name: String, symbol: String, initial_supply: u64) -> Self {
        let mut contract = TokenContract {
            name,
            symbol,
            total_supply: initial_supply,
            balances: HashMap::new(),
        };
        contract.balances.insert(String::from("deployer"), initial_supply);
        contract
    }
    fn balance_of(&self, address: &str) -> u64 {
        self.balances.get(address).copied().unwrap_or(0)
    }
    fn transfer(&mut self, from: String, to: String, amount: u64) -> Result<(), String> {
        let balance = self.balance_of(&from);
        if balance < amount {
            return Err(String::from("Insufficient balance"));
        }
        *self.balances.entry(from).or_insert(0) -= amount;
        *self.balances.entry(to).or_insert(0) += amount;
        Ok(())
    }
}
fn main() {
    let mut contract = TokenContract::new(
        String::from("MyToken"),
        String::from("MTK"),
        1000,
    );
    println!("Deployer balance: {}", contract.balance_of("deployer"));
    contract.transfer(
        String::from("deployer"),
        String::from("alice"),
        100,
    ).unwrap();
    println!("Alice balance: {}", contract.balance_of("alice"));
}

Explanation:

A token contract manages balances and allows transfers. It maintains a mapping of addresses to token balances and validates transfers.

Access Control

Implementing access control in contracts

RUST
struct Contract {
    owner: String,
    data: String,
}
impl Contract {
    fn new(owner: String) -> Self {
        Contract {
            owner,
            data: String::from("initial"),
        }
    }
    fn set_data(&mut self, caller: &str, new_data: String) -> Result<(), String> {
        if caller != self.owner {
            return Err(String::from("Only owner can modify"));
        }
        self.data = new_data;
        Ok(())
    }
    fn get_data(&self) -> &str {
        &self.data
    }
}
fn main() {
    let mut contract = Contract::new(String::from("owner"));
    contract.set_data("owner", String::from("modified")).unwrap();
    println!("Data: {}", contract.get_data());
    match contract.set_data("hacker", String::from("hacked")) {
        Ok(_) => println!("Modified"),
        Err(e) => println!("Error: {}", e),
    }
}

Explanation:

Access control ensures only authorized addresses can perform certain actions. This is critical for contract security.

Exercises

Create Token Contract

Create a token contract with transfer functionality!

Hard

Starter Code:

RUST
use std::collections::HashMap;
struct TokenContract {
    balances: HashMap<String, u64>,
}
fn main() {
    let mut contract = TokenContract::new();
    println!("Deployer balance: {}", contract.balance_of("deployer"));
}