-
Notifications
You must be signed in to change notification settings - Fork 99
/
Copy pathCustomOracleNAVIssuanceModule.sol
1125 lines (953 loc) · 44.2 KB
/
CustomOracleNAVIssuanceModule.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
/*
Copyright 2020 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol";
import { AddressArrayUtils } from "../../../lib/AddressArrayUtils.sol";
import { IController } from "../../../interfaces/IController.sol";
import { ISetValuer } from "../../../interfaces/ISetValuer.sol";
import { INAVIssuanceHook } from "../../../interfaces/INAVIssuanceHook.sol";
import { Invoke } from "../../lib/Invoke.sol";
import { ISetToken } from "../../../interfaces/ISetToken.sol";
import { IWETH } from "../../../interfaces/external/IWETH.sol";
import { ModuleBase } from "../../lib/ModuleBase.sol";
import { Position } from "../../lib/Position.sol";
import { PreciseUnitMath } from "../../../lib/PreciseUnitMath.sol";
import { ResourceIdentifier } from "../../lib/ResourceIdentifier.sol";
/**
* @title CustomOracleNavIssuanceModule
* @author Set Protocol
*
* Module that enables issuance and redemption with any valid ERC20 token or ETH if allowed by the manager. Sender receives
* a proportional amount of SetTokens on issuance or ERC20 token on redemption based on the calculated net asset value using
* oracle prices. Manager is able to enforce a premium / discount on issuance / redemption to avoid arbitrage and front
* running when relying on oracle prices. Managers can charge a fee (denominated in reserve asset).
*/
contract CustomOracleNavIssuanceModule is ModuleBase, ReentrancyGuard {
using AddressArrayUtils for address[];
using Invoke for ISetToken;
using Position for ISetToken;
using PreciseUnitMath for uint256;
using PreciseUnitMath for int256;
using ResourceIdentifier for IController;
using SafeMath for uint256;
using SafeCast for int256;
using SafeCast for uint256;
using SignedSafeMath for int256;
/* ============ Events ============ */
event SetTokenNAVIssued(
ISetToken indexed _setToken,
address indexed _issuer,
address indexed _to,
address _reserveAsset,
address _hookContract,
uint256 _setTokenQuantity,
uint256 _managerFee,
uint256 _premium
);
event SetTokenNAVRedeemed(
ISetToken indexed _setToken,
address indexed _redeemer,
address indexed _to,
address _reserveAsset,
address _hookContract,
uint256 _setTokenQuantity,
uint256 _managerFee,
uint256 _premium
);
event ReserveAssetAdded(
ISetToken indexed _setToken,
address _newReserveAsset
);
event ReserveAssetRemoved(
ISetToken indexed _setToken,
address _removedReserveAsset
);
event PremiumEdited(
ISetToken indexed _setToken,
uint256 _newPremium
);
event ManagerFeeEdited(
ISetToken indexed _setToken,
uint256 _newManagerFee,
uint256 _index
);
event FeeRecipientEdited(
ISetToken indexed _setToken,
address _feeRecipient
);
/* ============ Structs ============ */
struct NAVIssuanceSettings {
INAVIssuanceHook managerIssuanceHook; // Issuance hook configurations
INAVIssuanceHook managerRedemptionHook; // Redemption hook configurations
ISetValuer setValuer; // Optional custom set valuer. If address(0) is provided, fetch the default one from the controller
address[] reserveAssets; // Allowed reserve assets - Must have a price enabled with the price oracle
address feeRecipient; // Manager fee recipient
uint256[2] managerFees; // Manager fees. 0 index is issue and 1 index is redeem fee (0.01% = 1e14, 1% = 1e16)
uint256 maxManagerFee; // Maximum fee manager is allowed to set for issue and redeem
uint256 premiumPercentage; // Premium percentage (0.01% = 1e14, 1% = 1e16). This premium is a buffer around oracle
// prices paid by user to the SetToken, which prevents arbitrage and oracle front running
uint256 maxPremiumPercentage; // Maximum premium percentage manager is allowed to set (configured by manager)
uint256 minSetTokenSupply; // Minimum SetToken supply required for issuance and redemption
// to prevent dramatic inflationary changes to the SetToken's position multiplier
}
struct ActionInfo {
uint256 preFeeReserveQuantity; // Reserve value before fees; During issuance, represents raw quantity
// During redeem, represents post-premium value
uint256 protocolFees; // Total protocol fees (direct + manager revenue share)
uint256 managerFee; // Total manager fee paid in reserve asset
uint256 netFlowQuantity; // When issuing, quantity of reserve asset sent to SetToken
// When redeeming, quantity of reserve asset sent to redeemer
uint256 setTokenQuantity; // When issuing, quantity of SetTokens minted to mintee
// When redeeming, quantity of SetToken redeemed
uint256 previousSetTokenSupply; // SetToken supply prior to issue/redeem action
uint256 newSetTokenSupply; // SetToken supply after issue/redeem action
int256 newPositionMultiplier; // SetToken position multiplier after issue/redeem
uint256 newReservePositionUnit; // SetToken reserve asset position unit after issue/redeem
}
/* ============ State Variables ============ */
// Wrapped ETH address
IWETH public immutable weth;
// Mapping of SetToken to NAV issuance settings struct
mapping(ISetToken => NAVIssuanceSettings) public navIssuanceSettings;
// Mapping to efficiently check a SetToken's reserve asset validity
// SetToken => reserveAsset => isReserveAsset
mapping(ISetToken => mapping(address => bool)) public isReserveAsset;
/* ============ Constants ============ */
// 0 index stores the manager fee in managerFees array, percentage charged on issue (denominated in reserve asset)
uint256 constant internal MANAGER_ISSUE_FEE_INDEX = 0;
// 1 index stores the manager fee percentage in managerFees array, charged on redeem
uint256 constant internal MANAGER_REDEEM_FEE_INDEX = 1;
// 0 index stores the manager revenue share protocol fee % on the controller, charged in the issuance function
uint256 constant internal PROTOCOL_ISSUE_MANAGER_REVENUE_SHARE_FEE_INDEX = 0;
// 1 index stores the manager revenue share protocol fee % on the controller, charged in the redeem function
uint256 constant internal PROTOCOL_REDEEM_MANAGER_REVENUE_SHARE_FEE_INDEX = 1;
// 2 index stores the direct protocol fee % on the controller, charged in the issuance function
uint256 constant internal PROTOCOL_ISSUE_DIRECT_FEE_INDEX = 2;
// 3 index stores the direct protocol fee % on the controller, charged in the redeem function
uint256 constant internal PROTOCOL_REDEEM_DIRECT_FEE_INDEX = 3;
/* ============ Constructor ============ */
/**
* @param _controller Address of controller contract
* @param _weth Address of wrapped eth
*/
constructor(IController _controller, IWETH _weth) public ModuleBase(_controller) {
weth = _weth;
}
/* ============ External Functions ============ */
/**
* Deposits the allowed reserve asset into the SetToken and mints the appropriate % of Net Asset Value of the SetToken
* to the specified _to address.
*
* @param _setToken Instance of the SetToken contract
* @param _reserveAsset Address of the reserve asset to issue with
* @param _reserveAssetQuantity Quantity of the reserve asset to issue with
* @param _minSetTokenReceiveQuantity Min quantity of SetToken to receive after issuance
* @param _to Address to mint SetToken to
*/
function issue(
ISetToken _setToken,
address _reserveAsset,
uint256 _reserveAssetQuantity,
uint256 _minSetTokenReceiveQuantity,
address _to
)
external
nonReentrant
onlyValidAndInitializedSet(_setToken)
{
_validateCommon(_setToken, _reserveAsset, _reserveAssetQuantity);
_callPreIssueHooks(_setToken, _reserveAsset, _reserveAssetQuantity, msg.sender, _to);
ActionInfo memory issueInfo = _createIssuanceInfo(_setToken, _reserveAsset, _reserveAssetQuantity);
_validateIssuanceInfo(_setToken, _minSetTokenReceiveQuantity, issueInfo);
_transferCollateralAndHandleFees(_setToken, IERC20(_reserveAsset), issueInfo);
_handleIssueStateUpdates(_setToken, _reserveAsset, _to, issueInfo);
}
/**
* Wraps ETH and deposits WETH if allowed into the SetToken and mints the appropriate % of Net Asset Value of the SetToken
* to the specified _to address.
*
* @param _setToken Instance of the SetToken contract
* @param _minSetTokenReceiveQuantity Min quantity of SetToken to receive after issuance
* @param _to Address to mint SetToken to
*/
function issueWithEther(
ISetToken _setToken,
uint256 _minSetTokenReceiveQuantity,
address _to
)
external
payable
nonReentrant
onlyValidAndInitializedSet(_setToken)
{
weth.deposit{ value: msg.value }();
_validateCommon(_setToken, address(weth), msg.value);
_callPreIssueHooks(_setToken, address(weth), msg.value, msg.sender, _to);
ActionInfo memory issueInfo = _createIssuanceInfo(_setToken, address(weth), msg.value);
_validateIssuanceInfo(_setToken, _minSetTokenReceiveQuantity, issueInfo);
_transferWETHAndHandleFees(_setToken, issueInfo);
_handleIssueStateUpdates(_setToken, address(weth), _to, issueInfo);
}
/**
* Redeems a SetToken into a valid reserve asset representing the appropriate % of Net Asset Value of the SetToken
* to the specified _to address. Only valid if there are available reserve units on the SetToken.
*
* @param _setToken Instance of the SetToken contract
* @param _reserveAsset Address of the reserve asset to redeem with
* @param _setTokenQuantity Quantity of SetTokens to redeem
* @param _minReserveReceiveQuantity Min quantity of reserve asset to receive
* @param _to Address to redeem reserve asset to
*/
function redeem(
ISetToken _setToken,
address _reserveAsset,
uint256 _setTokenQuantity,
uint256 _minReserveReceiveQuantity,
address _to
)
external
nonReentrant
onlyValidAndInitializedSet(_setToken)
{
_validateCommon(_setToken, _reserveAsset, _setTokenQuantity);
_callPreRedeemHooks(_setToken, _setTokenQuantity, msg.sender, _to);
ActionInfo memory redeemInfo = _createRedemptionInfo(_setToken, _reserveAsset, _setTokenQuantity);
_validateRedemptionInfo(_setToken, _minReserveReceiveQuantity, redeemInfo);
_setToken.burn(msg.sender, _setTokenQuantity);
// Instruct the SetToken to transfer the reserve asset back to the user
_setToken.strictInvokeTransfer(
_reserveAsset,
_to,
redeemInfo.netFlowQuantity
);
_handleRedemptionFees(_setToken, _reserveAsset, redeemInfo);
_handleRedeemStateUpdates(_setToken, _reserveAsset, _to, redeemInfo);
}
/**
* Redeems a SetToken into Ether (if WETH is valid) representing the appropriate % of Net Asset Value of the SetToken
* to the specified _to address. Only valid if there are available WETH units on the SetToken.
*
* @param _setToken Instance of the SetToken contract
* @param _setTokenQuantity Quantity of SetTokens to redeem
* @param _minReserveReceiveQuantity Min quantity of reserve asset to receive
* @param _to Address to redeem reserve asset to
*/
function redeemIntoEther(
ISetToken _setToken,
uint256 _setTokenQuantity,
uint256 _minReserveReceiveQuantity,
address payable _to
)
external
nonReentrant
onlyValidAndInitializedSet(_setToken)
{
_validateCommon(_setToken, address(weth), _setTokenQuantity);
_callPreRedeemHooks(_setToken, _setTokenQuantity, msg.sender, _to);
ActionInfo memory redeemInfo = _createRedemptionInfo(_setToken, address(weth), _setTokenQuantity);
_validateRedemptionInfo(_setToken, _minReserveReceiveQuantity, redeemInfo);
_setToken.burn(msg.sender, _setTokenQuantity);
// Instruct the SetToken to transfer WETH from SetToken to module
_setToken.strictInvokeTransfer(
address(weth),
address(this),
redeemInfo.netFlowQuantity
);
weth.withdraw(redeemInfo.netFlowQuantity);
_to.transfer(redeemInfo.netFlowQuantity);
_handleRedemptionFees(_setToken, address(weth), redeemInfo);
_handleRedeemStateUpdates(_setToken, address(weth), _to, redeemInfo);
}
/**
* SET MANAGER ONLY. Add an allowed reserve asset
*
* @param _setToken Instance of the SetToken
* @param _reserveAsset Address of the reserve asset to add
*/
function addReserveAsset(ISetToken _setToken, address _reserveAsset) external onlyManagerAndValidSet(_setToken) {
require(!isReserveAsset[_setToken][_reserveAsset], "Reserve asset already exists");
navIssuanceSettings[_setToken].reserveAssets.push(_reserveAsset);
isReserveAsset[_setToken][_reserveAsset] = true;
emit ReserveAssetAdded(_setToken, _reserveAsset);
}
/**
* SET MANAGER ONLY. Remove a reserve asset
*
* @param _setToken Instance of the SetToken
* @param _reserveAsset Address of the reserve asset to remove
*/
function removeReserveAsset(ISetToken _setToken, address _reserveAsset) external onlyManagerAndValidSet(_setToken) {
require(isReserveAsset[_setToken][_reserveAsset], "Reserve asset does not exist");
navIssuanceSettings[_setToken].reserveAssets = navIssuanceSettings[_setToken].reserveAssets.remove(_reserveAsset);
delete isReserveAsset[_setToken][_reserveAsset];
emit ReserveAssetRemoved(_setToken, _reserveAsset);
}
/**
* SET MANAGER ONLY. Edit the premium percentage
*
* @param _setToken Instance of the SetToken
* @param _premiumPercentage Premium percentage in 10e16 (e.g. 10e16 = 1%)
*/
function editPremium(ISetToken _setToken, uint256 _premiumPercentage) external onlyManagerAndValidSet(_setToken) {
require(_premiumPercentage <= navIssuanceSettings[_setToken].maxPremiumPercentage, "Premium must be less than maximum allowed");
navIssuanceSettings[_setToken].premiumPercentage = _premiumPercentage;
emit PremiumEdited(_setToken, _premiumPercentage);
}
/**
* SET MANAGER ONLY. Edit manager fee
*
* @param _setToken Instance of the SetToken
* @param _managerFeePercentage Manager fee percentage in 10e16 (e.g. 10e16 = 1%)
* @param _managerFeeIndex Manager fee index. 0 index is issue fee, 1 index is redeem fee
*/
function editManagerFee(
ISetToken _setToken,
uint256 _managerFeePercentage,
uint256 _managerFeeIndex
)
external
onlyManagerAndValidSet(_setToken)
{
require(_managerFeePercentage <= navIssuanceSettings[_setToken].maxManagerFee, "Manager fee must be less than maximum allowed");
navIssuanceSettings[_setToken].managerFees[_managerFeeIndex] = _managerFeePercentage;
emit ManagerFeeEdited(_setToken, _managerFeePercentage, _managerFeeIndex);
}
/**
* SET MANAGER ONLY. Edit the manager fee recipient
*
* @param _setToken Instance of the SetToken
* @param _managerFeeRecipient Manager fee recipient
*/
function editFeeRecipient(ISetToken _setToken, address _managerFeeRecipient) external onlyManagerAndValidSet(_setToken) {
require(_managerFeeRecipient != address(0), "Fee recipient must not be 0 address");
navIssuanceSettings[_setToken].feeRecipient = _managerFeeRecipient;
emit FeeRecipientEdited(_setToken, _managerFeeRecipient);
}
/**
* SET MANAGER ONLY. Initializes this module to the SetToken with hooks, allowed reserve assets,
* fees and issuance premium. Only callable by the SetToken's manager. Hook addresses are optional.
* Address(0) means that no hook will be called.
*
* @param _setToken Instance of the SetToken to issue
* @param _navIssuanceSettings NAVIssuanceSettings struct defining parameters
*/
function initialize(
ISetToken _setToken,
NAVIssuanceSettings memory _navIssuanceSettings
)
external
onlySetManager(_setToken, msg.sender)
onlyValidAndPendingSet(_setToken)
{
require(_navIssuanceSettings.reserveAssets.length > 0, "Reserve assets must be greater than 0");
require(_navIssuanceSettings.maxManagerFee < PreciseUnitMath.preciseUnit(), "Max manager fee must be less than 100%");
require(_navIssuanceSettings.maxPremiumPercentage < PreciseUnitMath.preciseUnit(), "Max premium percentage must be less than 100%");
require(_navIssuanceSettings.managerFees[0] <= _navIssuanceSettings.maxManagerFee, "Manager issue fee must be less than max");
require(_navIssuanceSettings.managerFees[1] <= _navIssuanceSettings.maxManagerFee, "Manager redeem fee must be less than max");
require(_navIssuanceSettings.premiumPercentage <= _navIssuanceSettings.maxPremiumPercentage, "Premium must be less than max");
require(_navIssuanceSettings.feeRecipient != address(0), "Fee Recipient must be non-zero address.");
// Initial mint of Set cannot use NAVIssuance since minSetTokenSupply must be > 0
require(_navIssuanceSettings.minSetTokenSupply > 0, "Min SetToken supply must be greater than 0");
for (uint256 i = 0; i < _navIssuanceSettings.reserveAssets.length; i++) {
require(!isReserveAsset[_setToken][_navIssuanceSettings.reserveAssets[i]], "Reserve assets must be unique");
isReserveAsset[_setToken][_navIssuanceSettings.reserveAssets[i]] = true;
}
navIssuanceSettings[_setToken] = _navIssuanceSettings;
_setToken.initializeModule();
}
/**
* Removes this module from the SetToken, via call by the SetToken. Issuance settings and
* reserve asset states are deleted.
*/
function removeModule() external override {
ISetToken setToken = ISetToken(msg.sender);
for (uint256 i = 0; i < navIssuanceSettings[setToken].reserveAssets.length; i++) {
delete isReserveAsset[setToken][navIssuanceSettings[setToken].reserveAssets[i]];
}
delete navIssuanceSettings[setToken];
}
receive() external payable {}
/* ============ External Getter Functions ============ */
function getReserveAssets(ISetToken _setToken) external view returns (address[] memory) {
return navIssuanceSettings[_setToken].reserveAssets;
}
function getIssuePremium(
ISetToken _setToken,
address _reserveAsset,
uint256 _reserveAssetQuantity
)
external
view
returns (uint256)
{
return _getIssuePremium(_setToken, _reserveAsset, _reserveAssetQuantity);
}
function getRedeemPremium(
ISetToken _setToken,
address _reserveAsset,
uint256 _setTokenQuantity
)
external
view
returns (uint256)
{
return _getRedeemPremium(_setToken, _reserveAsset, _setTokenQuantity);
}
function getManagerFee(ISetToken _setToken, uint256 _managerFeeIndex) external view returns (uint256) {
return navIssuanceSettings[_setToken].managerFees[_managerFeeIndex];
}
/**
* Get the expected SetTokens minted to recipient on issuance
*
* @param _setToken Instance of the SetToken
* @param _reserveAsset Address of the reserve asset
* @param _reserveAssetQuantity Quantity of the reserve asset to issue with
*
* @return uint256 Expected SetTokens to be minted to recipient
*/
function getExpectedSetTokenIssueQuantity(
ISetToken _setToken,
address _reserveAsset,
uint256 _reserveAssetQuantity
)
external
view
returns (uint256)
{
(,, uint256 netReserveFlow) = _getFees(
_setToken,
_reserveAssetQuantity,
PROTOCOL_ISSUE_MANAGER_REVENUE_SHARE_FEE_INDEX,
PROTOCOL_ISSUE_DIRECT_FEE_INDEX,
MANAGER_ISSUE_FEE_INDEX
);
uint256 setTotalSupply = _setToken.totalSupply();
return _getSetTokenMintQuantity(
_setToken,
_reserveAsset,
netReserveFlow,
setTotalSupply
);
}
/**
* Get the expected reserve asset to be redeemed
*
* @param _setToken Instance of the SetToken
* @param _reserveAsset Address of the reserve asset
* @param _setTokenQuantity Quantity of SetTokens to redeem
*
* @return uint256 Expected reserve asset quantity redeemed
*/
function getExpectedReserveRedeemQuantity(
ISetToken _setToken,
address _reserveAsset,
uint256 _setTokenQuantity
)
external
view
returns (uint256)
{
uint256 preFeeReserveQuantity = _getRedeemReserveQuantity(_setToken, _reserveAsset, _setTokenQuantity);
(,, uint256 netReserveFlows) = _getFees(
_setToken,
preFeeReserveQuantity,
PROTOCOL_REDEEM_MANAGER_REVENUE_SHARE_FEE_INDEX,
PROTOCOL_REDEEM_DIRECT_FEE_INDEX,
MANAGER_REDEEM_FEE_INDEX
);
return netReserveFlows;
}
/**
* Checks if issue is valid
*
* @param _setToken Instance of the SetToken
* @param _reserveAsset Address of the reserve asset
* @param _reserveAssetQuantity Quantity of the reserve asset to issue with
*
* @return bool Returns true if issue is valid
*/
function isIssueValid(
ISetToken _setToken,
address _reserveAsset,
uint256 _reserveAssetQuantity
)
external
view
returns (bool)
{
uint256 setTotalSupply = _setToken.totalSupply();
return _reserveAssetQuantity != 0
&& isReserveAsset[_setToken][_reserveAsset]
&& setTotalSupply >= navIssuanceSettings[_setToken].minSetTokenSupply;
}
/**
* Checks if redeem is valid
*
* @param _setToken Instance of the SetToken
* @param _reserveAsset Address of the reserve asset
* @param _setTokenQuantity Quantity of SetTokens to redeem
*
* @return bool Returns true if redeem is valid
*/
function isRedeemValid(
ISetToken _setToken,
address _reserveAsset,
uint256 _setTokenQuantity
)
external
view
returns (bool)
{
uint256 setTotalSupply = _setToken.totalSupply();
if (
_setTokenQuantity == 0
|| !isReserveAsset[_setToken][_reserveAsset]
|| setTotalSupply < navIssuanceSettings[_setToken].minSetTokenSupply.add(_setTokenQuantity)
) {
return false;
} else {
uint256 totalRedeemValue =_getRedeemReserveQuantity(_setToken, _reserveAsset, _setTokenQuantity);
(,, uint256 expectedRedeemQuantity) = _getFees(
_setToken,
totalRedeemValue,
PROTOCOL_REDEEM_MANAGER_REVENUE_SHARE_FEE_INDEX,
PROTOCOL_REDEEM_DIRECT_FEE_INDEX,
MANAGER_REDEEM_FEE_INDEX
);
uint256 existingUnit = _setToken.getDefaultPositionRealUnit(_reserveAsset).toUint256();
return existingUnit.preciseMul(setTotalSupply) >= expectedRedeemQuantity;
}
}
/* ============ Internal Functions ============ */
function _validateCommon(ISetToken _setToken, address _reserveAsset, uint256 _quantity) internal view {
require(_quantity > 0, "Quantity must be > 0");
require(isReserveAsset[_setToken][_reserveAsset], "Must be valid reserve asset");
}
function _validateIssuanceInfo(ISetToken _setToken, uint256 _minSetTokenReceiveQuantity, ActionInfo memory _issueInfo) internal view {
// Check that total supply is greater than min supply needed for issuance
// Note: A min supply amount is needed to avoid division by 0 when SetToken supply is 0
require(
_issueInfo.previousSetTokenSupply >= navIssuanceSettings[_setToken].minSetTokenSupply,
"Supply must be greater than minimum to enable issuance"
);
require(_issueInfo.setTokenQuantity >= _minSetTokenReceiveQuantity, "Must be greater than min SetToken");
}
function _validateRedemptionInfo(
ISetToken _setToken,
uint256 _minReserveReceiveQuantity,
ActionInfo memory _redeemInfo
)
internal
view
{
// Check that new supply is more than min supply needed for redemption
// Note: A min supply amount is needed to avoid division by 0 when redeeming SetToken to 0
require(
_redeemInfo.newSetTokenSupply >= navIssuanceSettings[_setToken].minSetTokenSupply,
"Supply must be greater than minimum to enable redemption"
);
require(_redeemInfo.netFlowQuantity >= _minReserveReceiveQuantity, "Must be greater than min receive reserve quantity");
}
function _createIssuanceInfo(
ISetToken _setToken,
address _reserveAsset,
uint256 _reserveAssetQuantity
)
internal
view
returns (ActionInfo memory)
{
ActionInfo memory issueInfo;
issueInfo.previousSetTokenSupply = _setToken.totalSupply();
issueInfo.preFeeReserveQuantity = _reserveAssetQuantity;
(issueInfo.protocolFees, issueInfo.managerFee, issueInfo.netFlowQuantity) = _getFees(
_setToken,
issueInfo.preFeeReserveQuantity,
PROTOCOL_ISSUE_MANAGER_REVENUE_SHARE_FEE_INDEX,
PROTOCOL_ISSUE_DIRECT_FEE_INDEX,
MANAGER_ISSUE_FEE_INDEX
);
issueInfo.setTokenQuantity = _getSetTokenMintQuantity(
_setToken,
_reserveAsset,
issueInfo.netFlowQuantity,
issueInfo.previousSetTokenSupply
);
(issueInfo.newSetTokenSupply, issueInfo.newPositionMultiplier) = _getIssuePositionMultiplier(_setToken, issueInfo);
issueInfo.newReservePositionUnit = _getIssuePositionUnit(_setToken, _reserveAsset, issueInfo);
return issueInfo;
}
function _createRedemptionInfo(
ISetToken _setToken,
address _reserveAsset,
uint256 _setTokenQuantity
)
internal
view
returns (ActionInfo memory)
{
ActionInfo memory redeemInfo;
redeemInfo.setTokenQuantity = _setTokenQuantity;
redeemInfo.preFeeReserveQuantity =_getRedeemReserveQuantity(_setToken, _reserveAsset, _setTokenQuantity);
(redeemInfo.protocolFees, redeemInfo.managerFee, redeemInfo.netFlowQuantity) = _getFees(
_setToken,
redeemInfo.preFeeReserveQuantity,
PROTOCOL_REDEEM_MANAGER_REVENUE_SHARE_FEE_INDEX,
PROTOCOL_REDEEM_DIRECT_FEE_INDEX,
MANAGER_REDEEM_FEE_INDEX
);
redeemInfo.previousSetTokenSupply = _setToken.totalSupply();
(redeemInfo.newSetTokenSupply, redeemInfo.newPositionMultiplier) = _getRedeemPositionMultiplier(_setToken, _setTokenQuantity, redeemInfo);
redeemInfo.newReservePositionUnit = _getRedeemPositionUnit(_setToken, _reserveAsset, redeemInfo);
return redeemInfo;
}
/**
* Transfer reserve asset from user to SetToken and fees from user to appropriate fee recipients
*/
function _transferCollateralAndHandleFees(ISetToken _setToken, IERC20 _reserveAsset, ActionInfo memory _issueInfo) internal {
transferFrom(_reserveAsset, msg.sender, address(_setToken), _issueInfo.netFlowQuantity);
if (_issueInfo.protocolFees > 0) {
transferFrom(_reserveAsset, msg.sender, controller.feeRecipient(), _issueInfo.protocolFees);
}
if (_issueInfo.managerFee > 0) {
transferFrom(_reserveAsset, msg.sender, navIssuanceSettings[_setToken].feeRecipient, _issueInfo.managerFee);
}
}
/**
* Transfer WETH from module to SetToken and fees from module to appropriate fee recipients
*/
function _transferWETHAndHandleFees(ISetToken _setToken, ActionInfo memory _issueInfo) internal {
weth.transfer(address(_setToken), _issueInfo.netFlowQuantity);
if (_issueInfo.protocolFees > 0) {
weth.transfer(controller.feeRecipient(), _issueInfo.protocolFees);
}
if (_issueInfo.managerFee > 0) {
weth.transfer(navIssuanceSettings[_setToken].feeRecipient, _issueInfo.managerFee);
}
}
function _handleIssueStateUpdates(
ISetToken _setToken,
address _reserveAsset,
address _to,
ActionInfo memory _issueInfo
)
internal
{
_setToken.editPositionMultiplier(_issueInfo.newPositionMultiplier);
_setToken.editDefaultPosition(_reserveAsset, _issueInfo.newReservePositionUnit);
_setToken.mint(_to, _issueInfo.setTokenQuantity);
emit SetTokenNAVIssued(
_setToken,
msg.sender,
_to,
_reserveAsset,
address(navIssuanceSettings[_setToken].managerIssuanceHook),
_issueInfo.setTokenQuantity,
_issueInfo.managerFee,
_issueInfo.protocolFees
);
}
function _handleRedeemStateUpdates(
ISetToken _setToken,
address _reserveAsset,
address _to,
ActionInfo memory _redeemInfo
)
internal
{
_setToken.editPositionMultiplier(_redeemInfo.newPositionMultiplier);
_setToken.editDefaultPosition(_reserveAsset, _redeemInfo.newReservePositionUnit);
emit SetTokenNAVRedeemed(
_setToken,
msg.sender,
_to,
_reserveAsset,
address(navIssuanceSettings[_setToken].managerRedemptionHook),
_redeemInfo.setTokenQuantity,
_redeemInfo.managerFee,
_redeemInfo.protocolFees
);
}
function _handleRedemptionFees(ISetToken _setToken, address _reserveAsset, ActionInfo memory _redeemInfo) internal {
// Instruct the SetToken to transfer protocol fee to fee recipient if there is a fee
payProtocolFeeFromSetToken(_setToken, _reserveAsset, _redeemInfo.protocolFees);
// Instruct the SetToken to transfer manager fee to manager fee recipient if there is a fee
if (_redeemInfo.managerFee > 0) {
_setToken.strictInvokeTransfer(
_reserveAsset,
navIssuanceSettings[_setToken].feeRecipient,
_redeemInfo.managerFee
);
}
}
/**
* Returns the issue premium percentage. Virtual function that can be overridden in future versions of the module
* and can contain arbitrary logic to calculate the issuance premium.
*/
function _getIssuePremium(
ISetToken _setToken,
address /* _reserveAsset */,
uint256 /* _reserveAssetQuantity */
)
virtual
internal
view
returns (uint256)
{
return navIssuanceSettings[_setToken].premiumPercentage;
}
/**
* Returns the redeem premium percentage. Virtual function that can be overridden in future versions of the module
* and can contain arbitrary logic to calculate the redemption premium.
*/
function _getRedeemPremium(
ISetToken _setToken,
address /* _reserveAsset */,
uint256 /* _setTokenQuantity */
)
virtual
internal
view
returns (uint256)
{
return navIssuanceSettings[_setToken].premiumPercentage;
}
/**
* Returns the fees attributed to the manager and the protocol. The fees are calculated as follows:
*
* ManagerFee = (manager fee % - % to protocol) * reserveAssetQuantity
* Protocol Fee = (% manager fee share + direct fee %) * reserveAssetQuantity
*
* @param _setToken Instance of the SetToken
* @param _reserveAssetQuantity Quantity of reserve asset to calculate fees from
* @param _protocolManagerFeeIndex Index to pull rev share NAV Issuance fee from the Controller
* @param _protocolDirectFeeIndex Index to pull direct NAV issuance fee from the Controller
* @param _managerFeeIndex Index from NAVIssuanceSettings (0 = issue fee, 1 = redeem fee)
*
* @return uint256 Fees paid to the protocol in reserve asset
* @return uint256 Fees paid to the manager in reserve asset
* @return uint256 Net reserve to user net of fees
*/
function _getFees(
ISetToken _setToken,
uint256 _reserveAssetQuantity,
uint256 _protocolManagerFeeIndex,
uint256 _protocolDirectFeeIndex,
uint256 _managerFeeIndex
)
internal
view
returns (uint256, uint256, uint256)
{
(uint256 protocolFeePercentage, uint256 managerFeePercentage) = _getProtocolAndManagerFeePercentages(
_setToken,
_protocolManagerFeeIndex,
_protocolDirectFeeIndex,
_managerFeeIndex
);
// Calculate total notional fees
uint256 protocolFees = protocolFeePercentage.preciseMul(_reserveAssetQuantity);
uint256 managerFee = managerFeePercentage.preciseMul(_reserveAssetQuantity);
uint256 netReserveFlow = _reserveAssetQuantity.sub(protocolFees).sub(managerFee);
return (protocolFees, managerFee, netReserveFlow);
}
function _getProtocolAndManagerFeePercentages(
ISetToken _setToken,
uint256 _protocolManagerFeeIndex,
uint256 _protocolDirectFeeIndex,
uint256 _managerFeeIndex
)
internal
view
returns(uint256, uint256)
{
// Get protocol fee percentages
uint256 protocolDirectFeePercent = controller.getModuleFee(address(this), _protocolDirectFeeIndex);
uint256 protocolManagerShareFeePercent = controller.getModuleFee(address(this), _protocolManagerFeeIndex);
uint256 managerFeePercent = navIssuanceSettings[_setToken].managerFees[_managerFeeIndex];
// Calculate revenue share split percentage
uint256 protocolRevenueSharePercentage = protocolManagerShareFeePercent.preciseMul(managerFeePercent);
uint256 managerRevenueSharePercentage = managerFeePercent.sub(protocolRevenueSharePercentage);
uint256 totalProtocolFeePercentage = protocolRevenueSharePercentage.add(protocolDirectFeePercent);
return (totalProtocolFeePercentage, managerRevenueSharePercentage);
}
function _getSetTokenMintQuantity(
ISetToken _setToken,
address _reserveAsset,
uint256 _netReserveFlows, // Value of reserve asset net of fees
uint256 _setTotalSupply
)
internal
view
returns (uint256)
{
uint256 premiumPercentage = _getIssuePremium(_setToken, _reserveAsset, _netReserveFlows);
uint256 premiumValue = _netReserveFlows.preciseMul(premiumPercentage);
// If the set manager provided a custom valuer at initialization time, use it. Otherwise get it from the controller
// Get valuation of the SetToken with the quote asset as the reserve asset. Returns value in precise units (1e18)
// Reverts if price is not found
uint256 setTokenValuation = _getSetValuer(_setToken).calculateSetTokenValuation(_setToken, _reserveAsset);
// Get reserve asset decimals
uint256 reserveAssetDecimals = ERC20(_reserveAsset).decimals();
uint256 normalizedTotalReserveQuantityNetFees = _netReserveFlows.preciseDiv(10 ** reserveAssetDecimals);
uint256 normalizedTotalReserveQuantityNetFeesAndPremium = _netReserveFlows.sub(premiumValue).preciseDiv(10 ** reserveAssetDecimals);
// Calculate SetTokens to mint to issuer
uint256 denominator = _setTotalSupply.preciseMul(setTokenValuation).add(normalizedTotalReserveQuantityNetFees).sub(normalizedTotalReserveQuantityNetFeesAndPremium);
return normalizedTotalReserveQuantityNetFeesAndPremium.preciseMul(_setTotalSupply).preciseDiv(denominator);
}
function _getRedeemReserveQuantity(
ISetToken _setToken,
address _reserveAsset,
uint256 _setTokenQuantity
)
internal
view
returns (uint256)
{
// Get valuation of the SetToken with the quote asset as the reserve asset. Returns value in precise units (10e18)
// Reverts if price is not found
uint256 setTokenValuation = _getSetValuer(_setToken).calculateSetTokenValuation(_setToken, _reserveAsset);
uint256 totalRedeemValueInPreciseUnits = _setTokenQuantity.preciseMul(setTokenValuation);
// Get reserve asset decimals
uint256 reserveAssetDecimals = ERC20(_reserveAsset).decimals();
uint256 prePremiumReserveQuantity = totalRedeemValueInPreciseUnits.preciseMul(10 ** reserveAssetDecimals);
uint256 premiumPercentage = _getRedeemPremium(_setToken, _reserveAsset, _setTokenQuantity);
uint256 premiumQuantity = prePremiumReserveQuantity.preciseMulCeil(premiumPercentage);
return prePremiumReserveQuantity.sub(premiumQuantity);
}
/**
* The new position multiplier is calculated as follows:
* inflationPercentage = (newSupply - oldSupply) / newSupply
* newMultiplier = (1 - inflationPercentage) * positionMultiplier
*/
function _getIssuePositionMultiplier(