Solidity – How to Calculate Exact Amount of Gas Used in Transaction with HardHat?

hardhatsolidity

I'm writing a Smart Contract that individuals can donate to, and a specific address can withdraw all ether from the contract. I'm trying to write a test that validates that the withdrawer receives all money stored in the contract, but my asserts are failing when checking the expected final balance. I'm trying to adjust for gas used on all transactions. The actual is always slight less than the expected so I'm assuming I'm missing some gas cost somewhere. I'm using hardhat for testing.

contract:

pragma solidity ^0.8.0;

// A donation campaign being run
contract Campaign {
  
  // the owner of the contract
  address payable public immutable owner;

  // the person running the donation campaign
  // this person may also be the beneficiary
  address payable public immutable runner;

  // the beneficiary of the campaign
  // this person may also be the campaign runner
  address payable public immutable beneficiary;

  // initializes owner, the runner of the campaign, and the contract beneficiary.
  // beneficiary and runner may have the same value
  constructor(address payable _runner, address payable _beneficiary) {
    owner = payable(msg.sender);
    runner = _runner;
    beneficiary = _beneficiary;
  }

  // accepts donation payment
  function donate() public payable {
    // nothing to do here... yet...
  }

  // allows either the owner or the beneficiary to withdraw the donations
  function withdraw() public {
    require(msg.sender == beneficiary || msg.sender == owner, "only beneficiary or owner can withdraw");
    payable(msg.sender).transfer(address(this).balance);
  }
}

test function:

async function assertWithdrawSucceeds(donator, withdrawer) {
  amount = ethers.utils.parseEther("2");
  // load contract with money
  txResp = await deployedCampaign.connect(donator).donate({value: amount});
  txReceipt = await txResp.wait();
  actuallyDonated = amount.sub(txReceipt.gasUsed);

  // store original balance
  originalWithdrawerBalance = await ethers.provider.getBalance(withdrawer.address);
  
  // withdraw
  txResp = await deployedCampaign.connect(withdrawer).withdraw();
  txReceipt = await txResp.wait();
  withdrawGas = ethers.BigNumber.from(txReceipt.gasUsed);
  
  // balances to check
  contractBalance = await ethers.provider.getBalance(deployedCampaign.address);
  withdrawerFinalBalance = await ethers.provider.getBalance(withdrawer.address);
  
  // asserts
  expect(contractBalance).to.equal(0);

  console.log("original withdraw balance: " + originalWithdrawerBalance);
  console.log("final withdraw balance: " + withdrawerFinalBalance);
  console.log("calculated final withdraw balance: " + originalWithdrawerBalance.add(actuallyDonated).sub(withdrawGas));

  expect(
    originalWithdrawerBalance.add(actuallyDonated).sub(withdrawGas)
    .eq(withdrawerFinalBalance)
  ).is.true();
}

hardhat.config.js

require("@nomiclabs/hardhat-waffle");

/**
 * @type import('hardhat/config').HardhatUserConfig
 */
module.exports = {
  solidity: "0.8.0",
  networks: {
    hardhat: {
      accounts: {
        accountsBalance: '10500000000000000000' // 10.5 ether
      }
    }
  }
};

Best Answer

receipt.gasUsed returns, as the name says, the used gas, not the Ether spent to consume that gas. If you think about it, what you are doing is wrong from a dimensional point of view: you are subtracting originalWithdrawerBalance (Ether) and gasUsed (a pure number).

To get the spent Ether you must multiply the gasUsed by the gas price of your transaction.

receipt.gasUsed.mul(receipt.effectiveGasPrice)
Related Topic