-
Notifications
You must be signed in to change notification settings - Fork 545
/
Copy pathTWAMM.sol
652 lines (568 loc) · 27.2 KB
/
TWAMM.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.15;
import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol";
import {TickBitmap} from "@uniswap/v4-core/contracts/libraries/TickBitmap.sol";
import {SqrtPriceMath} from "@uniswap/v4-core/contracts/libraries/SqrtPriceMath.sol";
import {FixedPoint96} from "@uniswap/v4-core/contracts/libraries/FixedPoint96.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/libraries/PoolId.sol";
import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol";
import {BaseHook} from "../../BaseHook.sol";
import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol";
import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol";
import {ITWAMM} from "../../interfaces/ITWAMM.sol";
import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol";
import {TransferHelper} from "../../libraries/TransferHelper.sol";
import {TwammMath} from "../../libraries/TWAMM/TwammMath.sol";
import {OrderPool} from "../../libraries/TWAMM/OrderPool.sol";
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/libraries/CurrencyLibrary.sol";
import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol";
import {PoolGetters} from "../../libraries/PoolGetters.sol";
contract TWAMM is BaseHook, ITWAMM {
using TransferHelper for IERC20Minimal;
using CurrencyLibrary for Currency;
using OrderPool for OrderPool.State;
using PoolIdLibrary for IPoolManager.PoolKey;
using TickMath for int24;
using TickMath for uint160;
using SafeCast for uint256;
using PoolGetters for IPoolManager;
using TickBitmap for mapping(int16 => uint256);
int256 internal constant MIN_DELTA = -1;
bool internal constant ZERO_FOR_ONE = true;
bool internal constant ONE_FOR_ZERO = false;
/// @notice Contains full state related to the TWAMM
/// @member lastVirtualOrderTimestamp Last timestamp in which virtual orders were executed
/// @member orderPool0For1 Order pool trading token0 for token1 of pool
/// @member orderPool1For0 Order pool trading token1 for token0 of pool
/// @member orders Mapping of orderId to individual orders on pool
struct State {
uint256 lastVirtualOrderTimestamp;
OrderPool.State orderPool0For1;
OrderPool.State orderPool1For0;
mapping(bytes32 => Order) orders;
}
/// @inheritdoc ITWAMM
uint256 public immutable expirationInterval;
// twammStates[poolId] => Twamm.State
mapping(PoolId => State) internal twammStates;
// tokensOwed[token][owner] => amountOwed
mapping(Currency => mapping(address => uint256)) public tokensOwed;
constructor(IPoolManager _poolManager, uint256 _expirationInterval) BaseHook(_poolManager) {
expirationInterval = _expirationInterval;
}
function getHooksCalls() public pure override returns (Hooks.Calls memory) {
return Hooks.Calls({
beforeInitialize: true,
afterInitialize: false,
beforeModifyPosition: true,
afterModifyPosition: false,
beforeSwap: true,
afterSwap: false,
beforeDonate: false,
afterDonate: false
});
}
function beforeInitialize(address, IPoolManager.PoolKey calldata key, uint160)
external
virtual
override
poolManagerOnly
returns (bytes4)
{
// one-time initialization enforced in PoolManager
initialize(_getTWAMM(key));
return BaseHook.beforeInitialize.selector;
}
function beforeModifyPosition(
address,
IPoolManager.PoolKey calldata key,
IPoolManager.ModifyPositionParams calldata
) external override poolManagerOnly returns (bytes4) {
executeTWAMMOrders(key);
return BaseHook.beforeModifyPosition.selector;
}
function beforeSwap(address, IPoolManager.PoolKey calldata key, IPoolManager.SwapParams calldata)
external
override
poolManagerOnly
returns (bytes4)
{
executeTWAMMOrders(key);
return BaseHook.beforeSwap.selector;
}
function lastVirtualOrderTimestamp(PoolId key) external view returns (uint256) {
return twammStates[key].lastVirtualOrderTimestamp;
}
function getOrder(IPoolManager.PoolKey calldata poolKey, OrderKey calldata orderKey)
external
view
returns (Order memory)
{
return _getOrder(twammStates[PoolId.wrap(keccak256(abi.encode(poolKey)))], orderKey);
}
function getOrderPool(IPoolManager.PoolKey calldata key, bool zeroForOne)
external
view
returns (uint256 sellRateCurrent, uint256 earningsFactorCurrent)
{
State storage twamm = _getTWAMM(key);
return zeroForOne
? (twamm.orderPool0For1.sellRateCurrent, twamm.orderPool0For1.earningsFactorCurrent)
: (twamm.orderPool1For0.sellRateCurrent, twamm.orderPool1For0.earningsFactorCurrent);
}
/// @notice Initialize TWAMM state
function initialize(State storage self) internal {
self.lastVirtualOrderTimestamp = block.timestamp;
}
struct CallbackData {
address sender;
IPoolManager.PoolKey key;
IPoolManager.SwapParams params;
}
/// @inheritdoc ITWAMM
function executeTWAMMOrders(IPoolManager.PoolKey memory key) public {
PoolId poolId = key.toId();
(uint160 sqrtPriceX96,,,,,) = poolManager.getSlot0(poolId);
State storage twamm = twammStates[poolId];
(bool zeroForOne, uint160 sqrtPriceLimitX96) = _executeTWAMMOrders(
twamm, poolManager, key, PoolParamsOnExecute(sqrtPriceX96, poolManager.getLiquidity(poolId))
);
if (sqrtPriceLimitX96 != 0 && sqrtPriceLimitX96 != sqrtPriceX96) {
poolManager.lock(abi.encode(key, IPoolManager.SwapParams(zeroForOne, type(int256).max, sqrtPriceLimitX96)));
}
}
/// @inheritdoc ITWAMM
function submitOrder(IPoolManager.PoolKey calldata key, OrderKey memory orderKey, uint256 amountIn)
external
returns (bytes32 orderId)
{
PoolId poolId = PoolId.wrap(keccak256(abi.encode(key)));
State storage twamm = twammStates[poolId];
executeTWAMMOrders(key);
uint256 sellRate;
unchecked {
// checks done in TWAMM library
uint256 duration = orderKey.expiration - block.timestamp;
sellRate = amountIn / duration;
orderId = _submitOrder(twamm, orderKey, sellRate);
IERC20Minimal(orderKey.zeroForOne ? Currency.unwrap(key.currency0) : Currency.unwrap(key.currency1))
.safeTransferFrom(msg.sender, address(this), sellRate * duration);
}
emit SubmitOrder(
poolId,
orderKey.owner,
orderKey.expiration,
orderKey.zeroForOne,
sellRate,
_getOrder(twamm, orderKey).earningsFactorLast
);
}
/// @notice Submits a new long term order into the TWAMM
/// @dev executeTWAMMOrders must be executed up to current timestamp before calling submitOrder
/// @param orderKey The OrderKey for the new order
function _submitOrder(State storage self, OrderKey memory orderKey, uint256 sellRate)
internal
returns (bytes32 orderId)
{
if (orderKey.owner != msg.sender) revert MustBeOwner(orderKey.owner, msg.sender);
if (self.lastVirtualOrderTimestamp == 0) revert NotInitialized();
if (orderKey.expiration <= block.timestamp) revert ExpirationLessThanBlocktime(orderKey.expiration);
if (sellRate == 0) revert SellRateCannotBeZero();
if (orderKey.expiration % expirationInterval != 0) revert ExpirationNotOnInterval(orderKey.expiration);
orderId = _orderId(orderKey);
if (self.orders[orderId].sellRate != 0) revert OrderAlreadyExists(orderKey);
OrderPool.State storage orderPool = orderKey.zeroForOne ? self.orderPool0For1 : self.orderPool1For0;
unchecked {
orderPool.sellRateCurrent += sellRate;
orderPool.sellRateEndingAtInterval[orderKey.expiration] += sellRate;
}
self.orders[orderId] = Order({sellRate: sellRate, earningsFactorLast: orderPool.earningsFactorCurrent});
}
/// @inheritdoc ITWAMM
function updateOrder(IPoolManager.PoolKey memory key, OrderKey memory orderKey, int256 amountDelta)
external
returns (uint256 tokens0Owed, uint256 tokens1Owed)
{
PoolId poolId = PoolId.wrap(keccak256(abi.encode(key)));
State storage twamm = twammStates[poolId];
executeTWAMMOrders(key);
// This call reverts if the caller is not the owner of the order
(uint256 buyTokensOwed, uint256 sellTokensOwed, uint256 newSellrate, uint256 newEarningsFactorLast) =
_updateOrder(twamm, orderKey, amountDelta);
if (orderKey.zeroForOne) {
tokens0Owed += sellTokensOwed;
tokens1Owed += buyTokensOwed;
} else {
tokens0Owed += buyTokensOwed;
tokens1Owed += sellTokensOwed;
}
tokensOwed[key.currency0][orderKey.owner] += tokens0Owed;
tokensOwed[key.currency1][orderKey.owner] += tokens1Owed;
if (amountDelta > 0) {
IERC20Minimal(orderKey.zeroForOne ? Currency.unwrap(key.currency0) : Currency.unwrap(key.currency1))
.safeTransferFrom(msg.sender, address(this), uint256(amountDelta));
}
emit UpdateOrder(
poolId, orderKey.owner, orderKey.expiration, orderKey.zeroForOne, newSellrate, newEarningsFactorLast
);
}
function _updateOrder(State storage self, OrderKey memory orderKey, int256 amountDelta)
internal
returns (uint256 buyTokensOwed, uint256 sellTokensOwed, uint256 newSellRate, uint256 earningsFactorLast)
{
Order storage order = _getOrder(self, orderKey);
OrderPool.State storage orderPool = orderKey.zeroForOne ? self.orderPool0For1 : self.orderPool1For0;
if (orderKey.owner != msg.sender) revert MustBeOwner(orderKey.owner, msg.sender);
if (order.sellRate == 0) revert OrderDoesNotExist(orderKey);
if (amountDelta != 0 && orderKey.expiration <= block.timestamp) revert CannotModifyCompletedOrder(orderKey);
unchecked {
uint256 earningsFactor = orderPool.earningsFactorCurrent - order.earningsFactorLast;
buyTokensOwed = (earningsFactor * order.sellRate) >> FixedPoint96.RESOLUTION;
earningsFactorLast = orderPool.earningsFactorCurrent;
order.earningsFactorLast = earningsFactorLast;
if (orderKey.expiration <= block.timestamp) {
delete self.orders[_orderId(orderKey)];
}
if (amountDelta != 0) {
uint256 duration = orderKey.expiration - block.timestamp;
uint256 unsoldAmount = order.sellRate * duration;
if (amountDelta == MIN_DELTA) amountDelta = -(unsoldAmount.toInt256());
int256 newSellAmount = unsoldAmount.toInt256() + amountDelta;
if (newSellAmount < 0) revert InvalidAmountDelta(orderKey, unsoldAmount, amountDelta);
newSellRate = uint256(newSellAmount) / duration;
if (amountDelta < 0) {
uint256 sellRateDelta = order.sellRate - newSellRate;
orderPool.sellRateCurrent -= sellRateDelta;
orderPool.sellRateEndingAtInterval[orderKey.expiration] -= sellRateDelta;
sellTokensOwed = uint256(-amountDelta);
} else {
uint256 sellRateDelta = newSellRate - order.sellRate;
orderPool.sellRateCurrent += sellRateDelta;
orderPool.sellRateEndingAtInterval[orderKey.expiration] += sellRateDelta;
}
if (newSellRate == 0) {
delete self.orders[_orderId(orderKey)];
} else {
order.sellRate = newSellRate;
}
}
}
}
/// @inheritdoc ITWAMM
function claimTokens(Currency token, address to, uint256 amountRequested)
external
returns (uint256 amountTransferred)
{
uint256 currentBalance = token.balanceOfSelf();
amountTransferred = tokensOwed[token][msg.sender];
if (amountRequested != 0 && amountRequested < amountTransferred) amountTransferred = amountRequested;
if (currentBalance < amountTransferred) amountTransferred = currentBalance; // to catch precision errors
tokensOwed[token][msg.sender] -= amountTransferred;
IERC20Minimal(Currency.unwrap(token)).safeTransfer(to, amountTransferred);
}
function lockAcquired(uint256, bytes calldata rawData) external override poolManagerOnly returns (bytes memory) {
(IPoolManager.PoolKey memory key, IPoolManager.SwapParams memory swapParams) =
abi.decode(rawData, (IPoolManager.PoolKey, IPoolManager.SwapParams));
BalanceDelta delta = poolManager.swap(key, swapParams);
if (swapParams.zeroForOne) {
if (delta.amount0() > 0) {
key.currency0.transfer(address(poolManager), uint256(uint128(delta.amount0())));
poolManager.settle(key.currency0);
}
if (delta.amount1() < 0) {
poolManager.take(key.currency1, address(this), uint256(uint128(-delta.amount1())));
}
} else {
if (delta.amount1() > 0) {
key.currency1.transfer(address(poolManager), uint256(uint128(delta.amount1())));
poolManager.settle(key.currency1);
}
if (delta.amount0() < 0) {
poolManager.take(key.currency0, address(this), uint256(uint128(-delta.amount0())));
}
}
return bytes("");
}
function _getTWAMM(IPoolManager.PoolKey memory key) private view returns (State storage) {
return twammStates[PoolId.wrap(keccak256(abi.encode(key)))];
}
struct PoolParamsOnExecute {
uint160 sqrtPriceX96;
uint128 liquidity;
}
/// @notice Executes all existing long term orders in the TWAMM
/// @param pool The relevant state of the pool
function _executeTWAMMOrders(
State storage self,
IPoolManager poolManager,
IPoolManager.PoolKey memory key,
PoolParamsOnExecute memory pool
) internal returns (bool zeroForOne, uint160 newSqrtPriceX96) {
if (!_hasOutstandingOrders(self)) {
self.lastVirtualOrderTimestamp = block.timestamp;
return (false, 0);
}
uint160 initialSqrtPriceX96 = pool.sqrtPriceX96;
uint256 prevTimestamp = self.lastVirtualOrderTimestamp;
uint256 nextExpirationTimestamp = prevTimestamp + (expirationInterval - (prevTimestamp % expirationInterval));
OrderPool.State storage orderPool0For1 = self.orderPool0For1;
OrderPool.State storage orderPool1For0 = self.orderPool1For0;
unchecked {
while (nextExpirationTimestamp <= block.timestamp) {
if (
orderPool0For1.sellRateEndingAtInterval[nextExpirationTimestamp] > 0
|| orderPool1For0.sellRateEndingAtInterval[nextExpirationTimestamp] > 0
) {
if (orderPool0For1.sellRateCurrent != 0 && orderPool1For0.sellRateCurrent != 0) {
pool = _advanceToNewTimestamp(
self,
poolManager,
key,
AdvanceParams(
expirationInterval,
nextExpirationTimestamp,
nextExpirationTimestamp - prevTimestamp,
pool
)
);
} else {
pool = _advanceTimestampForSinglePoolSell(
self,
poolManager,
key,
AdvanceSingleParams(
expirationInterval,
nextExpirationTimestamp,
nextExpirationTimestamp - prevTimestamp,
pool,
orderPool0For1.sellRateCurrent != 0
)
);
}
prevTimestamp = nextExpirationTimestamp;
}
nextExpirationTimestamp += expirationInterval;
if (!_hasOutstandingOrders(self)) break;
}
if (prevTimestamp < block.timestamp && _hasOutstandingOrders(self)) {
if (orderPool0For1.sellRateCurrent != 0 && orderPool1For0.sellRateCurrent != 0) {
pool = _advanceToNewTimestamp(
self,
poolManager,
key,
AdvanceParams(expirationInterval, block.timestamp, block.timestamp - prevTimestamp, pool)
);
} else {
pool = _advanceTimestampForSinglePoolSell(
self,
poolManager,
key,
AdvanceSingleParams(
expirationInterval,
block.timestamp,
block.timestamp - prevTimestamp,
pool,
orderPool0For1.sellRateCurrent != 0
)
);
}
}
}
self.lastVirtualOrderTimestamp = block.timestamp;
newSqrtPriceX96 = pool.sqrtPriceX96;
zeroForOne = initialSqrtPriceX96 > newSqrtPriceX96;
}
struct AdvanceParams {
uint256 expirationInterval;
uint256 nextTimestamp;
uint256 secondsElapsed;
PoolParamsOnExecute pool;
}
function _advanceToNewTimestamp(
State storage self,
IPoolManager poolManager,
IPoolManager.PoolKey memory poolKey,
AdvanceParams memory params
) private returns (PoolParamsOnExecute memory) {
uint160 finalSqrtPriceX96;
uint256 secondsElapsedX96 = params.secondsElapsed * FixedPoint96.Q96;
OrderPool.State storage orderPool0For1 = self.orderPool0For1;
OrderPool.State storage orderPool1For0 = self.orderPool1For0;
while (true) {
TwammMath.ExecutionUpdateParams memory executionParams = TwammMath.ExecutionUpdateParams(
secondsElapsedX96,
params.pool.sqrtPriceX96,
params.pool.liquidity,
orderPool0For1.sellRateCurrent,
orderPool1For0.sellRateCurrent
);
finalSqrtPriceX96 = TwammMath.getNewSqrtPriceX96(executionParams);
(bool crossingInitializedTick, int24 tick) =
_isCrossingInitializedTick(params.pool, poolManager, poolKey, finalSqrtPriceX96);
unchecked {
if (crossingInitializedTick) {
uint256 secondsUntilCrossingX96;
(params.pool, secondsUntilCrossingX96) = _advanceTimeThroughTickCrossing(
self,
poolManager,
poolKey,
TickCrossingParams(tick, params.nextTimestamp, secondsElapsedX96, params.pool)
);
secondsElapsedX96 = secondsElapsedX96 - secondsUntilCrossingX96;
} else {
(uint256 earningsFactorPool0, uint256 earningsFactorPool1) =
TwammMath.calculateEarningsUpdates(executionParams, finalSqrtPriceX96);
if (params.nextTimestamp % params.expirationInterval == 0) {
orderPool0For1.advanceToInterval(params.nextTimestamp, earningsFactorPool0);
orderPool1For0.advanceToInterval(params.nextTimestamp, earningsFactorPool1);
} else {
orderPool0For1.advanceToCurrentTime(earningsFactorPool0);
orderPool1For0.advanceToCurrentTime(earningsFactorPool1);
}
params.pool.sqrtPriceX96 = finalSqrtPriceX96;
break;
}
}
}
return params.pool;
}
struct AdvanceSingleParams {
uint256 expirationInterval;
uint256 nextTimestamp;
uint256 secondsElapsed;
PoolParamsOnExecute pool;
bool zeroForOne;
}
function _advanceTimestampForSinglePoolSell(
State storage self,
IPoolManager poolManager,
IPoolManager.PoolKey memory poolKey,
AdvanceSingleParams memory params
) private returns (PoolParamsOnExecute memory) {
OrderPool.State storage orderPool = params.zeroForOne ? self.orderPool0For1 : self.orderPool1For0;
uint256 sellRateCurrent = orderPool.sellRateCurrent;
uint256 amountSelling = sellRateCurrent * params.secondsElapsed;
uint256 totalEarnings;
while (true) {
uint160 finalSqrtPriceX96 = SqrtPriceMath.getNextSqrtPriceFromInput(
params.pool.sqrtPriceX96, params.pool.liquidity, amountSelling, params.zeroForOne
);
(bool crossingInitializedTick, int24 tick) =
_isCrossingInitializedTick(params.pool, poolManager, poolKey, finalSqrtPriceX96);
if (crossingInitializedTick) {
int128 liquidityNetAtTick = poolManager.getNetLiquidityAtTick(poolKey.toId(), tick);
uint160 initializedSqrtPrice = TickMath.getSqrtRatioAtTick(tick);
uint256 swapDelta0 = SqrtPriceMath.getAmount0Delta(
params.pool.sqrtPriceX96, initializedSqrtPrice, params.pool.liquidity, true
);
uint256 swapDelta1 = SqrtPriceMath.getAmount1Delta(
params.pool.sqrtPriceX96, initializedSqrtPrice, params.pool.liquidity, true
);
params.pool.liquidity = params.zeroForOne
? params.pool.liquidity - uint128(liquidityNetAtTick)
: params.pool.liquidity + uint128(-liquidityNetAtTick);
params.pool.sqrtPriceX96 = initializedSqrtPrice;
unchecked {
totalEarnings += params.zeroForOne ? swapDelta1 : swapDelta0;
amountSelling -= params.zeroForOne ? swapDelta0 : swapDelta1;
}
} else {
if (params.zeroForOne) {
totalEarnings += SqrtPriceMath.getAmount1Delta(
params.pool.sqrtPriceX96, finalSqrtPriceX96, params.pool.liquidity, true
);
} else {
totalEarnings += SqrtPriceMath.getAmount0Delta(
params.pool.sqrtPriceX96, finalSqrtPriceX96, params.pool.liquidity, true
);
}
uint256 accruedEarningsFactor = (totalEarnings * FixedPoint96.Q96) / sellRateCurrent;
if (params.nextTimestamp % params.expirationInterval == 0) {
orderPool.advanceToInterval(params.nextTimestamp, accruedEarningsFactor);
} else {
orderPool.advanceToCurrentTime(accruedEarningsFactor);
}
params.pool.sqrtPriceX96 = finalSqrtPriceX96;
break;
}
}
return params.pool;
}
struct TickCrossingParams {
int24 initializedTick;
uint256 nextTimestamp;
uint256 secondsElapsedX96;
PoolParamsOnExecute pool;
}
function _advanceTimeThroughTickCrossing(
State storage self,
IPoolManager poolManager,
IPoolManager.PoolKey memory poolKey,
TickCrossingParams memory params
) private returns (PoolParamsOnExecute memory, uint256) {
uint160 initializedSqrtPrice = params.initializedTick.getSqrtRatioAtTick();
uint256 secondsUntilCrossingX96 = TwammMath.calculateTimeBetweenTicks(
params.pool.liquidity,
params.pool.sqrtPriceX96,
initializedSqrtPrice,
self.orderPool0For1.sellRateCurrent,
self.orderPool1For0.sellRateCurrent
);
(uint256 earningsFactorPool0, uint256 earningsFactorPool1) = TwammMath.calculateEarningsUpdates(
TwammMath.ExecutionUpdateParams(
secondsUntilCrossingX96,
params.pool.sqrtPriceX96,
params.pool.liquidity,
self.orderPool0For1.sellRateCurrent,
self.orderPool1For0.sellRateCurrent
),
initializedSqrtPrice
);
self.orderPool0For1.advanceToCurrentTime(earningsFactorPool0);
self.orderPool1For0.advanceToCurrentTime(earningsFactorPool1);
unchecked {
// update pool
int128 liquidityNet = poolManager.getNetLiquidityAtTick(poolKey.toId(), params.initializedTick);
if (initializedSqrtPrice < params.pool.sqrtPriceX96) liquidityNet = -liquidityNet;
params.pool.liquidity = liquidityNet < 0
? params.pool.liquidity - uint128(-liquidityNet)
: params.pool.liquidity + uint128(liquidityNet);
params.pool.sqrtPriceX96 = initializedSqrtPrice;
}
return (params.pool, secondsUntilCrossingX96);
}
function _isCrossingInitializedTick(
PoolParamsOnExecute memory pool,
IPoolManager poolManager,
IPoolManager.PoolKey memory poolKey,
uint160 nextSqrtPriceX96
) internal view returns (bool crossingInitializedTick, int24 nextTickInit) {
// use current price as a starting point for nextTickInit
nextTickInit = pool.sqrtPriceX96.getTickAtSqrtRatio();
int24 targetTick = nextSqrtPriceX96.getTickAtSqrtRatio();
bool searchingLeft = nextSqrtPriceX96 < pool.sqrtPriceX96;
bool nextTickInitFurtherThanTarget = false; // initialize as false
// nextTickInit returns the furthest tick within one word if no tick within that word is initialized
// so we must keep iterating if we haven't reached a tick further than our target tick
while (!nextTickInitFurtherThanTarget) {
unchecked {
if (searchingLeft) nextTickInit -= 1;
}
(nextTickInit, crossingInitializedTick) = poolManager.getNextInitializedTickWithinOneWord(
poolKey.toId(), nextTickInit, poolKey.tickSpacing, searchingLeft
);
nextTickInitFurtherThanTarget = searchingLeft ? nextTickInit <= targetTick : nextTickInit > targetTick;
if (crossingInitializedTick == true) break;
}
if (nextTickInitFurtherThanTarget) crossingInitializedTick = false;
}
function _getOrder(State storage self, OrderKey memory key) internal view returns (Order storage) {
return self.orders[_orderId(key)];
}
function _orderId(OrderKey memory key) private pure returns (bytes32) {
return keccak256(abi.encode(key));
}
function _hasOutstandingOrders(State storage self) internal view returns (bool) {
return self.orderPool0For1.sellRateCurrent != 0 || self.orderPool1For0.sellRateCurrent != 0;
}
}