The swap_exact_in
and swap_exact_out
functions in swap_operations.rs
perform token swaps using integer division without additional validation for rounding losses. As a result, when the input amount is too small relative to the reserves, the computed output can round down to zero (amount_out = 0
), leading to direct loss of user funds. Even for moderate swaps, the use of floor division causes truncation of fractional values, leading to unaccounted slippage and potential exploit scenarios.
In both swap_exact_in
and swap_exact_out
, the output amount is calculated using the constant product formula:
This uses integer division (checked_div
), effectively applying a floor operation that discards any fractional part of the division result. The contract does not check whether amount_out == 0
nor does it require a minimum input to ensure meaningful output.
When amount_in
is small compared to reserve_in
, this calculation can result in amount_out == 0
, causing the user to send tokens and receive nothing. Even when amount_out > 0
, the truncation introduces unaccounted loss.
Loss of funds for small swaps: Users performing small swaps may receive zero tokens in return.
Rounding loss for moderate swaps: Even medium-sized swaps incur additional hidden slippage due to truncation.
Exploitability: An attacker can repeatedly submit micro-swaps (e.g., amount_in = 1
), accumulating tokens in one reserve without giving any in return, skewing the pool.
Denial of Service: Such manipulations can destabilize the pool, leading to extreme slippage and discouraging further use.
With reserve_A = 500
, reserve_B = 500
, and amount_in = 1
:
User sends 1 token and receives nothing.
With reserve_A = 1,000
, reserve_B = 1,000
, and amount_in = 50
:
User should receive 47.619 tokens, but gets only 47 due to truncation.
Deploy the contract without rounding protections.
Initialize pool with reserve_A = 500
, reserve_B = 500
.
Execute swap_exact_in(amount_in = 1)
.
Observe that amount_out = 0
. User receives nothing and pool becomes 501 A / 500 B.
Execute swap_exact_in(amount_in = 50)
on the same pool.
Observe amount_out = 47
, while a full-precision calculation would yield ≈ 47.619.
Manual review.
Reject swaps with zero output:
Add InsufficientOutputAmount
to AmmError
.
Require minimum amount_in
relative to reserve:
Define AmountBelowMinimum
in AmmError
.
User interface warning: Inform users that small swaps may result in zero output due to rounding.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.