Invariant: The Rust AMM That Treats Rounding Like an Attack Surface

Inside the `curve` crate, where fee math, checked arithmetic, and ceiling division work together to keep constant-product swaps solvent.

8 min read • View on GitHub • More from belivenn

A balance scale built from gears and coins, with a tiny dust trade on one side and a locked vault of arithmetic safeguards on the other. The image explains that in this AMM, even tiny rounding choices can shift value toward or away from the pool.
The core idea is not just swap math. It is defensive math that makes microscopic edge cases hard to exploit.
Key Takeaways

Why a swap needs a defense system

Most AMM explainers start with the formula. This one starts with the leak. In a constant-product pool, the danger is not the big trade you can see. It is the tiny mismatch at the edge of integer math, where a floor instead of a ceiling can quietly transfer value over and over again.

That is the design choice that makes the `curve` crate interesting. It does not just compute swaps. It tries to make every ambiguous step land in the pool’s favor, so rounding, fees, and overflow become explicit policy instead of accidental behavior.

The swap pipeline, step by step

The code is organized as a pipeline. Intent enters on the left, fees are stripped in the middle, and invariant math settles the trade on the right.

`calculator.rs` is the conductor. It separates trade direction from math, then hands off to the fee logic before calling the constant-product engine. That division matters because it makes the rules readable: first define what the user wants, then decide what fees apply, then compute the swap under the invariant.

// Conceptual flow
intent -> fees -> constant_product -> result

// The important part is the order.
// Fees are not an afterthought.
// Rounding is not a display choice.
// Both are part of the security model.

Fees are not one thing

The repo separates trading fee and protocol fee, which is exactly the sort of detail that signals a production-minded design. If fees are merged too early, you lose clarity about who receives what. If they are merged too late, you risk miscounting the pre-fee amount and letting dust slip through.

ConcernNaive AMM mathInvariant curve crate
Rounding policyUsually implicitExplicit, directional, and defended
Fee handlingOne blended subtractionTrading fee and protocol fee are separate
Overflow safetyOften assumed awayChecked arithmetic and large intermediates
Exact-output swapsEasy to underchargeCeiling division protects the pool
Dust resistanceDepends on luckTiny trades are forced through defensively
Testing postureExample-drivenProperty-based testing is part of the story

The standout primitive here is `calculate_pre_fee_amount`. It reverse-engineers what a trader must provide before fees are taken. That sounds small. It is not. In DeFi, the difference between "what the user asked for" and "what the pool must receive" is where the defensive work lives.

Constant product, but hardened

The curve itself is familiar. The hard part is how the crate applies it. Base-input swaps can use one rounding path. Base-output swaps must use another, because asking for an exact amount means the input side has to be rounded up. That protects solvency when the math is expressed in integers instead of floating points.

A close-up of a trade flowing through three precision valves. One branch diverts a fee stream, another feeds a constant-product engine, and the third exits as the user’s output. The image explains how the crate separates fee extraction from invariant calculation.
The fee split happens before the invariant is settled, which keeps the accounting legible and the swap outcome conservative.
// Exact-output swaps need ceiling division.
// If the user asks for a specific output, the input must be rounded up.
let required_input = checked_ceil_div(desired_output, effective_rate)?;

// That choice favors the pool.
// It closes the gap that would otherwise be created by integer math.

The real innovation is rounding direction

This is the conceptual center of the repo. Ceiling and floor are not cosmetic choices. They decide who benefits from ambiguity. In a naive implementation, the trader can occasionally win that ambiguity. In this one, the pool does.

That is why the article’s strongest claim is also the simplest: the crate treats rounding like an attack surface. Not every exploit is a dramatic bug. Sometimes it is just a thousand tiny favorable outcomes for the wrong side of the trade.

Rounding questionIf you round downIf you round up
Required input for exact outputPool can be short-changedPool stays whole
Fees on micro-tradesCan collapse to zeroRemain collectible
LP protectionWeakens at the edgesImproves at the edges
Attack surfaceDust can accumulate valueDust is absorbed by the pool

Why this feels built for production DeFi

The safety posture is everywhere. `U256` intermediates reduce overflow risk. Checked arithmetic keeps failures visible. Property-based tests suggest the authors are not only testing examples, but also trying to shake out invariant violations across many input shapes.

There are also signs of a young codebase, including debug-oriented traces in the calculator path. That does not weaken the architecture. It just means the repository looks like active engineering, not a frozen academic artifact.

How it compares

DimensionNaive constant-product AMMInvariant `curve` crateProduction-minded AMM math library
Main goalCompute swapsProtect solvency while computing swapsBalance safety, composability, and clarity
Rounding disciplineImplicit or inconsistentExplicit and directionalExplicit, usually with policy boundaries
Fee architectureOften blendedSplit into trading and protocol feesUsually modular and auditable
Arithmetic modelMay rely on smaller integersChecked math with large intermediatesChecked math with careful overflow handling
Exact-output safetyEasy to undershootUses ceiling behavior to defend the poolTypically guards the same class of risk
Testing philosophyUnit tests onlyProperty-based tests plus edge casesBroader invariant testing

The comparison that matters is not feature count. It is posture. A naive AMM asks whether the formula works. Invariant asks whether the formula still works when the numbers get ugly, tiny, or adversarial.