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

0xProject / protocol / 5325032067

pending completion
5325032067

push

github

web-flow
Fix code block rendering (#729)

223 of 1460 branches covered (15.27%)

Branch coverage included in aggregate %.

847 of 3391 relevant lines covered (24.98%)

3.36 hits per line

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

84.57
/contracts/zero-ex/contracts/src/features/multiplex/MultiplexFeature.sol
1
// SPDX-License-Identifier: Apache-2.0
2
/*
3
  Copyright 2023 ZeroEx Intl.
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

15
pragma solidity ^0.6.5;
16
pragma experimental ABIEncoderV2;
17

18
import "@0x/contracts-erc20/src/IERC20Token.sol";
19
import "@0x/contracts-erc20/src/IEtherToken.sol";
20
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
21
import "../../external/ILiquidityProviderSandbox.sol";
22
import "../../fixins/FixinCommon.sol";
23
import "../../fixins/FixinEIP712.sol";
24
import "../../migrations/LibMigrate.sol";
25
import "../interfaces/IFeature.sol";
26
import "../interfaces/IMultiplexFeature.sol";
27
import "./MultiplexLiquidityProvider.sol";
28
import "./MultiplexOtc.sol";
29
import "./MultiplexRfq.sol";
30
import "./MultiplexTransformERC20.sol";
31
import "./MultiplexUniswapV2.sol";
32
import "./MultiplexUniswapV3.sol";
33

34
/// @dev This feature enables efficient batch and multi-hop trades
35
///      using different liquidity sources.
36
contract MultiplexFeature is
37
    IFeature,
38
    IMultiplexFeature,
39
    FixinCommon,
40
    MultiplexLiquidityProvider,
41
    MultiplexOtc,
42
    MultiplexRfq,
43
    MultiplexTransformERC20,
44
    MultiplexUniswapV2,
45
    MultiplexUniswapV3
46
{
47
    /// @dev Name of this feature.
48
    string public constant override FEATURE_NAME = "MultiplexFeature";
49
    /// @dev Version of this feature.
50
    uint256 public immutable override FEATURE_VERSION = _encodeVersion(2, 0, 0);
51
    /// @dev The highest bit of a uint256 value.
52
    uint256 private constant HIGH_BIT = 2 ** 255;
53
    /// @dev Mask of the lower 255 bits of a uint256 value.
54
    uint256 private constant LOWER_255_BITS = HIGH_BIT - 1;
55

56
    /// @dev The WETH token contract.
57
    IEtherToken private immutable WETH;
58

59
    constructor(
60
        address zeroExAddress,
61
        IEtherToken weth,
62
        ILiquidityProviderSandbox sandbox,
63
        address uniswapFactory,
64
        address sushiswapFactory,
65
        bytes32 uniswapPairInitCodeHash,
66
        bytes32 sushiswapPairInitCodeHash
67
    )
68
        public
69
        FixinEIP712(zeroExAddress)
70
        MultiplexLiquidityProvider(sandbox)
71
        MultiplexUniswapV2(uniswapFactory, sushiswapFactory, uniswapPairInitCodeHash, sushiswapPairInitCodeHash)
72
    {
73
        WETH = weth;
74
    }
75

76
    /// @dev Initialize and register this feature.
77
    ///      Should be delegatecalled by `Migrate.migrate()`.
78
    /// @return success `LibMigrate.SUCCESS` on success.
79
    function migrate() external returns (bytes4 success) {
80
        _registerFeatureFunction(this.multiplexBatchSellEthForToken.selector);
1✔
81
        _registerFeatureFunction(this.multiplexBatchSellTokenForEth.selector);
1✔
82
        _registerFeatureFunction(this.multiplexBatchSellTokenForToken.selector);
1✔
83
        _registerFeatureFunction(this._multiplexBatchSell.selector);
1✔
84
        _registerFeatureFunction(this.multiplexMultiHopSellEthForToken.selector);
1✔
85
        _registerFeatureFunction(this.multiplexMultiHopSellTokenForEth.selector);
1✔
86
        _registerFeatureFunction(this.multiplexMultiHopSellTokenForToken.selector);
1✔
87
        _registerFeatureFunction(this._multiplexMultiHopSell.selector);
1✔
88
        return LibMigrate.MIGRATE_SUCCESS;
1✔
89
    }
90

91
    /// @dev Sells attached ETH for `outputToken` using the provided
92
    ///      calls.
93
    /// @param outputToken The token to buy.
94
    /// @param calls The calls to use to sell the attached ETH.
95
    /// @param minBuyAmount The minimum amount of `outputToken` that
96
    ///        must be bought for this function to not revert.
97
    /// @return boughtAmount The amount of `outputToken` bought.
98
    function multiplexBatchSellEthForToken(
99
        IERC20Token outputToken,
100
        BatchSellSubcall[] memory calls,
101
        uint256 minBuyAmount
102
    ) public payable override returns (uint256 boughtAmount) {
103
        // Wrap ETH.
104
        WETH.deposit{value: msg.value}();
×
105
        // WETH is now held by this contract,
106
        // so `useSelfBalance` is true.
107
        return
×
108
            _multiplexBatchSellPrivate(
×
109
                BatchSellParams({
110
                    inputToken: WETH,
111
                    outputToken: outputToken,
112
                    sellAmount: msg.value,
113
                    calls: calls,
114
                    useSelfBalance: true,
115
                    recipient: msg.sender,
116
                    payer: msg.sender
117
                }),
118
                minBuyAmount
119
            );
120
    }
121

122
    /// @dev Sells `sellAmount` of the given `inputToken` for ETH
123
    ///      using the provided calls.
124
    /// @param inputToken The token to sell.
125
    /// @param calls The calls to use to sell the input tokens.
126
    /// @param sellAmount The amount of `inputToken` to sell.
127
    /// @param minBuyAmount The minimum amount of ETH that
128
    ///        must be bought for this function to not revert.
129
    /// @return boughtAmount The amount of ETH bought.
130
    function multiplexBatchSellTokenForEth(
131
        IERC20Token inputToken,
132
        BatchSellSubcall[] memory calls,
133
        uint256 sellAmount,
134
        uint256 minBuyAmount
135
    ) public override returns (uint256 boughtAmount) {
136
        // The outputToken is implicitly WETH. The `recipient`
137
        // of the WETH is set to  this contract, since we
138
        // must unwrap the WETH and transfer the resulting ETH.
139
        boughtAmount = _multiplexBatchSellPrivate(
×
140
            BatchSellParams({
141
                inputToken: inputToken,
142
                outputToken: WETH,
143
                sellAmount: sellAmount,
144
                calls: calls,
145
                useSelfBalance: false,
146
                recipient: address(this),
147
                payer: msg.sender
148
            }),
149
            minBuyAmount
150
        );
151
        // Unwrap WETH.
152
        WETH.withdraw(boughtAmount);
×
153
        // Transfer ETH to `msg.sender`.
154
        _transferEth(msg.sender, boughtAmount);
×
155
    }
156

157
    /// @dev Sells `sellAmount` of the given `inputToken` for
158
    ///      `outputToken` using the provided calls.
159
    /// @param inputToken The token to sell.
160
    /// @param outputToken The token to buy.
161
    /// @param calls The calls to use to sell the input tokens.
162
    /// @param sellAmount The amount of `inputToken` to sell.
163
    /// @param minBuyAmount The minimum amount of `outputToken`
164
    ///        that must be bought for this function to not revert.
165
    /// @return boughtAmount The amount of `outputToken` bought.
166
    function multiplexBatchSellTokenForToken(
167
        IERC20Token inputToken,
168
        IERC20Token outputToken,
169
        BatchSellSubcall[] memory calls,
170
        uint256 sellAmount,
171
        uint256 minBuyAmount
172
    ) public override returns (uint256 boughtAmount) {
173
        return
11✔
174
            _multiplexBatchSellPrivate(
11✔
175
                BatchSellParams({
176
                    inputToken: inputToken,
177
                    outputToken: outputToken,
178
                    sellAmount: sellAmount,
179
                    calls: calls,
180
                    useSelfBalance: false,
181
                    recipient: msg.sender,
182
                    payer: msg.sender
183
                }),
184
                minBuyAmount
185
            );
186
    }
187

188
    /// @dev Executes a batch sell and checks that at least
189
    ///      `minBuyAmount` of `outputToken` was bought. Internal variant.
190
    /// @param params Batch sell parameters.
191
    /// @param minBuyAmount The minimum amount of `outputToken` that
192
    ///        must be bought for this function to not revert.
193
    /// @return boughtAmount The amount of `outputToken` bought.
194
    function _multiplexBatchSell(
195
        BatchSellParams memory params,
196
        uint256 minBuyAmount
197
    ) public override onlySelf returns (uint256 boughtAmount) {
198
        return _multiplexBatchSellPrivate(params, minBuyAmount);
12✔
199
    }
200

201
    /// @dev Executes a batch sell and checks that at least
202
    ///      `minBuyAmount` of `outputToken` was bought.
203
    /// @param params Batch sell parameters.
204
    /// @param minBuyAmount The minimum amount of `outputToken` that
205
    ///        must be bought for this function to not revert.
206
    /// @return boughtAmount The amount of `outputToken` bought.
207
    function _multiplexBatchSellPrivate(
208
        BatchSellParams memory params,
209
        uint256 minBuyAmount
210
    ) private returns (uint256 boughtAmount) {
211
        // Cache the recipient's initial balance of the output token.
212
        uint256 balanceBefore = params.outputToken.balanceOf(params.recipient);
23✔
213
        // Execute the batch sell.
214
        BatchSellState memory state = _executeBatchSell(params);
23✔
215
        // Compute the change in balance of the output token.
216
        uint256 balanceDelta = params.outputToken.balanceOf(params.recipient).safeSub(balanceBefore);
20✔
217
        // Use the minimum of the balanceDelta and the returned bought
218
        // amount in case of weird tokens and whatnot.
219
        boughtAmount = LibSafeMathV06.min256(balanceDelta, state.boughtAmount);
20✔
220
        // Enforce `minBuyAmount`.
221
        require(boughtAmount >= minBuyAmount, "MultiplexFeature::_multiplexBatchSell/UNDERBOUGHT");
20✔
222
    }
223

224
    /// @dev Sells attached ETH via the given sequence of tokens
225
    ///      and calls. `tokens[0]` must be WETH.
226
    ///      The last token in `tokens` is the output token that
227
    ///      will ultimately be sent to `msg.sender`
228
    /// @param tokens The sequence of tokens to use for the sell,
229
    ///        i.e. `tokens[i]` will be sold for `tokens[i+1]` via
230
    ///        `calls[i]`.
231
    /// @param calls The sequence of calls to use for the sell.
232
    /// @param minBuyAmount The minimum amount of output tokens that
233
    ///        must be bought for this function to not revert.
234
    /// @return boughtAmount The amount of output tokens bought.
235
    function multiplexMultiHopSellEthForToken(
236
        address[] memory tokens,
237
        MultiHopSellSubcall[] memory calls,
238
        uint256 minBuyAmount
239
    ) public payable override returns (uint256 boughtAmount) {
240
        // First token must be WETH.
241
        require(tokens[0] == address(WETH), "MultiplexFeature::multiplexMultiHopSellEthForToken/NOT_WETH");
×
242
        // Wrap ETH.
243
        WETH.deposit{value: msg.value}();
×
244
        // WETH is now held by this contract,
245
        // so `useSelfBalance` is true.
246
        return
×
247
            _multiplexMultiHopSellPrivate(
×
248
                MultiHopSellParams({
249
                    tokens: tokens,
250
                    sellAmount: msg.value,
251
                    calls: calls,
252
                    useSelfBalance: true,
253
                    recipient: msg.sender,
254
                    payer: msg.sender
255
                }),
256
                minBuyAmount
257
            );
258
    }
259

260
    /// @dev Sells `sellAmount` of the input token (`tokens[0]`)
261
    ///      for ETH via the given sequence of tokens and calls.
262
    ///      The last token in `tokens` must be WETH.
263
    /// @param tokens The sequence of tokens to use for the sell,
264
    ///        i.e. `tokens[i]` will be sold for `tokens[i+1]` via
265
    ///        `calls[i]`.
266
    /// @param calls The sequence of calls to use for the sell.
267
    /// @param sellAmount The amount of `inputToken` to sell.
268
    /// @param minBuyAmount The minimum amount of ETH that
269
    ///        must be bought for this function to not revert.
270
    /// @return boughtAmount The amount of ETH bought.
271
    function multiplexMultiHopSellTokenForEth(
272
        address[] memory tokens,
273
        MultiHopSellSubcall[] memory calls,
274
        uint256 sellAmount,
275
        uint256 minBuyAmount
276
    ) public override returns (uint256 boughtAmount) {
277
        // Last token must be WETH.
278
        require(
×
279
            tokens[tokens.length - 1] == address(WETH),
280
            "MultiplexFeature::multiplexMultiHopSellTokenForEth/NOT_WETH"
281
        );
282
        // The `recipient of the WETH is set to  this contract, since
283
        // we must unwrap the WETH and transfer the resulting ETH.
284
        boughtAmount = _multiplexMultiHopSellPrivate(
×
285
            MultiHopSellParams({
286
                tokens: tokens,
287
                sellAmount: sellAmount,
288
                calls: calls,
289
                useSelfBalance: false,
290
                recipient: address(this),
291
                payer: msg.sender
292
            }),
293
            minBuyAmount
294
        );
295
        // Unwrap WETH.
296
        WETH.withdraw(boughtAmount);
×
297
        // Transfer ETH to `msg.sender`.
298
        _transferEth(msg.sender, boughtAmount);
×
299
    }
300

301
    /// @dev Sells `sellAmount` of the input token (`tokens[0]`)
302
    ///      via the given sequence of tokens and calls.
303
    ///      The last token in `tokens` is the output token that
304
    ///      will ultimately be sent to `msg.sender`
305
    /// @param tokens The sequence of tokens to use for the sell,
306
    ///        i.e. `tokens[i]` will be sold for `tokens[i+1]` via
307
    ///        `calls[i]`.
308
    /// @param calls The sequence of calls to use for the sell.
309
    /// @param sellAmount The amount of `inputToken` to sell.
310
    /// @param minBuyAmount The minimum amount of output tokens that
311
    ///        must be bought for this function to not revert.
312
    /// @return boughtAmount The amount of output tokens bought.
313
    function multiplexMultiHopSellTokenForToken(
314
        address[] memory tokens,
315
        MultiHopSellSubcall[] memory calls,
316
        uint256 sellAmount,
317
        uint256 minBuyAmount
318
    ) public override returns (uint256 boughtAmount) {
319
        return
8✔
320
            _multiplexMultiHopSellPrivate(
8✔
321
                MultiHopSellParams({
322
                    tokens: tokens,
323
                    sellAmount: sellAmount,
324
                    calls: calls,
325
                    useSelfBalance: false,
326
                    recipient: msg.sender,
327
                    payer: msg.sender
328
                }),
329
                minBuyAmount
330
            );
331
    }
332

333
    /// @dev Executes a multi-hop sell. Internal variant.
334
    /// @param params Multi-hop sell parameters.
335
    /// @param minBuyAmount The minimum amount of output tokens that
336
    ///        must be bought for this function to not revert.
337
    /// @return boughtAmount The amount of output tokens bought.
338
    function _multiplexMultiHopSell(
339
        MultiHopSellParams memory params,
340
        uint256 minBuyAmount
341
    ) public override onlySelf returns (uint256 boughtAmount) {
342
        return _multiplexMultiHopSellPrivate(params, minBuyAmount);
7✔
343
    }
344

345
    /// @dev Executes a multi-hop sell and checks that at least
346
    ///      `minBuyAmount` of output tokens were bought.
347
    /// @param params Multi-hop sell parameters.
348
    /// @param minBuyAmount The minimum amount of output tokens that
349
    ///        must be bought for this function to not revert.
350
    /// @return boughtAmount The amount of output tokens bought.
351
    function _multiplexMultiHopSellPrivate(
352
        MultiHopSellParams memory params,
353
        uint256 minBuyAmount
354
    ) private returns (uint256 boughtAmount) {
355
        // There should be one call/hop between every two tokens
356
        // in the path.
357
        // tokens[0]––calls[0]––>tokens[1]––...––calls[n-1]––>tokens[n]
358
        require(
15✔
359
            params.tokens.length == params.calls.length + 1,
360
            "MultiplexFeature::_multiplexMultiHopSell/MISMATCHED_ARRAY_LENGTHS"
361
        );
362
        // The output token is the last token in the path.
363
        IERC20Token outputToken = IERC20Token(params.tokens[params.tokens.length - 1]);
14✔
364
        // Cache the recipient's balance of the output token.
365
        uint256 balanceBefore = outputToken.balanceOf(params.recipient);
14✔
366
        // Execute the multi-hop sell.
367
        MultiHopSellState memory state = _executeMultiHopSell(params);
14✔
368
        // Compute the change in balance of the output token.
369
        uint256 balanceDelta = outputToken.balanceOf(params.recipient).safeSub(balanceBefore);
13✔
370
        // Use the minimum of the balanceDelta and the returned bought
371
        // amount in case of weird tokens and whatnot.
372
        boughtAmount = LibSafeMathV06.min256(balanceDelta, state.outputTokenAmount);
13✔
373
        // Enforce `minBuyAmount`.
374
        require(boughtAmount >= minBuyAmount, "MultiplexFeature::_multiplexMultiHopSell/UNDERBOUGHT");
13✔
375
    }
376

377
    /// @dev Iterates through the constituent calls of a batch
378
    ///      sell and executes each one, until the full amount
379
    //       has been sold.
380
    /// @param params Batch sell parameters.
381
    /// @return state A struct containing the amounts of `inputToken`
382
    ///         sold and `outputToken` bought.
383
    function _executeBatchSell(BatchSellParams memory params) private returns (BatchSellState memory state) {
384
        // Iterate through the calls and execute each one
385
        // until the full amount has been sold.
386
        for (uint256 i = 0; i != params.calls.length; i++) {
26✔
387
            // Check if we've hit our target.
388
            if (state.soldAmount >= params.sellAmount) {
40✔
389
                break;
2✔
390
            }
391
            BatchSellSubcall memory subcall = params.calls[i];
38✔
392
            // Compute the input token amount.
393
            uint256 inputTokenAmount = _normalizeSellAmount(subcall.sellAmount, params.sellAmount, state.soldAmount);
38✔
394
            if (subcall.id == MultiplexSubcall.RFQ) {
38✔
395
                _batchSellRfqOrder(state, params, subcall.data, inputTokenAmount);
13✔
396
            } else if (subcall.id == MultiplexSubcall.OTC) {
25✔
397
                _batchSellOtcOrder(state, params, subcall.data, inputTokenAmount);
3✔
398
            } else if (subcall.id == MultiplexSubcall.UniswapV2) {
22✔
399
                _batchSellUniswapV2(state, params, subcall.data, inputTokenAmount);
7✔
400
            } else if (subcall.id == MultiplexSubcall.UniswapV3) {
15✔
401
                _batchSellUniswapV3(state, params, subcall.data, inputTokenAmount);
8✔
402
            } else if (subcall.id == MultiplexSubcall.LiquidityProvider) {
7✔
403
                _batchSellLiquidityProvider(state, params, subcall.data, inputTokenAmount);
2✔
404
            } else if (subcall.id == MultiplexSubcall.TransformERC20) {
5✔
405
                _batchSellTransformERC20(state, params, subcall.data, inputTokenAmount);
2✔
406
            } else if (subcall.id == MultiplexSubcall.MultiHopSell) {
3✔
407
                _nestedMultiHopSell(state, params, subcall.data, inputTokenAmount);
2✔
408
            } else {
409
                revert("MultiplexFeature::_executeBatchSell/INVALID_SUBCALL");
1✔
410
            }
411
        }
412
        require(state.soldAmount == params.sellAmount, "MultiplexFeature::_executeBatchSell/INCORRECT_AMOUNT_SOLD");
25✔
413
    }
414

415
    // This function executes a sequence of fills "hopping" through the
416
    // path of tokens given by `params.tokens`.
417
    function _executeMultiHopSell(MultiHopSellParams memory params) private returns (MultiHopSellState memory state) {
418
        // This variable is used for the input and output amounts of
419
        // each hop. After the final hop, this will contain the output
420
        // amount of the multi-hop fill.
421
        state.outputTokenAmount = params.sellAmount;
16✔
422
        // The first call may expect the input tokens to be held by
423
        // `payer`, `address(this)`, or some other address.
424
        // Compute the expected address and transfer the input tokens
425
        // there if necessary.
426
        state.from = _computeHopTarget(params, 0);
16✔
427
        // If the input tokens are currently held by `payer` but
428
        // the first hop expects them elsewhere, perform a `transferFrom`.
429
        if (!params.useSelfBalance && state.from != params.payer) {
15!
430
            _transferERC20TokensFrom(IERC20Token(params.tokens[0]), params.payer, state.from, params.sellAmount);
7✔
431
        }
432
        // If the input tokens are currently held by `address(this)` but
433
        // the first hop expects them elsewhere, perform a `transfer`.
434
        if (params.useSelfBalance && state.from != address(this)) {
15!
435
            _transferERC20Tokens(IERC20Token(params.tokens[0]), state.from, params.sellAmount);
×
436
        }
437
        // Iterate through the calls and execute each one.
438
        for (state.hopIndex = 0; state.hopIndex != params.calls.length; state.hopIndex++) {
15✔
439
            MultiHopSellSubcall memory subcall = params.calls[state.hopIndex];
23✔
440
            // Compute the recipient of the tokens that will be
441
            // bought by the current hop.
442
            state.to = _computeHopTarget(params, state.hopIndex + 1);
23✔
443

444
            if (subcall.id == MultiplexSubcall.UniswapV2) {
23✔
445
                _multiHopSellUniswapV2(state, params, subcall.data);
7✔
446
            } else if (subcall.id == MultiplexSubcall.UniswapV3) {
16✔
447
                _multiHopSellUniswapV3(state, params, subcall.data);
8✔
448
            } else if (subcall.id == MultiplexSubcall.LiquidityProvider) {
8✔
449
                _multiHopSellLiquidityProvider(state, params, subcall.data);
3✔
450
            } else if (subcall.id == MultiplexSubcall.BatchSell) {
5✔
451
                _nestedBatchSell(state, params, subcall.data);
3✔
452
            } else if (subcall.id == MultiplexSubcall.OTC) {
2!
453
                _multiHopSellOtcOrder(state, params, subcall.data);
2✔
454
            } else {
455
                revert("MultiplexFeature::_executeMultiHopSell/INVALID_SUBCALL");
×
456
            }
457
            // The recipient of the current hop will be the source
458
            // of tokens for the next hop.
459
            state.from = state.to;
23✔
460
        }
461
    }
462

463
    function _nestedMultiHopSell(
464
        IMultiplexFeature.BatchSellState memory state,
465
        IMultiplexFeature.BatchSellParams memory params,
466
        bytes memory data,
467
        uint256 sellAmount
468
    ) private {
469
        MultiHopSellParams memory multiHopParams;
2✔
470
        // Decode the tokens and calls for the nested
471
        // multi-hop sell.
472
        (multiHopParams.tokens, multiHopParams.calls) = abi.decode(data, (address[], MultiHopSellSubcall[]));
2✔
473
        multiHopParams.sellAmount = sellAmount;
2✔
474
        // If the batch sell is using input tokens held by
475
        // `address(this)`, then so should the nested
476
        // multi-hop sell.
477
        multiHopParams.useSelfBalance = params.useSelfBalance;
2✔
478
        // Likewise, the recipient of the multi-hop sell is
479
        // equal to the recipient of its containing batch sell.
480
        multiHopParams.recipient = params.recipient;
2✔
481
        // The payer is the same too.
482
        multiHopParams.payer = params.payer;
2✔
483
        // Execute the nested multi-hop sell.
484
        uint256 outputTokenAmount = _executeMultiHopSell(multiHopParams).outputTokenAmount;
2✔
485
        // Increment the sold and bought amounts.
486
        state.soldAmount = state.soldAmount.safeAdd(sellAmount);
2✔
487
        state.boughtAmount = state.boughtAmount.safeAdd(outputTokenAmount);
2✔
488
    }
489

490
    function _nestedBatchSell(
491
        IMultiplexFeature.MultiHopSellState memory state,
492
        IMultiplexFeature.MultiHopSellParams memory params,
493
        bytes memory data
494
    ) private {
495
        BatchSellParams memory batchSellParams;
3✔
496
        // Decode the calls for the nested batch sell.
497
        batchSellParams.calls = abi.decode(data, (BatchSellSubcall[]));
3✔
498
        // The input and output tokens of the batch
499
        // sell are the current and next tokens in
500
        // `params.tokens`, respectively.
501
        batchSellParams.inputToken = IERC20Token(params.tokens[state.hopIndex]);
3✔
502
        batchSellParams.outputToken = IERC20Token(params.tokens[state.hopIndex + 1]);
3✔
503
        // The `sellAmount` for the batch sell is the
504
        // `outputTokenAmount` from the previous hop.
505
        batchSellParams.sellAmount = state.outputTokenAmount;
3✔
506
        // If the nested batch sell is the first hop
507
        // and `useSelfBalance` for the containing multi-
508
        // hop sell is false, the nested batch sell should
509
        // pull tokens from `payer` (so  `batchSellParams.useSelfBalance`
510
        // should be false). Otherwise `batchSellParams.useSelfBalance`
511
        // should be true.
512
        batchSellParams.useSelfBalance = state.hopIndex > 0 || params.useSelfBalance;
3✔
513
        // `state.to` has been populated with the address
514
        // that should receive the output tokens of the
515
        // batch sell.
516
        batchSellParams.recipient = state.to;
3✔
517
        // payer shound be the same too.
518
        batchSellParams.payer = params.payer;
3✔
519
        // Execute the nested batch sell.
520
        state.outputTokenAmount = _executeBatchSell(batchSellParams).boughtAmount;
3✔
521
    }
522

523
    // This function computes the "target" address of hop index `i` within
524
    // a multi-hop sell.
525
    // If `i == 0`, the target is the address which should hold the input
526
    // tokens prior to executing `calls[0]`. Otherwise, it is the address
527
    // that should receive `tokens[i]` upon executing `calls[i-1]`.
528
    function _computeHopTarget(MultiHopSellParams memory params, uint256 i) private view returns (address target) {
529
        if (i == params.calls.length) {
39✔
530
            // The last call should send the output tokens to the
531
            // multi-hop sell recipient.
532
            target = params.recipient;
15✔
533
        } else {
534
            MultiHopSellSubcall memory subcall = params.calls[i];
24✔
535
            if (subcall.id == MultiplexSubcall.UniswapV2) {
24!
536
                // UniswapV2 (and Sushiswap) allow tokens to be
537
                // transferred into the pair contract before `swap`
538
                // is called, so we compute the pair contract's address.
539
                (address[] memory tokens, bool isSushi) = abi.decode(subcall.data, (address[], bool));
7✔
540
                target = _computeUniswapPairAddress(tokens[0], tokens[1], isSushi);
7✔
541
            } else if (subcall.id == MultiplexSubcall.LiquidityProvider) {
17✔
542
                // Similar to UniswapV2, LiquidityProvider contracts
543
                // allow tokens to be transferred in before the swap
544
                // is executed, so we the target is the address encoded
545
                // in the subcall data.
546
                (target, ) = abi.decode(subcall.data, (address, bytes));
3✔
547
            } else if (
13✔
548
                subcall.id == MultiplexSubcall.UniswapV3 ||
14✔
549
                subcall.id == MultiplexSubcall.BatchSell ||
550
                subcall.id == MultiplexSubcall.OTC
551
            ) {
552
                // UniswapV3 uses a callback to pull in the tokens being
553
                // sold to it. The callback implemented in `UniswapV3Feature`
554
                // can either:
555
                // - call `transferFrom` to move tokens from `payer` to the
556
                //   UniswapV3 pool, or
557
                // - call `transfer` to move tokens from `address(this)` to the
558
                //   UniswapV3 pool.
559
                // A nested batch sell is similar, in that it can either:
560
                // - use tokens from `payer`, or
561
                // - use tokens held by `address(this)`.
562

563
                // Suppose UniswapV3/BatchSell is the first call in the multi-hop
564
                // path. The input tokens are either held by `payer`,
565
                // or in the case of `multiplexMultiHopSellEthForToken` WETH is
566
                // held by `address(this)`. The target is set accordingly.
567

568
                // If this is _not_ the first call in the multi-hop path, we
569
                // are dealing with an "intermediate" token in the multi-hop path,
570
                // which `payer` may not have an allowance set for. Thus
571
                // target must be set to `address(this)` for `i > 0`.
572
                if (i == 0 && !params.useSelfBalance) {
13✔
573
                    target = params.payer;
8✔
574
                } else {
575
                    target = address(this);
5✔
576
                }
577
            } else {
578
                revert("MultiplexFeature::_computeHopTarget/INVALID_SUBCALL");
1✔
579
            }
580
        }
581
        require(target != address(0), "MultiplexFeature::_computeHopTarget/TARGET_IS_NULL");
38!
582
    }
583

584
    // If `rawAmount` encodes a proportion of `totalSellAmount`, this function
585
    // converts it to an absolute quantity. Caps the normalized amount to
586
    // the remaining sell amount (`totalSellAmount - soldAmount`).
587
    function _normalizeSellAmount(
588
        uint256 rawAmount,
589
        uint256 totalSellAmount,
590
        uint256 soldAmount
591
    ) private pure returns (uint256 normalized) {
592
        if ((rawAmount & HIGH_BIT) == HIGH_BIT) {
38!
593
            // If the high bit of `rawAmount` is set then the lower 255 bits
594
            // specify a fraction of `totalSellAmount`.
595
            return
4✔
596
                LibSafeMathV06.min256(
4✔
597
                    (totalSellAmount * LibSafeMathV06.min256(rawAmount & LOWER_255_BITS, 1e18)) / 1e18,
598
                    totalSellAmount.safeSub(soldAmount)
599
                );
600
        } else {
601
            return LibSafeMathV06.min256(rawAmount, totalSellAmount.safeSub(soldAmount));
34✔
602
        }
603
    }
604
}
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