Bit-exact: how we check our pool math against the chain
A quote is only as good as the router's copy of each pool's math. How we port AMM and tick math in exact integers and check it against the chain.
Every quote Tirio gives starts with a question asked over and over, for every pool and every amount it considers: if this pool receives this amount, how much does it pay out? The answer has to be right to the last unit of the token, not approximately right. This post explains why, how we reproduce each pool's arithmetic, and the layers of testing that tell us our numbers match the chain.
Why "close" is not good enough
Three reasons make exactness matter more than it first seems.
- The minimum output is enforced on chain. A quote that is slightly optimistic produces transactions that revert at tight slippage settings, or a minimum that is weaker than it should be.
- Errors compound across hops. On a multi-hop route, the output of one pool is the input of the next. A rounding error in the first pool shifts every amount after it.
- Settlement uses exact integers. The route that runs on chain passes precise amounts from pool to pool. Our plan has to use the same integers the contracts will see, or the plan and the transaction drift apart.
Exact here means bit-exact: the same integer result the pool's own code would produce, including which way each division rounds.
Reproducing the math, not approximating it
AMM contracts do all their arithmetic in unsigned integers, usually 256 bits wide, and every division rounds in a deliberate direction, typically in the pool's favour. Our engine ports that arithmetic rather than modelling it.
- Constant-product pools. The Uniswap V2 library computes an output as
amountIn × 997 × reserveOut ÷ (reserveIn × 1000 + amountIn × 997)for a 0.3 % fee, with integer division. PancakeSwap V2's router uses 9975 and 10000 for its 0.25 % fee. Forks often change the constant, so each one gets its own. - Concentrated-liquidity pools. Uniswap V3's core libraries for tick-to-price conversion, square-root price movement and swap steps are ported line by line, with fixed-width 256-bit integers, so overflow and rounding behave exactly as they do in the contract. A swap is walked tick by tick, crossing initialised ticks and updating liquidity the way the pool does. The same math covers PancakeSwap V3 and the concentrated-liquidity pools of Uniswap V4 and PancakeSwap Infinity.
- Exact output. Buying an exact amount needs the inverse functions: V2's
getAmountIn, which rounds up and adds one unit, and the exact-output step of the V3 swap. These are ported too.
Where a venue's pricing cannot be reproduced locally, for example an on-chain market maker or a hook with custom logic, we do not guess. Such a venue is priced on chain, and a quote with a leg that is not computed exactly is marked Estimated in the app.
Layer 1: known answers at the edges
The first line of tests uses fixed vectors: inputs with known outputs, concentrated on the places where integer math breaks. The minimum and maximum ticks, prices at tick boundaries and the price limits a swap must never cross. If a port is wrong anywhere, it is usually wrong at an edge.
Layer 2: differential testing and fuzzing
Fixed vectors only cover the cases someone thought of. The second layer compares two independent implementations of the same math across a very large number of random inputs.
When we moved the concentrated-liquidity math from arbitrary-precision integers to fixed-width 256-bit arithmetic for speed, we kept the old implementation as a reference. Every function of the new one is run against it on inputs generated by a fuzzer, which keeps hunting for any input where the two disagree. Any disagreement is a failing test. This is called differential testing, and it is one of the most effective ways to catch a subtle porting error, because the fuzzer explores combinations no human would write down.
Layer 3: the chain is the reference
Tests against our own code can only prove that two of our implementations agree. The final authority is the chain. So the third layer asks the live chain the same questions in the same block and compares the answers.
- Concentrated-liquidity pools are compared with the official quoter contracts, which run the pool's real swap code and report the result. For Uniswap V3-style pools that is QuoterV2, for Uniswap V4 its quoter, for PancakeSwap Infinity its CL quoter. Exact-output quotes are compared with the quoter's exact-output function.
- Constant-product pools are compared with the router's own
getAmountsOut. - The requirement is equality. Not within a basis point. The same integer.
Layer 4: running real swaps
Matching a quoter proves the math. It does not prove that a whole swap, with transfers and settlement, behaves as planned. So the last layer simulates complete swaps through Tirio's actual Router contract at the latest block and compares the simulated output with the engine's plan.
This layer has caught things the other three could not. One example: a V2 fork whose code seemed to point to a 0.2 % fee actually charged 0.3 %. The hint was misleading, and the simulation was right.
And then every quote is simulated anyway
All of this testing happens before the engine runs in production. In production there is one more safety net: every quote you sign is simulated as the complete transaction at the latest block before you see it, and the simulated output is what the quote shows. Our post on why every quote is simulated explains that step.
The testing is what lets the plan and the simulation agree. The simulation is what protects you when they do not.
What this buys you
- Quotes that hold. When the math is exact, the plan and the simulation agree unless the market moved or a venue behaved differently.
- Honest labels. A quote is marked Exact quote when every leg was computed exactly, and Estimated when a leg was not.
- Splits you can trust. Splitting an order across pools depends on comparing the marginal output of each one. If those numbers are off, the split is off.
If you want to see the result for yourself, the API returns every hop of a route with its pool, fee and exact integer amounts, and you can compare any of them with the pool's own quoter at the same block.