refactor!: align precompile reverts with Solidity errors#1099
Conversation
|
Rebase on main |
0c7c32b to
2e1567f
Compare
|
@greptile review |
Greptile SummaryThis PR replaces string-based precompile reverts with ABI-encoded Solidity custom errors across all modules, introducing
Confidence Score: 3/5Two P1 bugs should be fixed before merging: a dead-code type assertion that silently drops structured revert data from PostTxProcessing hooks, and a wrong-error classification in the ERC20 transfer path. The overall refactor direction is correct and most of the Solidity error wiring is verified to match abi.json. However, the value-type assertion in state_transition.go means the entire new structured-revert pathway for post-tx hooks is never exercised, and the ERC20 transfer balance-check heuristic can return an incorrect custom error selector under edge-case bank failures. Both issues affect correctness of the primary feature introduced by this PR. x/vm/keeper/state_transition.go (wrong type assertion) and precompiles/erc20/tx.go (balance heuristic in transfer) Important Files Changed
|
| if rdp, ok := err.(types.RevertError); ok { | ||
| res.VmError = vm.ErrExecutionReverted.Error() | ||
| res.Ret = rdp.RevertData() | ||
| } else { | ||
| res.VmError = errorsmod.Wrap(err, "failed to execute post transaction processing").Error() |
There was a problem hiding this comment.
Wrong type assertion — new revert pathway never fires
types.RevertError is a struct type, and NewExecErrorWithReason returns *types.RevertError (pointer receiver). In Go, a concrete type assertion err.(types.RevertError) only succeeds when the dynamic type is the exact non-pointer value type. Since every hook that returns a RevertError does so via the pointer (&RevertError{...}), this assertion always evaluates to ok = false, the else branch always runs, and res.Ret is never populated with structured revert data — the whole new structured pathway is silently dead code.
| if rdp, ok := err.(types.RevertError); ok { | |
| res.VmError = vm.ErrExecutionReverted.Error() | |
| res.Ret = rdp.RevertData() | |
| } else { | |
| res.VmError = errorsmod.Wrap(err, "failed to execute post transaction processing").Error() | |
| if err != nil { | |
| // If hooks returns an error, revert the whole tx. | |
| if rdp, ok := err.(*types.RevertError); ok { |
| msgSrv := NewMsgServerImpl(p.BankKeeper) | ||
| if err = msgSrv.Send(ctx, msg); err != nil { | ||
| // This should return an error to avoid the contract from being executed and an event being emitted | ||
| return nil, ConvertErrToERC20Error(err) | ||
| spendable := p.BankKeeper.SpendableCoin(ctx, from.Bytes(), p.tokenPair.Denom) | ||
| bal := spendable.Amount.BigInt() | ||
| if amount.Cmp(bal) > 0 { | ||
| return nil, cmn.NewRevertWithSolidityError(p.ABI, SolidityErrERC20InsufficientBalance, from, bal, amount) | ||
| } | ||
| return nil, cmn.NewRevertWithSolidityError(p.ABI, cmn.SolidityErrMsgServerFailed, method.Name, err.Error()) | ||
| } |
There was a problem hiding this comment.
Post-hoc balance heuristic returns wrong error for non-balance send failures
After msgSrv.Send fails, the code checks whether amount > SpendableCoin to decide whether to return ERC20InsufficientBalance. This heuristic can mis-classify errors: if the send fails for a reason unrelated to balance (e.g., sends disabled for the denom, a blocked recipient, module account restrictions), but the sender's balance also happens to be less than the requested amount, ERC20InsufficientBalance is returned with incorrect balance and needed fields instead of the real underlying error. The previous ConvertErrToERC20Error at least inspected the actual error message; this approach ignores the actual failure cause entirely.
| return &RevertWithData{data: revertReasonBz} | ||
| } | ||
|
|
||
| data, err := customErr.Inputs.Pack(args...) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| return &RevertWithData{data: append(customErr.ID[:4], data...)} | ||
| } | ||
|
|
There was a problem hiding this comment.
ABI-pack failures silently fall back to
Error(string) with an ABI error message
When customErr.Inputs.Pack(args...) fails (e.g., a caller passes the wrong number or type of arguments), NewRevertWithSolidityError returns the raw pack error directly — not a RevertWithData. ReturnRevertError then encodes err.Error() (the pack diagnostic) as an Error(string) revert, so the on-chain revert data will be an ABI encoding error string rather than the intended custom error. There is no log, no panic, and no compile-time guard, making mismatches invisible until a unit test catches them.
|
@nowooj a few conflicts here |
vladjdk
left a comment
There was a problem hiding this comment.
Few greptile fixes required and conflicts need fixing.
2346ec2 to
5ce5acd
Compare
aljo242
left a comment
There was a problem hiding this comment.
ci has not run: all workflows are action_required -- fork pr, needs maintainer approval to trigger.
no abi regeneration hook: abi.json artifacts are manually edited. if solidity source drifts from its abi.json, the custom error selector will be silently wrong at runtime (falls back to string error with no build-time signal). add a make generate-abis or equivalent target and run it in ci.
unverified ReturnRevertError wrapping: several helpers (bech32/methods.go, distribution/query.go, etc.) return NewRevertWithSolidityError directly. confirm all call sites at the precompile Run() boundary wrap them in ReturnRevertError -- an unwrapped return leaks the raw revert bytes as a Go error instead of EVM revert data.
missing changelog entry: this is a breaking change (callers decoding Error(string) reverts now receive 4-byte selector custom errors). needs a breaking changes entry in CHANGELOG.md.
Added a For the |
Description
Precompile failures no longer rely primarily on string reasons. Failures are now returned as Solidity custom errors encoded from each module’s
abi.json, viaNewRevertWithSolidityErrorandReturnRevertErrorinprecompiles/common, including interpreter return data where applicable.The ERC-20 precompile drops the split
IERC20/IERC20Metadatasetup in favor ofERC20I, combining OpenZeppelinIERC20Metadata, IERC6093IERC20Errors, and the sharedIPrecompileinterface. Other precompiles (bank, distribution, gov, ics02, ics20, slashing, staking, etc.) were updated for interfaces,errors.go, and regeneratedabi.json.Tooling: Solidity 0.8.26, Hardhat
evmVersion: cancun, and OpenZeppelin v5, with contracts recompiled and artifacts aligned to Hardhat 3 (hh3-artifact-1).Tests: Go integration tests and Solidity suites (
revert_cases, ERC20) assert custom error selectors / revert data where relevant.Review focus:
precompiles/common/revert.go, use ofNewRevertWithSolidityErrorin each module’serrors.go/tx.go/query.go, and consistency betweenERC20I.soland generatedabi.json.Breaking change: Call sites that assumed only
Error(string)reverts may need to decode standard custom errors instead.Closes: #954
Author Checklist
All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.
mainbranch