When testing a transaction via Truffle & Ganache, it appears that I must set the gas-limit of both sides to at least twice the gas used in the transaction.
One should be able to set the gas-limit (of both sides) to the gas used in the transaction.
One must set the gas-limit (of both sides) to at least twice the gas used in the transaction.
On-Chain Code:
pragma solidity 0.4.25;
contract MyContract {
bool public x;
function func(uint i) external {
for (uint n = 0; n < i; n++)
x = !x;
}
}
Off-Chain Code:
contract("Test", function(accounts) {
let myContract;
before(async function() {
myContract = await artifacts.require("MyContract").new();
});
it("test", async function() {
let response = await myContract.func(1000);
console.log("gasUsed =", response.receipt.gasUsed);
});
});
The results depending on the gas-limit configuration of Truffle and Ganache:
Truffle | Ganache | Test Result
---------|----------|------------
12803719 | 12803719 | Failure: VM Exception while processing transaction: out of gas
12803719 | 12803720 | Failure: VM Exception while processing transaction: out of gas
12803720 | 12803719 | Failure: Exceeds block gas limit
12803720 | 12803720 | Success: gasUsed = 6401860
The Exceeds block gas limit error occurs at the before part, so I'm pretty sure that it is due to a violation of the gas-limit configuration between Truffle and Ganache (i.e., Truffle gas-limit must be less than or equal to Ganache gas-limit).
But the other cases imply that in order for the test to pass, I must set the gas-limit in both Truffle and Ganache to at least twice the gas used in a transaction (12803720 = 6401860 * 2).
Can you please explain this behavior?
Is it possible that the response.receipt.gasUsed returned from Ganache is only half of the actual gas used in the transaction?
"truffle": "4.1.15")"ganache-cli": "6.2.5")Thanks
When you set x = true; then x = false; you incur 2 SSTORE gas costs (20000 for x = true; and 5000 for x = false;) and 1 SSTORE gas refund of 15000 (given whenever x is set _back_ to false). Gas refunds are refunded _after_ the transaction is completed. What appears to be happening is that this contract function actually uses 12803720 gas, but after the transaction is complete some of that gas is refunded, resulting in a total gas used of 6401860.
Additionally, the accumulated refund can not exceed half the gas used for the context (in this case, there is only the single calling context, which is the top-level call to func), which is exactly was appears to be happening here.
Closing as everything seems to be working as intended. Let me know if you believe this is not the case.
@davidmurdoch:
Thank you very much for the detailed explanation.
My primary concern was for receipt.gasUsed not reflecting the correct amount of gas-used in a transaction, since I have relied on it in order to produce an estimation of our expected overall gas-cost.
I take it (according to your description), that gasUsed here is accurate despite the fact that I needed to configure the gas-limit to a much higher value, because the latter stems from the "intermediate" gas consumption (i.e., before the refund).
If my understanding is correct and I can continue relying on receipt.gasUsed for estimations, then it's all good (please inform me if otherwise).
Thanks again for your help :)
You should be using eth_estimateGas for calculating gas estimations. This will return the gasUsed + gasRefund. Note that in Ganache we currently respect the gas limit when running estimateGas, so an OOG error is still possible when using the default gasLimit for transactions with very high gas usage (like in your example).
@davidmurdoch :
I have done so in the past (via web3.js), but I found it to be:
estimateGas and then send.transaction.estimateGas throwing an exception (I never posted it, because it was hard to describe and I couldn't be bothered at some point).Should I expect any problem using gasUsed for this matter?
Thanks :)
estimateGas whenever it makes sense to do so. Using JavaScript's async/await and [email protected] should make transitioning your code to calling web3.eth.estimateGas relatively straight forward.v0.x to v1.x is a breaking change and you'll need to upgrade your calling code.But yes, you will run into problems using only gasUsed in transactions that contain gas refunds.
Thanks!
@davidmurdoch :
transaction.estimateGas() with a transaction.send() (so that the transaction will take effect, the state of the chain will change, and the gas-estimation of the next transaction will be corresponding to the new state of the chain). I find the alternative - printing receipt.gasUsed after every transaction - to be much more simple, given that I already have a sequence at hand (whether it's a test, a deployment script or a runtime script).let gas = await transaction.estimateGas() with let gas = (await web3.eth.getBlock("latest")).gasLimit, which proved much more robust for my purpose (since my entire code is "in-house", I don't need to worry about using the maximum gas-limit). I will see if I can reproduce it and report accordingly.Thanks
Most helpful comment
When you set
x = true;thenx = false;you incur 2SSTOREgas costs (20000forx = true;and5000forx = false;) and 1SSTOREgas refund of15000(given whenever x is set _back_ tofalse). Gas refunds are refunded _after_ the transaction is completed. What appears to be happening is that this contract function actually uses12803720gas, but after the transaction is complete some of that gas is refunded, resulting in a total gas used of6401860.Additionally, the accumulated refund can not exceed half the gas used for the context (in this case, there is only the single calling context, which is the top-level call to
func), which is exactly was appears to be happening here.Closing as everything seems to be working as intended. Let me know if you believe this is not the case.