{"content":{"title":"理解ERC1820标准","body":"之前学习了下ERC165标准，[理解ERC165标准](https://learnblockchain.cn/article/8997)，发现ERC标准是一环扣一环，怪是越打越多的。希望以本文记录一下ERC1820的一些细节和主要实现，不会具体分析合约方法。\r\n\r\n## 【ERC1820总览】\r\n\r\n  **下面要介绍的ERC1820是以太坊区块链世界中的\"中心化注册表\"，任何人可以通过它查询哪些合约或EOA地址是否支持哪些接口，以更加确定性的方式去交互。**\r\n  \r\n  **当然你也可以为一个地址注册接口，使别人也可以随时查询到你的合约或EOA地址是否支持指定的接口。**\r\n\r\n.\r\n**ERC1820标准的功能主要为查询、和注册。**\r\n\r\n**查询**\r\n  查询一个指定的地址(可以是EOA，也可以是合约)，是否实现了指定的接口(这里兼容ERC165接口)。\r\n\r\n.\r\n**注册**\r\n注册可以达到三个效果，你可以选择如何注册。\r\n1. 为一个合约地址注册指定的已经实现的接口\r\n2. 为一个合约地址注册指定的已经实现的接口，但是可以注册为代理实现者(一个外部合约实现这个接口)。\r\n3. 为一个EOA地址，注册一个接口，并指定代理实现合约地址。\r\n\r\n.\r\n\r\n**通过不同的身份理解ERC1820**\r\n\r\n  ERC1820有三个身份，分别是 **目标地址(target)**、**管理者(manager)**、**实现者(implementer)**，这三个身份分别有不同的作用。\r\n  \r\n**目标地址(target)**\r\n>1.默认要为哪个地址查询(或实现)接口\r\n>2.查询接口也是通过这个地址 + 要查的接口Hash 来进行查询\r\n\r\n    \r\n**管理者(manager)**\r\n> 1.只有manager才能改变一个地址的接口\r\n>2. 一个目标地址的默认manager是这个目标地址本身\r\n> 3.manager可以设置修改一个新的manager\r\n\r\n\r\n**实现者(implementer)**\r\n> 1.默认是目标地址自身\r\n> 2.当为EOA地址注册接口，或希望为合约代理实现接口，此时implementer就是那个代理实现合约。\r\n> 3.在通过erc1820查询接口是否注册时，返回的就是implementer地址。\r\n\r\n\r\n\r\n**了解了这三个身份，就可以很好的理解ERC1820的设计。**\r\n\r\n## 【主要方法理解】\r\n下面列出ERC1820的主要方法，并了解如何使用。\r\n\r\n\r\n\r\n    //【作用：查询一个目标地址是否实现了某个接口】\r\n    //1.未注册返回0地址，注册则返回 实现者(implementer)地址 \r\n    //2.接口Hash(_interfaceHash)的计算方法\r\n\t//keccak256(abi.encodePacked(_interfaceName));\r\n        \r\n\t//3.这里参数_interfaceName 就是接口的字符串，比如\"IERC20Token\"\r\n    //4._interfaceHash也可以兼容传入erc165接口ID，但需要填充满bytes32(erc165接口ID是bytes4)\r\n    function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address);\r\n    \r\n    \r\n    \r\n    // 【作用：设置接口实现者】\r\n\t//1._addr必须是msg.sender，所以这就意味着EOA地址的接口注册也需要这个EOA来完成。\r\n        \r\n\t//2.若是为自己实现接口，默认_addr和_implementer是同一个地址\r\n        \r\n\t//3.ERC1820中只能兼容查ERC165接口，但不能为ERC165注册。 所以_interfaceHash只能是ERC1820接口。\r\n        \r\n\t//4.为了方便多签，若把_addr传入0地址，则默认会把_addr设为msg.sender。这样多签可以复用同样的交易数据。\r\n    function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external;\r\n    \r\n    \r\n    \r\n    // 【作用：设置地址的管理者】\r\n    \r\n\t//_addr必须为原manager，默认是目标地址\r\n    function setManager(address _addr, address _newManager) external;\r\n\r\n\r\n    // 【作用：获取地址的管理者】\r\n    function getManager(address _addr) external view returns(address);\r\n\r\n\r\n    //【 作用：计算接口哈希值】\r\n    \r\n\t//此方法是ERC1820标准为了方便人们算接口Hash提供的，其实也只是keccak256 + encodePacked 接口名称。\r\n    function interfaceHash(string calldata _interfaceName) external pure returns(bytes32);\r\n\r\n\r\n    // 【作用：更新 ERC165 缓存】\r\n\t//此方法必须手动调用更新缓存，ERC1820不会主动更新缓存。\r\n        \r\n\t//如果你想通过ERC1820储存你项目的ERC165接口，那么当你每次接口改变，最好同时也调用此方法更新缓存记录。\r\n    function updateERC165Cache(address _contract, bytes4 _interfaceId) external;\r\n\r\n    // 检查合约是否实现了 ERC165 接口\r\n    function implementsERC165Interface(address _contract, bytes4 _interfaceId) external view returns (bool);\r\n\r\n    // 当没有ERC165缓存时，以staticall方式去目标合约查。\r\n    function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) external view returns (bool);\r\n\r\n .\r\n\r\n.\r\n**【ERC165和1820查询实现不同细节对比】**\r\n\tERC1820的实现中有很多值得学习和需要细心发现的细节。\r\n- ERC165和ERC1820接口查询区别\r\n    1. ERC165中使用bytes4查询\r\n    2. ERC1820中使用Bytes32查询\r\n        -即使兼容ERC165查询，也需要用bytes32查\r\n.\r\n- ERC165通过返回的bool，而ERC1820则通过返回的\"地址\"判断接口是否存在\r\n    - 1.ERC1820中对于代理实现的合约需要返回 ERC1820_ACCEPT_MAGIC 标识符，这是因为要防止代理合约没有实现canImplementInterfaceForAddress，但却因为实现了带bool返回值的fallback方法而出现误判。\r\n    - 2.所以从这个角度来看，ERC165标准要发送两次staticcall(一次预期返回false，一次要查询的接口)来确认一个ERC1665接口是否存在其实是必要的。\r\n\r\n\r\n## 【部署】\r\n### 部署原理\r\n**对于ERC1820的部署，其实需要先简单理解一下交易签名。**\r\n- 1.以太坊区块链上所有的write操作(包括write合约方法和交易)，无论通过什么方式发送，本质上都是打包你交易中的nonce、from、to、data、gas、r、v、s等信息。然后使用ECDSA签名，得到一个交易字符串。 \r\n- 2.ERC1820官方提案中就是提供了这样一个用于部署的交易字符串\r\n\r\n\r\n\r\n\r\n **为了实现任何人可以在任何链上，都能够部署得到同样的ERC1820合约地址，达到统一的效果，采用了一种无需私钥的部署方式。**\r\n\r\n  知识点：\r\n1. 以太坊交易签名的时候，from地址由于可控，所以是不被包含在签名里面的。\r\n2. .最终上链的交易中的from地址，是节点通过签名恢复出的from地址。但你可以自定义签名(比如r、v、s)，其中很多签名都可以恢复出一个对应的随机地址。\r\n3.所以ERC1820的合约部署的r、v、s是自定义的\r\n\r\n如下：ERC1820的签名r、v、s数据\r\n```js\r\nv: 27,\r\nr: 0x1820182018201820182018201820182018201820182018201820182018201820'\r\ns: 0x1820182018201820182018201820182018201820182018201820182018201820'\r\n```\r\n\r\n**这里有一些细节**\r\n- 1为了实现在所有链部署都得到同样的地址，所以不能使用EIP155的标准。\r\n    - EIP155比默认情况下多一个链ID，会导致同样的交易数据在不同的链部署后地址不同。\r\n- .这种方式恢复出的地址，是一次性的，部署后的from会是\"0xa990077c3205cbDf861e17Fa532eeB069cE9fF96\"\r\n    - 但之后由于相同的nonce已经使用，无法再恢复出这个地址。\r\n- 部署后的ERC1820合约地址，在任何链上都会是：0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24\r\n- 这个交易中默认给了比较高的gas price 100 gwei，也是为了在链上gas波动情况下，增加保障。\r\n\r\n### 部署步骤\r\n**上面是分析了一下部署原理，下面说一下步骤。**\r\n由于部署后的from是0xa990077c3205cbDf861e17Fa532eeB069cE9fF96\r\n- 1.所以部署前需要先转入0.08ETH到这个地址。\r\n- 2.使用交易发送方法如ethers.js的 sendTransaction() 方法，发送提案中提供的原始部署交易。\r\n\r\n## 【思考与总结】\r\n### **关于ERC1820可以为EOA地址注册接口实现的思考**\r\n  理想来说，作为开发者或项目方，你可以为一个指定的用户(EOA)地址，实现一些特定的逻辑。\r\n  \r\n`  比如当用户收到代币、就代他进行投票、或存款之类的操作。 如ERC777就默认实现了转账接收钩子(tokensReceived)。`\r\n\r\n  **但这个为EOA地址实现接口功能，2点原因导致使用受限；**\r\n- 1.需要是标准场景下的操作\r\n    - 比如ERC777的默认转账回调，在用户收到代币的情况下，才会执行。\r\n    - 缺少场景丰富又同时有共识的标准。\r\n- 2.或者是自身项目内部生态\r\n    - 如果想为指定EOA用户实现指定的接口功能实现，那你可能要自己定义接口，又要为用户去注册和实现。这种情况也许只在自己项目生态中比较好实现了。\r\n\r\n>   另外还有一个不算友好的地方，ERC1820中只能由msg.sender来为自己注册接口，所以这个EOA用户也只能自己去调用方法，对用户的要求比较\"高\"。\r\n\r\n\r\n### 【总结】\r\n  最近抽空学习ERC1820的官方提案，断断续续用了大概一周多的时间。\r\n  \r\n**ERC1820的合约实现的也有很多很巧妙的地方，很值得去学习。这过程中涉及到一些相关的内容、比如以太坊的签名与交易、无秘钥部署、EIP155、ERC165等..**\r\n        \r\n感觉想把ERC1820通过一篇文章写的非常清晰，是挺不容易的，只能算一些笔，还是感觉有不少收获的。\r\n\r\n  另外看到以太坊主网的ERC1820合约部署五年多，也没几个人调用注册，真是可惜了这么好的合约。\r\n\r\n--------------------------------------------------------\r\n**参考来源**\r\n\tEIP1820官方提案：https://eips.ethereum.org/EIPS/eip-1820\r\n\tERC1820优质文章：https://www.decipherclub.com/erc-1820/"},"author":{"user":"https://learnblockchain.cn/people/6617","address":"0xE799DD812A4B3F6d67bdE9715c73E69eD1950daE"},"history":"bafkreif5n4enfdlyjmfn4cu6kmfsuye7cirm5ozfnc6pf2lxgabgsn2gfu","timestamp":1726210658,"version":1}