2

I have the following scenario.

contract GovernanceERC20 is ERC165Upgradeable, ERC20VotesUpgradeable, {
   
   bytes4 private constant GOVERNANCE_INTERFACE_ID;

   /// @inheritdoc ERC165Upgradeable
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return
            interfaceId == GOVERNANCE_INTERFACE_ID ||
            interfaceId == type(IERC20Upgradeable).interfaceId ||
            interfaceId == type(IERC20PermitUpgradeable).interfaceId ||
            interfaceId == type(IERC20MetadataUpgradeable).interfaceId ||
            interfaceId == type(ERC20VotesUpgradeable).interfaceId ||
            super.supportsInterface(interfaceId);
    }
   
   function initialize() public view returns(uint) {
        return 10;
   }

   function mint(address to, uint256 amount) external {
        _mint(to, amount);
    }

   function great() public view returns(uint) {
        return 10;
   }
  
}

As you can see, I am thinking what GOVERNANCE_INTERFACE_ID should be.

Way 1: I can't do GOVERNANCE_INTERFACE_ID = type(GovernanceERC20).interfaceId as GovernanceERC20` isn't abstract.

Way 2: I don't want to create any interface for GovernanceERC20 .

What should GOVERNANCE_INTERFACE_ID be ?

Option A:

GOVERNANCE_INTERFACE_ID = this.initialize.selector ^ this.great.selector ^ this.mint.selector;

Option B:

GOVERNANCE_INTERFACE_ID = type(IERC20Upgradeable).interfaceId ^ type(IERC20PermitUpgradeable).interfaceId ^ type(IERC20MetadataUpgradeable).interfaceId ^ type(ERC20VotesUpgradeable).interfaceId ^ this.initialize.selector ^ this.great.selector ^ this.mint.selector;

1 Answer 1

2
+100

Strictly speaking it doesn't matter much as long as it doesn't conflict with other identifiers.

As you probably know, EIP-165 specifies how contracts are determined to implement an interface. It has an example: return i.hello.selector ^ i.world.selector;. So basically XORing together the selectors of its functions. There's an EIP-881 which goes into a bit more details, but it's not finalized.

As for your options A and B, option A is more in line with how the IDs are calculated in interfaces: an interface doesn't care what other interfaces are implemented and the ID only states what that specific interface supports. Therefore it's often useful to check multiple supportsInterface IDs to see what all functionality an implementation supports. In this sense I would go with option A.

I'm not sure why you don't want to just add an interface for your extra functions, but still I would calculate the ID the same way as if it was from an interface. So option A.

3
  • Thanks so much for this. One question: OZ has UUPSUpgradable abstract contract. I inherit from it in my abtract contract Plugin is UUPSUpgradable. how would supportsInterface would work here ? is it a good idea to do type(UUPSUpgradable).interfaceId or this is only recommended for interfaces ? Commented Oct 17, 2022 at 10:48
  • I guess it's mostly meant for interfaces, but surely you can add "support" for it without an interface as well. If you have further questions, please post a new question. Commented Oct 17, 2022 at 11:06
  • Hey Lauri, could you take a look into this ? ethereum.stackexchange.com/questions/137622/… Commented Oct 17, 2022 at 13:14

Not the answer you're looking for? Browse other questions tagged or ask your own question.