Skip to content

Surface transfer simulation revert reasons in order creation API#4321

Merged
squadgazzz merged 15 commits intomainfrom
surface-transfer-revert-reason
Apr 10, 2026
Merged

Surface transfer simulation revert reasons in order creation API#4321
squadgazzz merged 15 commits intomainfrom
surface-transfer-revert-reason

Conversation

@squadgazzz
Copy link
Copy Markdown
Contributor

@squadgazzz squadgazzz commented Apr 9, 2026

Summary

  • When order creation fails with TransferSimulationFailed, the API now includes the actual revert reason (e.g. 0x5b263df7 = LtvValidationFailed()) instead of the generic "sell token cannot be transferred"
  • Changed Balances.sol helper contract to capture revert bytes from the transferFromAccounts try/catch block
  • Threaded revert bytes through SimulationTransferSimulationError::TransferFailed(Vec<u8>)ValidationError::TransferSimulationFailed(Vec<u8>) → API error description
  • Deployed new Balances contract at 0x88b4B74082BffB2976C306CB3f7E9093AE48B94F on all chains (except Lens which keeps the old deployment)
  • Updated e2e forked test block numbers to include the new deployment

Motivation

Aave aTokens (e.g. aBasUSDC) revert with LtvValidationFailed() when a user has zero-LTV collateral blocking transfers. The current generic error gives no indication of why the transfer fails, making it very difficult to debug.

API Change

Before:

{"errorType": "TransferSimulationFailed", "description": "sell token cannot be transferred"}

After (when revert data available):

{"errorType": "TransferSimulationFailed", "description": "sell token cannot be transferred, token reverted with: 0x5b263df7"}

The 4-byte selector can be decoded client-side (e.g. cast 4byte 0x5b263df7LtvValidationFailed()).

Changes

  • contracts/solidity/Balances.solcatchcatch (bytes memory reason), added transferRevertReason return value
  • contracts/artifacts/Balances.json — Rebuilt with solc
  • crates/account-balances/src/lib.rs — Added transfer_revert_reason to Simulation, changed TransferFailed to carry Vec<u8>, switched to abi_decode_params (required for dynamic return types)
  • crates/account-balances/src/simulation.rs — Pass revert bytes into TransferFailed
  • crates/shared/src/order_validation.rs — Thread Vec<u8> through TransferSimulationFailed
  • crates/orderbook/src/api/post_order.rs — Format revert selector in error description
  • contracts/src/main.rs — Updated Balances addresses to new deployment 0x88b4B74082BffB2976C306CB3f7E9093AE48B94F
  • Justfile — Added formatting steps to generate-contracts
  • e2e forked tests — Updated fork block numbers and whale addresses for new deployment

New Balances Deployment

Address 0x88b4B74082BffB2976C306CB3f7E9093AE48B94F on: Mainnet, Arbitrum, Base, Avalanche, BNB, Optimism, Polygon, Gnosis, Linea, Ink, Sepolia, Plasma. And also Optimism, since we deployed everything there already and in case we will add support for this chain sometime later.

Lens keeps the old address (0x3e8C6De9510e7ECad902D005DE3Ab52f35cF4f1b), since it will be dropped from the codebase in a follow-up PR.

Test plan

  • Existing unit tests pass (account-balances, shared, orderbook)
  • Clippy clean
  • E2E local node tests pass
  • E2E forked node tests pass
  • Manual test: submit order with aToken that has LTV-blocked transfer, verify revert selector appears in error

@squadgazzz squadgazzz force-pushed the surface-transfer-revert-reason branch from 849c9b0 to f618907 Compare April 9, 2026 13:24
@squadgazzz squadgazzz force-pushed the surface-transfer-revert-reason branch from 354c05b to 01076f4 Compare April 9, 2026 14:03
@squadgazzz squadgazzz marked this pull request as ready for review April 9, 2026 14:06
@squadgazzz squadgazzz requested a review from a team as a code owner April 9, 2026 14:06
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enhances the balance simulation by capturing and propagating the revert reason when a transfer fails. Changes span the Solidity contract, generated bindings, and the backend validation logic. Feedback was provided regarding the error mapping in the order validator, where internal simulation errors are currently converted into client-side validation failures, potentially masking RPC or node issues and returning incorrect status codes.

TransferSimulationError::Other(err) => {
tracing::warn!("TransferSimulation failed: {:?}", err);
Err(ValidationError::TransferSimulationFailed)
Err(ValidationError::TransferSimulationFailed(Vec::new()))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Mapping TransferSimulationError::Other to ValidationError::TransferSimulationFailed(Vec::new()) is potentially misleading. If the simulation failed due to an internal error (e.g., node connection issue, RPC failure), it should ideally be propagated as ValidationError::Other to trigger a 500 Internal Server Error instead of a 400 Bad Request. While this preserves existing behavior, the addition of the empty Vec highlights that we are losing the actual error context here.

Suggested change
Err(ValidationError::TransferSimulationFailed(Vec::new()))
Err(ValidationError::Other(err))

@squadgazzz squadgazzz marked this pull request as draft April 9, 2026 14:31
The return type now includes `bytes` (a dynamic type), so the tuple
is dynamically encoded. `abi_decode` wraps data in a single-element
tuple expecting an offset prefix, while `abi_decode_params` correctly
decodes the raw ABI-encoded tuple from simulateDelegatecall.
Mainnet: 23112197/23326100/23688436 -> 24843565
Gnosis: 41502478 -> 45588623
The previous whale (0x762d) ran out of ETH by block 24843565.
Use Binance (0x28c6) which has both DAI and ETH.
@squadgazzz squadgazzz marked this pull request as ready for review April 9, 2026 18:09
@squadgazzz squadgazzz requested a review from fafk April 9, 2026 18:10
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the Balances contract and simulation infrastructure to capture and return the revert reason when a transfer simulation fails. The changes involve Solidity contract updates, Rust binding regenerations, and API response modifications. Feedback was provided regarding the truncation of revert data in the API response; instead of providing only the first four bytes, the full revert data should be returned to enable clients to decode standard error strings and panic reasons.

Include the entire revert_data hex instead of just the 4-byte
selector, so clients can decode standard Error(string) and
Panic(uint256) reasons.
Copy link
Copy Markdown
Contributor

@jmg-duarte jmg-duarte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Copy Markdown
Contributor

@MartinquaXD MartinquaXD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good change! 👍

@squadgazzz squadgazzz enabled auto-merge April 10, 2026 16:54
@squadgazzz squadgazzz added this pull request to the merge queue Apr 10, 2026
Merged via the queue into main with commit 9281fe1 Apr 10, 2026
19 checks passed
@squadgazzz squadgazzz deleted the surface-transfer-revert-reason branch April 10, 2026 17:14
@github-actions github-actions bot locked and limited conversation to collaborators Apr 10, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants