{"content":{"title":"理解ERC165标准","body":"## 一.理解ERC165\r\n\r\n  最近打算认真学习下ERC1155，但发现ERC777、ERC1820、ERC165很难绕过去，所以本着扎实的想法，那就从ERC165开始吧。\r\n\r\n  所以本文主要是围绕着EIP165提案，结合我对其他人学习资料的学习，希望能够以简单易懂、准确，容易理解的方式，分享和记录我对ERC165标准的理解。\r\n\r\n\r\n### ERC165是什么？\r\n  1.一种接口检测查询和发布标准\r\n  2.更具体的说是**一种可以查询目标合约是否支持了特定的接口**，当然同时**你也可以发布自己实现的接口，以供他人查询和交互**。\r\n  \r\n### ERC165有什么用？\r\n**可以通过其确定一个合约支持哪些接口，这样就知道如何与它进行交互。**\r\n\r\n例子：\r\n  如对于NFT市场来说，就可以通过ERC165标准来查询你的智能合约是不是实现了ERC721接口，如果是的话就可以使用ERC721的各种方法与你的合约进行交互和操作。\r\n\t\r\n  **所以ERC165提供了一种确定性的互操作支持。**\r\n\r\n\r\n\r\n## 二.ERC165如何实现注册和查询接口？\r\n  根据官方定义\r\n>   一个符合ERC-165规范的合约需要实现如下接口：\r\n\r\n```\r\ninterface ERC165 {\r\n    function supportsInterface(bytes4 interfaceID) external view returns (bool);\r\n}\r\n```\r\n  \r\n  1.任何人都可以根据这个方法，加上 要查询的“**接口标识符(interfaceId)**”来查询任意合约是否支持指定的接口。\r\n  \r\n  2.实现了则返回true，未实现则返回false。这样就有了一种清晰和确定性的方式能够知道目标合约是否支持哪些具体的接口与它交互。\r\n  \r\n  \r\n \r\n 那么这里**接口标识符(interface)** 又是什么呢？\r\n  根据提案解释\r\n> 将接口标识符(interfaceId)定义为接口中所有函数选择器的异或。\r\n\r\n**下面代码示例显示了如何计算接口标识符**\r\n\r\n```\r\npragma solidity ^0.4.20;\r\n\r\ninterface Solidity101 {\r\n    function hello() external pure;\r\n    function world(int) external pure;\r\n}\r\n\r\ncontract Selector {\r\n    function calculateSelector() public pure returns (bytes4) {\r\n        Solidity101 i;\r\n        return i.hello.selector ^ i.world.selector;\r\n    }\r\n}\r\n```\r\n\r\n**简单的说就是一个接口，无论定义了多少个方法，这个interfaceId就是其所有方法(函数选择器)的异或结果。**\r\n\r\n  至于为什么使用异或，使用异或是因为这是一种很方便结合得到一个接口中所有方法选择器的唯一标识表示方式。 ps: 其实可能会有异或函数选择器重叠的概率，但非常小。\r\n\r\n  **所以从这里可以得到关于interfaceId的几个关键信息**\r\n- interfaceId并不是单个函数的选择器，而是整个接口的所有函数选择器的共同异或结果。\r\n- 在solidity中，你可以使用 type(yourInterface).interfaceId 来获得任何接口的接口ID(因为solidity语言适配了使用ERC165方式计算得到interfaceId的支持)\r\n- 所以对于固定常用的接口，接口ID是固定的。比如ERC165的接口ID就是0x01ffc9a7\r\n\r\n\r\n**到这里就可以简单总结一下对于ERC165标准的理解：**\r\n`只要一个合约内实现了一个名为\"supportsInterface的方法\"，并能够根据你查询的接口ID(interfaceId)是否存在，而返回true 或 false ，即这个合约就算实现了ERC165。`\r\n\r\n## 三.具体应用\r\n### **1.怎样识别合约是否支持ERC165？**\r\n  **需要2步来检测**\r\n  \r\n\r\n```solidity\r\n//1.使用 staticall发送\r\n0x01ffc9a701ffc9a700000000000000000000000000000000000000000000000000000000\r\n\r\n//其实就等同于调用 \r\ncontract.supportsInterface(0x01ffc9a7)\r\n```\r\n  **并预期合约返回true**\r\n  \r\n  ``` \r\n//2.使用 staticall发送0x01ffc9a7ffffffff00000000000000000000000000000000000000000000000000000000\r\n\r\n//其实就等同于调用 \r\ncontract.supportsInterface(0xffffffff)\r\n  ```\r\n  **并预期合约返回false**\r\n  \r\n  ----\r\n  **那么为什么要发送两次呢？**\r\n  \r\n   第一次是为了确定合约实现了ERC165的supportsInterface方法，所以会返回true。\r\n   \r\n  第二次是为了确定合约正确实现了此方法，这里有两个原因：\r\n> 因为0xffffffff(换算成二进制就是32位个全1)这个值是几乎不太可能是一个正常的接口ID\r\n>     所以\r\n>     1.确保你的实现是正确的，不会有不存在的interface你也返回true。\r\n>     2.尤其是对于在erc165标准出现之前已部署的合约，不可能返回true，只能是false。\r\n\r\nTips:实际上可以不用staticall，直接调用合约的supportsInterface()方法传参接口ID即可。\r\n\r\n\r\n###  **2.如何检测一个已部署的合约是否支持指定的接口？**\r\n  如同上门如何识别ERC165接口，只需要传参你想查询的interfaceId。\r\nsolidity代码:\r\n```\r\n//可以先通过type(yourInterface).interfaceId 得到你要查询的接口ID 如 0x80ac58cd (ERC721的接口ID)\r\n\r\n//调用查询\r\nIERC165(token).supportsInterface(0x80ac58cd);\r\n```\r\n\r\n### **3.实现和发布自己的ERC165接口**\r\n\r\n  要让自己的合约支持ERC165接口查询，本质上只要实现了\"supportsInterface(bytes4 interfaceID)\"，并能正确返回true 或 false 即可。\r\n\r\n但是有两种不同的实现方式：\r\n- 1第一种是通过纯函数直接写返回\r\n- 第二种是把要注册的接口ID通过mapping来进行保存\r\n\r\n`Tip：\r\n纯函数返回的优势是若注册的接口数量少于3个，gas成本更低。mapping的方式优势是若注册的接口数量为3个或以上，则成本更低。`\r\n\r\n\r\n**下面为ERC165官方提案中两种不同的实现方式代码例子**\r\n\r\n**1.以mapping的方式注册**\r\n<!--StartFragment-->\r\n\r\n```\r\npragma solidity ^0.4.20;\r\n\r\nimport \"./ERC165.sol\";\r\n\r\ncontract ERC165MappingImplementation is ERC165 {\r\n    /// @dev You must not set element 0xffffffff to true\r\n    mapping(bytes4 => bool) internal supportedInterfaces;\r\n\r\n    function ERC165MappingImplementation() internal {\r\n        supportedInterfaces[this.supportsInterface.selector] = true;\r\n    }\r\n\r\n    function supportsInterface(bytes4 interfaceID) external view returns (bool) {\r\n        return supportedInterfaces[interfaceID];\r\n    }\r\n}\r\n\r\ninterface Simpson {\r\n    function is2D() external returns (bool);\r\n    function skinColor() external returns (string);\r\n}\r\n\r\ncontract Lisa is ERC165MappingImplementation, Simpson {\r\n    function Lisa() public {\r\n        supportedInterfaces[this.is2D.selector ^ this.skinColor.selector] = true;\r\n    }\r\n\r\n    function is2D() external returns (bool){}\r\n    function skinColor() external returns (string){}\r\n}\r\n```\r\n\r\n<!--EndFragment-->\r\n\r\n\r\n\r\n**2.以纯函数的方式直接返回**\r\n<!--StartFragment-->\r\n\r\n```\r\npragma solidity ^0.4.20;\r\n\r\nimport \"./ERC165.sol\";\r\n\r\ninterface Simpson {\r\n    function is2D() external returns (bool);\r\n    function skinColor() external returns (string);\r\n}\r\n\r\ncontract Homer is ERC165, Simpson {\r\n    function supportsInterface(bytes4 interfaceID) external view returns (bool) {\r\n        return\r\n          interfaceID == this.supportsInterface.selector || // ERC165\r\n          interfaceID == this.is2D.selector\r\n                         ^ this.skinColor.selector; // Simpson\r\n    }\r\n\r\n    function is2D() external returns (bool){}\r\n    function skinColor() external returns (string){}\r\n}\r\n```\r\n\r\n<!--EndFragment-->\r\n\r\n**Tip：** ERC165规范supportsInterface()的实现Gas消耗不要超过30000，以获得统一的可预期的交互保障。\r\n\r\n\r\n\r\n## ● 总结与参考\r\nERC-165标准为智能合约的开发提供了重要的互操作性支持，同时也看到了一个提案或标准，往往是很多贡献者的长期点滴的构筑，最后才让这个提案更加成熟。\r\n\r\n---\r\n参考来源：\r\nEIP165官方提案：https://eips.ethereum.org/EIPS/eip-165\r\nGithub EIP165提案历史讨论：https://github.com/ethereum/EIPs/issues/165\r\n其他ERC165优质学习笔记：https://www.cnblogs.com/wanghui-garcia/p/9507128.html"},"author":{"user":"https://learnblockchain.cn/people/6617","address":null},"history":"bafkreiaxkpbmbxx433sj566ukbqr6u7h5kslyr2odwdbg2ka7dexzc64ky","timestamp":1723275187,"version":1}