⚠️ 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]
| Opcode | Name | Parameters |
|---|---|---|
| 0x00 | Initialize | [name_len:2][name:N][symbol_len:2][symbol:N][decimals:1][initial_supply:8] |
| 0x01 | Transfer | [to:32][amount:8] |
| 0x02 | Approve | [spender:32][amount:8] |
| 0x03 | TransferFrom | [from:32][to:32][amount:8] |
| 0x04 | Mint | [to:32][amount:8] |
| 0x05 | Burn | [amount:8] |
| 0x10 | BalanceOf | [account:32] (query) |
| 0x11 | Allowance | [owner:32][spender:32] (query) |
| 0x12 | TotalSupply | “ (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] -> u64Core 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(¶ms[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(¶ms[0..32]);
let mut to = [0u8; 32];
to.copy_from_slice(¶ms[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
| Code | Meaning |
|---|---|
| 1001 | Already initialized |
| 1002 | Not initialized |
| 1003 | Invalid instruction |
| 1004 | Invalid parameters |
| 1005 | Insufficient balance |
| 1006 | Insufficient allowance |
| 1007 | Unauthorized (not owner) |
| 1008 | Invalid 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 00e8d4a510000000Transfer
# 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
| Operation | Compute Units | Approx. 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
- Zero Address Check: Always validate recipient is not zero address
- Overflow Protection: Use
saturating_add/subfor all arithmetic - Owner-Only Functions: Mint should be restricted to owner
- 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