Transaction Tries and Transaction Receipt Tries are indeed independent data structures with distinct roots stored on the blockchain header and differ in both purpose and content.
Purpose:
Content:
Parameters used in composing a Transaction Trie [details in section 4.3 of the yellow paper]:
- nonce,
- gas price,
- gas limit,
- recipient,
- transfer value,
- transaction signature values, and
- account initialization (if transaction is of contract creation type), or transaction data (if transaction is a message call)
Parameters used in composing a Transaction Receipt Trie [details in section 4.4.1 of the yellow paper]:
- post-transaction state,
- the cumulative gas used,
- the set of logs created through execution of the transaction, and
- the Bloom filter composed from information in those logs
SOLUTION 1:
Best solution is using expectRevert
cheat code from forge-std:
vm.expectRevert(ExampleBridgeContract.InvalidCaller.selector);
exampleBridge.convert(empty, empty, empty, empty, 0, 0, 0, address(0));
If you are using an error with parameters pass calldata instead of selector to the vm.expectRevert(...)
method:
vm.expectRevert(abi.encodeWithSignature("IncorrectStatus(uint8,uint8)", 1, 0));
SOLUTION 2:
try pathRegistry.swap(WETH, LUSD, amountIn, type(uint256).max) {
assertTrue(false, "swap(..) should revert when amountOut < amountIn.");
} catch (bytes memory reason) {
bytes4 desiredSelector = bytes4(keccak256(bytes("InsufficientAmountOut(uint256,uint256)")));
bytes4 receivedSelector = bytes4(reason);
assertEq(desiredSelector, receivedSelector);
}
Since custom errors are encoded the same way functions are, bytes memory reason
contains function/error selector and the encoded values. I don't want to work with the values so I simply verify the selector. To do that I convert bytes memory reason
to bytes4
type which removes all the bytes related to the encoded values and leaves only the selector in place. Then I simply compare with assertEq whether the selector really corresponds to the custom error selector which is supposed to be thrown in this test.
EDIT:
As @Ismael pointed out it's possible to receive the selector directly from the error definition. The code then would look like this:
try pathRegistry.swap(WETH, LUSD, amountIn, type(uint256).max) {
assertTrue(false, "swap(..) should revert when amountOut < amountIn.");
} catch (bytes memory reason) {
bytes4 expectedSelector = PathRegistry.InsufficientAmountOut.selector;
bytes4 receivedSelector = bytes4(reason);
assertEq(expectedSelector, receivedSelector);
}
Best Answer
You can take a look at how does custom-errors are being handled when Ethereum transaction fails with this example:
https://medium.com/coinmonks/solidity-revert-with-custom-error-explained-with-example-d9dff8937ef4
Etherscan can deduce the revert reasons based on the explanation on the reasons:
https://info.etherscan.com/reason-for-failed-transaction/
Failed transactions on Etherscan