0

So, I have been testing a transaction with hardhat and etherjs, that internally makes two staticcalls and a low-level call. However, by default, the transaction fails at the low-level call, as it is not getting enough gas.

 function buy() external{

    uint balance = getbalance();
    uint allowance = getAllowance();
   
    require(balance>=price && allowance>=price, "Incorrect balance or allowance");
    _mint(msg.sender, ++Counters);
    getFunds();
}

function getFunds() internal returns(bool result){
    bytes memory data = abi.encodeWithSelector(token.transferFrom.selector, msg.sender, address(this), 50000);
    address tokenAdd = address(token);
    assembly{
    let status:= call(260000, tokenAdd, 0, add(data, 32), mload(data), 0, 0)
    if eq(status, 1) {
        if eq(returndatasize(), 32) {
            returndatacopy(0, 0, 32)
            result := mload(0)
            }
        }
    }
}


function getbalance() internal view returns(uint result){
    bytes memory data = abi.encodeWithSelector(token.balanceOf.selector, msg.sender);
    result = 0;
    address tokenAdd = address(token);
    assembly{
       let status:= staticcall(16000, tokenAdd, add(data, 32), mload(data), 0, 0)
       if eq(status, 1) {
        if eq(returndatasize(), 32) {
            returndatacopy(0, 0, 32)
            result := mload(0)
            }
        }
    }
}
function getAllowance() internal view returns(uint result){
    bytes memory data = abi.encodeWithSelector(token.allowance.selector, msg.sender, address(this));
    result = 0;
    address tokenAdd = address(token);
    assembly{
       let status:= staticcall(16000, tokenAdd, add(data, 32), mload(data), 0, 0)
       if eq(status, 1) {
        if eq(returndatasize(), 32) {
            returndatacopy(0, 0, 32)
            result := mload(0)
            }
        }
    }
}

If I try to execute buy as await contract.buy(), it will be estimating incorrect gaslimit for the low-level call (function getFunds()) and hence it will fail. It only works if I manually provide a gasLimit while making the function call like await contract.buy({gasLimit: 560000})

However, it works fine in a forked environment in hardhat.

What's the actual problem, when executing transaction on the testnet with hardhat & ethersjs? Why its incorrectly estimating gaslimit?

1 Answer 1

0

Gas estimation gets complicated if there are multiple execution paths. The way hardhat network implements eth_estimateGas is first it passes block gas limit to get gas used, then tries with gas used and if tx goes out of gas it does a binary search between gas used and block gas limit to achieve a execution path that does not revert with minimum gas limit.

In your case, there are multiple non reverting execution path that depend on gas limit. Basically, in second try the low level call would fail but the overall tx is still successful.

I can think of these solutions:

  1. Just forget eth_estimateGas, you can yourself try and find a gas limit manually that barely works for you (if you don’t want to pass a high gas limit value).
  2. Redesign your function such that it has a single non reverting execution path. E.g. you can pass a Boolean parameter such that if it is true then low level call failure would revert as well. Then eth_estimateGas would help you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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