Widen AMM reserve math to Uint256 + make oracle overflow-safe (#464, #465) #1002

Merged
Brouie merged 1 commit from qa/464-465-amm-256bit-widening into main 2026-07-01 13:55:40 +00:00
Brouie commented 2026-07-01 13:19:17 +00:00 (Migrated from gitlab.com)

Fixes the two AMM launch-blockers from the security sweep. Same root cause (native-width reserve arithmetic), same file, so patched together.

#464 — constant-product overflow

Every reserve product in the pair ran in native Uint128 with no 256-bit widening, so reserve_a * reserve_b overflows at ~1.85e19 raw per side (~18 whole tokens for an 18-dec asset), reverting swaps and quotes. Widened all the product sites to Uint256, narrowing results back to Uint128 (they're always bounded by a reserve/supply):

  • swap k + k-invariant recheck (contract.rs), and the swap simulation
  • first-deposit LP mint (isqrt now over Uint256) + subsequent mint + slippage expected_lp
  • withdraw numerators
  • spot_linear_spread_over_gross
  • scale_hybrid_template book split
  • hybrid_reverse.rs pool-net + reverse-seed products

Behaviour is identical for every existing (small-reserve) case — 256-bit is a superset, same division/rounding, same ceil_div guarantee.

#465 — oracle panic → brick + fund lock

oracle_update used the panicking Decimal::from_ratio; an extreme reserve ratio (reserve_b/reserve_a > Decimal::MAX) panicked, and since oracle_update runs on the swap AND withdraw paths that permanently bricked the pair and locked LP funds. Switched to Decimal::checked_from_ratio and skip the observation on overflow (graceful degrade, no state committed) instead of aborting.

Tests

  • Full contract suite green: 459/0 (456 baseline + 3 new).
  • pool_net_output_survives_18dec_scale_reserves — pool math at 2e19/side reserves (pre-fix overflowed here).
  • oracle_overflow_tests::extreme_ratio_degrades_gracefully_instead_of_panicking + normal_ratio_still_records_observation.
  • Both new tests fail against the pre-fix code (deterministic u128 overflow / from_ratio panic), so they genuinely guard the fix.

Note: an end-to-end 18-dec pool integration test (custom whitelisted large-token pair) is a good follow-up; the pool-math regression + the fully-green existing suite (which exercises swap/mint/withdraw/hybrid/oracle on the widened math) cover the core here.

@PlasticDigits — these are the two launch-blockers, ready for review.

Fixes the two AMM launch-blockers from the security sweep. Same root cause (native-width reserve arithmetic), same file, so patched together. ## #464 — constant-product overflow Every reserve product in the pair ran in native `Uint128` with no 256-bit widening, so `reserve_a * reserve_b` overflows at ~1.85e19 raw per side (~18 whole tokens for an 18-dec asset), reverting swaps and quotes. Widened all the product sites to `Uint256`, narrowing results back to `Uint128` (they're always bounded by a reserve/supply): - swap `k` + k-invariant recheck (contract.rs), and the swap simulation - first-deposit LP mint (`isqrt` now over `Uint256`) + subsequent mint + slippage `expected_lp` - withdraw numerators - `spot_linear_spread_over_gross` - `scale_hybrid_template` book split - `hybrid_reverse.rs` pool-net + reverse-seed products Behaviour is identical for every existing (small-reserve) case — 256-bit is a superset, same division/rounding, same `ceil_div` guarantee. ## #465 — oracle panic → brick + fund lock `oracle_update` used the panicking `Decimal::from_ratio`; an extreme reserve ratio (`reserve_b/reserve_a > Decimal::MAX`) panicked, and since `oracle_update` runs on the swap AND withdraw paths that permanently bricked the pair and locked LP funds. Switched to `Decimal::checked_from_ratio` and skip the observation on overflow (graceful degrade, no state committed) instead of aborting. ## Tests - Full contract suite green: **459/0** (456 baseline + 3 new). - `pool_net_output_survives_18dec_scale_reserves` — pool math at 2e19/side reserves (pre-fix overflowed here). - `oracle_overflow_tests::extreme_ratio_degrades_gracefully_instead_of_panicking` + `normal_ratio_still_records_observation`. - Both new tests fail against the pre-fix code (deterministic u128 overflow / from_ratio panic), so they genuinely guard the fix. Note: an end-to-end 18-dec pool integration test (custom whitelisted large-token pair) is a good follow-up; the pool-math regression + the fully-green existing suite (which exercises swap/mint/withdraw/hybrid/oracle on the widened math) cover the core here. @PlasticDigits — these are the two launch-blockers, ready for review.
Brouie commented 2026-07-01 13:19:35 +00:00 (Migrated from gitlab.com)

mentioned in issue #464

mentioned in issue #464
Brouie commented 2026-07-01 13:19:36 +00:00 (Migrated from gitlab.com)

mentioned in issue #465

mentioned in issue #465
PlasticDigits commented 2026-07-01 13:27:33 +00:00 (Migrated from gitlab.com)

Security review — MR !1002

Commit reviewed: 4811caf939f0661a6574f32b2431fa6ab8e45164
Base: 21381a0e19412a96c6e34487f657e62e7d2b8e85

Scope

  • smartcontracts/contracts/pair/src/contract.rs — Uint256 widening for constant-product reserve math (isqrt_u256, ceil_div_u256, narrow_u128); oracle_update switched to Decimal::checked_from_ratio with graceful skip (#465)
  • smartcontracts/contracts/pair/src/hybrid_reverse.rs — matching 256-bit reserve math for reverse-quote seeding
  • New regression tests for 18-dec reserve scale and oracle extreme-ratio behaviour

Method

Traced attacker-controlled inputs (swap amounts, liquidity deposits/withdrawals, reserve imbalance) through widened product/division sites and the oracle update path. Checked whether narrow_u128 can mint or pay out beyond Uint128 bounds, whether rounding diverges from pre-fix semantics, and whether oracle skip re-opens the #465 fund-lock class.

Outcome: FINDINGS: 0 (no medium+)

Uint256 widening (#464): All narrowed outputs are bounded by an existing Uint128 reserve, LP supply, or withdraw pro-rata share. ceil_div_u256 preserves the same pool-favourable rounding as the removed ceil_div; k-invariant checks are unchanged in intent. Widening also correctly allows provide-liquidity paths whose intermediate amount × supply product exceeds u128 but whose floored LP result still fits — a false rejection fix, not a mint inflation vector.

Oracle (#465): Replacing the panicking Decimal::from_ratio on the swap/withdraw/provide execute path with checked_from_ratio + skip removes the launch-blocker fund-lock. Skipped observations do not commit oracle state; swaps and withdrawals proceed. The remaining Decimal::from_ratio in oracle_observe_single is query-only (Observe) — a failed query does not mutate state or block execute; out of scope for the execute-path brick this MR targets. A secondary price_times_dt overflow returning ContractError::Oracle on very high (but sub-Decimal::MAX) ratios with multi-second dt is pre-existing behaviour (pre-fix code reached price_times_dt after successful from_ratio); this MR does not widen that failure mode and actually un-bricks the above-Decimal::MAX regime via skip.

Inline threads: none (no medium+ findings).


Automated security review — no block:security label applied.

## Security review — MR !1002 **Commit reviewed:** `4811caf939f0661a6574f32b2431fa6ab8e45164` **Base:** `21381a0e19412a96c6e34487f657e62e7d2b8e85` ### Scope - `smartcontracts/contracts/pair/src/contract.rs` — Uint256 widening for constant-product reserve math (`isqrt_u256`, `ceil_div_u256`, `narrow_u128`); `oracle_update` switched to `Decimal::checked_from_ratio` with graceful skip (#465) - `smartcontracts/contracts/pair/src/hybrid_reverse.rs` — matching 256-bit reserve math for reverse-quote seeding - New regression tests for 18-dec reserve scale and oracle extreme-ratio behaviour ### Method Traced attacker-controlled inputs (swap amounts, liquidity deposits/withdrawals, reserve imbalance) through widened product/division sites and the oracle update path. Checked whether `narrow_u128` can mint or pay out beyond `Uint128` bounds, whether rounding diverges from pre-fix semantics, and whether oracle skip re-opens the #465 fund-lock class. ### Outcome: **FINDINGS: 0** (no medium+) **Uint256 widening (#464):** All narrowed outputs are bounded by an existing `Uint128` reserve, LP supply, or withdraw pro-rata share. `ceil_div_u256` preserves the same pool-favourable rounding as the removed `ceil_div`; `k`-invariant checks are unchanged in intent. Widening also correctly allows provide-liquidity paths whose intermediate `amount × supply` product exceeds `u128` but whose floored LP result still fits — a false rejection fix, not a mint inflation vector. **Oracle (#465):** Replacing the panicking `Decimal::from_ratio` on the swap/withdraw/provide execute path with `checked_from_ratio` + skip removes the launch-blocker fund-lock. Skipped observations do not commit oracle state; swaps and withdrawals proceed. The remaining `Decimal::from_ratio` in `oracle_observe_single` is query-only (Observe) — a failed query does not mutate state or block execute; out of scope for the execute-path brick this MR targets. A secondary `price_times_dt` overflow returning `ContractError::Oracle` on very high (but sub-`Decimal::MAX`) ratios with multi-second `dt` is pre-existing behaviour (pre-fix code reached `price_times_dt` after successful `from_ratio`); this MR does not widen that failure mode and actually un-bricks the above-`Decimal::MAX` regime via skip. **Inline threads:** none (no medium+ findings). --- *Automated security review — no `block:security` label applied.*
PlasticDigits commented 2026-07-01 13:55:41 +00:00 (Migrated from gitlab.com)

mentioned in commit fcdcbd87f2

mentioned in commit fcdcbd87f20d7a74d7bd56bf50d77d0a97671ccc
PlasticDigits (Migrated from gitlab.com) merged commit fcdcbd87f2 into main 2026-07-01 13:55:41 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic!1002
No description provided.