EIP

ERC777-1820

Posted by Thomas_Xu on 2022-11-02

ERC777,1820协议分析


author:Thomas_Xu

ERC777

与以太坊生态系统中广泛采用的ERC20标准不同,ERC777标准通常被称为城里的新生儿。一个潜力巨大的孩子。有些人甚至声称ERC777标准是ERC20标准的有效继承者。ERC777标准规范在EIP777(以太坊改进提案文件中有详尽描述。

ERC777解决了哪些ERC20的问题?

转账通知机制

由于ERC20 标准没有一个转账通知机制,很多ERC20代币误转到合约之后,再也没有办法把币转移出来,已经有大量的ERC20 因为这个原因被锁死,如锁死的QTUM锁死的EOS

运营商机制

假设您希望允许第三方帐户(或合约)使用您的代币。最常见的需求是在使用去中心化交易所智能合约交易代币时。

此过程中的第一步是你需要通过调用ERC20智能合约上的approve方法函数来批准转账账户并设置他们有权获得的资金限额。除了用你的资金信任第三方之外,你还需要确保支出限额始终足以满足所需的交易,还需考虑gas等因素对交易的影响。

在ERC777的运营商机制下,你只需要转移操作权限后就什么都不用操心了。

交易额外信息

另外一个问题是ERC20 转账时,无法携带额外的信息,例如:我们有一些客户希望让用户使用 ERC20 代币购买商品,因为转账没法携带额外的信息, 用户的代币转移过来,不知道用户具体要购买哪件商品,从而展加了线下额外的沟通成本。

ERC777很好的解决了这些问题,同时ERC777 也兼容 ERC20 标准。因此强烈建议新开发的代币使用ERC777标准。

ERC777 在 ERC20的基础上定义了 send(dest, value, data) 来转移代币, send函数额外的参数用来携带其他的信息,send函数会检查持有者和接收者是否实现了相应的钩子函数,如果有实现(不管是普通用户地址还是合约地址都可以实现钩子函数),则调用相应的钩子函数。

交易监听

由于ERC1820接口的实现,使ERC777具有很完备的交易监听机制,利用这种监听可以完成许多功能,例如慈善机构的捐款记录,支出情况监听,等等。

ERC20 标准下,可以通过一个变通的办法,采用两个交易组合完成,方法是:第1步:先让用户把要转移的金额用 ERC20 的approve 授权的存币合约(这步通常称为解锁),第2步:再次让用户调用存币合约的计息函数,目标函数中通过 transferFrom 把代币从用户手里转移的合约内,并开始监听到交易后的操作。

ERC1820 接口注册表合约

即便是一个普通用户地址,同样可以实现对 ERC777 转账的监听, 听起来有点神奇,其实这是通过 ERC1820 接口注册表合约来是实现的。

ERC1820 相当的重要,以至于ERC777单独把它拆出来作为一个EIP。

动机

在以太坊上有很多方法定义伪自省,ERC165不能由普通用户帐户使用。 ERC672 则使用了反向 ENS,反向 ENS 有两个问题:增加了不必要的复杂度,其次,ENS 是由多签控制的中心化合约。 从理论上讲,这种多签能够修改系统。

ERC1820标准比 ERC672 简单得多,并且完全去中心化。

此标准还为所有链提供一个唯一(相同的)地址0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24。从而解决了解决不同链的查找注册表地址的问题。

ERC1820规范

以下是ERC1820的源码及解释

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
pragma solidity 0.5.3;
// IV is value needed to have a vanity address starting with '0x1820'.
// IV: 53759

/// @dev 如果合约为其他的地址实现了接口, 则必须实现这个接口。
interface ERC1820ImplementerInterface {
/// @notice 指示合约是否为地址 “addr” 实现接口 “interfaceHash”。
/// @param interfaceHash 接口名称的 keccak256 哈希值
/// @param addr 为哪一个地址实现接口
/// @return 只有当合约为地址'addr'实现'interfaceHash'时返回 ERC1820_ACCEPT_MAGIC
function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32);
}


/// @title ERC1820 伪自省注册表合约
/// @notice 该合约是ERC1820注册表的官方实现。
contract ERC1820Registry {
/// @notice ERC165 无效 ID.
bytes4 constant internal INVALID_ID = 0xffffffff;
/// @notice ERC165 的 supportsInterface 接口ID (= `bytes4(keccak256('supportsInterface(bytes4)'))`).
bytes4 constant internal ERC165ID = 0x01ffc9a7;
/// @notice 如果合约代表某个其他地址实现接口,则返回Magic值。
bytes32 constant internal ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC"));

/// @notice 映射地址及接口到对应的实现合约地址
mapping(address => mapping(bytes32 => address)) internal interfaces;
/// @notice 映射地址到管理者
mapping(address => address) internal managers;
/// @notice 每个地址和erc165接口的flag,指示是否被缓存。
mapping(address => mapping(bytes4 => bool)) internal erc165Cached;

/// @notice 表示合约是'addr'的'interfaceHash'的'实现者'。
event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer);
/// @notice 表示'newManager'是'addr'的新管理者的地址。
event ManagerChanged(address indexed addr, address indexed newManager);

/// @notice 查询地址是否实现了接口以及通过哪个合约实现的。
/// @param _addr 查询地址(如果'_addr'是零地址,则假定为'msg.sender')。
/// @param _interfaceHash 查询接口,它是接口名称字符串的 keccak256 哈希值
/// 例如: 'web3.utils.keccak256("ERC777TokensRecipient")' 表示 'ERC777TokensRecipient' 接口.
/// @return 返回实现者的地址,没有实现返回 ‘0’
function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address) {
address addr = _addr == address(0) ? msg.sender : _addr;
if (isERC165Interface(_interfaceHash)) {
bytes4 erc165InterfaceHash = bytes4(_interfaceHash);
return implementsERC165Interface(addr, erc165InterfaceHash) ? addr : address(0);
}
return interfaces[addr][_interfaceHash];
}

/// @notice 设置某个地址的接口由哪个合约实现,需要由管理员来设置。(每个地址是他自己的管理员,直到设置了一个新的地址)。
/// @param _addr 待设置的关联接口的地址(如果'_addr'是零地址,则假定为'msg.sender')
/// @param _interfaceHash 接口,它是接口名称字符串的 keccak256 哈希值
/// 例如: 'web3.utils.keccak256("ERC777TokensRecipient")' 表示 'ERC777TokensRecipient' 接口。
/// @param _implementer 为地址'_addr'实现了 '_interfaceHash'接口的合约地址
function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external {
address addr = _addr == address(0) ? msg.sender : _addr;
require(getManager(addr) == msg.sender, "Not the manager");

require(!isERC165Interface(_interfaceHash), "Must not be an ERC165 hash");
if (_implementer != address(0) && _implementer != msg.sender) {
require(
ERC1820ImplementerInterface(_implementer)
.canImplementInterfaceForAddress(_interfaceHash, addr) == ERC1820_ACCEPT_MAGIC,
"Does not implement the interface"
);
}
interfaces[addr][_interfaceHash] = _implementer;
emit InterfaceImplementerSet(addr, _interfaceHash, _implementer);
}

/// @notice 为地址_addr 设置新的管理员地址_newManager, 新的管理员能给'_addr' 调用 'setInterfaceImplementer' 设置是实现者。
/// (传 '0x0' 为地址_addr 重置管理员)

function setManager(address _addr, address _newManager) external {
require(getManager(_addr) == msg.sender, "Not the manager");
managers[_addr] = _newManager == _addr ? address(0) : _newManager;
emit ManagerChanged(_addr, _newManager);
}

/// @notice 获取地址 _addr的管理员
function getManager(address _addr) public view returns(address) {
// By default the manager of an address is the same address
if (managers[_addr] == address(0)) {
return _addr;
} else {
return managers[_addr];
}
}

/// @notice 计算给定名称的接口的keccak256哈希值。
function interfaceHash(string calldata _interfaceName) external pure returns(bytes32) {
return keccak256(abi.encodePacked(_interfaceName));
}

/// ERC165 相关方法

/// @notice 更新合约是否实现了ERC165接口的缓存。
function updateERC165Cache(address _contract, bytes4 _interfaceId) external {
interfaces[_contract][_interfaceId] = implementsERC165InterfaceNoCache(
_contract, _interfaceId) ? _contract : address(0);
erc165Cached[_contract][_interfaceId] = true;
}

/// @notice 检查合约是否实现ERC165接口。
// 如果未缓存结果,则对合约地址进行查找。 如果结果未缓存或缓存已过期,则必须通过使用合约地址调用“updateERC165Cache”手动更新缓存。
/// @param _contract 要检查的合约地址。
/// @param _interfaceId 要检查ERC165接口。
/// @return True 如果合约实现了接口返回 true, 否则false.
function implementsERC165Interface(address _contract, bytes4 _interfaceId) public view returns (bool) {
if (!erc165Cached[_contract][_interfaceId]) {
return implementsERC165InterfaceNoCache(_contract, _interfaceId);
}
return interfaces[_contract][_interfaceId] == _contract;
}

/// @notice 在不使用或更新缓存的情况下检查合约是否实现ERC165接口。
/// @param _contract 要检查的合约地址。
/// @param _interfaceId 要检查ERC165接口。
/// @return True 如果合约实现了接口返回 true, 否则false.
function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) public view returns (bool) {
uint256 success;
uint256 result;

(success, result) = noThrowCall(_contract, ERC165ID);
if (success == 0 || result == 0) {
return false;
}

(success, result) = noThrowCall(_contract, INVALID_ID);
if (success == 0 || result != 0) {
return false;
}

(success, result) = noThrowCall(_contract, _interfaceId);
if (success == 1 && result == 1) {
return true;
}
return false;
}

/// @notice 检查_interfaceHash 是否是ERC165接口(以28个零结尾)。
/// @param _interfaceHash 要检查接口 hash。
/// @return 如果 '_interfaceHash'是ERC165接口返回 True, 否则返回false
function isERC165Interface(bytes32 _interfaceHash) internal pure returns (bool) {
return _interfaceHash & 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0;
}

/// @dev 调用合约接口,如果函数不存在也不抛出异常。
function noThrowCall(address _contract, bytes4 _interfaceId)
internal view returns (uint256 success, uint256 result)
{
bytes4 erc165ID = ERC165ID;

assembly {
let x := mload(0x40) // Find empty storage location using "free memory pointer"
mstore(x, erc165ID) // Place signature at beginning of empty storage
mstore(add(x, 0x04), _interfaceId) // Place first argument directly next to signature

success := staticcall(
30000, // 30k gas
_contract, // To addr
x, // Inputs are stored at location x
0x24, // Inputs are 36 (4 + 32) bytes long
x, // Store output over input (saves space)
0x20 // Outputs are 32 bytes long
)

result := mload(x) // Load the result
}
}
}

部署方法

ERC1820的部署方式相当巧妙,使用了无密钥部署方法(Nick的方法),该方法依赖于一次性地址。

有兴趣的朋友可以去看EIP1820官方文档

主要接口

ERC1820合约提过了两个主要接口:

  • setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer)
    用来设置地址(_addr)的接口(_interfaceHash 接口名称的 keccak256 )由哪个合约实现(_implementer)。
  • getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address)
    这个函数用来查询地址(_addr)的接口由哪个合约实现。

setInterfaceImplementer函数会参数信息记录到下面这个interfaces映射里:

1
2
// 记录 地址(第一个键) 的接口(第二个键)的实现地址(第二个值)
mapping(address => mapping(bytes32 => address)) interfaces;

相对应的 getInterfaceImplementer() 通过 interfaces 这个mapping 来获得接口的实现。

ERC777 使用 send转账时会分别在持有者和接收者地址上使用ERC1820 的getInterfaceImplementer函数进行查询,查看是否有对应的实现合约,ERC777 标准规范里预定了接口及函数名称,如果有实现则进行相应的调用。

ERC777 标准规范

接口

ERC777 为了在实现上可以兼容ERC20,除了查询函数和ERC20一致外,操作接口均采用的独立的命名(避免相同的命令无法分辨是哪个标准),ERC777的接口定义如下,要求所有的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
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);
}

接口说明与实现约定

ERC777 合约必须要通过 ERC1820 注册 ERC777Token 接口,这样任何人都可以查询合约是否是ERC777标准的合约,注册方法是: 调用ERC1820 注册合约的 setInterfaceImplementer 方法,参数 _addr 及 _implementer 均是合约的地址,_interfaceHash 是 ERC777Token 的 keccak256 哈希值(0xac7fbab5…177054)

如果 ERC777 要实现ERC20标准,还必须通过ERC1820 注册ERC20Token接口。

ERC777 信息说明函数

name(),symbol(),totalSupply(),balanceOf(address) 和含义和在ERC20 中完全一样。

唯一不一样的是granularity(),用来定义代币最小的划分粒度(>=1), 要求必须在创建时设定,之后不可以更改,不管是在铸币、发送还是销毁操作的代币数量,必需是粒度的整数倍。

granularity 和 ERC20 的 decimals 不一样,decimals用来定义小数位数,decimals 是ERC20 可选函数,为了兼容 ERC20 代币, decimals 函数要求必须返回18。而 granularity 表示的是基于最小位数(内部存储)的划分粒度。例如:0.5个代币存储为 500,000,000,000,000,000 (0.5 X 10^18),如果粒度为2,则最小转账单位是2(相对于500,000,000,000,000,000)。

ERC777运营商

ERC777 定义了一个新的运营商角色,运营商被作为移动代币的地址。 每个地址直观地移动自己的代币,将持有人和运营商的概念分开可以提供更大的灵活性。

此外,ERC777还可以定义默认操作员(默认操作员列表只能在代币创建时定义的,并且不能更改),默认操作员是被所有持有人授权的操作员,这可以为项目方管理代币带来方便,当然认何持有人仍然有权撤销默认操作员。

相关函数:

  • defaultOperators(): 获取代币合约默认的操作员列表.
  • authorizeOperator(address operator): 设置一个地址作为msg.sender 的操作员,需要触发AuthorizedOperator事件。
  • revokeOperator(address operator): 移除 msg.sender 上 operator 操作员的权限, 需要触发RevokedOperator事件。
  • isOperatorFor(address operator, address holder): 是否是某个持有者的操作员。

利用运营商功能的另一个小的(并且不太明显的)好处是用户不再需要担心交易费Gas价格。现在可以将其委托给运营商。

发送代币

ERC777主要通过两个函数发送代币

1
2
3
4
5
6
7
8
9
send(address to, uint256 amount, bytes calldata data) external

function operatorSend(
address from,
address to,
uint256 amount,
bytes calldata data,
bytes calldata operatorData
) external

operatorSend 可以通过参数operatorData携带操作者的信息,发送代币除了执行对应账户的余额加减和触发事件之外,还有额外的规定

  1. 如果持有者有通过 ERC1820 注册 ERC777TokensSender 实现接口, 代币合约必须调用其 tokensToSend 钩子函数。
  2. 如果接收者有通过 ERC1820 注册 ERC777TokensRecipient 实现接口, 代币合约必须调用其 tokensReceived 钩子函数。
  3. 如果有 tokensToSend 钩子函数,必须在修改余额状态之前调用。
  4. 如果有 tokensReceived 钩子函数,必须在修改余额状态之后调用。
  5. 调用钩子函数及触发事件时, dataoperatorData必须原样传递,因为 tokensToSend 和 tokensReceived 函数可能根据这个数据取消转账(触发 revert)。

让我们来看看_send函数就能理解ERC777设计的巧妙之处了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function _send(
address from,
address to,
uint256 amount,
bytes memory userData,
bytes memory operatorData,
bool requireReceptionAck
) internal virtual {
require(from != address(0), "ERC777: transfer from the zero address");
require(to != address(0), "ERC777: transfer to the zero address");

address operator = _msgSender();

_callTokensToSend(operator, from, to, amount, userData, operatorData);

_move(operator, from, to, amount, userData, operatorData);

_callTokensReceived(operator, from, to, amount, userData, operatorData, requireReceptionAck);
}

这里将交易分成了三个部分

_callTokensToSend

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function _callTokensToSend(
address operator,
address from,
address to,
uint256 amount,
bytes memory userData,
bytes memory operatorData
) private {
address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(from, _TOKENS_SENDER_INTERFACE_HASH);
// 执行钩子函数
if (implementer != address(0)) {
IERC777Sender(implementer).tokensToSend(operator, from, to, amount, userData, operatorData);
}
}

​ 此函数会检查from合约是否在ERC1820注册表上注册了tokensToSend接口,如果注册了,就会触发钩子函数。

ERC777TokensSender 接口定义如下:

1
2
3
4
5
6
7
8
9
10
interface ERC777TokensSender {
function tokensToSend(
address operator,
address from,
address to,
uint256 amount,
bytes calldata userData,
bytes calldata operatorData
) external;
}

如上,如果持有者希望在转账时收到代币转移通知,就需要在ERC1820合约上注册及实现 ERC777TokensSender 接口。

有一个地方需要注意: 对于所有的 ERC777 合约, 一个持有者地址只能注册一个ERC777TokensSender接口实现。因此 ERC777TokensSender 实现会被多个ERC777合约调用,在ERC777TokensSender接口的实现合约里, msg.sender 是ERC777合约地址,而不是操作者。

_move

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function _move(
address operator,
address from,
address to,
uint256 amount,
bytes memory userData,
bytes memory operatorData
) private {
_beforeTokenTransfer(operator, from, to, amount);

uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC777: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
}
_balances[to] += amount;

emit Sent(operator, from, to, amount, userData, operatorData);
emit Transfer(from, to, amount);
}

​ 执行代币发送,实现和ERC20并无区别。在这里不过多介绍

_callTokensReceived

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function _callTokensReceived(
address operator,
address from,
address to,
uint256 amount,
bytes memory userData,
bytes memory operatorData,
bool requireReceptionAck
) private {
address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(to, _TOKENS_RECIPIENT_INTERFACE_HASH);
if (implementer != address(0)) {
IERC777Recipient(implementer).tokensReceived(operator, from, to, amount, userData, operatorData);
} else if (requireReceptionAck) {
require(!to.isContract(), "ERC777: token recipient contract has no implementer for ERC777TokensRecipient");
}
}

此函数会检查to合约是否在ERC1820注册表上注册tokensReceived接口,若注册,则触发钩子函数。

ERC777TokensRecipient 接口定义如下:

1
2
3
4
5
6
7
8
9
10
interface ERC777TokensRecipient {
function tokensReceived(
address operator,
address from,
address to,
uint256 amount,
bytes calldata data,
bytes calldata operatorData
) external;
}

如果接收者希望在转账时收到代币转移通知,就需要在ERC1820合约上注册及实现 ERC777TokensRecipient 接口。

普通账户地址监听代币转出

在这里贴出一个Tiny熊写的监听示例,我觉得非常适合小白去理解监听机制。

监听代币的转出可以让持有者对发出去的代币有更多的控制,例如持有者可以设置一些黑名单,禁止操作员对黑名单内账号转账,

根据 ERC1820 标准,只有账号的管理者才可以为账号注册接口实现合约,
如果一个合约要为某个地址(或自身)实现某个接口, 则需要实现下面这个接口:

1
2
3
4
5
6
7
interface ERC1820ImplementerInterface {
/// @notice 指示合约是否为地址 “addr” 实现接口 “interfaceHash”。
/// @param interfaceHash 接口名称的 keccak256 哈希值
/// @param addr 为哪一个地址实现接口
/// @return 只有当合约为地址'addr'实现'interfaceHash'时返回 ERC1820_ACCEPT_MAGIC
function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32);
}

通过在 canImplementInterfaceForAddress 返回 ERC1820_ACCEPT_MAGIC 以声明实现了 interfaceHash 对应的接口。在调用ERC1820的 setInterfaceImplementer 函数设置接口实现时,会通过 canImplementInterfaceForAddress 检查合约时候实现了接口。

因此实现监听转出会和功德箱合约有些不一样:

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
pragma solidity ^0.5.0;

import "@openzeppelin/contracts/token/ERC777/IERC777Sender.sol";
import "@openzeppelin/contracts/token/ERC777/IERC777.sol";
import "@openzeppelin/contracts/introspection/IERC1820Registry.sol";
import "@openzeppelin/contracts/introspection/IERC1820Implementer.sol";


contract SenderControl is IERC777Sender, IERC1820Implementer {

IERC1820Registry private _erc1820 = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
bytes32 constant private ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC"));

// keccak256("ERC777TokensSender")
bytes32 constant private TOKENS_SENDER_INTERFACE_HASH =
0x29ddb589b1fb5fc7cf394961c1adf5f8c6454761adf795e67fe149f658abe895;

mapping(address => bool) blacklist;
address _owner;

constructor() public {
_owner = msg.sender;
}

// account call erc1820.setInterfaceImplementer
function canImplementInterfaceForAddress(bytes32 interfaceHash, address account) external view returns (bytes32) {
if (interfaceHash == TOKENS_SENDER_INTERFACE_HASH) {
return ERC1820_ACCEPT_MAGIC;
} else {
return bytes32(0x00);
}
}

function setBlack(address account, bool b) external {
require(msg.sender == _owner, "no premission");
blacklist[account] = b;
}

function tokensToSend(
address operator,
address from,
address to,
uint amount,
bytes calldata userData,
bytes calldata operatorData
) external {
if (blacklist[to]) {
revert("ohh... on blacklist");
}
}

}

合约实现 canImplementInterfaceForAddress 函数,以声明对 ERC777TokensSender 接口的实现,返回 ERC1820_ACCEPT_MAGIC。

函数setBlack用来设置黑名单,它使用一个blacklist映射来管理黑名单, 在 tokensToSend函数的实现里,先检查接收者是否在黑名单内,如果在则revert 回退交易阻止转账。

给发送者账号(假设为A)设置代理合约的方法为:先部署代理合约,获得代理合约地址, 然后用A账号去调用 ERC1820 的 setInterfaceImplementer函数,参数分别是 A的地址、接口的 keccak256 即0x29ddb589b1fb5fc7cf394961c1adf5f8c6454761adf795e67fe149f658abe895 以及 代理合约地址。


notice

true

This is copyright.

...

...

00:00
00:00