/
LiquidityPool.sol
1058 lines (979 loc) · 35.8 KB
/
LiquidityPool.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.0;
import "./Protocol.sol";
import "./PriceFeed.sol";
import "./VolatilityFeed.sol";
import "./tokens/ERC20.sol";
import "./utils/ReentrancyGuard.sol";
import "./libraries/BlackScholes.sol";
import "./libraries/CustomErrors.sol";
import "./libraries/AccessControl.sol";
import "./libraries/OptionsCompute.sol";
import "./libraries/SafeTransferLib.sol";
import "./interfaces/IAccounting.sol";
import "./interfaces/IOptionRegistry.sol";
import "./interfaces/IHedgingReactor.sol";
import "./interfaces/IPortfolioValuesFeed.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
/**
* @title Contract used as the Dynamic Hedging Vault for storing funds, issuing shares and processing options transactions
* @dev Interacts with the OptionRegistry for options behaviour, Interacts with hedging reactors for alternative derivatives
* Interacts with Handlers for periphary user options interactions. Interacts with Chainlink price feeds throughout.
* Interacts with Volatility Feed via getImpliedVolatility(), interacts with a chainlink PortfolioValues external adaptor
* oracle via PortfolioValuesFeed.
*/
contract LiquidityPool is ERC20, AccessControl, ReentrancyGuard, Pausable {
using PRBMathSD59x18 for int256;
using PRBMathUD60x18 for uint256;
///////////////////////////
/// immutable variables ///
///////////////////////////
// Protocol management contract
Protocol public immutable protocol;
// asset that denominates the strike price
address public immutable strikeAsset;
// asset that is used as the reference asset
address public immutable underlyingAsset;
// asset that is used for collateral asset
address public immutable collateralAsset;
/////////////////////////
/// dynamic variables ///
/////////////////////////
// amount of collateralAsset allocated as collateral
uint256 public collateralAllocated;
// ephemeral liabilities of the pool
int256 public ephemeralLiabilities;
// ephemeral delta of the pool
int256 public ephemeralDelta;
// epoch of the price per share round for deposits
uint256 public depositEpoch;
// epoch of the price per share round for withdrawals
uint256 public withdrawalEpoch;
// epoch PPS for deposits
mapping(uint256 => uint256) public depositEpochPricePerShare;
// epoch PPS for withdrawals
mapping(uint256 => uint256) public withdrawalEpochPricePerShare;
// deposit receipts for users
mapping(address => IAccounting.DepositReceipt) public depositReceipts;
// withdrawal receipts for users
mapping(address => IAccounting.WithdrawalReceipt) public withdrawalReceipts;
// pending deposits for a round - collateral denominated (collateral decimals)
uint256 public pendingDeposits;
// pending withdrawals for a round - DHV token e18 denominated
uint256 public pendingWithdrawals;
// withdrawal amount that has been executed and is pending completion. These funds are to be excluded from all book balances.
uint256 public partitionedFunds;
/////////////////////////////////////
/// governance settable variables ///
/////////////////////////////////////
// buffer of funds to not be used to write new options in case of margin requirements (as percentage - for 20% enter 2000)
uint256 public bufferPercentage = 5000;
// list of addresses for hedging reactors
address[] public hedgingReactors;
// max total supply of collateral, denominated in e18
uint256 public collateralCap = type(uint256).max;
// option issuance parameters
Types.OptionParams public optionParams;
// riskFreeRate as a percentage PRBMath Float. IE: 3% -> 0.03 * 10**18
uint256 public riskFreeRate;
// handlers who are approved to interact with options functionality
mapping(address => bool) public handler;
// is the purchase and sale of options paused
bool public isTradingPaused;
// max time to allow between oracle updates for an underlying and strike
uint256 public maxTimeDeviationThreshold = 600;
// max price difference to allow between oracle updates for an underlying and strike
uint256 public maxPriceDeviationThreshold = 1e18;
// keeper mapping
mapping(address => bool) public keeper;
//////////////////////////
/// constant variables ///
//////////////////////////
// BIPS
uint256 private constant MAX_BPS = 10_000;
/////////////////////////
/// structs && events ///
/////////////////////////
event DepositEpochExecuted(uint256 epoch);
event WithdrawalEpochExecuted(uint256 epoch);
event Withdraw(address recipient, uint256 amount, uint256 shares);
event Deposit(address recipient, uint256 amount, uint256 epoch);
event Redeem(address recipient, uint256 amount, uint256 epoch);
event InitiateWithdraw(address recipient, uint256 amount, uint256 epoch);
event WriteOption(address series, uint256 amount, uint256 premium, uint256 escrow, address buyer);
event RebalancePortfolioDelta(int256 deltaChange);
event TradingPaused();
event TradingUnpaused();
event SettleVault(
address series,
uint256 collateralReturned,
uint256 collateralLost,
address closer
);
event BuybackOption(
address series,
uint256 amount,
uint256 premium,
uint256 escrowReturned,
address seller
);
constructor(
address _protocol,
address _strikeAsset,
address _underlyingAsset,
address _collateralAsset,
uint256 rfr,
string memory name,
string memory symbol,
Types.OptionParams memory _optionParams,
address _authority
) ERC20(name, symbol, 18) AccessControl(IAuthority(_authority)) {
if (ERC20(_collateralAsset).decimals() > 18) {
revert CustomErrors.InvalidDecimals();
}
strikeAsset = _strikeAsset;
riskFreeRate = rfr;
underlyingAsset = _underlyingAsset;
collateralAsset = _collateralAsset;
protocol = Protocol(_protocol);
optionParams = _optionParams;
depositEpochPricePerShare[0] = 1e18;
withdrawalEpochPricePerShare[0] = 1e18;
depositEpoch++;
withdrawalEpoch++;
}
///////////////
/// setters ///
///////////////
function pause() external {
_onlyGuardian();
_pause();
}
function pauseUnpauseTrading(bool _pause) external {
_onlyGuardian();
isTradingPaused = _pause;
if (_pause) {
emit TradingPaused();
} else {
emit TradingUnpaused();
}
}
function unpause() external {
_onlyGuardian();
_unpause();
}
/**
* @notice set a new hedging reactor
* @param _reactorAddress append a new hedging reactor
* @dev only governance can call this function
*/
function setHedgingReactorAddress(address _reactorAddress) external {
_onlyGovernor();
if (_reactorAddress == address(0)) {
revert CustomErrors.InvalidAddress();
}
uint256 arrayLength = hedgingReactors.length;
for (uint256 i = 0; i < arrayLength; i++) {
if (hedgingReactors[i] == _reactorAddress) {
revert CustomErrors.ReactorAlreadyExists();
}
}
hedgingReactors.push(_reactorAddress);
SafeTransferLib.safeApprove(ERC20(collateralAsset), _reactorAddress, type(uint256).max);
}
/**
* @notice remove a new hedging reactor by index
* @param _index remove a hedging reactor
* @param _override whether to override whether the reactor is wound down
(THE REACTOR SHOULD BE WOUND DOWN SEPERATELY)
* @dev only governance can call this function
*/
function removeHedgingReactorAddress(uint256 _index, bool _override) external {
_onlyGovernor();
address[] memory hedgingReactors_ = hedgingReactors;
address reactorAddress = hedgingReactors_[_index];
if (!_override) {
IHedgingReactor reactor = IHedgingReactor(reactorAddress);
int256 delta = reactor.getDelta();
if (delta != 0) {
reactor.hedgeDelta(delta);
}
reactor.withdraw(type(uint256).max);
}
SafeTransferLib.safeApprove(ERC20(collateralAsset), reactorAddress, 0);
uint256 maxIndex = hedgingReactors_.length - 1;
for (uint256 i = _index; i < maxIndex; i++) {
hedgingReactors[i] = hedgingReactors_[i + 1];
}
hedgingReactors.pop();
}
function getHedgingReactors() external view returns (address[] memory) {
return hedgingReactors;
}
/**
* @notice update all optionParam variables for max and min strikes and max and
* min expiries for options that the DHV can issue
* @dev only management or above can call this function
*/
function setNewOptionParams(
uint128 _newMinCallStrike,
uint128 _newMaxCallStrike,
uint128 _newMinPutStrike,
uint128 _newMaxPutStrike,
uint128 _newMinExpiry,
uint128 _newMaxExpiry
) external {
_onlyManager();
optionParams.minCallStrikePrice = _newMinCallStrike;
optionParams.maxCallStrikePrice = _newMaxCallStrike;
optionParams.minPutStrikePrice = _newMinPutStrike;
optionParams.maxPutStrikePrice = _newMaxPutStrike;
optionParams.minExpiry = _newMinExpiry;
optionParams.maxExpiry = _newMaxExpiry;
}
/**
* @notice set the maximum collateral amount allowed in the pool
* @param _collateralCap of the collateral held
* @dev only governance can call this function
*/
function setCollateralCap(uint256 _collateralCap) external {
_onlyGovernor();
collateralCap = _collateralCap;
}
/**
* @notice update the liquidity pool buffer limit
* @param _bufferPercentage the minimum balance the liquidity pool must have as a percentage of collateral allocated to options. (for 20% enter 2000)
* @dev only governance can call this function
*/
function setBufferPercentage(uint256 _bufferPercentage) external {
_onlyGovernor();
bufferPercentage = _bufferPercentage;
}
/**
* @notice update the liquidity pool risk free rate
* @param _riskFreeRate the risk free rate of the market
*/
function setRiskFreeRate(uint256 _riskFreeRate) external {
_onlyGovernor();
riskFreeRate = _riskFreeRate;
}
/**
* @notice update the max oracle time deviation threshold
*/
function setMaxTimeDeviationThreshold(uint256 _maxTimeDeviationThreshold) external {
_onlyGovernor();
maxTimeDeviationThreshold = _maxTimeDeviationThreshold;
}
/**
* @notice update the max oracle price deviation threshold
*/
function setMaxPriceDeviationThreshold(uint256 _maxPriceDeviationThreshold) external {
_onlyGovernor();
maxPriceDeviationThreshold = _maxPriceDeviationThreshold;
}
/**
* @notice change the status of a handler
*/
function changeHandler(address _handler, bool auth) external {
_onlyGovernor();
if (_handler == address(0)) {
revert CustomErrors.InvalidAddress();
}
handler[_handler] = auth;
}
/**
* @notice change the status of a keeper
*/
function setKeeper(address _keeper, bool _auth) external {
_onlyGovernor();
if (_keeper == address(0)) {
revert CustomErrors.InvalidAddress();
}
keeper[_keeper] = _auth;
}
//////////////////////////////////////////////////////
/// access-controlled state changing functionality ///
//////////////////////////////////////////////////////
/**
* @notice function for hedging portfolio delta through external means
* @param delta the current portfolio delta
* @param reactorIndex the index of the reactor in the hedgingReactors array to use
*/
function rebalancePortfolioDelta(int256 delta, uint256 reactorIndex) external {
_onlyManager();
IHedgingReactor(hedgingReactors[reactorIndex]).hedgeDelta(delta);
emit RebalancePortfolioDelta(delta);
}
/**
* @notice adjust the collateral held in a specific vault because of health
* @param lpCollateralDifference amount of collateral taken from or given to the liquidity pool in collateral decimals
* @param addToLpBalance true if collateral is returned to liquidity pool, false if collateral is withdrawn from liquidity pool
* @dev called by the option registry only
*/
function adjustCollateral(uint256 lpCollateralDifference, bool addToLpBalance) external {
IOptionRegistry optionRegistry = _getOptionRegistry();
require(msg.sender == address(optionRegistry));
// assumes in collateral decimals
if (addToLpBalance) {
collateralAllocated -= lpCollateralDifference;
} else {
SafeTransferLib.safeApprove(
ERC20(collateralAsset),
address(optionRegistry),
lpCollateralDifference
);
collateralAllocated += lpCollateralDifference;
}
}
/**
* @notice closes an oToken vault, returning collateral (minus ITM option expiry value) back to the pool
* @param seriesAddress the address of the oToken vault to close
* @return collatReturned the amount of collateral returned to the liquidity pool, assumes in collateral decimals
*/
function settleVault(address seriesAddress) external returns (uint256) {
_isKeeper();
// get number of options in vault and collateral returned to recalculate our position without these options
// returns in collat decimals, collat decimals and e8
(, uint256 collatReturned, uint256 collatLost, ) = _getOptionRegistry().settle(seriesAddress);
emit SettleVault(seriesAddress, collatReturned, collatLost, msg.sender);
// if the vault expired ITM then when settled the oracle will still have accounted for it as a liability. When
// the settle happens the liability is wiped off as it is now accounted for in collateralAllocated but because the
// oracle doesn't know this yet we need to temporarily reduce the liability value.
_adjustVariables(collatReturned, collatLost, 0, false);
collateralAllocated -= collatLost;
return collatReturned;
}
/**
* @notice issue an option
* @param optionSeries the series detail of the option - strike decimals in e18
* @dev only callable by a handler contract
*/
function handlerIssue(Types.OptionSeries memory optionSeries) external returns (address) {
_isHandler();
// series strike in e18
return _issue(optionSeries, _getOptionRegistry());
}
/**
* @notice write an option that already exists
* @param optionSeries the series detail of the option - strike decimals in e8
* @param seriesAddress the series address of the oToken
* @param amount the number of options to write - in e18
* @param optionRegistry the registry used for options writing
* @param premium the premium of the option - in collateral decimals
* @param delta the delta of the option - in e18
* @param recipient the receiver of the option
* @dev only callable by a handler contract
*/
function handlerWriteOption(
Types.OptionSeries memory optionSeries,
address seriesAddress,
uint256 amount,
IOptionRegistry optionRegistry,
uint256 premium,
int256 delta,
address recipient
) external returns (uint256) {
_isTradingNotPaused();
_isHandler();
return
_writeOption(
optionSeries, // series strike in e8
seriesAddress,
amount, // in e18
optionRegistry,
premium, // in collat decimals
delta,
checkBuffer(), // in e6
recipient
);
}
/**
* @notice write an option that doesnt exist
* @param optionSeries the series detail of the option - strike decimals in e18
* @param amount the number of options to write - in e18
* @param premium the premium of the option - in collateral decimals
* @param delta the delta of the option - in e18
* @param recipient the receiver of the option
* @dev only callable by a handler contract
*/
function handlerIssueAndWriteOption(
Types.OptionSeries memory optionSeries,
uint256 amount,
uint256 premium,
int256 delta,
address recipient
) external returns (uint256, address) {
_isTradingNotPaused();
_isHandler();
IOptionRegistry optionRegistry = _getOptionRegistry();
// series strike passed in as e18
address seriesAddress = _issue(optionSeries, optionRegistry);
// series strike received in e8, retrieved from the option registry instead of
// using one in memory because formatStrikePrice might have slightly changed the
// strike
optionSeries = optionRegistry.getSeriesInfo(seriesAddress);
return (
_writeOption(
optionSeries, // strike in e8
seriesAddress,
amount, // in e18
optionRegistry,
premium, // in collat decimals
delta,
checkBuffer(), // in e6
recipient
),
seriesAddress
);
}
/**
* @notice buy back an option that already exists
* @param optionSeries the series detail of the option - strike decimals in e8
* @param amount the number of options to buyback - in e18
* @param optionRegistry the registry used for options writing
* @param seriesAddress the series address of the oToken
* @param premium the premium of the option - in collateral decimals
* @param delta the delta of the option - in e18
* @param seller the receiver of the option
* @dev only callable by a handler contract
*/
function handlerBuybackOption(
Types.OptionSeries memory optionSeries,
uint256 amount,
IOptionRegistry optionRegistry,
address seriesAddress,
uint256 premium,
int256 delta,
address seller
) external returns (uint256) {
_isTradingNotPaused();
_isHandler();
// strike passed in as e8
return
_buybackOption(optionSeries, amount, optionRegistry, seriesAddress, premium, delta, seller);
}
/**
* @notice reset the temporary portfolio and delta values that have been changed since the last oracle update
* @dev only callable by the portfolio values feed oracle contract
*/
function resetEphemeralValues() external {
require(msg.sender == address(_getPortfolioValuesFeed()));
delete ephemeralLiabilities;
delete ephemeralDelta;
}
/**
* @notice reset the temporary portfolio and delta values that have been changed since the last oracle update
* @dev this function must be called in order to execute an epoch calculation
*/
function pauseTradingAndRequest() external returns (bytes32) {
_isKeeper();
// pause trading
isTradingPaused = true;
emit TradingPaused();
// make an oracle request
return _getPortfolioValuesFeed().requestPortfolioData(underlyingAsset, strikeAsset);
}
/**
* @notice execute the epoch and set all the price per shares
* @dev this function must be called in order to execute an epoch calculation and batch a mutual fund epoch
*/
function executeEpochCalculation() external whenNotPaused {
_isKeeper();
if (!isTradingPaused) {
revert CustomErrors.TradingNotPaused();
}
(
uint256 newPricePerShareDeposit,
uint256 newPricePerShareWithdrawal,
uint256 sharesToMint,
uint256 totalWithdrawAmount,
uint256 amountNeeded
) = _getAccounting().executeEpochCalculation(totalSupply, _getAssets(), _getLiabilities());
// deposits always get executed
depositEpochPricePerShare[depositEpoch] = newPricePerShareDeposit;
delete pendingDeposits;
emit DepositEpochExecuted(depositEpoch);
depositEpoch++;
isTradingPaused = false;
emit TradingUnpaused();
_mint(address(this), sharesToMint);
// loop through the reactors and move funds if found
if (amountNeeded > 0) {
address[] memory hedgingReactors_ = hedgingReactors;
for (uint8 i = 0; i < hedgingReactors_.length; i++) {
amountNeeded -= IHedgingReactor(hedgingReactors_[i]).withdraw(amountNeeded);
if (amountNeeded <= 0) {
break;
}
}
// if not enough funds in liquidity pool and reactors, dont process withdrawals this epoch
if (amountNeeded > 0) {
return;
}
}
withdrawalEpochPricePerShare[withdrawalEpoch] = newPricePerShareWithdrawal;
partitionedFunds += totalWithdrawAmount;
emit WithdrawalEpochExecuted(withdrawalEpoch);
_burn(address(this), pendingWithdrawals);
delete pendingWithdrawals;
withdrawalEpoch++;
}
/////////////////////////////////////////////
/// external state changing functionality ///
/////////////////////////////////////////////
/**
* @notice function for adding liquidity to the options liquidity pool
* @param _amount amount of the collateral asset to deposit
* @return success
* @dev entry point to provide liquidity to dynamic hedging vault
*/
function deposit(uint256 _amount) external whenNotPaused nonReentrant returns (bool) {
if (_amount == 0) {
revert CustomErrors.InvalidAmount();
}
(uint256 depositAmount, uint256 unredeemedShares) = _getAccounting().deposit(msg.sender, _amount);
emit Deposit(msg.sender, _amount, depositEpoch);
// create the deposit receipt
depositReceipts[msg.sender] = IAccounting.DepositReceipt({
epoch: uint128(depositEpoch),
amount: uint128(depositAmount),
unredeemedShares: unredeemedShares
});
pendingDeposits += _amount;
// Pull in tokens from sender
SafeTransferLib.safeTransferFrom(collateralAsset, msg.sender, address(this), _amount);
return true;
}
/**
* @notice function for allowing a user to redeem their shares from a previous epoch
* @param _shares the number of shares to redeem
* @return the number of shares actually returned
*/
function redeem(uint256 _shares) external nonReentrant returns (uint256) {
if (_shares == 0) {
revert CustomErrors.InvalidShareAmount();
}
return _redeem(_shares);
}
/**
* @notice function for initiating a withdraw request from the pool
* @param _shares amount of shares to return
* @dev entry point to remove liquidity to dynamic hedging vault
*/
function initiateWithdraw(uint256 _shares) external whenNotPaused nonReentrant {
if (_shares == 0) {
revert CustomErrors.InvalidShareAmount();
}
IAccounting.DepositReceipt memory depositReceipt = depositReceipts[msg.sender];
if (depositReceipt.amount > 0 || depositReceipt.unredeemedShares > 0) {
// redeem so a user can use a completed deposit as shares for an initiation
_redeem(type(uint256).max);
}
IAccounting.WithdrawalReceipt memory withdrawalReceipt = _getAccounting().initiateWithdraw(
msg.sender,
_shares
);
withdrawalReceipts[msg.sender] = withdrawalReceipt;
pendingWithdrawals += _shares;
emit InitiateWithdraw(msg.sender, _shares, withdrawalEpoch);
transfer(address(this), _shares);
}
/**
* @notice function for completing the withdraw from a pool
* @dev entry point to remove liquidity to dynamic hedging vault
*/
function completeWithdraw() external whenNotPaused nonReentrant returns (uint256) {
(
uint256 withdrawalAmount,
uint256 withdrawalShares,
IAccounting.WithdrawalReceipt memory withdrawalReceipt
) = _getAccounting().completeWithdraw(msg.sender);
withdrawalReceipts[msg.sender] = withdrawalReceipt;
emit Withdraw(msg.sender, withdrawalAmount, withdrawalShares);
// these funds are taken from the partitioned funds
partitionedFunds -= withdrawalAmount;
SafeTransferLib.safeTransfer(ERC20(collateralAsset), msg.sender, withdrawalAmount);
return withdrawalAmount;
}
///////////////////////
/// complex getters ///
///////////////////////
/**
* @notice Returning balance in 1e18 format
* @param asset address of the asset to get balance and normalize
* @return normalizedBalance balance in 1e18 format
*/
function _getNormalizedBalance(address asset) internal view returns (uint256 normalizedBalance) {
normalizedBalance = OptionsCompute.convertFromDecimals(
ERC20(asset).balanceOf(address(this)) - partitionedFunds,
ERC20(asset).decimals()
);
}
/**
* @notice Returning balance in 1e6 format
* @param asset address of the asset to get balance
* @return balance of the address accounting for partitionedFunds
*/
function getBalance(address asset) public view returns (uint256) {
return ERC20(asset).balanceOf(address(this)) - partitionedFunds;
}
/**
* @notice get the delta of the hedging reactors
* @return externalDelta hedging reactor delta in e18 format
*/
function getExternalDelta() public view returns (int256 externalDelta) {
address[] memory hedgingReactors_ = hedgingReactors;
for (uint8 i = 0; i < hedgingReactors_.length; i++) {
externalDelta += IHedgingReactor(hedgingReactors_[i]).getDelta();
}
}
/**
* @notice get the delta of the portfolio
* @return portfolio delta
*/
function getPortfolioDelta() public view returns (int256) {
// assumes in e18
Types.PortfolioValues memory portfolioValues = _getPortfolioValuesFeed().getPortfolioValues(
underlyingAsset,
strikeAsset
);
// check that the portfolio values are acceptable
OptionsCompute.validatePortfolioValues(
_getUnderlyingPrice(underlyingAsset, strikeAsset),
portfolioValues,
maxTimeDeviationThreshold,
maxPriceDeviationThreshold
);
return portfolioValues.delta + getExternalDelta() + ephemeralDelta;
}
///////////////////////////
/// non-complex getters ///
///////////////////////////
/**
* @notice get the current implied volatility from the feed
* @param isPut Is the option a call or put?
* @param underlyingPrice The underlying price - assumed in e18
* @param strikePrice The strike price of the option - assumed in e18
* @param expiration expiration timestamp of option as a PRBMath Float
* @return Implied volatility adjusted for volatility surface - assumed in e18
*/
function getImpliedVolatility(
bool isPut,
uint256 underlyingPrice,
uint256 strikePrice,
uint256 expiration
) public view returns (uint256) {
return getVolatilityFeed().getImpliedVolatility(isPut, underlyingPrice, strikePrice, expiration);
}
function getAssets() external view returns (uint256) {
return _getAssets();
}
function getNAV() external view returns (uint256) {
return _getNAV();
}
//////////////////////////
/// internal utilities ///
//////////////////////////
/**
* @notice functionality for allowing a user to redeem their shares from a previous epoch
* @param _shares the number of shares to redeem
* @return toRedeem the number of shares actually returned
*/
function _redeem(uint256 _shares) internal returns (uint256) {
(uint256 toRedeem, IAccounting.DepositReceipt memory depositReceipt) = _getAccounting().redeem(
msg.sender,
_shares
);
if (toRedeem == 0) {
return 0;
}
depositReceipts[msg.sender] = depositReceipt;
allowance[address(this)][msg.sender] = toRedeem;
emit Redeem(msg.sender, toRedeem, depositReceipt.epoch);
// transfer as the shares will have been minted in the epoch execution
transferFrom(address(this), msg.sender, toRedeem);
return toRedeem;
}
/**
* @notice get the Net Asset Value
* @return Net Asset Value in e18 decimal format
*/
function _getNAV() internal view returns (uint256) {
// equities = assets - liabilities
// assets: Any token such as eth usd, collateral sent to OptionRegistry, hedging reactor stuff in e18
// liabilities: Options that we wrote in e18
uint256 assets = _getAssets();
int256 liabilities = _getLiabilities();
// if this ever happens then something has gone very wrong so throw here
if (int256(assets) < liabilities) {
revert CustomErrors.LiabilitiesGreaterThanAssets();
}
return uint256(int256(assets) - liabilities);
}
/**
* @notice get the Asset Value
* @return assets Asset Value in e18 decimal format
*/
function _getAssets() internal view returns (uint256 assets) {
// assets: Any token such as eth usd, collateral sent to OptionRegistry, hedging reactor stuff in e18
// liabilities: Options that we wrote in e18
assets =
_getNormalizedBalance(collateralAsset) +
OptionsCompute.convertFromDecimals(collateralAllocated, ERC20(collateralAsset).decimals());
address[] memory hedgingReactors_ = hedgingReactors;
for (uint8 i = 0; i < hedgingReactors_.length; i++) {
// should always return value in e18 decimals
assets += IHedgingReactor(hedgingReactors_[i]).getPoolDenominatedValue();
}
}
function _getLiabilities() internal view returns (int256 liabilities) {
Types.PortfolioValues memory portfolioValues = _getPortfolioValuesFeed().getPortfolioValues(
underlyingAsset,
strikeAsset
);
// check that the portfolio values are acceptable
OptionsCompute.validatePortfolioValues(
_getUnderlyingPrice(underlyingAsset, strikeAsset),
portfolioValues,
maxTimeDeviationThreshold,
maxPriceDeviationThreshold
);
// ephemeralLiabilities can be +/-, portfolioValues.callPutsValue could be +/-
liabilities = portfolioValues.callPutsValue + ephemeralLiabilities;
}
/**
* @notice calculates amount of liquidity that can be used before hitting buffer
* @return bufferRemaining the amount of liquidity available before reaching buffer in e6
*/
function checkBuffer() public view returns (int256 bufferRemaining) {
// calculate max amount of liquidity pool funds that can be used before reaching max buffer allowance
uint256 collateralBalance = getBalance(collateralAsset);
uint256 collateralBuffer = (collateralAllocated * bufferPercentage) / MAX_BPS;
bufferRemaining = int256(collateralBalance) - int256(collateralBuffer);
}
/**
* @notice create the option contract in the options registry
* @param optionSeries option type to mint - option series strike in e18
* @param optionRegistry interface for the options issuer
* @return series the address of the option series minted
*/
function _issue(
Types.OptionSeries memory optionSeries,
IOptionRegistry optionRegistry
) internal returns (address series) {
// make sure option is being issued with correct assets
if (optionSeries.collateral != collateralAsset) {
revert CustomErrors.CollateralAssetInvalid();
}
if (optionSeries.underlying != underlyingAsset) {
revert CustomErrors.UnderlyingAssetInvalid();
}
if (optionSeries.strikeAsset != strikeAsset) {
revert CustomErrors.StrikeAssetInvalid();
}
// cache
Types.OptionParams memory optionParams_ = optionParams;
// check the expiry is within the allowed bounds
if (
block.timestamp + optionParams_.minExpiry > optionSeries.expiration ||
optionSeries.expiration > block.timestamp + optionParams_.maxExpiry
) {
revert CustomErrors.OptionExpiryInvalid();
}
// check that the option strike is within the range of the min and max acceptable strikes of calls and puts
if (optionSeries.isPut) {
if (
optionParams_.minPutStrikePrice > optionSeries.strike ||
optionSeries.strike > optionParams_.maxPutStrikePrice
) {
revert CustomErrors.OptionStrikeInvalid();
}
} else {
if (
optionParams_.minCallStrikePrice > optionSeries.strike ||
optionSeries.strike > optionParams_.maxCallStrikePrice
) {
revert CustomErrors.OptionStrikeInvalid();
}
}
// issue the option from the option registry (its characteristics will be stored in the optionsRegistry)
series = optionRegistry.issue(optionSeries);
if (series == address(0)) {
revert CustomErrors.IssuanceFailed();
}
}
/**
* @notice write a number of options for a given OptionSeries
* @param optionSeries option type to mint - strike in e8
* @param seriesAddress the address of the options series
* @param amount the amount to be written - in e18
* @param optionRegistry the option registry of the pool
* @param premium the premium to charge the user - in collateral decimals
* @param delta the delta of the option position - in e18
* @param bufferRemaining the amount of buffer that can be used - in e6
* @return the amount that was written
*/
function _writeOption(
Types.OptionSeries memory optionSeries,
address seriesAddress,
uint256 amount,
IOptionRegistry optionRegistry,
uint256 premium,
int256 delta,
int256 bufferRemaining,
address recipient
) internal returns (uint256) {
// strike decimals come into this function as e8
uint256 collateralAmount = optionRegistry.getCollateral(optionSeries, amount);
if (bufferRemaining < int256(collateralAmount)) {
revert CustomErrors.MaxLiquidityBufferReached();
}
ERC20(collateralAsset).approve(address(optionRegistry), collateralAmount);
(, collateralAmount) = optionRegistry.open(seriesAddress, amount, collateralAmount);
emit WriteOption(seriesAddress, amount, premium, collateralAmount, recipient);
// convert e8 strike to e18 strike
optionSeries.strike = uint128(
OptionsCompute.convertFromDecimals(optionSeries.strike, ERC20(seriesAddress).decimals())
);
_adjustVariables(collateralAmount, premium, delta, true);
SafeTransferLib.safeTransfer(
ERC20(seriesAddress),
recipient,
OptionsCompute.convertToDecimals(amount, ERC20(seriesAddress).decimals())
);
// returns in e18
return amount;
}
/**
* @notice buys a number of options back and burns the tokens
* @param optionSeries the option token series to buyback - strike passed in as e8
* @param amount the number of options to buyback expressed in 1e18
* @param optionRegistry the registry
* @param seriesAddress the series being sold
* @param premium the premium to be sent back to the owner (in collat decimals)
* @param delta the delta of the option
* @param seller the address
* @return the number of options burned in e18
*/
function _buybackOption(
Types.OptionSeries memory optionSeries,
uint256 amount,
IOptionRegistry optionRegistry,
address seriesAddress,
uint256 premium,
int256 delta,
address seller
) internal returns (uint256) {
SafeTransferLib.safeApprove(
ERC20(seriesAddress),
address(optionRegistry),
OptionsCompute.convertToDecimals(amount, ERC20(seriesAddress).decimals())
);
(, uint256 collateralReturned) = optionRegistry.close(seriesAddress, amount);
emit BuybackOption(seriesAddress, amount, premium, collateralReturned, seller);
// convert e8 strike to e18 strike
optionSeries.strike = uint128(
OptionsCompute.convertFromDecimals(optionSeries.strike, ERC20(seriesAddress).decimals())
);
_adjustVariables(collateralReturned, premium, delta, false);
if (getBalance(collateralAsset) < premium) {
revert CustomErrors.WithdrawExceedsLiquidity();
}
SafeTransferLib.safeTransfer(ERC20(collateralAsset), seller, premium);
return amount;
}
function adjustVariables(
uint256 collateralAmount,
uint256 optionsValue,
int256 delta,
bool isSale
) external {
_isHandler();
_adjustVariables(collateralAmount, optionsValue, delta, isSale);
}
/**
* @notice adjust the variables of the pool
* @param collateralAmount the amount of collateral transferred to change on collateral allocated in collateral decimals
* @param optionsValue the value of the options in e18 decimals
* @param delta the delta of the options in e18 decimals
* @param isSale whether the action was an option sale or not
*/
function _adjustVariables(
uint256 collateralAmount,
uint256 optionsValue,
int256 delta,
bool isSale
) internal {
if (isSale) {
collateralAllocated += collateralAmount;
ephemeralLiabilities += int256(
OptionsCompute.convertFromDecimals(optionsValue, ERC20(collateralAsset).decimals())
);
ephemeralDelta -= delta;
} else {
collateralAllocated -= collateralAmount;
ephemeralLiabilities -= int256(
OptionsCompute.convertFromDecimals(optionsValue, ERC20(collateralAsset).decimals())
);
ephemeralDelta += delta;
}
}
/**
* @notice get the volatility feed used by the liquidity pool
* @return the volatility feed contract interface
*/
function getVolatilityFeed() public view returns (VolatilityFeed) {
return VolatilityFeed(protocol.volatilityFeed());
}