Replace polymarket positions input with ERC1155 reconstruction - #9696
Conversation
Co-authored-by: 0xRob <0xRobin@users.noreply.github.com>
Co-authored-by: 0xRob <0xRobin@users.noreply.github.com>
Co-authored-by: 0xRob <0xRobin@users.noreply.github.com>
Co-authored-by: 0xRob <0xRobin@users.noreply.github.com>
Co-authored-by: 0xRob <0xRobin@users.noreply.github.com>
Co-authored-by: 0xRob <0xRobin@users.noreply.github.com>
Co-authored-by: 0xRob <0xRobin@users.noreply.github.com>
Co-authored-by: 0xRob <0xRobin@users.noreply.github.com>
Co-authored-by: 0xRob <0xRobin@users.noreply.github.com>
Co-authored-by: 0xRob <0xRobin@users.noreply.github.com>
Co-authored-by: 0xRob <0xRobin@users.noreply.github.com>
Co-authored-by: 0xRob <0xRobin@users.noreply.github.com>
Co-authored-by: 0xRob <0xRobin@users.noreply.github.com>
Co-authored-by: 0xRob <0xRobin@users.noreply.github.com>
Co-authored-by: 0xRob <0xRobin@users.noreply.github.com>
Co-authored-by: 0xRob <0xRobin@users.noreply.github.com>
- Use int256 throughout the running balance computation instead of double-precision floats. Native uint/int256 arithmetic avoids the precision loss the legacy macro hides via clamping. - Filter daily_deltas with HAVING sum(delta) <> 0 so days with only no-op (value=0) transfers do not produce phantom 'balance=0' anchor rows. This is the source of the ~540k spurious rows/day seen in the initial CI vs prod diff. - Drop unused block_number/block_time aggregation; the model never emits them. - Collapse the redundant daily_flows + daily_deltas pair into a single group-by; same shape, less work. - Remove the disabled singular equivalence test. It compared the repro to the legacy snapshot-based macro, which is itself the source of truth we are intentionally moving off (it carries stale balances when the snapshot table misses burn events). Equivalence is validated out-of-band against the CI table; the schema-level uniqueness and not_null tests remain the in-CI guardrails.
The previous 'HAVING sum(delta) <> 0' filter dropped legitimate intraday mint-then-burn closures alongside the no-op (value=0) phantom rows. Switch to 'HAVING max(abs(delta)) > 0' so days with non-zero transfers that net to zero still anchor a closure marker row, matching the legacy table's behavior on those days. Days where every transfer carries value=0 are still filtered out.
|
@BugBot review |
PR SummaryMedium Risk Overview A new incremental model Tests: Reviewed by Cursor Bugbot for commit 3d02747. Configure here. |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 3d02747. Configure here.
dennisp42's review caught three real issues in the incremental path:
1. `prior_balances` aggregated by `max(day)` from `{{ this }}`, but
`{{ this }}` carries forward-filled rows where `day` is the fill
day, not the original change day. The follow-up forward-fill then
set `last_updated = anchor_day`, so every incremental run rewrote
`last_updated` for inactive pairs to ~yesterday. Confirmed against
prod: ~99 % of pre-window inactive pairs are forward-fills, with
drift up to ~5.3 years. Aggregate by `last_updated` instead, which
is preserved across forward-fill.
2. Cold pairs (anchor far in the past) made the forward-fill sequence
expand the full history just for `incremental_predicate` to clip
it. Clip `fill_start` to the window start in incremental runs.
3. Closed positions expanded `[closure_day, current_date - 1]` only
for the final WHERE to drop everything but the closure day. Cap
`fill_end` at the closure day when `balance_raw = uint256 '0'`.
The combination needs the guard `fill_end >= fill_start` (otherwise a
closed position before the window would feed an invalid sequence range
into the unnest).
Replace
polymarket_polygon.positions_raw's upstream dependency ontokens_polygon.balances_daily_agg_base(which is built from a snapshot source that carries stale balances when burn events are missed) with a new model,polymarket_polygon.positions_balances_repro, that reconstructs daily Polymarket CTF balances directly from on-chainerc1155_polygon.evt_TransferSingleandevt_TransferBatchevents.Linear: CUR2-2534
Changes
polymarket_polygon.positions_balances_repro:(address, token_id)from Polymarket CTF transfer events.value = 0(no-op transfers in batches), but keeps intraday round-trips with non-zero amounts so closure markers are emitted.int256running sum, clamped to[0, uint256_max], cast touint256. No double-precision intermediate.balance = 0.{{ this }}over the prior window, aggregated bylast_updated(notday) solast_updatedis preserved across forward-filled rows.fill_startis clipped to the window start so cold pairs don't expand their full history before the predicate filters it.balance_raw = 0) thefill_endis capped at the closure day so we don't expand[closure_day, current_date - 1]only to be dropped by the final WHERE.polymarket_polygon.positions_rawnow reads from the new model instead ofbalances_incremental_subset_daily(...). Output schema unchanged._schema.yml: adds the new model withdbt_utils.unique_combination_of_columns(scoped to last 14 days for CI runtime) andnot_nulltests on key columns.CI vs prod comparison (last day, run 26572354448)
balance_matching(same key, samebalance_raw)balance_different(same key, differentbalance_raw)ci_only(in CI, missing in prod)prod_only(in prod, missing in CI)Over the last 7 days the aggregate balance sums agree to within 0.001 % (
16.45607777e18vs16.45625946e18raw).last_updatedparity on the same 68.2 M common rows: 99.993 % identical, 4,779 differ. All observed differences are negative drift (CI'slast_updatedis later than prod's), consistent with prod's snapshot source missing intermediate change events.Why the residual diffs are expected and acceptable
I sampled rows from each diff bucket and traced the underlying ERC1155 events:
balance_different: legacybalances_polygon_0002snapshots miss some burn events, leaving stale positive balances that diverge from on-chain reality.0xc4c41cd12f991e76ceeff92b531cd74c02497bc2/ token1119...477. Prod =10_527_110_704. Sum of all on-chain deltas for this pair =1_316_636_682_369(last event 2022-03-26). The new repro matches the on-chain sum exactly. Prod has been wrong for ~4 years.prod_only: 100 % havebalance_raw > 0, all are forward-fills (last_updated < day), 39,829 of 40,605 havelast_updatedolder than 90 days. These are the same class of stuck legacy snapshot rows. The repro correctly omits them because the on-chain net delta is zero.ci_only: 100 % havebalance_raw > 0, all are forward-fills of real on-chain positions that the legacy snapshot source never indexed. The repro correctly includes them.Net effect: the repro fixes ~69k stale/missing rows per day relative to the legacy table while matching the remaining 99.9 % exactly, and additionally restores accurate
last_updatedvalues for pairs whose intermediate changes were missed by the snapshot source.Performance
CI initial build: ~20 min, ~9.4 B rows materialized; incremental merge: ~3 min, ~200 M rows. Same order as the legacy model.
Validation
dbt_utils.unique_combination_of_columnsandnot_nullschema tests on the new model.last_updatedparity check (above) confirms the seed-by-last_updatedfix prevents drift on inactive pairs (~99 % of pre-window pairs are forward-fills with drift up to 5.3 years under the naive seed-by-dayapproach).What I removed from the original Cursor-agent draft
tests/polymarket_polygon_positions_balances_repro_equivalence.sql). It compared the repro againstbalances_incremental_subset_daily(...)— which is the legacy table we are intentionally moving off and is itself the source of the divergences above. Any threshold tight enough to be useful would have made CI red on legitimate fixes; loose enough not to was useless. The schema-level uniqueness / not-null tests stay as the in-CI guardrails.doubleprecision cast in the running balance computation.sum(int256) OVER (...)works natively and avoids the rounding risk for large positions.daily_flows→daily_deltasaggregation and themax(block_number)/max(block_time)columns that were computed but never emitted.quick links for more information: