diff --git a/contracts/zero-ex/tests/Multiplex.t.sol b/contracts/zero-ex/tests/Multiplex.t.sol new file mode 100644 index 0000000000..940e667d28 --- /dev/null +++ b/contracts/zero-ex/tests/Multiplex.t.sol @@ -0,0 +1,615 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + + Copyright 2023 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6; +pragma experimental ABIEncoderV2; + +import {LibNativeOrder} from "src/features/libs/LibNativeOrder.sol"; +import {IMultiplexFeature} from "src/features/interfaces/IMultiplexFeature.sol"; +import {LocalTest} from "utils/LocalTest.sol"; +import {MultiplexUtils} from "utils/MultiplexUtils.sol"; + +contract Multiplex is LocalTest, MultiplexUtils { + event RfqOrderFilled( + bytes32 orderHash, + address maker, + address taker, + address makerToken, + address takerToken, + uint128 takerTokenFilledAmount, + uint128 makerTokenFilledAmount, + bytes32 pool + ); + + event OtcOrderFilled( + bytes32 orderHash, + address maker, + address taker, + address makerToken, + address takerToken, + uint128 makerTokenFilledAmount, + uint128 takerTokenFilledAmount + ); + + event ExpiredRfqOrder(bytes32 orderHash, address maker, uint64 expiry); + + event ExpiredOtcOrder(bytes32 orderHash, address maker, uint64 expiry); + + event Transfer(address token, address from, address to, uint256 value); + + event MintTransform( + address context, + address caller, + address sender, + address taker, + bytes data, + uint256 inputTokenBalance, + uint256 ethBalance + ); + + //// batch sells + + // reverts if minBuyAmount is not satisfied + function test_multiplexBatchSellTokenForToken_revertsIfMinBuyAmountIsNotSatisfied() public { + LibNativeOrder.RfqOrder memory rfqOrder = _makeTestRfqOrder(); + IMultiplexFeature.BatchSellSubcall memory rfqSubcall = _makeRfqSubcall(rfqOrder); + _mintTo(address(rfqOrder.takerToken), rfqOrder.taker, rfqOrder.takerAmount); + + vm.expectRevert("MultiplexFeature::_multiplexBatchSell/UNDERBOUGHT"); + zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken( + dai, + zrx, + _makeArray(rfqSubcall), + rfqOrder.takerAmount, + rfqOrder.makerAmount + 1 + ); + } + + // reverts if given an invalid subcall type + function test_multiplexBatchSellTokenForToken_revertsIfGivenAnInvalidSubcallType() public { + uint256 sellAmount = 1e18; + + vm.expectRevert("MultiplexFeature::_executeBatchSell/INVALID_SUBCALL"); + zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken( + dai, + zrx, + _makeArray( + IMultiplexFeature.BatchSellSubcall({ + id: IMultiplexFeature.MultiplexSubcall.Invalid, + sellAmount: sellAmount, + data: hex"" + }) + ), + sellAmount, + 0 + ); + } + + // reverts if the full sell amount is not sold + function test_multiplexBatchSellTokenForToken_revertsIfTheFullSellAmountIsNotSold() public { + LibNativeOrder.RfqOrder memory rfqOrder = _makeTestRfqOrder(); + IMultiplexFeature.BatchSellSubcall memory rfqSubcall = _makeRfqSubcall(rfqOrder); + _mintTo(address(rfqOrder.takerToken), rfqOrder.taker, rfqOrder.takerAmount); + + vm.expectRevert("MultiplexFeature::_executeBatchSell/INCORRECT_AMOUNT_SOLD"); + zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken( + rfqOrder.takerToken, + rfqOrder.makerToken, + _makeArray(rfqSubcall), + rfqOrder.takerAmount + 1, + rfqOrder.makerAmount + ); + } + + // RFQ, fallback(UniswapV2) + function test_multiplexBatchSellTokenForToken_rfqFallbackUniswapV2() public { + LibNativeOrder.RfqOrder memory rfqOrder = _makeTestRfqOrder(); + _createUniswapV2Pool(uniV2Factory, dai, zrx, 10e18, 10e18); + _mintTo(address(rfqOrder.takerToken), rfqOrder.taker, rfqOrder.takerAmount); + + vm.expectEmit(true, true, true, true); + emit RfqOrderFilled( + zeroExDeployed.features.nativeOrdersFeature.getRfqOrderHash(rfqOrder), + rfqOrder.maker, + rfqOrder.taker, + address(rfqOrder.makerToken), + address(rfqOrder.takerToken), + rfqOrder.takerAmount, + rfqOrder.makerAmount, + rfqOrder.pool + ); + + zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken( + rfqOrder.takerToken, + zrx, + _makeArray( + _makeRfqSubcall(rfqOrder), + _makeUniswapV2BatchSubcall(_makeArray(address(dai), address(zrx)), rfqOrder.takerAmount, false) + ), + rfqOrder.takerAmount, + 0 + ); + } + + // OTC, fallback(UniswapV2) + function test_multiplexBatchSellTokenForToken_otcFallbackUniswapV2() public { + LibNativeOrder.OtcOrder memory otcOrder = _makeTestOtcOrder(); + _createUniswapV2Pool(uniV2Factory, dai, zrx, 10e18, 10e18); + _mintTo(address(otcOrder.takerToken), otcOrder.taker, otcOrder.takerAmount); + + vm.expectEmit(true, true, true, true); + emit OtcOrderFilled( + zeroExDeployed.features.otcOrdersFeature.getOtcOrderHash(otcOrder), + otcOrder.maker, + otcOrder.taker, + address(otcOrder.makerToken), + address(otcOrder.takerToken), + otcOrder.makerAmount, + otcOrder.takerAmount + ); + + zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken( + otcOrder.takerToken, + zrx, + _makeArray( + _makeOtcSubcall(otcOrder), + _makeUniswapV2BatchSubcall(_makeArray(address(dai), address(zrx)), otcOrder.takerAmount, false) + ), + otcOrder.takerAmount, + 0 + ); + } + + // expired RFQ, fallback(UniswapV2) + function test_multiplexBatchSellTokenForToken_expiredRfqFallbackUniswapV2() public { + LibNativeOrder.RfqOrder memory rfqOrder = _makeTestRfqOrder(); + address uniV2Pool = _createUniswapV2Pool(uniV2Factory, dai, zrx, 10e18, 10e18); + _mintTo(address(rfqOrder.takerToken), rfqOrder.taker, rfqOrder.takerAmount); + rfqOrder.expiry = 0; + + vm.expectEmit(true, true, true, true); + emit ExpiredRfqOrder( + zeroExDeployed.features.nativeOrdersFeature.getRfqOrderHash(rfqOrder), + rfqOrder.maker, + rfqOrder.expiry + ); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(dai), rfqOrder.taker, uniV2Pool, rfqOrder.takerAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(zrx), uniV2Pool, rfqOrder.taker, 906610893880149131); + + zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken( + rfqOrder.takerToken, + zrx, + _makeArray( + _makeRfqSubcall(rfqOrder), + _makeUniswapV2BatchSubcall(_makeArray(address(dai), address(zrx)), rfqOrder.takerAmount, false) + ), + rfqOrder.takerAmount, + 0 + ); + } + + // expired OTC, fallback(UniswapV2) + function test_multiplexBatchSellTokenForToken_expiredOtcFallbackUniswapV2() public { + LibNativeOrder.OtcOrder memory otcOrder = _makeTestOtcOrder(); + address uniV2Pool = _createUniswapV2Pool(uniV2Factory, dai, zrx, 10e18, 10e18); + _mintTo(address(otcOrder.takerToken), otcOrder.taker, otcOrder.takerAmount); + otcOrder.expiryAndNonce = 1; + + vm.expectEmit(true, true, true, true); + emit ExpiredOtcOrder( + zeroExDeployed.features.otcOrdersFeature.getOtcOrderHash(otcOrder), + otcOrder.maker, + uint64(otcOrder.expiryAndNonce >> 192) + ); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(dai), otcOrder.taker, uniV2Pool, otcOrder.takerAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(zrx), uniV2Pool, otcOrder.taker, 906610893880149131); + + zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken( + otcOrder.takerToken, + zrx, + _makeArray( + _makeOtcSubcall(otcOrder), + _makeUniswapV2BatchSubcall(_makeArray(address(dai), address(zrx)), otcOrder.takerAmount, false) + ), + otcOrder.takerAmount, + 0 + ); + } + + // expired RFQ, fallback(TransformERC20) + function test_multiplexBatchSellTokenForToken_expiredRfqFallbackTransformErc20() public { + LibNativeOrder.RfqOrder memory rfqOrder = _makeTestRfqOrder(); + _mintTo(address(rfqOrder.takerToken), rfqOrder.taker, rfqOrder.takerAmount); + rfqOrder.expiry = 0; + + vm.expectEmit(true, true, true, true); + emit ExpiredRfqOrder( + zeroExDeployed.features.nativeOrdersFeature.getRfqOrderHash(rfqOrder), + rfqOrder.maker, + rfqOrder.expiry + ); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(dai), rfqOrder.taker, address(flashWallet), rfqOrder.takerAmount); + + vm.expectEmit(true, true, true, true); + emit MintTransform( + address(flashWallet), + address(zeroExDeployed.zeroEx), + address(zeroExDeployed.zeroEx), + rfqOrder.taker, + abi.encode(dai, zrx, rfqOrder.takerAmount, 5e17, 0), + rfqOrder.takerAmount, + 0 + ); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(dai), address(flashWallet), address(0), 1e18); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(zrx), address(flashWallet), rfqOrder.taker, 5e17); + + zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken( + rfqOrder.takerToken, + zrx, + _makeArray(_makeRfqSubcall(rfqOrder), _makeMockTransformERC20Subcall(dai, zrx, rfqOrder.takerAmount, 5e17)), + rfqOrder.takerAmount, + 0 + ); + } + + // LiquidityProvider, UniV3, Sushiswap + function test_multiplexBatchSellTokenForToken_liquidityProviderUniV3Sushiswap() public { + address sushiswapPool = _createUniswapV2Pool(sushiFactory, dai, zrx, 10e18, 10e18); + address uniV3Pool = _createUniswapV3Pool(uniV3Factory, dai, zrx, 10e18, 10e18); + + address[] memory tokens = _makeArray(address(dai), address(zrx)); + IMultiplexFeature.BatchSellSubcall memory lpSubcall = _makeMockLiquidityProviderBatchSubcall(4e17); + IMultiplexFeature.BatchSellSubcall memory uniV3Subcall = _makeUniswapV3BatchSubcall(tokens, 5e17); + IMultiplexFeature.BatchSellSubcall memory sushiswapSubcall = _makeUniswapV2BatchSubcall(tokens, 6e17, true); + uint256 sellAmount = lpSubcall.sellAmount + uniV3Subcall.sellAmount + sushiswapSubcall.sellAmount - 1; + + _mintTo(address(dai), address(this), sellAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(dai), address(this), address(liquidityProvider), lpSubcall.sellAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(zrx), address(liquidityProvider), address(this), 0); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(zrx), uniV3Pool, address(this), 10e18); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(dai), address(this), uniV3Pool, uniV3Subcall.sellAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(dai), address(this), sushiswapPool, sushiswapSubcall.sellAmount - 1); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(zrx), sushiswapPool, address(this), 564435470174180520); + + zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken( + dai, + zrx, + _makeArray(lpSubcall, uniV3Subcall, sushiswapSubcall), + sellAmount, + 0 + ); + } + + // proportional fill amounts + function test_multiplexBatchSellTokenForToken_proportionalFillAmounts() public { + LibNativeOrder.RfqOrder memory rfqOrder = _makeTestRfqOrder(); + address uniV2Pool = _createUniswapV2Pool(uniV2Factory, dai, zrx, 10e18, 10e18); + + uint256 sellAmount = 1e18; + _mintTo(address(dai), address(this), sellAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(dai), rfqOrder.taker, rfqOrder.maker, 42e16); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(zrx), rfqOrder.maker, rfqOrder.taker, 42e16); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(dai), rfqOrder.taker, uniV2Pool, 58e16); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(zrx), uniV2Pool, rfqOrder.taker, 546649448964196380); + + zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken( + dai, + zrx, + _makeArray( + _makeRfqSubcall(rfqOrder, _encodeFractionalFillAmount(42)), + _makeUniswapV2BatchSubcall( + _makeArray(address(dai), address(zrx)), + _encodeFractionalFillAmount(100), + false + ) + ), + sellAmount, + 0 + ); + } + + // RFQ, MultiHop(UniV3, UniV2) + function test_multiplexBatchSellTokenForToken_rfqMultiHopUniV3UniV2() public { + address uniV2Pool = _createUniswapV2Pool(uniV2Factory, shib, zrx, 10e18, 10e18); + address uniV3Pool = _createUniswapV3Pool(uniV3Factory, dai, shib, 10e18, 10e18); + + LibNativeOrder.RfqOrder memory rfqOrder = _makeTestRfqOrder(); + IMultiplexFeature.BatchSellSubcall memory rfqSubcall = _makeRfqSubcall(rfqOrder); + IMultiplexFeature.BatchSellSubcall memory nestedMultiHopSubcall = _makeNestedMultiHopSellSubcall( + _makeArray(address(dai), address(shib), address(zrx)), + _makeArray( + _makeUniswapV3MultiHopSubcall(_makeArray(address(dai), address(shib))), + _makeUniswapV2MultiHopSubcall(_makeArray(address(shib), address(zrx)), false) + ), + 5e17 + ); + + uint256 sellAmount = rfqSubcall.sellAmount + nestedMultiHopSubcall.sellAmount; + _mintTo(address(dai), address(this), sellAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(dai), rfqOrder.taker, rfqOrder.maker, rfqOrder.takerAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(zrx), rfqOrder.maker, rfqOrder.taker, rfqOrder.makerAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(shib), uniV3Pool, uniV2Pool, 10e18); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(dai), rfqOrder.taker, uniV3Pool, nestedMultiHopSubcall.sellAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(zrx), uniV2Pool, rfqOrder.taker, 4992488733099649474); + + zeroExDeployed.zeroEx.multiplexBatchSellTokenForToken( + dai, + zrx, + _makeArray(rfqSubcall, nestedMultiHopSubcall), + sellAmount, + 0 + ); + } + + //// multihop sells + + // reverts if given an invalid subcall type + function test_multiplexMultiHopSellTokenForToken_revertsIfGivenAnInvalidSubcallType() public { + vm.expectRevert("MultiplexFeature::_computeHopTarget/INVALID_SUBCALL"); + zeroExDeployed.zeroEx.multiplexMultiHopSellTokenForToken( + _makeArray(address(dai), address(zrx)), + _makeArray( + IMultiplexFeature.MultiHopSellSubcall({id: IMultiplexFeature.MultiplexSubcall.Invalid, data: hex""}) + ), + 1e18, + 0 + ); + } + + // reverts if minBuyAmount is not satisfied + function test_multiplexMultiHopSellTokenForToken_revertsIfMinBuyAmountIsNotSatisfied() public { + _createUniswapV2Pool(uniV2Factory, dai, zrx, 10e18, 10e18); + + uint256 sellAmount = 5e17; + _mintTo(address(dai), address(this), sellAmount); + + vm.expectRevert("MultiplexFeature::_multiplexMultiHopSell/UNDERBOUGHT"); + zeroExDeployed.zeroEx.multiplexMultiHopSellTokenForToken( + _makeArray(address(dai), address(zrx)), + _makeArray(_makeUniswapV2MultiHopSubcall(_makeArray(address(dai), address(zrx)), false)), + sellAmount, + type(uint256).max + ); + } + + // reverts if array lengths are mismatched + function test_multiplexMultiHopSellTokenForToken_revertsIfArrayLengthsAreMismatched() public { + _createUniswapV2Pool(uniV2Factory, dai, zrx, 10e18, 10e18); + IMultiplexFeature.MultiHopSellSubcall memory uniswapV2Subcall = _makeUniswapV2MultiHopSubcall( + _makeArray(address(dai), address(zrx)), + false + ); + + uint256 sellAmount = 5e17; + _mintTo(address(dai), address(this), sellAmount); + + vm.expectRevert("MultiplexFeature::_multiplexMultiHopSell/MISMATCHED_ARRAY_LENGTHS"); + zeroExDeployed.zeroEx.multiplexMultiHopSellTokenForToken( + _makeArray(address(dai), address(zrx)), + _makeArray(uniswapV2Subcall, uniswapV2Subcall), + sellAmount, + 0 + ); + } + + // UniswapV2 -> LiquidityProvider + function test_multiplexMultiHopSellTokenForToken_uniswapV2ToLiquidityProvider() public { + address uniV2Pool = _createUniswapV2Pool(uniV2Factory, dai, shib, 10e18, 10e18); + IMultiplexFeature.MultiHopSellSubcall memory lpSubcall = _makeMockLiquidityProviderMultiHopSubcall(); + IMultiplexFeature.MultiHopSellSubcall memory uniswapV2Subcall = _makeUniswapV2MultiHopSubcall( + _makeArray(address(dai), address(shib)), + false + ); + + uint256 sellAmount = 5e17; + uint256 buyAmount = 6e17; + _mintTo(address(dai), address(this), sellAmount); + _mintTo(address(zrx), address(liquidityProvider), buyAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(dai), address(this), uniV2Pool, sellAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(shib), uniV2Pool, address(liquidityProvider), 474829737581559270); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(zrx), address(liquidityProvider), address(this), buyAmount); + + zeroExDeployed.zeroEx.multiplexMultiHopSellTokenForToken( + _makeArray(address(dai), address(shib), address(zrx)), + _makeArray(uniswapV2Subcall, lpSubcall), + sellAmount, + buyAmount + ); + } + + // LiquidityProvider -> Sushiswap + function test_multiplexMultiHopSellTokenForToken_liquidityProviderToSushiswap() public { + address sushiPool = _createUniswapV2Pool(sushiFactory, shib, zrx, 10e18, 10e18); + IMultiplexFeature.MultiHopSellSubcall memory lpSubcall = _makeMockLiquidityProviderMultiHopSubcall(); + IMultiplexFeature.MultiHopSellSubcall memory sushiswapSubcall = _makeUniswapV2MultiHopSubcall( + _makeArray(address(shib), address(zrx)), + true + ); + + uint256 sellAmount = 5e17; + uint256 shibAmount = 6e17; + _mintTo(address(dai), address(this), sellAmount); + _mintTo(address(shib), address(liquidityProvider), shibAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(dai), address(this), address(liquidityProvider), sellAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(shib), address(liquidityProvider), sushiPool, shibAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(zrx), sushiPool, address(this), 564435470174180521); + + zeroExDeployed.zeroEx.multiplexMultiHopSellTokenForToken( + _makeArray(address(dai), address(shib), address(zrx)), + _makeArray(lpSubcall, sushiswapSubcall), + sellAmount, + 0 + ); + } + + // UniswapV3 -> BatchSell(RFQ, UniswapV2) + function test_multiplexMultiHopSellTokenForToken_uniswapV3ToBatchSellRfqUniswapV2() public { + address uniV2Pool = _createUniswapV2Pool(uniV2Factory, shib, zrx, 10e18, 10e18); + address uniV3Pool = _createUniswapV3Pool(uniV3Factory, dai, shib, 10e18, 10e18); + + LibNativeOrder.RfqOrder memory rfqOrder = _makeTestRfqOrder(); + rfqOrder.takerToken = shib; + rfqOrder.makerToken = zrx; + + IMultiplexFeature.BatchSellSubcall memory rfqSubcall = _makeRfqSubcall(rfqOrder); + rfqSubcall.sellAmount = _encodeFractionalFillAmount(42); + + uint256 sellAmount = 5e17; + _mintTo(address(dai), address(this), sellAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(shib), uniV3Pool, address(zeroExDeployed.zeroEx), 10e18); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(dai), address(this), uniV3Pool, sellAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(shib), address(zeroExDeployed.zeroEx), rfqOrder.maker, 1e18); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(zrx), rfqOrder.maker, address(this), 1e18); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(shib), address(zeroExDeployed.zeroEx), uniV2Pool, 9e18); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(zrx), uniV2Pool, address(this), 4729352237389975227); + + zeroExDeployed.zeroEx.multiplexMultiHopSellTokenForToken( + _makeArray(address(dai), address(shib), address(zrx)), + _makeArray( + _makeUniswapV3MultiHopSubcall(_makeArray(address(dai), address(shib))), + _makeNestedBatchSellSubcall( + _makeArray( + rfqSubcall, + _makeUniswapV2BatchSubcall( + _makeArray(address(shib), address(zrx)), + _encodeFractionalFillAmount(100), + false + ) + ) + ) + ), + sellAmount, + 0 + ); + } + + // BatchSell(RFQ, UniswapV2) -> UniswapV3 + function test_multiplexMultiHopSellTokenForToken_batchSellRfqUniswapV2ToUniswapV3() public { + address uniV2Pool = _createUniswapV2Pool(uniV2Factory, dai, shib, 10e18, 10e18); + address uniV3Pool = _createUniswapV3Pool(uniV3Factory, shib, zrx, 10e18, 10e18); + + LibNativeOrder.RfqOrder memory rfqOrder = _makeTestRfqOrder({makerToken: shib, takerToken: dai}); + + IMultiplexFeature.BatchSellSubcall memory rfqSubcall = _makeRfqSubcall(rfqOrder); + IMultiplexFeature.BatchSellSubcall memory uniswapV2Subcall = _makeUniswapV2BatchSubcall( + _makeArray(address(dai), address(shib)), + 5e17, + false + ); + + uint256 sellAmount = rfqSubcall.sellAmount + uniswapV2Subcall.sellAmount; + _mintTo(address(dai), address(this), sellAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(dai), address(this), rfqOrder.maker, rfqOrder.takerAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(shib), rfqOrder.maker, address(zeroExDeployed.zeroEx), rfqOrder.takerAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(dai), address(this), uniV2Pool, uniswapV2Subcall.sellAmount); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(shib), uniV2Pool, address(zeroExDeployed.zeroEx), 474829737581559270); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(zrx), uniV3Pool, address(this), 10e18); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(shib), address(zeroExDeployed.zeroEx), uniV3Pool, 1474829737581559270); + + zeroExDeployed.zeroEx.multiplexMultiHopSellTokenForToken( + _makeArray(address(dai), address(shib), address(zrx)), + _makeArray( + _makeNestedBatchSellSubcall(_makeArray(rfqSubcall, uniswapV2Subcall)), + _makeUniswapV3MultiHopSubcall(_makeArray(address(shib), address(zrx))) + ), + sellAmount, + 0 + ); + } +} diff --git a/contracts/zero-ex/tests/WrapEth.t.sol b/contracts/zero-ex/tests/WrapEth.t.sol index 8914473936..5b76e65849 100644 --- a/contracts/zero-ex/tests/WrapEth.t.sol +++ b/contracts/zero-ex/tests/WrapEth.t.sol @@ -15,7 +15,7 @@ pragma solidity ^0.6; pragma experimental ABIEncoderV2; -import "utils/ForkUtils.sol"; +import {ForkUtils} from "utils/ForkUtils.sol"; import "utils/TestUtils.sol"; import "utils/DeployZeroEx.sol"; import "forge-std/Test.sol"; @@ -33,7 +33,9 @@ contract WrapEth is Test, ForkUtils, TestUtils { DeployZeroEx.ZeroExDeployed zeroExDeployed; function setUp() public { - zeroExDeployed = new DeployZeroEx().deployZeroEx(); + zeroExDeployed = new DeployZeroEx( + DeployZeroEx.ZeroExDeployConfiguration(address(0), address(0), address(0), 0, 0, 0, true) + ).deployZeroEx(); vm.deal(address(this), 1e19); } diff --git a/contracts/zero-ex/tests/forked/RfqtV2Test.t.sol b/contracts/zero-ex/tests/forked/RfqtV2Test.t.sol index 819393d427..29342915a6 100644 --- a/contracts/zero-ex/tests/forked/RfqtV2Test.t.sol +++ b/contracts/zero-ex/tests/forked/RfqtV2Test.t.sol @@ -103,7 +103,7 @@ contract RfqtV2Test is Test, ForkUtils, TestUtils { order.makerAmount = 1e18; order.takerAmount = 1e18; uint privateKey; - (order.maker, privateKey) = getSigner(); + (order.maker, privateKey) = _getSigner(); deal(address(order.makerToken), order.maker, 1e20); vm.prank(order.maker); IERC20Token(tokens.USDC).approve(address(addresses.exchangeProxy), 1e20); diff --git a/contracts/zero-ex/tests/forked/WrapEthTest.t.sol b/contracts/zero-ex/tests/forked/WrapEthTest.t.sol index 0ad736c93c..53158c145f 100644 --- a/contracts/zero-ex/tests/forked/WrapEthTest.t.sol +++ b/contracts/zero-ex/tests/forked/WrapEthTest.t.sol @@ -15,7 +15,7 @@ pragma solidity ^0.6; pragma experimental ABIEncoderV2; -import "../utils/ForkUtils.sol"; +import {ForkUtils} from "../utils/ForkUtils.sol"; import "../utils/TestUtils.sol"; import "../utils/DeployZeroEx.sol"; import "forge-std/Test.sol"; @@ -33,7 +33,9 @@ contract WrapEthTest is Test, ForkUtils, TestUtils { DeployZeroEx.ZeroExDeployed zeroExDeployed; function setUp() public { - zeroExDeployed = new DeployZeroEx().deployZeroEx(); + zeroExDeployed = new DeployZeroEx( + DeployZeroEx.ZeroExDeployConfiguration(address(0), address(0), address(0), 0, 0, 0, true) + ).deployZeroEx(); vm.deal(address(this), 1e19); } diff --git a/contracts/zero-ex/tests/utils/DeployZeroEx.sol b/contracts/zero-ex/tests/utils/DeployZeroEx.sol index bb176fd580..9d9f231a60 100644 --- a/contracts/zero-ex/tests/utils/DeployZeroEx.sol +++ b/contracts/zero-ex/tests/utils/DeployZeroEx.sol @@ -31,9 +31,11 @@ import "src/features/MetaTransactionsFeature.sol"; import "src/features/nft_orders/ERC1155OrdersFeature.sol"; import "src/features/nft_orders/ERC721OrdersFeature.sol"; import "src/features/UniswapFeature.sol"; +import "src/features/UniswapV3Feature.sol"; import "src/features/multiplex/MultiplexFeature.sol"; import "src/external/TransformerDeployer.sol"; import "src/external/FeeCollectorController.sol"; +import "src/external/LiquidityProviderSandbox.sol"; import "src/transformers/WethTransformer.sol"; import "src/transformers/FillQuoteTransformer.sol"; import "src/transformers/PayTakerTransformer.sol"; @@ -62,6 +64,7 @@ contract DeployZeroEx is Test { BatchFillNativeOrdersFeature batchFillNativeOrdersFeature; OtcOrdersFeature otcOrdersFeature; UniswapFeature uniswapFeature; + UniswapV3Feature uniswapV3Feature; FundRecoveryFeature fundRecoveryFeature; TransformERC20Feature transformERC20Feature; MetaTransactionsFeature metaTransactionsFeature; @@ -89,6 +92,22 @@ contract DeployZeroEx is Test { ZeroExDeployed ZERO_EX_DEPLOYED; + struct ZeroExDeployConfiguration { + address uniswapFactory; + address sushiswapFactory; + address uniswapV3Factory; + bytes32 uniswapPairInitCodeHash; + bytes32 sushiswapPairInitCodeHash; + bytes32 uniswapV3PoolInitCodeHash; + bool logDeployed; + } + + ZeroExDeployConfiguration ZERO_EX_DEPLOY_CONFIG; + + constructor(ZeroExDeployConfiguration memory configuration) public { + ZERO_EX_DEPLOY_CONFIG = configuration; + } + function getDeployedZeroEx() public returns (ZeroExDeployed memory) { if (!isDeployed) { deployZeroEx(); @@ -111,6 +130,7 @@ contract DeployZeroEx is Test { ); emit log_named_address("OtcOrdersFeature", address(ZERO_EX_DEPLOYED.features.otcOrdersFeature)); emit log_named_address("UniswapFeature", address(ZERO_EX_DEPLOYED.features.uniswapFeature)); + emit log_named_address("UniswapV3Feature", address(ZERO_EX_DEPLOYED.features.uniswapV3Feature)); emit log_named_address("FundRecoveryFeature", address(ZERO_EX_DEPLOYED.features.fundRecoveryFeature)); emit log_named_address("MetaTransactionsFeature", address(ZERO_EX_DEPLOYED.features.metaTransactionsFeature)); emit log_named_address("ERC1155OrdersFeature", address(ZERO_EX_DEPLOYED.features.erc1155OrdersFeature)); @@ -174,6 +194,11 @@ contract DeployZeroEx is Test { ZERO_EX_DEPLOYED.features.batchFillNativeOrdersFeature = new BatchFillNativeOrdersFeature(address(ZERO_EX)); ZERO_EX_DEPLOYED.features.otcOrdersFeature = new OtcOrdersFeature(address(ZERO_EX), ZERO_EX_DEPLOYED.weth); ZERO_EX_DEPLOYED.features.uniswapFeature = new UniswapFeature(ZERO_EX_DEPLOYED.weth); + ZERO_EX_DEPLOYED.features.uniswapV3Feature = new UniswapV3Feature( + ZERO_EX_DEPLOYED.weth, + ZERO_EX_DEPLOY_CONFIG.uniswapV3Factory, + ZERO_EX_DEPLOY_CONFIG.uniswapV3PoolInitCodeHash + ); ZERO_EX_DEPLOYED.features.fundRecoveryFeature = new FundRecoveryFeature(); ZERO_EX_DEPLOYED.features.metaTransactionsFeature = new MetaTransactionsFeature(address(ZERO_EX)); ZERO_EX_DEPLOYED.features.erc1155OrdersFeature = new ERC1155OrdersFeature( @@ -187,11 +212,11 @@ contract DeployZeroEx is Test { ZERO_EX_DEPLOYED.features.multiplexFeature = new MultiplexFeature( address(ZERO_EX), ZERO_EX_DEPLOYED.weth, - ILiquidityProviderSandbox(address(0)), - address(0), // uniswapFactory - address(0), // sushiswapFactory - bytes32(0), // uniswapPairInitCodeHash - bytes32(0) // sushiswapPairInitCodeHash + new LiquidityProviderSandbox(address(ZERO_EX)), + ZERO_EX_DEPLOY_CONFIG.uniswapFactory, + ZERO_EX_DEPLOY_CONFIG.sushiswapFactory, + ZERO_EX_DEPLOY_CONFIG.uniswapPairInitCodeHash, + ZERO_EX_DEPLOY_CONFIG.sushiswapPairInitCodeHash ); initialMigration.initializeZeroEx( @@ -230,6 +255,11 @@ contract DeployZeroEx is Test { abi.encodeWithSelector(UniswapFeature.migrate.selector), address(this) ); + IZERO_EX.migrate( + address(ZERO_EX_DEPLOYED.features.uniswapV3Feature), + abi.encodeWithSelector(UniswapV3Feature.migrate.selector), + address(this) + ); IZERO_EX.migrate( address(ZERO_EX_DEPLOYED.features.fundRecoveryFeature), abi.encodeWithSelector(FundRecoveryFeature.migrate.selector), @@ -293,7 +323,10 @@ contract DeployZeroEx is Test { ZERO_EX_DEPLOYED.zeroEx = IZERO_EX; isDeployed = true; - logDeployedZeroEx(); + if (ZERO_EX_DEPLOY_CONFIG.logDeployed) { + logDeployedZeroEx(); + } + return ZERO_EX_DEPLOYED; } } diff --git a/contracts/zero-ex/tests/utils/ForkUtils.sol b/contracts/zero-ex/tests/utils/ForkUtils.sol index 4fef842678..54164367cd 100644 --- a/contracts/zero-ex/tests/utils/ForkUtils.sol +++ b/contracts/zero-ex/tests/utils/ForkUtils.sol @@ -227,15 +227,6 @@ contract ForkUtils is Test { } } - //gets a dummy signer to be used for an OTC order - function getSigner() public returns (address, uint) { - string memory mnemonic = "test test test test test test test test test test test junk"; - uint256 privateKey = vm.deriveKey(mnemonic, 0); - - vm.label(vm.addr(privateKey), "zeroEx/MarketMaker"); - return (vm.addr(privateKey), privateKey); - } - //read the uniswapV2 router addresses from file function readLiquiditySourceAddresses() internal returns (string memory) { string memory root = vm.projectRoot(); diff --git a/contracts/zero-ex/tests/utils/LocalTest.sol b/contracts/zero-ex/tests/utils/LocalTest.sol new file mode 100644 index 0000000000..d127d1b910 --- /dev/null +++ b/contracts/zero-ex/tests/utils/LocalTest.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + Copyright 2023 ZeroEx Intl. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +pragma solidity ^0.6; +pragma experimental ABIEncoderV2; + +import {Test} from "forge-std/Test.sol"; +import {IERC20Token} from "@0x/contracts-erc20/src/IERC20Token.sol"; +import {IEtherToken} from "@0x/contracts-erc20/src/IEtherToken.sol"; +import {WETH9V06} from "@0x/contracts-erc20/src/v06/WETH9V06.sol"; +import {IFlashWallet} from "src/external/IFlashWallet.sol"; +import {LibERC20Transformer} from "src/transformers/LibERC20Transformer.sol"; +import {LibNativeOrder} from "src/features/libs/LibNativeOrder.sol"; +import {LibSignature} from "src/features/libs/LibSignature.sol"; +import {IMultiplexFeature} from "src/features/interfaces/IMultiplexFeature.sol"; +import {ITransformERC20Feature} from "src/features/interfaces/ITransformERC20Feature.sol"; +import {TestUtils} from "utils/TestUtils.sol"; +import {DeployZeroEx} from "utils/DeployZeroEx.sol"; +import {TestMintTokenERC20Transformer} from "../../contracts/test/TestMintTokenERC20Transformer.sol"; +import {TestMintableERC20Token} from "../../contracts/test/tokens/TestMintableERC20Token.sol"; +import {TestUniswapV2Factory} from "../../contracts/test/integration/TestUniswapV2Factory.sol"; +import {TestUniswapV2Pool} from "../../contracts/test/integration/TestUniswapV2Pool.sol"; +import {TestUniswapV3Factory} from "../../contracts/test/integration/TestUniswapV3Factory.sol"; +import {TestUniswapV3Pool} from "../../contracts/test/integration/TestUniswapV3Pool.sol"; +import {TestLiquidityProvider} from "../../contracts/test/integration/TestLiquidityProvider.sol"; + +contract LocalTest is Test, TestUtils { + uint24 internal constant UNIV3_POOL_FEE = 1234; + + DeployZeroEx.ZeroExDeployed internal zeroExDeployed; + IFlashWallet internal flashWallet; + IERC20Token internal shib; + IERC20Token internal dai; + IERC20Token internal zrx; + IEtherToken internal weth; + + TestUniswapV2Factory internal sushiFactory; + TestUniswapV2Factory internal uniV2Factory; + TestUniswapV3Factory internal uniV3Factory; + TestLiquidityProvider internal liquidityProvider; + uint256 internal transformerNonce; + + address internal signerAddress; + uint256 internal signerKey; + + function _infiniteApprovals() private { + shib.approve(address(zeroExDeployed.zeroEx), type(uint256).max); + dai.approve(address(zeroExDeployed.zeroEx), type(uint256).max); + zrx.approve(address(zeroExDeployed.zeroEx), type(uint256).max); + weth.approve(address(zeroExDeployed.zeroEx), type(uint256).max); + } + + function setUp() public { + (signerAddress, signerKey) = _getSigner(); + + sushiFactory = new TestUniswapV2Factory(); + uniV2Factory = new TestUniswapV2Factory(); + uniV3Factory = new TestUniswapV3Factory(); + liquidityProvider = new TestLiquidityProvider(); + + zeroExDeployed = new DeployZeroEx( + DeployZeroEx.ZeroExDeployConfiguration({ + uniswapFactory: address(uniV2Factory), + sushiswapFactory: address(sushiFactory), + uniswapV3Factory: address(uniV3Factory), + uniswapPairInitCodeHash: uniV2Factory.POOL_INIT_CODE_HASH(), + sushiswapPairInitCodeHash: sushiFactory.POOL_INIT_CODE_HASH(), + uniswapV3PoolInitCodeHash: uniV3Factory.POOL_INIT_CODE_HASH(), + logDeployed: false + }) + ).deployZeroEx(); + + transformerNonce = zeroExDeployed.transformerDeployer.nonce(); + vm.prank(zeroExDeployed.transformerDeployer.authorities(0)); + zeroExDeployed.transformerDeployer.deploy(type(TestMintTokenERC20Transformer).creationCode); + + flashWallet = zeroExDeployed.zeroEx.getTransformWallet(); + + shib = IERC20Token(address(new TestMintableERC20Token())); + dai = IERC20Token(address(new TestMintableERC20Token())); + zrx = IERC20Token(address(new TestMintableERC20Token())); + weth = zeroExDeployed.weth; + + _infiniteApprovals(); + vm.startPrank(signerAddress); + _infiniteApprovals(); + vm.stopPrank(); + + vm.deal(address(this), 10e18); + } + + function _mintTo(address token, address recipient, uint256 amount) internal { + if (token == address(weth)) { + IEtherToken(token).deposit{value: amount}(); + WETH9V06(payable(token)).transfer(recipient, amount); + } else { + TestMintableERC20Token(token).mint(recipient, amount); + } + } + + function _createUniswapV2Pool( + TestUniswapV2Factory factory, + IERC20Token tokenA, + IERC20Token tokenB, + uint112 balanceA, + uint112 balanceB + ) internal returns (address poolAddress) { + TestUniswapV2Pool pool = factory.createPool(tokenA, tokenB); + _mintTo(address(tokenA), address(pool), balanceA); + _mintTo(address(tokenB), address(pool), balanceB); + + (uint112 balance0, uint112 balance1) = tokenA < tokenB ? (balanceA, balanceB) : (balanceB, balanceA); + pool.setReserves(balance0, balance1, 0); + + return address(pool); + } + + function _createUniswapV3Pool( + TestUniswapV3Factory factory, + IERC20Token tokenA, + IERC20Token tokenB, + uint112 balanceA, + uint112 balanceB + ) internal returns (address poolAddress) { + poolAddress = address(factory.createPool(tokenA, tokenB, UNIV3_POOL_FEE)); + _mintTo(address(tokenA), poolAddress, balanceA); + _mintTo(address(tokenB), poolAddress, balanceB); + } +} diff --git a/contracts/zero-ex/tests/utils/MultiplexUtils.sol b/contracts/zero-ex/tests/utils/MultiplexUtils.sol new file mode 100644 index 0000000000..5b6a8370cd --- /dev/null +++ b/contracts/zero-ex/tests/utils/MultiplexUtils.sol @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + + Copyright 2023 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6; +pragma experimental ABIEncoderV2; + +import {IERC20Token} from "@0x/contracts-erc20/src/IERC20Token.sol"; +import {LibNativeOrder} from "src/features/libs/LibNativeOrder.sol"; +import {LibSignature} from "src/features/libs/LibSignature.sol"; +import {IMultiplexFeature} from "src/features/interfaces/IMultiplexFeature.sol"; +import {ITransformERC20Feature} from "src/features/interfaces/ITransformERC20Feature.sol"; +import {LocalTest} from "utils/LocalTest.sol"; + +contract MultiplexUtils is LocalTest { + function _makeTestRfqOrder( + IERC20Token makerToken, + IERC20Token takerToken + ) internal returns (LibNativeOrder.RfqOrder memory order) { + order = LibNativeOrder.RfqOrder({ + makerToken: makerToken, + takerToken: takerToken, + makerAmount: 1e18, + takerAmount: 1e18, + maker: signerAddress, + taker: address(this), + txOrigin: tx.origin, + pool: 0x0000000000000000000000000000000000000000000000000000000000000000, + expiry: uint64(block.timestamp + 60), + salt: 123 + }); + _mintTo(address(order.makerToken), order.maker, order.makerAmount); + } + + function _makeTestRfqOrder() internal returns (LibNativeOrder.RfqOrder memory order) { + return _makeTestRfqOrder(zrx, dai); + } + + function _makeTestOtcOrder() internal returns (LibNativeOrder.OtcOrder memory order) { + order = LibNativeOrder.OtcOrder({ + makerToken: zrx, + takerToken: dai, + makerAmount: 1e18, + takerAmount: 1e18, + maker: signerAddress, + taker: address(this), + txOrigin: tx.origin, + expiryAndNonce: ((block.timestamp + 60) << 192) | 1 + }); + _mintTo(address(order.makerToken), order.maker, order.makerAmount); + } + + function _makeRfqSubcall( + LibNativeOrder.RfqOrder memory order, + uint256 sellAmount + ) internal view returns (IMultiplexFeature.BatchSellSubcall memory) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + signerKey, + zeroExDeployed.features.nativeOrdersFeature.getRfqOrderHash(order) + ); + LibSignature.Signature memory sig = LibSignature.Signature(LibSignature.SignatureType.EIP712, v, r, s); + + return + IMultiplexFeature.BatchSellSubcall({ + id: IMultiplexFeature.MultiplexSubcall.RFQ, + sellAmount: sellAmount, + data: abi.encode(order, sig) + }); + } + + function _makeRfqSubcall( + LibNativeOrder.RfqOrder memory order + ) internal view returns (IMultiplexFeature.BatchSellSubcall memory) { + return _makeRfqSubcall(order, order.takerAmount); + } + + function _makeOtcSubcall( + LibNativeOrder.OtcOrder memory order + ) internal view returns (IMultiplexFeature.BatchSellSubcall memory) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + signerKey, + zeroExDeployed.features.otcOrdersFeature.getOtcOrderHash(order) + ); + LibSignature.Signature memory sig = LibSignature.Signature(LibSignature.SignatureType.EIP712, v, r, s); + + return + IMultiplexFeature.BatchSellSubcall({ + id: IMultiplexFeature.MultiplexSubcall.OTC, + sellAmount: order.takerAmount, + data: abi.encode(order, sig) + }); + } + + function _makeUniswapV2MultiHopSubcall( + address[] memory tokens, + bool isSushi + ) internal pure returns (IMultiplexFeature.MultiHopSellSubcall memory) { + return + IMultiplexFeature.MultiHopSellSubcall({ + id: IMultiplexFeature.MultiplexSubcall.UniswapV2, + data: abi.encode(tokens, isSushi) + }); + } + + function _makeUniswapV2BatchSubcall( + address[] memory tokens, + uint256 sellAmount, + bool isSushi + ) internal pure returns (IMultiplexFeature.BatchSellSubcall memory) { + return + IMultiplexFeature.BatchSellSubcall({ + id: IMultiplexFeature.MultiplexSubcall.UniswapV2, + sellAmount: sellAmount, + data: abi.encode(tokens, isSushi) + }); + } + + function _encodePathUniswapV3(address[] memory tokens) private pure returns (bytes memory path) { + path = new bytes(tokens.length * 23 - 3); + for (uint256 i = 0; i < tokens.length; i++) { + assembly { + let p := add(add(path, 32), mul(i, 23)) + if gt(i, 0) { + mstore(sub(p, 3), shl(232, UNIV3_POOL_FEE)) + } + + let a := add(add(tokens, 32), mul(i, 32)) + mstore(p, shl(96, mload(a))) + } + } + } + + function _makeUniswapV3MultiHopSubcall( + address[] memory tokens + ) internal pure returns (IMultiplexFeature.MultiHopSellSubcall memory) { + return + IMultiplexFeature.MultiHopSellSubcall({ + id: IMultiplexFeature.MultiplexSubcall.UniswapV3, + data: _encodePathUniswapV3(tokens) + }); + } + + function _makeUniswapV3BatchSubcall( + address[] memory tokens, + uint256 sellAmount + ) internal pure returns (IMultiplexFeature.BatchSellSubcall memory) { + return + IMultiplexFeature.BatchSellSubcall({ + id: IMultiplexFeature.MultiplexSubcall.UniswapV3, + sellAmount: sellAmount, + data: _encodePathUniswapV3(tokens) + }); + } + + function _makeMockLiquidityProviderMultiHopSubcall() + internal + view + returns (IMultiplexFeature.MultiHopSellSubcall memory) + { + return + IMultiplexFeature.MultiHopSellSubcall({ + id: IMultiplexFeature.MultiplexSubcall.LiquidityProvider, + data: abi.encode(address(liquidityProvider), hex"") + }); + } + + function _makeMockLiquidityProviderBatchSubcall( + uint256 sellAmount + ) internal view returns (IMultiplexFeature.BatchSellSubcall memory) { + return + IMultiplexFeature.BatchSellSubcall({ + id: IMultiplexFeature.MultiplexSubcall.LiquidityProvider, + sellAmount: sellAmount, + data: abi.encode(address(liquidityProvider), hex"") + }); + } + + function _makeMockTransformERC20Subcall( + IERC20Token inputToken, + IERC20Token outputToken, + uint256 sellAmount, + uint256 mintAmount + ) internal view returns (IMultiplexFeature.BatchSellSubcall memory) { + ITransformERC20Feature.Transformation[] memory transformations = new ITransformERC20Feature.Transformation[](1); + transformations[0] = ITransformERC20Feature.Transformation( + uint32(transformerNonce), + abi.encode(address(inputToken), address(outputToken), sellAmount, mintAmount, 0) + ); + + return + IMultiplexFeature.BatchSellSubcall({ + id: IMultiplexFeature.MultiplexSubcall.TransformERC20, + sellAmount: sellAmount, + data: abi.encode(transformations) + }); + } + + function _makeNestedBatchSellSubcall( + IMultiplexFeature.BatchSellSubcall[] memory calls + ) internal pure returns (IMultiplexFeature.MultiHopSellSubcall memory) { + return + IMultiplexFeature.MultiHopSellSubcall({ + id: IMultiplexFeature.MultiplexSubcall.BatchSell, + data: abi.encode(calls) + }); + } + + function _makeNestedMultiHopSellSubcall( + address[] memory tokens, + IMultiplexFeature.MultiHopSellSubcall[] memory calls, + uint256 sellAmount + ) internal pure returns (IMultiplexFeature.BatchSellSubcall memory) { + return + IMultiplexFeature.BatchSellSubcall({ + id: IMultiplexFeature.MultiplexSubcall.MultiHopSell, + sellAmount: sellAmount, + data: abi.encode(tokens, calls) + }); + } + + function _makeArray( + IMultiplexFeature.MultiHopSellSubcall memory first + ) internal pure returns (IMultiplexFeature.MultiHopSellSubcall[] memory subcalls) { + subcalls = new IMultiplexFeature.MultiHopSellSubcall[](1); + subcalls[0] = first; + } + + function _makeArray( + IMultiplexFeature.MultiHopSellSubcall memory first, + IMultiplexFeature.MultiHopSellSubcall memory second + ) internal pure returns (IMultiplexFeature.MultiHopSellSubcall[] memory subcalls) { + subcalls = new IMultiplexFeature.MultiHopSellSubcall[](2); + subcalls[0] = first; + subcalls[1] = second; + } + + function _makeArray( + IMultiplexFeature.BatchSellSubcall memory first + ) internal pure returns (IMultiplexFeature.BatchSellSubcall[] memory subcalls) { + subcalls = new IMultiplexFeature.BatchSellSubcall[](1); + subcalls[0] = first; + } + + function _makeArray( + IMultiplexFeature.BatchSellSubcall memory first, + IMultiplexFeature.BatchSellSubcall memory second + ) internal pure returns (IMultiplexFeature.BatchSellSubcall[] memory subcalls) { + subcalls = new IMultiplexFeature.BatchSellSubcall[](2); + subcalls[0] = first; + subcalls[1] = second; + } + + function _makeArray( + IMultiplexFeature.BatchSellSubcall memory first, + IMultiplexFeature.BatchSellSubcall memory second, + IMultiplexFeature.BatchSellSubcall memory third + ) internal pure returns (IMultiplexFeature.BatchSellSubcall[] memory subcalls) { + subcalls = new IMultiplexFeature.BatchSellSubcall[](3); + subcalls[0] = first; + subcalls[1] = second; + subcalls[2] = third; + } + + function _makeArray(address first) internal pure returns (address[] memory addresses) { + addresses = new address[](1); + addresses[0] = first; + } + + function _makeArray(address first, address second) internal pure returns (address[] memory addresses) { + addresses = new address[](2); + addresses[0] = first; + addresses[1] = second; + } + + function _makeArray( + address first, + address second, + address third + ) internal pure returns (address[] memory addresses) { + addresses = new address[](3); + addresses[0] = first; + addresses[1] = second; + addresses[2] = third; + } + + function _encodeFractionalFillAmount(uint256 frac) internal pure returns (uint256) { + return (2 ** 255) + (frac * 1e16); + } +} diff --git a/contracts/zero-ex/tests/utils/TestUtils.sol b/contracts/zero-ex/tests/utils/TestUtils.sol index b4e3ab858e..d9375fbf3e 100644 --- a/contracts/zero-ex/tests/utils/TestUtils.sol +++ b/contracts/zero-ex/tests/utils/TestUtils.sol @@ -29,4 +29,13 @@ contract TestUtils is Test { } } } + + // gets a dummy signer + function _getSigner() internal returns (address, uint) { + string memory mnemonic = "test test test test test test test test test test test junk"; + uint256 privateKey = vm.deriveKey(mnemonic, 0); + + vm.label(vm.addr(privateKey), "zeroEx/MarketMaker"); + return (vm.addr(privateKey), privateKey); + } }