xyyme.eth

Posted on Mar 11, 2022Read on Mirror.xyz

ERC165使用方法

了解过 NFT 合约的同学应该注意到,每个 NFT 合约都实现了 supportsInterface 方法,但是每个合约中的具体方法实现又不尽相同,那么在实际开发过程中,究竟应该怎么实现这个方法呢,这篇文章就来讲讲这个内容

ERC165的具体内容这里就不再多说了,不了解的同学可以 Google 一下

首先我们需要知道计算一个接口的 interfaceId 都有几种方法。

假设对于如下接口:

interface IExample {
    function foo() external view returns (uint);

    function bar() external view returns (bool);

    function baz() external view returns (bytes32);
}

那么可以这样计算它的 interfaceId

使用硬编码函数签名计算(不推荐,容易出错且麻烦)

function calcInterfaceId1() public view returns (bytes4) {
    return bytes4(keccak256("foo()"))
        ^ bytes4(keccak256("bar()"))
        ^ bytes4(keccak256("baz()"));
}

使用方法 selector 计算(不推荐,不过在 0.6.7 之前的版本要用这个)

function calcInterfaceId2() public view returns (bytes4) {
    IExample i;
    return i.foo.selector ^ i.bar.selector ^ i.baz.selector;
}

直接使用 type 关键词(推荐,0.6.7版本及之后支持)

function calcInterfaceId3() public view returns (bytes4) {
    return type(IExample).interfaceId;
}

这三种方法都可以用来计算 interfaceId,结果相同,都是 0x9bb235aa

接下来,我们看看实际开发中都是怎么实现 supportsInterface 方法的。

来看几个例子:

同时继承与实现包含 IERC165 的合约(ERC721)和接口(IERC2981)

Invisible Friends代码

// 合约继承了 ERC721,实现了 IERC2981接口
contract InvisibleFriends is ERC721, IERC2981, Ownable,                                                                ReentrancyGuard {
}
// override 后面包括 ERC721 和 IERC2981 的最上层基类 IERC165
function supportsInterface(bytes4 interfaceId)
    public
    view
    override(ERC721, IERC165)
    returns (bool)
{
    // 需要显式声明 IERC2981
    return
        interfaceId == type(IERC2981).interfaceId ||
        super.supportsInterface(interfaceId);
}

合约实现的基类中,ERC721 和 IERC2981 中都包括 IERC165,因此需要显式重写 supportsInterface 方法。override后面的括号需要包含 ERC721,由于 IERC2981 是接口,因此要包含 IERC2981 的最上层基类 IERC165,同时需要在方法实现中显示声明 IERC2981 接口本身。

同时继承多个包含 IERC165接口的合约(ERC721,ERC721Enumerable)

mfers代码

// 合约继承了 ERC721,ERC721Enumerable
contract mfers is ERC721, ERC721Enumerable, Ownable {
}
// override 后面包括 ERC721 和 ERC721Enumerable
function supportsInterface(bytes4 interfaceId)
    public
    view
    virtual
    override(ERC721, ERC721Enumerable)
    returns (bool)
{
    // 无需显式声明接口
    return super.supportsInterface(interfaceId);
}

合约实现的基类中,ERC721 和 ERC721Enumerable 中都包括 IERC165。因此需要显式重写 supportsInterface 方法。而由于两者均非接口,因此直接在 override 后面声明。在方法实现中无需显式声明。

只继承一个包含 IERC165 接口的合约(ERC721A)

Tasty Bones代码

// 合约继承了 ERC721
contract TastyBones is Ownable, ERC721A, ReentrancyGuard {
}

// 没有显式实现 supportsInterface 方法

合约实现的基类中,只有 ERC721A 一个合约包括 IERC165 接口,因此合约的最终实现无需显式实现 supportsInterface 方法。

这三个合约基本上代表了几种具体实现方式,总结一下:

  • 如果合约继承的基类中只有一个合约包含 IERC165,即没有冲突,那么无需显式实现
  • 如果基类中存在 IERC165 冲突,且均非接口,需要显式实现,override 之后需要包括这些基类名,方法实现中无需显式包括合约
  • 如果基类中存在 IERC165 冲突,且存在接口,需要显式实现,override 之后需要包括合约以及接口的最上层基类 IERC165,方法实现中需要显式包括继承的接口

参考

https://eips.ethereum.org/EIPS/eip-165