Feat/multiplex/v2 (#263)
* Refactor Multiplex into multiple files * Pull UniswapV3 into separate file * Add support for multihop nested within batch sell * Add useSelfBalance and recipient to _fillRfqOrder * Expose onlySelf variant in UniswapV3Feature for Multiplex * Add useSelfBalance and recipient to _transformERC20 * Add support for proportional fill amounts in batchSell * Comments and renaming * Unit tests * Use caps for immutables * Rename taker -> recipient in TransformContext and SettleOrderInfo * lint * Address nits * Swallow reverts for LiquidityProvider and UniswapV2 batch sells * Address spot-check findings (#279) * Check didSucceed in _callWithOptionalBooleanResult * Add takerToken=ETH support to OtcOrdersFeature (#287) * Add takerToken=ETH support to OtcOrdersFeature * Add batchFillTakerSignedOtcOrders * Add support for OTC to Multiplex * Address PR feedback * Update TransformERC20Feature (#303) * remove multiplex_utils * Update changelog * unbreak tests
This commit is contained in:
parent
692231c2ea
commit
b46eeadc64
@ -28,7 +28,7 @@ library LibERC20TokenV06 {
|
|||||||
bytes constant private DECIMALS_CALL_DATA = hex"313ce567";
|
bytes constant private DECIMALS_CALL_DATA = hex"313ce567";
|
||||||
|
|
||||||
/// @dev Calls `IERC20TokenV06(token).approve()`.
|
/// @dev Calls `IERC20TokenV06(token).approve()`.
|
||||||
/// Reverts if the result fails `isSuccessfulResult()` or the call reverts.
|
/// Reverts if the return data is invalid or the call reverts.
|
||||||
/// @param token The address of the token contract.
|
/// @param token The address of the token contract.
|
||||||
/// @param spender The address that receives an allowance.
|
/// @param spender The address that receives an allowance.
|
||||||
/// @param allowance The allowance to set.
|
/// @param allowance The allowance to set.
|
||||||
@ -49,7 +49,7 @@ library LibERC20TokenV06 {
|
|||||||
|
|
||||||
/// @dev Calls `IERC20TokenV06(token).approve()` and sets the allowance to the
|
/// @dev Calls `IERC20TokenV06(token).approve()` and sets the allowance to the
|
||||||
/// maximum if the current approval is not already >= an amount.
|
/// maximum if the current approval is not already >= an amount.
|
||||||
/// Reverts if the result fails `isSuccessfulResult()` or the call reverts.
|
/// Reverts if the return data is invalid or the call reverts.
|
||||||
/// @param token The address of the token contract.
|
/// @param token The address of the token contract.
|
||||||
/// @param spender The address that receives an allowance.
|
/// @param spender The address that receives an allowance.
|
||||||
/// @param amount The minimum allowance needed.
|
/// @param amount The minimum allowance needed.
|
||||||
@ -66,7 +66,7 @@ library LibERC20TokenV06 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Calls `IERC20TokenV06(token).transfer()`.
|
/// @dev Calls `IERC20TokenV06(token).transfer()`.
|
||||||
/// Reverts if the result fails `isSuccessfulResult()` or the call reverts.
|
/// Reverts if the return data is invalid or the call reverts.
|
||||||
/// @param token The address of the token contract.
|
/// @param token The address of the token contract.
|
||||||
/// @param to The address that receives the tokens
|
/// @param to The address that receives the tokens
|
||||||
/// @param amount Number of tokens to transfer.
|
/// @param amount Number of tokens to transfer.
|
||||||
@ -86,7 +86,7 @@ library LibERC20TokenV06 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Calls `IERC20TokenV06(token).transferFrom()`.
|
/// @dev Calls `IERC20TokenV06(token).transferFrom()`.
|
||||||
/// Reverts if the result fails `isSuccessfulResult()` or the call reverts.
|
/// Reverts if the return data is invalid or the call reverts.
|
||||||
/// @param token The address of the token contract.
|
/// @param token The address of the token contract.
|
||||||
/// @param from The owner of the tokens.
|
/// @param from The owner of the tokens.
|
||||||
/// @param to The address that receives the tokens
|
/// @param to The address that receives the tokens
|
||||||
@ -168,27 +168,6 @@ library LibERC20TokenV06 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Check if the data returned by a non-static call to an ERC20 token
|
|
||||||
/// is a successful result. Supported functions are `transfer()`,
|
|
||||||
/// `transferFrom()`, and `approve()`.
|
|
||||||
/// @param resultData The raw data returned by a non-static call to the ERC20 token.
|
|
||||||
/// @return isSuccessful Whether the result data indicates success.
|
|
||||||
function isSuccessfulResult(bytes memory resultData)
|
|
||||||
internal
|
|
||||||
pure
|
|
||||||
returns (bool isSuccessful)
|
|
||||||
{
|
|
||||||
if (resultData.length == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (resultData.length >= 32) {
|
|
||||||
uint256 result = LibBytesV06.readUint256(resultData, 0);
|
|
||||||
if (result == 1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Executes a call on address `target` with calldata `callData`
|
/// @dev Executes a call on address `target` with calldata `callData`
|
||||||
/// and asserts that either nothing was returned or a single boolean
|
/// and asserts that either nothing was returned or a single boolean
|
||||||
/// was returned equal to `true`.
|
/// was returned equal to `true`.
|
||||||
@ -201,9 +180,31 @@ library LibERC20TokenV06 {
|
|||||||
private
|
private
|
||||||
{
|
{
|
||||||
(bool didSucceed, bytes memory resultData) = target.call(callData);
|
(bool didSucceed, bytes memory resultData) = target.call(callData);
|
||||||
if (didSucceed && isSuccessfulResult(resultData)) {
|
// Revert if the call reverted.
|
||||||
|
if (!didSucceed) {
|
||||||
|
LibRichErrorsV06.rrevert(resultData);
|
||||||
|
}
|
||||||
|
// If we get back 0 returndata, this may be a non-standard ERC-20 that
|
||||||
|
// does not return a boolean. Check that it at least contains code.
|
||||||
|
if (resultData.length == 0) {
|
||||||
|
uint256 size;
|
||||||
|
assembly { size := extcodesize(target) }
|
||||||
|
require(size > 0, "invalid token address, contains no code");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// If we get back at least 32 bytes, we know the target address
|
||||||
|
// contains code, and we assume it is a token that returned a boolean
|
||||||
|
// success value, which must be true.
|
||||||
|
if (resultData.length >= 32) {
|
||||||
|
uint256 result = LibBytesV06.readUint256(resultData, 0);
|
||||||
|
if (result == 1) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
LibRichErrorsV06.rrevert(resultData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If 0 < returndatasize < 32, the target is a contract, but not a
|
||||||
|
// valid token.
|
||||||
LibRichErrorsV06.rrevert(resultData);
|
LibRichErrorsV06.rrevert(resultData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,6 @@ export function verifyEventsFromLogs<TEventArgs>(
|
|||||||
const _logs = filterLogsToArguments<TEventArgs>(logs, eventName);
|
const _logs = filterLogsToArguments<TEventArgs>(logs, eventName);
|
||||||
expect(_logs.length, `Number of ${eventName} events emitted`).to.eq(expectedEvents.length);
|
expect(_logs.length, `Number of ${eventName} events emitted`).to.eq(expectedEvents.length);
|
||||||
_logs.forEach((log, index) => {
|
_logs.forEach((log, index) => {
|
||||||
expect(log, `${eventName} event ${index}`).to.deep.equal(expectedEvents[index]);
|
expect(log, `${eventName} event ${index}`).to.deep.equal({ ...log, ...expectedEvents[index] });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -21,13 +21,10 @@ pragma solidity ^0.6.12;
|
|||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
|
||||||
import "./IStaking.sol";
|
import "./IStaking.sol";
|
||||||
|
|
||||||
|
|
||||||
contract DefaultPoolOperator {
|
contract DefaultPoolOperator {
|
||||||
using LibERC20TokenV06 for IERC20TokenV06;
|
|
||||||
|
|
||||||
// Immutables
|
// Immutables
|
||||||
IStaking public immutable stakingProxy;
|
IStaking public immutable stakingProxy;
|
||||||
IERC20TokenV06 public immutable weth;
|
IERC20TokenV06 public immutable weth;
|
||||||
@ -57,7 +54,7 @@ contract DefaultPoolOperator {
|
|||||||
function returnStakingRewards()
|
function returnStakingRewards()
|
||||||
external
|
external
|
||||||
{
|
{
|
||||||
uint256 wethBalance = weth.compatBalanceOf(address(this));
|
uint256 wethBalance = weth.balanceOf(address(this));
|
||||||
weth.compatTransfer(address(stakingProxy), wethBalance);
|
weth.transfer(address(stakingProxy), wethBalance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,25 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "0.28.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Transfer output tokens in TransformERC20Feature",
|
||||||
|
"pr": 279
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Add support for takerToken=0xeee... in OtcOrdersFeature",
|
||||||
|
"pr": 287
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Add support for OTC orders in MultiplexFeature",
|
||||||
|
"pr": 287
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Multiplex v2: Refactor into multiple files, add ETH support, and other miscellanea",
|
||||||
|
"pr": 263
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1628665757,
|
"timestamp": 1628665757,
|
||||||
"version": "0.27.1",
|
"version": "0.27.1",
|
||||||
|
@ -88,23 +88,6 @@ library LibNativeOrdersRichErrors {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function OrderNotSignedByTakerError(
|
|
||||||
bytes32 orderHash,
|
|
||||||
address signer,
|
|
||||||
address taker
|
|
||||||
)
|
|
||||||
internal
|
|
||||||
pure
|
|
||||||
returns (bytes memory)
|
|
||||||
{
|
|
||||||
return abi.encodeWithSelector(
|
|
||||||
bytes4(keccak256("OrderNotSignedByTakerError(bytes32,address,address)")),
|
|
||||||
orderHash,
|
|
||||||
signer,
|
|
||||||
taker
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function InvalidSignerError(
|
function InvalidSignerError(
|
||||||
address maker,
|
address maker,
|
||||||
address signer
|
address signer
|
||||||
|
@ -48,7 +48,7 @@ contract BatchFillNativeOrdersFeature is
|
|||||||
/// @dev Name of this feature.
|
/// @dev Name of this feature.
|
||||||
string public constant override FEATURE_NAME = "BatchFill";
|
string public constant override FEATURE_NAME = "BatchFill";
|
||||||
/// @dev Version of this feature.
|
/// @dev Version of this feature.
|
||||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
|
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 1, 0);
|
||||||
|
|
||||||
constructor(address zeroExAddress)
|
constructor(address zeroExAddress)
|
||||||
public
|
public
|
||||||
@ -170,6 +170,8 @@ contract BatchFillNativeOrdersFeature is
|
|||||||
orders[i],
|
orders[i],
|
||||||
signatures[i],
|
signatures[i],
|
||||||
takerTokenFillAmounts[i],
|
takerTokenFillAmounts[i],
|
||||||
|
msg.sender,
|
||||||
|
false,
|
||||||
msg.sender
|
msg.sender
|
||||||
)
|
)
|
||||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||||
|
@ -78,7 +78,7 @@ contract MetaTransactionsFeature is
|
|||||||
/// @dev Name of this feature.
|
/// @dev Name of this feature.
|
||||||
string public constant override FEATURE_NAME = "MetaTransactions";
|
string public constant override FEATURE_NAME = "MetaTransactions";
|
||||||
/// @dev Version of this feature.
|
/// @dev Version of this feature.
|
||||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 1, 1);
|
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 2, 0);
|
||||||
/// @dev EIP712 typehash of the `MetaTransactionData` struct.
|
/// @dev EIP712 typehash of the `MetaTransactionData` struct.
|
||||||
bytes32 public immutable MTX_EIP712_TYPEHASH = keccak256(
|
bytes32 public immutable MTX_EIP712_TYPEHASH = keccak256(
|
||||||
"MetaTransactionData("
|
"MetaTransactionData("
|
||||||
@ -415,7 +415,9 @@ contract MetaTransactionsFeature is
|
|||||||
outputToken: args.outputToken,
|
outputToken: args.outputToken,
|
||||||
inputTokenAmount: args.inputTokenAmount,
|
inputTokenAmount: args.inputTokenAmount,
|
||||||
minOutputTokenAmount: args.minOutputTokenAmount,
|
minOutputTokenAmount: args.minOutputTokenAmount,
|
||||||
transformations: args.transformations
|
transformations: args.transformations,
|
||||||
|
useSelfBalance: false,
|
||||||
|
recipient: state.mtx.signer
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
state.mtx.value
|
state.mtx.value
|
||||||
@ -498,7 +500,9 @@ contract MetaTransactionsFeature is
|
|||||||
order,
|
order,
|
||||||
signature,
|
signature,
|
||||||
takerTokenFillAmount,
|
takerTokenFillAmount,
|
||||||
state.mtx.signer // taker is mtx signer
|
state.mtx.signer, // taker is mtx signer
|
||||||
|
false,
|
||||||
|
state.mtx.signer
|
||||||
),
|
),
|
||||||
state.mtx.value
|
state.mtx.value
|
||||||
);
|
);
|
||||||
|
@ -1,820 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
/*
|
|
||||||
|
|
||||||
Copyright 2021 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.5;
|
|
||||||
pragma experimental ABIEncoderV2;
|
|
||||||
|
|
||||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
|
||||||
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
|
||||||
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
|
||||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
|
||||||
import "../external/ILiquidityProviderSandbox.sol";
|
|
||||||
import "../fixins/FixinCommon.sol";
|
|
||||||
import "../fixins/FixinEIP712.sol";
|
|
||||||
import "../fixins/FixinTokenSpender.sol";
|
|
||||||
import "../migrations/LibMigrate.sol";
|
|
||||||
import "../transformers/LibERC20Transformer.sol";
|
|
||||||
import "../vendor/ILiquidityProvider.sol";
|
|
||||||
import "../vendor/IUniswapV2Pair.sol";
|
|
||||||
import "./interfaces/IFeature.sol";
|
|
||||||
import "./interfaces/IMultiplexFeature.sol";
|
|
||||||
import "./interfaces/INativeOrdersFeature.sol";
|
|
||||||
import "./interfaces/ITransformERC20Feature.sol";
|
|
||||||
import "./interfaces/IUniswapV3Feature.sol";
|
|
||||||
import "./libs/LibNativeOrder.sol";
|
|
||||||
|
|
||||||
|
|
||||||
/// @dev This feature enables efficient batch and multi-hop trades
|
|
||||||
/// using different liquidity sources.
|
|
||||||
contract MultiplexFeature is
|
|
||||||
IFeature,
|
|
||||||
IMultiplexFeature,
|
|
||||||
FixinCommon,
|
|
||||||
FixinEIP712,
|
|
||||||
FixinTokenSpender
|
|
||||||
{
|
|
||||||
using LibERC20Transformer for IERC20TokenV06;
|
|
||||||
using LibSafeMathV06 for uint128;
|
|
||||||
using LibSafeMathV06 for uint256;
|
|
||||||
|
|
||||||
/// @dev Name of this feature.
|
|
||||||
string public constant override FEATURE_NAME = "MultiplexFeature";
|
|
||||||
/// @dev Version of this feature.
|
|
||||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 1, 0);
|
|
||||||
|
|
||||||
/// @dev The WETH token contract.
|
|
||||||
IEtherTokenV06 private immutable weth;
|
|
||||||
/// @dev The sandbox contract address.
|
|
||||||
ILiquidityProviderSandbox public immutable sandbox;
|
|
||||||
// address of the UniswapV2Factory contract.
|
|
||||||
address private constant UNISWAP_FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
|
|
||||||
// address of the (Sushiswap) UniswapV2Factory contract.
|
|
||||||
address private constant SUSHISWAP_FACTORY = 0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac;
|
|
||||||
// Init code hash of the UniswapV2Pair contract.
|
|
||||||
uint256 private constant UNISWAP_PAIR_INIT_CODE_HASH = 0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f;
|
|
||||||
// Init code hash of the (Sushiswap) UniswapV2Pair contract.
|
|
||||||
uint256 private constant SUSHISWAP_PAIR_INIT_CODE_HASH = 0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
address zeroExAddress,
|
|
||||||
IEtherTokenV06 weth_,
|
|
||||||
ILiquidityProviderSandbox sandbox_
|
|
||||||
)
|
|
||||||
public
|
|
||||||
FixinEIP712(zeroExAddress)
|
|
||||||
{
|
|
||||||
weth = weth_;
|
|
||||||
sandbox = sandbox_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Initialize and register this feature.
|
|
||||||
/// Should be delegatecalled by `Migrate.migrate()`.
|
|
||||||
/// @return success `LibMigrate.SUCCESS` on success.
|
|
||||||
function migrate()
|
|
||||||
external
|
|
||||||
returns (bytes4 success)
|
|
||||||
{
|
|
||||||
_registerFeatureFunction(this.batchFill.selector);
|
|
||||||
_registerFeatureFunction(this.multiHopFill.selector);
|
|
||||||
return LibMigrate.MIGRATE_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Executes a batch of fills selling `fillData.inputToken`
|
|
||||||
/// for `fillData.outputToken` in sequence. Refer to the
|
|
||||||
/// internal variant `_batchFill` for the allowed nested
|
|
||||||
/// operations.
|
|
||||||
/// @param fillData Encodes the input/output tokens, the sell
|
|
||||||
/// amount, and the nested operations for this batch fill.
|
|
||||||
/// @param minBuyAmount The minimum amount of `fillData.outputToken`
|
|
||||||
/// to buy. Reverts if this amount is not met.
|
|
||||||
/// @return outputTokenAmount The amount of the output token bought.
|
|
||||||
function batchFill(
|
|
||||||
BatchFillData memory fillData,
|
|
||||||
uint256 minBuyAmount
|
|
||||||
)
|
|
||||||
public
|
|
||||||
payable
|
|
||||||
override
|
|
||||||
returns (uint256 outputTokenAmount)
|
|
||||||
{
|
|
||||||
// Cache the sender's balance of the output token.
|
|
||||||
outputTokenAmount = fillData.outputToken.getTokenBalanceOf(msg.sender);
|
|
||||||
// Cache the contract's ETH balance prior to this call.
|
|
||||||
uint256 ethBalanceBefore = address(this).balance.safeSub(msg.value);
|
|
||||||
|
|
||||||
// Perform the batch fill.
|
|
||||||
_batchFill(fillData);
|
|
||||||
|
|
||||||
// The `outputTokenAmount` returned by `_batchFill` may not
|
|
||||||
// be fully accurate (e.g. due to some janky token).
|
|
||||||
outputTokenAmount = fillData.outputToken.getTokenBalanceOf(msg.sender)
|
|
||||||
.safeSub(outputTokenAmount);
|
|
||||||
require(
|
|
||||||
outputTokenAmount >= minBuyAmount,
|
|
||||||
"MultiplexFeature::batchFill/UNDERBOUGHT"
|
|
||||||
);
|
|
||||||
|
|
||||||
uint256 ethBalanceAfter = address(this).balance;
|
|
||||||
require(
|
|
||||||
ethBalanceAfter >= ethBalanceBefore,
|
|
||||||
"MultiplexFeature::batchFill/OVERSPENT_ETH"
|
|
||||||
);
|
|
||||||
// Refund ETH
|
|
||||||
if (ethBalanceAfter > ethBalanceBefore) {
|
|
||||||
_transferEth(msg.sender, ethBalanceAfter - ethBalanceBefore);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Executes a sequence of fills "hopping" through the
|
|
||||||
/// path of tokens given by `fillData.tokens`. Refer to the
|
|
||||||
/// internal variant `_multiHopFill` for the allowed nested
|
|
||||||
/// operations.
|
|
||||||
/// @param fillData Encodes the path of tokens, the sell amount,
|
|
||||||
/// and the nested operations for this multi-hop fill.
|
|
||||||
/// @param minBuyAmount The minimum amount of the output token
|
|
||||||
/// to buy. Reverts if this amount is not met.
|
|
||||||
/// @return outputTokenAmount The amount of the output token bought.
|
|
||||||
function multiHopFill(
|
|
||||||
MultiHopFillData memory fillData,
|
|
||||||
uint256 minBuyAmount
|
|
||||||
)
|
|
||||||
public
|
|
||||||
payable
|
|
||||||
override
|
|
||||||
returns (uint256 outputTokenAmount)
|
|
||||||
{
|
|
||||||
IERC20TokenV06 outputToken = IERC20TokenV06(fillData.tokens[fillData.tokens.length - 1]);
|
|
||||||
// Cache the sender's balance of the output token.
|
|
||||||
outputTokenAmount = outputToken.getTokenBalanceOf(msg.sender);
|
|
||||||
// Cache the contract's ETH balance prior to this call.
|
|
||||||
uint256 ethBalanceBefore = address(this).balance.safeSub(msg.value);
|
|
||||||
|
|
||||||
// Perform the multi-hop fill. Pass in `msg.value` as the maximum
|
|
||||||
// allowable amount of ETH for the wrapped calls to consume.
|
|
||||||
_multiHopFill(fillData, msg.value);
|
|
||||||
|
|
||||||
// The `outputTokenAmount` returned by `_multiHopFill` may not
|
|
||||||
// be fully accurate (e.g. due to some janky token).
|
|
||||||
outputTokenAmount = outputToken.getTokenBalanceOf(msg.sender)
|
|
||||||
.safeSub(outputTokenAmount);
|
|
||||||
require(
|
|
||||||
outputTokenAmount >= minBuyAmount,
|
|
||||||
"MultiplexFeature::multiHopFill/UNDERBOUGHT"
|
|
||||||
);
|
|
||||||
|
|
||||||
uint256 ethBalanceAfter = address(this).balance;
|
|
||||||
require(
|
|
||||||
ethBalanceAfter >= ethBalanceBefore,
|
|
||||||
"MultiplexFeature::multiHopFill/OVERSPENT_ETH"
|
|
||||||
);
|
|
||||||
// Refund ETH
|
|
||||||
if (ethBalanceAfter > ethBalanceBefore) {
|
|
||||||
_transferEth(msg.sender, ethBalanceAfter - ethBalanceBefore);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Similar to FQT. If `fillData.sellAmount` is set to `type(uint256).max`,
|
|
||||||
// this is effectively a batch fill. Otherwise it can be set to perform a
|
|
||||||
// market sell of some amount. Note that the `outputTokenAmount` returned
|
|
||||||
// by this function could theoretically be inaccurate if `msg.sender` has
|
|
||||||
// set a token allowance on an external contract that gets called during
|
|
||||||
// the execution of this function.
|
|
||||||
function _batchFill(BatchFillData memory fillData)
|
|
||||||
internal
|
|
||||||
returns (uint256 outputTokenAmount, uint256 remainingEth)
|
|
||||||
{
|
|
||||||
// Track the remaining ETH allocated to this call.
|
|
||||||
remainingEth = msg.value;
|
|
||||||
// Track the amount of input token sold.
|
|
||||||
uint256 soldAmount;
|
|
||||||
for (uint256 i = 0; i != fillData.calls.length; i++) {
|
|
||||||
// Check if we've hit our target.
|
|
||||||
if (soldAmount >= fillData.sellAmount) { break; }
|
|
||||||
WrappedBatchCall memory wrappedCall = fillData.calls[i];
|
|
||||||
// Compute the fill amount.
|
|
||||||
uint256 inputTokenAmount = LibSafeMathV06.min256(
|
|
||||||
wrappedCall.sellAmount,
|
|
||||||
fillData.sellAmount.safeSub(soldAmount)
|
|
||||||
);
|
|
||||||
if (wrappedCall.selector == INativeOrdersFeature._fillRfqOrder.selector) {
|
|
||||||
// Decode the RFQ order and signature.
|
|
||||||
(
|
|
||||||
LibNativeOrder.RfqOrder memory order,
|
|
||||||
LibSignature.Signature memory signature
|
|
||||||
) = abi.decode(
|
|
||||||
wrappedCall.data,
|
|
||||||
(LibNativeOrder.RfqOrder, LibSignature.Signature)
|
|
||||||
);
|
|
||||||
if (order.expiry <= uint64(block.timestamp)) {
|
|
||||||
bytes32 orderHash = _getEIP712Hash(
|
|
||||||
LibNativeOrder.getRfqOrderStructHash(order)
|
|
||||||
);
|
|
||||||
emit ExpiredRfqOrder(
|
|
||||||
orderHash,
|
|
||||||
order.maker,
|
|
||||||
order.expiry
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
require(
|
|
||||||
order.takerToken == fillData.inputToken &&
|
|
||||||
order.makerToken == fillData.outputToken,
|
|
||||||
"MultiplexFeature::_batchFill/RFQ_ORDER_INVALID_TOKENS"
|
|
||||||
);
|
|
||||||
// Try filling the RFQ order. Swallows reverts.
|
|
||||||
try
|
|
||||||
INativeOrdersFeature(address(this))._fillRfqOrder
|
|
||||||
(
|
|
||||||
order,
|
|
||||||
signature,
|
|
||||||
inputTokenAmount.safeDowncastToUint128(),
|
|
||||||
msg.sender
|
|
||||||
)
|
|
||||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
|
||||||
{
|
|
||||||
// Increment the sold and bought amounts.
|
|
||||||
soldAmount = soldAmount.safeAdd(takerTokenFilledAmount);
|
|
||||||
outputTokenAmount = outputTokenAmount.safeAdd(makerTokenFilledAmount);
|
|
||||||
} catch {}
|
|
||||||
} else if (wrappedCall.selector == this._sellToUniswap.selector) {
|
|
||||||
(address[] memory tokens, bool isSushi) = abi.decode(
|
|
||||||
wrappedCall.data,
|
|
||||||
(address[], bool)
|
|
||||||
);
|
|
||||||
require(
|
|
||||||
tokens.length >= 2 &&
|
|
||||||
tokens[0] == address(fillData.inputToken) &&
|
|
||||||
tokens[tokens.length - 1] == address(fillData.outputToken),
|
|
||||||
"MultiplexFeature::_batchFill/UNISWAP_INVALID_TOKENS"
|
|
||||||
);
|
|
||||||
// Perform the Uniswap/Sushiswap trade.
|
|
||||||
uint256 outputTokenAmount_ = _sellToUniswap(
|
|
||||||
tokens,
|
|
||||||
inputTokenAmount,
|
|
||||||
isSushi,
|
|
||||||
address(0),
|
|
||||||
msg.sender
|
|
||||||
);
|
|
||||||
// Increment the sold and bought amounts.
|
|
||||||
soldAmount = soldAmount.safeAdd(inputTokenAmount);
|
|
||||||
outputTokenAmount = outputTokenAmount.safeAdd(outputTokenAmount_);
|
|
||||||
} else if (wrappedCall.selector == IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector) {
|
|
||||||
(bool success, bytes memory resultData) = address(this).delegatecall(
|
|
||||||
abi.encodeWithSelector(
|
|
||||||
IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector,
|
|
||||||
wrappedCall.data,
|
|
||||||
inputTokenAmount,
|
|
||||||
0,
|
|
||||||
msg.sender
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (success) {
|
|
||||||
uint256 outputTokenAmount_ = abi.decode(resultData, (uint256));
|
|
||||||
// Increment the sold and bought amounts.
|
|
||||||
soldAmount = soldAmount.safeAdd(inputTokenAmount);
|
|
||||||
outputTokenAmount = outputTokenAmount.safeAdd(outputTokenAmount_);
|
|
||||||
}
|
|
||||||
} else if (wrappedCall.selector == this._sellToLiquidityProvider.selector) {
|
|
||||||
(address provider, bytes memory auxiliaryData) = abi.decode(
|
|
||||||
wrappedCall.data,
|
|
||||||
(address, bytes)
|
|
||||||
);
|
|
||||||
if (fillData.inputToken.isTokenETH()) {
|
|
||||||
inputTokenAmount = LibSafeMathV06.min256(
|
|
||||||
inputTokenAmount,
|
|
||||||
remainingEth
|
|
||||||
);
|
|
||||||
// Transfer the input ETH to the provider.
|
|
||||||
_transferEth(payable(provider), inputTokenAmount);
|
|
||||||
// Count that ETH as spent.
|
|
||||||
remainingEth -= inputTokenAmount;
|
|
||||||
} else {
|
|
||||||
// Transfer input ERC20 tokens to the provider.
|
|
||||||
_transferERC20TokensFrom(
|
|
||||||
fillData.inputToken,
|
|
||||||
msg.sender,
|
|
||||||
provider,
|
|
||||||
inputTokenAmount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Perform the PLP trade.
|
|
||||||
uint256 outputTokenAmount_ = _sellToLiquidityProvider(
|
|
||||||
fillData.inputToken,
|
|
||||||
fillData.outputToken,
|
|
||||||
inputTokenAmount,
|
|
||||||
ILiquidityProvider(provider),
|
|
||||||
msg.sender,
|
|
||||||
auxiliaryData
|
|
||||||
);
|
|
||||||
// Increment the sold and bought amounts.
|
|
||||||
soldAmount = soldAmount.safeAdd(inputTokenAmount);
|
|
||||||
outputTokenAmount = outputTokenAmount.safeAdd(outputTokenAmount_);
|
|
||||||
} else if (wrappedCall.selector == ITransformERC20Feature._transformERC20.selector) {
|
|
||||||
ITransformERC20Feature.TransformERC20Args memory args;
|
|
||||||
args.taker = msg.sender;
|
|
||||||
args.inputToken = fillData.inputToken;
|
|
||||||
args.outputToken = fillData.outputToken;
|
|
||||||
args.inputTokenAmount = inputTokenAmount;
|
|
||||||
args.minOutputTokenAmount = 0;
|
|
||||||
uint256 ethValue;
|
|
||||||
(args.transformations, ethValue) = abi.decode(
|
|
||||||
wrappedCall.data,
|
|
||||||
(ITransformERC20Feature.Transformation[], uint256)
|
|
||||||
);
|
|
||||||
// Do not spend more than the remaining ETH.
|
|
||||||
ethValue = LibSafeMathV06.min256(
|
|
||||||
ethValue,
|
|
||||||
remainingEth
|
|
||||||
);
|
|
||||||
if (ethValue > 0) {
|
|
||||||
require(
|
|
||||||
args.inputToken.isTokenETH(),
|
|
||||||
"MultiplexFeature::_batchFill/ETH_TRANSFORM_ONLY"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
try ITransformERC20Feature(address(this))._transformERC20
|
|
||||||
{value: ethValue}
|
|
||||||
(args)
|
|
||||||
returns (uint256 outputTokenAmount_)
|
|
||||||
{
|
|
||||||
remainingEth -= ethValue;
|
|
||||||
soldAmount = soldAmount.safeAdd(inputTokenAmount);
|
|
||||||
outputTokenAmount = outputTokenAmount.safeAdd(outputTokenAmount_);
|
|
||||||
} catch {}
|
|
||||||
} else if (wrappedCall.selector == this._multiHopFill.selector) {
|
|
||||||
MultiHopFillData memory multiHopFillData;
|
|
||||||
uint256 ethValue;
|
|
||||||
(
|
|
||||||
multiHopFillData.tokens,
|
|
||||||
multiHopFillData.calls,
|
|
||||||
ethValue
|
|
||||||
) = abi.decode(
|
|
||||||
wrappedCall.data,
|
|
||||||
(address[], WrappedMultiHopCall[], uint256)
|
|
||||||
);
|
|
||||||
multiHopFillData.sellAmount = inputTokenAmount;
|
|
||||||
// Do not spend more than the remaining ETH.
|
|
||||||
ethValue = LibSafeMathV06.min256(
|
|
||||||
ethValue,
|
|
||||||
remainingEth
|
|
||||||
);
|
|
||||||
// Subtract the ethValue allocated to the nested multi-hop fill.
|
|
||||||
remainingEth -= ethValue;
|
|
||||||
(uint256 outputTokenAmount_, uint256 leftoverEth) =
|
|
||||||
_multiHopFill(multiHopFillData, ethValue);
|
|
||||||
// Increment the sold and bought amounts.
|
|
||||||
soldAmount = soldAmount.safeAdd(inputTokenAmount);
|
|
||||||
outputTokenAmount = outputTokenAmount.safeAdd(outputTokenAmount_);
|
|
||||||
// Add back any ETH that wasn't used by the nested multi-hop fill.
|
|
||||||
remainingEth += leftoverEth;
|
|
||||||
} else {
|
|
||||||
revert("MultiplexFeature::_batchFill/UNRECOGNIZED_SELECTOR");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal variant of `multiHopFill`. This function can be nested within
|
|
||||||
// a `_batchFill`.
|
|
||||||
// This function executes a sequence of fills "hopping" through the
|
|
||||||
// path of tokens given by `fillData.tokens`. The nested operations that
|
|
||||||
// can be used as "hops" are:
|
|
||||||
// - WETH.deposit (wraps ETH)
|
|
||||||
// - WETH.withdraw (unwraps WETH)
|
|
||||||
// - _sellToUniswap (executes a Uniswap/Sushiswap swap)
|
|
||||||
// - _sellToLiquidityProvider (executes a PLP swap)
|
|
||||||
// - _transformERC20 (executes arbitrary ERC20 Transformations)
|
|
||||||
// This function optimizes the number of ERC20 transfers performed
|
|
||||||
// by having each hop transfer its output tokens directly to the
|
|
||||||
// target address of the next hop. Note that the `outputTokenAmount` returned
|
|
||||||
// by this function could theoretically be inaccurate if `msg.sender` has
|
|
||||||
// set a token allowance on an external contract that gets called during
|
|
||||||
// the execution of this function.
|
|
||||||
function _multiHopFill(MultiHopFillData memory fillData, uint256 totalEth)
|
|
||||||
public
|
|
||||||
returns (uint256 outputTokenAmount, uint256 remainingEth)
|
|
||||||
{
|
|
||||||
// There should be one call/hop between every two tokens
|
|
||||||
// in the path.
|
|
||||||
// tokens[0]––calls[0]––>tokens[1]––...––calls[n-1]––>tokens[n]
|
|
||||||
require(
|
|
||||||
fillData.tokens.length == fillData.calls.length + 1,
|
|
||||||
"MultiplexFeature::_multiHopFill/MISMATCHED_ARRAY_LENGTHS"
|
|
||||||
);
|
|
||||||
// Track the remaining ETH allocated to this call.
|
|
||||||
remainingEth = totalEth;
|
|
||||||
// This variable is used as the input and output amounts of
|
|
||||||
// each hop. After the final hop, this will contain the output
|
|
||||||
// amount of the multi-hop fill.
|
|
||||||
outputTokenAmount = fillData.sellAmount;
|
|
||||||
// This variable is used to cache the address to target in the
|
|
||||||
// next hop. See `_computeHopRecipient` for details.
|
|
||||||
address nextTarget;
|
|
||||||
for (uint256 i = 0; i != fillData.calls.length; i++) {
|
|
||||||
WrappedMultiHopCall memory wrappedCall = fillData.calls[i];
|
|
||||||
if (wrappedCall.selector == this._sellToUniswap.selector) {
|
|
||||||
// If the next hop supports a "transfer then execute" pattern,
|
|
||||||
// the recipient will not be `msg.sender`. See `_computeHopRecipient`
|
|
||||||
// for details.
|
|
||||||
address recipient = _computeHopRecipient(fillData.calls, i);
|
|
||||||
(address[] memory tokens, bool isSushi) = abi.decode(
|
|
||||||
wrappedCall.data,
|
|
||||||
(address[], bool)
|
|
||||||
);
|
|
||||||
// Perform the Uniswap/Sushiswap trade.
|
|
||||||
outputTokenAmount = _sellToUniswap(
|
|
||||||
tokens,
|
|
||||||
outputTokenAmount,
|
|
||||||
isSushi,
|
|
||||||
nextTarget,
|
|
||||||
recipient
|
|
||||||
);
|
|
||||||
// If the recipient was not `msg.sender`, it must be the target
|
|
||||||
// contract for the next hop.
|
|
||||||
nextTarget = recipient == msg.sender ? address(0) : recipient;
|
|
||||||
} else if (wrappedCall.selector == this._sellToLiquidityProvider.selector) {
|
|
||||||
// If the next hop supports a "transfer then execute" pattern,
|
|
||||||
// the recipient will not be `msg.sender`. See `_computeHopRecipient`
|
|
||||||
// for details.
|
|
||||||
address recipient = _computeHopRecipient(fillData.calls, i);
|
|
||||||
// If `nextTarget` was not set in the previous hop, then we
|
|
||||||
// need to send in the input ETH/tokens to the liquidity provider
|
|
||||||
// contract before executing the trade.
|
|
||||||
if (nextTarget == address(0)) {
|
|
||||||
(address provider, bytes memory auxiliaryData) = abi.decode(
|
|
||||||
wrappedCall.data,
|
|
||||||
(address, bytes)
|
|
||||||
);
|
|
||||||
// Transfer input ETH or ERC20 tokens to the liquidity
|
|
||||||
// provider contract.
|
|
||||||
if (IERC20TokenV06(fillData.tokens[i]).isTokenETH()) {
|
|
||||||
outputTokenAmount = LibSafeMathV06.min256(
|
|
||||||
outputTokenAmount,
|
|
||||||
remainingEth
|
|
||||||
);
|
|
||||||
_transferEth(payable(provider), outputTokenAmount);
|
|
||||||
remainingEth -= outputTokenAmount;
|
|
||||||
} else {
|
|
||||||
_transferERC20TokensFrom(
|
|
||||||
IERC20TokenV06(fillData.tokens[i]),
|
|
||||||
msg.sender,
|
|
||||||
provider,
|
|
||||||
outputTokenAmount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
outputTokenAmount = _sellToLiquidityProvider(
|
|
||||||
IERC20TokenV06(fillData.tokens[i]),
|
|
||||||
IERC20TokenV06(fillData.tokens[i + 1]),
|
|
||||||
outputTokenAmount,
|
|
||||||
ILiquidityProvider(provider),
|
|
||||||
recipient,
|
|
||||||
auxiliaryData
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
(, bytes memory auxiliaryData) = abi.decode(
|
|
||||||
wrappedCall.data,
|
|
||||||
(address, bytes)
|
|
||||||
);
|
|
||||||
// Tokens and ETH have already been transferred to
|
|
||||||
// the liquidity provider contract in the previous hop.
|
|
||||||
outputTokenAmount = _sellToLiquidityProvider(
|
|
||||||
IERC20TokenV06(fillData.tokens[i]),
|
|
||||||
IERC20TokenV06(fillData.tokens[i + 1]),
|
|
||||||
outputTokenAmount,
|
|
||||||
ILiquidityProvider(nextTarget),
|
|
||||||
recipient,
|
|
||||||
auxiliaryData
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// If the recipient was not `msg.sender`, it must be the target
|
|
||||||
// contract for the next hop.
|
|
||||||
nextTarget = recipient == msg.sender ? address(0) : recipient;
|
|
||||||
} else if (wrappedCall.selector == ITransformERC20Feature._transformERC20.selector) {
|
|
||||||
ITransformERC20Feature.TransformERC20Args memory args;
|
|
||||||
args.inputToken = IERC20TokenV06(fillData.tokens[i]);
|
|
||||||
args.outputToken = IERC20TokenV06(fillData.tokens[i + 1]);
|
|
||||||
args.minOutputTokenAmount = 0;
|
|
||||||
args.taker = payable(_computeHopRecipient(fillData.calls, i));
|
|
||||||
if (nextTarget != address(0)) {
|
|
||||||
// If `nextTarget` was set in the previous hop, then the input
|
|
||||||
// token was already sent to the FlashWallet. Setting
|
|
||||||
// `inputTokenAmount` to 0 indicates that no tokens need to
|
|
||||||
// be pulled into the FlashWallet before executing the
|
|
||||||
// transformations.
|
|
||||||
args.inputTokenAmount = 0;
|
|
||||||
} else if (
|
|
||||||
args.taker != msg.sender &&
|
|
||||||
!args.inputToken.isTokenETH()
|
|
||||||
) {
|
|
||||||
address flashWallet = address(
|
|
||||||
ITransformERC20Feature(address(this)).getTransformWallet()
|
|
||||||
);
|
|
||||||
// The input token has _not_ already been sent to the
|
|
||||||
// FlashWallet. We also want PayTakerTransformer to
|
|
||||||
// send the output token to some address other than
|
|
||||||
// msg.sender, so we must transfer the input token
|
|
||||||
// to the FlashWallet here.
|
|
||||||
_transferERC20TokensFrom(
|
|
||||||
args.inputToken,
|
|
||||||
msg.sender,
|
|
||||||
flashWallet,
|
|
||||||
outputTokenAmount
|
|
||||||
);
|
|
||||||
args.inputTokenAmount = 0;
|
|
||||||
} else {
|
|
||||||
// Otherwise, either:
|
|
||||||
// (1) args.taker == msg.sender, in which case
|
|
||||||
// `_transformERC20` will pull the input token
|
|
||||||
// into the FlashWallet, or
|
|
||||||
// (2) args.inputToken == ETH_TOKEN_ADDRESS, in which
|
|
||||||
// case ETH is attached to the call and no token
|
|
||||||
// transfer occurs.
|
|
||||||
args.inputTokenAmount = outputTokenAmount;
|
|
||||||
}
|
|
||||||
uint256 ethValue;
|
|
||||||
(args.transformations, ethValue) = abi.decode(
|
|
||||||
wrappedCall.data,
|
|
||||||
(ITransformERC20Feature.Transformation[], uint256)
|
|
||||||
);
|
|
||||||
// Do not spend more than the remaining ETH.
|
|
||||||
ethValue = LibSafeMathV06.min256(ethValue, remainingEth);
|
|
||||||
if (ethValue > 0) {
|
|
||||||
require(
|
|
||||||
args.inputToken.isTokenETH(),
|
|
||||||
"MultiplexFeature::_multiHopFill/ETH_TRANSFORM_ONLY"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Call `_transformERC20`.
|
|
||||||
outputTokenAmount = ITransformERC20Feature(address(this))
|
|
||||||
._transformERC20{value: ethValue}(args);
|
|
||||||
// Decrement the remaining ETH.
|
|
||||||
remainingEth -= ethValue;
|
|
||||||
// If the recipient was not `msg.sender`, it must be the target
|
|
||||||
// contract for the next hop.
|
|
||||||
nextTarget = args.taker == msg.sender ? address(0) : args.taker;
|
|
||||||
} else if (wrappedCall.selector == IEtherTokenV06.deposit.selector) {
|
|
||||||
require(
|
|
||||||
i == 0,
|
|
||||||
"MultiplexFeature::_multiHopFill/DEPOSIT_FIRST_HOP_ONLY"
|
|
||||||
);
|
|
||||||
uint256 ethValue = LibSafeMathV06.min256(outputTokenAmount, remainingEth);
|
|
||||||
// Wrap ETH.
|
|
||||||
weth.deposit{value: ethValue}();
|
|
||||||
nextTarget = _computeHopRecipient(fillData.calls, i);
|
|
||||||
weth.transfer(nextTarget, ethValue);
|
|
||||||
remainingEth -= ethValue;
|
|
||||||
} else if (wrappedCall.selector == IEtherTokenV06.withdraw.selector) {
|
|
||||||
require(
|
|
||||||
i == fillData.calls.length - 1,
|
|
||||||
"MultiplexFeature::_multiHopFill/WITHDRAW_LAST_HOP_ONLY"
|
|
||||||
);
|
|
||||||
// Unwrap WETH and send to `msg.sender`.
|
|
||||||
weth.withdraw(outputTokenAmount);
|
|
||||||
_transferEth(msg.sender, outputTokenAmount);
|
|
||||||
nextTarget = address(0);
|
|
||||||
} else {
|
|
||||||
revert("MultiplexFeature::_multiHopFill/UNRECOGNIZED_SELECTOR");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Similar to the UniswapFeature, but with a couple of differences:
|
|
||||||
// - Does not perform the transfer in if `pairAddress` is given,
|
|
||||||
// which indicates that the transfer in was already performed
|
|
||||||
// in the previous hop of a multi-hop fill.
|
|
||||||
// - Does not include a minBuyAmount check (which is performed in
|
|
||||||
// either `batchFill` or `multiHopFill`).
|
|
||||||
// - Takes a `recipient` address parameter, so the output of the
|
|
||||||
// final `swap` call can be sent to an address other than `msg.sender`.
|
|
||||||
function _sellToUniswap(
|
|
||||||
address[] memory tokens,
|
|
||||||
uint256 sellAmount,
|
|
||||||
bool isSushi,
|
|
||||||
address pairAddress,
|
|
||||||
address recipient
|
|
||||||
)
|
|
||||||
public
|
|
||||||
returns (uint256 outputTokenAmount)
|
|
||||||
{
|
|
||||||
require(tokens.length > 1, "MultiplexFeature::_sellToUniswap/InvalidTokensLength");
|
|
||||||
|
|
||||||
if (pairAddress == address(0)) {
|
|
||||||
pairAddress = _computeUniswapPairAddress(tokens[0], tokens[1], isSushi);
|
|
||||||
_transferERC20TokensFrom(
|
|
||||||
IERC20TokenV06(tokens[0]),
|
|
||||||
msg.sender,
|
|
||||||
pairAddress,
|
|
||||||
sellAmount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint256 i = 0; i < tokens.length - 1; i++) {
|
|
||||||
(address inputToken, address outputToken) = (tokens[i], tokens[i + 1]);
|
|
||||||
outputTokenAmount = _computeUniswapOutputAmount(
|
|
||||||
pairAddress,
|
|
||||||
inputToken,
|
|
||||||
outputToken,
|
|
||||||
sellAmount
|
|
||||||
);
|
|
||||||
(uint256 amount0Out, uint256 amount1Out) = inputToken < outputToken
|
|
||||||
? (uint256(0), outputTokenAmount)
|
|
||||||
: (outputTokenAmount, uint256(0));
|
|
||||||
address to = i < tokens.length - 2
|
|
||||||
? _computeUniswapPairAddress(outputToken, tokens[i + 2], isSushi)
|
|
||||||
: recipient;
|
|
||||||
IUniswapV2Pair(pairAddress).swap(
|
|
||||||
amount0Out,
|
|
||||||
amount1Out,
|
|
||||||
to,
|
|
||||||
new bytes(0)
|
|
||||||
);
|
|
||||||
pairAddress = to;
|
|
||||||
sellAmount = outputTokenAmount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same as the LiquidityProviderFeature, but without the transfer in
|
|
||||||
// (which is potentially done in the previous hop of a multi-hop fill)
|
|
||||||
// and without the minBuyAmount check (which is performed at the top, i.e.
|
|
||||||
// in either `batchFill` or `multiHopFill`).
|
|
||||||
function _sellToLiquidityProvider(
|
|
||||||
IERC20TokenV06 inputToken,
|
|
||||||
IERC20TokenV06 outputToken,
|
|
||||||
uint256 inputTokenAmount,
|
|
||||||
ILiquidityProvider provider,
|
|
||||||
address recipient,
|
|
||||||
bytes memory auxiliaryData
|
|
||||||
)
|
|
||||||
public
|
|
||||||
returns (uint256 outputTokenAmount)
|
|
||||||
{
|
|
||||||
uint256 balanceBefore = IERC20TokenV06(outputToken).getTokenBalanceOf(recipient);
|
|
||||||
if (IERC20TokenV06(inputToken).isTokenETH()) {
|
|
||||||
sandbox.executeSellEthForToken(
|
|
||||||
provider,
|
|
||||||
outputToken,
|
|
||||||
recipient,
|
|
||||||
0,
|
|
||||||
auxiliaryData
|
|
||||||
);
|
|
||||||
} else if (IERC20TokenV06(outputToken).isTokenETH()) {
|
|
||||||
sandbox.executeSellTokenForEth(
|
|
||||||
provider,
|
|
||||||
inputToken,
|
|
||||||
recipient,
|
|
||||||
0,
|
|
||||||
auxiliaryData
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
sandbox.executeSellTokenForToken(
|
|
||||||
provider,
|
|
||||||
inputToken,
|
|
||||||
outputToken,
|
|
||||||
recipient,
|
|
||||||
0,
|
|
||||||
auxiliaryData
|
|
||||||
);
|
|
||||||
}
|
|
||||||
outputTokenAmount = IERC20TokenV06(outputToken).getTokenBalanceOf(recipient)
|
|
||||||
.safeSub(balanceBefore);
|
|
||||||
emit LiquidityProviderSwap(
|
|
||||||
address(inputToken),
|
|
||||||
address(outputToken),
|
|
||||||
inputTokenAmount,
|
|
||||||
outputTokenAmount,
|
|
||||||
address(provider),
|
|
||||||
recipient
|
|
||||||
);
|
|
||||||
return outputTokenAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _transferEth(address payable recipient, uint256 amount)
|
|
||||||
private
|
|
||||||
{
|
|
||||||
(bool success,) = recipient.call{value: amount}("");
|
|
||||||
require(success, "MultiplexFeature::_transferEth/TRANSFER_FALIED");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some liquidity sources (e.g. Uniswap, Sushiswap, and PLP) can be passed
|
|
||||||
// a `recipient` parameter so the boguht tokens are transferred to the
|
|
||||||
// `recipient` address rather than `msg.sender`.
|
|
||||||
// Some liquidity sources (also Uniswap, Sushiswap, and PLP incidentally)
|
|
||||||
// support a "transfer then execute" pattern, where the token being sold
|
|
||||||
// can be transferred into the contract before calling a swap function to
|
|
||||||
// execute the trade.
|
|
||||||
// If the current hop in a multi-hop fill satisfies the first condition,
|
|
||||||
// and the next hop satisfies the second condition, the tokens bought
|
|
||||||
// in the current hop can be directly sent to the target contract of
|
|
||||||
// the next hop to save a transfer.
|
|
||||||
function _computeHopRecipient(
|
|
||||||
WrappedMultiHopCall[] memory calls,
|
|
||||||
uint256 i
|
|
||||||
)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (address recipient)
|
|
||||||
{
|
|
||||||
recipient = msg.sender;
|
|
||||||
if (i < calls.length - 1) {
|
|
||||||
WrappedMultiHopCall memory nextCall = calls[i + 1];
|
|
||||||
if (nextCall.selector == this._sellToUniswap.selector) {
|
|
||||||
(address[] memory tokens, bool isSushi) = abi.decode(
|
|
||||||
nextCall.data,
|
|
||||||
(address[], bool)
|
|
||||||
);
|
|
||||||
recipient = _computeUniswapPairAddress(tokens[0], tokens[1], isSushi);
|
|
||||||
} else if (nextCall.selector == this._sellToLiquidityProvider.selector) {
|
|
||||||
(recipient,) = abi.decode(
|
|
||||||
nextCall.data,
|
|
||||||
(address, bytes)
|
|
||||||
);
|
|
||||||
} else if (nextCall.selector == IEtherTokenV06.withdraw.selector) {
|
|
||||||
recipient = address(this);
|
|
||||||
} else if (nextCall.selector == ITransformERC20Feature._transformERC20.selector) {
|
|
||||||
recipient = address(
|
|
||||||
ITransformERC20Feature(address(this)).getTransformWallet()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
require(
|
|
||||||
recipient != address(0),
|
|
||||||
"MultiplexFeature::_computeHopRecipient/RECIPIENT_IS_NULL"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Computes the the amount of output token that would be bought
|
|
||||||
// from Uniswap/Sushiswap given the input amount.
|
|
||||||
function _computeUniswapOutputAmount(
|
|
||||||
address pairAddress,
|
|
||||||
address inputToken,
|
|
||||||
address outputToken,
|
|
||||||
uint256 inputAmount
|
|
||||||
)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (uint256 outputAmount)
|
|
||||||
{
|
|
||||||
require(
|
|
||||||
inputAmount > 0,
|
|
||||||
"MultiplexFeature::_computeUniswapOutputAmount/INSUFFICIENT_INPUT_AMOUNT"
|
|
||||||
);
|
|
||||||
(uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pairAddress).getReserves();
|
|
||||||
require(
|
|
||||||
reserve0 > 0 && reserve1 > 0,
|
|
||||||
'MultiplexFeature::_computeUniswapOutputAmount/INSUFFICIENT_LIQUIDITY'
|
|
||||||
);
|
|
||||||
(uint256 inputReserve, uint256 outputReserve) = inputToken < outputToken
|
|
||||||
? (reserve0, reserve1)
|
|
||||||
: (reserve1, reserve0);
|
|
||||||
uint256 inputAmountWithFee = inputAmount.safeMul(997);
|
|
||||||
uint256 numerator = inputAmountWithFee.safeMul(outputReserve);
|
|
||||||
uint256 denominator = inputReserve.safeMul(1000).safeAdd(inputAmountWithFee);
|
|
||||||
return numerator / denominator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Computes the Uniswap/Sushiswap pair contract address for the
|
|
||||||
// given tokens.
|
|
||||||
function _computeUniswapPairAddress(
|
|
||||||
address tokenA,
|
|
||||||
address tokenB,
|
|
||||||
bool isSushi
|
|
||||||
)
|
|
||||||
private
|
|
||||||
pure
|
|
||||||
returns (address pairAddress)
|
|
||||||
{
|
|
||||||
(address token0, address token1) = tokenA < tokenB
|
|
||||||
? (tokenA, tokenB)
|
|
||||||
: (tokenB, tokenA);
|
|
||||||
if (isSushi) {
|
|
||||||
return address(uint256(keccak256(abi.encodePacked(
|
|
||||||
hex'ff',
|
|
||||||
SUSHISWAP_FACTORY,
|
|
||||||
keccak256(abi.encodePacked(token0, token1)),
|
|
||||||
SUSHISWAP_PAIR_INIT_CODE_HASH
|
|
||||||
))));
|
|
||||||
} else {
|
|
||||||
return address(uint256(keccak256(abi.encodePacked(
|
|
||||||
hex'ff',
|
|
||||||
UNISWAP_FACTORY,
|
|
||||||
keccak256(abi.encodePacked(token0, token1)),
|
|
||||||
UNISWAP_PAIR_INIT_CODE_HASH
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -34,7 +34,7 @@ contract NativeOrdersFeature is
|
|||||||
/// @dev Name of this feature.
|
/// @dev Name of this feature.
|
||||||
string public constant override FEATURE_NAME = "LimitOrders";
|
string public constant override FEATURE_NAME = "LimitOrders";
|
||||||
/// @dev Version of this feature.
|
/// @dev Version of this feature.
|
||||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 2, 0);
|
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 3, 0);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
address zeroExAddress,
|
address zeroExAddress,
|
||||||
|
@ -47,21 +47,12 @@ contract OtcOrdersFeature is
|
|||||||
using LibSafeMathV06 for uint256;
|
using LibSafeMathV06 for uint256;
|
||||||
using LibSafeMathV06 for uint128;
|
using LibSafeMathV06 for uint128;
|
||||||
|
|
||||||
/// @dev Options for handling ETH/WETH conversion
|
|
||||||
/// @param LeaveAsWeth Neither unwrap nor wrap.
|
|
||||||
/// @param WrapEth Wrap attached ETH.
|
|
||||||
/// @param UnwrapWeth Unwrap WETH before transferring
|
|
||||||
/// to taker.
|
|
||||||
enum WethOptions {
|
|
||||||
LeaveAsWeth,
|
|
||||||
WrapEth,
|
|
||||||
UnwrapWeth
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Name of this feature.
|
/// @dev Name of this feature.
|
||||||
string public constant override FEATURE_NAME = "OtcOrders";
|
string public constant override FEATURE_NAME = "OtcOrders";
|
||||||
/// @dev Version of this feature.
|
/// @dev Version of this feature.
|
||||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
|
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
|
||||||
|
/// @dev ETH pseudo-token address.
|
||||||
|
address constant private ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
||||||
/// @dev The WETH token contract.
|
/// @dev The WETH token contract.
|
||||||
IEtherTokenV06 private immutable WETH;
|
IEtherTokenV06 private immutable WETH;
|
||||||
|
|
||||||
@ -80,8 +71,12 @@ contract OtcOrdersFeature is
|
|||||||
returns (bytes4 success)
|
returns (bytes4 success)
|
||||||
{
|
{
|
||||||
_registerFeatureFunction(this.fillOtcOrder.selector);
|
_registerFeatureFunction(this.fillOtcOrder.selector);
|
||||||
|
_registerFeatureFunction(this.fillOtcOrderForEth.selector);
|
||||||
_registerFeatureFunction(this.fillOtcOrderWithEth.selector);
|
_registerFeatureFunction(this.fillOtcOrderWithEth.selector);
|
||||||
|
_registerFeatureFunction(this.fillTakerSignedOtcOrderForEth.selector);
|
||||||
_registerFeatureFunction(this.fillTakerSignedOtcOrder.selector);
|
_registerFeatureFunction(this.fillTakerSignedOtcOrder.selector);
|
||||||
|
_registerFeatureFunction(this.batchFillTakerSignedOtcOrders.selector);
|
||||||
|
_registerFeatureFunction(this._fillOtcOrder.selector);
|
||||||
_registerFeatureFunction(this.getOtcOrderInfo.selector);
|
_registerFeatureFunction(this.getOtcOrderInfo.selector);
|
||||||
_registerFeatureFunction(this.getOtcOrderHash.selector);
|
_registerFeatureFunction(this.getOtcOrderHash.selector);
|
||||||
_registerFeatureFunction(this.lastOtcTxOriginNonce.selector);
|
_registerFeatureFunction(this.lastOtcTxOriginNonce.selector);
|
||||||
@ -93,36 +88,90 @@ contract OtcOrdersFeature is
|
|||||||
/// @param makerSignature The order signature from the maker.
|
/// @param makerSignature The order signature from the maker.
|
||||||
/// @param takerTokenFillAmount Maximum taker token amount to fill this
|
/// @param takerTokenFillAmount Maximum taker token amount to fill this
|
||||||
/// order with.
|
/// order with.
|
||||||
/// @param unwrapWeth Whether or not to unwrap bought WETH into ETH
|
|
||||||
/// before transferring it to the taker. Should be set to false
|
|
||||||
/// if the maker token is not WETH.
|
|
||||||
/// @return takerTokenFilledAmount How much taker token was filled.
|
/// @return takerTokenFilledAmount How much taker token was filled.
|
||||||
/// @return makerTokenFilledAmount How much maker token was filled.
|
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||||
function fillOtcOrder(
|
function fillOtcOrder(
|
||||||
LibNativeOrder.OtcOrder memory order,
|
LibNativeOrder.OtcOrder memory order,
|
||||||
LibSignature.Signature memory makerSignature,
|
LibSignature.Signature memory makerSignature,
|
||||||
uint128 takerTokenFillAmount,
|
uint128 takerTokenFillAmount
|
||||||
bool unwrapWeth
|
|
||||||
)
|
)
|
||||||
public
|
public
|
||||||
override
|
override
|
||||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||||
{
|
{
|
||||||
if (!_isSenderValidTaker(order.taker)) {
|
LibNativeOrder.OtcOrderInfo memory orderInfo = getOtcOrderInfo(order);
|
||||||
bytes32 orderHash = getOtcOrderHash(order);
|
_validateOtcOrder(
|
||||||
LibNativeOrdersRichErrors.OrderNotFillableByTakerError(
|
order,
|
||||||
orderHash,
|
orderInfo,
|
||||||
msg.sender,
|
|
||||||
order.taker
|
|
||||||
).rrevert();
|
|
||||||
}
|
|
||||||
LibSignature.Signature memory nullSignature;
|
|
||||||
return _fillOtcOrderPrivate(
|
|
||||||
order,
|
|
||||||
makerSignature,
|
makerSignature,
|
||||||
nullSignature,
|
msg.sender
|
||||||
|
);
|
||||||
|
(takerTokenFilledAmount, makerTokenFilledAmount) = _settleOtcOrder(
|
||||||
|
order,
|
||||||
takerTokenFillAmount,
|
takerTokenFillAmount,
|
||||||
unwrapWeth ? WethOptions.UnwrapWeth : WethOptions.LeaveAsWeth
|
msg.sender,
|
||||||
|
msg.sender
|
||||||
|
);
|
||||||
|
|
||||||
|
emit OtcOrderFilled(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
order.maker,
|
||||||
|
msg.sender,
|
||||||
|
address(order.makerToken),
|
||||||
|
address(order.takerToken),
|
||||||
|
makerTokenFilledAmount,
|
||||||
|
takerTokenFilledAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Fill an OTC order for up to `takerTokenFillAmount` taker tokens.
|
||||||
|
/// Unwraps bought WETH into ETH. before sending it to
|
||||||
|
/// the taker.
|
||||||
|
/// @param order The OTC order.
|
||||||
|
/// @param makerSignature The order signature from the maker.
|
||||||
|
/// @param takerTokenFillAmount Maximum taker token amount to fill this
|
||||||
|
/// order with.
|
||||||
|
/// @return takerTokenFilledAmount How much taker token was filled.
|
||||||
|
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||||
|
function fillOtcOrderForEth(
|
||||||
|
LibNativeOrder.OtcOrder memory order,
|
||||||
|
LibSignature.Signature memory makerSignature,
|
||||||
|
uint128 takerTokenFillAmount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
override
|
||||||
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||||
|
{
|
||||||
|
require(
|
||||||
|
order.makerToken == WETH,
|
||||||
|
"OtcOrdersFeature::fillOtcOrderForEth/MAKER_TOKEN_NOT_WETH"
|
||||||
|
);
|
||||||
|
LibNativeOrder.OtcOrderInfo memory orderInfo = getOtcOrderInfo(order);
|
||||||
|
_validateOtcOrder(
|
||||||
|
order,
|
||||||
|
orderInfo,
|
||||||
|
makerSignature,
|
||||||
|
msg.sender
|
||||||
|
);
|
||||||
|
(takerTokenFilledAmount, makerTokenFilledAmount) = _settleOtcOrder(
|
||||||
|
order,
|
||||||
|
takerTokenFillAmount,
|
||||||
|
msg.sender,
|
||||||
|
address(this)
|
||||||
|
);
|
||||||
|
// Unwrap WETH
|
||||||
|
WETH.withdraw(makerTokenFilledAmount);
|
||||||
|
// Transfer ETH to taker
|
||||||
|
_transferEth(msg.sender, makerTokenFilledAmount);
|
||||||
|
|
||||||
|
emit OtcOrderFilled(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
order.maker,
|
||||||
|
msg.sender,
|
||||||
|
address(order.makerToken),
|
||||||
|
address(order.takerToken),
|
||||||
|
makerTokenFilledAmount,
|
||||||
|
takerTokenFilledAmount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,21 +190,47 @@ contract OtcOrdersFeature is
|
|||||||
payable
|
payable
|
||||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||||
{
|
{
|
||||||
if (!_isSenderValidTaker(order.taker)) {
|
if (order.takerToken == WETH) {
|
||||||
bytes32 orderHash = getOtcOrderHash(order);
|
// Wrap ETH
|
||||||
LibNativeOrdersRichErrors.OrderNotFillableByTakerError(
|
WETH.deposit{value: msg.value}();
|
||||||
orderHash,
|
} else {
|
||||||
msg.sender,
|
require(
|
||||||
order.taker
|
address(order.takerToken) == ETH_TOKEN_ADDRESS,
|
||||||
).rrevert();
|
"OtcOrdersFeature::fillOtcOrderWithEth/INVALID_TAKER_TOKEN"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
LibSignature.Signature memory nullSignature;
|
|
||||||
return _fillOtcOrderPrivate(
|
LibNativeOrder.OtcOrderInfo memory orderInfo = getOtcOrderInfo(order);
|
||||||
order,
|
_validateOtcOrder(
|
||||||
|
order,
|
||||||
|
orderInfo,
|
||||||
makerSignature,
|
makerSignature,
|
||||||
nullSignature,
|
msg.sender
|
||||||
|
);
|
||||||
|
|
||||||
|
(takerTokenFilledAmount, makerTokenFilledAmount) = _settleOtcOrder(
|
||||||
|
order,
|
||||||
msg.value.safeDowncastToUint128(),
|
msg.value.safeDowncastToUint128(),
|
||||||
WethOptions.WrapEth
|
address(this),
|
||||||
|
msg.sender
|
||||||
|
);
|
||||||
|
if (takerTokenFilledAmount < msg.value) {
|
||||||
|
uint256 refundAmount = msg.value - uint256(takerTokenFilledAmount);
|
||||||
|
if (order.takerToken == WETH) {
|
||||||
|
WETH.withdraw(refundAmount);
|
||||||
|
}
|
||||||
|
// Refund unused ETH
|
||||||
|
_transferEth(msg.sender, refundAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit OtcOrderFilled(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
order.maker,
|
||||||
|
msg.sender,
|
||||||
|
address(order.makerToken),
|
||||||
|
address(order.takerToken),
|
||||||
|
makerTokenFilledAmount,
|
||||||
|
takerTokenFilledAmount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,112 +239,28 @@ contract OtcOrdersFeature is
|
|||||||
/// @param order The OTC order.
|
/// @param order The OTC order.
|
||||||
/// @param makerSignature The order signature from the maker.
|
/// @param makerSignature The order signature from the maker.
|
||||||
/// @param takerSignature The order signature from the taker.
|
/// @param takerSignature The order signature from the taker.
|
||||||
/// @param unwrapWeth Whether or not to unwrap bought WETH into ETH
|
|
||||||
/// before transferring it to the taker. Should be set to false
|
|
||||||
/// if the maker token is not WETH.
|
|
||||||
/// @return takerTokenFilledAmount How much taker token was filled.
|
|
||||||
/// @return makerTokenFilledAmount How much maker token was filled.
|
|
||||||
function fillTakerSignedOtcOrder(
|
function fillTakerSignedOtcOrder(
|
||||||
LibNativeOrder.OtcOrder memory order,
|
LibNativeOrder.OtcOrder memory order,
|
||||||
LibSignature.Signature memory makerSignature,
|
LibSignature.Signature memory makerSignature,
|
||||||
LibSignature.Signature memory takerSignature,
|
LibSignature.Signature memory takerSignature
|
||||||
bool unwrapWeth
|
|
||||||
)
|
)
|
||||||
public
|
public
|
||||||
override
|
override
|
||||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
|
||||||
{
|
|
||||||
return _fillOtcOrderPrivate(
|
|
||||||
order,
|
|
||||||
makerSignature,
|
|
||||||
takerSignature,
|
|
||||||
order.takerAmount,
|
|
||||||
unwrapWeth ? WethOptions.UnwrapWeth : WethOptions.LeaveAsWeth
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Fill an OTC order. Private variant.
|
|
||||||
/// @param order The OTC order.
|
|
||||||
/// @param makerSignature The order signature from the maker.
|
|
||||||
/// @param takerSignature The order signature from the taker.
|
|
||||||
/// Ignored if msg.sender == order.taker.
|
|
||||||
/// @param takerTokenFillAmount Maximum taker token amount to
|
|
||||||
/// fill this order with.
|
|
||||||
/// @return takerTokenFilledAmount How much taker token was filled.
|
|
||||||
/// @return makerTokenFilledAmount How much maker token was filled.
|
|
||||||
function _fillOtcOrderPrivate(
|
|
||||||
LibNativeOrder.OtcOrder memory order,
|
|
||||||
LibSignature.Signature memory makerSignature,
|
|
||||||
LibSignature.Signature memory takerSignature,
|
|
||||||
uint128 takerTokenFillAmount,
|
|
||||||
WethOptions wethOptions
|
|
||||||
)
|
|
||||||
private
|
|
||||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
|
||||||
{
|
{
|
||||||
LibNativeOrder.OtcOrderInfo memory orderInfo = getOtcOrderInfo(order);
|
LibNativeOrder.OtcOrderInfo memory orderInfo = getOtcOrderInfo(order);
|
||||||
|
address taker = LibSignature.getSignerOfHash(orderInfo.orderHash, takerSignature);
|
||||||
// Must be fillable.
|
|
||||||
if (orderInfo.status != LibNativeOrder.OrderStatus.FILLABLE) {
|
_validateOtcOrder(
|
||||||
LibNativeOrdersRichErrors.OrderNotFillableError(
|
order,
|
||||||
orderInfo.orderHash,
|
orderInfo,
|
||||||
uint8(orderInfo.status)
|
makerSignature,
|
||||||
).rrevert();
|
taker
|
||||||
}
|
);
|
||||||
|
_settleOtcOrder(
|
||||||
address taker = msg.sender;
|
|
||||||
{
|
|
||||||
LibNativeOrdersStorage.Storage storage stor =
|
|
||||||
LibNativeOrdersStorage.getStorage();
|
|
||||||
|
|
||||||
// Must be fillable by the tx.origin.
|
|
||||||
if (
|
|
||||||
order.txOrigin != tx.origin &&
|
|
||||||
!stor.originRegistry[order.txOrigin][tx.origin]
|
|
||||||
) {
|
|
||||||
LibNativeOrdersRichErrors.OrderNotFillableByOriginError(
|
|
||||||
orderInfo.orderHash,
|
|
||||||
tx.origin,
|
|
||||||
order.txOrigin
|
|
||||||
).rrevert();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maker signature must be valid for the order.
|
|
||||||
address makerSigner = LibSignature.getSignerOfHash(orderInfo.orderHash, makerSignature);
|
|
||||||
if (
|
|
||||||
makerSigner != order.maker &&
|
|
||||||
!stor.orderSignerRegistry[order.maker][makerSigner]
|
|
||||||
) {
|
|
||||||
LibNativeOrdersRichErrors.OrderNotSignedByMakerError(
|
|
||||||
orderInfo.orderHash,
|
|
||||||
makerSigner,
|
|
||||||
order.maker
|
|
||||||
).rrevert();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If msg.sender is not the taker, validate the taker signature.
|
|
||||||
if (!_isSenderValidTaker(order.taker)) {
|
|
||||||
address takerSigner = LibSignature.getSignerOfHash(orderInfo.orderHash, takerSignature);
|
|
||||||
if (
|
|
||||||
takerSigner != order.taker &&
|
|
||||||
!stor.orderSignerRegistry[order.taker][takerSigner]
|
|
||||||
) {
|
|
||||||
LibNativeOrdersRichErrors.OrderNotSignedByTakerError(
|
|
||||||
orderInfo.orderHash,
|
|
||||||
takerSigner,
|
|
||||||
order.taker
|
|
||||||
).rrevert();
|
|
||||||
}
|
|
||||||
taker = order.taker;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Settle between the maker and taker.
|
|
||||||
(takerTokenFilledAmount, makerTokenFilledAmount) = _settleOtcOrder(
|
|
||||||
order,
|
order,
|
||||||
|
order.takerAmount,
|
||||||
taker,
|
taker,
|
||||||
takerTokenFillAmount,
|
taker
|
||||||
wethOptions
|
|
||||||
);
|
);
|
||||||
|
|
||||||
emit OtcOrderFilled(
|
emit OtcOrderFilled(
|
||||||
@ -278,22 +269,227 @@ contract OtcOrdersFeature is
|
|||||||
taker,
|
taker,
|
||||||
address(order.makerToken),
|
address(order.makerToken),
|
||||||
address(order.takerToken),
|
address(order.takerToken),
|
||||||
takerTokenFilledAmount,
|
order.makerAmount,
|
||||||
makerTokenFilledAmount
|
order.takerAmount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @dev Fully fill an OTC order. "Meta-transaction" variant,
|
||||||
|
/// requires order to be signed by both maker and taker.
|
||||||
|
/// Unwraps bought WETH into ETH. before sending it to
|
||||||
|
/// the taker.
|
||||||
|
/// @param order The OTC order.
|
||||||
|
/// @param makerSignature The order signature from the maker.
|
||||||
|
/// @param takerSignature The order signature from the taker.
|
||||||
|
function fillTakerSignedOtcOrderForEth(
|
||||||
|
LibNativeOrder.OtcOrder memory order,
|
||||||
|
LibSignature.Signature memory makerSignature,
|
||||||
|
LibSignature.Signature memory takerSignature
|
||||||
|
)
|
||||||
|
public
|
||||||
|
override
|
||||||
|
{
|
||||||
|
require(
|
||||||
|
order.makerToken == WETH,
|
||||||
|
"OtcOrdersFeature::fillTakerSignedOtcOrder/MAKER_TOKEN_NOT_WETH"
|
||||||
|
);
|
||||||
|
LibNativeOrder.OtcOrderInfo memory orderInfo = getOtcOrderInfo(order);
|
||||||
|
address taker = LibSignature.getSignerOfHash(orderInfo.orderHash, takerSignature);
|
||||||
|
|
||||||
|
_validateOtcOrder(
|
||||||
|
order,
|
||||||
|
orderInfo,
|
||||||
|
makerSignature,
|
||||||
|
taker
|
||||||
|
);
|
||||||
|
_settleOtcOrder(
|
||||||
|
order,
|
||||||
|
order.takerAmount,
|
||||||
|
taker,
|
||||||
|
address(this)
|
||||||
|
);
|
||||||
|
// Unwrap WETH
|
||||||
|
WETH.withdraw(order.makerAmount);
|
||||||
|
// Transfer ETH to taker
|
||||||
|
_transferEth(taker, order.makerAmount);
|
||||||
|
|
||||||
|
emit OtcOrderFilled(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
order.maker,
|
||||||
|
taker,
|
||||||
|
address(order.makerToken),
|
||||||
|
address(order.takerToken),
|
||||||
|
order.makerAmount,
|
||||||
|
order.takerAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Fills multiple taker-signed OTC orders.
|
||||||
|
/// @param orders Array of OTC orders.
|
||||||
|
/// @param makerSignatures Array of maker signatures for each order.
|
||||||
|
/// @param takerSignatures Array of taker signatures for each order.
|
||||||
|
/// @param unwrapWeth Array of booleans representing whether or not
|
||||||
|
/// to unwrap bought WETH into ETH for each order. Should be set
|
||||||
|
/// to false if the maker token is not WETH.
|
||||||
|
/// @return successes Array of booleans representing whether or not
|
||||||
|
/// each order in `orders` was filled successfully.
|
||||||
|
function batchFillTakerSignedOtcOrders(
|
||||||
|
LibNativeOrder.OtcOrder[] memory orders,
|
||||||
|
LibSignature.Signature[] memory makerSignatures,
|
||||||
|
LibSignature.Signature[] memory takerSignatures,
|
||||||
|
bool[] memory unwrapWeth
|
||||||
|
)
|
||||||
|
public
|
||||||
|
override
|
||||||
|
returns (bool[] memory successes)
|
||||||
|
{
|
||||||
|
require(
|
||||||
|
orders.length == makerSignatures.length &&
|
||||||
|
orders.length == takerSignatures.length &&
|
||||||
|
orders.length == unwrapWeth.length,
|
||||||
|
"OtcOrdersFeature::batchFillTakerSignedOtcOrders/MISMATCHED_ARRAY_LENGTHS"
|
||||||
|
);
|
||||||
|
successes = new bool[](orders.length);
|
||||||
|
for (uint256 i = 0; i != orders.length; i++) {
|
||||||
|
bytes4 fnSelector = unwrapWeth[i]
|
||||||
|
? this.fillTakerSignedOtcOrderForEth.selector
|
||||||
|
: this.fillTakerSignedOtcOrder.selector;
|
||||||
|
// Swallow reverts
|
||||||
|
(successes[i], ) = _implementation.delegatecall(
|
||||||
|
abi.encodeWithSelector(
|
||||||
|
fnSelector,
|
||||||
|
orders[i],
|
||||||
|
makerSignatures[i],
|
||||||
|
takerSignatures[i]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Fill an OTC order for up to `takerTokenFillAmount` taker tokens.
|
||||||
|
/// Internal variant.
|
||||||
|
/// @param order The OTC order.
|
||||||
|
/// @param makerSignature The order signature from the maker.
|
||||||
|
/// @param takerTokenFillAmount Maximum taker token amount to fill this
|
||||||
|
/// order with.
|
||||||
|
/// @param taker The address to fill the order in the context of.
|
||||||
|
/// @param useSelfBalance Whether to use the Exchange Proxy's balance
|
||||||
|
/// of input tokens.
|
||||||
|
/// @param recipient The recipient of the bought maker tokens.
|
||||||
|
/// @return takerTokenFilledAmount How much taker token was filled.
|
||||||
|
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||||
|
function _fillOtcOrder(
|
||||||
|
LibNativeOrder.OtcOrder memory order,
|
||||||
|
LibSignature.Signature memory makerSignature,
|
||||||
|
uint128 takerTokenFillAmount,
|
||||||
|
address taker,
|
||||||
|
bool useSelfBalance,
|
||||||
|
address recipient
|
||||||
|
)
|
||||||
|
public
|
||||||
|
override
|
||||||
|
onlySelf
|
||||||
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||||
|
{
|
||||||
|
LibNativeOrder.OtcOrderInfo memory orderInfo = getOtcOrderInfo(order);
|
||||||
|
_validateOtcOrder(
|
||||||
|
order,
|
||||||
|
orderInfo,
|
||||||
|
makerSignature,
|
||||||
|
taker
|
||||||
|
);
|
||||||
|
(takerTokenFilledAmount, makerTokenFilledAmount) = _settleOtcOrder(
|
||||||
|
order,
|
||||||
|
takerTokenFillAmount,
|
||||||
|
useSelfBalance ? address(this) : taker,
|
||||||
|
recipient
|
||||||
|
);
|
||||||
|
|
||||||
|
emit OtcOrderFilled(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
order.maker,
|
||||||
|
taker,
|
||||||
|
address(order.makerToken),
|
||||||
|
address(order.takerToken),
|
||||||
|
makerTokenFilledAmount,
|
||||||
|
takerTokenFilledAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Validates an OTC order, reverting if the order cannot be
|
||||||
|
/// filled by the given taker.
|
||||||
|
/// @param order The OTC order.
|
||||||
|
/// @param orderInfo Info on the order.
|
||||||
|
/// @param makerSignature The order signature from the maker.
|
||||||
|
/// @param taker The order taker.
|
||||||
|
function _validateOtcOrder(
|
||||||
|
LibNativeOrder.OtcOrder memory order,
|
||||||
|
LibNativeOrder.OtcOrderInfo memory orderInfo,
|
||||||
|
LibSignature.Signature memory makerSignature,
|
||||||
|
address taker
|
||||||
|
)
|
||||||
|
private
|
||||||
|
view
|
||||||
|
{
|
||||||
|
// Must be fillable.
|
||||||
|
if (orderInfo.status != LibNativeOrder.OrderStatus.FILLABLE) {
|
||||||
|
LibNativeOrdersRichErrors.OrderNotFillableError(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
uint8(orderInfo.status)
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be a valid taker for the order.
|
||||||
|
if (order.taker != address(0) && order.taker != taker) {
|
||||||
|
LibNativeOrdersRichErrors.OrderNotFillableByTakerError(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
taker,
|
||||||
|
order.taker
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
|
||||||
|
LibNativeOrdersStorage.Storage storage stor =
|
||||||
|
LibNativeOrdersStorage.getStorage();
|
||||||
|
|
||||||
|
// Must be fillable by the tx.origin.
|
||||||
|
if (
|
||||||
|
order.txOrigin != tx.origin &&
|
||||||
|
!stor.originRegistry[order.txOrigin][tx.origin]
|
||||||
|
) {
|
||||||
|
LibNativeOrdersRichErrors.OrderNotFillableByOriginError(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
tx.origin,
|
||||||
|
order.txOrigin
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maker signature must be valid for the order.
|
||||||
|
address makerSigner = LibSignature.getSignerOfHash(orderInfo.orderHash, makerSignature);
|
||||||
|
if (
|
||||||
|
makerSigner != order.maker &&
|
||||||
|
!stor.orderSignerRegistry[order.maker][makerSigner]
|
||||||
|
) {
|
||||||
|
LibNativeOrdersRichErrors.OrderNotSignedByMakerError(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
makerSigner,
|
||||||
|
order.maker
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// @dev Settle the trade between an OTC order's maker and taker.
|
/// @dev Settle the trade between an OTC order's maker and taker.
|
||||||
/// @param order The OTC order.
|
/// @param order The OTC order.
|
||||||
/// @param takerTokenFillAmount Maximum taker token amount to fill this
|
/// @param takerTokenFillAmount Maximum taker token amount to fill this
|
||||||
/// order with.
|
/// order with.
|
||||||
|
/// @param payer The address holding the taker tokens.
|
||||||
|
/// @param recipient The recipient of the maker tokens.
|
||||||
/// @return takerTokenFilledAmount How much taker token was filled.
|
/// @return takerTokenFilledAmount How much taker token was filled.
|
||||||
/// @return makerTokenFilledAmount How much maker token was filled.
|
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||||
function _settleOtcOrder(
|
function _settleOtcOrder(
|
||||||
LibNativeOrder.OtcOrder memory order,
|
LibNativeOrder.OtcOrder memory order,
|
||||||
address taker,
|
|
||||||
uint128 takerTokenFillAmount,
|
uint128 takerTokenFillAmount,
|
||||||
WethOptions wethOptions
|
address payer,
|
||||||
|
address recipient
|
||||||
)
|
)
|
||||||
private
|
private
|
||||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||||
@ -326,57 +522,34 @@ contract OtcOrdersFeature is
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wethOptions == WethOptions.WrapEth) {
|
if (payer == address(this)) {
|
||||||
require(
|
if (address(order.takerToken) == ETH_TOKEN_ADDRESS) {
|
||||||
order.takerToken == WETH,
|
// Transfer ETH to the maker.
|
||||||
"OtcOrdersFeature/INVALID_WRAP_ETH"
|
payable(order.maker).transfer(takerTokenFilledAmount);
|
||||||
);
|
} else {
|
||||||
// Wrap ETH
|
// Transfer this -> maker.
|
||||||
WETH.deposit{value: takerTokenFilledAmount}();
|
_transferERC20Tokens(
|
||||||
// Transfer WETH to maker
|
order.takerToken,
|
||||||
WETH.transfer(order.maker, takerTokenFilledAmount);
|
order.maker,
|
||||||
if (takerTokenFilledAmount < msg.value) {
|
takerTokenFilledAmount
|
||||||
// Refund unused ETH
|
);
|
||||||
_transferEth(
|
|
||||||
msg.sender,
|
|
||||||
msg.value - uint256(takerTokenFilledAmount)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Transfer taker -> maker
|
// Transfer taker -> maker
|
||||||
_transferERC20TokensFrom(
|
_transferERC20TokensFrom(
|
||||||
order.takerToken,
|
order.takerToken,
|
||||||
taker,
|
payer,
|
||||||
order.maker,
|
order.maker,
|
||||||
takerTokenFilledAmount
|
takerTokenFilledAmount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// Transfer maker -> recipient.
|
||||||
if (wethOptions == WethOptions.UnwrapWeth) {
|
_transferERC20TokensFrom(
|
||||||
require(
|
order.makerToken,
|
||||||
order.makerToken == WETH,
|
order.maker,
|
||||||
"OtcOrdersFeature/INVALID_UNWRAP_WETH"
|
recipient,
|
||||||
);
|
makerTokenFilledAmount
|
||||||
// Transfer maker tokens in
|
);
|
||||||
_transferERC20TokensFrom(
|
|
||||||
order.makerToken,
|
|
||||||
order.maker,
|
|
||||||
address(this),
|
|
||||||
makerTokenFilledAmount
|
|
||||||
);
|
|
||||||
// Unwrap WETH
|
|
||||||
WETH.withdraw(makerTokenFilledAmount);
|
|
||||||
// Transfer ETH to taker
|
|
||||||
_transferEth(taker, makerTokenFilledAmount);
|
|
||||||
} else {
|
|
||||||
// Transfer maker -> taker.
|
|
||||||
_transferERC20TokensFrom(
|
|
||||||
order.makerToken,
|
|
||||||
order.maker,
|
|
||||||
taker,
|
|
||||||
makerTokenFilledAmount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Get the order info for an OTC order.
|
/// @dev Get the order info for an OTC order.
|
||||||
@ -461,12 +634,4 @@ contract OtcOrdersFeature is
|
|||||||
revertData.rrevert();
|
revertData.rrevert();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _isSenderValidTaker(address orderTaker)
|
|
||||||
private
|
|
||||||
view
|
|
||||||
returns (bool)
|
|
||||||
{
|
|
||||||
return orderTaker == address(0) || orderTaker == msg.sender;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ pragma experimental ABIEncoderV2;
|
|||||||
|
|
||||||
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||||
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
|
||||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
import "../errors/LibTransformERC20RichErrors.sol";
|
import "../errors/LibTransformERC20RichErrors.sol";
|
||||||
@ -51,16 +52,14 @@ contract TransformERC20Feature is
|
|||||||
struct TransformERC20PrivateState {
|
struct TransformERC20PrivateState {
|
||||||
IFlashWallet wallet;
|
IFlashWallet wallet;
|
||||||
address transformerDeployer;
|
address transformerDeployer;
|
||||||
uint256 takerOutputTokenBalanceBefore;
|
uint256 recipientOutputTokenBalanceBefore;
|
||||||
uint256 takerOutputTokenBalanceAfter;
|
uint256 recipientOutputTokenBalanceAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Name of this feature.
|
/// @dev Name of this feature.
|
||||||
string public constant override FEATURE_NAME = "TransformERC20";
|
string public constant override FEATURE_NAME = "TransformERC20";
|
||||||
/// @dev Version of this feature.
|
/// @dev Version of this feature.
|
||||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 3, 1);
|
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 4, 0);
|
||||||
|
|
||||||
constructor() public {}
|
|
||||||
|
|
||||||
/// @dev Initialize and register this feature.
|
/// @dev Initialize and register this feature.
|
||||||
/// Should be delegatecalled by `Migrate.migrate()`.
|
/// Should be delegatecalled by `Migrate.migrate()`.
|
||||||
@ -76,7 +75,7 @@ contract TransformERC20Feature is
|
|||||||
_registerFeatureFunction(this.setTransformerDeployer.selector);
|
_registerFeatureFunction(this.setTransformerDeployer.selector);
|
||||||
_registerFeatureFunction(this.setQuoteSigner.selector);
|
_registerFeatureFunction(this.setQuoteSigner.selector);
|
||||||
_registerFeatureFunction(this.getQuoteSigner.selector);
|
_registerFeatureFunction(this.getQuoteSigner.selector);
|
||||||
_registerFeatureFunction(this.transformERC20.selector);
|
_registerFeatureFunction(this.transformERC20Staging.selector);
|
||||||
_registerFeatureFunction(this._transformERC20.selector);
|
_registerFeatureFunction(this._transformERC20.selector);
|
||||||
if (this.getTransformWallet() == IFlashWallet(address(0))) {
|
if (this.getTransformWallet() == IFlashWallet(address(0))) {
|
||||||
// Create the transform wallet if it doesn't exist.
|
// Create the transform wallet if it doesn't exist.
|
||||||
@ -146,6 +145,44 @@ contract TransformERC20Feature is
|
|||||||
LibTransformERC20Storage.getStorage().wallet = wallet;
|
LibTransformERC20Storage.getStorage().wallet = wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @dev Wrapper for `transformERC20`. This selector will be temporarily
|
||||||
|
/// registered to the Exchange Proxy so that we can migrate 0x API
|
||||||
|
/// with no downtime. Once 0x API has been updated to point to this
|
||||||
|
/// function, we can safely re-register `transformERC20`, point
|
||||||
|
/// 0x API back to `transformERC20`, and deregister this function.
|
||||||
|
/// @param inputToken The token being provided by the sender.
|
||||||
|
/// If `0xeee...`, ETH is implied and should be provided with the call.`
|
||||||
|
/// @param outputToken The token to be acquired by the sender.
|
||||||
|
/// `0xeee...` implies ETH.
|
||||||
|
/// @param inputTokenAmount The amount of `inputToken` to take from the sender.
|
||||||
|
/// If set to `uint256(-1)`, the entire spendable balance of the taker
|
||||||
|
/// will be solt.
|
||||||
|
/// @param minOutputTokenAmount The minimum amount of `outputToken` the sender
|
||||||
|
/// must receive for the entire transformation to succeed. If set to zero,
|
||||||
|
/// the minimum output token transfer will not be asserted.
|
||||||
|
/// @param transformations The transformations to execute on the token balance(s)
|
||||||
|
/// in sequence.
|
||||||
|
/// @return outputTokenAmount The amount of `outputToken` received by the sender.
|
||||||
|
function transformERC20Staging(
|
||||||
|
IERC20TokenV06 inputToken,
|
||||||
|
IERC20TokenV06 outputToken,
|
||||||
|
uint256 inputTokenAmount,
|
||||||
|
uint256 minOutputTokenAmount,
|
||||||
|
Transformation[] memory transformations
|
||||||
|
)
|
||||||
|
public
|
||||||
|
payable
|
||||||
|
returns (uint256 outputTokenAmount)
|
||||||
|
{
|
||||||
|
return transformERC20(
|
||||||
|
inputToken,
|
||||||
|
outputToken,
|
||||||
|
inputTokenAmount,
|
||||||
|
minOutputTokenAmount,
|
||||||
|
transformations
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// @dev Executes a series of transformations to convert an ERC20 `inputToken`
|
/// @dev Executes a series of transformations to convert an ERC20 `inputToken`
|
||||||
/// to an ERC20 `outputToken`.
|
/// to an ERC20 `outputToken`.
|
||||||
/// @param inputToken The token being provided by the sender.
|
/// @param inputToken The token being provided by the sender.
|
||||||
@ -180,7 +217,9 @@ contract TransformERC20Feature is
|
|||||||
outputToken: outputToken,
|
outputToken: outputToken,
|
||||||
inputTokenAmount: inputTokenAmount,
|
inputTokenAmount: inputTokenAmount,
|
||||||
minOutputTokenAmount: minOutputTokenAmount,
|
minOutputTokenAmount: minOutputTokenAmount,
|
||||||
transformations: transformations
|
transformations: transformations,
|
||||||
|
useSelfBalance: false,
|
||||||
|
recipient: msg.sender
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -208,7 +247,7 @@ contract TransformERC20Feature is
|
|||||||
{
|
{
|
||||||
// If the input token amount is -1 and we are not selling ETH,
|
// If the input token amount is -1 and we are not selling ETH,
|
||||||
// transform the taker's entire spendable balance.
|
// transform the taker's entire spendable balance.
|
||||||
if (args.inputTokenAmount == uint256(-1)) {
|
if (!args.useSelfBalance && args.inputTokenAmount == uint256(-1)) {
|
||||||
if (LibERC20Transformer.isTokenETH(args.inputToken)) {
|
if (LibERC20Transformer.isTokenETH(args.inputToken)) {
|
||||||
// We can't pull more ETH from the taker, so we just set the
|
// We can't pull more ETH from the taker, so we just set the
|
||||||
// input token amount to the value attached to the call.
|
// input token amount to the value attached to the call.
|
||||||
@ -225,17 +264,12 @@ contract TransformERC20Feature is
|
|||||||
state.wallet = getTransformWallet();
|
state.wallet = getTransformWallet();
|
||||||
state.transformerDeployer = getTransformerDeployer();
|
state.transformerDeployer = getTransformerDeployer();
|
||||||
|
|
||||||
// Remember the initial output token balance of the taker.
|
// Remember the initial output token balance of the recipient.
|
||||||
state.takerOutputTokenBalanceBefore =
|
state.recipientOutputTokenBalanceBefore =
|
||||||
LibERC20Transformer.getTokenBalanceOf(args.outputToken, args.taker);
|
LibERC20Transformer.getTokenBalanceOf(args.outputToken, args.recipient);
|
||||||
|
|
||||||
// Pull input tokens from the taker to the wallet and transfer attached ETH.
|
// Pull input tokens from the taker to the wallet and transfer attached ETH.
|
||||||
_transferInputTokensAndAttachedEth(
|
_transferInputTokensAndAttachedEth(args, address(state.wallet));
|
||||||
args.inputToken,
|
|
||||||
args.taker,
|
|
||||||
address(state.wallet),
|
|
||||||
args.inputTokenAmount
|
|
||||||
);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
// Perform transformations.
|
// Perform transformations.
|
||||||
@ -244,22 +278,29 @@ contract TransformERC20Feature is
|
|||||||
state.wallet,
|
state.wallet,
|
||||||
args.transformations[i],
|
args.transformations[i],
|
||||||
state.transformerDeployer,
|
state.transformerDeployer,
|
||||||
args.taker
|
args.recipient
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// Transfer output tokens from wallet to recipient
|
||||||
|
outputTokenAmount = _executeOutputTokenTransfer(
|
||||||
|
args.outputToken,
|
||||||
|
state.wallet,
|
||||||
|
args.recipient
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute how much output token has been transferred to the taker.
|
// Compute how much output token has been transferred to the recipient.
|
||||||
state.takerOutputTokenBalanceAfter =
|
state.recipientOutputTokenBalanceAfter =
|
||||||
LibERC20Transformer.getTokenBalanceOf(args.outputToken, args.taker);
|
LibERC20Transformer.getTokenBalanceOf(args.outputToken, args.recipient);
|
||||||
if (state.takerOutputTokenBalanceAfter < state.takerOutputTokenBalanceBefore) {
|
if (state.recipientOutputTokenBalanceAfter < state.recipientOutputTokenBalanceBefore) {
|
||||||
LibTransformERC20RichErrors.NegativeTransformERC20OutputError(
|
LibTransformERC20RichErrors.NegativeTransformERC20OutputError(
|
||||||
address(args.outputToken),
|
address(args.outputToken),
|
||||||
state.takerOutputTokenBalanceBefore - state.takerOutputTokenBalanceAfter
|
state.recipientOutputTokenBalanceBefore - state.recipientOutputTokenBalanceAfter
|
||||||
).rrevert();
|
).rrevert();
|
||||||
}
|
}
|
||||||
outputTokenAmount = state.takerOutputTokenBalanceAfter.safeSub(
|
outputTokenAmount = LibSafeMathV06.min256(
|
||||||
state.takerOutputTokenBalanceBefore
|
outputTokenAmount,
|
||||||
|
state.recipientOutputTokenBalanceAfter.safeSub(state.recipientOutputTokenBalanceBefore)
|
||||||
);
|
);
|
||||||
// Ensure enough output token has been sent to the taker.
|
// Ensure enough output token has been sent to the taker.
|
||||||
if (outputTokenAmount < args.minOutputTokenAmount) {
|
if (outputTokenAmount < args.minOutputTokenAmount) {
|
||||||
@ -292,38 +333,49 @@ contract TransformERC20Feature is
|
|||||||
return LibTransformERC20Storage.getStorage().wallet;
|
return LibTransformERC20Storage.getStorage().wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Transfer input tokens from the taker and any attached ETH to `to`
|
/// @dev Transfer input tokens and any attached ETH to `to`
|
||||||
/// @param inputToken The token to pull from the taker.
|
/// @param args A `TransformERC20Args` struct.
|
||||||
/// @param from The from (taker) address.
|
|
||||||
/// @param to The recipient of tokens and ETH.
|
/// @param to The recipient of tokens and ETH.
|
||||||
/// @param amount Amount of `inputToken` tokens to transfer.
|
|
||||||
function _transferInputTokensAndAttachedEth(
|
function _transferInputTokensAndAttachedEth(
|
||||||
IERC20TokenV06 inputToken,
|
TransformERC20Args memory args,
|
||||||
address from,
|
address payable to
|
||||||
address payable to,
|
|
||||||
uint256 amount
|
|
||||||
)
|
)
|
||||||
private
|
private
|
||||||
{
|
{
|
||||||
|
if (
|
||||||
|
LibERC20Transformer.isTokenETH(args.inputToken) &&
|
||||||
|
msg.value < args.inputTokenAmount
|
||||||
|
) {
|
||||||
|
// Token is ETH, so the caller must attach enough ETH to the call.
|
||||||
|
LibTransformERC20RichErrors.InsufficientEthAttachedError(
|
||||||
|
msg.value,
|
||||||
|
args.inputTokenAmount
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
|
||||||
// Transfer any attached ETH.
|
// Transfer any attached ETH.
|
||||||
if (msg.value != 0) {
|
if (msg.value != 0) {
|
||||||
to.transfer(msg.value);
|
to.transfer(msg.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer input tokens.
|
// Transfer input tokens.
|
||||||
if (!LibERC20Transformer.isTokenETH(inputToken) && amount != 0) {
|
if (!LibERC20Transformer.isTokenETH(args.inputToken)) {
|
||||||
// Token is not ETH, so pull ERC20 tokens.
|
if (args.useSelfBalance) {
|
||||||
_transferERC20TokensFrom(
|
// Use EP balance input token.
|
||||||
inputToken,
|
_transferERC20Tokens(
|
||||||
from,
|
args.inputToken,
|
||||||
to,
|
to,
|
||||||
amount
|
args.inputTokenAmount
|
||||||
);
|
);
|
||||||
} else if (msg.value < amount) {
|
} else {
|
||||||
// Token is ETH, so the caller must attach enough ETH to the call.
|
// Pull ERC20 tokens from taker.
|
||||||
LibTransformERC20RichErrors.InsufficientEthAttachedError(
|
_transferERC20TokensFrom(
|
||||||
msg.value,
|
args.inputToken,
|
||||||
amount
|
args.taker,
|
||||||
).rrevert();
|
to,
|
||||||
|
args.inputTokenAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,12 +383,12 @@ contract TransformERC20Feature is
|
|||||||
/// @param wallet The wallet instance.
|
/// @param wallet The wallet instance.
|
||||||
/// @param transformation The transformation.
|
/// @param transformation The transformation.
|
||||||
/// @param transformerDeployer The address of the transformer deployer.
|
/// @param transformerDeployer The address of the transformer deployer.
|
||||||
/// @param taker The taker address.
|
/// @param recipient The recipient address.
|
||||||
function _executeTransformation(
|
function _executeTransformation(
|
||||||
IFlashWallet wallet,
|
IFlashWallet wallet,
|
||||||
Transformation memory transformation,
|
Transformation memory transformation,
|
||||||
address transformerDeployer,
|
address transformerDeployer,
|
||||||
address payable taker
|
address payable recipient
|
||||||
)
|
)
|
||||||
private
|
private
|
||||||
{
|
{
|
||||||
@ -354,7 +406,7 @@ contract TransformERC20Feature is
|
|||||||
IERC20Transformer.transform.selector,
|
IERC20Transformer.transform.selector,
|
||||||
IERC20Transformer.TransformContext({
|
IERC20Transformer.TransformContext({
|
||||||
sender: msg.sender,
|
sender: msg.sender,
|
||||||
taker: taker,
|
recipient: recipient,
|
||||||
data: transformation.data
|
data: transformation.data
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -370,4 +422,52 @@ contract TransformERC20Feature is
|
|||||||
).rrevert();
|
).rrevert();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _executeOutputTokenTransfer(
|
||||||
|
IERC20TokenV06 outputToken,
|
||||||
|
IFlashWallet wallet,
|
||||||
|
address payable recipient
|
||||||
|
)
|
||||||
|
private
|
||||||
|
returns (uint256 transferAmount)
|
||||||
|
{
|
||||||
|
transferAmount =
|
||||||
|
LibERC20Transformer.getTokenBalanceOf(outputToken, address(wallet));
|
||||||
|
if (LibERC20Transformer.isTokenETH(outputToken)) {
|
||||||
|
wallet.executeCall(
|
||||||
|
recipient,
|
||||||
|
"",
|
||||||
|
transferAmount
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
bytes memory resultData = wallet.executeCall(
|
||||||
|
payable(address(outputToken)),
|
||||||
|
abi.encodeWithSelector(
|
||||||
|
IERC20TokenV06.transfer.selector,
|
||||||
|
recipient,
|
||||||
|
transferAmount
|
||||||
|
),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
if (resultData.length == 0) {
|
||||||
|
// If we get back 0 returndata, this may be a non-standard ERC-20 that
|
||||||
|
// does not return a boolean. Check that it at least contains code.
|
||||||
|
uint256 size;
|
||||||
|
assembly { size := extcodesize(outputToken) }
|
||||||
|
require(size > 0, "invalid token address, contains no code");
|
||||||
|
} else if (resultData.length >= 32) {
|
||||||
|
// If we get back at least 32 bytes, we know the target address
|
||||||
|
// contains code, and we assume it is a token that returned a boolean
|
||||||
|
// success value, which must be true.
|
||||||
|
uint256 result = LibBytesV06.readUint256(resultData, 0);
|
||||||
|
if (result != 1) {
|
||||||
|
LibRichErrorsV06.rrevert(resultData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If 0 < returndatasize < 32, the target is a contract, but not a
|
||||||
|
// valid token.
|
||||||
|
LibRichErrorsV06.rrevert(resultData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ contract UniswapV3Feature is
|
|||||||
/// @dev Name of this feature.
|
/// @dev Name of this feature.
|
||||||
string public constant override FEATURE_NAME = "UniswapV3Feature";
|
string public constant override FEATURE_NAME = "UniswapV3Feature";
|
||||||
/// @dev Version of this feature.
|
/// @dev Version of this feature.
|
||||||
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
|
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 1, 0);
|
||||||
/// @dev WETH contract.
|
/// @dev WETH contract.
|
||||||
IEtherTokenV06 private immutable WETH;
|
IEtherTokenV06 private immutable WETH;
|
||||||
/// @dev UniswapV3 Factory contract address prepended with '0xff' and left-aligned.
|
/// @dev UniswapV3 Factory contract address prepended with '0xff' and left-aligned.
|
||||||
@ -88,6 +88,7 @@ contract UniswapV3Feature is
|
|||||||
_registerFeatureFunction(this.sellEthForTokenToUniswapV3.selector);
|
_registerFeatureFunction(this.sellEthForTokenToUniswapV3.selector);
|
||||||
_registerFeatureFunction(this.sellTokenForEthToUniswapV3.selector);
|
_registerFeatureFunction(this.sellTokenForEthToUniswapV3.selector);
|
||||||
_registerFeatureFunction(this.sellTokenForTokenToUniswapV3.selector);
|
_registerFeatureFunction(this.sellTokenForTokenToUniswapV3.selector);
|
||||||
|
_registerFeatureFunction(this._sellHeldTokenForTokenToUniswapV3.selector);
|
||||||
_registerFeatureFunction(this.uniswapV3SwapCallback.selector);
|
_registerFeatureFunction(this.uniswapV3SwapCallback.selector);
|
||||||
return LibMigrate.MIGRATE_SUCCESS;
|
return LibMigrate.MIGRATE_SUCCESS;
|
||||||
}
|
}
|
||||||
@ -175,6 +176,33 @@ contract UniswapV3Feature is
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @dev Sell a token for another token directly against uniswap v3.
|
||||||
|
/// Private variant, uses tokens held by `address(this)`.
|
||||||
|
/// @param encodedPath Uniswap-encoded path.
|
||||||
|
/// @param sellAmount amount of the first token in the path to sell.
|
||||||
|
/// @param minBuyAmount Minimum amount of the last token in the path to buy.
|
||||||
|
/// @param recipient The recipient of the bought tokens. Can be zero for sender.
|
||||||
|
/// @return buyAmount Amount of the last token in the path bought.
|
||||||
|
function _sellHeldTokenForTokenToUniswapV3(
|
||||||
|
bytes memory encodedPath,
|
||||||
|
uint256 sellAmount,
|
||||||
|
uint256 minBuyAmount,
|
||||||
|
address recipient
|
||||||
|
)
|
||||||
|
public
|
||||||
|
override
|
||||||
|
onlySelf
|
||||||
|
returns (uint256 buyAmount)
|
||||||
|
{
|
||||||
|
buyAmount = _swap(
|
||||||
|
encodedPath,
|
||||||
|
sellAmount,
|
||||||
|
minBuyAmount,
|
||||||
|
address(this),
|
||||||
|
_normalizeRecipient(recipient)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// @dev The UniswapV3 pool swap callback which pays the funds requested
|
/// @dev The UniswapV3 pool swap callback which pays the funds requested
|
||||||
/// by the caller/pool to the pool. Can only be called by a valid
|
/// by the caller/pool to the pool. Can only be called by a valid
|
||||||
/// UniswapV3 pool.
|
/// UniswapV3 pool.
|
||||||
|
@ -24,9 +24,21 @@ import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
|||||||
|
|
||||||
|
|
||||||
interface IMultiplexFeature {
|
interface IMultiplexFeature {
|
||||||
|
// Identifies the type of subcall.
|
||||||
|
enum MultiplexSubcall {
|
||||||
|
Invalid,
|
||||||
|
RFQ,
|
||||||
|
OTC,
|
||||||
|
UniswapV2,
|
||||||
|
UniswapV3,
|
||||||
|
LiquidityProvider,
|
||||||
|
TransformERC20,
|
||||||
|
BatchSell,
|
||||||
|
MultiHopSell
|
||||||
|
}
|
||||||
|
|
||||||
// Parameters for `batchFill`.
|
// Parameters for a batch sell.
|
||||||
struct BatchFillData {
|
struct BatchSellParams {
|
||||||
// The token being sold.
|
// The token being sold.
|
||||||
IERC20TokenV06 inputToken;
|
IERC20TokenV06 inputToken;
|
||||||
// The token being bought.
|
// The token being bought.
|
||||||
@ -34,84 +46,182 @@ interface IMultiplexFeature {
|
|||||||
// The amount of `inputToken` to sell.
|
// The amount of `inputToken` to sell.
|
||||||
uint256 sellAmount;
|
uint256 sellAmount;
|
||||||
// The nested calls to perform.
|
// The nested calls to perform.
|
||||||
WrappedBatchCall[] calls;
|
BatchSellSubcall[] calls;
|
||||||
|
// Whether to use the Exchange Proxy's balance
|
||||||
|
// of input tokens.
|
||||||
|
bool useSelfBalance;
|
||||||
|
// The recipient of the bought output tokens.
|
||||||
|
address recipient;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Represents a call nested within a `batchFill`.
|
// Represents a constituent call of a batch sell.
|
||||||
struct WrappedBatchCall {
|
struct BatchSellSubcall {
|
||||||
// The selector of the function to call.
|
// The function to call.
|
||||||
bytes4 selector;
|
MultiplexSubcall id;
|
||||||
// Amount of `inputToken` to sell.
|
// Amount of input token to sell. If the highest bit is 1,
|
||||||
|
// this value represents a proportion of the total
|
||||||
|
// `sellAmount` of the batch sell. See `_normalizeSellAmount`
|
||||||
|
// for details.
|
||||||
uint256 sellAmount;
|
uint256 sellAmount;
|
||||||
// ABI-encoded parameters needed to perform the call.
|
// ABI-encoded parameters needed to perform the call.
|
||||||
bytes data;
|
bytes data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parameters for `multiHopFill`.
|
// Parameters for a multi-hop sell.
|
||||||
struct MultiHopFillData {
|
struct MultiHopSellParams {
|
||||||
// The sell path, i.e.
|
// The sell path, i.e.
|
||||||
// tokens = [inputToken, hopToken1, ..., hopTokenN, outputToken]
|
// tokens = [inputToken, hopToken1, ..., hopTokenN, outputToken]
|
||||||
address[] tokens;
|
address[] tokens;
|
||||||
// The amount of `tokens[0]` to sell.
|
// The amount of `tokens[0]` to sell.
|
||||||
uint256 sellAmount;
|
uint256 sellAmount;
|
||||||
// The nested calls to perform.
|
// The nested calls to perform.
|
||||||
WrappedMultiHopCall[] calls;
|
MultiHopSellSubcall[] calls;
|
||||||
|
// Whether to use the Exchange Proxy's balance
|
||||||
|
// of input tokens.
|
||||||
|
bool useSelfBalance;
|
||||||
|
// The recipient of the bought output tokens.
|
||||||
|
address recipient;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Represents a call nested within a `multiHopFill`.
|
// Represents a constituent call of a multi-hop sell.
|
||||||
struct WrappedMultiHopCall {
|
struct MultiHopSellSubcall {
|
||||||
// The selector of the function to call.
|
// The function to call.
|
||||||
bytes4 selector;
|
MultiplexSubcall id;
|
||||||
// ABI-encoded parameters needed to perform the call.
|
// ABI-encoded parameters needed to perform the call.
|
||||||
bytes data;
|
bytes data;
|
||||||
}
|
}
|
||||||
|
|
||||||
event LiquidityProviderSwap(
|
struct BatchSellState {
|
||||||
address inputToken,
|
// Tracks the amount of input token sold.
|
||||||
address outputToken,
|
uint256 soldAmount;
|
||||||
uint256 inputTokenAmount,
|
// Tracks the amount of output token bought.
|
||||||
uint256 outputTokenAmount,
|
uint256 boughtAmount;
|
||||||
address provider,
|
}
|
||||||
address recipient
|
|
||||||
);
|
|
||||||
|
|
||||||
event ExpiredRfqOrder(
|
struct MultiHopSellState {
|
||||||
bytes32 orderHash,
|
// This variable is used for the input and output amounts of
|
||||||
address maker,
|
// each hop. After the final hop, this will contain the output
|
||||||
uint64 expiry
|
// amount of the multi-hop sell.
|
||||||
);
|
uint256 outputTokenAmount;
|
||||||
|
// For each hop in a multi-hop sell, `from` is the
|
||||||
|
// address that holds the input tokens of the hop,
|
||||||
|
// `to` is the address that receives the output tokens
|
||||||
|
// of the hop.
|
||||||
|
// See `_computeHopTarget` for details.
|
||||||
|
address from;
|
||||||
|
address to;
|
||||||
|
// The index of the current hop in the multi-hop chain.
|
||||||
|
uint256 hopIndex;
|
||||||
|
}
|
||||||
|
|
||||||
/// @dev Executes a batch of fills selling `fillData.inputToken`
|
/// @dev Sells attached ETH for `outputToken` using the provided
|
||||||
/// for `fillData.outputToken` in sequence. Refer to the
|
/// calls.
|
||||||
/// internal variant `_batchFill` for the allowed nested
|
/// @param outputToken The token to buy.
|
||||||
/// operations.
|
/// @param calls The calls to use to sell the attached ETH.
|
||||||
/// @param fillData Encodes the input/output tokens, the sell
|
/// @param minBuyAmount The minimum amount of `outputToken` that
|
||||||
/// amount, and the nested operations for this batch fill.
|
/// must be bought for this function to not revert.
|
||||||
/// @param minBuyAmount The minimum amount of `fillData.outputToken`
|
/// @return boughtAmount The amount of `outputToken` bought.
|
||||||
/// to buy. Reverts if this amount is not met.
|
function multiplexBatchSellEthForToken(
|
||||||
/// @return outputTokenAmount The amount of the output token bought.
|
IERC20TokenV06 outputToken,
|
||||||
function batchFill(
|
BatchSellSubcall[] calldata calls,
|
||||||
BatchFillData calldata fillData,
|
|
||||||
uint256 minBuyAmount
|
uint256 minBuyAmount
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
payable
|
payable
|
||||||
returns (uint256 outputTokenAmount);
|
returns (uint256 boughtAmount);
|
||||||
|
|
||||||
/// @dev Executes a sequence of fills "hopping" through the
|
/// @dev Sells `sellAmount` of the given `inputToken` for ETH
|
||||||
/// path of tokens given by `fillData.tokens`. Refer to the
|
/// using the provided calls.
|
||||||
/// internal variant `_multiHopFill` for the allowed nested
|
/// @param inputToken The token to sell.
|
||||||
/// operations.
|
/// @param calls The calls to use to sell the input tokens.
|
||||||
/// @param fillData Encodes the path of tokens, the sell amount,
|
/// @param sellAmount The amount of `inputToken` to sell.
|
||||||
/// and the nested operations for this multi-hop fill.
|
/// @param minBuyAmount The minimum amount of ETH that
|
||||||
/// @param minBuyAmount The minimum amount of the output token
|
/// must be bought for this function to not revert.
|
||||||
/// to buy. Reverts if this amount is not met.
|
/// @return boughtAmount The amount of ETH bought.
|
||||||
/// @return outputTokenAmount The amount of the output token bought.
|
function multiplexBatchSellTokenForEth(
|
||||||
function multiHopFill(
|
IERC20TokenV06 inputToken,
|
||||||
MultiHopFillData calldata fillData,
|
BatchSellSubcall[] calldata calls,
|
||||||
|
uint256 sellAmount,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
)
|
||||||
|
external
|
||||||
|
returns (uint256 boughtAmount);
|
||||||
|
|
||||||
|
/// @dev Sells `sellAmount` of the given `inputToken` for
|
||||||
|
/// `outputToken` using the provided calls.
|
||||||
|
/// @param inputToken The token to sell.
|
||||||
|
/// @param outputToken The token to buy.
|
||||||
|
/// @param calls The calls to use to sell the input tokens.
|
||||||
|
/// @param sellAmount The amount of `inputToken` to sell.
|
||||||
|
/// @param minBuyAmount The minimum amount of `outputToken`
|
||||||
|
/// that must be bought for this function to not revert.
|
||||||
|
/// @return boughtAmount The amount of `outputToken` bought.
|
||||||
|
function multiplexBatchSellTokenForToken(
|
||||||
|
IERC20TokenV06 inputToken,
|
||||||
|
IERC20TokenV06 outputToken,
|
||||||
|
BatchSellSubcall[] calldata calls,
|
||||||
|
uint256 sellAmount,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
)
|
||||||
|
external
|
||||||
|
returns (uint256 boughtAmount);
|
||||||
|
|
||||||
|
/// @dev Sells attached ETH via the given sequence of tokens
|
||||||
|
/// and calls. `tokens[0]` must be WETH.
|
||||||
|
/// The last token in `tokens` is the output token that
|
||||||
|
/// will ultimately be sent to `msg.sender`
|
||||||
|
/// @param tokens The sequence of tokens to use for the sell,
|
||||||
|
/// i.e. `tokens[i]` will be sold for `tokens[i+1]` via
|
||||||
|
/// `calls[i]`.
|
||||||
|
/// @param calls The sequence of calls to use for the sell.
|
||||||
|
/// @param minBuyAmount The minimum amount of output tokens that
|
||||||
|
/// must be bought for this function to not revert.
|
||||||
|
/// @return boughtAmount The amount of output tokens bought.
|
||||||
|
function multiplexMultiHopSellEthForToken(
|
||||||
|
address[] calldata tokens,
|
||||||
|
MultiHopSellSubcall[] calldata calls,
|
||||||
uint256 minBuyAmount
|
uint256 minBuyAmount
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
payable
|
payable
|
||||||
returns (uint256 outputTokenAmount);
|
returns (uint256 boughtAmount);
|
||||||
|
|
||||||
|
/// @dev Sells `sellAmount` of the input token (`tokens[0]`)
|
||||||
|
/// for ETH via the given sequence of tokens and calls.
|
||||||
|
/// The last token in `tokens` must be WETH.
|
||||||
|
/// @param tokens The sequence of tokens to use for the sell,
|
||||||
|
/// i.e. `tokens[i]` will be sold for `tokens[i+1]` via
|
||||||
|
/// `calls[i]`.
|
||||||
|
/// @param calls The sequence of calls to use for the sell.
|
||||||
|
/// @param minBuyAmount The minimum amount of ETH that
|
||||||
|
/// must be bought for this function to not revert.
|
||||||
|
/// @return boughtAmount The amount of ETH bought.
|
||||||
|
function multiplexMultiHopSellTokenForEth(
|
||||||
|
address[] calldata tokens,
|
||||||
|
MultiHopSellSubcall[] calldata calls,
|
||||||
|
uint256 sellAmount,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
)
|
||||||
|
external
|
||||||
|
returns (uint256 boughtAmount);
|
||||||
|
|
||||||
|
/// @dev Sells `sellAmount` of the input token (`tokens[0]`)
|
||||||
|
/// via the given sequence of tokens and calls.
|
||||||
|
/// The last token in `tokens` is the output token that
|
||||||
|
/// will ultimately be sent to `msg.sender`
|
||||||
|
/// @param tokens The sequence of tokens to use for the sell,
|
||||||
|
/// i.e. `tokens[i]` will be sold for `tokens[i+1]` via
|
||||||
|
/// `calls[i]`.
|
||||||
|
/// @param calls The sequence of calls to use for the sell.
|
||||||
|
/// @param minBuyAmount The minimum amount of output tokens that
|
||||||
|
/// must be bought for this function to not revert.
|
||||||
|
/// @return boughtAmount The amount of output tokens bought.
|
||||||
|
function multiplexMultiHopSellTokenForToken(
|
||||||
|
address[] calldata tokens,
|
||||||
|
MultiHopSellSubcall[] calldata calls,
|
||||||
|
uint256 sellAmount,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
)
|
||||||
|
external
|
||||||
|
returns (uint256 boughtAmount);
|
||||||
}
|
}
|
||||||
|
@ -126,13 +126,18 @@ interface INativeOrdersFeature is
|
|||||||
/// @param signature The order signature.
|
/// @param signature The order signature.
|
||||||
/// @param takerTokenFillAmount Maximum taker token to fill this order with.
|
/// @param takerTokenFillAmount Maximum taker token to fill this order with.
|
||||||
/// @param taker The order taker.
|
/// @param taker The order taker.
|
||||||
|
/// @param useSelfBalance Whether to use the ExchangeProxy's transient
|
||||||
|
/// balance of taker tokens to fill the order.
|
||||||
|
/// @param recipient The recipient of the maker tokens.
|
||||||
/// @return takerTokenFilledAmount How much maker token was filled.
|
/// @return takerTokenFilledAmount How much maker token was filled.
|
||||||
/// @return makerTokenFilledAmount How much maker token was filled.
|
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||||
function _fillRfqOrder(
|
function _fillRfqOrder(
|
||||||
LibNativeOrder.RfqOrder calldata order,
|
LibNativeOrder.RfqOrder calldata order,
|
||||||
LibSignature.Signature calldata signature,
|
LibSignature.Signature calldata signature,
|
||||||
uint128 takerTokenFillAmount,
|
uint128 takerTokenFillAmount,
|
||||||
address taker
|
address taker,
|
||||||
|
bool useSelfBalance,
|
||||||
|
address recipient
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount);
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount);
|
||||||
|
@ -31,16 +31,16 @@ interface IOtcOrdersFeature {
|
|||||||
/// @param orderHash The canonical hash of the order.
|
/// @param orderHash The canonical hash of the order.
|
||||||
/// @param maker The maker of the order.
|
/// @param maker The maker of the order.
|
||||||
/// @param taker The taker of the order.
|
/// @param taker The taker of the order.
|
||||||
/// @param takerTokenFilledAmount How much taker token was filled.
|
|
||||||
/// @param makerTokenFilledAmount How much maker token was filled.
|
/// @param makerTokenFilledAmount How much maker token was filled.
|
||||||
|
/// @param takerTokenFilledAmount How much taker token was filled.
|
||||||
event OtcOrderFilled(
|
event OtcOrderFilled(
|
||||||
bytes32 orderHash,
|
bytes32 orderHash,
|
||||||
address maker,
|
address maker,
|
||||||
address taker,
|
address taker,
|
||||||
address makerToken,
|
address makerToken,
|
||||||
address takerToken,
|
address takerToken,
|
||||||
uint128 takerTokenFilledAmount,
|
uint128 makerTokenFilledAmount,
|
||||||
uint128 makerTokenFilledAmount
|
uint128 takerTokenFilledAmount
|
||||||
);
|
);
|
||||||
|
|
||||||
/// @dev Fill an OTC order for up to `takerTokenFillAmount` taker tokens.
|
/// @dev Fill an OTC order for up to `takerTokenFillAmount` taker tokens.
|
||||||
@ -48,15 +48,29 @@ interface IOtcOrdersFeature {
|
|||||||
/// @param makerSignature The order signature from the maker.
|
/// @param makerSignature The order signature from the maker.
|
||||||
/// @param takerTokenFillAmount Maximum taker token amount to fill this
|
/// @param takerTokenFillAmount Maximum taker token amount to fill this
|
||||||
/// order with.
|
/// order with.
|
||||||
/// @param unwrapWeth Whether or not to unwrap bought WETH into ETH
|
|
||||||
/// before transferring it to the taker. Should be set to false
|
|
||||||
/// @return takerTokenFilledAmount How much taker token was filled.
|
/// @return takerTokenFilledAmount How much taker token was filled.
|
||||||
/// @return makerTokenFilledAmount How much maker token was filled.
|
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||||
function fillOtcOrder(
|
function fillOtcOrder(
|
||||||
LibNativeOrder.OtcOrder calldata order,
|
LibNativeOrder.OtcOrder calldata order,
|
||||||
LibSignature.Signature calldata makerSignature,
|
LibSignature.Signature calldata makerSignature,
|
||||||
uint128 takerTokenFillAmount,
|
uint128 takerTokenFillAmount
|
||||||
bool unwrapWeth
|
)
|
||||||
|
external
|
||||||
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount);
|
||||||
|
|
||||||
|
/// @dev Fill an OTC order for up to `takerTokenFillAmount` taker tokens.
|
||||||
|
/// Unwraps bought WETH into ETH before sending it to
|
||||||
|
/// the taker.
|
||||||
|
/// @param order The OTC order.
|
||||||
|
/// @param makerSignature The order signature from the maker.
|
||||||
|
/// @param takerTokenFillAmount Maximum taker token amount to fill this
|
||||||
|
/// order with.
|
||||||
|
/// @return takerTokenFilledAmount How much taker token was filled.
|
||||||
|
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||||
|
function fillOtcOrderForEth(
|
||||||
|
LibNativeOrder.OtcOrder calldata order,
|
||||||
|
LibSignature.Signature calldata makerSignature,
|
||||||
|
uint128 takerTokenFillAmount
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount);
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount);
|
||||||
@ -80,16 +94,64 @@ interface IOtcOrdersFeature {
|
|||||||
/// @param order The OTC order.
|
/// @param order The OTC order.
|
||||||
/// @param makerSignature The order signature from the maker.
|
/// @param makerSignature The order signature from the maker.
|
||||||
/// @param takerSignature The order signature from the taker.
|
/// @param takerSignature The order signature from the taker.
|
||||||
/// @param unwrapWeth Whether or not to unwrap bought WETH into ETH
|
|
||||||
/// before transferring it to the taker. Should be set to false
|
|
||||||
/// if the maker token is not WETH.
|
|
||||||
/// @return takerTokenFilledAmount How much taker token was filled.
|
|
||||||
/// @return makerTokenFilledAmount How much maker token was filled.
|
|
||||||
function fillTakerSignedOtcOrder(
|
function fillTakerSignedOtcOrder(
|
||||||
LibNativeOrder.OtcOrder calldata order,
|
LibNativeOrder.OtcOrder calldata order,
|
||||||
LibSignature.Signature calldata makerSignature,
|
LibSignature.Signature calldata makerSignature,
|
||||||
LibSignature.Signature calldata takerSignature,
|
LibSignature.Signature calldata takerSignature
|
||||||
bool unwrapWeth
|
)
|
||||||
|
external;
|
||||||
|
|
||||||
|
/// @dev Fully fill an OTC order. "Meta-transaction" variant,
|
||||||
|
/// requires order to be signed by both maker and taker.
|
||||||
|
/// Unwraps bought WETH into ETH before sending it to
|
||||||
|
/// the taker.
|
||||||
|
/// @param order The OTC order.
|
||||||
|
/// @param makerSignature The order signature from the maker.
|
||||||
|
/// @param takerSignature The order signature from the taker.
|
||||||
|
function fillTakerSignedOtcOrderForEth(
|
||||||
|
LibNativeOrder.OtcOrder calldata order,
|
||||||
|
LibSignature.Signature calldata makerSignature,
|
||||||
|
LibSignature.Signature calldata takerSignature
|
||||||
|
)
|
||||||
|
external;
|
||||||
|
|
||||||
|
/// @dev Fills multiple taker-signed OTC orders.
|
||||||
|
/// @param orders Array of OTC orders.
|
||||||
|
/// @param makerSignatures Array of maker signatures for each order.
|
||||||
|
/// @param takerSignatures Array of taker signatures for each order.
|
||||||
|
/// @param unwrapWeth Array of booleans representing whether or not
|
||||||
|
/// to unwrap bought WETH into ETH for each order. Should be set
|
||||||
|
/// to false if the maker token is not WETH.
|
||||||
|
/// @return successes Array of booleans representing whether or not
|
||||||
|
/// each order in `orders` was filled successfully.
|
||||||
|
function batchFillTakerSignedOtcOrders(
|
||||||
|
LibNativeOrder.OtcOrder[] calldata orders,
|
||||||
|
LibSignature.Signature[] calldata makerSignatures,
|
||||||
|
LibSignature.Signature[] calldata takerSignatures,
|
||||||
|
bool[] calldata unwrapWeth
|
||||||
|
)
|
||||||
|
external
|
||||||
|
returns (bool[] memory successes);
|
||||||
|
|
||||||
|
/// @dev Fill an OTC order for up to `takerTokenFillAmount` taker tokens.
|
||||||
|
/// Internal variant.
|
||||||
|
/// @param order The OTC order.
|
||||||
|
/// @param makerSignature The order signature from the maker.
|
||||||
|
/// @param takerTokenFillAmount Maximum taker token amount to fill this
|
||||||
|
/// order with.
|
||||||
|
/// @param taker The address to fill the order in the context of.
|
||||||
|
/// @param useSelfBalance Whether to use the Exchange Proxy's balance
|
||||||
|
/// of input tokens.
|
||||||
|
/// @param recipient The recipient of the bought maker tokens.
|
||||||
|
/// @return takerTokenFilledAmount How much taker token was filled.
|
||||||
|
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||||
|
function _fillOtcOrder(
|
||||||
|
LibNativeOrder.OtcOrder calldata order,
|
||||||
|
LibSignature.Signature calldata makerSignature,
|
||||||
|
uint128 takerTokenFillAmount,
|
||||||
|
address taker,
|
||||||
|
bool useSelfBalance,
|
||||||
|
address recipient
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount);
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount);
|
||||||
|
@ -59,6 +59,10 @@ interface ITransformERC20Feature {
|
|||||||
// The transformations to execute on the token balance(s)
|
// The transformations to execute on the token balance(s)
|
||||||
// in sequence.
|
// in sequence.
|
||||||
Transformation[] transformations;
|
Transformation[] transformations;
|
||||||
|
// Whether to use the Exchange Proxy's balance of `inputToken`.
|
||||||
|
bool useSelfBalance;
|
||||||
|
// The recipient of the bought `outputToken`.
|
||||||
|
address payable recipient;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Raised upon a successful `transformERC20`.
|
/// @dev Raised upon a successful `transformERC20`.
|
||||||
|
@ -70,6 +70,22 @@ interface IUniswapV3Feature {
|
|||||||
external
|
external
|
||||||
returns (uint256 buyAmount);
|
returns (uint256 buyAmount);
|
||||||
|
|
||||||
|
/// @dev Sell a token for another token directly against uniswap v3.
|
||||||
|
/// Private variant, uses tokens held by `address(this)`.
|
||||||
|
/// @param encodedPath Uniswap-encoded path.
|
||||||
|
/// @param sellAmount amount of the first token in the path to sell.
|
||||||
|
/// @param minBuyAmount Minimum amount of the last token in the path to buy.
|
||||||
|
/// @param recipient The recipient of the bought tokens. Can be zero for sender.
|
||||||
|
/// @return buyAmount Amount of the last token in the path bought.
|
||||||
|
function _sellHeldTokenForTokenToUniswapV3(
|
||||||
|
bytes memory encodedPath,
|
||||||
|
uint256 sellAmount,
|
||||||
|
uint256 minBuyAmount,
|
||||||
|
address recipient
|
||||||
|
)
|
||||||
|
external
|
||||||
|
returns (uint256 buyAmount);
|
||||||
|
|
||||||
/// @dev The UniswapV3 pool swap callback which pays the funds requested
|
/// @dev The UniswapV3 pool swap callback which pays the funds requested
|
||||||
/// by the caller/pool to the pool. Can only be called by a valid
|
/// by the caller/pool to the pool. Can only be called by a valid
|
||||||
/// UniswapV3 pool.
|
/// UniswapV3 pool.
|
||||||
|
@ -0,0 +1,742 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
/*
|
||||||
|
|
||||||
|
Copyright 2021 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.5;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||||
|
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
|
import "../../external/ILiquidityProviderSandbox.sol";
|
||||||
|
import "../../fixins/FixinCommon.sol";
|
||||||
|
import "../../fixins/FixinEIP712.sol";
|
||||||
|
import "../../migrations/LibMigrate.sol";
|
||||||
|
import "../interfaces/IFeature.sol";
|
||||||
|
import "../interfaces/IMultiplexFeature.sol";
|
||||||
|
import "./MultiplexLiquidityProvider.sol";
|
||||||
|
import "./MultiplexOtc.sol";
|
||||||
|
import "./MultiplexRfq.sol";
|
||||||
|
import "./MultiplexTransformERC20.sol";
|
||||||
|
import "./MultiplexUniswapV2.sol";
|
||||||
|
import "./MultiplexUniswapV3.sol";
|
||||||
|
|
||||||
|
|
||||||
|
/// @dev This feature enables efficient batch and multi-hop trades
|
||||||
|
/// using different liquidity sources.
|
||||||
|
contract MultiplexFeature is
|
||||||
|
IFeature,
|
||||||
|
IMultiplexFeature,
|
||||||
|
FixinCommon,
|
||||||
|
MultiplexLiquidityProvider,
|
||||||
|
MultiplexOtc,
|
||||||
|
MultiplexRfq,
|
||||||
|
MultiplexTransformERC20,
|
||||||
|
MultiplexUniswapV2,
|
||||||
|
MultiplexUniswapV3
|
||||||
|
{
|
||||||
|
/// @dev Name of this feature.
|
||||||
|
string public constant override FEATURE_NAME = "MultiplexFeature";
|
||||||
|
/// @dev Version of this feature.
|
||||||
|
uint256 public immutable override FEATURE_VERSION = _encodeVersion(2, 0, 0);
|
||||||
|
/// @dev The highest bit of a uint256 value.
|
||||||
|
uint256 private constant HIGH_BIT = 2 ** 255;
|
||||||
|
/// @dev Mask of the lower 255 bits of a uint256 value.
|
||||||
|
uint256 private constant LOWER_255_BITS = HIGH_BIT - 1;
|
||||||
|
|
||||||
|
/// @dev The WETH token contract.
|
||||||
|
IEtherTokenV06 private immutable WETH;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
address zeroExAddress,
|
||||||
|
IEtherTokenV06 weth,
|
||||||
|
ILiquidityProviderSandbox sandbox,
|
||||||
|
address uniswapFactory,
|
||||||
|
address sushiswapFactory,
|
||||||
|
bytes32 uniswapPairInitCodeHash,
|
||||||
|
bytes32 sushiswapPairInitCodeHash
|
||||||
|
)
|
||||||
|
public
|
||||||
|
FixinEIP712(zeroExAddress)
|
||||||
|
MultiplexLiquidityProvider(sandbox)
|
||||||
|
MultiplexUniswapV2(
|
||||||
|
uniswapFactory,
|
||||||
|
sushiswapFactory,
|
||||||
|
uniswapPairInitCodeHash,
|
||||||
|
sushiswapPairInitCodeHash
|
||||||
|
)
|
||||||
|
{
|
||||||
|
WETH = weth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Initialize and register this feature.
|
||||||
|
/// Should be delegatecalled by `Migrate.migrate()`.
|
||||||
|
/// @return success `LibMigrate.SUCCESS` on success.
|
||||||
|
function migrate()
|
||||||
|
external
|
||||||
|
returns (bytes4 success)
|
||||||
|
{
|
||||||
|
_registerFeatureFunction(this.multiplexBatchSellEthForToken.selector);
|
||||||
|
_registerFeatureFunction(this.multiplexBatchSellTokenForEth.selector);
|
||||||
|
_registerFeatureFunction(this.multiplexBatchSellTokenForToken.selector);
|
||||||
|
_registerFeatureFunction(this.multiplexMultiHopSellEthForToken.selector);
|
||||||
|
_registerFeatureFunction(this.multiplexMultiHopSellTokenForEth.selector);
|
||||||
|
_registerFeatureFunction(this.multiplexMultiHopSellTokenForToken.selector);
|
||||||
|
return LibMigrate.MIGRATE_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Sells attached ETH for `outputToken` using the provided
|
||||||
|
/// calls.
|
||||||
|
/// @param outputToken The token to buy.
|
||||||
|
/// @param calls The calls to use to sell the attached ETH.
|
||||||
|
/// @param minBuyAmount The minimum amount of `outputToken` that
|
||||||
|
/// must be bought for this function to not revert.
|
||||||
|
/// @return boughtAmount The amount of `outputToken` bought.
|
||||||
|
function multiplexBatchSellEthForToken(
|
||||||
|
IERC20TokenV06 outputToken,
|
||||||
|
BatchSellSubcall[] memory calls,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
override
|
||||||
|
payable
|
||||||
|
returns (uint256 boughtAmount)
|
||||||
|
{
|
||||||
|
// Wrap ETH.
|
||||||
|
WETH.deposit{value: msg.value}();
|
||||||
|
// WETH is now held by this contract,
|
||||||
|
// so `useSelfBalance` is true.
|
||||||
|
return _multiplexBatchSell(
|
||||||
|
BatchSellParams({
|
||||||
|
inputToken: WETH,
|
||||||
|
outputToken: outputToken,
|
||||||
|
sellAmount: msg.value,
|
||||||
|
calls: calls,
|
||||||
|
useSelfBalance: true,
|
||||||
|
recipient: msg.sender
|
||||||
|
}),
|
||||||
|
minBuyAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Sells `sellAmount` of the given `inputToken` for ETH
|
||||||
|
/// using the provided calls.
|
||||||
|
/// @param inputToken The token to sell.
|
||||||
|
/// @param calls The calls to use to sell the input tokens.
|
||||||
|
/// @param sellAmount The amount of `inputToken` to sell.
|
||||||
|
/// @param minBuyAmount The minimum amount of ETH that
|
||||||
|
/// must be bought for this function to not revert.
|
||||||
|
/// @return boughtAmount The amount of ETH bought.
|
||||||
|
function multiplexBatchSellTokenForEth(
|
||||||
|
IERC20TokenV06 inputToken,
|
||||||
|
BatchSellSubcall[] memory calls,
|
||||||
|
uint256 sellAmount,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
override
|
||||||
|
returns (uint256 boughtAmount)
|
||||||
|
{
|
||||||
|
// The outputToken is implicitly WETH. The `recipient`
|
||||||
|
// of the WETH is set to this contract, since we
|
||||||
|
// must unwrap the WETH and transfer the resulting ETH.
|
||||||
|
boughtAmount = _multiplexBatchSell(
|
||||||
|
BatchSellParams({
|
||||||
|
inputToken: inputToken,
|
||||||
|
outputToken: WETH,
|
||||||
|
sellAmount: sellAmount,
|
||||||
|
calls: calls,
|
||||||
|
useSelfBalance: false,
|
||||||
|
recipient: address(this)
|
||||||
|
}),
|
||||||
|
minBuyAmount
|
||||||
|
);
|
||||||
|
// Unwrap WETH.
|
||||||
|
WETH.withdraw(boughtAmount);
|
||||||
|
// Transfer ETH to `msg.sender`.
|
||||||
|
_transferEth(msg.sender, boughtAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Sells `sellAmount` of the given `inputToken` for
|
||||||
|
/// `outputToken` using the provided calls.
|
||||||
|
/// @param inputToken The token to sell.
|
||||||
|
/// @param outputToken The token to buy.
|
||||||
|
/// @param calls The calls to use to sell the input tokens.
|
||||||
|
/// @param sellAmount The amount of `inputToken` to sell.
|
||||||
|
/// @param minBuyAmount The minimum amount of `outputToken`
|
||||||
|
/// that must be bought for this function to not revert.
|
||||||
|
/// @return boughtAmount The amount of `outputToken` bought.
|
||||||
|
function multiplexBatchSellTokenForToken(
|
||||||
|
IERC20TokenV06 inputToken,
|
||||||
|
IERC20TokenV06 outputToken,
|
||||||
|
BatchSellSubcall[] memory calls,
|
||||||
|
uint256 sellAmount,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
override
|
||||||
|
returns (uint256 boughtAmount)
|
||||||
|
{
|
||||||
|
return _multiplexBatchSell(
|
||||||
|
BatchSellParams({
|
||||||
|
inputToken: inputToken,
|
||||||
|
outputToken: outputToken,
|
||||||
|
sellAmount: sellAmount,
|
||||||
|
calls: calls,
|
||||||
|
useSelfBalance: false,
|
||||||
|
recipient: msg.sender
|
||||||
|
}),
|
||||||
|
minBuyAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Executes a batch sell and checks that at least
|
||||||
|
/// `minBuyAmount` of `outputToken` was bought.
|
||||||
|
/// @param params Batch sell parameters.
|
||||||
|
/// @param minBuyAmount The minimum amount of `outputToken` that
|
||||||
|
/// must be bought for this function to not revert.
|
||||||
|
/// @return boughtAmount The amount of `outputToken` bought.
|
||||||
|
function _multiplexBatchSell(
|
||||||
|
BatchSellParams memory params,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
)
|
||||||
|
private
|
||||||
|
returns (uint256 boughtAmount)
|
||||||
|
{
|
||||||
|
// Cache the recipient's initial balance of the output token.
|
||||||
|
uint256 balanceBefore = params.outputToken.balanceOf(params.recipient);
|
||||||
|
// Execute the batch sell.
|
||||||
|
BatchSellState memory state = _executeBatchSell(params);
|
||||||
|
// Compute the change in balance of the output token.
|
||||||
|
uint256 balanceDelta = params.outputToken.balanceOf(params.recipient)
|
||||||
|
.safeSub(balanceBefore);
|
||||||
|
// Use the minimum of the balanceDelta and the returned bought
|
||||||
|
// amount in case of weird tokens and whatnot.
|
||||||
|
boughtAmount = LibSafeMathV06.min256(balanceDelta, state.boughtAmount);
|
||||||
|
// Enforce `minBuyAmount`.
|
||||||
|
require(
|
||||||
|
boughtAmount >= minBuyAmount,
|
||||||
|
"MultiplexFeature::_multiplexBatchSell/UNDERBOUGHT"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Sells attached ETH via the given sequence of tokens
|
||||||
|
/// and calls. `tokens[0]` must be WETH.
|
||||||
|
/// The last token in `tokens` is the output token that
|
||||||
|
/// will ultimately be sent to `msg.sender`
|
||||||
|
/// @param tokens The sequence of tokens to use for the sell,
|
||||||
|
/// i.e. `tokens[i]` will be sold for `tokens[i+1]` via
|
||||||
|
/// `calls[i]`.
|
||||||
|
/// @param calls The sequence of calls to use for the sell.
|
||||||
|
/// @param minBuyAmount The minimum amount of output tokens that
|
||||||
|
/// must be bought for this function to not revert.
|
||||||
|
/// @return boughtAmount The amount of output tokens bought.
|
||||||
|
function multiplexMultiHopSellEthForToken(
|
||||||
|
address[] memory tokens,
|
||||||
|
MultiHopSellSubcall[] memory calls,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
override
|
||||||
|
payable
|
||||||
|
returns (uint256 boughtAmount)
|
||||||
|
{
|
||||||
|
// First token must be WETH.
|
||||||
|
require(
|
||||||
|
tokens[0] == address(WETH),
|
||||||
|
"MultiplexFeature::multiplexMultiHopSellEthForToken/NOT_WETH"
|
||||||
|
);
|
||||||
|
// Wrap ETH.
|
||||||
|
WETH.deposit{value: msg.value}();
|
||||||
|
// WETH is now held by this contract,
|
||||||
|
// so `useSelfBalance` is true.
|
||||||
|
return _multiplexMultiHopSell(
|
||||||
|
MultiHopSellParams({
|
||||||
|
tokens: tokens,
|
||||||
|
sellAmount: msg.value,
|
||||||
|
calls: calls,
|
||||||
|
useSelfBalance: true,
|
||||||
|
recipient: msg.sender
|
||||||
|
}),
|
||||||
|
minBuyAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Sells `sellAmount` of the input token (`tokens[0]`)
|
||||||
|
/// for ETH via the given sequence of tokens and calls.
|
||||||
|
/// The last token in `tokens` must be WETH.
|
||||||
|
/// @param tokens The sequence of tokens to use for the sell,
|
||||||
|
/// i.e. `tokens[i]` will be sold for `tokens[i+1]` via
|
||||||
|
/// `calls[i]`.
|
||||||
|
/// @param calls The sequence of calls to use for the sell.
|
||||||
|
/// @param sellAmount The amount of `inputToken` to sell.
|
||||||
|
/// @param minBuyAmount The minimum amount of ETH that
|
||||||
|
/// must be bought for this function to not revert.
|
||||||
|
/// @return boughtAmount The amount of ETH bought.
|
||||||
|
function multiplexMultiHopSellTokenForEth(
|
||||||
|
address[] memory tokens,
|
||||||
|
MultiHopSellSubcall[] memory calls,
|
||||||
|
uint256 sellAmount,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
override
|
||||||
|
returns (uint256 boughtAmount)
|
||||||
|
{
|
||||||
|
// Last token must be WETH.
|
||||||
|
require(
|
||||||
|
tokens[tokens.length - 1] == address(WETH),
|
||||||
|
"MultiplexFeature::multiplexMultiHopSellTokenForEth/NOT_WETH"
|
||||||
|
);
|
||||||
|
// The `recipient of the WETH is set to this contract, since
|
||||||
|
// we must unwrap the WETH and transfer the resulting ETH.
|
||||||
|
boughtAmount = _multiplexMultiHopSell(
|
||||||
|
MultiHopSellParams({
|
||||||
|
tokens: tokens,
|
||||||
|
sellAmount: sellAmount,
|
||||||
|
calls: calls,
|
||||||
|
useSelfBalance: false,
|
||||||
|
recipient: address(this)
|
||||||
|
}),
|
||||||
|
minBuyAmount
|
||||||
|
);
|
||||||
|
// Unwrap WETH.
|
||||||
|
WETH.withdraw(boughtAmount);
|
||||||
|
// Transfer ETH to `msg.sender`.
|
||||||
|
_transferEth(msg.sender, boughtAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Sells `sellAmount` of the input token (`tokens[0]`)
|
||||||
|
/// via the given sequence of tokens and calls.
|
||||||
|
/// The last token in `tokens` is the output token that
|
||||||
|
/// will ultimately be sent to `msg.sender`
|
||||||
|
/// @param tokens The sequence of tokens to use for the sell,
|
||||||
|
/// i.e. `tokens[i]` will be sold for `tokens[i+1]` via
|
||||||
|
/// `calls[i]`.
|
||||||
|
/// @param calls The sequence of calls to use for the sell.
|
||||||
|
/// @param sellAmount The amount of `inputToken` to sell.
|
||||||
|
/// @param minBuyAmount The minimum amount of output tokens that
|
||||||
|
/// must be bought for this function to not revert.
|
||||||
|
/// @return boughtAmount The amount of output tokens bought.
|
||||||
|
function multiplexMultiHopSellTokenForToken(
|
||||||
|
address[] memory tokens,
|
||||||
|
MultiHopSellSubcall[] memory calls,
|
||||||
|
uint256 sellAmount,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
override
|
||||||
|
returns (uint256 boughtAmount)
|
||||||
|
{
|
||||||
|
return _multiplexMultiHopSell(
|
||||||
|
MultiHopSellParams({
|
||||||
|
tokens: tokens,
|
||||||
|
sellAmount: sellAmount,
|
||||||
|
calls: calls,
|
||||||
|
useSelfBalance: false,
|
||||||
|
recipient: msg.sender
|
||||||
|
}),
|
||||||
|
minBuyAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Executes a multi-hop sell and checks that at least
|
||||||
|
/// `minBuyAmount` of output tokens were bought.
|
||||||
|
/// @param params Multi-hop sell parameters.
|
||||||
|
/// @param minBuyAmount The minimum amount of output tokens that
|
||||||
|
/// must be bought for this function to not revert.
|
||||||
|
/// @return boughtAmount The amount of output tokens bought.
|
||||||
|
function _multiplexMultiHopSell(
|
||||||
|
MultiHopSellParams memory params,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
)
|
||||||
|
private
|
||||||
|
returns (uint256 boughtAmount)
|
||||||
|
{
|
||||||
|
// There should be one call/hop between every two tokens
|
||||||
|
// in the path.
|
||||||
|
// tokens[0]––calls[0]––>tokens[1]––...––calls[n-1]––>tokens[n]
|
||||||
|
require(
|
||||||
|
params.tokens.length == params.calls.length + 1,
|
||||||
|
"MultiplexFeature::_multiplexMultiHopSell/MISMATCHED_ARRAY_LENGTHS"
|
||||||
|
);
|
||||||
|
// The output token is the last token in the path.
|
||||||
|
IERC20TokenV06 outputToken = IERC20TokenV06(
|
||||||
|
params.tokens[params.tokens.length - 1]
|
||||||
|
);
|
||||||
|
// Cache the recipient's balance of the output token.
|
||||||
|
uint256 balanceBefore = outputToken.balanceOf(params.recipient);
|
||||||
|
// Execute the multi-hop sell.
|
||||||
|
MultiHopSellState memory state = _executeMultiHopSell(params);
|
||||||
|
// Compute the change in balance of the output token.
|
||||||
|
uint256 balanceDelta = outputToken.balanceOf(params.recipient)
|
||||||
|
.safeSub(balanceBefore);
|
||||||
|
// Use the minimum of the balanceDelta and the returned bought
|
||||||
|
// amount in case of weird tokens and whatnot.
|
||||||
|
boughtAmount = LibSafeMathV06.min256(balanceDelta, state.outputTokenAmount);
|
||||||
|
// Enforce `minBuyAmount`.
|
||||||
|
require(
|
||||||
|
boughtAmount >= minBuyAmount,
|
||||||
|
"MultiplexFeature::_multiplexMultiHopSell/UNDERBOUGHT"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Iterates through the constituent calls of a batch
|
||||||
|
/// sell and executes each one, until the full amount
|
||||||
|
// has been sold.
|
||||||
|
/// @param params Batch sell parameters.
|
||||||
|
/// @return state A struct containing the amounts of `inputToken`
|
||||||
|
/// sold and `outputToken` bought.
|
||||||
|
function _executeBatchSell(BatchSellParams memory params)
|
||||||
|
private
|
||||||
|
returns (BatchSellState memory state)
|
||||||
|
{
|
||||||
|
// Iterate through the calls and execute each one
|
||||||
|
// until the full amount has been sold.
|
||||||
|
for (uint256 i = 0; i != params.calls.length; i++) {
|
||||||
|
// Check if we've hit our target.
|
||||||
|
if (state.soldAmount >= params.sellAmount) { break; }
|
||||||
|
BatchSellSubcall memory subcall = params.calls[i];
|
||||||
|
// Compute the input token amount.
|
||||||
|
uint256 inputTokenAmount = _normalizeSellAmount(
|
||||||
|
subcall.sellAmount,
|
||||||
|
params.sellAmount,
|
||||||
|
state.soldAmount
|
||||||
|
);
|
||||||
|
if (subcall.id == MultiplexSubcall.RFQ) {
|
||||||
|
_batchSellRfqOrder(
|
||||||
|
state,
|
||||||
|
params,
|
||||||
|
subcall.data,
|
||||||
|
inputTokenAmount
|
||||||
|
);
|
||||||
|
} else if (subcall.id == MultiplexSubcall.OTC) {
|
||||||
|
_batchSellOtcOrder(
|
||||||
|
state,
|
||||||
|
params,
|
||||||
|
subcall.data,
|
||||||
|
inputTokenAmount
|
||||||
|
);
|
||||||
|
} else if (subcall.id == MultiplexSubcall.UniswapV2) {
|
||||||
|
_batchSellUniswapV2(
|
||||||
|
state,
|
||||||
|
params,
|
||||||
|
subcall.data,
|
||||||
|
inputTokenAmount
|
||||||
|
);
|
||||||
|
} else if (subcall.id == MultiplexSubcall.UniswapV3) {
|
||||||
|
_batchSellUniswapV3(
|
||||||
|
state,
|
||||||
|
params,
|
||||||
|
subcall.data,
|
||||||
|
inputTokenAmount
|
||||||
|
);
|
||||||
|
} else if (subcall.id == MultiplexSubcall.LiquidityProvider) {
|
||||||
|
_batchSellLiquidityProvider(
|
||||||
|
state,
|
||||||
|
params,
|
||||||
|
subcall.data,
|
||||||
|
inputTokenAmount
|
||||||
|
);
|
||||||
|
} else if (subcall.id == MultiplexSubcall.TransformERC20) {
|
||||||
|
_batchSellTransformERC20(
|
||||||
|
state,
|
||||||
|
params,
|
||||||
|
subcall.data,
|
||||||
|
inputTokenAmount
|
||||||
|
);
|
||||||
|
} else if (subcall.id == MultiplexSubcall.MultiHopSell) {
|
||||||
|
_nestedMultiHopSell(
|
||||||
|
state,
|
||||||
|
params,
|
||||||
|
subcall.data,
|
||||||
|
inputTokenAmount
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
revert("MultiplexFeature::_executeBatchSell/INVALID_SUBCALL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require(
|
||||||
|
state.soldAmount == params.sellAmount,
|
||||||
|
"MultiplexFeature::_executeBatchSell/INCORRECT_AMOUNT_SOLD"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function executes a sequence of fills "hopping" through the
|
||||||
|
// path of tokens given by `params.tokens`.
|
||||||
|
function _executeMultiHopSell(MultiHopSellParams memory params)
|
||||||
|
private
|
||||||
|
returns (MultiHopSellState memory state)
|
||||||
|
{
|
||||||
|
// This variable is used for the input and output amounts of
|
||||||
|
// each hop. After the final hop, this will contain the output
|
||||||
|
// amount of the multi-hop fill.
|
||||||
|
state.outputTokenAmount = params.sellAmount;
|
||||||
|
// The first call may expect the input tokens to be held by
|
||||||
|
// `msg.sender`, `address(this)`, or some other address.
|
||||||
|
// Compute the expected address and transfer the input tokens
|
||||||
|
// there if necessary.
|
||||||
|
state.from = _computeHopTarget(params, 0);
|
||||||
|
// If the input tokens are currently held by `msg.sender` but
|
||||||
|
// the first hop expects them elsewhere, perform a `transferFrom`.
|
||||||
|
if (!params.useSelfBalance && state.from != msg.sender) {
|
||||||
|
_transferERC20TokensFrom(
|
||||||
|
IERC20TokenV06(params.tokens[0]),
|
||||||
|
msg.sender,
|
||||||
|
state.from,
|
||||||
|
params.sellAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// If the input tokens are currently held by `address(this)` but
|
||||||
|
// the first hop expects them elsewhere, perform a `transfer`.
|
||||||
|
if (params.useSelfBalance && state.from != address(this)) {
|
||||||
|
_transferERC20Tokens(
|
||||||
|
IERC20TokenV06(params.tokens[0]),
|
||||||
|
state.from,
|
||||||
|
params.sellAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Iterate through the calls and execute each one.
|
||||||
|
for (state.hopIndex = 0; state.hopIndex != params.calls.length; state.hopIndex++) {
|
||||||
|
MultiHopSellSubcall memory subcall = params.calls[state.hopIndex];
|
||||||
|
// Compute the recipient of the tokens that will be
|
||||||
|
// bought by the current hop.
|
||||||
|
state.to = _computeHopTarget(params, state.hopIndex + 1);
|
||||||
|
|
||||||
|
if (subcall.id == MultiplexSubcall.UniswapV2) {
|
||||||
|
_multiHopSellUniswapV2(
|
||||||
|
state,
|
||||||
|
params,
|
||||||
|
subcall.data
|
||||||
|
);
|
||||||
|
} else if (subcall.id == MultiplexSubcall.UniswapV3) {
|
||||||
|
_multiHopSellUniswapV3(state, subcall.data);
|
||||||
|
} else if (subcall.id == MultiplexSubcall.LiquidityProvider) {
|
||||||
|
_multiHopSellLiquidityProvider(
|
||||||
|
state,
|
||||||
|
params,
|
||||||
|
subcall.data
|
||||||
|
);
|
||||||
|
} else if (subcall.id == MultiplexSubcall.BatchSell) {
|
||||||
|
_nestedBatchSell(
|
||||||
|
state,
|
||||||
|
params,
|
||||||
|
subcall.data
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
revert("MultiplexFeature::_executeMultiHopSell/INVALID_SUBCALL");
|
||||||
|
}
|
||||||
|
// The recipient of the current hop will be the source
|
||||||
|
// of tokens for the next hop.
|
||||||
|
state.from = state.to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _nestedMultiHopSell(
|
||||||
|
IMultiplexFeature.BatchSellState memory state,
|
||||||
|
IMultiplexFeature.BatchSellParams memory params,
|
||||||
|
bytes memory data,
|
||||||
|
uint256 sellAmount
|
||||||
|
)
|
||||||
|
private
|
||||||
|
{
|
||||||
|
MultiHopSellParams memory multiHopParams;
|
||||||
|
// Decode the tokens and calls for the nested
|
||||||
|
// multi-hop sell.
|
||||||
|
(
|
||||||
|
multiHopParams.tokens,
|
||||||
|
multiHopParams.calls
|
||||||
|
) = abi.decode(
|
||||||
|
data,
|
||||||
|
(address[], MultiHopSellSubcall[])
|
||||||
|
);
|
||||||
|
multiHopParams.sellAmount = sellAmount;
|
||||||
|
// If the batch sell is using input tokens held by
|
||||||
|
// `address(this)`, then so should the nested
|
||||||
|
// multi-hop sell.
|
||||||
|
multiHopParams.useSelfBalance = params.useSelfBalance;
|
||||||
|
// Likewise, the recipient of the multi-hop sell is
|
||||||
|
// equal to the recipient of its containing batch sell.
|
||||||
|
multiHopParams.recipient = params.recipient;
|
||||||
|
// Execute the nested multi-hop sell.
|
||||||
|
uint256 outputTokenAmount =
|
||||||
|
_executeMultiHopSell(multiHopParams).outputTokenAmount;
|
||||||
|
// Increment the sold and bought amounts.
|
||||||
|
state.soldAmount = state.soldAmount.safeAdd(sellAmount);
|
||||||
|
state.boughtAmount = state.boughtAmount.safeAdd(outputTokenAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _nestedBatchSell(
|
||||||
|
IMultiplexFeature.MultiHopSellState memory state,
|
||||||
|
IMultiplexFeature.MultiHopSellParams memory params,
|
||||||
|
bytes memory data
|
||||||
|
)
|
||||||
|
private
|
||||||
|
{
|
||||||
|
BatchSellParams memory batchSellParams;
|
||||||
|
// Decode the calls for the nested batch sell.
|
||||||
|
batchSellParams.calls = abi.decode(
|
||||||
|
data,
|
||||||
|
(BatchSellSubcall[])
|
||||||
|
);
|
||||||
|
// The input and output tokens of the batch
|
||||||
|
// sell are the current and next tokens in
|
||||||
|
// `params.tokens`, respectively.
|
||||||
|
batchSellParams.inputToken = IERC20TokenV06(
|
||||||
|
params.tokens[state.hopIndex]
|
||||||
|
);
|
||||||
|
batchSellParams.outputToken = IERC20TokenV06(
|
||||||
|
params.tokens[state.hopIndex + 1]
|
||||||
|
);
|
||||||
|
// The `sellAmount` for the batch sell is the
|
||||||
|
// `outputTokenAmount` from the previous hop.
|
||||||
|
batchSellParams.sellAmount = state.outputTokenAmount;
|
||||||
|
// If the nested batch sell is the first hop
|
||||||
|
// and `useSelfBalance` for the containing multi-
|
||||||
|
// hop sell is false, the nested batch sell should
|
||||||
|
// pull tokens from `msg.sender` (so `batchSellParams.useSelfBalance`
|
||||||
|
// should be false). Otherwise `batchSellParams.useSelfBalance`
|
||||||
|
// should be true.
|
||||||
|
batchSellParams.useSelfBalance = state.hopIndex > 0 || params.useSelfBalance;
|
||||||
|
// `state.to` has been populated with the address
|
||||||
|
// that should receive the output tokens of the
|
||||||
|
// batch sell.
|
||||||
|
batchSellParams.recipient = state.to;
|
||||||
|
// Execute the nested batch sell.
|
||||||
|
state.outputTokenAmount =
|
||||||
|
_executeBatchSell(batchSellParams).boughtAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfers some amount of ETH to the given recipient and
|
||||||
|
// reverts if the transfer fails.
|
||||||
|
function _transferEth(address payable recipient, uint256 amount)
|
||||||
|
private
|
||||||
|
{
|
||||||
|
(bool success,) = recipient.call{value: amount}("");
|
||||||
|
require(success, "MultiplexFeature::_transferEth/TRANSFER_FAILED");
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function computes the "target" address of hop index `i` within
|
||||||
|
// a multi-hop sell.
|
||||||
|
// If `i == 0`, the target is the address which should hold the input
|
||||||
|
// tokens prior to executing `calls[0]`. Otherwise, it is the address
|
||||||
|
// that should receive `tokens[i]` upon executing `calls[i-1]`.
|
||||||
|
function _computeHopTarget(
|
||||||
|
MultiHopSellParams memory params,
|
||||||
|
uint256 i
|
||||||
|
)
|
||||||
|
private
|
||||||
|
view
|
||||||
|
returns (address target)
|
||||||
|
{
|
||||||
|
if (i == params.calls.length) {
|
||||||
|
// The last call should send the output tokens to the
|
||||||
|
// multi-hop sell recipient.
|
||||||
|
target = params.recipient;
|
||||||
|
} else {
|
||||||
|
MultiHopSellSubcall memory subcall = params.calls[i];
|
||||||
|
if (subcall.id == MultiplexSubcall.UniswapV2) {
|
||||||
|
// UniswapV2 (and Sushiswap) allow tokens to be
|
||||||
|
// transferred into the pair contract before `swap`
|
||||||
|
// is called, so we compute the pair contract's address.
|
||||||
|
(address[] memory tokens, bool isSushi) = abi.decode(
|
||||||
|
subcall.data,
|
||||||
|
(address[], bool)
|
||||||
|
);
|
||||||
|
target = _computeUniswapPairAddress(
|
||||||
|
tokens[0],
|
||||||
|
tokens[1],
|
||||||
|
isSushi
|
||||||
|
);
|
||||||
|
} else if (subcall.id == MultiplexSubcall.LiquidityProvider) {
|
||||||
|
// Similar to UniswapV2, LiquidityProvider contracts
|
||||||
|
// allow tokens to be transferred in before the swap
|
||||||
|
// is executed, so we the target is the address encoded
|
||||||
|
// in the subcall data.
|
||||||
|
(target,) = abi.decode(
|
||||||
|
subcall.data,
|
||||||
|
(address, bytes)
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
subcall.id == MultiplexSubcall.UniswapV3 ||
|
||||||
|
subcall.id == MultiplexSubcall.BatchSell
|
||||||
|
) {
|
||||||
|
// UniswapV3 uses a callback to pull in the tokens being
|
||||||
|
// sold to it. The callback implemented in `UniswapV3Feature`
|
||||||
|
// can either:
|
||||||
|
// - call `transferFrom` to move tokens from `msg.sender` to the
|
||||||
|
// UniswapV3 pool, or
|
||||||
|
// - call `transfer` to move tokens from `address(this)` to the
|
||||||
|
// UniswapV3 pool.
|
||||||
|
// A nested batch sell is similar, in that it can either:
|
||||||
|
// - use tokens from `msg.sender`, or
|
||||||
|
// - use tokens held by `address(this)`.
|
||||||
|
|
||||||
|
// Suppose UniswapV3/BatchSell is the first call in the multi-hop
|
||||||
|
// path. The input tokens are either held by `msg.sender`,
|
||||||
|
// or in the case of `multiplexMultiHopSellEthForToken` WETH is
|
||||||
|
// held by `address(this)`. The target is set accordingly.
|
||||||
|
|
||||||
|
// If this is _not_ the first call in the multi-hop path, we
|
||||||
|
// are dealing with an "intermediate" token in the multi-hop path,
|
||||||
|
// which `msg.sender` may not have an allowance set for. Thus
|
||||||
|
// target must be set to `address(this)` for `i > 0`.
|
||||||
|
if (i == 0 && !params.useSelfBalance) {
|
||||||
|
target = msg.sender;
|
||||||
|
} else {
|
||||||
|
target = address(this);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
revert("MultiplexFeature::_computeHopTarget/INVALID_SUBCALL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require(
|
||||||
|
target != address(0),
|
||||||
|
"MultiplexFeature::_computeHopTarget/TARGET_IS_NULL"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If `rawAmount` encodes a proportion of `totalSellAmount`, this function
|
||||||
|
// converts it to an absolute quantity. Caps the normalized amount to
|
||||||
|
// the remaining sell amount (`totalSellAmount - soldAmount`).
|
||||||
|
function _normalizeSellAmount(
|
||||||
|
uint256 rawAmount,
|
||||||
|
uint256 totalSellAmount,
|
||||||
|
uint256 soldAmount
|
||||||
|
)
|
||||||
|
private
|
||||||
|
pure
|
||||||
|
returns (uint256 normalized)
|
||||||
|
{
|
||||||
|
if ((rawAmount & HIGH_BIT) == HIGH_BIT) {
|
||||||
|
// If the high bit of `rawAmount` is set then the lower 255 bits
|
||||||
|
// specify a fraction of `totalSellAmount`.
|
||||||
|
return LibSafeMathV06.min256(
|
||||||
|
totalSellAmount
|
||||||
|
* LibSafeMathV06.min256(rawAmount & LOWER_255_BITS, 1e18)
|
||||||
|
/ 1e18,
|
||||||
|
totalSellAmount.safeSub(soldAmount)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return LibSafeMathV06.min256(
|
||||||
|
rawAmount,
|
||||||
|
totalSellAmount.safeSub(soldAmount)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,202 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
/*
|
||||||
|
|
||||||
|
Copyright 2021 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.5;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||||
|
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
|
import "../../external/ILiquidityProviderSandbox.sol";
|
||||||
|
import "../../fixins/FixinCommon.sol";
|
||||||
|
import "../../fixins/FixinTokenSpender.sol";
|
||||||
|
import "../../vendor/ILiquidityProvider.sol";
|
||||||
|
import "../interfaces/IMultiplexFeature.sol";
|
||||||
|
|
||||||
|
|
||||||
|
abstract contract MultiplexLiquidityProvider is
|
||||||
|
FixinCommon,
|
||||||
|
FixinTokenSpender
|
||||||
|
{
|
||||||
|
using LibERC20TokenV06 for IERC20TokenV06;
|
||||||
|
using LibSafeMathV06 for uint256;
|
||||||
|
|
||||||
|
// Same event fired by LiquidityProviderFeature
|
||||||
|
event LiquidityProviderSwap(
|
||||||
|
address inputToken,
|
||||||
|
address outputToken,
|
||||||
|
uint256 inputTokenAmount,
|
||||||
|
uint256 outputTokenAmount,
|
||||||
|
address provider,
|
||||||
|
address recipient
|
||||||
|
);
|
||||||
|
|
||||||
|
/// @dev The sandbox contract address.
|
||||||
|
ILiquidityProviderSandbox private immutable SANDBOX;
|
||||||
|
|
||||||
|
constructor(ILiquidityProviderSandbox sandbox)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
SANDBOX = sandbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A payable external function that we can delegatecall to
|
||||||
|
// swallow reverts and roll back the input token transfer.
|
||||||
|
function _batchSellLiquidityProviderExternal(
|
||||||
|
IMultiplexFeature.BatchSellParams calldata params,
|
||||||
|
bytes calldata wrappedCallData,
|
||||||
|
uint256 sellAmount
|
||||||
|
)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
returns (uint256 boughtAmount)
|
||||||
|
{
|
||||||
|
// Revert if not a delegatecall.
|
||||||
|
require(
|
||||||
|
address(this) != _implementation,
|
||||||
|
"MultiplexLiquidityProvider::_batchSellLiquidityProviderExternal/ONLY_DELEGATECALL"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Decode the provider address and auxiliary data.
|
||||||
|
(address provider, bytes memory auxiliaryData) = abi.decode(
|
||||||
|
wrappedCallData,
|
||||||
|
(address, bytes)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (params.useSelfBalance) {
|
||||||
|
// If `useSelfBalance` is true, use the input tokens
|
||||||
|
// held by `address(this)`.
|
||||||
|
_transferERC20Tokens(
|
||||||
|
params.inputToken,
|
||||||
|
provider,
|
||||||
|
sellAmount
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Otherwise, transfer the input tokens from `msg.sender`.
|
||||||
|
_transferERC20TokensFrom(
|
||||||
|
params.inputToken,
|
||||||
|
msg.sender,
|
||||||
|
provider,
|
||||||
|
sellAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Cache the recipient's balance of the output token.
|
||||||
|
uint256 balanceBefore = params.outputToken
|
||||||
|
.balanceOf(params.recipient);
|
||||||
|
// Execute the swap.
|
||||||
|
SANDBOX.executeSellTokenForToken(
|
||||||
|
ILiquidityProvider(provider),
|
||||||
|
params.inputToken,
|
||||||
|
params.outputToken,
|
||||||
|
params.recipient,
|
||||||
|
0,
|
||||||
|
auxiliaryData
|
||||||
|
);
|
||||||
|
// Compute amount of output token received by the
|
||||||
|
// recipient.
|
||||||
|
boughtAmount = params.outputToken
|
||||||
|
.balanceOf(params.recipient)
|
||||||
|
.safeSub(balanceBefore);
|
||||||
|
|
||||||
|
emit LiquidityProviderSwap(
|
||||||
|
address(params.inputToken),
|
||||||
|
address(params.outputToken),
|
||||||
|
sellAmount,
|
||||||
|
boughtAmount,
|
||||||
|
provider,
|
||||||
|
params.recipient
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _batchSellLiquidityProvider(
|
||||||
|
IMultiplexFeature.BatchSellState memory state,
|
||||||
|
IMultiplexFeature.BatchSellParams memory params,
|
||||||
|
bytes memory wrappedCallData,
|
||||||
|
uint256 sellAmount
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
// Swallow reverts
|
||||||
|
(bool success, bytes memory resultData) = _implementation.delegatecall(
|
||||||
|
abi.encodeWithSelector(
|
||||||
|
this._batchSellLiquidityProviderExternal.selector,
|
||||||
|
params,
|
||||||
|
wrappedCallData,
|
||||||
|
sellAmount
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (success) {
|
||||||
|
// Decode the output token amount on success.
|
||||||
|
uint256 boughtAmount = abi.decode(resultData, (uint256));
|
||||||
|
// Increment the sold and bought amounts.
|
||||||
|
state.soldAmount = state.soldAmount.safeAdd(sellAmount);
|
||||||
|
state.boughtAmount = state.boughtAmount.safeAdd(boughtAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is called after tokens have already been transferred
|
||||||
|
// into the liquidity provider contract (in the previous hop).
|
||||||
|
function _multiHopSellLiquidityProvider(
|
||||||
|
IMultiplexFeature.MultiHopSellState memory state,
|
||||||
|
IMultiplexFeature.MultiHopSellParams memory params,
|
||||||
|
bytes memory wrappedCallData
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
IERC20TokenV06 inputToken = IERC20TokenV06(params.tokens[state.hopIndex]);
|
||||||
|
IERC20TokenV06 outputToken = IERC20TokenV06(params.tokens[state.hopIndex + 1]);
|
||||||
|
// Decode the provider address and auxiliary data.
|
||||||
|
(address provider, bytes memory auxiliaryData) = abi.decode(
|
||||||
|
wrappedCallData,
|
||||||
|
(address, bytes)
|
||||||
|
);
|
||||||
|
// Cache the recipient's balance of the output token.
|
||||||
|
uint256 balanceBefore = outputToken
|
||||||
|
.balanceOf(state.to);
|
||||||
|
// Execute the swap.
|
||||||
|
SANDBOX.executeSellTokenForToken(
|
||||||
|
ILiquidityProvider(provider),
|
||||||
|
inputToken,
|
||||||
|
outputToken,
|
||||||
|
state.to,
|
||||||
|
0,
|
||||||
|
auxiliaryData
|
||||||
|
);
|
||||||
|
// The previous `ouputTokenAmount` was effectively the
|
||||||
|
// input amount for this call. Cache the value before
|
||||||
|
// overwriting it with the new output token amount so
|
||||||
|
// that both the input and ouput amounts can be in the
|
||||||
|
// `LiquidityProviderSwap` event.
|
||||||
|
uint256 sellAmount = state.outputTokenAmount;
|
||||||
|
// Compute amount of output token received by the
|
||||||
|
// recipient.
|
||||||
|
state.outputTokenAmount = outputToken
|
||||||
|
.balanceOf(state.to)
|
||||||
|
.safeSub(balanceBefore);
|
||||||
|
|
||||||
|
emit LiquidityProviderSwap(
|
||||||
|
address(inputToken),
|
||||||
|
address(outputToken),
|
||||||
|
sellAmount,
|
||||||
|
state.outputTokenAmount,
|
||||||
|
provider,
|
||||||
|
state.to
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
/*
|
||||||
|
|
||||||
|
Copyright 2021 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.5;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
|
import "../../fixins/FixinEIP712.sol";
|
||||||
|
import "../interfaces/IMultiplexFeature.sol";
|
||||||
|
import "../interfaces/IOtcOrdersFeature.sol";
|
||||||
|
import "../libs/LibNativeOrder.sol";
|
||||||
|
|
||||||
|
|
||||||
|
abstract contract MultiplexOtc is
|
||||||
|
FixinEIP712
|
||||||
|
{
|
||||||
|
using LibSafeMathV06 for uint256;
|
||||||
|
|
||||||
|
event ExpiredOtcOrder(
|
||||||
|
bytes32 orderHash,
|
||||||
|
address maker,
|
||||||
|
uint64 expiry
|
||||||
|
);
|
||||||
|
|
||||||
|
function _batchSellOtcOrder(
|
||||||
|
IMultiplexFeature.BatchSellState memory state,
|
||||||
|
IMultiplexFeature.BatchSellParams memory params,
|
||||||
|
bytes memory wrappedCallData,
|
||||||
|
uint256 sellAmount
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
// Decode the Otc order and signature.
|
||||||
|
(
|
||||||
|
LibNativeOrder.OtcOrder memory order,
|
||||||
|
LibSignature.Signature memory signature
|
||||||
|
) = abi.decode(
|
||||||
|
wrappedCallData,
|
||||||
|
(LibNativeOrder.OtcOrder, LibSignature.Signature)
|
||||||
|
);
|
||||||
|
// Validate tokens.
|
||||||
|
require(
|
||||||
|
order.takerToken == params.inputToken &&
|
||||||
|
order.makerToken == params.outputToken,
|
||||||
|
"MultiplexOtc::_batchSellOtcOrder/OTC_ORDER_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;
|
||||||
|
}
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
// Increment the sold and bought amounts.
|
||||||
|
state.soldAmount = state.soldAmount.safeAdd(takerTokenFilledAmount);
|
||||||
|
state.boughtAmount = state.boughtAmount.safeAdd(makerTokenFilledAmount);
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
/*
|
||||||
|
|
||||||
|
Copyright 2021 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.5;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
|
import "../../fixins/FixinEIP712.sol";
|
||||||
|
import "../interfaces/IMultiplexFeature.sol";
|
||||||
|
import "../interfaces/INativeOrdersFeature.sol";
|
||||||
|
import "../libs/LibNativeOrder.sol";
|
||||||
|
|
||||||
|
|
||||||
|
abstract contract MultiplexRfq is
|
||||||
|
FixinEIP712
|
||||||
|
{
|
||||||
|
using LibSafeMathV06 for uint256;
|
||||||
|
|
||||||
|
event ExpiredRfqOrder(
|
||||||
|
bytes32 orderHash,
|
||||||
|
address maker,
|
||||||
|
uint64 expiry
|
||||||
|
);
|
||||||
|
|
||||||
|
function _batchSellRfqOrder(
|
||||||
|
IMultiplexFeature.BatchSellState memory state,
|
||||||
|
IMultiplexFeature.BatchSellParams memory params,
|
||||||
|
bytes memory wrappedCallData,
|
||||||
|
uint256 sellAmount
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
// Decode the RFQ order and signature.
|
||||||
|
(
|
||||||
|
LibNativeOrder.RfqOrder memory order,
|
||||||
|
LibSignature.Signature memory signature
|
||||||
|
) = abi.decode(
|
||||||
|
wrappedCallData,
|
||||||
|
(LibNativeOrder.RfqOrder, LibSignature.Signature)
|
||||||
|
);
|
||||||
|
// Pre-emptively check if the order is expired.
|
||||||
|
if (order.expiry <= uint64(block.timestamp)) {
|
||||||
|
bytes32 orderHash = _getEIP712Hash(
|
||||||
|
LibNativeOrder.getRfqOrderStructHash(order)
|
||||||
|
);
|
||||||
|
emit ExpiredRfqOrder(
|
||||||
|
orderHash,
|
||||||
|
order.maker,
|
||||||
|
order.expiry
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Validate tokens.
|
||||||
|
require(
|
||||||
|
order.takerToken == params.inputToken &&
|
||||||
|
order.makerToken == params.outputToken,
|
||||||
|
"MultiplexRfq::_batchSellRfqOrder/RFQ_ORDER_INVALID_TOKENS"
|
||||||
|
);
|
||||||
|
// Try filling the RFQ order. Swallows reverts.
|
||||||
|
try
|
||||||
|
INativeOrdersFeature(address(this))._fillRfqOrder
|
||||||
|
(
|
||||||
|
order,
|
||||||
|
signature,
|
||||||
|
sellAmount.safeDowncastToUint128(),
|
||||||
|
msg.sender,
|
||||||
|
params.useSelfBalance,
|
||||||
|
params.recipient
|
||||||
|
)
|
||||||
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||||
|
{
|
||||||
|
// Increment the sold and bought amounts.
|
||||||
|
state.soldAmount = state.soldAmount.safeAdd(takerTokenFilledAmount);
|
||||||
|
state.boughtAmount = state.boughtAmount.safeAdd(makerTokenFilledAmount);
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
/*
|
||||||
|
|
||||||
|
Copyright 2021 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.5;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
|
import "../interfaces/IMultiplexFeature.sol";
|
||||||
|
import "../interfaces/ITransformERC20Feature.sol";
|
||||||
|
|
||||||
|
|
||||||
|
abstract contract MultiplexTransformERC20 {
|
||||||
|
|
||||||
|
using LibSafeMathV06 for uint256;
|
||||||
|
|
||||||
|
function _batchSellTransformERC20(
|
||||||
|
IMultiplexFeature.BatchSellState memory state,
|
||||||
|
IMultiplexFeature.BatchSellParams memory params,
|
||||||
|
bytes memory wrappedCallData,
|
||||||
|
uint256 sellAmount
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
ITransformERC20Feature.TransformERC20Args memory args;
|
||||||
|
// We want the TransformedERC20 event to have
|
||||||
|
// `msg.sender` as the taker.
|
||||||
|
args.taker = msg.sender;
|
||||||
|
args.inputToken = params.inputToken;
|
||||||
|
args.outputToken = params.outputToken;
|
||||||
|
args.inputTokenAmount = sellAmount;
|
||||||
|
args.minOutputTokenAmount = 0;
|
||||||
|
args.useSelfBalance = params.useSelfBalance;
|
||||||
|
args.recipient = payable(params.recipient);
|
||||||
|
(args.transformations) = abi.decode(
|
||||||
|
wrappedCallData,
|
||||||
|
(ITransformERC20Feature.Transformation[])
|
||||||
|
);
|
||||||
|
// Execute the transformations and swallow reverts.
|
||||||
|
try ITransformERC20Feature(address(this))._transformERC20
|
||||||
|
(args)
|
||||||
|
returns (uint256 outputTokenAmount)
|
||||||
|
{
|
||||||
|
// Increment the sold and bought amounts.
|
||||||
|
state.soldAmount = state.soldAmount.safeAdd(sellAmount);
|
||||||
|
state.boughtAmount = state.boughtAmount.safeAdd(outputTokenAmount);
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,290 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
/*
|
||||||
|
|
||||||
|
Copyright 2021 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.5;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
|
import "../../fixins/FixinCommon.sol";
|
||||||
|
import "../../fixins/FixinTokenSpender.sol";
|
||||||
|
import "../../vendor/IUniswapV2Pair.sol";
|
||||||
|
import "../interfaces/IMultiplexFeature.sol";
|
||||||
|
|
||||||
|
|
||||||
|
abstract contract MultiplexUniswapV2 is
|
||||||
|
FixinCommon,
|
||||||
|
FixinTokenSpender
|
||||||
|
{
|
||||||
|
using LibSafeMathV06 for uint256;
|
||||||
|
|
||||||
|
// address of the UniswapV2Factory contract.
|
||||||
|
address private immutable UNISWAP_FACTORY;
|
||||||
|
// address of the (Sushiswap) UniswapV2Factory contract.
|
||||||
|
address private immutable SUSHISWAP_FACTORY;
|
||||||
|
// Init code hash of the UniswapV2Pair contract.
|
||||||
|
bytes32 private immutable UNISWAP_PAIR_INIT_CODE_HASH;
|
||||||
|
// Init code hash of the (Sushiswap) UniswapV2Pair contract.
|
||||||
|
bytes32 private immutable SUSHISWAP_PAIR_INIT_CODE_HASH;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
address uniswapFactory,
|
||||||
|
address sushiswapFactory,
|
||||||
|
bytes32 uniswapPairInitCodeHash,
|
||||||
|
bytes32 sushiswapPairInitCodeHash
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
UNISWAP_FACTORY = uniswapFactory;
|
||||||
|
SUSHISWAP_FACTORY = sushiswapFactory;
|
||||||
|
UNISWAP_PAIR_INIT_CODE_HASH = uniswapPairInitCodeHash;
|
||||||
|
SUSHISWAP_PAIR_INIT_CODE_HASH = sushiswapPairInitCodeHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A payable external function that we can delegatecall to
|
||||||
|
// swallow reverts and roll back the input token transfer.
|
||||||
|
function _batchSellUniswapV2External(
|
||||||
|
IMultiplexFeature.BatchSellParams calldata params,
|
||||||
|
bytes calldata wrappedCallData,
|
||||||
|
uint256 sellAmount
|
||||||
|
)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
returns (uint256 boughtAmount)
|
||||||
|
{
|
||||||
|
// Revert is not a delegatecall.
|
||||||
|
require(
|
||||||
|
address(this) != _implementation,
|
||||||
|
"MultiplexLiquidityProvider::_batchSellUniswapV2External/ONLY_DELEGATECALL"
|
||||||
|
);
|
||||||
|
|
||||||
|
(address[] memory tokens, bool isSushi) = abi.decode(
|
||||||
|
wrappedCallData,
|
||||||
|
(address[], bool)
|
||||||
|
);
|
||||||
|
// Validate tokens
|
||||||
|
require(
|
||||||
|
tokens.length >= 2 &&
|
||||||
|
tokens[0] == address(params.inputToken) &&
|
||||||
|
tokens[tokens.length - 1] == address(params.outputToken),
|
||||||
|
"MultiplexUniswapV2::_batchSellUniswapV2/INVALID_TOKENS"
|
||||||
|
);
|
||||||
|
// Compute the address of the first Uniswap pair
|
||||||
|
// contract that will execute a swap.
|
||||||
|
address firstPairAddress = _computeUniswapPairAddress(
|
||||||
|
tokens[0],
|
||||||
|
tokens[1],
|
||||||
|
isSushi
|
||||||
|
);
|
||||||
|
// `_sellToUniswapV2` assumes the input tokens have been
|
||||||
|
// transferred into the pair contract before it is called,
|
||||||
|
// so we transfer the tokens in now (either from `msg.sender`
|
||||||
|
// or using the Exchange Proxy's balance).
|
||||||
|
if (params.useSelfBalance) {
|
||||||
|
_transferERC20Tokens(
|
||||||
|
IERC20TokenV06(tokens[0]),
|
||||||
|
firstPairAddress,
|
||||||
|
sellAmount
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_transferERC20TokensFrom(
|
||||||
|
IERC20TokenV06(tokens[0]),
|
||||||
|
msg.sender,
|
||||||
|
firstPairAddress,
|
||||||
|
sellAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Execute the Uniswap/Sushiswap trade.
|
||||||
|
return _sellToUniswapV2(
|
||||||
|
tokens,
|
||||||
|
sellAmount,
|
||||||
|
isSushi,
|
||||||
|
firstPairAddress,
|
||||||
|
params.recipient
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _batchSellUniswapV2(
|
||||||
|
IMultiplexFeature.BatchSellState memory state,
|
||||||
|
IMultiplexFeature.BatchSellParams memory params,
|
||||||
|
bytes memory wrappedCallData,
|
||||||
|
uint256 sellAmount
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
// Swallow reverts
|
||||||
|
(bool success, bytes memory resultData) = _implementation.delegatecall(
|
||||||
|
abi.encodeWithSelector(
|
||||||
|
this._batchSellUniswapV2External.selector,
|
||||||
|
params,
|
||||||
|
wrappedCallData,
|
||||||
|
sellAmount
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (success) {
|
||||||
|
// Decode the output token amount on success.
|
||||||
|
uint256 boughtAmount = abi.decode(resultData, (uint256));
|
||||||
|
// Increment the sold and bought amounts.
|
||||||
|
state.soldAmount = state.soldAmount.safeAdd(sellAmount);
|
||||||
|
state.boughtAmount = state.boughtAmount.safeAdd(boughtAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _multiHopSellUniswapV2(
|
||||||
|
IMultiplexFeature.MultiHopSellState memory state,
|
||||||
|
IMultiplexFeature.MultiHopSellParams memory params,
|
||||||
|
bytes memory wrappedCallData
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
(address[] memory tokens, bool isSushi) = abi.decode(
|
||||||
|
wrappedCallData,
|
||||||
|
(address[], bool)
|
||||||
|
);
|
||||||
|
// Validate the tokens
|
||||||
|
require(
|
||||||
|
tokens.length >= 2 &&
|
||||||
|
tokens[0] == params.tokens[state.hopIndex] &&
|
||||||
|
tokens[tokens.length - 1] == params.tokens[state.hopIndex + 1],
|
||||||
|
"MultiplexUniswapV2::_multiHopSellUniswapV2/INVALID_TOKENS"
|
||||||
|
);
|
||||||
|
// Execute the Uniswap/Sushiswap trade.
|
||||||
|
state.outputTokenAmount = _sellToUniswapV2(
|
||||||
|
tokens,
|
||||||
|
state.outputTokenAmount,
|
||||||
|
isSushi,
|
||||||
|
state.from,
|
||||||
|
state.to
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _sellToUniswapV2(
|
||||||
|
address[] memory tokens,
|
||||||
|
uint256 sellAmount,
|
||||||
|
bool isSushi,
|
||||||
|
address pairAddress,
|
||||||
|
address recipient
|
||||||
|
)
|
||||||
|
private
|
||||||
|
returns (uint256 outputTokenAmount)
|
||||||
|
{
|
||||||
|
// Iterate through `tokens` perform a swap against the Uniswap
|
||||||
|
// pair contract for each `(tokens[i], tokens[i+1])`.
|
||||||
|
for (uint256 i = 0; i < tokens.length - 1; i++) {
|
||||||
|
(address inputToken, address outputToken) = (tokens[i], tokens[i + 1]);
|
||||||
|
// Compute the output token amount
|
||||||
|
outputTokenAmount = _computeUniswapOutputAmount(
|
||||||
|
pairAddress,
|
||||||
|
inputToken,
|
||||||
|
outputToken,
|
||||||
|
sellAmount
|
||||||
|
);
|
||||||
|
(uint256 amount0Out, uint256 amount1Out) = inputToken < outputToken
|
||||||
|
? (uint256(0), outputTokenAmount)
|
||||||
|
: (outputTokenAmount, uint256(0));
|
||||||
|
// The Uniswap pair contract will transfer the output tokens to
|
||||||
|
// the next pair contract if there is one, otherwise transfer to
|
||||||
|
// `recipient`.
|
||||||
|
address to = i < tokens.length - 2
|
||||||
|
? _computeUniswapPairAddress(outputToken, tokens[i + 2], isSushi)
|
||||||
|
: recipient;
|
||||||
|
// Execute the swap.
|
||||||
|
IUniswapV2Pair(pairAddress).swap(
|
||||||
|
amount0Out,
|
||||||
|
amount1Out,
|
||||||
|
to,
|
||||||
|
new bytes(0)
|
||||||
|
);
|
||||||
|
// To avoid recomputing the pair address of the next pair, store
|
||||||
|
// `to` in `pairAddress`.
|
||||||
|
pairAddress = to;
|
||||||
|
// The outputTokenAmount
|
||||||
|
sellAmount = outputTokenAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computes the Uniswap/Sushiswap pair contract address for the
|
||||||
|
// given tokens.
|
||||||
|
function _computeUniswapPairAddress(
|
||||||
|
address tokenA,
|
||||||
|
address tokenB,
|
||||||
|
bool isSushi
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
view
|
||||||
|
returns (address pairAddress)
|
||||||
|
{
|
||||||
|
// Tokens are lexicographically sorted in the Uniswap contract.
|
||||||
|
(address token0, address token1) = tokenA < tokenB
|
||||||
|
? (tokenA, tokenB)
|
||||||
|
: (tokenB, tokenA);
|
||||||
|
if (isSushi) {
|
||||||
|
// Use the Sushiswap factory address and codehash
|
||||||
|
return address(uint256(keccak256(abi.encodePacked(
|
||||||
|
hex'ff',
|
||||||
|
SUSHISWAP_FACTORY,
|
||||||
|
keccak256(abi.encodePacked(token0, token1)),
|
||||||
|
SUSHISWAP_PAIR_INIT_CODE_HASH
|
||||||
|
))));
|
||||||
|
} else {
|
||||||
|
// Use the Uniswap factory address and codehash
|
||||||
|
return address(uint256(keccak256(abi.encodePacked(
|
||||||
|
hex'ff',
|
||||||
|
UNISWAP_FACTORY,
|
||||||
|
keccak256(abi.encodePacked(token0, token1)),
|
||||||
|
UNISWAP_PAIR_INIT_CODE_HASH
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computes the the amount of output token that would be bought
|
||||||
|
// from Uniswap/Sushiswap given the input amount.
|
||||||
|
function _computeUniswapOutputAmount(
|
||||||
|
address pairAddress,
|
||||||
|
address inputToken,
|
||||||
|
address outputToken,
|
||||||
|
uint256 inputAmount
|
||||||
|
)
|
||||||
|
private
|
||||||
|
view
|
||||||
|
returns (uint256 outputAmount)
|
||||||
|
{
|
||||||
|
// Input amount should be non-zero.
|
||||||
|
require(
|
||||||
|
inputAmount > 0,
|
||||||
|
"MultiplexUniswapV2::_computeUniswapOutputAmount/INSUFFICIENT_INPUT_AMOUNT"
|
||||||
|
);
|
||||||
|
// Query the reserves of the pair contract.
|
||||||
|
(uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pairAddress).getReserves();
|
||||||
|
// Reserves must be non-zero.
|
||||||
|
require(
|
||||||
|
reserve0 > 0 && reserve1 > 0,
|
||||||
|
'MultiplexUniswapV2::_computeUniswapOutputAmount/INSUFFICIENT_LIQUIDITY'
|
||||||
|
);
|
||||||
|
// Tokens are lexicographically sorted in the Uniswap contract.
|
||||||
|
(uint256 inputReserve, uint256 outputReserve) = inputToken < outputToken
|
||||||
|
? (reserve0, reserve1)
|
||||||
|
: (reserve1, reserve0);
|
||||||
|
// Compute the output amount.
|
||||||
|
uint256 inputAmountWithFee = inputAmount.safeMul(997);
|
||||||
|
uint256 numerator = inputAmountWithFee.safeMul(outputReserve);
|
||||||
|
uint256 denominator = inputReserve.safeMul(1000).safeAdd(inputAmountWithFee);
|
||||||
|
return numerator / denominator;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
/*
|
||||||
|
|
||||||
|
Copyright 2021 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.5;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
|
import "../../fixins/FixinTokenSpender.sol";
|
||||||
|
import "../interfaces/IMultiplexFeature.sol";
|
||||||
|
import "../interfaces/IUniswapV3Feature.sol";
|
||||||
|
|
||||||
|
|
||||||
|
abstract contract MultiplexUniswapV3 is
|
||||||
|
FixinTokenSpender
|
||||||
|
{
|
||||||
|
using LibSafeMathV06 for uint256;
|
||||||
|
|
||||||
|
function _batchSellUniswapV3(
|
||||||
|
IMultiplexFeature.BatchSellState memory state,
|
||||||
|
IMultiplexFeature.BatchSellParams memory params,
|
||||||
|
bytes memory wrappedCallData,
|
||||||
|
uint256 sellAmount
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
bool success;
|
||||||
|
bytes memory resultData;
|
||||||
|
if (params.useSelfBalance) {
|
||||||
|
// If the tokens are held by `address(this)`, we call
|
||||||
|
// the `onlySelf` variant `_sellHeldTokenForTokenToUniswapV3`,
|
||||||
|
// which uses the Exchange Proxy's balance of input token.
|
||||||
|
(success, resultData) = address(this).call(
|
||||||
|
abi.encodeWithSelector(
|
||||||
|
IUniswapV3Feature._sellHeldTokenForTokenToUniswapV3.selector,
|
||||||
|
wrappedCallData,
|
||||||
|
sellAmount,
|
||||||
|
0,
|
||||||
|
params.recipient
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Otherwise, we self-delegatecall the normal variant
|
||||||
|
// `sellTokenForTokenToUniswapV3`, which pulls the input token
|
||||||
|
// from `msg.sender`.
|
||||||
|
(success, resultData) = address(this).delegatecall(
|
||||||
|
abi.encodeWithSelector(
|
||||||
|
IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector,
|
||||||
|
wrappedCallData,
|
||||||
|
sellAmount,
|
||||||
|
0,
|
||||||
|
params.recipient
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
// Decode the output token amount on success.
|
||||||
|
uint256 outputTokenAmount = abi.decode(resultData, (uint256));
|
||||||
|
// Increment the sold and bought amounts.
|
||||||
|
state.soldAmount = state.soldAmount.safeAdd(sellAmount);
|
||||||
|
state.boughtAmount = state.boughtAmount.safeAdd(outputTokenAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _multiHopSellUniswapV3(
|
||||||
|
IMultiplexFeature.MultiHopSellState memory state,
|
||||||
|
bytes memory wrappedCallData
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
bool success;
|
||||||
|
bytes memory resultData;
|
||||||
|
if (state.from == address(this)) {
|
||||||
|
// If the tokens are held by `address(this)`, we call
|
||||||
|
// the `onlySelf` variant `_sellHeldTokenForTokenToUniswapV3`,
|
||||||
|
// which uses the Exchange Proxy's balance of input token.
|
||||||
|
(success, resultData) = address(this).call(
|
||||||
|
abi.encodeWithSelector(
|
||||||
|
IUniswapV3Feature._sellHeldTokenForTokenToUniswapV3.selector,
|
||||||
|
wrappedCallData,
|
||||||
|
state.outputTokenAmount,
|
||||||
|
0,
|
||||||
|
state.to
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Otherwise, we self-delegatecall the normal variant
|
||||||
|
// `sellTokenForTokenToUniswapV3`, which pulls the input token
|
||||||
|
// from `msg.sender`.
|
||||||
|
(success, resultData) = address(this).delegatecall(
|
||||||
|
abi.encodeWithSelector(
|
||||||
|
IUniswapV3Feature.sellTokenForTokenToUniswapV3.selector,
|
||||||
|
wrappedCallData,
|
||||||
|
state.outputTokenAmount,
|
||||||
|
0,
|
||||||
|
state.to
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
// Decode the output token amount on success.
|
||||||
|
state.outputTokenAmount = abi.decode(resultData, (uint256));
|
||||||
|
} else {
|
||||||
|
revert("MultiplexUniswapV3::_multiHopSellUniswapV3/SWAP_FAILED");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -52,8 +52,10 @@ abstract contract NativeOrdersSettlement is
|
|||||||
bytes32 orderHash;
|
bytes32 orderHash;
|
||||||
// Maker of the order.
|
// Maker of the order.
|
||||||
address maker;
|
address maker;
|
||||||
// Taker of the order.
|
// The address holding the taker tokens.
|
||||||
address taker;
|
address payer;
|
||||||
|
// Recipient of the maker tokens.
|
||||||
|
address recipient;
|
||||||
// Maker token.
|
// Maker token.
|
||||||
IERC20TokenV06 makerToken;
|
IERC20TokenV06 makerToken;
|
||||||
// Taker token.
|
// Taker token.
|
||||||
@ -82,6 +84,22 @@ abstract contract NativeOrdersSettlement is
|
|||||||
address sender;
|
address sender;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @dev Params for `_fillRfqOrderPrivate()`
|
||||||
|
struct FillRfqOrderPrivateParams {
|
||||||
|
LibNativeOrder.RfqOrder order;
|
||||||
|
// The order signature.
|
||||||
|
LibSignature.Signature signature;
|
||||||
|
// Maximum taker token to fill this order with.
|
||||||
|
uint128 takerTokenFillAmount;
|
||||||
|
// The order taker.
|
||||||
|
address taker;
|
||||||
|
// Whether to use the Exchange Proxy's balance
|
||||||
|
// of taker tokens.
|
||||||
|
bool useSelfBalance;
|
||||||
|
// The recipient of the maker tokens.
|
||||||
|
address recipient;
|
||||||
|
}
|
||||||
|
|
||||||
// @dev Fill results returned by `_fillLimitOrderPrivate()` and
|
// @dev Fill results returned by `_fillLimitOrderPrivate()` and
|
||||||
/// `_fillRfqOrderPrivate()`.
|
/// `_fillRfqOrderPrivate()`.
|
||||||
struct FillNativeOrderResults {
|
struct FillNativeOrderResults {
|
||||||
@ -154,12 +172,14 @@ abstract contract NativeOrdersSettlement is
|
|||||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||||
{
|
{
|
||||||
FillNativeOrderResults memory results =
|
FillNativeOrderResults memory results =
|
||||||
_fillRfqOrderPrivate(
|
_fillRfqOrderPrivate(FillRfqOrderPrivateParams({
|
||||||
order,
|
order: order,
|
||||||
signature,
|
signature: signature,
|
||||||
takerTokenFillAmount,
|
takerTokenFillAmount: takerTokenFillAmount,
|
||||||
msg.sender
|
taker: msg.sender,
|
||||||
);
|
useSelfBalance: false,
|
||||||
|
recipient: msg.sender
|
||||||
|
}));
|
||||||
(takerTokenFilledAmount, makerTokenFilledAmount) = (
|
(takerTokenFilledAmount, makerTokenFilledAmount) = (
|
||||||
results.takerTokenFilledAmount,
|
results.takerTokenFilledAmount,
|
||||||
results.makerTokenFilledAmount
|
results.makerTokenFilledAmount
|
||||||
@ -220,12 +240,14 @@ abstract contract NativeOrdersSettlement is
|
|||||||
returns (uint128 makerTokenFilledAmount)
|
returns (uint128 makerTokenFilledAmount)
|
||||||
{
|
{
|
||||||
FillNativeOrderResults memory results =
|
FillNativeOrderResults memory results =
|
||||||
_fillRfqOrderPrivate(
|
_fillRfqOrderPrivate(FillRfqOrderPrivateParams({
|
||||||
order,
|
order: order,
|
||||||
signature,
|
signature: signature,
|
||||||
takerTokenFillAmount,
|
takerTokenFillAmount: takerTokenFillAmount,
|
||||||
msg.sender
|
taker: msg.sender,
|
||||||
);
|
useSelfBalance: false,
|
||||||
|
recipient: msg.sender
|
||||||
|
}));
|
||||||
// Must have filled exactly the amount requested.
|
// Must have filled exactly the amount requested.
|
||||||
if (results.takerTokenFilledAmount < takerTokenFillAmount) {
|
if (results.takerTokenFilledAmount < takerTokenFillAmount) {
|
||||||
LibNativeOrdersRichErrors.FillOrKillFailedError(
|
LibNativeOrdersRichErrors.FillOrKillFailedError(
|
||||||
@ -260,33 +282,36 @@ abstract contract NativeOrdersSettlement is
|
|||||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||||
{
|
{
|
||||||
FillNativeOrderResults memory results =
|
FillNativeOrderResults memory results =
|
||||||
_fillLimitOrderPrivate(FillLimitOrderPrivateParams({
|
_fillLimitOrderPrivate(FillLimitOrderPrivateParams(
|
||||||
order: order,
|
order,
|
||||||
signature: signature,
|
signature,
|
||||||
takerTokenFillAmount: takerTokenFillAmount,
|
takerTokenFillAmount,
|
||||||
taker: taker,
|
taker,
|
||||||
sender: sender
|
sender
|
||||||
}));
|
));
|
||||||
(takerTokenFilledAmount, makerTokenFilledAmount) = (
|
(takerTokenFilledAmount, makerTokenFilledAmount) = (
|
||||||
results.takerTokenFilledAmount,
|
results.takerTokenFilledAmount,
|
||||||
results.makerTokenFilledAmount
|
results.makerTokenFilledAmount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Fill an RFQ order. Internal variant. ETH protocol fees can be
|
/// @dev Fill an RFQ order. Internal variant.
|
||||||
/// attached to this call. Any unspent ETH will be refunded to
|
|
||||||
/// `msg.sender` (not `sender`).
|
|
||||||
/// @param order The RFQ order.
|
/// @param order The RFQ order.
|
||||||
/// @param signature The order signature.
|
/// @param signature The order signature.
|
||||||
/// @param takerTokenFillAmount Maximum taker token to fill this order with.
|
/// @param takerTokenFillAmount Maximum taker token to fill this order with.
|
||||||
/// @param taker The order taker.
|
/// @param taker The order taker.
|
||||||
|
/// @param useSelfBalance Whether to use the ExchangeProxy's transient
|
||||||
|
/// balance of taker tokens to fill the order.
|
||||||
|
/// @param recipient The recipient of the maker tokens.
|
||||||
/// @return takerTokenFilledAmount How much maker token was filled.
|
/// @return takerTokenFilledAmount How much maker token was filled.
|
||||||
/// @return makerTokenFilledAmount How much maker token was filled.
|
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||||
function _fillRfqOrder(
|
function _fillRfqOrder(
|
||||||
LibNativeOrder.RfqOrder memory order,
|
LibNativeOrder.RfqOrder memory order,
|
||||||
LibSignature.Signature memory signature,
|
LibSignature.Signature memory signature,
|
||||||
uint128 takerTokenFillAmount,
|
uint128 takerTokenFillAmount,
|
||||||
address taker
|
address taker,
|
||||||
|
bool useSelfBalance,
|
||||||
|
address recipient
|
||||||
)
|
)
|
||||||
public
|
public
|
||||||
virtual
|
virtual
|
||||||
@ -294,12 +319,14 @@ abstract contract NativeOrdersSettlement is
|
|||||||
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||||
{
|
{
|
||||||
FillNativeOrderResults memory results =
|
FillNativeOrderResults memory results =
|
||||||
_fillRfqOrderPrivate(
|
_fillRfqOrderPrivate(FillRfqOrderPrivateParams(
|
||||||
order,
|
order,
|
||||||
signature,
|
signature,
|
||||||
takerTokenFillAmount,
|
takerTokenFillAmount,
|
||||||
taker
|
taker,
|
||||||
);
|
useSelfBalance,
|
||||||
|
recipient
|
||||||
|
));
|
||||||
(takerTokenFilledAmount, makerTokenFilledAmount) = (
|
(takerTokenFilledAmount, makerTokenFilledAmount) = (
|
||||||
results.takerTokenFilledAmount,
|
results.takerTokenFilledAmount,
|
||||||
results.makerTokenFilledAmount
|
results.makerTokenFilledAmount
|
||||||
@ -387,7 +414,8 @@ abstract contract NativeOrdersSettlement is
|
|||||||
SettleOrderInfo({
|
SettleOrderInfo({
|
||||||
orderHash: orderInfo.orderHash,
|
orderHash: orderInfo.orderHash,
|
||||||
maker: params.order.maker,
|
maker: params.order.maker,
|
||||||
taker: params.taker,
|
payer: params.taker,
|
||||||
|
recipient: params.taker,
|
||||||
makerToken: IERC20TokenV06(params.order.makerToken),
|
makerToken: IERC20TokenV06(params.order.makerToken),
|
||||||
takerToken: IERC20TokenV06(params.order.takerToken),
|
takerToken: IERC20TokenV06(params.order.takerToken),
|
||||||
makerAmount: params.order.makerAmount,
|
makerAmount: params.order.makerAmount,
|
||||||
@ -427,22 +455,14 @@ abstract contract NativeOrdersSettlement is
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Fill an RFQ order. Private variant. Does not refund protocol fees.
|
/// @dev Fill an RFQ order. Private variant.
|
||||||
/// @param order The RFQ order.
|
/// @param params Function params.
|
||||||
/// @param signature The order signature.
|
|
||||||
/// @param takerTokenFillAmount Maximum taker token to fill this order with.
|
|
||||||
/// @param taker The order taker.
|
|
||||||
/// @return results Results of the fill.
|
/// @return results Results of the fill.
|
||||||
function _fillRfqOrderPrivate(
|
function _fillRfqOrderPrivate(FillRfqOrderPrivateParams memory params)
|
||||||
LibNativeOrder.RfqOrder memory order,
|
|
||||||
LibSignature.Signature memory signature,
|
|
||||||
uint128 takerTokenFillAmount,
|
|
||||||
address taker
|
|
||||||
)
|
|
||||||
private
|
private
|
||||||
returns (FillNativeOrderResults memory results)
|
returns (FillNativeOrderResults memory results)
|
||||||
{
|
{
|
||||||
LibNativeOrder.OrderInfo memory orderInfo = getRfqOrderInfo(order);
|
LibNativeOrder.OrderInfo memory orderInfo = getRfqOrderInfo(params.order);
|
||||||
|
|
||||||
// Must be fillable.
|
// Must be fillable.
|
||||||
if (orderInfo.status != LibNativeOrder.OrderStatus.FILLABLE) {
|
if (orderInfo.status != LibNativeOrder.OrderStatus.FILLABLE) {
|
||||||
@ -457,32 +477,41 @@ abstract contract NativeOrdersSettlement is
|
|||||||
LibNativeOrdersStorage.getStorage();
|
LibNativeOrdersStorage.getStorage();
|
||||||
|
|
||||||
// Must be fillable by the tx.origin.
|
// Must be fillable by the tx.origin.
|
||||||
if (order.txOrigin != tx.origin && !stor.originRegistry[order.txOrigin][tx.origin]) {
|
if (
|
||||||
|
params.order.txOrigin != tx.origin &&
|
||||||
|
!stor.originRegistry[params.order.txOrigin][tx.origin]
|
||||||
|
) {
|
||||||
LibNativeOrdersRichErrors.OrderNotFillableByOriginError(
|
LibNativeOrdersRichErrors.OrderNotFillableByOriginError(
|
||||||
orderInfo.orderHash,
|
orderInfo.orderHash,
|
||||||
tx.origin,
|
tx.origin,
|
||||||
order.txOrigin
|
params.order.txOrigin
|
||||||
).rrevert();
|
).rrevert();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must be fillable by the taker.
|
// Must be fillable by the taker.
|
||||||
if (order.taker != address(0) && order.taker != taker) {
|
if (params.order.taker != address(0) && params.order.taker != params.taker) {
|
||||||
LibNativeOrdersRichErrors.OrderNotFillableByTakerError(
|
LibNativeOrdersRichErrors.OrderNotFillableByTakerError(
|
||||||
orderInfo.orderHash,
|
orderInfo.orderHash,
|
||||||
taker,
|
params.taker,
|
||||||
order.taker
|
params.order.taker
|
||||||
).rrevert();
|
).rrevert();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signature must be valid for the order.
|
// Signature must be valid for the order.
|
||||||
{
|
{
|
||||||
address signer = LibSignature.getSignerOfHash(orderInfo.orderHash, signature);
|
address signer = LibSignature.getSignerOfHash(
|
||||||
if (signer != order.maker && !isValidOrderSigner(order.maker, signer)) {
|
orderInfo.orderHash,
|
||||||
|
params.signature
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
signer != params.order.maker &&
|
||||||
|
!isValidOrderSigner(params.order.maker, signer)
|
||||||
|
) {
|
||||||
LibNativeOrdersRichErrors.OrderNotSignedByMakerError(
|
LibNativeOrdersRichErrors.OrderNotSignedByMakerError(
|
||||||
orderInfo.orderHash,
|
orderInfo.orderHash,
|
||||||
signer,
|
signer,
|
||||||
order.maker
|
params.order.maker
|
||||||
).rrevert();
|
).rrevert();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -491,26 +520,27 @@ abstract contract NativeOrdersSettlement is
|
|||||||
(results.takerTokenFilledAmount, results.makerTokenFilledAmount) = _settleOrder(
|
(results.takerTokenFilledAmount, results.makerTokenFilledAmount) = _settleOrder(
|
||||||
SettleOrderInfo({
|
SettleOrderInfo({
|
||||||
orderHash: orderInfo.orderHash,
|
orderHash: orderInfo.orderHash,
|
||||||
maker: order.maker,
|
maker: params.order.maker,
|
||||||
taker: taker,
|
payer: params.useSelfBalance ? address(this) : params.taker,
|
||||||
makerToken: IERC20TokenV06(order.makerToken),
|
recipient: params.recipient,
|
||||||
takerToken: IERC20TokenV06(order.takerToken),
|
makerToken: IERC20TokenV06(params.order.makerToken),
|
||||||
makerAmount: order.makerAmount,
|
takerToken: IERC20TokenV06(params.order.takerToken),
|
||||||
takerAmount: order.takerAmount,
|
makerAmount: params.order.makerAmount,
|
||||||
takerTokenFillAmount: takerTokenFillAmount,
|
takerAmount: params.order.takerAmount,
|
||||||
|
takerTokenFillAmount: params.takerTokenFillAmount,
|
||||||
takerTokenFilledAmount: orderInfo.takerTokenFilledAmount
|
takerTokenFilledAmount: orderInfo.takerTokenFilledAmount
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
emit RfqOrderFilled(
|
emit RfqOrderFilled(
|
||||||
orderInfo.orderHash,
|
orderInfo.orderHash,
|
||||||
order.maker,
|
params.order.maker,
|
||||||
taker,
|
params.taker,
|
||||||
address(order.makerToken),
|
address(params.order.makerToken),
|
||||||
address(order.takerToken),
|
address(params.order.takerToken),
|
||||||
results.takerTokenFilledAmount,
|
results.takerTokenFilledAmount,
|
||||||
results.makerTokenFilledAmount,
|
results.makerTokenFilledAmount,
|
||||||
order.pool
|
params.order.pool
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -549,19 +579,28 @@ abstract contract NativeOrdersSettlement is
|
|||||||
// function if the order is cancelled.
|
// function if the order is cancelled.
|
||||||
settleInfo.takerTokenFilledAmount.safeAdd128(takerTokenFilledAmount);
|
settleInfo.takerTokenFilledAmount.safeAdd128(takerTokenFilledAmount);
|
||||||
|
|
||||||
// Transfer taker -> maker.
|
if (settleInfo.payer == address(this)) {
|
||||||
_transferERC20TokensFrom(
|
// Transfer this -> maker.
|
||||||
settleInfo.takerToken,
|
_transferERC20Tokens(
|
||||||
settleInfo.taker,
|
settleInfo.takerToken,
|
||||||
settleInfo.maker,
|
settleInfo.maker,
|
||||||
takerTokenFilledAmount
|
takerTokenFilledAmount
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
// Transfer taker -> maker.
|
||||||
|
_transferERC20TokensFrom(
|
||||||
|
settleInfo.takerToken,
|
||||||
|
settleInfo.payer,
|
||||||
|
settleInfo.maker,
|
||||||
|
takerTokenFilledAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Transfer maker -> taker.
|
// Transfer maker -> recipient.
|
||||||
_transferERC20TokensFrom(
|
_transferERC20TokensFrom(
|
||||||
settleInfo.makerToken,
|
settleInfo.makerToken,
|
||||||
settleInfo.maker,
|
settleInfo.maker,
|
||||||
settleInfo.taker,
|
settleInfo.recipient,
|
||||||
makerTokenFilledAmount
|
makerTokenFilledAmount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -137,8 +137,8 @@ contract FillQuoteTransformer is
|
|||||||
/// @dev Mask of the lower 255 bits of a uint256 value.
|
/// @dev Mask of the lower 255 bits of a uint256 value.
|
||||||
uint256 private constant LOWER_255_BITS = HIGH_BIT - 1;
|
uint256 private constant LOWER_255_BITS = HIGH_BIT - 1;
|
||||||
/// @dev If `refundReceiver` is set to this address, unpsent
|
/// @dev If `refundReceiver` is set to this address, unpsent
|
||||||
/// protocol fees will be sent to the taker.
|
/// protocol fees will be sent to the transform recipient.
|
||||||
address private constant REFUND_RECEIVER_TAKER = address(1);
|
address private constant REFUND_RECEIVER_RECIPIENT = address(1);
|
||||||
/// @dev If `refundReceiver` is set to this address, unpsent
|
/// @dev If `refundReceiver` is set to this address, unpsent
|
||||||
/// protocol fees will be sent to the sender.
|
/// protocol fees will be sent to the sender.
|
||||||
address private constant REFUND_RECEIVER_SENDER = address(2);
|
address private constant REFUND_RECEIVER_SENDER = address(2);
|
||||||
@ -272,8 +272,8 @@ contract FillQuoteTransformer is
|
|||||||
// Refund unspent protocol fees.
|
// Refund unspent protocol fees.
|
||||||
if (state.ethRemaining > 0 && data.refundReceiver != address(0)) {
|
if (state.ethRemaining > 0 && data.refundReceiver != address(0)) {
|
||||||
bool transferSuccess;
|
bool transferSuccess;
|
||||||
if (data.refundReceiver == REFUND_RECEIVER_TAKER) {
|
if (data.refundReceiver == REFUND_RECEIVER_RECIPIENT) {
|
||||||
(transferSuccess,) = context.taker.call{value: state.ethRemaining}("");
|
(transferSuccess,) = context.recipient.call{value: state.ethRemaining}("");
|
||||||
} else if (data.refundReceiver == REFUND_RECEIVER_SENDER) {
|
} else if (data.refundReceiver == REFUND_RECEIVER_SENDER) {
|
||||||
(transferSuccess,) = context.sender.call{value: state.ethRemaining}("");
|
(transferSuccess,) = context.sender.call{value: state.ethRemaining}("");
|
||||||
} else {
|
} else {
|
||||||
|
@ -30,9 +30,9 @@ interface IERC20Transformer {
|
|||||||
struct TransformContext {
|
struct TransformContext {
|
||||||
// The caller of `TransformERC20.transformERC20()`.
|
// The caller of `TransformERC20.transformERC20()`.
|
||||||
address payable sender;
|
address payable sender;
|
||||||
// taker The taker address, which may be distinct from `sender` in the case
|
// The recipient address, which may be distinct from `sender` e.g. in
|
||||||
// meta-transactions.
|
// meta-transactions.
|
||||||
address payable taker;
|
address payable recipient;
|
||||||
// Arbitrary data to pass to the transformer.
|
// Arbitrary data to pass to the transformer.
|
||||||
bytes data;
|
bytes data;
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ contract LogMetadataTransformer is
|
|||||||
override
|
override
|
||||||
returns (bytes4 success)
|
returns (bytes4 success)
|
||||||
{
|
{
|
||||||
emit TransformerMetadata(context.sender, context.taker, context.data);
|
emit TransformerMetadata(context.sender, context.recipient, context.data);
|
||||||
return LibERC20Transformer.TRANSFORMER_SUCCESS;
|
return LibERC20Transformer.TRANSFORMER_SUCCESS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ contract PayTakerTransformer is
|
|||||||
amount = data.tokens[i].getTokenBalanceOf(address(this));
|
amount = data.tokens[i].getTokenBalanceOf(address(this));
|
||||||
}
|
}
|
||||||
if (amount != 0) {
|
if (amount != 0) {
|
||||||
data.tokens[i].transformerTransfer(context.taker, amount);
|
data.tokens[i].transformerTransfer(context.recipient, amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return LibERC20Transformer.TRANSFORMER_SUCCESS;
|
return LibERC20Transformer.TRANSFORMER_SUCCESS;
|
||||||
|
@ -33,7 +33,7 @@ contract TestFillQuoteTransformerHost is
|
|||||||
TestMintableERC20Token inputToken,
|
TestMintableERC20Token inputToken,
|
||||||
uint256 inputTokenAmount,
|
uint256 inputTokenAmount,
|
||||||
address payable sender,
|
address payable sender,
|
||||||
address payable taker,
|
address payable recipient,
|
||||||
bytes calldata data
|
bytes calldata data
|
||||||
)
|
)
|
||||||
external
|
external
|
||||||
@ -47,7 +47,7 @@ contract TestFillQuoteTransformerHost is
|
|||||||
transformer,
|
transformer,
|
||||||
IERC20Transformer.TransformContext({
|
IERC20Transformer.TransformContext({
|
||||||
sender: sender,
|
sender: sender,
|
||||||
taker: taker,
|
recipient: recipient,
|
||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -46,16 +46,6 @@ contract TestLiquidityProvider {
|
|||||||
uint256 inputTokenBalance
|
uint256 inputTokenBalance
|
||||||
);
|
);
|
||||||
|
|
||||||
IERC20TokenV06 public immutable xAsset;
|
|
||||||
IERC20TokenV06 public immutable yAsset;
|
|
||||||
|
|
||||||
constructor(IERC20TokenV06 xAsset_, IERC20TokenV06 yAsset_)
|
|
||||||
public
|
|
||||||
{
|
|
||||||
xAsset = xAsset_;
|
|
||||||
yAsset = yAsset_;
|
|
||||||
}
|
|
||||||
|
|
||||||
receive() external payable {}
|
receive() external payable {}
|
||||||
|
|
||||||
/// @dev Trades `inputToken` for `outputToken`. The amount of `inputToken`
|
/// @dev Trades `inputToken` for `outputToken`. The amount of `inputToken`
|
||||||
@ -83,6 +73,8 @@ contract TestLiquidityProvider {
|
|||||||
minBuyAmount,
|
minBuyAmount,
|
||||||
IERC20TokenV06(inputToken).balanceOf(address(this))
|
IERC20TokenV06(inputToken).balanceOf(address(this))
|
||||||
);
|
);
|
||||||
|
uint256 outputTokenBalance = IERC20TokenV06(outputToken).balanceOf(address(this));
|
||||||
|
IERC20TokenV06(outputToken).transfer(recipient, outputTokenBalance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Trades ETH for token. ETH must be sent to the contract prior to
|
/// @dev Trades ETH for token. ETH must be sent to the contract prior to
|
||||||
@ -106,6 +98,8 @@ contract TestLiquidityProvider {
|
|||||||
minBuyAmount,
|
minBuyAmount,
|
||||||
address(this).balance
|
address(this).balance
|
||||||
);
|
);
|
||||||
|
uint256 outputTokenBalance = IERC20TokenV06(outputToken).balanceOf(address(this));
|
||||||
|
IERC20TokenV06(outputToken).transfer(recipient, outputTokenBalance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Trades token for ETH. The token must be sent to the contract prior
|
/// @dev Trades token for ETH. The token must be sent to the contract prior
|
||||||
@ -129,5 +123,6 @@ contract TestLiquidityProvider {
|
|||||||
minBuyAmount,
|
minBuyAmount,
|
||||||
IERC20TokenV06(inputToken).balanceOf(address(this))
|
IERC20TokenV06(inputToken).balanceOf(address(this))
|
||||||
);
|
);
|
||||||
|
recipient.transfer(address(this).balance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,9 @@ contract TestMetaTransactionsNativeOrdersFeature is
|
|||||||
LibNativeOrder.RfqOrder memory order,
|
LibNativeOrder.RfqOrder memory order,
|
||||||
LibSignature.Signature memory signature,
|
LibSignature.Signature memory signature,
|
||||||
uint128 takerTokenFillAmount,
|
uint128 takerTokenFillAmount,
|
||||||
address taker
|
address taker,
|
||||||
|
bool /* useSelfBalance */,
|
||||||
|
address /* recipient */
|
||||||
)
|
)
|
||||||
public
|
public
|
||||||
override
|
override
|
||||||
|
@ -57,7 +57,7 @@ contract TestMintTokenERC20Transformer is
|
|||||||
address(this),
|
address(this),
|
||||||
msg.sender,
|
msg.sender,
|
||||||
context.sender,
|
context.sender,
|
||||||
context.taker,
|
context.recipient,
|
||||||
context.data,
|
context.data,
|
||||||
LibERC20Transformer.isTokenETH(data.inputToken)
|
LibERC20Transformer.isTokenETH(data.inputToken)
|
||||||
? address(this).balance
|
? address(this).balance
|
||||||
@ -71,15 +71,22 @@ contract TestMintTokenERC20Transformer is
|
|||||||
data.inputToken.transfer(address(0), data.burnAmount);
|
data.inputToken.transfer(address(0), data.burnAmount);
|
||||||
}
|
}
|
||||||
// Mint output tokens.
|
// Mint output tokens.
|
||||||
if (LibERC20Transformer.isTokenETH(IERC20TokenV06(address(data.outputToken)))) {
|
if (!LibERC20Transformer.isTokenETH(IERC20TokenV06(address(data.outputToken)))) {
|
||||||
context.taker.transfer(data.mintAmount);
|
if (data.feeAmount > data.mintAmount) {
|
||||||
} else {
|
data.outputToken.burn(
|
||||||
data.outputToken.mint(
|
context.recipient,
|
||||||
context.taker,
|
data.feeAmount - data.mintAmount
|
||||||
data.mintAmount
|
);
|
||||||
);
|
} else {
|
||||||
// Burn fees from output.
|
data.outputToken.mint(
|
||||||
data.outputToken.burn(context.taker, data.feeAmount);
|
address(this),
|
||||||
|
data.mintAmount
|
||||||
|
);
|
||||||
|
data.outputToken.burn(
|
||||||
|
context.recipient,
|
||||||
|
data.feeAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return LibERC20Transformer.TRANSFORMER_SUCCESS;
|
return LibERC20Transformer.TRANSFORMER_SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,12 @@ pragma experimental ABIEncoderV2;
|
|||||||
|
|
||||||
|
|
||||||
contract TestMintableERC20Token {
|
contract TestMintableERC20Token {
|
||||||
|
event Transfer(
|
||||||
|
address token,
|
||||||
|
address from,
|
||||||
|
address to,
|
||||||
|
uint256 value
|
||||||
|
);
|
||||||
|
|
||||||
mapping(address => uint256) public balanceOf;
|
mapping(address => uint256) public balanceOf;
|
||||||
mapping(address => mapping(address => uint256)) public allowance;
|
mapping(address => mapping(address => uint256)) public allowance;
|
||||||
@ -81,6 +87,7 @@ contract TestMintableERC20Token {
|
|||||||
require(balanceOf[from] >= amount, "TestMintableERC20Token/INSUFFICIENT_FUNDS");
|
require(balanceOf[from] >= amount, "TestMintableERC20Token/INSUFFICIENT_FUNDS");
|
||||||
balanceOf[from] -= amount;
|
balanceOf[from] -= amount;
|
||||||
balanceOf[to] += amount;
|
balanceOf[to] += amount;
|
||||||
|
emit Transfer(address(this), from, to, amount);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
44
contracts/zero-ex/contracts/test/TestUniswapV2Factory.sol
Normal file
44
contracts/zero-ex/contracts/test/TestUniswapV2Factory.sol
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
pragma solidity ^0.6;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||||
|
import "./TestUniswapV2Pool.sol";
|
||||||
|
|
||||||
|
contract TestUniswapV2Factory {
|
||||||
|
struct CreationParameters {
|
||||||
|
IERC20TokenV06 token0;
|
||||||
|
IERC20TokenV06 token1;
|
||||||
|
}
|
||||||
|
|
||||||
|
event PoolCreated(TestUniswapV2Pool pool);
|
||||||
|
|
||||||
|
bytes32 public immutable POOL_INIT_CODE_HASH;
|
||||||
|
mapping (IERC20TokenV06 => mapping (IERC20TokenV06 => TestUniswapV2Pool)) public getPool;
|
||||||
|
CreationParameters public creationParameters;
|
||||||
|
|
||||||
|
constructor() public {
|
||||||
|
POOL_INIT_CODE_HASH = keccak256(type(TestUniswapV2Pool).creationCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPool(IERC20TokenV06 tokenA, IERC20TokenV06 tokenB)
|
||||||
|
external
|
||||||
|
returns (TestUniswapV2Pool pool)
|
||||||
|
{
|
||||||
|
(IERC20TokenV06 token0, IERC20TokenV06 token1) = tokenA < tokenB
|
||||||
|
? (tokenA, tokenB)
|
||||||
|
: (tokenB, tokenA);
|
||||||
|
require(
|
||||||
|
getPool[token0][token1] == TestUniswapV2Pool(0),
|
||||||
|
"TestUniswapV2Factory/POOL_ALREADY_EXISTS"
|
||||||
|
);
|
||||||
|
creationParameters = CreationParameters({
|
||||||
|
token0: token0,
|
||||||
|
token1: token1
|
||||||
|
});
|
||||||
|
pool = new TestUniswapV2Pool
|
||||||
|
{ salt: keccak256(abi.encodePacked(token0, token1)) }();
|
||||||
|
getPool[token0][token1] = pool;
|
||||||
|
getPool[token1][token0] = pool;
|
||||||
|
emit PoolCreated(pool);
|
||||||
|
}
|
||||||
|
}
|
67
contracts/zero-ex/contracts/test/TestUniswapV2Pool.sol
Normal file
67
contracts/zero-ex/contracts/test/TestUniswapV2Pool.sol
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
pragma solidity ^0.6;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||||
|
import "../src/vendor/IUniswapV2Pair.sol";
|
||||||
|
|
||||||
|
interface IUniswapV2PoolDeployer {
|
||||||
|
struct CreationParameters {
|
||||||
|
IERC20TokenV06 token0;
|
||||||
|
IERC20TokenV06 token1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function creationParameters() external view returns (CreationParameters memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
contract TestUniswapV2Pool is IUniswapV2Pair {
|
||||||
|
IERC20TokenV06 public immutable token0;
|
||||||
|
IERC20TokenV06 public immutable token1;
|
||||||
|
|
||||||
|
uint112 reserve0;
|
||||||
|
uint112 reserve1;
|
||||||
|
uint32 blockTimestampLast;
|
||||||
|
|
||||||
|
constructor() public {
|
||||||
|
IUniswapV2PoolDeployer.CreationParameters memory params =
|
||||||
|
IUniswapV2PoolDeployer(msg.sender).creationParameters();
|
||||||
|
(token0, token1) = (params.token0, params.token1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function swap(
|
||||||
|
uint256 amount0Out,
|
||||||
|
uint256 amount1Out,
|
||||||
|
address to,
|
||||||
|
bytes calldata /* data */
|
||||||
|
)
|
||||||
|
external
|
||||||
|
override
|
||||||
|
{
|
||||||
|
if (amount0Out > 0) {
|
||||||
|
token0.transfer(to, amount0Out);
|
||||||
|
}
|
||||||
|
if (amount1Out > 0) {
|
||||||
|
token1.transfer(to, amount1Out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setReserves(
|
||||||
|
uint112 reserve0_,
|
||||||
|
uint112 reserve1_,
|
||||||
|
uint32 blockTimestampLast_
|
||||||
|
)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
reserve0 = reserve0_;
|
||||||
|
reserve1 = reserve1_;
|
||||||
|
blockTimestampLast = blockTimestampLast_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReserves()
|
||||||
|
external
|
||||||
|
override
|
||||||
|
view
|
||||||
|
returns (uint112, uint112, uint32)
|
||||||
|
{
|
||||||
|
return (reserve0, reserve1, blockTimestampLast);
|
||||||
|
}
|
||||||
|
}
|
@ -26,11 +26,15 @@ import "./TestMintableERC20Token.sol";
|
|||||||
contract TestWeth is
|
contract TestWeth is
|
||||||
TestMintableERC20Token
|
TestMintableERC20Token
|
||||||
{
|
{
|
||||||
|
event Deposit(address owner, uint256 value);
|
||||||
|
event Withdrawal(address owner, uint256 value);
|
||||||
|
|
||||||
function deposit()
|
function deposit()
|
||||||
external
|
external
|
||||||
payable
|
payable
|
||||||
{
|
{
|
||||||
this.mint(msg.sender, msg.value);
|
this.mint(msg.sender, msg.value);
|
||||||
|
emit Deposit(msg.sender, msg.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function depositTo(address owner)
|
function depositTo(address owner)
|
||||||
@ -38,6 +42,7 @@ contract TestWeth is
|
|||||||
payable
|
payable
|
||||||
{
|
{
|
||||||
this.mint(owner, msg.value);
|
this.mint(owner, msg.value);
|
||||||
|
emit Deposit(owner, msg.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function withdraw(uint256 amount)
|
function withdraw(uint256 amount)
|
||||||
@ -46,5 +51,6 @@ contract TestWeth is
|
|||||||
require(balanceOf[msg.sender] >= amount, "TestWeth/INSUFFICIENT_FUNDS");
|
require(balanceOf[msg.sender] >= amount, "TestWeth/INSUFFICIENT_FUNDS");
|
||||||
balanceOf[msg.sender] -= amount;
|
balanceOf[msg.sender] -= amount;
|
||||||
msg.sender.transfer(amount);
|
msg.sender.transfer(amount);
|
||||||
|
emit Withdrawal(msg.sender, amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ contract TestWethTransformerHost is
|
|||||||
transformer,
|
transformer,
|
||||||
IERC20Transformer.TransformContext({
|
IERC20Transformer.TransformContext({
|
||||||
sender: msg.sender,
|
sender: msg.sender,
|
||||||
taker: msg.sender,
|
recipient: msg.sender,
|
||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature",
|
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature,OtcOrdersFeature,IOtcOrdersFeature",
|
||||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||||
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeProtocols|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBalancerV2|MixinBancor|MixinClipper|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinKyber|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeProtocols|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOtcOrdersFeature|IOwnableFeature|IPancakeSwapFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IUniswapV3Feature|IUniswapV3Pool|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOtcOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBalancerV2|MixinBancor|MixinClipper|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinCurveV2|MixinDodo|MixinDodoV2|MixinKyber|MixinKyberDmm|MixinLido|MixinMStable|MixinMakerPSM|MixinMooniswap|MixinNerve|MixinOasis|MixinShell|MixinUniswap|MixinUniswapV2|MixinUniswapV3|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|MultiplexLiquidityProvider|MultiplexOtc|MultiplexRfq|MultiplexTransformERC20|MultiplexUniswapV2|MultiplexUniswapV3|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OtcOrdersFeature|OwnableFeature|PancakeSwapFeature|PayTakerTransformer|PermissionlessTransformerDeployer|PositiveSlippageFeeTransformer|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestCurve|TestDelegateCaller|TestFeeCollectorController|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFixinTokenSpender|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLiquidityProvider|TestMetaTransactionsNativeOrdersFeature|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestMooniswap|TestNativeOrdersFeature|TestNoEthRecipient|TestOrderSignerRegistryWithContractWallet|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestUniswapV2Factory|TestUniswapV2Pool|TestUniswapV3Factory|TestUniswapV3Feature|TestUniswapV3Pool|TestWeth|TestWethTransformerHost|TestZeroExFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|UniswapV3Feature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -104,6 +104,12 @@ import * as MixinUniswapV3 from '../test/generated-artifacts/MixinUniswapV3.json
|
|||||||
import * as MixinZeroExBridge from '../test/generated-artifacts/MixinZeroExBridge.json';
|
import * as MixinZeroExBridge from '../test/generated-artifacts/MixinZeroExBridge.json';
|
||||||
import * as MooniswapLiquidityProvider from '../test/generated-artifacts/MooniswapLiquidityProvider.json';
|
import * as MooniswapLiquidityProvider from '../test/generated-artifacts/MooniswapLiquidityProvider.json';
|
||||||
import * as MultiplexFeature from '../test/generated-artifacts/MultiplexFeature.json';
|
import * as MultiplexFeature from '../test/generated-artifacts/MultiplexFeature.json';
|
||||||
|
import * as MultiplexLiquidityProvider from '../test/generated-artifacts/MultiplexLiquidityProvider.json';
|
||||||
|
import * as MultiplexOtc from '../test/generated-artifacts/MultiplexOtc.json';
|
||||||
|
import * as MultiplexRfq from '../test/generated-artifacts/MultiplexRfq.json';
|
||||||
|
import * as MultiplexTransformERC20 from '../test/generated-artifacts/MultiplexTransformERC20.json';
|
||||||
|
import * as MultiplexUniswapV2 from '../test/generated-artifacts/MultiplexUniswapV2.json';
|
||||||
|
import * as MultiplexUniswapV3 from '../test/generated-artifacts/MultiplexUniswapV3.json';
|
||||||
import * as NativeOrdersCancellation from '../test/generated-artifacts/NativeOrdersCancellation.json';
|
import * as NativeOrdersCancellation from '../test/generated-artifacts/NativeOrdersCancellation.json';
|
||||||
import * as NativeOrdersFeature from '../test/generated-artifacts/NativeOrdersFeature.json';
|
import * as NativeOrdersFeature from '../test/generated-artifacts/NativeOrdersFeature.json';
|
||||||
import * as NativeOrdersInfo from '../test/generated-artifacts/NativeOrdersInfo.json';
|
import * as NativeOrdersInfo from '../test/generated-artifacts/NativeOrdersInfo.json';
|
||||||
@ -151,6 +157,8 @@ import * as TestTransformerBase from '../test/generated-artifacts/TestTransforme
|
|||||||
import * as TestTransformERC20 from '../test/generated-artifacts/TestTransformERC20.json';
|
import * as TestTransformERC20 from '../test/generated-artifacts/TestTransformERC20.json';
|
||||||
import * as TestTransformerDeployerTransformer from '../test/generated-artifacts/TestTransformerDeployerTransformer.json';
|
import * as TestTransformerDeployerTransformer from '../test/generated-artifacts/TestTransformerDeployerTransformer.json';
|
||||||
import * as TestTransformerHost from '../test/generated-artifacts/TestTransformerHost.json';
|
import * as TestTransformerHost from '../test/generated-artifacts/TestTransformerHost.json';
|
||||||
|
import * as TestUniswapV2Factory from '../test/generated-artifacts/TestUniswapV2Factory.json';
|
||||||
|
import * as TestUniswapV2Pool from '../test/generated-artifacts/TestUniswapV2Pool.json';
|
||||||
import * as TestUniswapV3Factory from '../test/generated-artifacts/TestUniswapV3Factory.json';
|
import * as TestUniswapV3Factory from '../test/generated-artifacts/TestUniswapV3Factory.json';
|
||||||
import * as TestUniswapV3Feature from '../test/generated-artifacts/TestUniswapV3Feature.json';
|
import * as TestUniswapV3Feature from '../test/generated-artifacts/TestUniswapV3Feature.json';
|
||||||
import * as TestUniswapV3Pool from '../test/generated-artifacts/TestUniswapV3Pool.json';
|
import * as TestUniswapV3Pool from '../test/generated-artifacts/TestUniswapV3Pool.json';
|
||||||
@ -192,7 +200,6 @@ export const artifacts = {
|
|||||||
BootstrapFeature: BootstrapFeature as ContractArtifact,
|
BootstrapFeature: BootstrapFeature as ContractArtifact,
|
||||||
LiquidityProviderFeature: LiquidityProviderFeature as ContractArtifact,
|
LiquidityProviderFeature: LiquidityProviderFeature as ContractArtifact,
|
||||||
MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact,
|
MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact,
|
||||||
MultiplexFeature: MultiplexFeature as ContractArtifact,
|
|
||||||
NativeOrdersFeature: NativeOrdersFeature as ContractArtifact,
|
NativeOrdersFeature: NativeOrdersFeature as ContractArtifact,
|
||||||
OtcOrdersFeature: OtcOrdersFeature as ContractArtifact,
|
OtcOrdersFeature: OtcOrdersFeature as ContractArtifact,
|
||||||
OwnableFeature: OwnableFeature as ContractArtifact,
|
OwnableFeature: OwnableFeature as ContractArtifact,
|
||||||
@ -219,6 +226,13 @@ export const artifacts = {
|
|||||||
IUniswapV3Feature: IUniswapV3Feature as ContractArtifact,
|
IUniswapV3Feature: IUniswapV3Feature as ContractArtifact,
|
||||||
LibNativeOrder: LibNativeOrder as ContractArtifact,
|
LibNativeOrder: LibNativeOrder as ContractArtifact,
|
||||||
LibSignature: LibSignature as ContractArtifact,
|
LibSignature: LibSignature as ContractArtifact,
|
||||||
|
MultiplexFeature: MultiplexFeature as ContractArtifact,
|
||||||
|
MultiplexLiquidityProvider: MultiplexLiquidityProvider as ContractArtifact,
|
||||||
|
MultiplexOtc: MultiplexOtc as ContractArtifact,
|
||||||
|
MultiplexRfq: MultiplexRfq as ContractArtifact,
|
||||||
|
MultiplexTransformERC20: MultiplexTransformERC20 as ContractArtifact,
|
||||||
|
MultiplexUniswapV2: MultiplexUniswapV2 as ContractArtifact,
|
||||||
|
MultiplexUniswapV3: MultiplexUniswapV3 as ContractArtifact,
|
||||||
NativeOrdersCancellation: NativeOrdersCancellation as ContractArtifact,
|
NativeOrdersCancellation: NativeOrdersCancellation as ContractArtifact,
|
||||||
NativeOrdersInfo: NativeOrdersInfo as ContractArtifact,
|
NativeOrdersInfo: NativeOrdersInfo as ContractArtifact,
|
||||||
NativeOrdersProtocolFees: NativeOrdersProtocolFees as ContractArtifact,
|
NativeOrdersProtocolFees: NativeOrdersProtocolFees as ContractArtifact,
|
||||||
@ -320,6 +334,8 @@ export const artifacts = {
|
|||||||
TestTransformerBase: TestTransformerBase as ContractArtifact,
|
TestTransformerBase: TestTransformerBase as ContractArtifact,
|
||||||
TestTransformerDeployerTransformer: TestTransformerDeployerTransformer as ContractArtifact,
|
TestTransformerDeployerTransformer: TestTransformerDeployerTransformer as ContractArtifact,
|
||||||
TestTransformerHost: TestTransformerHost as ContractArtifact,
|
TestTransformerHost: TestTransformerHost as ContractArtifact,
|
||||||
|
TestUniswapV2Factory: TestUniswapV2Factory as ContractArtifact,
|
||||||
|
TestUniswapV2Pool: TestUniswapV2Pool as ContractArtifact,
|
||||||
TestUniswapV3Factory: TestUniswapV3Factory as ContractArtifact,
|
TestUniswapV3Factory: TestUniswapV3Factory as ContractArtifact,
|
||||||
TestUniswapV3Feature: TestUniswapV3Feature as ContractArtifact,
|
TestUniswapV3Feature: TestUniswapV3Feature as ContractArtifact,
|
||||||
TestUniswapV3Pool: TestUniswapV3Pool as ContractArtifact,
|
TestUniswapV3Pool: TestUniswapV3Pool as ContractArtifact,
|
||||||
|
@ -72,8 +72,6 @@ blockchainTests('LiquidityProvider feature', env => {
|
|||||||
env.provider,
|
env.provider,
|
||||||
env.txDefaults,
|
env.txDefaults,
|
||||||
artifacts,
|
artifacts,
|
||||||
token.address,
|
|
||||||
weth.address,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
blockchainTests.resets('Sandbox', () => {
|
blockchainTests.resets('Sandbox', () => {
|
||||||
|
@ -354,6 +354,8 @@ blockchainTests.resets('MetaTransactions feature', env => {
|
|||||||
inputTokenAmount: args.inputTokenAmount,
|
inputTokenAmount: args.inputTokenAmount,
|
||||||
minOutputTokenAmount: args.minOutputTokenAmount,
|
minOutputTokenAmount: args.minOutputTokenAmount,
|
||||||
transformations: args.transformations,
|
transformations: args.transformations,
|
||||||
|
useSelfBalance: false,
|
||||||
|
recipient: mtx.signer,
|
||||||
})
|
})
|
||||||
.getABIEncodedTransactionData();
|
.getABIEncodedTransactionData();
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,7 @@ import {
|
|||||||
|
|
||||||
blockchainTests.resets('OtcOrdersFeature', env => {
|
blockchainTests.resets('OtcOrdersFeature', env => {
|
||||||
const { NULL_ADDRESS, MAX_UINT256, ZERO_AMOUNT: ZERO } = constants;
|
const { NULL_ADDRESS, MAX_UINT256, ZERO_AMOUNT: ZERO } = constants;
|
||||||
|
const ETH_TOKEN_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
||||||
let maker: string;
|
let maker: string;
|
||||||
let taker: string;
|
let taker: string;
|
||||||
let notMaker: string;
|
let notMaker: string;
|
||||||
@ -309,7 +310,7 @@ blockchainTests.resets('OtcOrdersFeature', env => {
|
|||||||
const order = getTestOtcOrder();
|
const order = getTestOtcOrder();
|
||||||
await testUtils.prepareBalancesForOrdersAsync([order], taker);
|
await testUtils.prepareBalancesForOrdersAsync([order], taker);
|
||||||
const tx = zeroEx
|
const tx = zeroEx
|
||||||
.fillOtcOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount, false)
|
.fillOtcOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount)
|
||||||
.awaitTransactionSuccessAsync({ from: taker, value: 1 });
|
.awaitTransactionSuccessAsync({ from: taker, value: 1 });
|
||||||
// This will revert at the language level because the fill function is not payable.
|
// This will revert at the language level because the fill function is not payable.
|
||||||
return expect(tx).to.be.rejectedWith('revert');
|
return expect(tx).to.be.rejectedWith('revert');
|
||||||
@ -395,7 +396,7 @@ blockchainTests.resets('OtcOrdersFeature', env => {
|
|||||||
it('reverts if `unwrapWeth` is true but maker token is not WETH', async () => {
|
it('reverts if `unwrapWeth` is true but maker token is not WETH', async () => {
|
||||||
const order = getTestOtcOrder();
|
const order = getTestOtcOrder();
|
||||||
const tx = testUtils.fillOtcOrderAsync(order, order.takerAmount, taker, true);
|
const tx = testUtils.fillOtcOrderAsync(order, order.takerAmount, taker, true);
|
||||||
return expect(tx).to.revertWith('OtcOrdersFeature/INVALID_UNWRAP_WETH');
|
return expect(tx).to.revertWith('OtcOrdersFeature::fillOtcOrderForEth/MAKER_TOKEN_NOT_WETH');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows for fills on orders signed by a approved signer', async () => {
|
it('allows for fills on orders signed by a approved signer', async () => {
|
||||||
@ -415,7 +416,7 @@ blockchainTests.resets('OtcOrdersFeature', env => {
|
|||||||
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
|
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
|
||||||
// fill should succeed
|
// fill should succeed
|
||||||
const receipt = await zeroEx
|
const receipt = await zeroEx
|
||||||
.fillOtcOrder(order, sig, order.takerAmount, false)
|
.fillOtcOrder(order, sig, order.takerAmount)
|
||||||
.awaitTransactionSuccessAsync({ from: taker });
|
.awaitTransactionSuccessAsync({ from: taker });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
@ -445,9 +446,7 @@ blockchainTests.resets('OtcOrdersFeature', env => {
|
|||||||
.registerAllowedOrderSigner(contractWalletSigner, false)
|
.registerAllowedOrderSigner(contractWalletSigner, false)
|
||||||
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
|
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
|
||||||
// fill should revert
|
// fill should revert
|
||||||
const tx = zeroEx
|
const tx = zeroEx.fillOtcOrder(order, sig, order.takerAmount).awaitTransactionSuccessAsync({ from: taker });
|
||||||
.fillOtcOrder(order, sig, order.takerAmount, false)
|
|
||||||
.awaitTransactionSuccessAsync({ from: taker });
|
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new RevertErrors.NativeOrders.OrderNotSignedByMakerError(
|
new RevertErrors.NativeOrders.OrderNotSignedByMakerError(
|
||||||
order.getHash(),
|
order.getHash(),
|
||||||
@ -465,16 +464,14 @@ blockchainTests.resets('OtcOrdersFeature', env => {
|
|||||||
// need to provide contract wallet with a balance
|
// need to provide contract wallet with a balance
|
||||||
await makerToken.mint(contractWallet.address, order.makerAmount).awaitTransactionSuccessAsync();
|
await makerToken.mint(contractWallet.address, order.makerAmount).awaitTransactionSuccessAsync();
|
||||||
// fill should revert
|
// fill should revert
|
||||||
const tx = zeroEx
|
const tx = zeroEx.fillOtcOrder(order, sig, order.takerAmount).awaitTransactionSuccessAsync({ from: taker });
|
||||||
.fillOtcOrder(order, sig, order.takerAmount, false)
|
|
||||||
.awaitTransactionSuccessAsync({ from: taker });
|
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new RevertErrors.NativeOrders.OrderNotSignedByMakerError(order.getHash(), maker, order.maker),
|
new RevertErrors.NativeOrders.OrderNotSignedByMakerError(order.getHash(), maker, order.maker),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('fillOtcOrderWithEth()', () => {
|
describe('fillOtcOrderWithEth()', () => {
|
||||||
it('Can fill an order with ETH', async () => {
|
it('Can fill an order with ETH (takerToken=WETH)', async () => {
|
||||||
const order = getTestOtcOrder({ takerToken: wethToken.address });
|
const order = getTestOtcOrder({ takerToken: wethToken.address });
|
||||||
const receipt = await testUtils.fillOtcOrderWithEthAsync(order);
|
const receipt = await testUtils.fillOtcOrderWithEthAsync(order);
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
@ -484,7 +481,25 @@ blockchainTests.resets('OtcOrdersFeature', env => {
|
|||||||
);
|
);
|
||||||
await assertExpectedFinalBalancesFromOtcOrderFillAsync(order);
|
await assertExpectedFinalBalancesFromOtcOrderFillAsync(order);
|
||||||
});
|
});
|
||||||
it('Can partially fill an order with ETH', async () => {
|
it('Can fill an order with ETH (takerToken=ETH)', async () => {
|
||||||
|
const order = getTestOtcOrder({ takerToken: ETH_TOKEN_ADDRESS });
|
||||||
|
const makerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(maker);
|
||||||
|
const receipt = await testUtils.fillOtcOrderWithEthAsync(order);
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
receipt.logs,
|
||||||
|
[testUtils.createOtcOrderFilledEventArgs(order)],
|
||||||
|
IZeroExEvents.OtcOrderFilled,
|
||||||
|
);
|
||||||
|
const takerBalance = await new TestMintableERC20TokenContract(order.makerToken, env.provider)
|
||||||
|
.balanceOf(taker)
|
||||||
|
.callAsync();
|
||||||
|
expect(takerBalance, 'taker balance').to.bignumber.eq(order.makerAmount);
|
||||||
|
const makerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(maker);
|
||||||
|
expect(makerEthBalanceAfter.minus(makerEthBalanceBefore), 'maker balance').to.bignumber.equal(
|
||||||
|
order.takerAmount,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('Can partially fill an order with ETH (takerToken=WETH)', async () => {
|
||||||
const order = getTestOtcOrder({ takerToken: wethToken.address });
|
const order = getTestOtcOrder({ takerToken: wethToken.address });
|
||||||
const fillAmount = order.takerAmount.minus(1);
|
const fillAmount = order.takerAmount.minus(1);
|
||||||
const receipt = await testUtils.fillOtcOrderWithEthAsync(order, fillAmount);
|
const receipt = await testUtils.fillOtcOrderWithEthAsync(order, fillAmount);
|
||||||
@ -495,7 +510,27 @@ blockchainTests.resets('OtcOrdersFeature', env => {
|
|||||||
);
|
);
|
||||||
await assertExpectedFinalBalancesFromOtcOrderFillAsync(order, fillAmount);
|
await assertExpectedFinalBalancesFromOtcOrderFillAsync(order, fillAmount);
|
||||||
});
|
});
|
||||||
it('Can refund excess ETH is msg.value > order.takerAmount', async () => {
|
it('Can partially fill an order with ETH (takerToken=ETH)', async () => {
|
||||||
|
const order = getTestOtcOrder({ takerToken: ETH_TOKEN_ADDRESS });
|
||||||
|
const fillAmount = order.takerAmount.minus(1);
|
||||||
|
const makerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(maker);
|
||||||
|
const receipt = await testUtils.fillOtcOrderWithEthAsync(order, fillAmount);
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
receipt.logs,
|
||||||
|
[testUtils.createOtcOrderFilledEventArgs(order, fillAmount)],
|
||||||
|
IZeroExEvents.OtcOrderFilled,
|
||||||
|
);
|
||||||
|
const { makerTokenFilledAmount, takerTokenFilledAmount } = computeOtcOrderFilledAmounts(order, fillAmount);
|
||||||
|
const takerBalance = await new TestMintableERC20TokenContract(order.makerToken, env.provider)
|
||||||
|
.balanceOf(taker)
|
||||||
|
.callAsync();
|
||||||
|
expect(takerBalance, 'taker balance').to.bignumber.eq(makerTokenFilledAmount);
|
||||||
|
const makerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(maker);
|
||||||
|
expect(makerEthBalanceAfter.minus(makerEthBalanceBefore), 'maker balance').to.bignumber.equal(
|
||||||
|
takerTokenFilledAmount,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('Can refund excess ETH is msg.value > order.takerAmount (takerToken=WETH)', async () => {
|
||||||
const order = getTestOtcOrder({ takerToken: wethToken.address });
|
const order = getTestOtcOrder({ takerToken: wethToken.address });
|
||||||
const fillAmount = order.takerAmount.plus(420);
|
const fillAmount = order.takerAmount.plus(420);
|
||||||
const takerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker);
|
const takerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker);
|
||||||
@ -509,10 +544,34 @@ blockchainTests.resets('OtcOrdersFeature', env => {
|
|||||||
expect(takerEthBalanceBefore.minus(takerEthBalanceAfter)).to.bignumber.equal(order.takerAmount);
|
expect(takerEthBalanceBefore.minus(takerEthBalanceAfter)).to.bignumber.equal(order.takerAmount);
|
||||||
await assertExpectedFinalBalancesFromOtcOrderFillAsync(order);
|
await assertExpectedFinalBalancesFromOtcOrderFillAsync(order);
|
||||||
});
|
});
|
||||||
it('Cannot fill an order if taker token is not WETH', async () => {
|
it('Can refund excess ETH is msg.value > order.takerAmount (takerToken=ETH)', async () => {
|
||||||
|
const order = getTestOtcOrder({ takerToken: ETH_TOKEN_ADDRESS });
|
||||||
|
const fillAmount = order.takerAmount.plus(420);
|
||||||
|
const takerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker);
|
||||||
|
const makerEthBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(maker);
|
||||||
|
const receipt = await testUtils.fillOtcOrderWithEthAsync(order, fillAmount);
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
receipt.logs,
|
||||||
|
[testUtils.createOtcOrderFilledEventArgs(order)],
|
||||||
|
IZeroExEvents.OtcOrderFilled,
|
||||||
|
);
|
||||||
|
const takerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(taker);
|
||||||
|
expect(takerEthBalanceBefore.minus(takerEthBalanceAfter), 'taker eth balance').to.bignumber.equal(
|
||||||
|
order.takerAmount,
|
||||||
|
);
|
||||||
|
const takerBalance = await new TestMintableERC20TokenContract(order.makerToken, env.provider)
|
||||||
|
.balanceOf(taker)
|
||||||
|
.callAsync();
|
||||||
|
expect(takerBalance, 'taker balance').to.bignumber.eq(order.makerAmount);
|
||||||
|
const makerEthBalanceAfter = await env.web3Wrapper.getBalanceInWeiAsync(maker);
|
||||||
|
expect(makerEthBalanceAfter.minus(makerEthBalanceBefore), 'maker balance').to.bignumber.equal(
|
||||||
|
order.takerAmount,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('Cannot fill an order if taker token is not ETH or WETH', async () => {
|
||||||
const order = getTestOtcOrder();
|
const order = getTestOtcOrder();
|
||||||
const tx = testUtils.fillOtcOrderWithEthAsync(order);
|
const tx = testUtils.fillOtcOrderWithEthAsync(order);
|
||||||
return expect(tx).to.revertWith('OtcOrdersFeature/INVALID_WRAP_ETH');
|
return expect(tx).to.revertWith('OtcOrdersFeature::fillOtcOrderWithEth/INVALID_TAKER_TOKEN');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -578,15 +637,22 @@ blockchainTests.resets('OtcOrdersFeature', env => {
|
|||||||
const order = getTestOtcOrder({ taker, txOrigin });
|
const order = getTestOtcOrder({ taker, txOrigin });
|
||||||
const tx = testUtils.fillTakerSignedOtcOrderAsync(order, txOrigin, notTaker);
|
const tx = testUtils.fillTakerSignedOtcOrderAsync(order, txOrigin, notTaker);
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new RevertErrors.NativeOrders.OrderNotSignedByTakerError(order.getHash(), notTaker, taker),
|
new RevertErrors.NativeOrders.OrderNotFillableByTakerError(order.getHash(), notTaker, taker),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cannot fill order with bad maker signature', async () => {
|
it('cannot fill order with bad maker signature', async () => {
|
||||||
const order = getTestOtcOrder({ taker, txOrigin });
|
const order = getTestOtcOrder({ taker, txOrigin });
|
||||||
// Overwrite chainId to result in a different hash and therefore different
|
const anotherOrder = getTestOtcOrder({ taker, txOrigin });
|
||||||
// signature.
|
await testUtils.prepareBalancesForOrdersAsync([order], taker);
|
||||||
const tx = testUtils.fillTakerSignedOtcOrderAsync(order.clone({ chainId: 1234 }));
|
const tx = zeroEx
|
||||||
|
.fillTakerSignedOtcOrder(
|
||||||
|
order,
|
||||||
|
await anotherOrder.getSignatureWithProviderAsync(env.provider),
|
||||||
|
await order.getSignatureWithProviderAsync(env.provider, SignatureType.EthSign, taker),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ from: txOrigin });
|
||||||
|
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new RevertErrors.NativeOrders.OrderNotSignedByMakerError(order.getHash(), undefined, order.maker),
|
new RevertErrors.NativeOrders.OrderNotSignedByMakerError(order.getHash(), undefined, order.maker),
|
||||||
);
|
);
|
||||||
@ -600,7 +666,6 @@ blockchainTests.resets('OtcOrdersFeature', env => {
|
|||||||
order,
|
order,
|
||||||
await order.getSignatureWithProviderAsync(env.provider),
|
await order.getSignatureWithProviderAsync(env.provider),
|
||||||
await order.getSignatureWithProviderAsync(env.provider, SignatureType.EthSign, taker),
|
await order.getSignatureWithProviderAsync(env.provider, SignatureType.EthSign, taker),
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({ from: txOrigin, value: 1 });
|
.awaitTransactionSuccessAsync({ from: txOrigin, value: 1 });
|
||||||
// This will revert at the language level because the fill function is not payable.
|
// This will revert at the language level because the fill function is not payable.
|
||||||
@ -702,51 +767,102 @@ blockchainTests.resets('OtcOrdersFeature', env => {
|
|||||||
it('reverts if `unwrapWeth` is true but maker token is not WETH', async () => {
|
it('reverts if `unwrapWeth` is true but maker token is not WETH', async () => {
|
||||||
const order = getTestOtcOrder({ taker, txOrigin });
|
const order = getTestOtcOrder({ taker, txOrigin });
|
||||||
const tx = testUtils.fillTakerSignedOtcOrderAsync(order, txOrigin, taker, true);
|
const tx = testUtils.fillTakerSignedOtcOrderAsync(order, txOrigin, taker, true);
|
||||||
return expect(tx).to.revertWith('OtcOrdersFeature/INVALID_UNWRAP_WETH');
|
return expect(tx).to.revertWith('OtcOrdersFeature::fillTakerSignedOtcOrder/MAKER_TOKEN_NOT_WETH');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('allows for fills on orders signed by a approved signer (taker)', async () => {
|
describe('batchFillTakerSignedOtcOrders()', () => {
|
||||||
const order = getTestOtcOrder({ txOrigin, taker: contractWallet.address });
|
it('Fills multiple orders', async () => {
|
||||||
await testUtils.prepareBalancesForOrdersAsync([order], contractWallet.address);
|
const order1 = getTestOtcOrder({ taker, txOrigin });
|
||||||
// allow signer
|
const order2 = getTestOtcOrder({
|
||||||
await contractWallet
|
taker: notTaker,
|
||||||
.registerAllowedOrderSigner(contractWalletSigner, true)
|
txOrigin,
|
||||||
.awaitTransactionSuccessAsync({ from: contractWalletOwner });
|
nonceBucket: order1.nonceBucket,
|
||||||
// fill should succeed
|
nonce: order1.nonce.plus(1),
|
||||||
const receipt = await zeroEx
|
});
|
||||||
.fillTakerSignedOtcOrder(
|
await testUtils.prepareBalancesForOrdersAsync([order1], taker);
|
||||||
order,
|
await testUtils.prepareBalancesForOrdersAsync([order2], notTaker);
|
||||||
await order.getSignatureWithProviderAsync(env.provider),
|
const tx = await zeroEx
|
||||||
await order.getSignatureWithProviderAsync(
|
.batchFillTakerSignedOtcOrders(
|
||||||
env.provider,
|
[order1, order2],
|
||||||
SignatureType.EthSign,
|
[
|
||||||
contractWalletSigner,
|
await order1.getSignatureWithProviderAsync(env.provider),
|
||||||
),
|
await order2.getSignatureWithProviderAsync(env.provider),
|
||||||
false,
|
],
|
||||||
|
[
|
||||||
|
await order1.getSignatureWithProviderAsync(env.provider, SignatureType.EthSign, taker),
|
||||||
|
await order2.getSignatureWithProviderAsync(env.provider, SignatureType.EthSign, notTaker),
|
||||||
|
],
|
||||||
|
[false, false],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({ from: txOrigin });
|
.awaitTransactionSuccessAsync({ from: txOrigin });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
tx.logs,
|
||||||
[testUtils.createOtcOrderFilledEventArgs(order)],
|
[testUtils.createOtcOrderFilledEventArgs(order1), testUtils.createOtcOrderFilledEventArgs(order2)],
|
||||||
IZeroExEvents.OtcOrderFilled,
|
IZeroExEvents.OtcOrderFilled,
|
||||||
);
|
);
|
||||||
await assertExpectedFinalBalancesFromOtcOrderFillAsync(order);
|
|
||||||
});
|
});
|
||||||
|
it('Fills multiple orders and unwraps WETH', async () => {
|
||||||
it(`doesn't allow fills with an unapproved signer (taker)`, async () => {
|
const order1 = getTestOtcOrder({ taker, txOrigin });
|
||||||
const order = getTestOtcOrder({ txOrigin, taker: contractWallet.address });
|
const order2 = getTestOtcOrder({
|
||||||
await testUtils.prepareBalancesForOrdersAsync([order], contractWallet.address);
|
taker: notTaker,
|
||||||
// fill should succeed
|
txOrigin,
|
||||||
const tx = zeroEx
|
nonceBucket: order1.nonceBucket,
|
||||||
.fillTakerSignedOtcOrder(
|
nonce: order1.nonce.plus(1),
|
||||||
order,
|
makerToken: wethToken.address,
|
||||||
await order.getSignatureWithProviderAsync(env.provider),
|
makerAmount: new BigNumber('1e18'),
|
||||||
await order.getSignatureWithProviderAsync(env.provider, SignatureType.EthSign, notTaker),
|
});
|
||||||
false,
|
await testUtils.prepareBalancesForOrdersAsync([order1], taker);
|
||||||
|
await testUtils.prepareBalancesForOrdersAsync([order2], notTaker);
|
||||||
|
await wethToken.deposit().awaitTransactionSuccessAsync({ from: maker, value: order2.makerAmount });
|
||||||
|
const tx = await zeroEx
|
||||||
|
.batchFillTakerSignedOtcOrders(
|
||||||
|
[order1, order2],
|
||||||
|
[
|
||||||
|
await order1.getSignatureWithProviderAsync(env.provider),
|
||||||
|
await order2.getSignatureWithProviderAsync(env.provider),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
await order1.getSignatureWithProviderAsync(env.provider, SignatureType.EthSign, taker),
|
||||||
|
await order2.getSignatureWithProviderAsync(env.provider, SignatureType.EthSign, notTaker),
|
||||||
|
],
|
||||||
|
[false, true],
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({ from: txOrigin });
|
.awaitTransactionSuccessAsync({ from: txOrigin });
|
||||||
return expect(tx).to.revertWith(
|
verifyEventsFromLogs(
|
||||||
new RevertErrors.NativeOrders.OrderNotSignedByTakerError(order.getHash(), notTaker, order.taker),
|
tx.logs,
|
||||||
|
[testUtils.createOtcOrderFilledEventArgs(order1), testUtils.createOtcOrderFilledEventArgs(order2)],
|
||||||
|
IZeroExEvents.OtcOrderFilled,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('Skips over unfillable orders', async () => {
|
||||||
|
const order1 = getTestOtcOrder({ taker, txOrigin });
|
||||||
|
const order2 = getTestOtcOrder({
|
||||||
|
taker: notTaker,
|
||||||
|
txOrigin,
|
||||||
|
nonceBucket: order1.nonceBucket,
|
||||||
|
nonce: order1.nonce.plus(1),
|
||||||
|
});
|
||||||
|
await testUtils.prepareBalancesForOrdersAsync([order1], taker);
|
||||||
|
await testUtils.prepareBalancesForOrdersAsync([order2], notTaker);
|
||||||
|
const tx = await zeroEx
|
||||||
|
.batchFillTakerSignedOtcOrders(
|
||||||
|
[order1, order2],
|
||||||
|
[
|
||||||
|
await order1.getSignatureWithProviderAsync(env.provider),
|
||||||
|
await order2.getSignatureWithProviderAsync(env.provider),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
await order1.getSignatureWithProviderAsync(env.provider, SignatureType.EthSign, taker),
|
||||||
|
await order2.getSignatureWithProviderAsync(env.provider, SignatureType.EthSign, taker), // Invalid signature for order2
|
||||||
|
],
|
||||||
|
[false, false],
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ from: txOrigin });
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
tx.logs,
|
||||||
|
[testUtils.createOtcOrderFilledEventArgs(order1)],
|
||||||
|
IZeroExEvents.OtcOrderFilled,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -243,6 +243,8 @@ blockchainTests.resets('TransformERC20 feature', env => {
|
|||||||
inputTokenAmount,
|
inputTokenAmount,
|
||||||
minOutputTokenAmount,
|
minOutputTokenAmount,
|
||||||
transformations: [transformation],
|
transformations: [transformation],
|
||||||
|
useSelfBalance: false,
|
||||||
|
recipient: taker,
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync({ value: callValue });
|
.awaitTransactionSuccessAsync({ value: callValue });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
@ -281,7 +283,7 @@ blockchainTests.resets('TransformERC20 feature', env => {
|
|||||||
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
|
const inputTokenAmount = getRandomPortion(startingInputTokenBalance);
|
||||||
const minOutputTokenAmount = getRandomInteger(1, '1e18');
|
const minOutputTokenAmount = getRandomInteger(1, '1e18');
|
||||||
const outputTokenMintAmount = minOutputTokenAmount;
|
const outputTokenMintAmount = minOutputTokenAmount;
|
||||||
const callValue = outputTokenMintAmount.times(2);
|
const callValue = outputTokenMintAmount;
|
||||||
const transformation = createMintTokenTransformation({
|
const transformation = createMintTokenTransformation({
|
||||||
outputTokenMintAmount,
|
outputTokenMintAmount,
|
||||||
inputTokenBurnAmunt: inputTokenAmount,
|
inputTokenBurnAmunt: inputTokenAmount,
|
||||||
@ -296,6 +298,8 @@ blockchainTests.resets('TransformERC20 feature', env => {
|
|||||||
inputTokenAmount,
|
inputTokenAmount,
|
||||||
minOutputTokenAmount,
|
minOutputTokenAmount,
|
||||||
transformations: [transformation],
|
transformations: [transformation],
|
||||||
|
useSelfBalance: false,
|
||||||
|
recipient: taker,
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync({ value: callValue });
|
.awaitTransactionSuccessAsync({ value: callValue });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
@ -352,6 +356,8 @@ blockchainTests.resets('TransformERC20 feature', env => {
|
|||||||
inputTokenAmount,
|
inputTokenAmount,
|
||||||
minOutputTokenAmount,
|
minOutputTokenAmount,
|
||||||
transformations: [transformation],
|
transformations: [transformation],
|
||||||
|
useSelfBalance: false,
|
||||||
|
recipient: taker,
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync({ value: callValue });
|
.awaitTransactionSuccessAsync({ value: callValue });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
@ -406,6 +412,8 @@ blockchainTests.resets('TransformERC20 feature', env => {
|
|||||||
inputTokenBurnAmunt: inputTokenAmount,
|
inputTokenBurnAmunt: inputTokenAmount,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
useSelfBalance: false,
|
||||||
|
recipient: taker,
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync({ value: callValue });
|
.awaitTransactionSuccessAsync({ value: callValue });
|
||||||
const expectedError = new ZeroExRevertErrors.TransformERC20.IncompleteTransformERC20Error(
|
const expectedError = new ZeroExRevertErrors.TransformERC20.IncompleteTransformERC20Error(
|
||||||
@ -438,6 +446,8 @@ blockchainTests.resets('TransformERC20 feature', env => {
|
|||||||
inputTokenBurnAmunt: inputTokenAmount,
|
inputTokenBurnAmunt: inputTokenAmount,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
useSelfBalance: false,
|
||||||
|
recipient: taker,
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync({ value: callValue });
|
.awaitTransactionSuccessAsync({ value: callValue });
|
||||||
const expectedError = new ZeroExRevertErrors.TransformERC20.NegativeTransformERC20OutputError(
|
const expectedError = new ZeroExRevertErrors.TransformERC20.NegativeTransformERC20OutputError(
|
||||||
@ -475,6 +485,8 @@ blockchainTests.resets('TransformERC20 feature', env => {
|
|||||||
inputTokenAmount,
|
inputTokenAmount,
|
||||||
minOutputTokenAmount,
|
minOutputTokenAmount,
|
||||||
transformations,
|
transformations,
|
||||||
|
useSelfBalance: false,
|
||||||
|
recipient: taker,
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync({ value: callValue });
|
.awaitTransactionSuccessAsync({ value: callValue });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
@ -520,6 +532,8 @@ blockchainTests.resets('TransformERC20 feature', env => {
|
|||||||
inputTokenAmount,
|
inputTokenAmount,
|
||||||
minOutputTokenAmount,
|
minOutputTokenAmount,
|
||||||
transformations,
|
transformations,
|
||||||
|
useSelfBalance: false,
|
||||||
|
recipient: taker,
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync({ value: callValue });
|
.awaitTransactionSuccessAsync({ value: callValue });
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
@ -549,6 +563,8 @@ blockchainTests.resets('TransformERC20 feature', env => {
|
|||||||
inputTokenAmount: MAX_UINT256,
|
inputTokenAmount: MAX_UINT256,
|
||||||
minOutputTokenAmount,
|
minOutputTokenAmount,
|
||||||
transformations: [transformation],
|
transformations: [transformation],
|
||||||
|
useSelfBalance: false,
|
||||||
|
recipient: taker,
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync({ value: callValue });
|
.awaitTransactionSuccessAsync({ value: callValue });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
@ -584,6 +600,8 @@ blockchainTests.resets('TransformERC20 feature', env => {
|
|||||||
inputTokenAmount: MAX_UINT256,
|
inputTokenAmount: MAX_UINT256,
|
||||||
minOutputTokenAmount,
|
minOutputTokenAmount,
|
||||||
transformations: [transformation],
|
transformations: [transformation],
|
||||||
|
useSelfBalance: false,
|
||||||
|
recipient: taker,
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync({ value: ethAttchedAmount });
|
.awaitTransactionSuccessAsync({ value: ethAttchedAmount });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
|
@ -70,7 +70,7 @@ blockchainTests.resets('Full migration', env => {
|
|||||||
TransformERC20: {
|
TransformERC20: {
|
||||||
contractType: ITransformERC20FeatureContract,
|
contractType: ITransformERC20FeatureContract,
|
||||||
fns: [
|
fns: [
|
||||||
'transformERC20',
|
// 'transformERC20', TODO
|
||||||
'_transformERC20',
|
'_transformERC20',
|
||||||
'createTransformWallet',
|
'createTransformWallet',
|
||||||
'getTransformWallet',
|
'getTransformWallet',
|
||||||
@ -148,6 +148,9 @@ blockchainTests.resets('Full migration', env => {
|
|||||||
if (item.type === 'byte') {
|
if (item.type === 'byte') {
|
||||||
return hexUtils.random(1);
|
return hexUtils.random(1);
|
||||||
}
|
}
|
||||||
|
if (item.type === 'bool') {
|
||||||
|
return Math.random() > 0.5;
|
||||||
|
}
|
||||||
if (/^bytes$/.test(item.type)) {
|
if (/^bytes$/.test(item.type)) {
|
||||||
return hexUtils.random(_.random(0, 128));
|
return hexUtils.random(_.random(0, 128));
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ blockchainTests.resets('AffiliateFeeTransformer', env => {
|
|||||||
.rawExecuteTransform(transformer.address, {
|
.rawExecuteTransform(transformer.address, {
|
||||||
data,
|
data,
|
||||||
sender: randomAddress(),
|
sender: randomAddress(),
|
||||||
taker: randomAddress(),
|
recipient: randomAddress(),
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES);
|
expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES);
|
||||||
@ -119,7 +119,7 @@ blockchainTests.resets('AffiliateFeeTransformer', env => {
|
|||||||
.rawExecuteTransform(transformer.address, {
|
.rawExecuteTransform(transformer.address, {
|
||||||
data,
|
data,
|
||||||
sender: randomAddress(),
|
sender: randomAddress(),
|
||||||
taker: randomAddress(),
|
recipient: randomAddress(),
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES);
|
expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES);
|
||||||
@ -149,7 +149,7 @@ blockchainTests.resets('AffiliateFeeTransformer', env => {
|
|||||||
.rawExecuteTransform(transformer.address, {
|
.rawExecuteTransform(transformer.address, {
|
||||||
data,
|
data,
|
||||||
sender: randomAddress(),
|
sender: randomAddress(),
|
||||||
taker: randomAddress(),
|
recipient: randomAddress(),
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
expect(await getBalancesAsync(host.address)).to.deep.eq({
|
expect(await getBalancesAsync(host.address)).to.deep.eq({
|
||||||
|
@ -80,7 +80,7 @@ blockchainTests.resets('PayTakerTransformer', env => {
|
|||||||
await host
|
await host
|
||||||
.rawExecuteTransform(transformer.address, {
|
.rawExecuteTransform(transformer.address, {
|
||||||
data,
|
data,
|
||||||
taker,
|
recipient: taker,
|
||||||
sender: randomAddress(),
|
sender: randomAddress(),
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
@ -102,7 +102,7 @@ blockchainTests.resets('PayTakerTransformer', env => {
|
|||||||
await host
|
await host
|
||||||
.rawExecuteTransform(transformer.address, {
|
.rawExecuteTransform(transformer.address, {
|
||||||
data,
|
data,
|
||||||
taker,
|
recipient: taker,
|
||||||
sender: randomAddress(),
|
sender: randomAddress(),
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
@ -124,7 +124,7 @@ blockchainTests.resets('PayTakerTransformer', env => {
|
|||||||
await host
|
await host
|
||||||
.rawExecuteTransform(transformer.address, {
|
.rawExecuteTransform(transformer.address, {
|
||||||
data,
|
data,
|
||||||
taker,
|
recipient: taker,
|
||||||
sender: randomAddress(),
|
sender: randomAddress(),
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
@ -146,7 +146,7 @@ blockchainTests.resets('PayTakerTransformer', env => {
|
|||||||
await host
|
await host
|
||||||
.rawExecuteTransform(transformer.address, {
|
.rawExecuteTransform(transformer.address, {
|
||||||
data,
|
data,
|
||||||
taker,
|
recipient: taker,
|
||||||
sender: randomAddress(),
|
sender: randomAddress(),
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
|
@ -70,7 +70,7 @@ blockchainTests.resets('PositiveSlippageFeeTransformer', env => {
|
|||||||
.rawExecuteTransform(transformer.address, {
|
.rawExecuteTransform(transformer.address, {
|
||||||
data,
|
data,
|
||||||
sender: randomAddress(),
|
sender: randomAddress(),
|
||||||
taker: randomAddress(),
|
recipient: randomAddress(),
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
expect(await getBalancesAsync(host.address)).to.deep.eq(beforeBalanceHost);
|
expect(await getBalancesAsync(host.address)).to.deep.eq(beforeBalanceHost);
|
||||||
@ -92,7 +92,7 @@ blockchainTests.resets('PositiveSlippageFeeTransformer', env => {
|
|||||||
.rawExecuteTransform(transformer.address, {
|
.rawExecuteTransform(transformer.address, {
|
||||||
data,
|
data,
|
||||||
sender: randomAddress(),
|
sender: randomAddress(),
|
||||||
taker: randomAddress(),
|
recipient: randomAddress(),
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
expect(await getBalancesAsync(host.address)).to.deep.eq(beforeBalanceHost);
|
expect(await getBalancesAsync(host.address)).to.deep.eq(beforeBalanceHost);
|
||||||
@ -112,7 +112,7 @@ blockchainTests.resets('PositiveSlippageFeeTransformer', env => {
|
|||||||
.rawExecuteTransform(transformer.address, {
|
.rawExecuteTransform(transformer.address, {
|
||||||
data,
|
data,
|
||||||
sender: randomAddress(),
|
sender: randomAddress(),
|
||||||
taker: randomAddress(),
|
recipient: randomAddress(),
|
||||||
})
|
})
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
expect(await getBalancesAsync(host.address)).to.deep.eq({
|
expect(await getBalancesAsync(host.address)).to.deep.eq({
|
||||||
|
@ -156,14 +156,23 @@ export class NativeOrdersTestEnvironment {
|
|||||||
unwrapWeth: boolean = false,
|
unwrapWeth: boolean = false,
|
||||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||||
await this.prepareBalancesForOrdersAsync([order], taker);
|
await this.prepareBalancesForOrdersAsync([order], taker);
|
||||||
return this.zeroEx
|
if (unwrapWeth) {
|
||||||
.fillOtcOrder(
|
return this.zeroEx
|
||||||
order,
|
.fillOtcOrderForEth(
|
||||||
await order.getSignatureWithProviderAsync(this._env.provider),
|
order,
|
||||||
new BigNumber(fillAmount),
|
await order.getSignatureWithProviderAsync(this._env.provider),
|
||||||
unwrapWeth,
|
new BigNumber(fillAmount),
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({ from: taker });
|
.awaitTransactionSuccessAsync({ from: taker });
|
||||||
|
} else {
|
||||||
|
return this.zeroEx
|
||||||
|
.fillOtcOrder(
|
||||||
|
order,
|
||||||
|
await order.getSignatureWithProviderAsync(this._env.provider),
|
||||||
|
new BigNumber(fillAmount),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ from: taker });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async fillTakerSignedOtcOrderAsync(
|
public async fillTakerSignedOtcOrderAsync(
|
||||||
@ -173,14 +182,23 @@ export class NativeOrdersTestEnvironment {
|
|||||||
unwrapWeth: boolean = false,
|
unwrapWeth: boolean = false,
|
||||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||||
await this.prepareBalancesForOrdersAsync([order], taker);
|
await this.prepareBalancesForOrdersAsync([order], taker);
|
||||||
return this.zeroEx
|
if (unwrapWeth) {
|
||||||
.fillTakerSignedOtcOrder(
|
return this.zeroEx
|
||||||
order,
|
.fillTakerSignedOtcOrderForEth(
|
||||||
await order.getSignatureWithProviderAsync(this._env.provider),
|
order,
|
||||||
await order.getSignatureWithProviderAsync(this._env.provider, SignatureType.EthSign, taker),
|
await order.getSignatureWithProviderAsync(this._env.provider),
|
||||||
unwrapWeth,
|
await order.getSignatureWithProviderAsync(this._env.provider, SignatureType.EthSign, taker),
|
||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({ from: origin });
|
.awaitTransactionSuccessAsync({ from: origin });
|
||||||
|
} else {
|
||||||
|
return this.zeroEx
|
||||||
|
.fillTakerSignedOtcOrder(
|
||||||
|
order,
|
||||||
|
await order.getSignatureWithProviderAsync(this._env.provider),
|
||||||
|
await order.getSignatureWithProviderAsync(this._env.provider, SignatureType.EthSign, taker),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ from: origin });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async fillOtcOrderWithEthAsync(
|
public async fillOtcOrderWithEthAsync(
|
||||||
|
@ -102,6 +102,12 @@ export * from '../test/generated-wrappers/mixin_uniswap_v3';
|
|||||||
export * from '../test/generated-wrappers/mixin_zero_ex_bridge';
|
export * from '../test/generated-wrappers/mixin_zero_ex_bridge';
|
||||||
export * from '../test/generated-wrappers/mooniswap_liquidity_provider';
|
export * from '../test/generated-wrappers/mooniswap_liquidity_provider';
|
||||||
export * from '../test/generated-wrappers/multiplex_feature';
|
export * from '../test/generated-wrappers/multiplex_feature';
|
||||||
|
export * from '../test/generated-wrappers/multiplex_liquidity_provider';
|
||||||
|
export * from '../test/generated-wrappers/multiplex_otc';
|
||||||
|
export * from '../test/generated-wrappers/multiplex_rfq';
|
||||||
|
export * from '../test/generated-wrappers/multiplex_transform_erc20';
|
||||||
|
export * from '../test/generated-wrappers/multiplex_uniswap_v2';
|
||||||
|
export * from '../test/generated-wrappers/multiplex_uniswap_v3';
|
||||||
export * from '../test/generated-wrappers/native_orders_cancellation';
|
export * from '../test/generated-wrappers/native_orders_cancellation';
|
||||||
export * from '../test/generated-wrappers/native_orders_feature';
|
export * from '../test/generated-wrappers/native_orders_feature';
|
||||||
export * from '../test/generated-wrappers/native_orders_info';
|
export * from '../test/generated-wrappers/native_orders_info';
|
||||||
@ -149,6 +155,8 @@ export * from '../test/generated-wrappers/test_transform_erc20';
|
|||||||
export * from '../test/generated-wrappers/test_transformer_base';
|
export * from '../test/generated-wrappers/test_transformer_base';
|
||||||
export * from '../test/generated-wrappers/test_transformer_deployer_transformer';
|
export * from '../test/generated-wrappers/test_transformer_deployer_transformer';
|
||||||
export * from '../test/generated-wrappers/test_transformer_host';
|
export * from '../test/generated-wrappers/test_transformer_host';
|
||||||
|
export * from '../test/generated-wrappers/test_uniswap_v2_factory';
|
||||||
|
export * from '../test/generated-wrappers/test_uniswap_v2_pool';
|
||||||
export * from '../test/generated-wrappers/test_uniswap_v3_factory';
|
export * from '../test/generated-wrappers/test_uniswap_v3_factory';
|
||||||
export * from '../test/generated-wrappers/test_uniswap_v3_feature';
|
export * from '../test/generated-wrappers/test_uniswap_v3_feature';
|
||||||
export * from '../test/generated-wrappers/test_uniswap_v3_pool';
|
export * from '../test/generated-wrappers/test_uniswap_v3_pool';
|
||||||
|
@ -135,6 +135,12 @@
|
|||||||
"test/generated-artifacts/MixinZeroExBridge.json",
|
"test/generated-artifacts/MixinZeroExBridge.json",
|
||||||
"test/generated-artifacts/MooniswapLiquidityProvider.json",
|
"test/generated-artifacts/MooniswapLiquidityProvider.json",
|
||||||
"test/generated-artifacts/MultiplexFeature.json",
|
"test/generated-artifacts/MultiplexFeature.json",
|
||||||
|
"test/generated-artifacts/MultiplexLiquidityProvider.json",
|
||||||
|
"test/generated-artifacts/MultiplexOtc.json",
|
||||||
|
"test/generated-artifacts/MultiplexRfq.json",
|
||||||
|
"test/generated-artifacts/MultiplexTransformERC20.json",
|
||||||
|
"test/generated-artifacts/MultiplexUniswapV2.json",
|
||||||
|
"test/generated-artifacts/MultiplexUniswapV3.json",
|
||||||
"test/generated-artifacts/NativeOrdersCancellation.json",
|
"test/generated-artifacts/NativeOrdersCancellation.json",
|
||||||
"test/generated-artifacts/NativeOrdersFeature.json",
|
"test/generated-artifacts/NativeOrdersFeature.json",
|
||||||
"test/generated-artifacts/NativeOrdersInfo.json",
|
"test/generated-artifacts/NativeOrdersInfo.json",
|
||||||
@ -182,6 +188,8 @@
|
|||||||
"test/generated-artifacts/TestTransformerBase.json",
|
"test/generated-artifacts/TestTransformerBase.json",
|
||||||
"test/generated-artifacts/TestTransformerDeployerTransformer.json",
|
"test/generated-artifacts/TestTransformerDeployerTransformer.json",
|
||||||
"test/generated-artifacts/TestTransformerHost.json",
|
"test/generated-artifacts/TestTransformerHost.json",
|
||||||
|
"test/generated-artifacts/TestUniswapV2Factory.json",
|
||||||
|
"test/generated-artifacts/TestUniswapV2Pool.json",
|
||||||
"test/generated-artifacts/TestUniswapV3Factory.json",
|
"test/generated-artifacts/TestUniswapV3Factory.json",
|
||||||
"test/generated-artifacts/TestUniswapV3Feature.json",
|
"test/generated-artifacts/TestUniswapV3Feature.json",
|
||||||
"test/generated-artifacts/TestUniswapV3Pool.json",
|
"test/generated-artifacts/TestUniswapV3Pool.json",
|
||||||
|
@ -49,20 +49,6 @@ export class OrderNotSignedByMakerError extends RevertError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OrderNotSignedByTakerError extends RevertError {
|
|
||||||
constructor(orderHash?: string, signer?: string, taker?: string) {
|
|
||||||
super(
|
|
||||||
'OrderNotSignedByTakerError',
|
|
||||||
'OrderNotSignedByTakerError(bytes32 orderHash, address signer, address taker)',
|
|
||||||
{
|
|
||||||
orderHash,
|
|
||||||
signer,
|
|
||||||
taker,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class InvalidSignerError extends RevertError {
|
export class InvalidSignerError extends RevertError {
|
||||||
constructor(maker?: string, signer?: string) {
|
constructor(maker?: string, signer?: string) {
|
||||||
super('InvalidSignerError', 'InvalidSignerError(address maker, address signer)', {
|
super('InvalidSignerError', 'InvalidSignerError(address maker, address signer)', {
|
||||||
@ -152,7 +138,6 @@ const types = [
|
|||||||
OrderNotFillableByOriginError,
|
OrderNotFillableByOriginError,
|
||||||
OrderNotFillableError,
|
OrderNotFillableError,
|
||||||
OrderNotSignedByMakerError,
|
OrderNotSignedByMakerError,
|
||||||
OrderNotSignedByTakerError,
|
|
||||||
OrderNotFillableBySenderError,
|
OrderNotFillableBySenderError,
|
||||||
OrderNotFillableByTakerError,
|
OrderNotFillableByTakerError,
|
||||||
CancelSaltTooLowError,
|
CancelSaltTooLowError,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user