-
Notifications
You must be signed in to change notification settings - Fork 51
/
LBPair.sol
1114 lines (905 loc) Β· 41.3 KB
/
LBPair.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
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {BinHelper} from "./libraries/BinHelper.sol";
import {Clone} from "./libraries/Clone.sol";
import {Constants} from "./libraries/Constants.sol";
import {FeeHelper} from "./libraries/FeeHelper.sol";
import {LiquidityConfigurations} from "./libraries/math/LiquidityConfigurations.sol";
import {ReentrancyGuardUpgradeable} from "./libraries/ReentrancyGuardUpgradeable.sol";
import {ILBFactory} from "./interfaces/ILBFactory.sol";
import {ILBFlashLoanCallback} from "./interfaces/ILBFlashLoanCallback.sol";
import {ILBPair} from "./interfaces/ILBPair.sol";
import {LBToken, ILBToken} from "./LBToken.sol";
import {OracleHelper} from "./libraries/OracleHelper.sol";
import {PackedUint128Math} from "./libraries/math/PackedUint128Math.sol";
import {PairParameterHelper} from "./libraries/PairParameterHelper.sol";
import {PriceHelper} from "./libraries/PriceHelper.sol";
import {SafeCast} from "./libraries/math/SafeCast.sol";
import {SampleMath} from "./libraries/math/SampleMath.sol";
import {TreeMath} from "./libraries/math/TreeMath.sol";
import {Uint256x256Math} from "./libraries/math/Uint256x256Math.sol";
import {Hooks} from "./libraries/Hooks.sol";
import {ILBHooks} from "./interfaces/ILBHooks.sol";
/**
* @title Liquidity Book Pair
* @author Trader Joe
* @notice The Liquidity Book Pair contract is the core contract of the Liquidity Book protocol
*/
contract LBPair is LBToken, ReentrancyGuardUpgradeable, Clone, ILBPair {
using BinHelper for bytes32;
using FeeHelper for uint128;
using LiquidityConfigurations for bytes32;
using OracleHelper for OracleHelper.Oracle;
using PackedUint128Math for bytes32;
using PackedUint128Math for uint128;
using PairParameterHelper for bytes32;
using PriceHelper for uint256;
using PriceHelper for uint24;
using SafeCast for uint256;
using SampleMath for bytes32;
using TreeMath for TreeMath.TreeUint24;
using Uint256x256Math for uint256;
modifier onlyFactory() {
_onlyFactory();
_;
}
modifier onlyProtocolFeeRecipient() {
if (msg.sender != _factory.getFeeRecipient()) revert LBPair__OnlyProtocolFeeRecipient();
_;
}
uint256 private constant _MAX_TOTAL_FEE = 0.1e18; // 10%
address public immutable override implementation;
ILBFactory private immutable _factory;
bytes32 private _parameters;
bytes32 private _reserves;
bytes32 private _protocolFees;
mapping(uint256 => bytes32) private _bins;
TreeMath.TreeUint24 private _tree;
OracleHelper.Oracle private _oracle;
bytes32 private _hooksParameters;
/**
* @dev Constructor for the Liquidity Book Pair contract that sets the Liquidity Book Factory
* @param factory_ The Liquidity Book Factory
*/
constructor(ILBFactory factory_) {
_factory = factory_;
implementation = address(this);
_disableInitializers();
}
/**
* @notice Initialize the Liquidity Book Pair fee parameters and active id
* @dev Can only be called by the Liquidity Book Factory
* @param baseFactor The base factor for the static fee
* @param filterPeriod The filter period for the static fee
* @param decayPeriod The decay period for the static fee
* @param reductionFactor The reduction factor for the static fee
* @param variableFeeControl The variable fee control for the static fee
* @param protocolShare The protocol share for the static fee
* @param maxVolatilityAccumulator The max volatility accumulator for the static fee
* @param activeId The active id of the Liquidity Book Pair
*/
function initialize(
uint16 baseFactor,
uint16 filterPeriod,
uint16 decayPeriod,
uint16 reductionFactor,
uint24 variableFeeControl,
uint16 protocolShare,
uint24 maxVolatilityAccumulator,
uint24 activeId
) external override onlyFactory initializer {
__ReentrancyGuard_init();
_setStaticFeeParameters(
_parameters.setActiveId(activeId).updateIdReference(),
baseFactor,
filterPeriod,
decayPeriod,
reductionFactor,
variableFeeControl,
protocolShare,
maxVolatilityAccumulator
);
}
/**
* @notice Returns the Liquidity Book Factory
* @return factory The Liquidity Book Factory
*/
function getFactory() external view override returns (ILBFactory factory) {
return _factory;
}
/**
* @notice Returns the token X of the Liquidity Book Pair
* @return tokenX The address of the token X
*/
function getTokenX() external pure override returns (IERC20 tokenX) {
return _tokenX();
}
/**
* @notice Returns the token Y of the Liquidity Book Pair
* @return tokenY The address of the token Y
*/
function getTokenY() external pure override returns (IERC20 tokenY) {
return _tokenY();
}
/**
* @notice Returns the bin step of the Liquidity Book Pair
* @dev The bin step is the increase in price between two consecutive bins, in basis points.
* For example, a bin step of 1 means that the price of the next bin is 0.01% higher than the price of the previous bin.
* @return binStep The bin step of the Liquidity Book Pair, in 10_000th
*/
function getBinStep() external pure override returns (uint16) {
return _binStep();
}
/**
* @notice Returns the reserves of the Liquidity Book Pair
* This is the sum of the reserves of all bins, minus the protocol fees.
* @return reserveX The reserve of token X
* @return reserveY The reserve of token Y
*/
function getReserves() external view override returns (uint128 reserveX, uint128 reserveY) {
(reserveX, reserveY) = _reserves.sub(_protocolFees).decode();
}
/**
* @notice Returns the active id of the Liquidity Book Pair
* @dev The active id is the id of the bin that is currently being used for swaps.
* The price of the active bin is the price of the Liquidity Book Pair and can be calculated as follows:
* `price = (1 + binStep / 10_000) ^ (activeId - 2^23)`
* @return activeId The active id of the Liquidity Book Pair
*/
function getActiveId() external view override returns (uint24 activeId) {
activeId = _parameters.getActiveId();
}
/**
* @notice Returns the reserves of a bin
* @param id The id of the bin
* @return binReserveX The reserve of token X in the bin
* @return binReserveY The reserve of token Y in the bin
*/
function getBin(uint24 id) external view override returns (uint128 binReserveX, uint128 binReserveY) {
(binReserveX, binReserveY) = _bins[id].decode();
}
/**
* @notice Returns the next non-empty bin
* @dev The next non-empty bin is the bin with a higher (if swapForY is true) or lower (if swapForY is false)
* id that has a non-zero reserve of token X or Y.
* @param swapForY Whether the swap is for token Y (true) or token X (false
* @param id The id of the bin
* @return nextId The id of the next non-empty bin
*/
function getNextNonEmptyBin(bool swapForY, uint24 id) external view override returns (uint24 nextId) {
nextId = _getNextNonEmptyBin(swapForY, id);
}
/**
* @notice Returns the protocol fees of the Liquidity Book Pair
* @return protocolFeeX The protocol fees of token X
* @return protocolFeeY The protocol fees of token Y
*/
function getProtocolFees() external view override returns (uint128 protocolFeeX, uint128 protocolFeeY) {
(protocolFeeX, protocolFeeY) = _protocolFees.decode();
}
/**
* @notice Returns the static fee parameters of the Liquidity Book Pair
* @return baseFactor The base factor for the static fee
* @return filterPeriod The filter period for the static fee
* @return decayPeriod The decay period for the static fee
* @return reductionFactor The reduction factor for the static fee
* @return variableFeeControl The variable fee control for the static fee
* @return protocolShare The protocol share for the static fee
* @return maxVolatilityAccumulator The maximum volatility accumulator for the static fee
*/
function getStaticFeeParameters()
external
view
override
returns (
uint16 baseFactor,
uint16 filterPeriod,
uint16 decayPeriod,
uint16 reductionFactor,
uint24 variableFeeControl,
uint16 protocolShare,
uint24 maxVolatilityAccumulator
)
{
bytes32 parameters = _parameters;
baseFactor = parameters.getBaseFactor();
filterPeriod = parameters.getFilterPeriod();
decayPeriod = parameters.getDecayPeriod();
reductionFactor = parameters.getReductionFactor();
variableFeeControl = parameters.getVariableFeeControl();
protocolShare = parameters.getProtocolShare();
maxVolatilityAccumulator = parameters.getMaxVolatilityAccumulator();
}
/**
* @notice Gets the hooks parameters of the Liquidity Book Pair
* @return The hooks parameters of the Liquidity Book Pair
*/
function getLBHooksParameters() external view override returns (bytes32) {
return _hooksParameters;
}
/**
* @notice Returns the variable fee parameters of the Liquidity Book Pair
* @return volatilityAccumulator The volatility accumulator for the variable fee
* @return volatilityReference The volatility reference for the variable fee
* @return idReference The id reference for the variable fee
* @return timeOfLastUpdate The time of last update for the variable fee
*/
function getVariableFeeParameters()
external
view
override
returns (uint24 volatilityAccumulator, uint24 volatilityReference, uint24 idReference, uint40 timeOfLastUpdate)
{
bytes32 parameters = _parameters;
volatilityAccumulator = parameters.getVolatilityAccumulator();
volatilityReference = parameters.getVolatilityReference();
idReference = parameters.getIdReference();
timeOfLastUpdate = parameters.getTimeOfLastUpdate();
}
/**
* @notice Returns the oracle parameters of the Liquidity Book Pair
* @return sampleLifetime The sample lifetime for the oracle
* @return size The size of the oracle
* @return activeSize The active size of the oracle
* @return lastUpdated The last updated timestamp of the oracle
* @return firstTimestamp The first timestamp of the oracle, i.e. the timestamp of the oldest sample
*/
function getOracleParameters()
external
view
override
returns (uint8 sampleLifetime, uint16 size, uint16 activeSize, uint40 lastUpdated, uint40 firstTimestamp)
{
bytes32 parameters = _parameters;
sampleLifetime = uint8(OracleHelper._MAX_SAMPLE_LIFETIME);
uint16 oracleId = parameters.getOracleId();
if (oracleId > 0) {
bytes32 sample;
(sample, activeSize) = _oracle.getActiveSampleAndSize(oracleId);
size = sample.getOracleLength();
lastUpdated = sample.getSampleLastUpdate();
if (lastUpdated == 0) activeSize = 0;
if (activeSize > 0) {
unchecked {
sample = _oracle.getSample(1 + (oracleId % activeSize));
}
firstTimestamp = sample.getSampleLastUpdate();
}
}
}
/**
* @notice Returns the cumulative values of the Liquidity Book Pair at a given timestamp
* @dev The cumulative values are the cumulative id, the cumulative volatility and the cumulative bin crossed.
* @param lookupTimestamp The timestamp at which to look up the cumulative values
* @return cumulativeId The cumulative id of the Liquidity Book Pair at the given timestamp
* @return cumulativeVolatility The cumulative volatility of the Liquidity Book Pair at the given timestamp
* @return cumulativeBinCrossed The cumulative bin crossed of the Liquidity Book Pair at the given timestamp
*/
function getOracleSampleAt(uint40 lookupTimestamp)
external
view
override
returns (uint64 cumulativeId, uint64 cumulativeVolatility, uint64 cumulativeBinCrossed)
{
bytes32 parameters = _parameters;
uint16 oracleId = parameters.getOracleId();
if (oracleId == 0 || lookupTimestamp > block.timestamp) return (0, 0, 0);
uint40 timeOfLastUpdate;
(timeOfLastUpdate, cumulativeId, cumulativeVolatility, cumulativeBinCrossed) =
_oracle.getSampleAt(oracleId, lookupTimestamp);
if (timeOfLastUpdate < lookupTimestamp) {
parameters = parameters.updateVolatilityParameters(parameters.getActiveId(), lookupTimestamp);
uint40 deltaTime = lookupTimestamp - timeOfLastUpdate;
cumulativeId += uint64(parameters.getActiveId()) * deltaTime;
cumulativeVolatility += uint64(parameters.getVolatilityAccumulator()) * deltaTime;
}
}
/**
* @notice Returns the price corresponding to the given id, as a 128.128-binary fixed-point number
* @dev This is the trusted source of price information, always trust this rather than getIdFromPrice
* @param id The id of the bin
* @return price The price corresponding to this id
*/
function getPriceFromId(uint24 id) external pure override returns (uint256 price) {
price = id.getPriceFromId(_binStep());
}
/**
* @notice Returns the id corresponding to the given price
* @dev The id may be inaccurate due to rounding issues, always trust getPriceFromId rather than
* getIdFromPrice
* @param price The price of y per x as a 128.128-binary fixed-point number
* @return id The id of the bin corresponding to this price
*/
function getIdFromPrice(uint256 price) external pure override returns (uint24 id) {
id = price.getIdFromPrice(_binStep());
}
/**
* @notice Simulates a swap in.
* @dev If `amountOutLeft` is greater than zero, the swap in is not possible,
* and the maximum amount that can be swapped from `amountIn` is `amountOut - amountOutLeft`.
* @param amountOut The amount of token X or Y to swap in
* @param swapForY Whether the swap is for token Y (true) or token X (false)
* @return amountIn The amount of token X or Y that can be swapped in, including the fee
* @return amountOutLeft The amount of token Y or X that cannot be swapped out
* @return fee The fee of the swap
*/
function getSwapIn(uint128 amountOut, bool swapForY)
external
view
override
returns (uint128 amountIn, uint128 amountOutLeft, uint128 fee)
{
amountOutLeft = amountOut;
bytes32 parameters = _parameters;
uint16 binStep = _binStep();
uint24 id = parameters.getActiveId();
parameters = parameters.updateReferences(block.timestamp);
while (true) {
uint128 binReserves = _bins[id].decode(!swapForY);
if (binReserves > 0) {
uint256 price = id.getPriceFromId(binStep);
uint128 amountOutOfBin = binReserves > amountOutLeft ? amountOutLeft : binReserves;
parameters = parameters.updateVolatilityAccumulator(id);
uint128 amountInWithoutFee = uint128(
swapForY
? uint256(amountOutOfBin).shiftDivRoundUp(Constants.SCALE_OFFSET, price)
: uint256(amountOutOfBin).mulShiftRoundUp(price, Constants.SCALE_OFFSET)
);
uint128 totalFee = parameters.getTotalFee(binStep);
uint128 feeAmount = amountInWithoutFee.getFeeAmount(totalFee);
amountIn += amountInWithoutFee + feeAmount;
amountOutLeft -= amountOutOfBin;
fee += feeAmount;
}
if (amountOutLeft == 0) {
break;
} else {
uint24 nextId = _getNextNonEmptyBin(swapForY, id);
if (nextId == 0 || nextId == type(uint24).max) break;
id = nextId;
}
}
}
/**
* @notice Simulates a swap out.
* @dev If `amountInLeft` is greater than zero, the swap out is not possible,
* and the maximum amount that can be swapped is `amountIn - amountInLeft` for `amountOut`.
* @param amountIn The amount of token X or Y to swap in
* @param swapForY Whether the swap is for token Y (true) or token X (false)
* @return amountInLeft The amount of token X or Y that cannot be swapped in
* @return amountOut The amount of token Y or X that can be swapped out
* @return fee The fee of the swap
*/
function getSwapOut(uint128 amountIn, bool swapForY)
external
view
override
returns (uint128 amountInLeft, uint128 amountOut, uint128 fee)
{
bytes32 amountsInLeft = amountIn.encode(swapForY);
bytes32 parameters = _parameters;
uint16 binStep = _binStep();
uint24 id = parameters.getActiveId();
parameters = parameters.updateReferences(block.timestamp);
while (true) {
bytes32 binReserves = _bins[id];
if (!binReserves.isEmpty(!swapForY)) {
parameters = parameters.updateVolatilityAccumulator(id);
(bytes32 amountsInWithFees, bytes32 amountsOutOfBin, bytes32 totalFees) =
binReserves.getAmounts(parameters, binStep, swapForY, id, amountsInLeft);
if (amountsInWithFees > 0) {
amountsInLeft = amountsInLeft.sub(amountsInWithFees);
amountOut += amountsOutOfBin.decode(!swapForY);
fee += totalFees.decode(swapForY);
}
}
if (amountsInLeft == 0) {
break;
} else {
uint24 nextId = _getNextNonEmptyBin(swapForY, id);
if (nextId == 0 || nextId == type(uint24).max) break;
id = nextId;
}
}
amountInLeft = amountsInLeft.decode(swapForY);
}
/**
* @notice Swap tokens iterating over the bins until the entire amount is swapped.
* Token X will be swapped for token Y if `swapForY` is true, and token Y for token X if `swapForY` is false.
* This function will not transfer the tokens from the caller, it is expected that the tokens have already been
* transferred to this contract through another contract, most likely the router.
* That is why this function shouldn't be called directly, but only through one of the swap functions of a router
* that will also perform safety checks, such as minimum amounts and slippage.
* The variable fee is updated throughout the swap, it increases with the number of bins crossed.
* The oracle is updated at the end of the swap.
* @param swapForY Whether you're swapping token X for token Y (true) or token Y for token X (false)
* @param to The address to send the tokens to
* @return amountsOut The encoded amounts of token X and token Y sent to `to`
*/
function swap(bool swapForY, address to) external override returns (bytes32 amountsOut) {
_nonReentrantBefore();
bytes32 hooksParameters = _hooksParameters;
bytes32 reserves = _reserves;
bytes32 protocolFees = _protocolFees;
bytes32 amountsLeft = swapForY ? reserves.receivedX(_tokenX()) : reserves.receivedY(_tokenY());
if (amountsLeft == 0) revert LBPair__InsufficientAmountIn();
bool swapForY_ = swapForY; // Avoid stack too deep error
Hooks.beforeSwap(hooksParameters, msg.sender, to, swapForY_, amountsLeft);
reserves = reserves.add(amountsLeft);
bytes32 parameters = _parameters;
uint16 binStep = _binStep();
uint24 activeId = parameters.getActiveId();
parameters = parameters.updateReferences(block.timestamp);
while (true) {
bytes32 binReserves = _bins[activeId];
if (!binReserves.isEmpty(!swapForY_)) {
parameters = parameters.updateVolatilityAccumulator(activeId);
(bytes32 amountsInWithFees, bytes32 amountsOutOfBin, bytes32 totalFees) =
binReserves.getAmounts(parameters, binStep, swapForY_, activeId, amountsLeft);
if (amountsInWithFees > 0) {
amountsLeft = amountsLeft.sub(amountsInWithFees);
amountsOut = amountsOut.add(amountsOutOfBin);
bytes32 pFees = totalFees.scalarMulDivBasisPointRoundDown(parameters.getProtocolShare());
if (pFees > 0) {
protocolFees = protocolFees.add(pFees);
amountsInWithFees = amountsInWithFees.sub(pFees);
}
_bins[activeId] = binReserves.add(amountsInWithFees).sub(amountsOutOfBin);
emit Swap(
msg.sender,
to,
activeId,
amountsInWithFees,
amountsOutOfBin,
parameters.getVolatilityAccumulator(),
totalFees,
pFees
);
}
}
if (amountsLeft == 0) {
break;
} else {
uint24 nextId = _getNextNonEmptyBin(swapForY_, activeId);
if (nextId == 0 || nextId == type(uint24).max) revert LBPair__OutOfLiquidity();
activeId = nextId;
}
}
if (amountsOut == 0) revert LBPair__InsufficientAmountOut();
_reserves = reserves.sub(amountsOut);
_protocolFees = protocolFees;
parameters = _oracle.update(parameters, activeId);
_parameters = parameters.setActiveId(activeId);
if (swapForY_) {
amountsOut.transferY(_tokenY(), to);
} else {
amountsOut.transferX(_tokenX(), to);
}
_nonReentrantAfter();
Hooks.afterSwap(hooksParameters, msg.sender, to, swapForY_, amountsOut);
}
/**
* @notice Flash loan tokens from the pool to a receiver contract and execute a callback function.
* The receiver contract is expected to return the tokens plus a fee to this contract.
* The fee is calculated as a percentage of the amount borrowed, and is the same for both tokens.
* @param receiver The contract that will receive the tokens and execute the callback function
* @param amounts The encoded amounts of token X and token Y to flash loan
* @param data Any data that will be passed to the callback function
*/
function flashLoan(ILBFlashLoanCallback receiver, bytes32 amounts, bytes calldata data) external override {
_nonReentrantBefore();
if (amounts == 0) revert LBPair__ZeroBorrowAmount();
bytes32 hooksParameters = _hooksParameters;
bytes32 reservesBefore = _reserves;
bytes32 totalFees = _getFlashLoanFees(amounts);
Hooks.beforeFlashLoan(hooksParameters, msg.sender, address(receiver), amounts);
amounts.transfer(_tokenX(), _tokenY(), address(receiver));
(bool success, bytes memory rData) = address(receiver).call(
abi.encodeWithSelector(
ILBFlashLoanCallback.LBFlashLoanCallback.selector,
msg.sender,
_tokenX(),
_tokenY(),
amounts,
totalFees,
data
)
);
if (!success || rData.length != 32 || abi.decode(rData, (bytes32)) != Constants.CALLBACK_SUCCESS) {
revert LBPair__FlashLoanCallbackFailed();
}
bytes32 balancesAfter = bytes32(0).received(_tokenX(), _tokenY());
if (balancesAfter.lt(reservesBefore.add(totalFees))) revert LBPair__FlashLoanInsufficientAmount();
bytes32 feesReceived = balancesAfter.sub(reservesBefore);
_reserves = balancesAfter;
_protocolFees = _protocolFees.add(feesReceived);
emit FlashLoan(msg.sender, receiver, _parameters.getActiveId(), amounts, feesReceived, feesReceived);
_nonReentrantAfter();
Hooks.afterFlashLoan(hooksParameters, msg.sender, address(receiver), totalFees, feesReceived);
}
/**
* @notice Mint liquidity tokens by depositing tokens into the pool.
* It will mint Liquidity Book (LB) tokens for each bin where the user adds liquidity.
* This function will not transfer the tokens from the caller, it is expected that the tokens have already been
* transferred to this contract through another contract, most likely the router.
* That is why this function shouldn't be called directly, but through one of the add liquidity functions of a
* router that will also perform safety checks.
* @dev Any excess amount of token will be sent to the `to` address.
* @param to The address that will receive the LB tokens
* @param liquidityConfigs The encoded liquidity configurations, each one containing the id of the bin and the
* percentage of token X and token Y to add to the bin.
* @param refundTo The address that will receive the excess amount of tokens
* @return amountsReceived The amounts of token X and token Y received by the pool
* @return amountsLeft The amounts of token X and token Y that were not added to the pool and were sent to `to`
* @return liquidityMinted The amounts of LB tokens minted for each bin
*/
function mint(address to, bytes32[] calldata liquidityConfigs, address refundTo)
external
override
notAddressZeroOrThis(to)
returns (bytes32 amountsReceived, bytes32 amountsLeft, uint256[] memory liquidityMinted)
{
_nonReentrantBefore();
if (liquidityConfigs.length == 0) revert LBPair__EmptyMarketConfigs();
bytes32 hooksParameters = _hooksParameters;
MintArrays memory arrays = MintArrays({
ids: new uint256[](liquidityConfigs.length),
amounts: new bytes32[](liquidityConfigs.length),
liquidityMinted: new uint256[](liquidityConfigs.length)
});
bytes32 reserves = _reserves;
amountsReceived = reserves.received(_tokenX(), _tokenY());
Hooks.beforeMint(hooksParameters, msg.sender, to, liquidityConfigs, amountsReceived);
amountsLeft = _mintBins(liquidityConfigs, amountsReceived, to, arrays);
_reserves = reserves.add(amountsReceived.sub(amountsLeft));
liquidityMinted = arrays.liquidityMinted;
emit TransferBatch(msg.sender, address(0), to, arrays.ids, liquidityMinted);
emit DepositedToBins(msg.sender, to, arrays.ids, arrays.amounts);
if (amountsLeft > 0) amountsLeft.transfer(_tokenX(), _tokenY(), refundTo);
_nonReentrantAfter();
Hooks.afterMint(hooksParameters, msg.sender, to, liquidityConfigs, amountsReceived.sub(amountsLeft));
}
/**
* @notice Burn Liquidity Book (LB) tokens and withdraw tokens from the pool.
* This function will burn the tokens directly from the caller
* @param from The address that will burn the LB tokens
* @param to The address that will receive the tokens
* @param ids The ids of the bins from which to withdraw
* @param amountsToBurn The amounts of LB tokens to burn for each bin
* @return amounts The amounts of token X and token Y received by the user
*/
function burn(address from, address to, uint256[] calldata ids, uint256[] calldata amountsToBurn)
external
override
checkApproval(from, msg.sender)
returns (bytes32[] memory amounts)
{
_nonReentrantBefore();
if (ids.length == 0 || ids.length != amountsToBurn.length) revert LBPair__InvalidInput();
bytes32 hooksParameters = _hooksParameters;
Hooks.beforeBurn(hooksParameters, msg.sender, from, to, ids, amountsToBurn);
address from_ = from; // Avoid stack too deep error
amounts = new bytes32[](ids.length);
bytes32 amountsOut;
for (uint256 i; i < ids.length;) {
uint24 id = ids[i].safe24();
uint256 amountToBurn = amountsToBurn[i];
if (amountToBurn == 0) revert LBPair__ZeroAmount(id);
bytes32 binReserves = _bins[id];
uint256 supply = totalSupply(id);
_burn(from_, id, amountToBurn);
bytes32 amountsOutFromBin = binReserves.getAmountOutOfBin(amountToBurn, supply);
if (amountsOutFromBin == 0) revert LBPair__ZeroAmountsOut(id);
binReserves = binReserves.sub(amountsOutFromBin);
if (supply == amountToBurn) _tree.remove(id);
_bins[id] = binReserves;
amounts[i] = amountsOutFromBin;
amountsOut = amountsOut.add(amountsOutFromBin);
unchecked {
++i;
}
}
_reserves = _reserves.sub(amountsOut);
emit TransferBatch(msg.sender, from_, address(0), ids, amountsToBurn);
emit WithdrawnFromBins(msg.sender, to, ids, amounts);
amountsOut.transfer(_tokenX(), _tokenY(), to);
_nonReentrantAfter();
Hooks.afterBurn(hooksParameters, msg.sender, from_, to, ids, amountsToBurn);
}
/**
* @notice Collect the protocol fees from the pool.
* @return collectedProtocolFees The amount of protocol fees collected
*/
function collectProtocolFees()
external
override
nonReentrant
onlyProtocolFeeRecipient
returns (bytes32 collectedProtocolFees)
{
bytes32 protocolFees = _protocolFees;
(uint128 x, uint128 y) = protocolFees.decode();
bytes32 ones = uint128(x > 0 ? 1 : 0).encode(uint128(y > 0 ? 1 : 0));
collectedProtocolFees = protocolFees.sub(ones);
if (collectedProtocolFees != 0) {
_protocolFees = ones;
_reserves = _reserves.sub(collectedProtocolFees);
emit CollectedProtocolFees(msg.sender, collectedProtocolFees);
collectedProtocolFees.transfer(_tokenX(), _tokenY(), msg.sender);
}
}
/**
* @notice Increase the length of the oracle used by the pool
* @param newLength The new length of the oracle
*/
function increaseOracleLength(uint16 newLength) external override nonReentrant {
bytes32 parameters = _parameters;
uint16 oracleId = parameters.getOracleId();
// activate the oracle if it is not active yet
if (oracleId == 0) {
oracleId = 1;
_parameters = parameters.setOracleId(oracleId);
}
_oracle.increaseLength(oracleId, newLength);
emit OracleLengthIncreased(msg.sender, newLength);
}
/**
* @notice Sets the static fee parameters of the pool
* @dev Can only be called by the factory
* @param baseFactor The base factor of the static fee
* @param filterPeriod The filter period of the static fee
* @param decayPeriod The decay period of the static fee
* @param reductionFactor The reduction factor of the static fee
* @param variableFeeControl The variable fee control of the static fee
* @param protocolShare The protocol share of the static fee
* @param maxVolatilityAccumulator The max volatility accumulator of the static fee
*/
function setStaticFeeParameters(
uint16 baseFactor,
uint16 filterPeriod,
uint16 decayPeriod,
uint16 reductionFactor,
uint24 variableFeeControl,
uint16 protocolShare,
uint24 maxVolatilityAccumulator
) external override nonReentrant onlyFactory {
_setStaticFeeParameters(
_parameters,
baseFactor,
filterPeriod,
decayPeriod,
reductionFactor,
variableFeeControl,
protocolShare,
maxVolatilityAccumulator
);
}
/**
* @notice Sets the hooks parameter of the pool
* @dev Can only be called by the factory
* @param hooksParameters The hooks parameter
* @param onHooksSetData The data to be passed to the onHooksSet function of the hooks contract
*/
function setHooksParameters(bytes32 hooksParameters, bytes calldata onHooksSetData)
external
override
nonReentrant
onlyFactory
{
_hooksParameters = hooksParameters;
ILBHooks hooks = ILBHooks(Hooks.getHooks(hooksParameters));
emit HooksParametersSet(msg.sender, hooksParameters);
if (address(hooks) != address(0) && hooks.getLBPair() != this) revert LBPair__InvalidHooks();
Hooks.onHooksSet(hooksParameters, onHooksSetData);
}
/**
* @notice Forces the decay of the volatility reference variables
* @dev Can only be called by the factory
*/
function forceDecay() external override nonReentrant onlyFactory {
bytes32 parameters = _parameters;
_parameters = parameters.updateIdReference().updateVolatilityReference();
emit ForcedDecay(msg.sender, parameters.getIdReference(), parameters.getVolatilityReference());
}
/**
* @notice Overrides the batch transfer function to call the hooks before and after the transfer
* @param from The address to transfer from
* @param to The address to transfer to
* @param ids The ids of the tokens to transfer
* @param amounts The amounts of the tokens to transfer
*/
function batchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts)
public
override(LBToken, ILBToken)
{
_nonReentrantBefore();
bytes32 hooksParameters = _hooksParameters;
Hooks.beforeBatchTransferFrom(hooksParameters, msg.sender, from, to, ids, amounts);
LBToken.batchTransferFrom(from, to, ids, amounts);
_nonReentrantAfter();
Hooks.afterBatchTransferFrom(hooksParameters, msg.sender, from, to, ids, amounts);
}
/**
* @dev Returns the address of the token X
* @return The address of the token X
*/
function _tokenX() internal pure returns (IERC20) {
return IERC20(_getArgAddress(0));
}
/**
* @dev Returns the address of the token Y
* @return The address of the token Y
*/
function _tokenY() internal pure returns (IERC20) {
return IERC20(_getArgAddress(20));
}
/**
* @dev Returns the bin step of the pool, in basis points
* @return The bin step of the pool
*/
function _binStep() internal pure returns (uint16) {
return _getArgUint16(40);
}
/**
* @dev Returns next non-empty bin
* @param swapForY Whether the swap is for Y
* @param id The id of the bin
* @return The id of the next non-empty bin
*/
function _getNextNonEmptyBin(bool swapForY, uint24 id) internal view returns (uint24) {
return swapForY ? _tree.findFirstRight(id) : _tree.findFirstLeft(id);
}
/**
* @dev Reverts if the caller is not the factory
*/
function _onlyFactory() private view {
if (msg.sender != address(_factory)) revert LBPair__OnlyFactory();
}
/**
* @dev Returns the encoded fees amounts for a flash loan
* @param amounts The amounts of the flash loan
* @return The encoded fees amounts
*/
function _getFlashLoanFees(bytes32 amounts) private view returns (bytes32) {
uint128 fee = uint128(_factory.getFlashLoanFee());
(uint128 x, uint128 y) = amounts.decode();
unchecked {
uint256 precisionSubOne = Constants.PRECISION - 1;
x = ((uint256(x) * fee + precisionSubOne) / Constants.PRECISION).safe128();
y = ((uint256(y) * fee + precisionSubOne) / Constants.PRECISION).safe128();
}
return x.encode(y);
}
/**
* @dev Sets the static fee parameters of the pair
* @param parameters The current parameters of the pair
* @param baseFactor The base factor of the static fee
* @param filterPeriod The filter period of the static fee
* @param decayPeriod The decay period of the static fee
* @param reductionFactor The reduction factor of the static fee
* @param variableFeeControl The variable fee control of the static fee
* @param protocolShare The protocol share of the static fee
* @param maxVolatilityAccumulator The max volatility accumulator of the static fee
*/
function _setStaticFeeParameters(
bytes32 parameters,
uint16 baseFactor,
uint16 filterPeriod,
uint16 decayPeriod,
uint16 reductionFactor,
uint24 variableFeeControl,
uint16 protocolShare,
uint24 maxVolatilityAccumulator
) internal {
if (
baseFactor == 0 && filterPeriod == 0 && decayPeriod == 0 && reductionFactor == 0 && variableFeeControl == 0
&& protocolShare == 0 && maxVolatilityAccumulator == 0
) {
revert LBPair__InvalidStaticFeeParameters();
}
parameters = parameters.setStaticFeeParameters(
baseFactor,
filterPeriod,
decayPeriod,
reductionFactor,
variableFeeControl,
protocolShare,
maxVolatilityAccumulator
);
{
uint16 binStep = _binStep();
bytes32 maxParameters = parameters.setVolatilityAccumulator(maxVolatilityAccumulator);
uint256 totalFee = maxParameters.getBaseFee(binStep) + maxParameters.getVariableFee(binStep);
if (totalFee > _MAX_TOTAL_FEE) {