Deploying two identical contracts using Truffle and referencing them in the future upgrades

Repro steps

  1. mkdir repro && cd repro
  2. npm init -y
  3. Install npm packages
npm i --save-dev truffle &&
npm i --save-dev @openzeppelin/truffle-upgrades &&
npm i --save-dev @openzeppelin/contracts-upgradeable &&
npm i --save-dev @truffle/hdwallet-provider
  1. npx truffle init

  2. contracts/AppleCoin.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.0;

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

contract OrangeCoin is ERC20PresetMinterPauserUpgradeable {    
    function initialize(string memory name, string memory symbol) public override virtual initializer {
        ERC20PresetMinterPauserUpgradeable.initialize(name, symbol);
    }
}

contract AppleCoin is ERC20PresetMinterPauserUpgradeable {    
    function initialize(string memory name, string memory symbol) public override virtual initializer {
        ERC20PresetMinterPauserUpgradeable.initialize(name, symbol);
    }
}
  1. migrations/2_deploy_orange_apple.js
const AppleCoin = artifacts.require('AppleCoin');
const OrangeCoin = artifacts.require('OrangeCoin');

const { deployProxy } = require('@openzeppelin/truffle-upgrades');
 
module.exports = async function (deployer) {

    await deployProxy(AppleCoin,  ["Apple Coin", "ACN"],  { deployer, unsafeAllowCustomTypes : true, initializer: 'initialize' });
    await deployProxy(OrangeCoin, ["Orange Coin", "OCN"], { deployer, unsafeAllowCustomTypes : true, initializer: 'initialize' });
};
  1. Put the required stuff in truffle-config.js such as Infura key

  2. npx truffle migrate network --rinkeby

Result

Some data is stored in .openzeppelin/rinkeby.json

Some data is stored in build/contracts/AppleCoin.json and OrangeCoin.json

Does it mean I need to keep build folder in the repo to keep track of deployments?
(currently I had it in .gitignore)

Some data is displayed in the truffle console

2_deploy_orange_apple.js
========================

Warning: Potentially unsafe deployment of AppleCoin

    You are using the `unsafeAllowCustomTypes` flag to skip storage checks for structs and enums.
    Make sure you have manually checked the storage layout for incompatibilities.


   Deploying 'AppleCoin'
   ---------------------
   > transaction hash:    0x610808c26130f970cb0cd806059ca982ee09c9f48b917e393773c1b7514d6f8e
   > Blocks: 1            Seconds: 12
   > contract address:    0x70a53f2E794EBBaBb3e860Fe457BFa6f9a528778
   > block number:        7836548
   > block timestamp:     1609775689
   > account:             0xBAE8E5F877E15df0a1Ec36bFa6a9903B837d003a
   > balance:             7.6445294721
   > gas used:            1803517 (0x1b84fd)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.03607034 ETH

   Pausing for 1 confirmations...
   ------------------------------
   > confirmation number: 1 (block: 7836549)

   Deploying 'ProxyAdmin'
   ----------------------
   > transaction hash:    0x1f869f042afb812571f295e32f0ae3285abe72ac4661442d97ad1c7e7f29498c
   > Blocks: 2            Seconds: 24
   > contract address:    0xa0b8826281583Bd05a644392158e1DC1C106Ec9B
   > block number:        7836551
   > block timestamp:     1609775734
   > account:             0xBAE8E5F877E15df0a1Ec36bFa6a9903B837d003a
   > balance:             7.6269302321
   > gas used:            879962 (0xd6d5a)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.01759924 ETH

   Pausing for 1 confirmations...
   ------------------------------
   > confirmation number: 1 (block: 7836552)

   Deploying 'AdminUpgradeabilityProxy'
   ------------------------------------
   > transaction hash:    0x906643ac72752811841c88febf0782612e816c1ded52c48efea9649100751a64
   > Blocks: 1            Seconds: 24
   > contract address:    0x0EF0eEBD75eb3cAf29a4bfc3348cF998cf5624e5
   > block number:        7836554
   > block timestamp:     1609775779
   > account:             0xBAE8E5F877E15df0a1Ec36bFa6a9903B837d003a
   > balance:             7.6088829121
   > gas used:            902366 (0xdc4de)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.01804732 ETH

   Pausing for 1 confirmations...
   ------------------------------
   > confirmation number: 1 (block: 7836555)

Warning: Potentially unsafe deployment of OrangeCoin

    You are using the `unsafeAllowCustomTypes` flag to skip storage checks for structs and enums.
    Make sure you have manually checked the storage layout for incompatibilities.


   Deploying 'AdminUpgradeabilityProxy'
   ------------------------------------
   > transaction hash:    0x4bb22381d055686fae41e8fa0f75a88fa5224d2e6b6dae9465a94e04a6b2bdb5
   > Blocks: 1            Seconds: 20
   > contract address:    0x919720A1C2B31d9c706ac0853f5b8B3B6466C297
   > block number:        7836557
   > block timestamp:     1609775824
   > account:             0xBAE8E5F877E15df0a1Ec36bFa6a9903B837d003a
   > balance:             7.5908353521
   > gas used:            902378 (0xdc4ea)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.01804756 ETH

   Pausing for 1 confirmations...
   ------------------------------
   > confirmation number: 1 (block: 7836558)

   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.08976446 ETH


Summary
=======
> Total deployments:   4
> Final cost:          0.08976446 ETH

Deployed contracts:

Verify on Etherscan: truffle-flattener contracts/OrangeCoin.sol > merged.sol (and remove duplicate SPDX)

EDIT / UPDATE:

As I’m figuring out what is going on will surely update this post :innocent:

I think I’ll create a base contract and add a dummy function so that the contracts are not identical.

I think there are some “optimisations” along the way that try to preserve gas. This might be simpler, maybe, potentially.

contract OrangeCoin is ERC20PresetMinterPauserUpgradeable {    

    function dummyOrange() public returns(string memory) {
        return "orange";
    }

    function initialize(string memory name, string memory symbol) public override virtual initializer {
        ERC20PresetMinterPauserUpgradeable.initialize(name, symbol);
    }
}

contract AppleCoin is ERC20PresetMinterPauserUpgradeable {    

    function dummyApple() public returns(string memory) {
        return "apple";
    }

    function initialize(string memory name, string memory symbol) public override virtual initializer {
        ERC20PresetMinterPauserUpgradeable.initialize(name, symbol);
    }
}
1 Like

Hi @marsxr,

If I understand correctly, you wanted to deploy an AppleCoin and an OrangeCoin upgradeable project.

The following were deployed:

  1. AppleCoin (no need to deploy OrangeCoin as it had the same bytecode as AppleCoin)
  2. ProxyAdmin (one per project per network, see: https://docs.openzeppelin.com/upgrades-plugins/1.x/faq#what-is-a-proxy-admin)
  3. Proxy for AppleCoin
  4. Proxy for OrangeCoin

The implementation address and the ProxyAdmin address is in the network file, see the documentation for details: https://docs.openzeppelin.com/upgrades-plugins/1.x/network-files

The proxy address is stored in the Truffle artifact of the first implementation.
(I have created an issue to document this: https://github.com/OpenZeppelin/openzeppelin-upgrades/issues/270)

The following shows interacting with the contracts and the address of the proxy

$ npx truffle console --network rinkeby
truffle(rinkeby)> apple = await AppleCoin.deployed()
undefined
truffle(rinkeby)> orange = await OrangeCoin.deployed()
undefined
truffle(rinkeby)> apple.address
'0xc25BFA201Ad67C90A58996D5f051A1BbcF71adbA'
truffle(rinkeby)> orange.address
truffle(rinkeby)> await apple.name()
'Apple Coin'
truffle(rinkeby)> await orange.name()
'Orange Coin'
'0x8351b73658Bb0Fa2d44453B46f626636f4378bC0'

So that appears as I would expect.

I will need to check on this. Currently this is the only place that the proxy address is stored but the build directory also includes full path names.

1 Like

Thank you for being super awesome and having a look :+1:


When I first encountered this problem, I had a structure like this.

contracts/FruitCoin.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.0;

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

contract FruitCoin is ERC20PresetMinterPauserUpgradeable {    
    function initialize(string memory name, string memory symbol) public override virtual initializer { // I think I could skip that function, realised that only after deploying and verifying on Etherscan
        ERC20PresetMinterPauserUpgradeable.initialize(name, symbol);
    }
}

migrations/2_deploy_fruit_coins.js

const FruitCoin = artifacts.require('FruitCoin');

const { deployProxy } = require('@openzeppelin/truffle-upgrades');
 
module.exports = async function (deployer) {
    await deployProxy(FruitCoin, ["Apple Coin via Fruit",  "ACVF"], { deployer, unsafeAllowCustomTypes : true, initializer: 'initialize' });
    await deployProxy(FruitCoin, ["Orange Coin via Fruit", "OCVF"], { deployer, unsafeAllowCustomTypes : true, initializer: 'initialize' });
};

Fruitcoin implementation: https://rinkeby.etherscan.io/address/0x6d93101d8f3647104c8f0bc98b9f480815e8f0e9

Apple Coin via Fruit: https://rinkeby.etherscan.io/address/0xEBf64e4DebE23d1Baa0CD936835523242C5C5759#readProxyContract

Orange Coin via Fruit: https://rinkeby.etherscan.io/address/0xa16C44D740B1D02851FD6d788CD1174833337DAc#readProxyContract

Deployment logs

2_deploy_fruts_orange_apple.js
==============================

Warning: Potentially unsafe deployment of FruitCoin

    You are using the `unsafeAllowCustomTypes` flag to skip storage checks for structs and enums.
    Make sure you have manually checked the storage layout for incompatibilities.


   Deploying 'FruitCoin'
   ---------------------
   > transaction hash:    0x95a8b44f6391f3f1551742097042c51613b9ac314f69aa1c04ae1308310996ca
   > Blocks: 2            Seconds: 24
   > contract address:    0x6D93101d8f3647104C8f0Bc98b9f480815E8F0E9
   > block number:        7840748
   > block timestamp:     1609838698
   > account:             0xBAE8E5F877E15df0a1Ec36bFa6a9903B837d003a
   > balance:             7.1910132321
   > gas used:            1803517 (0x1b84fd)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.03607034 ETH


   Deploying 'ProxyAdmin'
   ----------------------
   > transaction hash:    0x619a1094b315d4580206aae70c8516e383c3f4073f4f01da4757fe9a60a15224
   > Blocks: 1            Seconds: 8
   > contract address:    0xAFF816C4E7e17009d35A0Fb4BD5b1CbD5c9df4ba
   > block number:        7840749
   > block timestamp:     1609838713
   > account:             0xBAE8E5F877E15df0a1Ec36bFa6a9903B837d003a
   > balance:             7.1734139921
   > gas used:            879962 (0xd6d5a)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.01759924 ETH


   Deploying 'AdminUpgradeabilityProxy'
   ------------------------------------
   > transaction hash:    0xb9f67fba3279c87aab0964157d1cffef87a778acb9352f5c94c220daf7b67d94
   > Blocks: 2            Seconds: 24
   > contract address:    0xEBf64e4DebE23d1Baa0CD936835523242C5C5759
   > block number:        7840751
   > block timestamp:     1609838743
   > account:             0xBAE8E5F877E15df0a1Ec36bFa6a9903B837d003a
   > balance:             7.1553640321
   > gas used:            902498 (0xdc562)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.01804996 ETH


Warning: Potentially unsafe deployment of FruitCoin

    You are using the `unsafeAllowCustomTypes` flag to skip storage checks for structs and enums.
    Make sure you have manually checked the storage layout for incompatibilities.


   Deploying 'AdminUpgradeabilityProxy'
   ------------------------------------
   > transaction hash:    0x8b64446086c9aa94e423a1bdbfacf11b66fe738486b1ed0593a1e6383c90a0cf
   > Blocks: 1            Seconds: 20
   > contract address:    0xa16C44D740B1D02851FD6d788CD1174833337DAc
   > block number:        7840753
   > block timestamp:     1609838773
   > account:             0xBAE8E5F877E15df0a1Ec36bFa6a9903B837d003a
   > balance:             7.1373138321
   > gas used:            902510 (0xdc56e)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.0180502 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.08976974 ETH

I think the setup above is out of luck

I think that “pass through inheritance” (initial post) is a better strategy

contract OrangeCoin is FruitCoin {  
 // empty
}

contract AppleCoin is FruitCoin { 
 // empty
}
1 Like

Hi @marsxr,

You can add the truffle build artifacts to version control, but please note Truffle artifacts include full paths.

For upgrading, we need the address of the proxy, see: https://docs.openzeppelin.com/upgrades-plugins/1.x/api-truffle-upgrades#upgrade-proxy

Rather than ERC20PresetMinterPauserUpgradeable.initialize I would probably call __ERC20PresetMinterPauser_init in your initialize.

Pass through inheritance where you have multiple of the same token in the one project is cleaner, because then you can use the Truffle artifacts to obtain the proxy, but with gas prices the way they are, sharing the one implementation contract is much cheaper.

I’ve commented on that on the GitHub issue: https://github.com/OpenZeppelin/openzeppelin-upgrades/issues/270#issuecomment-755230579

Using one address as another is messy. CODE SMELL

Then, when using plugin: Easy multifile verification on block explorers - #5 by marsxr it forces to me manually override the address.