I have a UUPS Upgradeable ERC20 contract deployed to a testnet using Truffle. Let's call it ContractA.sol
, which looks like the following:
contractA.sol:
// pragma, imports and docstrings
contract ContractA is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, AnotherContractA, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() initializer {}
function initialize(
string memory _name,
string memory _symbol,
address _somevariable1
) public initializer {
__ERC20_init(_name, _symbol);
__ERC20Burnable_init();
__UUPSUpgradeable_init();
__AnotherContractA_init(_somevariable1);
}
function _authorizeUpgrade(address newImplementation) onlyOwner internal override {}
}
Now I want to add new features to this contract by inheriting AnotherContractB
, which includes _somevariable2
that also needs to be initialized like _somevariable1
. So I create ContractB
that will inherit ContractA
and AnotherContractB
like so:
contractB.sol:
// pragma, imports and docstrings
contract ContractB is ContractA, AnotherContractB {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() initializer {}
function initialize(
address _somevariable2
) public initializer {
__AnotherContractB_init(_somevariable2);
}
}
However, I keep getting the error The contract code couldn't be stored, please check your gas limit.
when migrating. I think it's because I have two initializers (one in ContractA
and another in ContractB
)? Basically, how do I initialize a new variable from a new contract when upgrading?
Hi @lalalei, I'm not sure if your error is related, but I noticed a few issues with your contract:
- It is using
onlyOwner
so it should have import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
, extend from OwnableUpgradable
, and call __Ownable_init();
in its initializer
-
contractB
extends from contractA
so they cannot both have constructor() initializer {}
because the initializer
modifier can only be called once.
Regarding your question, an initializer function cannot be called during upgrade because the contract instance (the proxy itself) was already initialized when it was first deployed. Instead, you can call an arbitrary function during upgrade (but that function cannot be an initializer).
For example, you can upgrade and call a function at the same time like this:
const upgraded = await upgradeProxy(contractA.address, ContractB, {
call: { fn: 'yourUpgradeFunction', args: ['argument for your upgrade function'] },
});
1 Like
Thanks so much for your answer, @ericglau !
Regarding point 1, yes, I actually imported OwnableUpgradeable
. I removed it along with the other inherited contracts in my snippet for conciseness and I forgot that the contract uses onlyOwner
. But thanks for pointing that out! Will edit it.
Regarding point 2 and your answer, I changed ContractB.sol
to just:
ContractB.sol
// pragma, imports and docstrings
contract ContractB is ContractA, AnotherContractB {
}
While my deployer looks like this now:
let somevariable2 = 'foo';
const instance = await upgradeProxy(contractA.address,
ContractB, {
deployer, call: { fn: '__AnotherContractB_init', args: [somevariable2] },
});
However, when migrating, I get this error:
TypeError: contract.methods[call.fn] is not a function
Edit: Oops. My bad. __AnotherContractB_init
has the internal
modifier, which should be made external/public. Andinitializer
modifier, which should be removed. This solves the issue.
Thanks very much @ericglau ! You've been very helpful.