-
Notifications
You must be signed in to change notification settings - Fork 108
Expand file tree
/
Copy pathDebtIssuanceModule.sol
More file actions
747 lines (660 loc) · 30.4 KB
/
DebtIssuanceModule.sol
File metadata and controls
747 lines (660 loc) · 30.4 KB
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
/*
Copyright 2021 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 { 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 { IManagerIssuanceHook } from "../../interfaces/IManagerIssuanceHook.sol";
import { IModuleIssuanceHook } from "../../interfaces/IModuleIssuanceHook.sol";
import { Invoke } from "../lib/Invoke.sol";
import { ISetToken } from "../../interfaces/ISetToken.sol";
import { ModuleBase } from "../lib/ModuleBase.sol";
import { Position } from "../lib/Position.sol";
import { PreciseUnitMath } from "../../lib/PreciseUnitMath.sol";
/**
* @title DebtIssuanceModule
* @author Set Protocol
*
* The DebtIssuanceModule is a module that enables users to issue and redeem SetTokens that contain default and all
* external positions, including debt positions. Module hooks are added to allow for syncing of positions, and component
* level hooks are added to ensure positions are replicated correctly. The manager can define arbitrary issuance logic
* in the manager hook, as well as specify issue and redeem fees.
*
* CHANGELOG 12/15/2021:
* - add _hookContract parameter to SetTokenRedeemed
* - call manager pre-redeem hooks on redemption
* - add function for updating the manager hooks
*/
contract DebtIssuanceModule is ModuleBase, ReentrancyGuard {
/* ============ Structs ============ */
// NOTE: moduleIssuanceHooks uses address[] for compatibility with AddressArrayUtils library
struct IssuanceSettings {
uint256 maxManagerFee; // Max issue/redeem fee defined on instantiation
uint256 managerIssueFee; // Current manager issuance fees in precise units (10^16 = 1%)
uint256 managerRedeemFee; // Current manager redeem fees in precise units (10^16 = 1%)
address feeRecipient; // Address that receives all manager issue and redeem fees
IManagerIssuanceHook managerIssuanceHook; // Instance of manager defined hook, can hold arbitrary logic
address[] moduleIssuanceHooks; // Array of modules that are registered with this module
mapping(address => bool) isModuleHook; // Mapping of modules to if they've registered a hook
}
/* ============ Events ============ */
event SetTokenIssued(
ISetToken indexed _setToken,
address indexed _issuer,
address indexed _to,
address _hookContract,
uint256 _quantity,
uint256 _managerFee,
uint256 _protocolFee
);
event SetTokenRedeemed(
ISetToken indexed _setToken,
address indexed _redeemer,
address indexed _to,
address _hookContract,
uint256 _quantity,
uint256 _managerFee,
uint256 _protocolFee
);
event FeeRecipientUpdated(ISetToken indexed _setToken, address _newFeeRecipient);
event IssueFeeUpdated(ISetToken indexed _setToken, uint256 _newIssueFee);
event RedeemFeeUpdated(ISetToken indexed _setToken, uint256 _newRedeemFee);
/* ============ Constants ============ */
uint256 private constant ISSUANCE_MODULE_PROTOCOL_FEE_SPLIT_INDEX = 0;
/* ============ State ============ */
mapping(ISetToken => IssuanceSettings) public issuanceSettings;
/* ============ Constructor ============ */
constructor(IController _controller) public ModuleBase(_controller) {}
/* ============ External Functions ============ */
/**
* Deposits components to the SetToken, replicates any external module component positions and mints
* the SetToken. If the token has a debt position all collateral will be transferred in first then debt
* will be returned to the minting address. If specified, a fee will be charged on issuance.
*
* @param _setToken Instance of the SetToken to issue
* @param _quantity Quantity of SetToken to issue
* @param _to Address to mint SetToken to
*/
function issue(
ISetToken _setToken,
uint256 _quantity,
address _to
)
external
virtual
nonReentrant
onlyValidAndInitializedSet(_setToken)
{
require(_quantity > 0, "Issue quantity must be > 0");
address hookContract = _callManagerPreIssueHooks(_setToken, _quantity, msg.sender, _to);
_callModulePreIssueHooks(_setToken, _quantity);
(
uint256 quantityWithFees,
uint256 managerFee,
uint256 protocolFee
) = calculateTotalFees(_setToken, _quantity, true);
(
address[] memory components,
uint256[] memory equityUnits,
uint256[] memory debtUnits
) = _calculateRequiredComponentIssuanceUnits(_setToken, quantityWithFees, true);
_resolveEquityPositions(_setToken, quantityWithFees, _to, true, components, equityUnits);
_resolveDebtPositions(_setToken, quantityWithFees, true, components, debtUnits);
_resolveFees(_setToken, managerFee, protocolFee);
_setToken.mint(_to, _quantity);
emit SetTokenIssued(
_setToken,
msg.sender,
_to,
hookContract,
_quantity,
managerFee,
protocolFee
);
}
/**
* Returns components from the SetToken, unwinds any external module component positions and burns the SetToken.
* If the token has debt positions, the module transfers in the required debt amounts from the caller and uses
* those funds to repay the debts on behalf of the SetToken. All debt will be paid down first then equity positions
* will be returned to the minting address. If specified, a fee will be charged on redeem.
*
* @param _setToken Instance of the SetToken to redeem
* @param _quantity Quantity of SetToken to redeem
* @param _to Address to send collateral to
*/
function redeem(
ISetToken _setToken,
uint256 _quantity,
address _to
)
external
virtual
nonReentrant
onlyValidAndInitializedSet(_setToken)
{
require(_quantity > 0, "Redeem quantity must be > 0");
address hookContract = _callManagerPreRedeemHooks(_setToken, _quantity, msg.sender, _to);
_callModulePreRedeemHooks(_setToken, _quantity);
// Place burn after pre-redeem hooks because burning tokens may lead to false accounting of synced positions
_setToken.burn(msg.sender, _quantity);
(
uint256 quantityNetFees,
uint256 managerFee,
uint256 protocolFee
) = calculateTotalFees(_setToken, _quantity, false);
(
address[] memory components,
uint256[] memory equityUnits,
uint256[] memory debtUnits
) = _calculateRequiredComponentIssuanceUnits(_setToken, quantityNetFees, false);
_resolveDebtPositions(_setToken, quantityNetFees, false, components, debtUnits);
_resolveEquityPositions(_setToken, quantityNetFees, _to, false, components, equityUnits);
_resolveFees(_setToken, managerFee, protocolFee);
emit SetTokenRedeemed(
_setToken,
msg.sender,
_to,
hookContract,
_quantity,
managerFee,
protocolFee
);
}
/**
* MANAGER ONLY: Updates the address of the manager issuance hook. To remove the hook
* set the new hook address to address(0)
*
* @param _setToken Instance of the SetToken to update manager hook
* @param _newHook New manager hook contract address
*/
function updateManagerIssuanceHook(
ISetToken _setToken,
IManagerIssuanceHook _newHook
)
external
onlySetManager(_setToken, msg.sender)
onlyValidAndInitializedSet(_setToken)
{
issuanceSettings[_setToken].managerIssuanceHook = _newHook;
}
/**
* MANAGER ONLY: Updates address receiving issue/redeem fees for a given SetToken.
*
* @param _setToken Instance of the SetToken to update fee recipient
* @param _newFeeRecipient New fee recipient address
*/
function updateFeeRecipient(
ISetToken _setToken,
address _newFeeRecipient
)
external
onlyManagerAndValidSet(_setToken)
{
require(_newFeeRecipient != address(0), "Fee Recipient must be non-zero address.");
require(_newFeeRecipient != issuanceSettings[_setToken].feeRecipient, "Same fee recipient passed");
issuanceSettings[_setToken].feeRecipient = _newFeeRecipient;
emit FeeRecipientUpdated(_setToken, _newFeeRecipient);
}
/**
* MANAGER ONLY: Updates issue fee for passed SetToken
*
* @param _setToken Instance of the SetToken to update issue fee
* @param _newIssueFee New fee amount in preciseUnits (1% = 10^16)
*/
function updateIssueFee(
ISetToken _setToken,
uint256 _newIssueFee
)
external
onlyManagerAndValidSet(_setToken)
{
require(_newIssueFee <= issuanceSettings[_setToken].maxManagerFee, "Issue fee can't exceed maximum");
require(_newIssueFee != issuanceSettings[_setToken].managerIssueFee, "Same issue fee passed");
issuanceSettings[_setToken].managerIssueFee = _newIssueFee;
emit IssueFeeUpdated(_setToken, _newIssueFee);
}
/**
* MANAGER ONLY: Updates redeem fee for passed SetToken
*
* @param _setToken Instance of the SetToken to update redeem fee
* @param _newRedeemFee New fee amount in preciseUnits (1% = 10^16)
*/
function updateRedeemFee(
ISetToken _setToken,
uint256 _newRedeemFee
)
external
onlyManagerAndValidSet(_setToken)
{
require(_newRedeemFee <= issuanceSettings[_setToken].maxManagerFee, "Redeem fee can't exceed maximum");
require(_newRedeemFee != issuanceSettings[_setToken].managerRedeemFee, "Same redeem fee passed");
issuanceSettings[_setToken].managerRedeemFee = _newRedeemFee;
emit RedeemFeeUpdated(_setToken, _newRedeemFee);
}
/**
* MODULE ONLY: Adds calling module to array of modules that require they be called before component hooks are
* called. Can be used to sync debt positions before issuance.
*
* @param _setToken Instance of the SetToken to issue
*/
function registerToIssuanceModule(ISetToken _setToken) external onlyModule(_setToken) onlyValidAndInitializedSet(_setToken) {
require(!issuanceSettings[_setToken].isModuleHook[msg.sender], "Module already registered.");
issuanceSettings[_setToken].moduleIssuanceHooks.push(msg.sender);
issuanceSettings[_setToken].isModuleHook[msg.sender] = true;
}
/**
* MODULE ONLY: Removes calling module from array of modules that require they be called before component hooks are
* called.
*
* @param _setToken Instance of the SetToken to issue
*/
function unregisterFromIssuanceModule(ISetToken _setToken) external onlyModule(_setToken) onlyValidAndInitializedSet(_setToken) {
require(issuanceSettings[_setToken].isModuleHook[msg.sender], "Module not registered.");
issuanceSettings[_setToken].moduleIssuanceHooks.removeStorage(msg.sender);
issuanceSettings[_setToken].isModuleHook[msg.sender] = false;
}
/**
* MANAGER ONLY: Initializes this module to the SetToken with issuance-related hooks and fee information. 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 _maxManagerFee Maximum fee that can be charged on issue and redeem
* @param _managerIssueFee Fee to charge on issuance
* @param _managerRedeemFee Fee to charge on redemption
* @param _feeRecipient Address to send fees to
* @param _managerIssuanceHook Instance of the Manager Contract with the Pre-Issuance Hook function
*/
function initialize(
ISetToken _setToken,
uint256 _maxManagerFee,
uint256 _managerIssueFee,
uint256 _managerRedeemFee,
address _feeRecipient,
IManagerIssuanceHook _managerIssuanceHook
)
external
onlySetManager(_setToken, msg.sender)
onlyValidAndPendingSet(_setToken)
{
require(_managerIssueFee <= _maxManagerFee, "Issue fee can't exceed maximum fee");
require(_managerRedeemFee <= _maxManagerFee, "Redeem fee can't exceed maximum fee");
issuanceSettings[_setToken] = IssuanceSettings({
maxManagerFee: _maxManagerFee,
managerIssueFee: _managerIssueFee,
managerRedeemFee: _managerRedeemFee,
feeRecipient: _feeRecipient,
managerIssuanceHook: _managerIssuanceHook,
moduleIssuanceHooks: new address[](0)
});
_setToken.initializeModule();
}
/**
* SET TOKEN ONLY: Allows removal of module (and deletion of state) if no other modules are registered.
*/
function removeModule() external override {
require(issuanceSettings[ISetToken(msg.sender)].moduleIssuanceHooks.length == 0, "Registered modules must be removed.");
delete issuanceSettings[ISetToken(msg.sender)];
}
/* ============ External View Functions ============ */
/**
* Calculates the manager fee, protocol fee and resulting totalQuantity to use when calculating unit amounts. If fees are charged they
* are added to the total issue quantity, for example 1% fee on 100 Sets means 101 Sets are minted by caller, the _to address receives
* 100 and the feeRecipient receives 1. Conversely, on redemption the redeemer will only receive the collateral that collateralizes 99
* Sets, while the additional Set is given to the feeRecipient.
*
* @param _setToken Instance of the SetToken to issue
* @param _quantity Amount of SetToken issuer wants to receive/redeem
* @param _isIssue If issuing or redeeming
*
* @return totalQuantity Total amount of Sets to be issued/redeemed with fee adjustment
* @return managerFee Sets minted to the manager
* @return protocolFee Sets minted to the protocol
*/
function calculateTotalFees(
ISetToken _setToken,
uint256 _quantity,
bool _isIssue
)
public
view
returns (uint256 totalQuantity, uint256 managerFee, uint256 protocolFee)
{
IssuanceSettings memory setIssuanceSettings = issuanceSettings[_setToken];
uint256 protocolFeeSplit = controller.getModuleFee(address(this), ISSUANCE_MODULE_PROTOCOL_FEE_SPLIT_INDEX);
uint256 totalFeeRate = _isIssue ? setIssuanceSettings.managerIssueFee : setIssuanceSettings.managerRedeemFee;
uint256 totalFee = totalFeeRate.preciseMul(_quantity);
protocolFee = totalFee.preciseMul(protocolFeeSplit);
managerFee = totalFee.sub(protocolFee);
totalQuantity = _isIssue ? _quantity.add(totalFee) : _quantity.sub(totalFee);
}
/**
* Calculates the amount of each component needed to collateralize passed issue quantity plus fees of Sets as well as amount of debt
* that will be returned to caller. Values DO NOT take into account any updates from pre action manager or module hooks.
*
* @param _setToken Instance of the SetToken to issue
* @param _quantity Amount of Sets to be issued
*
* @return address[] Array of component addresses making up the Set
* @return uint256[] Array of equity notional amounts of each component, respectively, represented as uint256
* @return uint256[] Array of debt notional amounts of each component, respectively, represented as uint256
*/
function getRequiredComponentIssuanceUnits(
ISetToken _setToken,
uint256 _quantity
)
external
view
virtual
returns (address[] memory, uint256[] memory, uint256[] memory)
{
(
uint256 totalQuantity,,
) = calculateTotalFees(_setToken, _quantity, true);
return _calculateRequiredComponentIssuanceUnits(_setToken, totalQuantity, true);
}
/**
* Calculates the amount of each component will be returned on redemption net of fees as well as how much debt needs to be paid down to.
* redeem. Values DO NOT take into account any updates from pre action manager or module hooks.
*
* @param _setToken Instance of the SetToken to issue
* @param _quantity Amount of Sets to be redeemed
*
* @return address[] Array of component addresses making up the Set
* @return uint256[] Array of equity notional amounts of each component, respectively, represented as uint256
* @return uint256[] Array of debt notional amounts of each component, respectively, represented as uint256
*/
function getRequiredComponentRedemptionUnits(
ISetToken _setToken,
uint256 _quantity
)
external
view
virtual
returns (address[] memory, uint256[] memory, uint256[] memory)
{
(
uint256 totalQuantity,,
) = calculateTotalFees(_setToken, _quantity, false);
return _calculateRequiredComponentIssuanceUnits(_setToken, totalQuantity, false);
}
function getModuleIssuanceHooks(ISetToken _setToken) external view returns(address[] memory) {
return issuanceSettings[_setToken].moduleIssuanceHooks;
}
function isModuleIssuanceHook(ISetToken _setToken, address _hook) external view returns(bool) {
return issuanceSettings[_setToken].isModuleHook[_hook];
}
/* ============ Internal Functions ============ */
/**
* Calculates the amount of each component needed to collateralize passed issue quantity of Sets as well as amount of debt that will
* be returned to caller. Can also be used to determine how much collateral will be returned on redemption as well as how much debt
* needs to be paid down to redeem.
*
* @param _setToken Instance of the SetToken to issue
* @param _quantity Amount of Sets to be issued/redeemed
* @param _isIssue Whether Sets are being issued or redeemed
*
* @return address[] Array of component addresses making up the Set
* @return uint256[] Array of equity notional amounts of each component, respectively, represented as uint256
* @return uint256[] Array of debt notional amounts of each component, respectively, represented as uint256
*/
function _calculateRequiredComponentIssuanceUnits(
ISetToken _setToken,
uint256 _quantity,
bool _isIssue
)
internal
view
returns (address[] memory, uint256[] memory, uint256[] memory)
{
(
address[] memory components,
uint256[] memory equityUnits,
uint256[] memory debtUnits
) = _getTotalIssuanceUnits(_setToken);
uint256 componentsLength = components.length;
uint256[] memory totalEquityUnits = new uint256[](componentsLength);
uint256[] memory totalDebtUnits = new uint256[](componentsLength);
for (uint256 i = 0; i < components.length; i++) {
// Use preciseMulCeil to round up to ensure overcollateration when small issue quantities are provided
// and preciseMul to round down to ensure overcollateration when small redeem quantities are provided
totalEquityUnits[i] = _isIssue ?
equityUnits[i].preciseMulCeil(_quantity) :
equityUnits[i].preciseMul(_quantity);
totalDebtUnits[i] = _isIssue ?
debtUnits[i].preciseMul(_quantity) :
debtUnits[i].preciseMulCeil(_quantity);
}
return (components, totalEquityUnits, totalDebtUnits);
}
/**
* Sums total debt and equity units for each component, taking into account default and external positions.
*
* @param _setToken Instance of the SetToken to issue
*
* @return address[] Array of component addresses making up the Set
* @return uint256[] Array of equity unit amounts of each component, respectively, represented as uint256
* @return uint256[] Array of debt unit amounts of each component, respectively, represented as uint256
*/
function _getTotalIssuanceUnits(
ISetToken _setToken
)
internal
view
returns (address[] memory, uint256[] memory, uint256[] memory)
{
address[] memory components = _setToken.getComponents();
uint256 componentsLength = components.length;
uint256[] memory equityUnits = new uint256[](componentsLength);
uint256[] memory debtUnits = new uint256[](componentsLength);
for (uint256 i = 0; i < components.length; i++) {
address component = components[i];
int256 cumulativeEquity = _setToken.getDefaultPositionRealUnit(component);
int256 cumulativeDebt = 0;
address[] memory externalPositions = _setToken.getExternalPositionModules(component);
if (externalPositions.length > 0) {
for (uint256 j = 0; j < externalPositions.length; j++) {
int256 externalPositionUnit = _setToken.getExternalPositionRealUnit(component, externalPositions[j]);
// If positionUnit <= 0 it will be "added" to debt position
if (externalPositionUnit > 0) {
cumulativeEquity = cumulativeEquity.add(externalPositionUnit);
} else {
cumulativeDebt = cumulativeDebt.add(externalPositionUnit);
}
}
}
equityUnits[i] = cumulativeEquity.toUint256();
debtUnits[i] = cumulativeDebt.mul(-1).toUint256();
}
return (components, equityUnits, debtUnits);
}
/**
* Resolve equity positions associated with SetToken. On issuance, the total equity position for an asset (including default and external
* positions) is transferred in. Then any external position hooks are called to transfer the external positions to their necessary place.
* On redemption all external positions are recalled by the external position hook, then those position plus any default position are
* transferred back to the _to address.
*/
function _resolveEquityPositions(
ISetToken _setToken,
uint256 _quantity,
address _to,
bool _isIssue,
address[] memory _components,
uint256[] memory _componentEquityQuantities
)
internal
{
for (uint256 i = 0; i < _components.length; i++) {
address component = _components[i];
uint256 componentQuantity = _componentEquityQuantities[i];
if (componentQuantity > 0) {
if (_isIssue) {
transferFrom(
IERC20(component),
msg.sender,
address(_setToken),
componentQuantity
);
_executeExternalPositionHooks(_setToken, _quantity, IERC20(component), true, true);
} else {
_executeExternalPositionHooks(_setToken, _quantity, IERC20(component), false, true);
_setToken.strictInvokeTransfer(
component,
_to,
componentQuantity
);
}
}
}
}
/**
* Resolve debt positions associated with SetToken. On issuance, debt positions are entered into by calling the external position hook. The
* resulting debt is then returned to the calling address. On redemption, the module transfers in the required debt amount from the caller
* and uses those funds to repay the debt on behalf of the SetToken.
*/
function _resolveDebtPositions(
ISetToken _setToken,
uint256 _quantity,
bool _isIssue,
address[] memory _components,
uint256[] memory _componentDebtQuantities
)
internal
{
for (uint256 i = 0; i < _components.length; i++) {
address component = _components[i];
uint256 componentQuantity = _componentDebtQuantities[i];
if (componentQuantity > 0) {
if (_isIssue) {
_executeExternalPositionHooks(_setToken, _quantity, IERC20(component), true, false);
_setToken.strictInvokeTransfer(
component,
msg.sender,
componentQuantity
);
} else {
transferFrom(
IERC20(component),
msg.sender,
address(_setToken),
componentQuantity
);
_executeExternalPositionHooks(_setToken, _quantity, IERC20(component), false, false);
}
}
}
}
/**
* If any manager fees mints Sets to the defined feeRecipient. If protocol fee is enabled mints Sets to protocol
* feeRecipient.
*/
function _resolveFees(ISetToken _setToken, uint256 managerFee, uint256 protocolFee) internal {
if (managerFee > 0) {
_setToken.mint(issuanceSettings[_setToken].feeRecipient, managerFee);
// Protocol fee check is inside manager fee check because protocol fees are only collected on manager fees
if (protocolFee > 0) {
_setToken.mint(controller.feeRecipient(), protocolFee);
}
}
}
/**
* If a pre-issue hook has been configured, call the external-protocol contract. Pre-issue hook logic
* can contain arbitrary logic including validations, external function calls, etc.
*/
function _callManagerPreIssueHooks(
ISetToken _setToken,
uint256 _quantity,
address _caller,
address _to
)
internal
returns(address)
{
IManagerIssuanceHook preIssueHook = issuanceSettings[_setToken].managerIssuanceHook;
if (address(preIssueHook) != address(0)) {
preIssueHook.invokePreIssueHook(_setToken, _quantity, _caller, _to);
return address(preIssueHook);
}
return address(0);
}
/**
* If a pre-issue hook has been configured, call the external-protocol contract. Pre-issue hook logic
* can contain arbitrary logic including validations, external function calls, etc.
*/
function _callManagerPreRedeemHooks(
ISetToken _setToken,
uint256 _quantity,
address _caller,
address _to
)
internal
returns(address)
{
IManagerIssuanceHook preIssueHook = issuanceSettings[_setToken].managerIssuanceHook;
if (address(preIssueHook) != address(0)) {
preIssueHook.invokePreRedeemHook(_setToken, _quantity, _caller, _to);
return address(preIssueHook);
}
return address(0);
}
/**
* Calls all modules that have registered with the DebtIssuanceModule that have a moduleIssueHook.
*/
function _callModulePreIssueHooks(ISetToken _setToken, uint256 _quantity) internal {
address[] memory issuanceHooks = issuanceSettings[_setToken].moduleIssuanceHooks;
for (uint256 i = 0; i < issuanceHooks.length; i++) {
IModuleIssuanceHook(issuanceHooks[i]).moduleIssueHook(_setToken, _quantity);
}
}
/**
* Calls all modules that have registered with the DebtIssuanceModule that have a moduleRedeemHook.
*/
function _callModulePreRedeemHooks(ISetToken _setToken, uint256 _quantity) internal {
address[] memory issuanceHooks = issuanceSettings[_setToken].moduleIssuanceHooks;
for (uint256 i = 0; i < issuanceHooks.length; i++) {
IModuleIssuanceHook(issuanceHooks[i]).moduleRedeemHook(_setToken, _quantity);
}
}
/**
* For each component's external module positions, calculate the total notional quantity, and
* call the module's issue hook or redeem hook.
* Note: It is possible that these hooks can cause the states of other modules to change.
* It can be problematic if the hook called an external function that called back into a module, resulting in state inconsistencies.
*/
function _executeExternalPositionHooks(
ISetToken _setToken,
uint256 _setTokenQuantity,
IERC20 _component,
bool _isIssue,
bool _isEquity
)
internal
{
address[] memory externalPositionModules = _setToken.getExternalPositionModules(address(_component));
uint256 modulesLength = externalPositionModules.length;
if (_isIssue) {
for (uint256 i = 0; i < modulesLength; i++) {
IModuleIssuanceHook(externalPositionModules[i]).componentIssueHook(_setToken, _setTokenQuantity, _component, _isEquity);
}
} else {
for (uint256 i = 0; i < modulesLength; i++) {
IModuleIssuanceHook(externalPositionModules[i]).componentRedeemHook(_setToken, _setTokenQuantity, _component, _isEquity);
}
}
}
}