-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(cheatcodes
): support partial matching of custom errors in reverts
#3725
Comments
Some questions to flesh out the spec here more:
|
|
I'm wondering myself how to do this. The following happens when using vm.expectRevert(SamBankmanPlayingCards.MaxTokenAllotment.selector);
bankmanCardzInstance.mint{value: 0.1 ether}(1); [FAIL. Reason: Error != expected error: 0xa12804b00000000000000000000000000000000000000000000000000000000000000002 != 0xa12804b0] So I must catch the reason and then unpack the arguments. In my case the argument is uint8 and I expect it to be 2. try bankmanCardzInstance.mint{value: 0.1 ether}(1) {}
catch(bytes memory reason) {
//assert selector equality
bytes4 expectedSelector = SamBankmanPlayingCards.MaxTokenAllotment.selector;
bytes4 receivedSelector = bytes4(reason);
assertEq(expectedSelector, receivedSelector);
//assert argument equality
bytes32 parsed;
assembly {parsed := mload(add(reason, 36))}
assertEq(parsed, bytes32(uint(2)));
} Before this feature gets added, is there a more intuitive way to do this? |
What I did was to refactor my custom errors to not include values computed during the function execution, which are difficult to re-compute in a testing environment. These dynamic values are also not that useful for end users, who should care more about the inputs that triggered that custom errors. Therefore, I changed the dynamic values with the input values. |
@PaulRBerg I appreciate your explanation, however, in my scenario I don't use the Error arguments for the end user, nor for dynamic values. Instead, I use them in areas of the code that essentially utilize the same error. I want a way to debug which line of code or function the custom error was triggered from. This sort of reflection isn't available in solidity so I use To provide a method to capture beginsWith as such that it's useful for your scenario, the following is adequate: bytes4 expectedSelector = SamBankmanPlayingCards.MaxTokenAllotment.selector;
bytes4 receivedSelector = bytes4(reason);
assertEq(expectedSelector, receivedSelector); However, there could be a scenario even if I'd like to assert that the custom error argument is within a generally expected range using comparison operators. e.g. |
Thanks also for explaining your use case, @nidhhoggr. Interesting idea with the Regarding your need - are you aware of |
@PaulRBerg Yes I'm familiar with debug but I need unit testing to check that the error was throw from a specific conditional and/or line in the code. This isn't possibly by checking the error alone because there could be multiple spots in the function that throw the same error. While I could just create another custom error to distinguish the two, if the custom error explains a particular scenario like MaxTokenAllotment then providing a from parameter (instead of creating a different error) solves the issue: Example Testing: Last, I was just thinking about how the selector get computed, it's from the first four bytes of the function signatures keccack256 hash /**
_funcSig example: "transferFrom(address,address,uint256)"
*/
function getSelector(string calldata _funcSig) external pure returns (bytes4) {
return bytes4(keccak256(bytes(_funcSig)));
} For this reason I think the method your proposing being name startsWith could be misleading because it might make people think they can derive the same assertion results from functions with similar names. For that reason I would simply use assertExpectedSelector. Notice the difference in signatures even through the methods names are similar and both startsWith the word transfer:
|
Last but not least, the following answers my own question on a more "intuitive" way to use error MaxTokenAllotment(uint8 _from); Where
Fromfunction testExpectCustomErrorWithParam() public {
try contractInstance.mint{value: 0.1 ether}(1) {}
catch(bytes memory reason) {
//assert selector equality
bytes4 expectedSelector = MaxTokenAllotment.selector;
bytes4 receivedSelector = bytes4(reason);
assertEq(expectedSelector, receivedSelector);
//assert argument equality
bytes32 parsed;
assembly {parsed := mload(add(reason, 36))}
assertEq(parsed, bytes32(uint(2)));
}
} Tofunction testExpectCustomErrorWithParam() public {
vm.expectRevert(abi.encodePacked(MaxTokenAllotment.selector, uint(2)));
contractInstance.mint{value: 0.1 ether}(1);
} |
Related issue (misleading documentation in Vm.sol): |
cheatcodes
): support partial matching of custom errors in reverts
Component
Forge
Describe the feature you would like
Custom errors can have arguments, and sometimes these arguments are difficult to calculate in a testing environment, and they may be unrelated to the test at hand (e.g. a value computed in the internal function of a third-party contract).
It would be nice if we could expect a revert with a partial matching of custom errors, like this:
This may warrant a new VM cheatcode, to ensure backwards compatibility and avoid triggering false positives for people who assume the current
vm.expectRevert
cheatcode to look for an exact ABI match.Naming ideas:
vm.expectPartialRevert
orvm.expectCustomError
orvm.expectRevertStartsWith
.The text was updated successfully, but these errors were encountered: