
Alpha: This is one of these findings that comes up when you think about what happens if you call the same function multiple times with smaller amounts (applies to any kind of math, not just swaps).
In this case it's more of a business logic issue than for example exploiting a rounding issue, but the base thought process to get there is similar.
In the swap of geometric pair, the protocol matches the passive orders to the user's swap order.
However, users can swap at the best price by splitting swap into several small swap.
The reflect_curve function creates the params.limit numbers of ask and bid orders.
The bid orders' price are decreasing and ask orders' price are increasing.
By splitting the swap into several small swaps, users can swap at the best passive order.
Let's consider the following scenario:
Initial State
reserve of token1, token2: 1000, 1000
1 token1 = 10 usd, 1 token2 = 1 usd
swap_fee_rate is 0 for the simplicity
params.spacing: 1
params.limit: 3
params.ratio: 10%
Path 1
Alice is going to swap 300 token1 to token2 in one transaction:
reflect_curve function creates 5 passive bids: (price, size): (10, 100), (9, 111.11), (8, 125)
As a result, Alice receives 2712 amount of token2: 100 * 10 + 111.11 * 9 + 89 * 8 = 1000 + 1000 + 712 = 2712
Path 2
If Bob swaps 300 token1 using 3 times of swapping of 100 token1 in one transaction:
Bob receives 1000 token2 in one swap of 100 token1
Totally, Bob receives 3000 token2
As a result, Bob receives more tokens than Alice.
Conclusion
This finding would earn you $1633, and is not very difficult to come up with. Additionally, the explanation is not very in depth, but it does it job at reminding you to always think like an attacker and try to split txs in smaller txs.