Clones not verified under OpenZeppelin Contracts 4.2 or Solidity 0.8.6

Clones (minimal proxies) created where the factory and implementation contracts use OpenZeppelin Contracts 4.2 are not verified on Etherscan. They’re only verified under 4.1. And only as long as Solidity 0.8.5 is used (have not tried with earlier versions). If Solidity 0.8.6 is used, the clone is also not verified.

The following table shows the various scenarios:

# | Contracts | Solidity | Verified?
1 | 4.1       | 0.8.5    |    ✅
2 | 4.1       | 0.8.6    |    ❌
3 | 4.2       | 0.8.5    |    ❌
4 | 4.2       | 0.8.6    |    ❌

The following are examples of the above (as numbered) on Rinkeby:

  1. Factory | Implementation | Create Transaction | Clone :white_check_mark:
  2. Factory | Implementation | Create Transaction | Clone :x:
  3. Factory | Implementation | Create Transaction | Clone :x:
  4. Factory | Implementation | Create Transaction | Clone :x:

Clearly, there is a bug in OpenZeppelin Contracts 4.2 and/or Solidity 0.8.6, or there is a compatibility issue.

:1234: Code to reproduce

The code can of course be seen in the Etherscan links above, but I’ve put here for completeness:


pragma solidity ^0.8.0;

import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
import "@openzeppelin/contracts/token/ERC20/utils/TokenTimelock.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/TokenTimelockUpgradeable.sol";

contract SchnoodleTimelock is TokenTimelockUpgradeable {
    function initialize(IERC20Upgradeable token, address beneficiary, uint256 releaseTime) public initializer {
        __TokenTimelock_init(token, beneficiary, releaseTime);


pragma solidity ^0.8.0;

import "./SchnoodleTimelock.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";

contract SchnoodleTimelockFactory {
    address immutable schnoodleTimelock;

    constructor() {
        schnoodleTimelock = address(new SchnoodleTimelock());

    function create(IERC20Upgradeable token, address beneficiary, uint256 releaseTime) external returns (address) {
        address clone = Clones.clone(schnoodleTimelock);
        SchnoodleTimelock(clone).initialize(token, beneficiary, releaseTime);
        return clone;

:computer: Environment

I’m using Truffle 5.3.14.

Any ideas from the OpenZeppelin team on this?

Anyone going to answer this from OpenZeppelin??

The clones are identical. The difference is because for your 4.1 + 8.5 contract, the implementation is verified at 0x5641a75946021c420fa542c27291668a21c51f06. For your other contracts the implementation is not verified and thus the clone is not recognized by Etherscan. For example, for 4.1 + 8.6 the implementation is 0x7d8ae8de591a4193ffb9d81d4545fdf799b0f5de.

This problem is not due to OpenZeppelin Contracts.

You've misunderstood my question. I know 4.1 + 0.8.5 is verified and that the others are not. That's literally the point of and exactly what I said in my question.

So, why are they not verified? As your colleague @Amxx says here, Etherscan should recognise the clones and display them correctly if the implementation contract is verified, which it is in all my cases.

So, please can you explain why the clones are not verified under 4.2 even though the implementation is? This seems like a clear bug as it works absolutely fine under 4.1.

I can see on Etherscan that the implementations are not verified except the one corresponding to the only clone that you see verified.

I don't know where you got the implementation addresses that you linked in the table, but they're wrong. If you look at clone #1 you will see that Etherscan says "Minimal Proxy Contract for 0x5641a75946021c420fa542c27291668a21c51f06". This is a different implementation address from the one you included in the table.

So, where did you get these implementation addresses? I got the correct ones from looking at the bytecode for each clone, but this is a cumbersome process.


Ah, thank you. That helped.

I was effectively deploying the implementation contract twice, neglecting the fact that it's already being deployed as part of the factory contract (because it's imported). I was also deploying the implementation contract through a migration script in addition to the factory contract, and I was verifying that one instead (using truffle run verify).

I now realise that the contract shown as the 'To' address on the create_0_1 internal txn of the initial 'Contract Creation' txn on the factory contract is the one that must be verified. This results in correct behaviour.

The reason it was verified for 4.1 + 0.8.5 is because it was matching with a previous deployment of the implementation contract before I utilised the minimal proxy pattern.

Thanks for your help - your observations pointed me in the right direction. And apologies for presuming it was a bug when in fact it was my release pipeline that was incorrect.