-
Notifications
You must be signed in to change notification settings - Fork 46
/
TokenizedStrategy.sol
2046 lines (1830 loc) · 74.9 KB
/
TokenizedStrategy.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: AGPL-3.0
pragma solidity >=0.8.18;
/**$$$$$$$$$$$$$$$$$$$$$$$$$$$&Mr/|1+~>>iiiiiiiiiii>~+{|tuMW$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
$$$$$$$$$$$$$$$$$$$$$$$$$B#j]->iiiiiiiiiiiiiiiiiiiiiiiiiiii>-?f*B$$$$$$$$$$$$$$$$$$$$$$$$$
$$$$$$$$$$$$$$$$$$$$$@zj}~iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii~}fv@$$$$$$$$$$$$$$$$$$$$$
$$$$$$$$$$$$$$$$$$@z(+iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii+)zB$$$$$$$$$$$$$$$$$$
$$$$$$$$$$$$$$$$Mf~iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii~t#@$$$$$$$$$$$$$$$
$$$$$$$$$$$$$@u[iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii?n@$$$$$$$$$$$$$
$$$$$$$$$$$@z]iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii?u@$$$$$$$$$$$
$$$$$$$$$$v]iiiiiiiiiiiiiiii,.';iiiiiiiiiiiiiiiiiiiiiiiiii;'."iiiiiiiiiiiiiiii?u$$$$$$$$$$
$$$$$$$$%)>iiiiiiiiiiiiiii,. ';iiiiiiiiiiiiiiiiiiiiii;' ."iiiiiiiiiiiiiiii1%$$$$$$$$
$$$$$$$c~iiiiiiiiiiiiiii,. ';iiiiiiiiiiiiiiiiii;' ."iiiiiiiiiiiiiii~u$$$$$$$
$$$$$B/>iiiiiiiiiiiiii!' `IiiiiiiiiiiiiiiI` .Iiiiiiiiiiiiiii>|%$$$$$
$$$$@)iiiiiiiiiiiiiiiii;' `Iiiiiiiiiiil` ';iiiiiiiiiiiiiiiii}@$$$$
$$$B|iiiiiiiiiiiiiiiiiiii;' `Iiiiiiil` ';iiiiiiiiiiiiiiiiiiii1B$$$
$$@)iiiiiiiiiiiiiiiiiiiiiii:' `;iiI` ':iiiiiiiiiiiiiiiiiiiiiii{B$$
$$|iiiiiiiiiiiiiiiiiiiiiiiiii;' `` ':iiiiiiiiiiiiiiiiiiiiiiiiii1$$
$v>iiiiiiiiiiiiiiiiiiiiiiiiiiii:' ':iiiiiiiiiiiiiiiiiiiiiiiiiiii>x$
&?iiiiiiiiiiiiiiiiiiiiiiiiiiiiiii:' .,iiiiiiiiiiiiiiiiiiiiiiiiiiiiiii-W
ziiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii:' .,iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiv
-iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii:' .,iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii-
<iiiiiiiiiiiiiiiiiiii!.':iiiiiiiiiiiiii, "iiiiiiiiiiiiii;'.Iiiiiiiiiiiiiiiiiiiii<
iiiiiiiiiiiiiiiiiiiii' ';iiiiiiiiiiiii Iiiiiiiiiiiii;' .iiiiiiiiiiiiiiiiiiiii
iiiiiiiiiiiiiiiiiiii, ';iiiiiiiiiii IiiiiiiiiiiI` `iiiiiiiiiiiiiiiiiiii
iiiiiiiiiiiiiiiiiiii. `Iiiiiiiiii Iiiiiiiii!` !iiiiiiiiiiiiiiiiiii
iiiiiiiiiiiiiiiiiii; :iiiiiiiii Iiiiiiiii! ,iiiiiiiiiiiiiiiiiii
iiiiiiiiiiiiiiiiiii, iiiiiiiiii Iiiiiiiiii. ^iiiiiiiiiiiiiiiiiii
<iiiiiiiiiiiiiiiiii, iiiiiiiiii Iiiiiiiiii' ^iiiiiiiiiiiiiiiiii<
-iiiiiiiiiiiiiiiiii; Iiiiiiiiii Iiiiiiiiii. "iiiiiiiiiiiiiiiiii-
ziiiiiiiiiiiiiiiiiii. 'iiiiiiiii''''''''''liiiiiiii^ liiiiiiiiiiiiiiiiiiv
&?iiiiiiiiiiiiiiiiii^ ^iiiiiiiiiiiiiiiiiiiiiiiiii, `iiiiiiiiiiiiiiiiii_W
$u>iiiiiiiiiiiiiiiiii. `!iiiiiiiiiiiiiiiiiiiiiii^ .liiiiiiiiiiiiiiiiiir$
$$(iiiiiiiiiiiiiiiiii;. ."iiiiiiiiiiiiiiiiiiii,. :iiiiiiiiiiiiiiiiii}$$
$$@{iiiiiiiiiiiiiiiiii;. .`:iiiiiiiiiiiiii;^. :iiiiiiiiiiiiiiiiii}B$$
$$$B)iiiiiiiiiiiiiiiiii!' '`",::::,"`'. .Iiiiiiiiiiiiiiiiiii{%$$$
$$$$@1iiiiiiiiiiiiiiiiiii,. ^iiiiiiiiiiiiiiiiiii[@$$$$
$$$$$B|>iiiiiiiiiiiiiiiiii!^. `liiiiiiiiiiiiiiiiii>)%$$$$$
$$$$$$$c~iiiiiiiiiiiiiiiiiiii"' ."!iiiiiiiiiiiiiiiiiii~n$$$$$$$
$$$$$$$$B)iiiiiiiiiiiiiiiiiiiii!,`. .'"liiiiiiiiiiiiiiiiiiiii1%$$$$$$$$
$$$$$$$$$@u]iiiiiiiiiiiiiiiiiiiiiiil,^`'.. ..''^,liiiiiiiiiiiiiiiiiiiiiii-x@$$$$$$$$$
$$$$$$$$$$$@v?iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii-x$$$$$$$$$$$$
$$$$$$$$$$$$$@n?iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii-rB$$$$$$$$$$$$$
$$$$$$$$$$$$$$$$/~iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii<\*@$$$$$$$$$$$$$$$$
$$$$$$$$$$$$$$$$$$Bc1~iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii~{v%$$$$$$$$$$$$$$$$$$
$$$$$$$$$$$$$$$$$$$$$Bvf]<iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii<]tuB$$$$$$$$$$$$$$$$$$$$$
$$$$$$$$$$$$$$$$$$$$$$$$$%zt-+>iiiiiiiiiiiiiiiiiiiiiiiiiiiii+_tc%$$$$$$$$$$$$$$$$$$$$$$$$$
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$W#u/|{+~>iiiiiiiiiiii><+{|/n#W$$$$$$$$$$$$$$$$$$$$$$$$$$$$*/
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IFactory} from "./interfaces/IFactory.sol";
import {IBaseStrategy} from "./interfaces/IBaseStrategy.sol";
/**
* @title Yearn Tokenized Strategy
* @author yearn.finance
* @notice
* This TokenizedStrategy can be used by anyone wishing to easily build
* and deploy their own custom ERC4626 compliant single strategy Vault.
*
* The TokenizedStrategy contract is meant to be used as the proxy
* implementation contract that will handle all logic, storage and
* management for a custom strategy that inherits the `BaseStrategy`.
* Any function calls to the strategy that are not defined within that
* strategy will be forwarded through a delegateCall to this contract.
* A strategist only needs to override a few simple functions that are
* focused entirely on the strategy specific needs to easily and cheaply
* deploy their own permissionless 4626 compliant vault.
*/
contract TokenizedStrategy {
using Math for uint256;
using SafeERC20 for ERC20;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/**
* @notice Emitted when a strategy is shutdown.
*/
event StrategyShutdown();
/**
* @notice Emitted on the initialization of any new `strategy` that uses `asset`
* with this specific `apiVersion`.
*/
event NewTokenizedStrategy(
address indexed strategy,
address indexed asset,
string apiVersion
);
/**
* @notice Emitted when the strategy reports `profit` or `loss` and
* `performanceFees` and `protocolFees` are paid out.
*/
event Reported(
uint256 profit,
uint256 loss,
uint256 protocolFees,
uint256 performanceFees
);
/**
* @notice Emitted when the 'performanceFeeRecipient' address is
* updated to 'newPerformanceFeeRecipient'.
*/
event UpdatePerformanceFeeRecipient(
address indexed newPerformanceFeeRecipient
);
/**
* @notice Emitted when the 'keeper' address is updated to 'newKeeper'.
*/
event UpdateKeeper(address indexed newKeeper);
/**
* @notice Emitted when the 'performanceFee' is updated to 'newPerformanceFee'.
*/
event UpdatePerformanceFee(uint16 newPerformanceFee);
/**
* @notice Emitted when the 'management' address is updated to 'newManagement'.
*/
event UpdateManagement(address indexed newManagement);
/**
* @notice Emitted when the 'emergencyAdmin' address is updated to 'newEmergencyAdmin'.
*/
event UpdateEmergencyAdmin(address indexed newEmergencyAdmin);
/**
* @notice Emitted when the 'profitMaxUnlockTime' is updated to 'newProfitMaxUnlockTime'.
*/
event UpdateProfitMaxUnlockTime(uint256 newProfitMaxUnlockTime);
/**
* @notice Emitted when the 'pendingManagement' address is updated to 'newPendingManagement'.
*/
event UpdatePendingManagement(address indexed newPendingManagement);
/**
* @notice Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
/**
* @notice Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @notice Emitted when the `caller` has exchanged `assets` for `shares`,
* and transferred those `shares` to `owner`.
*/
event Deposit(
address indexed caller,
address indexed owner,
uint256 assets,
uint256 shares
);
/**
* @notice Emitted when the `caller` has exchanged `owner`s `shares` for `assets`,
* and transferred those `assets` to `receiver`.
*/
event Withdraw(
address indexed caller,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/*//////////////////////////////////////////////////////////////
STORAGE STRUCT
//////////////////////////////////////////////////////////////*/
/**
* @dev The struct that will hold all the storage data for each strategy
* that uses this implementation.
*
* This replaces all state variables for a traditional contract. This
* full struct will be initialized on the creation of the strategy
* and continually updated and read from for the life of the contract.
*
* We combine all the variables into one struct to limit the amount of
* times the custom storage slots need to be loaded during complex functions.
*
* Loading the corresponding storage slot for the struct does not
* load any of the contents of the struct into memory. So the size
* will not increase memory related gas usage.
*/
// prettier-ignore
struct StrategyData {
// The ERC20 compliant underlying asset that will be
// used by the Strategy
ERC20 asset;
// These are the corresponding ERC20 variables needed for the
// strategies token that is issued and burned on each deposit or withdraw.
uint8 decimals; // The amount of decimals that `asset` and strategy use.
string name; // The name of the token for the strategy.
uint256 totalSupply; // The total amount of shares currently issued.
mapping(address => uint256) nonces; // Mapping of nonces used for permit functions.
mapping(address => uint256) balances; // Mapping to track current balances for each account that holds shares.
mapping(address => mapping(address => uint256)) allowances; // Mapping to track the allowances for the strategies shares.
// We manually track `totalAssets` to prevent PPS manipulation through airdrops.
uint256 totalAssets;
// Variables for profit reporting and locking.
// We use uint96 for timestamps to fit in the same slot as an address. That overflows in 2.5e+21 years.
// I know Yearn moves slowly but surely V4 will be out by then.
// If the timestamps ever overflow tell the cyborgs still using this code I'm sorry for being cheap.
uint256 profitUnlockingRate; // The rate at which locked profit is unlocking.
uint96 fullProfitUnlockDate; // The timestamp at which all locked shares will unlock.
address keeper; // Address given permission to call {report} and {tend}.
uint32 profitMaxUnlockTime; // The amount of seconds that the reported profit unlocks over.
uint16 performanceFee; // The percent in basis points of profit that is charged as a fee.
address performanceFeeRecipient; // The address to pay the `performanceFee` to.
uint96 lastReport; // The last time a {report} was called.
// Access management variables.
address management; // Main address that can set all configurable variables.
address pendingManagement; // Address that is pending to take over `management`.
address emergencyAdmin; // Address to act in emergencies as well as `management`.
// Strategy Status
uint8 entered; // To prevent reentrancy. Use uint8 for gas savings.
bool shutdown; // Bool that can be used to stop deposits into the strategy.
}
/*//////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/
/**
* @dev Require that the call is coming from the strategies management.
*/
modifier onlyManagement() {
requireManagement(msg.sender);
_;
}
/**
* @dev Require that the call is coming from either the strategies
* management or the keeper.
*/
modifier onlyKeepers() {
requireKeeperOrManagement(msg.sender);
_;
}
/**
* @dev Require that the call is coming from either the strategies
* management or the emergencyAdmin.
*/
modifier onlyEmergencyAuthorized() {
requireEmergencyAuthorized(msg.sender);
_;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Placed over all state changing functions for increased safety.
*/
modifier nonReentrant() {
StrategyData storage S = _strategyStorage();
// On the first call to nonReentrant, `entered` will be false (2)
require(S.entered != ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
S.entered = ENTERED;
_;
// Reset to false (1) once call has finished.
S.entered = NOT_ENTERED;
}
/**
* @notice Require a caller is `management`.
* @dev Is left public so that it can be used by the Strategy.
*
* When the Strategy calls this the msg.sender would be the
* address of the strategy so we need to specify the sender.
*
* @param _sender The original msg.sender.
*/
function requireManagement(address _sender) public view {
require(_sender == _strategyStorage().management, "!management");
}
/**
* @notice Require a caller is the `keeper` or `management`.
* @dev Is left public so that it can be used by the Strategy.
*
* When the Strategy calls this the msg.sender would be the
* address of the strategy so we need to specify the sender.
*
* @param _sender The original msg.sender.
*/
function requireKeeperOrManagement(address _sender) public view {
StrategyData storage S = _strategyStorage();
require(_sender == S.keeper || _sender == S.management, "!keeper");
}
/**
* @notice Require a caller is the `management` or `emergencyAdmin`.
* @dev Is left public so that it can be used by the Strategy.
*
* When the Strategy calls this the msg.sender would be the
* address of the strategy so we need to specify the sender.
*
* @param _sender The original msg.sender.
*/
function requireEmergencyAuthorized(address _sender) public view {
StrategyData storage S = _strategyStorage();
require(
_sender == S.emergencyAdmin || _sender == S.management,
"!emergency authorized"
);
}
/*//////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////*/
/// @notice API version this TokenizedStrategy implements.
string internal constant API_VERSION = "3.0.4";
/// @notice Value to set the `entered` flag to during a call.
uint8 internal constant ENTERED = 2;
/// @notice Value to set the `entered` flag to at the end of the call.
uint8 internal constant NOT_ENTERED = 1;
/// @notice Maximum in Basis Points the Performance Fee can be set to.
uint16 public constant MAX_FEE = 5_000; // 50%
/// @notice Used for fee calculations.
uint256 internal constant MAX_BPS = 10_000;
/// @notice Used for profit unlocking rate calculations.
uint256 internal constant MAX_BPS_EXTENDED = 1_000_000_000_000;
/// @notice Seconds per year for max profit unlocking time.
uint256 internal constant SECONDS_PER_YEAR = 31_556_952; // 365.2425 days
/**
* @dev Custom storage slot that will be used to store the
* `StrategyData` struct that holds each strategies
* specific storage variables.
*
* Any storage updates done by the TokenizedStrategy actually update
* the storage of the calling contract. This variable points
* to the specific location that will be used to store the
* struct that holds all that data.
*
* We use a custom string in order to get a random
* storage slot that will allow for strategists to use any
* amount of storage in their strategy without worrying
* about collisions.
*/
bytes32 internal constant BASE_STRATEGY_STORAGE =
bytes32(uint256(keccak256("yearn.base.strategy.storage")) - 1);
/*//////////////////////////////////////////////////////////////
IMMUTABLE
//////////////////////////////////////////////////////////////*/
/// @notice Address of the previously deployed Vault factory that the
// protocol fee config is retrieved from.
address public immutable FACTORY;
/*//////////////////////////////////////////////////////////////
STORAGE GETTER
//////////////////////////////////////////////////////////////*/
/**
* @dev will return the actual storage slot where the strategy
* specific `StrategyData` struct is stored for both read
* and write operations.
*
* This loads just the slot location, not the full struct
* so it can be used in a gas efficient manner.
*/
function _strategyStorage() internal pure returns (StrategyData storage S) {
// Since STORAGE_SLOT is a constant, we have to put a variable
// on the stack to access it from an inline assembly block.
bytes32 slot = BASE_STRATEGY_STORAGE;
assembly {
S.slot := slot
}
}
/*//////////////////////////////////////////////////////////////
INITIALIZATION
//////////////////////////////////////////////////////////////*/
/**
* @notice Used to initialize storage for a newly deployed strategy.
* @dev This should be called atomically whenever a new strategy is
* deployed and can only be called once for each strategy.
*
* This will set all the default storage that must be set for a
* strategy to function. Any changes can be made post deployment
* through external calls from `management`.
*
* The function will also emit an event that off chain indexers can
* look for to track any new deployments using this TokenizedStrategy.
*
* @param _asset Address of the underlying asset.
* @param _name Name the strategy will use.
* @param _management Address to set as the strategies `management`.
* @param _performanceFeeRecipient Address to receive performance fees.
* @param _keeper Address to set as strategies `keeper`.
*/
function initialize(
address _asset,
string memory _name,
address _management,
address _performanceFeeRecipient,
address _keeper
) external {
// Cache storage pointer.
StrategyData storage S = _strategyStorage();
// Make sure we aren't initialized.
require(address(S.asset) == address(0), "initialized");
// Set the strategy's underlying asset.
S.asset = ERC20(_asset);
// Set the Strategy Tokens name.
S.name = _name;
// Set decimals based off the `asset`.
S.decimals = ERC20(_asset).decimals();
// Default to a 10 day profit unlock period.
S.profitMaxUnlockTime = 10 days;
// Set address to receive performance fees.
// Can't be address(0) or we will be burning fees.
require(_performanceFeeRecipient != address(0), "ZERO ADDRESS");
// Can't mint shares to its self because of profit locking.
require(_performanceFeeRecipient != address(this), "self");
S.performanceFeeRecipient = _performanceFeeRecipient;
// Default to a 10% performance fee.
S.performanceFee = 1_000;
// Set last report to this block.
S.lastReport = uint96(block.timestamp);
// Set the default management address. Can't be 0.
require(_management != address(0), "ZERO ADDRESS");
S.management = _management;
// Set the keeper address
S.keeper = _keeper;
// Emit event to signal a new strategy has been initialized.
emit NewTokenizedStrategy(address(this), _asset, API_VERSION);
}
/*//////////////////////////////////////////////////////////////
ERC4626 WRITE METHODS
//////////////////////////////////////////////////////////////*/
/**
* @notice Mints `shares` of strategy shares to `receiver` by
* depositing exactly `assets` of underlying tokens.
* @param assets The amount of underlying to deposit in.
* @param receiver The address to receive the `shares`.
* @return shares The actual amount of shares issued.
*/
function deposit(
uint256 assets,
address receiver
) external nonReentrant returns (uint256 shares) {
// Get the storage slot for all following calls.
StrategyData storage S = _strategyStorage();
// Deposit full balance if using max uint.
if (assets == type(uint256).max) {
assets = S.asset.balanceOf(msg.sender);
}
// Checking max deposit will also check if shutdown.
require(
assets <= _maxDeposit(S, receiver),
"ERC4626: deposit more than max"
);
// Check for rounding error.
require(
(shares = _convertToShares(S, assets, Math.Rounding.Down)) != 0,
"ZERO_SHARES"
);
_deposit(S, receiver, assets, shares);
}
/**
* @notice Mints exactly `shares` of strategy shares to
* `receiver` by depositing `assets` of underlying tokens.
* @param shares The amount of strategy shares mint.
* @param receiver The address to receive the `shares`.
* @return assets The actual amount of asset deposited.
*/
function mint(
uint256 shares,
address receiver
) external nonReentrant returns (uint256 assets) {
// Get the storage slot for all following calls.
StrategyData storage S = _strategyStorage();
// Checking max mint will also check if shutdown.
require(shares <= _maxMint(S, receiver), "ERC4626: mint more than max");
// Check for rounding error.
require(
(assets = _convertToAssets(S, shares, Math.Rounding.Up)) != 0,
"ZERO_ASSETS"
);
_deposit(S, receiver, assets, shares);
}
/**
* @notice Withdraws exactly `assets` from `owners` shares and sends
* the underlying tokens to `receiver`.
* @dev This will default to not allowing any loss to be taken.
* @param assets The amount of underlying to withdraw.
* @param receiver The address to receive `assets`.
* @param owner The address whose shares are burnt.
* @return shares The actual amount of shares burnt.
*/
function withdraw(
uint256 assets,
address receiver,
address owner
) external returns (uint256 shares) {
return withdraw(assets, receiver, owner, 0);
}
/**
* @notice Withdraws `assets` from `owners` shares and sends
* the underlying tokens to `receiver`.
* @dev This includes an added parameter to allow for losses.
* @param assets The amount of underlying to withdraw.
* @param receiver The address to receive `assets`.
* @param owner The address whose shares are burnt.
* @param maxLoss The amount of acceptable loss in Basis points.
* @return shares The actual amount of shares burnt.
*/
function withdraw(
uint256 assets,
address receiver,
address owner,
uint256 maxLoss
) public nonReentrant returns (uint256 shares) {
// Get the storage slot for all following calls.
StrategyData storage S = _strategyStorage();
require(
assets <= _maxWithdraw(S, owner),
"ERC4626: withdraw more than max"
);
// Check for rounding error or 0 value.
require(
(shares = _convertToShares(S, assets, Math.Rounding.Up)) != 0,
"ZERO_SHARES"
);
// Withdraw and track the actual amount withdrawn for loss check.
_withdraw(S, receiver, owner, assets, shares, maxLoss);
}
/**
* @notice Redeems exactly `shares` from `owner` and
* sends `assets` of underlying tokens to `receiver`.
* @dev This will default to allowing any loss passed to be realized.
* @param shares The amount of shares burnt.
* @param receiver The address to receive `assets`.
* @param owner The address whose shares are burnt.
* @return assets The actual amount of underlying withdrawn.
*/
function redeem(
uint256 shares,
address receiver,
address owner
) external returns (uint256) {
// We default to not limiting a potential loss.
return redeem(shares, receiver, owner, MAX_BPS);
}
/**
* @notice Redeems exactly `shares` from `owner` and
* sends `assets` of underlying tokens to `receiver`.
* @dev This includes an added parameter to allow for losses.
* @param shares The amount of shares burnt.
* @param receiver The address to receive `assets`.
* @param owner The address whose shares are burnt.
* @param maxLoss The amount of acceptable loss in Basis points.
* @return . The actual amount of underlying withdrawn.
*/
function redeem(
uint256 shares,
address receiver,
address owner,
uint256 maxLoss
) public nonReentrant returns (uint256) {
// Get the storage slot for all following calls.
StrategyData storage S = _strategyStorage();
require(
shares <= _maxRedeem(S, owner),
"ERC4626: redeem more than max"
);
uint256 assets;
// Check for rounding error or 0 value.
require(
(assets = _convertToAssets(S, shares, Math.Rounding.Down)) != 0,
"ZERO_ASSETS"
);
// We need to return the actual amount withdrawn in case of a loss.
return _withdraw(S, receiver, owner, assets, shares, maxLoss);
}
/*//////////////////////////////////////////////////////////////
EXTERNAL 4626 VIEW METHODS
//////////////////////////////////////////////////////////////*/
/**
* @notice Get the total amount of assets this strategy holds
* as of the last report.
*
* We manually track `totalAssets` to avoid any PPS manipulation.
*
* @return . Total assets the strategy holds.
*/
function totalAssets() external view returns (uint256) {
return _totalAssets(_strategyStorage());
}
/**
* @notice Get the current supply of the strategies shares.
*
* Locked shares issued to the strategy from profits are not
* counted towards the full supply until they are unlocked.
*
* As more shares slowly unlock the totalSupply will decrease
* causing the PPS of the strategy to increase.
*
* @return . Total amount of shares outstanding.
*/
function totalSupply() external view returns (uint256) {
return _totalSupply(_strategyStorage());
}
/**
* @notice The amount of shares that the strategy would
* exchange for the amount of assets provided, in an
* ideal scenario where all the conditions are met.
*
* @param assets The amount of underlying.
* @return . Expected shares that `assets` represents.
*/
function convertToShares(uint256 assets) external view returns (uint256) {
return _convertToShares(_strategyStorage(), assets, Math.Rounding.Down);
}
/**
* @notice The amount of assets that the strategy would
* exchange for the amount of shares provided, in an
* ideal scenario where all the conditions are met.
*
* @param shares The amount of the strategies shares.
* @return . Expected amount of `asset` the shares represents.
*/
function convertToAssets(uint256 shares) external view returns (uint256) {
return _convertToAssets(_strategyStorage(), shares, Math.Rounding.Down);
}
/**
* @notice Allows an on-chain or off-chain user to simulate
* the effects of their deposit at the current block, given
* current on-chain conditions.
* @dev This will round down.
*
* @param assets The amount of `asset` to deposits.
* @return . Expected shares that would be issued.
*/
function previewDeposit(uint256 assets) external view returns (uint256) {
return _convertToShares(_strategyStorage(), assets, Math.Rounding.Down);
}
/**
* @notice Allows an on-chain or off-chain user to simulate
* the effects of their mint at the current block, given
* current on-chain conditions.
* @dev This is used instead of convertToAssets so that it can
* round up for safer mints.
*
* @param shares The amount of shares to mint.
* @return . The needed amount of `asset` for the mint.
*/
function previewMint(uint256 shares) external view returns (uint256) {
return _convertToAssets(_strategyStorage(), shares, Math.Rounding.Up);
}
/**
* @notice Allows an on-chain or off-chain user to simulate
* the effects of their withdrawal at the current block,
* given current on-chain conditions.
* @dev This is used instead of convertToShares so that it can
* round up for safer withdraws.
*
* @param assets The amount of `asset` that would be withdrawn.
* @return . The amount of shares that would be burnt.
*/
function previewWithdraw(uint256 assets) external view returns (uint256) {
return _convertToShares(_strategyStorage(), assets, Math.Rounding.Up);
}
/**
* @notice Allows an on-chain or off-chain user to simulate
* the effects of their redemption at the current block,
* given current on-chain conditions.
* @dev This will round down.
*
* @param shares The amount of shares that would be redeemed.
* @return . The amount of `asset` that would be returned.
*/
function previewRedeem(uint256 shares) external view returns (uint256) {
return _convertToAssets(_strategyStorage(), shares, Math.Rounding.Down);
}
/**
* @notice Total number of underlying assets that can
* be deposited into the strategy, where `receiver`
* corresponds to the receiver of the shares of a {deposit} call.
*
* @param receiver The address receiving the shares.
* @return . The max that `receiver` can deposit in `asset`.
*/
function maxDeposit(address receiver) external view returns (uint256) {
return _maxDeposit(_strategyStorage(), receiver);
}
/**
* @notice Total number of shares that can be minted to `receiver`
* of a {mint} call.
*
* @param receiver The address receiving the shares.
* @return _maxMint The max that `receiver` can mint in shares.
*/
function maxMint(address receiver) external view returns (uint256) {
return _maxMint(_strategyStorage(), receiver);
}
/**
* @notice Total number of underlying assets that can be
* withdrawn from the strategy by `owner`, where `owner`
* corresponds to the msg.sender of a {redeem} call.
*
* @param owner The owner of the shares.
* @return _maxWithdraw Max amount of `asset` that can be withdrawn.
*/
function maxWithdraw(address owner) external view returns (uint256) {
return _maxWithdraw(_strategyStorage(), owner);
}
/**
* @notice Variable `maxLoss` is ignored.
* @dev Accepts a `maxLoss` variable in order to match the multi
* strategy vaults ABI.
*/
function maxWithdraw(
address owner,
uint256 /*maxLoss*/
) external view returns (uint256) {
return _maxWithdraw(_strategyStorage(), owner);
}
/**
* @notice Total number of strategy shares that can be
* redeemed from the strategy by `owner`, where `owner`
* corresponds to the msg.sender of a {redeem} call.
*
* @param owner The owner of the shares.
* @return _maxRedeem Max amount of shares that can be redeemed.
*/
function maxRedeem(address owner) external view returns (uint256) {
return _maxRedeem(_strategyStorage(), owner);
}
/**
* @notice Variable `maxLoss` is ignored.
* @dev Accepts a `maxLoss` variable in order to match the multi
* strategy vaults ABI.
*/
function maxRedeem(
address owner,
uint256 /*maxLoss*/
) external view returns (uint256) {
return _maxRedeem(_strategyStorage(), owner);
}
/*//////////////////////////////////////////////////////////////
INTERNAL 4626 VIEW METHODS
//////////////////////////////////////////////////////////////*/
/// @dev Internal implementation of {totalAssets}.
function _totalAssets(
StrategyData storage S
) internal view returns (uint256) {
return S.totalAssets;
}
/// @dev Internal implementation of {totalSupply}.
function _totalSupply(
StrategyData storage S
) internal view returns (uint256) {
return S.totalSupply - _unlockedShares(S);
}
/// @dev Internal implementation of {convertToShares}.
function _convertToShares(
StrategyData storage S,
uint256 assets,
Math.Rounding _rounding
) internal view returns (uint256) {
// Saves an extra SLOAD if values are non-zero.
uint256 totalSupply_ = _totalSupply(S);
// If supply is 0, PPS = 1.
if (totalSupply_ == 0) return assets;
uint256 totalAssets_ = _totalAssets(S);
// If assets are 0 but supply is not PPS = 0.
if (totalAssets_ == 0) return 0;
return assets.mulDiv(totalSupply_, totalAssets_, _rounding);
}
/// @dev Internal implementation of {convertToAssets}.
function _convertToAssets(
StrategyData storage S,
uint256 shares,
Math.Rounding _rounding
) internal view returns (uint256) {
// Saves an extra SLOAD if totalSupply() is non-zero.
uint256 supply = _totalSupply(S);
return
supply == 0
? shares
: shares.mulDiv(_totalAssets(S), supply, _rounding);
}
/// @dev Internal implementation of {maxDeposit}.
function _maxDeposit(
StrategyData storage S,
address receiver
) internal view returns (uint256) {
// Cannot deposit when shutdown or to the strategy.
if (S.shutdown || receiver == address(this)) return 0;
return IBaseStrategy(address(this)).availableDepositLimit(receiver);
}
/// @dev Internal implementation of {maxMint}.
function _maxMint(
StrategyData storage S,
address receiver
) internal view returns (uint256 maxMint_) {
// Cannot mint when shutdown or to the strategy.
if (S.shutdown || receiver == address(this)) return 0;
maxMint_ = IBaseStrategy(address(this)).availableDepositLimit(receiver);
if (maxMint_ != type(uint256).max) {
maxMint_ = _convertToShares(S, maxMint_, Math.Rounding.Down);
}
}
/// @dev Internal implementation of {maxWithdraw}.
function _maxWithdraw(
StrategyData storage S,
address owner
) internal view returns (uint256 maxWithdraw_) {
// Get the max the owner could withdraw currently.
maxWithdraw_ = IBaseStrategy(address(this)).availableWithdrawLimit(
owner
);
// If there is no limit enforced.
if (maxWithdraw_ == type(uint256).max) {
// Saves a min check if there is no withdrawal limit.
maxWithdraw_ = _convertToAssets(
S,
_balanceOf(S, owner),
Math.Rounding.Down
);
} else {
maxWithdraw_ = Math.min(
_convertToAssets(S, _balanceOf(S, owner), Math.Rounding.Down),
maxWithdraw_
);
}
}
/// @dev Internal implementation of {maxRedeem}.
function _maxRedeem(
StrategyData storage S,
address owner
) internal view returns (uint256 maxRedeem_) {
// Get the max the owner could withdraw currently.
maxRedeem_ = IBaseStrategy(address(this)).availableWithdrawLimit(owner);
// Conversion would overflow and saves a min check if there is no withdrawal limit.
if (maxRedeem_ == type(uint256).max) {
maxRedeem_ = _balanceOf(S, owner);
} else {
maxRedeem_ = Math.min(
// Can't redeem more than the balance.
_convertToShares(S, maxRedeem_, Math.Rounding.Down),
_balanceOf(S, owner)
);
}
}
/*//////////////////////////////////////////////////////////////
INTERNAL 4626 WRITE METHODS
//////////////////////////////////////////////////////////////*/
/**
* @dev Function to be called during {deposit} and {mint}.
*
* This function handles all logic including transfers,
* minting and accounting.
*
* We do all external calls before updating any internal
* values to prevent view reentrancy issues from the token
* transfers or the _deployFunds() calls.
*/
function _deposit(
StrategyData storage S,
address receiver,
uint256 assets,
uint256 shares
) internal {
// Cache storage variables used more than once.
ERC20 _asset = S.asset;
// Need to transfer before minting or ERC777s could reenter.
_asset.safeTransferFrom(msg.sender, address(this), assets);
// We can deploy the full loose balance currently held.
IBaseStrategy(address(this)).deployFunds(
_asset.balanceOf(address(this))
);
// Adjust total Assets.
S.totalAssets += assets;
// mint shares
_mint(S, receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
}
/**
* @dev To be called during {redeem} and {withdraw}.
*
* This will handle all logic, transfers and accounting
* in order to service the withdraw request.
*
* If we are not able to withdraw the full amount needed, it will
* be counted as a loss and passed on to the user.
*/
function _withdraw(
StrategyData storage S,
address receiver,
address owner,
uint256 assets,
uint256 shares,
uint256 maxLoss
) internal returns (uint256) {
require(receiver != address(0), "ZERO ADDRESS");
require(maxLoss <= MAX_BPS, "exceeds MAX_BPS");
// Spend allowance if applicable.