1

I want to properly and efficiently test a smart contract upgrade with hardhat-deploy.

Currently, I'm experimenting with hardhat forking, both networks that is localhost and hardhat networks.

I've copied deployment files to hardhat and localhost folders, both contracts are accessible in tests as they are deployed, but when I run deployments.fixtures['CONTRACT_NAME'] then I get newly deployed contract with new address and no old storage.

Can anyone assist me please?

3 Answers 3

1

I have some kind of solution, its dirty as F***, but I will post it anyway, maybe some1 will find if helpful...

My Makefile:

.PHONY: node-test local-node-mumbai copy-mumbai-deployments node-mumbai

node-test:
    npx hardhat test --network localhost

local-node-mumbai:
    screen -dmS local-node npx hardhat node --no-deploy --fork ${MUMBAI_ARCHIVE_NODE_URL}
    sleep 15

copy-mumbai-deployments:
    rm -Rf ./deployments/localhost
    mkdir ./deployments/localhost
    cp -r ./deployments/mumbai/* ./deployments/localhost
    echo "31337" > ./deployments/localhost/.chainId

node-mumbai: local-node-mumbai copy-mumbai-deployments

My hardhat-deploy deployment script:

import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { DeployFunction } from 'hardhat-deploy/types';

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
  const { deployments, getNamedAccounts } = hre;
  const { deploy } = deployments;
  const { deployer, proxyAdminOwner } = await getNamedAccounts();

  const DataTypes = await deploy('DataTypes', {
    from: deployer,
  });

  const Errors = await deploy('Errors', {
    from: deployer,
  });

  const MintingLogic = await deploy('MintingLogic', {
    from: deployer,
  });

  const ValidationLogic = await deploy('ValidationLogic', {
    from: deployer,
  });

  await deploy('ContractName', {
    from: deployer,
    contract: 'ContractName',
    log: true,
    proxy: {
      owner: proxyAdminOwner,
      proxyContract: 'OptimizedTransparentProxy',
      execute: {
        init: {
          methodName: 'initialize',
          args: [],
        },
        onUpgrade: {
          methodName: 'postUpgrade',
          args: [],
        },
      },
    },
    libraries: {
      Errors: Errors.address,
      DataTypes: DataTypes.address,
      ValidationLogic: ValidationLogic.address,
      MintingLogic: MintingLogic.address,
    },
  });
};

export default func;
func.tags = ['ContractName'];

Simple tests to prove a change (in this case introducing totalSupply function):

import { expect } from 'chai';
import { ethers, deployments } from 'hardhat';
import { ContractName } from '../../typechain-types';

describe('ContractName: postUpgrade', function () {
  let ContractName: ContractName;
  let ContractNameAddress: string;

  beforeEach(async () => {
    ContractName = await ethers.getContract('ContractName');
    ContractNameAddress = ContractName.address;
  });

  it('pre-updated contract does not have total supply function', async () => {
    let message: string = '';
    try {
      await ContractName.totalSupply();
    } catch (e: any) {
      message = e.message;
    }
    expect(message).to.eq('ContractName.totalSupply is not a function');
  });

  describe('after deployment', () => {
    beforeEach(async () => {
      await deployments.fixture(['ContractName'], {
        keepExistingDeployments: true,
      });

      ContractName = await ethers.getContract('ContractName');
    });

    it('does have total supply function', async () => {
      const totalSupply = await ContractName.totalSupply();

      expect(totalSupply).to.gt(0);
    });

    it('address of the contract stays the same', async () => {
      expect(ContractNameAddress).to.be.eq(
        ContractName.address
      );
    });
  });
});

⚠️ This solution doesn't solve all my problems, but its a good start to test against latest deployment

@edit: localhost accounts needs to be the same in hardhat.config.ts under networks:

(…)
    localhost: {
      accounts: accounts('mumbai'),
    },
(…)
2
  • I will give you a vote up for creativity.
    – Sky
    Jan 20 at 9:40
  • Thank you, that's very uplifting Jan 20 at 11:31
0

Using hardhat ethers library you can retreive the deployed contract address. It is included in hardhat helpers.

You can either use,

let baseContract = await hre.ethers.getContractFactory("Name of the contract");
let contract= baseContract.attach('Address of the contract');

or

const contract = await hre.ethers.getContractAt('Name of the contract',"Address of the contract");

also there is a implementation only using ABI's too. Refer hardhat-ethers

0

Testing Upgradable SmartContracts depends heavily on which design pattern did you use to implement such a contract.

For example, Diamon Pattern has many advantages (the big one being unlimited contract size), but the lack of libraries supporting it and increased complexity makes it harder to test.

UUPS, on the other hand, is much easier to implement and easier to test, (but has a limited contract size).

If you happened to use UUPS this link might be of help, as it shows you how to use Hardhat libraries for it. Because the issue here seems to be in the way how you are deploying them (getting empty storage)

1
  • Thank you for your response, I'm using OptimizedTransparentProxy and hardhat-deploy as deployment manager Jan 20 at 9:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.