从 Solidity 到 Leo
代币标准差异

介绍

Aleo 目前有两个活跃的代币标准,它们分别面向网络发展的不同时期:

:::caution 不建议新项目使用

代币注册表程序(ARC-21)仍然在主网上运行,并继续服务现有集成。但是,它不建议用于新项目。

较新的代币标准(ARC-20 和 ARC-22)目前正在社区中积极讨论,预计会取代它。新项目应该关注这些讨论,并在相关标准最终确定后考虑基于新标准构建。

:::

快速对比

功能ERC-20ARC-21 代币注册表ARC-20
代币创建部署新合约向注册表注册部署实现 ARC20 接口的新程序
状态管理每个合约独立管理中心化注册表每个程序独立管理
转账机制直接调用合约调用注册表程序通过 ARC20@(token_id)/function() 动态调用
隐私无内置私有转账内置私有转账
授权流程必需可选必需(公开)
代币元数据存在合约中存在注册表中存在程序中
供应量管理每个合约独立管理中心化注册表每个程序独立管理(可选 MintableToken 扩展)
动态分发不适用不需要(中心枢纽)核心设计要求

架构差异

程序结构

ERC-20

  • 每个代币都作为独立智能合约部署。
  • 代币转账需要授权流程。
  • 每创建一种新代币,都需要部署新的智能合约。

ARC-21 代币注册表

  • 单个程序(代币注册表)管理所有代币。
  • 基础转账不需要单独的授权流程。
  • 新代币通过注册创建,而不是通过部署新合约创建。

ARC-20

  • 每个代币都是一个独立部署的程序,并声明符合 ARC20 接口(Leo 中的 :InterfaceName)。
  • DeFi 程序可以通过动态分发在运行时与任意符合接口的代币交互,不需要编译时导入。
  • 可选的 MintableToken 接口扩展了 ARC20,适用于支持铸造和销毁的代币。

状态管理

ERC-20

  • 状态存储在每个代币合约内部。
  • 合约内部可以直接访问状态。
  • 所有状态只支持公开可见。

ARC-21 代币注册表

  • 状态由注册表程序管理。
  • 在 async 函数中通过 API 端点或 mappings::get() 调用访问状态。
  • 通过 mapping 支持公开状态可见性,并通过 record 支持私有状态。

ARC-20

  • 状态由每个代币自己的程序管理。
  • 公开余额存储在各程序自己的 mapping 中;私有余额保存在 Token record 中。
  • 符合接口的程序会被分别查询,而不是通过共享注册表查询。

功能差异

完整的 ARC-21 函数签名和参数详情,请参阅代币注册表程序参考。

代币创建

ERC-20 为每种代币部署一个新合约:

contract MyToken is ERC20 {
    constructor(string memory name, string memory symbol) ERC20(name, symbol) {
        _mint(msg.sender, 1000000 * 10 ** decimals());
    }
}

ARC-21 代币注册表通过共享注册表程序中的 register_token 注册新代币,需要提供唯一的 token_id、名称、符号、小数位和最大供应量。不需要部署合约。

可选的 external_authorization_required 标志允许指定的 external_authorization_party 使用有时间限制的授权来控制代币转账。这适用于归属期安排、支出限制或合规控制。可以使用 prehook_public / prehook_private 在满足条件后解锁转账。

ARC-20 部署一个声明接口符合性的新程序:

program my_token.aleo: MintableToken {
    record Token {
        owner: address,
        amount: u128,
    }

    fn transfer_public(public recipient: address, public amount: u128) -> Final { ... }
    fn transfer_private(input: Token, to: address, amount: u128) -> (Token, Token) { ... }
    // ... all ARC20 + MintableToken functions
}

之后,任意 DeFi 程序都可以在运行时按名称调用该代币,而不需要导入它:

ARC20@(token_id)/transfer_public(recipient, amount);

代币转账

ERC-20

function transfer(address to, uint256 amount) public returns (bool)
function transferFrom(address from, address to, uint256 amount) public returns (bool)

ARC-21 代币注册表提供多种转账变体:

函数说明
transfer_public从函数调用者发起公开转账
transfer_public_as_signer从交易签名者发起公开转账,适用于多程序调用
transfer_private使用 Token record 发起私有转账
transfer_from_public由已授权的支出者发起公开转账
transfer_public_to_private将公开余额转换为私有 Token record
transfer_from_public_to_private与上一个类似,但由已授权的支出者发起
transfer_private_to_public将私有 Token record 转换为公开余额

ARC-20 通过 ARC20 接口定义了一组类似函数,并使用动态分发在运行时调用:

函数说明
transfer_public公开转账
transfer_private使用 Token record 发起私有转账
transfer_private_to_public将私有 Token 转为公开余额,并向发送者发出 Metadata 收据
transfer_public_to_private将公开余额转为私有 Token
shield将签名者的公开余额转换为私有 Token
unshield将私有 Token 转回公开余额
transfer_from_public由已授权的支出者发起公开转账
transfer_from_public_to_private由已授权的支出者发起公开转私有

在 DeFi 程序中动态调用这些函数:

// Works with any ARC20-conformant token, resolved at runtime
ARC20@(token_id)/transfer_public(recipient, amount);
ARC20@(token_id)/transfer_from_public(owner, recipient, amount);

使用 dyn record(动态 record)进行私有代币操作:

fn deposit_private(
    private token_record: dyn record,
    public token_id: identifier,
    public amount: u128,
) -> (dyn record, Final) {
    let (change, f): (dyn record, Final) =
        ARC20@(token_id)/transfer_private_to_public(token_record, self.address, amount);
    return (change, final { f.run(); });
}

代币铸造

ERC-20

function mint(address to, uint256 amount) public onlyOwner

ARC-21 代币注册表支持 mint_public 和 mint_private。访问权限由角色控制:只有拥有 MINTER_ROLE、SUPPLY_MANAGER_ROLE 的地址,或管理员,才能铸造。角色通过 set_role / remove_role 管理。

ARC-20 通过可选的 MintableToken 扩展定义铸造能力:

fn mint_public(public recipient: address, public amount: u128) -> Final;
fn mint_private(public recipient: address, public amount: u128) -> (Token, Final);

访问控制由程序自身定义,接口本身不会强制执行。

代币销毁

ERC-20

function burn(uint256 amount) public

ARC-21 代币注册表支持 burn_public 和 burn_private,并采用与铸造相同的基于角色的访问控制(BURNER_ROLE 或 SUPPLY_MANAGER_ROLE)。

ARC-20 通过 MintableToken 支持:

fn burn_public(public amount: u128) -> Final;
fn burn_private(input: Token) -> Final;

代币授权

ERC-20

function approve(address spender, uint256 amount) public returns (bool)
function allowance(address owner, address spender) public view returns (uint256)

ARC-21 代币注册表使用 approve_public 授予支出者额度,并使用 unapprove_public 撤销或减少额度。

ARC-20 使用相同模式(approve_public / unapprove_public),但有一个关键区别:approve_public 会在现有额度上增加额度,而不是替换额度,从而避免 ERC-20 授权竞态问题。

余额和供应量查询

ERC-20

function balanceOf(address account) public view returns (uint256)
function totalSupply() public view returns (uint256)

ARC-21 代币注册表通过 RPC mapping 查询公开余额和供应量,不需要交易:

# Balance (composite key: token_id + account)
GET /{network}/program/token_registry.aleo/mapping/balances/{token_id}_{account}
 
# Total supply (within TokenMetadata)
GET /{network}/program/token_registry.aleo/mapping/registered_tokens/{token_id}

ARC-20 按各个程序分别查询:

# Balance in a specific ARC-20 token program
GET /{network}/program/{my_token}.aleo/mapping/account/{address}
 
# Supply stored in the program's own mapping
GET /{network}/program/{my_token}.aleo/mapping/supply/true

总结

ARC-21 代币注册表通过单个程序管理所有代币,提供隐私和基于角色的访问控制,同时不要求 DeFi 程序在编译时知道具体代币。它仍然是现有集成的推荐标准。

ARC-20 (opens in a new tab) 采用了由动态分发(ARC-0009) (opens in a new tab)支持的更去中心化的方法:每个代币都是自己的程序,任意 DeFi 程序都可以通过 ARC20@(token_id)/function() 在运行时与任意符合接口的代币交互。ARC-22 (opens in a new tab) 在此基础上增加了冻结列表执行和调查者可见的合规记录,适用于受监管发行方。二者目前都仍处于草案阶段,请关注相关讨论,并在标准最终确定后基于它们构建。