I'm currently using a Transparent Proxy and writing some tests to make sure that the implementation contract was initialized.
The basic idea is the following:
//coin is our proxy
const CoinV2Factory = await ethers.getContractFactory("CoinV2");
await upgrades.upgradeProxy(
coin.address,
CoinV2Factory,
{
call: {fn: "initialize", args: ["Coin Name", "COIN"]}
}
);
const impl2Address = await getImplementationAddress(ethers.provider, coin.address);
const implementation = await ethers.getContractAt("CoinV2", impl2Address);
await expect(implementation.initialize("Coin Name", "COIN")).to.be.reverted;
However, I am getting the error "'Initializable: contract is already initialized'".
Disclaimer: I know initializing the Name/Symbol of the Token does not matter from a security perspective, but just giving a simple example.
There are 3 different concepts here:
Initializing a proxy
Re-initializing a proxy during an upgrade
Initializing the implementation itself to prevent someone else from initializing it
1 and 2 are done by initializers or reinitializers (see https://docs.openzeppelin.com/contracts/4.x/api/proxy#Initializable ).
Initializers or reinitializers can be called from deployProxy
or upgradeProxy
as in your example above -- these happen in the context of the proxy's address.
3 is done by having a constructor like the below, which happens in the context of the implementation. See https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#initializing_the_implementation_contract
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
This sets the initialized version to the maximum value for the implementation itself, which prevents someone from calling an initializer or reinitializer in the context of the implementation address.
1 Like
Musk
November 13, 2022, 5:32am
3
Great write up! Just wanted to note that proxies have their own state, hence why front running is not an issue here with the public initialize()
.