165主要是为了 检查某个合约地址是否实现了某些接口
可以为任何地址设置接口实现的的合约,必须调用ERC1820注册表的以下函数:
function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external
为接口设置实现地址
可以为任何地址设置接口实现的的合约,必须调用ERC1820注册表的以下函数:
function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external
设置实现某个地址上某个接口的合约地址。需要由管理员来设置。每个地址是他自己的管理员,查看下面管理员部分了解详情。
注意: 如果 _addr
和 _implementer
是两个不同的地址,则: - _implementer
必须实现 ERC1820ImplementerInterface
接口(下面详细介绍)。 - 在_implementer
上用函数 _addr
_interfaceHash
调用 canImplementInterfaceForAddress
时,必须返回 ERC1820_ACCEPT_MAGIC
。
注意: _interfaceHash
不能是 ERC165 接口,即不能以28个字节0结尾。
注意: _addr
可以为 0
, 则假定为 msg.sender
。
此默认值通过multisig简化了交互,其中要签名的交易数据是常量,而不用管multisig实例的地址。
- 接口id:
29965a1d
- 参数:
- _addr: 要关联的地址(如果为
0
, 则假定为msg.sender
) - _interfaceHash 实现接口的Keccak256 hash, 如
web3.utils.keccak256('ERC777TokensRecipient')
用于ERC777TokensRecipient接口 - _implementer : 实现合约地址
- _addr: 要关联的地址(如果为
获取地址接口的实现合约
任何人都可以使用 getInterfaceImplementer
函数查询ERC1820注册表以获取代表某个地址实现接口的合约的地址。
function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address)
查询地址是否实现了接口以及通过哪个合约实现。
注意: 如果_interfaceHash
的最后28个字节是零(0
),那么前4个字节被认为是ERC165接口,注册表合约应该将调用转发到 _addr
合约,检查它是否实现了 ERC165接口(_interfaceHash
的前4个字节)。
注册表合约还应缓存ERC165查询以减少gas消耗。 任何人都可以调用 erc165UpdateCache
函数来更新合约是否实现了一个接口。
注意: _addr
可以为 0
, 则假定为 msg.sender
。此缺省值与setInterfaceImplementer
函数的行为一致,并通过multisigs简化了交互,其中要签名的交易数据是常量,而不管multisig实例的地址如何。
- 接口id:
aabbb8ca
- 参数:
- _addr: 要查询的地址(如果为
0
, 则假定为msg.sender
) - _interfaceHash 接口名的Keccak256 hash, 如
web3.utils.keccak256('ERC777Token')
- _addr: 要查询的地址(如果为
- 返回: 实现合约地址, 如果没有返回 0 地址。
接口实现 (ERC1820ImplementerInterface
)
接口实现 (ERC1820ImplementerInterface
)
interface ERC1820ImplementerInterface {
/// @notice 指示合约是否为地址 “addr” 实现接口 “interfaceHash”。
/// @param interfaceHash interfaceHash 接口名称的 keccak256 哈希值
/// @param addr 为哪一个地址实现接口
/// @return ERC1820_ACCEPT_MAGIC 只有当合约为地址'addr'实现'interfaceHash'时返回 ERC1820_ACCEPT_MAGIC
function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32);
}
任何合约要被注册为某地址的接口实现必须实现上面的接口。 此外,如果它代表不同的地址实现接口,合约也必须实现上面的ERC1820ImplementerInterface
。
function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32)
示合约是否为地址 “addr” 实现接口 “interfaceHash”。
当合约为地址’addr’实现’interfaceHash’时必须返回 ERC1820_ACCEPT_MAGIC
,如果没有实现一定不要返回ERC1820_ACCEPT_MAGIC
。
- 接口id:
f0083250
- 参数:
- _interfaceHash 接口hash
- addr: 要实现接口的地址
- 返回: 仅当实现了返回 ERC1820_ACCEPT_MAGIC。
特殊值ERC1820_ACCEPT_MAGIC
被定义为字符串"ERC1820_ACCEPT_MAGIC"
的keccka256
哈希。
bytes32 constant internal ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC"));
返回
ERC1820_ACCEPT_MAGIC
而不是布尔值的原因是为了防止合约未实现canImplementInterfaceForAddress
但实现了不抛出的回退函数。 在这种情况下,由于canImplementInterfaceForAddress
不存在,所以调用了回退函数,而没有抛出的情况下执行回退函数并返回“1”。 会看起来好像canImplementInterfaceForAddress
返回了true
。
ERC1820标准定义了一个通用注册表合约,任何地址(合约或普通用户帐户)都可以注册它支持的接口以及哪个智能合约负责接口实现。
ERC1820标准定义智能合约和普通用户帐户可以向注册表发布其实现了哪些功能(普通用户帐户通过代理合约实现)
任何人都可以查询此注册表,询问哪个地址是否实现了给定的接口以及哪个智能合约处理实现逻辑。
ERC1820注册表合约可以部署在任何链上,并在所有链上的地址是相同的。
接口的后28个字节都为0的话,会认为是 ERC165 接口,并且注册表将转发到合约以查看是否实现了接口。
此合约还充当 ERC165 缓存,以减少 gas 消耗。
在以太坊上有很多方法定义伪自省,ERC165不能由普通用户帐户使用。 ERC672 则使用了反向 ENS,反向 ENS 有两个问题:增加了不必要的复杂度,其次,ENS 是由多签控制的中心化合约。 从理论上讲,这种多签能够修改系统。
ERC1820标准比 ERC672 简单得多,并且完全去中心化。
此标准还为所有链提供一个唯一(相同的)地址。从而解决了解决不同链的查找注册表地址的问题。
管理员
地址管理员(常规帐户或合约)是唯一允许注册地址接口实现的实体。 默认情况下,任何地址都是自己的管理员。
管理员可以通过在注册表合约上调用“setManager”将其角色转移到另一个地址。
setManager
函数
function setManager(address _addr, address _newManager) external
设置 _newManager
作为 _addr
的管理员。 新的管理员可以为 _addr
调用 setInterfaceImplementer
。
如果 _newManager
是 0x0
, 则管理员重置为自身。
- 接口id:
5df8122f
- 参数:
- _addr: 要设置的地址
- _newManager: 新的管理员
getManager
函数
function getManager(address _addr) public view returns(address)
获取地址的管理员
- 接口id:
3d584063
- 参数:
- _addr: 要查询的地址
- 返回地址的管理员
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
}
}
}
1 设置账户管理员, 管理员可以设置接口实现
function setManager(address account, address newManager) external;
2 或者账户管理员
function getManager(address account) external view returns (address);
3 account管理员才能调用这个接口,
canImplementInterfaceForAddress
function setInterfaceImplementer(address account, bytes32 interfaceHash, address implementer) external;
4
function getInterfaceImplementer(address account, bytes32 interfaceHash) external view returns (address);
5
function interfaceHash(string calldata interfaceName) external pure returns (bytes32);
6
function updateBAC165Cache(address account, bytes4 interfaceId) external;
7
function implementsBAC165Interface(address account, bytes4 interfaceId) external view returns (bool);
8
function implementsBAC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool);