//! SYN-20 Token Contract //! //! A standard fungible token implementation for Synor, similar to ERC-20. //! //! # Features //! - Minting (owner only) //! - Burning //! - Transfers //! - Allowances (approve/transferFrom) //! - Ownership management //! //! # Methods //! - `init(name, symbol, decimals, initial_supply)` - Initialize token //! - `name() -> String` - Token name //! - `symbol() -> String` - Token symbol //! - `decimals() -> u8` - Decimal places //! - `total_supply() -> u64` - Total supply //! - `balance_of(owner) -> u64` - Balance of address //! - `transfer(to, amount) -> bool` - Transfer tokens //! - `approve(spender, amount) -> bool` - Approve allowance //! - `allowance(owner, spender) -> u64` - Check allowance //! - `transfer_from(from, to, amount) -> bool` - Transfer from allowance //! - `mint(to, amount)` - Mint new tokens (owner only) //! - `burn(amount)` - Burn tokens #![no_std] extern crate alloc; use alloc::string::String; use alloc::vec::Vec; use borsh::BorshDeserialize; use synor_sdk::prelude::*; use synor_sdk::{require, require_auth}; // ==================== Storage Keys ==================== mod keys { pub const NAME: &[u8] = b"syn20:name"; pub const SYMBOL: &[u8] = b"syn20:symbol"; pub const DECIMALS: &[u8] = b"syn20:decimals"; pub const TOTAL_SUPPLY: &[u8] = b"syn20:total_supply"; pub const OWNER: &[u8] = b"syn20:owner"; pub const BALANCES: &[u8] = b"syn20:balances"; pub const ALLOWANCES: &[u8] = b"syn20:allowances"; } // ==================== Storage Helpers ==================== fn balances() -> storage::Map<[u8; 34], u64> { storage::Map::new(keys::BALANCES) } fn allowances_key(owner: &Address, spender: &Address) -> Vec { let mut key = Vec::with_capacity(68); key.extend_from_slice(owner.as_bytes()); key.extend_from_slice(spender.as_bytes()); key } fn get_allowance(owner: &Address, spender: &Address) -> u64 { let key = allowances_key(owner, spender); storage::get_with_suffix::(keys::ALLOWANCES, &key).unwrap_or(0) } fn set_allowance(owner: &Address, spender: &Address, amount: u64) { let key = allowances_key(owner, spender); storage::set_with_suffix(keys::ALLOWANCES, &key, &amount); } fn get_owner() -> Option
{ storage::get::
(keys::OWNER) } // ==================== Entry Points ==================== synor_sdk::entry_point!(init, call); /// Initialize the token contract. fn init(params: &[u8]) -> Result<()> { #[derive(BorshDeserialize)] struct InitParams { name: String, symbol: String, decimals: u8, initial_supply: u64, } let params = InitParams::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid init params"))?; // Store token metadata storage::set(keys::NAME, ¶ms.name); storage::set(keys::SYMBOL, ¶ms.symbol); storage::set(keys::DECIMALS, ¶ms.decimals); storage::set(keys::TOTAL_SUPPLY, ¶ms.initial_supply); // Set owner let owner = caller(); storage::set(keys::OWNER, &owner); // Mint initial supply to owner if params.initial_supply > 0 { balances().set(&owner.0, ¶ms.initial_supply); // Emit transfer from zero address emit(&synor_sdk::events::Transfer { from: Address::zero(), to: owner, amount: params.initial_supply, }); } Ok(()) } /// Handle contract calls. fn call(selector: &[u8], params: &[u8]) -> Result> { // Method selectors (computed at runtime) let name_sel = synor_sdk::method_selector("name"); let symbol_sel = synor_sdk::method_selector("symbol"); let decimals_sel = synor_sdk::method_selector("decimals"); let total_supply_sel = synor_sdk::method_selector("total_supply"); let balance_of_sel = synor_sdk::method_selector("balance_of"); let transfer_sel = synor_sdk::method_selector("transfer"); let approve_sel = synor_sdk::method_selector("approve"); let allowance_sel = synor_sdk::method_selector("allowance"); let transfer_from_sel = synor_sdk::method_selector("transfer_from"); let mint_sel = synor_sdk::method_selector("mint"); let burn_sel = synor_sdk::method_selector("burn"); let owner_sel = synor_sdk::method_selector("owner"); let transfer_ownership_sel = synor_sdk::method_selector("transfer_ownership"); match selector { s if s == name_sel => { let name: String = storage::get(keys::NAME).unwrap_or_default(); Ok(borsh::to_vec(&name).unwrap()) } s if s == symbol_sel => { let symbol: String = storage::get(keys::SYMBOL).unwrap_or_default(); Ok(borsh::to_vec(&symbol).unwrap()) } s if s == decimals_sel => { let decimals: u8 = storage::get(keys::DECIMALS).unwrap_or(8); Ok(borsh::to_vec(&decimals).unwrap()) } s if s == total_supply_sel => { let supply: u64 = storage::get(keys::TOTAL_SUPPLY).unwrap_or(0); Ok(borsh::to_vec(&supply).unwrap()) } s if s == balance_of_sel => { #[derive(BorshDeserialize)] struct Args { owner: Address, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Expected owner address"))?; let balance = balances().get_or_default(&args.owner.0); Ok(borsh::to_vec(&balance).unwrap()) } s if s == transfer_sel => { #[derive(BorshDeserialize)] struct Args { to: Address, amount: u64, } let args = Args::try_from_slice(params).map_err(|_| Error::invalid_args("Expected to, amount"))?; let success = do_transfer(&caller(), &args.to, args.amount)?; Ok(borsh::to_vec(&success).unwrap()) } s if s == approve_sel => { #[derive(BorshDeserialize)] struct Args { spender: Address, amount: u64, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Expected spender, amount"))?; let owner = caller(); set_allowance(&owner, &args.spender, args.amount); emit(&synor_sdk::events::Approval { owner, spender: args.spender, amount: args.amount, }); Ok(borsh::to_vec(&true).unwrap()) } s if s == allowance_sel => { #[derive(BorshDeserialize)] struct Args { owner: Address, spender: Address, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Expected owner, spender"))?; let amount = get_allowance(&args.owner, &args.spender); Ok(borsh::to_vec(&amount).unwrap()) } s if s == transfer_from_sel => { #[derive(BorshDeserialize)] struct Args { from: Address, to: Address, amount: u64, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Expected from, to, amount"))?; let spender = caller(); let current_allowance = get_allowance(&args.from, &spender); require!( current_allowance >= args.amount, Error::InsufficientBalance { required: args.amount, available: current_allowance } ); // Decrease allowance set_allowance(&args.from, &spender, current_allowance - args.amount); // Do transfer let success = do_transfer(&args.from, &args.to, args.amount)?; Ok(borsh::to_vec(&success).unwrap()) } s if s == mint_sel => { #[derive(BorshDeserialize)] struct Args { to: Address, amount: u64, } let args = Args::try_from_slice(params).map_err(|_| Error::invalid_args("Expected to, amount"))?; // Only owner can mint let owner = get_owner().ok_or(Error::Unauthorized)?; require_auth!(owner); // Update total supply let supply: u64 = storage::get(keys::TOTAL_SUPPLY).unwrap_or(0); let new_supply = supply.checked_add(args.amount).ok_or(Error::Overflow)?; storage::set(keys::TOTAL_SUPPLY, &new_supply); // Credit recipient let balance = balances().get_or_default(&args.to.0); let new_balance = balance.checked_add(args.amount).ok_or(Error::Overflow)?; balances().set(&args.to.0, &new_balance); emit(&synor_sdk::events::Transfer { from: Address::zero(), to: args.to, amount: args.amount, }); Ok(borsh::to_vec(&true).unwrap()) } s if s == burn_sel => { #[derive(BorshDeserialize)] struct Args { amount: u64, } let args = Args::try_from_slice(params).map_err(|_| Error::invalid_args("Expected amount"))?; let burner = caller(); let balance = balances().get_or_default(&burner.0); require!( balance >= args.amount, Error::InsufficientBalance { required: args.amount, available: balance } ); // Decrease balance balances().set(&burner.0, &(balance - args.amount)); // Decrease total supply let supply: u64 = storage::get(keys::TOTAL_SUPPLY).unwrap_or(0); storage::set(keys::TOTAL_SUPPLY, &supply.saturating_sub(args.amount)); emit(&synor_sdk::events::Transfer { from: burner, to: Address::zero(), amount: args.amount, }); Ok(borsh::to_vec(&true).unwrap()) } s if s == owner_sel => { let owner = get_owner().unwrap_or(Address::zero()); Ok(borsh::to_vec(&owner).unwrap()) } s if s == transfer_ownership_sel => { #[derive(BorshDeserialize)] struct Args { new_owner: Address, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Expected new_owner"))?; let current_owner = get_owner().ok_or(Error::Unauthorized)?; require_auth!(current_owner); storage::set(keys::OWNER, &args.new_owner); emit(&synor_sdk::events::OwnershipTransferred { previous_owner: current_owner, new_owner: args.new_owner, }); Ok(Vec::new()) } _ => Err(Error::InvalidMethod), } } // ==================== Internal Functions ==================== fn do_transfer(from: &Address, to: &Address, amount: u64) -> Result { require!( *from != Address::zero(), Error::invalid_args("Cannot transfer from zero address") ); require!( *to != Address::zero(), Error::invalid_args("Cannot transfer to zero address") ); let from_balance = balances().get_or_default(&from.0); require!( from_balance >= amount, Error::InsufficientBalance { required: amount, available: from_balance } ); // Update balances balances().set(&from.0, &(from_balance - amount)); let to_balance = balances().get_or_default(&to.0); let new_to_balance = to_balance.checked_add(amount).ok_or(Error::Overflow)?; balances().set(&to.0, &new_to_balance); emit(&synor_sdk::events::Transfer { from: *from, to: *to, amount, }); Ok(true) }