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

IndexCoop / index-protocol / 3386a5b4-1090-4a99-af92-0eee2a8178c4

05 Feb 2025 06:12AM UTC coverage: 90.86% (-5.1%) from 95.955%
3386a5b4-1090-4a99-af92-0eee2a8178c4

push

circleci

ckoopmann
feat: Aero Slipstream Exchange Adapter

2178 of 2520 branches covered (86.43%)

Branch coverage included in aggregate %.

0 of 11 new or added lines in 1 file covered. (0.0%)

127 existing lines in 10 files now uncovered.

3429 of 3651 relevant lines covered (93.92%)

204.92 hits per line

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

64.04
/contracts/protocol/modules/v1/SlippageIssuanceModule.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 { DebtIssuanceModule } from "./DebtIssuanceModule.sol";
23
import { IController } from "../../../interfaces/IController.sol";
24
import { IModuleIssuanceHookV2 } from "../../../interfaces/IModuleIssuanceHookV2.sol";
25
import { ISetToken } from "../../../interfaces/ISetToken.sol";
26

27
/**
28
 * @title SlippageIssuanceModule
29
 * @author Set Protocol
30
 *
31
 * The SlippageIssuanceModule is a module that enables users to issue and redeem SetTokens that requires a transaction that incurs slippage
32
 * in order to replicate the Set. Like the DebtIssuanceModule, module hooks are added to allow for syncing of positions, and component
33
 * level hooks are added to ensure positions are replicated correctly. The manager can define arbitrary issuance logic in the manager hook,
34
 * as well as specify issue and redeem fees. The getRequiredComponentIssuanceUnits and it's redemption counterpart now also include any
35
 * changes to the position expected to happen during issuance thus providing much better estimations for positions that are synced or require
36
 * a trade. It is worth noting that this module inherits from DebtIssuanceModule, consequently it can also be used for issuances that do NOT
37
 * require slippage just by calling the issue and redeem endpoints.
38
 */
39
contract SlippageIssuanceModule is DebtIssuanceModule {
40

41
    constructor(IController _controller) public DebtIssuanceModule(_controller) {}
42

43
    /* ============ External Functions ============ */
44

45
    /**
46
     * @dev Reverts upon calling. Call `issueWithSlippage` instead.
47
     */
48
    function issue(ISetToken /*_setToken*/, uint256 /*_quantity*/, address /*_to*/) external override(DebtIssuanceModule) {
UNCOV
49
        revert("Call issueWithSlippage instead");
×
50
    }
51

52
    /**
53
     * @dev Reverts upon calling. Call `redeemWithSlippage` instead.
54
     */
55
    function redeem(ISetToken /*_setToken*/, uint256 /*_quantity*/, address /*_to*/) external override(DebtIssuanceModule) {
UNCOV
56
        revert("Call redeemWithSlippage instead");
×
57
    }
58

59
    /**
60
     * Deposits components to the SetToken, replicates any external module component positions and mints
61
     * the SetToken. If the token has a debt position all collateral will be transferred in first then debt
62
     * will be returned to the minting address. If specified, a fee will be charged on issuance. Issuer can
63
     * also pass in a max amount of tokens they are willing to pay for each component. They are NOT required
64
     * to pass in a limit for every component, and may in fact only want to pass in limits for components which
65
     * incur slippage to replicate (i.e. perpetuals). Passing in empty arrays for _checkComponents and
66
     * _maxTokenAmountsIn is equivalent to calling issue. NOTE: not passing in limits for positions that require
67
     * a trade for replication leaves the issuer open to sandwich attacks!
68
     *
69
     * @param _setToken             Instance of the SetToken to issue
70
     * @param _setQuantity          Quantity of SetToken to issue
71
     * @param _checkedComponents    Array of components to be checked to verify required collateral doesn't exceed
72
     *                                  defined max. Each entry must be unique.
73
     * @param _maxTokenAmountsIn    Maps to same index in _checkedComponents. Max amount of component willing to
74
     *                                  transfer in to collateralize _setQuantity amount of _setToken.
75
     * @param _to                   Address to mint SetToken to
76
     */
77
    function issueWithSlippage(
78
        ISetToken _setToken,
79
        uint256 _setQuantity,
80
        address[] memory _checkedComponents,
81
        uint256[] memory _maxTokenAmountsIn,
82
        address _to
83
    )
84
        external
85
        virtual
86
        nonReentrant
33!
87
        onlyValidAndInitializedSet(_setToken)
33!
88
    {
89
        _validateInputs(_setQuantity, _checkedComponents, _maxTokenAmountsIn);
33✔
90

91
        address hookContract = _callManagerPreIssueHooks(_setToken, _setQuantity, msg.sender, _to);
33✔
92

93
        bool isIssue = true;
33✔
94

95
        (
33✔
96
            uint256 quantityWithFees,
97
            uint256 managerFee,
98
            uint256 protocolFee
99
        ) = calculateTotalFees(_setToken, _setQuantity, isIssue);
100

101
        _callModulePreIssueHooks(_setToken, quantityWithFees);
33✔
102

103
        // Scoping logic to avoid stack too deep errors
104
        {
105
            (
33✔
106
                address[] memory components,
107
                uint256[] memory equityUnits,
108
                uint256[] memory debtUnits
109
            ) = _calculateRequiredComponentIssuanceUnits(_setToken, quantityWithFees, isIssue);
110

111
            // Validate the required token amounts don't exceed those passed by issuer
112
            _validateTokenTransferLimits(_checkedComponents, _maxTokenAmountsIn, components, equityUnits, isIssue);
33✔
113

114
            _resolveEquityPositions(_setToken, quantityWithFees, _to, isIssue, components, equityUnits);
33✔
115
            _resolveDebtPositions(_setToken, quantityWithFees, isIssue, components, debtUnits);
33✔
116
            _resolveFees(_setToken, managerFee, protocolFee);
33✔
117
        }
118

119
        _setToken.mint(_to, _setQuantity);
33✔
120

121
        emit SetTokenIssued(
33✔
122
            _setToken,
123
            msg.sender,
124
            _to,
125
            hookContract,
126
            _setQuantity,
127
            managerFee,
128
            protocolFee
129
        );
130
    }
131

132
    /**
133
     * Returns components from the SetToken, unwinds any external module component positions and burns the SetToken.
134
     * If the token has debt positions, the module transfers in the required debt amounts from the caller and uses
135
     * those funds to repay the debts on behalf of the SetToken. All debt will be paid down first then equity positions
136
     * will be returned to the minting address. If specified, a fee will be charged on redeem. Redeemer can
137
     * also pass in a min amount of tokens they want to receive for each component. They are NOT required
138
     * to pass in a limit for every component, and may in fact only want to pass in limits for components which
139
     * incur slippage to replicate (i.e. perpetuals). Passing in empty arrays for _checkComponents and
140
     * _minTokenAmountsOut is equivalent to calling redeem. NOTE: not passing in limits for positions that require
141
     * a trade for replication leaves the redeemer open to sandwich attacks!
142
     *
143
     * @param _setToken             Instance of the SetToken to redeem
144
     * @param _setQuantity          Quantity of SetToken to redeem
145
     * @param _checkedComponents    Array of components to be checked to verify received collateral isn't less than
146
     *                                  defined min. Each entry must be unique.
147
     * @param _minTokenAmountsOut   Maps to same index in _checkedComponents. Min amount of component willing to
148
     *                                  receive to redeem _setQuantity amount of _setToken.
149
     * @param _to                   Address to send collateral to
150
     */
151
    function redeemWithSlippage(
152
        ISetToken _setToken,
153
        uint256 _setQuantity,
154
        address[] memory _checkedComponents,
155
        uint256[] memory _minTokenAmountsOut,
156
        address _to
157
    )
158
        external
159
        virtual
160
        nonReentrant
30!
161
        onlyValidAndInitializedSet(_setToken)
30!
162
    {
163
        _validateInputs(_setQuantity, _checkedComponents, _minTokenAmountsOut);
30✔
164

165
        bool isIssue = false;
30✔
166

167
        (
30✔
168
            uint256 quantityNetFees,
169
            uint256 managerFee,
170
            uint256 protocolFee
171
        ) = calculateTotalFees(_setToken, _setQuantity, isIssue);
172

173
        _callModulePreRedeemHooks(_setToken, quantityNetFees);
30✔
174

175
        // Place burn after pre-redeem hooks because burning tokens may lead to false accounting of synced positions
176
        _setToken.burn(msg.sender, _setQuantity);
30✔
177

178
        (
30✔
179
            address[] memory components,
180
            uint256[] memory equityUnits,
181
            uint256[] memory debtUnits
182
        ) = _calculateRequiredComponentIssuanceUnits(_setToken, quantityNetFees, isIssue);
183

184
        // Validate the required token amounts don't exceed those passed by redeemer
185
        _validateTokenTransferLimits(_checkedComponents, _minTokenAmountsOut, components, equityUnits, isIssue);
30✔
186

187
        _resolveDebtPositions(_setToken, quantityNetFees, isIssue, components, debtUnits);
30✔
188
        _resolveEquityPositions(_setToken, quantityNetFees, _to, isIssue, components, equityUnits);
30✔
189
        _resolveFees(_setToken, managerFee, protocolFee);
30✔
190

191
        emit SetTokenRedeemed(
30✔
192
            _setToken,
193
            msg.sender,
194
            _to,
195
            _setQuantity,
196
            managerFee,
197
            protocolFee
198
        );
199
    }
200

201
    /**
202
     * Calculates the amount of each component needed to collateralize passed issue quantity plus fees of Sets as well as amount of debt
203
     * that will be returned to caller. Takes into account position updates from pre action module hooks.
204
     * (manager hooks not included).
205
     *
206
     * NOTE: This getter is non-view to allow module hooks to determine units by simulating state changes in an external protocol and
207
     * reverting. It should only be called by off-chain methods via static call.
208
     *
209
     * @param _setToken         Instance of the SetToken to issue
210
     * @param _quantity         Amount of Sets to be issued
211
     *
212
     * @return address[]        Array of component addresses making up the Set
213
     * @return uint256[]        Array of equity notional amounts of each component, respectively, represented as uint256
214
     * @return uint256[]        Array of debt notional amounts of each component, respectively, represented as uint256
215
     */
216
    function getRequiredComponentIssuanceUnitsOffChain(
217
        ISetToken _setToken,
218
        uint256 _quantity
219
    )
220
        external
221
        returns (address[] memory, uint256[] memory, uint256[] memory)
222
    {
UNCOV
223
        bool isIssue = true;
×
224

UNCOV
225
        (
×
226
            uint256 totalQuantity,,
227
        ) = calculateTotalFees(_setToken, _quantity, isIssue);
228

UNCOV
229
        (
×
230
            int256[] memory equityIssuanceAdjustments,
231
            int256[] memory debtIssuanceAdjustments
232
        )= _calculateAdjustments(_setToken, totalQuantity, isIssue);
233

UNCOV
234
        return _calculateAdjustedComponentIssuanceUnits(
×
235
            _setToken,
236
            totalQuantity,
237
            isIssue,
238
            equityIssuanceAdjustments,
239
            debtIssuanceAdjustments
240
        );
241
    }
242

243
    /**
244
     * Calculates the amount of each component that will be returned on redemption net of fees as well as how much debt needs to be paid down to
245
     * redeem. Takes into account position updates from pre action module hooks (manager hooks not included).
246
     *
247
     * NOTE: This getter is non-view to allow module hooks to determine units by simulating state changes in an external protocol and
248
     * reverting. It should only be called by off-chain methods via static call.
249
     *
250
     * @param _setToken         Instance of the SetToken to issue
251
     * @param _quantity         Amount of Sets to be redeemed
252
     *
253
     * @return address[]        Array of component addresses making up the Set
254
     * @return uint256[]        Array of equity notional amounts of each component, respectively, represented as uint256
255
     * @return uint256[]        Array of debt notional amounts of each component, respectively, represented as uint256
256
     */
257
    function getRequiredComponentRedemptionUnitsOffChain(
258
        ISetToken _setToken,
259
        uint256 _quantity
260
    )
261
        external
262
        returns (address[] memory, uint256[] memory, uint256[] memory)
263
    {
264
        bool isIssue = false;
2✔
265

266
        (
2✔
267
            uint256 totalQuantity,,
268
        ) = calculateTotalFees(_setToken, _quantity, isIssue);
269

270
        (
2✔
271
            int256[] memory equityRedemptionAdjustments,
272
            int256[] memory debtRedemptionAdjustments
273
        )= _calculateAdjustments(_setToken, totalQuantity, isIssue);
274

275
        return _calculateAdjustedComponentIssuanceUnits(
2✔
276
            _setToken,
277
            totalQuantity,
278
            isIssue,
279
            equityRedemptionAdjustments,
280
            debtRedemptionAdjustments
281
        );
282
    }
283

284
    /* ============ Internal Functions ============ */
285

286
    /**
287
     * Similar to _calculateRequiredComponentIssuanceUnits but adjustments for positions that will be updated DURING the issue
288
     * or redeem process are added in. Adjustments can be either positive or negative, a negative debt adjustment means there
289
     * is less debt than the pre-issue position unit indicates there will be.
290
     *
291
     * @param _setToken             Instance of the SetToken to redeem
292
     * @param _quantity             Quantity of SetToken to redeem
293
     * @param _isIssue              Boolean indicating whether Set is being issues
294
     * @param _equityAdjustments    Array of equity position unit adjustments that account for position changes during issuance
295
     *                                  (maps to getComponents array)
296
     * @param _debtAdjustments      Array of debt position unit adjustments that account for position changes during issuance
297
     *                                  (maps to getComponents array)
298
     */
299
    function _calculateAdjustedComponentIssuanceUnits(
300
        ISetToken _setToken,
301
        uint256 _quantity,
302
        bool _isIssue,
303
        int256[] memory _equityAdjustments,
304
        int256[] memory _debtAdjustments
305
    )
306
        internal
307
        view
308
        returns (address[] memory, uint256[] memory, uint256[] memory)
309
    {
310
        (
2✔
311
            address[] memory components,
312
            uint256[] memory equityUnits,
313
            uint256[] memory debtUnits
314
        ) = _getTotalIssuanceUnits(_setToken);
315

316
        // NOTE: components.length isn't stored in local variable to avoid stack too deep errors. Since this function is used
317
        // by view functions intended to be queried off-chain this seems acceptable
318
        uint256[] memory totalEquityUnits = new uint256[](components.length);
2✔
319
        uint256[] memory totalDebtUnits = new uint256[](components.length);
2✔
320
        for (uint256 i = 0; i < components.length; i++) {
2✔
321
            // NOTE: If equityAdjustment is negative and exceeds equityUnits in absolute value this will revert
322
            // When adjusting units if we have MORE equity as a result of issuance (ie adjustment is positive) we want to add that
323
            // to the unadjusted equity units hence we use addition. Vice versa if we want to remove equity, the adjustment is negative
324
            // hence adding adjusts the units lower
325
            uint256 adjustedEquityUnits = equityUnits[i].toInt256().add(_equityAdjustments[i]).toUint256();
2✔
326

327
            // Use preciseMulCeil to round up to ensure overcollateration when small issue quantities are provided
328
            // and preciseMul to round down to ensure overcollateration when small redeem quantities are provided
329
            totalEquityUnits[i] = _isIssue ?
330
                adjustedEquityUnits.preciseMulCeil(_quantity) :
2!
331
                adjustedEquityUnits.preciseMul(_quantity);
332

333
            // NOTE: If debtAdjustment is negative and exceeds debtUnits in absolute value this will revert
334
            // When adjusting units if we have MORE debt as a result of issuance (ie adjustment is negative) we want to increase
335
            // the unadjusted debt units hence we subtract. Vice versa if we want to remove debt the adjustment is positive
336
            // hence subtracting adjusts the units lower.
337
            uint256 adjustedDebtUnits = debtUnits[i].toInt256().sub(_debtAdjustments[i]).toUint256();
2✔
338

339
            // Use preciseMulCeil to round up to ensure overcollateration when small redeem quantities are provided
340
            // and preciseMul to round down to ensure overcollateration when small issue quantities are provided
341
            totalDebtUnits[i] = _isIssue ?
342
                adjustedDebtUnits.preciseMul(_quantity) :
2!
343
                adjustedDebtUnits.preciseMulCeil(_quantity);
344
        }
345

346
        return (components, totalEquityUnits, totalDebtUnits);
2✔
347
    }
348

349
    /**
350
     * Calculates all equity and debt adjustments that will be made to the positionUnits within the context of the current chain
351
     * state. Each module that registers a hook with the SlippageIssuanceModule is cycled through and returns how the module will
352
     * adjust the equity and debt positions for the Set. All changes are summed/netted against each other. The adjustment arrays
353
     * returned by each module are ordered according to the components array on the SetToken.
354
     *
355
     * @param _setToken             Instance of the SetToken to redeem
356
     * @param _quantity             Quantity of SetToken to redeem
357
     * @param _isIssue              Boolean indicating whether Set is being issues
358
     */
359
    function _calculateAdjustments(
360
        ISetToken _setToken,
361
        uint256 _quantity,
362
        bool _isIssue
363
    )
364
        internal
365
        returns (int256[] memory, int256[] memory)
366
    {
367
        uint256 componentsLength = _setToken.getComponents().length;
2✔
368
        int256[] memory cumulativeEquityAdjustments = new int256[](componentsLength);
2✔
369
        int256[] memory cumulativeDebtAdjustments = new int256[](componentsLength);
2✔
370

371
        address[] memory issuanceHooks = issuanceSettings[_setToken].moduleIssuanceHooks;
2✔
372
        for (uint256 i = 0; i < issuanceHooks.length; i++) {
2✔
373
            (
2✔
374
                int256[] memory equityAdjustments,
375
                int256[] memory debtAdjustments
376
            ) = _isIssue ? IModuleIssuanceHookV2(issuanceHooks[i]).getIssuanceAdjustments(_setToken, _quantity) :
2!
377
                IModuleIssuanceHookV2(issuanceHooks[i]).getRedemptionAdjustments(_setToken, _quantity);
378

379
            for (uint256 j = 0; j < componentsLength; j++) {
2✔
380
                cumulativeEquityAdjustments[j] = cumulativeEquityAdjustments[j].add(equityAdjustments[j]);
381
                cumulativeDebtAdjustments[j] = cumulativeDebtAdjustments[j].add(debtAdjustments[j]);
382
            }
383
        }
384

385
        return (cumulativeEquityAdjustments, cumulativeDebtAdjustments);
2✔
386
    }
387

388
    /**
389
     * Validates that the required token amounts to replicate/redeem an equity position are not greater or less than the limits
390
     * defined by the issuer/redeemer. Every component is NOT required to be checked however each checked component MUST be a
391
     * valid component for the Set.
392
     *
393
     * @param _checkedComponents            Components the issuer/redeemer wants checked
394
     * @param _tokenTransferLimits          If _isIssue true, max amount of checked component allowed to xfer, else min amount of
395
     *                                          of checked component the redeemer wants to receive
396
     * @param _components                   Array of SetToken components
397
     * @param _tokenTransferAmounts         Amount of component required for issuance or returned for redemption, maps to components
398
     * @param _isIssue                      Boolean indicating whether Set is being issues
399
     */
400
    function _validateTokenTransferLimits(
401
        address[] memory _checkedComponents,
402
        uint256[] memory _tokenTransferLimits,
403
        address[] memory _components,
404
        uint256[] memory _tokenTransferAmounts,
405
        bool _isIssue
406
    )
407
        internal
408
        pure
409
    {
410
        for(uint256 i = 0; i < _checkedComponents.length; i++) {
63✔
UNCOV
411
            (uint256 componentIndex, bool isIn) = _components.indexOf(_checkedComponents[i]);
×
412

UNCOV
413
            require(isIn, "Limit passed for invalid component");
×
414

UNCOV
415
            if (_isIssue) {
×
UNCOV
416
                require(_tokenTransferLimits[i] >= _tokenTransferAmounts[componentIndex], "Too many tokens required for issuance");
×
417
            } else {
UNCOV
418
                require(_tokenTransferLimits[i] <= _tokenTransferAmounts[componentIndex], "Too few tokens returned for redemption");
×
419
            }
420
        }
421
    }
422

423
    /**
424
     * Validates setQuantity great than 0 and that arrays are of equal length and components are not duplicated.
425
     */
426
    function _validateInputs(
427
        uint256 _setQuantity,
428
        address[] memory _components,
429
        uint256[] memory _componentLimits
430
    )
431
        internal
432
        pure
433
    {
434
        require(_setQuantity > 0, "SetToken quantity must be > 0");
63!
435

436
        uint256 componentsLength = _components.length;
63✔
437
        require(componentsLength == _componentLimits.length, "Array length mismatch");
63!
438
        if (componentsLength > 0) {
63!
UNCOV
439
            require(!_components.hasDuplicate(), "Cannot duplicate addresses");
×
440
        }
441
    }
442
}
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