• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IndexCoop / index-protocol / 9c861972-5406-431b-8c8c-fca6bc7b1929

22 Aug 2024 12:39PM UTC coverage: 93.76% (-4.1%) from 97.848%
9c861972-5406-431b-8c8c-fca6bc7b1929

push

circleci

web-flow
add calculateComponentValuation (#46)

2083 of 2308 branches covered (90.25%)

Branch coverage included in aggregate %.

12 of 12 new or added lines in 1 file covered. (100.0%)

113 existing lines in 10 files now uncovered.

3161 of 3285 relevant lines covered (96.23%)

234.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.19
/contracts/protocol/modules/v1/DebtIssuanceModule.sol
1
/*
2
    Copyright 2021 Set Labs Inc.
3

4
    Licensed under the Apache License, Version 2.0 (the "License");
5
    you may not use this file except in compliance with the License.
6
    You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
    Unless required by applicable law or agreed to in writing, software
11
    distributed under the License is distributed on an "AS IS" BASIS,
12
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
    See the License for the specific language governing permissions and
14
    limitations under the License.
15

16
    SPDX-License-Identifier: Apache License, Version 2.0
17
*/
18

19
pragma solidity 0.6.10;
20
pragma experimental "ABIEncoderV2";
21

22
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
23
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
24
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
25
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
26
import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol";
27

28
import { AddressArrayUtils } from "../../../lib/AddressArrayUtils.sol";
29
import { IController } from "../../../interfaces/IController.sol";
30
import { IManagerIssuanceHook } from "../../../interfaces/IManagerIssuanceHook.sol";
31
import { IModuleIssuanceHook } from "../../../interfaces/IModuleIssuanceHook.sol";
32
import { Invoke } from "../../lib/Invoke.sol";
33
import { ISetToken } from "../../../interfaces/ISetToken.sol";
34
import { ModuleBase } from "../../lib/ModuleBase.sol";
35
import { Position } from "../../lib/Position.sol";
36
import { PreciseUnitMath } from "../../../lib/PreciseUnitMath.sol";
37

38

39
/**
40
 * @title DebtIssuanceModule
41
 * @author Set Protocol
42
 *
43
 * The DebtIssuanceModule is a module that enables users to issue and redeem SetTokens that contain default and all
44
 * external positions, including debt positions. Module hooks are added to allow for syncing of positions, and component
45
 * level hooks are added to ensure positions are replicated correctly. The manager can define arbitrary issuance logic
46
 * in the manager hook, as well as specify issue and redeem fees.
47
 */
48
contract DebtIssuanceModule is ModuleBase, ReentrancyGuard {
49

50
    /* ============ Structs ============ */
51

52
    // NOTE: moduleIssuanceHooks uses address[] for compatibility with AddressArrayUtils library
53
    struct IssuanceSettings {
54
        uint256 maxManagerFee;                          // Max issue/redeem fee defined on instantiation
55
        uint256 managerIssueFee;                        // Current manager issuance fees in precise units (10^16 = 1%)
56
        uint256 managerRedeemFee;                       // Current manager redeem fees in precise units (10^16 = 1%)
57
        address feeRecipient;                           // Address that receives all manager issue and redeem fees
58
        IManagerIssuanceHook managerIssuanceHook;       // Instance of manager defined hook, can hold arbitrary logic
59
        address[] moduleIssuanceHooks;                  // Array of modules that are registered with this module
60
        mapping(address => bool) isModuleHook;          // Mapping of modules to if they've registered a hook
61
    }
62

63
    /* ============ Events ============ */
64

65
    event SetTokenIssued(
66
        ISetToken indexed _setToken,
67
        address indexed _issuer,
68
        address indexed _to,
69
        address _hookContract,
70
        uint256 _quantity,
71
        uint256 _managerFee,
72
        uint256 _protocolFee
73
    );
74
    event SetTokenRedeemed(
75
        ISetToken indexed _setToken,
76
        address indexed _redeemer,
77
        address indexed _to,
78
        uint256 _quantity,
79
        uint256 _managerFee,
80
        uint256 _protocolFee
81
    );
82
    event FeeRecipientUpdated(ISetToken indexed _setToken, address _newFeeRecipient);
83
    event IssueFeeUpdated(ISetToken indexed _setToken, uint256 _newIssueFee);
84
    event RedeemFeeUpdated(ISetToken indexed _setToken, uint256 _newRedeemFee);
85

86
    /* ============ Constants ============ */
87

88
    uint256 private constant ISSUANCE_MODULE_PROTOCOL_FEE_SPLIT_INDEX = 0;
89

90
    /* ============ State ============ */
91

92
    mapping(ISetToken => IssuanceSettings) public issuanceSettings;
93

94
    /* ============ Constructor ============ */
95

96
    constructor(IController _controller) public ModuleBase(_controller) {}
97

98
    /* ============ External Functions ============ */
99

100
    /**
101
     * Deposits components to the SetToken, replicates any external module component positions and mints
102
     * the SetToken. If the token has a debt position all collateral will be transferred in first then debt
103
     * will be returned to the minting address. If specified, a fee will be charged on issuance.
104
     *
105
     * @param _setToken         Instance of the SetToken to issue
106
     * @param _quantity         Quantity of SetToken to issue
107
     * @param _to               Address to mint SetToken to
108
     */
109
    function issue(
110
        ISetToken _setToken,
111
        uint256 _quantity,
112
        address _to
113
    )
114
        external
115
        virtual
116
        nonReentrant
19!
117
        onlyValidAndInitializedSet(_setToken)
19!
118
    {
119
        require(_quantity > 0, "Issue quantity must be > 0");
19!
120

121
        address hookContract = _callManagerPreIssueHooks(_setToken, _quantity, msg.sender, _to);
19✔
122

123
        _callModulePreIssueHooks(_setToken, _quantity);
19✔
124

125
        (
19✔
126
            uint256 quantityWithFees,
127
            uint256 managerFee,
128
            uint256 protocolFee
129
        ) = calculateTotalFees(_setToken, _quantity, true);
130

131
        (
19✔
132
            address[] memory components,
133
            uint256[] memory equityUnits,
134
            uint256[] memory debtUnits
135
        ) = _calculateRequiredComponentIssuanceUnits(_setToken, quantityWithFees, true);
136

137
        _resolveEquityPositions(_setToken, quantityWithFees, _to, true, components, equityUnits);
19✔
138
        _resolveDebtPositions(_setToken, quantityWithFees, true, components, debtUnits);
19✔
139
        _resolveFees(_setToken, managerFee, protocolFee);
19✔
140

141
        _setToken.mint(_to, _quantity);
19✔
142

143
        emit SetTokenIssued(
19✔
144
            _setToken,
145
            msg.sender,
146
            _to,
147
            hookContract,
148
            _quantity,
149
            managerFee,
150
            protocolFee
151
        );
152
    }
153

154
    /**
155
     * Returns components from the SetToken, unwinds any external module component positions and burns the SetToken.
156
     * If the token has debt positions, the module transfers in the required debt amounts from the caller and uses
157
     * those funds to repay the debts on behalf of the SetToken. All debt will be paid down first then equity positions
158
     * will be returned to the minting address. If specified, a fee will be charged on redeem.
159
     *
160
     * @param _setToken         Instance of the SetToken to redeem
161
     * @param _quantity         Quantity of SetToken to redeem
162
     * @param _to               Address to send collateral to
163
     */
164
    function redeem(
165
        ISetToken _setToken,
166
        uint256 _quantity,
167
        address _to
168
    )
169
        external
170
        virtual
171
        nonReentrant
12!
172
        onlyValidAndInitializedSet(_setToken)
12!
173
    {
174
        require(_quantity > 0, "Redeem quantity must be > 0");
12!
175

176
        _callModulePreRedeemHooks(_setToken, _quantity);
12✔
177

178
        // Place burn after pre-redeem hooks because burning tokens may lead to false accounting of synced positions
179
        _setToken.burn(msg.sender, _quantity);
12✔
180

181
        (
12✔
182
            uint256 quantityNetFees,
183
            uint256 managerFee,
184
            uint256 protocolFee
185
        ) = calculateTotalFees(_setToken, _quantity, false);
186

187
        (
12✔
188
            address[] memory components,
189
            uint256[] memory equityUnits,
190
            uint256[] memory debtUnits
191
        ) = _calculateRequiredComponentIssuanceUnits(_setToken, quantityNetFees, false);
192

193
        _resolveDebtPositions(_setToken, quantityNetFees, false, components, debtUnits);
12✔
194
        _resolveEquityPositions(_setToken, quantityNetFees, _to, false, components, equityUnits);
12✔
195
        _resolveFees(_setToken, managerFee, protocolFee);
12✔
196

197
        emit SetTokenRedeemed(
12✔
198
            _setToken,
199
            msg.sender,
200
            _to,
201
            _quantity,
202
            managerFee,
203
            protocolFee
204
        );
205
    }
206

207
    /**
208
     * MANAGER ONLY: Updates address receiving issue/redeem fees for a given SetToken.
209
     *
210
     * @param _setToken             Instance of the SetToken to update fee recipient
211
     * @param _newFeeRecipient      New fee recipient address
212
     */
213
    function updateFeeRecipient(
214
        ISetToken _setToken,
215
        address _newFeeRecipient
216
    )
217
        external
218
        onlyManagerAndValidSet(_setToken)
×
219
    {
UNCOV
220
        require(_newFeeRecipient != address(0), "Fee Recipient must be non-zero address.");
×
UNCOV
221
        require(_newFeeRecipient != issuanceSettings[_setToken].feeRecipient, "Same fee recipient passed");
×
222

223
        issuanceSettings[_setToken].feeRecipient = _newFeeRecipient;
224

UNCOV
225
        emit FeeRecipientUpdated(_setToken, _newFeeRecipient);
×
226
    }
227

228
    /**
229
     * MANAGER ONLY: Updates issue fee for passed SetToken
230
     *
231
     * @param _setToken             Instance of the SetToken to update issue fee
232
     * @param _newIssueFee          New fee amount in preciseUnits (1% = 10^16)
233
     */
234
    function updateIssueFee(
235
        ISetToken _setToken,
236
        uint256 _newIssueFee
237
    )
238
        external
239
        onlyManagerAndValidSet(_setToken)
×
240
    {
UNCOV
241
        require(_newIssueFee <= issuanceSettings[_setToken].maxManagerFee, "Issue fee can't exceed maximum");
×
UNCOV
242
        require(_newIssueFee != issuanceSettings[_setToken].managerIssueFee, "Same issue fee passed");
×
243

244
        issuanceSettings[_setToken].managerIssueFee = _newIssueFee;
245

UNCOV
246
        emit IssueFeeUpdated(_setToken, _newIssueFee);
×
247
    }
248

249
    /**
250
     * MANAGER ONLY: Updates redeem fee for passed SetToken
251
     *
252
     * @param _setToken             Instance of the SetToken to update redeem fee
253
     * @param _newRedeemFee         New fee amount in preciseUnits (1% = 10^16)
254
     */
255
    function updateRedeemFee(
256
        ISetToken _setToken,
257
        uint256 _newRedeemFee
258
    )
259
        external
260
        onlyManagerAndValidSet(_setToken)
×
261
    {
UNCOV
262
        require(_newRedeemFee <= issuanceSettings[_setToken].maxManagerFee, "Redeem fee can't exceed maximum");
×
UNCOV
263
        require(_newRedeemFee != issuanceSettings[_setToken].managerRedeemFee, "Same redeem fee passed");
×
264

265
        issuanceSettings[_setToken].managerRedeemFee = _newRedeemFee;
266

UNCOV
267
        emit RedeemFeeUpdated(_setToken, _newRedeemFee);
×
268
    }
269

270
    /**
271
     * MODULE ONLY: Adds calling module to array of modules that require they be called before component hooks are
272
     * called. Can be used to sync debt positions before issuance.
273
     *
274
     * @param _setToken             Instance of the SetToken to issue
275
     */
276
    function registerToIssuanceModule(ISetToken _setToken) external onlyModule(_setToken) onlyValidAndInitializedSet(_setToken) {
1,480!
277
        require(!issuanceSettings[_setToken].isModuleHook[msg.sender], "Module already registered.");
740!
278
        issuanceSettings[_setToken].moduleIssuanceHooks.push(msg.sender);
740✔
279
        issuanceSettings[_setToken].isModuleHook[msg.sender] = true;
280
    }
281

282
    /**
283
     * MODULE ONLY: Removes calling module from array of modules that require they be called before component hooks are
284
     * called.
285
     *
286
     * @param _setToken             Instance of the SetToken to issue
287
     */
288
    function unregisterFromIssuanceModule(ISetToken _setToken) external onlyModule(_setToken) onlyValidAndInitializedSet(_setToken) {
84!
289
        require(issuanceSettings[_setToken].isModuleHook[msg.sender], "Module not registered.");
42!
290
        issuanceSettings[_setToken].moduleIssuanceHooks.removeStorage(msg.sender);
42✔
291
        issuanceSettings[_setToken].isModuleHook[msg.sender] = false;
292
    }
293

294
    /**
295
     * MANAGER ONLY: Initializes this module to the SetToken with issuance-related hooks and fee information. Only callable
296
     * by the SetToken's manager. Hook addresses are optional. Address(0) means that no hook will be called
297
     *
298
     * @param _setToken                     Instance of the SetToken to issue
299
     * @param _maxManagerFee                Maximum fee that can be charged on issue and redeem
300
     * @param _managerIssueFee              Fee to charge on issuance
301
     * @param _managerRedeemFee             Fee to charge on redemption
302
     * @param _feeRecipient                 Address to send fees to
303
     * @param _managerIssuanceHook          Instance of the Manager Contract with the Pre-Issuance Hook function
304
     */
305
    function initialize(
306
        ISetToken _setToken,
307
        uint256 _maxManagerFee,
308
        uint256 _managerIssueFee,
309
        uint256 _managerRedeemFee,
310
        address _feeRecipient,
311
        IManagerIssuanceHook _managerIssuanceHook
312
    )
313
        external
314
        onlySetManager(_setToken, msg.sender)
771!
315
        onlyValidAndPendingSet(_setToken)
771!
316
    {
317
        require(_managerIssueFee <= _maxManagerFee, "Issue fee can't exceed maximum fee");
771!
318
        require(_managerRedeemFee <= _maxManagerFee, "Redeem fee can't exceed maximum fee");
771!
319

320
        issuanceSettings[_setToken] = IssuanceSettings({
321
            maxManagerFee: _maxManagerFee,
322
            managerIssueFee: _managerIssueFee,
323
            managerRedeemFee: _managerRedeemFee,
324
            feeRecipient: _feeRecipient,
325
            managerIssuanceHook: _managerIssuanceHook,
326
            moduleIssuanceHooks: new address[](0)
327
        });
328

329
        _setToken.initializeModule();
771✔
330
    }
331

332
    /**
333
     * SET TOKEN ONLY: Allows removal of module (and deletion of state) if no other modules are registered.
334
     */
335
    function removeModule() external override {
336
        require(issuanceSettings[ISetToken(msg.sender)].moduleIssuanceHooks.length == 0, "Registered modules must be removed.");
2!
337
        delete issuanceSettings[ISetToken(msg.sender)];
338
    }
339

340
    /* ============ External View Functions ============ */
341

342
    /**
343
     * Calculates the manager fee, protocol fee and resulting totalQuantity to use when calculating unit amounts. If fees are charged they
344
     * 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
345
     * 100 and the feeRecipient receives 1. Conversely, on redemption the redeemer will only receive the collateral that collateralizes 99
346
     * Sets, while the additional Set is given to the feeRecipient.
347
     *
348
     * @param _setToken                 Instance of the SetToken to issue
349
     * @param _quantity                 Amount of SetToken issuer wants to receive/redeem
350
     * @param _isIssue                  If issuing or redeeming
351
     *
352
     * @return totalQuantity           Total amount of Sets to be issued/redeemed with fee adjustment
353
     * @return managerFee              Sets minted to the manager
354
     * @return protocolFee             Sets minted to the protocol
355
     */
356
    function calculateTotalFees(
357
        ISetToken _setToken,
358
        uint256 _quantity,
359
        bool _isIssue
360
    )
361
        public
362
        view
363
        returns (uint256 totalQuantity, uint256 managerFee, uint256 protocolFee)
364
    {
365
        IssuanceSettings memory setIssuanceSettings = issuanceSettings[_setToken];
1,142✔
366
        uint256 protocolFeeSplit = controller.getModuleFee(address(this), ISSUANCE_MODULE_PROTOCOL_FEE_SPLIT_INDEX);
1,142✔
367
        uint256 totalFeeRate = _isIssue ? setIssuanceSettings.managerIssueFee : setIssuanceSettings.managerRedeemFee;
1,142✔
368

369
        uint256 totalFee = totalFeeRate.preciseMul(_quantity);
1,142✔
370
        protocolFee = totalFee.preciseMul(protocolFeeSplit);
371
        managerFee = totalFee.sub(protocolFee);
372

373
        totalQuantity = _isIssue ? _quantity.add(totalFee) : _quantity.sub(totalFee);
1,142✔
374
    }
375

376
    /**
377
     * Calculates the amount of each component needed to collateralize passed issue quantity plus fees of Sets as well as amount of debt
378
     * that will be returned to caller. Values DO NOT take into account any updates from pre action manager or module hooks.
379
     *
380
     * @param _setToken         Instance of the SetToken to issue
381
     * @param _quantity         Amount of Sets to be issued
382
     *
383
     * @return address[]        Array of component addresses making up the Set
384
     * @return uint256[]        Array of equity notional amounts of each component, respectively, represented as uint256
385
     * @return uint256[]        Array of debt notional amounts of each component, respectively, represented as uint256
386
     */
387
    function getRequiredComponentIssuanceUnits(
388
        ISetToken _setToken,
389
        uint256 _quantity
390
    )
391
        external
392
        view
393
        virtual
394
        returns (address[] memory, uint256[] memory, uint256[] memory)
395
    {
UNCOV
396
        (
×
397
            uint256 totalQuantity,,
398
        ) = calculateTotalFees(_setToken, _quantity, true);
399

UNCOV
400
        return _calculateRequiredComponentIssuanceUnits(_setToken, totalQuantity, true);
×
401
    }
402

403
    /**
404
     * 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.
405
     * redeem. Values DO NOT take into account any updates from pre action manager or module hooks.
406
     *
407
     * @param _setToken         Instance of the SetToken to issue
408
     * @param _quantity         Amount of Sets to be redeemed
409
     *
410
     * @return address[]        Array of component addresses making up the Set
411
     * @return uint256[]        Array of equity notional amounts of each component, respectively, represented as uint256
412
     * @return uint256[]        Array of debt notional amounts of each component, respectively, represented as uint256
413
     */
414
    function getRequiredComponentRedemptionUnits(
415
        ISetToken _setToken,
416
        uint256 _quantity
417
    )
418
        external
419
        view
420
        virtual
421
        returns (address[] memory, uint256[] memory, uint256[] memory)
422
    {
423
        (
10✔
424
            uint256 totalQuantity,,
425
        ) = calculateTotalFees(_setToken, _quantity, false);
426

427
        return _calculateRequiredComponentIssuanceUnits(_setToken, totalQuantity, false);
10✔
428
    }
429

430
    function getModuleIssuanceHooks(ISetToken _setToken) external view returns(address[] memory) {
UNCOV
431
        return issuanceSettings[_setToken].moduleIssuanceHooks;
×
432
    }
433

434
    function isModuleIssuanceHook(ISetToken _setToken, address _hook) external view returns(bool) {
UNCOV
435
        return issuanceSettings[_setToken].isModuleHook[_hook];
×
436
    }
437

438
    /* ============ Internal Functions ============ */
439

440
    /**
441
     * Calculates the amount of each component needed to collateralize passed issue quantity of Sets as well as amount of debt that will
442
     * be returned to caller. Can also be used to determine how much collateral will be returned on redemption as well as how much debt
443
     * needs to be paid down to redeem.
444
     *
445
     * @param _setToken         Instance of the SetToken to issue
446
     * @param _quantity         Amount of Sets to be issued/redeemed
447
     * @param _isIssue          Whether Sets are being issued or redeemed
448
     *
449
     * @return address[]        Array of component addresses making up the Set
450
     * @return uint256[]        Array of equity notional amounts of each component, respectively, represented as uint256
451
     * @return uint256[]        Array of debt notional amounts of each component, respectively, represented as uint256
452
     */
453
    function _calculateRequiredComponentIssuanceUnits(
454
        ISetToken _setToken,
455
        uint256 _quantity,
456
        bool _isIssue
457
    )
458
        internal
459
        view
460
        returns (address[] memory, uint256[] memory, uint256[] memory)
461
    {
462
        (
1,083✔
463
            address[] memory components,
464
            uint256[] memory equityUnits,
465
            uint256[] memory debtUnits
466
        ) = _getTotalIssuanceUnits(_setToken);
467

468
        uint256 componentsLength = components.length;
1,083✔
469
        uint256[] memory totalEquityUnits = new uint256[](componentsLength);
1,083✔
470
        uint256[] memory totalDebtUnits = new uint256[](componentsLength);
1,083✔
471
        for (uint256 i = 0; i < components.length; i++) {
1,083✔
472
            // Use preciseMulCeil to round up to ensure overcollateration when small issue quantities are provided
473
            // and preciseMul to round down to ensure overcollateration when small redeem quantities are provided
474
            totalEquityUnits[i] = _isIssue ?
475
                equityUnits[i].preciseMulCeil(_quantity) :
1,465✔
476
                equityUnits[i].preciseMul(_quantity);
477

478
            totalDebtUnits[i] = _isIssue ?
479
                debtUnits[i].preciseMul(_quantity) :
1,465✔
480
                debtUnits[i].preciseMulCeil(_quantity);
481
        }
482

483
        return (components, totalEquityUnits, totalDebtUnits);
1,083✔
484
    }
485

486
    /**
487
     * Sums total debt and equity units for each component, taking into account default and external positions.
488
     *
489
     * @param _setToken         Instance of the SetToken to issue
490
     *
491
     * @return address[]        Array of component addresses making up the Set
492
     * @return uint256[]        Array of equity unit amounts of each component, respectively, represented as uint256
493
     * @return uint256[]        Array of debt unit amounts of each component, respectively, represented as uint256
494
     */
495
    function _getTotalIssuanceUnits(
496
        ISetToken _setToken
497
    )
498
        internal
499
        view
500
        returns (address[] memory, uint256[] memory, uint256[] memory)
501
    {
502
        address[] memory components = _setToken.getComponents();
1,132✔
503
        uint256 componentsLength = components.length;
1,132✔
504

505
        uint256[] memory equityUnits = new uint256[](componentsLength);
1,132✔
506
        uint256[] memory debtUnits = new uint256[](componentsLength);
1,132✔
507

508
        for (uint256 i = 0; i < components.length; i++) {
1,132✔
509
            address component = components[i];
1,563✔
510
            int256 cumulativeEquity = _setToken.getDefaultPositionRealUnit(component);
1,563✔
511
            int256 cumulativeDebt = 0;
1,563✔
512
            address[] memory externalPositions = _setToken.getExternalPositionModules(component);
1,563✔
513

514
            if (externalPositions.length > 0) {
1,563✔
515
                for (uint256 j = 0; j < externalPositions.length; j++) {
334✔
516
                    int256 externalPositionUnit = _setToken.getExternalPositionRealUnit(component, externalPositions[j]);
336✔
517

518
                    // If positionUnit <= 0 it will be "added" to debt position
519
                    if (externalPositionUnit > 0) {
336✔
520
                        cumulativeEquity = cumulativeEquity.add(externalPositionUnit);
521
                    } else {
522
                        cumulativeDebt = cumulativeDebt.add(externalPositionUnit);
523
                    }
524
                }
525
            }
526

527
            equityUnits[i] = cumulativeEquity.toUint256();
528
            debtUnits[i] = cumulativeDebt.mul(-1).toUint256();
529
        }
530

531
        return (components, equityUnits, debtUnits);
1,132✔
532
    }
533

534
    /**
535
     * Resolve equity positions associated with SetToken. On issuance, the total equity position for an asset (including default and external
536
     * positions) is transferred in. Then any external position hooks are called to transfer the external positions to their necessary place.
537
     * On redemption all external positions are recalled by the external position hook, then those position plus any default position are
538
     * transferred back to the _to address.
539
     */
540
    function _resolveEquityPositions(
541
        ISetToken _setToken,
542
        uint256 _quantity,
543
        address _to,
544
        bool _isIssue,
545
        address[] memory _components,
546
        uint256[] memory _componentEquityQuantities
547
    )
548
        internal
549
    {
550
        for (uint256 i = 0; i < _components.length; i++) {
131✔
551
            address component = _components[i];
193✔
552
            uint256 componentQuantity = _componentEquityQuantities[i];
193✔
553
            if (componentQuantity > 0) {
193✔
554
                if (_isIssue) {
133✔
555
                    transferFrom(
83✔
556
                        IERC20(component),
557
                        msg.sender,
558
                        address(_setToken),
559
                        componentQuantity
560
                    );
561

562
                    _executeExternalPositionHooks(_setToken, _quantity, IERC20(component), true, true);
83✔
563
                } else {
564
                    _executeExternalPositionHooks(_setToken, _quantity, IERC20(component), false, true);
50✔
565

566
                    _setToken.strictInvokeTransfer(
50✔
567
                        component,
568
                        _to,
569
                        componentQuantity
570
                    );
571
                }
572
            }
573
        }
574
    }
575

576
    /**
577
     * Resolve debt positions associated with SetToken. On issuance, debt positions are entered into by calling the external position hook. The
578
     * resulting debt is then returned to the calling address. On redemption, the module transfers in the required debt amount from the caller
579
     * and uses those funds to repay the debt on behalf of the SetToken.
580
     */
581
    function _resolveDebtPositions(
582
        ISetToken _setToken,
583
        uint256 _quantity,
584
        bool _isIssue,
585
        address[] memory _components,
586
        uint256[] memory _componentDebtQuantities
587
    )
588
        internal
589
    {
590
        for (uint256 i = 0; i < _components.length; i++) {
131✔
591
            address component = _components[i];
193✔
592
            uint256 componentQuantity = _componentDebtQuantities[i];
193✔
593
            if (componentQuantity > 0) {
193✔
594
                if (_isIssue) {
58✔
595
                    _executeExternalPositionHooks(_setToken, _quantity, IERC20(component), true, false);
39✔
596
                    _setToken.strictInvokeTransfer(
39✔
597
                        component,
598
                        msg.sender,
599
                        componentQuantity
600
                    );
601
                } else {
602
                    transferFrom(
19✔
603
                        IERC20(component),
604
                        msg.sender,
605
                        address(_setToken),
606
                        componentQuantity
607
                    );
608
                    _executeExternalPositionHooks(_setToken, _quantity, IERC20(component), false, false);
19✔
609
                }
610
            }
611
        }
612
    }
613

614
    /**
615
     * If any manager fees mints Sets to the defined feeRecipient. If protocol fee is enabled mints Sets to protocol
616
     * feeRecipient.
617
     */
618
    function _resolveFees(ISetToken _setToken, uint256 managerFee, uint256 protocolFee) internal {
619
        if (managerFee > 0) {
997✔
620
            _setToken.mint(issuanceSettings[_setToken].feeRecipient, managerFee);
201✔
621

622
            // Protocol fee check is inside manager fee check because protocol fees are only collected on manager fees
623
            if (protocolFee > 0) {
201✔
624
                _setToken.mint(controller.feeRecipient(), protocolFee);
6✔
625
            }
626
        }
627
    }
628

629
    /**
630
     * If a pre-issue hook has been configured, call the external-protocol contract. Pre-issue hook logic
631
     * can contain arbitrary logic including validations, external function calls, etc.
632
     */
633
    function _callManagerPreIssueHooks(
634
        ISetToken _setToken,
635
        uint256 _quantity,
636
        address _caller,
637
        address _to
638
    )
639
        internal
640
        returns(address)
641
    {
642
        IManagerIssuanceHook preIssueHook = issuanceSettings[_setToken].managerIssuanceHook;
877✔
643
        if (address(preIssueHook) != address(0)) {
877✔
644
            preIssueHook.invokePreIssueHook(_setToken, _quantity, _caller, _to);
731✔
645
            return address(preIssueHook);
731✔
646
        }
647

648
        return address(0);
146✔
649
    }
650

651
    /**
652
     * Calls all modules that have registered with the DebtIssuanceModule that have a moduleIssueHook.
653
     */
654
    function _callModulePreIssueHooks(ISetToken _setToken, uint256 _quantity) internal {
655
        address[] memory issuanceHooks = issuanceSettings[_setToken].moduleIssuanceHooks;
877✔
656
        for (uint256 i = 0; i < issuanceHooks.length; i++) {
877✔
657
            IModuleIssuanceHook(issuanceHooks[i]).moduleIssueHook(_setToken, _quantity);
245✔
658
        }
659
    }
660

661
    /**
662
     * Calls all modules that have registered with the DebtIssuanceModule that have a moduleRedeemHook.
663
     */
664
    function _callModulePreRedeemHooks(ISetToken _setToken, uint256 _quantity) internal {
665
        address[] memory issuanceHooks = issuanceSettings[_setToken].moduleIssuanceHooks;
128✔
666
        for (uint256 i = 0; i < issuanceHooks.length; i++) {
128✔
667
            IModuleIssuanceHook(issuanceHooks[i]).moduleRedeemHook(_setToken, _quantity);
128✔
668
        }
669
    }
670

671
    /**
672
     * For each component's external module positions, calculate the total notional quantity, and
673
     * call the module's issue hook or redeem hook.
674
     * Note: It is possible that these hooks can cause the states of other modules to change.
675
     * It can be problematic if the hook called an external function that called back into a module, resulting in state inconsistencies.
676
     */
677
    function _executeExternalPositionHooks(
678
        ISetToken _setToken,
679
        uint256 _setTokenQuantity,
680
        IERC20 _component,
681
        bool _isIssue,
682
        bool _isEquity
683
    )
684
        internal
685
    {
686
        address[] memory externalPositionModules = _setToken.getExternalPositionModules(address(_component));
1,295✔
687
        uint256 modulesLength = externalPositionModules.length;
1,295✔
688
        if (_isIssue) {
1,295✔
689
            for (uint256 i = 0; i < modulesLength; i++) {
1,078✔
690
                IModuleIssuanceHook(externalPositionModules[i]).componentIssueHook(_setToken, _setTokenQuantity, _component, _isEquity);
111✔
691
            }
692
        } else {
693
            for (uint256 i = 0; i < modulesLength; i++) {
217✔
694
                IModuleIssuanceHook(externalPositionModules[i]).componentRedeemHook(_setToken, _setTokenQuantity, _component, _isEquity);
76✔
695
            }
696
        }
697
    }
698
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc