1

I declare my own interfaces that inherit from OpenZeppelin interfaces. Eg;

interface IBase is IERC20Upgradeable, IAccessControlEnumerableUpgradeable {

Howvever, in my implementation I can't directly access the OZ functions

abstract contract Mintable is IBase {
   function mintCoins(uint amount, address to, uint timestamp) public
    onlyMinter
  {
    // We can only mint coins to the TC address
    hasRole(THECOIN_ROLE, to); <-- Undeclared identifier

Instead I have to manually cast, which seems to make the whole inheritance thing pointless

    IAccessControlEnumerableUpgradeable(this).hasRole(THECOIN_ROLE, to);

I seem to be misunderstanding something fundamental here. I've tried inheriting from the IAccessControl directly in Mintable but solidity can't work out the inheritance tree - but isn't that the whole point of interfaces? There is no 'tree', because there is no memory allocated?

Any suggestions? Solidity is verbose enough with manually casting every time I need to access a virtual function.

1 Answer 1

1

You seem to be misunderstanding what interfaces are.

From the documentation:

Interfaces are similar to abstract contracts, but they cannot have any functions implemented.

So an interface is just a "convention" on the functions that should be exposed, it enforces nothing regarding their actual implementations. You can verify that by looking at the code of IAccessControlEnumerableUpgradeable, there is no implementation.

Howvever, in my implementation I can't directly access the OZ functions

You cannot access a function from the interface because it is not implemented.

This code reproduces your error, uncommenting the implementation of myFunction will make it compile as the identifier myFunction is now defined.

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

interface MyInterface {
    function myFunction() external;
}

abstract contract MyContract is MyInterface {
    constructor() {
         myFunction();
    }

    //function myFunction() public view override {}
}

However, tt is strange to inherit from OpenZeppelin interfaces only... My guess is that you might be better off using the implementation of the interfaces that you are using, that way you can inherit from their functions implementations too, which seems to be what you are looking for.

Like this:

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

import "https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/release-v4.3/contracts/access/AccessControlEnumerableUpgradeable.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/release-v4.3/contracts/token/ERC20/ERC20Upgradeable.sol";

contract MyContract is AccessControlEnumerableUpgradeable, ERC20Upgradeable {

    bytes32 public constant THECOIN_ROLE = keccak256("THECOIN_ROLE");
    
    // It is important to guard your function(s) with the initializer() modifer
    // when required. We don't want to initialize more than once.
    function initialize() initializer() public {
        // Initialize AccessControl and its parents : 
        // Context
        // ERC165
        __AccessControlEnumerable_init();

        // Initialize only ERC20 and not its parents :
        // Context : as it was already initialized though _AccessControlEnumerable_init()
        __ERC20_init_unchained("THE_COIN_NAME", "THE_COIN_SYMBOL");

        // This works fine now as we inherited from a contract that implements
        // the function hasRole. By opposition with inheriting from an interface
        // that cannot implement a single function.
        hasRole(THECOIN_ROLE, msg.sender);
    }
}
3
  • I was using interfaces to split my contract over several files without worrying about the diamond inheritance problem. Each sub-portion of my contract should be compiled independently and uses the interface, but the final contract inherited from the implementation.
    – FrozenKiwi
    Dec 2, 2021 at 20:44
  • And - yeah, your first point is what I thought too. However, when I change my inheritance pattern so my base inherits from the implementation, the issue goes away. So there must be some difference between inheriting a virtual function from an interface and a virtual fn from a concrete class. I didn't want to do it that way though because that means a specific inheritance tree, and breaks the isolation of each component somewhat
    – FrozenKiwi
    Dec 2, 2021 at 20:46
  • ` So there must be some difference between inheriting a virtual function from an interface and a virtual fn from a concrete class.` Yes, the difference is that a concrete class (non interface / non-abstract) must implement all of its functions. To be fair, diamond inheritence is very rarely justified. In your specific example, I feel like there is no point in splitting the implementation of your contract Token, neither of Access Control.
    – hroussille
    Dec 2, 2021 at 20:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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