added _multiHopSellOtcOrder and new path for _executeMultiHopSell
This commit is contained in:
@@ -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
|
||||
|
@@ -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 {}
|
||||
}
|
||||
}
|
||||
|
156
contracts/zero-ex/tests/forked/MultiplexRfqt.t.sol
Normal file
156
contracts/zero-ex/tests/forked/MultiplexRfqt.t.sol
Normal 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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user