0

I am trying to check for a custom error with expectRevert in my Foundry test. Everything works however, after converting the following test to a fuzz test, it no longer works. How can I resolve this?

function test_fee_too_large(uint256 fee) public {
    vm.assume(fee > 1 ether);
    hoax(alice);
    bytes4 selector = bytes4(keccak256("WrongInitiationFee(address)"));
    vm.expectRevert(abi.encodeWithSelector(selector, alice));
    emit log_named_uint("fee", fee);
    dao.join{value: fee}();
}

Error:

Failing tests:
Encountered 1 failing test in test/join.t.sol:Join
[FAIL. Reason: Call reverted as expected, but without data Counterexample: calldata=0xd874ad920000000000000000000000000000000100000000000000000000000000000001, args=[340282366920938463463374607431768211457]] test_fee_too_large(uint256) (runs: 2, μ: 23105, ~: 23105)

However, it works as expected when I send a hardcoded value in as the value to dao.join

function test_fee_too_large(uint256 fee) public {
    vm.assume(fee > 1 ether);
    hoax(alice);
    bytes4 selector = bytes4(keccak256("WrongInitiationFee(address)"));
    vm.expectRevert(abi.encodeWithSelector(selector, alice));
    emit log_named_uint("fee", fee);
    dao.join{value: 100 ether}();
}
error WrongInitiationFee(address caller);

join function on contract:

function join() public payable {
    if (members[msg.sender] == true) {
        revert AlreadyMember(msg.sender);
    }
    // only work with exactly 1 ETH
    if (msg.value != 1 ether) {
        revert WrongInitiationFee(msg.sender);
    }
    members[msg.sender] = true;
    memberCount++;
    emit MemberJoined(msg.sender);
    // console.log("New member joined: %s", members[msg.sender]);
}
3
  • I also tried bound(fee, 2 ether, 10 ether); instead of vm.assume(fee > 1 ether); Same error... Commented Mar 15, 2023 at 0:08
  • It looks like you've shared the same code twice? Can we see the code of the test before fuzzing as well as the fuzz test?
    – Josh Laird
    Commented Sep 7, 2023 at 14:41
  • Hi, @JoshLaird, nice to see you here! I had a similar problem so see my answer. Commented Feb 2 at 11:42

1 Answer 1

0

I can't reproduce the error that OP reports, but am providing a generic answer as this is one of the few search results for the error message (that's how I got here).

TL;DR some EVM-internal reverts will not return any data, so the "call reverted as expected" can be a red herring—yes it reverted, as you expected it to do, but it didn't revert in the expected way (English sucks).

Background

All of this is hidden by Foundry and regular Solidity, but under the hood, the revert opcode accepts a memory offset and a size (length) of memory to return* when reverting. Consider the ABI-encoded value of MyError(arg)—let's call it bytes memory err and let uint256 len = err.length—now a Solidity revert MyError(arg) will result in the err value being at some offset (position) in memory and a subsequent revert(offset, len).

The outer context will then have access to this data via returndatasize, which will return len, and returndatacopy, which allows for err to be copied into that context's memory.

"but without data"

A call to revert(x, 0) is perfectly legal EVMing. For example, a create2 when the address already exists currently results in this behaviour.

"call reverted as expected"

Yeah, nah. If we're being pedantic, it should read "as expected, the call reverted" because in this case "the call reverted" but not "as expected".

Conclusion

This is no different to the code under test reverting with a different but specific error. The problem arises because of uninformative reversion errors and subtly incorrect Foundry wording.


*I use return in the English sense as there is also a return opcode.

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.