• 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

67.5
/contracts/protocol/modules/v1/WrapModuleV2.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

27
import { IController } from "../../../interfaces/IController.sol";
28
import { IIntegrationRegistry } from "../../../interfaces/IIntegrationRegistry.sol";
29
import { Invoke } from "../../lib/Invoke.sol";
30
import { ISetToken } from "../../../interfaces/ISetToken.sol";
31
import { IWETH } from "../../../interfaces/external/IWETH.sol";
32
import { IWrapV2Adapter } from "../../../interfaces/IWrapV2Adapter.sol";
33
import { ModuleBase } from "../../lib/ModuleBase.sol";
34
import { Position } from "../../lib/Position.sol";
35
import { PreciseUnitMath } from "../../../lib/PreciseUnitMath.sol";
36

37
/**
38
 * @title WrapModuleV2
39
 * @author Set Protocol
40
 *
41
 * Module that enables the wrapping of ERC20 and Ether positions via third party protocols. The WrapModuleV2
42
 * works in conjunction with WrapV2Adapters, in which the wrapAdapterID / integrationNames are stored on the
43
 * integration registry.
44
 *
45
 * Some examples of wrap actions include wrapping, DAI to cDAI (Compound) or Dai to aDai (AAVE).
46
 * 
47
 * Warning: This module is designed solely for no-loss wrapping and unwrapping of assets. It should not be used
48
 * for wrapping assets that may experience a loss in value due to fees or other factors. For such operations, 
49
 * please use the TradeModule.
50
 * 
51
 * CHANGELOG:
52
 * - 8/7/24: Grant and revoke max approval when unwrapping
53
 */
54
contract WrapModuleV2 is ModuleBase, ReentrancyGuard {
55
    using SafeCast for int256;
56
    using PreciseUnitMath for uint256;
57
    using Position for uint256;
58
    using SafeMath for uint256;
59

60
    using Invoke for ISetToken;
61
    using Position for ISetToken.Position;
62
    using Position for ISetToken;
63

64
    /* ============ Events ============ */
65

66
    event ComponentWrapped(
67
        ISetToken indexed _setToken,
68
        address indexed _underlyingToken,
69
        address indexed _wrappedToken,
70
        uint256 _underlyingQuantity,
71
        uint256 _wrappedQuantity,
72
        string _integrationName
73
    );
74

75
    event ComponentUnwrapped(
76
        ISetToken indexed _setToken,
77
        address indexed _underlyingToken,
78
        address indexed _wrappedToken,
79
        uint256 _underlyingQuantity,
80
        uint256 _wrappedQuantity,
81
        string _integrationName
82
    );
83

84
    /* ============ State Variables ============ */
85

86
    // Wrapped ETH address
87
    IWETH public weth;
88

89
    /* ============ Constructor ============ */
90

91
    /**
92
     * @param _controller               Address of controller contract
93
     * @param _weth                     Address of wrapped eth
94
     */
95
    constructor(IController _controller, IWETH _weth) public ModuleBase(_controller) {
96
        weth = _weth;
97
    }
98

99
    /* ============ External Functions ============ */
100

101
    /**
102
     * MANAGER-ONLY: Instructs the SetToken to wrap an underlying asset into a wrappedToken via a specified adapter.
103
     *
104
     * @param _setToken             Instance of the SetToken
105
     * @param _underlyingToken      Address of the component to be wrapped
106
     * @param _wrappedToken         Address of the desired wrapped token
107
     * @param _underlyingUnits      Quantity of underlying units in Position units
108
     * @param _integrationName      Name of wrap module integration (mapping on integration registry)
109
     * @param _wrapData             Arbitrary bytes to pass into the WrapV2Adapter
110
     */
111
    function wrap(
112
        ISetToken _setToken,
113
        address _underlyingToken,
114
        address _wrappedToken,
115
        uint256 _underlyingUnits,
116
        string calldata _integrationName,
117
        bytes memory _wrapData
118
    )
119
        external
120
        nonReentrant
9!
121
        onlyManagerAndValidSet(_setToken)
9!
122
    {
123
        (
9✔
124
            uint256 notionalUnderlyingWrapped,
125
            uint256 notionalWrapped
126
        ) = _validateWrapAndUpdate(
127
            _integrationName,
128
            _setToken,
129
            _underlyingToken,
130
            _wrappedToken,
131
            _underlyingUnits,
132
            _wrapData,
133
            false // does not use Ether
134
        );
135

136
        emit ComponentWrapped(
9✔
137
            _setToken,
138
            _underlyingToken,
139
            _wrappedToken,
140
            notionalUnderlyingWrapped,
141
            notionalWrapped,
142
            _integrationName
143
        );
144
    }
145

146
    /**
147
     * MANAGER-ONLY: Instructs the SetToken to wrap Ether into a wrappedToken via a specified adapter. Since SetTokens
148
     * only hold WETH, in order to support protocols that collateralize with Ether the SetToken's WETH must be unwrapped
149
     * first before sending to the external protocol.
150
     *
151
     * @param _setToken             Instance of the SetToken
152
     * @param _wrappedToken         Address of the desired wrapped token
153
     * @param _underlyingUnits      Quantity of underlying units in Position units
154
     * @param _integrationName      Name of wrap module integration (mapping on integration registry)
155
     * @param _wrapData             Arbitrary bytes to pass into the WrapV2Adapter
156
     */
157
    function wrapWithEther(
158
        ISetToken _setToken,
159
        address _wrappedToken,
160
        uint256 _underlyingUnits,
161
        string calldata _integrationName,
162
        bytes memory _wrapData
163
    )
164
        external
165
        nonReentrant
×
166
        onlyManagerAndValidSet(_setToken)
×
167
    {
UNCOV
168
        (
×
169
            uint256 notionalUnderlyingWrapped,
170
            uint256 notionalWrapped
171
        ) = _validateWrapAndUpdate(
172
            _integrationName,
173
            _setToken,
174
            address(weth),
175
            _wrappedToken,
176
            _underlyingUnits,
177
            _wrapData,
178
            true // uses Ether
179
        );
180

UNCOV
181
        emit ComponentWrapped(
×
182
            _setToken,
183
            address(weth),
184
            _wrappedToken,
185
            notionalUnderlyingWrapped,
186
            notionalWrapped,
187
            _integrationName
188
        );
189
    }
190

191
    /**
192
     * MANAGER-ONLY: Instructs the SetToken to unwrap a wrapped asset into its underlying via a specified adapter.
193
     *
194
     * @param _setToken             Instance of the SetToken
195
     * @param _underlyingToken      Address of the underlying asset
196
     * @param _wrappedToken         Address of the component to be unwrapped
197
     * @param _wrappedUnits         Quantity of wrapped tokens in Position units
198
     * @param _integrationName      ID of wrap module integration (mapping on integration registry)
199
     * @param _unwrapData           Arbitrary bytes to pass into the WrapV2Adapter
200
     */
201
    function unwrap(
202
        ISetToken _setToken,
203
        address _underlyingToken,
204
        address _wrappedToken,
205
        uint256 _wrappedUnits,
206
        string calldata _integrationName,
207
        bytes memory _unwrapData
208
    )
209
        external
210
        nonReentrant
5!
211
        onlyManagerAndValidSet(_setToken)
5!
212
    {
213
        (
5✔
214
            uint256 notionalUnderlyingUnwrapped,
215
            uint256 notionalUnwrapped
216
        ) = _validateUnwrapAndUpdate(
217
            _integrationName,
218
            _setToken,
219
            _underlyingToken,
220
            _wrappedToken,
221
            _wrappedUnits,
222
            _unwrapData,
223
            false // uses Ether
224
        );
225

226
        emit ComponentUnwrapped(
4✔
227
            _setToken,
228
            _underlyingToken,
229
            _wrappedToken,
230
            notionalUnderlyingUnwrapped,
231
            notionalUnwrapped,
232
            _integrationName
233
        );
234
    }
235

236
    /**
237
     * MANAGER-ONLY: Instructs the SetToken to unwrap a wrapped asset collateralized by Ether into Wrapped Ether. Since
238
     * external protocol will send back Ether that Ether must be Wrapped into WETH in order to be accounted for by SetToken.
239
     *
240
     * @param _setToken                 Instance of the SetToken
241
     * @param _wrappedToken             Address of the component to be unwrapped
242
     * @param _wrappedUnits             Quantity of wrapped tokens in Position units
243
     * @param _integrationName          ID of wrap module integration (mapping on integration registry)
244
     * @param _unwrapData           Arbitrary bytes to pass into the WrapV2Adapter
245
     */
246
    function unwrapWithEther(
247
        ISetToken _setToken,
248
        address _wrappedToken,
249
        uint256 _wrappedUnits,
250
        string calldata _integrationName,
251
        bytes memory _unwrapData
252
    )
253
        external
254
        nonReentrant
×
255
        onlyManagerAndValidSet(_setToken)
×
256
    {
UNCOV
257
        (
×
258
            uint256 notionalUnderlyingUnwrapped,
259
            uint256 notionalUnwrapped
260
        ) = _validateUnwrapAndUpdate(
261
            _integrationName,
262
            _setToken,
263
            address(weth),
264
            _wrappedToken,
265
            _wrappedUnits,
266
            _unwrapData,
267
            true // uses Ether
268
        );
269

UNCOV
270
        emit ComponentUnwrapped(
×
271
            _setToken,
272
            address(weth),
273
            _wrappedToken,
274
            notionalUnderlyingUnwrapped,
275
            notionalUnwrapped,
276
            _integrationName
277
        );
278
    }
279

280
    /**
281
     * Initializes this module to the SetToken. Only callable by the SetToken's manager.
282
     *
283
     * @param _setToken             Instance of the SetToken to issue
284
     */
285
    function initialize(ISetToken _setToken) external onlySetManager(_setToken, msg.sender) {
5!
286
        require(controller.isSet(address(_setToken)), "Must be controller-enabled SetToken");
5!
287
        require(isSetPendingInitialization(_setToken), "Must be pending initialization");
5!
288
        _setToken.initializeModule();
5✔
289
    }
290

291
    /**
292
     * Removes this module from the SetToken, via call by the SetToken.
293
     */
294
    function removeModule() external override {}
295

296

297
    /* ============ Internal Functions ============ */
298

299
    /**
300
     * Validates the wrap operation is valid. In particular, the following checks are made:
301
     * - The position is Default
302
     * - The position has sufficient units given the transact quantity
303
     * - The transact quantity > 0
304
     *
305
     * It is expected that the adapter will check if wrappedToken/underlyingToken are a valid pair for the given
306
     * integration.
307
     */
308
    function _validateInputs(
309
        ISetToken _setToken,
310
        address _transactPosition,
311
        uint256 _transactPositionUnits
312
    )
313
        internal
314
        view
315
    {
316
        require(_transactPositionUnits > 0, "Target position units must be > 0");
14!
317
        require(_setToken.hasDefaultPosition(_transactPosition), "Target default position must be component");
14!
318
        require(
14!
319
            _setToken.hasSufficientDefaultUnits(_transactPosition, _transactPositionUnits),
320
            "Unit cant be greater than existing"
321
        );
322
    }
323

324
    /**
325
     * The WrapModule calculates the total notional underlying to wrap, approves the underlying to the 3rd party
326
     * integration contract, then invokes the SetToken to call wrap by passing its calldata along. When raw ETH
327
     * is being used (_usesEther = true) WETH position must first be unwrapped and underlyingAddress sent to
328
     * adapter must be external protocol's ETH representative address.
329
     *
330
     * Returns notional amount of underlying tokens and wrapped tokens that were wrapped.
331
     */
332
    function _validateWrapAndUpdate(
333
        string calldata _integrationName,
334
        ISetToken _setToken,
335
        address _underlyingToken,
336
        address _wrappedToken,
337
        uint256 _underlyingUnits,
338
        bytes memory _wrapData,
339
        bool _usesEther
340
    )
341
        internal
342
        returns (uint256, uint256)
343
    {
344
        _validateInputs(_setToken, _underlyingToken, _underlyingUnits);
9✔
345

346
        // Snapshot pre wrap balances
347
        (
9✔
348
            uint256 preActionUnderlyingNotional,
349
            uint256 preActionWrapNotional
350
        ) = _snapshotTargetAssetsBalance(_setToken, _underlyingToken, _wrappedToken);
351

352
        uint256 notionalUnderlying = _setToken.totalSupply().getDefaultTotalNotional(_underlyingUnits);
9✔
353
        IWrapV2Adapter wrapAdapter = IWrapV2Adapter(getAndValidateAdapter(_integrationName));
9✔
354

355
        // Execute any pre-wrap actions depending on if using raw ETH or not
356
        if (_usesEther) {
9!
UNCOV
357
            _setToken.invokeUnwrapWETH(address(weth), notionalUnderlying);
×
358
        } else {
359
            _setToken.invokeApprove(_underlyingToken, wrapAdapter.getSpenderAddress(_underlyingToken, _wrappedToken), notionalUnderlying);
9✔
360
        }
361

362
        // Get function call data and invoke on SetToken
363
        _createWrapDataAndInvoke(
9✔
364
            _setToken,
365
            wrapAdapter,
366
            _usesEther ? wrapAdapter.ETH_TOKEN_ADDRESS() : _underlyingToken,
367
            _wrappedToken,
368
            notionalUnderlying,
369
            _wrapData
370
        );
371

372
        // Snapshot post wrap balances
373
        (
9✔
374
            uint256 postActionUnderlyingNotional,
375
            uint256 postActionWrapNotional
376
        ) = _snapshotTargetAssetsBalance(_setToken, _underlyingToken, _wrappedToken);
377

378
        _updatePosition(_setToken, _underlyingToken, preActionUnderlyingNotional, postActionUnderlyingNotional);
9✔
379
        _updatePosition(_setToken, _wrappedToken, preActionWrapNotional, postActionWrapNotional);
9✔
380

381
        return (
9✔
382
            preActionUnderlyingNotional.sub(postActionUnderlyingNotional),
383
            postActionWrapNotional.sub(preActionWrapNotional)
384
        );
385
    }
386

387
    /**
388
     * The WrapModule calculates the total notional wrap token to unwrap, then invokes the SetToken to call
389
     * unwrap by passing its calldata along. When raw ETH is being used (_usesEther = true) underlyingAddress
390
     * sent to adapter must be set to external protocol's ETH representative address and ETH returned from
391
     * external protocol is wrapped.
392
     *
393
     * Returns notional amount of underlying tokens and wrapped tokens unwrapped.
394
     */
395
    function _validateUnwrapAndUpdate(
396
        string calldata _integrationName,
397
        ISetToken _setToken,
398
        address _underlyingToken,
399
        address _wrappedToken,
400
        uint256 _wrappedTokenUnits,
401
        bytes memory _unwrapData,
402
        bool _usesEther
403
    )
404
        internal
405
        returns (uint256, uint256)
406
    {
407
        _validateInputs(_setToken, _wrappedToken, _wrappedTokenUnits);
5✔
408

409
        (
5✔
410
            uint256 preActionUnderlyingNotional,
411
            uint256 preActionWrapNotional
412
        ) = _snapshotTargetAssetsBalance(_setToken, _underlyingToken, _wrappedToken);
413

414
        uint256 notionalWrappedToken = _setToken.totalSupply().getDefaultTotalNotional(_wrappedTokenUnits);
5✔
415
        IWrapV2Adapter wrapAdapter = IWrapV2Adapter(getAndValidateAdapter(_integrationName));
5✔
416

417
        // Max approve wrapped token for spending in case protocols require approvals to transfer wrapped tokens
418
        _setToken.invokeApprove(_wrappedToken, wrapAdapter.getSpenderAddress(_underlyingToken, _wrappedToken), type(uint256).max);
5✔
419

420
        // Get function call data and invoke on SetToken
421
        _createUnwrapDataAndInvoke(
5✔
422
            _setToken,
423
            wrapAdapter,
424
            _usesEther ? wrapAdapter.ETH_TOKEN_ADDRESS() : _underlyingToken,
425
            _wrappedToken,
426
            notionalWrappedToken,
427
            _unwrapData
428
        );
429

430
        if (_usesEther) {
4!
UNCOV
431
            _setToken.invokeWrapWETH(address(weth), address(_setToken).balance);
×
432
        }
433

434
        // Revoke wrapped token max approval for spending 
435
        _setToken.invokeApprove(_wrappedToken, wrapAdapter.getSpenderAddress(_underlyingToken, _wrappedToken), 0);
4✔
436

437
        (
4✔
438
            uint256 postActionUnderlyingNotional,
439
            uint256 postActionWrapNotional
440
        ) = _snapshotTargetAssetsBalance(_setToken, _underlyingToken, _wrappedToken);
441

442
        _updatePosition(_setToken, _underlyingToken, preActionUnderlyingNotional, postActionUnderlyingNotional);
4✔
443
        _updatePosition(_setToken, _wrappedToken, preActionWrapNotional, postActionWrapNotional);
4✔
444

445
        return (
4✔
446
            postActionUnderlyingNotional.sub(preActionUnderlyingNotional),
447
            preActionWrapNotional.sub(postActionWrapNotional)
448
        );
449
    }
450

451
    /**
452
     * Create the calldata for wrap and then invoke the call on the SetToken.
453
     */
454
    function _createWrapDataAndInvoke(
455
        ISetToken _setToken,
456
        IWrapV2Adapter _wrapAdapter,
457
        address _underlyingToken,
458
        address _wrappedToken,
459
        uint256 _notionalUnderlying,
460
        bytes memory _wrapData
461
    ) internal {
462
        (
9✔
463
            address callTarget,
464
            uint256 callValue,
465
            bytes memory callByteData
466
        ) = _wrapAdapter.getWrapCallData(
467
            _underlyingToken,
468
            _wrappedToken,
469
            _notionalUnderlying,
470
            address(_setToken),
471
            _wrapData
472
        );
473

474
        _setToken.invoke(callTarget, callValue, callByteData);
9✔
475
    }
476

477
    /**
478
     * Create the calldata for unwrap and then invoke the call on the SetToken.
479
     */
480
    function _createUnwrapDataAndInvoke(
481
        ISetToken _setToken,
482
        IWrapV2Adapter _wrapAdapter,
483
        address _underlyingToken,
484
        address _wrappedToken,
485
        uint256 _notionalUnderlying,
486
        bytes memory _unwrapData
487
    ) internal {
488
        (
5✔
489
            address callTarget,
490
            uint256 callValue,
491
            bytes memory callByteData
492
        ) = _wrapAdapter.getUnwrapCallData(
493
            _underlyingToken,
494
            _wrappedToken,
495
            _notionalUnderlying,
496
            address(_setToken),
497
            _unwrapData
498
        );
499

500
        _setToken.invoke(callTarget, callValue, callByteData);
4✔
501
    }
502

503
    /**
504
     * After a wrap/unwrap operation, check the underlying and wrap token quantities and recalculate
505
     * the units ((total tokens - airdrop)/ total supply). Then update the position on the SetToken.
506
     */
507
    function _updatePosition(
508
        ISetToken _setToken,
509
        address _token,
510
        uint256 _preActionTokenBalance,
511
        uint256 _postActionTokenBalance
512
    ) internal {
513
        uint256 newUnit = _setToken.totalSupply().calculateDefaultEditPositionUnit(
26✔
514
            _preActionTokenBalance,
515
            _postActionTokenBalance,
516
            _setToken.getDefaultPositionRealUnit(_token).toUint256()
517
        );
518

519
        _setToken.editDefaultPosition(_token, newUnit);
26✔
520
    }
521

522
    /**
523
     * Take snapshot of SetToken's balance of underlying and wrapped tokens.
524
     */
525
    function _snapshotTargetAssetsBalance(
526
        ISetToken _setToken,
527
        address _underlyingToken,
528
        address _wrappedToken
529
    ) internal view returns(uint256, uint256) {
530
        uint256 underlyingTokenBalance = IERC20(_underlyingToken).balanceOf(address(_setToken));
27✔
531
        uint256 wrapTokenBalance = IERC20(_wrappedToken).balanceOf(address(_setToken));
27✔
532

533
        return (
27✔
534
            underlyingTokenBalance,
535
            wrapTokenBalance
536
        );
537
    }
538
}
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