AMM Invariant Scaling Mismatch / Constant-Product Guard Failure
- Name: AMM invariant scaling mismatch / constant-product guard failure
- Category: exploit pattern / AMM accounting failure / fork-modification bug / invariant-desynchronization
- Summary: This exploit family breaks automated market makers when the formula that is supposed to enforce reserve solvency stops using one internally consistent unit system. The common version appears in forks of battle-tested AMMs: developers change fee multipliers, precision bases, or invariant coefficients in one part of
swap()math but forget to update the final guard that is meant to prove the pool remains solvent. Once the fee-adjusted balances and the invariant threshold are scaled differently, the contract can accept post-swap states that should be impossible and effectively lets traders withdraw reserves the pool has not really earned. - What it is:
- An AMM maintains a reserve invariant such as constant product, stable-swap balance, or another equation that defines which post-trade states are valid.
- The implementation computes some adjusted post-swap balances that incorporate fee deductions or precision scaling.
- A final
requireor equivalent guard compares those adjusted balances against a threshold derived from pre-swap reserves. - If one side of that comparison is modified to use new scaling constants or coefficients and the other side is not, the guard stops constraining the same economic quantity.
- A fast sanity check is to plug in unchanged reserves and ask whether the post-swap guard already got easier to satisfy for purely dimensional reasons. In Uranium’s famous case, moving the adjusted-balance side from
1000to10000while keeping the threshold at1000**2made the left/right ratio about100xtoo forgiving before any real trading economics even entered. - The attacker can then request outputs that honest AMM math would reject, because the contract’s proof of solvency has become numerically meaningless.
- Typical preconditions:
- The protocol is a fork or adaptation of an existing AMM rather than an implementation derived from first-principles proofs and tests.
- Developers change fee parameters, multipliers, decimals, or precision bases inside swap math.
- The protocol relies on a single arithmetic guard to certify that reserves remain consistent after output tokens are sent.
- There are no secondary sanity checks on reserve ratios, maximum outflows, or invariant monotonicity under the modified unit system.
- Liquidity is already sitting in live pools, so a successful invariant bypass immediately monetizes into real reserve drainage.
- Common vulnerable surfaces:
- Uniswap-v2-style
swap()implementations with custom fee multipliers or alteredbalanceAdjustedmath - forks that replace
1000-based or1e18-based constants in one calculation path but not in another - stable-swap and hybrid-pool implementations with hand-modified invariant coefficients
- audit diffs that look like tiny fee or precision changes but actually alter the invariant’s dimensional consistency
- migrations where old pool math and new fee schedules are stitched together without re-deriving the full guard condition
- Uniswap-v2-style
- Exploit sequence:
- The attacker identifies an AMM whose post-swap invariant check mixes old and new scaling systems.
- The attacker transfers a minimal amount of input token into the pool or otherwise sets up the balances needed to satisfy the broken formula.
- The attacker calls the public swap path asking for far more output than correct invariant math would permit.
- The contract evaluates the malformed guard and accepts the trade because the left-hand and right-hand sides no longer measure the same thing.
- The attacker receives most or all of one reserve side, often then using the stolen asset to drain the opposite side as well.
- The attacker repeats the same primitive across other pairs that share the same implementation.
- Variants:
- Fee-base mismatch — fee-adjusted balances move from one base such as
1000to another such as10000, but the invariant threshold still uses the old base. - Precision-denominator drift — one branch adopts higher precision or different decimal normalization while another branch keeps legacy coefficients.
- Forked-math partial edit — the protocol copies battle-tested AMM logic, edits only a handful of constants, and unknowingly invalidates the proof obligation those constants originally satisfied.
- Multi-pool replay drain — once the bug is confirmed on one pool, the same swap primitive can be replayed across every pool sharing the pair contract.
- Fee-base mismatch — fee-adjusted balances move from one base such as
Three solvency promotions reviewers should not collapse
-
Some balance-adjusted / invariant-shaped state object exists
- The pool can compute fee-adjusted balances, a reserve product, a stable-swap invariant term, or some other post-trade solvency-shaped object.
- This is only the weakest fact:
the arithmetic produced a number.
-
That computed object deserves to count as canonical solvency truth for this pool
- The harder question is whether every multiplier, denominator, precision basis, and threshold term still lives in one unit system and still proves the same economic object the pool claims to preserve.
- Uranium-style failures happen here: the protocol keeps a solvency-looking guard, but the guard has already stopped measuring pool truth because half the formula moved and the other half did not.
-
That accepted solvency truth deserves immediate reserve-release authority
- Even if a pool computes one coherent post-swap state, the protocol still decides how much inventory that state may move before staging, caps, pauses, or other containment intervene.
- This is where canary deployment, grouped bytecode ownership, and outflow bounds matter.
Healthy AMM review gets materially better once teams stop flattening those three promotions into one comforting claim like the invariant check passed.
- Detection heuristics:
- Treat every fee or precision constant in AMM math as part of one proof, not as independent tunables.
- Diff every modified AMM fork against the upstream implementation and ask whether each changed constant has a matching change everywhere the same unit system appears.
- Write property tests that assert the invariant in economic terms, not just exact source-level expressions copied from the fork.
- Fuzz swaps that request extreme output amounts after tiny inputs; broken unit systems often reveal themselves as obviously overgenerous
amountOutacceptance. - Run a
no-economic-changedimensional check: if both reserves stay effectively the same, the final guard should not suddenly have10x,100x, or1e12xmore slack just because one side was rescaled. - Test whether unchanged reserves with only scaling changes would make the post-swap guard easier to satisfy; if yes, the accounting proof has drifted.
- Defenses:
- Re-derive the invariant mathematically whenever changing fee bases, scaling constants, or precision systems; do not patch constants by intuition.
- Use differential testing against the upstream AMM for identical reserve states and trades whenever the modified fork claims to preserve the same economic invariant.
- Add explicit invariant assertions in tests and formal tools that operate on reserve economics, not only raw integer expressions.
- Minimize customization of battle-tested AMM core contracts; move product differentiation to routers or wrappers when possible.
- Treat tiny arithmetic diffs in forked AMMs as high-risk changes deserving line-by-line security review.
What sound design looks like
- The pool has one clearly documented unit system for the invariant object, fee adjustments, reserve normalization, and final solvency guard, and every code path that mutates those quantities is derived from the same math rather than from copied constants.
- Swap code is reviewed as one proof obligation, not as
calculation linesplus a laterrequire. The same economic quantity must survive from amount-in accounting through to the post-swap invariant check without base drift. - Forks preserve battle-tested core math unless there is a compelling reason to change it. When teams do change it, they can explain in one short derivation why every edited constant appears in every place the invariant expects it.
- The implementation has route-independent safety rails: extreme-out swaps fail closed, reserve depletion is bounded by invariant-aware checks, and monitoring can detect impossible reserve transitions quickly.
- Test infrastructure treats the upstream AMM as a reference model whenever the fork claims semantic equivalence. A fee or precision edit is not considered safe until differential tests and invariant tests agree. AMM invariant unit-consistency and fork-equivalence discipline is the positive-design companion for keeping one invariant object, one unit map, one changed-constants ledger, and one staged rollout story explicit before forked pool math reaches production.
Failure-prevention checklist
- Keep three questions explicit in review and rollout docs: what computed state object exists, why it still counts as canonical solvency truth, and how much live reserve authority that accepted truth is allowed to carry.
- Write the invariant in one canonical form, with units, before changing any fee base, multiplier, denominator, decimal normalization, or precision constant.
- Maintain a
changed constants inventoryfor core AMM math. If one constant moves from1000to10000, reviewers should verify every occurrence that participates in the same proof, not just the edited line. - Add property tests that assert economically meaningful postconditions: reserves cannot move to a state that would violate the intended invariant, and tiny inputs cannot buy nearly all of the opposite reserve.
- Differential-test every modified fork against its upstream parent across the same reserve states, trade directions, fee-on / fee-off settings, and extreme output requests.
- Bound blast radius at runtime with per-swap outflow caps, pool pausing, or invariant monitors that alarm on reserve states the intended math should never admit.
- Treat pair / pool math edits as protocol-wide changes, not local optimizations. One broken invariant usually repeats across every pool sharing the same implementation.
- Require reviewer sign-off from someone re-deriving the economic meaning of the guard, not only someone checking syntax or gas diffs.
Audit prompts
- What exact balance-adjusted or invariant-shaped object is the pool computing here, and is review accidentally treating
the arithmetic returned a numberas if that already proved solvency? - After every fee, scaling, or precision edit, do both sides of the final invariant check still measure the same quantity in the same unit system?
- Which constants are part of one shared proof but currently live as duplicated literals across several lines or helper functions?
- If the pool claims to be economically equivalent to Uniswap v2 or another parent design, what differential tests prove that claim for edge-state swaps and reserve depletion attempts?
- Can a tiny input followed by an extreme
amountOutrequest pass the final guard? What exact test demonstrates the answer is no across representative pools? - Are there any code paths where balance adjustments use one fee basis while reserve-product thresholds, protocol-fee minting, or reserve updates still use another?
- If this one arithmetic edit shipped to production, how many pools inherit it immediately, and what monitoring would notice impossible reserve states before attackers drain the fleet?
- Has anyone re-derived the invariant from first principles since the customization, or is the code relying on inherited safety from the upstream AMM despite changed constants?
- Even if the computed object is coherent, how much reserve can one accepted post-swap state move before canaries, pool-family pauses, or outflow bounds intervene?
Lessons that should already be standard
-
Tiny AMM math diffs are not tiny risk. Changing one denominator can silently invalidate the whole solvency proof.
-
The invariant check passedcollapses three different claims: a solvency-shaped object exists, that object still counts as canonical pool truth, and that accepted truth deserves live reserve authority. -
Forked from Uniswapis not a security property. The moment fee bases or scaling constants diverge, the team owns the full mathematical proof burden. -
The final invariant guard is not decorative. If it no longer constrains the same quantity the earlier math computed, the pool is effectively authorizing reserve withdrawals on fiction.
-
One production pair is enough to validate the bug; one shared pair implementation is enough to scale the loss across an ecosystem.
-
Differential testing against the upstream design should be standard whenever the stated product claim is
same AMM, different fees. -
Reviewers should read AMM arithmetic as a single security object. Splitting swap math into locally plausible edits is how dimension drift survives code review.
-
Runtime containment still matters after the math looks coherent. Healthy AMMs assume one proof obligation can fail and keep that failure from immediately becoming full-fleet reserve authority.
-
High-signal incidents:
- Uranium Finance v2 K-invariant scaling-mismatch drain (2021-04-28) — Uranium changed Uniswap v2’s fee-adjusted balance scaling from
1000to10000but left the finalKthreshold at1000**2, creating roughly100xfalse slack in the invariant check; public PoCs showed that1 WBNBcould then drain about99%of a large BUSD reserve before the attacker reversed direction and stripped the other side too.
- Uranium Finance v2 K-invariant scaling-mismatch drain (2021-04-28) — Uranium changed Uniswap v2’s fee-adjusted balance scaling from
Where this pattern stops
- It is not the general
rounding bug in AMMsbucket. Use directional-rounding-bias-roundtrip-non-invariance when the pool still checks the intended invariant object but one local rounding choice lets attackers iterate profitable edge-state loops. - It is not the concentrated-liquidity state-coordination bucket. Use tick-liquidity-desynchronization when price, tick-crossing state, and active-liquidity truth drift apart even though the protocol is still trying to enforce the right CLAMM object.
- It is not the
token behaved strangelybucket. Use fee-on-transfer accounting mismatch when the AMM math is locally consistent but external token transfer semantics make the observed balances lie. - The sharp fingerprint here is stricter: the AMM’s own final solvency guard no longer constrains the same quantity its earlier math just computed.
Internal linkages
- Best concrete case study: uranium-finance-v2-k-invariant-scaling-mismatch-drain-2021-04-28.
- Positive-design companion: amm-invariant-unit-consistency-and-fork-equivalence-discipline — use it when the sharper question is how AMM forks should keep one invariant object, one unit system, one reference-model equivalence claim, and one fleet rollout plan legible before edited pool math can touch real reserves.
- Adjacent pattern: directional-rounding-bias-roundtrip-non-invariance — both families can come from tiny arithmetic edits, but that family usually preserves the same invariant object and leaks value through edge-state rounding loops; this family breaks the guard that is supposed to certify solvency at all.
- Adjacent pattern: tick-liquidity-desynchronization — both are AMM math failures, but tick/liquidity desynchronization breaks boundary-state consistency in concentrated-liquidity systems whereas this pattern breaks the invariant guard itself.
- Adjacent pattern: fee-on-transfer accounting mismatch — both let nominal balances diverge from economically valid reserves, but fee-on-transfer mismatches come from token semantics while invariant-scaling mismatches come from the AMM’s own arithmetic edits.
Control surface
- The leverage sits in core pair math, fee multipliers, precision bases, invariant guards, and every fork diff that changes arithmetic without re-proving the economic meaning of the check.
- The danger is not simply
a typo in math. It is a protocol promotingsome computed balance-adjusted object existsintothis pool is solventand then intothis swap deserves live reserve authorityeven after the guard stopped measuring solvency at all. - The best review question is brutally literal:
do both sides of the final guard still live in the same unit system after every fee and precision edit?
Sources
- https://immunefi.com/blog/bug-fix-reviews/hack-analysis-uranium-finance-april-2021/
- https://raw.githubusercontent.com/SunWeb3Sec/DeFiHackLabs/main/src/test/2021-04/Uranium_exp.sol
Last reviewed
- 2026-07-29 UTC