ERC-777: Token Standard
与 ERC-20 的异同点
相同点
-
基本功能:
- 都支持代币的转账和余额查询
- 都实现了代币的基本属性(名称、符号、精度等)
- 都支持代币的授权机制
-
兼容性:
- ERC-777 完全兼容 ERC-20
- 可以通过 ERC-20 接口访问 ERC-777 代币
- 支持相同的代币查询方法
不同点
-
授权机制:
- ERC-20:使用 approve/transferFrom 模式,需要两步操作
- ERC-777:使用操作者(Operator)机制,更灵活且支持批量操作
-
转账通知:
- ERC-20:无内置通知机制,需要额外的事件监听
- ERC-777:提供内置的钩子函数(tokensReceived/tokensToSend)
-
数据支持:
- ERC-20:不支持在转账时附加额外数据
- ERC-777:支持在转账时附加任意数据(data 和 operatorData)
-
燃烧机制:
- ERC-20:无标准燃烧接口,需要自行实现
- ERC-777:提供标准燃烧接口,支持操作者燃烧
-
安全性:
- ERC-20:存在 approve/transferFrom 的安全隐患
- ERC-777:提供更安全的操作者机制和钩子函数
-
功能扩展:
- ERC-20:功能相对简单,扩展性有限
- ERC-777:支持更复杂的业务逻辑,扩展性更强
ERC-777 是一个以太坊代币标准,它在 ERC-20 的基础上进行了改进,提供了更高级的功能和更好的安全性。该标准由 Jacques Dafflon、Jordi Baylina 和 Thomas Shababi 于 2017 年 11 月提出。
标准概述
ERC-777 的主要目标是:
- 提供更直观的代币操作方式
- 改进 ERC-20 的批准机制
- 增加代币发送和接收的钩子函数
- 保持与 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;
}
主要特性
-
钩子函数:
tokensReceived
:接收代币时的回调tokensToSend
:发送代币时的回调- 允许合约在代币转移时执行自定义逻辑
-
操作者机制:
- 替代 ERC-20 的 approve/transferFrom 模式
- 更灵活和安全的授权系统
- 支持批量操作
-
数据字段:
- 支持在转账时附加额外数据
- 便于实现更复杂的业务逻辑
-
向后兼容性:
- 完全兼容 ERC-20
- 可以通过 ERC-20 接口访问
安全考虑
-
重入攻击防护:
- 钩子函数可能被用于重入攻击
- 实现时需要使用重入锁或检查-效果-交互模式
-
操作者权限:
- 需要仔细管理操作者权限
- 建议实现操作者白名单机制
-
数据验证:
- 需要验证传入的数据
- 防止恶意数据导致合约异常
应用场景
-
代币化资产:
- 股票、债券等金融工具
- 房地产等实物资产
-
支付系统:
- 自动支付处理
- 订阅服务
-
投票系统:
- 代币持有者投票
- 治理机制
-
奖励系统:
- 忠诚度计划
- 空投分发
最佳实践
-
实现检查:
- 实现所有必需的功能
- 确保符合 ERC-777 标准
-
事件记录:
- 记录所有重要操作
- 包括发送、接收、授权等
-
错误处理:
- 提供清晰的错误信息
- 使用 revert 而不是 require
-
测试覆盖:
- 编写完整的测试用例
- 包括钩子函数和操作者功能
与 ERC-20 的区别
-
授权机制:
- ERC-20:使用 approve/transferFrom
- ERC-777:使用操作者机制
-
转账通知:
- ERC-20:无通知机制
- ERC-777:提供钩子函数
-
数据支持:
- ERC-20:不支持额外数据
- ERC-777:支持附加数据
-
燃烧机制:
- 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"
}
]