· Alids · Web3 · 10 min read
以太坊ERC165标准深入研究
以太坊以及EVM solidity 生态中智能合约运行在区块链上,用户缺少方法知道区块链实现了哪些接口,所以有了ERC165针对此进行统一约定。

概述
ERC165标准是以太坊生态体系的一个重要基础,此标准用于发布和检测智能合约实现的接口。通过此接口用户可以判断智能合约实现了哪些标准接口。
以太坊以及EVM solidity 生态中智能合约运行在区块链上,用户缺少方法知道区块链实现了哪些接口。智能合约的ABI往往不会同合约的字节码共同存储与区块链,所以用户想要知道某个合约是否实现了某些接口依赖一种所有合约公认的准则,也就是ERC165。
摘要
需要提出一个通用标准化方法,涉及如下:
- 接口如何被识别
- 合约将如何发布其实现的接口
- 如何检测合约是否实现了ERC-165
- 如何检测合约是否实现了任何给定的接口
意义
对于一些“标准接口”如ERC-20代币接口,有时查询合约是否支持该接口以及如果支持,支持接口的哪个版本是有用的,以便调整与合约交互的方式。特别是对于ERC-20,已经提出了版本标识符。此提案标准化了接口的概念,并规范了接口的识别(命名)。ERC-165 标准的一些重要意义:
接口标准化: ERC-165 为智能合约中接口的标准化提供了方法。通过采用这一标准,不同的合约可以更一致地定义和实现接口,从而提高了智能合约之间的互操作性。
动态接口检测: ERC-165 提供了一种动态检测合约是否实现特定接口的机制。这对于在运行时动态适应合约行为非常有用。例如,在处理 ERC-20 代币时,一个智能合约可以使用 ERC-165 标准来检查另一个合约是否实现了特定的 ERC-20 接口。
智能合约适应性: 由于智能合约可以实现多个接口,而 ERC-165 提供了一种标准的方式来检测这些接口,因此可以根据实际情况动态地选择与合约交互的方式。这提高了智能合约之间的适应性和可组合性。
版本控制: ERC-165 还提供了一种为接口版本控制的机制。通过在接口标识中包含版本信息,可以确保合约与特定版本的接口兼容,从而防止由于接口更改而导致的不兼容性问题。
综上所述,ERC-165 为智能合约提供了一种更加灵活和互操作的接口机制,使得智能合约系统更具可扩展性和可维护性。
标准详解
如何计算接口的标识符ID
对于所有的solidity 合约接口标准,接口是根据以太坊 ABI 定义的函数选择器集合。这是 Solidity 接口概念的一个子集,与 Solidity 的 interface 关键字定义不同,后者还定义了返回类型、可变性和事件。
ERC165标准中计算接口标识符定义为接口中所有函数选择器的异或(XOR)结果。以下代码示例展示了如何计算接口标识符:
pragma solidity ^0.4.20;
interface Solidity101 {
function hello() external pure;
function world(int) external pure;
}
contract Demo {
function calculateInterfaceID() public pure returns (bytes4) {
Solidity101 i;
return i.hello.selector ^ i.world.selector;
}
}
Note:
在 ERC-165 标准中,接口不允许包含可选的函数。因此,接口标识符不会考虑这些可选函数。接口的标识符是通过计算接口中所有函数选择器的 XOR 运算结果而生成的,而不包括可选函数。这样的设计使得接口的标识符更加精确和确定性,能够清晰地表示一个合约是否实现了特定的接口。
合约如何发布自己支持哪些接口
一个符合 ERC-165 标准的合约应当实现以下接口(简称 IERC165.sol):
pragma solidity ^0.4.20;
interface IERC165 {
// @notice 查询合约是否实现了某个接口
// @param interfaceID 接口标识符ID,如在 ERC-165 中指定
// @dev 接口标识ID在 ERC-165 中进行了规定。此函数的执行消耗不超过 30,000 gas。
// @return 若合约实现了 `interfaceID` 且 `interfaceID` 不是 0xffffffff,则返回 `true`,
// 否则返回 `false`
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
该接口的标识符为 0x01ffc9a7。您可以通过运行 bytes4(keccak256('supportsInterface(bytes4)')) 或使用上面的 Selector 合约进行计算。
因此,实现合约将具有一个 supportsInterface 函数,其返回值如下:
- 当 interfaceID 为 0x01ffc9a7(EIP165 接口)时,返回 true
- 当 interfaceID 为 0xffffffff 时,返回 false
- 对于此合约实现的任何其他 interfaceID,返回 true
- 对于任何其他 interfaceID,返回 false
该函数必须返回一个布尔值且最多使用 30,000 gas。
如何检测合约实现了ERC165接口
- 源合约对目标合约地址进行
STATICCALL,输入数据为:0x01ffc9a701ffc9a700000000000000000000000000000000000000000000000000000000,gas 为 30,000。这对应于contract.supportsInterface(0x01ffc9a7)。 - 如果调用失败或返回 false,则目标合约没有实现 ERC-165。
- 如果调用返回 true,则进行第二次调用,输入数据为
0x01ffc9a7ffffffff00000000000000000000000000000000000000000000000000000000 - 如果第二次调用失败或返回 true,则目标合约没有实现 ERC-165。否则,它实现了 ERC-165。
如何检测合约是否实现任何给定接口
- 如果您不确定合约是否实现了 ERC-165,请使用上述步骤进行确认。
- 如果它没有实现 ERC-165,则必须通过传统方法查看它使用了哪些方法。
- 如果它实现了 ERC-165,则只需调用
supportsInterface(interfaceID)来确定它是否实现了您可以使用的接口。
详细实现
接口文件: IERC165.sol
/**
*
* SPDX-License-Identifier: Apache-2.0
*
* @author: Aldis
*/
pragma solidity ^0.8.0;
/**
* @dev A standard for detecting smart contract interfaces.
*/
interface IERC165
{
/**
* @dev Checks if the smart contract includes a specific interface.
* This function uses less than 30,000 gas.
* @param _interfaceID The interface identifier, as specified in ERC-165.
* @return True if _interfaceID is supported, false otherwise.
*/
function supportsInterface(
bytes4 _interfaceID
)
external
view
returns (bool);
}
实现文件: ERC165.sol
/**
*
* SPDX-License-Identifier: Apache-2.0
*
* @author: Aldis
*/
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Implementation of standard for detect smart contract interfaces.
*/
contract ERC165 is
IERC165
{
/**
* @dev Mapping of supported intefraces. You must not set element 0xffffffff to true.
*/
mapping(bytes4 => bool) internal supportedInterfaces;
/**
* @dev Contract constructor.
*/
constructor()
{
supportedInterfaces[this.supportsInterface.selector] = true; // ERC165
}
/**
* @dev Function to check which interfaces are suported by this contract.
* @param _interfaceID Id of the interface.
* @return True if _interfaceID is supported, false otherwise.
*/
function supportsInterface(
bytes4 _interfaceID
)
external
override
view
returns (bool)
{
return supportedInterfaces[_interfaceID];
}
}
实践样例: Demo.sol
interface HelloWorld {
function hello() external returns (bool);
function world() external returns (string);
}
contract Demo is ERC165MappingImplementation, HelloWorld {
function Demo() public {
supportedInterfaces[this.hello.selector ^ this.world.selector] = true;
}
function hello() external returns (bool){
return true;
}
function world() external returns (string){
return "hello world"
}
}
注意事项
这个标准保持尽可能简单,可以兼容多个不同的 Solidity 版本。上述描述的机制(使用 0xffffffff)应该适用于大多数在此标准之前的合约,以确定它们是否实现了 ERC-165。



