Skip to content

feat(ekubo): decode pool state from the anonymous swap log - #9970

Open
moodysalem wants to merge 2 commits into
duneanalytics:mainfrom
EkuboProtocol:ekubo-decode-swap-pool-state
Open

feat(ekubo): decode pool state from the anonymous swap log#9970
moodysalem wants to merge 2 commits into
duneanalytics:mainfrom
EkuboProtocol:ekubo-decode-swap-pool-state

Conversation

@moodysalem

@moodysalem moodysalem commented Aug 26, 2026

Copy link
Copy Markdown

Summary

Ekubo's swap parser currently reads the token amount deltas but leaves the post-swap pool state undecoded. This PR decodes the final 32 bytes of Core's 116-byte anonymous swap log, making price and active-liquidity history available directly from the liquidity-event tables.

Adds four columns to the Ethereum v1/v3 base models and exposes them through ekubo.base_liquidity_events and ekubo.liquidity_events. All four are null for non-swap events.

Column Value immediately after the swap
sqrt_ratio_after Raw packed uint96 SqrtRatio float
sqrt_ratio_after_fixed Q128 fixed point: sqrt(token1/token0 in raw units) * 2^128
tick_after Signed pool tick
liquidity_after Active liquidity

Implementation

  • ekubo_compatible_liquidity_events reads sqrtRatio at byte offset 84 (12 bytes), tick at 96 (4 bytes, signed), and liquidity at 100 (16 bytes). Offsets are zero-based; SQL substrings are one-based.
  • New macro ekubo_sqrt_ratio_to_fixed converts the packed value using fixed = mantissa << (32 * exponent + 2), matching the contract's toFixed. The upper two bits hold the exponent; the remaining 94 bits hold the mantissa. Four CASE branches use uint256 multiplication to implement the shifts.
  • Adds length(data) = 116 alongside topic0 is null to reject unexpected anonymous-log lengths before decoding.

Compatibility and rollout

Existing columns retain their values. The length guard excluded no rows in the original validation: all 5.27 million anonymous logs checked across both Core versions were 116 bytes. dex.trades does not consume the new columns.

Historical values in the incremental models require Dune's normal post-merge full refresh.

Validation

  • CI on the current commit compiled successfully, built all four affected models (5,740,754 rows in the enriched table), and passed uniqueness tests and incremental reruns.
  • Original Dune validation for August 26 returned 6,705 swaps with all four fields populated and nulls for non-swap events.
  • For this USDC/USDG swap, decoded values matched an independent CoreDataFetcher.poolState RPC read at the following block: tick -165, fixed sqrt ratio 340254383154770049453632155902269194240, and liquidity 10035484459699841.
  • Additional local arithmetic checks passed for 4,012 packed values across all four exponents and eight signed-tick boundary/sample cases. These checks validate the constants and arithmetic against the contract formula; they are separate from the Dune CI tests.

@github-actions
github-actions Bot marked this pull request as draft August 26, 2026 19:39
@cursor

cursor Bot commented Aug 26, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Additive DEX analytics columns with stricter swap log filtering; no changes to trades or existing column values, though downstream queries that assumed the old column set may need to ignore the new fields.

Overview
Extends Ekubo anonymous swap log parsing in ekubo_compatible_liquidity_events to read the last 32 bytes of the 116-byte payload (after amount0/amount1), exposing post-swap sqrt_ratio_after, tick_after, and liquidity_after on swap rows. Swap selection now requires length(data) = 116 so fixed byte offsets cannot mis-parse other anonymous Core logs.

Non-swap branches (modify_liquidity, fees events) add null placeholders for those fields so the union schema stays aligned. The final projection also adds sqrt_ratio_after_fixed via a new ekubo_sqrt_ratio_to_fixed macro (uint96 SqrtRatio float → Q128 fixed point).

ekubo_base_liquidity_events, ekubo_liquidity_events, and _schema.yml are updated to document and pass through the four new columns; existing columns and row semantics stay additive.

Reviewed by Cursor Bugbot for commit f74d077. Configure here.

@github-actions github-actions Bot added WIP work in progress dbt: dex covers the DEX dbt subproject labels Aug 26, 2026
Ekubo Core emits swaps as an anonymous log (topic0 is null), so there is
no decoded `core_evt_swapped` table on Dune and spellbook parses the raw
log bytes itself. The parser stopped after the two amount deltas, leaving
the last 32 bytes of the 116-byte payload unused.

Those bytes carry the pool state after the swap:

    off  len  field
      0   20  locker
     20   32  poolId
     52   16  amount0        (int128, signed)   already decoded
     68   16  amount1        (int128, signed)   already decoded
     84   12  sqrtRatio      (uint96 float)     added
     96    4  tick           (int32, signed)    added
    100   16  liquidity      (uint128)          added

`sqrt_ratio_after` is exposed as the raw uint96 float and, via the new
`ekubo_sqrt_ratio_to_fixed` macro, as `sqrt_ratio_after_fixed` in Q128
form (sqrt(token1/token0) * 2^128), which is what the contracts and the
interface expose. The float packs a 2-bit exponent above a 94-bit
mantissa: fixed = mantissa << (32 * exponent + 2).

Verified against independent RPC reads of CoreDataFetcher.poolState:
tick, sqrtRatio and liquidity decoded from tx 0x24b3dff9 match the pool
state read at the following block exactly, and the Q128 conversion
reproduces 2^128 * 1.000001^(tick/2) to within one tick across exponents
0-2 and ticks from -19.8M to +27M.

Also pins the swap filter to `length(data) = 116`. Every anonymous log
emitted by both the v1 and v3 Core contracts is 116 bytes today
(5.27M logs checked), so this is a no-op on current data and guards the
offsets if another anonymous event is ever added.

Non-swap events (modify_liquidity, fees_collected, fees_accumulated)
carry null for the three new columns.

Claude-Session: https://claude.ai/code/session_016Pzo1k2MwRueV2unUJq8Jh
@moodysalem
moodysalem force-pushed the ekubo-decode-swap-pool-state branch from f74d077 to cbb631b Compare August 31, 2026 15:28
@moodysalem

Copy link
Copy Markdown
Author

@jeff-dude @tomfutago — gentle bump on this one, it's been open since Aug 26 without a reviewer assigned.

Just rebased onto the latest main (head cbb631b), so it's conflict-free and current. The change is additive: it decodes the trailing 32 bytes of Ekubo's 116-byte anonymous swap log to surface post-swap sqrt_ratio_after, tick_after, and liquidity_after, plus a sqrt_ratio_after_fixed projection via a new ekubo_sqrt_ratio_to_fixed macro. Existing columns and row semantics are unchanged, and the new length(data) = 116 guard makes the fixed-offset parsing safe against other anonymous Core logs.

Happy to make any adjustments you'd like. Thanks!

@moodysalem

Copy link
Copy Markdown
Author

Following up — still no reviewer on this one. It's been open since Aug 26, and CI is fully green (build, CLA, and all dbt-ci/* jobs pass).

@jeff-dude @tomfutago — could one of you assign a reviewer, or point me at whoever owns the dex subproject these days? Happy to split this into a smaller diff if the macro addition is the sticking point, or to add test coverage if that would help it along.

For context, the whole change is additive: three decoded columns on Ekubo swap rows plus one projection macro, guarded by length(data) = 116. No existing column values or row semantics change.

@moodysalem

Copy link
Copy Markdown
Author

Third nudge, and this time tagging the team — @duneanalytics/team-curated-data.

I think I see why this has been silent: CODEOWNERS only covers .github/workflows/, dependabot.yml, and pyproject.toml/uv.lock, so a PR touching dbt_subprojects/dex/ never gets a reviewer auto-assigned. This one has had reviewers=0 since it opened on Aug 26.

Status: fully green (build, CLA, all dbt-ci/*), MERGEABLE, no conflicts. The change is additive — three decoded columns on Ekubo swap rows plus one projection macro, guarded by length(data) = 116, with no change to existing column values or row semantics.

@jeff-dude @tomfutago — could someone assign a reviewer? Happy to make any changes you'd like.

@moodysalem
moodysalem marked this pull request as ready for review September 9, 2026 18:10
@github-actions github-actions Bot added ready-for-review this PR development is complete, please review and removed WIP work in progress labels Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dbt: dex covers the DEX dbt subproject ready-for-review this PR development is complete, please review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant