diff --git a/tools/CLAUDE.md b/tools/CLAUDE.md index a67f2479f..4f1f2e01d 100644 --- a/tools/CLAUDE.md +++ b/tools/CLAUDE.md @@ -63,8 +63,12 @@ Four subcommands via `cargo run -p hindsight --release --`: - **`verify`** — Diff decoded trades against Allium's `aggregator_trades` ground truth. Requires `ALLIUM_API_KEY` and `ALLIUM_QUERY_ID`. - **`monitor`** — Live mode: drives an in-process `fynd-core` solver block-by-block, solving - each settled trade at top-of-block (N-1) and again at back-of-block (N), where the top route - is also re-executed to measure slippage. Emits JSONL and exposes Prometheus metrics. + each trade with known terms — settled or reverted — at top-of-block (N-1) and again at + back-of-block (N), where the top route is also re-executed to measure slippage. A settled + trade is judged against what it settled for; a decodable reverted Relay swap is judged against + its on-chain `min_amount_out` floor at both states, to quantify how many reverts a sequencer + filling against fresher state could avoid. Emits one JSONL stream (settled and reverted trades + told apart by a `status` field) and exposes Prometheus metrics. - **`report`** — Offline: render a self-contained HTML report from a `monitor` run's comparison JSONL (`--comparisons-dir`). No chain or network access. diff --git a/tools/hindsight/CLAUDE.md b/tools/hindsight/CLAUDE.md index 12a5abeb9..20f0bcac5 100644 --- a/tools/hindsight/CLAUDE.md +++ b/tools/hindsight/CLAUDE.md @@ -26,13 +26,16 @@ name needs `--registry`. `ALLIUM_API_KEY` and `ALLIUM_QUERY_ID`. - **`monitor`** — Live mode: drives an in-process `fynd-core` solver block-by-block. For each - block it decodes settled trades, solves each order at top-of-block (state N-1), then measures - twice at back-of-block (state N): the top route is re-executed via `fynd_core::replay_route` - to measure slippage between quote time and execution time, and the order is solved fresh to - show what routing at the block's end state would deliver. Emits `RangeComparison` JSONL - records. Exposes a Prometheus metrics endpoint (`--metrics-port`). `--max-lag-blocks` (default - 100, ~20 min on mainnet) bounds how far it may fall behind chain head before rebuilding the - solver. + block it decodes every trade — settled and reverted alike — solves each one with known terms at + top-of-block (state N-1), then measures twice at back-of-block (state N): the top route is + re-executed via `fynd_core::replay_route` to measure slippage between quote time and execution + time, and the trade is solved fresh to show what routing at the block's end state would + deliver. A settled trade is judged against what it settled for; a reverted trade whose calldata + parsed into a `SwapIntent` is judged against whether the quote would have cleared the trader's + on-chain floor (`min_amount_out`) instead — told apart by one `status` field, not by separate + record types. Emits one `RangeComparison` JSONL stream (`comparisons-*.jsonl`) and exposes a + Prometheus metrics endpoint (`--metrics-port`). `--max-lag-blocks` (default 100, ~20 min on + mainnet) bounds how far it may fall behind chain head before rebuilding the solver. - **`report`** — Offline: read the `comparisons-YYYY-MM-DD.jsonl` files a `monitor` run wrote (`--comparisons-dir`) and render a single self-contained HTML file (`-o`, defaults to @@ -58,7 +61,7 @@ Match → trace → decode → veto → record. | File/dir | Purpose | |---|---| -| `matching.rs` | Receipt-only filter: is this transaction a solver trade at all, plus match-time vetoes | +| `matching.rs` | Receipt-only filter: is this transaction a solver trade at all — settled or reverted, distinguished by `MatchedSolverTrade::reverted`. A settled match is filtered by tx.to/solver logs plus match-time vetoes; a reverted match is by entry point alone (gated by `REVERT_DECODED_VENUES`) — a revert emits no logs, so this is the only signal available | | `decode.rs` | The `TradeDecoder` trait, the matched entity → decoders mapping, `DecodeContext`, `TraderFlow` | | `netting_decoders.rs` | Netting toolkit (`sender_flow`, `venue_flow`) plus the `SenderNetting` decoder | | `transfer_ledger.rs` | Builds a transfer ledger from logs and native ETH flows | @@ -66,9 +69,9 @@ Match → trace → decode → veto → record. | `registry.rs` | Per-chain address book, loaded from TOML (see below) | | `sandwich.rs` | Flags trades bracketed by a front/back attacker pair (see the design spec) | | `venues/` | Per-venue `TradeDecoder` impls (Relay, MetaMask, Rabby), listed in `venues::decoders_for`. Relay has two, tried in order: `RelayCalldata` (calldata-primary, see below) then `RelayNetting` (the fallback) | -| `solvers/` | Per-solver knowledge: embedded quotes, match-time vetoes, attribution, and swap intents (`fly.rs`'s packed-calldata parser, `kyberswap.rs`'s ABI-decoded `swap` params, `zeroex.rs`'s ABI-decoded `AllowanceHolder.exec`/`Settler.execute`) recovered from a solver frame's own calldata, plus the declared output recipient (`output_recipient`) that lets `RelayCalldata` anchor the settled amount | +| `solvers/` | Per-solver knowledge: embedded quotes, match-time vetoes, attribution, swap intents (`fly.rs`'s packed-calldata parser, `kyberswap.rs`'s ABI-decoded `swap` params, `zeroex.rs`'s ABI-decoded `AllowanceHolder.exec`/`Settler.execute`) recovered from a solver frame's own calldata via `trace::find_solver_frame` — for settled trades that frame settled, for reverted trades the frame that tried — plus each solver's slippage-floor marker (`is_slippage_floor`, e.g. Fly's `InsufficientAmountOut()` selector, KyberSwap's "Return amount is not enough" reason, `zeroex.rs`'s `TooMuchSlippage(address,uint256,uint256)` selector) and the declared output recipient (`output_recipient`) that lets `RelayCalldata` anchor the settled amount | | `intents/` | Intent-role decoders (solver-sent, trader-not-sender): `cow.rs` reads CoW's `Trade` event, `netting.rs` is the generic net-flow finder, `decoders_for` lists them | -| `trace.rs` | Transaction trace fetching and processing | +| `trace.rs` | Transaction trace fetching and processing: `find_solver_frame` (the frame that settled the swap, or — when nothing settled — the frame that tried) and revert-cause classification (the generic subtree walk, out-of-gas, deepest-revert-reason; the slippage-floor marker itself is asked of `solvers::`, never hardcoded here) | `src/verify/` contains the Allium integration: - `allium.rs` — Allium API client for the `verify` subcommand @@ -93,10 +96,10 @@ their solver in calldata — `solver_aliases`). | File | Purpose | |---|---| -| `mod.rs` | `SteppingSolver` trait; `resolve_block_range` — solve all trades at top, advance, then re-execute each top route and solve each trade fresh at back | -| `compare.rs` | `Verdict` / `Deltas` / `Slippage` — bps diff, win/loss/coverage-miss classification, quote-vs-re-execution slippage | +| `mod.rs` | `SteppingSolver` trait; `resolve_block_range` — one wave over every trade (settled and reverted alike): solve those with known terms at top, advance, then re-execute each top route and solve fresh at back. A trade with unknown terms is recorded but not solved | +| `compare.rs` | `Verdict` / `Deltas` / `Slippage` — bps diff, win/loss/coverage-miss classification, quote-vs-re-execution slippage; `floor_judgment` — fillable/margin judgment against `min_amount_out`, used for both settled and reverted trades when a floor is known | | `monitor.rs` | Production `monitor` subcommand: in-process solver, block subscription, JSONL emission | -| `jsonl.rs` | Append-only JSONL writer used by `monitor` | +| `jsonl.rs` | `write_comparisons` — the one append-only JSONL stream (`comparisons-*.jsonl`) `monitor` writes, via the rotating-by-day `RotatingWriter`; every trade's record carries `tx_hash`, `sender` (the netted trader for a settled trade, the transaction sender for a reverted one — segments venue-filler fills like Relay's rotating filler without an on-chain lookup), a flat `status` (`settled`/`reverted`), and, for a revert, `cause`/`cause_detail` | ### Report (`src/report/`) @@ -105,7 +108,7 @@ self-contained HTML file. | File | Purpose | |---|---| -| `mod.rs` | `ReportArgs`; reads every `.jsonl` in the dir, skipping malformed lines | +| `mod.rs` | `ReportArgs`; reads every `comparisons-*.jsonl` in the dir, skipping malformed lines, then filters to `status == "settled"` before aggregating — savings and win-rate only mean something for a trade that delivered an output | | `record.rs` | The subset of the JSONL record the report deserializes; round-trip tested against `jsonl::write_comparisons` | | `aggregate.rs` | Pure aggregations over the records (verdicts, coverage, savings, per-group, movers) | | `html.rs` | Renders the aggregates to a self-contained HTML file (inline CSS, `
` bars, no assets) | @@ -146,14 +149,48 @@ fees are still recorded via `venue_fee_in`/`venue_fee_out` for transparency. See coverage rises from 60.0% (netting alone) to 91.4% (calldata-first union), with zero divergences across the 165 trades both paths could decode. +### Revert model + +A reverted trade is still a trade — one that did not fill — so it is modeled by `DecodedTrade`, +told apart from a settled one by its `status` field (`TradeStatus::Settled` or +`TradeStatus::Reverted { cause }`), not by a parallel type. Reverted Relay swaps are matched by +entry point alone (no logs to match on), decoded from their trace for the settling solver — via +the same `trace::find_solver_frame` a settled trade uses, whose strict-then-tolerant walk falls +back to "the frame that tried" when nothing settled — and, when that solver's calldata supports +it (Fly, KyberSwap), the trader's terms (`token_in`/`token_out`/`amount_in`/`min_amount_out`), +which land in the same fields a settled trade's terms do. `RevertCause` classifies why the +transaction failed: `SlippageFloor` (a registered solver's `is_slippage_floor` marker — Fly's +`InsufficientAmountOut()` selector, KyberSwap's "Return amount is not enough" reason, 0x's +`TooMuchSlippage(address,uint256,uint256)` selector or a routed-through liquidity source's +"return too low" — the avoidable class), `OutOfGas`, or `Other` (the deepest reverted frame's +error/revert reason, with the frame's raw output selector appended when the tracer decoded no +string reason, e.g. `"execution reverted (0x12345678)"` — several RPC providers never populate +a decoded reason at all, so the selector is what stays classifiable offline). + +A reverted trade with known terms is re-solved at top-of-block (N-1) and back-of-block (N) +through the same wave a settled trade goes through (`resolve_block_range`), then judged by the +same `StateResult` every trade gets: `fillable` (quote ≥ `min_amount_out`) and `margin_bps` +(signed bps of the quote against the floor), quantifying how many reverts a sequencer filling +against fresher state could avoid. A settled trade gets this judgment too, whenever a floor is +known — it cleared the floor by construction, but the margin is still informative. A trade with +unknown terms (a reverted trade whose solver calldata did not parse) is recorded but not solved — +only counted, so parser coverage stays measurable — and every trade, settled or reverted, writes +to the one `comparisons-YYYY-MM-DD.jsonl` stream; there is no separate revert stream or type. + ### Key types -- `DecodedTrade` — decoded on-chain trade; amounts are venue-fee-adjusted so re-solve compares - like-for-like. Carries `sandwich` evidence when a bracket pair was found, and `min_amount_out`, - `declared_quote`, and `quote_timestamp` (the calldata-declared terms copied off the settling - solver's `SwapIntent`, when one was recovered). -- `RangeComparison` — a trade solved at top and back, including gas-netted settled output and - the top route's `Slippage` between the two states (from its re-execution at back). +- `DecodedTrade` — a decoded on-chain trade, settled or reverted, told apart by `status: + TradeStatus`. A settled trade always carries `token_in`/`token_out`/`amount_in`/`amount_out` + (amounts venue-fee-adjusted so re-solve compares like-for-like) and, when a bracket pair was + found, `sandwich` evidence; a reverted trade carries `token_in`/`token_out`/`amount_in` only + when its solver frame's calldata parsed, and never `amount_out` — nothing was delivered. Both + carry `min_amount_out`, `declared_quote`, and `quote_timestamp` when known (the calldata-declared + terms copied off the settling solver's `SwapIntent`). +- `RangeComparison` — a trade solved at top and back — settled or reverted, told apart by + `status` — including gas-netted settled output (settled only), the top route's `Slippage` + between the two states, and, per state, the `fillable`/`margin_bps` judgment against + `min_amount_out` when a floor is known. `top`/`back`/`verdict`/`slippage` are absent when the + trade's terms were unknown. - `Outcome` — `Solved`, `Partial`, or `Unsolvable`. - `SolvedAmount` — a solved state's amounts plus `algorithm` (which worker pool won the quote) and `solved_route` (the full `fynd_core::types::Route`, kept in memory to replay at back-of-block). diff --git a/tools/hindsight/README.md b/tools/hindsight/README.md index 36c021d2d..b10de5196 100644 --- a/tools/hindsight/README.md +++ b/tools/hindsight/README.md @@ -226,7 +226,7 @@ collectors are re-verified on every chain a venue is added on. | You want to… | Touch | Without it | |---|---|---| | Track a new solver | One line in the address book's `[solvers]` section. No code — trades sent straight to the router then match on the entry point and decode like any other; the intent and veto rows below are optional extras | Trades sent directly to the solver's router never match, so they never appear in the output; trades a known venue routed through it still decode, but the solver is recorded as "unknown" | -| Recover a solver's swap terms (tokens, amounts, on-chain floor, and — when its calldata declares one — its off-chain quote) | A `swap_intent` method on its `SolverKnowledge` impl, dispatched with the settling solver frame's own input | A trade's record carries no `min_amount_out` / `declared_quote` / `quote_timestamp` | +| Recover a solver's swap terms (tokens, amounts, on-chain floor, and — when its calldata declares one — its off-chain quote) | A `swap_intent` method on its `SolverKnowledge` impl, dispatched with the settling solver frame's own input | A settled trade's record carries no `min_amount_out` / `declared_quote` / `quote_timestamp`; a revert is still recorded (venue, solver, cause) but with no `SwapIntent`, so it cannot be resolved or judged for avoidance — only counted as parser coverage | | Skip a solver's non-swap orders | A `solver_veto` method on its `SolverKnowledge` impl | Those orders decode as trades that never happened, with absurd rates | | Add a venue | A `[venues.]` section in the address book, a `TradeDecoder` in `venues/`, one arm in `venues::decoders_for` | The venue's trades are missed: with no entry-point match they only surface when a known solver logs inside them, and intent decoding then excludes the trader | | Extend what Hindsight knows about a venue | That venue's module in `venues/` — never anywhere else | Decoding degrades silently | @@ -241,20 +241,27 @@ collectors are re-verified on every chain a venue is added on. ``` tycho stream ──▶ in-process Fynd solver, held at block N-1 │ - decode block N │ re-solve every settled trade → top-of-block result + decode block N │ re-solve every trade with known terms → top-of-block result ▼ advance solver to N - │ re-solve every trade again → back-of-block result + │ re-solve every trade again → back-of-block result ▼ RangeComparison ──▶ JSONL records + Prometheus metrics ``` +A settled and a reverted trade go through the same wave: `RangeComparison` carries a `status` +field (`settled` or `reverted`, with a cause) rather than being two record types. A trade with +unknown terms — a reverted swap whose solver calldata did not parse — is recorded but not solved. + Top-of-block (state N-1) is the optimistic comparison — Fynd sees the pools before the block's -own swaps moved them; back-of-block (state N) is the pessimistic one. The headline verdict is -top-of-block: `Win`, `Loss`, `CoverageMiss` (Fynd filled under half the settled size), -`Unsolvable`, or `Sandwiched` (the settled output was moved by MEV; excluded from savings -aggregates). Watchdogs rebuild the solver when the tycho feed dies or the monitor falls too -far behind chain head (`--max-lag-blocks`). +own swaps moved them; back-of-block (state N) is the pessimistic one. For a settled trade, the +headline verdict is top-of-block: `Win`, `Loss`, `CoverageMiss` (Fynd filled under half the +settled size), `Unsolvable`, or `Sandwiched` (the settled output was moved by MEV; excluded from +savings aggregates). A reverted trade has no settled output to score a verdict against, but each +state still carries a `fillable`/`margin_bps` judgment against the trader's on-chain floor +(`min_amount_out`) — how many reverts a sequencer filling against fresher state could avoid. +Watchdogs rebuild the solver when the tycho feed dies or the monitor falls too far behind chain +head (`--max-lag-blocks`). Each solved state also records which algorithm won the quote and the route it took, rendered as `USDT -[uniswap_v2]-> DAI -[vm:balancer]-> WETH` (a split fans into ` + `-joined legs, each with its diff --git a/tools/hindsight/src/decoder/fixtures/trace_revert_fly_slippage_0xcba01d0c.json b/tools/hindsight/src/decoder/fixtures/trace_revert_fly_slippage_0xcba01d0c.json new file mode 100644 index 000000000..413444311 --- /dev/null +++ b/tools/hindsight/src/decoder/fixtures/trace_revert_fly_slippage_0xcba01d0c.json @@ -0,0 +1 @@ +{"from": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "gas": "0x11b15b", "gasUsed": "0x50c55", "to": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "input": "0xcd6e13f7000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000030000000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006f05b59d3b200000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000020446ec278a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001bd018e0040b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f0000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000006f05b59d3b20000e00114e00119e0011ea4a9ebda8d3a6595cf15b1d46ea0e440a9810c2b99a3e889c6b3b46f7ff0e5e1f0012300be558cc8c9aae9b6a3b4324923bea142611cbc9ece60da6178ee6f23cf1220f43e7e1a0ab105a2d26a31c60cf4c6bf310d7be4e0e2b91203eb8efad1d2feab3b1c00e06a69e7e3e038652385e038f6f7ebf027104200000000000000000000000000000000000006c006f05b59d3b200000201250e0139013900040a0b0000000000000000000000000000000000000000000000000000000000d05af3107a400003014a0e0139013903016bf513ef4cf80180fffffffffffffffffffffffffffffffff8000c0139013901017d0301810500000301810301830301810301940301810301136a6951db3bb99f76a8ccb9f8a8399b613666298b020070020070070500200500000142014a00000700200172017d0000000000019601b601b606002001ca01cd000003000001cd01d4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001a49bb43718000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "output": "0xe52970aa", "error": "execution reverted", "calls": [{"from": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "gas": "0x10b240", "gasUsed": "0x44ef2", "to": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "input": "0x46ec278a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001bd018e0040b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f0000000000000000000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000006f05b59d3b20000e00114e00119e0011ea4a9ebda8d3a6595cf15b1d46ea0e440a9810c2b99a3e889c6b3b46f7ff0e5e1f0012300be558cc8c9aae9b6a3b4324923bea142611cbc9ece60da6178ee6f23cf1220f43e7e1a0ab105a2d26a31c60cf4c6bf310d7be4e0e2b91203eb8efad1d2feab3b1c00e06a69e7e3e038652385e038f6f7ebf027104200000000000000000000000000000000000006c006f05b59d3b200000201250e0139013900040a0b0000000000000000000000000000000000000000000000000000000000d05af3107a400003014a0e0139013903016bf513ef4cf80180fffffffffffffffffffffffffffffffff8000c0139013901017d0301810500000301810301830301810301940301810301136a6951db3bb99f76a8ccb9f8a8399b613666298b020070020070070500200500000142014a00000700200172017d0000000000019601b601b606002001ca01cd000003000001cd01d40000000000", "output": "0xe52970aa", "error": "execution reverted", "calls": [{"from": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "gas": "0x105a72", "gasUsed": "0xbb8", "to": "0x0000000000000000000000000000000000000001", "input": "0x3c4eb8a32d205a531db02ad0a29d8ad5c32c2c6d66cbe3afa61218604fca1b0b000000000000000000000000000000000000000000000000000000000000001cbe558cc8c9aae9b6a3b4324923bea142611cbc9ece60da6178ee6f23cf1220f43e7e1a0ab105a2d26a31c60cf4c6bf310d7be4e0e2b91203eb8efad1d2feab3b", "output": "0x00000000000000000000000028de4113921bd79388b0ef80a259f871977442e9", "type": "STATICCALL"}, {"from": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "gas": "0x1039c8", "gasUsed": "0x2616", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a08231000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0xfdd5d", "gasUsed": "0x9f9", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a08231000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "gas": "0x101300", "gasUsed": "0xcb2", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a0823100000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c", "output": "0x0000000000000000000000000000000000000000000000000000000000101b00", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0xfd02f", "gasUsed": "0x9f9", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a0823100000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c", "output": "0x0000000000000000000000000000000000000000000000000000000000101b00", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "gas": "0xfd97d", "gasUsed": "0x1a2e", "to": "0x4200000000000000000000000000000000000006", "input": "0xd0e30db0", "value": "0x6f05b59d3b20000", "type": "CALL"}, {"from": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "gas": "0xfb4c7", "gasUsed": "0x6145", "to": "0x4200000000000000000000000000000000000006", "input": "0xa9059cbb00000000000000000000000009ad820aac5779683b481c4674208a4e1b024afa00000000000000000000000000000000000000000000000006f05b59d3b20000", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "CALL"}, {"from": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "gas": "0xf4860", "gasUsed": "0x2f655", "to": "0x09ad820aac5779683b481c4674208a4e1b024afa", "input": "0x158f6894000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001bd018e0040b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f4200000000000000000000000000000000000006833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000006f05b59d3b20000e00114e00119e0011ea4a9ebda8d3a6595cf15b1d46ea0e440a9810c2b99a3e889c6b3b46f7ff0e5e1f0012300be558cc8c9aae9b6a3b4324923bea142611cbc9ece60da6178ee6f23cf1220f43e7e1a0ab105a2d26a31c60cf4c6bf310d7be4e0e2b91203eb8efad1d2feab3b1c00e06a69e7e3e038652385e038f6f7ebf027104200000000000000000000000000000000000006c006f05b59d3b200000201250e0139013900040a0b0000000000000000000000000000000000000000000000000000000000d05af3107a400003014a0e0139013903016bf513ef4cf80180fffffffffffffffffffffffffffffffff8000c0139013901017d0301810500000301810301830301810301940301810301136a6951db3bb99f76a8ccb9f8a8399b613666298b020070020070070500200500000142014a00000700200172017d0000000000019601b601b606002001ca01cd000003000001cd01d40000", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x09ad820aac5779683b481c4674208a4e1b024afa", "gas": "0xef583", "gasUsed": "0x239f", "to": "0x4200000000000000000000000000000000000006", "input": "0x2e1a7d4d00000000000000000000000000000000000000000000000006f05b59d3b20000", "calls": [{"from": "0x4200000000000000000000000000000000000006", "gas": "0x8fc", "gasUsed": "0x37", "to": "0x09ad820aac5779683b481c4674208a4e1b024afa", "input": "0x", "value": "0x6f05b59d3b20000", "type": "CALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x09ad820aac5779683b481c4674208a4e1b024afa", "gas": "0xe96d8", "gasUsed": "0x26657", "to": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "input": "0xf513ef4c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000006a69e7e3", "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xe49f8", "gasUsed": "0x2535d", "to": "0x57dafd63aa581657b0a76db413f371e17a1b0cbe", "input": "0xf513ef4c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000006a69e7e3", "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xde542", "gasUsed": "0xac1", "to": "0x2a24ecb1962cdbaed8f41c587751550bacea063d", "input": "0xb8b5ce81", "value": "0x0", "type": "CALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xdd8ce", "gasUsed": "0x41e7", "to": "0x2a24ecb1962cdbaed8f41c587751550bacea063d", "input": "0x24675a0900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000138800000000000000000000000000000000ffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000001", "output": "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x2a24ecb1962cdbaed8f41c587751550bacea063d", "gas": "0xd950f", "gasUsed": "0xa4a", "to": "0x4ad6120c42d5e1bf8ec56f37c437da7b8621c582", "input": "0x9ab9bb6c", "output": "0x34046260d00000000000000000000000000000000000fb880fb8902eab32eab6", "type": "STATICCALL"}, {"from": "0x2a24ecb1962cdbaed8f41c587751550bacea063d", "gas": "0xd8939", "gasUsed": "0xa60", "to": "0x4ad6120c42d5e1bf8ec56f37c437da7b8621c582", "input": "0xa1dc9031", "output": "0x000000000000000000000000000000000000000004932c480ebf3a672aca6a8d", "type": "STATICCALL"}], "type": "STATICCALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xd87bf", "gasUsed": "0x59d4", "to": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "input": "0xc3f909d4", "output": "0x00000000000000000000000000000000000000000000000000005af3107a400000000000000000000000000000000000000000000000000000000000000000010000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000030bf17711657bfb5d6645aad39984d5ccb086ec0000000000000000000000003484f248f7b0dfe1f826b145e9d582a6e034ccb400000000000000000000000000000000000000000000000006f05b59d3b2000000000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "gas": "0xd3f45", "gasUsed": "0x46cb", "to": "0xf1466addf635bf0ba0a0c4bb9fecaa03b0945510", "input": "0xc3f909d4", "output": "0x00000000000000000000000000000000000000000000000000005af3107a400000000000000000000000000000000000000000000000000000000000000000010000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000030bf17711657bfb5d6645aad39984d5ccb086ec0000000000000000000000003484f248f7b0dfe1f826b145e9d582a6e034ccb400000000000000000000000000000000000000000000000006f05b59d3b2000000000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xd1e12", "gasUsed": "0xb92", "to": "0x3484f248f7b0dfe1f826b145e9d582a6e034ccb4", "input": "0xe146af470000000000000000000000000000000000000000000000000000010000000001000000000000000000000000000000000000000000000000000000000000138800000000000000000000000000000000ffffffffffffffffffffffffffffffff", "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "type": "STATICCALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xd0eae", "gasUsed": "0xf3e", "to": "0x4200000000000000000000000000000000000006", "input": "0xd0e30db0", "value": "0x0", "type": "CALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xcfddb", "gasUsed": "0x1193", "to": "0x4200000000000000000000000000000000000006", "input": "0x095ea7b30000000000000000000000007c976801c8589d924ce93bbcce6966d34edf91bf0000000000000000000000000000000000000000000000000000000000000000", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "CALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xceac1", "gasUsed": "0xc62", "to": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "input": "0x506dcce400000000000000000000000009ad820aac5779683b481c4674208a4e1b024afa0000000000000000000000006a6951db3bb99f76a8ccb9f8a8399b613666298b", "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "gas": "0xcb603", "gasUsed": "0xb26", "to": "0xf1466addf635bf0ba0a0c4bb9fecaa03b0945510", "input": "0x506dcce400000000000000000000000009ad820aac5779683b481c4674208a4e1b024afa0000000000000000000000006a6951db3bb99f76a8ccb9f8a8399b613666298b", "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xcdb7a", "gasUsed": "0x6fbd", "to": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "input": "0xf513ef4c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000006a69e6b9", "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "gas": "0xca6d6", "gasUsed": "0x6e57", "to": "0xf1466addf635bf0ba0a0c4bb9fecaa03b0945510", "input": "0xf513ef4c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000006a69e6b9", "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "gas": "0xc55b3", "gasUsed": "0x31c", "to": "0x3484f248f7b0dfe1f826b145e9d582a6e034ccb4", "input": "0xa558898f00000000000000000000000000000000000000000000000000000100000000010000000000000000000000000000000000000000000000000000000000001388", "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "type": "CALL"}, {"from": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "gas": "0xc2df7", "gasUsed": "0x153", "to": "0x3484f248f7b0dfe1f826b145e9d582a6e034ccb4", "input": "0x2e286683", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "type": "STATICCALL"}, {"from": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "gas": "0xc20cf", "gasUsed": "0x923", "to": "0x030bf17711657bfb5d6645aad39984d5ccb086ec", "input": "0x2e286683", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "type": "STATICCALL"}, {"from": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "gas": "0xc1537", "gasUsed": "0xb7f", "to": "0x4ad6120c42d5e1bf8ec56f37c437da7b8621c582", "input": "0x558f514800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xc57b2", "gasUsed": "0x1f25", "to": "0x01dda2b73ede58c840d73379710a18cbc5fe290e", "input": "0x4d35daf300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x01dda2b73ede58c840d73379710a18cbc5fe290e", "gas": "0xc09cc", "gasUsed": "0x123", "to": "0x947c13afe52b8676144664e12487544718089267", "input": "0x8d64be7e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "type": "STATICCALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xc3797", "gasUsed": "0x1c9", "to": "0x4200000000000000000000000000000000000006", "input": "0x70a082310000000000000000000000006a6951db3bb99f76a8ccb9f8a8399b613666298b", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "type": "STATICCALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xc33ff", "gasUsed": "0xcb2", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a082310000000000000000000000006a6951db3bb99f76a8ccb9f8a8399b613666298b", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0xc00aa", "gasUsed": "0x9f9", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a082310000000000000000000000006a6951db3bb99f76a8ccb9f8a8399b613666298b", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xc0c4d", "gasUsed": "0x37", "to": "0x09ad820aac5779683b481c4674208a4e1b024afa", "input": "0x", "value": "0x6f05b59d3b20000", "type": "CALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xc08c1", "gasUsed": "0x4154", "to": "0x2a24ecb1962cdbaed8f41c587751550bacea063d", "input": "0xec3a5314", "calls": [{"from": "0x2a24ecb1962cdbaed8f41c587751550bacea063d", "gas": "0xbcb5c", "gasUsed": "0x901", "to": "0x49b1a6b424c19803fa64a5b40baa7f0428e258b3", "input": "0x18160ddd", "output": "0x000000000000000000000000000000000000000000005987575d2b7d9d3cecb0", "type": "STATICCALL"}, {"from": "0x2a24ecb1962cdbaed8f41c587751550bacea063d", "gas": "0xbc0bc", "gasUsed": "0x27a", "to": "0x4ad6120c42d5e1bf8ec56f37c437da7b8621c582", "input": "0x9ab9bb6c", "output": "0x34046260d00000000000000000000000000000000000fb880fb8902eab32eab6", "type": "STATICCALL"}, {"from": "0x2a24ecb1962cdbaed8f41c587751550bacea063d", "gas": "0xbbc9f", "gasUsed": "0x290", "to": "0x4ad6120c42d5e1bf8ec56f37c437da7b8621c582", "input": "0xa1dc9031", "output": "0x000000000000000000000000000000000000000004932c480ebf3a672aca6a8d", "type": "STATICCALL"}], "value": "0x0", "type": "CALL"}], "value": "0x6f05b59d3b20000", "type": "DELEGATECALL"}], "value": "0x6f05b59d3b20000", "type": "CALL"}, {"from": "0x09ad820aac5779683b481c4674208a4e1b024afa", "gas": "0xc34b7", "gasUsed": "0xcb2", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a0823100000000000000000000000009ad820aac5779683b481c4674208a4e1b024afa", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0xc015f", "gasUsed": "0x9f9", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a0823100000000000000000000000009ad820aac5779683b481c4674208a4e1b024afa", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "gas": "0xc5c8c", "gasUsed": "0x4e2", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a0823100000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c", "output": "0x0000000000000000000000000000000000000000000000000000000000101b00", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0xc2895", "gasUsed": "0x229", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a0823100000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c", "output": "0x0000000000000000000000000000000000000000000000000000000000101b00", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "gas": "0xc557f", "gasUsed": "0x1cc3", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0xa9059cbb000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f0000000000000000000000000000000000000000000000000000000000000000", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0xc21a1", "gasUsed": "0x1a07", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0xa9059cbb000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f0000000000000000000000000000000000000000000000000000000000000000", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "gas": "0xc376a", "gasUsed": "0x4e2", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a08231000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0xc0407", "gasUsed": "0x229", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a08231000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}], "value": "0x6f05b59d3b20000", "type": "CALL"}], "value": "0x6f05b59d3b20000", "type": "CALL"} \ No newline at end of file diff --git a/tools/hindsight/src/decoder/fixtures/trace_revert_kyber_slippage_0xd3b7ffae.json b/tools/hindsight/src/decoder/fixtures/trace_revert_kyber_slippage_0xd3b7ffae.json new file mode 100644 index 000000000..5bb231338 --- /dev/null +++ b/tools/hindsight/src/decoder/fixtures/trace_revert_kyber_slippage_0xd3b7ffae.json @@ -0,0 +1 @@ +{"from": "0xad298577cc1040cce487922efc3f21a483226185", "gas": "0x2d075d", "gasUsed": "0x2225b9", "to": "0xccc88a9d1b4ed6b0eaba998850414b24f1c315be", "input": "0xf9e4bab400000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000ad298577cc1040cce487922efc3f21a483226185000000000000000000000000ad298577cc1040cce487922efc3f21a4832261850000000000000000000000000000000000000000000000000000000000001b2000000000000000000000000000000000000000000000000000000000000000010000000000000000000000002ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000033d426dbfc6b100000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000016c000000000000000000000000000000000000000000000000000000000000018400000000000000000000000002ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec220000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000006131b5fae19ea4f9d964eac0408e4408b66337b500000000000000000000000000000000000000000000000000033d426dbfc6b1000000000000000000000000000000000000000000000000000000000000000000000000000000006131b5fae19ea4f9d964eac0408e4408b66337b50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000001484e21fd0e900000000000000000000000000000000000000000000000000000000000000200000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000114000000000000000000000000000000000000000000000000000000000000013800000000000000000000000000000000000000000000000000000000000001080000000000000000000033d426dbfc6b1000000000000000000033d426dbfc6b1000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000004139dcd51bdd8e38e88f73a8bc3c044f57ba405359be607eabc7614e10bc7ad0ca7d2a4e5d188a5f1a20d056e2ad0bc1c33994d23e82c109bc4808e47dc9ddc38f1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f80000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001800000000000000000000313cbe842fcc10000000000000000000366b8f33c90a0000000000000000000033d426dbfc6b100000000000000000003b5cdfe7f410a000000000000000000000000000000000000003e3f13f80000000f42400000000000000000000000000000004f82e73edb06d29ff62c91ec8f5ff06571bdeb290000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a69eb3a0000000000000000000000000000000000000000000000000000000000000f60000000000000000000000000000000000000000000000000000000000000000161f598cd0000000000000000cd568949398283ba8608ed411f3706462e6e0e56000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d400000000000000000000000002ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec228000000000000000000000003658aa60000000000000000000033d426dbfc6b100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000033d426dbfc6b13b9d6e0900000000000000015455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a8e4c55d6daf4d768aeba2378c1ad94c112ef48a0000000000000000000000000000000000000000000000000000000100ad139d000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291380000000000000000000000000000001000000000000000000000000001e2a6e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000001e2a6e4f2d31ea00000000000100025455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000002226446ebe8170000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e03b9d6e0900000000000000005455c918e405a2831fbff8595c0aae35ee3db9d139072a4500000000000000001b70046376a3db8b129409f5643ac9474a3be813000000000000000000000000000000000000000000000000000000000000004000000000000000000000000067a11022b7b6ed66f81233f6c8ed6e48f78265300000000000000000000000000000000000000000000000000000000100ad139d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e03b9d6e0900000000000000005455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d00000000000000000000000000000000000000000000000000000001004a9780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000120d607af9f0000000000010000f329f5647432ec3195669550fa91c44168b374c60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000055555522005bcae1c2424d474bfd5ed477749e3e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001ca00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000001e2a6e8bc041a80000000000000102d1877a31a73c7cb31c02b9e7d7c336531562b21e00000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000240000000000000000000000000111111125421ca6dc452d289314280a0f8842a650000000000000000000000000000000000000000000001440000000000000000000000000000000000000000111111125421ca6dc452d289314280a0f8842a65000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001849fda64bd00000000000000000000000000000000000000000000000000000021904b489e0000000000000000000000007d94baf661d5ed8ad30d7241d1a50f3883083ef70000000000000000000000007d94baf661d5ed8ad30d7241d1a50f3883083ef7000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000002226446ebe8170000000000000000000000000000000000000000000000000000000000001e2a6e00000000000000000000000021904b489e006a69e6a85f87778827f7d113f996cdd5612f0edb4368976605be47033f7cce41ab78c22445ada8432ae1c3ffbf719b8847bf7e61303fe5eb77007bd5fa6350a95e9499484ab42e0b0297edb7fd4100000000000000000000000000000000000000000000000000000000001e2a6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf800000000000000000000023d604e36f00000000000000000222d06c17689a600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000222d06c17689a603b9d6e0900000000000000035455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ed39c843ca9bc596f5db20b8ebb8199345e7d643000000000000000000000000ffb57e36d5b8bf4597abf0bbcfe764cfe8b1211200000000000000000000000042000000000000000000000000000000000000068000000000000000000000003e3f13f800000000000000000003b5cdfe7f410a0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000003b5cdfe7f410a0000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee80000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000000000000000000000000000000033d426dbfc6b10000000000000000000000000000000000000000000000000003ac4e7e8319fc0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000033d426dbfc6b100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a97b22536f75726365223a2272656c6179222c22416d6f756e74496e555344223a22312e393832333635222c22416d6f756e744f7574555344223a22312e393932373931222c22416d6f756e744f7574223a2231303434333231323732383037363839222c22526f7574654944223a2231383031303631396e6c564b753739443a343166653161636147304a4d7971742d222c2254696d657374616d70223a313738353332353139347d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c4a6bd8c960000000000000000000000000000000000000000000000000000016913e6da47000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002119656d0c698d3daae33a49d0d7aed8305f627c573a77f5f5fd71f6098b48a518010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c4a6bd8c960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ad298577cc1040cce487922efc3f21a4832261850000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002119656d0c698d3daae33a49d0d7aed8305f627c573a77f5f5fd71f6098b48a518000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002119656d0c698d3daae33a49d0d7aed8305f627c573a77f5f5fd71f6098b48a5180000000000000000000000000000000000000000000000000000000000000000815a84b8906f17df5f5f77a375c726f5038dea7d0d94a33eaad3d896c0d65691", "output": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001b52657475726e20616d6f756e74206973206e6f7420656e6f7567680000000000", "error": "execution reverted", "revertReason": "Return amount is not enough", "calls": [{"from": "0xccc88a9d1b4ed6b0eaba998850414b24f1c315be", "gas": "0x2b31fd", "gasUsed": "0xa4d8", "to": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "input": "0x23b872dd000000000000000000000000ad298577cc1040cce487922efc3f21a483226185000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000000000000000000000000000000033d426dbfc6b1", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "calls": [{"from": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "gas": "0x2a723b", "gasUsed": "0x916a", "to": "0x07a71b9b835c9eba242836704d17da0953324e1f", "input": "0x23b872dd000000000000000000000000ad298577cc1040cce487922efc3f21a483226185000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000000000000000000000000000000033d426dbfc6b1", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0xccc88a9d1b4ed6b0eaba998850414b24f1c315be", "gas": "0x2a6612", "gasUsed": "0x203073", "to": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "input": "0xcd6e13f70000000000000000000000000000000000000000000000000000000000000080000000000000000000000000ad298577cc1040cce487922efc3f21a483226185000000000000000000000000ad298577cc1040cce487922efc3f21a4832261850000000000000000000000000000000000000000000000000000000000001a6000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000016c000000000000000000000000000000000000000000000000000000000000018400000000000000000000000002ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec220000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000006131b5fae19ea4f9d964eac0408e4408b66337b500000000000000000000000000000000000000000000000000033d426dbfc6b1000000000000000000000000000000000000000000000000000000000000000000000000000000006131b5fae19ea4f9d964eac0408e4408b66337b50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000001484e21fd0e900000000000000000000000000000000000000000000000000000000000000200000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000114000000000000000000000000000000000000000000000000000000000000013800000000000000000000000000000000000000000000000000000000000001080000000000000000000033d426dbfc6b1000000000000000000033d426dbfc6b1000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000004139dcd51bdd8e38e88f73a8bc3c044f57ba405359be607eabc7614e10bc7ad0ca7d2a4e5d188a5f1a20d056e2ad0bc1c33994d23e82c109bc4808e47dc9ddc38f1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f80000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001800000000000000000000313cbe842fcc10000000000000000000366b8f33c90a0000000000000000000033d426dbfc6b100000000000000000003b5cdfe7f410a000000000000000000000000000000000000003e3f13f80000000f42400000000000000000000000000000004f82e73edb06d29ff62c91ec8f5ff06571bdeb290000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a69eb3a0000000000000000000000000000000000000000000000000000000000000f60000000000000000000000000000000000000000000000000000000000000000161f598cd0000000000000000cd568949398283ba8608ed411f3706462e6e0e56000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d400000000000000000000000002ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec228000000000000000000000003658aa60000000000000000000033d426dbfc6b100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000033d426dbfc6b13b9d6e0900000000000000015455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a8e4c55d6daf4d768aeba2378c1ad94c112ef48a0000000000000000000000000000000000000000000000000000000100ad139d000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291380000000000000000000000000000001000000000000000000000000001e2a6e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000001e2a6e4f2d31ea00000000000100025455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000002226446ebe8170000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e03b9d6e0900000000000000005455c918e405a2831fbff8595c0aae35ee3db9d139072a4500000000000000001b70046376a3db8b129409f5643ac9474a3be813000000000000000000000000000000000000000000000000000000000000004000000000000000000000000067a11022b7b6ed66f81233f6c8ed6e48f78265300000000000000000000000000000000000000000000000000000000100ad139d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e03b9d6e0900000000000000005455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d00000000000000000000000000000000000000000000000000000001004a9780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000120d607af9f0000000000010000f329f5647432ec3195669550fa91c44168b374c60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000055555522005bcae1c2424d474bfd5ed477749e3e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001ca00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000001e2a6e8bc041a80000000000000102d1877a31a73c7cb31c02b9e7d7c336531562b21e00000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000240000000000000000000000000111111125421ca6dc452d289314280a0f8842a650000000000000000000000000000000000000000000001440000000000000000000000000000000000000000111111125421ca6dc452d289314280a0f8842a65000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001849fda64bd00000000000000000000000000000000000000000000000000000021904b489e0000000000000000000000007d94baf661d5ed8ad30d7241d1a50f3883083ef70000000000000000000000007d94baf661d5ed8ad30d7241d1a50f3883083ef7000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000002226446ebe8170000000000000000000000000000000000000000000000000000000000001e2a6e00000000000000000000000021904b489e006a69e6a85f87778827f7d113f996cdd5612f0edb4368976605be47033f7cce41ab78c22445ada8432ae1c3ffbf719b8847bf7e61303fe5eb77007bd5fa6350a95e9499484ab42e0b0297edb7fd4100000000000000000000000000000000000000000000000000000000001e2a6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf800000000000000000000023d604e36f00000000000000000222d06c17689a600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000222d06c17689a603b9d6e0900000000000000035455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ed39c843ca9bc596f5db20b8ebb8199345e7d643000000000000000000000000ffb57e36d5b8bf4597abf0bbcfe764cfe8b1211200000000000000000000000042000000000000000000000000000000000000068000000000000000000000003e3f13f800000000000000000003b5cdfe7f410a0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000003b5cdfe7f410a0000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee80000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000000000000000000000000000000033d426dbfc6b10000000000000000000000000000000000000000000000000003ac4e7e8319fc0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000033d426dbfc6b100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a97b22536f75726365223a2272656c6179222c22416d6f756e74496e555344223a22312e393832333635222c22416d6f756e744f7574555344223a22312e393932373931222c22416d6f756e744f7574223a2231303434333231323732383037363839222c22526f7574654944223a2231383031303631396e6c564b753739443a343166653161636147304a4d7971742d222c2254696d657374616d70223a313738353332353139347d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c4a6bd8c960000000000000000000000000000000000000000000000000000016913e6da47000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002119656d0c698d3daae33a49d0d7aed8305f627c573a77f5f5fd71f6098b48a518010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c4a6bd8c960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ad298577cc1040cce487922efc3f21a4832261850000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002119656d0c698d3daae33a49d0d7aed8305f627c573a77f5f5fd71f6098b48a518000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002119656d0c698d3daae33a49d0d7aed8305f627c573a77f5f5fd71f6098b48a5180000000000000000000000000000000000000000000000000000000000000000", "output": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001b52657475726e20616d6f756e74206973206e6f7420656e6f7567680000000000", "error": "execution reverted", "revertReason": "Return amount is not enough", "calls": [{"from": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "gas": "0x295b86", "gasUsed": "0x1f2a", "to": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "input": "0x095ea7b30000000000000000000000006131b5fae19ea4f9d964eac0408e4408b66337b500000000000000000000000000000000000000000000000000033d426dbfc6b1", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "calls": [{"from": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "gas": "0x28b471", "gasUsed": "0x1d56", "to": "0x07a71b9b835c9eba242836704d17da0953324e1f", "input": "0x095ea7b30000000000000000000000006131b5fae19ea4f9d964eac0408e4408b66337b500000000000000000000000000000000000000000000000000033d426dbfc6b1", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "gas": "0x291e88", "gasUsed": "0x1f8dfb", "to": "0x6131b5fae19ea4f9d964eac0408e4408b66337b5", "input": "0xe21fd0e900000000000000000000000000000000000000000000000000000000000000200000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000114000000000000000000000000000000000000000000000000000000000000013800000000000000000000000000000000000000000000000000000000000001080000000000000000000033d426dbfc6b1000000000000000000033d426dbfc6b1000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000004139dcd51bdd8e38e88f73a8bc3c044f57ba405359be607eabc7614e10bc7ad0ca7d2a4e5d188a5f1a20d056e2ad0bc1c33994d23e82c109bc4808e47dc9ddc38f1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f80000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001800000000000000000000313cbe842fcc10000000000000000000366b8f33c90a0000000000000000000033d426dbfc6b100000000000000000003b5cdfe7f410a000000000000000000000000000000000000003e3f13f80000000f42400000000000000000000000000000004f82e73edb06d29ff62c91ec8f5ff06571bdeb290000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a69eb3a0000000000000000000000000000000000000000000000000000000000000f60000000000000000000000000000000000000000000000000000000000000000161f598cd0000000000000000cd568949398283ba8608ed411f3706462e6e0e56000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d400000000000000000000000002ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec228000000000000000000000003658aa60000000000000000000033d426dbfc6b100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000033d426dbfc6b13b9d6e0900000000000000015455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a8e4c55d6daf4d768aeba2378c1ad94c112ef48a0000000000000000000000000000000000000000000000000000000100ad139d000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291380000000000000000000000000000001000000000000000000000000001e2a6e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000001e2a6e4f2d31ea00000000000100025455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000002226446ebe8170000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e03b9d6e0900000000000000005455c918e405a2831fbff8595c0aae35ee3db9d139072a4500000000000000001b70046376a3db8b129409f5643ac9474a3be813000000000000000000000000000000000000000000000000000000000000004000000000000000000000000067a11022b7b6ed66f81233f6c8ed6e48f78265300000000000000000000000000000000000000000000000000000000100ad139d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e03b9d6e0900000000000000005455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d00000000000000000000000000000000000000000000000000000001004a9780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000120d607af9f0000000000010000f329f5647432ec3195669550fa91c44168b374c60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000055555522005bcae1c2424d474bfd5ed477749e3e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001ca00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000001e2a6e8bc041a80000000000000102d1877a31a73c7cb31c02b9e7d7c336531562b21e00000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000240000000000000000000000000111111125421ca6dc452d289314280a0f8842a650000000000000000000000000000000000000000000001440000000000000000000000000000000000000000111111125421ca6dc452d289314280a0f8842a65000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001849fda64bd00000000000000000000000000000000000000000000000000000021904b489e0000000000000000000000007d94baf661d5ed8ad30d7241d1a50f3883083ef70000000000000000000000007d94baf661d5ed8ad30d7241d1a50f3883083ef7000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000002226446ebe8170000000000000000000000000000000000000000000000000000000000001e2a6e00000000000000000000000021904b489e006a69e6a85f87778827f7d113f996cdd5612f0edb4368976605be47033f7cce41ab78c22445ada8432ae1c3ffbf719b8847bf7e61303fe5eb77007bd5fa6350a95e9499484ab42e0b0297edb7fd4100000000000000000000000000000000000000000000000000000000001e2a6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf800000000000000000000023d604e36f00000000000000000222d06c17689a600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000222d06c17689a603b9d6e0900000000000000035455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ed39c843ca9bc596f5db20b8ebb8199345e7d643000000000000000000000000ffb57e36d5b8bf4597abf0bbcfe764cfe8b1211200000000000000000000000042000000000000000000000000000000000000068000000000000000000000003e3f13f800000000000000000003b5cdfe7f410a0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000003b5cdfe7f410a0000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee80000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000000000000000000000000000000033d426dbfc6b10000000000000000000000000000000000000000000000000003ac4e7e8319fc0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000033d426dbfc6b100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a97b22536f75726365223a2272656c6179222c22416d6f756e74496e555344223a22312e393832333635222c22416d6f756e744f7574555344223a22312e393932373931222c22416d6f756e744f7574223a2231303434333231323732383037363839222c22526f7574654944223a2231383031303631396e6c564b753739443a343166653161636147304a4d7971742d222c2254696d657374616d70223a313738353332353139347d0000000000000000000000000000000000000000000000", "output": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001b52657475726e20616d6f756e74206973206e6f7420656e6f7567680000000000", "error": "execution reverted", "revertReason": "Return amount is not enough", "calls": [{"from": "0x6131b5fae19ea4f9d964eac0408e4408b66337b5", "gas": "0x28635a", "gasUsed": "0x2af8", "to": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "input": "0x23b872dd000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f99600000000000000000000000000000000000000000000000000033d426dbfc6b1", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "calls": [{"from": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "gas": "0x27c020", "gasUsed": "0x291e", "to": "0x07a71b9b835c9eba242836704d17da0953324e1f", "input": "0x23b872dd000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f99600000000000000000000000000000000000000000000000000033d426dbfc6b1", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x6131b5fae19ea4f9d964eac0408e4408b66337b5", "gas": "0x282eb0", "gasUsed": "0xbe5", "to": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "input": "0x70a082310000000000000000000000006131b5fae19ea4f9d964eac0408e4408b66337b5", "output": "0x00000000000000000000000000000000000000000000000000000216f52eb703", "calls": [{"from": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "gas": "0x278c3d", "gasUsed": "0x9ff", "to": "0x07a71b9b835c9eba242836704d17da0953324e1f", "input": "0x70a082310000000000000000000000006131b5fae19ea4f9d964eac0408e4408b66337b5", "output": "0x00000000000000000000000000000000000000000000000000000216f52eb703", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x6131b5fae19ea4f9d964eac0408e4408b66337b5", "gas": "0x27f1b7", "gasUsed": "0x1efed5", "to": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "input": "0xd9c4535700000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000001080000000000000000000033d426dbfc6b1000000000000000000033d426dbfc6b1000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000004139dcd51bdd8e38e88f73a8bc3c044f57ba405359be607eabc7614e10bc7ad0ca7d2a4e5d188a5f1a20d056e2ad0bc1c33994d23e82c109bc4808e47dc9ddc38f1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f80000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001800000000000000000000313cbe842fcc10000000000000000000366b8f33c90a0000000000000000000033d426dbfc6b100000000000000000003b5cdfe7f410a000000000000000000000000000000000000003e3f13f80000000f42400000000000000000000000000000004f82e73edb06d29ff62c91ec8f5ff06571bdeb290000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a69eb3a0000000000000000000000000000000000000000000000000000000000000f60000000000000000000000000000000000000000000000000000000000000000161f598cd0000000000000000cd568949398283ba8608ed411f3706462e6e0e56000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d400000000000000000000000002ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec228000000000000000000000003658aa60000000000000000000033d426dbfc6b100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000033d426dbfc6b13b9d6e0900000000000000015455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a8e4c55d6daf4d768aeba2378c1ad94c112ef48a0000000000000000000000000000000000000000000000000000000100ad139d000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291380000000000000000000000000000001000000000000000000000000001e2a6e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000001e2a6e4f2d31ea00000000000100025455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000002226446ebe8170000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e03b9d6e0900000000000000005455c918e405a2831fbff8595c0aae35ee3db9d139072a4500000000000000001b70046376a3db8b129409f5643ac9474a3be813000000000000000000000000000000000000000000000000000000000000004000000000000000000000000067a11022b7b6ed66f81233f6c8ed6e48f78265300000000000000000000000000000000000000000000000000000000100ad139d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e03b9d6e0900000000000000005455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d00000000000000000000000000000000000000000000000000000001004a9780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000120d607af9f0000000000010000f329f5647432ec3195669550fa91c44168b374c60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000055555522005bcae1c2424d474bfd5ed477749e3e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001ca00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000001e2a6e8bc041a80000000000000102d1877a31a73c7cb31c02b9e7d7c336531562b21e00000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000240000000000000000000000000111111125421ca6dc452d289314280a0f8842a650000000000000000000000000000000000000000000001440000000000000000000000000000000000000000111111125421ca6dc452d289314280a0f8842a65000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001849fda64bd00000000000000000000000000000000000000000000000000000021904b489e0000000000000000000000007d94baf661d5ed8ad30d7241d1a50f3883083ef70000000000000000000000007d94baf661d5ed8ad30d7241d1a50f3883083ef7000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000002226446ebe8170000000000000000000000000000000000000000000000000000000000001e2a6e00000000000000000000000021904b489e006a69e6a85f87778827f7d113f996cdd5612f0edb4368976605be47033f7cce41ab78c22445ada8432ae1c3ffbf719b8847bf7e61303fe5eb77007bd5fa6350a95e9499484ab42e0b0297edb7fd4100000000000000000000000000000000000000000000000000000000001e2a6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf800000000000000000000023d604e36f00000000000000000222d06c17689a600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000222d06c17689a603b9d6e0900000000000000035455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ed39c843ca9bc596f5db20b8ebb8199345e7d643000000000000000000000000ffb57e36d5b8bf4597abf0bbcfe764cfe8b1211200000000000000000000000042000000000000000000000000000000000000068000000000000000000000003e3f13f800000000000000000003b5cdfe7f410a0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000003b5cdfe7f410a0000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee8000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x27367b", "gasUsed": "0x1ee126", "to": "0x440c1a9786d3c6f09da46b6d42320e0cf2f336a7", "input": "0xd9c4535700000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000001080000000000000000000033d426dbfc6b1000000000000000000033d426dbfc6b1000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000004139dcd51bdd8e38e88f73a8bc3c044f57ba405359be607eabc7614e10bc7ad0ca7d2a4e5d188a5f1a20d056e2ad0bc1c33994d23e82c109bc4808e47dc9ddc38f1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f80000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001800000000000000000000313cbe842fcc10000000000000000000366b8f33c90a0000000000000000000033d426dbfc6b100000000000000000003b5cdfe7f410a000000000000000000000000000000000000003e3f13f80000000f42400000000000000000000000000000004f82e73edb06d29ff62c91ec8f5ff06571bdeb290000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a69eb3a0000000000000000000000000000000000000000000000000000000000000f60000000000000000000000000000000000000000000000000000000000000000161f598cd0000000000000000cd568949398283ba8608ed411f3706462e6e0e56000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000a800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d400000000000000000000000002ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec228000000000000000000000003658aa60000000000000000000033d426dbfc6b100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000033d426dbfc6b13b9d6e0900000000000000015455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a8e4c55d6daf4d768aeba2378c1ad94c112ef48a0000000000000000000000000000000000000000000000000000000100ad139d000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291380000000000000000000000000000001000000000000000000000000001e2a6e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000001e2a6e4f2d31ea00000000000100025455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000002226446ebe8170000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e03b9d6e0900000000000000005455c918e405a2831fbff8595c0aae35ee3db9d139072a4500000000000000001b70046376a3db8b129409f5643ac9474a3be813000000000000000000000000000000000000000000000000000000000000004000000000000000000000000067a11022b7b6ed66f81233f6c8ed6e48f78265300000000000000000000000000000000000000000000000000000000100ad139d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e03b9d6e0900000000000000005455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d00000000000000000000000000000000000000000000000000000001004a9780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000120d607af9f0000000000010000f329f5647432ec3195669550fa91c44168b374c60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000055555522005bcae1c2424d474bfd5ed477749e3e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001ca00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000001e2a6e8bc041a80000000000000102d1877a31a73c7cb31c02b9e7d7c336531562b21e00000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000240000000000000000000000000111111125421ca6dc452d289314280a0f8842a650000000000000000000000000000000000000000000001440000000000000000000000000000000000000000111111125421ca6dc452d289314280a0f8842a65000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001849fda64bd00000000000000000000000000000000000000000000000000000021904b489e0000000000000000000000007d94baf661d5ed8ad30d7241d1a50f3883083ef70000000000000000000000007d94baf661d5ed8ad30d7241d1a50f3883083ef7000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000002226446ebe8170000000000000000000000000000000000000000000000000000000000001e2a6e00000000000000000000000021904b489e006a69e6a85f87778827f7d113f996cdd5612f0edb4368976605be47033f7cce41ab78c22445ada8432ae1c3ffbf719b8847bf7e61303fe5eb77007bd5fa6350a95e9499484ab42e0b0297edb7fd4100000000000000000000000000000000000000000000000000000000001e2a6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf800000000000000000000023d604e36f00000000000000000222d06c17689a600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000222d06c17689a603b9d6e0900000000000000035455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000800000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ed39c843ca9bc596f5db20b8ebb8199345e7d643000000000000000000000000ffb57e36d5b8bf4597abf0bbcfe764cfe8b1211200000000000000000000000042000000000000000000000000000000000000068000000000000000000000003e3f13f800000000000000000003b5cdfe7f410a0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000003b5cdfe7f410a0000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee8000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x2689e8", "gasUsed": "0xbb8", "to": "0x0000000000000000000000000000000000000001", "input": "0x9ae6f6bf9de10741eeab29670ad5e983b25cdec2ae128caec440404be53b25fc000000000000000000000000000000000000000000000000000000000000001c39dcd51bdd8e38e88f73a8bc3c044f57ba405359be607eabc7614e10bc7ad0ca7d2a4e5d188a5f1a20d056e2ad0bc1c33994d23e82c109bc4808e47dc9ddc38f", "output": "0x000000000000000000000000030016f75e627a42bdf96f31cdf843cb24002109", "type": "STATICCALL"}, {"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x26717c", "gasUsed": "0x415", "to": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "input": "0x70a082310000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996", "output": "0x00000000000000000000000000000000000000000000000000033d426dbfc6b2", "calls": [{"from": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "gas": "0x25d5fe", "gasUsed": "0x22f", "to": "0x07a71b9b835c9eba242836704d17da0953324e1f", "input": "0x70a082310000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996", "output": "0x00000000000000000000000000000000000000000000000000033d426dbfc6b2", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x26597f", "gasUsed": "0x15275", "to": "0x5455c918e405a2831fbff8595c0aae35ee3db9d1", "input": "0x3b9d6e0900000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000033d426dbfc6b10000000000000000000000002ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a8e4c55d6daf4d768aeba2378c1ad94c112ef48a0000000000000000000000000000000000000000000000000000000100ad139d", "output": "0x00000000000000000000000000000000000000000000000000000000001e2a55", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x25b1b5", "gasUsed": "0x139f4", "to": "0xa8e4c55d6daf4d768aeba2378c1ad94c112ef48a", "input": "0x128acb080000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000033d426dbfc6b10000000000000000000000000000000000000000000000000000000100ad139d00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000002ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "output": "0x00000000000000000000000000000000000000000000000000033d426dbfc6b1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1d5ab", "calls": [{"from": "0xa8e4c55d6daf4d768aeba2378c1ad94c112ef48a", "gas": "0x249862", "gasUsed": "0x5ba7", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0xa9059cbb0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f99600000000000000000000000000000000000000000000000000000000001e2a55", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x23ea7a", "gasUsed": "0x3f87", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0xa9059cbb0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f99600000000000000000000000000000000000000000000000000000000001e2a55", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0xa8e4c55d6daf4d768aeba2378c1ad94c112ef48a", "gas": "0x243ac3", "gasUsed": "0xbe5", "to": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "input": "0x70a08231000000000000000000000000a8e4c55d6daf4d768aeba2378c1ad94c112ef48a", "output": "0x00000000000000000000000000000000000000000000000014ba7a0ad6404936", "calls": [{"from": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "gas": "0x23a81f", "gasUsed": "0x9ff", "to": "0x07a71b9b835c9eba242836704d17da0953324e1f", "input": "0x70a08231000000000000000000000000a8e4c55d6daf4d768aeba2378c1ad94c112ef48a", "output": "0x00000000000000000000000000000000000000000000000014ba7a0ad6404936", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0xa8e4c55d6daf4d768aeba2378c1ad94c112ef48a", "gas": "0x242c16", "gasUsed": "0x3237", "to": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "input": "0xfa461e3300000000000000000000000000000000000000000000000000033d426dbfc6b1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1d5ab000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000002ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x2395f4", "gasUsed": "0x2ba5", "to": "0x440c1a9786d3c6f09da46b6d42320e0cf2f336a7", "input": "0xfa461e3300000000000000000000000000000000000000000000000000033d426dbfc6b1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1d5ab000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000002ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x22f9bd", "gasUsed": "0x1d44", "to": "0xcd568949398283ba8608ed411f3706462e6e0e56", "input": "0x61f598cd00000000000000000000000000000000000000000000000000033d426dbfc6b1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1d5ab000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000002ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x226a4c", "gasUsed": "0x193b", "to": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "input": "0xa9059cbb000000000000000000000000a8e4c55d6daf4d768aeba2378c1ad94c112ef48a00000000000000000000000000000000000000000000000000033d426dbfc6b1", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "calls": [{"from": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "gas": "0x21dee7", "gasUsed": "0x1752", "to": "0x07a71b9b835c9eba242836704d17da0953324e1f", "input": "0xa9059cbb000000000000000000000000a8e4c55d6daf4d768aeba2378c1ad94c112ef48a00000000000000000000000000000000000000000000000000033d426dbfc6b1", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0xa8e4c55d6daf4d768aeba2378c1ad94c112ef48a", "gas": "0x23f82f", "gasUsed": "0x415", "to": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "input": "0x70a08231000000000000000000000000a8e4c55d6daf4d768aeba2378c1ad94c112ef48a", "output": "0x00000000000000000000000000000000000000000000000014bdb74d44000fe7", "calls": [{"from": "0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22", "gas": "0x236696", "gasUsed": "0x22f", "to": "0x07a71b9b835c9eba242836704d17da0953324e1f", "input": "0x70a08231000000000000000000000000a8e4c55d6daf4d768aeba2378c1ad94c112ef48a", "output": "0x00000000000000000000000000000000000000000000000014bdb74d44000fe7", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}], "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "DELEGATECALL"}, {"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x24feea", "gasUsed": "0x4e2", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a082310000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996", "output": "0x00000000000000000000000000000000000000000000000000000000001e2a56", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x246869", "gasUsed": "0x229", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a082310000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996", "output": "0x00000000000000000000000000000000000000000000000000000000001e2a56", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x24e1a6", "gasUsed": "0x7415", "to": "0x1b70046376a3db8b129409f5643ac9474a3be813", "input": "0x39072a4500000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000001e2a55000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996000000000000000000000000000000000000000000000000000000000000004000000000000000000000000067a11022b7b6ed66f81233f6c8ed6e48f78265300000000000000000000000000000000000000000000000000000000100ad139d0000000000000000000000000000000000000000000000000000000000000000", "output": "0x0000000000000000000000000000000000000000000000000223ff9e9e0e4b96", "calls": [{"from": "0x1b70046376a3db8b129409f5643ac9474a3be813", "gas": "0x243ff2", "gasUsed": "0xa88", "to": "0x67a11022b7b6ed66f81233f6c8ed6e48f7826530", "input": "0x3850c7bd", "output": "0x00000000000000000000000000000000000444c4987f6fc374f1f75d6d1e1219000000000000000000000000000000000000000000000000000000000003d3dd000000000000000000000000000000000000000000000000000000000000013a0000000000000000000000000000000000000000000000000000000000000168000000000000000000000000000000000000000000000000000000000000016800000000000000000000000000000000000000000000000000000000000000660000000000000000000000000000000000000000000000000000000000000001", "type": "STATICCALL"}, {"from": "0x1b70046376a3db8b129409f5643ac9474a3be813", "gas": "0x243287", "gasUsed": "0x97c", "to": "0x67a11022b7b6ed66f81233f6c8ed6e48f7826530", "input": "0x1a686502", "output": "0x00000000000000000000000000000000000000000000000010dfca188c042f37", "type": "STATICCALL"}, {"from": "0x1b70046376a3db8b129409f5643ac9474a3be813", "gas": "0x242790", "gasUsed": "0x117", "to": "0x67a11022b7b6ed66f81233f6c8ed6e48f7826530", "input": "0xd0c93a7c", "output": "0x000000000000000000000000000000000000000000000000000000000000003c", "type": "STATICCALL"}, {"from": "0x1b70046376a3db8b129409f5643ac9474a3be813", "gas": "0x2424d3", "gasUsed": "0xfb", "to": "0x67a11022b7b6ed66f81233f6c8ed6e48f7826530", "input": "0xddca3f43", "output": "0x0000000000000000000000000000000000000000000000000000000000000bb8", "type": "STATICCALL"}, {"from": "0x1b70046376a3db8b129409f5643ac9474a3be813", "gas": "0x241d62", "gasUsed": "0x9dd", "to": "0x67a11022b7b6ed66f81233f6c8ed6e48f7826530", "input": "0x5339c2960000000000000000000000000000000000000000000000000000000000000010", "output": "0x0012810034000001c0001c0c523f9be945e57fea4dfa7f7eb6edfe379f30dcc0", "type": "STATICCALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x246824", "gasUsed": "0x217ae", "to": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "input": "0x049fd03500000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000223ff9e9e0e4b960000000000000000000000000000000000000000000000000222628218c862c8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e2a55000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e03b9d6e0900000000000000005455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d00000000000000000000000000000000000000000000000000000001004a97800000000000000000000000000000000000000000000000000000000000000000", "output": "0x00000000000000000000000000000000000000000000000002204d54b65533fe", "error": "execution reverted", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x23d0d0", "gasUsed": "0x21183", "to": "0x440c1a9786d3c6f09da46b6d42320e0cf2f336a7", "input": "0x049fd03500000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000223ff9e9e0e4b960000000000000000000000000000000000000000000000000222628218c862c8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e2a55000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e03b9d6e0900000000000000005455c918e405a2831fbff8595c0aae35ee3db9d100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d00000000000000000000000000000000000000000000000000000001004a97800000000000000000000000000000000000000000000000000000000000000000", "output": "0x00000000000000000000000000000000000000000000000002204d54b65533fe", "error": "execution reverted", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x233d07", "gasUsed": "0x20c90", "to": "0x5455c918e405a2831fbff8595c0aae35ee3db9d1", "input": "0x3b9d6e0900000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000001e2a55000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000040000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d00000000000000000000000000000000000000000000000000000001004a9780", "output": "0x00000000000000000000000000000000000000000000000002204d54b65533fe", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x22a1af", "gasUsed": "0x1f40f", "to": "0xf8593b97ae3ddf084da845cf7ddf591e3d52106d", "input": "0x128acb080000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000001e2a5500000000000000000000000000000000000000000000000000000001004a978000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913", "output": "0x00000000000000000000000000000000000000000000000000000000001e2a55fffffffffffffffffffffffffffffffffffffffffffffffffddfb2ab49aacc02", "calls": [{"from": "0xf8593b97ae3ddf084da845cf7ddf591e3d52106d", "gas": "0x220ce3", "gasUsed": "0x1e978", "to": "0xc770898522d2a9c8da7a10d63989b6b58305b665", "input": "0x128acb080000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000001e2a5500000000000000000000000000000000000000000000000000000001004a978000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913", "output": "0x00000000000000000000000000000000000000000000000000000000001e2a55fffffffffffffffffffffffffffffffffffffffffffffffffddfb2ab49aacc02", "calls": [{"from": "0xf8593b97ae3ddf084da845cf7ddf591e3d52106d", "gas": "0x214845", "gasUsed": "0x490e", "to": "0xf8f2eb4940cfe7d13603dddd87f123820fc061ef", "input": "0x35458dcc000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d", "output": "0x0000000000000000000000000000000000000000000000000000000000000a8c", "calls": [{"from": "0xf8f2eb4940cfe7d13603dddd87f123820fc061ef", "gas": "0x30d40", "gasUsed": "0x31f0", "to": "0x87d8f999bba9343e8099552426775b51c338e8cb", "input": "0xb88c9148000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d", "output": "0x0000000000000000000000000000000000000000000000000000000000000a8c", "calls": [{"from": "0x87d8f999bba9343e8099552426775b51c338e8cb", "gas": "0x2e4f7", "gasUsed": "0x34d", "to": "0xf8593b97ae3ddf084da845cf7ddf591e3d52106d", "input": "0x3850c7bd", "output": "0x000000000000000000000000000000000004412dc5f9055970615862cf483b04000000000000000000000000000000000000000000000000000000000003d39b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0xf8593b97ae3ddf084da845cf7ddf591e3d52106d", "gas": "0x2d8e4", "gasUsed": "0x289", "to": "0xc770898522d2a9c8da7a10d63989b6b58305b665", "input": "0x3850c7bd", "output": "0x000000000000000000000000000000000004412dc5f9055970615862cf483b04000000000000000000000000000000000000000000000000000000000003d39b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}], "type": "STATICCALL"}], "type": "STATICCALL"}, {"from": "0xf8593b97ae3ddf084da845cf7ddf591e3d52106d", "gas": "0x20d1f0", "gasUsed": "0x434d", "to": "0xf8f2eb4940cfe7d13603dddd87f123820fc061ef", "input": "0x48cf7a43000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d", "output": "0x00000000000000000000000000000000000000000000000000000000000186a0", "calls": [{"from": "0xf8f2eb4940cfe7d13603dddd87f123820fc061ef", "gas": "0x20429c", "gasUsed": "0xa72", "to": "0x16613524e02ad97edfef371bc883f2f5d6c480a5", "input": "0xb9a09fd5000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d", "output": "0x000000000000000000000000b5d2766888a0f33175f1be83f94e1c9d62569196", "type": "STATICCALL"}, {"from": "0xf8f2eb4940cfe7d13603dddd87f123820fc061ef", "gas": "0x20369f", "gasUsed": "0xa7a", "to": "0x16613524e02ad97edfef371bc883f2f5d6c480a5", "input": "0x1703e5f9000000000000000000000000b5d2766888a0f33175f1be83f94e1c9d62569196", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "type": "STATICCALL"}, {"from": "0xf8f2eb4940cfe7d13603dddd87f123820fc061ef", "gas": "0x30d40", "gasUsed": "0xa1b", "to": "0xc2cc3256434afbc36bb5e815e1bb2151310a1a0b", "input": "0xb88c9148000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d", "output": "0x00000000000000000000000000000000000000000000000000000000000186a0", "type": "STATICCALL"}], "type": "STATICCALL"}, {"from": "0xf8593b97ae3ddf084da845cf7ddf591e3d52106d", "gas": "0x203221", "gasUsed": "0x30c4", "to": "0xacfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf", "input": "0xa9059cbb0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f99600000000000000000000000000000000000000000000000002204d54b65533fe", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "CALL"}, {"from": "0xf8593b97ae3ddf084da845cf7ddf591e3d52106d", "gas": "0x1ff6a6", "gasUsed": "0xcb2", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a08231000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d", "output": "0x0000000000000000000000000000000000000000000000000000000022952101", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x1f7446", "gasUsed": "0x9f9", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a08231000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d", "output": "0x0000000000000000000000000000000000000000000000000000000022952101", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0xf8593b97ae3ddf084da845cf7ddf591e3d52106d", "gas": "0x1fe72f", "gasUsed": "0x2f1b", "to": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "input": "0xfa461e3300000000000000000000000000000000000000000000000000000000001e2a55fffffffffffffffffffffffffffffffffffffffffffffffffddfb2ab49aacc0200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x1f6221", "gasUsed": "0x2889", "to": "0x440c1a9786d3c6f09da46b6d42320e0cf2f336a7", "input": "0xfa461e3300000000000000000000000000000000000000000000000000000000001e2a55fffffffffffffffffffffffffffffffffffffffffffffffffddfb2ab49aacc0200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x1ee056", "gasUsed": "0x23ec", "to": "0xcd568949398283ba8608ed411f3706462e6e0e56", "input": "0x61f598cd00000000000000000000000000000000000000000000000000000000001e2a55fffffffffffffffffffffffffffffffffffffffffffffffffddfb2ab49aacc0200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x1e614b", "gasUsed": "0x1fe3", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0xa9059cbb000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d00000000000000000000000000000000000000000000000000000000001e2a55", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x1de53e", "gasUsed": "0x1d27", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0xa9059cbb000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d00000000000000000000000000000000000000000000000000000000001e2a55", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0xf8593b97ae3ddf084da845cf7ddf591e3d52106d", "gas": "0x1fb5f5", "gasUsed": "0x4e2", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a08231000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d", "output": "0x0000000000000000000000000000000000000000000000000000000022b34b56", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x1f3498", "gasUsed": "0x229", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a08231000000000000000000000000f8593b97ae3ddf084da845cf7ddf591e3d52106d", "output": "0x0000000000000000000000000000000000000000000000000000000022b34b56", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}], "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x2251be", "gasUsed": "0x17501a", "to": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "input": "0x049fd03500000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000223ff9e9e0e4b960000000000000000000000000000000000000000000000000222628218c862c8000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000001e2a55000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f99600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000120d607af9f0000000000010000f329f5647432ec3195669550fa91c44168b374c60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000055555522005bcae1c2424d474bfd5ed477749e3e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001ca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "output": "0x000000000000000000000000000000000000000000000000022378aef095ef4a", "error": "execution reverted", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x21c2b8", "gasUsed": "0x1749e3", "to": "0x440c1a9786d3c6f09da46b6d42320e0cf2f336a7", "input": "0x049fd03500000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000223ff9e9e0e4b960000000000000000000000000000000000000000000000000222628218c862c8000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000001e2a55000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f99600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000120d607af9f0000000000010000f329f5647432ec3195669550fa91c44168b374c60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000055555522005bcae1c2424d474bfd5ed477749e3e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001ca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "output": "0x000000000000000000000000000000000000000000000000022378aef095ef4a", "error": "execution reverted", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x212d7d", "gasUsed": "0x173b1f", "to": "0xf329f5647432ec3195669550fa91c44168b374c6", "input": "0xd607af9f00000000000000000000000000000000000000000000000000000000000000a080000000000000000000000000000000000000000000000000000000001e2a55000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996000000000000000000000000000000000000000000000000000000000000008000000000000000000000000055555522005bcae1c2424d474bfd5ed477749e3e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001ca00000000000000000000000000000000000000000000000000000000000000", "output": "0x000000000000000000000000000000000000000000000000022378aef095ef4a", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x20a419", "gasUsed": "0x2031", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x095ea7b300000000000000000000000055555522005bcae1c2424d474bfd5ed477749e3effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x201f20", "gasUsed": "0x1d95", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x095ea7b300000000000000000000000055555522005bcae1c2424d474bfd5ed477749e3effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x2082f5", "gasUsed": "0x4e2", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a082310000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996", "output": "0x00000000000000000000000000000000000000000000000000000000001e2a56", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x1ffe64", "gasUsed": "0x229", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a082310000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996", "output": "0x00000000000000000000000000000000000000000000000000000000001e2a56", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x20729f", "gasUsed": "0x9fb", "to": "0xacfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf", "input": "0x70a082310000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "type": "STATICCALL"}, {"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x205c44", "gasUsed": "0x16d8c7", "to": "0x55555522005bcae1c2424d474bfd5ed477749e3e", "input": "0x3ae8b298000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf00000000000000000000000000000000000000000000000000000000001e2a5500000000000000000000000000000000000000000000000000000000000000000000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f99600000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000001ca00000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x55555522005bcae1c2424d474bfd5ed477749e3e", "gas": "0x1fc560", "gasUsed": "0x1648f7", "to": "0x31e99e05fee3dce580af777c3fd63ee1b3b40c17", "input": "0xe70c3a4d000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf00000000000000000000000000000000000000000000000000000000001e2a550000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f99600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001ca00000000000000000000000000000000000000000000000000000000000000", "output": "0x00000000000000000000000000000000000000000000000000000000001e2a55000000000000000000000000000000000000000000000000022378aef095ef4a", "calls": [{"from": "0x31e99e05fee3dce580af777c3fd63ee1b3b40c17", "gas": "0x1f1ab0", "gasUsed": "0x1fe1", "to": "0x402e0d314fd6f55348df7cc478bab811826e3e91", "input": "0xc55dae63", "output": "0x000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf", "calls": [{"from": "0x402e0d314fd6f55348df7cc478bab811826e3e91", "gas": "0x1e89bc", "gasUsed": "0xae3", "to": "0xffeeb84898a2cd25701e3cfe766f1709821742c0", "input": "0xc55dae63", "output": "0x000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x31e99e05fee3dce580af777c3fd63ee1b3b40c17", "gas": "0x1ef98b", "gasUsed": "0xcab", "to": "0x402e0d314fd6f55348df7cc478bab811826e3e91", "input": "0x217a4b70", "output": "0x000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913", "calls": [{"from": "0x402e0d314fd6f55348df7cc478bab811826e3e91", "gas": "0x1e7a69", "gasUsed": "0x941", "to": "0xffeeb84898a2cd25701e3cfe766f1709821742c0", "input": "0x217a4b70", "output": "0x000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x31e99e05fee3dce580af777c3fd63ee1b3b40c17", "gas": "0x1ee9b4", "gasUsed": "0x15e920", "to": "0x402e0d314fd6f55348df7cc478bab811826e3e91", "input": "0x4c7c47b7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e2a550000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f99600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001ca00000000000000000000000000000000000000000000000000000000000000", "output": "0x000000000000000000000000000000000000000000000000022378aef095ef4a", "calls": [{"from": "0x402e0d314fd6f55348df7cc478bab811826e3e91", "gas": "0x1e6ab4", "gasUsed": "0x15e598", "to": "0xffeeb84898a2cd25701e3cfe766f1709821742c0", "input": "0x4c7c47b7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e2a550000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f99600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001ca00000000000000000000000000000000000000000000000000000000000000", "output": "0x000000000000000000000000000000000000000000000000022378aef095ef4a", "calls": [{"from": "0x402e0d314fd6f55348df7cc478bab811826e3e91", "gas": "0x1ddd01", "gasUsed": "0x196", "to": "0x31e99e05fee3dce580af777c3fd63ee1b3b40c17", "input": "0x22f3e2d4", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "type": "STATICCALL"}, {"from": "0x402e0d314fd6f55348df7cc478bab811826e3e91", "gas": "0x1db9c1", "gasUsed": "0x196", "to": "0x31e99e05fee3dce580af777c3fd63ee1b3b40c17", "input": "0x22f3e2d4", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "type": "STATICCALL"}, {"from": "0x402e0d314fd6f55348df7cc478bab811826e3e91", "gas": "0x1cd126", "gasUsed": "0xa47", "to": "0x31e99e05fee3dce580af777c3fd63ee1b3b40c17", "input": "0x8c4cdb49", "output": "0x0000000000000000000000000000000000000000000000000000000002efbfb7", "type": "STATICCALL"}, {"from": "0x402e0d314fd6f55348df7cc478bab811826e3e91", "gas": "0x1cac43", "gasUsed": "0x1b29", "to": "0x7034c5c74f66d3337777772c2964db31765db23e", "input": "0x4a89bec000000000000000000000000000000000000000000000000000000000000000ca", "output": "0x00000000000000000000000000000000000000000000000000000000000000c8", "type": "STATICCALL"}, {"from": "0x402e0d314fd6f55348df7cc478bab811826e3e91", "gas": "0x1c8482", "gasUsed": "0xa9c", "to": "0x31e99e05fee3dce580af777c3fd63ee1b3b40c17", "input": "0x8547d9720000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "type": "STATICCALL"}, {"from": "0x402e0d314fd6f55348df7cc478bab811826e3e91", "gas": "0x1c7863", "gasUsed": "0xa9c", "to": "0x31e99e05fee3dce580af777c3fd63ee1b3b40c17", "input": "0x8547d972000000000000000000000000ad298577cc1040cce487922efc3f21a483226185", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "type": "STATICCALL"}, {"from": "0x402e0d314fd6f55348df7cc478bab811826e3e91", "gas": "0x1c6a9c", "gasUsed": "0x2cc", "to": "0x31e99e05fee3dce580af777c3fd63ee1b3b40c17", "input": "0x8547d9720000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "type": "STATICCALL"}, {"from": "0x402e0d314fd6f55348df7cc478bab811826e3e91", "gas": "0x1c6620", "gasUsed": "0x2cc", "to": "0x31e99e05fee3dce580af777c3fd63ee1b3b40c17", "input": "0x8547d972000000000000000000000000ad298577cc1040cce487922efc3f21a483226185", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "type": "STATICCALL"}, {"from": "0x402e0d314fd6f55348df7cc478bab811826e3e91", "gas": "0x1c4618", "gasUsed": "0x10fb69", "to": "0x9f924c0765815851d9f4982c7030e2189a7828a9", "input": "0x1ed58fc8000000000000000000000000ad298577cc1040cce487922efc3f21a4832261850000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "type": "STATICCALL"}, {"from": "0x402e0d314fd6f55348df7cc478bab811826e3e91", "gas": "0xb8c17", "gasUsed": "0xb7b", "to": "0x31e99e05fee3dce580af777c3fd63ee1b3b40c17", "input": "0xeb101276", "output": "0x0000000000000000000000000000000000000000000000000000000077359400", "type": "STATICCALL"}], "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x55555522005bcae1c2424d474bfd5ed477749e3e", "gas": "0x9cb2a", "gasUsed": "0x3188", "to": "0xacfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf", "input": "0x23b872dd0000000000000000000000003dbe077e7986657e95e1cc50089f17a5a4af0aae0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996000000000000000000000000000000000000000000000000022378aef095ef4a", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "CALL"}, {"from": "0x55555522005bcae1c2424d474bfd5ed477749e3e", "gas": "0x998e4", "gasUsed": "0x33ac", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x23b872dd0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000003dbe077e7986657e95e1cc50089f17a5a4af0aae00000000000000000000000000000000000000000000000000000000001e2a55", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x97012", "gasUsed": "0x310a", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x23b872dd0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000003dbe077e7986657e95e1cc50089f17a5a4af0aae00000000000000000000000000000000000000000000000000000000001e2a55", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x9dd1b", "gasUsed": "0x4e2", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a082310000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x9b321", "gasUsed": "0x229", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a082310000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x9d628", "gasUsed": "0x22b", "to": "0xacfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf", "input": "0x70a082310000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996", "output": "0x000000000000000000000000000000000000000000000000022378aef095ef4b", "type": "STATICCALL"}], "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0xb59eb", "gasUsed": "0x107eb", "to": "0x5455c918e405a2831fbff8595c0aae35ee3db9d1", "input": "0x3b9d6e0900000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000001e2a55000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996000000000000000000000000000000000000000000000000000000000000004000000000000000000000000067a11022b7b6ed66f81233f6c8ed6e48f78265300000000000000000000000000000000000000000000000000000000100ad139d", "output": "0x0000000000000000000000000000000000000000000000000223ff9e9e0e4b96", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0xb27bc", "gasUsed": "0xf92e", "to": "0x67a11022b7b6ed66f81233f6c8ed6e48f7826530", "input": "0x128acb080000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000001e2a550000000000000000000000000000000000000000000000000000000100ad139d00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913", "output": "0x00000000000000000000000000000000000000000000000000000000001e2a55fffffffffffffffffffffffffffffffffffffffffffffffffddc006161f1b46a", "calls": [{"from": "0x67a11022b7b6ed66f81233f6c8ed6e48f7826530", "gas": "0xa8ca1", "gasUsed": "0x30c4", "to": "0xacfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf", "input": "0xa9059cbb0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000223ff9e9e0e4b96", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "CALL"}, {"from": "0x67a11022b7b6ed66f81233f6c8ed6e48f7826530", "gas": "0xa5938", "gasUsed": "0xcb2", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a0823100000000000000000000000067a11022b7b6ed66f81233f6c8ed6e48f7826530", "output": "0x0000000000000000000000000000000000000000000000000000005fe032c023", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0xa2d4e", "gasUsed": "0x9f9", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a0823100000000000000000000000067a11022b7b6ed66f81233f6c8ed6e48f7826530", "output": "0x0000000000000000000000000000000000000000000000000000005fe032c023", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x67a11022b7b6ed66f81233f6c8ed6e48f7826530", "gas": "0xa49c2", "gasUsed": "0x2f1b", "to": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "input": "0xfa461e3300000000000000000000000000000000000000000000000000000000001e2a55fffffffffffffffffffffffffffffffffffffffffffffffffddc006161f1b46a00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0xa1b2a", "gasUsed": "0x2889", "to": "0x440c1a9786d3c6f09da46b6d42320e0cf2f336a7", "input": "0xfa461e3300000000000000000000000000000000000000000000000000000000001e2a55fffffffffffffffffffffffffffffffffffffffffffffffffddc006161f1b46a00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x9ee7b", "gasUsed": "0x23ec", "to": "0xcd568949398283ba8608ed411f3706462e6e0e56", "input": "0x61f598cd00000000000000000000000000000000000000000000000000000000001e2a55fffffffffffffffffffffffffffffffffffffffffffffffffddc006161f1b46a00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x9c337", "gasUsed": "0x1fe3", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0xa9059cbb00000000000000000000000067a11022b7b6ed66f81233f6c8ed6e48f782653000000000000000000000000000000000000000000000000000000000001e2a55", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x999a2", "gasUsed": "0x1d27", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0xa9059cbb00000000000000000000000067a11022b7b6ed66f81233f6c8ed6e48f782653000000000000000000000000000000000000000000000000000000000001e2a55", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x67a11022b7b6ed66f81233f6c8ed6e48f7826530", "gas": "0xa18eb", "gasUsed": "0x4e2", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a0823100000000000000000000000067a11022b7b6ed66f81233f6c8ed6e48f7826530", "output": "0x0000000000000000000000000000000000000000000000000000005fe050ea78", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x9ee02", "gasUsed": "0x229", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a0823100000000000000000000000067a11022b7b6ed66f81233f6c8ed6e48f7826530", "output": "0x0000000000000000000000000000000000000000000000000000005fe050ea78", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}], "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "DELEGATECALL"}, {"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0xa487f", "gasUsed": "0x22b", "to": "0xacfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf", "input": "0x70a082310000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996", "output": "0x0000000000000000000000000000000000000000000000000223ff9e9e0e4b97", "type": "STATICCALL"}, {"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0xa3beb", "gasUsed": "0x1b4df", "to": "0x5455c918e405a2831fbff8595c0aae35ee3db9d1", "input": "0x3b9d6e0900000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000223ff9e9e0e4b96000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf00000000000000000000000042000000000000000000000000000000000000060000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ed39c843ca9bc596f5db20b8ebb8199345e7d643000000000000000000000000ffb57e36d5b8bf4597abf0bbcfe764cfe8b12112", "output": "0x0000000000000000000000000000000000000000000000000003ab35886b8369", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0xa0497", "gasUsed": "0x19c4e", "to": "0xed39c843ca9bc596f5db20b8ebb8199345e7d643", "input": "0x128acb080000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f99600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000223ff9e9e0e4b96000000000000000000000000ffb57e36d5b8bf4597abf0bbcfe764cfe8b1211200000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf", "output": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffc54ca77947c970000000000000000000000000000000000000000000000000223ff9e9e0e4b96", "calls": [{"from": "0xed39c843ca9bc596f5db20b8ebb8199345e7d643", "gas": "0x99ffa", "gasUsed": "0x5b16", "to": "0xc03573c6a7c8dfa885ed79d4cadb91be56dc446e", "input": "0x214a6fe2000000000000000000000000000000000000000000000000000000006a69e6a5", "calls": [{"from": "0xc03573c6a7c8dfa885ed79d4cadb91be56dc446e", "gas": "0x96437", "gasUsed": "0x2cb0", "to": "0xc6a2db661d5a5690172d8eb0a7dea2d3008665a3", "input": "0xa15ea89f000000000000000000000000ed39c843ca9bc596f5db20b8ebb8199345e7d643", "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a6b6b33", "type": "STATICCALL"}], "value": "0x0", "type": "CALL"}, {"from": "0xed39c843ca9bc596f5db20b8ebb8199345e7d643", "gas": "0x8c17d", "gasUsed": "0x3139", "to": "0x4200000000000000000000000000000000000006", "input": "0xa9059cbb0000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f9960000000000000000000000000000000000000000000000000003ab35886b8369", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "CALL"}, {"from": "0xed39c843ca9bc596f5db20b8ebb8199345e7d643", "gas": "0x88da3", "gasUsed": "0x9fb", "to": "0xacfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf", "input": "0x70a08231000000000000000000000000ed39c843ca9bc596f5db20b8ebb8199345e7d643", "output": "0x0000000000000000000000000000000000000000000000006042a4e01715234f", "type": "STATICCALL"}, {"from": "0xed39c843ca9bc596f5db20b8ebb8199345e7d643", "gas": "0x880d9", "gasUsed": "0x2586", "to": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "input": "0x23a69e75fffffffffffffffffffffffffffffffffffffffffffffffffffc54ca77947c970000000000000000000000000000000000000000000000000223ff9e9e0e4b9600000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x85964", "gasUsed": "0x1ef4", "to": "0x440c1a9786d3c6f09da46b6d42320e0cf2f336a7", "input": "0x23a69e75fffffffffffffffffffffffffffffffffffffffffffffffffffc54ca77947c970000000000000000000000000000000000000000000000000223ff9e9e0e4b9600000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x833bc", "gasUsed": "0x1a57", "to": "0xcd568949398283ba8608ed411f3706462e6e0e56", "input": "0x61f598cdfffffffffffffffffffffffffffffffffffffffffffffffffffc54ca77947c970000000000000000000000000000000000000000000000000223ff9e9e0e4b9600000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000acfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf", "calls": [{"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x80f49", "gasUsed": "0x1634", "to": "0xacfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf", "input": "0xa9059cbb000000000000000000000000ed39c843ca9bc596f5db20b8ebb8199345e7d6430000000000000000000000000000000000000000000000000223ff9e9e0e4b96", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0xed39c843ca9bc596f5db20b8ebb8199345e7d643", "gas": "0x85971", "gasUsed": "0x22b", "to": "0xacfe6019ed1a7dc6f7b508c02d1b04ec88cc21bf", "input": "0x70a08231000000000000000000000000ed39c843ca9bc596f5db20b8ebb8199345e7d643", "output": "0x0000000000000000000000000000000000000000000000006266a47eb5236ee5", "type": "STATICCALL"}], "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "DELEGATECALL"}, {"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x88865", "gasUsed": "0x1c9", "to": "0x4200000000000000000000000000000000000006", "input": "0x70a082310000000000000000000000008f10b468b06c6fd214b65f87778827f7d113f996", "output": "0x0000000000000000000000000000000000000000000000000003ab35886b836a", "type": "STATICCALL"}, {"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x87de7", "gasUsed": "0x23ab", "to": "0x4200000000000000000000000000000000000006", "input": "0x2e1a7d4d0000000000000000000000000000000000000000000000000003ab35886b8369", "calls": [{"from": "0x4200000000000000000000000000000000000006", "gas": "0x8fc", "gasUsed": "0x43", "to": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "input": "0x", "value": "0x3ab35886b8369", "type": "CALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x8f10b468b06c6fd214b65f87778827f7d113f996", "gas": "0x839a7", "gasUsed": "0x575", "to": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "input": "0x", "value": "0x3ab35886b8369", "type": "CALL"}], "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "CALL"} \ No newline at end of file diff --git a/tools/hindsight/src/decoder/fixtures/trace_revert_out_of_gas_0x08fee57c.json b/tools/hindsight/src/decoder/fixtures/trace_revert_out_of_gas_0x08fee57c.json new file mode 100644 index 000000000..ab7f130cb --- /dev/null +++ b/tools/hindsight/src/decoder/fixtures/trace_revert_out_of_gas_0x08fee57c.json @@ -0,0 +1 @@ +{"from": "0x7efeb39ecc2b02d7c651cd8c547eed22f83c1665", "gas": "0x82de4", "gasUsed": "0x813ac", "to": "0xccc88a9d1b4ed6b0eaba998850414b24f1c315be", "input": "0xf9e4bab400000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001400000000000000000000000007efeb39ecc2b02d7c651cd8c547eed22f83c16650000000000000000000000007efeb39ecc2b02d7c651cd8c547eed22f83c166500000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000d0ef00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000003e00000000000000000000000000000000000000000000000000000000000000680000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c000000000000000000000000000000000000000000000000000000000000d0ef00000000000000000000000000000000000000000000000000000000000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001a49bb43718000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001cb0000000000000000000000000000000000000000000000000000000000000021582f3f59ff81e5e0c1b952abba1b7f70ee487db08547df4c6fe71b773e8d437901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001e446ec278a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000019101590040b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf24e00114d00119d001205247dd5e3941528e217ec203345b1780d16a25c8c20340603b95ef04708ad37bf0012700f0467b96f558daf66eecc4b42930c61495d1f12d71ccfbed82ddb2a78757905b3621c9eccef7aec22e0ba4af72bfc11d95a7072b0b91c04ac71c552df1d8d4871b00e06a69e7bad0193c7f05dd1ad0195cf60351fef02710833589fcd6edb6e08f4c7c32d4f71b54bda029138569213d10e502b0573f9cd6d33be324144b894ff0cf2402012902013d0e015101513df02124f801f80001015f0301630301650e015101510301654200000000000000000000000000000000000006020178020178050000020070020070070500200100000154015f000000000001670178013d060020018c018f0000050000018f019500000600200195019800000300000198019f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c4a6bd8c9600000000000000000000000000000000000000000000000000000000000000000000000000000000000000007efeb39ecc2b02d7c651cd8c547eed22f83c166500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000021582f3f59ff81e5e0c1b952abba1b7f70ee487db08547df4c6fe71b773e8d43790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021582f3f59ff81e5e0c1b952abba1b7f70ee487db08547df4c6fe71b773e8d437900000000000000000000000000000000000000000000000000000000000000009734d8e377b17ef6c4fd74580bd784ee07f7b1abba259b1c0e5e18ff95f3f285", "error": "execution reverted", "calls": [{"from": "0xccc88a9d1b4ed6b0eaba998850414b24f1c315be", "gas": "0x761ef", "gasUsed": "0xbd2c", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x23b872dd0000000000000000000000007efeb39ecc2b02d7c651cd8c547eed22f83c1665000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f000000000000000000000000000000000000000000000000000000000000d0ef", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x728fa", "gasUsed": "0xa126", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x23b872dd0000000000000000000000007efeb39ecc2b02d7c651cd8c547eed22f83c1665000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f000000000000000000000000000000000000000000000000000000000000d0ef", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0xccc88a9d1b4ed6b0eaba998850414b24f1c315be", "gas": "0x681ac", "gasUsed": "0x681ac", "to": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "input": "0xcd6e13f700000000000000000000000000000000000000000000000000000000000000800000000000000000000000007efeb39ecc2b02d7c651cd8c547eed22f83c16650000000000000000000000007efeb39ecc2b02d7c651cd8c547eed22f83c166500000000000000000000000000000000000000000000000000000000000008a000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000003e00000000000000000000000000000000000000000000000000000000000000680000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c000000000000000000000000000000000000000000000000000000000000d0ef00000000000000000000000000000000000000000000000000000000000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001a49bb43718000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001cb0000000000000000000000000000000000000000000000000000000000000021582f3f59ff81e5e0c1b952abba1b7f70ee487db08547df4c6fe71b773e8d437901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001e446ec278a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000019101590040b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf24e00114d00119d001205247dd5e3941528e217ec203345b1780d16a25c8c20340603b95ef04708ad37bf0012700f0467b96f558daf66eecc4b42930c61495d1f12d71ccfbed82ddb2a78757905b3621c9eccef7aec22e0ba4af72bfc11d95a7072b0b91c04ac71c552df1d8d4871b00e06a69e7bad0193c7f05dd1ad0195cf60351fef02710833589fcd6edb6e08f4c7c32d4f71b54bda029138569213d10e502b0573f9cd6d33be324144b894ff0cf2402012902013d0e015101513df02124f801f80001015f0301630301650e015101510301654200000000000000000000000000000000000006020178020178050000020070020070070500200100000154015f000000000001670178013d060020018c018f0000050000018f019500000600200195019800000300000198019f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c4a6bd8c9600000000000000000000000000000000000000000000000000000000000000000000000000000000000000007efeb39ecc2b02d7c651cd8c547eed22f83c166500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000021582f3f59ff81e5e0c1b952abba1b7f70ee487db08547df4c6fe71b773e8d43790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021582f3f59ff81e5e0c1b952abba1b7f70ee487db08547df4c6fe71b773e8d43790000000000000000000000000000000000000000000000000000000000000000", "error": "out of gas", "calls": [{"from": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "gas": "0x606b2", "gasUsed": "0x2031", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x095ea7b300000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c000000000000000000000000000000000000000000000000000000000000d0ef", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x5ec2f", "gasUsed": "0x1d95", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x095ea7b300000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c000000000000000000000000000000000000000000000000000000000000d0ef", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "gas": "0x5d609", "gasUsed": "0x3e34", "to": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "input": "0x9bb43718000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001cb0000000000000000000000000000000000000000000000000000000000000021582f3f59ff81e5e0c1b952abba1b7f70ee487db08547df4c6fe71b773e8d43790100000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "gas": "0x5b5b8", "gasUsed": "0x27b3", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0xa9059cbb000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef00000000000000000000000000000000000000000000000000000000000001cb", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x59c59", "gasUsed": "0x24f7", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0xa9059cbb000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef00000000000000000000000000000000000000000000000000000000000001cb", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "CALL"}, {"from": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "gas": "0x5741a", "gasUsed": "0x5471e", "to": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "input": "0x46ec278a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000019101590040b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf24e00114d00119d001205247dd5e3941528e217ec203345b1780d16a25c8c20340603b95ef04708ad37bf0012700f0467b96f558daf66eecc4b42930c61495d1f12d71ccfbed82ddb2a78757905b3621c9eccef7aec22e0ba4af72bfc11d95a7072b0b91c04ac71c552df1d8d4871b00e06a69e7bad0193c7f05dd1ad0195cf60351fef02710833589fcd6edb6e08f4c7c32d4f71b54bda029138569213d10e502b0573f9cd6d33be324144b894ff0cf2402012902013d0e015101513df02124f801f80001015f0301630301650e015101510301654200000000000000000000000000000000000006020178020178050000020070020070070500200100000154015f000000000001670178013d060020018c018f0000050000018f019500000600200195019800000300000198019f0000000000000000000000000000000000", "output": "0x0000000000000000000000000000000000000000000000000000193fd5c83779", "calls": [{"from": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "gas": "0x54958", "gasUsed": "0xbb8", "to": "0x0000000000000000000000000000000000000001", "input": "0x16ea7b1e2a0c132304bd27f39948dfca4a5b2db21cdb0cfaf558327a4cc9ecfb000000000000000000000000000000000000000000000000000000000000001bf0467b96f558daf66eecc4b42930c61495d1f12d71ccfbed82ddb2a78757905b3621c9eccef7aec22e0ba4af72bfc11d95a7072b0b91c04ac71c552df1d8d487", "output": "0x00000000000000000000000028de4113921bd79388b0ef80a259f871977442e9", "type": "STATICCALL"}, {"from": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "gas": "0x527ec", "gasUsed": "0x7678", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x23b872dd000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000009ad820aac5779683b481c4674208a4e1b024afa000000000000000000000000000000000000000000000000000000000000cf24", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x510de", "gasUsed": "0x73d6", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x23b872dd000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000009ad820aac5779683b481c4674208a4e1b024afa000000000000000000000000000000000000000000000000000000000000cf24", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "gas": "0x4a6bd", "gasUsed": "0x44eec", "to": "0x09ad820aac5779683b481c4674208a4e1b024afa", "input": "0x158f68940000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000019101590040b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf24e00114d00119d001205247dd5e3941528e217ec203345b1780d16a25c8c20340603b95ef04708ad37bf0012700f0467b96f558daf66eecc4b42930c61495d1f12d71ccfbed82ddb2a78757905b3621c9eccef7aec22e0ba4af72bfc11d95a7072b0b91c04ac71c552df1d8d4871b00e06a69e7bad0193c7f05dd1ad0195cf60351fef02710833589fcd6edb6e08f4c7c32d4f71b54bda029138569213d10e502b0573f9cd6d33be324144b894ff0cf2402012902013d0e015101513df02124f801f80001015f0301630301650e015101510301654200000000000000000000000000000000000006020178020178050000020070020070070500200100000154015f000000000001670178013d060020018c018f0000050000018f019500000600200195019800000300000198019f0000", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x09ad820aac5779683b481c4674208a4e1b024afa", "gas": "0x47e43", "gasUsed": "0x62fd", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x095ea7b30000000000000000000000008569213d10e502b0573f9cd6d33be324144b894f000000000000000000000000000000000000000000000000000000000000cf24", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x469e1", "gasUsed": "0x6061", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x095ea7b30000000000000000000000008569213d10e502b0573f9cd6d33be324144b894f000000000000000000000000000000000000000000000000000000000000cf24", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x09ad820aac5779683b481c4674208a4e1b024afa", "gas": "0x40a2b", "gasUsed": "0x368c6", "to": "0x8569213d10e502b0573f9cd6d33be324144b894f", "input": "0x3df0212400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf240000000000000000000000000000000000000000000000000000000000000000", "output": "0x0000000000000000000000000000000000000000000000000000193fd5c83779", "calls": [{"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x3d65e", "gasUsed": "0x1648e", "to": "0xa30f6ef7211d4c8882036a2d2a0f01351a10e84b", "input": "0x3ba0b9a900000000000000000000000000000000000000000000000000000000", "output": "0x000000000000000000000000000000000000000000000067870891e50ed2b000", "calls": [{"from": "0xa30f6ef7211d4c8882036a2d2a0f01351a10e84b", "gas": "0x39857", "gasUsed": "0x11331", "to": "0xb2cc224c1c9fee385f8ad6a55b4d94e92359dc59", "input": "0x883bdbfd0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000", "output": "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002fffffffffffffffffffffffffffffffffffffffffffffffffffff350119bd4c7fffffffffffffffffffffffffffffffffffffffffffffffffffff3501198c4820000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000006a00000014856537ffa4f1bcbeab3c3c38a000000000000000000000000000006a00000014856537ffdc97ec4097df8ed85", "calls": [{"from": "0xb2cc224c1c9fee385f8ad6a55b4d94e92359dc59", "gas": "0x37fc2", "gasUsed": "0x10891", "to": "0xec8e5342b19977b4ef8892e02d8daecfa1315831", "input": "0x883bdbfd0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000", "output": "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002fffffffffffffffffffffffffffffffffffffffffffffffffffff350119bd4c7fffffffffffffffffffffffffffffffffffffffffffffffffffff3501198c4820000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000006a00000014856537ffa4f1bcbeab3c3c38a000000000000000000000000000006a00000014856537ffdc97ec4097df8ed85", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}], "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x27474", "gasUsed": "0x18", "to": "0x0000000000000000000000000000000000000004", "input": "0x0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000067870891e50ed2b000000000000000000000000000000000000000000c9f2c9cd04674edea40000000", "output": "0x0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000067870891e50ed2b000000000000000000000000000000000000000000c9f2c9cd04674edea40000000", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x273c1", "gasUsed": "0x18", "to": "0x0000000000000000000000000000000000000004", "input": "0x0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000067870891e50ed2b000000000000000000000000000000000000000000c9f2c9cd04674edea40000000", "output": "0x0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000067870891e50ed2b000000000000000000000000000000000000000000c9f2c9cd04674edea40000000", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x23f5b", "gasUsed": "0x18", "to": "0x0000000000000000000000000000000000000004", "input": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000005d95d43223cec11b0000000000000000000000000000000000000000000000000000000094f677cc", "output": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000005d95d43223cec11b0000000000000000000000000000000000000000000000000000000094f677cc", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x23ea7", "gasUsed": "0x18", "to": "0x0000000000000000000000000000000000000004", "input": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000005d95d43223cec11b0000000000000000000000000000000000000000000000000000000094f677cc", "output": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000005d95d43223cec11b0000000000000000000000000000000000000000000000000000000094f677cc", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x23e00", "gasUsed": "0x18", "to": "0x0000000000000000000000000000000000000004", "input": "0x0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000067870891e50ed2b000000000000000000000000000000000000000000c9f2c9cd04674edea40000000", "output": "0x0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000067870891e50ed2b000000000000000000000000000000000000000000c9f2c9cd04674edea40000000", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x23d5b", "gasUsed": "0x18", "to": "0x0000000000000000000000000000000000000004", "input": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000005d95d43223cec11b0000000000000000000000000000000000000000000000000000000094f677cc", "output": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000005d95d43223cec11b0000000000000000000000000000000000000000000000000000000094f677cc", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x23a3f", "gasUsed": "0x18", "to": "0x0000000000000000000000000000000000000004", "input": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002ba23fbc9f9e7c9fb6e0000000000000000000000000000000000000000000000877b18b83f8df8c000", "output": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002ba23fbc9f9e7c9fb6e0000000000000000000000000000000000000000000000877b18b83f8df8c000", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x2398c", "gasUsed": "0x18", "to": "0x0000000000000000000000000000000000000004", "input": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002ba23fbc9f9e7c9fb6e0000000000000000000000000000000000000000000000877b18b83f8df8c000", "output": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002ba23fbc9f9e7c9fb6e0000000000000000000000000000000000000000000000877b18b83f8df8c000", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x23854", "gasUsed": "0xcb2", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a082310000000000000000000000008569213d10e502b0573f9cd6d33be324144b894f", "output": "0x00000000000000000000000000000000000000000000000000000000a970676d", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x22ced", "gasUsed": "0x9f9", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a082310000000000000000000000008569213d10e502b0573f9cd6d33be324144b894f", "output": "0x00000000000000000000000000000000000000000000000000000000a970676d", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x22a87", "gasUsed": "0x240c", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x23b872dd00000000000000000000000009ad820aac5779683b481c4674208a4e1b024afa0000000000000000000000008569213d10e502b0573f9cd6d33be324144b894f000000000000000000000000000000000000000000000000000000000000cf24", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x21f6e", "gasUsed": "0x216a", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x23b872dd00000000000000000000000009ad820aac5779683b481c4674208a4e1b024afa0000000000000000000000008569213d10e502b0573f9cd6d33be324144b894f000000000000000000000000000000000000000000000000000000000000cf24", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x205c5", "gasUsed": "0x4e2", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a082310000000000000000000000008569213d10e502b0573f9cd6d33be324144b894f", "output": "0x00000000000000000000000000000000000000000000000000000000a9713691", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x1fb29", "gasUsed": "0x229", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a082310000000000000000000000008569213d10e502b0573f9cd6d33be324144b894f", "output": "0x00000000000000000000000000000000000000000000000000000000a9713691", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x1f2b2", "gasUsed": "0x18", "to": "0x0000000000000000000000000000000000000004", "input": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002ba23fbc9f9e7c9fb6e0000000000000000000000000000000000000000000000877b18b83f8df8c000", "output": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002ba23fbc9f9e7c9fb6e0000000000000000000000000000000000000000000000877b18b83f8df8c000", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x1f20d", "gasUsed": "0x18", "to": "0x0000000000000000000000000000000000000004", "input": "0x0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000067870891e50ed2b000000000000000000000000000000000000000000c9f2c9cd04674edea40000000", "output": "0x0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000067870891e50ed2b000000000000000000000000000000000000000000c9f2c9cd04674edea40000000", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x1e0ae", "gasUsed": "0x18", "to": "0x0000000000000000000000000000000000000004", "input": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002ba23fbc9f9e7c9fb6e0000000000000000000000000000000000000000000000877b18b83f8df8c000", "output": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002ba23fbc9f9e7c9fb6e0000000000000000000000000000000000000000000000877b18b83f8df8c000", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x1ced3", "gasUsed": "0x18", "to": "0x0000000000000000000000000000000000000004", "input": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002ba23fbc9f9e7c9fb6e0000000000000000000000000000000000000000000000877b18b83f8df8c000", "output": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002ba23fbc9f9e7c9fb6e0000000000000000000000000000000000000000000000877b18b83f8df8c000", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x19e2d", "gasUsed": "0x18", "to": "0x0000000000000000000000000000000000000004", "input": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002ba23fbc9f9e7c9fb6e0000000000000000000000000000000000000000000000877b18b83f8df8c000", "output": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002ba23fbc9f9e7c9fb6e0000000000000000000000000000000000000000000000877b18b83f8df8c000", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x19d1c", "gasUsed": "0x18", "to": "0x0000000000000000000000000000000000000004", "input": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002ba233f0d84426c3c030000000000000000000000000000000000000000000000877bd51ceeed1f0000", "output": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002ba233f0d84426c3c030000000000000000000000000000000000000000000000877bd51ceeed1f0000", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x19400", "gasUsed": "0x15", "to": "0x0000000000000000000000000000000000000004", "input": "0x000000000000000000000000000000000000000000000000000000006a69e681000000000000000000000000000000000000000000000000000000006a69e681", "output": "0x000000000000000000000000000000000000000000000000000000006a69e681000000000000000000000000000000000000000000000000000000006a69e681", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x18211", "gasUsed": "0x15", "to": "0x0000000000000000000000000000000000000004", "input": "0x000000000000000000000000000000000000000000000000000000000000000100000000000000000de104b22b6139ea00000000000000000de72dfca2472490", "output": "0x000000000000000000000000000000000000000000000000000000000000000100000000000000000de104b22b6139ea00000000000000000de72dfca2472490", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x1816e", "gasUsed": "0x18", "to": "0x0000000000000000000000000000000000000004", "input": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002ba233f0d84426c3c030000000000000000000000000000000000000000000000877bd51ceeed1f0000", "output": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002ba233f0d84426c3c030000000000000000000000000000000000000000000000877bd51ceeed1f0000", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x17b69", "gasUsed": "0x15", "to": "0x0000000000000000000000000000000000000004", "input": "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000de72de9d1f0db24", "output": "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000de72de9d1f0db24", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x17ab9", "gasUsed": "0x15", "to": "0x0000000000000000000000000000000000000004", "input": "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000de72de9d1f0db24", "output": "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000de72de9d1f0db24", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x16cec", "gasUsed": "0x15", "to": "0x0000000000000000000000000000000000000004", "input": "0x0000000000000000000000000000000000000000000000000de72de9d1f0db240000000000000000000000000000000000000000000000000de12c42d31343fb", "output": "0x0000000000000000000000000000000000000000000000000de72de9d1f0db240000000000000000000000000000000000000000000000000de12c42d31343fb", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x14aac", "gasUsed": "0x15", "to": "0x0000000000000000000000000000000000000004", "input": "0x0000000000000000000000000000000000000000000003417b65ba2dd2ea5b7d000000000000000000000000000000000000000000000333388f3d402de1d758", "output": "0x0000000000000000000000000000000000000000000003417b65ba2dd2ea5b7d000000000000000000000000000000000000000000000333388f3d402de1d758", "type": "STATICCALL"}, {"from": "0x8569213d10e502b0573f9cd6d33be324144b894f", "gas": "0x11a9c", "gasUsed": "0x7405", "to": "0x4200000000000000000000000000000000000006", "input": "0xa9059cbb00000000000000000000000009ad820aac5779683b481c4674208a4e1b024afa0000000000000000000000000000000000000000000000000000193fd5c83779", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x09ad820aac5779683b481c4674208a4e1b024afa", "gas": "0xa9c9", "gasUsed": "0x1c9", "to": "0x4200000000000000000000000000000000000006", "input": "0x70a0823100000000000000000000000009ad820aac5779683b481c4674208a4e1b024afa", "output": "0x0000000000000000000000000000000000000000000000000000193fd5c83779", "type": "STATICCALL"}, {"from": "0x09ad820aac5779683b481c4674208a4e1b024afa", "gas": "0xa1af", "gasUsed": "0x239f", "to": "0x4200000000000000000000000000000000000006", "input": "0x2e1a7d4d0000000000000000000000000000000000000000000000000000193fd5c83779", "calls": [{"from": "0x4200000000000000000000000000000000000006", "gas": "0x8fc", "gasUsed": "0x37", "to": "0x09ad820aac5779683b481c4674208a4e1b024afa", "input": "0x", "value": "0x193fd5c83779", "type": "CALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x09ad820aac5779683b481c4674208a4e1b024afa", "gas": "0x588e", "gasUsed": "0x37", "to": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "input": "0x", "value": "0x193fd5c83779", "type": "CALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "gas": "0x4ca2", "gasUsed": "0x575", "to": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "input": "0x", "value": "0x193fd5c83779", "type": "CALL"}], "value": "0x0", "type": "CALL"}, {"from": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "gas": "0x245e", "gasUsed": "0x245e", "to": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "input": "0xa6bd8c9600000000000000000000000000000000000000000000000000000000000000000000000000000000000000007efeb39ecc2b02d7c651cd8c547eed22f83c166500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000021582f3f59ff81e5e0c1b952abba1b7f70ee487db08547df4c6fe71b773e8d43790000000000000000000000000000000000000000000000000000000000000000", "error": "out of gas", "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "CALL"} \ No newline at end of file diff --git a/tools/hindsight/src/decoder/fixtures/trace_revert_transfer_failure_0x12d802d5.json b/tools/hindsight/src/decoder/fixtures/trace_revert_transfer_failure_0x12d802d5.json new file mode 100644 index 000000000..70e737c36 --- /dev/null +++ b/tools/hindsight/src/decoder/fixtures/trace_revert_transfer_failure_0x12d802d5.json @@ -0,0 +1 @@ +{"from": "0x4d3dc340825d4814cad5f9161a55bcecd5053347", "gas": "0x46d77", "gasUsed": "0x3299e", "to": "0xccc88a9d1b4ed6b0eaba998850414b24f1c315be", "input": "0xf9e4bab400000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef00000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fb42da273158b0f642f59f2ba7cc1d5457481677000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000707ed010ffa5ea4440000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000520000000000000000000000000fb42da273158b0f642f59f2ba7cc1d54574816770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c00000000000000000000000000000000000000000000000707ed010ffa5ea4440000000000000000000000000000000000000000000000000000000000000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000030446ec278a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000002a6026e00e0b92fe925dc43a0ecde6c8b1a2709c170ec4fff4ffb42da273158b0f642f59f2ba7cc1d5457481677833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000707ed010ffa5ea444e00114e80119e8011d5247dd5e3941528e217ec203345b1780d16a25c8c20340603b95ef04708ad37bf0012100737558fbc499693830614d9970cd462c591fad84dc7f50ac6742b5556bbc0075201fc69582f0a9ce58fac80660faccb6dd8c4d5ffce4c41b6098a6ec1e5b444f1c00e06a69e79ee8106450e811bef0f02710128acb08f800b80707ed010ffa5ea444000000000000000000000000fffd8963efd1fc6a506488495d951d5263988d2500000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000014fb42da273158b0f642f59f2ba7cc1d5457481677000000000000000000000000010123060301270e0129012904013300809399da51c1a85e64cce4b30b554875d2b89b244500070a0000000000000000000000000000000000000000000000000000000000000301d8050000f80100000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000144200000000000000000000000000000000000006000000000000000000000000010123060301ff0500400402010080aad23a67f2ac693abbe543489aeb3f24f561d5170301d8050080020070020070070500c000004001b301c401c407002001f901ff000000004002810290029007002002a402aa000006002002aa02ad000003000002ad02b40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000022473b7bb2f000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000000000010000000000000000000000004cd00e387622c35bddb9b4c962c136462338bc310000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000645a1ee3ac0000000000000000000000004d3dc340825d4814cad5f9161a55bcecd5053347000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913ce8d84b62a6b2adb46cc898e66842b9445275d1b74989c64fffd1f9fc1d6e50b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000214e487265f90e44f2364464f1957efde34a656cb1ff1139b3dd83cea608c5ee090000000000000000000000000000000000000000000000000000000000000000ce8d84b62a6b2adb46cc898e66842b9445275d1b74989c64fffd1f9fc1d6e50b", "output": "0xe450d38c000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f000000000000000000000000000000000000000000000006daed7aa2c74f79c4000000000000000000000000000000000000000000000006f16d3dd960d70f04", "error": "execution reverted", "calls": [{"from": "0xccc88a9d1b4ed6b0eaba998850414b24f1c315be", "gas": "0x3adcb", "gasUsed": "0xcf61", "to": "0xfb42da273158b0f642f59f2ba7cc1d5457481677", "input": "0x23b872dd0000000000000000000000004d3dc340825d4814cad5f9161a55bcecd5053347000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000000000000000000000000000707ed010ffa5ea444", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "CALL"}, {"from": "0xccc88a9d1b4ed6b0eaba998850414b24f1c315be", "gas": "0x2bdec", "gasUsed": "0x184f3", "to": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "input": "0xcd6e13f70000000000000000000000000000000000000000000000000000000000000080000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef00000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000520000000000000000000000000fb42da273158b0f642f59f2ba7cc1d54574816770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c00000000000000000000000000000000000000000000000707ed010ffa5ea4440000000000000000000000000000000000000000000000000000000000000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000030446ec278a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000002a6026e00e0b92fe925dc43a0ecde6c8b1a2709c170ec4fff4ffb42da273158b0f642f59f2ba7cc1d5457481677833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000707ed010ffa5ea444e00114e80119e8011d5247dd5e3941528e217ec203345b1780d16a25c8c20340603b95ef04708ad37bf0012100737558fbc499693830614d9970cd462c591fad84dc7f50ac6742b5556bbc0075201fc69582f0a9ce58fac80660faccb6dd8c4d5ffce4c41b6098a6ec1e5b444f1c00e06a69e79ee8106450e811bef0f02710128acb08f800b80707ed010ffa5ea444000000000000000000000000fffd8963efd1fc6a506488495d951d5263988d2500000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000014fb42da273158b0f642f59f2ba7cc1d5457481677000000000000000000000000010123060301270e0129012904013300809399da51c1a85e64cce4b30b554875d2b89b244500070a0000000000000000000000000000000000000000000000000000000000000301d8050000f80100000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000144200000000000000000000000000000000000006000000000000000000000000010123060301ff0500400402010080aad23a67f2ac693abbe543489aeb3f24f561d5170301d8050080020070020070070500c000004001b301c401c407002001f901ff000000004002810290029007002002a402aa000006002002aa02ad000003000002ad02b40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000022473b7bb2f000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000000000010000000000000000000000004cd00e387622c35bddb9b4c962c136462338bc310000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000645a1ee3ac0000000000000000000000004d3dc340825d4814cad5f9161a55bcecd5053347000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913ce8d84b62a6b2adb46cc898e66842b9445275d1b74989c64fffd1f9fc1d6e50b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000214e487265f90e44f2364464f1957efde34a656cb1ff1139b3dd83cea608c5ee090000000000000000000000000000000000000000000000000000000000000000", "output": "0xe450d38c000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f000000000000000000000000000000000000000000000006daed7aa2c74f79c4000000000000000000000000000000000000000000000006f16d3dd960d70f04", "error": "execution reverted", "calls": [{"from": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "gas": "0x252a5", "gasUsed": "0x632c", "to": "0xfb42da273158b0f642f59f2ba7cc1d5457481677", "input": "0x095ea7b300000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c00000000000000000000000000000000000000000000000707ed010ffa5ea444", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "CALL"}, {"from": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "gas": "0x1d62e", "gasUsed": "0xa2a7", "to": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "input": "0x46ec278a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000002a6026e00e0b92fe925dc43a0ecde6c8b1a2709c170ec4fff4ffb42da273158b0f642f59f2ba7cc1d5457481677833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000707ed010ffa5ea444e00114e80119e8011d5247dd5e3941528e217ec203345b1780d16a25c8c20340603b95ef04708ad37bf0012100737558fbc499693830614d9970cd462c591fad84dc7f50ac6742b5556bbc0075201fc69582f0a9ce58fac80660faccb6dd8c4d5ffce4c41b6098a6ec1e5b444f1c00e06a69e79ee8106450e811bef0f02710128acb08f800b80707ed010ffa5ea444000000000000000000000000fffd8963efd1fc6a506488495d951d5263988d2500000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000014fb42da273158b0f642f59f2ba7cc1d5457481677000000000000000000000000010123060301270e0129012904013300809399da51c1a85e64cce4b30b554875d2b89b244500070a0000000000000000000000000000000000000000000000000000000000000301d8050000f80100000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000144200000000000000000000000000000000000006000000000000000000000000010123060301ff0500400402010080aad23a67f2ac693abbe543489aeb3f24f561d5170301d8050080020070020070070500c000004001b301c401c407002001f901ff000000004002810290029007002002a402aa000006002002aa02ad000003000002ad02b400000000000000000000000000000000000000000000000000000000", "output": "0xe450d38c000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f000000000000000000000000000000000000000000000006daed7aa2c74f79c4000000000000000000000000000000000000000000000006f16d3dd960d70f04", "error": "execution reverted", "calls": [{"from": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "gas": "0x1b9d0", "gasUsed": "0xbb8", "to": "0x0000000000000000000000000000000000000001", "input": "0xffbd6c0ae1f6e4bcb5711fd14675933f9e2412e8bbbe6f27743cf04c0c27bba3000000000000000000000000000000000000000000000000000000000000001c737558fbc499693830614d9970cd462c591fad84dc7f50ac6742b5556bbc0075201fc69582f0a9ce58fac80660faccb6dd8c4d5ffce4c41b6098a6ec1e5b444f", "output": "0x00000000000000000000000028de4113921bd79388b0ef80a259f871977442e9", "type": "STATICCALL"}, {"from": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "gas": "0x1997b", "gasUsed": "0x2616", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a08231000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x17791", "gasUsed": "0x9f9", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a08231000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "gas": "0x172b3", "gasUsed": "0xcb2", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a0823100000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c", "output": "0x0000000000000000000000000000000000000000000000000000000000101b00", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0x16a63", "gasUsed": "0x9f9", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a0823100000000000000000000000020f6ee51340adeed01a59b0e65cb3703f3dc860c", "output": "0x0000000000000000000000000000000000000000000000000000000000101b00", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x20f6ee51340adeed01a59b0e65cb3703f3dc860c", "gas": "0x15bdf", "gasUsed": "0x2db7", "to": "0xfb42da273158b0f642f59f2ba7cc1d5457481677", "input": "0x23b872dd000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000009ad820aac5779683b481c4674208a4e1b024afa00000000000000000000000000000000000000000000000707ed010ffa5ea444", "output": "0xe450d38c000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f000000000000000000000000000000000000000000000006daed7aa2c74f79c4000000000000000000000000000000000000000000000006f16d3dd960d70f04", "error": "execution reverted", "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "CALL"} \ No newline at end of file diff --git a/tools/hindsight/src/decoder/fixtures/trace_revert_zeroex_slippage_0x157e025b.json b/tools/hindsight/src/decoder/fixtures/trace_revert_zeroex_slippage_0x157e025b.json new file mode 100644 index 000000000..772d4b904 --- /dev/null +++ b/tools/hindsight/src/decoder/fixtures/trace_revert_zeroex_slippage_0x157e025b.json @@ -0,0 +1 @@ +{"from": "0xf70da97812cb96acdf810712aa562db8dfa3dbef", "gas": "0x121681", "gasUsed": "0x4a627", "to": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "input": "0xcd6e13f70000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008400000000000000000000000000000000000001ff3684f28c67538d4d072c2273400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c01d57175ff16a1000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000007442213bc0b0000000000000000000000007747f8d2a76bd6345cc29622a946a929647f235900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c01d57175ff16a10000000000000000000000007747f8d2a76bd6345cc29622a946a929647f235900000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000006641fff991f000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000d92d88c000000000000000000000000000000000000000000000000000000000000000a0138b51c04603f9ab1fe5513d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000000000000044bd01c226000000000000000000000000000000000000000000000000000000006a6e693a0000000000000000000000000000000000000000000000001c01d57175ff16a100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000027100000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000024d0e30db000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000027100000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000242e1a7d4d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104d47868c9000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000027100000000000000000000000006a6951db3bb99f76a8ccb9f8a8399b613666298b00000000000000000000000000000000000000000000000000005af3107a400000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000dd2ab92600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008434ee90ca000000000000000000000000f5c4f3dc02c3fb9279495a8fef7b0741da956157000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000dd9c30950000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001a49bb43718000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "output": "0x97a6f3b9000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000dd2ab9260000000000000000000000000000000000000000000000000000000000000000", "error": "execution reverted", "calls": [{"from": "0xb92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "gas": "0x11011d", "gasUsed": "0x3d3b3", "to": "0x0000000000001ff3684f28c67538d4d072c22734", "input": "0x2213bc0b0000000000000000000000007747f8d2a76bd6345cc29622a946a929647f235900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c01d57175ff16a10000000000000000000000007747f8d2a76bd6345cc29622a946a929647f235900000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000006641fff991f000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000d92d88c000000000000000000000000000000000000000000000000000000000000000a0138b51c04603f9ab1fe5513d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000000000000044bd01c226000000000000000000000000000000000000000000000000000000006a6e693a0000000000000000000000000000000000000000000000001c01d57175ff16a100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000027100000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000024d0e30db000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000027100000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000242e1a7d4d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104d47868c9000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000027100000000000000000000000006a6951db3bb99f76a8ccb9f8a8399b613666298b00000000000000000000000000000000000000000000000000005af3107a400000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000dd2ab92600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008434ee90ca000000000000000000000000f5c4f3dc02c3fb9279495a8fef7b0741da956157000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000dd9c309500000000000000000000000000000000000000000000000000000000000027100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "output": "0x97a6f3b9000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000dd2ab9260000000000000000000000000000000000000000000000000000000000000000", "error": "execution reverted", "calls": [{"from": "0x0000000000001ff3684f28c67538d4d072c22734", "gas": "0x10b138", "gasUsed": "0xe6", "to": "0x7747f8d2a76bd6345cc29622a946a929647f2359", "input": "0x70a08231000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "output": "0x00", "type": "STATICCALL"}, {"from": "0x0000000000001ff3684f28c67538d4d072c22734", "gas": "0x109399", "gasUsed": "0x3a92e", "to": "0x7747f8d2a76bd6345cc29622a946a929647f2359", "input": "0x1fff991f000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000d92d88c000000000000000000000000000000000000000000000000000000000000000a0138b51c04603f9ab1fe5513d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000000000000044bd01c226000000000000000000000000000000000000000000000000000000006a6e693a0000000000000000000000000000000000000000000000001c01d57175ff16a100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000027100000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000024d0e30db000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000027100000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000242e1a7d4d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104d47868c9000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000027100000000000000000000000006a6951db3bb99f76a8ccb9f8a8399b613666298b00000000000000000000000000000000000000000000000000005af3107a400000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000dd2ab92600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008434ee90ca000000000000000000000000f5c4f3dc02c3fb9279495a8fef7b0741da956157000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000dd9c3095000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000b92fe925dc43a0ecde6c8b1a2709c170ec4fff4f", "output": "0x97a6f3b9000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000dd2ab9260000000000000000000000000000000000000000000000000000000000000000", "error": "execution reverted", "calls": [{"from": "0x7747f8d2a76bd6345cc29622a946a929647f2359", "gas": "0x102155", "gasUsed": "0x5cfa", "to": "0x4200000000000000000000000000000000000006", "input": "0xd0e30db00000000000000000000000000000000000000000000000001c01d57175ff16a1", "value": "0x1c01d57175ff16a1", "type": "CALL"}, {"from": "0x7747f8d2a76bd6345cc29622a946a929647f2359", "gas": "0xfbf14", "gasUsed": "0x1c9", "to": "0x4200000000000000000000000000000000000006", "input": "0x70a082310000000000000000000000007747f8d2a76bd6345cc29622a946a929647f2359", "output": "0x0000000000000000000000000000000000000000000000001c01d57175ff16a1", "type": "STATICCALL"}, {"from": "0x7747f8d2a76bd6345cc29622a946a929647f2359", "gas": "0xfbba5", "gasUsed": "0x23b3", "to": "0x4200000000000000000000000000000000000006", "input": "0x2e1a7d4d0000000000000000000000000000000000000000000000001c01d57175ff16a1", "calls": [{"from": "0x4200000000000000000000000000000000000006", "gas": "0x8fc", "gasUsed": "0x4b", "to": "0x7747f8d2a76bd6345cc29622a946a929647f2359", "input": "0x", "value": "0x1c01d57175ff16a1", "type": "CALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x7747f8d2a76bd6345cc29622a946a929647f2359", "gas": "0xf6e22", "gasUsed": "0x2aba7", "to": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "input": "0xf513ef4c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000004ed5000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xf1de5", "gasUsed": "0x298ad", "to": "0x57dafd63aa581657b0a76db413f371e17a1b0cbe", "input": "0xf513ef4c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000004ed5000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xeadb7", "gasUsed": "0x4cbf", "to": "0x4ad6120c42d5e1bf8ec56f37c437da7b8621c582", "input": "0xb8b5ce81", "value": "0x0", "type": "CALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xe604e", "gasUsed": "0x20e1", "to": "0x4ad6120c42d5e1bf8ec56f37c437da7b8621c582", "input": "0x24675a09000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004ed500000000000000000000000000000000ffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000001", "output": "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "type": "STATICCALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xe2fc0", "gasUsed": "0x59d4", "to": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "input": "0xc3f909d4", "output": "0x00000000000000000000000000000000000000000000000000005af3107a400000000000000000000000000000000000000000000000000000000000000000010000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000030bf17711657bfb5d6645aad39984d5ccb086ec0000000000000000000000003484f248f7b0dfe1f826b145e9d582a6e034ccb400000000000000000000000000000000000000000000000006f05b59d3b2000000000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "gas": "0xde4a6", "gasUsed": "0x46cb", "to": "0xf1466addf635bf0ba0a0c4bb9fecaa03b0945510", "input": "0xc3f909d4", "output": "0x00000000000000000000000000000000000000000000000000005af3107a400000000000000000000000000000000000000000000000000000000000000000010000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000030bf17711657bfb5d6645aad39984d5ccb086ec0000000000000000000000003484f248f7b0dfe1f826b145e9d582a6e034ccb400000000000000000000000000000000000000000000000006f05b59d3b2000000000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xdc613", "gasUsed": "0xb92", "to": "0x3484f248f7b0dfe1f826b145e9d582a6e034ccb4", "input": "0xe146af4700000000000000000000000000000000000000000000000000000100000000010000000000000000000000000000000000000000000000000000000000004ed500000000000000000000000000000000ffffffffffffffffffffffffffffffff", "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "type": "STATICCALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xdb6af", "gasUsed": "0xf3e", "to": "0x4200000000000000000000000000000000000006", "input": "0xd0e30db0", "value": "0x0", "type": "CALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xda5dc", "gasUsed": "0x1193", "to": "0x4200000000000000000000000000000000000006", "input": "0x095ea7b30000000000000000000000007c976801c8589d924ce93bbcce6966d34edf91bf0000000000000000000000000000000000000000000000000000000000000000", "output": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "type": "CALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xd92c2", "gasUsed": "0xc62", "to": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "input": "0x506dcce40000000000000000000000007747f8d2a76bd6345cc29622a946a929647f23590000000000000000000000006a6951db3bb99f76a8ccb9f8a8399b613666298b", "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "gas": "0xd5b64", "gasUsed": "0xb26", "to": "0xf1466addf635bf0ba0a0c4bb9fecaa03b0945510", "input": "0x506dcce40000000000000000000000007747f8d2a76bd6345cc29622a946a929647f23590000000000000000000000006a6951db3bb99f76a8ccb9f8a8399b613666298b", "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xd837b", "gasUsed": "0x6fbd", "to": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "input": "0xf513ef4c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000004ed5000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000006a6e6811", "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "gas": "0xd4c37", "gasUsed": "0x6e57", "to": "0xf1466addf635bf0ba0a0c4bb9fecaa03b0945510", "input": "0xf513ef4c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000004ed5000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000006a6e6811", "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "gas": "0xcf87f", "gasUsed": "0x31c", "to": "0x3484f248f7b0dfe1f826b145e9d582a6e034ccb4", "input": "0xa558898f00000000000000000000000000000000000000000000000000000100000000010000000000000000000000000000000000000000000000000000000000004ed5", "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "type": "CALL"}, {"from": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "gas": "0xcd0c2", "gasUsed": "0x153", "to": "0x3484f248f7b0dfe1f826b145e9d582a6e034ccb4", "input": "0x2e286683", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "type": "STATICCALL"}, {"from": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "gas": "0xcc39b", "gasUsed": "0x923", "to": "0x030bf17711657bfb5d6645aad39984d5ccb086ec", "input": "0x2e286683", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "type": "STATICCALL"}, {"from": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "gas": "0xcb803", "gasUsed": "0xb7f", "to": "0x4ad6120c42d5e1bf8ec56f37c437da7b8621c582", "input": "0x558f514800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "type": "CALL"}], "value": "0x0", "type": "DELEGATECALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xcffb3", "gasUsed": "0x1f25", "to": "0x01dda2b73ede58c840d73379710a18cbc5fe290e", "input": "0x4d35daf300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x01dda2b73ede58c840d73379710a18cbc5fe290e", "gas": "0xcaf2d", "gasUsed": "0x123", "to": "0x947c13afe52b8676144664e12487544718089267", "input": "0x8d64be7e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "type": "STATICCALL"}], "value": "0x0", "type": "CALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xcdf98", "gasUsed": "0x1c9", "to": "0x4200000000000000000000000000000000000006", "input": "0x70a082310000000000000000000000006a6951db3bb99f76a8ccb9f8a8399b613666298b", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "type": "STATICCALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xcd264", "gasUsed": "0x2616", "to": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "input": "0x70a082310000000000000000000000006a6951db3bb99f76a8ccb9f8a8399b613666298b", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "gas": "0xc8397", "gasUsed": "0x9f9", "to": "0x2ce6311ddae708829bc0784c967b7d77d19fd779", "input": "0x70a082310000000000000000000000006a6951db3bb99f76a8ccb9f8a8399b613666298b", "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xc91b3", "gasUsed": "0x4b", "to": "0x7747f8d2a76bd6345cc29622a946a929647f2359", "input": "0x", "value": "0x1c01bb41f5374000", "type": "CALL"}, {"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xc8d9c", "gasUsed": "0x39ae", "to": "0x4ad6120c42d5e1bf8ec56f37c437da7b8621c582", "input": "0xec3a5314", "calls": [{"from": "0x4ad6120c42d5e1bf8ec56f37c437da7b8621c582", "gas": "0xc4e3a", "gasUsed": "0x901", "to": "0x49b1a6b424c19803fa64a5b40baa7f0428e258b3", "input": "0x18160ddd", "output": "0x000000000000000000000000000000000000000000005987575d2b7d9d3cecb0", "type": "STATICCALL"}], "value": "0x0", "type": "CALL"}], "value": "0x1c01bb41f5374000", "type": "DELEGATECALL"}], "value": "0x1c01bb41f5374000", "type": "CALL"}, {"from": "0x7747f8d2a76bd6345cc29622a946a929647f2359", "gas": "0xccc28", "gasUsed": "0x1561", "to": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "input": "0xc3f909d4", "output": "0x00000000000000000000000000000000000000000000000000005af3107a400000000000000000000000000000000000000000000000000000000000000000010000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000030bf17711657bfb5d6645aad39984d5ccb086ec0000000000000000000000003484f248f7b0dfe1f826b145e9d582a6e034ccb400000000000000000000000000000000000000000000000006f05b59d3b2000000000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xc97ea", "gasUsed": "0x13ec", "to": "0x57dafd63aa581657b0a76db413f371e17a1b0cbe", "input": "0xc3f909d4", "output": "0x00000000000000000000000000000000000000000000000000005af3107a400000000000000000000000000000000000000000000000000000000000000000010000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000030bf17711657bfb5d6645aad39984d5ccb086ec0000000000000000000000003484f248f7b0dfe1f826b145e9d582a6e034ccb400000000000000000000000000000000000000000000000006f05b59d3b2000000000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x6a6951db3bb99f76a8ccb9f8a8399b613666298b", "gas": "0xc61aa", "gasUsed": "0x9c0", "to": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "input": "0xc3f909d4", "output": "0x00000000000000000000000000000000000000000000000000005af3107a400000000000000000000000000000000000000000000000000000000000000000010000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000030bf17711657bfb5d6645aad39984d5ccb086ec0000000000000000000000003484f248f7b0dfe1f826b145e9d582a6e034ccb400000000000000000000000000000000000000000000000006f05b59d3b2000000000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "calls": [{"from": "0x7c976801c8589d924ce93bbcce6966d34edf91bf", "gas": "0xc2f16", "gasUsed": "0x84b", "to": "0xf1466addf635bf0ba0a0c4bb9fecaa03b0945510", "input": "0xc3f909d4", "output": "0x00000000000000000000000000000000000000000000000000005af3107a400000000000000000000000000000000000000000000000000000000000000000010000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000030bf17711657bfb5d6645aad39984d5ccb086ec0000000000000000000000003484f248f7b0dfe1f826b145e9d582a6e034ccb400000000000000000000000000000000000000000000000006f05b59d3b2000000000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}], "value": "0x0", "type": "DELEGATECALL"}], "type": "STATICCALL"}], "value": "0x1c01d57175ff16a1", "type": "CALL"}], "value": "0x1c01d57175ff16a1", "type": "CALL"}], "value": "0x1c01d57175ff16a1", "type": "CALL"} \ No newline at end of file diff --git a/tools/hindsight/src/decoder/matching.rs b/tools/hindsight/src/decoder/matching.rs index 5c980e3f8..5ebd9079a 100644 --- a/tools/hindsight/src/decoder/matching.rs +++ b/tools/hindsight/src/decoder/matching.rs @@ -12,53 +12,150 @@ use tracing::debug; use crate::decoder::{registry::Registry, solvers}; -/// A transaction identified as a solver trade, ready to be traced and decoded. +/// A transaction identified as a solver trade, ready to be traced and decoded — settled or +/// reverted, distinguished by `reverted`. A reverted match carries no logs to net a settled +/// amount from, so it is decoded differently (see `decoder::decode_transaction`), but it is the +/// same shape as a settled one otherwise: it was still a trade, just one that did not fill. Every +/// field is a reference or `Copy`, so the type itself is `Copy` — passed around freely rather than +/// threaded by move. +#[derive(Clone, Copy)] pub(crate) struct MatchedSolverTrade<'a> { pub receipt: &'a AnyTransactionReceipt, /// The contract the transaction entered through (`tx.to`). pub entry_point: Address, + pub reverted: bool, } -/// Match a receipt as a solver trade. +/// Venues whose reverted swaps are worth decoding for the trader's `min_amount_out`. A reverted +/// transaction emits no logs, so entry-point matching is the only signal available — gated by +/// venue name (not chain), so a venue not listed here is simply not attempted. +const REVERT_DECODED_VENUES: &[&str] = &["relay"]; + +/// Match a receipt as a solver trade, settled or reverted. +/// +/// A settled transaction qualifies two ways: its entry point (`tx.to`) is a known venue or +/// solver, or one of its logs was emitted by a known solver (filler-initiated intent fills, +/// where `tx.to` is a rotating filler). Matched transactions whose logs mark a non-swap order +/// shape are vetoed here (see `solvers::solver_veto`), before they cost a trace. /// -/// A transaction qualifies two ways: its entry point (`tx.to`) is a known -/// venue or solver, or one of its logs was emitted by a known solver -/// (filler-initiated intent fills, where `tx.to` is a rotating filler). -/// Matched transactions whose logs mark a non-swap order shape are vetoed -/// here (see `solvers::solver_veto`), before they cost a trace. +/// A reverted transaction emits no logs, so entry-point matching is the only signal available: it +/// matches only when `tx.to` is a venue in [`REVERT_DECODED_VENUES`]. There is no veto path for +/// reverts — a veto reads logs a revert never has. pub(crate) fn select<'a>( receipt: &'a AnyTransactionReceipt, registry: &Registry, ) -> Option> { - let matched = match_entry(receipt, registry)?; - if let Some(veto) = solvers::solver_veto(matched.receipt.logs(), matched.entry_point, registry) - { - debug!( - tx = %matched.receipt.transaction_hash, - venue = %registry.label(matched.entry_point), - ?veto, - "matched transaction is not a same-chain swap; skipping" - ); - return None; + if receipt.status() { + let matched = match_settled(receipt, registry)?; + if let Some(veto) = + solvers::solver_veto(matched.receipt.logs(), matched.entry_point, registry) + { + debug!( + tx = %matched.receipt.transaction_hash, + venue = %registry.label(matched.entry_point), + ?veto, + "matched transaction is not a same-chain swap; skipping" + ); + return None; + } + return Some(matched); } - Some(matched) + match_reverted(receipt, registry) } -/// Match a receipt by its entry point or its solver logs. -fn match_entry<'a>( +/// Match a settled receipt by its entry point or its solver logs. +fn match_settled<'a>( receipt: &'a AnyTransactionReceipt, registry: &Registry, ) -> Option> { - if !receipt.status() { - return None; - } let entry_point = receipt.to?; if registry.is_known(entry_point) { - return Some(MatchedSolverTrade { receipt, entry_point }); + return Some(MatchedSolverTrade { receipt, entry_point, reverted: false }); } let via_log = receipt .logs() .iter() .any(|log| registry.is_solver(log.address())); - via_log.then_some(MatchedSolverTrade { receipt, entry_point }) + via_log.then_some(MatchedSolverTrade { receipt, entry_point, reverted: false }) +} + +/// Match a reverted receipt as a candidate for revert decoding: `tx.to` is an entry point of a +/// venue in [`REVERT_DECODED_VENUES`]. +fn match_reverted<'a>( + receipt: &'a AnyTransactionReceipt, + registry: &Registry, +) -> Option> { + let entry_point = receipt.to?; + let venue_name = registry.venue_name(entry_point)?; + REVERT_DECODED_VENUES + .iter() + .find(|&&name| name == venue_name)?; + Some(MatchedSolverTrade { receipt, entry_point, reverted: true }) +} + +#[cfg(test)] +mod tests { + use alloy::primitives::address; + + use super::*; + use crate::decoder::test_utils::{addr, receipt, reverted_receipt, tx_hash}; + + fn relay_entry_point(registry: &Registry) -> Address { + *registry + .venue("relay") + .unwrap() + .entry_points + .iter() + .next() + .unwrap() + } + + #[test] + fn test_select_reverted_relay_tx_matches() { + let registry = Registry::ethereum(); + let relay = relay_entry_point(®istry); + let tx = reverted_receipt(tx_hash(1), addr(1), Some(relay)); + + let matched = select(&tx, ®istry).unwrap(); + assert_eq!(matched.entry_point, relay); + assert!(matched.reverted); + } + + #[test] + fn test_select_reverted_non_venue_tx_does_not_match() { + let registry = Registry::ethereum(); + // A registered solver (not a revert-decoded venue) reverting must not match: reverts are + // only decoded for venues in REVERT_DECODED_VENUES. + let oneinch = address!("0x111111125421ca6dc452d289314280a0f8842a65"); + let tx = reverted_receipt(tx_hash(1), addr(1), Some(oneinch)); + assert!(select(&tx, ®istry).is_none()); + + // An address the registry knows nothing about. + let unknown = reverted_receipt(tx_hash(2), addr(1), Some(addr(200))); + assert!(select(&unknown, ®istry).is_none()); + } + + #[test] + fn test_select_reverted_requires_reverted_status() { + let registry = Registry::ethereum(); + let relay = relay_entry_point(®istry); + let successful = receipt(tx_hash(1), addr(1), Some(relay), vec![]); + let matched = select(&successful, ®istry).unwrap(); + assert!(!matched.reverted); + } + + #[test] + fn test_select_successful_path_unchanged() { + // The existing successful-match path is untouched by the revert path. + let registry = Registry::ethereum(); + let relay = relay_entry_point(®istry); + let successful = receipt(tx_hash(1), addr(1), Some(relay), vec![]); + let matched = select(&successful, ®istry).unwrap(); + assert_eq!(matched.entry_point, relay); + assert!(!matched.reverted); + + let reverted = reverted_receipt(tx_hash(2), addr(1), Some(relay)); + let matched = select(&reverted, ®istry).unwrap(); + assert!(matched.reverted); + } } diff --git a/tools/hindsight/src/decoder/mod.rs b/tools/hindsight/src/decoder/mod.rs index c3b30fad4..3b2ec8f6d 100644 --- a/tools/hindsight/src/decoder/mod.rs +++ b/tools/hindsight/src/decoder/mod.rs @@ -46,17 +46,43 @@ use tracing::{debug, warn}; use crate::decoder::{ decode::{recover, DecodeContext, GasScope, TraderFlow}, matching::MatchedSolverTrade, - solvers::SwapIntent, trace::{collect_native_transfers, fetch_trace, route_gas}, transfer_ledger::TransferLedger, }; pub(crate) use crate::decoder::{ - registry::Registry, sandwich::SandwichEvidence, solvers::attribution::AttributionSource, + registry::Registry, + sandwich::SandwichEvidence, + solvers::{attribution::AttributionSource, SwapIntent}, + trace::RevertCause, }; -/// A decoded solver trade: what token went in, what came out. +/// Whether a matched transaction settled or reverted on-chain. Serialized flattened into +/// `DecodedTrade`'s own JSON object (`"status":"settled"`, or `"status":"reverted"` plus +/// `"cause"`), so a settled and a reverted trade read as the same record shape, told apart by +/// this one field rather than by which fields happen to be present. +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] +#[serde(tag = "status", rename_all = "snake_case")] +pub(crate) enum TradeStatus { + Settled, + Reverted { cause: RevertCause }, +} + +/// A decoded solver trade: what token went in, what came out — settled or reverted, told apart by +/// `status`. Both are trades in the same sense: a trader routed through a solver; one just did +/// not fill. /// /// Native ETH is represented as `Address::ZERO`. +/// +/// # The settled/reverted invariant +/// +/// A settled trade always carries `token_in`/`token_out`/`amount_in`/`amount_out`: +/// `Decoder::decode_settled` only ever returns `Some(DecodedTrade)` once a `TraderFlow` decoder +/// has recovered a real swap, and never leaves them empty. A reverted trade carries +/// `token_in`/`token_out`/`amount_in` only when its solver frame's calldata parsed into a +/// `SwapIntent` — a revert emits no logs, so calldata is the only source; when it did not parse, +/// all three are `None`. The trade is still recorded (not filtered out) so parser coverage stays +/// measurable against every reverted candidate. `amount_out` is settled-only: nothing was +/// delivered on a revert, by definition. #[derive(Debug, Clone, serde::Serialize)] pub(crate) struct DecodedTrade { pub tx_hash: TxHash, @@ -64,39 +90,54 @@ pub(crate) struct DecodedTrade { /// The transaction's position in its block, from the receipt (falls back to its position in /// the fetched receipt slice when the RPC omitted it). pub tx_index: u64, + #[serde(flatten)] + pub status: TradeStatus, pub venue: String, pub solver: String, /// The evidence tier the solver label came from (see `solvers::attribution`). Downstream /// analysis weighs low-trust tiers (`largest_call`, fallback) differently — e.g. when judging /// an embedded quote. pub solver_source: AttributionSource, - /// Which decoder recovered this trade (see `decode`). Once several decoders can carry a - /// venue's trades this measures how often each one carries a trade the others could not. + /// Which decoder recovered this trade (see `decode`), or `"reverted"` when nothing settled to + /// hand to a `TradeDecoder` — a reverted trade's terms, when recoverable, come straight from + /// the solver frame's own calldata, not the netting/calldata decoder chain. Once several + /// decoders can carry a venue's trades this measures how often each one carries a trade the + /// others could not. pub decoder: &'static str, pub sender: Address, - pub token_in: Address, - pub token_out: Address, + /// `None` only for a reverted trade whose solver frame's calldata did not parse — see the + /// settled/reverted invariant above. + #[serde(skip_serializing_if = "Option::is_none")] + pub token_in: Option
, + #[serde(skip_serializing_if = "Option::is_none")] + pub token_out: Option
, /// Input amount that actually entered the swap — a venue fee taken from the input (see - /// `venue_fee_in`) is already subtracted, so a re-solve compares like-for-like. - pub amount_in: U256, + /// `venue_fee_in`) is already subtracted, so a re-solve compares like-for-like. For a + /// reverted trade this is the calldata-declared amount, not a netted one (see the + /// settled/reverted invariant above). + #[serde(skip_serializing_if = "Option::is_none")] + pub amount_in: Option, /// Gross swap output — a venue fee taken from the output (see `venue_fee_out`) is added /// back, so the settled amount is the full swap proceeds, comparable to Fynd's gross output. - pub amount_out: U256, + /// `None` for a reverted trade: nothing was delivered. + #[serde(skip_serializing_if = "Option::is_none")] + pub amount_out: Option, /// Venue fee taken from the input token before swapping (e.g. Relay's fee), in `token_in` - /// units. `None` when no known fee collector took a cut. Recorded for transparency; it is - /// already excluded from `amount_in`. + /// units. `None` when no known fee collector took a cut, or the trade reverted. Recorded for + /// transparency; it is already excluded from `amount_in`. #[serde(skip_serializing_if = "Option::is_none")] pub venue_fee_in: Option, /// Venue fee taken from the output token after swapping, in `token_out` units. `None` when - /// no known fee collector took a cut. Recorded for transparency; it is already added back into - /// `amount_out`. + /// no known fee collector took a cut, or the trade reverted. Recorded for transparency; it is + /// already added back into `amount_out`. #[serde(skip_serializing_if = "Option::is_none")] pub venue_fee_out: Option, /// Wei cost of the gas the trader paid for the settled route (`gas_used × /// effective_gas_price`). For venue-wrapped entries (Relay, `MetaMask`) the venue's own /// overhead is excluded — it is charged whichever router the venue picks, like the venue /// fee. `None` when the trader did not pay the transaction's gas (intent fills, solver - /// rebalances) or the route's gas could not be isolated from the trace. + /// rebalances), the route's gas could not be isolated from the trace, or the trade reverted + /// (nothing settled to charge gas against). #[serde(skip_serializing_if = "Option::is_none")] pub settled_gas: Option, /// The on-chain enforced floor declared in the settling solver frame's own calldata (see @@ -115,11 +156,22 @@ pub(crate) struct DecodedTrade { #[serde(skip_serializing_if = "Option::is_none")] pub quote_timestamp: Option, /// Evidence that a front-run and a back-run bracketed this trade (see - /// `sandwich::detect`). `None` when no bracket pair was found. + /// `sandwich::detect`). `None` when no bracket pair was found, or the trade reverted — + /// nothing settled to be sandwiched around. #[serde(skip_serializing_if = "Option::is_none")] pub sandwich: Option, } +impl DecodedTrade { + /// This trade's swap terms, when known — always `Some` for a settled trade (see the + /// settled/reverted invariant above), and for a reverted trade only when its solver frame's + /// calldata parsed. `resolve::resolve_block_range` uses this to decide whether a trade can be + /// solved at all: a trade with unknown terms is recorded but not re-solved. + pub(crate) fn terms(&self) -> Option<(Address, Address, U256)> { + Some((self.token_in?, self.token_out?, self.amount_in?)) + } +} + /// Log a disagreement between the calldata-recovered intent and the netted flow, on any of the /// three terms they both claim. The ledger stays authoritative for what settled; two /// independently-derived readings landing on different terms is diagnostic signal we would @@ -153,7 +205,7 @@ fn warn_on_intent_disagreement( } /// Copy the calldata-declared terms off a parsed intent, or all-`None` when no intent was -/// recovered. Split out of `decode_transaction` purely to keep it under the line limit. +/// recovered. fn intent_fields(intent: Option<&SwapIntent>) -> (Option, Option, Option) { let min_amount_out = intent.map(|intent| intent.min_amount_out); let declared_quote = intent.and_then(SwapIntent::declared_quote); @@ -194,15 +246,16 @@ impl Decoder

{ &self.registry } - /// Decode solver trades from a block. + /// Decode solver trades from a block — settled and reverted alike, as one list told apart by + /// `DecodedTrade::status`. /// - /// Fetches all receipts in one `eth_getBlockReceipts` call, then matches a - /// transaction two ways: its entry point (`tx.to`) is a known venue or - /// solver, or one of its logs was emitted by a known solver. The - /// second case catches filler-initiated intent fills (`UniswapX`, 1inch - /// limit orders) where `tx.to` is a rotating filler. Matched transactions are - /// traced concurrently; the trace recovers native ETH flows and attributes - /// the settling solver. + /// Fetches all receipts in one `eth_getBlockReceipts` call, then matches each transaction one + /// of three ways: a settled trade, matched by entry point or by a known solver's log (the log + /// path catches filler-initiated intent fills, `UniswapX`, 1inch limit orders, where `tx.to` + /// is a rotating filler); a reverted candidate, matched by entry point alone (a revert emits + /// no logs — see `matching::select`); or neither, and the transaction is dropped before it + /// costs a trace. Both matched shapes join one bounded trace wave; the trace recovers native + /// ETH flows and attributes the settling (or attempted) solver either way. pub(crate) async fn decode_block( &mut self, block_number: u64, @@ -251,20 +304,17 @@ impl Decoder

{ .collect::>() .await; - let mut trades = Vec::with_capacity(matched.len()); + let mut trades = Vec::new(); for ((index, matched), trace) in matched.into_iter().zip(traces) { let tx_index = matched .receipt .transaction_index .unwrap_or(index as u64); + let tx_hash = matched.receipt.transaction_hash; let root = match trace { Ok(root) => root, Err(e) => { - warn!( - block = block_number, - tx = %matched.receipt.transaction_hash, - "skipping untraceable transaction: {e}" - ); + warn!(block = block_number, tx = %tx_hash, "skipping untraceable transaction: {e}"); crate::telemetry::record_untraced_transaction(); continue; } @@ -273,25 +323,99 @@ impl Decoder

{ .decode_transaction(matched, &root, block_number, tx_index) .await { - let evidence = sandwich::detect(&receipts, index, &trade, &self.registry); - trade.sandwich = evidence; + // Sandwich detection only makes sense around a trade that actually moved the + // pools; a reverted trade has nothing to be sandwiched around. + if trade.status == TradeStatus::Settled { + trade.sandwich = sandwich::detect(&receipts, index, &trade, &self.registry); + } trades.push(trade); } } Ok(trades) } - /// Decode one matched transaction from its trace: build the transfer ledger, run the decoders - /// for its entity, veto non-trades, attribute the solver, and account gas and quote. + /// Decode one matched transaction: settled trades run the full `TraderFlow` decoder chain + /// (`decode_settled`); reverted candidates skip it — there is no netted flow to decode, only + /// the settling solver frame's own calldata to read (`decode_reverted`). async fn decode_transaction( &mut self, matched: MatchedSolverTrade<'_>, root: &CallFrame, block_number: u64, tx_index: u64, + ) -> Option { + if matched.reverted { + return Some(self.decode_reverted(matched, root, block_number, tx_index)); + } + self.decode_settled(matched, root, block_number, tx_index) + .await + } + + /// Decode a reverted candidate from its trace: attribute the solver the same way a settled + /// trade would be (the strict-then-tolerant `find_solver_frame` walk falls back to the frame + /// that tried, since nothing settled), recover its swap terms when that solver's calldata + /// supports it, and classify why the transaction failed. The terms are `None` — not an + /// error — when no solver frame was found, its solver has no `swap_intent` support, or the + /// calldata did not parse; the trade is still recorded so parser coverage is measurable + /// against every reverted candidate. Always produces a trade: unlike a settled decode, there + /// is no veto or missing-flow case to decline on. + fn decode_reverted( + &self, + matched: MatchedSolverTrade<'_>, + root: &CallFrame, + block_number: u64, + tx_index: u64, + ) -> DecodedTrade { + let registry = &self.registry; + let MatchedSolverTrade { receipt, entry_point, .. } = matched; + let sender = receipt.from; + let venue = registry.label(entry_point); + let attribution = + solvers::attribution::attribute(None, root, entry_point, sender, registry); + // A reverted trade has no netted flow to draw an input-amount hint from — only the + // ABI/offset-based extractors (Fly, KyberSwap) can recover an intent here. + let intent = trace::find_solver_frame(root, registry) + .and_then(|frame| solvers::swap_intent(&attribution.solver, &frame.input, None)); + let (min_amount_out, declared_quote, quote_timestamp) = intent_fields(intent.as_ref()); + let (token_in, token_out, amount_in) = match &intent { + Some(intent) => (Some(intent.token_in), Some(intent.token_out), Some(intent.amount_in)), + None => (None, None, None), + }; + DecodedTrade { + tx_hash: receipt.transaction_hash, + block_number, + tx_index, + status: TradeStatus::Reverted { cause: trace::classify_revert_cause(root) }, + venue, + solver: attribution.solver, + solver_source: attribution.source, + decoder: "reverted", + sender, + token_in, + token_out, + amount_in, + amount_out: None, + venue_fee_in: None, + venue_fee_out: None, + settled_gas: None, + min_amount_out, + declared_quote, + quote_timestamp, + sandwich: None, + } + } + + /// Decode one settled transaction from its trace: build the transfer ledger, run the + /// decoders for its entity, veto non-trades, attribute the solver, and account gas and quote. + async fn decode_settled( + &mut self, + matched: MatchedSolverTrade<'_>, + root: &CallFrame, + block_number: u64, + tx_index: u64, ) -> Option { let Self { provider, registry, code_cache } = self; - let MatchedSolverTrade { receipt, entry_point } = matched; + let MatchedSolverTrade { receipt, entry_point, .. } = matched; let logs = receipt.logs(); let sender = receipt.from; @@ -388,15 +512,16 @@ impl Decoder

{ tx_hash: receipt.transaction_hash, block_number, tx_index, + status: TradeStatus::Settled, venue, solver: attribution.solver, solver_source: attribution.source, decoder, sender: flow.tracked, - token_in: flow.swap.token_in, - token_out: flow.swap.token_out, - amount_in: flow.swap.amount_in, - amount_out: flow.swap.amount_out, + token_in: Some(flow.swap.token_in), + token_out: Some(flow.swap.token_out), + amount_in: Some(flow.swap.amount_in), + amount_out: Some(flow.swap.amount_out), venue_fee_in: flow.venue_fee_in, venue_fee_out: flow.venue_fee_out, settled_gas, @@ -461,4 +586,47 @@ mod tests { assert_eq!(trades.len(), 1); assert_eq!(trades[0].tx_hash, tx_hash(2)); } + + /// A Relay transaction that reverted before settling, decoded end to end through + /// `decode_block`: matched by entry point alone, attributed via the trace's tolerant + /// fallback, and recorded with no swap terms (an unregistered "solver" frame, so its calldata + /// carries no `swap_intent`). + #[tokio::test] + async fn test_reverted_relay_transaction_decodes_with_a_reverted_status() { + let registry = Registry::ethereum(); + let relay = *registry + .venue("relay") + .unwrap() + .entry_points + .iter() + .next() + .unwrap(); + let sender = addr(1); + + let asserter = Asserter::new(); + asserter.push_success(&vec![crate::decoder::test_utils::reverted_receipt( + tx_hash(1), + sender, + Some(relay), + )]); + let mut root = frame("CALL", sender, relay, 0); + root.error = Some("execution reverted".to_string()); + + asserter.push_success(&root); + + let mut decoder = + Decoder::new(ProviderBuilder::default().connect_mocked_client(asserter), registry); + let trades = decoder + .decode_block(21_000_000) + .await + .expect("decode_block should succeed"); + + assert_eq!(trades.len(), 1); + let trade = &trades[0]; + assert!(matches!(trade.status, TradeStatus::Reverted { .. })); + assert_eq!(trade.venue, "relay"); + assert!(trade.token_in.is_none()); + assert!(trade.amount_out.is_none()); + assert!(trade.sandwich.is_none()); + } } diff --git a/tools/hindsight/src/decoder/sandwich.rs b/tools/hindsight/src/decoder/sandwich.rs index 57e4cdfbb..0411a580a 100644 --- a/tools/hindsight/src/decoder/sandwich.rs +++ b/tools/hindsight/src/decoder/sandwich.rs @@ -63,7 +63,10 @@ pub(crate) fn detect( if victim_pools.is_empty() { return None; } - let token = direction_token(victim.token_out, registry); + // Sandwich detection only makes sense for a trade that actually moved the pools; a reverted + // trade (or one whose calldata did not parse) has no settled output token to key the + // direction on. + let token = direction_token(victim.token_out?, registry); let front_start = victim_index.saturating_sub(WINDOW); let back_end = (victim_index + WINDOW).min(receipts.len().saturating_sub(1)); @@ -272,15 +275,16 @@ mod tests { tx_hash: TxHash::default(), block_number: 1, tx_index: 0, + status: crate::decoder::TradeStatus::Settled, venue: "relay".into(), solver: "1inch".into(), solver_source: AttributionSource::TraceMatch, decoder: "sender-netting", sender, - token_in: addr(59), - token_out, - amount_in: U256::from(1_000u64), - amount_out: U256::from(2_000u64), + token_in: Some(addr(59)), + token_out: Some(token_out), + amount_in: Some(U256::from(1_000u64)), + amount_out: Some(U256::from(2_000u64)), venue_fee_in: None, venue_fee_out: None, settled_gas: None, diff --git a/tools/hindsight/src/decoder/solvers/fly.rs b/tools/hindsight/src/decoder/solvers/fly.rs index 9512b7a01..da9f4cca6 100644 --- a/tools/hindsight/src/decoder/solvers/fly.rs +++ b/tools/hindsight/src/decoder/solvers/fly.rs @@ -23,6 +23,10 @@ const SELECTORS: [[u8; 4]; 5] = [ [0x62, 0x7d, 0xd5, 0x6a], ]; +/// Fly's `InsufficientAmountOut()` custom error selector (`DexAggregator.sol`) — the marker for a +/// slippage-floor revert. +const INSUFFICIENT_AMOUNT_OUT: [u8; 4] = [0xe5, 0x29, 0x70, 0xaa]; + const TO_ADDRESS_OFFSET: usize = 72; const FROM_ASSET_OFFSET: usize = 92; const TO_ASSET_OFFSET: usize = 112; @@ -108,6 +112,11 @@ impl SolverKnowledge for Fly { has_fly_selector(input)?; Some(Address::from_slice(input.get(TO_ADDRESS_OFFSET..TO_ADDRESS_OFFSET + ADDRESS_LEN)?)) } + + /// `InsufficientAmountOut()`'s selector as the frame's revert output. + fn is_slippage_floor(&self, output: Option<&[u8]>, _revert_reason: Option<&str>) -> bool { + output.is_some_and(|output| output.starts_with(&INSUFFICIENT_AMOUNT_OUT)) + } } #[cfg(test)] @@ -133,7 +142,7 @@ mod tests { assert_eq!(intent.token_out, Address::ZERO); assert_eq!(intent.amount_in, U256::from(19_694_643u64)); assert_eq!(intent.min_amount_out, U256::from(10_217_898_321_149_381u64)); - assert_eq!(intent.quoted_amount_out(), U256::from(10_321_109_415_302_405u64)); + assert_eq!(intent.declared_quote(), Some(U256::from(10_321_109_415_302_405u64))); } #[test] @@ -193,6 +202,14 @@ mod tests { assert!(Fly.swap_intent(&input, None).is_none()); } + #[test] + fn test_is_slippage_floor_matches_the_selector() { + assert!(Fly.is_slippage_floor(Some(&INSUFFICIENT_AMOUNT_OUT), None)); + assert!(Fly.is_slippage_floor(Some(&[0xe5, 0x29, 0x70, 0xaa, 0x00, 0x01]), None)); + assert!(!Fly.is_slippage_floor(Some(&[0xde, 0xad, 0xbe, 0xef]), None)); + assert!(!Fly.is_slippage_floor(None, None)); + } + #[test] fn test_amount_out_min_above_expected_rejected() { let mut input = real_input(); diff --git a/tools/hindsight/src/decoder/solvers/kyberswap.rs b/tools/hindsight/src/decoder/solvers/kyberswap.rs index 34347c28e..e6c0f6d63 100644 --- a/tools/hindsight/src/decoder/solvers/kyberswap.rs +++ b/tools/hindsight/src/decoder/solvers/kyberswap.rs @@ -19,6 +19,9 @@ use crate::decoder::solvers::{SolverKnowledge, SwapIntent}; const KYBERSWAP_NATIVE: Address = alloy::primitives::address!("0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"); +/// `KyberSwap`'s slippage-floor revert reason. +const INSUFFICIENT_RETURN: &str = "Return amount is not enough"; + sol! { /// `MetaAggregationRouterV2.swap`'s parameter shape, verified against a live reverted trade /// (tx 0xd3b7ffae…, Base): decoding recovered `srcToken`/`dstToken`/`amount`/`minReturnAmount` @@ -122,6 +125,11 @@ impl SolverKnowledge for Kyberswap { let call = swapCall::abi_decode(input).ok()?; Some(call.execution.desc.dstReceiver) } + + /// "Return amount is not enough" as the frame's revert reason. + fn is_slippage_floor(&self, _output: Option<&[u8]>, revert_reason: Option<&str>) -> bool { + revert_reason.is_some_and(|reason| reason.contains(INSUFFICIENT_RETURN)) + } } #[cfg(test)] @@ -208,8 +216,8 @@ mod tests { assert_eq!(intent.token_out, dst); assert_eq!(intent.amount_in, U256::from(1_000_000u64)); assert_eq!(intent.min_amount_out, U256::from(990_000u64)); - // No clientData quote declared: the accessor falls back to the floor. - assert_eq!(intent.quoted_amount_out(), U256::from(990_000u64)); + // No clientData quote declared. + assert_eq!(intent.declared_quote(), None); assert_eq!(intent.timestamp, None); } @@ -238,7 +246,7 @@ mod tests { .swap_intent(&swap_calldata(src, dst, 1_000_000, 990_000, BLOB), None) .unwrap(); assert_eq!(intent.min_amount_out, U256::from(990_000u64)); - assert_eq!(intent.quoted_amount_out(), U256::from(70_400_409_935u64)); + assert_eq!(intent.declared_quote(), Some(U256::from(70_400_409_935u64))); assert_eq!(intent.timestamp, Some(1_783_421_726)); } @@ -254,7 +262,7 @@ mod tests { None, ) .unwrap(); - assert_eq!(intent.quoted_amount_out(), U256::from(990_000u64)); + assert_eq!(intent.declared_quote(), None); assert_eq!(intent.timestamp, None); } @@ -282,6 +290,15 @@ mod tests { .is_none()); } + #[test] + fn test_is_slippage_floor_matches_the_revert_reason() { + assert!(Kyberswap.is_slippage_floor(None, Some(INSUFFICIENT_RETURN))); + assert!(Kyberswap + .is_slippage_floor(None, Some("execution reverted: Return amount is not enough"))); + assert!(!Kyberswap.is_slippage_floor(None, Some("execution reverted"))); + assert!(!Kyberswap.is_slippage_floor(None, None)); + } + #[test] fn test_swap_intent_garbage_input() { assert!(Kyberswap diff --git a/tools/hindsight/src/decoder/solvers/mod.rs b/tools/hindsight/src/decoder/solvers/mod.rs index a7cbe80b6..b8d760c3e 100644 --- a/tools/hindsight/src/decoder/solvers/mod.rs +++ b/tools/hindsight/src/decoder/solvers/mod.rs @@ -29,9 +29,8 @@ use crate::decoder::{registry::Registry, veto::Veto}; /// revert emits no logs to net a settled amount from. The declared quote is different: it is the /// number the venue compared against at decision time — what the solver's API promised — as /// opposed to the settled amount, which is what execution delivered. It is self-reported and not -/// every solver declares one, so it is read through [`SwapIntent::quoted_amount_out`] (falls back -/// to the floor) or [`SwapIntent::declared_quote`] (the raw value, for callers that must tell a -/// real quote from the fallback). +/// every solver declares one, so it is read through [`SwapIntent::declared_quote`], `None` when +/// the calldata carried no quote. #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)] pub(crate) struct SwapIntent { /// `Address::ZERO` for native ETH. @@ -76,24 +75,7 @@ impl SwapIntent { self } - /// The best available "what was promised": the solver's declared quote, or — when absent — - /// the enforced floor. - #[cfg_attr( - not(test), - expect( - dead_code, - reason = "only called from tests in this PR; its production caller is the \ - reverted-swap path in the stacked follow-up PR" - ) - )] - pub(crate) fn quoted_amount_out(&self) -> U256 { - self.quoted_amount_out - .unwrap_or(self.min_amount_out) - } - - /// The raw declared quote, `None` when the calldata carried none. Distinct from - /// [`SwapIntent::quoted_amount_out`], which falls back to the floor — analysts need to tell - /// a real quote from the fallback. + /// The solver's declared off-chain quote, `None` when the calldata carried none. pub(crate) fn declared_quote(&self) -> Option { self.quoted_amount_out } @@ -147,6 +129,15 @@ pub(crate) trait SolverKnowledge: Send + Sync { fn integrator(&self, _logs: &[Log]) -> Option { None } + + /// Whether a reverted call frame's output or revert reason matches this solver's + /// slippage-floor marker — the avoidable class of revert a fresher quote could have cleared + /// (Fly's `InsufficientAmountOut()` selector, `KyberSwap`'s "Return amount is not enough" + /// revert reason). Checked against every frame in a reverted trace's subtree (see + /// `trace::classify_revert_cause`), so a solver need not be attributed yet to be recognized. + fn is_slippage_floor(&self, _output: Option<&[u8]>, _revert_reason: Option<&str>) -> bool { + false + } } /// The solvers with a `SolverKnowledge` implementation, by address-book name. A solver absent @@ -211,6 +202,16 @@ pub(crate) fn output_recipient(solver: &str, input: &[u8]) -> Option

{ knowledge.output_recipient(input) } +/// Whether a reverted call frame's output or revert reason matches any registered solver's +/// slippage-floor marker. Unscoped by attribution — the marker is a hard fact about the frame's +/// own bytes, and only one solver's check can ever match a given frame — so every implementation +/// is tried. +pub(crate) fn is_slippage_floor(output: Option<&[u8]>, revert_reason: Option<&str>) -> bool { + IMPLEMENTATIONS + .iter() + .any(|(_, knowledge)| knowledge.is_slippage_floor(output, revert_reason)) +} + /// Whether a declared quote is in the same units as the settled output. /// /// Quotes are self-reported calldata: integrators sometimes fill them in a different token or diff --git a/tools/hindsight/src/decoder/solvers/paraswap.rs b/tools/hindsight/src/decoder/solvers/paraswap.rs index 48dff8cbc..fc367d69b 100644 --- a/tools/hindsight/src/decoder/solvers/paraswap.rs +++ b/tools/hindsight/src/decoder/solvers/paraswap.rs @@ -125,7 +125,7 @@ mod tests { assert_eq!(intent.token_out, address_from_word(dst_token)); assert_eq!(intent.amount_in, amount_in); assert_eq!(intent.min_amount_out, U256::from(171_430_663u64)); - assert_eq!(intent.quoted_amount_out(), U256::from(171_602_266u64)); + assert_eq!(intent.declared_quote(), Some(U256::from(171_602_266u64))); } #[test] diff --git a/tools/hindsight/src/decoder/solvers/zeroex.rs b/tools/hindsight/src/decoder/solvers/zeroex.rs index 4a9e7c434..b0c2433b4 100644 --- a/tools/hindsight/src/decoder/solvers/zeroex.rs +++ b/tools/hindsight/src/decoder/solvers/zeroex.rs @@ -17,6 +17,17 @@ //! bare entry is declined rather than guessed, per the "no dead code, no guessing" rule; if bare //! entries turn out to matter, the `token_in` question needs its own investigation, not a shortcut //! here. +//! +//! `is_slippage_floor` covers 0x's own `TooMuchSlippage(address,uint256,uint256)`, thrown by +//! `SettlerAbstract`/`SettlerErrors.sol` (`revertTooMuchSlippage`) — also verified against +//! 0x-settler's source and confirmed live: the selector appears in real reverted Base traces +//! bubbling through both registered 0x addresses (Settler and `AllowanceHolder`), full +//! ABI-encoded as `(token, expected, actual)` (see +//! `fixtures/trace_revert_zeroex_slippage_0x157e025b.json`, whose deepest frame decodes to +//! `token=USDC, expected=0xdd2ab926, actual=0`). `"return too low"` is not 0x's own error — it is +//! the revert string of at least one inner liquidity source 0x's Settler routes through (observed +//! live from a `Kipseli PropAMM` pool) — but 0x is the only address our registry knows about +//! anywhere in those traces, so it is recognized here rather than left unclassified. use alloy::{ primitives::{Address, U256}, @@ -69,6 +80,13 @@ fn normalize_native(token: Address) -> Address { } } +/// `keccak256("TooMuchSlippage(address,uint256,uint256)")[..4]`. +const TOO_MUCH_SLIPPAGE: [u8; 4] = [0x97, 0xa6, 0xf3, 0xb9]; + +/// Revert reason from a liquidity source 0x's Settler routed through, when its own output floor +/// was not met. +const RETURN_TOO_LOW: &str = "return too low"; + /// Settler's own terms, decoded from an `execute` call regardless of how it was reached (wrapped /// in `AllowanceHolder.exec` or, if it ever occurs, called directly). struct SettlerTerms { @@ -137,6 +155,14 @@ impl SolverKnowledge for ZeroEx { } decode_execute(input).map(|terms| terms.recipient) } + + /// Whether a reverted frame's output is 0x's own `TooMuchSlippage` selector, or its revert + /// reason is the "return too low" string a liquidity source 0x's Settler routes through + /// returns. + fn is_slippage_floor(&self, output: Option<&[u8]>, revert_reason: Option<&str>) -> bool { + output.is_some_and(|output| output.starts_with(&TOO_MUCH_SLIPPAGE)) || + revert_reason.is_some_and(|reason| reason.contains(RETURN_TOO_LOW)) + } } #[cfg(test)] @@ -287,4 +313,26 @@ mod tests { assert_eq!(normalize_native(ZEROEX_NATIVE), Address::ZERO); assert_eq!(normalize_native(USDC), USDC); } + + #[test] + fn test_is_slippage_floor_matches_the_selector() { + // The full ABI-encoded shape (token, expected, actual) — only the selector is checked. + let mut output = TOO_MUCH_SLIPPAGE.to_vec(); + output.extend_from_slice(&[0u8; 96]); + assert!(ZeroEx.is_slippage_floor(Some(&output), None)); + assert!(!ZeroEx.is_slippage_floor(Some(&[0xde, 0xad, 0xbe, 0xef]), None)); + } + + #[test] + fn test_is_slippage_floor_matches_the_revert_reason() { + assert!(ZeroEx.is_slippage_floor(None, Some("return too low"))); + // Substring, not exact match: geth's callTracer sometimes carries surrounding context. + assert!(ZeroEx.is_slippage_floor(None, Some("execution reverted: return too low"))); + assert!(!ZeroEx.is_slippage_floor(None, Some("out of gas"))); + } + + #[test] + fn test_is_slippage_floor_declines_when_neither_is_present() { + assert!(!ZeroEx.is_slippage_floor(None, None)); + } } diff --git a/tools/hindsight/src/decoder/test_utils.rs b/tools/hindsight/src/decoder/test_utils.rs index c57909fe7..3b64b83c7 100644 --- a/tools/hindsight/src/decoder/test_utils.rs +++ b/tools/hindsight/src/decoder/test_utils.rs @@ -120,3 +120,35 @@ pub(crate) fn receipt( contract_address: None, }) } + +/// A synthetic reverted (status-0) receipt: no logs, since a reverted transaction emits none. +pub(crate) fn reverted_receipt( + hash: TxHash, + from: Address, + to: Option
, +) -> AnyTransactionReceipt { + WithOtherFields::new(TransactionReceipt { + inner: AnyReceiptEnvelope { + inner: ReceiptWithBloom { + receipt: Receipt { + status: Eip658Value::Eip658(false), + cumulative_gas_used: 0, + logs: vec![], + }, + logs_bloom: Bloom::default(), + }, + r#type: 0, + }, + transaction_hash: hash, + transaction_index: None, + block_hash: None, + block_number: None, + gas_used: 0, + effective_gas_price: 0, + blob_gas_used: None, + blob_gas_price: None, + from, + to, + contract_address: None, + }) +} diff --git a/tools/hindsight/src/decoder/trace.rs b/tools/hindsight/src/decoder/trace.rs index 5eb2f7654..abfe1c4db 100644 --- a/tools/hindsight/src/decoder/trace.rs +++ b/tools/hindsight/src/decoder/trace.rs @@ -5,7 +5,7 @@ use alloy::{ }; use anyhow::Context; -use crate::decoder::registry::Registry; +use crate::decoder::{registry::Registry, solvers}; /// Fetch the callTracer root frame for a transaction. /// @@ -79,18 +79,40 @@ pub(crate) fn route_gas(root: &CallFrame, registry: &Registry) -> Option { .filter(|gas| !gas.is_zero()) } -/// Depth-first search for the first call frame into a known solver, skipping reverted frames -/// (and their subtrees), which settle nothing. +/// Depth-first search for the call frame that settled or tried the swap with a known solver. /// -/// The one walk serves both questions asked of a trace: *who* settled the swap (the frame's -/// `to`, for attribution) and *what the route cost* (the frame's `gas_used`, for gas -/// accounting) — so the gas charged is always the gas of the exact frame the solver label came -/// from. +/// Prefers the frame that actually settled — skipping reverted frames and their subtrees, which +/// settle nothing — and, only when that search finds nothing, falls back to a tolerant search +/// that descends into reverted frames too. Plain "ignore every revert" (the simpler-looking +/// alternative) would get a settled trade wrong: a router that tried solver A (which reverted) +/// before succeeding via solver B has both frames in its trace, and a revert-blind walk can +/// attribute to whichever it reaches first in depth-first order, not whichever actually settled. +/// The two-phase preference handles both shapes with one function: a settled trade always finds +/// its real solver frame in the first (strict) pass, and a reverted trade — which has no settled +/// frame by definition — falls through to the second (tolerant) pass, since a revert emits no +/// logs and the frame that tried is the only frame there is to attribute to or recover calldata +/// from (see the trace fixtures for both shapes observed live). +/// +/// The one walk serves every question asked of a trace: *who* settled or tried the swap (the +/// frame's `to`, for attribution and calldata extraction) and *what the route cost* (the frame's +/// `gas_used`, for gas accounting) — so the gas charged is always the gas of the exact frame the +/// solver label came from. pub(crate) fn find_solver_frame<'a>( frame: &'a CallFrame, registry: &Registry, ) -> Option<&'a CallFrame> { - if frame.error.is_some() { + find_solver_frame_impl(frame, registry, false) + .or_else(|| find_solver_frame_impl(frame, registry, true)) +} + +/// Shared walk for both passes of [`find_solver_frame`]: the only difference is whether a frame's +/// own revert stops the search or not. +fn find_solver_frame_impl<'a>( + frame: &'a CallFrame, + registry: &Registry, + tolerate_reverts: bool, +) -> Option<&'a CallFrame> { + if frame.error.is_some() && !tolerate_reverts { return None; } if let Some(to) = frame.to { @@ -101,7 +123,103 @@ pub(crate) fn find_solver_frame<'a>( frame .calls .iter() - .find_map(|child| find_solver_frame(child, registry)) + .find_map(|child| find_solver_frame_impl(child, registry, tolerate_reverts)) +} + +/// The geth call tracer's exact error string for a frame that ran out of gas. +const OUT_OF_GAS: &str = "out of gas"; + +/// Why a reverted transaction failed, classified from its trace. +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] +#[serde(tag = "kind", content = "detail", rename_all = "snake_case")] +pub(crate) enum RevertCause { + /// A slippage-floor revert: Fly's `InsufficientAmountOut()` or `KyberSwap`'s "Return amount is + /// not enough" — the class of revert a fresher quote could have avoided. + SlippageFloor, + /// A frame ran out of gas. + OutOfGas, + /// Any other cause, taken from the deepest reverted frame's error or revert reason. + Other(String), +} + +/// Classify why a reverted transaction's root frame failed, from its whole reverted subtree. +/// +/// Checked in order of specificity: a slippage-floor marker (Fly's custom-error selector in any +/// frame's output, or `KyberSwap`'s revert reason string) anywhere in the subtree wins even when a +/// deeper frame failed for an unrelated reason — real reverts often cascade through several +/// frames, and the slippage-floor check is the one classification callers act on (it is the +/// avoidable class). Otherwise, any frame with the exact "out of gas" error settles it. Anything +/// else falls back to the deepest reverted frame's own error or revert reason. +pub(crate) fn classify_revert_cause(root: &CallFrame) -> RevertCause { + if has_slippage_floor_marker(root) { + return RevertCause::SlippageFloor; + } + if has_out_of_gas(root) { + return RevertCause::OutOfGas; + } + RevertCause::Other(deepest_revert_reason(root).unwrap_or_else(|| "unknown revert".to_string())) +} + +fn has_slippage_floor_marker(frame: &CallFrame) -> bool { + let output = frame.output.as_ref().map(AsRef::as_ref); + solvers::is_slippage_floor(output, frame.revert_reason.as_deref()) || + frame + .calls + .iter() + .any(has_slippage_floor_marker) +} + +fn has_out_of_gas(frame: &CallFrame) -> bool { + frame.error.as_deref() == Some(OUT_OF_GAS) || frame.calls.iter().any(has_out_of_gas) +} + +/// The error or revert reason of the deepest reverted frame, by depth-first search — the last +/// (deepest) hit wins, since a subtree's own failure is usually more specific than an ancestor's +/// bubbled-up "execution reverted". +fn deepest_revert_reason(frame: &CallFrame) -> Option { + let mut deepest: Option<(usize, String)> = None; + collect_deepest_revert_reason(frame, 0, &mut deepest); + deepest.map(|(_, reason)| reason) +} + +fn collect_deepest_revert_reason( + frame: &CallFrame, + depth: usize, + deepest: &mut Option<(usize, String)>, +) { + if let Some(reason) = frame_revert_reason(frame) { + if deepest + .as_ref() + .is_none_or(|(best_depth, _)| depth >= *best_depth) + { + *deepest = Some((depth, reason)); + } + } + for child in &frame.calls { + collect_deepest_revert_reason(child, depth + 1, deepest); + } +} + +/// One frame's revert reason: the tracer's own ABI-decoded string when it provides one, otherwise +/// the generic error message with the frame's raw output selector appended when there is one to +/// show (`"execution reverted (0x12345678)"`). Several RPC providers never populate +/// `revert_reason` at all, even for a frame whose output does encode a selector (a custom error, +/// or an `Error(string)` the tracer did not bother decoding) — the selector is what is left to +/// classify offline, so it is surfaced rather than dropped. +fn frame_revert_reason(frame: &CallFrame) -> Option { + if let Some(reason) = frame.revert_reason.clone() { + return Some(reason); + } + let error = frame.error.clone()?; + let selector = frame + .output + .as_ref() + .filter(|output| output.len() >= 4) + .map(|output| format!("0x{}", alloy::hex::encode(&output[..4]))); + Some(match selector { + Some(selector) => format!("{error} ({selector})"), + None => error, + }) } /// Best guess at an unknown router: the entry point's direct child call that moved the most @@ -144,6 +262,7 @@ pub(crate) fn largest_external_call( #[cfg(test)] mod tests { use alloy::primitives::address; + use tycho_simulation::tycho_common::models::Chain; use super::*; use crate::decoder::test_utils::{addr, frame}; @@ -270,15 +389,183 @@ mod tests { } #[test] - fn test_find_solver_frame_reverted_frames() { + fn test_find_solver_frame_prefers_a_settled_frame_over_a_reverted_attempt() { + // The exact shape the doc comment calls out: a router tried solver A (which reverted) + // before succeeding via solver B. The strict pass must attribute to B, not whichever it + // reaches first in depth-first order. let registry = Registry::ethereum(); let oneinch = address!("0x111111125421ca6dc452d289314280a0f8842a65"); + let zerox = address!("0x0000000000001ff3684f28c67538d4d072c22734"); - let mut reverted = frame("CALL", addr(2), oneinch, 0); - reverted.error = Some("execution reverted".to_string()); + let mut attempt_a = frame("CALL", addr(2), oneinch, 0); + attempt_a.error = Some("execution reverted".to_string()); + let attempt_b = frame("CALL", addr(2), zerox, 0); let mut root = frame("CALL", addr(1), addr(2), 0); - root.calls = vec![reverted]; + root.calls = vec![attempt_a, attempt_b]; + + let found = find_solver_frame(&root, ®istry).unwrap(); + assert_eq!(found.to, Some(zerox)); + } + + #[test] + fn test_find_solver_frame_falls_back_when_nothing_settled() { + // A reverted trade has no settled frame by definition: the strict pass finds nothing, so + // the tolerant fallback is the only way to recover which router the trader was routed + // through — the frame that tried is the only frame there is to find. + let registry = Registry::ethereum(); + let oneinch = address!("0x111111125421ca6dc452d289314280a0f8842a65"); - assert!(find_solver_frame(&root, ®istry).is_none()); + let mut solver_call = frame("CALL", addr(2), oneinch, 0); + solver_call.error = Some("execution reverted".to_string()); + let mut root = frame("CALL", addr(1), addr(2), 0); + root.error = Some("execution reverted".to_string()); + root.calls = vec![solver_call]; + + let found = find_solver_frame(&root, ®istry).unwrap(); + assert_eq!(found.to, Some(oneinch)); + } + + #[test] + fn test_find_solver_frame_real_fly_slippage_trace() { + // Real reverted trace (Base, Relay -> Fly): the fly frame itself reverted + // (InsufficientAmountOut), nested inside the reverted router and approval-proxy frames. + // Nothing settled, so the strict pass finds nothing and the fallback recovers it. + let root: CallFrame = serde_json::from_str(include_str!( + "fixtures/trace_revert_fly_slippage_0xcba01d0c.json" + )) + .unwrap(); + let fly = address!("0x20f6ee51340adeed01a59b0e65cb3703f3dc860c"); + let found = find_solver_frame(&root, &Registry::builtin(Chain::Base).unwrap()).unwrap(); + assert_eq!(found.to, Some(fly)); + } + + #[test] + fn test_find_solver_frame_real_out_of_gas_trace() { + // Real reverted trace (Base): the fly frame itself succeeded (err = None) — the router + // ran out of gas on a later, unrelated call — so the strict pass already finds it, even + // though it sits under two reverted ancestors. + let root: CallFrame = + serde_json::from_str(include_str!("fixtures/trace_revert_out_of_gas_0x08fee57c.json")) + .unwrap(); + let fly = address!("0x20f6ee51340adeed01a59b0e65cb3703f3dc860c"); + let found = find_solver_frame(&root, &Registry::builtin(Chain::Base).unwrap()).unwrap(); + assert_eq!(found.to, Some(fly)); + assert!(found.error.is_none()); + } + + #[test] + fn test_classify_revert_cause_real_fly_slippage() { + let root: CallFrame = serde_json::from_str(include_str!( + "fixtures/trace_revert_fly_slippage_0xcba01d0c.json" + )) + .unwrap(); + assert_eq!(classify_revert_cause(&root), RevertCause::SlippageFloor); + } + + #[test] + fn test_classify_revert_cause_real_kyber_slippage() { + let root: CallFrame = serde_json::from_str(include_str!( + "fixtures/trace_revert_kyber_slippage_0xd3b7ffae.json" + )) + .unwrap(); + assert_eq!(classify_revert_cause(&root), RevertCause::SlippageFloor); + } + + #[test] + fn test_classify_revert_cause_real_out_of_gas() { + let root: CallFrame = + serde_json::from_str(include_str!("fixtures/trace_revert_out_of_gas_0x08fee57c.json")) + .unwrap(); + assert_eq!(classify_revert_cause(&root), RevertCause::OutOfGas); + } + + #[test] + fn test_classify_revert_cause_real_other() { + // A `transferFrom` failure deep inside Fly's frame (a custom ERC-20 error, not one of the + // known slippage markers): neither Fly's nor KyberSwap's marker matches, so it falls back + // to the deepest reverted frame's own error — this RPC never populates `revertReason`, so + // the frame's raw output selector (0xe450d38c, `ERC20InsufficientBalance(address,uint256, + // uint256)`) is appended to the generic message rather than left unclassifiable. + let root: CallFrame = serde_json::from_str(include_str!( + "fixtures/trace_revert_transfer_failure_0x12d802d5.json" + )) + .unwrap(); + assert_eq!( + classify_revert_cause(&root), + RevertCause::Other("execution reverted (0xe450d38c)".to_string()) + ); + } + + #[test] + fn test_classify_revert_cause_synthetic_slippage_and_gas() { + let mut fly_floor = frame("CALL", addr(1), addr(2), 0); + fly_floor.error = Some("execution reverted".to_string()); + fly_floor.output = Some(alloy::primitives::Bytes::from_static(&[0xe5, 0x29, 0x70, 0xaa])); + assert_eq!(classify_revert_cause(&fly_floor), RevertCause::SlippageFloor); + + let mut kyber_floor = frame("CALL", addr(1), addr(2), 0); + kyber_floor.error = Some("execution reverted".to_string()); + kyber_floor.revert_reason = Some("Return amount is not enough".to_string()); + assert_eq!(classify_revert_cause(&kyber_floor), RevertCause::SlippageFloor); + + let mut out_of_gas = frame("CALL", addr(1), addr(2), 0); + out_of_gas.error = Some("out of gas".to_string()); + assert_eq!(classify_revert_cause(&out_of_gas), RevertCause::OutOfGas); + } + + #[test] + fn test_classify_revert_cause_prefers_deepest_reason() { + let mut inner = frame("CALL", addr(2), addr(3), 0); + inner.error = Some("transferFrom failed".to_string()); + let mut outer = frame("CALL", addr(1), addr(2), 0); + outer.error = Some("execution reverted".to_string()); + outer.calls = vec![inner]; + + assert_eq!( + classify_revert_cause(&outer), + RevertCause::Other("transferFrom failed".to_string()) + ); + } + + #[test] + fn test_classify_revert_cause_real_zeroex_slippage() { + // Real reverted trace (Base): 0x's own TooMuchSlippage(address,uint256,uint256) bubbles + // through the Settler and AllowanceHolder frames, both registered as solver "0x". + let root: CallFrame = serde_json::from_str(include_str!( + "fixtures/trace_revert_zeroex_slippage_0x157e025b.json" + )) + .unwrap(); + assert_eq!(classify_revert_cause(&root), RevertCause::SlippageFloor); + } + + #[test] + fn test_frame_revert_reason_appends_the_selector_when_undecoded() { + // No `revert_reason` (this RPC never populates it) but the frame's raw output carries a + // selector: the generic message gets it appended, so an unrecognized custom error stays + // classifiable offline instead of collapsing into a bare "execution reverted". + let mut custom_error = frame("CALL", addr(1), addr(2), 0); + custom_error.error = Some("execution reverted".to_string()); + custom_error.output = + Some(alloy::primitives::Bytes::from_static(&[0x12, 0x34, 0x56, 0x78])); + assert_eq!( + frame_revert_reason(&custom_error), + Some("execution reverted (0x12345678)".to_string()) + ); + } + + #[test] + fn test_frame_revert_reason_prefers_the_decoded_reason() { + let mut decoded = frame("CALL", addr(1), addr(2), 0); + decoded.error = Some("execution reverted".to_string()); + decoded.revert_reason = Some("Return amount is not enough".to_string()); + decoded.output = Some(alloy::primitives::Bytes::from_static(&[0x12, 0x34, 0x56, 0x78])); + assert_eq!(frame_revert_reason(&decoded), Some("Return amount is not enough".to_string())); + } + + #[test] + fn test_frame_revert_reason_no_output_stays_bare() { + let mut bare = frame("CALL", addr(1), addr(2), 0); + bare.error = Some("execution reverted".to_string()); + assert_eq!(frame_revert_reason(&bare), Some("execution reverted".to_string())); } } diff --git a/tools/hindsight/src/main.rs b/tools/hindsight/src/main.rs index 45db9be84..e73d4ba18 100644 --- a/tools/hindsight/src/main.rs +++ b/tools/hindsight/src/main.rs @@ -16,7 +16,7 @@ use tracing_subscriber::EnvFilter; use tycho_simulation::tycho_common::models::Chain; use crate::{ - decoder::{DecodedTrade, Decoder, Registry}, + decoder::{DecodedTrade, Decoder, Registry, TradeStatus}, report::ReportArgs, resolve::monitor::MonitorArgs, verify::allium::AlliumClient, @@ -182,11 +182,11 @@ async fn run_decode(args: DecodeArgs) -> anyhow::Result<()> { for block_number in &blocks { info!(block = block_number, "decoding solver trades"); let start = Instant::now(); - let trades = match decoder + let decoded = match decoder .decode_block(*block_number) .await { - Ok(trades) => trades, + Ok(decoded) => decoded, Err(error) => { warn!(block = block_number, %error, "failed to decode block; skipping"); continue; @@ -194,12 +194,22 @@ async fn run_decode(args: DecodeArgs) -> anyhow::Result<()> { }; let elapsed_ms = start.elapsed().as_millis(); - if trades.is_empty() { + if decoded.is_empty() { info!(block = block_number, elapsed_ms, "no solver trades found"); } else { - info!(block = block_number, count = trades.len(), elapsed_ms, "decoded trades"); + let reverted = decoded + .iter() + .filter(|trade| trade.status != TradeStatus::Settled) + .count(); + info!( + block = block_number, + trades = decoded.len(), + reverted, + elapsed_ms, + "decoded trades" + ); } - all_trades.extend(trades); + all_trades.extend(decoded); } if args.json { @@ -256,6 +266,9 @@ pub(crate) async fn resolve_blocks( } } +/// Print every decoded trade — settled or reverted, told apart by `status`. A settled trade's +/// terms are always known (see `DecodedTrade`'s settled/reverted invariant); a reverted one may +/// have none, when its solver calldata did not parse. #[expect(clippy::print_stdout)] fn print_trades(trades: &[DecodedTrade]) { if trades.is_empty() { @@ -271,10 +284,14 @@ fn print_trades(trades: &[DecodedTrade]) { println!(" venue: {}", trade.venue); println!(" solver: {}", trade.solver); println!(" sender: {}", trade.sender); - println!(" token_in: {}", trade.token_in); - println!(" amount_in: {}", trade.amount_in); - println!(" token_out: {}", trade.token_out); - println!(" amount_out: {}", trade.amount_out); + match &trade.status { + TradeStatus::Settled => println!(" status: settled"), + TradeStatus::Reverted { cause } => println!(" status: reverted ({cause:?})"), + } + print_option("token_in:", trade.token_in); + print_option("amount_in:", trade.amount_in); + print_option("token_out:", trade.token_out); + print_option("amount_out:", trade.amount_out); if let Some(sandwich) = &trade.sandwich { println!( " sandwich: front={} back={} attacker={}", @@ -285,6 +302,16 @@ fn print_trades(trades: &[DecodedTrade]) { } } +/// Print a labeled field that may be unknown — `unknown` (not blank) so a reverted trade whose +/// terms did not parse still reads as "we checked and found nothing" rather than an empty value. +#[expect(clippy::print_stdout)] +fn print_option(label: &str, value: Option) { + match value { + Some(value) => println!(" {label:<12}{value}"), + None => println!(" {label:<12}unknown"), + } +} + const MAX_RANGE_BLOCKS: u64 = 1000; fn parse_range(range: &str) -> anyhow::Result> { diff --git a/tools/hindsight/src/report/aggregate.rs b/tools/hindsight/src/report/aggregate.rs index 793c0ba1e..46aa05261 100644 --- a/tools/hindsight/src/report/aggregate.rs +++ b/tools/hindsight/src/report/aggregate.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; -use crate::report::record::Comparison; +use crate::report::record::{Comparison, State}; /// Number of trades listed in the biggest-wins and biggest-losses tables. const TOP_TRADES: usize = 10; @@ -75,14 +75,27 @@ pub(crate) struct GroupStats { /// One row in the biggest-wins or biggest-losses table. pub(crate) struct TradeRow { - pub settled_tx: String, + pub tx_hash: String, pub venue: String, pub solver: String, pub net_bps: Option, pub improvement_usd: f64, } -/// Aggregate every view from the parsed records. +/// A settled record's `top` state. `report::run` filters records to `status == "settled"` before +/// this module ever sees them, and a settled trade always carries a `top` state by construction +/// (see `DecodedTrade`'s settled/reverted invariant) — a missing one here means that filter was +/// bypassed. +fn top(record: &Comparison) -> &State { + record + .top + .as_ref() + .expect("settled comparison record must carry a top state") +} + +/// Aggregate every view from the parsed records. Callers must pre-filter to `status == "settled"` +/// (see `report::run`) — every aggregation here is judged on `top`, which only a settled record is +/// guaranteed to carry. pub(crate) fn build(records: &[Comparison]) -> Report { Report { summary: summary(records), @@ -110,10 +123,9 @@ fn verdict_stats(records: &[Comparison]) -> Vec { let mut counts: HashMap<&str, usize> = HashMap::new(); let mut notional: HashMap<&str, f64> = HashMap::new(); for record in records { - let verdict = record.top.verdict.as_str(); + let verdict = top(record).verdict.as_str(); *counts.entry(verdict).or_default() += 1; - *notional.entry(verdict).or_default() += record - .top + *notional.entry(verdict).or_default() += top(record) .settled_value_usd .unwrap_or(0.0); } @@ -136,26 +148,26 @@ fn verdict_stats(records: &[Comparison]) -> Vec { fn savings(records: &[Comparison]) -> Savings { let scored: Vec<&Comparison> = records .iter() - .filter(|r| r.top.is_scored()) + .filter(|r| top(r).is_scored()) .collect(); // The savings-bps headline is over wins only — how much better Fynd was when it won, not // diluted by the losses. let mut win_bps: Vec = scored .iter() - .filter(|r| r.top.verdict == "win") - .filter_map(|r| r.top.net_bps) + .filter(|r| top(r).verdict == "win") + .filter_map(|r| top(r).net_bps) .collect(); Savings { scored: scored.len(), wins: scored .iter() - .filter(|r| r.top.verdict == "win") + .filter(|r| top(r).verdict == "win") .count(), median_win_bps: median(&mut win_bps), won_usd: scored .iter() - .filter(|r| r.top.verdict == "win") - .filter_map(|r| r.top.improvement_usd) + .filter(|r| top(r).verdict == "win") + .filter_map(|r| top(r).improvement_usd) .sum(), } } @@ -173,29 +185,29 @@ fn group_stats(records: &[Comparison], key: impl Fn(&Comparison) -> &String) -> .map(|(name, group)| { let mut net_bps: Vec = group .iter() - .filter(|r| r.top.is_scored()) - .filter_map(|r| r.top.net_bps) + .filter(|r| top(r).is_scored()) + .filter_map(|r| top(r).net_bps) .collect(); GroupStats { name: name.clone(), count: group.len(), wins: group .iter() - .filter(|r| r.top.verdict == "win") + .filter(|r| top(r).verdict == "win") .count(), losses: group .iter() - .filter(|r| r.top.verdict == "loss") + .filter(|r| top(r).verdict == "loss") .count(), unsolved: group .iter() - .filter(|r| !r.top.is_served()) + .filter(|r| !top(r).is_served()) .count(), median_net_bps: median(&mut net_bps), total_improvement_usd: group .iter() - .filter(|r| r.top.is_scored()) - .filter_map(|r| r.top.improvement_usd) + .filter(|r| top(r).is_scored()) + .filter_map(|r| top(r).improvement_usd) .sum(), } }) @@ -231,15 +243,15 @@ fn top_losses(records: &[Comparison]) -> Vec { fn trade_rows(records: &[Comparison], verdict: &str) -> Vec { records .iter() - .filter(|r| r.top.verdict == verdict) + .filter(|r| top(r).verdict == verdict) .filter_map(|r| { - r.top + top(r) .improvement_usd .map(|usd| TradeRow { - settled_tx: r.settled_tx.clone(), + tx_hash: r.tx_hash.clone(), venue: r.venue.clone(), solver: r.solver.clone(), - net_bps: r.top.net_bps, + net_bps: top(r).net_bps, improvement_usd: usd, }) }) @@ -250,14 +262,14 @@ fn unsolvable_tokens(records: &[Comparison]) -> Vec { let mut counts: HashMap<&str, usize> = HashMap::new(); for record in records .iter() - .filter(|r| !r.top.is_served()) + .filter(|r| !top(r).is_served()) { - *counts - .entry(record.token_in.as_str()) - .or_default() += 1; - *counts - .entry(record.token_out.as_str()) - .or_default() += 1; + if let Some(token_in) = record.token_in.as_deref() { + *counts.entry(token_in).or_default() += 1; + } + if let Some(token_out) = record.token_out.as_deref() { + *counts.entry(token_out).or_default() += 1; + } } let mut ranked: Vec = counts .into_iter() @@ -299,11 +311,12 @@ mod tests { ) -> Comparison { serde_json::from_value(serde_json::json!({ "block": block, - "settled_tx": format!("0x{block:064x}"), + "tx_hash": format!("0x{block:064x}"), "venue": venue, "solver": solver, "token_in": "0xaaa", "token_out": "0xbbb", + "status": "settled", "top": { "verdict": verdict, "net_bps": bps, diff --git a/tools/hindsight/src/report/html.rs b/tools/hindsight/src/report/html.rs index ec69abc85..1f5717bb7 100644 --- a/tools/hindsight/src/report/html.rs +++ b/tools/hindsight/src/report/html.rs @@ -241,7 +241,7 @@ fn trades_section(title: &str, trades: &[TradeRow]) -> String { table, "{}{}{}\ {}{}", - escape(&short_hash(&trade.settled_tx)), + escape(&short_hash(&trade.tx_hash)), escape(&trade.venue), escape(&trade.solver), fmt_bps(trade.net_bps), @@ -421,13 +421,15 @@ mod tests { fn sample_report() -> Report { let records: Vec = vec![ serde_json::json!({ - "block": 1, "settled_tx": "0xabc0000000000000000000000000000000000000000000000000000000000001", + "block": 1, "tx_hash": "0xabc0000000000000000000000000000000000000000000000000000000000001", "venue": "relay", "solver": "1inch", "token_in": "0xaaa", "token_out": "0xbbb", + "status": "settled", "top": {"verdict": "win", "net_bps": 20.0, "improvement_usd": 12.0, "settled_value_usd": 1000.0} }), serde_json::json!({ - "block": 2, "settled_tx": "0xdef0000000000000000000000000000000000000000000000000000000000002", + "block": 2, "tx_hash": "0xdef0000000000000000000000000000000000000000000000000000000000002", "venue": "relay", "solver": "0x", "token_in": "0xccc", "token_out": "0xddd", + "status": "settled", "top": {"verdict": "unsolvable", "net_bps": null, "improvement_usd": null, "settled_value_usd": 50.0} }), ] diff --git a/tools/hindsight/src/report/mod.rs b/tools/hindsight/src/report/mod.rs index 174d80240..996dd5e5a 100644 --- a/tools/hindsight/src/report/mod.rs +++ b/tools/hindsight/src/report/mod.rs @@ -41,7 +41,13 @@ pub(crate) fn run(args: ReportArgs) -> anyhow::Result<()> { if all.is_empty() { bail!("no comparison records found in {}", args.comparisons_dir.display()); } - let records = filter_by_venue(all, &args.venue)?; + // Savings and win-rate only mean something for a trade that actually delivered an output — + // filter reverted trades out explicitly rather than relying on their absent `top` state. + let settled: Vec = all + .into_iter() + .filter(|record| record.status == "settled") + .collect(); + let records = filter_by_venue(settled, &args.venue)?; let report = aggregate::build(&records); let filter = (!args.venue.is_empty()).then(|| args.venue.join(", ")); let html = html::render(&report, filter.as_deref()); @@ -85,7 +91,8 @@ fn filter_by_venue(records: Vec, venues: &[String]) -> anyhow::Resul Ok(filtered) } -/// Read and parse every `.jsonl` file in `dir` into comparison records. Malformed lines are +/// Read and parse every `comparisons-*.jsonl` file in `dir` into comparison records — settled and +/// reverted trades alike; `run` filters to settled before aggregating. Malformed lines are /// counted and skipped rather than failing the whole report — a truncated final line from an /// interrupted run should not lose the rest of the data. fn read_comparisons(dir: &Path) -> anyhow::Result> { @@ -95,12 +102,15 @@ fn read_comparisons(dir: &Path) -> anyhow::Result> { .map(|entry| entry.path()) .filter(|path| { path.extension() - .is_some_and(|ext| ext == "jsonl") + .is_some_and(|ext| ext == "jsonl") && + path.file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| name.starts_with("comparisons-")) }) .collect(); files.sort(); if files.is_empty() { - bail!("no .jsonl files in {}", dir.display()); + bail!("no comparisons-*.jsonl files in {}", dir.display()); } let mut records = Vec::new(); @@ -142,8 +152,9 @@ mod tests { fn line(block: u64, verdict: &str) -> String { serde_json::json!({ - "block": block, "settled_tx": format!("0x{block:064x}"), + "block": block, "tx_hash": format!("0x{block:064x}"), "venue": "relay", "solver": "1inch", "token_in": "0xaaa", "token_out": "0xbbb", + "status": "settled", "top": {"verdict": verdict, "net_bps": 1.0, "improvement_usd": 1.0, "settled_value_usd": 1.0}, }) .to_string() @@ -171,6 +182,22 @@ mod tests { fs::remove_dir_all(&dir).unwrap(); } + #[test] + fn test_ignores_non_comparisons_jsonl_files() { + // Only `comparisons-*.jsonl` files are read; anything else in the directory (an old + // stream from a prior version, a stray file) is left alone rather than fed to the parser. + let dir = write_dir( + "with-other-files", + &[ + ("comparisons-2026-07-20.jsonl", &format!("{}\n", line(1, "win"))), + ("other-2026-07-20.jsonl", "{\"block\":1}\n"), + ], + ); + let records = read_comparisons(&dir).unwrap(); + assert_eq!(records.len(), 1); + fs::remove_dir_all(&dir).unwrap(); + } + #[test] fn test_empty_dir_is_an_error() { let dir = write_dir("empty", &[]); @@ -180,8 +207,8 @@ mod tests { fn venue_record(venue: &str) -> Comparison { serde_json::from_value(serde_json::json!({ - "block": 1, "settled_tx": "0x1", "venue": venue, "solver": "1inch", - "token_in": "0xaaa", "token_out": "0xbbb", + "block": 1, "tx_hash": "0x1", "venue": venue, "solver": "1inch", + "token_in": "0xaaa", "token_out": "0xbbb", "status": "settled", "top": {"verdict": "win", "net_bps": 1.0, "improvement_usd": 1.0, "settled_value_usd": 1.0}, })) .unwrap() diff --git a/tools/hindsight/src/report/record.rs b/tools/hindsight/src/report/record.rs index 2cb233ab0..4440b6134 100644 --- a/tools/hindsight/src/report/record.rs +++ b/tools/hindsight/src/report/record.rs @@ -9,16 +9,25 @@ use serde::Deserialize; /// One re-solved trade: the settled trade's identity plus Fynd's result at each block state. +/// Carries both settled and reverted trades (told apart by `status`) — the report filters to +/// `status == "settled"` before aggregating (see `report::run`), since Allium-style savings and +/// win-rate views only make sense for a trade that actually delivered an output. #[derive(Debug, Clone, Deserialize)] pub(crate) struct Comparison { pub block: u64, - pub settled_tx: String, + pub tx_hash: String, pub venue: String, pub solver: String, - pub token_in: String, - pub token_out: String, + /// `None` only for a reverted trade with no known terms — always present once filtered to + /// `status == "settled"`. + pub token_in: Option, + pub token_out: Option, + pub status: String, /// Optimistic state (N-1); the report's headline, matching the monitor's headline verdict. - pub top: State, + /// `None` only for a reverted trade with no known terms — a settled trade always carries one + /// (see `DecodedTrade`'s settled/reverted invariant), so every record this module aggregates + /// (already filtered to `status == "settled"`) has one. + pub top: Option, } /// Fynd's result at one block state. @@ -81,15 +90,16 @@ mod tests { tx_hash: TxHash::repeat_byte(0x42), block_number: 25_000_000, tx_index: 0, + status: crate::decoder::TradeStatus::Settled, venue: "relay".into(), solver: "1inch".into(), solver_source: AttributionSource::TraceMatch, decoder: "sender-netting", sender: Address::ZERO, - token_in: weth, - token_out: usdc, - amount_in: U256::from(1_000u64), - amount_out: U256::from(1_000_000_000u64), // settled 1000 USDC + token_in: Some(weth), + token_out: Some(usdc), + amount_in: Some(U256::from(1_000u64)), + amount_out: Some(U256::from(1_000_000_000u64)), // settled 1000 USDC venue_fee_in: None, venue_fee_out: None, settled_gas: None, @@ -110,9 +120,7 @@ mod tests { let range = build_range( &trade, &prices, - top, - Outcome::Unsolvable("x".into()), - &Outcome::Unsolvable("x".into()), + Some((top, Outcome::Unsolvable("x".into()), Outcome::Unsolvable("x".into()))), ); let mut buf = Vec::new(); @@ -123,10 +131,12 @@ mod tests { assert_eq!(record.block, 25_000_000); assert_eq!(record.venue, "relay"); assert_eq!(record.solver, "1inch"); - assert_eq!(record.top.verdict, "win"); - assert!(record.top.is_scored()); - assert!(record.top.net_bps.unwrap() > 0.0); - assert!((record.top.improvement_usd.unwrap() - 10.0).abs() < 1e-3); - assert_eq!(record.token_out, format!("{usdc:#x}")); + assert_eq!(record.status, "settled"); + let top = record.top.unwrap(); + assert_eq!(top.verdict, "win"); + assert!(top.is_scored()); + assert!(top.net_bps.unwrap() > 0.0); + assert!((top.improvement_usd.unwrap() - 10.0).abs() < 1e-3); + assert_eq!(record.token_out, Some(format!("{usdc:#x}"))); } } diff --git a/tools/hindsight/src/resolve/compare.rs b/tools/hindsight/src/resolve/compare.rs index 98deccae7..e098bb04f 100644 --- a/tools/hindsight/src/resolve/compare.rs +++ b/tools/hindsight/src/resolve/compare.rs @@ -14,7 +14,9 @@ fn to_biguint(amount: U256) -> BigUint { } /// Basis-point deltas of a Fynd quote against the settled amount (positive = Fynd better). -#[derive(Debug, Clone, Copy, PartialEq, Serialize)] +/// `Deltas::default()` (both `None`) when there is nothing settled to compare against — an +/// unsolved outcome, or a reverted trade. +#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize)] pub(crate) struct Deltas { /// `fynd amount_out` vs the settled amount, both gross of gas — always like-for-like, and /// the basis of the headline `Verdict`. @@ -26,10 +28,6 @@ pub(crate) struct Deltas { pub net_bps: Option, } -impl Deltas { - const NONE: Self = Self { raw_bps: None, net_bps: None }; -} - /// Slippage of the top-of-block route re-executed at back-of-block: how the route's output moved /// between quote time (N-1) and execution time (N). Positive = the route produced more than /// quoted — the surplus we would keep if we charged positive slippage. @@ -115,7 +113,7 @@ pub(crate) fn compare( settled_net_gas: U256, ) -> Deltas { let Outcome::Solved(solved) = outcome else { - return Deltas::NONE; + return Deltas::default(); }; Deltas { raw_bps: raw_bps_diff(&to_biguint(solved.amount_out), &to_biguint(settled_amount_out)), @@ -141,6 +139,29 @@ pub(crate) fn verdict(outcome: &Outcome, deltas: &Deltas) -> Verdict { } } +/// Judge a solved outcome against a floor (`min_amount_out`), independent of whether anything +/// settled — the same judgment a reverted trade needs against its on-chain floor, and a settled +/// trade can use too when a floor happens to be known (it cleared the floor by construction, but +/// the margin is still informative). `(None, None)` when the state was not solved or no floor is +/// known. +/// +/// Returns `(fillable, margin_bps)`: `fillable` is whether the quoted output would have cleared +/// the floor; `margin_bps` is the signed bps of `(quote - floor) / floor`, positive meaning the +/// quote cleared it with room to spare. `margin_bps` is `None` when the floor is zero (no +/// denominator to divide by) even though `fillable` still answers in that case, since any output +/// clears a zero floor. +pub(crate) fn floor_judgment( + outcome: &Outcome, + min_amount_out: Option, +) -> (Option, Option) { + let (Outcome::Solved(solved), Some(floor)) = (outcome, min_amount_out) else { + return (None, None); + }; + let fillable = solved.amount_out >= floor; + let margin_bps = raw_bps_diff(&to_biguint(solved.amount_out), &to_biguint(floor)); + (Some(fillable), margin_bps) +} + #[cfg(test)] mod tests { use super::*; @@ -191,7 +212,7 @@ mod tests { fn test_compare_unsolvable() { let (settled, net) = gross(10_000); let d = compare(&Outcome::Unsolvable("no route".into()), settled, net); - assert_eq!(d, Deltas::NONE); + assert_eq!(d, Deltas::default()); } #[test] @@ -286,4 +307,51 @@ mod tests { fn slippage_none_for_zero_quoted_output() { assert_eq!(slippage(&solved(0, 0), &solved(10, 10)), None); } + + #[test] + fn test_floor_judgment_exactly_at_floor() { + let (fillable, margin_bps) = + floor_judgment(&solved(10_000, 9_900), Some(U256::from(10_000u64))); + assert_eq!(fillable, Some(true)); + assert_eq!(margin_bps, Some(0.0)); + } + + #[test] + fn test_floor_judgment_above_and_below_floor() { + let (fillable, margin_bps) = + floor_judgment(&solved(10_100, 10_000), Some(U256::from(10_000u64))); + assert_eq!(fillable, Some(true)); + assert!(margin_bps.unwrap() > 0.0); + + let (fillable, margin_bps) = + floor_judgment(&solved(9_900, 9_800), Some(U256::from(10_000u64))); + assert_eq!(fillable, Some(false)); + assert!(margin_bps.unwrap() < 0.0); + } + + #[test] + fn test_floor_judgment_unsolved() { + let (fillable, margin_bps) = + floor_judgment(&Outcome::Unsolvable("no route".into()), Some(U256::from(10_000u64))); + assert_eq!(fillable, None); + assert_eq!(margin_bps, None); + } + + #[test] + fn test_floor_judgment_no_floor_known() { + // A solved outcome with no floor to judge against (e.g. a settled trade whose calldata + // declared no min_amount_out) — neither field applies. + let (fillable, margin_bps) = floor_judgment(&solved(10_000, 9_900), None); + assert_eq!(fillable, None); + assert_eq!(margin_bps, None); + } + + #[test] + fn test_floor_judgment_zero_floor() { + // Any non-negative quote clears a zero floor: fillable is Some(true), but there is no + // denominator to divide by for the margin. + let (fillable, margin_bps) = floor_judgment(&solved(10_000, 9_900), Some(U256::ZERO)); + assert_eq!(fillable, Some(true)); + assert_eq!(margin_bps, None); + } } diff --git a/tools/hindsight/src/resolve/jsonl.rs b/tools/hindsight/src/resolve/jsonl.rs index 1a1e53383..957134734 100644 --- a/tools/hindsight/src/resolve/jsonl.rs +++ b/tools/hindsight/src/resolve/jsonl.rs @@ -16,13 +16,20 @@ use fynd_core::types::{OrderQuote, Swap, Transaction}; use tracing::{info, warn}; use crate::{ + decoder::{RevertCause, TradeStatus}, resolve::{render_route, Outcome, RangeComparison, StateResult}, + telemetry::revert_cause_label, usd::Prices, }; -/// Append-only comparisons writer that rotates to a new file at each UTC day boundary — -/// `comparisons-YYYY-MM-DD.jsonl` inside its directory — so an external sync job (e.g. an S3 -/// upload `CronJob`) ships closed daily files instead of re-shipping one ever-growing one. +/// The rotating file's name prefix — every trade, settled or reverted, writes here (see +/// `RangeComparison`'s `status` field); there is only one stream. +const PREFIX: &str = "comparisons"; + +/// Append-only writer that rotates to a new file at each UTC day boundary — `comparisons-YYYY-MM +/// -DD.jsonl` inside its directory — so an external sync job (e.g. an S3 upload `CronJob`) ships +/// closed daily files instead of re-shipping one ever-growing one. `monitor` opens one of these in +/// its `--comparisons-dir`. pub(crate) struct RotatingWriter { dir: PathBuf, date: String, @@ -30,7 +37,8 @@ pub(crate) struct RotatingWriter { } impl RotatingWriter { - /// Open today's file inside `dir` for appending, creating the directory if needed. + /// Open today's `comparisons-*.jsonl` file inside `dir` for appending, creating the directory + /// if needed. pub(crate) fn open(dir: impl Into) -> anyhow::Result { let dir = dir.into(); std::fs::create_dir_all(&dir) @@ -59,23 +67,23 @@ impl RotatingWriter { return; } if let Err(e) = self.writer.flush() { - warn!(error = %e, "failed to flush comparisons file before rotation"); + warn!(error = %e, "failed to flush jsonl file before rotation"); } match open_dated(&self.dir, &date) { Ok(writer) => { - info!(path = %dated_path(&self.dir, &date).display(), "rotated comparisons file"); + info!(path = %dated_path(&self.dir, &date).display(), "rotated jsonl file"); self.writer = writer; self.date = date; } Err(e) => { - warn!(error = %e, "failed to rotate comparisons file; keeping the previous day's"); + warn!(error = %e, "failed to rotate jsonl file; keeping the previous day's"); } } } } fn dated_path(dir: &Path, date: &str) -> PathBuf { - dir.join(format!("comparisons-{date}.jsonl")) + dir.join(format!("{PREFIX}-{date}.jsonl")) } fn open_dated(dir: &Path, date: &str) -> anyhow::Result> { @@ -139,61 +147,87 @@ pub(crate) fn write_comparisons( } } -/// Build the JSON record for one re-solved trade: block, settled tx, decoded amounts, a `top` -/// and `back` state (each with its verdict, bps, USD delta, and slim route/calldata or unsolvable -/// reason), and the top route's slippage between the two states. Top is valued at N-1 prices, -/// back (and the slippage) at N prices, matching the state each was produced at. +/// A record's flat status fields: `status` ("settled"/"reverted"), and — for a revert — a bounded +/// `cause` label (matching `telemetry::revert_cause_label`) plus its free-text `cause_detail`. +/// Kept flat (not the nested `{"kind":...}` shape `RevertCause` itself serializes to) so a jq +/// pass can filter on `status`/`cause` directly, matching the old `reverts-*.jsonl` ergonomics +/// now that both streams are one. +fn status_fields(status: &TradeStatus) -> (&'static str, Option<&'static str>, Option<&str>) { + match status { + TradeStatus::Settled => ("settled", None, None), + TradeStatus::Reverted { cause } => { + let detail = match cause { + RevertCause::Other(detail) => Some(detail.as_str()), + RevertCause::SlippageFloor | RevertCause::OutOfGas => None, + }; + ("reverted", Some(revert_cause_label(cause)), detail) + } + } +} + +/// Build the JSON record for one re-solved trade — settled or reverted, told apart by `status`: +/// block, tx, decoded amounts, a `top` and `back` state (each with its verdict, bps, fillable/ +/// margin judgment, and slim route/calldata or unsolvable reason), and the top route's slippage +/// between the two states. `top`/`back`/`slippage` are `null` when the trade's terms were +/// unknown — there was nothing to solve. Top is valued at N-1 prices, back (and the slippage) at +/// N prices, matching the state each was produced at. fn comparison_record( range: &RangeComparison, prices_top: &Prices, prices_back: &Prices, ) -> serde_json::Value { + let (status, cause, cause_detail) = status_fields(&range.status); // Signed in both directions; the positive records are the "revenue if we charged positive // slippage" view, filtered downstream. let slippage = range.slippage.map(|slippage| { - serde_json::json!({ - "bps": slippage.bps, - "usd": prices_back.savings_usd( - range.token_out, + let usd = range.token_out.and_then(|token_out| { + prices_back.savings_usd( + token_out, slippage.reexecuted_amount_out, slippage.quoted_amount_out, - ), - }) + ) + }); + serde_json::json!({ "bps": slippage.bps, "usd": usd }) }); serde_json::json!({ "block": range.block_number, "tx_index": range.tx_index, - "settled_tx": range.tx_hash, + "tx_hash": range.tx_hash, + "status": status, + "cause": cause, + "cause_detail": cause_detail, "venue": range.venue, "solver": range.solver, "solver_source": range.solver_source, "decoder": range.decoder, - "token_in": format!("{:#x}", range.token_in), - "token_out": format!("{:#x}", range.token_out), - "amount_in": range.amount_in.to_string(), - "settled_amount_out": range.settled_amount_out.to_string(), - "settled_amount_out_net_gas": range.settled_amount_out_net_gas.to_string(), + "sender": format!("{:#x}", range.sender), + "token_in": range.token_in.map(|token| format!("{token:#x}")), + "token_out": range.token_out.map(|token| format!("{token:#x}")), + "amount_in": range.amount_in.map(|amount| amount.to_string()), + "settled_amount_out": range.settled_amount_out.map(|amount| amount.to_string()), + "settled_amount_out_net_gas": range.settled_amount_out_net_gas.map(|amount| amount.to_string()), "settled_gas_cost": range.settled_gas.map(|gas| gas.to_string()), "min_amount_out": range.min_amount_out.map(|amount| amount.to_string()), "quoted_amount_out": range.declared_quote.map(|amount| amount.to_string()), "quote_timestamp": range.quote_timestamp, "sandwich": range.sandwich, "slippage": slippage, - "top": state_record(&range.top, range, prices_top), - "back": state_record(&range.back, range, prices_back), + "top": range.top.as_ref().map(|top| state_record(top, range, prices_top)), + "back": range.back.as_ref().map(|back| state_record(back, range, prices_back)), }) } -/// JSON for one block-state of an improvement: verdict, bps, Fynd amounts, the USD improvement -/// (gross Fynd output minus the gross settled output, valued at `prices` — the same basis as the -/// headline verdict), the winning route's algorithm and rendered path, and the slim quote. -/// `settled_value_usd` stays gross — it is the trade's notional, not a comparison. +/// JSON for one block-state of a trade: verdict, bps, Fynd amounts, the USD improvement (gross +/// Fynd output minus the gross settled output, valued at `prices` — the same basis as the +/// headline verdict), the winning route's algorithm and rendered path, the slim quote, and the +/// fillable/margin judgment against `min_amount_out` (present whenever a floor is known, settled +/// or reverted). `settled_value_usd`/`improvement_usd` are `null` for a reverted trade — nothing +/// settled to value or improve on. fn state_record( state: &StateResult, range: &RangeComparison, prices: &Prices, ) -> serde_json::Value { - let token_out = range.token_out; let solved = match &state.outcome { Outcome::Solved(solved) => Some(solved), Outcome::Partial(_) | Outcome::Unsolvable(_) => None, @@ -204,13 +238,25 @@ fn state_record( Outcome::Unsolvable(reason) | Outcome::Partial(reason) => Some(reason.as_str()), Outcome::Solved(_) => None, }; - let improvement_usd = - solved.and_then(|s| prices.savings_usd(token_out, s.amount_out, range.settled_amount_out)); - let fynd_value_usd = solved.and_then(|s| prices.value_usd(token_out, s.amount_out)); + let token_and_settled = range + .token_out + .zip(range.settled_amount_out); + let improvement_usd = solved.and_then(|s| { + token_and_settled + .and_then(|(token_out, settled)| prices.savings_usd(token_out, s.amount_out, settled)) + }); + let fynd_value_usd = range + .token_out + .zip(solved) + .and_then(|(token_out, s)| prices.value_usd(token_out, s.amount_out)); + let settled_value_usd = + token_and_settled.and_then(|(token_out, settled)| prices.value_usd(token_out, settled)); serde_json::json!({ "verdict": state.verdict, "net_bps": state.deltas.net_bps, "raw_bps": state.deltas.raw_bps, + "fillable": state.fillable, + "margin_bps": state.margin_bps, "fynd_amount_out": solved.map(|s| s.amount_out.to_string()), "fynd_amount_out_net_gas": solved.map(|s| s.amount_out_net_gas.to_string()), "gas_estimate": solved.map(|s| s.gas_estimate.to_string()), @@ -220,7 +266,7 @@ fn state_record( "route": solved.map(|s| s.solved_route.as_deref().map(render_route).unwrap_or_default()), "improvement_usd": improvement_usd, "fynd_value_usd": fynd_value_usd, - "settled_value_usd": prices.value_usd(token_out, range.settled_amount_out), + "settled_value_usd": settled_value_usd, "unsolvable_reason": unsolvable_reason, "quote": solved .and_then(|s| s.quote_json.as_deref()) @@ -331,15 +377,16 @@ mod tests { tx_hash: TxHash::default(), block_number: 25_480_207, tx_index: 3, + status: TradeStatus::Settled, venue: "relay".into(), solver: "kyberswap".into(), solver_source: AttributionSource::TraceMatch, decoder: "sender-netting", - sender: Address::ZERO, - token_in: Address::ZERO, - token_out: Address::repeat_byte(0x22), - amount_in: U256::from(1_000u64), - amount_out: U256::from(69_996_280_564u64), + sender: Address::repeat_byte(0x77), + token_in: Some(Address::ZERO), + token_out: Some(Address::repeat_byte(0x22)), + amount_in: Some(U256::from(1_000u64)), + amount_out: Some(U256::from(69_996_280_564u64)), venue_fee_in: None, venue_fee_out: None, settled_gas: None, @@ -351,12 +398,16 @@ mod tests { let range = build_range( &trade, &empty_prices(), - Outcome::Unsolvable("x".into()), - Outcome::Unsolvable("x".into()), - &Outcome::Unsolvable("x".into()), + Some(( + Outcome::Unsolvable("x".into()), + Outcome::Unsolvable("x".into()), + Outcome::Unsolvable("x".into()), + )), ); let rec = comparison_record(&range, &empty_prices(), &empty_prices()); assert_eq!(rec.pointer("/tx_index").unwrap(), 3); + assert_eq!(rec.pointer("/sender").unwrap(), &format!("{:#x}", Address::repeat_byte(0x77))); + assert_eq!(rec.pointer("/status").unwrap(), "settled"); assert_eq!( rec.pointer("/quoted_amount_out") .unwrap(), @@ -409,15 +460,16 @@ mod tests { tx_hash: TxHash::default(), block_number: 25_000_000, tx_index: 0, + status: TradeStatus::Settled, venue: "relay".into(), solver: "1inch".into(), solver_source: AttributionSource::TraceMatch, decoder: "sender-netting", sender: Address::ZERO, - token_in: weth, - token_out: usdc, - amount_in: U256::from(1_000u64), - amount_out: U256::from(1_000_000_000u64), // settled 1000 USDC + token_in: Some(weth), + token_out: Some(usdc), + amount_in: Some(U256::from(1_000u64)), + amount_out: Some(U256::from(1_000_000_000u64)), // settled 1000 USDC venue_fee_in: None, venue_fee_out: None, settled_gas: None, @@ -456,7 +508,7 @@ mod tests { quote_json: quote, solved_route: Some(solved_route), }); - let range = build_range(&trade, &prices, top, back.clone(), &back); + let range = build_range(&trade, &prices, Some((top, back.clone(), back))); comparison_record(&range, &prices, &prices) } @@ -533,15 +585,16 @@ mod tests { tx_hash: TxHash::default(), block_number: 25_000_000, tx_index: 0, + status: TradeStatus::Settled, venue: "relay".into(), solver: "1inch".into(), solver_source: AttributionSource::TraceMatch, decoder: "sender-netting", sender: Address::ZERO, - token_in: Address::repeat_byte(0x11), - token_out: Address::repeat_byte(0x22), - amount_in: U256::from(1_000u64), - amount_out: U256::from(1_000u64), + token_in: Some(Address::repeat_byte(0x11)), + token_out: Some(Address::repeat_byte(0x22)), + amount_in: Some(U256::from(1_000u64)), + amount_out: Some(U256::from(1_000u64)), venue_fee_in: None, venue_fee_out: None, settled_gas: None, @@ -554,9 +607,11 @@ mod tests { let range = build_range( &trade, &empty_prices(), - Outcome::Unsolvable("missing token in Tycho".into()), - Outcome::Unsolvable("missing token in Tycho".into()), - &Outcome::Unsolvable("no top-of-block route to re-execute".into()), + Some(( + Outcome::Unsolvable("missing token in Tycho".into()), + Outcome::Unsolvable("missing token in Tycho".into()), + Outcome::Unsolvable("no top-of-block route to re-execute".into()), + )), ); let rec = comparison_record(&range, &empty_prices(), &empty_prices()); assert_eq!(rec.pointer("/top/verdict").unwrap(), "unsolvable"); @@ -592,15 +647,16 @@ mod tests { tx_hash: TxHash::default(), block_number: 25_000_000, tx_index: 42, + status: TradeStatus::Settled, venue: "relay".into(), solver: "1inch".into(), solver_source: AttributionSource::TraceMatch, decoder: "sender-netting", sender: Address::ZERO, - token_in: Address::repeat_byte(0x11), - token_out: Address::repeat_byte(0x22), - amount_in: U256::from(1_000u64), - amount_out: U256::from(1_000u64), + token_in: Some(Address::repeat_byte(0x11)), + token_out: Some(Address::repeat_byte(0x22)), + amount_in: Some(U256::from(1_000u64)), + amount_out: Some(U256::from(1_000u64)), venue_fee_in: None, venue_fee_out: None, settled_gas: None, @@ -627,8 +683,11 @@ mod tests { solved_route: None, }) }; - let range = - build_range(&trade, &empty_prices(), solved(1_100), solved(1_050), &solved(1_050)); + let range = build_range( + &trade, + &empty_prices(), + Some((solved(1_100), solved(1_050), solved(1_050))), + ); let rec = comparison_record(&range, &empty_prices(), &empty_prices()); assert_eq!(rec.pointer("/tx_index").unwrap(), 42); @@ -645,4 +704,148 @@ mod tests { &format!("{:#x}", Address::repeat_byte(0x44)) ); } + + fn revert_solved(amount_out: u64) -> Outcome { + Outcome::Solved(SolvedAmount { + amount_out: U256::from(amount_out), + amount_out_net_gas: U256::from(amount_out), + gas_estimate: U256::from(21_000), + algorithm: String::new(), + quote_json: None, + solved_route: None, + }) + } + + fn reverted_trade(cause: RevertCause) -> DecodedTrade { + DecodedTrade { + tx_hash: TxHash::repeat_byte(0x55), + block_number: 25_000_000, + tx_index: 9, + status: TradeStatus::Reverted { cause }, + venue: "relay".into(), + solver: "fly".into(), + solver_source: AttributionSource::TraceMatch, + decoder: "reverted", + sender: Address::repeat_byte(0x99), + token_in: Some(Address::repeat_byte(0x11)), + token_out: Some(Address::repeat_byte(0x22)), + amount_in: Some(U256::from(1_000u64)), + amount_out: None, + venue_fee_in: None, + venue_fee_out: None, + settled_gas: None, + min_amount_out: Some(U256::from(10_000u64)), + declared_quote: None, + quote_timestamp: None, + sandwich: None, + } + } + + #[test] + fn test_reverted_record_slippage_floor_fillable_at_back() { + let trade = reverted_trade(RevertCause::SlippageFloor); + let range = build_range( + &trade, + &empty_prices(), + Some((revert_solved(9_800), revert_solved(10_200), revert_solved(10_200))), + ); + let rec = comparison_record(&range, &empty_prices(), &empty_prices()); + + assert_eq!(rec.pointer("/tx_index").unwrap(), 9); + assert_eq!(rec.pointer("/tx_hash").unwrap(), &TxHash::repeat_byte(0x55).to_string()); + // A reverted trade still records who sent it — the tx sender, since there is no netted + // flow to draw a different tracked party from. + assert_eq!(rec.pointer("/sender").unwrap(), &format!("{:#x}", Address::repeat_byte(0x99))); + assert_eq!(rec.pointer("/status").unwrap(), "reverted"); + assert_eq!(rec.pointer("/cause").unwrap(), "slippage_floor"); + assert!(rec + .pointer("/cause_detail") + .unwrap() + .is_null()); + assert_eq!(rec.pointer("/min_amount_out").unwrap(), "10000"); + // Nothing settled: settled-only fields are absent. + assert!(rec + .pointer("/settled_amount_out") + .unwrap() + .is_null()); + assert_eq!(rec.pointer("/top/fillable").unwrap(), false); + assert_eq!(rec.pointer("/back/fillable").unwrap(), true); + assert!( + rec.pointer("/back/margin_bps") + .unwrap() + .as_f64() + .unwrap() > + 0.0 + ); + } + + #[test] + fn test_reverted_record_other_cause_carries_detail() { + let trade = reverted_trade(RevertCause::Other("execution reverted".to_string())); + let range = build_range( + &trade, + &empty_prices(), + Some(( + Outcome::Unsolvable("no route".into()), + Outcome::Unsolvable("no route".into()), + Outcome::Unsolvable("no route".into()), + )), + ); + let rec = comparison_record(&range, &empty_prices(), &empty_prices()); + + assert_eq!(rec.pointer("/cause").unwrap(), "other"); + assert_eq!(rec.pointer("/cause_detail").unwrap(), "execution reverted"); + assert!(rec + .pointer("/top/fillable") + .unwrap() + .is_null()); + } + + #[test] + fn test_reverted_trade_with_unknown_terms_has_no_top_or_back() { + let mut trade = reverted_trade(RevertCause::Other("unknown revert".to_string())); + trade.token_in = None; + trade.token_out = None; + trade.amount_in = None; + trade.min_amount_out = None; + let range = build_range(&trade, &empty_prices(), None); + let rec = comparison_record(&range, &empty_prices(), &empty_prices()); + + assert_eq!(rec.pointer("/status").unwrap(), "reverted"); + assert!(rec.pointer("/top").unwrap().is_null()); + assert!(rec.pointer("/back").unwrap().is_null()); + assert!(rec + .pointer("/token_in") + .unwrap() + .is_null()); + } + + #[test] + fn test_write_comparisons_appends_lines_for_settled_and_reverted() { + let settled_range = build_range( + &reverted_trade(RevertCause::OutOfGas), + &empty_prices(), + Some((Outcome::Unsolvable("x".into()), revert_solved(10_000), revert_solved(10_000))), + ); + let mut buf: Vec = Vec::new(); + write_comparisons( + &mut buf, + std::slice::from_ref(&settled_range), + &empty_prices(), + &empty_prices(), + ); + write_comparisons( + &mut buf, + std::slice::from_ref(&settled_range), + &empty_prices(), + &empty_prices(), + ); + + let text = String::from_utf8(buf).unwrap(); + assert_eq!(text.lines().count(), 2); + for line in text.lines() { + let value: serde_json::Value = serde_json::from_str(line).unwrap(); + assert_eq!(value["cause"], "out_of_gas"); + } + } } diff --git a/tools/hindsight/src/resolve/mod.rs b/tools/hindsight/src/resolve/mod.rs index eaace080b..413b70380 100644 --- a/tools/hindsight/src/resolve/mod.rs +++ b/tools/hindsight/src/resolve/mod.rs @@ -22,7 +22,7 @@ use serde::Serialize; use tycho_simulation::tycho_common::models::Address as CoreAddress; use crate::{ - decoder::{AttributionSource, DecodedTrade, SandwichEvidence}, + decoder::{AttributionSource, DecodedTrade, SandwichEvidence, TradeStatus}, usd::Prices, }; @@ -207,39 +207,70 @@ pub(crate) enum Outcome { #[derive(Debug, Clone, Serialize)] pub(crate) struct StateResult { pub outcome: Outcome, + /// Fynd vs the settled output. `Deltas::default()` (all `None`) when nothing settled to + /// compare against — a reverted trade. pub deltas: Deltas, pub verdict: Verdict, + /// Whether the quote cleared the trade's on-chain floor (`min_amount_out`), when one is + /// known. Computed for both settled and reverted trades — a settled trade cleared its floor + /// by construction, but the margin is still informative. `None` when the state was not + /// solved or no floor is known. + pub fillable: Option, + pub margin_bps: Option, } impl StateResult { - fn new(outcome: Outcome, settled_amount_out: U256, settled_net_gas: U256) -> Self { - let outcome = compare::served(outcome, settled_amount_out); - let deltas = compare::compare(&outcome, settled_amount_out, settled_net_gas); + /// `settled` is `Some((gross, net_gas))` for a settled trade, `None` for a reverted one — + /// there is nothing settled to compare gross output against, so `deltas`/`verdict` fall back + /// to their unsolved-comparison values (`Deltas::default()`, `Verdict::Unsolvable`) even when + /// `outcome` itself solved; `fillable`/`margin_bps` are unaffected, since they judge the + /// outcome against `min_amount_out`, not against a settled amount. + fn new(outcome: Outcome, settled: Option<(U256, U256)>, min_amount_out: Option) -> Self { + let outcome = match settled { + Some((gross, _)) => compare::served(outcome, gross), + None => outcome, + }; + let deltas = match settled { + Some((gross, net)) => compare::compare(&outcome, gross, net), + None => Deltas::default(), + }; let verdict = compare::verdict(&outcome, &deltas); - Self { outcome, deltas, verdict } + let (fillable, margin_bps) = compare::floor_judgment(&outcome, min_amount_out); + Self { outcome, deltas, verdict, fillable, margin_bps } } } -/// A trade re-solved at both block states, presented as a range. +/// A trade re-solved at both block states, presented as a range — settled or reverted, told +/// apart by `status`. A reverted trade's settled-only fields (`settled_amount_out` and friends) +/// are simply absent; everything else about the record has the same shape. #[derive(Debug, Clone, Serialize)] pub(crate) struct RangeComparison { pub tx_hash: TxHash, pub block_number: u64, pub tx_index: u64, + #[serde(flatten)] + pub status: TradeStatus, pub venue: String, pub solver: String, /// The evidence tier the solver label came from (from the decoder). pub solver_source: AttributionSource, - /// Which decoder recovered the settled trade. + /// Which decoder recovered the trade. pub decoder: &'static str, - pub token_in: Address, - pub token_out: Address, - pub amount_in: U256, - pub settled_amount_out: U256, + /// The trader, from the decoder: the netted flow's tracked party for a settled trade, or the + /// transaction sender for a reverted one (there is no netted flow to draw a different party + /// from). Lets a venue-filler fill (e.g. Relay's rotating filler) be segmented by its actual + /// trader without an on-chain lookup. + pub sender: Address, + /// `None` only for a reverted trade whose solver frame's calldata did not parse. + pub token_in: Option
, + pub token_out: Option
, + pub amount_in: Option, + /// `None` for a reverted trade: nothing was delivered. + pub settled_amount_out: Option, /// Settled output after the gas the trader paid for the route, in `token_out` units. Equals /// `settled_amount_out` when that gas is unknown, was paid by someone else, or the output - /// token is unpriced. - pub settled_amount_out_net_gas: U256, + /// token is unpriced. `None` for a reverted trade. + pub settled_amount_out_net_gas: Option, /// Wei cost of the settled route's gas, when the trader paid it (from the decoder). pub settled_gas: Option, /// The on-chain enforced floor declared in the settling solver frame's own calldata (from @@ -251,17 +282,18 @@ pub(crate) struct RangeComparison { /// Unix timestamp of `declared_quote`, when the calldata carries one. pub quote_timestamp: Option, /// Evidence that a front-run and a back-run bracketed this trade (from the decoder). `None` - /// when no bracket pair was found. + /// when no bracket pair was found, or the trade reverted. pub sandwich: Option, - /// Optimistic: solved at state N-1, before the block's swaps moved the pools. - pub top: StateResult, + /// Optimistic: solved at state N-1, before the block's swaps moved the pools. `None` when + /// the trade's terms were unknown — there was nothing to solve. + pub top: Option, /// Pessimistic: solved fresh at state N, after the block's swaps moved the pools — what - /// routing at the block's end state would deliver. - pub back: StateResult, - /// Headline verdict — top-of-block (the optimistic default). - pub verdict: Verdict, + /// routing at the block's end state would deliver. `None` alongside `top`. + pub back: Option, + /// Headline verdict — top-of-block (the optimistic default). `None` alongside `top`. + pub verdict: Option, /// Slippage of the top route between quote time (N-1) and re-execution (N). `None` when the - /// top was unsolved or the re-execution failed. + /// top was unsolved, the re-execution failed, or the trade's terms were unknown. pub slippage: Option, } @@ -280,14 +312,16 @@ pub(crate) trait SteppingSolver { async fn reexecute(&self, top: &SolvedAmount) -> Outcome; } -/// Build a `RangeComparison` from a trade's three outcomes: the top-of-block solve, the fresh -/// back-of-block solve, and the top route's re-execution at back-of-block (which feeds only the -/// `slippage` field). +/// Build a `RangeComparison` from a trade and, when its terms were known, the three outcomes +/// solving it produced: the top-of-block solve, the fresh back-of-block solve, and the top +/// route's re-execution at back-of-block (which feeds only the `slippage` field). `None` when the +/// trade's terms were unknown — there was nothing to solve, so `top`/`back`/`verdict`/`slippage` +/// on the returned comparison are all `None` too, but the trade is still recorded. /// /// When the decoder isolated the gas the trader paid for the settled route, its cost is converted /// into `token_out` units at the `prices` snapshot (top-of-block — a fine approximation for a gas /// deduction) and subtracted from the settled output, so both sides of the net comparison carry -/// their own gas. +/// their own gas. Reverted trades have no settled output to deduct from — `settled` is `None`. /// /// When the decoder flagged the trade as sandwiched, each *solved* state's verdict becomes /// `Verdict::Sandwiched`: its win or loss measures the MEV that moved the settled output, not @@ -298,40 +332,55 @@ pub(crate) trait SteppingSolver { pub(crate) fn build_range( trade: &DecodedTrade, prices: &Prices, - top: Outcome, - back: Outcome, - reexecuted: &Outcome, + solved: Option<(Outcome, Outcome, Outcome)>, ) -> RangeComparison { - let settled_net_gas = trade - .settled_gas - .and_then(|gas| prices.gas_in_token(gas, trade.token_out)) - .map_or(trade.amount_out, |gas_out| trade.amount_out.saturating_sub(gas_out)); - // Computed from the raw outcomes: the coverage-miss reclassification below discards the - // solved amounts the slippage is measured from. - let slippage = compare::slippage(&top, reexecuted); - let mut top = StateResult::new(top, trade.amount_out, settled_net_gas); - let mut back = StateResult::new(back, trade.amount_out, settled_net_gas); - if trade.sandwich.is_some() { - for state in [&mut top, &mut back] { - if let Outcome::Solved(_) = state.outcome { - state.verdict = Verdict::Sandwiched; + let settled = trade.amount_out.map(|gross| { + let net_gas = trade + .settled_gas + .and_then(|gas| { + trade + .token_out + .and_then(|token| prices.gas_in_token(gas, token)) + }) + .map_or(gross, |gas_out| gross.saturating_sub(gas_out)); + (gross, net_gas) + }); + + let (top, back, verdict, slippage) = match solved { + Some((top_outcome, back_outcome, reexecuted)) => { + // Computed from the raw outcomes: the coverage-miss reclassification below discards + // the solved amounts the slippage is measured from. + let slippage = compare::slippage(&top_outcome, &reexecuted); + let mut top = StateResult::new(top_outcome, settled, trade.min_amount_out); + let mut back = StateResult::new(back_outcome, settled, trade.min_amount_out); + if trade.sandwich.is_some() { + for state in [&mut top, &mut back] { + if let Outcome::Solved(_) = state.outcome { + state.verdict = Verdict::Sandwiched; + } + } } + let verdict = top.verdict; + (Some(top), Some(back), Some(verdict), slippage) } - } - let verdict = top.verdict; + None => (None, None, None, None), + }; + RangeComparison { tx_hash: trade.tx_hash, block_number: trade.block_number, tx_index: trade.tx_index, + status: trade.status.clone(), venue: trade.venue.clone(), solver: trade.solver.clone(), solver_source: trade.solver_source, decoder: trade.decoder, + sender: trade.sender, token_in: trade.token_in, token_out: trade.token_out, amount_in: trade.amount_in, - settled_amount_out: trade.amount_out, - settled_amount_out_net_gas: settled_net_gas, + settled_amount_out: settled.map(|(gross, _)| gross), + settled_amount_out_net_gas: settled.map(|(_, net)| net), settled_gas: trade.settled_gas, min_amount_out: trade.min_amount_out, declared_quote: trade.declared_quote, @@ -345,10 +394,15 @@ pub(crate) fn build_range( } /// Re-solve every trade in a held block at top-of-block, advance to back-of-block, then measure -/// each trade twice at the new state: re-execute its top route against the pools as the block -/// left them (for the slippage), and solve it fresh (for the `back` comparison). Solving all -/// trades at one state before advancing keeps each state's reads consistent and steps the chain -/// only once per block. +/// each again at the new state: its top route is re-executed against the pools as the block left +/// them (for the slippage) and it is solved fresh (for the `back` comparison). Solving everything +/// at one state before advancing keeps each state's reads consistent and steps the chain only +/// once per block. Settled and reverted trades go through the same wave — a reverted trade whose +/// solver calldata parsed into a `SwapIntent` is solved exactly like a settled one, judged against +/// its `min_amount_out` floor instead of a settled amount. +/// +/// A trade with unknown terms (a reverted trade whose solver calldata did not parse) is recorded +/// but not solved — there is nothing to feed the solver. pub(crate) async fn resolve_block_range( solver: &S, trades: &[DecodedTrade], @@ -356,28 +410,41 @@ pub(crate) async fn resolve_block_range( ) -> anyhow::Result> { let mut tops = Vec::with_capacity(trades.len()); for trade in trades { - tops.push( - solver - .solve(trade.token_in, trade.token_out, trade.amount_in) - .await, - ); + let top = match trade.terms() { + Some((token_in, token_out, amount_in)) => Some( + solver + .solve(token_in, token_out, amount_in) + .await, + ), + None => None, + }; + tops.push(top); } solver.advance().await?; let mut ranges = Vec::with_capacity(trades.len()); for (trade, top) in trades.iter().zip(tops) { + let Some(top) = top else { + ranges.push(build_range(trade, prices, None)); + continue; + }; let reexecuted = match &top { Outcome::Solved(solved) => solver.reexecute(solved).await, Outcome::Partial(_) | Outcome::Unsolvable(_) => { Outcome::Unsolvable("no top-of-block route to re-execute".to_string()) } }; + // `top` was solved from `trade.terms()`, so the terms are known here too. + let (token_in, token_out, amount_in) = trade + .terms() + .expect("a solved top implies known terms"); let back = solver - .solve(trade.token_in, trade.token_out, trade.amount_in) + .solve(token_in, token_out, amount_in) .await; - ranges.push(build_range(trade, prices, top, back, &reexecuted)); + ranges.push(build_range(trade, prices, Some((top, back, reexecuted)))); } + Ok(ranges) } @@ -397,15 +464,71 @@ mod tests { tx_hash: TxHash::default(), block_number: 21_000_000, tx_index: 0, + status: TradeStatus::Settled, venue: "relay".into(), solver: "tycho".into(), solver_source: AttributionSource::TraceMatch, decoder: "sender-netting", sender: Address::ZERO, - token_in: Address::repeat_byte(0x11), - token_out: Address::repeat_byte(0x22), - amount_in: U256::from(1_000u64), - amount_out: U256::from(settled), + token_in: Some(Address::repeat_byte(0x11)), + token_out: Some(Address::repeat_byte(0x22)), + amount_in: Some(U256::from(1_000u64)), + amount_out: Some(U256::from(settled)), + venue_fee_in: None, + venue_fee_out: None, + settled_gas: None, + min_amount_out: None, + declared_quote: None, + quote_timestamp: None, + sandwich: None, + } + } + + /// A reverted trade whose solver calldata parsed into a `SwapIntent` — carries terms and a + /// floor (`min_amount_out`) but no settled output. + fn reverted_trade(cause: crate::decoder::RevertCause, min_amount_out: u64) -> DecodedTrade { + DecodedTrade { + tx_hash: TxHash::repeat_byte(0x02), + block_number: 21_000_000, + tx_index: 1, + status: TradeStatus::Reverted { cause }, + venue: "relay".into(), + solver: "fly".into(), + solver_source: AttributionSource::TraceMatch, + decoder: "reverted", + sender: Address::ZERO, + token_in: Some(Address::repeat_byte(0x11)), + token_out: Some(Address::repeat_byte(0x22)), + amount_in: Some(U256::from(1_000u64)), + amount_out: None, + venue_fee_in: None, + venue_fee_out: None, + settled_gas: None, + min_amount_out: Some(U256::from(min_amount_out)), + declared_quote: None, + quote_timestamp: None, + sandwich: None, + } + } + + /// A reverted trade whose solver calldata did not parse — no terms, nothing to solve. + fn reverted_trade_without_terms() -> DecodedTrade { + DecodedTrade { + tx_hash: TxHash::repeat_byte(0x03), + block_number: 21_000_000, + tx_index: 2, + status: TradeStatus::Reverted { + cause: crate::decoder::RevertCause::Other("unknown revert".to_string()), + }, + venue: "relay".into(), + solver: "relay".into(), + solver_source: AttributionSource::Fallback, + decoder: "reverted", + sender: Address::ZERO, + token_in: None, + token_out: None, + amount_in: None, + amount_out: None, venue_fee_in: None, venue_fee_out: None, settled_gas: None, @@ -470,12 +593,12 @@ mod tests { let range = build_range( &trade(10_000), &empty_prices(), - solved(10_200, 10_100), - solved(10_010, 9_990), - &solved(10_010, 9_990), + Some((solved(10_200, 10_100), solved(10_010, 9_990), solved(10_010, 9_990))), ); - assert_eq!(range.verdict, Verdict::Win); // top is the headline - assert!(range.top.deltas.raw_bps.unwrap() > range.back.deltas.raw_bps.unwrap()); + assert_eq!(range.verdict, Some(Verdict::Win)); // top is the headline + let top = range.top.unwrap(); + let back = range.back.unwrap(); + assert!(top.deltas.raw_bps.unwrap() > back.deltas.raw_bps.unwrap()); } #[test] @@ -484,13 +607,12 @@ mod tests { let range = build_range( &trade(10_000), &empty_prices(), - solved(1_000, 990), - solved(1_000, 990), - &solved(1_000, 990), + Some((solved(1_000, 990), solved(1_000, 990), solved(1_000, 990))), ); - assert_eq!(range.verdict, Verdict::CoverageMiss); - assert_eq!(range.top.deltas, Deltas { raw_bps: None, net_bps: None }); - assert!(matches!(range.top.outcome, Outcome::Partial(_))); + assert_eq!(range.verdict, Some(Verdict::CoverageMiss)); + let top = range.top.unwrap(); + assert_eq!(top.deltas, Deltas { raw_bps: None, net_bps: None }); + assert!(matches!(top.outcome, Outcome::Partial(_))); } #[test] @@ -505,17 +627,17 @@ mod tests { let range = build_range( &sandwiched, &empty_prices(), - solved(10_200, 10_100), - solved(9_800, 9_700), - &solved(9_800, 9_700), + Some((solved(10_200, 10_100), solved(9_800, 9_700), solved(9_800, 9_700))), ); - assert_eq!(range.verdict, Verdict::Sandwiched); - assert_eq!(range.top.verdict, Verdict::Sandwiched); - assert_eq!(range.back.verdict, Verdict::Sandwiched); + assert_eq!(range.verdict, Some(Verdict::Sandwiched)); + let top = range.top.unwrap(); + let back = range.back.unwrap(); + assert_eq!(top.verdict, Verdict::Sandwiched); + assert_eq!(back.verdict, Verdict::Sandwiched); // Deltas are unaffected by the override: still computed for offline analysis. - assert!(range.top.deltas.raw_bps.unwrap() > 0.0); - assert!(range.back.deltas.raw_bps.unwrap() < 0.0); + assert!(top.deltas.raw_bps.unwrap() > 0.0); + assert!(back.deltas.raw_bps.unwrap() < 0.0); } #[test] @@ -532,14 +654,16 @@ mod tests { let range = build_range( &sandwiched, &empty_prices(), - solved(10_200, 10_100), - Outcome::Unsolvable("missing token in Tycho".into()), - &Outcome::Unsolvable("re-execution failed".into()), + Some(( + solved(10_200, 10_100), + Outcome::Unsolvable("missing token in Tycho".into()), + Outcome::Unsolvable("re-execution failed".into()), + )), ); - assert_eq!(range.top.verdict, Verdict::Sandwiched); - assert_eq!(range.back.verdict, Verdict::Unsolvable); - assert_eq!(range.verdict, Verdict::Sandwiched); // headline follows top + assert_eq!(range.top.unwrap().verdict, Verdict::Sandwiched); + assert_eq!(range.back.unwrap().verdict, Verdict::Unsolvable); + assert_eq!(range.verdict, Some(Verdict::Sandwiched)); // headline follows top } #[test] @@ -549,18 +673,16 @@ mod tests { let mut with_gas = trade(10_000); with_gas.settled_gas = Some(U256::from(100u64)); let mut prices = empty_prices(); - prices.insert(with_gas.token_out, 2.0); + prices.insert(with_gas.token_out.unwrap(), 2.0); let range = build_range( &with_gas, &prices, - solved(10_050, 9_990), - solved(10_050, 9_990), - &solved(10_050, 9_990), + Some((solved(10_050, 9_990), solved(10_050, 9_990), solved(10_050, 9_990))), ); - assert_eq!(range.settled_amount_out_net_gas, U256::from(9_800u64)); - assert_eq!(range.settled_amount_out, U256::from(10_000u64)); - assert_eq!(range.verdict, Verdict::Win); + assert_eq!(range.settled_amount_out_net_gas, Some(U256::from(9_800u64))); + assert_eq!(range.settled_amount_out, Some(U256::from(10_000u64))); + assert_eq!(range.verdict, Some(Verdict::Win)); } #[test] @@ -573,12 +695,42 @@ mod tests { let range = build_range( &with_gas, &empty_prices(), - solved(10_050, 9_990), - solved(10_050, 9_990), - &solved(10_050, 9_990), + Some((solved(10_050, 9_990), solved(10_050, 9_990), solved(10_050, 9_990))), + ); + assert_eq!(range.settled_amount_out_net_gas, Some(U256::from(10_000u64))); + assert_eq!(range.verdict, Some(Verdict::Win)); + } + + #[test] + fn test_build_range_unknown_terms_records_without_solving() { + // A reverted trade whose solver calldata did not parse: nothing to solve, but it is + // still recorded, tagged by its cause. + let range = build_range(&reverted_trade_without_terms(), &empty_prices(), None); + assert!(matches!(range.status, TradeStatus::Reverted { .. })); + assert!(range.token_in.is_none()); + assert!(range.top.is_none()); + assert!(range.back.is_none()); + assert_eq!(range.verdict, None); + assert_eq!(range.slippage, None); + } + + #[test] + fn test_build_range_reverted_trade_judged_against_floor() { + // A reverted trade with known terms and a floor: no settled amount to compare against + // (deltas/verdict stay at their unsolved defaults), but fillable/margin_bps still judge + // the quote against min_amount_out. + let range = build_range( + &reverted_trade(crate::decoder::RevertCause::SlippageFloor, 10_000), + &empty_prices(), + Some((solved(9_800, 9_700), solved(10_100, 10_000), solved(10_100, 10_000))), ); - assert_eq!(range.settled_amount_out_net_gas, U256::from(10_000u64)); - assert_eq!(range.verdict, Verdict::Win); + assert!(range.settled_amount_out.is_none()); + let top = range.top.unwrap(); + let back = range.back.unwrap(); + assert_eq!(top.deltas, Deltas { raw_bps: None, net_bps: None }); + assert_eq!(top.fillable, Some(false)); + assert_eq!(back.fillable, Some(true)); + assert!(back.margin_bps.unwrap() > 0.0); } #[tokio::test] @@ -598,9 +750,11 @@ mod tests { assert_eq!(ranges.len(), 2); for range in &ranges { - assert_eq!(range.top.verdict, Verdict::Win); - assert_eq!(range.back.verdict, Verdict::Loss); - assert!(range.top.deltas.raw_bps.unwrap() > range.back.deltas.raw_bps.unwrap()); + let top = range.top.as_ref().unwrap(); + let back = range.back.as_ref().unwrap(); + assert_eq!(top.verdict, Verdict::Win); + assert_eq!(back.verdict, Verdict::Loss); + assert!(top.deltas.raw_bps.unwrap() > back.deltas.raw_bps.unwrap()); let slippage = range.slippage.unwrap(); assert!(slippage.bps < 0.0, "re-execution below quote must be negative slippage"); assert_eq!(slippage.quoted_amount_out, U256::from(10_200u64)); @@ -623,11 +777,48 @@ mod tests { .await .unwrap(); - assert_eq!(ranges[0].top.verdict, Verdict::Unsolvable); - assert_eq!(ranges[0].back.verdict, Verdict::Win); + assert_eq!(ranges[0].top.as_ref().unwrap().verdict, Verdict::Unsolvable); + assert_eq!(ranges[0].back.as_ref().unwrap().verdict, Verdict::Win); assert_eq!(ranges[0].slippage, None); } + #[tokio::test] + async fn resolve_block_range_solves_reverts_and_skips_unknown_terms() { + // The floor is 10_000: unfillable at top, fillable at back — the shape a sequencer that + // fills against fresher state would avoid. The intent-less revert has no terms, so it is + // recorded but never reaches the solver. + let solver = MockStepping { + advanced: std::sync::atomic::AtomicBool::new(false), + top: solved(9_800, 9_700), + back: solved(10_100, 10_000), + reexecuted: solved(10_100, 10_000), + }; + let trades = [ + reverted_trade(crate::decoder::RevertCause::SlippageFloor, 10_000), + reverted_trade_without_terms(), + ]; + let ranges = resolve_block_range(&solver, &trades, &empty_prices()) + .await + .unwrap(); + + assert_eq!(ranges.len(), 2); + assert!(ranges[1].top.is_none(), "the terms-less revert must not be solved"); + + let comparison = &ranges[0]; + assert_eq!(comparison.min_amount_out, Some(U256::from(10_000u64))); + assert_eq!( + comparison + .top + .as_ref() + .unwrap() + .fillable, + Some(false) + ); + let back = comparison.back.as_ref().unwrap(); + assert_eq!(back.fillable, Some(true)); + assert!(back.margin_bps.unwrap() > 0.0); + } + #[test] fn build_range_positive_slippage_from_raw_outcomes() { // The route re-executed to more than quoted: the surplus we could charge. The fresh @@ -635,9 +826,7 @@ mod tests { let range = build_range( &trade(10_000), &empty_prices(), - solved(10_000, 9_900), - solved(10_500, 10_400), - &solved(10_050, 9_950), + Some((solved(10_000, 9_900), solved(10_500, 10_400), solved(10_050, 9_950))), ); let slippage = range.slippage.unwrap(); assert!((slippage.bps - 50.0).abs() < 0.01, "expected +50 bps, got {}", slippage.bps); @@ -650,11 +839,13 @@ mod tests { let range = build_range( &trade(10_000), &empty_prices(), - solved(10_000, 9_900), - Outcome::Unsolvable("no route at back-of-block".into()), - &solved(10_050, 9_950), + Some(( + solved(10_000, 9_900), + Outcome::Unsolvable("no route at back-of-block".into()), + solved(10_050, 9_950), + )), ); - assert_eq!(range.back.verdict, Verdict::Unsolvable); + assert_eq!(range.back.unwrap().verdict, Verdict::Unsolvable); let slippage = range.slippage.unwrap(); assert!((slippage.bps - 50.0).abs() < 0.01, "expected +50 bps, got {}", slippage.bps); } @@ -667,11 +858,9 @@ mod tests { let range = build_range( &trade(10_000), &empty_prices(), - solved(1_000, 990), - solved(1_010, 1_000), - &solved(1_010, 1_000), + Some((solved(1_000, 990), solved(1_010, 1_000), solved(1_010, 1_000))), ); - assert_eq!(range.verdict, Verdict::CoverageMiss); + assert_eq!(range.verdict, Some(Verdict::CoverageMiss)); let slippage = range.slippage.unwrap(); assert!((slippage.bps - 100.0).abs() < 0.01, "expected +100 bps, got {}", slippage.bps); } diff --git a/tools/hindsight/src/resolve/monitor.rs b/tools/hindsight/src/resolve/monitor.rs index b09463e34..c8258a718 100644 --- a/tools/hindsight/src/resolve/monitor.rs +++ b/tools/hindsight/src/resolve/monitor.rs @@ -33,7 +33,7 @@ use tycho_simulation::tycho_common::models::{Address as CoreAddress, Chain}; use crate::{ decoder::{DecodedTrade, Decoder, Registry}, provider_from, - resolve::{resolve_block_range, Outcome, SolvedAmount, SteppingSolver}, + resolve::{resolve_block_range, Outcome, RangeComparison, SolvedAmount, SteppingSolver}, telemetry, usd::Prices, }; @@ -500,9 +500,12 @@ pub(crate) async fn run(cfg: MonitorArgs) -> anyhow::Result<()> { let mut comparisons = match cfg.comparisons_dir.as_ref() { Some(dir) => { - let writer = super::jsonl::RotatingWriter::open(dir)?; - info!(path = %writer.current_path().display(), "appending comparisons to JSONL"); - Some(writer) + let comparisons = super::jsonl::RotatingWriter::open(dir)?; + info!( + comparisons_path = %comparisons.current_path().display(), + "appending comparisons to JSONL" + ); + Some(comparisons) } None => None, }; @@ -617,59 +620,50 @@ async fn run_session( } } - let trades = match decode_block_when_available(decoder, target, pacing.rpc_lag_budget).await - { - Ok(trades) => trades, - Err(e) => { - totals.skipped_blocks += 1; - telemetry::record_skipped_block(); - warn!( - block = target, - skipped_total = totals.skipped_blocks, - "decode failed, skipping block: {e}" - ); - if let Err(e) = adapter.advance().await { - return SessionEnd::Unhealthy(e.to_string()); + let decoded = + match decode_block_when_available(decoder, target, pacing.rpc_lag_budget).await { + Ok(decoded) => decoded, + Err(e) => { + totals.skipped_blocks += 1; + telemetry::record_skipped_block(); + warn!( + block = target, + skipped_total = totals.skipped_blocks, + "decode failed, skipping block: {e}" + ); + if let Err(e) = adapter.advance().await { + return SessionEnd::Unhealthy(e.to_string()); + } + continue; } - continue; - } - }; + }; let start = Instant::now(); // Snapshot token prices at top-of-block (N-1) for the headline metric and the top-of-block // USD valuation. let prices_top = snapshot_prices(adapter.solver, decoder.registry()).await; - let ranges = match resolve_block_range(adapter, &trades, &prices_top).await { - Ok(ranges) => ranges, + let (ranges, prices_back) = match resolve_and_snapshot_back( + adapter, + decoder, + &decoded, + &prices_top, + target, + ) + .await + { + Ok(resolved) => resolved, Err(e) => return SessionEnd::Unhealthy(e.to_string()), }; - // resolve_block_range advanced the solver to back-of-block (N); snapshot again so the - // back-of-block improvement is valued against the state it was solved at. - let prices_back = snapshot_prices(adapter.solver, decoder.registry()).await; - // The back-of-block solve should land on `target`. On a reorg/gap/resync the stream can - // apply a different block, silently pairing the back state with another block's trades. - // The top-of-block (N-1) headline is unaffected; warn so the mispaired back state is - // visible. - let applied = adapter.current_block().await; - if applied != Some(target) { - warn!( - target, - applied = ?applied, - "back-of-block state is not the target block; back comparison may be off" - ); - } - for range in &ranges { - telemetry::record_range( - range, - &cfg.chain.name, - &prices_top, - &prices_back, - decoder.registry(), - ); - } - if let Some(rotating) = comparisons.as_mut() { - super::jsonl::write_comparisons(rotating.writer(), &ranges, &prices_top, &prices_back); - } + record_block_resolution( + &BlockRecording { + chain: &cfg.chain.name, + registry: decoder.registry(), + ranges: &ranges, + prices_top: &prices_top, + prices_back: &prices_back, + }, + comparisons, + ); let elapsed_s = start.elapsed().as_secs_f64(); telemetry::record_block_seconds(elapsed_s); @@ -686,6 +680,71 @@ async fn run_session( } } +/// Resolve one block's trades (settled and reverted) at both states, then snapshot back-of-block +/// prices. +/// +/// `resolve_block_range` advances the solver to back-of-block (N) as a side effect, so the price +/// snapshot must be taken after it returns to value the back-of-block improvement against the +/// state it was solved at. The back-of-block solve should land on `target`; on a reorg, gap, or +/// resync the stream can apply a different block, silently pairing the back state with another +/// block's trades — the top-of-block (N-1) headline is unaffected, but this is worth a warning so +/// the mispairing is visible. +async fn resolve_and_snapshot_back( + adapter: &StepAdapter<'_>, + decoder: &Decoder

, + decoded: &[DecodedTrade], + prices_top: &Prices, + target: u64, +) -> anyhow::Result<(Vec, Prices)> { + let ranges = resolve_block_range(adapter, decoded, prices_top).await?; + let prices_back = snapshot_prices(adapter.solver, decoder.registry()).await; + let applied = adapter.current_block().await; + if applied != Some(target) { + warn!( + target, + applied = ?applied, + "back-of-block state is not the target block; back comparison may be off" + ); + } + Ok((ranges, prices_back)) +} + +/// One block's re-solved trades, bundled so the recording helper below takes a handful of +/// parameters instead of every constituent piece separately. +struct BlockRecording<'a> { + chain: &'a str, + registry: &'a Registry, + ranges: &'a [RangeComparison], + prices_top: &'a Prices, + prices_back: &'a Prices, +} + +/// Record and persist one block's re-solved trades — settled and reverted alike, told apart by +/// each record's `status`: Prometheus metrics, plus the single JSONL stream when the run was +/// configured with `--comparisons-dir`. +fn record_block_resolution( + block: &BlockRecording<'_>, + comparisons: &mut Option, +) { + for range in block.ranges { + telemetry::record_range( + range, + block.chain, + block.prices_top, + block.prices_back, + block.registry, + ); + } + if let Some(rotating) = comparisons.as_mut() { + super::jsonl::write_comparisons( + rotating.writer(), + block.ranges, + block.prices_top, + block.prices_back, + ); + } +} + /// Snapshot the solver's current token prices as `Prices` (token native-units per wei of /// the gas token), anchored by `registry`'s USD anchor tokens. Empty until the first /// derived-data computation completes; tokens with an unconvertible price are skipped. diff --git a/tools/hindsight/src/telemetry.rs b/tools/hindsight/src/telemetry.rs index 1822ea3f9..cb41d29c5 100644 --- a/tools/hindsight/src/telemetry.rs +++ b/tools/hindsight/src/telemetry.rs @@ -6,12 +6,13 @@ use std::time::Duration; use actix_web::{web, App, HttpResponse, HttpServer, Responder}; +use alloy::primitives::{Address, U256}; use metrics::{counter, describe_counter, describe_histogram, histogram, Unit}; use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle}; use tracing::{error, info, warn}; use crate::{ - decoder::Registry, + decoder::{Registry, RevertCause, TradeStatus}, resolve::{render_route, Outcome, RangeComparison, StateResult, Verdict}, usd::Prices, }; @@ -30,6 +31,9 @@ const RPC_INDEX_WAIT: &str = "hindsight_rpc_index_wait_seconds"; const SKIPPED_BLOCKS: &str = "hindsight_skipped_blocks_total"; const FEED_REBUILDS: &str = "hindsight_feed_rebuilds_total"; const UNTRACED_TRANSACTIONS: &str = "hindsight_untraced_transactions_total"; +const REVERTED_SWAPS_TOTAL: &str = "hindsight_reverted_swaps_total"; +const REVERT_FILLABLE_TOTAL: &str = "hindsight_revert_fillable_total"; +const REVERT_MARGIN_BPS: &str = "hindsight_revert_margin_bps"; /// Absolute USD savings beyond which a comparison is logged with full per-trade context, so large /// outliers can be traced and classified (a genuinely large trade vs a token-mispricing artifact @@ -107,9 +111,20 @@ pub(crate) fn outcome_label(verdict: Verdict) -> &'static str { } } +/// Metric label for a reverted swap's cause — a closed, bounded set. `Other`'s free-text detail +/// stays in the JSONL records, never in a label. +pub(crate) fn revert_cause_label(cause: &RevertCause) -> &'static str { + match cause { + RevertCause::SlippageFloor => "slippage_floor", + RevertCause::OutOfGas => "out_of_gas", + RevertCause::Other(_) => "other", + } +} + /// Register metric descriptions with the active recorder. pub(crate) fn describe() { describe_trade_metrics(); + describe_revert_metrics(); } /// Register descriptions for the per-trade re-solve metrics: savings, slippage, volume, and the @@ -208,9 +223,29 @@ fn describe_trade_metrics() { ); } -/// Record a two-state range: the top-of-block (N-1) and back-of-block (N) outcomes, each tagged -/// with a `state` label ("top"/"back"). `prices_top`/`prices_back` are the solver's token-price -/// snapshots at each state, used to value savings in USD; an empty map disables USD for that state. +/// Register descriptions for the reverted-swap metrics. +fn describe_revert_metrics() { + describe_counter!( + REVERTED_SWAPS_TOTAL, + "Reverted Relay swaps seen, labeled by venue / solver / chain / cause \ + (slippage_floor|out_of_gas|other) / decoded (true|false). The decoded=false slice is \ + parser-coverage: reverts whose solver frame or calldata could not be recovered" + ); + describe_counter!( + REVERT_FILLABLE_TOTAL, + "Decoded, solved reverts whose quote is judged against the trader's on-chain floor \ + (min_amount_out), labeled by venue / solver / chain / state (top|back) / fillable \ + (true|false). The avoidance rate is fillable=true over the total at either state" + ); + describe_histogram!( + REVERT_MARGIN_BPS, + Unit::Count, + "Signed bps of (quote - min_amount_out) / min_amount_out for decoded, solved reverts \ + (positive = cleared the floor with room to spare), labeled by venue / solver / chain / \ + state (top|back)" + ); +} +/// Record one re-solved trade — settled or reverted, told apart by `range.status`. pub(crate) fn record_range( range: &RangeComparison, chain: &str, @@ -218,6 +253,49 @@ pub(crate) fn record_range( prices_back: &Prices, registry: &Registry, ) { + match &range.status { + TradeStatus::Settled => { + record_settled_range(range, chain, prices_top, prices_back, registry); + } + TradeStatus::Reverted { cause } => record_reverted_range(range, cause, chain, registry), + } +} + +/// Record a settled trade's two-state range: the top-of-block (N-1) and back-of-block (N) +/// outcomes, each tagged with a `state` label ("top"/"back"). `prices_top`/`prices_back` are the +/// solver's token-price snapshots at each state, used to value savings in USD; an empty map +/// disables USD for that state. +fn record_settled_range( + range: &RangeComparison, + chain: &str, + prices_top: &Prices, + prices_back: &Prices, + registry: &Registry, +) { + // A settled trade always carries these by construction (see `DecodedTrade`'s settled/ + // reverted invariant); the early return is a defensive guard, not the expected path. + let ( + Some(top), + Some(back), + Some(verdict), + Some(token_in), + Some(token_out), + Some(amount_in), + Some(settled_amount_out), + ) = ( + &range.top, + &range.back, + range.verdict, + range.token_in, + range.token_out, + range.amount_in, + range.settled_amount_out, + ) + else { + warn!(tx = %range.tx_hash, "settled range is missing terms or solved states; skipping metrics"); + return; + }; + let labels = MetricLabels { venue: venue_label(&range.venue, registry), solver: solver_label(&range.solver, registry), @@ -231,15 +309,15 @@ pub(crate) fn record_range( // outside the solver's graph) — without the fallback, unsolvable volume would be // systematically undercounted. let volume = prices_top - .value_usd(range.token_out, range.settled_amount_out) - .or_else(|| prices_top.value_usd(range.token_in, range.amount_in)); + .value_usd(token_out, settled_amount_out) + .or_else(|| prices_top.value_usd(token_in, amount_in)); if let Some(volume) = volume { histogram!( VOLUME_USD, "venue" => labels.venue.to_string(), "solver" => labels.solver.to_string(), "chain" => labels.chain.to_string(), - "outcome" => outcome_label(range.verdict).to_string(), + "outcome" => outcome_label(verdict).to_string(), ) .record(volume); } @@ -249,9 +327,10 @@ pub(crate) fn record_range( // notional and is kept. The notional is a per-trade quantity, so both states share this gate. let above_floor = volume.is_none_or(|usd| usd >= MIN_NOTIONAL_USD); - let savings_top = record_state(range, &range.top, "top", &labels, prices_top, above_floor); - record_state(range, &range.back, "back", &labels, prices_back, above_floor); - record_slippage(range, &labels, prices_back); + let settled = (token_out, settled_amount_out); + let savings_top = record_state(range, top, "top", &labels, prices_top, above_floor, settled); + record_state(range, back, "back", &labels, prices_back, above_floor, settled); + record_slippage(range, &labels, prices_back, token_out, verdict); // One structured line per priced comparison, on the headline basis (top-of-block, gross). // Loki ingests pod stdout, so this line feeds the dashboard's top-trades table; keep the @@ -262,10 +341,10 @@ pub(crate) fn record_range( // `route` is last on purpose: it is the only field whose value contains spaces, so a LogQL // regexp can only bound it by end-of-line. Keep it last, or the dashboard's route column // silently swallows every field after it. - if let (Some(savings_usd), Outcome::Solved(solved)) = (savings_top, &range.top.outcome) { + if let (Some(savings_usd), Outcome::Solved(solved)) = (savings_top, &top.outcome) { let priced = |amount| { prices_top - .value_usd(range.token_out, amount) + .value_usd(token_out, amount) .unwrap_or(0.0) }; info!( @@ -273,12 +352,12 @@ pub(crate) fn record_range( block = range.block_number, venue = %range.venue, solver = %range.solver, - token_in = %range.token_in, - token_out = %range.token_out, - verdict = %outcome_label(range.verdict), - algorithm = %algorithm_label(&range.top.outcome), + token_in = %token_in, + token_out = %token_out, + verdict = %outcome_label(verdict), + algorithm = %algorithm_label(&top.outcome), volume_usd = volume.unwrap_or(0.0), - settled_usd = priced(range.settled_amount_out), + settled_usd = priced(settled_amount_out), fynd_usd = priced(solved.amount_out), quoted_usd = range.declared_quote.map_or(0.0, priced), savings_usd, @@ -288,16 +367,16 @@ pub(crate) fn record_range( } } -/// Record one block-state of a range under a `state` label. Emits the trade counter, and — for a -/// solved state — the gross bps delta, the signed USD savings, and the USD uplift (only when -/// Fynd beats the settled trade; a venue routes elsewhere when Fynd is worse). All highlighted -/// metrics compare gross vs gross, matching the headline verdict. +/// Record one block-state of a settled range under a `state` label. Emits the trade counter, and +/// — for a solved state — the gross bps delta, the signed USD savings, and the USD uplift (only +/// when Fynd beats the settled trade; a venue routes elsewhere when Fynd is worse). All +/// highlighted metrics compare gross vs gross, matching the headline verdict. /// /// A sandwiched state's output was moved by MEV, not by Fynd's own routing, so it skips the /// `SAVINGS_BPS`/`SAVINGS_USD`/`IMPROVEMENT_USD` histograms — the USD histograms carry no /// outcome label, so skipping is the only way to keep the "value of adding Fynd" aggregates /// clean. The USD value is still computed and returned so the per-trade Loki line (in -/// `record_range`) keeps logging. +/// `record_settled_range`) keeps logging. /// /// Returns the signed USD savings it computed, `None` when the state is unsolved or unpriced. fn record_state( @@ -307,7 +386,9 @@ fn record_state( labels: &MetricLabels<'_>, prices: &Prices, above_floor: bool, + settled: (Address, U256), ) -> Option { + let (token_out, settled_amount_out) = settled; let algorithm = algorithm_label(&state.outcome); if above_floor { @@ -343,7 +424,7 @@ fn record_state( let Outcome::Solved(solved) = &state.outcome else { return None; }; - let usd = prices.savings_usd(range.token_out, solved.amount_out, range.settled_amount_out)?; + let usd = prices.savings_usd(token_out, solved.amount_out, settled_amount_out)?; if sandwiched { return Some(usd); } @@ -355,12 +436,9 @@ fn record_state( state = state_label, venue = %range.venue, solver = %range.solver, - token_in = %range.token_in, - token_out = %range.token_out, - amount_in = %range.amount_in, - settled_out = %range.settled_amount_out, + settled_out = %settled_amount_out, fynd_out = %solved.amount_out, - token_out_price = ?prices.get(range.token_out), + token_out_price = ?prices.get(token_out), usd, "USD outlier — inspect for token mispricing vs genuinely large trade" ); @@ -396,11 +474,17 @@ fn record_state( /// Valued at `prices_back`, the state the surplus is realized at. Sandwiched trades are not /// skipped: the comparison is Fynd-quote vs Fynd-re-execution, so the settled trade's MEV does /// not enter it, and block N's pool moves are real either way. -fn record_slippage(range: &RangeComparison, labels: &MetricLabels<'_>, prices: &Prices) { +fn record_slippage( + range: &RangeComparison, + labels: &MetricLabels<'_>, + prices: &Prices, + token_out: Address, + verdict: Verdict, +) { let Some(slippage) = range.slippage else { return; }; - let outcome = outcome_label(range.verdict).to_string(); + let outcome = outcome_label(verdict).to_string(); histogram!( SLIPPAGE_BPS, "venue" => labels.venue.to_string(), @@ -410,11 +494,9 @@ fn record_slippage(range: &RangeComparison, labels: &MetricLabels<'_>, prices: & ) .record(slippage.bps); - let Some(usd) = prices.savings_usd( - range.token_out, - slippage.reexecuted_amount_out, - slippage.quoted_amount_out, - ) else { + let Some(usd) = + prices.savings_usd(token_out, slippage.reexecuted_amount_out, slippage.quoted_amount_out) + else { return; }; histogram!( @@ -435,10 +517,10 @@ fn record_slippage(range: &RangeComparison, labels: &MetricLabels<'_>, prices: & block = range.block_number, venue = %range.venue, solver = %range.solver, - token_out = %range.token_out, + token_out = %token_out, quoted_out = %slippage.quoted_amount_out, reexecuted_out = %slippage.reexecuted_amount_out, - token_out_price = ?prices.get(range.token_out), + token_out_price = ?prices.get(token_out), usd, "positive slippage USD outlier — inspect for token mispricing vs a genuine pool move" ); @@ -452,6 +534,64 @@ fn record_slippage(range: &RangeComparison, labels: &MetricLabels<'_>, prices: & .record(usd); } +/// Record a reverted trade: always the "seen" counter (parser coverage, via the `decoded` label — +/// whether its solver calldata parsed into terms), and — when its terms were known enough to +/// solve — each state's fillable/margin judgment against `min_amount_out`. +fn record_reverted_range( + range: &RangeComparison, + cause: &RevertCause, + chain: &str, + registry: &Registry, +) { + counter!( + REVERTED_SWAPS_TOTAL, + "venue" => venue_label(&range.venue, registry).to_string(), + "solver" => solver_label(&range.solver, registry).to_string(), + "chain" => chain.to_string(), + "cause" => revert_cause_label(cause).to_string(), + "decoded" => if range.token_in.is_some() { "true" } else { "false" }, + ) + .increment(1); + + let labels = MetricLabels { + venue: venue_label(&range.venue, registry), + solver: solver_label(&range.solver, registry), + chain, + }; + if let Some(top) = &range.top { + record_revert_state(top, "top", &labels); + } + if let Some(back) = &range.back { + record_revert_state(back, "back", &labels); + } +} + +/// Record one state of a revert judgment: the fillable counter and the signed margin histogram, +/// both only when the state was solved (there is nothing to judge otherwise). +fn record_revert_state(state: &StateResult, state_label: &'static str, labels: &MetricLabels<'_>) { + if let Some(fillable) = state.fillable { + counter!( + REVERT_FILLABLE_TOTAL, + "venue" => labels.venue.to_string(), + "solver" => labels.solver.to_string(), + "chain" => labels.chain.to_string(), + "state" => state_label, + "fillable" => if fillable { "true" } else { "false" }, + ) + .increment(1); + } + if let Some(margin_bps) = state.margin_bps { + histogram!( + REVERT_MARGIN_BPS, + "venue" => labels.venue.to_string(), + "solver" => labels.solver.to_string(), + "chain" => labels.chain.to_string(), + "state" => state_label, + ) + .record(margin_bps); + } +} + pub(crate) fn record_block_seconds(seconds: f64) { histogram!(BLOCK_SECONDS).record(seconds); } @@ -557,6 +697,7 @@ fn configure_buckets( .set_buckets_for_metric(Matcher::Full(IMPROVEMENT_USD.into()), SAVINGS_USD_BUCKETS)? .set_buckets_for_metric(Matcher::Full(SLIPPAGE_BPS.into()), SAVINGS_BPS_BUCKETS)? .set_buckets_for_metric(Matcher::Full(SLIPPAGE_USD.into()), SAVINGS_USD_BUCKETS)? + .set_buckets_for_metric(Matcher::Full(REVERT_MARGIN_BPS.into()), SAVINGS_BPS_BUCKETS)? .set_buckets_for_metric( Matcher::Full(POSITIVE_SLIPPAGE_USD.into()), POSITIVE_SLIPPAGE_USD_BUCKETS, @@ -571,6 +712,7 @@ fn configure_buckets( mod tests { use alloy::primitives::{address, Address, TxHash, U256}; use metrics_exporter_prometheus::PrometheusBuilder; + use tycho_simulation::tycho_common::models::Chain; use super::*; use crate::{ @@ -587,15 +729,68 @@ mod tests { tx_hash: TxHash::default(), block_number: 21_000_000, tx_index: 0, + status: TradeStatus::Settled, venue: "relay".into(), solver: "tycho".into(), solver_source: AttributionSource::TraceMatch, decoder: "sender-netting", sender: Address::ZERO, - token_in: Address::repeat_byte(0x11), - token_out, - amount_in: U256::from(1_000u64), - amount_out: U256::from(settled), + token_in: Some(Address::repeat_byte(0x11)), + token_out: Some(token_out), + amount_in: Some(U256::from(1_000u64)), + amount_out: Some(U256::from(settled)), + venue_fee_in: None, + venue_fee_out: None, + settled_gas: None, + min_amount_out: None, + declared_quote: None, + quote_timestamp: None, + sandwich: None, + } + } + + /// A reverted trade with known terms and a floor, for the unified revert-metrics tests. + fn reverted_trade(cause: RevertCause, min_amount_out: u64) -> DecodedTrade { + DecodedTrade { + tx_hash: TxHash::default(), + block_number: 21_000_000, + tx_index: 0, + status: TradeStatus::Reverted { cause }, + venue: "relay".into(), + solver: "fly".into(), + solver_source: AttributionSource::TraceMatch, + decoder: "reverted", + sender: Address::ZERO, + token_in: Some(Address::repeat_byte(0x11)), + token_out: Some(Address::repeat_byte(0x22)), + amount_in: Some(U256::from(1_000u64)), + amount_out: None, + venue_fee_in: None, + venue_fee_out: None, + settled_gas: None, + min_amount_out: Some(U256::from(min_amount_out)), + declared_quote: None, + quote_timestamp: None, + sandwich: None, + } + } + + /// A reverted trade whose solver calldata did not parse — no terms, nothing to solve. + fn undecoded_reverted_trade(cause: RevertCause) -> DecodedTrade { + DecodedTrade { + tx_hash: TxHash::default(), + block_number: 21_000_000, + tx_index: 1, + status: TradeStatus::Reverted { cause }, + venue: "relay".into(), + solver: "relay".into(), + solver_source: AttributionSource::Fallback, + decoder: "reverted", + sender: Address::ZERO, + token_in: None, + token_out: None, + amount_in: None, + amount_out: None, venue_fee_in: None, venue_fee_out: None, settled_gas: None, @@ -649,9 +844,11 @@ mod tests { let range = build_range( &trade(usdc, 1_000_000_000), &empty_prices(), - solved_by("path_frank_wolfe", 1_010_000_000, 1_005_000_000), - solved_by("path_frank_wolfe", 1_010_000_000, 1_005_000_000), - &Outcome::Unsolvable("x".into()), + Some(( + solved_by("path_frank_wolfe", 1_010_000_000, 1_005_000_000), + solved_by("path_frank_wolfe", 1_010_000_000, 1_005_000_000), + Outcome::Unsolvable("x".into()), + )), ); let mut prices = empty_prices(); prices.insert(usdc, 2e-9); @@ -690,9 +887,11 @@ mod tests { let range = build_range( &trade(Address::repeat_byte(0x22), 1_000), &empty_prices(), - Outcome::Unsolvable("missing token in Tycho".into()), - Outcome::Unsolvable("missing token in Tycho".into()), - &Outcome::Unsolvable("no top-of-block route to re-execute".into()), + Some(( + Outcome::Unsolvable("missing token in Tycho".into()), + Outcome::Unsolvable("missing token in Tycho".into()), + Outcome::Unsolvable("no top-of-block route to re-execute".into()), + )), ); let recorder = PrometheusBuilder::new().build_recorder(); let handle = recorder.handle(); @@ -747,9 +946,11 @@ mod tests { let range = build_range( &trade(usdc, 1_000_000_000), &empty_prices(), - solved(1_010_000_000, 1_005_000_000), - solved(998_000_000, 995_000_000), - &solved(998_000_000, 995_000_000), + Some(( + solved(1_010_000_000, 1_005_000_000), + solved(998_000_000, 995_000_000), + solved(998_000_000, 995_000_000), + )), ); // USDC priced at 2e-9 native-units per ETH-wei (ETH = $2000) anchors ETH→USD. let mut prices = empty_prices(); @@ -802,9 +1003,11 @@ mod tests { let range = build_range( &trade(usdc, 1_000_000_000), &empty_prices(), - solved(1_000_000_000, 995_000_000), - solved(1_005_000_000, 1_000_000_000), - &solved(1_005_000_000, 1_000_000_000), + Some(( + solved(1_000_000_000, 995_000_000), + solved(1_005_000_000, 1_000_000_000), + solved(1_005_000_000, 1_000_000_000), + )), ); let mut prices = empty_prices(); prices.insert(usdc, 2e-9); @@ -845,9 +1048,11 @@ mod tests { let range = build_range( &trade(usdc, 1_000_000_000), &empty_prices(), - solved(1_000_000_000, 995_000_000), - solved(995_000_000, 990_000_000), - &solved(995_000_000, 990_000_000), + Some(( + solved(1_000_000_000, 995_000_000), + solved(995_000_000, 990_000_000), + solved(995_000_000, 990_000_000), + )), ); let mut prices = empty_prices(); prices.insert(usdc, 2e-9); @@ -886,9 +1091,11 @@ mod tests { let range = build_range( &trade(usdc, 1_000_000_000), &empty_prices(), - solved(1_000_000_000, 995_000_000), - solved(1_002_000_000, 997_000_000), - &Outcome::Unsolvable("re-execution failed: no simulation state".into()), + Some(( + solved(1_000_000_000, 995_000_000), + solved(1_002_000_000, 997_000_000), + Outcome::Unsolvable("re-execution failed: no simulation state".into()), + )), ); let mut prices = empty_prices(); prices.insert(usdc, 2e-9); @@ -937,9 +1144,7 @@ mod tests { let range = build_range( &t, &empty_prices(), - solved(1_100, 1_050), - solved(1_100, 1_050), - &solved(1_100, 1_050), + Some((solved(1_100, 1_050), solved(1_100, 1_050), solved(1_100, 1_050))), ); let recorder = PrometheusBuilder::new().build_recorder(); @@ -970,9 +1175,7 @@ mod tests { let range = build_range( &t, &empty_prices(), - solved(1_100, 1_050), - solved(1_100, 1_050), - &solved(1_100, 1_050), + Some((solved(1_100, 1_050), solved(1_100, 1_050), solved(1_100, 1_050))), ); let recorder = PrometheusBuilder::new().build_recorder(); @@ -998,14 +1201,16 @@ mod tests { // unsolvable volume would be systematically undercounted. let usdc = address!("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"); let mut t = trade(Address::repeat_byte(0x42), 1_000); - t.token_in = usdc; - t.amount_in = U256::from(1_000_000_000u64); // 1000 USDC + t.token_in = Some(usdc); + t.amount_in = Some(U256::from(1_000_000_000u64)); // 1000 USDC let range = build_range( &t, &empty_prices(), - Outcome::Unsolvable("no route".into()), - Outcome::Unsolvable("no route".into()), - &Outcome::Unsolvable("no top-of-block route to re-execute".into()), + Some(( + Outcome::Unsolvable("no route".into()), + Outcome::Unsolvable("no route".into()), + Outcome::Unsolvable("no top-of-block route to re-execute".into()), + )), ); let mut prices = empty_prices(); prices.insert(usdc, 2e-9); @@ -1030,9 +1235,11 @@ mod tests { let range = build_range( &trade(Address::repeat_byte(0x22), 1_000), &empty_prices(), - Outcome::Unsolvable("x".into()), - Outcome::Unsolvable("x".into()), - &Outcome::Unsolvable("x".into()), + Some(( + Outcome::Unsolvable("x".into()), + Outcome::Unsolvable("x".into()), + Outcome::Unsolvable("x".into()), + )), ); let recorder = PrometheusBuilder::new().build_recorder(); let handle = recorder.handle(); @@ -1067,11 +1274,13 @@ mod tests { let range = build_range( &sandwiched, &prices, - solved(1_100_000_000, 1_090_000_000), - solved(1_100_000_000, 1_090_000_000), - &solved(1_100_000_000, 1_090_000_000), + Some(( + solved(1_100_000_000, 1_090_000_000), + solved(1_100_000_000, 1_090_000_000), + solved(1_100_000_000, 1_090_000_000), + )), ); - assert_eq!(range.verdict, Verdict::Sandwiched); + assert_eq!(range.verdict, Some(Verdict::Sandwiched)); let recorder = configure_buckets(PrometheusBuilder::new()) .unwrap() @@ -1099,9 +1308,11 @@ mod tests { let range = build_range( &trade(usdc, 1_000_000), &empty_prices(), - solved(1_005_000, 1_005_000), - solved(1_005_000, 1_005_000), - &solved(1_005_000, 1_005_000), + Some(( + solved(1_005_000, 1_005_000), + solved(1_005_000, 1_005_000), + solved(1_005_000, 1_005_000), + )), ); let mut prices = empty_prices(); prices.insert(usdc, 2e-9); @@ -1130,9 +1341,7 @@ mod tests { let range = build_range( &trade(Address::repeat_byte(0x42), 1_000), &empty_prices(), - solved(1_100, 1_050), - solved(1_100, 1_050), - &solved(1_100, 1_050), + Some((solved(1_100, 1_050), solved(1_100, 1_050), solved(1_100, 1_050))), ); let recorder = configure_buckets(PrometheusBuilder::new()) .unwrap() @@ -1151,4 +1360,133 @@ mod tests { assert!(rendered.contains("hindsight_trades_total"), "rendered: {rendered}"); assert!(rendered.contains("hindsight_savings_bps"), "rendered: {rendered}"); } + + #[test] + fn test_revert_cause_labels() { + assert_eq!(revert_cause_label(&RevertCause::SlippageFloor), "slippage_floor"); + assert_eq!(revert_cause_label(&RevertCause::OutOfGas), "out_of_gas"); + assert_eq!( + revert_cause_label(&RevertCause::Other("execution reverted".to_string())), + "other" + ); + } + + #[test] + fn test_record_range_reverted_labels_decoded_and_cause() { + let decoded = + build_range(&reverted_trade(RevertCause::SlippageFloor, 10_000), &empty_prices(), None); + let undecoded = + build_range(&undecoded_reverted_trade(RevertCause::OutOfGas), &empty_prices(), None); + + let recorder = PrometheusBuilder::new().build_recorder(); + let handle = recorder.handle(); + metrics::with_local_recorder(&recorder, || { + record_range( + &decoded, + "base", + &empty_prices(), + &empty_prices(), + &Registry::builtin(Chain::Base).unwrap(), + ); + record_range( + &undecoded, + "base", + &empty_prices(), + &empty_prices(), + &Registry::builtin(Chain::Base).unwrap(), + ); + }); + let rendered = handle.render(); + + assert!(rendered.contains("cause=\"slippage_floor\""), "rendered: {rendered}"); + assert!(rendered.contains("cause=\"out_of_gas\""), "rendered: {rendered}"); + assert!(rendered.contains("decoded=\"true\""), "rendered: {rendered}"); + assert!(rendered.contains("decoded=\"false\""), "rendered: {rendered}"); + } + + #[test] + fn test_record_range_reverted_fillable_and_margin() { + let trade = reverted_trade(RevertCause::SlippageFloor, 10_000); + let range = build_range( + &trade, + &empty_prices(), + Some((solved(9_800, 9_700), solved(10_200, 10_100), solved(10_200, 10_100))), + ); + + let recorder = configure_buckets(PrometheusBuilder::new()) + .unwrap() + .build_recorder(); + let handle = recorder.handle(); + metrics::with_local_recorder(&recorder, || { + record_range( + &range, + "base", + &empty_prices(), + &empty_prices(), + &Registry::builtin(Chain::Base).unwrap(), + ); + }); + let rendered = handle.render(); + + assert!(rendered.contains("hindsight_revert_fillable_total"), "rendered: {rendered}"); + assert!(rendered.contains("state=\"top\""), "rendered: {rendered}"); + assert!(rendered.contains("state=\"back\""), "rendered: {rendered}"); + assert!(rendered.contains("fillable=\"false\""), "rendered: {rendered}"); + assert!(rendered.contains("fillable=\"true\""), "rendered: {rendered}"); + assert!(rendered.contains("hindsight_revert_margin_bps_bucket"), "rendered: {rendered}"); + } + + #[test] + fn test_record_range_reverted_unsolved_state_skips_fillable_metrics() { + let trade = reverted_trade(RevertCause::OutOfGas, 10_000); + let range = build_range( + &trade, + &empty_prices(), + Some(( + Outcome::Unsolvable("no route".into()), + Outcome::Unsolvable("no route".into()), + Outcome::Unsolvable("no route".into()), + )), + ); + + let recorder = PrometheusBuilder::new().build_recorder(); + let handle = recorder.handle(); + metrics::with_local_recorder(&recorder, || { + record_range( + &range, + "base", + &empty_prices(), + &empty_prices(), + &Registry::builtin(Chain::Base).unwrap(), + ); + }); + let rendered = handle.render(); + + assert!(!rendered.contains("hindsight_revert_fillable_total"), "rendered: {rendered}"); + assert!(!rendered.contains("hindsight_revert_margin_bps"), "rendered: {rendered}"); + } + + #[test] + fn test_record_range_reverted_with_unknown_terms_still_counted_as_seen() { + // No terms to solve, but the "seen" counter (parser coverage) must still fire. + let trade = undecoded_reverted_trade(RevertCause::Other("execution reverted".to_string())); + let range = build_range(&trade, &empty_prices(), None); + + let recorder = PrometheusBuilder::new().build_recorder(); + let handle = recorder.handle(); + metrics::with_local_recorder(&recorder, || { + record_range( + &range, + "base", + &empty_prices(), + &empty_prices(), + &Registry::builtin(Chain::Base).unwrap(), + ); + }); + let rendered = handle.render(); + + assert!(rendered.contains("hindsight_reverted_swaps_total"), "rendered: {rendered}"); + assert!(rendered.contains("decoded=\"false\""), "rendered: {rendered}"); + assert!(!rendered.contains("hindsight_revert_fillable_total"), "rendered: {rendered}"); + } } diff --git a/tools/hindsight/src/verify/mod.rs b/tools/hindsight/src/verify/mod.rs index f220eb9bd..91b93328f 100644 --- a/tools/hindsight/src/verify/mod.rs +++ b/tools/hindsight/src/verify/mod.rs @@ -17,7 +17,7 @@ use anyhow::Context; use tracing::warn; use crate::{ - decoder::{DecodedTrade, Decoder}, + decoder::{DecodedTrade, Decoder, TradeStatus}, verify::allium::{AlliumClient, AlliumRow}, }; @@ -109,7 +109,12 @@ pub(crate) async fn run( let mut decimals = HashMap::new(); for &block in blocks { let ours = match decoder.decode_block(block).await { - Ok(ours) => ours, + // Allium's `aggregator_trades` only records swaps that settled; a reverted trade has + // no counterpart there to diff against. + Ok(decoded) => decoded + .into_iter() + .filter(|trade| trade.status == TradeStatus::Settled) + .collect::>(), Err(error) => { warn!(block, %error, "failed to decode block; skipping"); continue; @@ -188,6 +193,27 @@ async fn compare_block( } } +/// The settled swap terms a comparison needs, extracted from a `DecodedTrade` up front so the +/// rest of this module can work with plain values rather than re-checking `Option`s everywhere. +/// `run` only ever passes settled trades into `compare_block` (Allium has nothing to diff a +/// revert against), so by the settled/reverted invariant these are always present — but the type +/// system does not know that from a filter, so a missing term is still handled, not unwrapped. +struct SettledTerms { + token_in: Address, + token_out: Address, + amount_in: U256, + amount_out: U256, +} + +fn settled_terms(trade: &DecodedTrade) -> Option { + Some(SettledTerms { + token_in: trade.token_in?, + token_out: trade.token_out?, + amount_in: trade.amount_in?, + amount_out: trade.amount_out?, + }) +} + async fn compare_trade( provider: &P, ours: &DecodedTrade, @@ -195,10 +221,20 @@ async fn compare_trade( tolerance_bps: f64, decimals: &mut HashMap, ) -> TxComparison { + let Some(terms) = settled_terms(ours) else { + return TxComparison { + tx_hash: ours.tx_hash, + status: Status::OursOnly, + detail: "settled trade has no known terms (should have been filtered out earlier)" + .to_string(), + }; + }; + let mut detail = Vec::new(); - let token_ok = tokens_agree(ours, rows, &mut detail); + let token_ok = tokens_agree(&terms, rows, &mut detail); let solver_ok = solver_agrees(ours, rows, &mut detail); - let amount_ok = amounts_agree(provider, ours, rows, tolerance_bps, decimals, &mut detail).await; + let amount_ok = + amounts_agree(provider, &terms, rows, tolerance_bps, decimals, &mut detail).await; let status = if !token_ok { Status::TokenMismatch @@ -215,8 +251,8 @@ async fn compare_trade( /// Allium splits a multi-leg swap into per-leg rows, so check membership in sets rather than /// exact one-to-one matching: our netted `token_in` must appear among the sold tokens and our -/// `token_out` among the bought tokens across all rows for this tx. -fn tokens_agree(ours: &DecodedTrade, rows: &[&AlliumRow], detail: &mut Vec) -> bool { +/// `token_out` among the bought tokens across all rows for a tx. +fn tokens_agree(terms: &SettledTerms, rows: &[&AlliumRow], detail: &mut Vec) -> bool { let sold: HashSet

= rows .iter() .filter_map(|r| { @@ -234,13 +270,13 @@ fn tokens_agree(ours: &DecodedTrade, rows: &[&AlliumRow], detail: &mut Vec( provider: &P, - ours: &DecodedTrade, + terms: &SettledTerms, rows: &[&AlliumRow], tolerance_bps: f64, decimals: &mut HashMap, @@ -281,11 +317,15 @@ async fn amounts_agree( return true; }; - let in_leg = - AmountLeg { token: ours.token_in, ours: ours.amount_in, theirs: sold, field: "amount_in" }; + let in_leg = AmountLeg { + token: terms.token_in, + ours: terms.amount_in, + theirs: sold, + field: "amount_in", + }; let out_leg = AmountLeg { - token: ours.token_out, - ours: ours.amount_out, + token: terms.token_out, + ours: terms.amount_out, theirs: bought, field: "amount_out", }; @@ -418,15 +458,16 @@ mod tests { tx_hash: TxHash::ZERO, block_number: 1, tx_index: 0, + status: crate::decoder::TradeStatus::Settled, venue: "relay".to_string(), solver: solver.to_string(), solver_source: AttributionSource::TraceMatch, decoder: "sender-netting", sender: addr(1), - token_in, - token_out, - amount_in: U256::from(1000), - amount_out: U256::from(2000), + token_in: Some(token_in), + token_out: Some(token_out), + amount_in: Some(U256::from(1000)), + amount_out: Some(U256::from(2000)), venue_fee_in: None, venue_fee_out: None, settled_gas: None, @@ -451,21 +492,33 @@ mod tests { #[test] fn test_token_match_both_present() { let ours = trade(addr(10), addr(11), "1inch"); + let terms = settled_terms(&ours).unwrap(); let allium = row(addr(10), addr(11), "1inch"); let mut detail = Vec::new(); - assert!(tokens_agree(&ours, &[&allium], &mut detail)); + assert!(tokens_agree(&terms, &[&allium], &mut detail)); assert!(detail.is_empty()); } #[test] fn test_token_match_mismatched_tokens() { let ours = trade(addr(10), addr(99), "1inch"); + let terms = settled_terms(&ours).unwrap(); let allium = row(addr(10), addr(11), "1inch"); let mut detail = Vec::new(); - assert!(!tokens_agree(&ours, &[&allium], &mut detail)); + assert!(!tokens_agree(&terms, &[&allium], &mut detail)); assert_eq!(detail.len(), 1); } + #[test] + fn test_settled_terms_is_none_for_a_reverted_trade() { + let mut ours = trade(addr(10), addr(11), "1inch"); + ours.status = crate::decoder::TradeStatus::Reverted { + cause: crate::decoder::RevertCause::Other("execution reverted".to_string()), + }; + ours.token_in = None; + assert!(settled_terms(&ours).is_none()); + } + #[test] fn test_solver_match_prefix() { let ours = trade(addr(10), addr(11), "uniswap");