A complete blockchain implementation featuring: - synord: Full node with GHOSTDAG consensus - explorer-web: Modern React blockchain explorer with 3D DAG visualization - CLI wallet and tools - Smart contract SDK and example contracts (DEX, NFT, token) - WASM crypto library for browser/mobile
372 lines
12 KiB
Rust
372 lines
12 KiB
Rust
//! 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<u8> {
|
|
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::<u64>(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<Address> {
|
|
storage::get::<Address>(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<Vec<u8>> {
|
|
// 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<bool> {
|
|
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)
|
|
}
|