Motivation
Provider now captures the raw revert payload -- RpcErrorInfo carries the JSON-RPC code and the error.data hex (truncated to 512 bytes) -- but the caller still gets a hex string. Every modern Solidity contract reverts with a custom error (error InsufficientBalance(uint256 available, uint256 required)), so "why did my call fail" is currently answered with 0x556f1830... and a manual trip to a 4-byte database.
The two halves needed to close this are both already in the tree and simply not wired together: abi_json.zig parses "type": "error" entries into abi_types.AbiError, and abi_types.selectorFromSignature computes the 4-byte selector. This is a small, high-visibility quality-of-life win and a good first issue.
Scope
decodeRevert(allocator, revert_data, abi) !DecodedRevert in a new src/revert.zig (or alongside abi_decode), matching revert_data[0..4] against the selector of each AbiError in the parsed ABI and decoding the remainder with the existing abi_decode machinery
- Handle the three standard shapes before any custom error:
Error(string) -- selector 0x08c379a0, the classic require(cond, "message")
Panic(uint256) -- selector 0x4e487b71, with the panic codes mapped to names (0x01 assert, 0x11 arithmetic overflow, 0x12 division by zero, 0x32 array out of bounds, and the rest)
- empty revert data -- a bare
revert() or an out-of-gas, which must be reported as "no reason given" rather than as a decode failure
DecodedRevert as a tagged union over { string_error, panic, custom, unknown }, where unknown preserves the raw bytes and the unmatched selector so the caller can still log something useful
- A comptime twin on the abigen bindings:
Contract.decodeRevert(revert_data) resolving against that contract's own errors, matching how decodeEvent already works there
- Note the 512-byte truncation in
RpcErrorInfo: a revert carrying a long string or a dynamic array can be cut off. Either raise the cap for the decode path or return a clearly-flagged partial decode -- silently decoding truncated data into wrong values would be worse than not decoding at all.
Testing
Entirely offline. Encode known reverts by hand and assert the round trip: a require string, each panic code, a custom error with static params, a custom error with a dynamic param, an unknown selector, and empty data. abi_json.zig already has an InsufficientBalance error fixture in its tests to build on.
Pointers
src/provider.zig (the RpcErrorInfo capture path and its truncation comment near line 42), src/abi_json.zig:53 (error-entry parsing) and src/abi_types.zig:307 (AbiError), src/abi_types.zig:316 (selectorFromSignature), src/abi_decode.zig for the parameter decoding, and src/abigen.zig:329 (decodeEvent) as the template for the comptime binding.
Motivation
Providernow captures the raw revert payload --RpcErrorInfocarries the JSON-RPC code and theerror.datahex (truncated to 512 bytes) -- but the caller still gets a hex string. Every modern Solidity contract reverts with a custom error (error InsufficientBalance(uint256 available, uint256 required)), so "why did my call fail" is currently answered with0x556f1830...and a manual trip to a 4-byte database.The two halves needed to close this are both already in the tree and simply not wired together:
abi_json.zigparses"type": "error"entries intoabi_types.AbiError, andabi_types.selectorFromSignaturecomputes the 4-byte selector. This is a small, high-visibility quality-of-life win and a good first issue.Scope
decodeRevert(allocator, revert_data, abi) !DecodedRevertin a newsrc/revert.zig(or alongsideabi_decode), matchingrevert_data[0..4]against the selector of eachAbiErrorin the parsed ABI and decoding the remainder with the existingabi_decodemachineryError(string)-- selector0x08c379a0, the classicrequire(cond, "message")Panic(uint256)-- selector0x4e487b71, with the panic codes mapped to names (0x01 assert, 0x11 arithmetic overflow, 0x12 division by zero, 0x32 array out of bounds, and the rest)revert()or an out-of-gas, which must be reported as "no reason given" rather than as a decode failureDecodedRevertas a tagged union over{ string_error, panic, custom, unknown }, whereunknownpreserves the raw bytes and the unmatched selector so the caller can still log something usefulContract.decodeRevert(revert_data)resolving against that contract's own errors, matching howdecodeEventalready works thereRpcErrorInfo: a revert carrying a long string or a dynamic array can be cut off. Either raise the cap for the decode path or return a clearly-flagged partial decode -- silently decoding truncated data into wrong values would be worse than not decoding at all.Testing
Entirely offline. Encode known reverts by hand and assert the round trip: a
requirestring, each panic code, a custom error with static params, a custom error with a dynamic param, an unknown selector, and empty data.abi_json.zigalready has anInsufficientBalanceerror fixture in its tests to build on.Pointers
src/provider.zig(theRpcErrorInfocapture path and its truncation comment near line 42),src/abi_json.zig:53(error-entry parsing) andsrc/abi_types.zig:307(AbiError),src/abi_types.zig:316(selectorFromSignature),src/abi_decode.zigfor the parameter decoding, andsrc/abigen.zig:329(decodeEvent) as the template for the comptime binding.