Based on this answer, I was able to write the following custom error:

error ReachedInvestmentCeiling(uint256 providedVal, string errorMessage);

if (hasReachedInvestmentCeiling(cumReceivedInvestments, tiers) ) {
      revert ReachedInvestmentCeiling(cumReceivedInvestments, "Investment ceiling is reached.");

Accompanied by the following test:

vm.expectRevert(abi.encodeWithSignature("ReachedInvestmentCeiling(uint256,string)", 30 ether+1 wei, "Investment ceiling is reached."));


and I noticed that if the expected error message is off by one character, e.g. missing the dot at the end, the error message on the failing forge test becomes:

Failing tests:
Encountered 1 failing test in test/unit/helper.t.sol:HelperTest
[FAIL. Reason: Error != expected error: 0x57f3b2d7000000000000000000000000000000000000000000000001a055690d9db800010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001d496e766573746d656e74206365696c696e672069732072656163686564000000 != 0x57f3b2d7000000000000000000000000000000000000000000000001a055690d9db800010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001e496e766573746d656e74206365696c696e6720697320726561636865642e0000] testExceedInvestmentCeiling() (gas: 24063)

Which makes it either hard to debug if an error behaves different than the test expects, or labour-intensive to write separate code to see what actually happens (like the old: vm.expectRevert(bytes("Investment ceiling is reached."));, or a console2.log( statement).


Am I doing something wrong, should I write it differently to make the failing test display something other than the hashes of the error and expected error, or is this behaviour desirable, or perhaps something else?


I can understand from a contract perspective, custom errors are desirable, and SolHint recommends one uses custom errors instead of require statements, which I am fine with. This question is merely about how to test those effectively, not about whether one should or should not use custom errors.

1 Answer 1


Since custom errors are encoded the same way functions are, so one way is to manually decode the bytes hex-data that you're getting in:

[FAIL. Reason: Error != expected error: 0x57f3b2d7.... != 0x57f3b2d7....]

using cast --calldata-decode command separately like this:

Decoding LHS of !=

cast --calldata-decode "ReachedInvestmentCeiling(uint256, string)" 0x57f3b2d7000000000000000000000000000000000000000000000001a055690d9db800010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001d496e766573746d656e74206365696c696e672069732072656163686564000000


30000000000000000001 [3e19]
"Investment ceiling is reached"

Decoding RHS of !=

cast --calldata-decode "ReachedInvestmentCeiling(uint256, string)" 0x57f3b2d7000000000000000000000000000000000000000000000001a055690d9db800010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001e496e766573746d656e74206365696c696e6720697320726561636865642e0000


30000000000000000001 [3e19]
"Investment ceiling is reached."

P.S. You could also decode the same using abi.decode, but for that you might have to use try/catch, as the error would fall into the catch, then you could potentially decode from there.

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.