added _multiHopSellOtcOrder and new path for _executeMultiHopSell

This commit is contained in:
dextracker
2023-02-21 21:58:08 -05:00
parent 459fb3ee28
commit 4a7a5d1c7a
3 changed files with 202 additions and 1 deletions

View File

@@ -416,7 +416,9 @@ contract MultiplexFeature is
_multiHopSellLiquidityProvider(state, params, subcall.data);
} else if (subcall.id == MultiplexSubcall.BatchSell) {
_nestedBatchSell(state, params, subcall.data);
} else {
} else if (subcall.id == MultiplexSubcall.OTC){
_multiHopSellOtcOrder(state, params, subcall.data);
}else {
revert("MultiplexFeature::_executeMultiHopSell/INVALID_SUBCALL");
}
// The recipient of the current hop will be the source

View File

@@ -65,4 +65,47 @@ abstract contract MultiplexOtc is FixinEIP712 {
state.boughtAmount = state.boughtAmount.safeAdd(makerTokenFilledAmount);
} catch {}
}
function _multiHopSellOtcOrder(
IMultiplexFeature.MultiHopSellState memory state,
IMultiplexFeature.MultiHopSellParams memory params,
bytes memory wrappedCallData
) internal {
// Decode the tokens[], Otc order, and signature.
(address[] memory tokens, LibNativeOrder.OtcOrder memory order, LibSignature.Signature memory signature) = abi.decode(
wrappedCallData,
(address[], LibNativeOrder.OtcOrder, LibSignature.Signature)
);
// Validate tokens.
require(
tokens.length >= 2 &&
tokens[0] == params.tokens[state.hopIndex] &&
tokens[tokens.length - 1] == params.tokens[state.hopIndex + 1],
"MultiplexOtcOrder::_multiHopSellOtcOrder/INVALID_TOKENS"
);
// Pre-emptively check if the order is expired.
uint64 expiry = uint64(order.expiryAndNonce >> 192);
if (expiry <= uint64(block.timestamp)) {
bytes32 orderHash = _getEIP712Hash(LibNativeOrder.getOtcOrderStructHash(order));
emit ExpiredOtcOrder(orderHash, order.maker, expiry);
return;
}
uint256 sellAmount = state.outputTokenAmount
// Try filling the Otc order. Swallows reverts.
try
IOtcOrdersFeature(address(this))._fillOtcOrder(
order,
signature,
sellAmount.safeDowncastToUint128(),
msg.sender,
params.useSelfBalance,
params.recipient
)
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount) {
//store the amount bought from the otc order
state.outputTokenAmount = makerTokenFilledAmount;
} catch {}
}
}

View File

@@ -0,0 +1,156 @@
// 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 "../utils/ForkUtils.sol";
import "../utils/TestUtils.sol";
import "src/IZeroEx.sol";
import "@0x/contracts-erc20/src/IEtherToken.sol";
import "src/features/TransformERC20Feature.sol";
import "src/external/TransformerDeployer.sol";
import "src/transformers/WethTransformer.sol";
import "src/transformers/FillQuoteTransformer.sol";
import "src/transformers/bridges/BridgeProtocols.sol";
import "src/features/OtcOrdersFeature.sol";
contract MultiplexRfqtTest is Test, ForkUtils, TestUtils {
function setUp() public {
_setup();
}
function test_swapEthForUSDTThroughFqtOtcs() public {
log_string("SwapEthForUSDTThroughFqtOtc");
/* */
for (uint256 i = 0; i < 1; i++) {
//skip fantom/avax failing test
if (i == 3 || i == 4) {
continue;
}
vm.selectFork(forkIds[chains[i]]);
log_named_string(" Selecting Fork On", chains[i]);
vm.deal(address(this), 1e18);
labelAddresses(
chains[i],
indexChainsByChain[chains[i]],
getTokens(i),
getContractAddresses(i),
getLiquiditySourceAddresses(i)
);
swapWithOtcOrder(getTokens(i), getContractAddresses(i), getLiquiditySourceAddresses(i));
}
}
/* solhint-disable function-max-lines */
function swapWithOtcOrder(
TokenAddresses memory tokens,
ContractAddresses memory addresses,
LiquiditySources memory sources
) public onlyForked {
IZERO_EX = IZeroEx(addresses.exchangeProxy);
address USDC = address(tokens.USDC);
// Create our list of transformations, let's do WethTransformer and FillQuoteTransformer
ITransformERC20Feature.Transformation[] memory transformations = new ITransformERC20Feature.Transformation[](2);
// Use our cheeky search helper to find the nonce rather than hardcode it
transformations[0].deploymentNonce = _findTransformerNonce(
address(addresses.transformers.wethTransformer),
address(addresses.exchangeProxyTransformerDeployer)
);
emit log_named_uint(" WethTransformer nonce", transformations[0].deploymentNonce);
createNewFQT(tokens.WrappedNativeToken, addresses.exchangeProxy, addresses.exchangeProxyTransformerDeployer);
// Set the first transformation to transform ETH into WETH
transformations[0].data = abi.encode(LibERC20Transformer.ETH_TOKEN_ADDRESS, 1e18);
transformations[1].deploymentNonce = _findTransformerNonce(
address(fillQuoteTransformer),
address(addresses.exchangeProxyTransformerDeployer)
);
log_named_uint(" FillQuoteTransformer nonce", transformations[1].deploymentNonce);
// Set up the FillQuoteTransformer data
FillQuoteTransformer.TransformData memory fqtData;
fqtData.side = FillQuoteTransformer.Side.Sell;
fqtData.sellToken = IERC20Token(address(tokens.WrappedNativeToken));
fqtData.buyToken = tokens.USDC;
// the FQT has a sequence, e.g first RFQ then Limit then Bridge
// since solidity doesn't support arrays of different types, this is one simple solution
// We use a Bridge order type here as we will fill on UniswapV2
fqtData.fillSequence = new FillQuoteTransformer.OrderType[](1);
fqtData.fillSequence[0] = FillQuoteTransformer.OrderType.Otc;
// The amount to fill
fqtData.fillAmount = 1e18;
// Now let's set up an OTC fill
fqtData.otcOrders = new FillQuoteTransformer.OtcOrderInfo[](1);
LibNativeOrder.OtcOrder memory order;
FillQuoteTransformer.OtcOrderInfo memory orderInfo;
order.makerToken = fqtData.buyToken;
order.takerToken = fqtData.sellToken;
order.makerAmount = 1e18;
order.takerAmount = 1e18;
uint privateKey;
(order.maker, privateKey) = getSigner();
deal(address(order.makerToken), order.maker, 1e20);
vm.prank(order.maker);
IERC20Token(tokens.USDC).approve(address(addresses.exchangeProxy), 1e20);
vm.prank(order.maker);
order.taker = address(0);
order.txOrigin = address(tx.origin);
order.expiryAndNonce = encodeExpiryAndNonce(order.maker);
orderInfo.order = order;
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, IZERO_EX.getOtcOrderHash(order));
// How much we want to fill on this order, which can be different to the total
// e.g 50/50 split this would be half
order.takerAmount = 1e18;
// Set this low as the price of ETH/USDC can change
order.makerAmount = 1e18;
orderInfo.signature.signatureType = LibSignature.SignatureType.EIP712;
orderInfo.signature.v = v;
orderInfo.signature.r = r;
orderInfo.signature.s = s;
orderInfo.maxTakerTokenFillAmount = 1e18;
fqtData.otcOrders[0] = orderInfo;
transformations[1].data = abi.encode(fqtData);
log_string(" Successful fill, makerTokens bought");
IZERO_EX.transformERC20{value: 1e18}(
// input token
IERC20Token(LibERC20Transformer.ETH_TOKEN_ADDRESS),
// output token
tokens.USDC,
// input token amount
order.takerAmount,
// min output token amount
order.makerAmount,
// list of transform
transformations
);
assert(tokens.USDC.balanceOf(address(this)) > 0);
}
/* solhint-enable function-max-lines */
function encodeExpiryAndNonce(address maker) public returns (uint256) {
uint256 expiry = (block.timestamp + 120) << 192;
uint256 bucket = 0 << 128;
uint256 nonce = vm.getNonce(maker);
return expiry | bucket | nonce;
}
}