805 lines
24 KiB
Rust
805 lines
24 KiB
Rust
//! ACC-20: NAC 原生同质化代币标准
|
||
//! 协议 UID: nac.acc.ACC20Token.v2
|
||
//! 完全基于 NAC 原生类型系统,无以太坊模式残留
|
||
|
||
use std::collections::HashMap;
|
||
use serde::{Deserialize, Serialize};
|
||
use crate::primitives::{Address, Hash, Timestamp};
|
||
|
||
// ========================
|
||
// 错误类型
|
||
// ========================
|
||
|
||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||
/// ACC20Error 错误类型
|
||
pub enum ACC20Error {
|
||
/// 余额不足错误
|
||
InsufficientBalance {
|
||
/// 持有方账户地址
|
||
holder: Address,
|
||
/// 所需数量
|
||
required: u128,
|
||
/// 可用数量
|
||
available: u128,
|
||
},
|
||
/// 授权额度不足错误
|
||
InsufficientAllowance {
|
||
/// 所有者账户地址
|
||
owner: Address,
|
||
/// 被授权方账户地址
|
||
spender: Address,
|
||
/// 所需数量
|
||
required: u128,
|
||
/// 可用数量
|
||
available: u128,
|
||
},
|
||
/// 宪法收据无效错误
|
||
InvalidConstitutionalReceipt,
|
||
/// 账户已冻结错误
|
||
AccountFrozen(Address),
|
||
/// 转账已暂停错误
|
||
TransferHalted,
|
||
/// 金额为零错误
|
||
ZeroAmount,
|
||
/// 数值溢出错误
|
||
Overflow,
|
||
/// 未授权操作错误
|
||
Unauthorized(Address),
|
||
/// 无效地址错误
|
||
InvalidAddress,
|
||
/// 超出供应上限错误
|
||
SupplyCapExceeded {
|
||
/// 供应量上限
|
||
cap: u128,
|
||
/// 当前数量
|
||
current: u128,
|
||
/// 请求数量
|
||
requested: u128,
|
||
},
|
||
}
|
||
|
||
impl std::fmt::Display for ACC20Error {
|
||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
match self {
|
||
Self::InsufficientBalance { holder, required, available } =>
|
||
write!(f, "余额不足: 持有者 {} 需要 {} 实际 {}", holder.to_hex(), required, available),
|
||
Self::InsufficientAllowance { owner, spender, required, available } =>
|
||
write!(f, "授权额度不足: 所有者 {} 授权给 {} 需要 {} 实际 {}", owner.to_hex(), spender.to_hex(), required, available),
|
||
Self::InvalidConstitutionalReceipt => write!(f, "宪法收据无效"),
|
||
Self::AccountFrozen(a) => write!(f, "账户已冻结: {}", a.to_hex()),
|
||
Self::TransferHalted => write!(f, "转账已暂停"),
|
||
Self::ZeroAmount => write!(f, "金额不能为零"),
|
||
Self::Overflow => write!(f, "数值溢出"),
|
||
Self::Unauthorized(a) => write!(f, "未授权操作: {}", a.to_hex()),
|
||
Self::InvalidAddress => write!(f, "无效地址"),
|
||
Self::SupplyCapExceeded { cap, current, requested } =>
|
||
write!(f, "超出供应上限: 上限 {} 当前 {} 请求 {}", cap, current, requested),
|
||
}
|
||
}
|
||
}
|
||
|
||
// ========================
|
||
// 协议事件(CSNP 广播)
|
||
// ========================
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
/// ACC20Event 协议事件
|
||
pub enum ACC20Event {
|
||
/// 代币转移
|
||
Transfer {
|
||
/// 发送方账户地址
|
||
from: Address,
|
||
/// 接收方账户地址
|
||
to: Address,
|
||
/// 代币数量(最小单位)
|
||
amount: u128,
|
||
/// 宪法收据哈希(CBPP 共识凭证)
|
||
constitutional_receipt: Hash,
|
||
/// 操作时间戳(UTC Unix 毫秒)
|
||
timestamp: Timestamp,
|
||
},
|
||
/// 授权额度变更
|
||
Approval {
|
||
/// 所有者账户地址
|
||
owner: Address,
|
||
/// 被授权方账户地址
|
||
spender: Address,
|
||
/// 代币数量(最小单位)
|
||
amount: u128,
|
||
/// 操作时间戳(UTC Unix 毫秒)
|
||
timestamp: Timestamp,
|
||
},
|
||
/// 铸造
|
||
Mint {
|
||
/// 接收方账户地址
|
||
to: Address,
|
||
/// 代币数量(最小单位)
|
||
amount: u128,
|
||
/// 宪法收据哈希(CBPP 共识凭证)
|
||
constitutional_receipt: Hash,
|
||
/// 操作时间戳(UTC Unix 毫秒)
|
||
timestamp: Timestamp,
|
||
},
|
||
/// 销毁
|
||
Burn {
|
||
/// 发送方账户地址
|
||
from: Address,
|
||
/// 代币数量(最小单位)
|
||
amount: u128,
|
||
/// 宪法收据哈希(CBPP 共识凭证)
|
||
constitutional_receipt: Hash,
|
||
/// 操作时间戳(UTC Unix 毫秒)
|
||
timestamp: Timestamp,
|
||
},
|
||
/// 账户冻结
|
||
AccountFrozen {
|
||
/// account 字段
|
||
account: Address,
|
||
/// 操作原因说明
|
||
reason: String,
|
||
/// 宪法收据哈希(CBPP 共识凭证)
|
||
constitutional_receipt: Hash,
|
||
/// 操作时间戳(UTC Unix 毫秒)
|
||
timestamp: Timestamp,
|
||
},
|
||
/// 账户解冻
|
||
AccountUnfrozen {
|
||
/// account 字段
|
||
account: Address,
|
||
/// 宪法收据哈希(CBPP 共识凭证)
|
||
constitutional_receipt: Hash,
|
||
/// 操作时间戳(UTC Unix 毫秒)
|
||
timestamp: Timestamp,
|
||
},
|
||
/// 转账暂停
|
||
TransferHalted {
|
||
/// 操作原因说明
|
||
reason: String,
|
||
/// 宪法收据哈希(CBPP 共识凭证)
|
||
constitutional_receipt: Hash,
|
||
/// 操作时间戳(UTC Unix 毫秒)
|
||
timestamp: Timestamp,
|
||
},
|
||
/// 转账恢复
|
||
TransferResumed {
|
||
/// 宪法收据哈希(CBPP 共识凭证)
|
||
constitutional_receipt: Hash,
|
||
/// 操作时间戳(UTC Unix 毫秒)
|
||
timestamp: Timestamp,
|
||
},
|
||
}
|
||
|
||
// ========================
|
||
// 主结构体
|
||
// ========================
|
||
|
||
/// ACC-20 生产级别同质化代币协议
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
/// ACC20Token 代币协议实现
|
||
pub struct ACC20Token {
|
||
// 协议标识
|
||
/// 协议唯一标识符
|
||
pub protocol_uid: String,
|
||
/// NAC-Lens 协议向量
|
||
pub lens_protocol_vector: String,
|
||
|
||
// 代币基本信息
|
||
/// 名称
|
||
pub name: String,
|
||
/// 代币符号
|
||
pub symbol: String,
|
||
/// 代币精度(小数位数)
|
||
pub decimals: u8,
|
||
/// 代币总供应量
|
||
pub total_supply: u128,
|
||
/// 供应量上限(None 表示无上限)
|
||
pub supply_cap: Option<u128>,
|
||
|
||
// 核心状态(NAC 原生:holdings 而非 balances)
|
||
/// 持有量映射(地址 → 数量)
|
||
pub holdings: HashMap<Address, u128>,
|
||
/// 主权授权(NAC 原生:sovereignty_authorizations 而非 allowances)
|
||
pub sovereignty_authorizations: HashMap<Address, HashMap<Address, u128>>,
|
||
|
||
// 合规控制
|
||
/// 已冻结账户映射(地址 → 冻结原因)
|
||
pub frozen_accounts: HashMap<Address, String>,
|
||
/// 转账是否已暂停
|
||
pub transfer_halted: bool,
|
||
/// 暂停原因(None 表示未暂停)
|
||
pub halt_reason: Option<String>,
|
||
|
||
// 权限
|
||
/// 所有者账户地址
|
||
pub owner: Address,
|
||
/// 铸造者地址列表
|
||
pub minters: Vec<Address>,
|
||
|
||
// 待广播事件
|
||
/// 待广播的 CSNP 协议事件队列
|
||
pub pending_events: Vec<ACC20Event>,
|
||
|
||
// 时间戳
|
||
/// 创建时间戳
|
||
pub created_at: Timestamp,
|
||
/// 最后更新时间戳
|
||
pub updated_at: Timestamp,
|
||
}
|
||
|
||
impl ACC20Token {
|
||
/// 创建新的 ACC-20 代币
|
||
pub fn new(
|
||
name: String,
|
||
symbol: String,
|
||
decimals: u8,
|
||
owner: Address,
|
||
initial_supply: u128,
|
||
supply_cap: Option<u128>,
|
||
constitutional_receipt: Hash,
|
||
timestamp: Timestamp,
|
||
) -> Result<Self, ACC20Error> {
|
||
if constitutional_receipt.is_zero() {
|
||
return Err(ACC20Error::InvalidConstitutionalReceipt);
|
||
}
|
||
if let Some(cap) = supply_cap {
|
||
if initial_supply > cap {
|
||
return Err(ACC20Error::SupplyCapExceeded {
|
||
cap,
|
||
current: 0,
|
||
requested: initial_supply,
|
||
});
|
||
}
|
||
}
|
||
|
||
let mut holdings = HashMap::new();
|
||
if initial_supply > 0 {
|
||
holdings.insert(owner.clone(), initial_supply);
|
||
}
|
||
|
||
let mut token = Self {
|
||
protocol_uid: "nac.acc.ACC20Token.v2".to_string(),
|
||
lens_protocol_vector: "ACC-20".to_string(),
|
||
name,
|
||
symbol,
|
||
decimals,
|
||
total_supply: initial_supply,
|
||
supply_cap,
|
||
holdings,
|
||
sovereignty_authorizations: HashMap::new(),
|
||
frozen_accounts: HashMap::new(),
|
||
transfer_halted: false,
|
||
halt_reason: None,
|
||
owner: owner.clone(),
|
||
minters: vec![owner.clone()],
|
||
pending_events: Vec::new(),
|
||
created_at: timestamp.clone(),
|
||
updated_at: timestamp.clone(),
|
||
};
|
||
|
||
if initial_supply > 0 {
|
||
token.pending_events.push(ACC20Event::Mint {
|
||
to: owner,
|
||
amount: initial_supply,
|
||
constitutional_receipt,
|
||
timestamp,
|
||
});
|
||
}
|
||
|
||
Ok(token)
|
||
}
|
||
|
||
// ========================
|
||
// 查询方法
|
||
// ========================
|
||
|
||
/// 查询持仓余额
|
||
pub fn balance_of(&self, holder: &Address) -> u128 {
|
||
self.holdings.get(holder).copied().unwrap_or(0)
|
||
}
|
||
|
||
/// 查询主权授权额度
|
||
pub fn allowance(&self, owner: &Address, spender: &Address) -> u128 {
|
||
self.sovereignty_authorizations
|
||
.get(owner)
|
||
.and_then(|m| m.get(spender))
|
||
.copied()
|
||
.unwrap_or(0)
|
||
}
|
||
|
||
/// 检查账户是否冻结
|
||
pub fn is_frozen(&self, account: &Address) -> bool {
|
||
self.frozen_accounts.contains_key(account)
|
||
}
|
||
|
||
// ========================
|
||
// 转账方法
|
||
// ========================
|
||
|
||
/// 直接转账
|
||
pub fn transfer(
|
||
&mut self,
|
||
from: &Address,
|
||
to: &Address,
|
||
amount: u128,
|
||
constitutional_receipt: Hash,
|
||
timestamp: Timestamp,
|
||
) -> Result<(), ACC20Error> {
|
||
if constitutional_receipt.is_zero() {
|
||
return Err(ACC20Error::InvalidConstitutionalReceipt);
|
||
}
|
||
if amount == 0 {
|
||
return Err(ACC20Error::ZeroAmount);
|
||
}
|
||
if self.transfer_halted {
|
||
return Err(ACC20Error::TransferHalted);
|
||
}
|
||
if self.is_frozen(from) {
|
||
return Err(ACC20Error::AccountFrozen(from.clone()));
|
||
}
|
||
if self.is_frozen(to) {
|
||
return Err(ACC20Error::AccountFrozen(to.clone()));
|
||
}
|
||
|
||
let from_balance = self.balance_of(from);
|
||
if from_balance < amount {
|
||
return Err(ACC20Error::InsufficientBalance {
|
||
holder: from.clone(),
|
||
required: amount,
|
||
available: from_balance,
|
||
});
|
||
}
|
||
|
||
*self.holdings.get_mut(from).expect("FIX-006: unexpected None/Err") -= amount;
|
||
*self.holdings.entry(to.clone()).or_insert(0) += amount;
|
||
|
||
self.pending_events.push(ACC20Event::Transfer {
|
||
from: from.clone(),
|
||
to: to.clone(),
|
||
amount,
|
||
constitutional_receipt,
|
||
timestamp,
|
||
});
|
||
self.updated_at = Timestamp::now();
|
||
Ok(())
|
||
}
|
||
|
||
/// 代理转账(消耗授权额度)
|
||
pub fn transfer_from(
|
||
&mut self,
|
||
spender: &Address,
|
||
from: &Address,
|
||
to: &Address,
|
||
amount: u128,
|
||
constitutional_receipt: Hash,
|
||
timestamp: Timestamp,
|
||
) -> Result<(), ACC20Error> {
|
||
if constitutional_receipt.is_zero() {
|
||
return Err(ACC20Error::InvalidConstitutionalReceipt);
|
||
}
|
||
if amount == 0 {
|
||
return Err(ACC20Error::ZeroAmount);
|
||
}
|
||
if self.transfer_halted {
|
||
return Err(ACC20Error::TransferHalted);
|
||
}
|
||
if self.is_frozen(from) {
|
||
return Err(ACC20Error::AccountFrozen(from.clone()));
|
||
}
|
||
if self.is_frozen(to) {
|
||
return Err(ACC20Error::AccountFrozen(to.clone()));
|
||
}
|
||
|
||
let current_allowance = self.allowance(from, spender);
|
||
if current_allowance < amount {
|
||
return Err(ACC20Error::InsufficientAllowance {
|
||
owner: from.clone(),
|
||
spender: spender.clone(),
|
||
required: amount,
|
||
available: current_allowance,
|
||
});
|
||
}
|
||
|
||
let from_balance = self.balance_of(from);
|
||
if from_balance < amount {
|
||
return Err(ACC20Error::InsufficientBalance {
|
||
holder: from.clone(),
|
||
required: amount,
|
||
available: from_balance,
|
||
});
|
||
}
|
||
|
||
// 扣减授权额度
|
||
*self.sovereignty_authorizations
|
||
.get_mut(from)
|
||
.expect("FIX-006: unexpected None/Err")
|
||
.get_mut(spender)
|
||
.expect("FIX-006: unexpected None/Err") -= amount;
|
||
|
||
// 执行转账
|
||
*self.holdings.get_mut(from).expect("FIX-006: unexpected None/Err") -= amount;
|
||
*self.holdings.entry(to.clone()).or_insert(0) += amount;
|
||
|
||
self.pending_events.push(ACC20Event::Transfer {
|
||
from: from.clone(),
|
||
to: to.clone(),
|
||
amount,
|
||
constitutional_receipt,
|
||
timestamp,
|
||
});
|
||
self.updated_at = Timestamp::now();
|
||
Ok(())
|
||
}
|
||
|
||
// ========================
|
||
// 授权方法
|
||
// ========================
|
||
|
||
/// 设置主权授权额度
|
||
pub fn approve(
|
||
&mut self,
|
||
owner: &Address,
|
||
spender: &Address,
|
||
amount: u128,
|
||
timestamp: Timestamp,
|
||
) -> Result<(), ACC20Error> {
|
||
if self.is_frozen(owner) {
|
||
return Err(ACC20Error::AccountFrozen(owner.clone()));
|
||
}
|
||
|
||
self.sovereignty_authorizations
|
||
.entry(owner.clone())
|
||
.or_insert_with(HashMap::new)
|
||
.insert(spender.clone(), amount);
|
||
|
||
self.pending_events.push(ACC20Event::Approval {
|
||
owner: owner.clone(),
|
||
spender: spender.clone(),
|
||
amount,
|
||
timestamp,
|
||
});
|
||
Ok(())
|
||
}
|
||
|
||
// ========================
|
||
// 铸造/销毁方法
|
||
// ========================
|
||
|
||
/// 铸造代币
|
||
pub fn mint(
|
||
&mut self,
|
||
minter: &Address,
|
||
to: &Address,
|
||
amount: u128,
|
||
constitutional_receipt: Hash,
|
||
timestamp: Timestamp,
|
||
) -> Result<(), ACC20Error> {
|
||
if constitutional_receipt.is_zero() {
|
||
return Err(ACC20Error::InvalidConstitutionalReceipt);
|
||
}
|
||
if amount == 0 {
|
||
return Err(ACC20Error::ZeroAmount);
|
||
}
|
||
if !self.minters.contains(minter) {
|
||
return Err(ACC20Error::Unauthorized(minter.clone()));
|
||
}
|
||
if self.is_frozen(to) {
|
||
return Err(ACC20Error::AccountFrozen(to.clone()));
|
||
}
|
||
|
||
if let Some(cap) = self.supply_cap {
|
||
if self.total_supply.saturating_add(amount) > cap {
|
||
return Err(ACC20Error::SupplyCapExceeded {
|
||
cap,
|
||
current: self.total_supply,
|
||
requested: amount,
|
||
});
|
||
}
|
||
}
|
||
|
||
self.total_supply = self.total_supply.saturating_add(amount);
|
||
*self.holdings.entry(to.clone()).or_insert(0) += amount;
|
||
|
||
self.pending_events.push(ACC20Event::Mint {
|
||
to: to.clone(),
|
||
amount,
|
||
constitutional_receipt,
|
||
timestamp,
|
||
});
|
||
self.updated_at = Timestamp::now();
|
||
Ok(())
|
||
}
|
||
|
||
/// 销毁代币
|
||
pub fn burn(
|
||
&mut self,
|
||
from: &Address,
|
||
amount: u128,
|
||
constitutional_receipt: Hash,
|
||
timestamp: Timestamp,
|
||
) -> Result<(), ACC20Error> {
|
||
if constitutional_receipt.is_zero() {
|
||
return Err(ACC20Error::InvalidConstitutionalReceipt);
|
||
}
|
||
if amount == 0 {
|
||
return Err(ACC20Error::ZeroAmount);
|
||
}
|
||
|
||
let balance = self.balance_of(from);
|
||
if balance < amount {
|
||
return Err(ACC20Error::InsufficientBalance {
|
||
holder: from.clone(),
|
||
required: amount,
|
||
available: balance,
|
||
});
|
||
}
|
||
|
||
*self.holdings.get_mut(from).expect("FIX-006: unexpected None/Err") -= amount;
|
||
self.total_supply = self.total_supply.saturating_sub(amount);
|
||
|
||
self.pending_events.push(ACC20Event::Burn {
|
||
from: from.clone(),
|
||
amount,
|
||
constitutional_receipt,
|
||
timestamp,
|
||
});
|
||
self.updated_at = Timestamp::now();
|
||
Ok(())
|
||
}
|
||
|
||
// ========================
|
||
// 合规控制方法
|
||
// ========================
|
||
|
||
/// 冻结账户
|
||
pub fn freeze_account(
|
||
&mut self,
|
||
caller: &Address,
|
||
account: &Address,
|
||
reason: String,
|
||
constitutional_receipt: Hash,
|
||
timestamp: Timestamp,
|
||
) -> Result<(), ACC20Error> {
|
||
if constitutional_receipt.is_zero() {
|
||
return Err(ACC20Error::InvalidConstitutionalReceipt);
|
||
}
|
||
if caller != &self.owner {
|
||
return Err(ACC20Error::Unauthorized(caller.clone()));
|
||
}
|
||
|
||
self.frozen_accounts.insert(account.clone(), reason.clone());
|
||
self.pending_events.push(ACC20Event::AccountFrozen {
|
||
account: account.clone(),
|
||
reason,
|
||
constitutional_receipt,
|
||
timestamp,
|
||
});
|
||
Ok(())
|
||
}
|
||
|
||
/// 解冻账户
|
||
pub fn unfreeze_account(
|
||
&mut self,
|
||
caller: &Address,
|
||
account: &Address,
|
||
constitutional_receipt: Hash,
|
||
timestamp: Timestamp,
|
||
) -> Result<(), ACC20Error> {
|
||
if constitutional_receipt.is_zero() {
|
||
return Err(ACC20Error::InvalidConstitutionalReceipt);
|
||
}
|
||
if caller != &self.owner {
|
||
return Err(ACC20Error::Unauthorized(caller.clone()));
|
||
}
|
||
|
||
self.frozen_accounts.remove(account);
|
||
self.pending_events.push(ACC20Event::AccountUnfrozen {
|
||
account: account.clone(),
|
||
constitutional_receipt,
|
||
timestamp,
|
||
});
|
||
Ok(())
|
||
}
|
||
|
||
/// 暂停所有转账
|
||
pub fn halt_transfers(
|
||
&mut self,
|
||
caller: &Address,
|
||
reason: String,
|
||
constitutional_receipt: Hash,
|
||
timestamp: Timestamp,
|
||
) -> Result<(), ACC20Error> {
|
||
if constitutional_receipt.is_zero() {
|
||
return Err(ACC20Error::InvalidConstitutionalReceipt);
|
||
}
|
||
if caller != &self.owner {
|
||
return Err(ACC20Error::Unauthorized(caller.clone()));
|
||
}
|
||
|
||
self.transfer_halted = true;
|
||
self.halt_reason = Some(reason.clone());
|
||
self.pending_events.push(ACC20Event::TransferHalted {
|
||
reason,
|
||
constitutional_receipt,
|
||
timestamp,
|
||
});
|
||
Ok(())
|
||
}
|
||
|
||
/// 恢复所有转账
|
||
pub fn resume_transfers(
|
||
&mut self,
|
||
caller: &Address,
|
||
constitutional_receipt: Hash,
|
||
timestamp: Timestamp,
|
||
) -> Result<(), ACC20Error> {
|
||
if constitutional_receipt.is_zero() {
|
||
return Err(ACC20Error::InvalidConstitutionalReceipt);
|
||
}
|
||
if caller != &self.owner {
|
||
return Err(ACC20Error::Unauthorized(caller.clone()));
|
||
}
|
||
|
||
self.transfer_halted = false;
|
||
self.halt_reason = None;
|
||
self.pending_events.push(ACC20Event::TransferResumed {
|
||
constitutional_receipt,
|
||
timestamp,
|
||
});
|
||
Ok(())
|
||
}
|
||
|
||
/// 添加铸造者
|
||
pub fn add_minter(
|
||
&mut self,
|
||
caller: &Address,
|
||
minter: Address,
|
||
) -> Result<(), ACC20Error> {
|
||
if caller != &self.owner {
|
||
return Err(ACC20Error::Unauthorized(caller.clone()));
|
||
}
|
||
if !self.minters.contains(&minter) {
|
||
self.minters.push(minter);
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// 排空待广播事件
|
||
pub fn drain_pending_events(&mut self) -> Vec<ACC20Event> {
|
||
std::mem::take(&mut self.pending_events)
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
fn make_addr(b: u8) -> Address { Address::new([b; 32]) }
|
||
fn make_hash(b: u8) -> Hash { Hash::sha3_384(&[b; 48]) }
|
||
|
||
#[test]
|
||
fn test_acc20_creation() {
|
||
let owner = make_addr(1);
|
||
let receipt = make_hash(9);
|
||
let ts = Timestamp::now();
|
||
let token = ACC20Token::new(
|
||
"NAC Test Token".to_string(),
|
||
"NTT".to_string(),
|
||
18,
|
||
owner.clone(),
|
||
1_000_000 * 10u128.pow(18),
|
||
Some(10_000_000 * 10u128.pow(18)),
|
||
receipt,
|
||
ts,
|
||
).expect("FIX-006: unexpected None/Err");
|
||
assert_eq!(token.balance_of(&owner), 1_000_000 * 10u128.pow(18));
|
||
assert_eq!(token.total_supply, 1_000_000 * 10u128.pow(18));
|
||
}
|
||
|
||
#[test]
|
||
fn test_transfer() {
|
||
let owner = make_addr(1);
|
||
let receiver = make_addr(2);
|
||
let receipt = make_hash(9);
|
||
let ts = Timestamp::now();
|
||
let mut token = ACC20Token::new(
|
||
"Test".to_string(), "TST".to_string(), 18,
|
||
owner.clone(), 1000, None, receipt.clone(), ts.clone(),
|
||
).expect("FIX-006: unexpected None/Err");
|
||
|
||
token.transfer(&owner, &receiver, 300, receipt, ts).expect("FIX-006: unexpected None/Err");
|
||
assert_eq!(token.balance_of(&owner), 700);
|
||
assert_eq!(token.balance_of(&receiver), 300);
|
||
}
|
||
|
||
#[test]
|
||
fn test_freeze_prevents_transfer() {
|
||
let owner = make_addr(1);
|
||
let victim = make_addr(2);
|
||
let receipt = make_hash(9);
|
||
let ts = Timestamp::now();
|
||
let mut token = ACC20Token::new(
|
||
"Test".to_string(), "TST".to_string(), 18,
|
||
owner.clone(), 1000, None, receipt.clone(), ts.clone(),
|
||
).expect("FIX-006: unexpected None/Err");
|
||
token.transfer(&owner, &victim, 500, receipt.clone(), ts.clone()).expect("FIX-006: unexpected None/Err");
|
||
token.freeze_account(&owner, &victim, "AML".to_string(), receipt.clone(), ts.clone()).expect("FIX-006: unexpected None/Err");
|
||
let result = token.transfer(&victim, &owner, 100, receipt, ts);
|
||
assert!(matches!(result, Err(ACC20Error::AccountFrozen(_))));
|
||
}
|
||
|
||
#[test]
|
||
fn test_supply_cap() {
|
||
let owner = make_addr(1);
|
||
let receipt = make_hash(9);
|
||
let ts = Timestamp::now();
|
||
let mut token = ACC20Token::new(
|
||
"Test".to_string(), "TST".to_string(), 18,
|
||
owner.clone(), 900, Some(1000), receipt.clone(), ts.clone(),
|
||
).expect("FIX-006: unexpected None/Err");
|
||
// 铸造到上限
|
||
token.mint(&owner, &owner, 100, receipt.clone(), ts.clone()).expect("FIX-006: unexpected None/Err");
|
||
// 超出上限
|
||
let result = token.mint(&owner, &owner, 1, receipt, ts);
|
||
assert!(matches!(result, Err(ACC20Error::SupplyCapExceeded { .. })));
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// ACC-20 上层接口(供 nac-asset-onboarding 等模块使用)
|
||
// ============================================================
|
||
|
||
/// ACC-20 代币部署请求
|
||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||
pub struct TokenDeployRequest {
|
||
/// 代币名称
|
||
pub name: String,
|
||
/// 代币符号
|
||
pub symbol: String,
|
||
/// 总供应量
|
||
pub total_supply: u128,
|
||
/// 精度(小数位数)
|
||
pub decimals: u8,
|
||
/// GNACS 资产分类码
|
||
pub gnacs_code: String,
|
||
/// 部署者地址(32 字节,NAC 原生地址格式)
|
||
pub deployer: String,
|
||
}
|
||
|
||
/// ACC-20 代币元数据
|
||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||
pub struct TokenMetadata {
|
||
/// 合约地址(32 字节)
|
||
pub contract_address: String,
|
||
/// 代币名称
|
||
pub name: String,
|
||
/// 代币符号
|
||
pub symbol: String,
|
||
/// 总供应量
|
||
pub total_supply: u128,
|
||
/// 精度
|
||
pub decimals: u8,
|
||
/// 部署区块高度
|
||
pub deploy_block: u64,
|
||
/// 部署交易哈希(SHA3-384,48 字节)
|
||
pub deploy_tx_hash: String,
|
||
}
|
||
|
||
/// ACC-20 协议接口
|
||
pub struct ACC20Protocol;
|
||
|
||
impl ACC20Protocol {
|
||
/// 部署新的 ACC-20 代币合约
|
||
pub async fn deploy(&self, request: TokenDeployRequest) -> Result<TokenMetadata, String> {
|
||
// TODO: 通过 NVM 部署 Charter 合约
|
||
Ok(TokenMetadata {
|
||
contract_address: format!("nac1{}", &request.name.to_lowercase()),
|
||
name: request.name,
|
||
symbol: request.symbol,
|
||
total_supply: request.total_supply,
|
||
decimals: request.decimals,
|
||
deploy_block: 0,
|
||
deploy_tx_hash: String::new(),
|
||
})
|
||
}
|
||
}
|