0

I have an ERC20 token already deployed on the Ropsten testnet with two versions.

V1 is a simple unproxied ERC20 token and looks like this:

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyToken is ERC20, Ownable {
    constructor() ERC20("MyToken", "MTK") {}

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

I can interact with this contract using web3:

const Web3 = require('web3');
const MyToken = require('./build/contracts/MyToken.json');
const HDWalletProvider = require('@truffle/hdwallet-provider');

const provider = new HDWalletProvider(process.env.ACCOUNT_SECRET, process.env.INFURA_URL);
const web3 = new Web3(provider);
const contract = new web3.eth.Contract(MyToken.abi, process.env.CONTRACT_ADDRESS);

For example, here is a call that retrieves the owner of the contract:

await contract.methods.owner().call();

On the other hand, V2 is a UUPS upgradeable contract which looks like this:

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

import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract MyToken is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() initializer {}

    function initialize() initializer public {
        __ERC20_init("MyToken", "MTK");
        __Ownable_init();
        __UUPSUpgradeable_init();
    }

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

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

To interact with V2 using the same web3 nodejs code, I tried updating the build/abi as well as process.env.CONTRACT_ADDRESS from V1's address to V2's. However, whenever I retrieve the owner using the same code, it always returns the zero address.

I think the call should be proxied or something, but I don't know how and I can't find resources (docs/tutorials) on this.

  • Contracts V1 and V2 are generated from wizard.openzeppelin.com. Nothing was modified.

  • V2 passes the get owner, symbol, and name truffle tests.

TylerH
  • 20,799
  • 66
  • 75
  • 101
L. Lei
  • 31
  • 4

1 Answers1

0

I'm not really sure what's the best practice regarding the initializer {} modifier in constructor(). My guess is that OpenZeppelin recommends to use it with constructor in case you're also setting other variables in constructor and not using other init function.

However, the effect of your implementation is that it simply sets the initialized variable to true without executing the top-level initialize() function - effectively not setting the owner variable (and others, such as name and symbol).

I was able to perform a quick fix by removing the initializer modifier (since it's already with the initialize() function, and calling the initialize() from the constructor. Please check if there aren't any side effects to it.

// removed the modifier
// added the call to `initialize()`
constructor() {
    initialize();
}

// stays the same
function initialize() initializer public {
Pang
  • 9,564
  • 146
  • 81
  • 122
Petr Hejda
  • 40,554
  • 8
  • 72
  • 100
  • Thanks for your answer! However, (my bad) I probably should have added that V1 and V2 are generated from `wizard.openzeppelin.com`. Absolutely nothing was modified. Regarding the `constructor() initializer {}`, here is the [reference](https://forum.openzeppelin.com/t/security-advisory-initialize-uups-implementation-contracts/15301) as to why it's been added as a security standard. So I'm kind of hesitant to modify that part of the code. Moreover, I should mention that V2 passes the get owner, symbol, name, etc truffle tests. – L. Lei Nov 05 '21 at 13:01
  • @L.Lei I see. I was able to reproduce your issue (by generating the "faulty" source code from the wizard). From [this discussion](https://forum.openzeppelin.com/t/error-when-deploying-a-openzeppelin-wizard-smart-contract-using-remix-missing-admin-role/17845) it seems that upgradable OZ contracts are generally not working in Remix. I'll try to dig deeper into this when I have time. – Petr Hejda Nov 05 '21 at 13:43
  • Yes, instead of remix, I used [OZ upgrades plugins](https://docs.openzeppelin.com/upgrades-plugins/1.x/truffle-upgrades) (the nodejs package mentioned in your link) with Truffle for the deployment and testing of my V2 since it's UUPS proxied (I would also like to note that in post that you linked, the op was using Transparent proxy). So far, they worked just fine according to the docs. I just really can't figure out how to interact with a UUPS upgradeable contract like V2 using web3. Also, I do appreciate your trying to help, thank you! – L. Lei Nov 05 '21 at 14:01