@@ -35,19 +35,25 @@ import {Position} from "./lib/Position.sol";
3535import {SafeCast} from "./lib/SafeCast.sol " ;
3636import {IERC20 } from "./interfaces/IERC20.sol " ;
3737import {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
3942contract 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
0 commit comments