Skip to content

Commit e167462

Browse files
committed
swap w/o fees
1 parent 38c49d4 commit e167462

4 files changed

Lines changed: 362 additions & 9 deletions

File tree

src/CLAMM.sol

Lines changed: 179 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,25 @@ import {Position} from "./lib/Position.sol";
3535
import {SafeCast} from "./lib/SafeCast.sol";
3636
import {IERC20} from "./interfaces/IERC20.sol";
3737
import {SqrtPriceMath} from "./lib/SqrtPriceMath.sol";
38+
import {SwapMath} from "./lib/SwapMath.sol";
39+
import {BitMath} from "./lib/BitMath.sol";
40+
import {TickBitmap} from "./lib/TickBitmap.sol";
3841

3942
contract CLAMM {
4043
using SafeCast for int256;
44+
using SafeCast for uint256;
4145
using Position for mapping(bytes32 => Position.Info);
4246
using Position for Position.Info;
4347
using Tick for mapping(int24 => Tick.Info);
48+
using TickBitmap for mapping(int16 => uint256);
4449

4550
error CLAMM__AlreadyInitialized();
4651
error CLAMM__Locked();
4752
error CLAMM__AmountInvalid();
4853
error CLAMM__tickLowerGreaterThanOrEqualToUpper();
4954
error CLAMM__tickLowerLessThanMin();
5055
error CLAMM__tickUpperGreaterThanMax();
56+
error CLAMM_InvalidSqrtPriceLimit();
5157

5258
address public immutable i_token0;
5359
address public immutable i_token1;
@@ -68,9 +74,37 @@ contract CLAMM {
6874
int128 liquidityDelta;
6975
}
7076

77+
struct SwapCache {
78+
//liquidity at the beginning of the swap
79+
uint128 liquidityStart;
80+
}
81+
82+
// the top level state of the swap, the results of which are recorded in storage at the end
83+
struct SwapState {
84+
int256 amountSpecifiedRemaining; // the amount remaining to be swapped in/out of the inpur/output asset
85+
int256 amountCalculated; // the amount alreadt swapped out/in of the output/input asset
86+
uint160 sqrtPriceX96; // current sprt(price)
87+
int24 tick; // the tick associated with the current price
88+
uint256 feeGrowthGlobalX128; // the global fee growth of the input token
89+
uint128 liquidity; // the current liquidity in range
90+
}
91+
92+
struct StepComputation {
93+
uint160 sqrtPriceStartX96; // the sqrt price at the begining of the step
94+
int24 tickNext; // the next tick to swap to from the current tick in the swap direction
95+
bool initialized; // whether tickNext is initialized or not
96+
uint160 sqrtPriceNextX96; // sqrt(price) for the next tick (1/0)
97+
uint256 amountIn; // how much is being swapped in in this step
98+
uint256 amountOut; // how much is being swapped out
99+
uint256 feeAmount; // how much fee is being paid in
100+
}
101+
71102
Slot0 public slot0;
72103
uint128 public liquidity;
104+
uint256 public feeGrowthGlobal0X128;
105+
uint256 public feeGrowthGlobal1X128;
73106
mapping(int24 => Tick.Info) public ticks;
107+
mapping(int16 => uint256) public tickBitmap;
74108
mapping(bytes32 => Position.Info) public positions;
75109

76110
event Initialize(uint160 indexed sqrtPriceX96, int24 indexed tick);
@@ -151,11 +185,11 @@ contract CLAMM {
151185
amount0 = amount0Requested > position.tokensOwed0 ? position.tokensOwed0 : amount0Requested;
152186
amount1 = amount1Requested > position.tokensOwed1 ? position.tokensOwed1 : amount1Requested;
153187

154-
if(amount0 > 0) {
188+
if (amount0 > 0) {
155189
position.tokensOwed0 -= amount0;
156190
IERC20(i_token0).transfer(recipient, amount0);
157191
}
158-
if(amount1 > 0) {
192+
if (amount1 > 0) {
159193
position.tokensOwed1 -= amount1;
160194
IERC20(i_token1).transfer(recipient, amount1);
161195
}
@@ -179,23 +213,152 @@ contract CLAMM {
179213
amount1 = uint256(-amount1Int);
180214

181215
if (amount0 > 0 || amount1 > 0) {
182-
(position.tokensOwed0, position.tokensOwed1) = (
183-
position.tokensOwed0 + uint128(amount0),
184-
position.tokensOwed1 + uint128(amount1)
185-
);
216+
(position.tokensOwed0, position.tokensOwed1) =
217+
(position.tokensOwed0 + uint128(amount0), position.tokensOwed1 + uint128(amount1));
186218
}
187219
}
188220

189221
function swap(
190-
address recipient,
222+
address recipient,
191223
bool zeroForOne,
192224
int256 amountSpecified,
193225
uint160 sqrtPriceLimitX96,
194226
bytes calldata data
195-
) external returns(int256 amount0, int256 amount1) {
196-
227+
) external lock returns (int256 amount0, int256 amount1) {
228+
if (amountSpecified == 0) revert CLAMM__AmountInvalid();
229+
230+
Slot0 memory slot0Start = slot0;
231+
232+
if (zeroForOne) {
233+
if (sqrtPriceLimitX96 >= slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 <= TickMath.MIN_SQRT_RATIO) {
234+
revert CLAMM_InvalidSqrtPriceLimit();
235+
}
236+
} else {
237+
if (sqrtPriceLimitX96 <= slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 >= TickMath.MAX_SQRT_RATIO) {
238+
revert CLAMM_InvalidSqrtPriceLimit();
239+
}
240+
}
241+
242+
SwapCache memory cache = SwapCache({liquidityStart: liquidity});
243+
244+
bool exactInput = amountSpecified > 0;
245+
246+
SwapState memory state = SwapState({
247+
amountSpecifiedRemaining: amountSpecified,
248+
amountCalculated: 0,
249+
sqrtPriceX96: slot0Start.sqrtPriceX96,
250+
tick: slot0Start.tick,
251+
feeGrowthGlobalX128: zeroForOne ? feeGrowthGlobal0X128 : feeGrowthGlobal1X128,
252+
liquidity: cache.liquidityStart
253+
});
254+
255+
// continue swapping as long as there is liquidity and the amount specified is not zero
256+
//TODO
257+
while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != sqrtPriceLimitX96) {
258+
StepComputation memory step;
259+
260+
step.sqrtPriceStartX96 = state.sqrtPriceX96;
261+
262+
// TODO next initialized tick
263+
264+
(step.tickNext, step.initialized) =
265+
tickBitmap.nextInitializedTickWithinOneWord(state.tick, i_tickSpacing, zeroForOne);
266+
267+
//Bound tick next
268+
if (step.tickNext < TickMath.MIN_TICK) {
269+
step.tickNext = TickMath.MIN_TICK;
270+
} else if (step.tickNext > TickMath.MAX_TICK) {
271+
step.tickNext = TickMath.MAX_TICK;
272+
}
273+
274+
step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext);
275+
276+
(state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = SwapMath.computeSwapStep(
277+
state.sqrtPriceX96,
278+
(zeroForOne ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 : step.sqrtPriceNextX96 > sqrtPriceLimitX96)
279+
? sqrtPriceLimitX96
280+
: step.sqrtPriceNextX96,
281+
state.liquidity,
282+
state.amountSpecifiedRemaining,
283+
i_fee
284+
);
285+
286+
if (exactInput) {
287+
state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount).toInt256();
288+
state.amountCalculated -= step.amountOut.toInt256();
289+
} else {
290+
state.amountSpecifiedRemaining += step.amountOut.toInt256();
291+
state.amountCalculated += (step.amountIn + step.feeAmount).toInt256();
292+
}
293+
294+
// TODO update global fee tracker
295+
296+
//TODO
297+
if (state.sqrtPriceX96 == step.sqrtPriceNextX96) {
298+
if(step.initialized){
299+
int128 liquidityNet = ticks.cross(
300+
step.tickNext,
301+
zeroForOne
302+
? state.feeGrowthGlobalX128
303+
: feeGrowthGlobal0X128,
304+
zeroForOne
305+
? feeGrowthGlobal1X128
306+
: state.feeGrowthGlobalX128
307+
);
308+
309+
if(zeroForOne) {
310+
liquidityNet = -liquidityNet;
311+
}
312+
313+
state.liquidity = liquidity < 0
314+
? state.liquidity - uint128(-liquidityNet)
315+
: state.liquidity + uint128(liquidityNet);
316+
317+
}
318+
state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext;
319+
} else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) {
320+
state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96);
321+
}
322+
}
323+
324+
// if tick at the begining of the trade and afte the trade are not equal then Update the sqrtPticeX96 and tick
325+
if (state.tick != slot0Start.tick) {
326+
(slot0.sqrtPriceX96, slot0.tick) = (state.sqrtPriceX96, state.tick);
327+
} else {
328+
slot0.sqrtPriceX96 = state.sqrtPriceX96;
329+
}
330+
331+
//update liquidity
332+
if (cache.liquidityStart != state.liquidity) {
333+
liquidity = state.liquidity;
334+
}
335+
336+
//update fee growth
337+
if (zeroForOne) {
338+
feeGrowthGlobal0X128 = state.feeGrowthGlobalX128;
339+
} else {
340+
feeGrowthGlobal1X128 = state.feeGrowthGlobalX128;
341+
}
342+
343+
(amount0, amount1) = zeroForOne == exactInput
344+
? (amountSpecified - state.amountSpecifiedRemaining, state.amountCalculated)
345+
: (state.amountCalculated, amountSpecified - state.amountSpecifiedRemaining);
346+
347+
if (zeroForOne) {
348+
if (amount1 < 0) {
349+
IERC20(i_token1).transfer(recipient, uint256(-amount1));
350+
IERC20(i_token0).transferFrom(msg.sender, address(this), uint256(amount0));
351+
}
352+
} else {
353+
if (amount0 < 0) {
354+
IERC20(i_token0).transfer(recipient, uint256(-amount0));
355+
IERC20(i_token1).transferFrom(msg.sender, address(this), uint256(amount1));
356+
}
357+
}
197358
}
198359

360+
361+
199362
function _updatePostion(address owner, int24 tickUpper, int24 tickLower, int128 liquidityDelta, int24 tick)
200363
private
201364
returns (Position.Info storage position)
@@ -227,6 +390,13 @@ contract CLAMM {
227390
true, // upper tick
228391
i_maxLiquidityPerTick
229392
);
393+
394+
if (flippedLower) {
395+
tickBitmap.flipTick(tickLower, i_tickSpacing);
396+
}
397+
if (flippedUpper) {
398+
tickBitmap.flipTick(tickUpper, i_tickSpacing);
399+
}
230400
}
231401

232402
// TODO fees

src/lib/BitMath.sol

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.19;
3+
4+
// Copied from https://github.com/Uniswap/v4-core
5+
6+
/// @title BitMath
7+
/// @dev This library provides functionality for computing bit properties of an unsigned integer
8+
library BitMath {
9+
/// @notice Returns the index of the most significant bit of the number,
10+
/// where the least significant bit is at index 0 and the most significant bit is at index 255
11+
/// @dev The function satisfies the property:
12+
/// x >= 2**mostSignificantBit(x) and x < 2**(mostSignificantBit(x)+1)
13+
/// @param x the value for which to compute the most significant bit, must be greater than 0
14+
/// @return r the index of the most significant bit
15+
function mostSignificantBit(uint256 x) internal pure returns (uint8 r) {
16+
require(x > 0);
17+
18+
unchecked {
19+
if (x >= 0x100000000000000000000000000000000) {
20+
x >>= 128;
21+
r += 128;
22+
}
23+
if (x >= 0x10000000000000000) {
24+
x >>= 64;
25+
r += 64;
26+
}
27+
if (x >= 0x100000000) {
28+
x >>= 32;
29+
r += 32;
30+
}
31+
if (x >= 0x10000) {
32+
x >>= 16;
33+
r += 16;
34+
}
35+
if (x >= 0x100) {
36+
x >>= 8;
37+
r += 8;
38+
}
39+
if (x >= 0x10) {
40+
x >>= 4;
41+
r += 4;
42+
}
43+
if (x >= 0x4) {
44+
x >>= 2;
45+
r += 2;
46+
}
47+
if (x >= 0x2) r += 1;
48+
}
49+
}
50+
51+
/// @notice Returns the index of the least significant bit of the number,
52+
/// where the least significant bit is at index 0 and the most significant bit is at index 255
53+
/// @dev The function satisfies the property:
54+
/// (x & 2**leastSignificantBit(x)) != 0 and (x & (2**(leastSignificantBit(x)) - 1)) == 0)
55+
/// @param x the value for which to compute the least significant bit, must be greater than 0
56+
/// @return r the index of the least significant bit
57+
function leastSignificantBit(uint256 x) internal pure returns (uint8 r) {
58+
require(x > 0);
59+
60+
unchecked {
61+
r = 255;
62+
if (x & type(uint128).max > 0) {
63+
r -= 128;
64+
} else {
65+
x >>= 128;
66+
}
67+
if (x & type(uint64).max > 0) {
68+
r -= 64;
69+
} else {
70+
x >>= 64;
71+
}
72+
if (x & type(uint32).max > 0) {
73+
r -= 32;
74+
} else {
75+
x >>= 32;
76+
}
77+
if (x & type(uint16).max > 0) {
78+
r -= 16;
79+
} else {
80+
x >>= 16;
81+
}
82+
if (x & type(uint8).max > 0) {
83+
r -= 8;
84+
} else {
85+
x >>= 8;
86+
}
87+
if (x & 0xf > 0) {
88+
r -= 4;
89+
} else {
90+
x >>= 4;
91+
}
92+
if (x & 0x3 > 0) {
93+
r -= 2;
94+
} else {
95+
x >>= 2;
96+
}
97+
if (x & 0x1 > 0) r -= 1;
98+
}
99+
}
100+
}

src/lib/Tick.sol

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ library Tick {
5454
? info.liquidityNet + liquidityDelta
5555
: info.liquidityNet - liquidityDelta;
5656
}
57+
function cross(
58+
mapping(int24 => Info) storage self,
59+
int24 tick,
60+
uint256 feeGrowthGlobal0X128,
61+
uint256 feeGrowthGlobal1X128
62+
) internal returns(int128 liquidityNet) {
63+
Info storage info = self[tick];
64+
info.feeGrowthOutside0X128 = feeGrowthGlobal0X128 - info.feeGrowthOutside0X128;
65+
info.feeGrowthOutside1X128 = feeGrowthGlobal1X128 - info.feeGrowthOutside1X128;
66+
liquidityNet = info.liquidityNet;
67+
}
5768

5869
function clear(mapping(int24 => Info) storage self, int24 tick) internal {
5970
delete self[tick];

0 commit comments

Comments
 (0)