Right way to extend both `MulticallUpgradeable` and `UUPSUpgradeable`?

I am trying to extend MulticallUpgradeable and UUPSUpgradeable in the same contract; however, the two parts clash, as both define private _functionDelegateCall, making overriding to resolve the conflict not an option.

What's the right approach here?

:1234: Code to reproduce

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

import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol";

contract ERC721Burnable is Initializable, ERC721Upgradeable, ERC721BurnableUpgradeable, MulticallUpgradeable, OwnableUpgradeable, UUPSUpgradeable {
    function initialize() initializer public {
        __ERC721_init("ERC721Burnable", "MTK");
        __ERC721Burnable_init();
        __Multicall_init();
        __Ownable_init();
        __UUPSUpgradeable_init();
    }

    function _authorizeUpgrade(address newImplementation)
        internal
        onlyOwner
        override
    {}
}

Expected: contract compiles.
Observed:

TypeError: Derived contract must override function "_functionDelegateCall". Two or more base classes define function with same name and parameter types.
  --> src/ERC721Burnable.sol:13:1:
   |
13 | contract ERC721Burnable is
   | ^ (Relevant source part starts here and spans across multiple lines).
Note: Definition in "ERC1967UpgradeUpgradeable":
   --> @openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:207:5:
    |
207 |     function _functionDelegateCall(address target, bytes memory data) private returns (bytes memory) {
    |     ^ (Relevant source part starts here and spans across multiple lines).
Note: Definition in "MulticallUpgradeable":
  --> @openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol:37:5:
   |
37 |     function _functionDelegateCall(address target, bytes memory data) private returns (bytes memory) {
   |     ^ (Relevant source part starts here and spans across multiple lines).


Error HH600: Compilation failed

or, if an attempt to override _functionDelegateCall is made,

TypeError: Trying to override non-virtual function. Did you forget to add "virtual"?
  --> @openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol:37:5:
   |
37 |     function _functionDelegateCall(address target, bytes memory data) private returns (bytes memory) {
   |     ^ (Relevant source part starts here and spans across multiple lines).
Note: Overriding function is here:
  --> src/ERC721Burnable.sol:78:5:
   |
78 |     function _functionDelegateCall(address target, bytes memory data)
   |     ^ (Relevant source part starts here and spans across multiple lines).


TypeError: Trying to override non-virtual function. Did you forget to add "virtual"?
   --> @openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:207:5:
    |
207 |     function _functionDelegateCall(address target, bytes memory data) private returns (bytes memory) {
    |     ^ (Relevant source part starts here and spans across multiple lines).
Note: Overriding function is here:
  --> src/ERC721Burnable.sol:78:5:
   |
78 |     function _functionDelegateCall(address target, bytes memory data)
   |     ^ (Relevant source part starts here and spans across multiple lines).


Error HH600: Compilation failed

:computer: Environment

  • hardhat 2.6.1
  • solc 0.8.7
  • @openzeppelin/contracts-upgradeable 4.3.1
1 Like

@ericglau Im having the same issue. We would like to implement Multicall in an Upgradeable contract and cannot resolve this. One possible workaround would be to move the multicall to the Proxy itself, but we are worried about the implications. Any suggestions on how to use Muticall in Upgradeable contracts? Thanks!

Example contract for the approach @julianmrodri is referring to:

pragma solidity 0.8.9;

import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/utils/Multicall.sol";

/**
 * @title ERC1967Multicall
 * @notice A proxy implementation that supports multicall.
 */
contract ERC1967Multicall is ERC1967Proxy, Multicall {
    /**
     * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
     *
     * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
     * function call, and allows initializating the storage of the proxy like a Solidity constructor.
     */
    constructor(address _logic, bytes memory _data) payable ERC1967Proxy(_logic, _data) {}
}

I believe this does work, I'm just not sure if it's safe.

This is a Solidity issue and it is tracked here: https://github.com/ethereum/solidity/issues/11889

The specific use case with MulticallUpgradeable and UUPSUpgradeable is also referenced in this comment, and we will need to wait for the issue to be fixed in Solidity.

We don't recommend inheriting Multicall in the proxy itself, because that part won't be upgradeable and it could clash with a function in the implementation, but it looks like it would work because Multicall (the non-upgradeable version) does not use any storage slots.

Alternatively, you could try inheriting Multicall instead of MulticallUpgradeable in the implementation. If you are using the Upgrades Plugins, you would need to use unsafeAllow: ['delegatecall'] (see docs) to allow its delegatecall function.

1 Like

Thanks Eric, using Multicall was our workaround for the moment. We will stick to this for now, Thanks!