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.
- Invariant treats rounding direction as a solvency control, not a formatting detail.
- The `curve` crate splits swap handling into fees, checked arithmetic, and invariant math so each boundary case is handled explicitly.
- Exact-output swaps are protected by ceiling division, which makes the pool pay attention when ambiguity appears.
- The project’s real difference is defensive arithmetic around a familiar constant-product model.
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
`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.
| Concern | Naive AMM math | Invariant curve crate |
|---|---|---|
| Rounding policy | Usually implicit | Explicit, directional, and defended |
| Fee handling | One blended subtraction | Trading fee and protocol fee are separate |
| Overflow safety | Often assumed away | Checked arithmetic and large intermediates |
| Exact-output swaps | Easy to undercharge | Ceiling division protects the pool |
| Dust resistance | Depends on luck | Tiny trades are forced through defensively |
| Testing posture | Example-driven | Property-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.
// 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 question | If you round down | If you round up |
|---|---|---|
| Required input for exact output | Pool can be short-changed | Pool stays whole |
| Fees on micro-trades | Can collapse to zero | Remain collectible |
| LP protection | Weakens at the edges | Improves at the edges |
| Attack surface | Dust can accumulate value | Dust 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
| Dimension | Naive constant-product AMM | Invariant `curve` crate | Production-minded AMM math library |
|---|---|---|---|
| Main goal | Compute swaps | Protect solvency while computing swaps | Balance safety, composability, and clarity |
| Rounding discipline | Implicit or inconsistent | Explicit and directional | Explicit, usually with policy boundaries |
| Fee architecture | Often blended | Split into trading and protocol fees | Usually modular and auditable |
| Arithmetic model | May rely on smaller integers | Checked math with large intermediates | Checked math with careful overflow handling |
| Exact-output safety | Easy to undershoot | Uses ceiling behavior to defend the pool | Typically guards the same class of risk |
| Testing philosophy | Unit tests only | Property-based tests plus edge cases | Broader 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.