Lab · the bug class behind RF-01
Small outputs, zero input
An exact-output swap works backwards: you name what you want out, and the pool computes what you must pay. Round that division down and, when the input token has few decimals, small outputs cost nothing.
A minimal pool written for this page, not Revert Finance’s code. The accepted finding is RF-01, medium, on Cantina.
Constant product, no feepool
- reserveIn
- 100000000 units1,000,000.00 GUSD
- reserveOut
- 1000000000000000000000000 wei1,000,000 DAI
The source
Bug · rounds down in use
function getAmountIn(
uint256 amountOut,
uint256 reserveIn,
uint256 reserveOut
) internal pure returns (uint256) {
return amountOut * reserveIn
/ (reserveOut - amountOut); // rounds down
}
Fix · rounds up in use
return Math.mulDiv(
amountOut,
reserveIn,
reserveOut - amountOut,
Math.Rounding.Ceil
);
Round in the pool’s favor. Rounding up costs the trader at most one unit. Rounding down costs the pool.
Impact
Rounding down undercharges by less than one unit of the input token per call. With GUSD’s two decimals, that means each call on this pool can take just under 0.01 DAI for nothing, and taking 1 DAI takes 101 free swaps. On Ethereum mainnet the gas for a swap usually costs more than that. It still breaks the pool’s promise that output is never free, which matters on cheap chains, at scale, and anywhere other code relies on it.
Stage 4 of 4 · one question
What is the largest amountOut (in wei) that costs
zero input on the initial pool?
The simulator will get you there, and so will the inequality. Reset first: every swap moves the edge.