Skip to content

Commit ba05340

Browse files
committed
add slippage protection
1 parent f5f0f0a commit ba05340

File tree

2 files changed

+165
-10
lines changed

2 files changed

+165
-10
lines changed

src/swapModules/SwapAlgebra.sol

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ contract SwapAlgebra is ISwap, Ownable {
2727
uint160 internal constant MIN_SQRT_RATIO = 4295128739;
2828
uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;
2929

30+
// Maximum allowed slippage (1%)
31+
uint256 private constant MAX_SLIPPAGE = 100;
32+
uint256 private constant BASIS_POINTS_DENOMINATOR = 10000;
33+
3034
// Algebra Factory address
3135
IAlgebraFactory public immutable algebraFactory;
3236

@@ -111,6 +115,15 @@ contract SwapAlgebra is ISwap, Ownable {
111115
return amounts[0];
112116
}
113117

118+
/**
119+
* @dev Calculates the minimum amount out based on the given slippage tolerance
120+
* @param amountOut The expected amount out
121+
* @return The minimum amount out that satisfies the MAX_SLIPPAGE tolerance
122+
*/
123+
function calculateMinAmountOutWithSlippage(uint256 amountOut) public pure returns (uint256) {
124+
return (amountOut * (BASIS_POINTS_DENOMINATOR - MAX_SLIPPAGE)) / BASIS_POINTS_DENOMINATOR;
125+
}
126+
114127
/// @notice Returns a valid `limitSqrtPrice` just beyond the current price
115128
/// @param zeroForOne If true, token0 → token1; else token1 → token0
116129
function getValidLimitSqrtPrice(bool zeroForOne) internal pure returns (uint160) {
@@ -153,6 +166,12 @@ contract SwapAlgebra is ISwap, Ownable {
153166
uint256 gasFee,
154167
string memory tokenName
155168
) public returns (uint256 amountOut) {
169+
// Record initial balances for slippage calculation
170+
uint256 initialOutBalance = 0;
171+
if (tokenOut != address(0)) {
172+
initialOutBalance = IERC20(tokenOut).balanceOf(address(this));
173+
}
174+
156175
// Transfer tokens from sender to this contract
157176
IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
158177

@@ -234,8 +253,24 @@ contract SwapAlgebra is ISwap, Ownable {
234253
emit SwapWithIntermediary(tokenIn, intermediaryToken, tokenOut, amountInForMainSwap, amountOut);
235254
}
236255

256+
// Calculate actual balance change to account for any existing balance
257+
uint256 finalOutBalance = IERC20(tokenOut).balanceOf(address(this));
258+
uint256 actualAmountOut = finalOutBalance - initialOutBalance;
259+
260+
// Apply slippage check using constant MAX_SLIPPAGE (1%)
261+
uint256 minRequiredAmount = calculateMinAmountOutWithSlippage(amountOut);
262+
263+
// Verify we received at least the minimum amount expected
264+
require(actualAmountOut >= minRequiredAmount, "Slippage tolerance exceeded");
265+
266+
// Use the actual received amount for the transfer to the user
267+
uint256 amountToTransfer = actualAmountOut;
268+
237269
// Transfer output tokens to sender
238-
IERC20(tokenOut).safeTransfer(msg.sender, amountOut);
270+
IERC20(tokenOut).safeTransfer(msg.sender, amountToTransfer);
271+
272+
// Update the return value to match what was actually received and transferred
273+
amountOut = amountToTransfer;
239274
}
240275

241276
return amountOut;
@@ -260,7 +295,6 @@ contract SwapAlgebra is ISwap, Ownable {
260295

261296
IERC20(tokenIn).approve(pool, amountIn);
262297

263-
// (uint160 currentPrice, , , , , , ) = IAlgebraPool(pool).globalState();
264298
uint160 limitSqrtPrice = getValidLimitSqrtPrice(zeroToOne);
265299

266300
// Perform the swap

test/swapModules/SwapAlgebra.t.sol

Lines changed: 129 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,16 +111,32 @@ contract MockAlgebraPool is IAlgebraPool {
111111
address public token0;
112112
address public token1;
113113
address private immutable factory;
114+
bool public shouldApplySlippage;
115+
uint256 public slippageAmount; // How much to reduce the output by
116+
address public targetContract; // The contract we're testing (SwapAlgebra)
114117

115118
constructor(address _token0, address _token1, address _factory) {
116119
token0 = _token0;
117120
token1 = _token1;
118121
factory = _factory;
122+
shouldApplySlippage = false;
123+
slippageAmount = 0;
124+
}
125+
126+
// Set whether this pool should apply slippage to test slippage protection
127+
function setSlippage(bool _shouldApplySlippage, uint256 _slippageAmount) external {
128+
shouldApplySlippage = _shouldApplySlippage;
129+
slippageAmount = _slippageAmount;
130+
}
131+
132+
// Set the target contract that we're testing
133+
function setTargetContract(address _targetContract) external {
134+
targetContract = _targetContract;
119135
}
120136

121137
function globalState()
122138
external
123-
view
139+
pure
124140
returns (
125141
uint160 price,
126142
int24 tick,
@@ -134,22 +150,28 @@ contract MockAlgebraPool is IAlgebraPool {
134150
return (0, 0, 0, 0, 0, 0, true);
135151
}
136152

137-
function swap(address recipient, bool zeroToOne, int256 amountRequired, uint160 limitSqrtPrice, bytes calldata data)
153+
function swap(address recipient, bool zeroToOne, int256 amountRequired, uint160, bytes calldata data)
138154
external
139155
returns (int256 amount0, int256 amount1)
140156
{
141-
// Determine which token is being swapped in
157+
// Determine which token is being swapped in/out
142158
address tokenOut = zeroToOne ? token1 : token0;
143159

144160
// Calculate the amount in (positive) and out (negative)
145161
uint256 amountIn = uint256(amountRequired);
162+
uint256 amountOut = amountIn;
146163

147-
// Mock 1:1 swap for testing
164+
// Apply slippage if configured to do so
165+
if (shouldApplySlippage) {
166+
amountOut = amountIn - slippageAmount;
167+
}
168+
169+
// Mock swap with or without slippage
148170
if (zeroToOne) {
149171
amount0 = int256(amountIn); // Positive (tokens in)
150-
amount1 = -int256(amountIn); // Negative (tokens out)
172+
amount1 = -int256(amountOut); // Negative (tokens out)
151173
} else {
152-
amount0 = -int256(amountIn); // Negative (tokens out)
174+
amount0 = -int256(amountOut); // Negative (tokens out)
153175
amount1 = int256(amountIn); // Positive (tokens in)
154176
}
155177

@@ -158,10 +180,10 @@ contract MockAlgebraPool is IAlgebraPool {
158180

159181
// Check output token balance
160182
uint256 outTokenBalance = IERC20(tokenOut).balanceOf(address(this));
161-
require(outTokenBalance >= amountIn, "Insufficient output token balance");
183+
require(outTokenBalance >= amountOut, "Insufficient output token balance");
162184

163185
// Transfer output tokens to the recipient
164-
IERC20(tokenOut).transfer(recipient, amountIn);
186+
IERC20(tokenOut).transfer(recipient, amountOut);
165187
}
166188
}
167189

@@ -198,6 +220,20 @@ contract MockAlgebraFactory is IAlgebraFactory {
198220
}
199221
}
200222

223+
// Special mock contract to test the slippage protection directly
224+
contract MockSwapAlgebraForSlippage is SwapAlgebra {
225+
constructor(address _algebraFactory, address _uniswapV2Router, address _wzeta)
226+
SwapAlgebra(_algebraFactory, _uniswapV2Router, _wzeta)
227+
{}
228+
229+
// Function that directly tests the slippage protection
230+
function testSlippageProtection(uint256 expectedAmount, uint256 actualAmount) external pure {
231+
// This mimics the slippage check in the swap function
232+
uint256 minRequiredAmount = calculateMinAmountOutWithSlippage(expectedAmount);
233+
require(actualAmount >= minRequiredAmount, "Slippage tolerance exceeded");
234+
}
235+
}
236+
201237
contract SwapAlgebraTest is Test {
202238
SwapAlgebra public swapAlgebra;
203239
MockUniswapV2Router public mockUniswapV2Router;
@@ -432,4 +468,89 @@ contract SwapAlgebraTest is Test {
432468
vm.expectRevert("Invalid intermediary token address");
433469
swapAlgebra.setIntermediaryToken(TOKEN_NAME, address(0));
434470
}
471+
472+
function test_CalculateMinAmountOutWithSlippage() public view {
473+
// Test the calculation with different amounts
474+
uint256 amount1 = 1000 ether;
475+
uint256 amount2 = 1 ether;
476+
uint256 amount3 = 100;
477+
478+
// Using the 1% MAX_SLIPPAGE constant in the contract
479+
uint256 expectedMin1 = 990 ether; // 1000 - 1%
480+
uint256 expectedMin2 = 0.99 ether; // 1 - 1%
481+
uint256 expectedMin3 = 99; // 100 - 1%
482+
483+
assertEq(
484+
swapAlgebra.calculateMinAmountOutWithSlippage(amount1), expectedMin1, "Incorrect min amount for 1000 ether"
485+
);
486+
assertEq(
487+
swapAlgebra.calculateMinAmountOutWithSlippage(amount2), expectedMin2, "Incorrect min amount for 1 ether"
488+
);
489+
assertEq(swapAlgebra.calculateMinAmountOutWithSlippage(amount3), expectedMin3, "Incorrect min amount for 100");
490+
491+
// Test with 0 amount
492+
assertEq(swapAlgebra.calculateMinAmountOutWithSlippage(0), 0, "Incorrect min amount for 0");
493+
}
494+
495+
function test_SwapWithSlippageProtection() public {
496+
uint256 initialBalance = inputToken.balanceOf(user);
497+
uint256 swapAmount = AMOUNT;
498+
uint256 expectedOutput = swapAmount - GAS_FEE; // 1:1 swap with gas fee deduction
499+
500+
vm.prank(user);
501+
uint256 amountOut =
502+
swapAlgebra.swap(address(inputToken), address(outputToken), swapAmount, address(gasToken), GAS_FEE);
503+
504+
assertEq(amountOut, expectedOutput, "Incorrect output amount");
505+
assertEq(inputToken.balanceOf(user), initialBalance - swapAmount, "Input tokens not transferred from user");
506+
assertEq(outputToken.balanceOf(user), expectedOutput, "Output tokens not received by user");
507+
assertEq(gasToken.balanceOf(user), GAS_FEE, "Gas tokens not received by user");
508+
}
509+
510+
function test_RevertWhenSlippageExceeded() public {
511+
// Create a direct mock of SwapAlgebra just for testing slippage
512+
MockSwapAlgebraForSlippage mockSwap =
513+
new MockSwapAlgebraForSlippage(address(mockAlgebraFactory), address(mockUniswapV2Router), address(wzeta));
514+
515+
// Set up test values
516+
uint256 expectedAmount = 1000 ether;
517+
518+
// Calculate min amount based on 1% slippage
519+
uint256 minRequiredAmount = expectedAmount * 99 / 100;
520+
521+
// Test with amount just below the minimum (should fail)
522+
uint256 tooLowAmount = minRequiredAmount - 1;
523+
524+
// This should revert with slippage error
525+
vm.expectRevert("Slippage tolerance exceeded");
526+
mockSwap.testSlippageProtection(expectedAmount, tooLowAmount);
527+
528+
// This should succeed (amount is exactly at minimum)
529+
mockSwap.testSlippageProtection(expectedAmount, minRequiredAmount);
530+
531+
// This should succeed (amount is above minimum)
532+
mockSwap.testSlippageProtection(expectedAmount, minRequiredAmount + 1);
533+
}
534+
535+
function test_SwapSucceedsWithSlippageJustUnderLimit() public {
536+
uint256 swapAmount = AMOUNT;
537+
538+
// Configure the mock pool to apply slippage
539+
// Set slippage to be just under 1% of the output amount
540+
uint256 outputAmount = swapAmount - GAS_FEE;
541+
uint256 maxAllowedSlippage = outputAmount / 100; // 1%
542+
uint256 acceptableSlippage = maxAllowedSlippage - 1; // Just under 1%
543+
544+
MockAlgebraPool pool = MockAlgebraPool(mockAlgebraFactory.poolByPair(address(inputToken), address(outputToken)));
545+
pool.setSlippage(true, acceptableSlippage);
546+
547+
vm.prank(user);
548+
uint256 amountOut =
549+
swapAlgebra.swap(address(inputToken), address(outputToken), swapAmount, address(gasToken), GAS_FEE);
550+
551+
// Expected output is now reduced by the slippage
552+
uint256 expectedOutput = outputAmount - acceptableSlippage;
553+
assertEq(amountOut, expectedOutput, "Incorrect output amount");
554+
assertEq(outputToken.balanceOf(user), expectedOutput, "Output tokens not received by user");
555+
}
435556
}

0 commit comments

Comments
 (0)