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.
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().