wighawag / hardhat-deploy

hardhat deployment plugin
MIT License
1.17k stars 283 forks source link

How to test upgradable contracts #413

Open CodeWarriorr opened 1 year ago

CodeWarriorr commented 1 year ago

How to properly test upgradable contracts ? There is no mention in documentation and i need to perform tests before and after the upgrade is deployed. I've tried to fork testnet with deployed version of code but no real progress there so far. Could you please help ?

CodeWarriorr commented 1 year ago

I have SOME solution, its very far from ideal, surely there must be a better way to do this. I've described the solution here: https://ethereum.stackexchange.com/a/143342/115007

If you have any advice here, please share it with me, I'd be very grateful.

jack-wombat commented 1 year ago

hardhat-upgrades has a validateUpgrade function

CodeWarriorr commented 1 year ago

@jack-wombat thank you for your suggestion. I do use hardhat-deploy and I want to use all its features because it's awesome. I also do not have V2 versions of contract, I keep the contracts the same, I'm just moving forward along on one set of contracts while keeping storage in check. I just want to be extra sure and test it. I know that @wighawag mentioned upgradeIndex as a tool, but the explanation was very vague and the feature is not documented. I've tried poking around with upgradeIndex but its not working for me (I probably don't know how to use it, or maybe this should be used from the start, not added after deployment )

lookeey commented 1 year ago

I have spent some time myself debugging a similar issue, as I was testing the upgrades themselves too. Specifically, my issue was that the contracts wouldn't be upgraded in tests, but rather redeployed with a new proxy. It turned out that deployments.run([...]) has a default option resetMemory, which erased previous deployments, so running await deployments.run(["ContractUpgradeMock"], { resetMemory: false }); fixed the problem for me.

jack-wombat commented 1 year ago

@lookeey @CodeWarriorr What you could do in test. Here is the set up code:

box = await getDeployedContract('BoxV1') // this get the proxy. You can be specific to fetch BoxV1_Proxy
proxyAdmin = await getDeployedContract('DefaultProxyAdmin')
boxV2 = await ethers.deployContract('BoxV2')
await proxyAdmin.upgrade(box.address, boxV2.address)
// verify you are using the new impl
expect(await proxyAdmin.getProxyImplementation(box.address)).to.eql(boxV2.address)
// more test below
// ... read some existing state from box
// ... call some new api using box v2

getDeployedContract is a short-cut I use in my code:

export async function getDeployedContract(contract: string, deploymentName = contract): Promise<Contract> {
  const deployment = await deployments.get(deploymentName)
  return ethers.getContractAt(contract, deployment.address)
}

Take a look at this tutorial by OZ as well: https://forum.openzeppelin.com/t/tutorial-on-using-a-gnosis-safe-multisig-with-a-timelock-to-upgrade-contracts-and-use-functions-in-a-proxy-contract/7272