简介 ERC777 保持与 ERC20 兼容。
操作员通常利用合约发送token,并具备发送 / 接收钩子,以使得持有者对其token拥有更多的控制权。
它利用 ERC1820 来了解在合同和用户地址收到token时是否以及在何处通知它们。
解决的问题(相对于ERC20的优点)
同样使用 send(dest, value, data) 发送Ether
使用 tokensToSend 钩子,合约和用户地址都可以控制和拒绝发送出去的token
使用 tokensReceived 钩子,合约和用户地址都可以控制和拒绝接收到的token
tokensReceived 钩子允许发送token到合约地址,并在交易中心发出通知。而ERC20需要approve/transferFrom 两次调用才能达到这样的效果
token持有人可以指定/去除操作员(合约)地址,这些操作员通常是已经验证过的合约contract,例如交易所,支票处理器或自动收费系统
每一笔交易含有 data 和 operatorData ,它们是bytes字段,在token持有人和操作员之间传递信息
tokensReceived 钩子函数可以通过代理合约来部署,所以ERC777是与ERC20钱包兼容的
构建ERC777合约 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 pragma solidity ^0.5 .0 ; import "@openzeppelin/contracts/token/ERC777/ERC777.sol" ;contract GLDToken is ERC777 { constructor ( uint256 initialSupply, address[] memory defaultOperators ) ERC777 ("Gold" , "GLD" , defaultOperators) public { _mint (msg.sender , msg.sender , initialSupply, "" , "" ); } }
ERC777扩展了ERC20,对ERC20提供兼容性支持。使用 _mint 将 initialSupply 分配给部署者帐户。与ERC20的_mint不同,此参数包含一些额外的参数,但是可以放心地忽略它们。
initialSupply 没有设定小数。ERC777规定小数始终返回固定值18,因此无需自行设置。
最后,我们需要设置defaultOperators:操作员帐户(通常是智能合约地址),该帐户将能够代表其持有者转让token。如果不打算在token中使用操作员(合约),则只需传递一个空数组即可。
基本的ERC777合约就是这样! 现在,对其进行部署,并使用相同的balanceOf方法来查询部署者的余额:
1 2 > GLDToken.balanceOf(deployerAddress) 1000
要将token从一个帐户转移到另一个帐户,既可以使用ERC20的“transfer”方法,也可以使用新的ERC777的“send”方法,其作用非常相似,但是增加了一个可选的data字段:
1 2 3 4 5 6 > GLDToken.transfer(otherAddress, 300) > GLDToken.send(otherAddress, 300, "" ) > GLDToken.balanceOf(otherAddress) 600 > GLDToken.balanceOf(deployerAddress) 400
ERC777接口定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 interface ERC777Token { function name ( ) external view returns (string memory); function symbol ( ) external view returns (string memory); function totalSupply ( ) external view returns (uint256); function balanceOf (address holder ) external view returns (uint256); function granularity ( ) external view returns (uint256); function defaultOperators ( ) external view returns (address[] memory); function isOperatorFor ( address operator, address holder ) external view returns (bool); function authorizeOperator (address operator ) external; function revokeOperator (address operator ) external; function send (address to, uint256 amount, bytes calldata data ) external; function operatorSend ( address from , address to, uint256 amount, bytes calldata data, bytes calldata operatorData ) external; function burn (uint256 amount, bytes calldata data ) external; function operatorBurn ( address from , uint256 amount, bytes calldata data, bytes calldata operatorData ) external; event Sent ( address indexed operator, address indexed from , address indexed to, uint256 amount, bytes data, bytes operatorData ); event Minted ( address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData ); event Burned ( address indexed operator, address indexed from , uint256 amount, bytes data, bytes operatorData ); event AuthorizedOperator ( address indexed operator, address indexed holder ); event RevokedOperator (address indexed operator, address indexed holder); }
钩子函数如何工作
首先来了解一下 ERC1820, ERC1820是一个全局的合约,在以太坊链上有一个唯一的合约地址,它总是 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24。
ERC1820 合约提过了两个主要接口:
setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) 用来设置地址(_addr)的接口(_interfaceHash 接口名称的 keccak256 )由哪个合约实现(_implementer)。
getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address) 这个函数用来查询地址(_addr)的接口由哪个合约实现。
ERC777 使用 send 转账时,会分别在持有者和接收者地址上使用 ERC1820 的 getInterfaceImplementer 函数进行查询,查看是否有对应的实现合约,ERC777 标准规范里预定了接口及函数名称,如果有实现则进行相应的调用。
ERC777 使用 operatorSend 转账时,可以通过参数operatorData携带操作者的信息,发送代币除了执行对应账户的余额加减和触发事件之外,还有额外的规定 :
如果持有者有通过 ERC1820 注册 ERC777TokensSender 实现接口,代币合约必须调用其 tokensToSend 钩子函数。
如果接收者有通过 ERC1820 注册 ERC777TokensRecipient 实现接口, 代币合约必须调用其 tokensReceived 钩子函数。
如果有 tokensToSend 钩子函数,必须在修改余额状态之前调用。
如果有 tokensReceived 钩子函数,必须在修改余额状态之后调用。
调用钩子函数及触发事件时,data 和 operatorData必须原样传递,因为 tokensToSend 和 tokensReceived 函数可能根据这个数据取消转账(触发 revert)。
从而保证了ERC777交易以及钩子函数的原子性。
参考链接 ==>
https://eips.ethereum.org/EIPS/eip-777
https://github.com/0xjac/ERC777
https://docs.openzeppelin.com/contracts/2.x/erc777
https://github.com/OpenZeppelin/openzeppelin-contracts