0

I have a simple function.

function withdraw() public onlyOwner {
    uint256 ownerBalance = address(this).balance;
    require(ownerBalance > 0, "No balance in the contact");

    (bool sent, ) = msg.sender.call{value: address(this).balance}("");
    require(sent, "Failed to send user balance back to the owner");
}

And what I want to test is the last line of the code require(sent, "Failed to send user balance back to the owner");

How can I make sent as false during unit test? (So that way, I can test the failure case of require)

Note1: I have seen this but I am not sure creating a new contract for unit test is ultimate solution. I am looking for more concise way.

Note2: Using solidity 0.8.17 and hardhat for testing. Note3: My intention is to make coverage 100%

4
  • I don't think that there is another way to do it. There are some mocking libraries but they mock contracts call and NOT native ether transfer call like yours msg.sender.call{value: address(this).balance}("")
    – Majd TL
    Commented Oct 6, 2022 at 15:40
  • wait a second maybe you can place (bool sent, ) = msg.sender.call{value: address(this).balance}(""); inside an internal function and then mock this function in your contract :D using Waffle (not sure if it works for internal functions) ethereum.stackexchange.com/questions/127626/…. ethereum-waffle.readthedocs.io/en/latest/mock-contract.html
    – Majd TL
    Commented Oct 6, 2022 at 15:49
  • @MajdTL thanks for the suggestion. I have tried with mock-contract and used "revertsWithReason". The mocking works well but it doesn't cover the coverage of my code. So even if I mocked "revert", the coverage doesn't consider the "else" case is tested: require(sent, "Failed to send user balance back to the owner");
    – Téwa
    Commented Oct 6, 2022 at 22:24
  • @MajdTL The other way it could fail is if it runs out of gas inside call, but I'm not sure if solidity reservers enought to process the revert.
    – Ismael
    Commented Oct 7, 2022 at 5:47

1 Answer 1

1

You could set the owner to a mock contract that is able to call the withdraw() function but doesn't implement a receive() or fallback() function, in which case the low-level call will fail.

In case you're setting the owner to msg.sender during deployment, you can add the deployment of your base contract to the mock contract and test it via the mock. Something similar to:


contract BaseContract is Ownable {
    constructor() Ownable() {}

    // Whatever you need it to do

    function withdraw() public onlyOwner {
      uint256 ownerBalance = address(this).balance;
      require(ownerBalance > 0, "No balance in the contact");

      (bool sent, ) = msg.sender.call{value: address(this).balance}("");
      require(sent, "Failed to send user balance back to the owner");
    }
}

contract MockReceiver {
    BaseContract base;

    constructor(){
       base = new BaseContract();
    }

    function testWithdraw() {
      base.withdraw();
    }

}

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.