Solidity – How to Implement SupportsInterface for Non-Abstract Contracts

contract-deploymentcontract-designcontract-developmentsoliditysolidity-0.8.x

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;

Best Answer

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.