to recreate…
Lets say I’ve got a token.sol it is is Initializable, ERC20, ERC20Detailed, ERC20Burnable, ERC20Pausable
Did a full upgradable deploy… But after this deploy I noticed that the contract’s initializer only initialized ERC20Detailed and ERC20Burnable ERC20Detailed.initialize(name, symbol, decimals); ERC20Mintable.initialize(msg.sender);
Modifying the token contract with a new method function initPausable() public { ERC20Pausable.initialize(msg.sender); } I expect to be able to init the Pausable class…
No such luck. No mater what I do, I get :
ALERT: [ethjs-rpc] rpc error with payload {"id":8737563104927,"jsonrpc":"2.0","params":["0xf86f0a8504a817c800832dc6c09483752ecafebf4707258dedffbd9c7443148169db8084e2b646238602d6d5a8bcf7a0c2f6bfdcfe0c75fbdd3b0173a78049a669558696a5e3b75c5ce98bfb680bc955a01bcff7758e610aac16f0833c06e847b3b99c2745f1543156d488c35ea2337e04"],"method":"eth_sendRawTransaction"} Error: VM Exception while processing transaction: revert Contract instance has already been initialized
Add an initializePausable function to the contract.
function initializePausable(address sender) public {
ERC20Pausable.initialize(sender);
}
Then upgrade the contract, which fails with a VM revert. npx zos update MyToken --init initializePausable --args 0x90da32D93d03cA2cC5693e64E0d00c64F53E3FD6
I assume that when you inherit from ERC20Pausable but don’t explicitly initialize, it is implicitly initialized but without assigning the PauserRole to any address. Meaning that you can’t then Pause the token.
The ERC20Pausable contract initializer was not run, but the entire contract is marked as initialized. Which means you can’t initialize it again.
You could upgrade the contract and add a Pauser, though you would need to protect this initialization so that it is only called once, otherwise anyone could call this function and add a Pauser.
I recommend testing any upgrades, including testing prior to upgrading in production:
Added initializePausable with protection to prevent it being run again
Depending on how you wish to perform the upgrade will depend if you update the existing MyToken contract or have a version 2.
pragma solidity ^0.5.0;
import "zos-lib/contracts/Initializable.sol";
import "openzeppelin-eth/contracts/token/ERC20/ERC20Detailed.sol";
import "openzeppelin-eth/contracts/token/ERC20/ERC20Mintable.sol";
import "openzeppelin-eth/contracts/token/ERC20/ERC20Burnable.sol";
import "openzeppelin-eth/contracts/token/ERC20/ERC20Pausable.sol";
contract MyToken2 is Initializable, ERC20Detailed, ERC20Burnable, ERC20Mintable, ERC20Pausable {
bool private _pausableInitialized;
function initialize(address sender, string memory name, string memory symbol, uint8 decimals) public initializer {
ERC20Detailed.initialize(name, symbol, decimals);
ERC20Mintable.initialize(sender);
// Missing: ERC20Pausable.initialize(sender)
}
function initializePausable(address sender) public {
require(!_pausableInitialized, "MyToken2: pausable has already been initialized");
_pausableInitialized = true;
_addPauser(sender);
}
}
I updated the source code of the contract to add initializePausable with the check if it had already been run and followed the instructions on Upgrading
As well as automated tests for the upgrade. I manually tested in the console to check that the Pauser had been added and that calling initializePausable reverted when called again to prevent anyone from adding a new pauser using the initializePausable function again.
truffle(ganache)> m = await MyToken.deployed()
truffle(ganache)> m.isPauser("0x7BbD3B8c606ffA0fCC54F5852719eF0C00f929b0")
false
truffle(ganache)> m.isPauser("0x90da32D93d03cA2cC5693e64E0d00c64F53E3FD6")
true
truffle(ganache)> m.initializePausable("0x7BbD3B8c606ffA0fCC54F5852719eF0C00f929b0")
Thrown:
{ Error: Returned error: VM Exception while processing transaction: revert MyToken2: pausable has already been initialized -- Reason given: MyToken2: pausable has already been initialized.
Thank you so very much… Your test gave me a clue as well as your initializePausable method…
So I was trying to do ERC20Pausable.initialize(sender) inside my initializePausable method and it was failing… but using _addPauser(sender) worked
Also your test helped me realize I should be initializing when using zos update
so zod update MyToken --init initializePausable --args “MyAddress” worked perfectly (It utilizes the “Upgrade and Call” feature of the admin/upgrade contracts
Awesome. Glad I could help. Thanks for being part of the community @genecyber.
The main thing is to make sure that the initializePausable is protected so that it can only be called once, otherwise everyone would make themselves a Pauser
I should have put my zos commands in my reply. (I have done an update to add these in for completeness).
A note on house keeping, I moved the topic to the #support:zeppelinos category and marked my reply as the solution and removed Solved from the title, as we get a little check box instead.