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
- State: Contract storage
- Events: Log important actions
- Functions: Public and private methods
- Initialization: Contract setup
Token Contract Example
RUSTstruct 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!
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"));
}