A pure function doesn't cost anything when called from an off-chain script (or from some other constant function).
But it does cost when called from a transaction (i.e., from a non-constant function).
I would like to measure this gas-cost, specifically for a pure function which does nothing but returning true.
Here is my Solidity code:
pragma solidity 0.4.26;
contract Tested {
function func() public pure returns (bool) {
return true;
}
}
contract Tester {
bytes4 private constant FUNC_SELECTOR = bytes4(keccak256("func()"));
function test(uint256 _gas, address _tested) public view returns (bool) {
bool success;
uint256[1] memory ret;
bytes memory data = abi.encodeWithSelector(FUNC_SELECTOR);
assembly {
success := staticcall(
_gas, // gas
_tested, // destination address
add(data, 32), // input buffer (starts after the first 32 bytes in the `data` array)
mload(data), // input length (loaded from the first 32 bytes in the `data` array)
ret, // output buffer
32 // output length
)
}
return success && ret[0] != 0;
}
}
And here is my Truffle test:
contract('Test', () => {
it('test', async () => {
const tester = await artifacts.require('Tester').new();
const tested = await artifacts.require('Tested').new();
console.log();
console.log(await tester.test(189, tested.address)); // prints 'false'
console.log(await tester.test(190, tested.address)); // prints 'true'
});
});
As you can see, this test shows that the gas-cost is 190.
Of course, in the real contract, I hard-code this value, and I would like to keep it as low as possible in order to avoid spending all the gas when staticcall
fails.
For example:
contract Tested1 {
function func() public pure returns (bool) {
return true;
}
}
contract Tested2 {
function() external payable {
assert(false); // will consume the remaining gas
}
}
Let's add a gas-measurement function in contract Tester
:
uint256 public gasUsed;
function measureGas(uint256 _gas, address _tested) public returns (bool) {
uint256 gasLeft = gasleft();
bool result = test(_gas, _tested);
gasUsed = gasLeft - gasleft();
return result;
}
And let's measure the gas-cost of each case:
contract('Test', () => {
it('test', async () => {
const tester = await artifacts.require('Tester').new();
const tested1 = await artifacts.require('Tested1').new();
const tested2 = await artifacts.require('Tested2').new();
const gasUsed1 = await gasUsed(tester, 100000, tested1);
const gasUsed2 = await gasUsed(tester, 100000, tested2);
console.log();
console.log('gasUsed1 =', gasUsed1.toString());
console.log('gasUsed2 =', gasUsed2.toString());
});
});
async function gasUsed(tester, gas, tested) {
await tester.measureGas(gas, tested.address);
return await tester.gasUsed();
}
The results on ganache-cli are:
gasUsed1 = 1299
gasUsed2 = 101095
For the first case, I assume that 1299 is the sum of:
- 190 for the pure function
- 700 for
staticcall
itself (see this EIP) - A few minor things before and after calling
gasleft
As you can see, the second case has depleted the entire amount of gas passed to staticcall
.
Therefore, I would like to keep this amount to the minimum required for the pure function to complete successfully.
Do I have anything to worry about in setting it to 190? Is there any chance that this will be different depending on the contract deployment address, its bytecode (i.e., the existence of other functions in it), or even future forks in the network?
Thank you!