Conversation
`query_balance_via_system_call` mapped every error, `EVMError::Database` included, to a zero balance. A failed state read therefore came back as "this account holds no tokens" and the transaction was rejected for insufficient funds — and the `Err(EVMError::Database(e)) => Err(e)` arm in `read_token_balance_with_fallback`, which exists precisely to propagate it, was unreachable. Report the database error and leave the revert / short-return cases as a zero balance, which are genuine statements about the token. At admission a failure to even get a state provider became `TransactionValidationOutcome::Invalid`. That is a verdict on the transaction: the pool records it as known-bad and the network layer holds the sending peer responsible for something that may be perfectly valid and merely could not be checked. Route `TokenInfoFetchFailed` to `TransactionValidationOutcome::Error` instead, which discards the attempt without blaming anyone. `TokenInfoFetchFailed::token_id` becomes `Option<u16>`: the provider failure happens before any token ID is known and was reporting a hardcoded `0`, so the error read "failed to fetch token info for ID 0" for a token that had nothing to do with it. Claude-Session: https://claude.ai/code/session_01PiUjd47Da71WG2BFkDQz9q
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (4)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe change propagates token database read errors, represents unknown token IDs explicitly, and maps token state read failures to validation errors instead of invalid transactions. ChangesToken state error handling
Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Bug fix Merge Risk: ⚪ Minimal · up to Token state-read failures are handled without an identified unresolved merge risk. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Warning Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
A failed state read is not a statement about the transaction. Two places treated it as one.
1. A database error came back as a zero balance
query_balance_via_system_call(crates/revm/src/token_fee.rs) ended with:The second arm swallows
EVMError::Databasealong with everything else, which made the arm one level up — inread_token_balance_with_fallback— dead code:So for a call-mode fee token, a failed storage read reported "this account holds no tokens" and the caller rejected the transaction for insufficient funds.
Now the database error is reported. A revert or an unusable return value still maps to a zero balance — those are statements about the token, and they match what the caller should conclude.
Reachability is not theoretical: the maintenance task holds a state provider bound to one tip and then walks every MorphTx in the pool, one storage read at a time.
2. A missing state provider marked the transaction known-bad
validate_morph_tx_balancewraps a failure to obtain a state provider asTokenInfoFetchFailed, and the caller mapped everyMorphTxErrortoTransactionValidationOutcome::Invalid.Invalidis a verdict. The pool emits aninvalidevent for it, which is what the network layer uses to hold the sending peer responsible — for a transaction that may be entirely valid and simply could not be checked.Erroremitsdiscardedinstead: the attempt is dropped, nobody is blamed, and the sender can retry.The mapping is now explicit in
morph_tx_validation_outcome, so the distinction is stated in one place rather than implied by a fall-through.3.
TokenInfoFetchFailed::token_idis nowOption<u16>The provider failure happens before any token ID is known, and the call site passed a hardcoded
0with a// token_id not yet extractedcomment. The error rendered as "failed to fetch token info for ID 0" — pointing at a token that had nothing to do with it.Tests
balance_of_fallback_reports_a_failed_state_read_instead_of_a_zero_balance— the same call-mode token is read twice, once from readable state and once with the token's storage failing. Confirmed to fail before this change.an_unreadable_fee_token_state_is_an_error_not_an_invalid_transaction/a_real_fee_token_failure_is_still_an_invalid_transaction— pins both directions of the mapping.make lint,cargo test --allandcargo test --docpass.Relationship to the other maintenance PRs
Independent of #202 and #203, and safe to merge in any order. #202 is what makes the maintenance task act on the distinction this PR restores — before it, the task removed the transaction either way.
Not in this PR
The balance query still runs against a fabricated block environment (
BlockEnv::default(),chain_id: 1) and withSYSTEM_ADDRESSasmsg.senderat a 30M gas limit, where morph-geth uses the real header, the real chain config, the user as sender and a 200k cap. That is a consensus-facing difference for any call-mode token whosebalanceOfreads block context ormsg.sender, and it gets its own PR — it touches the execution path.Found by an external review of the pool maintenance path.
https://claude.ai/code/session_01PiUjd47Da71WG2BFkDQz9q
Summary by CodeRabbit
Bug Fixes
Tests