Calling initialize() after deployment from a factory reverts

:computer: Environment

Truffle, @openzeppelin/upgrades, @openzeppelin/test-helpers

:memo:Details

I guess my question is how can I initialize after the deployment?

this.proxy = await this.project.createProxy(MyContract);
this.proxy.methods.initialize(tokenName, tokenSymbol).send();

the above will revert… there is probably a way but I’m failing to find it!

the below work:

this.proxy = await this.project.createProxy(MyContract, { initMethod: 'initialize', initArgs: [tokenName, tokenSymbol] });

thanks,

1 Like

Hi @CIX,

What type of project are you using? SimpleProject or ProxyAdminProject? Are you able to share your test/script?

The following is based on the example: https://github.com/OpenZeppelin/openzeppelin-sdk/tree/master/examples/upgrades-library

I modified the script so that initialize is called in a separate transaction.

Though we should initialize as part of creating the proxy, otherwise anyone can create a transaction that calls initialize.

const Web3 = require('web3');
const { Contracts, ProxyAdminProject, ZWeb3 } = require('@openzeppelin/upgrades')

async function main() {
  // Create web3 provider and initialize OpenZeppelin upgrades
  const web3 = new Web3('http://localhost:8545');
  ZWeb3.initialize(web3.currentProvider)

  // Create an OpenZeppelin project
  const [from] = await ZWeb3.eth.getAccounts();
  const project = new ProxyAdminProject('MyProject', null, null, { from, gas: 1e6, gasPrice: 1e9 });

  // Deploy an instance of MyContractV0
  console.log('Creating an upgradeable instance of v0...');
  const MyContractV0 = Contracts.getFromLocal('MyContractV0');
  //const instance = await project.createProxy(MyContractV0, { initArgs: [42] });
  const instance = await project.createProxy(MyContractV0);
  await instance.methods.initialize(42).send();

  const address = instance.options.address;
  console.log(`Contract created at ${address}`);

  // And check its initial value
  const initialValue = await instance.methods.value().call();
  console.log(`Initial value is ${initialValue.toString()}\n`);

  // Upgrade it to V1
  console.log('Upgrading to v1...');
  const MyContractV1 = Contracts.getFromLocal('MyContractV1');
  const instanceV1 = await project.upgradeProxy(instance.options.address, MyContractV1);
  console.log(`Contract upgraded at ${instanceV1.options.address}`);

  // And check its new `add` method, note that we use instanceV1 since V0 has no `add` in its ABI
  await instanceV1.methods.add(10).send({ from, gas: 1e5, gasPrice: 1e9 });
  const newValue = await instance.methods.value().call();
  console.log(`Updated value is ${newValue.toString()}\n`);
}

main();

Hi @abcoathup,

const MyContract = Contracts.getFromNodeModules('@openzeppelin/contracts-ethereum-package', 'ERC20PresetMinterPauserUpgradeSafe');

const initialSupply = web3.utils.toWei('240', 'ether');
const tokenName = 'Name_test';
const tokenSymbol = 'Symbol_test';

contract('tokenTest', function ([admin, anotherAccount, recipient]) {
    beforeEach(async function () {
        this.project = await TestHelper();
        this.proxy = await this.project.createProxy(MyContract, { initMethod: 'initialize', initArgs: [tokenName, tokenSymbol] });
        await this.proxy.methods.mint(admin, initialSupply).send();
      }) 

    describe('initialize', function () {

    it('should initilize the contract if it was not initialize', async function () {
      const newProxy = await this.project.createProxy(MyContract);
      await newProxy.methods.initialize(tokenName, tokenSymbol).send();
    })

    it('should not be able to initialize the contract more than once', async function () {
      await expectRevert(this.proxy.methods.initialize(tokenName, tokenSymbol).send(),
      'transaction: revert Contract instance has already been initialized'
      );
    })
      })
})

This is what I have been using, all my tests work the first initialize() function is the only one failing. moreover, when I manually use the SDK to deploy ERC20PresetMinterPauserUpgradeSafe and then try to use truffle/web3 to initialize the contract I get a revert.

myContract.methods.initialize(name, symbol).send()

When I create the proxy via the SDK I can then call later the function initialize() with “npx oz send-tx”
So I guess there must be a way or m’I not understanding this right?

? Pick a contract to deploy @openzeppelin/contracts-ethereum-package/ERC20PresetMinterPauserUpgradeSafe
All implementations are up to date
? Call a function to initialize the instance after creating it? No
Possible initialization method (initialize) found in contract. Make sure you initialize your instance.
✓ Instance created at 0x8A15026B0b594C295bef7E40204AA87CF4adac01
To upgrade this instance run ‘oz upgrade’
0x8A15026B0b594C295bef7E40204AA87CF4adac01
? Pick a network development
? Pick an instance ERC20PresetMinterPauserUpgradeSafe at 0x8A15026B0b594C295bef7E40204AA87CF4adac01
? Select which function initialize(name: string, symbol: string)
? name: string: name_test
? symbol: string: SMB
✓ Transaction successful. Transaction hash: 0xeeb4d424bd2b769885a1007e1c293ee3e2382515535ca6fdda423fae21ef8f51

I hope this is a little clearer.

Thanks again for your help

Hi @abcoathup,

I found the solution it’s

await newProxy.methods.initialize(tokenName, tokenSymbol).send({gas: 1e6, gasPrice: 1e9});

I gues what threw me off is that I didn’t get an out of gas…

also you are right, anyone can call the initialize contract after the creation.

did you guys discuss about adding a rule that only the initial msg.sender or the admin_role can call the function? Would make sense, I think!

thanks again,

CIX

1 Like

Hi @CIX,

I’m glad that you were able to resolve.

I don’t know if this was discussed previously. I think the concern would be adding complexity, when the best practice is calling initialize when the proxy is created.