ERC-777: Token Standard

与 ERC-20 的异同点

相同点

  1. 基本功能

    • 都支持代币的转账和余额查询
    • 都实现了代币的基本属性(名称、符号、精度等)
    • 都支持代币的授权机制
  2. 兼容性

    • ERC-777 完全兼容 ERC-20
    • 可以通过 ERC-20 接口访问 ERC-777 代币
    • 支持相同的代币查询方法

不同点

  1. 授权机制

    • ERC-20:使用 approve/transferFrom 模式,需要两步操作
    • ERC-777:使用操作者(Operator)机制,更灵活且支持批量操作
  2. 转账通知

    • ERC-20:无内置通知机制,需要额外的事件监听
    • ERC-777:提供内置的钩子函数(tokensReceived/tokensToSend)
  3. 数据支持

    • ERC-20:不支持在转账时附加额外数据
    • ERC-777:支持在转账时附加任意数据(data 和 operatorData)
  4. 燃烧机制

    • ERC-20:无标准燃烧接口,需要自行实现
    • ERC-777:提供标准燃烧接口,支持操作者燃烧
  5. 安全性

    • ERC-20:存在 approve/transferFrom 的安全隐患
    • ERC-777:提供更安全的操作者机制和钩子函数
  6. 功能扩展

    • ERC-20:功能相对简单,扩展性有限
    • ERC-777:支持更复杂的业务逻辑,扩展性更强

ERC-777 是一个以太坊代币标准,它在 ERC-20 的基础上进行了改进,提供了更高级的功能和更好的安全性。该标准由 Jacques Dafflon、Jordi Baylina 和 Thomas Shababi 于 2017 年 11 月提出。

标准概述

ERC-777 的主要目标是:

  1. 提供更直观的代币操作方式
  2. 改进 ERC-20 的批准机制
  3. 增加代币发送和接收的钩子函数
  4. 保持与 ERC-20 的向后兼容性

核心接口

interface IERC777 {
    // 返回代币名称
    function name() external view returns (string memory);

    // 返回代币符号
    function symbol() external view returns (string memory);

    // 返回代币精度
    function granularity() external view returns (uint256);

    // 返回总供应量
    function totalSupply() external view returns (uint256);

    // 返回账户余额
    function balanceOf(address holder) external view returns (uint256);

    // 发送代币
    function send(address recipient, uint256 amount, bytes calldata data) external;

    // 燃烧代币
    function burn(uint256 amount, bytes calldata data) external;

    // 检查是否是操作者
    function isOperatorFor(address operator, address tokenHolder) external view returns (bool);

    // 授权操作者
    function authorizeOperator(address operator) external;

    // 撤销操作者
    function revokeOperator(address operator) external;

    // 默认操作者发送代币
    function operatorSend(
        address sender,
        address recipient,
        uint256 amount,
        bytes calldata data,
        bytes calldata operatorData
    ) external;

    // 默认操作者燃烧代币
    function operatorBurn(
        address account,
        uint256 amount,
        bytes calldata data,
        bytes calldata operatorData
    ) external;
}

interface IERC777Recipient {
    // 代币接收钩子
    function tokensReceived(
        address operator,
        address from,
        address to,
        uint256 amount,
        bytes calldata data,
        bytes calldata operatorData
    ) external;
}

interface IERC777Sender {
    // 代币发送钩子
    function tokensToSend(
        address operator,
        address from,
        address to,
        uint256 amount,
        bytes calldata data,
        bytes calldata operatorData
    ) external;
}

主要特性

  1. 钩子函数

    • tokensReceived:接收代币时的回调
    • tokensToSend:发送代币时的回调
    • 允许合约在代币转移时执行自定义逻辑
  2. 操作者机制

    • 替代 ERC-20 的 approve/transferFrom 模式
    • 更灵活和安全的授权系统
    • 支持批量操作
  3. 数据字段

    • 支持在转账时附加额外数据
    • 便于实现更复杂的业务逻辑
  4. 向后兼容性

    • 完全兼容 ERC-20
    • 可以通过 ERC-20 接口访问

安全考虑

  1. 重入攻击防护

    • 钩子函数可能被用于重入攻击
    • 实现时需要使用重入锁或检查-效果-交互模式
  2. 操作者权限

    • 需要仔细管理操作者权限
    • 建议实现操作者白名单机制
  3. 数据验证

    • 需要验证传入的数据
    • 防止恶意数据导致合约异常

应用场景

  1. 代币化资产

    • 股票、债券等金融工具
    • 房地产等实物资产
  2. 支付系统

    • 自动支付处理
    • 订阅服务
  3. 投票系统

    • 代币持有者投票
    • 治理机制
  4. 奖励系统

    • 忠诚度计划
    • 空投分发

最佳实践

  1. 实现检查

    • 实现所有必需的功能
    • 确保符合 ERC-777 标准
  2. 事件记录

    • 记录所有重要操作
    • 包括发送、接收、授权等
  3. 错误处理

    • 提供清晰的错误信息
    • 使用 revert 而不是 require
  4. 测试覆盖

    • 编写完整的测试用例
    • 包括钩子函数和操作者功能

与 ERC-20 的区别

  1. 授权机制

    • ERC-20:使用 approve/transferFrom
    • ERC-777:使用操作者机制
  2. 转账通知

    • ERC-20:无通知机制
    • ERC-777:提供钩子函数
  3. 数据支持

    • ERC-20:不支持额外数据
    • ERC-777:支持附加数据
  4. 燃烧机制

    • ERC-20:无标准燃烧接口
    • ERC-777:提供标准燃烧接口

ERC-777 是一个功能更强大的代币标准,它在保持与 ERC-20 兼容的同时,提供了更灵活和安全的代币操作方式。通过钩子函数和操作者机制,它能够支持更复杂的业务场景,同时提供了更好的安全性保障。在实现时,需要注意安全性、兼容性和用户体验等方面。

ABI 定义

[
  {
    "constant": true,
    "inputs": [],
    "name": "name",
    "outputs": [{ "name": "", "type": "string" }],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "symbol",
    "outputs": [{ "name": "", "type": "string" }],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "granularity",
    "outputs": [{ "name": "", "type": "uint256" }],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "totalSupply",
    "outputs": [{ "name": "", "type": "uint256" }],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [{ "name": "holder", "type": "address" }],
    "name": "balanceOf",
    "outputs": [{ "name": "", "type": "uint256" }],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      { "name": "recipient", "type": "address" },
      { "name": "amount", "type": "uint256" },
      { "name": "data", "type": "bytes" }
    ],
    "name": "send",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      { "name": "amount", "type": "uint256" },
      { "name": "data", "type": "bytes" }
    ],
    "name": "burn",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      { "name": "operator", "type": "address" },
      { "name": "tokenHolder", "type": "address" }
    ],
    "name": "isOperatorFor",
    "outputs": [{ "name": "", "type": "bool" }],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [{ "name": "operator", "type": "address" }],
    "name": "authorizeOperator",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [{ "name": "operator", "type": "address" }],
    "name": "revokeOperator",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      { "name": "sender", "type": "address" },
      { "name": "recipient", "type": "address" },
      { "name": "amount", "type": "uint256" },
      { "name": "data", "type": "bytes" },
      { "name": "operatorData", "type": "bytes" }
    ],
    "name": "operatorSend",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      { "name": "account", "type": "address" },
      { "name": "amount", "type": "uint256" },
      { "name": "data", "type": "bytes" },
      { "name": "operatorData", "type": "bytes" }
    ],
    "name": "operatorBurn",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "anonymous": false,
    "inputs": [
      { "indexed": true, "name": "operator", "type": "address" },
      { "indexed": true, "name": "to", "type": "address" },
      { "indexed": true, "name": "from", "type": "address" },
      { "indexed": false, "name": "amount", "type": "uint256" },
      { "indexed": false, "name": "data", "type": "bytes" },
      { "indexed": false, "name": "operatorData", "type": "bytes" }
    ],
    "name": "Sent",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      { "indexed": true, "name": "operator", "type": "address" },
      { "indexed": true, "name": "from", "type": "address" },
      { "indexed": false, "name": "amount", "type": "uint256" },
      { "indexed": false, "name": "data", "type": "bytes" },
      { "indexed": false, "name": "operatorData", "type": "bytes" }
    ],
    "name": "Burned",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      { "indexed": true, "name": "operator", "type": "address" },
      { "indexed": true, "name": "tokenHolder", "type": "address" }
    ],
    "name": "AuthorizedOperator",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      { "indexed": true, "name": "operator", "type": "address" },
      { "indexed": true, "name": "tokenHolder", "type": "address" }
    ],
    "name": "RevokedOperator",
    "type": "event"
  }
]