How to set gasLimit and gasPrice with Hardhat upgrades plugin?

I'm trying to deploy an upgradeable smart contract using "@openzeppelin/contracts-upgradeable": "^4.5.2". Since I can't pass gas parameters to the upgrades.deployProxy(foo), I have to overwrite the default Hardhat's provider. Once I do it, the call still uses the default Hardhat provider. Is there a way to force the call to use the overwritten provider?

In the accompanying code the line const token = await upgrades.deployProxy(Token); already fails. The txParams() are working as expected and provide the desired values. The overwritten provider is an operational and verified provider, so there should be no issues with it.

:1234: Code to reproduce

const { ethers, upgrades } = require("hardhat");
const { txParams } = require("../utils/transactionHelpers.js");
const { EvmRpcProvider } = require("@acala-network/eth-providers");

async function main() {
  const ethParams = await txParams();
  /* -------------------------------------------------------------------------------------- */
  // https://github.com/OpenZeppelin/openzeppelin-upgrades/issues/85#issuecomment-1028435049

  // Wrap the provider so we can override fee data.
  const provider = EvmRpcProvider.from('ws://localhost:9944');
  await provider.isReady();

  provider.getFeeData = async () => ({
    gasPrice: ethParams.txGasPrice,
    gasLimit: ethParams.txGasLimit,
  });

  // Create the signer for the mnemonic, connected to the provider with hardcoded fee data
  const signer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC).connect(provider);
  
  /* -------------------------------------------------------------------------------------- */

  // Deploy FooToken
  const Token = await ethers.getContractFactory("FooTokenV2", signer);
  const token = await upgrades.deployProxy(Token);
  await token.deployed();
  console.log('Token deployed')

  // Protocol contract args: penaltyPerc=10, lockingPeriod=51840 (3 days in 5 sec block's amount)
  const Protocol = await ethers.getContractFactory("FooProtocol", signer);
  const protocol = await upgrades.deployProxy(Protocol, [10, 51840, token.address]);
  await protocol.deployed();
  console.log('Protocol deployed')

  // Deploy FooGovernor with FooToken address as argument
  const Governor = await ethers.getContractFactory("FooGovernor", signer);
  const governor = await Governor.deploy(token.address, 5, 5, ethers.utils.parseEther('1000'), [4, 25, 50]);
  // const governor = await Governor.deploy(token.address, 17280 /* 1 day */, 17280 /* 1 day */, ethers.utils.parseEther('5000000'), [4, 25, 50]);
  await governor.deployed();
  console.log('Governor deployed')

  // Grant governor admin role in the protocol contract
  await protocol.grantRole('0x0000000000000000000000000000000000000000000000000000000000000000', governor.address);

  console.log("FooToken deployed at:", token.address);
  console.log("FooProtocol deployed at:", protocol.address);
  console.log("FooGovernor deployed at:", governor.address);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

:computer: Environment

These are the dependencies that I'm using:

  "dependencies": {
    "@acala-network/eth-providers": "^2.4.1",
    "@nomiclabs/hardhat-waffle": "^2.0.3",
    "@openzeppelin/contracts": "^4.5.0",
    "@openzeppelin/contracts-upgradeable": "^4.5.2",
    "@openzeppelin/hardhat-upgrades": "^1.17.0",
    "chai": "^4.3.6",
    "dotenv": "^16.0.0",
    "hardhat": "^2.9.3",
    "hardhat-contract-sizer": "^2.5.1",
    "solidity-bytes-utils": "^0.8.0"
  }

I'm trying to run the deploy script on a local Acala EVM+ (https://evmdocs.acala.network/network/node-setup).
My device is a M1 Max Apple silicon device with 64 GB of unified memory and macOS Monterey v 12.1. I also have Rosetta 2 installed.

Hi @ThunderDeliverer,

provider.getFeeData is of type FeeData from the ethers API. This only supports gasPrice (for legacy transactions), or maxFeePerGas and maxPriorityFeePerGas (for EIP-1559 transactions).

If you want to set the gasLimit, you can override signer.estimateGas instead.

So for your example, you can do something like:

  ...

  provider.getFeeData = async () => ({
      gasPrice: ethParams.txGasPrice
  });

  const signer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC).connect(provider);

  signer.estimateGas = async(transaction) => {
    return ethParams.txGasLimit;
  }

  // Deploy FooToken
  const Token = await ethers.getContractFactory("FooTokenV2", signer);
  const token = await upgrades.deployProxy(Token);
  ...
1 Like

I have tried to override the default proxy provider with a JsonRpcProvider because I need to manually set the gas price for deployment. My deployment was stuck in Polygon previously because of too low gas price.

However, the deployProxy function get stuck when I use the JsonRpcProvider created as following:

const overrideProvider = new hre.ethers.providers.JsonRpcProvider('http://localhost:8545');
overrideProvider.getFeeData = async () => ({
    gasPrice: maxGasFees.maxFeePerGas,
    maxFeePerGas: maxGasFees.maxFeePerGas,
    maxPriorityFeePerGas: maxGasFees.maxPriorityFeePerGas
});
let newSigner = new Wallet('<private_key>', overrideProvider);
let UpgradableContract = await hre.ethers.getContractFactory('<contract>', newSigner);
const contractProxy = await hre.upgrades.deployProxy(UpgradableContract, [options.name, options.symbol]);

I tried it using my local hardhat node (npx hardhat node), I can see there is a transaction triggered but in my JS code, the deployProxy function never returns. It works when I use the default hardhat provider.

eth_sendRawTransaction
  Contract deployment: <contract name>
  Contract address:    0x0b306bf915c4d645ff596e518faf3f9669b97016
  Transaction:         0x21dd9b9a68e3e7660d36611fff14772c2033416f72cd34684426e4c5a9460c9b
  From:                <address>
  Value:               0 ETH
  Gas used:            4502014 of 4502014
  Block #18:           0xc6d699540e838325d860c138c6d7527fd70fb314bd6ab586213134e8920c5edb

@Max_Lau Can you enable debug logging with export DEBUG=@openzeppelin:*, then try again and provide the output?

As a workaround, the upgrades plugin has functions that you can use to separately validate your implementation, then deploy the implementation and proxy either using the plugin's functions or using the standard deploy() function (and then you can forceImport your deployments into the plugin for future upgrades).

Hey @ericglau and OZ team, I would like to +1 adding support for injecting gas price overrides for the deployProxy function. I'm having to do all these complicated workarounds to change the gas settings on polygon.

Hi, I also got "stuck" like that. Turns out since you create your own JsonRpcProvider, that will be used for deployment (since it's the signer), BUT for polling whether the transaction was mined, the upgrades plugin uses hardhat's provider. So the transaction goes through just fine, only upgrades doesn't notice it.

How to fix this? Add something like this to your hardhat.config.ts:

networks: {
  local: {
    chainId: 1234,
    url: "http://localhost:8545",
    accounts: ["0x0000000000000000000000000000000000000000000000000000000000000000"]
  }
}

then run your script with --network local on the command-line.

Why does it do that? Beats me. Of course it should use the same provider for polling that it uses for deployment.

The Hardhat Upgrades plugin's functions now have a txOverrides option where you can pass in an ethers.Overrides object to override transaction parameters. It is no longer necessary to use the above signer/provider workarounds. See txOverrides in the documentation, and there is an example in this comment.