"multiple matching functions" with Hardhat and contracts-upgradeable

I’ve recently “upgraded” my deployment setup to use Hardhat and the latest @openzeppelin/contracts-upgradeable package.

Unsurprisingly, this means some changes to my smart contract. Unfortunately, while compilation works, I cannot get my contract to successfully deploy, as this error is returned:

{ Error: multiple matching functions (argument="name", value="initialize", code=INVALID_ARGUMENT, version=abi/5.0.10)
    at Logger.makeError (/contract/node_modules/@ethersproject/logger/src.ts/index.ts:205:28)
    at Logger.throwError (/contract/node_modules/@ethersproject/logger/src.ts/index.ts:217:20)
    at Logger.throwArgumentError (/contract/node_modules/@ethersproject/logger/src.ts/index.ts:221:21)
    at Interface.getFunction (/contract/node_modules/@ethersproject/abi/src.ts/interface.ts:196:24)
    at getInitializerData (/contract/node_modules/@openzeppelin/hardhat-upgrades/src/deploy-proxy.ts:74:46)
    at Proxy.deployProxy (/contract/node_modules/@openzeppelin/hardhat-upgrades/src/deploy-proxy.ts:54:18)
    at processTicksAndRejections (internal/process/task_queues.js:86:5)
  reason: 'multiple matching functions',
  code: 'INVALID_ARGUMENT',
  argument: 'name',
  value: 'initialize' }

Contract code looks like this, and I’m not sure where I’ve gone wrong:

import "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/presets/ERC721PresetMinterPauserAutoIdUpgradeable.sol";

contract TEST is Initializable, ERC721PresetMinterPauserAutoIdUpgradeable {
    function initialize() public initializer {
        __ERC721PresetMinterPauserAutoId_init(
            "name",
            "symbol",
            "baseURI"
        );
    }
...
}
4 Likes

Making progress! Will update shortly.

1 Like

Hi @EvilJordan,

Multiple initialize functions are being found. I think you just need to add override to your initialize function.

That makes sense, but it didn’t solve my problem. I had to completely rename the contract’s initialize() function to something different, then call it with:
await upgrades.deployProxy(TEST, [], { initializer: 'TESTInit' });

That works, and everything is running as expected, but verification isn’t working at all and I’m not sure why.

1 Like

Hi @EvilJordan,

I am not sure if there is a way around having to use a different function name.

As for verification: The proxy contract should already be verified. You will need to verify your implementation contract using the same process as any other contract: Verify smart contract inheriting from OpenZeppelin Contracts

You’d think this would work, but it does not, as override can’t figure out what it’s supposed to be overriding. Very odd.

import "@openzeppelin/contracts-upgradeable/presets/ERC721PresetMinterPauserAutoIdUpgradeable.sol";

contract TEST is ERC721PresetMinterPauserAutoIdUpgradeable {
    function initialize() public override initializer {
        __ERC721PresetMinterPauserAutoId_init(
            "TEST",
            "TEST",
            "https://TEST.testnet.TEST.com/"
        );
    }
    ...
}

Re: Verification: The address it spits out post deployment is the DEPLOYED_CONTRACT_ADDRESS, right? I can see that address is verified in Etherscan already through a “Similar Match,” but I don’t see any of my “custom” functions in there and verification consistently fails with:

Error in plugin @nomiclabs/hardhat-etherscan: The address provided as argument contains a contract, but its bytecode doesn't match any of your local contracts.

Possible causes are:
  - Contract code changed after the deployment was executed. This includes code for seemingly unrelated contracts.
  - A solidity file was added, moved, deleted or renamed after the deployment was executed. This includes files for seemingly unrelated contracts.
  - Solidity compiler settings were modified after the deployment was executed (like the optimizer, target EVM, etc.).
  - The given address is wrong.
  - The selected network (rinkeby) is wrong.
1 Like

Hi @EvilJordan,

You need to use the address of your implementation contract.
You can find this in your network file: https://docs.openzeppelin.com/upgrades-plugins/1.x/network-files

On Etherscan under more options you can click Is this a proxy and it will ask you to verify your implementation contract: https://medium.com/etherscan-blog/and-finally-proxy-contract-support-on-etherscan-693e3da0714b

1 Like

Yes, the “implementation contract” is the same as the DEPLOYED_CONTRACT_ADDRESS, or what hardhat outputs with a call to:

const TEST = await ethers.getContractFactory('TEST');
const test = await upgrades.deployProxy(TEST, [], { initializer: 'init' });
await test.deployed();
console.log('TEST deployed to:', test.address);

I’m wondering if it has something to do with constructor arguments since initializers are involved? I’ve not had problems verifying single-file or contracts that don’t inherit other libraries.

1 Like

GOT IT!!

You (@abcoathup) were right, as usual. The “implementation contract” is not the same as DEPLOYED_CONTRACT_ADDRESS. The “implementation contract” is what needs to be verified and can be found one of two ways:

  1. Explore the ./.openzeppelin/NETWORK.json file for the deployed contract, but it’s tough to know which entry might be the right one if you’ve got more than one compilation/deployment.
  2. As per your directions, in Etherscan, under “more options” you can click Is this a proxy and it will ask you to verify your implementation contract… THEN you click through to the next screen to see the address of the implementation contract that needs verification. This IMPLEMENTATION_ADDRESS will match with the appropriate address in the ./openzeppelin/NETWORK.json file.

Then run:
npx hardhat verify --network rinkeby IMPLEMENTATION_ADDRESS
and you’re off to the races!

1 Like

Hi @EvilJordan,

Glad you were able to verify, unfortunately it isn’t as simple as I would like. This is still a pain point for the ecosystem.

You can now interact with your upgradeable contract via the proxy address on Etherscan.

The reason you’re getting a function name clash is that the two initialize functions have different signatures. This is also the reason why you can’t override.

One way around this is to specify the full function signature. If I’m not mistaken, you should be able to use options { initializer: 'initialize()' }. Note the parentheses.

4 Likes