UUPSUpgradeable contract - how to verify implementation contract

Hi, first time posting here, hopefully I'm doing this correctly.

I'm trying to deploy a UUPSUgradeable contract. I was able to deploy it locally on Ganache and then on the ropsten testnet, but I am running into an issue with verifying the implementation contract on ropsten etherscan.

TTP8.sol is my implementation contract (see below)
I've also included the content of 2_deploy_contracts.js below.

I deployed the following 3 contracts:
TestTP8 - 0x42d613469421a1D33fCc8e6D5D9F230aF97E57e9
ProxyAdmin - 0x459D8AD49E61e314469B039bf67b1308a3362a30
TransparentUpgradeableProxy - 0x401C312d588d879F58BC12B55Ff293D6b2F7eaE0 --> I also have a side question about this one. From all the forum help I reviewed, I keep seeing that this proxy should be called 'AdminUpgradeabilityProxy' and the fact that I generated the code using the OpenZeppelin Wizard by selecting UUPSUpgradeable instead of Transparent makes me wonder why I see get a TransparentUpgradeableProxy. Would you be able to help me understand this point to see if I did anything wrong?

Now, with regards to my verify issue:
I tried it with 1) truffle-verify-plugin and 2) truffle-flattener. I've been using truffle-verify-plugin successfully on ropsten for other test contracts I deployed that are not upgradeable. But I get this output when I try it on my TTP8 upgradeable implementation contract:

C:\Solidity\ttp8>truffle run verify testtp8 --network ropsten
Verifying testtp8
Fail - Unable to verify
Failed to verify 1 contract(s): testtp8

I found in a different forum question that someone has used the truffle-flattener successfully instead, so I tried it as well and unfortunately, could not get it to work. I got this error:

C:\Solidity\ttp8>truffle-flattener .\contracts\ttp8.sol
(node:13420) Warning: Accessing non-existent property 'INVALID_ALT_NUMBER' of module exports inside circular dependency
(Use `node --trace-warnings ...` to show where the warning was created)
(node:13420) Warning: Accessing non-existent property 'INVALID_ALT_NUMBER' of module exports inside circular dependency
Error: Could not parse C:\Solidity\ttp8\node_modules\@openzeppelin\contracts-upgradeable\token\ERC20\ERC20Upgradeable.sol for extracting its imports: ParserError: missing ';' at '{' (164:18)
    at getDependencies (C:\Users\name\AppData\Roaming\npm\node_modules\truffle-flattener\index.js:44:11)
    at dependenciesDfs (C:\Users\name\AppData\Roaming\npm\node_modules\truffle-flattener\index.js:64:24)
    at async dependenciesDfs (C:\Users\name\AppData\Roaming\npm\node_modules\truffle-flattener\index.js:73:7)
    at async getSortedFilePaths (C:\Users\name\AppData\Roaming\npm\node_modules\truffle-flattener\index.js:83:5)
    at async flatten (C:\Users\name\AppData\Roaming\npm\node_modules\truffle-flattener\index.js:180:23)
    at async main (C:\Users\name\AppData\Roaming\npm\node_modules\truffle-flattener\index.js:237:3)

I've tried to use the multi file verify option on ropsten etherscan by uploading all the respective .sol files that I see in my build folder, but that did not work as well (I was not able to find any good instructions on how to use the multifile verify option on etherscan in general, is there any good resources on this that anyone can share?). I got these errors (this is just the start of it, the list of errors goes on and on in a similar format):

ParserError: Source "utils/ContextUpgradeable.sol" not found: File import callback not supported
 --> AccessControlUpgradeable.sol:6:1:
  |
6 | import "../utils/ContextUpgradeable.sol"
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

ParserError: Source "utils/StringsUpgradeable.sol" not found: File import callback not supported
 --> AccessControlUpgradeable.sol:7:1:
  |
7 | import "../utils/StringsUpgradeable.sol"
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

ParserError: Source "utils/introspection/ERC165Upgradeable.sol" not found: File import callback not supported
 --> AccessControlUpgradeable.sol:8:1:
  |
8 | import "../utils/introspection/ERC165Upgradeable.sol"
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

ParserError: Source "proxy/utils/Initializable.sol" not found: File import callback not supported
 --> AccessControlUpgradeable.sol:9:1:
  |

Thanks a lot for your help!

:1234: Code to reproduce

TTP8.sol -->

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

import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract TestTP8 is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, PausableUpgradeable, AccessControlUpgradeable, UUPSUpgradeable {
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");

    function initialize() initializer public {
        __ERC20_init("TestTP8", "TTP8");
        __ERC20Burnable_init();
        __Pausable_init();
        __AccessControl_init();
        __UUPSUpgradeable_init();

        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _setupRole(PAUSER_ROLE, msg.sender);
        _mint(msg.sender, 25000 * 10 ** decimals());
        _setupRole(MINTER_ROLE, msg.sender);
        _setupRole(UPGRADER_ROLE, msg.sender);
    }

    function pause() public onlyRole(PAUSER_ROLE) {
        _pause();
    }

    function unpause() public onlyRole(PAUSER_ROLE) {
        _unpause();
    }

    function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
        _mint(to, amount);
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount)
        internal
        whenNotPaused
        override
    {
        super._beforeTokenTransfer(from, to, amount);
    }

    function _authorizeUpgrade(address newImplementation)
        internal
        onlyRole(UPGRADER_ROLE)
        override
    {}

        //Extra code added from this point on
    function burnFrom(address account, uint256 amount) public virtual override {
        require(
            hasRole(DEFAULT_ADMIN_ROLE, _msgSender()),
            "ModifiedAccessControl: Only DEFAULT_ADMIN_ROLE may use burnFrom"
        );

        super.burnFrom(account, amount);
    }


    function burn(uint256 amount) public virtual override {
      require(
            hasRole(DEFAULT_ADMIN_ROLE, _msgSender()),
            "ModifiedAccessControl: Only DEFAULT_ADMIN_ROLE may use burn"
        );

        super.burn(amount);
    }

    function multiTransfer(address[] calldata dests, uint256[] calldata values) external returns (uint256) {
       
        uint256 i = 0;
        while (i < dests.length) {
        transfer(dests[i], values[i]);
        i += 1;
        }
        return(i);
    }
}

2_deploy_contracts.js -->

const TestTP8 = artifacts.require('TestTP8');
 
const { deployProxy } = require('@openzeppelin/truffle-upgrades');
 
module.exports = async function (deployer) {
  await deployProxy(TestTP8, [], { deployer, initializer: 'initialize' });
};

:computer: Environment

Truffle v5.4.6 (core: 5.4.6)
Solidity - 0.8.4 (solc-js)
Node v14.17.5
Web3.js v1.5.1

1 Like

Sorry about the delay in responding here.

You need to specify kind: 'uups' as an option to deployProxy. We're working on detecting this automatically if the contract inherits UUPSUpgradeable. Sorry it's not working like that yet.

I haven't used the Truffle verify plugin, but it looks strange that you're specifying the contract name in lowercase. Shouldn't it be TestTP8 like the contract name?

I would strongly suggest you keep tyring to get the Truffle verify plugin to work, instead of veifying manually on Etherscan. You can first try to deploy and verify a simple non-upgradeable contract, just to make sure that you know it works. Then proceed with the upgradeable contract.

The error might be because in the artifact file for TestTP8 the upgrades plugin will store the address of the proxy. You will not be able to verify that contract as TestTP8 because it contains the code for the proxy. The proxy should be automatically verified on Etherscan, but you can verify the implementation contract using the plugin by calling npx truffle run verify --network rinkeby TestTP8@<impl address>. Where <impl address> is the address of the implementation. You can obtain this using the function upgrades.erc1967.getImplementationAddress(proxyAddress) (note that this function is included in the latest release so you may need to update your dependency).

1 Like

Thank you! I ended up using hardhat instead of truffle and was able to verify the implementation contract.

Hi @Pck2wan ,

I tried to compile with hardhat as follows:
npx hardhat verify --network goerli <implementation addr>

and

npx hardhat verify --network goerli NFTLottery@<implementation addr>

However, I receive the following:

An unexpected error occurred:

[Error: ENOENT: no such file or directory, open 'Project\artifacts\build-info\248d95627692f80a08e60d945c32a647.json'] {
  errno: -4058,
  code: 'ENOENT',
  syscall: 'open',
  path: 'Project\\artifacts\\build-info\\248d95627692f80a08e60d945c32a647.json'
}

and

ProviderError: HttpProviderError
    at HttpProvider.request (Project\node_modules\hardhat\src\internal\core\providers\http.ts:78:19)
    at LocalAccountsProvider.request (Project\node_modules\hardhat\src\internal\core\providers\accounts.ts:187:34)
    at getStorageAt (Project\node_modules\@openzeppelin\upgrades-core\src\provider.ts:47:19)
    at getStorageFallback (Project\node_modules\@openzeppelin\upgrades-core\src\eip-1967.ts:51:15)
    at getImplementationAddress (CProject\node_modules\@openzeppelin\upgrades-core\src\eip-1967.ts:21:19)
    at isTransparentOrUUPSProxy (Project\node_modules\@openzeppelin\upgrades-core\src\eip-1967-type.ts:13:5)
    at verify (Project\node_modules\@openzeppelin\hardhat-upgrades\src\verify-proxy.ts:94:7)
    at OverriddenTaskDefinition._action (Project\node_modules\@openzeppelin\hardhat-upgrades\src\index.ts:156:12)
    at Environment._runTaskDefinition (Project\node_modules\hardhat\src\internal\core\runtime-environment.ts:308:14)
    at Environment.run (Project\node_modules\hardhat\src\internal\core\runtime-environment.ts:156:14)

respectively.

Any suggestions what is going on ?

EDIT: I found the fix from - https://ethereum.stackexchange.com/questions/121176/error-when-trying-to-verify-contract

2 Likes