了解过 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)
// 合约继承了 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)
// 合约继承了 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)
// 合约继承了 ERC721
contract TastyBones is Ownable, ERC721A, ReentrancyGuard {
}
// 没有显式实现 supportsInterface 方法
合约实现的基类中,只有 ERC721A 一个合约包括 IERC165 接口,因此合约的最终实现无需显式实现 supportsInterface 方法。
这三个合约基本上代表了几种具体实现方式,总结一下:
- 如果合约继承的基类中只有一个合约包含 IERC165,即没有冲突,那么无需显式实现
- 如果基类中存在 IERC165 冲突,且均非接口,需要显式实现,override 之后需要包括这些基类名,方法实现中无需显式包括合约
- 如果基类中存在 IERC165 冲突,且存在接口,需要显式实现,override 之后需要包括合约以及接口的最上层基类 IERC165,方法实现中需要显式包括继承的接口