Skip to Content

⚠️ Disclaimer: This document is provided as a smart contract coding example only. The code has NOT been professionally audited. Use at your own risk - you bear full responsibility for any losses if deployed to production.

ERC20 Token Contract

A production-ready ERC20 token implementation following OpenZeppelin patterns.

Features

  • Full ERC20 compliance (transfer, approve, transferFrom)
  • Minting and burning capabilities
  • Overflow protection with saturating arithmetic
  • Comprehensive error handling
  • Detailed logging

Instruction Format

All instructions use format: [opcode:1][params:N]

OpcodeNameParameters
0x00Initialize[name_len:2][name:N][symbol_len:2][symbol:N][decimals:1][initial_supply:8]
0x01Transfer[to:32][amount:8]
0x02Approve[spender:32][amount:8]
0x03TransferFrom[from:32][to:32][amount:8]
0x04Mint[to:32][amount:8]
0x05Burn[amount:8]
0x10BalanceOf[account:32] (query)
0x11Allowance[owner:32][spender:32] (query)
0x12TotalSupply“ (query)

Storage Layout

// Storage key prefixes const KEY_INITIALIZED: u8 = 0x01; const KEY_TOTAL_SUPPLY: u8 = 0x02; const KEY_NAME: u8 = 0x03; const KEY_SYMBOL: u8 = 0x04; const KEY_DECIMALS: u8 = 0x05; const KEY_OWNER: u8 = 0x06; const KEY_BALANCE_PREFIX: u8 = 0x10; // [0x10 | address:32] -> u64 const KEY_ALLOWANCE_PREFIX: u8 = 0x20; // [0x20 | owner:32 | spender:32] -> u64

Core Implementation

Balance Operations

/// Get balance of an account fn get_balance(account: &[u8; 32]) -> u64 { let mut key = [0u8; 33]; key[0] = KEY_BALANCE_PREFIX; key[1..33].copy_from_slice(account); let mut buffer = [0u8; 8]; let len = storage_read(&key, &mut buffer); if len == 8 { u64::from_le_bytes(buffer) } else { 0 } } /// Set balance of an account fn set_balance(account: &[u8; 32], amount: u64) { let mut key = [0u8; 33]; key[0] = KEY_BALANCE_PREFIX; key[1..33].copy_from_slice(account); let _ = storage_write(&key, &amount.to_le_bytes()); }

Transfer Implementation

fn op_transfer(params: &[u8]) -> u64 { log("ERC20: Transfer"); if !is_initialized() { return ERR_NOT_INITIALIZED; } if params.len() < 40 { return ERR_INVALID_PARAMS; } // Parse parameters let mut to = [0u8; 32]; to.copy_from_slice(&params[0..32]); let amount = u64::from_le_bytes([ params[32], params[33], params[34], params[35], params[36], params[37], params[38], params[39], ]); // Validate if is_zero_address(&to) { return ERR_INVALID_ADDRESS; } let from = get_tx_sender(); // Check balance let from_balance = get_balance(&from); if from_balance < amount { log("ERC20: Insufficient balance"); return ERR_INSUFFICIENT_BALANCE; } // Update balances (saturating for safety) let new_from = from_balance.saturating_sub(amount); let new_to = get_balance(&to).saturating_add(amount); set_balance(&from, new_from); set_balance(&to, new_to); log("ERC20: Transfer successful"); SUCCESS }

Allowance Operations

/// Get allowance fn get_allowance(owner: &[u8; 32], spender: &[u8; 32]) -> u64 { let mut key = [0u8; 65]; key[0] = KEY_ALLOWANCE_PREFIX; key[1..33].copy_from_slice(owner); key[33..65].copy_from_slice(spender); let mut buffer = [0u8; 8]; let len = storage_read(&key, &mut buffer); if len == 8 { u64::from_le_bytes(buffer) } else { 0 } } /// Set allowance fn set_allowance(owner: &[u8; 32], spender: &[u8; 32], amount: u64) { let mut key = [0u8; 65]; key[0] = KEY_ALLOWANCE_PREFIX; key[1..33].copy_from_slice(owner); key[33..65].copy_from_slice(spender); let _ = storage_write(&key, &amount.to_le_bytes()); }

TransferFrom Implementation

fn op_transfer_from(params: &[u8]) -> u64 { log("ERC20: TransferFrom"); if params.len() < 72 { return ERR_INVALID_PARAMS; } // Parse parameters let mut from = [0u8; 32]; from.copy_from_slice(&params[0..32]); let mut to = [0u8; 32]; to.copy_from_slice(&params[32..64]); let amount = u64::from_le_bytes([ params[64], params[65], params[66], params[67], params[68], params[69], params[70], params[71], ]); let spender = get_tx_sender(); // Check allowance let allowance = get_allowance(&from, &spender); if allowance < amount { return ERR_INSUFFICIENT_ALLOWANCE; } // Check balance let from_balance = get_balance(&from); if from_balance < amount { return ERR_INSUFFICIENT_BALANCE; } // Update balances set_balance(&from, from_balance.saturating_sub(amount)); set_balance(&to, get_balance(&to).saturating_add(amount)); // Update allowance set_allowance(&from, &spender, allowance.saturating_sub(amount)); log("ERC20: TransferFrom successful"); SUCCESS }

Entry Point

#[no_mangle] pub extern "C" fn entrypoint() -> u64 { let mut input = [0u8; 1024]; let len = get_input_data(&mut input); if len == 0 { return ERR_INVALID_INSTRUCTION; } let opcode = input[0]; let params = &input[1..len as usize]; match opcode { OP_INITIALIZE => op_initialize(params), OP_TRANSFER => op_transfer(params), OP_APPROVE => op_approve(params), OP_TRANSFER_FROM => op_transfer_from(params), OP_MINT => op_mint(params), OP_BURN => op_burn(params), OP_BALANCE_OF => op_balance_of(params), OP_ALLOWANCE => op_allowance(params), OP_TOTAL_SUPPLY => op_total_supply(), _ => ERR_INVALID_INSTRUCTION, } }

Error Codes

CodeMeaning
1001Already initialized
1002Not initialized
1003Invalid instruction
1004Invalid parameters
1005Insufficient balance
1006Insufficient allowance
1007Unauthorized (not owner)
1008Invalid address (zero address)

Usage Examples

Initialize Token

# Create "MyToken" (MTK) with 18 decimals and 1M supply # Hex: 00 + name_len + "MyToken" + symbol_len + "MTK" + decimals + supply tos_wallet call_contract <address> \ 000007 4d79546f6b656e 0003 4d544b 12 00e8d4a510000000

Transfer

# Transfer 100 tokens (18 decimals) to recipient tos_wallet call_contract <address> \ 01 <recipient:32bytes> <amount:8bytes>

Check Balance

# Query balance tos_wallet call_contract <address> 10 <account:32bytes>

Gas Costs

OperationCompute UnitsApprox. Cost
Initialize~80,000$0.08
Transfer~45,000$0.045
Approve~25,000$0.025
TransferFrom~50,000$0.05
Mint~45,000$0.045
Burn~40,000$0.04
BalanceOf~500$0.0005

Security Considerations

  1. Zero Address Check: Always validate recipient is not zero address
  2. Overflow Protection: Use saturating_add/sub for all arithmetic
  3. Owner-Only Functions: Mint should be restricted to owner
  4. Reentrancy: Not applicable in TAKO (no callbacks during execution)

Full Source

The complete ERC20 implementation is available at: github.com/tos-network/tck/contracts/erc20-openzeppelin 

Last updated on