This repository has been archived by the owner on May 26, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLender.sol
1054 lines (904 loc) · 36.6 KB
/
Lender.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: UNLICENSED
pragma solidity 0.8.16;
import 'src/MarketPlace.sol'; // library of MarketPlace specific constructs
import 'src/lib/Swivel.sol'; // library of Swivel specific constructs
import 'src/lib/Element.sol'; // library of Element specific constructs
import 'src/lib/Safe.sol';
import 'src/lib/Cast.sol';
import 'src/lib/RevertMsgExtractor.sol';
import 'src/errors/Exception.sol';
import 'src/interfaces/ITempus.sol';
import 'src/interfaces/ITempusAMM.sol';
import 'src/interfaces/ITempusPool.sol';
import 'src/interfaces/ITempusToken.sol';
import 'src/interfaces/IERC20.sol';
import 'src/interfaces/IERC5095.sol';
import 'src/interfaces/ISensePeriphery.sol';
import 'src/interfaces/ISenseAdapter.sol';
import 'src/interfaces/ISenseDivider.sol';
import 'src/interfaces/IERC20.sol';
import 'src/interfaces/IYield.sol';
import 'src/interfaces/IYieldToken.sol';
import 'src/interfaces/ISwivel.sol';
import 'src/interfaces/IElementToken.sol';
import 'src/interfaces/IElementVault.sol';
import 'src/interfaces/ISwivel.sol';
import 'src/interfaces/IAPWineAMMPool.sol';
import 'src/interfaces/IAPWineRouter.sol';
import 'src/interfaces/IAPWineToken.sol';
import 'src/interfaces/IAPWineFutureVault.sol';
import 'src/interfaces/IAPWineController.sol';
import 'src/interfaces/INotional.sol';
import 'src/interfaces/IPendle.sol';
import 'src/interfaces/IPendleToken.sol';
/// @title Lender
/// @author Sourabh Marathe, Julian Traversa, Rob Robbins
/// @notice The lender contract executes loans on behalf of users
/// @notice The contract holds the principal tokens and mints an ERC-5095 tokens to users to represent their loans
contract Lender {
/// @notice minimum wait before the admin may withdraw funds or change the fee rate
uint256 public constant HOLD = 3 days;
/// @notice address that is allowed to set and withdraw fees, disable principals, etc. It is commonly used in the authorized modifier.
address public admin;
/// @notice address of the MarketPlace contract, used to access the markets mapping
address public marketPlace;
/// @notice mapping that determines if a principal token has been paused by the admin
mapping(address => mapping(uint256 => bool[9])) public paused;
/// @notice contract used to execute swaps on Swivel's exchange
address public immutable swivelAddr;
/// @notice a SushiSwap router used by Pendle to execute swaps
address public immutable pendleAddr;
/// @notice this value determines the amount of fees paid on loans
uint256 public feenominator;
/// @notice represents a point in time where the feenominator may change
uint256 public feeChange;
/// @notice represents a minimum that the feenominator must exceed
uint256 public constant MIN_FEENOMINATOR = 500;
/// @notice maps underlying tokens to the amount of fees accumulated for that token
mapping(address => uint256) public fees;
/// @notice maps a token address to a point in time, a hold, after which a withdrawal can be made
mapping(address => uint256) public withdrawals;
/// @notice emitted upon lending to a protocol
event Lend(
uint8 principal,
address indexed underlying,
uint256 indexed maturity,
uint256 returned,
uint256 spent,
address sender
);
/// @notice emitted upon minting Illuminate principal tokens
event Mint(
uint8 principal,
address indexed underlying,
uint256 indexed maturity,
uint256 amount
);
/// @notice emitted upon scheduling a withdrawal
event ScheduleWithdrawal(address indexed token, uint256 hold);
/// @notice emitted upon blocking a scheduled withdrawal
event BlockWithdrawal(address indexed token);
/// @notice emitted upon changing the admin
event SetAdmin(address indexed admin);
/// @notice emitted upon setting the fee rate
event SetFee(uint256 indexed fee);
/// @notice emitted upon scheduling a fee change
event ScheduleFeeChange(uint256 when);
/// @notice emitted upon blocking a scheduled fee change
event BlockFeeChange();
/// @notice emitted upon pausing or unpausing of a principal token
event PauseMarket(
uint8 principal,
address indexed underlying,
uint256 indexed maturity,
address token,
bool indexed state
);
/// @notice ensures that only a certain address can call the function
/// @param a address that msg.sender must be to be authorized
modifier authorized(address a) {
if (msg.sender != a) {
revert Exception(0, 0, 0, msg.sender, a);
}
_;
}
/// @notice reverts on all markets where the paused mapping returns true
/// @param u address of an underlying asset
/// @param m maturity (timestamp) of the market
/// @param p principal value according to the MarketPlace's Principals Enum
modifier unpaused(
address u,
uint256 m,
uint8 p
) {
if (paused[u][m][p]) {
revert Exception(1, p, 0, address(0), address(0));
}
_;
}
/// @notice initializes the Lender contract
/// @param s the Swivel contract
/// @param p the Pendle contract
constructor(address s, address p) {
admin = msg.sender;
swivelAddr = s;
pendleAddr = p;
feenominator = 1000;
}
/// @notice approves the redeemer contract to spend the principal tokens held by the lender contract.
/// @param u address of an underlying asset
/// @param m maturity (timestamp) of the market
/// @param r the address being approved, in this case the redeemer contract
/// @return bool true if the approval was successful
function approve(
address u,
uint256 m,
address r
) external authorized(admin) returns (bool) {
// approve the underlying for max per given principal
for (uint8 i; i != 9; ) {
// get the principal token's address
address token = IMarketPlace(marketPlace).token(u, m, i);
// check that the token is defined for this particular market
if (token != address(0)) {
// max approve the token
Safe.approve(IERC20(token), r, type(uint256).max);
}
unchecked {
++i;
}
}
return true;
}
/// @notice bulk approves the usage of addresses at the given ERC20 addresses.
/// @dev the lengths of the inputs must match because the arrays are paired by index
/// @param u array of ERC20 token addresses that will be approved on
/// @param a array of addresses that will be approved
/// @return true if successful
function approve(address[] calldata u, address[] calldata a)
external
authorized(admin)
returns (bool)
{
for (uint256 i; i != u.length; ) {
IERC20 uToken = IERC20(u[i]);
if (address(0) != (address(uToken))) {
Safe.approve(uToken, a[i], type(uint256).max);
}
unchecked {
++i;
}
}
return true;
}
/// @notice approves market contracts that require lender approval
/// @param u address of an underlying asset
/// @param a APWine's router contract
/// @param e Element's vault contract
/// @param n Notional's token contract
function approve(
address u,
address a,
address e,
address n
) external authorized(marketPlace) {
uint256 max = type(uint256).max;
IERC20 uToken = IERC20(u);
if (a != address(0)) {
Safe.approve(uToken, a, max);
}
if (e != address(0)) {
Safe.approve(uToken, e, max);
}
if (n != address(0)) {
Safe.approve(uToken, n, max);
}
if (IERC20(u).allowance(address(this), swivelAddr) == 0) {
Safe.approve(uToken, swivelAddr, max);
}
}
/// @notice sets the admin address
/// @param a address of a new admin
/// @return bool true if successful
function setAdmin(address a) external authorized(admin) returns (bool) {
admin = a;
emit SetAdmin(a);
return true;
}
/// @notice sets the feenominator to the given value
/// @param f the new value of the feenominator, fees are not collected when the feenominator is 0
/// @return bool true if successful
function setFee(uint256 f) external authorized(admin) returns (bool) {
uint256 feeTime = feeChange;
if (feeTime == 0) {
revert Exception(23, 0, 0, address(0), address(0));
} else if (block.timestamp < feeTime) {
revert Exception(
24,
block.timestamp,
feeTime,
address(0),
address(0)
);
} else if (f < MIN_FEENOMINATOR) {
revert Exception(25, 0, 0, address(0), address(0));
}
feenominator = f;
delete feeChange;
emit SetFee(f);
return true;
}
/// @notice sets the address of the marketplace contract which contains the addresses of all the fixed rate markets
/// @param m the address of the marketplace contract
/// @return bool true if the address was set
function setMarketPlace(address m)
external
authorized(admin)
returns (bool)
{
if (marketPlace != address(0)) {
revert Exception(5, 0, 0, marketPlace, address(0));
}
marketPlace = m;
return true;
}
/// @notice mint swaps the sender's principal tokens for Illuminate's ERC5095 tokens in effect, this opens a new fixed rate position for the sender on Illuminate
/// @param p principal value according to the MarketPlace's Principals Enum
/// @param u address of an underlying asset
/// @param m maturity (timestamp) of the market
/// @param a amount being minted
/// @return bool true if the mint was successful
function mint(
uint8 p,
address u,
uint256 m,
uint256 a
) external unpaused(u, m, p) returns (bool) {
// Fetch the desired principal token
address principal = IMarketPlace(marketPlace).token(u, m, p);
// Transfer the users principal tokens to the lender contract
Safe.transferFrom(IERC20(principal), msg.sender, address(this), a);
// Mint the tokens received from the user
IERC5095(principalToken(u, m)).authMint(msg.sender, a);
emit Mint(p, u, m, a);
return true;
}
/// @notice lend method for the Illuminate and Yield protocols
/// @param p principal value according to the MarketPlace's Principals Enum
/// @param u address of an underlying asset
/// @param m maturity (timestamp) of the market
/// @param a amount of underlying tokens to lend
/// @param y Yield Space Pool for the principal token
/// @param minimum slippage limit, minimum amount to PTs to buy
/// @return uint256 the amount of principal tokens lent out
function lend(
uint8 p,
address u,
uint256 m,
uint256 a,
address y,
uint256 minimum
) external unpaused(u, m, p) returns (uint256) {
// Check that the principal is Illuminate or Yield
if (
p != uint8(MarketPlace.Principals.Illuminate) &&
p != uint8(MarketPlace.Principals.Yield)
) {
revert Exception(6, 0, 0, address(0), address(0));
}
// Get principal token for this market
address principal = IMarketPlace(marketPlace).token(u, m, p);
// Extract fee
fees[u] = fees[u] + a / feenominator;
// Transfer underlying from user to the lender contract
Safe.transferFrom(IERC20(u), msg.sender, address(this), a);
if (p == uint8(MarketPlace.Principals.Yield)) {
// Make sure the Yield Space Pool matches principal token
address fyToken = IYield(y).fyToken();
if (IYield(y).fyToken() != principal) {
revert Exception(12, 0, 0, fyToken, principal);
}
}
// Swap underlying for PTs to lender
uint256 returned = yield(
u,
y,
a - a / feenominator,
address(this),
principal,
minimum
);
// Mint Illuminate PTs to msg.sender
IERC5095(principalToken(u, m)).authMint(msg.sender, returned);
emit Lend(p, u, m, returned, a, msg.sender);
return returned;
}
/// @notice lend method signature for Swivel
/// @param p principal value according to the MarketPlace's Principals Enum
/// @param u address of an underlying asset
/// @param m maturity (timestamp) of the market
/// @param a array of amounts of underlying tokens lent to each order in the orders array
/// @param y Yield Space Pool for the Illuminate PT in this market
/// @param o array of Swivel orders being filled
/// @param s array of signatures for each order in the orders array
/// @param e flag to indicate if returned funds should be swapped in Yield Space Pool
/// @param premiumSlippage slippage limit, minimum amount to PTs to buy
/// @return uint256 the amount of principal tokens lent out
function lend(
uint8 p,
address u,
uint256 m,
uint256[] memory a,
address y,
Swivel.Order[] calldata o,
Swivel.Components[] calldata s,
bool e,
uint256 premiumSlippage
) external unpaused(u, m, p) returns (uint256) {
{
// Check that the principal is Swivel
if (p != uint8(MarketPlace.Principals.Swivel)) {
revert Exception(
6,
p,
uint8(MarketPlace.Principals.Swivel),
address(0),
address(0)
);
}
// Lent represents the total amount of underlying to be lent
uint256 lent = swivelAmount(a);
// Transfer underlying token from user to Illuminate
Safe.transferFrom(IERC20(u), msg.sender, address(this), lent);
// Get the underlying balance prior to calling initiate
uint256 starting = IERC20(u).balanceOf(address(this));
// Verify and collect the fee
{
// Calculate fee for the total amount to be lent
uint256 fee = lent / feenominator;
// Get last order to be processed's index
uint256 lastIndex = a.length - 1;
// Add the accumulated fees to the total
a[lastIndex] = a[lastIndex] - fee; // Revert here if fee not paid
// Extract fee
fees[u] += fee;
}
uint256 received;
{
// Get the starting amount of principal tokens
uint256 startingZcTokens = IERC20(
IMarketPlace(marketPlace).token(u, m, p)
).balanceOf(address(this));
// Fill the given orders on Swivel
ISwivel(swivelAddr).initiate(o, a, s);
if (e) {
// Calculate the premium
uint256 premium = IERC20(u).balanceOf(address(this)) -
starting;
// Swap the premium for Illuminate principal tokens
swivelLendPremium(u, m, y, premium, premiumSlippage);
}
// Compute how many principal tokens were received
received =
IERC20(IMarketPlace(marketPlace).token(u, m, p)).balanceOf(
address(this)
) -
startingZcTokens;
}
// Mint Illuminate principal tokens to the user
IERC5095(principalToken(u, m)).authMint(msg.sender, received);
{
emit Lend(
uint8(MarketPlace.Principals.Swivel),
u,
m,
received,
lent,
msg.sender
);
}
return received;
}
}
/// @notice lend method signature for Element
/// @param p principal value according to the MarketPlace's Principals Enum
/// @param u address of an underlying asset
/// @param m maturity (timestamp) of the market
/// @param a amount of underlying tokens to lend
/// @param r slippage limit, minimum amount to PTs to buy
/// @param d deadline is a timestamp by which the swap must be executed
/// @param e Element pool that is lent to
/// @param i the id of the pool
/// @return uint256 the amount of principal tokens lent out
function lend(
uint8 p,
address u,
uint256 m,
uint256 a,
uint256 r,
uint256 d,
address e,
bytes32 i
) external unpaused(u, m, p) returns (uint256) {
// Get the principal token for this market for Element
address principal = IMarketPlace(marketPlace).token(u, m, p);
// Transfer underlying token from user to Illuminate
Safe.transferFrom(IERC20(u), msg.sender, address(this), a);
// Track the accumulated fees
fees[u] = fees[u] + a / feenominator;
uint256 purchased;
{
// Calculate the amount to be lent
uint256 lent = a - a / feenominator;
// Create the variables needed to execute an Element swap
Element.FundManagement memory fund = Element.FundManagement({
sender: address(this),
recipient: payable(address(this)),
fromInternalBalance: false,
toInternalBalance: false
});
Element.SingleSwap memory swap = Element.SingleSwap({
poolId: i,
amount: lent,
kind: Element.SwapKind.GIVEN_IN,
assetIn: IAny(u),
assetOut: IAny(principal),
userData: '0x00000000000000000000000000000000000000000000000000000000000000'
});
// Conduct the swap on Element
purchased = elementSwap(e, swap, fund, r, d);
}
// Mint tokens to the user
IERC5095(principalToken(u, m)).authMint(msg.sender, purchased);
emit Lend(p, u, m, purchased, a, msg.sender);
return purchased;
}
/// @notice lend method signature for Pendle
/// @param p principal value according to the MarketPlace's Principals Enum
/// @param u address of an underlying asset
/// @param m maturity (timestamp) of the market
/// @param a amount of underlying tokens to lend
/// @param r slippage limit, minimum amount to PTs to buy
/// @param d deadline is a timestamp by which the swap must be executed
/// @return uint256 the amount of principal tokens lent out
function lend(
uint8 p,
address u,
uint256 m,
uint256 a,
uint256 r,
uint256 d
) external unpaused(u, m, p) returns (uint256) {
// Instantiate market and tokens
address principal = IMarketPlace(marketPlace).token(u, m, p);
// Transfer funds from user to Illuminate
Safe.transferFrom(IERC20(u), msg.sender, address(this), a);
uint256 returned;
{
// Add the accumulated fees to the total
uint256 fee = a / feenominator;
fees[u] = fees[u] + fee;
address[] memory path = new address[](2);
path[0] = u;
path[1] = principal;
// Swap on the Pendle Router using the provided market and params
returned = IPendle(pendleAddr).swapExactTokensForTokens(
a - fee,
r,
path,
address(this),
d
)[1];
}
// Mint Illuminate zero coupons
IERC5095(principalToken(u, m)).authMint(msg.sender, returned);
emit Lend(p, u, m, returned, a, msg.sender);
return returned;
}
/// @notice lend method signature for APWine
/// @param p principal value according to the MarketPlace's Principals Enum
/// @param u address of an underlying asset
/// @param m maturity (timestamp) of the market
/// @param a amount of underlying tokens to lend
/// @param r slippage limit, minimum amount to PTs to buy
/// @param d deadline is a timestamp by which the swap must be executed
/// @param x APWine router that executes the swap
/// @param pool the AMM pool used by APWine to execute the swap
/// @return uint256 the amount of principal tokens lent out
function lend(
uint8 p,
address u,
uint256 m,
uint256 a,
uint256 r,
uint256 d,
address x,
address pool
) external unpaused(u, m, p) returns (uint256) {
address principal = IMarketPlace(marketPlace).token(u, m, p);
// Transfer funds from user to Illuminate
Safe.transferFrom(IERC20(u), msg.sender, address(this), a);
uint256 lent;
{
// Add the accumulated fees to the total
uint256 fee = a / feenominator;
fees[u] = fees[u] + fee;
// Calculate amount to be lent out
lent = a - fee;
}
// Get the starting APWine token balance
uint256 starting = IERC20(principal).balanceOf(address(this));
// Swap on the APWine Pool using the provided market and params
IAPWineRouter(x).swapExactAmountIn(
pool,
apwinePairPath(),
apwineTokenPath(),
lent,
r,
address(this),
d,
address(0)
);
// Calculate the amount of APWine principal tokens received after the swap
uint256 received = IERC20(principal).balanceOf(address(this)) -
starting;
// Mint Illuminate zero coupons
IERC5095(principalToken(u, m)).authMint(msg.sender, received);
emit Lend(p, u, m, received, a, msg.sender);
return received;
}
/// @notice lend method signature for Tempus
/// @param p value of a specific principal according to the Illuminate Principals Enum
/// @param u address of an underlying asset
/// @param m maturity (timestamp) of the market
/// @param a amount of principal tokens to lend
/// @param r minimum amount to return when executing the swap (sets a limit to slippage)
/// @param d deadline is a timestamp by which the swap must be executed
/// @param x Tempus AMM that executes the swap
/// @return uint256 the amount of principal tokens lent out
function lend(
uint8 p,
address u,
uint256 m,
uint256 a,
uint256 r,
uint256 d,
address x
) external unpaused(u, m, p) returns (uint256) {
address principal = IMarketPlace(marketPlace).token(u, m, p);
// Transfer funds from user to Illuminate
Safe.transferFrom(IERC20(u), msg.sender, address(this), a);
uint256 lent;
{
// Add the accumulated fees to the total
uint256 fee = a / feenominator;
fees[u] = fees[u] + fee;
// Calculate amount to be lent out
lent = a - fee;
}
// Get the starting balance of the principal token
uint256 start = IERC20(principal).balanceOf(address(this));
// Get the Tempus Router from the principal token
address controller = ITempusPool(ITempusToken(principal).pool())
.controller();
// Swap on the Tempus Router using the provided market and params
ITempus(controller).depositAndFix(x, lent, true, r, d);
// Calculate the amount of Tempus principal tokens received after the deposit
uint256 received = IERC20(principal).balanceOf(address(this)) - start;
// Verify that a minimum number of principal tokens were received
if (received < r) {
revert Exception(11, received, r, address(0), address(0));
}
// Mint Illuminate zero coupons
IERC5095(principalToken(u, m)).authMint(msg.sender, received);
emit Lend(p, u, m, received, a, msg.sender);
return received;
}
/// @notice lend method signature for Sense
/// @dev this method can be called before maturity to lend to Sense while minting Illuminate tokens
/// @dev Sense provides a [divider] contract that splits [target] assets (underlying) into PTs and YTs. Each [target] asset has a [series] of contracts, each identifiable by their [maturity].
/// @param p principal value according to the MarketPlace's Principals Enum
/// @param u address of an underlying asset
/// @param m maturity (timestamp) of the market
/// @param a amount of underlying tokens to lend
/// @param r slippage limit, minimum amount to PTs to buy
/// @param x AMM that is used to conduct the swap
/// @param s Sense's maturity for the given market
/// @param adapter Sense's adapter necessary to facilitate the swap
/// @return uint256 the amount of principal tokens lent out
function lend(
uint8 p,
address u,
uint256 m,
uint128 a,
uint256 r,
address x,
uint256 s,
address adapter
) external unpaused(u, m, p) returns (uint256) {
// Retrieve the principal token for this market
IERC20 token = IERC20(IMarketPlace(marketPlace).token(u, m, p));
// Transfer funds from user to Illuminate
Safe.transferFrom(IERC20(u), msg.sender, address(this), a);
// Determine the fee
uint256 fee = a / feenominator;
// Add the accumulated fees to the total
fees[u] = fees[u] + fee;
// Determine lent amount after fees
uint256 lent = a - fee;
// Stores the amount of principal tokens received in swap for underlying
uint256 received;
{
// Get the starting balance of the principal token
uint256 starting = token.balanceOf(address(this));
// Swap those tokens for the principal tokens
ISensePeriphery(x).swapUnderlyingForPTs(adapter, s, lent, r);
// Calculate number of principal tokens received in the swap
received = token.balanceOf(address(this)) - starting;
// Verify that we received the principal tokens
if (received < r) {
revert Exception(11, 0, 0, address(0), address(0));
}
}
// Mint the Illuminate tokens based on the returned amount
IERC5095(principalToken(u, m)).authMint(msg.sender, received);
emit Lend(p, u, m, received, a, msg.sender);
return received;
}
/// @dev lend method signature for Notional
/// @param p principal value according to the MarketPlace's Principals Enum
/// @param u address of an underlying asset
/// @param m maturity (timestamp) of the market
/// @param a amount of underlying tokens to lend
/// @param r slippage limit, minimum amount to PTs to buy
/// @return uint256 the amount of principal tokens lent out
function lend(
uint8 p,
address u,
uint256 m,
uint256 a,
uint256 r
) external unpaused(u, m, p) returns (uint256) {
// Instantiate Notional princpal token
address token = IMarketPlace(marketPlace).token(u, m, p);
// Transfer funds from user to Illuminate
Safe.transferFrom(IERC20(u), msg.sender, address(this), a);
// Add the accumulated fees to the total
uint256 fee = a / feenominator;
fees[u] = fees[u] + fee;
// Swap on the Notional Token wrapper
uint256 received = INotional(token).deposit(a - fee, address(this));
// Verify that we received the principal tokens
if (received < r) {
revert Exception(16, received, r, address(0), address(0));
}
// Mint Illuminate zero coupons
IERC5095(principalToken(u, m)).authMint(msg.sender, received);
emit Lend(p, u, m, received, a, msg.sender);
return received;
}
/// @notice allows the admin to schedule the withdrawal of tokens
/// @param e address of (erc20) token to withdraw
/// @return bool true if successful
function scheduleWithdrawal(address e)
external
authorized(admin)
returns (bool)
{
uint256 when = block.timestamp + HOLD;
withdrawals[e] = when;
emit ScheduleWithdrawal(e, when);
return true;
}
/// @notice emergency function to block unplanned withdrawals
/// @param e address of token withdrawal to block
/// @return bool true if successful
function blockWithdrawal(address e)
external
authorized(admin)
returns (bool)
{
delete withdrawals[e];
emit BlockWithdrawal(e);
return true;
}
/// @notice allows the admin to schedule a change to the fee denominators
function scheduleFeeChange() external authorized(admin) returns (bool) {
uint256 when = block.timestamp + HOLD;
feeChange = when;
emit ScheduleFeeChange(when);
return true;
}
/// @notice Emergency function to block unplanned changes to fee structure
function blockFeeChange() external authorized(admin) returns (bool) {
delete feeChange;
emit BlockFeeChange();
return true;
}
/// @notice allows the admin to withdraw the given token, provided the holding period has been observed
/// @param e Address of token to withdraw
/// @return bool true if successful
function withdraw(address e) external authorized(admin) returns (bool) {
uint256 when = withdrawals[e];
if (when == 0) {
revert Exception(18, 0, 0, address(0), address(0));
}
if (block.timestamp < when) {
revert Exception(19, 0, 0, address(0), address(0));
}
delete withdrawals[e];
delete fees[e];
IERC20 token = IERC20(e);
Safe.transfer(token, admin, token.balanceOf(address(this)));
return true;
}
/// @notice withdraws accumulated lending fees of the underlying token
/// @param e address of the underlying token to withdraw
/// @return bool true if successful
function withdrawFee(address e) external authorized(admin) returns (bool) {
// Get the token to be withdrawn
IERC20 token = IERC20(e);
// Get the balance to be transferred
uint256 balance = fees[e];
// Reset accumulated fees of the token to 0
fees[e] = 0;
// Transfer the accumulated fees to the admin
Safe.transfer(token, admin, balance);
return true;
}
/// @notice pauses a market and prevents execution of all lending for that principal
/// @param u address of an underlying asset
/// @param m maturity (timestamp) of the market
/// @param p principal value according to the MarketPlace's Principals Enum
/// @param b bool representing whether to pause or unpause
/// @return bool true if successful
function pause(
address u,
uint256 m,
uint8 p,
bool b
) external authorized(admin) returns (bool) {
paused[u][m][p] = b;
emit PauseMarket(p, u, m, IMarketPlace(marketPlace).token(u, m, p), b);
return true;
}
/// @notice Tranfers FYTs for APWine redemptions
/// @param f FYT contract address
/// @param a amount of tokens to send to the redeemer
function transferFYTs(address f, uint256 a)
external
authorized(IMarketPlace(marketPlace).redeemer())
{
Safe.transfer(IERC20(f), IMarketPlace(marketPlace).redeemer(), a);
}
/// @notice Allows batched call to self (this contract).
/// @param c An array of inputs for each call.
function batch(bytes[] calldata c)
external
payable
returns (bytes[] memory results)
{
results = new bytes[](c.length);
for (uint256 i; i < c.length; i++) {
(bool success, bytes memory result) = address(this).delegatecall(
c[i]
);
if (!success) revert(RevertMsgExtractor.getRevertMsg(result));
results[i] = result;
}
}
/// @notice swaps underlying premium via a Yield Space Pool
/// @dev this method is only used by the Yield, Illuminate and Swivel protocols
/// @param u address of an underlying asset
/// @param y Yield Space Pool for the principal token
/// @param a amount of underlying tokens to lend
/// @param r the receiving address for PTs
/// @param p the principal token in the Yield Space Pool
/// @param m the minimum amount to purchase
/// @return uint256 the amount of tokens sent to the Yield Space Pool
function yield(
address u,
address y,
uint256 a,
address r,
address p,
uint256 m
) internal returns (uint256) {
// Get the starting balance (to verify receipt of tokens)
uint256 starting = IERC20(p).balanceOf(r);
// Get the amount of tokens received for swapping underlying
uint128 returned = IYield(y).sellBasePreview(Cast.u128(a));
// Send the remaining amount to the Yield pool
Safe.transfer(IERC20(u), y, a);
// Lend out the remaining tokens in the Yield pool
IYield(y).sellBase(r, returned);
// Get the ending balance of principal tokens (must be at least starting + returned)
uint256 received = IERC20(p).balanceOf(r) - starting;
// Verify receipt of PTs from Yield Space Pool
if (received <= m) {
revert Exception(11, received, m, address(0), address(0));
}
return received;
}
/// @notice lends the leftover underlying premium to the Illuminate PT's Yield Space Pool
function swivelLendPremium(
address u,
uint256 m,
address y,
uint256 p,
uint256 slippageTolerance
) internal {
// Lend remaining funds to Illuminate's Yield Space Pool
uint256 swapped = yield(
u,
y,
p,
address(this),
IMarketPlace(marketPlace).token(u, m, 0),
slippageTolerance
);
// Mint the remaining tokens
IERC5095(principalToken(u, m)).authMint(msg.sender, swapped);
}
/// @notice returns the amount of underlying tokens to be used in a Swivel lend
function swivelAmount(uint256[] memory a) internal pure returns (uint256) {
uint256 lent;
// Iterate through each order a calculate the total lent and returned
for (uint256 i; i != a.length; ) {
{
// Sum the total amount lent to Swivel
lent = lent + a[i];
}
unchecked {
++i;
}
}
return lent;
}
/// @notice executes a swap for and verifies receipt of Element PTs