1

While doing an openzepplin challange (please do not provide me an answer), I tried to create a new contract Con1 that waste most of the gas (but not all) when calling Con1.withdraw.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "hardhat/console.sol";

contract Denial {

    address public partner; // withdrawal partner - pay the gas, split the withdraw
    address public constant owner = address(0xA9E);
    uint timeLastWithdrawn;
    mapping(address => uint) withdrawPartnerBalances; // keep track of partners balances

    function setWithdrawPartner(address _partner) public {
        partner = _partner;
    }

    // withdraw 1% to recipient and 1% to owner
    function withdraw() public {
        uint amountToSend = address(this).balance / 100;
        // perform a call without checking return
        // The recipient can revert, the owner will still get their share
        console.log("stav0",gasleft());
        (bool s,) = partner.call{value:amountToSend}("");
        require(s,"failed to call!!!");
        console.log("stav5",gasleft());
        payable(owner).transfer(amountToSend);
        // keep track of last withdrawal time
        timeLastWithdrawn = block.timestamp;
        withdrawPartnerBalances[partner] +=  amountToSend;
    }

    // allow deposit of funds
    receive() external payable {}

    // convenience function
    function contractBalance() public view returns (uint) {
        return address(this).balance;
    }
}

contract Con1 {
     Denial public d = new Denial();
    //  Denial public d = Denial(payable(0x6A358FD7B7700887b0cd974202CdF93208F793E2));

    function addMySelf() public {
        // payable(0x6A358FD7B7700887b0cd974202CdF93208F793E2).transfer(0);
        // Denial d = Denial(payable(0x6A358FD7B7700887b0cd974202CdF93208F793E2));
        d.setWithdrawPartner(address(this));
    }

    function widthraw()public {
        addMySelf();
        d.withdraw();
    }

    receive() external payable {
        console.log("stav1",gasleft());
        uint256 i = 0;
        while (gasleft() >= 4000){
            i++;
            d.contractBalance();
        }
        console.log("stav4",gasleft());
    }
}

Expected gas-left logs:

stav0 1801598
stav1 1769491
stav4 3622
stav5 <less than 3622 gas>

Actual gas-left logs:

stav0 1801598
stav1 1769491
stav4 3622
stav5 30626     <<<<----------- How can it be?!

Question

Why stav5 30626 is printed? How can it be?

1 Answer 1

1

I found an answer:

If the developer does not specify custom gas amount when calling an external contract, not all the gas can be used in the external call, only 63/64 of the current gasleft().

Example:

By calling B.b1() with 1M gas:

contract A{
   function receive() public {
      // `gasleft()` == 1M * 63/64
      
   }
}

contract B{
   A a = 0x....address of A...

   function b1() public {
       // `gasleft()` == 1M
       address(a).call{value:1000}("");
       // `gasleft()` == 1M * 1/64 + any gas left from `a.receive()`
   }
}

Source:

https://ethernaut.openzeppelin.com/

Level: Denial

Note: An external CALL can use at most 63/64 of the gas currently available at the time of the CALL. Thus, depending on how much gas is required to complete a transaction, a transaction of sufficiently high gas (i.e. one such that 1/64 of the gas is capable of completing the remaining opcodes in the parent call) can be used to mitigate this particular attack.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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