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

IndexCoop / index-coop-smart-contracts / d679dfe2-8702-40e6-85e7-5777faaff1d4

pending completion
d679dfe2-8702-40e6-85e7-5777faaff1d4

push

circleci

Ramy Melo
test: add Awaited utility type defnition.

807 of 1104 branches covered (73.1%)

Branch coverage included in aggregate %.

2267 of 2851 relevant lines covered (79.52%)

68.76 hits per line

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

0.0
/contracts/exchangeIssuance/FlashMint4626.sol
1
/*
2
    Copyright 2022 Index Cooperative
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
    http://www.apache.org/licenses/LICENSE-2.0
8
    Unless required by applicable law or agreed to in writing, software
9
    distributed under the License is distributed on an "AS IS" BASIS,
10
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
    See the License for the specific language governing permissions and
12
    limitations under the License.
13

14
    SPDX-License-Identifier: Apache License, Version 2.0
15
*/
16

17
pragma solidity 0.6.10;
18
pragma experimental ABIEncoderV2;
19

20
import { Address} from "@openzeppelin/contracts/utils/Address.sol";
21
import { IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
22
import { SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
23
import { SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
24
import { ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
25

26
import { IController } from "../interfaces/IController.sol";
27
import { IDebtIssuanceModule} from "../interfaces/IDebtIssuanceModule.sol";
28
import { ISetToken} from "../interfaces/ISetToken.sol";
29
import { IWETH} from "../interfaces/IWETH.sol";
30
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
31
import { DEXAdapter } from "./DEXAdapter.sol";
32

33
abstract contract IERC4626 is IERC20 {
34
    function asset() external view virtual returns (address assetTokenAddress);
35
    function totalAssets() external view virtual returns (uint256 totalManagedAssets);
36
    function convertToShares(uint256 assets) external view virtual returns (uint256 shares);
37
    function convertToAssets(uint256 shares) external view virtual returns (uint256 assets);
38
    function maxDeposit(address receiver) external view virtual returns (uint256 maxAssets);
39
    function previewDeposit(uint256 assets) external view virtual returns (uint256 shares);
40
    function deposit(uint256 assets, address receiver) external virtual returns (uint256 shares);
41
    function maxMint(address receiver) external view virtual returns (uint256 maxShares);
42
    function previewMint(uint256 shares) external view virtual returns (uint256 assets);
43
    function mint(uint256 shares, address receiver) external virtual returns (uint256 assets);
44
    function maxWithdraw(address owner) external view virtual returns (uint256 maxAssets);
45
    function previewWithdraw(uint256 assets) external view virtual returns (uint256 shares);
46
    function withdraw(uint256 assets, address receiver, address owner) external virtual returns (uint256 shares);
47
    function maxRedeem(address owner) external view virtual returns (uint256 maxShares);
48
    function previewRedeem(uint256 shares) external view virtual returns (uint256 assets);
49
    function redeem(uint256 shares, address receiver, address owner) external virtual returns (uint256 assets);
50
    event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
51
    event Withdraw(address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares);
52
}
53

54
abstract contract  SupplyVault is IERC4626 {
55
    function morpho() external view virtual returns (address);
56
    function morphoToken() external view virtual returns (address);
57
    function poolToken() external view virtual returns (address);
58
}
59

60
interface Morpho {
61
    function updateIndexes(address _poolToken) external;
62
}
63

64
/**
65
 * @title FlashMint4626
66
 *
67
 * Flash issues SetTokens whose components contain ERC4626 shares.
68
 *
69
 * Compatible with:
70
 * IssuanceModules: DebtIssuanceModule, DebtIssuanceModuleV2
71
 *
72
 * Supports flash minting for sets that contain both unwrapped and wrapped components.
73
 * Does not support debt positions on Set token.
74
 * Wrapping / Unwrapping is skipped for a component if ComponentSwapData[component_index].underlyingERC20 address == set component address
75
 *
76
 * If the set contains both the wrapped and unwrapped version of a token (e.g. DAI and cDAI)
77
 * ->  two separate component data points have to be supplied in _swapData and _wrapData
78
 * e.g. for issue
79
 * Set components at index 0 = DAI; then -> ComponentSwapData[0].underlyingERC20 = DAI; (no wrapping will happen)
80
 * Set components at index 1 = cDAI; then -> ComponentSwapData[1].underlyingERC20 = DAI; (wrapping will happen)
81
 */
82
contract FlashMint4626 is Ownable, ReentrancyGuard {
83
  using DEXAdapter for DEXAdapter.Addresses;
84
  using Address for address payable;
85
  using Address for address;
86
  using SafeMath for uint256;
87
  using SafeERC20 for IERC20;
88
  using SafeERC20 for ISetToken;
89

90
  /* ============ Structs ============ */
91

92
  struct ComponentSwapData {
93
    // swap data for DEX operation: fees, path, etc. see DEXAdapter.SwapData
94
    DEXAdapter.SwapData dexData;
95
  }
96

97
  struct BalanceSnapshot{
98
    uint256 balanceBefore;
99
    uint256 balanceAfter;
100
    uint256 balanceObtained;
101
    uint256 balanceRequired;
102
  }
103

104
  /* ============ Constants ============= */
105

106
  uint256 private constant MAX_UINT256 = type(uint256).max;
107

108
  /* ============ Immutables ============ */
109

110
  IController public immutable setController;
111
  IDebtIssuanceModule public immutable issuanceModule; // interface is compatible with DebtIssuanceModuleV2
112

113
  /* ============ State Variables ============ */
114

115
  DEXAdapter.Addresses public dexAdapter;
116

117
  /* ============ Events ============ */
118

119
  event FlashMint(
120
    address indexed _recipient, // The recipient address of the minted Set token
121
    ISetToken indexed _setToken, // The minted Set token
122
    IERC20 indexed _inputToken, // The address of the input asset(ERC20/ETH) used to mint the Set tokens
123
    uint256 _amountInputToken, // The amount of input tokens used for minting
124
    uint256 _amountSetIssued // The amount of Set tokens received by the recipient
125
  );
126

127
  event FlashRedeem(
128
    address indexed _recipient, // The recipient address which redeemed the Set token
129
    ISetToken indexed _setToken, // The redeemed Set token
130
    IERC20 indexed _outputToken, // The address of output asset(ERC20/ETH) received by the recipient
131
    uint256 _amountSetRedeemed, // The amount of Set token redeemed for output tokens
132
    uint256 _amountOutputToken // The amount of output tokens received by the recipient
133
  );
134

135
  /* ============ Modifiers ============ */
136

137
  /**
138
   * checks that _setToken is a valid listed set token on the setController
139
   *
140
   * @param _setToken       set token to check
141
   */
142
  modifier isSetToken(ISetToken _setToken) {
143
    require(setController.isSet(address(_setToken)), "FlashMint: INVALID_SET");
×
144
    _;
145
  }
146

147
  /**
148
   * checks that _inputToken is the first adress in _path and _outputToken is the last address in _path
149
   *
150
   * @param _path                      Array of addresses for a DEX swap path
151
   * @param _inputToken                input token of DEX swap
152
   * @param _outputToken               output token of DEX swap
153
   */
154
  modifier isValidPath(
155
    address[] memory _path,
156
    address _inputToken,
157
    address _outputToken
158
  ) {
159
    if (_inputToken != _outputToken) {
×
160
      require(
×
161
        _path[0] == _inputToken ||
162
          (_inputToken == dexAdapter.weth && _path[0] == DEXAdapter.ETH_ADDRESS),
163
        "FlashMint: INPUT_TOKEN_NOT_IN_PATH"
164
      );
165
      require(
×
166
        _path[_path.length - 1] == _outputToken ||
167
          (_outputToken == dexAdapter.weth && _path[_path.length - 1] == DEXAdapter.ETH_ADDRESS),
168
        "FlashMint: OUTPUT_TOKEN_NOT_IN_PATH"
169
      );
170
    }
171
    _;
172
  }
173

174
  /* ========== Constructor ========== */
175

176
  /**
177
   * Constructor initializes various addresses
178
   *
179
   * @param _dexAddresses              Address of quickRouter, sushiRouter, uniV3Router, uniV3Router, curveAddressProvider, curveCalculator and weth.
180
   * @param _setController             Set token controller used to verify a given token is a set
181
   * @param _issuanceModule            IDebtIssuanceModule used to issue and redeem tokens
182
   */
183
  constructor(
184
    DEXAdapter.Addresses memory _dexAddresses,
185
    IController _setController,
186
    IDebtIssuanceModule _issuanceModule
187
  ) public {
188
    dexAdapter = _dexAddresses;
×
189
    setController = _setController;
×
190
    issuanceModule = _issuanceModule;
×
191
  }
192

193
  /* ============ External Functions ============ */
194
  receive() external payable {
195
    // required for weth.withdraw() to work properly
196
    require(msg.sender == dexAdapter.weth, "FlashMint: DEPOSITS_NOT_ALLOWED");
×
197
  }
198

199
  /**
200
  * Withdraw slippage to selected address
201
  *
202
  * @param _tokens    Addresses of tokens to withdraw, specifiy ETH_ADDRESS to withdraw ETH
203
  * @param _to        Address to send the tokens to
204
  */
205
  function withdrawTokens(IERC20[] calldata _tokens, address payable _to) external onlyOwner payable {
206
      for(uint256 i = 0; i < _tokens.length; i++) {
×
207
          if(address(_tokens[i]) == DEXAdapter.ETH_ADDRESS){
×
208
              _to.sendValue(address(this).balance);
×
209
          }
210
          else{
211
              _tokens[i].safeTransfer(_to, _tokens[i].balanceOf(address(this)));
×
212
          }
213
      }
214
  }
215

216
  /**
217
   * Runs all the necessary approval functions required before issuing
218
   * or redeeming a SetToken. This function need to be called only once before the first time
219
   * this smart contract is used on any particular SetToken.
220
   *
221
   * @param _setToken          Address of the SetToken being initialized
222
   */
223
  function approveSetToken(ISetToken _setToken) external isSetToken(_setToken) {
224
    address[] memory _components = _setToken.getComponents();
×
225
    for (uint256 i = 0; i < _components.length; ++i) {
×
226
      DEXAdapter._safeApprove(IERC20(_components[i]), address(issuanceModule), MAX_UINT256);
×
227
    }
228
  }
229

230
  /**
231
   * Issues an exact amount of SetTokens for given amount of input ERC20 tokens.
232
   * The excess amount of input tokens is returned.
233
   * The sender must have approved the _maxAmountInputToken for input token to this contract.
234
   *
235
   * @param _setToken              Address of the SetToken to be issued
236
   * @param _inputToken            Address of the ERC20 input token
237
   * @param _amountSetToken        Amount of SetTokens to issue
238
   * @param _maxAmountInputToken   Maximum amount of input tokens to be used
239
   * @param _swapData              ComponentSwapData (inputToken -> component) for each set component in the same order
240
   *
241
   * @return totalInputTokenSold   Amount of input tokens spent for issuance
242
   */
243
  function issueExactSetFromERC20(
244
    ISetToken _setToken,
245
    IERC20 _inputToken,
246
    uint256 _amountSetToken,
247
    uint256 _maxAmountInputToken,
248
    ComponentSwapData[] calldata _swapData
249
  ) external nonReentrant returns (uint256) {
250
    return
×
251
      _issueExactSet(
252
        _setToken,
253
        _inputToken,
254
        _amountSetToken,
255
        _maxAmountInputToken,
256
        _swapData,
257
        false
258
      );
259
  }
260

261
  /**
262
   * Issues an exact amount of SetTokens for given amount of ETH. Max amount of ETH used is the transferred amount in msg.value.
263
   * The excess amount of input ETH is returned.
264
   *
265
   * @param _setToken              Address of the SetToken to be issued
266
   * @param _amountSetToken        Amount of SetTokens to issue
267
   * @param _swapData              ComponentSwapData (WETH -> component) for each set component in the same order
268
   *
269
   * @return totalETHSold          Amount of ETH spent for issuance
270
   */
271
  function issueExactSetFromETH(
272
    ISetToken _setToken,
273
    uint256 _amountSetToken,
274
    ComponentSwapData[] calldata _swapData
275
  ) external payable nonReentrant returns (uint256) {
276
    // input token for all operations is WETH (any sent in ETH will be wrapped)
277
    IERC20 inputToken = IERC20(dexAdapter.weth);
×
278
    uint256 maxAmountInputToken = msg.value; // = deposited amount ETH -> WETH
×
279

280
    return
×
281
      _issueExactSet(
282
        _setToken,
283
        inputToken,
284
        _amountSetToken,
285
        maxAmountInputToken,
286
        _swapData,
287
        true
288
      );
289
  }
290

291
  /**
292
   * Redeems an exact amount of SetTokens for an ERC20 token.
293
   * The sender must have approved the _amountSetToken of _setToken to this contract.
294
   *
295
   * @param _setToken              Address of the SetToken to be redeemed
296
   * @param _outputToken           Address of the ERC20 output token
297
   * @param _amountSetToken        Amount of SetTokens to redeem
298
   * @param _minOutputReceive      Minimum amount of output tokens to be received
299
   * @param _swapData              ComponentSwapData (underlyingERC20 -> output token) for each _redeemComponents in the same order
300
   *
301
   * @return outputAmount          Amount of output tokens sent to the caller
302
   */
303
  function redeemExactSetForERC20(
304
    ISetToken _setToken,
305
    IERC20 _outputToken,
306
    uint256 _amountSetToken,
307
    uint256 _minOutputReceive,
308
    ComponentSwapData[] calldata _swapData
309
  ) external nonReentrant returns (uint256) {
310
    return
×
311
      _redeemExactSet(
312
        _setToken,
313
        _outputToken,
314
        _amountSetToken,
315
        _minOutputReceive,
316
        _swapData,
317
        false
318
      );
319
  }
320

321
  /**
322
   * Redeems an exact amount of SetTokens for ETH.
323
   * The sender must have approved the _amountSetToken of _setToken to this contract.
324
   *
325
   * @param _setToken              Address of the SetToken to be redemeed
326
   * @param _amountSetToken        Amount of SetTokens to redeem
327
   * @param _minOutputReceive      Minimum amount of output tokens to be received
328
   * @param _swapData              ComponentSwapData (underlyingERC20 -> output token) for each _redeemComponents in the same order
329
   *
330
   * @return outputAmount          Amount of ETH sent to the caller
331
   */
332
  function redeemExactSetForETH(
333
    ISetToken _setToken,
334
    uint256 _amountSetToken,
335
    uint256 _minOutputReceive,
336
    ComponentSwapData[] calldata _swapData
337
    ) external nonReentrant returns (uint256) {
338
    // output token for all operations is WETH (it will be unwrapped in the end and sent as ETH)
339
    IERC20 outputToken = IERC20(dexAdapter.weth);
×
340

341
    return
×
342
      _redeemExactSet(
343
        _setToken,
344
        outputToken,
345
        _amountSetToken,
346
        _minOutputReceive,
347
        _swapData,
348
        true
349
      );
350
  }
351

352
  /**
353
  * ESTIMATES the amount of output ERC20 tokens required to issue an exact amount of SetTokens based on component swap data.
354
  * Simulates swapping input to all components in swap data. 
355
  * This function is not marked view, but should be static called off-chain.
356
  *
357
  * @param _setToken              Address of the SetToken being issued
358
  * @param _inputToken            Address of the input token used to pay for issuance
359
  * @param _setAmount             Amount of SetTokens to issue
360
  * @param _swapData              ComponentSwapData (inputToken -> component) for each set component in the same order
361
  *
362
  * @return amountInputNeeded     Amount of tokens needed to issue specified amount of SetTokens
363
  */
364
  function getIssueExactSet(
365
      ISetToken _setToken,
366
      address _inputToken,
367
      uint256 _setAmount,
368
      ComponentSwapData[] calldata _swapData
369
  )
370
      external
371
      returns(uint256 amountInputNeeded)
372
  {
373
      require(_setAmount > 0 || _inputToken != address(0), "FlashMint: INVALID_INPUTS");
×
374

375
      (address[] memory requiredComponents, uint256[] memory requiredAmounts) = _validateIssueParams(_setToken, _setAmount, MAX_UINT256, _swapData);
×
376

377
      for (uint256 i = 0; i < requiredComponents.length; ++i) {
×
378

379
        SupplyVault vault = SupplyVault(requiredComponents[i]);
×
380
        IERC20 underlyingAsset = IERC20(vault.asset());
×
381
        Morpho morpho;
×
382
        try vault.morpho() returns (address _morpho){
×
383
          morpho = Morpho(_morpho);
×
384
          morpho.updateIndexes(vault.poolToken());
×
385
        }catch {
386
         morpho = Morpho(address(0));
×
387
      }
388

389
        uint256 requiredUnderlyingAmount = vault.previewMint(requiredAmounts[i]);
×
390
        // if the input token is the swap target token, no swapping is needed
391
        if (_inputToken == address(underlyingAsset)) {
×
392
          amountInputNeeded = amountInputNeeded.add(requiredUnderlyingAmount);
×
393
          continue;
394
        }
395
        
396
        // add required input amount to swap to desired buyUnderlyingAmount
397
        uint256 amountInNeeded = dexAdapter.getAmountIn(_swapData[i].dexData, requiredUnderlyingAmount);
×
398
        amountInputNeeded = amountInputNeeded.add(amountInNeeded);
×
399
      }
400
  }
401

402
  /**
403
  * ESTIMATES the amount of output ERC20 tokens received when redeeming an exact amount of SetTokens based on component swap data.
404
  * Simulates swapping all components to the output token in swap data. 
405
  * This function is not marked view, but should be static called off-chain.
406
  *
407
  * Note that _swapData.buyUnderlyingAmount has to be specified here with the expected underlying amount received after unwrapping
408
  *
409
  * @param _setToken              Address of the SetToken being redeemed
410
  * @param _outputToken           Address of the output token expected to teceive (if redeeming to ETH, outputToken here is WETH)
411
  * @param _setAmount             Amount of SetTokens to redeem
412
  * @param _swapData              ComponentSwapData (component -> outputToken) for each set component in the same order
413
  *
414
  * @return amountOutputReceived     Amount of output tokens received
415
  */
416
  function getRedeemExactSet(
417
      ISetToken _setToken,
418
      address _outputToken,
419
      uint256 _setAmount,
420
      ComponentSwapData[] calldata _swapData
421
  )
422
      external
423
      returns(uint256 amountOutputReceived)
424
{
425
      require(_setAmount > 0 || _outputToken != address(0), "FlashMint: INVALID_INPUTS");
×
426

427
      (address[] memory redeemedComponents, uint256[] memory redeemedAmounts) = _validateRedeemParams(_setToken,IERC20(_outputToken), _setAmount, _swapData);
×
428

429
      for (uint256 i = 0; i < redeemedComponents.length; ++i) {
×
430

431
        SupplyVault vault = SupplyVault(redeemedComponents[i]);
×
432
        IERC20 underlyingAsset = IERC20(vault.asset());
×
433
        Morpho morpho;
×
434
        try vault.morpho() returns (address _morpho){
×
435
          morpho = Morpho(_morpho);
×
436
          morpho.updateIndexes(vault.poolToken());
×
437
        }catch {
438
         morpho = Morpho(address(0));
×
439
      }
440

441
        uint256 redeemedUnderlyingAmount = vault.previewRedeem(redeemedAmounts[i]);
×
442
        // if the input token is the swap target token, no swapping is needed
443
        if (_outputToken == address(underlyingAsset)) {
×
444
          amountOutputReceived = amountOutputReceived.add(redeemedUnderlyingAmount);
×
445
          continue;
446
        }
447
        
448
        // add received output amount from swap
449
        uint256 swapAmountOut = dexAdapter.getAmountOut(_swapData[i].dexData, redeemedUnderlyingAmount);
×
450
        amountOutputReceived = amountOutputReceived.add(swapAmountOut);
×
451
      }
452

453
  }
454

455
  /* ============ Internal Functions ============ */
456

457
  /**
458
   * Issues an exact amount of SetTokens for given amount of input. Excess amounts are returned
459
   *
460
   * @param _setToken                   Address of the SetToken to be issued
461
   * @param _inputToken                 Address of the ERC20 input token
462
   * @param _amountSetToken             Amount of SetTokens to issue
463
   * @param _maxAmountInputToken        Maximum amount of input tokens to be used
464
   * @param _swapData                   ComponentSwapData (input token -> underlyingERC20) for each _requiredComponents in the same order
465
   * @param _issueFromETH               boolean flag to identify if issuing from ETH or from ERC20 tokens
466
   *
467
   * @return totalInputSold             Amount of input token spent for issuance
468
   */
469
  function _issueExactSet(
470
    ISetToken _setToken,
471
    IERC20 _inputToken,
472
    uint256 _amountSetToken,
473
    uint256 _maxAmountInputToken,
474
    ComponentSwapData[] calldata _swapData,
475
    bool _issueFromETH
476
  ) internal returns (uint256) {
477
    // 1. validate input params, get required components with amounts and snapshot input token balance before
478
    require(address(_inputToken) != address(0), "FlashMint: INVALID_INPUTS");
×
479
    uint256 inputTokenBalanceBefore = IERC20(_inputToken).balanceOf(address(this));
×
480
    
481
    // Prevent stack too deep
482
    {
483
      (
×
484
        address[] memory requiredComponents,
485
        uint256[] memory requiredAmounts
486
      ) = _validateIssueParams(
487
          _setToken,
488
          _amountSetToken,
489
          _maxAmountInputToken,
490
          _swapData);
491

492
      // 2. transfer input to this contract
493
      if (_issueFromETH) {
×
494
        // wrap sent in ETH to WETH for all operations
495
        IWETH(dexAdapter.weth).deposit{value: msg.value}();
×
496
      } else {
497
        _inputToken.safeTransferFrom(msg.sender, address(this), _maxAmountInputToken);
×
498
      }
499

500
      // 3. swap input token to all components, then wrap them if needed
501
      _swapAndWrapComponents(
×
502
        _inputToken,
503
        _maxAmountInputToken,
504
        _swapData,
505
        requiredComponents,
506
        requiredAmounts
507
      );
508
    }
509

510
    // 4. issue set tokens
511
    issuanceModule.issue(_setToken, _amountSetToken, msg.sender);
×
512

513
    // 5. ensure not too much of input token was spent (covers case where initial input token balance was > 0)
514
    uint256 spentInputTokenAmount = _validateMaxAmountInputToken(
×
515
      _inputToken,
516
      inputTokenBalanceBefore,
517
      _maxAmountInputToken
518
    );
519

520
    // 6. return excess inputs
521
    _returnExcessInput(_inputToken, _maxAmountInputToken, spentInputTokenAmount, _issueFromETH);
×
522

523
    // 7. emit event and return amount spent
524
    emit FlashMint(
×
525
      msg.sender,
526
      _setToken,
527
      _issueFromETH ? IERC20(DEXAdapter.ETH_ADDRESS) : _inputToken,
528
      spentInputTokenAmount,
529
      _amountSetToken
530
    );
531

532
    return spentInputTokenAmount;
×
533
  }
534

535
  /**
536
   * Redeems an exact amount of SetTokens.
537
   *
538
   * @param _setToken              Address of the SetToken to be issued
539
   * @param _outputToken           Address of the ERC20 output token
540
   * @param _amountSetToken        Amount of SetTokens to redeem
541
   * @param _minOutputReceive      Minimum amount of output tokens to be received
542
   * @param _swapData              ComponentSwapData (underlyingERC20 -> output token) for each _redeemComponents in the same order
543
   * @param _redeemToETH           boolean flag to identify if redeeming to ETH or to ERC20 tokens
544
   *
545
   * @return outputAmount          Amount of output received
546
   */
547
  function _redeemExactSet(
548
    ISetToken _setToken,
549
    IERC20 _outputToken,
550
    uint256 _amountSetToken,
551
    uint256 _minOutputReceive,
552
    ComponentSwapData[] calldata _swapData,
553
    bool _redeemToETH
554
  ) internal returns (uint256) {
555
    // 1. validate input params and get required components
556
    (address[] memory redeemComponents, uint256[] memory redeemAmounts) = _validateRedeemParams(
×
557
      _setToken,
558
      _outputToken,
559
      _amountSetToken,
560
      _swapData);
561

562
    // 2. transfer set tokens to be redeemed to this
563
    _setToken.safeTransferFrom(msg.sender, address(this), _amountSetToken);
×
564

565
    // 3. redeem set tokens
566
    issuanceModule.redeem(_setToken, _amountSetToken, address(this));
×
567

568
    // 4. unwrap all components if needed and swap them to output token
569
    uint256 totalOutputTokenObtained = _unwrapAndSwapComponents(
×
570
      _outputToken,
571
      _swapData,
572
      redeemComponents,
573
      redeemAmounts
574
    );
575

576
    // 5. ensure expected minimum output amount has been obtained
577
    require(totalOutputTokenObtained >= _minOutputReceive, "FlashMint: INSUFFICIENT_OUTPUT_AMOUNT");
×
578

579
    // 6. transfer obtained output tokens to msg.sender
580
    _sendObtainedOutputToSender(_outputToken, totalOutputTokenObtained, _redeemToETH);
×
581

582
    // 7. emit event and return amount obtained
583
    emit FlashRedeem(
×
584
      msg.sender,
585
      _setToken,
586
      _redeemToETH ? IERC20(DEXAdapter.ETH_ADDRESS) : _outputToken,
587
      _amountSetToken,
588
      totalOutputTokenObtained
589
    );
590

591
    return totalOutputTokenObtained;
×
592
  }
593

594
  /**
595
   * Validates input params for _issueExactSet operations
596
   *
597
   * @param _setToken                   Address of the SetToken to be redeemed
598
   * @param _amountSetToken             Amount of SetTokens to issue
599
   * @param _maxAmountToken             Maximum amount of input token to spend
600
   * @param _swapData                   ComponentSwapData (input token -> underlyingERC20) for each _requiredComponents in the same order
601
   *
602
   * @return requiredComponents         Array of required issuance components gotten from IDebtIssuanceModule.getRequiredComponentIssuanceUnits()
603
   * @return requiredAmounts            Array of required issuance component amounts gotten from IDebtIssuanceModule.getRequiredComponentIssuanceUnits()
604
   */
605
  function _validateIssueParams(
606
    ISetToken _setToken,
607
    uint256 _amountSetToken,
608
    uint256 _maxAmountToken,
609
    ComponentSwapData[] calldata _swapData
610
  )
611
    internal
612
    view
613
    isSetToken(_setToken)
614
    returns (address[] memory requiredComponents, uint256[] memory requiredAmounts)
615
  {
616
    require(_amountSetToken > 0 && _maxAmountToken > 0, "FlashMint: INVALID_INPUTS");
×
617

618
    (requiredComponents, requiredAmounts, ) = issuanceModule.getRequiredComponentIssuanceUnits(
×
619
      _setToken,
620
      _amountSetToken
621
    );
622

623
    require(
×
624
      _swapData.length == requiredComponents.length,
625
      "FlashMint: MISMATCH_INPUT_ARRAYS"
626
    );
627
  }
628

629
  /**
630
   * Validates input params for _redeemExactSet operations
631
   *
632
   * @param _setToken                  Address of the SetToken to be redeemed
633
   * @param _outputToken               Output token that will be redeemed to
634
   * @param _amountSetToken            Amount of SetTokens to redeem
635
   * @param _swapData                  ComponentSwapData (underlyingERC20 -> output token) for each _redeemComponents in the same order
636
   *
637
   * @return redeemComponents          Array of redemption components gotten from IDebtIssuanceModule.getRequiredComponentRedemptionUnits()
638
   * @return redeemAmounts             Array of redemption component amounts gotten from IDebtIssuanceModule.getRequiredComponentRedemptionUnits()
639
   */
640
  function _validateRedeemParams(
641
    ISetToken _setToken,
642
    IERC20 _outputToken,
643
    uint256 _amountSetToken,
644
    ComponentSwapData[] calldata _swapData
645
  )
646
    internal
647
    view
648
    isSetToken(_setToken)
649
    returns (address[] memory redeemComponents, uint256[] memory redeemAmounts)
650
  {
651
    require(
×
652
      _amountSetToken > 0 && address(_outputToken) != address(0),
653
      "FlashMint: INVALID_INPUTS"
654
    );
655

656
    (redeemComponents, redeemAmounts, ) = issuanceModule.getRequiredComponentRedemptionUnits(
×
657
      _setToken,
658
      _amountSetToken
659
    );
660

661
    require(
×
662
     _swapData.length == redeemComponents.length,
663
      "FlashMint: MISMATCH_INPUT_ARRAYS"
664
    );
665
  }
666

667
  /**
668
   * Swaps and then wraps each _requiredComponents sequentially based on _swapData and _wrapData
669
   *
670
   * @param _inputToken                 Input token that will be sold
671
   * @param _maxAmountInputToken        Maximum amount of input token that can be spent
672
   * @param _swapData                   ComponentSwapData (input token -> underlyingERC20) for each _requiredComponents in the same order
673
   * @param _requiredComponents         Issuance components gotten from IDebtIssuanceModule.getRequiredComponentIssuanceUnits()
674
   * @param _requiredAmounts            Issuance units gotten from IDebtIssuanceModule.getRequiredComponentIssuanceUnits()
675
   */
676
  function _swapAndWrapComponents(
677
    IERC20 _inputToken,
678
    uint256 _maxAmountInputToken,
679
    ComponentSwapData[] calldata _swapData,
680
    address[] memory _requiredComponents,
681
    uint256[] memory _requiredAmounts
682
  ) internal {
683
    // if the required set components contain the input token, we have to make sure that the required amount
684
    // for issuance is actually still left over at the end of swapping and wrapping
685
    uint256 requiredLeftOverInputTokenAmount = 0;
×
686

687
    // for each component in the swapData / wrapData / requiredComponents array:
688
    // 1. swap from input token to unwrapped component (exact to buyUnderlyingAmount)
689
    // 2. wrap from unwrapped component to wrapped component (unless unwrapped component == wrapped component)
690
    // 3. ensure amount in contract covers required amount for issuance
691
    for (uint256 i = 0; i < _requiredComponents.length; ++i) {
×
692
      BalanceSnapshot memory componentBalance; 
×
693
      componentBalance.balanceRequired = _requiredAmounts[i];
×
694

695
      if (address(_inputToken) == _requiredComponents[i]) {
×
696
        requiredLeftOverInputTokenAmount = componentBalance.balanceRequired;
×
697
        continue;
698
      }
699

700
      // snapshot balance of required component before swap and wrap operations
701
      componentBalance.balanceBefore = IERC20(_requiredComponents[i]).balanceOf(address(this));
×
702
      // we now assume that the required component is a vault and the required amount are shares
703
      SupplyVault vault = SupplyVault(_requiredComponents[i]);
×
704
      IERC20 underlyingAsset = IERC20(vault.asset());
×
705

706
      Morpho morpho;
×
707
      try vault.morpho() returns (address _morpho){
×
708
        morpho = Morpho(_morpho);
×
709
        morpho.updateIndexes(vault.poolToken());
×
710
      }catch {
711
        morpho = Morpho(address(0));
×
712
      }
713

714
      uint256 requiredUnderlying = vault.previewMint(componentBalance.balanceRequired);
×
715
      // swap input token to underlying token
716
      _swapToExact(
×
717
        _inputToken, // input
718
        underlyingAsset, // output
719
        requiredUnderlying, // buy amount
720
        _maxAmountInputToken, // maximum spend amount: _maxAmountInputToken as transferred by the flash mint caller
721
        _swapData[i].dexData // dex path fees data etc.
722
      );
723

724
      // transform underlying token into wrapped version (unless it's the same)
725
      DEXAdapter._safeApprove(underlyingAsset, address(vault), requiredUnderlying);
×
726
      vault.mint(componentBalance.balanceRequired, address(this));
×
727

728
      // ensure obtained component amount covers required component amount for issuance
729
      // this is not already covered through _swapToExact because it does not take wrapping into consideration
730
      componentBalance.balanceAfter = IERC20(_requiredComponents[i]).balanceOf(address(this));
×
731
      componentBalance.balanceObtained = componentBalance.balanceAfter.sub(componentBalance.balanceBefore);
×
732
      require(componentBalance.balanceObtained >= componentBalance.balanceRequired, "FlashMint: UNDERBOUGHT_COMPONENT");
×
733
    }
734

735
    // ensure left over input token amount covers issuance for component if input token is one of the Set components
736
    require(
×
737
      IERC20(_inputToken).balanceOf(address(this)) >= requiredLeftOverInputTokenAmount,
738
      "FlashMint: NOT_ENOUGH_INPUT"
739
    );
740
  }
741

742
  /**
743
   * Unwraps and then swaps each _redeemComponents sequentially based on _swapData and _unwrapData
744
   *
745
   * @param _outputToken                Output token that will be bought
746
   * @param _swapData                   ComponentSwapData (underlyingERC20 -> output token) for each _redeemComponents in the same order
747
   * @param _redeemComponents           redemption components gotten from IDebtIssuanceModule.getRequiredComponentRedemptionUnits()
748
   * @param _redeemAmounts              redemption units gotten from IDebtIssuanceModule.getRequiredComponentRedemptionUnits()
749
   *
750
   * @return totalOutputTokenObtained   total output token amount obtained
751
   */
752
  function _unwrapAndSwapComponents(
753
    IERC20 _outputToken,
754
    ComponentSwapData[] calldata _swapData,
755
    address[] memory _redeemComponents,
756
    uint256[] memory _redeemAmounts
757
  ) internal returns (uint256 totalOutputTokenObtained) {
758
    // for each component in the swapData / wrapData / redeemComponents array:
759
    // 1. unwrap from wrapped set component to unwrapped underlyingERC20 in swapData
760
    // 2. swap from underlyingERC20 token to output token (exact from obtained underlyingERC20 amount)
761

762
    for (uint256 i = 0; i < _redeemComponents.length; ++i) {
×
763
      // default redeemed amount is maximum possible amount that was redeemed for this component
764
      // this is recomputed if the redeemed amount is unwrapped to the actual unwrapped amount
765
      uint256 redeemedAmount = _redeemAmounts[i];
×
766

767
      // if the set component is the output token, no swapping or wrapping is needed
768
      if (address(_outputToken) == _redeemComponents[i]) {
×
769
        // add maximum possible amount that was redeemed for this component to totalOutputTokenObtained (=redeemedAmount)
770
        totalOutputTokenObtained = totalOutputTokenObtained.add(redeemedAmount);
×
771
        continue;
772
      }
773

774
      SupplyVault vault = SupplyVault(_redeemComponents[i]);
×
775
      IERC20 underlyingAsset = IERC20(vault.asset());
×
776
      redeemedAmount = vault.redeem(redeemedAmount, address(this), address(this));
×
777

778
      // swap redeemed token to output token unless it's the same token
779
      uint256 boughtOutputTokenAmount =  _outputToken != underlyingAsset ?_swapFromExact(
×
780
        underlyingAsset, // input
781
        _outputToken, // output
782
        redeemedAmount, // sell amount of input token
783
        _swapData[i].dexData // dex path fees data etc.
784
      ): redeemedAmount;
785

786
      totalOutputTokenObtained = totalOutputTokenObtained.add(boughtOutputTokenAmount);
×
787
    }
788
  }
789

790
  /**
791
   * Swaps _inputToken to exact _amount to _outputToken through _swapDexData
792
   *
793
   * @param _inputToken           Input token that will be sold
794
   * @param _outputToken          Output token that will be bought
795
   * @param _amount               Amount that will be bought
796
   * @param _maxAmountIn          Maximum aount of input token that can be spent
797
   * @param _swapDexData          DEXAdapter.SwapData with path, fees, etc. for inputToken -> outputToken swap
798
   *
799
   * @return Amount of spent _inputToken
800
   */
801
  function _swapToExact(
802
    IERC20 _inputToken,
803
    IERC20 _outputToken,
804
    uint256 _amount,
805
    uint256 _maxAmountIn,
806
    DEXAdapter.SwapData calldata _swapDexData
807
  )
808
    internal
809
    isValidPath(_swapDexData.path, address(_inputToken), address(_outputToken))
810
    returns (uint256)
811
  {
812
    // safe approves are done right in the dexAdapter library
813
    // swaps are skipped there too if inputToken == outputToken (depending on path)
814
    return dexAdapter.swapTokensForExactTokens(_amount, _maxAmountIn, _swapDexData);
×
815
  }
816

817
  /**
818
   * Swaps exact _amount of _inputToken to _outputToken through _swapDexData
819
   *
820
   * @param _inputToken           Input token that will be sold
821
   * @param _outputToken          Output token that will be bought
822
   * @param _amount               Amount that will be sold
823
   * @param _swapDexData          DEXAdapter.SwapData with path, fees, etc. for inputToken -> outputToken swap
824
   *
825
   * @return amount of received _outputToken
826
   */
827
  function _swapFromExact(
828
    IERC20 _inputToken,
829
    IERC20 _outputToken,
830
    uint256 _amount,
831
    DEXAdapter.SwapData calldata _swapDexData
832
  )
833
    internal
834
    isValidPath(_swapDexData.path, address(_inputToken), address(_outputToken))
835
    returns (uint256)
836
  {
837
    // safe approves are done right in the dexAdapter library
838
    return
×
839
      dexAdapter.swapExactTokensForTokens(
840
        _amount,
841
        // _minAmountOut is 0 here since we don't know what to check against because for wrapped components
842
        // we only have the required amounts for the wrapped component, but not for the underlying we swap to here
843
        // This is covered indirectly in later checks though
844
        // e.g. directly through the issue call (not enough _outputToken -> wrappedComponent -> issue will fail)
845
        0,
846
        _swapDexData
847
      );
848
  }
849

850
  /**
851
   * Validates that not more than the requested max amount of the input token has been spent
852
   *
853
   * @param _inputToken                 Address of the input token to return
854
   * @param _inputTokenBalanceBefore    input token balance before at the beginning of the operation
855
   * @param _maxAmountInputToken        maximum amount that could be spent
856

857
   * @return spentInputTokenAmount      actual spent amount of the input token
858
   */
859
  function _validateMaxAmountInputToken(
860
    IERC20 _inputToken,
861
    uint256 _inputTokenBalanceBefore,
862
    uint256 _maxAmountInputToken
863
  ) internal view returns (uint256 spentInputTokenAmount) {
864
    uint256 inputTokenBalanceAfter = _inputToken.balanceOf(address(this));
×
865

866
    // _maxAmountInputToken amount has been transferred to this contract after _inputTokenBalanceBefore snapshot
867
    spentInputTokenAmount = _inputTokenBalanceBefore.add(_maxAmountInputToken).sub(
×
868
      inputTokenBalanceAfter
869
    );
870

871
    require(spentInputTokenAmount <= _maxAmountInputToken, "FlashMint: OVERSPENT_INPUT_TOKEN");
×
872
  }
873

874
  /**
875
   * Returns excess input token
876
   *
877
   * @param _inputToken         Address of the input token to return
878
   * @param _receivedAmount     Amount received by the caller
879
   * @param _spentAmount        Amount spent for issuance
880
   * @param _returnETH          Boolean flag to identify if ETH should be returned or the input token
881
   */
882
  function _returnExcessInput(
883
    IERC20 _inputToken,
884
    uint256 _receivedAmount,
885
    uint256 _spentAmount,
886
    bool _returnETH
887
  ) internal {
888
    uint256 amountTokenReturn = _receivedAmount.sub(_spentAmount);
×
889
    if (amountTokenReturn > 0) {
×
890
      if (_returnETH) {
×
891
        // unwrap from WETH -> ETH and send ETH amount back to sender
892
        IWETH(dexAdapter.weth).withdraw(amountTokenReturn);
×
893
        (payable(msg.sender)).sendValue(amountTokenReturn);
×
894
      } else {
895
        _inputToken.safeTransfer(msg.sender, amountTokenReturn);
×
896
      }
897
    }
898
  }
899

900
  /**
901
   * Sends the obtained amount of output token / ETH to msg.sender
902
   *
903
   * @param _outputToken         Address of the output token to return
904
   * @param _amount              Amount to transfer
905
   * @param _redeemToETH         Boolean flag to identify if ETH or the output token should be sent
906
   */
907
  function _sendObtainedOutputToSender(
908
    IERC20 _outputToken,
909
    uint256 _amount,
910
    bool _redeemToETH
911
  ) internal {
912
    if (_redeemToETH) {
×
913
      // unwrap from WETH -> ETH and send ETH amount back to sender
914
      IWETH(dexAdapter.weth).withdraw(_amount);
×
915
      (payable(msg.sender)).sendValue(_amount);
×
916
    } else {
917
      _outputToken.safeTransfer(msg.sender, _amount);
×
918
    }
919
  }
920
}
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

© 2025 Coveralls, Inc