Derived contract must override function "supportsInterface"

When inheriting from both ERC1155 and AccessControl I receive the error in the following error:

Derived contract must override function "supportsInterface". 
Two or more base classes define function with same name and parameter types.

:computer: Environment

OpenZeppeling 4.0.0-beta.0

:memo:Details

:1234: Code to reproduce

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

contract MyContract is ERC1155, AccessControl { }
4 Likes

Hi @dylkil,

Welcome to the community :wave:

As both ERC1155 and AccessControl include supportsInterface we need to override both.

The following compiles:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

contract MyContract is ERC1155, AccessControl { 

    constructor (string memory uri) ERC1155(uri) {}

    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155, AccessControl) returns (bool) {
        return super.supportsInterface(interfaceId);
    }
}
8 Likes

Thanks a lot! It help me!

1 Like

Hi @abcoathup , even though this will compile is it technically correct? My understanding is that solidity's super keyword calls the immediate parent contract's implementation of the target method.

In this case, wouldn't that mean that super.supportsInterface() always accesses Access Control's implementation?

Thanks for any and all help!

If you check out the implementation, it's an OR (||) chain in both AccountControl and the ERC contract, and they both call super() at the end. The override(parent1, parent2) tells the order in which to traverse the parents. So this happens:

  • Call MyContract.supportsInterface()
  • MyContract calls super.supportsInterface(interfaceId) which equates to ERC1155.supportsInterface(interfaceId)
  • If interfaceId matches the interface for ERC1155, it short circuits and returns true, otherwise...
  • ERC1155 calls super.supportsInterface(interfaceId) which equates to AccessControl.supportsInterface(interfaceId)
  • If interfaceId matches the interface for AccessControl, it returns true, otherwise...
  • It finally returns false, meaning that the given interfaceId doesn't match any of our supported interfaces. This chains all the way back up
2 Likes

That makes much more sense. Thanks for the reply

Still doesn't make sense to me. ERC1155 is not derived from AccessControl or the other way around. So calling super.supportsInterface(interfaceId) will only return the one from the last one which is AccessControl.
I have written some test smart contracts to validate this, and it confirms my point.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract A {

  function func1(uint256 id) virtual public returns (bool) {
        return id==1;
  }
}

contract B {

  function func1(uint256 id) virtual public returns (bool) {
        return id==2;
  }
}

contract AB is A, B {

  function func1(uint256 id) public virtual override (A,B) returns (bool) {
        return super.func1(id);
  }
}

here calling AB.func1(1) returns false while AB.func1(2) returns true.

So I think your code should be written in this way :

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

contract MyContract is ERC1155, AccessControl { 

    constructor (string memory uri) ERC1155(uri) {}

    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155, AccessControl) returns (bool) {
        return ERC1155.supportsInterface(interfaceId) || AccessControl.supportsInterface(interfaceId);
    }
}

I think you need to call super in the parent functions, or at least in A. Does that make it work?

Well I think we are both saying the same thing, not sure why I started with "Still doesn't make sense to me.". Calling super will only call the AccessControl one so it is needs
|| ERC1155.supportsInterface(interfaceId)

Huh, actually you are 100% right and my original post is incorrect. I had assumed that the compiler was sort of linearizing the inheritance chain and letting super() from B call the func from A. This is wrong!

So in the case where we want to return true if either parent is true, we should not use super.

Here's the correct implementation for my contract which inherits from several standard contracts:

function supportsInterface(bytes4 interfaceId) public view override(ERC721, AccessControl, ERC2981, IERC165, ERC165) returns (bool) {
        return (
            ERC721.supportsInterface(interfaceId) || 
            AccessControl.supportsInterface(interfaceId) ||
            ERC2981.supportsInterface(interfaceId) ||
            ERC165.supportsInterface(interfaceId)
        );
    }
1 Like

FYI this is incorrect! See the followup below

The correct answer to this question is to use super as shown here:

The example shared by @Mohandes is not representative of the way ERC165 works. All implementations of supportsInterface will check for an id and also "forward" the call via super, as seen here:

This means that when the compiler linearizes the inheritance graph, all implementations of supportsInterface will be chained and executed sequentially (in linearization order).

The example shared by @Mohandes should be changed so that A and B both inherit from a grandparent G with a base definition of func1, and A.func1 and B.func1 both return id == X || super.func1(id).

2 Likes

Thanks @frangio , I see my example does not apply in this case. Thanks for clarification.