💎

[Solidity] Derived contract must override function ~ エラーと菱形継承問題

2022/10/25に公開

問題

OpenZeppelinなどを利用して開発している際に、コントラクトにおいて多重継承を行い且つ継承元が同じfunctionを定義している場合、下記エラーが生じます。

Derived contract must override function {function名}. Two or more base classes define function with same name and parameter types.

解決方法

例えば、OpenZeppelin利用時にERC721URIStorage, ERC721Enumerableを継承するとエラーが生じますが、重複したものすべてについて下記のように定義するとエラーが解消されます。

contract DemoNFT is ERC721URIStorage, ERC721Enumerable {

~

  function tokenURI(uint256 tokenId)
    public
    view
    override(ERC721, ERC721URIStorage) // 両方のインターフェイスをoverrideする
    returns (string memory)
  {
    return super.tokenURI(tokenId); // 親のfunctionを呼び出す
  }

~

}

Diamond problem

今回の場合、ERC721URIStorage, ERC721EnumerableがどちらもERC721を継承しているので、それらを継承しているDemoNFTにてoverrideが必要になっています。

このときの継承の関係は次のようになっています。

           ERC721
       /            \
ERC721URIStorage    ERC721Enumerable
       \            /
           DemoNFT

ERC721URIStorage, ERC721EnumerableでERC721のfunctionがoverrideされている場合、DemoNFTではそのどちらを呼び出すかにおいて曖昧さが発生します。

これは菱形継承問題(ひしがたけいしょうもんだい、[英] diamond problemと呼ばれる問題です。

この問題に対するアプローチは複数ありますが、Solidityの場合C3 Linearizationによる解決を行なっています。これにより、線型化されて呼び出し順序が明確になります。

また、Solidityではfunctionが複数で定義されている場合はisキーワードの右から左に探索します。

// Contracts can inherit from multiple parent contracts.
// When a function is called that is defined multiple times in
// different contracts, parent contracts are searched from
/ right to left, and in depth-first manner.

Inheritance | Solidity by Example | 0.8.13

以上よりcontract DemoNFT is ERC721URIStorage, ERC721Enumerable { ~とした場合の解決順序は
DemoNFT→ERC721Enumerable→ERC721URIStorage→ERC721
となり呼び出し順序が一意に決定します。

また、C3 Linearizationを利用しているためsuperを利用した呼び出しが、親ではなく兄弟関係にあるコンストラクトへの呼び出しになることがあります。
Solidity Diamond Inheritance - Smart Contracts / Guides and Tutorials - OpenZeppelin Forum

参考

GitHubで編集を提案

Discussion