Incorrect trade-volume calculation
Description
DeltaSwap is intended to charge swap fees only if the recent trade volume is greater than a configurable percentage of the recent average liquidity. The two averages are computed as exponential moving averages (EMAs), giving more weight to more recent data, based on the difference between the block number when the EMA was last updated and the current block number.
The swap
function determines the fee to be applied (in basis points) with the following line of code (reformatted for readability)
fee = calcTradingFee(
_updateLiquidityTradedEMA(
DSMath.calcTradeLiquidity(amount0In, amount1In, _reserve0, _reserve1)
),
liquidityEMA
)
The line also computes and updates the trade liquidity EMA value, which is then passed to calcTradingFee
to compute the trading fee.
The problem lies in how calcTradeLiquidity
determines the trade liquidity amount, since it is assuming only one of amount0In
and amount1In
are greater than zero:
function calcTradeLiquidity(uint256 amount0, uint256 amount1, uint256 reserve0, uint256 reserve1) internal pure returns(uint256) {
if(amount0 > 0) {
return calcSingleSideLiquidity(amount0, reserve0, reserve1);
} else if(amount1 > 0) {
return calcSingleSideLiquidity(amount1, reserve1, reserve0);
}
return 0;
}
function calcSingleSideLiquidity(uint256 amount, uint256 reserve0, uint256 reserve1) internal pure returns(uint256) {
uint256 amount0 = amount / 2;
uint256 amount1 = amount0 * reserve1 / reserve0;
return DSMath.sqrt(amount0 * amount1);
}
Impact
A user could leverage this behavior to artificially lower the traded volume when trading token1
for token0
. To do so, they can perform a swap, making sure to supply an amount0Out
value that is slightly less than the maximum value that would still satisfy the K-invariant. Alternatively, they could also use the callback to transfer a small amount of token0
to the contract.
This would cause calcTradeLiquidity
to consider the small amount of input token0
to calculate the trade volume, returning an artificially low trade volume and also influencing the trade EMA less than intended.
Recommendations
Consider updating function DeltaSwapPair.swap()
to charge a trading fee that depends on max(amount0, amount1)
instead of only one of the values being greater than zero in function DSMath.calcTradeLiquidity()
.
Remediation
This issue has been acknowledged by GammaSwap Protocol, and a fix was implemented in commit 07e3464c↗..
The calcTradeLiquidity
function was changed to return the maximum trade of the two individually computed single sided trade liquidities. In addition, calcTradingFee
was updated to consider the greater number between the liquidity EMA and the current trade liquidity when determining whether fees should be charged.