ERC165使用方法
March 11th, 2022

了解过 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,方法实现中需要显式包括继承的接口

参考

Subscribe to xyyme.eth
Receive the latest updates directly to your inbox.
Verification
This entry has been permanently stored onchain and signed by its creator.
More from xyyme.eth

Skeleton

Skeleton

Skeleton