MultiplexFeature and BatchFillNativeOrdersFeature (#140)
* WrappedFillFeature * Address internal feedback * create features/interfaces/ directory * Split NativeOrdersFeature into mixins * Rename mixins to use NativeOrders namespace * Add BatchFillNativeOrdersFeature * Rename WrapperFillFeature => MultiplexFeature and add natspec comments * Emit LiquidityProviderSwap event * post-rebase fixes * Multiplex mainnet fork tests * lint * Add tests for batch fill functions * Remove market functions * Addres PR feedback * Remove nested _batchFill calls from _multiHopFill * Add BatchFillIncompleteRevertError type * Use call{value: amount}() instead of transfer(amount) * Remove outdated comment * Update some comments * Add events * Address spot-check recommendations * Remove-top level events, add ExpiredRfqOrder event * Update changelog * Change ExpiredRfqOrder event * Update IZeroEx artifact and contract wrapper
This commit is contained in:
parent
22c8e0b6db
commit
3cc639c8d0
@ -6,6 +6,7 @@ export {
|
|||||||
WETH9Events,
|
WETH9Events,
|
||||||
WETH9DepositEventArgs,
|
WETH9DepositEventArgs,
|
||||||
WETH9TransferEventArgs,
|
WETH9TransferEventArgs,
|
||||||
|
WETH9WithdrawalEventArgs,
|
||||||
ZRXTokenContract,
|
ZRXTokenContract,
|
||||||
DummyERC20TokenTransferEventArgs,
|
DummyERC20TokenTransferEventArgs,
|
||||||
ERC20TokenEventArgs,
|
ERC20TokenEventArgs,
|
||||||
|
@ -9,6 +9,10 @@
|
|||||||
{
|
{
|
||||||
"note": "Emit `LiquidityProviderFill` event in `CurveLiquidityProvider`",
|
"note": "Emit `LiquidityProviderFill` event in `CurveLiquidityProvider`",
|
||||||
"pr": 143
|
"pr": 143
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note": "Add BatchFillNativeOrdersFeature and MultiplexFeature",
|
||||||
|
"pr": 140
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -20,14 +20,16 @@
|
|||||||
pragma solidity ^0.6.5;
|
pragma solidity ^0.6.5;
|
||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "./features/IOwnableFeature.sol";
|
import "./features/interfaces/IOwnableFeature.sol";
|
||||||
import "./features/ISimpleFunctionRegistryFeature.sol";
|
import "./features/interfaces/ISimpleFunctionRegistryFeature.sol";
|
||||||
import "./features/ITokenSpenderFeature.sol";
|
import "./features/interfaces/ITokenSpenderFeature.sol";
|
||||||
import "./features/ITransformERC20Feature.sol";
|
import "./features/interfaces/ITransformERC20Feature.sol";
|
||||||
import "./features/IMetaTransactionsFeature.sol";
|
import "./features/interfaces/IMetaTransactionsFeature.sol";
|
||||||
import "./features/IUniswapFeature.sol";
|
import "./features/interfaces/IUniswapFeature.sol";
|
||||||
import "./features/ILiquidityProviderFeature.sol";
|
import "./features/interfaces/ILiquidityProviderFeature.sol";
|
||||||
import "./features/INativeOrdersFeature.sol";
|
import "./features/interfaces/INativeOrdersFeature.sol";
|
||||||
|
import "./features/interfaces/IBatchFillNativeOrdersFeature.sol";
|
||||||
|
import "./features/interfaces/IMultiplexFeature.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @dev Interface for a fully featured Exchange Proxy.
|
/// @dev Interface for a fully featured Exchange Proxy.
|
||||||
@ -39,7 +41,9 @@ interface IZeroEx is
|
|||||||
IMetaTransactionsFeature,
|
IMetaTransactionsFeature,
|
||||||
IUniswapFeature,
|
IUniswapFeature,
|
||||||
ILiquidityProviderFeature,
|
ILiquidityProviderFeature,
|
||||||
INativeOrdersFeature
|
INativeOrdersFeature,
|
||||||
|
IBatchFillNativeOrdersFeature,
|
||||||
|
IMultiplexFeature
|
||||||
{
|
{
|
||||||
// solhint-disable state-visibility
|
// solhint-disable state-visibility
|
||||||
|
|
||||||
|
@ -170,4 +170,21 @@ library LibNativeOrdersRichErrors {
|
|||||||
maker
|
maker
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function BatchFillIncompleteError(
|
||||||
|
bytes32 orderHash,
|
||||||
|
uint256 takerTokenFilledAmount,
|
||||||
|
uint256 takerTokenFillAmount
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
pure
|
||||||
|
returns (bytes memory)
|
||||||
|
{
|
||||||
|
return abi.encodeWithSelector(
|
||||||
|
bytes4(keccak256("BatchFillIncompleteError(bytes32,uint256,uint256)")),
|
||||||
|
orderHash,
|
||||||
|
takerTokenFilledAmount,
|
||||||
|
takerTokenFillAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,198 @@
|
|||||||
|
// 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/errors/LibRichErrorsV06.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
||||||
|
import "../errors/LibNativeOrdersRichErrors.sol";
|
||||||
|
import "../fixins/FixinCommon.sol";
|
||||||
|
import "../fixins/FixinEIP712.sol";
|
||||||
|
import "../migrations/LibMigrate.sol";
|
||||||
|
import "./interfaces/IFeature.sol";
|
||||||
|
import "./interfaces/IBatchFillNativeOrdersFeature.sol";
|
||||||
|
import "./interfaces/INativeOrdersFeature.sol";
|
||||||
|
import "./libs/LibNativeOrder.sol";
|
||||||
|
import "./libs/LibSignature.sol";
|
||||||
|
|
||||||
|
|
||||||
|
/// @dev Feature for batch/market filling limit and RFQ orders.
|
||||||
|
contract BatchFillNativeOrdersFeature is
|
||||||
|
IFeature,
|
||||||
|
IBatchFillNativeOrdersFeature,
|
||||||
|
FixinCommon,
|
||||||
|
FixinEIP712
|
||||||
|
{
|
||||||
|
using LibSafeMathV06 for uint128;
|
||||||
|
using LibSafeMathV06 for uint256;
|
||||||
|
using LibRichErrorsV06 for bytes;
|
||||||
|
|
||||||
|
/// @dev Name of this feature.
|
||||||
|
string public constant override FEATURE_NAME = "BatchFill";
|
||||||
|
/// @dev Version of this feature.
|
||||||
|
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
|
||||||
|
|
||||||
|
constructor(address zeroExAddress)
|
||||||
|
public
|
||||||
|
FixinEIP712(zeroExAddress)
|
||||||
|
{
|
||||||
|
// solhint-disable no-empty-blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @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.batchFillLimitOrders.selector);
|
||||||
|
_registerFeatureFunction(this.batchFillRfqOrders.selector);
|
||||||
|
return LibMigrate.MIGRATE_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Fills multiple limit orders.
|
||||||
|
/// @param orders Array of limit orders.
|
||||||
|
/// @param signatures Array of signatures corresponding to each order.
|
||||||
|
/// @param takerTokenFillAmounts Array of desired amounts to fill each order.
|
||||||
|
/// @param revertIfIncomplete If true, reverts if this function fails to
|
||||||
|
/// fill the full fill amount for any individual order.
|
||||||
|
/// @return takerTokenFilledAmounts Array of amounts filled, in taker token.
|
||||||
|
/// @return makerTokenFilledAmounts Array of amounts filled, in maker token.
|
||||||
|
function batchFillLimitOrders(
|
||||||
|
LibNativeOrder.LimitOrder[] calldata orders,
|
||||||
|
LibSignature.Signature[] calldata signatures,
|
||||||
|
uint128[] calldata takerTokenFillAmounts,
|
||||||
|
bool revertIfIncomplete
|
||||||
|
)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
override
|
||||||
|
returns (
|
||||||
|
uint128[] memory takerTokenFilledAmounts,
|
||||||
|
uint128[] memory makerTokenFilledAmounts
|
||||||
|
)
|
||||||
|
{
|
||||||
|
require(
|
||||||
|
orders.length == signatures.length && orders.length == takerTokenFillAmounts.length,
|
||||||
|
'BatchFillNativeOrdersFeature::batchFillLimitOrders/MISMATCHED_ARRAY_LENGTHS'
|
||||||
|
);
|
||||||
|
takerTokenFilledAmounts = new uint128[](orders.length);
|
||||||
|
makerTokenFilledAmounts = new uint128[](orders.length);
|
||||||
|
uint256 protocolFee = uint256(INativeOrdersFeature(address(this)).getProtocolFeeMultiplier())
|
||||||
|
.safeMul(tx.gasprice);
|
||||||
|
uint256 ethProtocolFeePaid;
|
||||||
|
for (uint256 i = 0; i != orders.length; i++) {
|
||||||
|
try
|
||||||
|
INativeOrdersFeature(address(this))._fillLimitOrder
|
||||||
|
(
|
||||||
|
orders[i],
|
||||||
|
signatures[i],
|
||||||
|
takerTokenFillAmounts[i],
|
||||||
|
msg.sender,
|
||||||
|
msg.sender
|
||||||
|
)
|
||||||
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||||
|
{
|
||||||
|
// Update amounts filled.
|
||||||
|
(takerTokenFilledAmounts[i], makerTokenFilledAmounts[i]) =
|
||||||
|
(takerTokenFilledAmount, makerTokenFilledAmount);
|
||||||
|
ethProtocolFeePaid = ethProtocolFeePaid.safeAdd(protocolFee);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (
|
||||||
|
revertIfIncomplete &&
|
||||||
|
takerTokenFilledAmounts[i] < takerTokenFillAmounts[i]
|
||||||
|
) {
|
||||||
|
bytes32 orderHash = _getEIP712Hash(
|
||||||
|
LibNativeOrder.getLimitOrderStructHash(orders[i])
|
||||||
|
);
|
||||||
|
// Did not fill the amount requested.
|
||||||
|
LibNativeOrdersRichErrors.BatchFillIncompleteError(
|
||||||
|
orderHash,
|
||||||
|
takerTokenFilledAmounts[i],
|
||||||
|
takerTokenFillAmounts[i]
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LibNativeOrder.refundExcessProtocolFeeToSender(ethProtocolFeePaid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Fills multiple RFQ orders.
|
||||||
|
/// @param orders Array of RFQ orders.
|
||||||
|
/// @param signatures Array of signatures corresponding to each order.
|
||||||
|
/// @param takerTokenFillAmounts Array of desired amounts to fill each order.
|
||||||
|
/// @param revertIfIncomplete If true, reverts if this function fails to
|
||||||
|
/// fill the full fill amount for any individual order.
|
||||||
|
/// @return takerTokenFilledAmounts Array of amounts filled, in taker token.
|
||||||
|
/// @return makerTokenFilledAmounts Array of amounts filled, in maker token.
|
||||||
|
function batchFillRfqOrders(
|
||||||
|
LibNativeOrder.RfqOrder[] calldata orders,
|
||||||
|
LibSignature.Signature[] calldata signatures,
|
||||||
|
uint128[] calldata takerTokenFillAmounts,
|
||||||
|
bool revertIfIncomplete
|
||||||
|
)
|
||||||
|
external
|
||||||
|
override
|
||||||
|
returns (
|
||||||
|
uint128[] memory takerTokenFilledAmounts,
|
||||||
|
uint128[] memory makerTokenFilledAmounts
|
||||||
|
)
|
||||||
|
{
|
||||||
|
require(
|
||||||
|
orders.length == signatures.length && orders.length == takerTokenFillAmounts.length,
|
||||||
|
'BatchFillNativeOrdersFeature::batchFillRfqOrders/MISMATCHED_ARRAY_LENGTHS'
|
||||||
|
);
|
||||||
|
takerTokenFilledAmounts = new uint128[](orders.length);
|
||||||
|
makerTokenFilledAmounts = new uint128[](orders.length);
|
||||||
|
for (uint256 i = 0; i != orders.length; i++) {
|
||||||
|
try
|
||||||
|
INativeOrdersFeature(address(this))._fillRfqOrder
|
||||||
|
(
|
||||||
|
orders[i],
|
||||||
|
signatures[i],
|
||||||
|
takerTokenFillAmounts[i],
|
||||||
|
msg.sender
|
||||||
|
)
|
||||||
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||||
|
{
|
||||||
|
// Update amounts filled.
|
||||||
|
(takerTokenFilledAmounts[i], makerTokenFilledAmounts[i]) =
|
||||||
|
(takerTokenFilledAmount, makerTokenFilledAmount);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (
|
||||||
|
revertIfIncomplete &&
|
||||||
|
takerTokenFilledAmounts[i] < takerTokenFillAmounts[i]
|
||||||
|
) {
|
||||||
|
// Did not fill the amount requested.
|
||||||
|
bytes32 orderHash = _getEIP712Hash(
|
||||||
|
LibNativeOrder.getRfqOrderStructHash(orders[i])
|
||||||
|
);
|
||||||
|
LibNativeOrdersRichErrors.BatchFillIncompleteError(
|
||||||
|
orderHash,
|
||||||
|
takerTokenFilledAmounts[i],
|
||||||
|
takerTokenFillAmounts[i]
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,7 @@ pragma experimental ABIEncoderV2;
|
|||||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||||
import "../migrations/LibBootstrap.sol";
|
import "../migrations/LibBootstrap.sol";
|
||||||
import "../storage/LibProxyStorage.sol";
|
import "../storage/LibProxyStorage.sol";
|
||||||
import "./IBootstrapFeature.sol";
|
import "./interfaces/IBootstrapFeature.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @dev Detachable `bootstrap()` feature.
|
/// @dev Detachable `bootstrap()` feature.
|
||||||
|
@ -31,8 +31,8 @@ import "../fixins/FixinCommon.sol";
|
|||||||
import "../fixins/FixinTokenSpender.sol";
|
import "../fixins/FixinTokenSpender.sol";
|
||||||
import "../migrations/LibMigrate.sol";
|
import "../migrations/LibMigrate.sol";
|
||||||
import "../transformers/LibERC20Transformer.sol";
|
import "../transformers/LibERC20Transformer.sol";
|
||||||
import "./IFeature.sol";
|
import "./interfaces/IFeature.sol";
|
||||||
import "./ILiquidityProviderFeature.sol";
|
import "./interfaces/ILiquidityProviderFeature.sol";
|
||||||
|
|
||||||
|
|
||||||
contract LiquidityProviderFeature is
|
contract LiquidityProviderFeature is
|
||||||
|
@ -30,11 +30,11 @@ import "../fixins/FixinTokenSpender.sol";
|
|||||||
import "../fixins/FixinEIP712.sol";
|
import "../fixins/FixinEIP712.sol";
|
||||||
import "../migrations/LibMigrate.sol";
|
import "../migrations/LibMigrate.sol";
|
||||||
import "../storage/LibMetaTransactionsStorage.sol";
|
import "../storage/LibMetaTransactionsStorage.sol";
|
||||||
import "./IMetaTransactionsFeature.sol";
|
import "./interfaces/IFeature.sol";
|
||||||
import "./ITransformERC20Feature.sol";
|
import "./interfaces/IMetaTransactionsFeature.sol";
|
||||||
|
import "./interfaces/INativeOrdersFeature.sol";
|
||||||
|
import "./interfaces/ITransformERC20Feature.sol";
|
||||||
import "./libs/LibSignature.sol";
|
import "./libs/LibSignature.sol";
|
||||||
import "./IFeature.sol";
|
|
||||||
import "./INativeOrdersFeature.sol";
|
|
||||||
|
|
||||||
/// @dev MetaTransactions feature.
|
/// @dev MetaTransactions feature.
|
||||||
contract MetaTransactionsFeature is
|
contract MetaTransactionsFeature is
|
||||||
|
805
contracts/zero-ex/contracts/src/features/MultiplexFeature.sol
Normal file
805
contracts/zero-ex/contracts/src/features/MultiplexFeature.sol
Normal file
@ -0,0 +1,805 @@
|
|||||||
|
// 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 "./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, 0, 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_,
|
||||||
|
bytes32 greedyTokensBloomFilter
|
||||||
|
)
|
||||||
|
public
|
||||||
|
FixinEIP712(zeroExAddress)
|
||||||
|
FixinTokenSpender(greedyTokensBloomFilter)
|
||||||
|
{
|
||||||
|
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 == 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.
|
||||||
|
_transferERC20Tokens(
|
||||||
|
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 {
|
||||||
|
_transferERC20Tokens(
|
||||||
|
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.
|
||||||
|
_transferERC20Tokens(
|
||||||
|
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);
|
||||||
|
_transferERC20Tokens(
|
||||||
|
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
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -26,8 +26,8 @@ import "../errors/LibOwnableRichErrors.sol";
|
|||||||
import "../storage/LibOwnableStorage.sol";
|
import "../storage/LibOwnableStorage.sol";
|
||||||
import "../migrations/LibBootstrap.sol";
|
import "../migrations/LibBootstrap.sol";
|
||||||
import "../migrations/LibMigrate.sol";
|
import "../migrations/LibMigrate.sol";
|
||||||
import "./IFeature.sol";
|
import "./interfaces/IFeature.sol";
|
||||||
import "./IOwnableFeature.sol";
|
import "./interfaces/IOwnableFeature.sol";
|
||||||
import "./SimpleFunctionRegistryFeature.sol";
|
import "./SimpleFunctionRegistryFeature.sol";
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,8 +26,8 @@ import "../storage/LibProxyStorage.sol";
|
|||||||
import "../storage/LibSimpleFunctionRegistryStorage.sol";
|
import "../storage/LibSimpleFunctionRegistryStorage.sol";
|
||||||
import "../errors/LibSimpleFunctionRegistryRichErrors.sol";
|
import "../errors/LibSimpleFunctionRegistryRichErrors.sol";
|
||||||
import "../migrations/LibBootstrap.sol";
|
import "../migrations/LibBootstrap.sol";
|
||||||
import "./IFeature.sol";
|
import "./interfaces/IFeature.sol";
|
||||||
import "./ISimpleFunctionRegistryFeature.sol";
|
import "./interfaces/ISimpleFunctionRegistryFeature.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @dev Basic registry management features.
|
/// @dev Basic registry management features.
|
||||||
|
@ -29,8 +29,8 @@ import "../fixins/FixinCommon.sol";
|
|||||||
import "../migrations/LibMigrate.sol";
|
import "../migrations/LibMigrate.sol";
|
||||||
import "../external/IAllowanceTarget.sol";
|
import "../external/IAllowanceTarget.sol";
|
||||||
import "../storage/LibTokenSpenderStorage.sol";
|
import "../storage/LibTokenSpenderStorage.sol";
|
||||||
import "./ITokenSpenderFeature.sol";
|
import "./interfaces/IFeature.sol";
|
||||||
import "./IFeature.sol";
|
import "./interfaces/ITokenSpenderFeature.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @dev Feature that allows spending token allowances.
|
/// @dev Feature that allows spending token allowances.
|
||||||
|
@ -33,8 +33,8 @@ import "../external/FlashWallet.sol";
|
|||||||
import "../storage/LibTransformERC20Storage.sol";
|
import "../storage/LibTransformERC20Storage.sol";
|
||||||
import "../transformers/IERC20Transformer.sol";
|
import "../transformers/IERC20Transformer.sol";
|
||||||
import "../transformers/LibERC20Transformer.sol";
|
import "../transformers/LibERC20Transformer.sol";
|
||||||
import "./ITransformERC20Feature.sol";
|
import "./interfaces/IFeature.sol";
|
||||||
import "./IFeature.sol";
|
import "./interfaces/ITransformERC20Feature.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @dev Feature to composably transform between ERC20 tokens.
|
/// @dev Feature to composably transform between ERC20 tokens.
|
||||||
@ -313,7 +313,7 @@ contract TransformERC20Feature is
|
|||||||
to.transfer(msg.value);
|
to.transfer(msg.value);
|
||||||
}
|
}
|
||||||
// Transfer input tokens.
|
// Transfer input tokens.
|
||||||
if (!LibERC20Transformer.isTokenETH(inputToken)) {
|
if (!LibERC20Transformer.isTokenETH(inputToken) && amount != 0) {
|
||||||
// Token is not ETH, so pull ERC20 tokens.
|
// Token is not ETH, so pull ERC20 tokens.
|
||||||
_transferERC20Tokens(
|
_transferERC20Tokens(
|
||||||
inputToken,
|
inputToken,
|
||||||
|
@ -25,8 +25,8 @@ import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
|||||||
import "../migrations/LibMigrate.sol";
|
import "../migrations/LibMigrate.sol";
|
||||||
import "../external/IAllowanceTarget.sol";
|
import "../external/IAllowanceTarget.sol";
|
||||||
import "../fixins/FixinCommon.sol";
|
import "../fixins/FixinCommon.sol";
|
||||||
import "./IFeature.sol";
|
import "./interfaces/IFeature.sol";
|
||||||
import "./IUniswapFeature.sol";
|
import "./interfaces/IUniswapFeature.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @dev VIP uniswap fill functions.
|
/// @dev VIP uniswap fill functions.
|
||||||
@ -380,7 +380,7 @@ contract UniswapFeature is
|
|||||||
// will eat all our gas.
|
// will eat all our gas.
|
||||||
if isTokenPossiblyGreedy(token) {
|
if isTokenPossiblyGreedy(token) {
|
||||||
// Check if we have enough direct allowance by calling
|
// Check if we have enough direct allowance by calling
|
||||||
// `token.allowance()``
|
// `token.allowance()`
|
||||||
mstore(0xB00, ALLOWANCE_CALL_SELECTOR_32)
|
mstore(0xB00, ALLOWANCE_CALL_SELECTOR_32)
|
||||||
mstore(0xB04, caller())
|
mstore(0xB04, caller())
|
||||||
mstore(0xB24, address())
|
mstore(0xB24, address())
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
// 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 "../libs/LibNativeOrder.sol";
|
||||||
|
import "../libs/LibSignature.sol";
|
||||||
|
|
||||||
|
|
||||||
|
/// @dev Feature for batch/market filling limit and RFQ orders.
|
||||||
|
interface IBatchFillNativeOrdersFeature {
|
||||||
|
|
||||||
|
/// @dev Fills multiple limit orders.
|
||||||
|
/// @param orders Array of limit orders.
|
||||||
|
/// @param signatures Array of signatures corresponding to each order.
|
||||||
|
/// @param takerTokenFillAmounts Array of desired amounts to fill each order.
|
||||||
|
/// @param revertIfIncomplete If true, reverts if this function fails to
|
||||||
|
/// fill the full fill amount for any individual order.
|
||||||
|
/// @return takerTokenFilledAmounts Array of amounts filled, in taker token.
|
||||||
|
/// @return makerTokenFilledAmounts Array of amounts filled, in maker token.
|
||||||
|
function batchFillLimitOrders(
|
||||||
|
LibNativeOrder.LimitOrder[] calldata orders,
|
||||||
|
LibSignature.Signature[] calldata signatures,
|
||||||
|
uint128[] calldata takerTokenFillAmounts,
|
||||||
|
bool revertIfIncomplete
|
||||||
|
)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
returns (
|
||||||
|
uint128[] memory takerTokenFilledAmounts,
|
||||||
|
uint128[] memory makerTokenFilledAmounts
|
||||||
|
);
|
||||||
|
|
||||||
|
/// @dev Fills multiple RFQ orders.
|
||||||
|
/// @param orders Array of RFQ orders.
|
||||||
|
/// @param signatures Array of signatures corresponding to each order.
|
||||||
|
/// @param takerTokenFillAmounts Array of desired amounts to fill each order.
|
||||||
|
/// @param revertIfIncomplete If true, reverts if this function fails to
|
||||||
|
/// fill the full fill amount for any individual order.
|
||||||
|
/// @return takerTokenFilledAmounts Array of amounts filled, in taker token.
|
||||||
|
/// @return makerTokenFilledAmounts Array of amounts filled, in maker token.
|
||||||
|
function batchFillRfqOrders(
|
||||||
|
LibNativeOrder.RfqOrder[] calldata orders,
|
||||||
|
LibSignature.Signature[] calldata signatures,
|
||||||
|
uint128[] calldata takerTokenFillAmounts,
|
||||||
|
bool revertIfIncomplete
|
||||||
|
)
|
||||||
|
external
|
||||||
|
returns (
|
||||||
|
uint128[] memory takerTokenFilledAmounts,
|
||||||
|
uint128[] memory makerTokenFilledAmounts
|
||||||
|
);
|
||||||
|
}
|
@ -21,7 +21,7 @@ pragma solidity ^0.6.5;
|
|||||||
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 "../vendor/ILiquidityProvider.sol";
|
import "../../vendor/ILiquidityProvider.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @dev Feature to swap directly with an on-chain liquidity provider.
|
/// @dev Feature to swap directly with an on-chain liquidity provider.
|
@ -21,7 +21,7 @@ pragma solidity ^0.6.5;
|
|||||||
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 "./libs/LibSignature.sol";
|
import "../libs/LibSignature.sol";
|
||||||
|
|
||||||
/// @dev Meta-transactions feature.
|
/// @dev Meta-transactions feature.
|
||||||
interface IMetaTransactionsFeature {
|
interface IMetaTransactionsFeature {
|
@ -0,0 +1,117 @@
|
|||||||
|
// 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";
|
||||||
|
|
||||||
|
|
||||||
|
interface IMultiplexFeature {
|
||||||
|
|
||||||
|
// Parameters for `batchFill`.
|
||||||
|
struct BatchFillData {
|
||||||
|
// The token being sold.
|
||||||
|
IERC20TokenV06 inputToken;
|
||||||
|
// The token being bought.
|
||||||
|
IERC20TokenV06 outputToken;
|
||||||
|
// The amount of `inputToken` to sell.
|
||||||
|
uint256 sellAmount;
|
||||||
|
// The nested calls to perform.
|
||||||
|
WrappedBatchCall[] calls;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Represents a call nested within a `batchFill`.
|
||||||
|
struct WrappedBatchCall {
|
||||||
|
// The selector of the function to call.
|
||||||
|
bytes4 selector;
|
||||||
|
// Amount of `inputToken` to sell.
|
||||||
|
uint256 sellAmount;
|
||||||
|
// ABI-encoded parameters needed to perform the call.
|
||||||
|
bytes data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameters for `multiHopFill`.
|
||||||
|
struct MultiHopFillData {
|
||||||
|
// The sell path, i.e.
|
||||||
|
// tokens = [inputToken, hopToken1, ..., hopTokenN, outputToken]
|
||||||
|
address[] tokens;
|
||||||
|
// The amount of `tokens[0]` to sell.
|
||||||
|
uint256 sellAmount;
|
||||||
|
// The nested calls to perform.
|
||||||
|
WrappedMultiHopCall[] calls;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Represents a call nested within a `multiHopFill`.
|
||||||
|
struct WrappedMultiHopCall {
|
||||||
|
// The selector of the function to call.
|
||||||
|
bytes4 selector;
|
||||||
|
// ABI-encoded parameters needed to perform the call.
|
||||||
|
bytes data;
|
||||||
|
}
|
||||||
|
|
||||||
|
event LiquidityProviderSwap(
|
||||||
|
address inputToken,
|
||||||
|
address outputToken,
|
||||||
|
uint256 inputTokenAmount,
|
||||||
|
uint256 outputTokenAmount,
|
||||||
|
address provider,
|
||||||
|
address recipient
|
||||||
|
);
|
||||||
|
|
||||||
|
event ExpiredRfqOrder(
|
||||||
|
bytes32 orderHash,
|
||||||
|
address maker,
|
||||||
|
uint64 expiry
|
||||||
|
);
|
||||||
|
|
||||||
|
/// @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 calldata fillData,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
returns (uint256 outputTokenAmount);
|
||||||
|
|
||||||
|
/// @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 calldata fillData,
|
||||||
|
uint256 minBuyAmount
|
||||||
|
)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
returns (uint256 outputTokenAmount);
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
// 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 "../libs/LibSignature.sol";
|
||||||
|
import "../libs/LibNativeOrder.sol";
|
||||||
|
|
||||||
|
|
||||||
|
/// @dev Events emitted by NativeOrdersFeature.
|
||||||
|
interface INativeOrdersEvents {
|
||||||
|
|
||||||
|
/// @dev Emitted whenever a `LimitOrder` is filled.
|
||||||
|
/// @param orderHash The canonical hash of the order.
|
||||||
|
/// @param maker The maker of the order.
|
||||||
|
/// @param taker The taker of the order.
|
||||||
|
/// @param feeRecipient Fee recipient of the order.
|
||||||
|
/// @param takerTokenFilledAmount How much taker token was filled.
|
||||||
|
/// @param makerTokenFilledAmount How much maker token was filled.
|
||||||
|
/// @param protocolFeePaid How much protocol fee was paid.
|
||||||
|
/// @param pool The fee pool associated with this order.
|
||||||
|
event LimitOrderFilled(
|
||||||
|
bytes32 orderHash,
|
||||||
|
address maker,
|
||||||
|
address taker,
|
||||||
|
address feeRecipient,
|
||||||
|
address makerToken,
|
||||||
|
address takerToken,
|
||||||
|
uint128 takerTokenFilledAmount,
|
||||||
|
uint128 makerTokenFilledAmount,
|
||||||
|
uint128 takerTokenFeeFilledAmount,
|
||||||
|
uint256 protocolFeePaid,
|
||||||
|
bytes32 pool
|
||||||
|
);
|
||||||
|
|
||||||
|
/// @dev Emitted whenever an `RfqOrder` is filled.
|
||||||
|
/// @param orderHash The canonical hash of the order.
|
||||||
|
/// @param maker The maker 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 pool The fee pool associated with this order.
|
||||||
|
event RfqOrderFilled(
|
||||||
|
bytes32 orderHash,
|
||||||
|
address maker,
|
||||||
|
address taker,
|
||||||
|
address makerToken,
|
||||||
|
address takerToken,
|
||||||
|
uint128 takerTokenFilledAmount,
|
||||||
|
uint128 makerTokenFilledAmount,
|
||||||
|
bytes32 pool
|
||||||
|
);
|
||||||
|
|
||||||
|
/// @dev Emitted whenever a limit or RFQ order is cancelled.
|
||||||
|
/// @param orderHash The canonical hash of the order.
|
||||||
|
/// @param maker The order maker.
|
||||||
|
event OrderCancelled(
|
||||||
|
bytes32 orderHash,
|
||||||
|
address maker
|
||||||
|
);
|
||||||
|
|
||||||
|
/// @dev Emitted whenever Limit orders are cancelled by pair by a maker.
|
||||||
|
/// @param maker The maker of the order.
|
||||||
|
/// @param makerToken The maker token in a pair for the orders cancelled.
|
||||||
|
/// @param takerToken The taker token in a pair for the orders cancelled.
|
||||||
|
/// @param minValidSalt The new minimum valid salt an order with this pair must
|
||||||
|
/// have.
|
||||||
|
event PairCancelledLimitOrders(
|
||||||
|
address maker,
|
||||||
|
address makerToken,
|
||||||
|
address takerToken,
|
||||||
|
uint256 minValidSalt
|
||||||
|
);
|
||||||
|
|
||||||
|
/// @dev Emitted whenever RFQ orders are cancelled by pair by a maker.
|
||||||
|
/// @param maker The maker of the order.
|
||||||
|
/// @param makerToken The maker token in a pair for the orders cancelled.
|
||||||
|
/// @param takerToken The taker token in a pair for the orders cancelled.
|
||||||
|
/// @param minValidSalt The new minimum valid salt an order with this pair must
|
||||||
|
/// have.
|
||||||
|
event PairCancelledRfqOrders(
|
||||||
|
address maker,
|
||||||
|
address makerToken,
|
||||||
|
address takerToken,
|
||||||
|
uint256 minValidSalt
|
||||||
|
);
|
||||||
|
|
||||||
|
/// @dev Emitted when new addresses are allowed or disallowed to fill
|
||||||
|
/// orders with a given txOrigin.
|
||||||
|
/// @param origin The address doing the allowing.
|
||||||
|
/// @param addrs The address being allowed/disallowed.
|
||||||
|
/// @param allowed Indicates whether the address should be allowed.
|
||||||
|
event RfqOrderOriginsAllowed(
|
||||||
|
address origin,
|
||||||
|
address[] addrs,
|
||||||
|
bool allowed
|
||||||
|
);
|
||||||
|
}
|
@ -21,98 +21,15 @@ pragma solidity ^0.6.5;
|
|||||||
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 "./libs/LibSignature.sol";
|
import "../libs/LibSignature.sol";
|
||||||
import "./libs/LibNativeOrder.sol";
|
import "../libs/LibNativeOrder.sol";
|
||||||
|
import "./INativeOrdersEvents.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @dev Feature for interacting with limit orders.
|
/// @dev Feature for interacting with limit orders.
|
||||||
interface INativeOrdersFeature {
|
interface INativeOrdersFeature is
|
||||||
|
INativeOrdersEvents
|
||||||
/// @dev Emitted whenever a `LimitOrder` is filled.
|
{
|
||||||
/// @param orderHash The canonical hash of the order.
|
|
||||||
/// @param maker The maker of the order.
|
|
||||||
/// @param taker The taker of the order.
|
|
||||||
/// @param feeRecipient Fee recipient of the order.
|
|
||||||
/// @param takerTokenFilledAmount How much taker token was filled.
|
|
||||||
/// @param makerTokenFilledAmount How much maker token was filled.
|
|
||||||
/// @param protocolFeePaid How much protocol fee was paid.
|
|
||||||
/// @param pool The fee pool associated with this order.
|
|
||||||
event LimitOrderFilled(
|
|
||||||
bytes32 orderHash,
|
|
||||||
address maker,
|
|
||||||
address taker,
|
|
||||||
address feeRecipient,
|
|
||||||
address makerToken,
|
|
||||||
address takerToken,
|
|
||||||
uint128 takerTokenFilledAmount,
|
|
||||||
uint128 makerTokenFilledAmount,
|
|
||||||
uint128 takerTokenFeeFilledAmount,
|
|
||||||
uint256 protocolFeePaid,
|
|
||||||
bytes32 pool
|
|
||||||
);
|
|
||||||
|
|
||||||
/// @dev Emitted whenever an `RfqOrder` is filled.
|
|
||||||
/// @param orderHash The canonical hash of the order.
|
|
||||||
/// @param maker The maker 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 pool The fee pool associated with this order.
|
|
||||||
event RfqOrderFilled(
|
|
||||||
bytes32 orderHash,
|
|
||||||
address maker,
|
|
||||||
address taker,
|
|
||||||
address makerToken,
|
|
||||||
address takerToken,
|
|
||||||
uint128 takerTokenFilledAmount,
|
|
||||||
uint128 makerTokenFilledAmount,
|
|
||||||
bytes32 pool
|
|
||||||
);
|
|
||||||
|
|
||||||
/// @dev Emitted whenever a limit or RFQ order is cancelled.
|
|
||||||
/// @param orderHash The canonical hash of the order.
|
|
||||||
/// @param maker The order maker.
|
|
||||||
event OrderCancelled(
|
|
||||||
bytes32 orderHash,
|
|
||||||
address maker
|
|
||||||
);
|
|
||||||
|
|
||||||
/// @dev Emitted whenever Limit orders are cancelled by pair by a maker.
|
|
||||||
/// @param maker The maker of the order.
|
|
||||||
/// @param makerToken The maker token in a pair for the orders cancelled.
|
|
||||||
/// @param takerToken The taker token in a pair for the orders cancelled.
|
|
||||||
/// @param minValidSalt The new minimum valid salt an order with this pair must
|
|
||||||
/// have.
|
|
||||||
event PairCancelledLimitOrders(
|
|
||||||
address maker,
|
|
||||||
address makerToken,
|
|
||||||
address takerToken,
|
|
||||||
uint256 minValidSalt
|
|
||||||
);
|
|
||||||
|
|
||||||
/// @dev Emitted whenever RFQ orders are cancelled by pair by a maker.
|
|
||||||
/// @param maker The maker of the order.
|
|
||||||
/// @param makerToken The maker token in a pair for the orders cancelled.
|
|
||||||
/// @param takerToken The taker token in a pair for the orders cancelled.
|
|
||||||
/// @param minValidSalt The new minimum valid salt an order with this pair must
|
|
||||||
/// have.
|
|
||||||
event PairCancelledRfqOrders(
|
|
||||||
address maker,
|
|
||||||
address makerToken,
|
|
||||||
address takerToken,
|
|
||||||
uint256 minValidSalt
|
|
||||||
);
|
|
||||||
|
|
||||||
/// @dev Emitted when new addresses are allowed or disallowed to fill
|
|
||||||
/// orders with a given txOrigin.
|
|
||||||
/// @param origin The address doing the allowing.
|
|
||||||
/// @param addrs The address being allowed/disallowed.
|
|
||||||
/// @param allowed Indicates whether the address should be allowed.
|
|
||||||
event RfqOrderOriginsAllowed(
|
|
||||||
address origin,
|
|
||||||
address[] addrs,
|
|
||||||
bool allowed
|
|
||||||
);
|
|
||||||
|
|
||||||
/// @dev Transfers protocol fees from the `FeeCollector` pools into
|
/// @dev Transfers protocol fees from the `FeeCollector` pools into
|
||||||
/// the staking contract.
|
/// the staking contract.
|
@ -21,8 +21,8 @@ pragma solidity ^0.6.5;
|
|||||||
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 "../transformers/IERC20Transformer.sol";
|
import "../../transformers/IERC20Transformer.sol";
|
||||||
import "../external/IFlashWallet.sol";
|
import "../../external/IFlashWallet.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @dev Feature to composably transform between ERC20 tokens.
|
/// @dev Feature to composably transform between ERC20 tokens.
|
@ -21,10 +21,15 @@ pragma solidity ^0.6.5;
|
|||||||
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-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
|
import "../../errors/LibNativeOrdersRichErrors.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @dev A library for common native order operations.
|
/// @dev A library for common native order operations.
|
||||||
library LibNativeOrder {
|
library LibNativeOrder {
|
||||||
|
using LibSafeMathV06 for uint256;
|
||||||
|
using LibRichErrorsV06 for bytes;
|
||||||
|
|
||||||
enum OrderStatus {
|
enum OrderStatus {
|
||||||
INVALID,
|
INVALID,
|
||||||
@ -216,4 +221,23 @@ library LibNativeOrder {
|
|||||||
structHash := keccak256(mem, 0x160)
|
structHash := keccak256(mem, 0x160)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @dev Refund any leftover protocol fees in `msg.value` to `msg.sender`.
|
||||||
|
/// @param ethProtocolFeePaid How much ETH was paid in protocol fees.
|
||||||
|
function refundExcessProtocolFeeToSender(uint256 ethProtocolFeePaid)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
if (msg.value > ethProtocolFeePaid && msg.sender != address(this)) {
|
||||||
|
uint256 refundAmount = msg.value.safeSub(ethProtocolFeePaid);
|
||||||
|
(bool success,) = msg
|
||||||
|
.sender
|
||||||
|
.call{value: refundAmount}("");
|
||||||
|
if (!success) {
|
||||||
|
LibNativeOrdersRichErrors.ProtocolFeeRefundFailed(
|
||||||
|
msg.sender,
|
||||||
|
refundAmount
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,266 @@
|
|||||||
|
// 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/errors/LibRichErrorsV06.sol";
|
||||||
|
import "../../errors/LibNativeOrdersRichErrors.sol";
|
||||||
|
import "../../storage/LibNativeOrdersStorage.sol";
|
||||||
|
import "../interfaces/INativeOrdersEvents.sol";
|
||||||
|
import "../libs/LibSignature.sol";
|
||||||
|
import "../libs/LibNativeOrder.sol";
|
||||||
|
import "./NativeOrdersInfo.sol";
|
||||||
|
|
||||||
|
/// @dev Feature for cancelling limit and RFQ orders.
|
||||||
|
abstract contract NativeOrdersCancellation is
|
||||||
|
INativeOrdersEvents,
|
||||||
|
NativeOrdersInfo
|
||||||
|
{
|
||||||
|
using LibRichErrorsV06 for bytes;
|
||||||
|
|
||||||
|
/// @dev Highest bit of a uint256, used to flag cancelled orders.
|
||||||
|
uint256 private constant HIGH_BIT = 1 << 255;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
address zeroExAddress,
|
||||||
|
bytes32 greedyTokensBloomFilter
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
NativeOrdersInfo(zeroExAddress, greedyTokensBloomFilter)
|
||||||
|
{
|
||||||
|
// solhint-disable no-empty-blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Cancel a single limit order. The caller must be the maker.
|
||||||
|
/// Silently succeeds if the order has already been cancelled.
|
||||||
|
/// @param order The limit order.
|
||||||
|
function cancelLimitOrder(LibNativeOrder.LimitOrder memory order)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
bytes32 orderHash = getLimitOrderHash(order);
|
||||||
|
if (msg.sender != order.maker) {
|
||||||
|
LibNativeOrdersRichErrors.OnlyOrderMakerAllowed(
|
||||||
|
orderHash,
|
||||||
|
msg.sender,
|
||||||
|
order.maker
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
_cancelOrderHash(orderHash, order.maker);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Cancel a single RFQ order. The caller must be the maker.
|
||||||
|
/// Silently succeeds if the order has already been cancelled.
|
||||||
|
/// @param order The RFQ order.
|
||||||
|
function cancelRfqOrder(LibNativeOrder.RfqOrder memory order)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
bytes32 orderHash = getRfqOrderHash(order);
|
||||||
|
if (msg.sender != order.maker) {
|
||||||
|
LibNativeOrdersRichErrors.OnlyOrderMakerAllowed(
|
||||||
|
orderHash,
|
||||||
|
msg.sender,
|
||||||
|
order.maker
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
_cancelOrderHash(orderHash, order.maker);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Cancel multiple limit orders. The caller must be the maker.
|
||||||
|
/// Silently succeeds if the order has already been cancelled.
|
||||||
|
/// @param orders The limit orders.
|
||||||
|
function batchCancelLimitOrders(LibNativeOrder.LimitOrder[] memory orders)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
for (uint256 i = 0; i < orders.length; ++i) {
|
||||||
|
cancelLimitOrder(orders[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Cancel multiple RFQ orders. The caller must be the maker.
|
||||||
|
/// Silently succeeds if the order has already been cancelled.
|
||||||
|
/// @param orders The RFQ orders.
|
||||||
|
function batchCancelRfqOrders(LibNativeOrder.RfqOrder[] memory orders)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
for (uint256 i = 0; i < orders.length; ++i) {
|
||||||
|
cancelRfqOrder(orders[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Cancel all limit orders for a given maker and pair with a salt less
|
||||||
|
/// than the value provided. The caller must be the maker. Subsequent
|
||||||
|
/// calls to this function with the same caller and pair require the
|
||||||
|
/// new salt to be >= the old salt.
|
||||||
|
/// @param makerToken The maker token.
|
||||||
|
/// @param takerToken The taker token.
|
||||||
|
/// @param minValidSalt The new minimum valid salt.
|
||||||
|
function cancelPairLimitOrders(
|
||||||
|
IERC20TokenV06 makerToken,
|
||||||
|
IERC20TokenV06 takerToken,
|
||||||
|
uint256 minValidSalt
|
||||||
|
)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
LibNativeOrdersStorage.Storage storage stor =
|
||||||
|
LibNativeOrdersStorage.getStorage();
|
||||||
|
|
||||||
|
uint256 oldMinValidSalt =
|
||||||
|
stor.limitOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt
|
||||||
|
[msg.sender]
|
||||||
|
[address(makerToken)]
|
||||||
|
[address(takerToken)];
|
||||||
|
|
||||||
|
// New min salt must >= the old one.
|
||||||
|
if (oldMinValidSalt > minValidSalt) {
|
||||||
|
LibNativeOrdersRichErrors.
|
||||||
|
CancelSaltTooLowError(minValidSalt, oldMinValidSalt)
|
||||||
|
.rrevert();
|
||||||
|
}
|
||||||
|
|
||||||
|
stor.limitOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt
|
||||||
|
[msg.sender]
|
||||||
|
[address(makerToken)]
|
||||||
|
[address(takerToken)] = minValidSalt;
|
||||||
|
|
||||||
|
emit PairCancelledLimitOrders(
|
||||||
|
msg.sender,
|
||||||
|
address(makerToken),
|
||||||
|
address(takerToken),
|
||||||
|
minValidSalt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Cancel all limit orders for a given maker and pair with a salt less
|
||||||
|
/// than the value provided. The caller must be the maker. Subsequent
|
||||||
|
/// calls to this function with the same caller and pair require the
|
||||||
|
/// new salt to be >= the old salt.
|
||||||
|
/// @param makerTokens The maker tokens.
|
||||||
|
/// @param takerTokens The taker tokens.
|
||||||
|
/// @param minValidSalts The new minimum valid salts.
|
||||||
|
function batchCancelPairLimitOrders(
|
||||||
|
IERC20TokenV06[] memory makerTokens,
|
||||||
|
IERC20TokenV06[] memory takerTokens,
|
||||||
|
uint256[] memory minValidSalts
|
||||||
|
)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
require(
|
||||||
|
makerTokens.length == takerTokens.length &&
|
||||||
|
makerTokens.length == minValidSalts.length,
|
||||||
|
"NativeOrdersFeature/MISMATCHED_PAIR_ORDERS_ARRAY_LENGTHS"
|
||||||
|
);
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < makerTokens.length; ++i) {
|
||||||
|
cancelPairLimitOrders(
|
||||||
|
makerTokens[i],
|
||||||
|
takerTokens[i],
|
||||||
|
minValidSalts[i]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Cancel all RFQ orders for a given maker and pair with a salt less
|
||||||
|
/// than the value provided. The caller must be the maker. Subsequent
|
||||||
|
/// calls to this function with the same caller and pair require the
|
||||||
|
/// new salt to be >= the old salt.
|
||||||
|
/// @param makerToken The maker token.
|
||||||
|
/// @param takerToken The taker token.
|
||||||
|
/// @param minValidSalt The new minimum valid salt.
|
||||||
|
function cancelPairRfqOrders(
|
||||||
|
IERC20TokenV06 makerToken,
|
||||||
|
IERC20TokenV06 takerToken,
|
||||||
|
uint256 minValidSalt
|
||||||
|
)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
LibNativeOrdersStorage.Storage storage stor =
|
||||||
|
LibNativeOrdersStorage.getStorage();
|
||||||
|
|
||||||
|
uint256 oldMinValidSalt =
|
||||||
|
stor.rfqOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt
|
||||||
|
[msg.sender]
|
||||||
|
[address(makerToken)]
|
||||||
|
[address(takerToken)];
|
||||||
|
|
||||||
|
// New min salt must >= the old one.
|
||||||
|
if (oldMinValidSalt > minValidSalt) {
|
||||||
|
LibNativeOrdersRichErrors.
|
||||||
|
CancelSaltTooLowError(minValidSalt, oldMinValidSalt)
|
||||||
|
.rrevert();
|
||||||
|
}
|
||||||
|
|
||||||
|
stor.rfqOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt
|
||||||
|
[msg.sender]
|
||||||
|
[address(makerToken)]
|
||||||
|
[address(takerToken)] = minValidSalt;
|
||||||
|
|
||||||
|
emit PairCancelledRfqOrders(
|
||||||
|
msg.sender,
|
||||||
|
address(makerToken),
|
||||||
|
address(takerToken),
|
||||||
|
minValidSalt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Cancel all RFQ orders for a given maker and pair with a salt less
|
||||||
|
/// than the value provided. The caller must be the maker. Subsequent
|
||||||
|
/// calls to this function with the same caller and pair require the
|
||||||
|
/// new salt to be >= the old salt.
|
||||||
|
/// @param makerTokens The maker tokens.
|
||||||
|
/// @param takerTokens The taker tokens.
|
||||||
|
/// @param minValidSalts The new minimum valid salts.
|
||||||
|
function batchCancelPairRfqOrders(
|
||||||
|
IERC20TokenV06[] memory makerTokens,
|
||||||
|
IERC20TokenV06[] memory takerTokens,
|
||||||
|
uint256[] memory minValidSalts
|
||||||
|
)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
require(
|
||||||
|
makerTokens.length == takerTokens.length &&
|
||||||
|
makerTokens.length == minValidSalts.length,
|
||||||
|
"NativeOrdersFeature/MISMATCHED_PAIR_ORDERS_ARRAY_LENGTHS"
|
||||||
|
);
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < makerTokens.length; ++i) {
|
||||||
|
cancelPairRfqOrders(
|
||||||
|
makerTokens[i],
|
||||||
|
takerTokens[i],
|
||||||
|
minValidSalts[i]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Cancel a limit or RFQ order directly by its order hash.
|
||||||
|
/// @param orderHash The order's order hash.
|
||||||
|
/// @param maker The order's maker.
|
||||||
|
function _cancelOrderHash(bytes32 orderHash, address maker)
|
||||||
|
private
|
||||||
|
{
|
||||||
|
LibNativeOrdersStorage.Storage storage stor =
|
||||||
|
LibNativeOrdersStorage.getStorage();
|
||||||
|
// Set the high bit on the raw taker token fill amount to indicate
|
||||||
|
// a cancel. It's OK to cancel twice.
|
||||||
|
stor.orderHashToTakerTokenFilledAmount[orderHash] |= HIGH_BIT;
|
||||||
|
|
||||||
|
emit OrderCancelled(orderHash, maker);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,394 @@
|
|||||||
|
// 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 "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
||||||
|
import "../../fixins/FixinEIP712.sol";
|
||||||
|
import "../../fixins/FixinTokenSpender.sol";
|
||||||
|
import "../../storage/LibNativeOrdersStorage.sol";
|
||||||
|
import "../libs/LibSignature.sol";
|
||||||
|
import "../libs/LibNativeOrder.sol";
|
||||||
|
|
||||||
|
|
||||||
|
/// @dev Feature for getting info about limit and RFQ orders.
|
||||||
|
abstract contract NativeOrdersInfo is
|
||||||
|
FixinEIP712,
|
||||||
|
FixinTokenSpender
|
||||||
|
{
|
||||||
|
using LibSafeMathV06 for uint256;
|
||||||
|
using LibRichErrorsV06 for bytes;
|
||||||
|
|
||||||
|
// @dev Params for `_getActualFillableTakerTokenAmount()`.
|
||||||
|
struct GetActualFillableTakerTokenAmountParams {
|
||||||
|
address maker;
|
||||||
|
IERC20TokenV06 makerToken;
|
||||||
|
uint128 orderMakerAmount;
|
||||||
|
uint128 orderTakerAmount;
|
||||||
|
LibNativeOrder.OrderInfo orderInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Highest bit of a uint256, used to flag cancelled orders.
|
||||||
|
uint256 private constant HIGH_BIT = 1 << 255;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
address zeroExAddress,
|
||||||
|
bytes32 greedyTokensBloomFilter
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
FixinEIP712(zeroExAddress)
|
||||||
|
FixinTokenSpender(greedyTokensBloomFilter)
|
||||||
|
{
|
||||||
|
// solhint-disable no-empty-blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Get the order info for a limit order.
|
||||||
|
/// @param order The limit order.
|
||||||
|
/// @return orderInfo Info about the order.
|
||||||
|
function getLimitOrderInfo(LibNativeOrder.LimitOrder memory order)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
returns (LibNativeOrder.OrderInfo memory orderInfo)
|
||||||
|
{
|
||||||
|
// Recover maker and compute order hash.
|
||||||
|
orderInfo.orderHash = getLimitOrderHash(order);
|
||||||
|
uint256 minValidSalt = LibNativeOrdersStorage.getStorage()
|
||||||
|
.limitOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt
|
||||||
|
[order.maker]
|
||||||
|
[address(order.makerToken)]
|
||||||
|
[address(order.takerToken)];
|
||||||
|
_populateCommonOrderInfoFields(
|
||||||
|
orderInfo,
|
||||||
|
order.takerAmount,
|
||||||
|
order.expiry,
|
||||||
|
order.salt,
|
||||||
|
minValidSalt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Get the order info for an RFQ order.
|
||||||
|
/// @param order The RFQ order.
|
||||||
|
/// @return orderInfo Info about the order.
|
||||||
|
function getRfqOrderInfo(LibNativeOrder.RfqOrder memory order)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
returns (LibNativeOrder.OrderInfo memory orderInfo)
|
||||||
|
{
|
||||||
|
// Recover maker and compute order hash.
|
||||||
|
orderInfo.orderHash = getRfqOrderHash(order);
|
||||||
|
uint256 minValidSalt = LibNativeOrdersStorage.getStorage()
|
||||||
|
.rfqOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt
|
||||||
|
[order.maker]
|
||||||
|
[address(order.makerToken)]
|
||||||
|
[address(order.takerToken)];
|
||||||
|
_populateCommonOrderInfoFields(
|
||||||
|
orderInfo,
|
||||||
|
order.takerAmount,
|
||||||
|
order.expiry,
|
||||||
|
order.salt,
|
||||||
|
minValidSalt
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check for missing txOrigin.
|
||||||
|
if (order.txOrigin == address(0)) {
|
||||||
|
orderInfo.status = LibNativeOrder.OrderStatus.INVALID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Get the canonical hash of a limit order.
|
||||||
|
/// @param order The limit order.
|
||||||
|
/// @return orderHash The order hash.
|
||||||
|
function getLimitOrderHash(LibNativeOrder.LimitOrder memory order)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
returns (bytes32 orderHash)
|
||||||
|
{
|
||||||
|
return _getEIP712Hash(
|
||||||
|
LibNativeOrder.getLimitOrderStructHash(order)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Get the canonical hash of an RFQ order.
|
||||||
|
/// @param order The RFQ order.
|
||||||
|
/// @return orderHash The order hash.
|
||||||
|
function getRfqOrderHash(LibNativeOrder.RfqOrder memory order)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
returns (bytes32 orderHash)
|
||||||
|
{
|
||||||
|
return _getEIP712Hash(
|
||||||
|
LibNativeOrder.getRfqOrderStructHash(order)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Get order info, fillable amount, and signature validity for a limit order.
|
||||||
|
/// Fillable amount is determined using balances and allowances of the maker.
|
||||||
|
/// @param order The limit order.
|
||||||
|
/// @param signature The order signature.
|
||||||
|
/// @return orderInfo Info about the order.
|
||||||
|
/// @return actualFillableTakerTokenAmount How much of the order is fillable
|
||||||
|
/// based on maker funds, in taker tokens.
|
||||||
|
/// @return isSignatureValid Whether the signature is valid.
|
||||||
|
function getLimitOrderRelevantState(
|
||||||
|
LibNativeOrder.LimitOrder memory order,
|
||||||
|
LibSignature.Signature calldata signature
|
||||||
|
)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
returns (
|
||||||
|
LibNativeOrder.OrderInfo memory orderInfo,
|
||||||
|
uint128 actualFillableTakerTokenAmount,
|
||||||
|
bool isSignatureValid
|
||||||
|
)
|
||||||
|
{
|
||||||
|
orderInfo = getLimitOrderInfo(order);
|
||||||
|
actualFillableTakerTokenAmount = _getActualFillableTakerTokenAmount(
|
||||||
|
GetActualFillableTakerTokenAmountParams({
|
||||||
|
maker: order.maker,
|
||||||
|
makerToken: order.makerToken,
|
||||||
|
orderMakerAmount: order.makerAmount,
|
||||||
|
orderTakerAmount: order.takerAmount,
|
||||||
|
orderInfo: orderInfo
|
||||||
|
})
|
||||||
|
);
|
||||||
|
isSignatureValid = order.maker ==
|
||||||
|
LibSignature.getSignerOfHash(orderInfo.orderHash, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Get order info, fillable amount, and signature validity for an RFQ order.
|
||||||
|
/// Fillable amount is determined using balances and allowances of the maker.
|
||||||
|
/// @param order The RFQ order.
|
||||||
|
/// @param signature The order signature.
|
||||||
|
/// @return orderInfo Info about the order.
|
||||||
|
/// @return actualFillableTakerTokenAmount How much of the order is fillable
|
||||||
|
/// based on maker funds, in taker tokens.
|
||||||
|
/// @return isSignatureValid Whether the signature is valid.
|
||||||
|
function getRfqOrderRelevantState(
|
||||||
|
LibNativeOrder.RfqOrder memory order,
|
||||||
|
LibSignature.Signature memory signature
|
||||||
|
)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
returns (
|
||||||
|
LibNativeOrder.OrderInfo memory orderInfo,
|
||||||
|
uint128 actualFillableTakerTokenAmount,
|
||||||
|
bool isSignatureValid
|
||||||
|
)
|
||||||
|
{
|
||||||
|
orderInfo = getRfqOrderInfo(order);
|
||||||
|
actualFillableTakerTokenAmount = _getActualFillableTakerTokenAmount(
|
||||||
|
GetActualFillableTakerTokenAmountParams({
|
||||||
|
maker: order.maker,
|
||||||
|
makerToken: order.makerToken,
|
||||||
|
orderMakerAmount: order.makerAmount,
|
||||||
|
orderTakerAmount: order.takerAmount,
|
||||||
|
orderInfo: orderInfo
|
||||||
|
})
|
||||||
|
);
|
||||||
|
isSignatureValid = order.maker ==
|
||||||
|
LibSignature.getSignerOfHash(orderInfo.orderHash, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Batch version of `getLimitOrderRelevantState()`, without reverting.
|
||||||
|
/// Orders that would normally cause `getLimitOrderRelevantState()`
|
||||||
|
/// to revert will have empty results.
|
||||||
|
/// @param orders The limit orders.
|
||||||
|
/// @param signatures The order signatures.
|
||||||
|
/// @return orderInfos Info about the orders.
|
||||||
|
/// @return actualFillableTakerTokenAmounts How much of each order is fillable
|
||||||
|
/// based on maker funds, in taker tokens.
|
||||||
|
/// @return isSignatureValids Whether each signature is valid for the order.
|
||||||
|
function batchGetLimitOrderRelevantStates(
|
||||||
|
LibNativeOrder.LimitOrder[] calldata orders,
|
||||||
|
LibSignature.Signature[] calldata signatures
|
||||||
|
)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (
|
||||||
|
LibNativeOrder.OrderInfo[] memory orderInfos,
|
||||||
|
uint128[] memory actualFillableTakerTokenAmounts,
|
||||||
|
bool[] memory isSignatureValids
|
||||||
|
)
|
||||||
|
{
|
||||||
|
require(
|
||||||
|
orders.length == signatures.length,
|
||||||
|
"NativeOrdersFeature/MISMATCHED_ARRAY_LENGTHS"
|
||||||
|
);
|
||||||
|
orderInfos = new LibNativeOrder.OrderInfo[](orders.length);
|
||||||
|
actualFillableTakerTokenAmounts = new uint128[](orders.length);
|
||||||
|
isSignatureValids = new bool[](orders.length);
|
||||||
|
for (uint256 i = 0; i < orders.length; ++i) {
|
||||||
|
try
|
||||||
|
this.getLimitOrderRelevantState(orders[i], signatures[i])
|
||||||
|
returns (
|
||||||
|
LibNativeOrder.OrderInfo memory orderInfo,
|
||||||
|
uint128 actualFillableTakerTokenAmount,
|
||||||
|
bool isSignatureValid
|
||||||
|
)
|
||||||
|
{
|
||||||
|
orderInfos[i] = orderInfo;
|
||||||
|
actualFillableTakerTokenAmounts[i] = actualFillableTakerTokenAmount;
|
||||||
|
isSignatureValids[i] = isSignatureValid;
|
||||||
|
}
|
||||||
|
catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Batch version of `getRfqOrderRelevantState()`, without reverting.
|
||||||
|
/// Orders that would normally cause `getRfqOrderRelevantState()`
|
||||||
|
/// to revert will have empty results.
|
||||||
|
/// @param orders The RFQ orders.
|
||||||
|
/// @param signatures The order signatures.
|
||||||
|
/// @return orderInfos Info about the orders.
|
||||||
|
/// @return actualFillableTakerTokenAmounts How much of each order is fillable
|
||||||
|
/// based on maker funds, in taker tokens.
|
||||||
|
/// @return isSignatureValids Whether each signature is valid for the order.
|
||||||
|
function batchGetRfqOrderRelevantStates(
|
||||||
|
LibNativeOrder.RfqOrder[] calldata orders,
|
||||||
|
LibSignature.Signature[] calldata signatures
|
||||||
|
)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (
|
||||||
|
LibNativeOrder.OrderInfo[] memory orderInfos,
|
||||||
|
uint128[] memory actualFillableTakerTokenAmounts,
|
||||||
|
bool[] memory isSignatureValids
|
||||||
|
)
|
||||||
|
{
|
||||||
|
require(
|
||||||
|
orders.length == signatures.length,
|
||||||
|
"NativeOrdersFeature/MISMATCHED_ARRAY_LENGTHS"
|
||||||
|
);
|
||||||
|
orderInfos = new LibNativeOrder.OrderInfo[](orders.length);
|
||||||
|
actualFillableTakerTokenAmounts = new uint128[](orders.length);
|
||||||
|
isSignatureValids = new bool[](orders.length);
|
||||||
|
for (uint256 i = 0; i < orders.length; ++i) {
|
||||||
|
try
|
||||||
|
this.getRfqOrderRelevantState(orders[i], signatures[i])
|
||||||
|
returns (
|
||||||
|
LibNativeOrder.OrderInfo memory orderInfo,
|
||||||
|
uint128 actualFillableTakerTokenAmount,
|
||||||
|
bool isSignatureValid
|
||||||
|
)
|
||||||
|
{
|
||||||
|
orderInfos[i] = orderInfo;
|
||||||
|
actualFillableTakerTokenAmounts[i] = actualFillableTakerTokenAmount;
|
||||||
|
isSignatureValids[i] = isSignatureValid;
|
||||||
|
}
|
||||||
|
catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Populate `status` and `takerTokenFilledAmount` fields in
|
||||||
|
/// `orderInfo`, which use the same code path for both limit and
|
||||||
|
/// RFQ orders.
|
||||||
|
/// @param orderInfo `OrderInfo` with `orderHash` and `maker` filled.
|
||||||
|
/// @param takerAmount The order's taker token amount..
|
||||||
|
/// @param expiry The order's expiry.
|
||||||
|
/// @param salt The order's salt.
|
||||||
|
/// @param salt The minimum valid salt for the maker and pair combination.
|
||||||
|
function _populateCommonOrderInfoFields(
|
||||||
|
LibNativeOrder.OrderInfo memory orderInfo,
|
||||||
|
uint128 takerAmount,
|
||||||
|
uint64 expiry,
|
||||||
|
uint256 salt,
|
||||||
|
uint256 minValidSalt
|
||||||
|
)
|
||||||
|
private
|
||||||
|
view
|
||||||
|
{
|
||||||
|
LibNativeOrdersStorage.Storage storage stor =
|
||||||
|
LibNativeOrdersStorage.getStorage();
|
||||||
|
|
||||||
|
// Get the filled and direct cancel state.
|
||||||
|
{
|
||||||
|
// The high bit of the raw taker token filled amount will be set
|
||||||
|
// if the order was cancelled.
|
||||||
|
uint256 rawTakerTokenFilledAmount =
|
||||||
|
stor.orderHashToTakerTokenFilledAmount[orderInfo.orderHash];
|
||||||
|
orderInfo.takerTokenFilledAmount = uint128(rawTakerTokenFilledAmount);
|
||||||
|
if (orderInfo.takerTokenFilledAmount >= takerAmount) {
|
||||||
|
orderInfo.status = LibNativeOrder.OrderStatus.FILLED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (rawTakerTokenFilledAmount & HIGH_BIT != 0) {
|
||||||
|
orderInfo.status = LibNativeOrder.OrderStatus.CANCELLED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for expiration.
|
||||||
|
if (expiry <= uint64(block.timestamp)) {
|
||||||
|
orderInfo.status = LibNativeOrder.OrderStatus.EXPIRED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the order was cancelled by salt.
|
||||||
|
if (minValidSalt > salt) {
|
||||||
|
orderInfo.status = LibNativeOrder.OrderStatus.CANCELLED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
orderInfo.status = LibNativeOrder.OrderStatus.FILLABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Calculate the actual fillable taker token amount of an order
|
||||||
|
/// based on maker allowance and balances.
|
||||||
|
function _getActualFillableTakerTokenAmount(
|
||||||
|
GetActualFillableTakerTokenAmountParams memory params
|
||||||
|
)
|
||||||
|
private
|
||||||
|
view
|
||||||
|
returns (uint128 actualFillableTakerTokenAmount)
|
||||||
|
{
|
||||||
|
if (params.orderMakerAmount == 0 || params.orderTakerAmount == 0) {
|
||||||
|
// Empty order.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (params.orderInfo.status != LibNativeOrder.OrderStatus.FILLABLE) {
|
||||||
|
// Not fillable.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the fillable maker amount based on the order quantities and
|
||||||
|
// previously filled amount
|
||||||
|
uint256 fillableMakerTokenAmount = LibMathV06.getPartialAmountFloor(
|
||||||
|
uint256(
|
||||||
|
params.orderTakerAmount
|
||||||
|
- params.orderInfo.takerTokenFilledAmount
|
||||||
|
),
|
||||||
|
uint256(params.orderTakerAmount),
|
||||||
|
uint256(params.orderMakerAmount)
|
||||||
|
);
|
||||||
|
// Clamp it to the amount of maker tokens we can spend on behalf of the
|
||||||
|
// maker.
|
||||||
|
fillableMakerTokenAmount = LibSafeMathV06.min256(
|
||||||
|
fillableMakerTokenAmount,
|
||||||
|
_getSpendableERC20BalanceOf(params.makerToken, params.maker)
|
||||||
|
);
|
||||||
|
// Convert to taker token amount.
|
||||||
|
return LibMathV06.getPartialAmountCeil(
|
||||||
|
fillableMakerTokenAmount,
|
||||||
|
uint256(params.orderMakerAmount),
|
||||||
|
uint256(params.orderTakerAmount)
|
||||||
|
).safeDowncastToUint128();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
// 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/IEtherTokenV06.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
|
import "../../fixins/FixinProtocolFees.sol";
|
||||||
|
import "../../errors/LibNativeOrdersRichErrors.sol";
|
||||||
|
import "../../vendor/v3/IStaking.sol";
|
||||||
|
|
||||||
|
|
||||||
|
/// @dev Mixin for protocol fee utility functions.
|
||||||
|
abstract contract NativeOrdersProtocolFees is
|
||||||
|
FixinProtocolFees
|
||||||
|
{
|
||||||
|
using LibSafeMathV06 for uint256;
|
||||||
|
using LibRichErrorsV06 for bytes;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
IEtherTokenV06 weth,
|
||||||
|
IStaking staking,
|
||||||
|
FeeCollectorController feeCollectorController,
|
||||||
|
uint32 protocolFeeMultiplier
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
FixinProtocolFees(weth, staking, feeCollectorController, protocolFeeMultiplier)
|
||||||
|
{
|
||||||
|
// solhint-disable no-empty-blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Transfers protocol fees from the `FeeCollector` pools into
|
||||||
|
/// the staking contract.
|
||||||
|
/// @param poolIds Staking pool IDs
|
||||||
|
function transferProtocolFeesForPools(bytes32[] calldata poolIds)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
for (uint256 i = 0; i < poolIds.length; ++i) {
|
||||||
|
_transferFeesForPool(poolIds[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Get the protocol fee multiplier. This should be multiplied by the
|
||||||
|
/// gas price to arrive at the required protocol fee to fill a native order.
|
||||||
|
/// @return multiplier The protocol fee multiplier.
|
||||||
|
function getProtocolFeeMultiplier()
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (uint32 multiplier)
|
||||||
|
{
|
||||||
|
return PROTOCOL_FEE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,569 @@
|
|||||||
|
// 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/errors/LibRichErrorsV06.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
||||||
|
import "../../errors/LibNativeOrdersRichErrors.sol";
|
||||||
|
import "../../fixins/FixinCommon.sol";
|
||||||
|
import "../../storage/LibNativeOrdersStorage.sol";
|
||||||
|
import "../../vendor/v3/IStaking.sol";
|
||||||
|
import "../interfaces/INativeOrdersEvents.sol";
|
||||||
|
import "../libs/LibSignature.sol";
|
||||||
|
import "../libs/LibNativeOrder.sol";
|
||||||
|
import "./NativeOrdersCancellation.sol";
|
||||||
|
import "./NativeOrdersProtocolFees.sol";
|
||||||
|
|
||||||
|
|
||||||
|
/// @dev Mixin for settling limit and RFQ orders.
|
||||||
|
abstract contract NativeOrdersSettlement is
|
||||||
|
INativeOrdersEvents,
|
||||||
|
NativeOrdersCancellation,
|
||||||
|
NativeOrdersProtocolFees,
|
||||||
|
FixinCommon
|
||||||
|
{
|
||||||
|
using LibSafeMathV06 for uint128;
|
||||||
|
using LibRichErrorsV06 for bytes;
|
||||||
|
|
||||||
|
/// @dev Params for `_settleOrder()`.
|
||||||
|
struct SettleOrderInfo {
|
||||||
|
// Order hash.
|
||||||
|
bytes32 orderHash;
|
||||||
|
// Maker of the order.
|
||||||
|
address maker;
|
||||||
|
// Taker of the order.
|
||||||
|
address taker;
|
||||||
|
// Maker token.
|
||||||
|
IERC20TokenV06 makerToken;
|
||||||
|
// Taker token.
|
||||||
|
IERC20TokenV06 takerToken;
|
||||||
|
// Maker token amount.
|
||||||
|
uint128 makerAmount;
|
||||||
|
// Taker token amount.
|
||||||
|
uint128 takerAmount;
|
||||||
|
// Maximum taker token amount to fill.
|
||||||
|
uint128 takerTokenFillAmount;
|
||||||
|
// How much taker token amount has already been filled in this order.
|
||||||
|
uint128 takerTokenFilledAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Params for `_fillLimitOrderPrivate()`
|
||||||
|
struct FillLimitOrderPrivateParams {
|
||||||
|
// The limit order.
|
||||||
|
LibNativeOrder.LimitOrder order;
|
||||||
|
// The order signature.
|
||||||
|
LibSignature.Signature signature;
|
||||||
|
// Maximum taker token to fill this order with.
|
||||||
|
uint128 takerTokenFillAmount;
|
||||||
|
// The order taker.
|
||||||
|
address taker;
|
||||||
|
// The order sender.
|
||||||
|
address sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @dev Fill results returned by `_fillLimitOrderPrivate()` and
|
||||||
|
/// `_fillRfqOrderPrivate()`.
|
||||||
|
struct FillNativeOrderResults {
|
||||||
|
uint256 ethProtocolFeePaid;
|
||||||
|
uint128 takerTokenFilledAmount;
|
||||||
|
uint128 makerTokenFilledAmount;
|
||||||
|
uint128 takerTokenFeeFilledAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
address zeroExAddress,
|
||||||
|
IEtherTokenV06 weth,
|
||||||
|
IStaking staking,
|
||||||
|
FeeCollectorController feeCollectorController,
|
||||||
|
uint32 protocolFeeMultiplier,
|
||||||
|
bytes32 greedyTokensBloomFilter
|
||||||
|
)
|
||||||
|
public
|
||||||
|
NativeOrdersCancellation(zeroExAddress, greedyTokensBloomFilter)
|
||||||
|
NativeOrdersProtocolFees(weth, staking, feeCollectorController, protocolFeeMultiplier)
|
||||||
|
{
|
||||||
|
// solhint-disable no-empty-blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Fill a limit order. The taker and sender will be the caller.
|
||||||
|
/// @param order The limit order. ETH protocol fees can be
|
||||||
|
/// attached to this call. Any unspent ETH will be refunded to
|
||||||
|
/// the caller.
|
||||||
|
/// @param signature The order signature.
|
||||||
|
/// @param takerTokenFillAmount Maximum taker token amount to fill this order with.
|
||||||
|
/// @return takerTokenFilledAmount How much maker token was filled.
|
||||||
|
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||||
|
function fillLimitOrder(
|
||||||
|
LibNativeOrder.LimitOrder memory order,
|
||||||
|
LibSignature.Signature memory signature,
|
||||||
|
uint128 takerTokenFillAmount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
payable
|
||||||
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||||
|
{
|
||||||
|
FillNativeOrderResults memory results =
|
||||||
|
_fillLimitOrderPrivate(FillLimitOrderPrivateParams({
|
||||||
|
order: order,
|
||||||
|
signature: signature,
|
||||||
|
takerTokenFillAmount: takerTokenFillAmount,
|
||||||
|
taker: msg.sender,
|
||||||
|
sender: msg.sender
|
||||||
|
}));
|
||||||
|
LibNativeOrder.refundExcessProtocolFeeToSender(results.ethProtocolFeePaid);
|
||||||
|
(takerTokenFilledAmount, makerTokenFilledAmount) = (
|
||||||
|
results.takerTokenFilledAmount,
|
||||||
|
results.makerTokenFilledAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Fill an RFQ order for up to `takerTokenFillAmount` taker tokens.
|
||||||
|
/// The taker will be the caller. ETH should be attached to pay the
|
||||||
|
/// protocol fee.
|
||||||
|
/// @param order The RFQ order.
|
||||||
|
/// @param signature The order signature.
|
||||||
|
/// @param takerTokenFillAmount Maximum taker token amount to fill this order with.
|
||||||
|
/// @return takerTokenFilledAmount How much maker token was filled.
|
||||||
|
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||||
|
function fillRfqOrder(
|
||||||
|
LibNativeOrder.RfqOrder memory order,
|
||||||
|
LibSignature.Signature memory signature,
|
||||||
|
uint128 takerTokenFillAmount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||||
|
{
|
||||||
|
FillNativeOrderResults memory results =
|
||||||
|
_fillRfqOrderPrivate(
|
||||||
|
order,
|
||||||
|
signature,
|
||||||
|
takerTokenFillAmount,
|
||||||
|
msg.sender
|
||||||
|
);
|
||||||
|
(takerTokenFilledAmount, makerTokenFilledAmount) = (
|
||||||
|
results.takerTokenFilledAmount,
|
||||||
|
results.makerTokenFilledAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Fill an RFQ order for exactly `takerTokenFillAmount` taker tokens.
|
||||||
|
/// The taker will be the caller. ETH protocol fees can be
|
||||||
|
/// attached to this call. Any unspent ETH will be refunded to
|
||||||
|
/// the caller.
|
||||||
|
/// @param order The limit order.
|
||||||
|
/// @param signature The order signature.
|
||||||
|
/// @param takerTokenFillAmount How much taker token to fill this order with.
|
||||||
|
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||||
|
function fillOrKillLimitOrder(
|
||||||
|
LibNativeOrder.LimitOrder memory order,
|
||||||
|
LibSignature.Signature memory signature,
|
||||||
|
uint128 takerTokenFillAmount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
payable
|
||||||
|
returns (uint128 makerTokenFilledAmount)
|
||||||
|
{
|
||||||
|
FillNativeOrderResults memory results =
|
||||||
|
_fillLimitOrderPrivate(FillLimitOrderPrivateParams({
|
||||||
|
order: order,
|
||||||
|
signature: signature,
|
||||||
|
takerTokenFillAmount: takerTokenFillAmount,
|
||||||
|
taker: msg.sender,
|
||||||
|
sender: msg.sender
|
||||||
|
}));
|
||||||
|
// Must have filled exactly the amount requested.
|
||||||
|
if (results.takerTokenFilledAmount < takerTokenFillAmount) {
|
||||||
|
LibNativeOrdersRichErrors.FillOrKillFailedError(
|
||||||
|
getLimitOrderHash(order),
|
||||||
|
results.takerTokenFilledAmount,
|
||||||
|
takerTokenFillAmount
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
LibNativeOrder.refundExcessProtocolFeeToSender(results.ethProtocolFeePaid);
|
||||||
|
makerTokenFilledAmount = results.makerTokenFilledAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Fill an RFQ order for exactly `takerTokenFillAmount` taker tokens.
|
||||||
|
/// The taker will be the caller. ETH protocol fees can be
|
||||||
|
/// attached to this call. Any unspent ETH will be refunded to
|
||||||
|
/// the caller.
|
||||||
|
/// @param order The RFQ order.
|
||||||
|
/// @param signature The order signature.
|
||||||
|
/// @param takerTokenFillAmount How much taker token to fill this order with.
|
||||||
|
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||||
|
function fillOrKillRfqOrder(
|
||||||
|
LibNativeOrder.RfqOrder memory order,
|
||||||
|
LibSignature.Signature memory signature,
|
||||||
|
uint128 takerTokenFillAmount
|
||||||
|
)
|
||||||
|
public
|
||||||
|
returns (uint128 makerTokenFilledAmount)
|
||||||
|
{
|
||||||
|
FillNativeOrderResults memory results =
|
||||||
|
_fillRfqOrderPrivate(
|
||||||
|
order,
|
||||||
|
signature,
|
||||||
|
takerTokenFillAmount,
|
||||||
|
msg.sender
|
||||||
|
);
|
||||||
|
// Must have filled exactly the amount requested.
|
||||||
|
if (results.takerTokenFilledAmount < takerTokenFillAmount) {
|
||||||
|
LibNativeOrdersRichErrors.FillOrKillFailedError(
|
||||||
|
getRfqOrderHash(order),
|
||||||
|
results.takerTokenFilledAmount,
|
||||||
|
takerTokenFillAmount
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
makerTokenFilledAmount = results.makerTokenFilledAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Fill a limit order. Internal variant. ETH protocol fees can be
|
||||||
|
/// attached to this call.
|
||||||
|
/// @param order The limit order.
|
||||||
|
/// @param signature The order signature.
|
||||||
|
/// @param takerTokenFillAmount Maximum taker token to fill this order with.
|
||||||
|
/// @param taker The order taker.
|
||||||
|
/// @param sender The order sender.
|
||||||
|
/// @return takerTokenFilledAmount How much maker token was filled.
|
||||||
|
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||||
|
function _fillLimitOrder(
|
||||||
|
LibNativeOrder.LimitOrder memory order,
|
||||||
|
LibSignature.Signature memory signature,
|
||||||
|
uint128 takerTokenFillAmount,
|
||||||
|
address taker,
|
||||||
|
address sender
|
||||||
|
)
|
||||||
|
public
|
||||||
|
virtual
|
||||||
|
payable
|
||||||
|
onlySelf
|
||||||
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||||
|
{
|
||||||
|
FillNativeOrderResults memory results =
|
||||||
|
_fillLimitOrderPrivate(FillLimitOrderPrivateParams({
|
||||||
|
order: order,
|
||||||
|
signature: signature,
|
||||||
|
takerTokenFillAmount: takerTokenFillAmount,
|
||||||
|
taker: taker,
|
||||||
|
sender: sender
|
||||||
|
}));
|
||||||
|
(takerTokenFilledAmount, makerTokenFilledAmount) = (
|
||||||
|
results.takerTokenFilledAmount,
|
||||||
|
results.makerTokenFilledAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Fill an RFQ order. Internal variant. ETH protocol fees can be
|
||||||
|
/// attached to this call. Any unspent ETH will be refunded to
|
||||||
|
/// `msg.sender` (not `sender`).
|
||||||
|
/// @param order The RFQ order.
|
||||||
|
/// @param signature The order signature.
|
||||||
|
/// @param takerTokenFillAmount Maximum taker token to fill this order with.
|
||||||
|
/// @param taker The order taker.
|
||||||
|
/// @return takerTokenFilledAmount How much maker token was filled.
|
||||||
|
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||||
|
function _fillRfqOrder(
|
||||||
|
LibNativeOrder.RfqOrder memory order,
|
||||||
|
LibSignature.Signature memory signature,
|
||||||
|
uint128 takerTokenFillAmount,
|
||||||
|
address taker
|
||||||
|
)
|
||||||
|
public
|
||||||
|
virtual
|
||||||
|
onlySelf
|
||||||
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||||
|
{
|
||||||
|
FillNativeOrderResults memory results =
|
||||||
|
_fillRfqOrderPrivate(
|
||||||
|
order,
|
||||||
|
signature,
|
||||||
|
takerTokenFillAmount,
|
||||||
|
taker
|
||||||
|
);
|
||||||
|
(takerTokenFilledAmount, makerTokenFilledAmount) = (
|
||||||
|
results.takerTokenFilledAmount,
|
||||||
|
results.makerTokenFilledAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Mark what tx.origin addresses are allowed to fill an order that
|
||||||
|
/// specifies the message sender as its txOrigin.
|
||||||
|
/// @param origins An array of origin addresses to update.
|
||||||
|
/// @param allowed True to register, false to unregister.
|
||||||
|
function registerAllowedRfqOrigins(
|
||||||
|
address[] memory origins,
|
||||||
|
bool allowed
|
||||||
|
)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
require(msg.sender == tx.origin,
|
||||||
|
"NativeOrdersFeature/NO_CONTRACT_ORIGINS");
|
||||||
|
|
||||||
|
LibNativeOrdersStorage.Storage storage stor =
|
||||||
|
LibNativeOrdersStorage.getStorage();
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < origins.length; i++) {
|
||||||
|
stor.originRegistry[msg.sender][origins[i]] = allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit RfqOrderOriginsAllowed(msg.sender, origins, allowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Fill a limit order. Private variant. Does not refund protocol fees.
|
||||||
|
/// @param params Function params.
|
||||||
|
/// @return results Results of the fill.
|
||||||
|
function _fillLimitOrderPrivate(FillLimitOrderPrivateParams memory params)
|
||||||
|
private
|
||||||
|
returns (FillNativeOrderResults memory results)
|
||||||
|
{
|
||||||
|
LibNativeOrder.OrderInfo memory orderInfo = getLimitOrderInfo(params.order);
|
||||||
|
|
||||||
|
// Must be fillable.
|
||||||
|
if (orderInfo.status != LibNativeOrder.OrderStatus.FILLABLE) {
|
||||||
|
LibNativeOrdersRichErrors.OrderNotFillableError(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
uint8(orderInfo.status)
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be fillable by the taker.
|
||||||
|
if (params.order.taker != address(0) && params.order.taker != params.taker) {
|
||||||
|
LibNativeOrdersRichErrors.OrderNotFillableByTakerError(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
params.taker,
|
||||||
|
params.order.taker
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be fillable by the sender.
|
||||||
|
if (params.order.sender != address(0) && params.order.sender != params.sender) {
|
||||||
|
LibNativeOrdersRichErrors.OrderNotFillableBySenderError(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
params.sender,
|
||||||
|
params.order.sender
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signature must be valid for the order.
|
||||||
|
{
|
||||||
|
address signer = LibSignature.getSignerOfHash(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
params.signature
|
||||||
|
);
|
||||||
|
if (signer != params.order.maker) {
|
||||||
|
LibNativeOrdersRichErrors.OrderNotSignedByMakerError(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
signer,
|
||||||
|
params.order.maker
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pay the protocol fee.
|
||||||
|
results.ethProtocolFeePaid = _collectProtocolFee(params.order.pool);
|
||||||
|
|
||||||
|
// Settle between the maker and taker.
|
||||||
|
(results.takerTokenFilledAmount, results.makerTokenFilledAmount) = _settleOrder(
|
||||||
|
SettleOrderInfo({
|
||||||
|
orderHash: orderInfo.orderHash,
|
||||||
|
maker: params.order.maker,
|
||||||
|
taker: params.taker,
|
||||||
|
makerToken: IERC20TokenV06(params.order.makerToken),
|
||||||
|
takerToken: IERC20TokenV06(params.order.takerToken),
|
||||||
|
makerAmount: params.order.makerAmount,
|
||||||
|
takerAmount: params.order.takerAmount,
|
||||||
|
takerTokenFillAmount: params.takerTokenFillAmount,
|
||||||
|
takerTokenFilledAmount: orderInfo.takerTokenFilledAmount
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Pay the fee recipient.
|
||||||
|
if (params.order.takerTokenFeeAmount > 0) {
|
||||||
|
results.takerTokenFeeFilledAmount = uint128(LibMathV06.getPartialAmountFloor(
|
||||||
|
results.takerTokenFilledAmount,
|
||||||
|
params.order.takerAmount,
|
||||||
|
params.order.takerTokenFeeAmount
|
||||||
|
));
|
||||||
|
_transferERC20Tokens(
|
||||||
|
params.order.takerToken,
|
||||||
|
params.taker,
|
||||||
|
params.order.feeRecipient,
|
||||||
|
uint256(results.takerTokenFeeFilledAmount)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit LimitOrderFilled(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
params.order.maker,
|
||||||
|
params.taker,
|
||||||
|
params.order.feeRecipient,
|
||||||
|
address(params.order.makerToken),
|
||||||
|
address(params.order.takerToken),
|
||||||
|
results.takerTokenFilledAmount,
|
||||||
|
results.makerTokenFilledAmount,
|
||||||
|
results.takerTokenFeeFilledAmount,
|
||||||
|
results.ethProtocolFeePaid,
|
||||||
|
params.order.pool
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Fill an RFQ order. Private variant. Does not refund protocol fees.
|
||||||
|
/// @param order The RFQ order.
|
||||||
|
/// @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.
|
||||||
|
function _fillRfqOrderPrivate(
|
||||||
|
LibNativeOrder.RfqOrder memory order,
|
||||||
|
LibSignature.Signature memory signature,
|
||||||
|
uint128 takerTokenFillAmount,
|
||||||
|
address taker
|
||||||
|
)
|
||||||
|
private
|
||||||
|
returns (FillNativeOrderResults memory results)
|
||||||
|
{
|
||||||
|
LibNativeOrder.OrderInfo memory orderInfo = getRfqOrderInfo(order);
|
||||||
|
|
||||||
|
// Must be fillable.
|
||||||
|
if (orderInfo.status != LibNativeOrder.OrderStatus.FILLABLE) {
|
||||||
|
LibNativeOrdersRichErrors.OrderNotFillableError(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
uint8(orderInfo.status)
|
||||||
|
).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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be fillable by the taker.
|
||||||
|
if (order.taker != address(0) && order.taker != taker) {
|
||||||
|
LibNativeOrdersRichErrors.OrderNotFillableByTakerError(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
taker,
|
||||||
|
order.taker
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signature must be valid for the order.
|
||||||
|
{
|
||||||
|
address signer = LibSignature.getSignerOfHash(orderInfo.orderHash, signature);
|
||||||
|
if (signer != order.maker) {
|
||||||
|
LibNativeOrdersRichErrors.OrderNotSignedByMakerError(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
signer,
|
||||||
|
order.maker
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settle between the maker and taker.
|
||||||
|
(results.takerTokenFilledAmount, results.makerTokenFilledAmount) = _settleOrder(
|
||||||
|
SettleOrderInfo({
|
||||||
|
orderHash: orderInfo.orderHash,
|
||||||
|
maker: order.maker,
|
||||||
|
taker: taker,
|
||||||
|
makerToken: IERC20TokenV06(order.makerToken),
|
||||||
|
takerToken: IERC20TokenV06(order.takerToken),
|
||||||
|
makerAmount: order.makerAmount,
|
||||||
|
takerAmount: order.takerAmount,
|
||||||
|
takerTokenFillAmount: takerTokenFillAmount,
|
||||||
|
takerTokenFilledAmount: orderInfo.takerTokenFilledAmount
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
emit RfqOrderFilled(
|
||||||
|
orderInfo.orderHash,
|
||||||
|
order.maker,
|
||||||
|
taker,
|
||||||
|
address(order.makerToken),
|
||||||
|
address(order.takerToken),
|
||||||
|
results.takerTokenFilledAmount,
|
||||||
|
results.makerTokenFilledAmount,
|
||||||
|
order.pool
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Settle the trade between an order's maker and taker.
|
||||||
|
/// @param settleInfo Information needed to execute the settlement.
|
||||||
|
/// @return takerTokenFilledAmount How much taker token was filled.
|
||||||
|
/// @return makerTokenFilledAmount How much maker token was filled.
|
||||||
|
function _settleOrder(SettleOrderInfo memory settleInfo)
|
||||||
|
private
|
||||||
|
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
|
||||||
|
{
|
||||||
|
// Clamp the taker token fill amount to the fillable amount.
|
||||||
|
takerTokenFilledAmount = LibSafeMathV06.min128(
|
||||||
|
settleInfo.takerTokenFillAmount,
|
||||||
|
settleInfo.takerAmount.safeSub128(settleInfo.takerTokenFilledAmount)
|
||||||
|
);
|
||||||
|
// Compute the maker token amount.
|
||||||
|
// This should never overflow because the values are all clamped to
|
||||||
|
// (2^128-1).
|
||||||
|
makerTokenFilledAmount = uint128(LibMathV06.getPartialAmountFloor(
|
||||||
|
uint256(takerTokenFilledAmount),
|
||||||
|
uint256(settleInfo.takerAmount),
|
||||||
|
uint256(settleInfo.makerAmount)
|
||||||
|
));
|
||||||
|
|
||||||
|
if (takerTokenFilledAmount == 0 || makerTokenFilledAmount == 0) {
|
||||||
|
// Nothing to do.
|
||||||
|
return (0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update filled state for the order.
|
||||||
|
LibNativeOrdersStorage
|
||||||
|
.getStorage()
|
||||||
|
.orderHashToTakerTokenFilledAmount[settleInfo.orderHash] =
|
||||||
|
// OK to overwrite the whole word because we shouldn't get to this
|
||||||
|
// function if the order is cancelled.
|
||||||
|
settleInfo.takerTokenFilledAmount.safeAdd128(takerTokenFilledAmount);
|
||||||
|
|
||||||
|
// Transfer taker -> maker.
|
||||||
|
_transferERC20Tokens(
|
||||||
|
settleInfo.takerToken,
|
||||||
|
settleInfo.taker,
|
||||||
|
settleInfo.maker,
|
||||||
|
takerTokenFilledAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
// Transfer maker -> taker.
|
||||||
|
_transferERC20Tokens(
|
||||||
|
settleInfo.makerToken,
|
||||||
|
settleInfo.maker,
|
||||||
|
settleInfo.taker,
|
||||||
|
makerTokenFilledAmount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -23,8 +23,8 @@ pragma experimental ABIEncoderV2;
|
|||||||
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol";
|
||||||
import "../errors/LibCommonRichErrors.sol";
|
import "../errors/LibCommonRichErrors.sol";
|
||||||
import "../errors/LibOwnableRichErrors.sol";
|
import "../errors/LibOwnableRichErrors.sol";
|
||||||
import "../features/IOwnableFeature.sol";
|
import "../features/interfaces/IOwnableFeature.sol";
|
||||||
import "../features/ISimpleFunctionRegistryFeature.sol";
|
import "../features/interfaces/ISimpleFunctionRegistryFeature.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @dev Common feature utilities.
|
/// @dev Common feature utilities.
|
||||||
|
@ -22,7 +22,7 @@ pragma experimental ABIEncoderV2;
|
|||||||
|
|
||||||
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
||||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
import "../features/ITokenSpenderFeature.sol";
|
import "../features/interfaces/ITokenSpenderFeature.sol";
|
||||||
import "../errors/LibSpenderRichErrors.sol";
|
import "../errors/LibSpenderRichErrors.sol";
|
||||||
import "../external/FeeCollector.sol";
|
import "../external/FeeCollector.sol";
|
||||||
import "../vendor/v3/IStaking.sol";
|
import "../vendor/v3/IStaking.sol";
|
||||||
|
@ -21,7 +21,7 @@ pragma solidity ^0.6.5;
|
|||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "../ZeroEx.sol";
|
import "../ZeroEx.sol";
|
||||||
import "../features/IOwnableFeature.sol";
|
import "../features/interfaces/IOwnableFeature.sol";
|
||||||
import "../features/TokenSpenderFeature.sol";
|
import "../features/TokenSpenderFeature.sol";
|
||||||
import "../features/TransformERC20Feature.sol";
|
import "../features/TransformERC20Feature.sol";
|
||||||
import "../features/MetaTransactionsFeature.sol";
|
import "../features/MetaTransactionsFeature.sol";
|
||||||
|
@ -21,7 +21,7 @@ pragma solidity ^0.6.5;
|
|||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "../ZeroEx.sol";
|
import "../ZeroEx.sol";
|
||||||
import "../features/IBootstrapFeature.sol";
|
import "../features/interfaces/IBootstrapFeature.sol";
|
||||||
import "../features/SimpleFunctionRegistryFeature.sol";
|
import "../features/SimpleFunctionRegistryFeature.sol";
|
||||||
import "../features/OwnableFeature.sol";
|
import "../features/OwnableFeature.sol";
|
||||||
import "./LibBootstrap.sol";
|
import "./LibBootstrap.sol";
|
||||||
|
@ -26,7 +26,7 @@ import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
|||||||
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
||||||
import "../errors/LibTransformERC20RichErrors.sol";
|
import "../errors/LibTransformERC20RichErrors.sol";
|
||||||
import "../features/INativeOrdersFeature.sol";
|
import "../features/interfaces/INativeOrdersFeature.sol";
|
||||||
import "../features/libs/LibNativeOrder.sol";
|
import "../features/libs/LibNativeOrder.sol";
|
||||||
import "./bridges/IBridgeAdapter.sol";
|
import "./bridges/IBridgeAdapter.sol";
|
||||||
import "./Transformer.sol";
|
import "./Transformer.sol";
|
||||||
@ -132,8 +132,6 @@ contract FillQuoteTransformer is
|
|||||||
/// @param orderHash The hash of the order that was skipped.
|
/// @param orderHash The hash of the order that was skipped.
|
||||||
event ProtocolFeeUnfunded(bytes32 orderHash);
|
event ProtocolFeeUnfunded(bytes32 orderHash);
|
||||||
|
|
||||||
/// @dev Maximum uint256 value.
|
|
||||||
uint256 private constant MAX_UINT256 = uint256(-1);
|
|
||||||
/// @dev The highest bit of a uint256 value.
|
/// @dev The highest bit of a uint256 value.
|
||||||
uint256 private constant HIGH_BIT = 2 ** 255;
|
uint256 private constant HIGH_BIT = 2 ** 255;
|
||||||
/// @dev Mask of the lower 255 bits of a uint256 value.
|
/// @dev Mask of the lower 255 bits of a uint256 value.
|
||||||
|
48
contracts/zero-ex/contracts/src/vendor/IUniswapV2Pair.sol
vendored
Normal file
48
contracts/zero-ex/contracts/src/vendor/IUniswapV2Pair.sol
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// 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.12;
|
||||||
|
|
||||||
|
|
||||||
|
interface IUniswapV2Pair {
|
||||||
|
event Swap(
|
||||||
|
address indexed sender,
|
||||||
|
uint256 amount0In,
|
||||||
|
uint256 amount1In,
|
||||||
|
uint256 amount0Out,
|
||||||
|
uint256 amount1Out,
|
||||||
|
address indexed to
|
||||||
|
);
|
||||||
|
|
||||||
|
function swap(
|
||||||
|
uint amount0Out,
|
||||||
|
uint amount1Out,
|
||||||
|
address to,
|
||||||
|
bytes calldata data
|
||||||
|
) external;
|
||||||
|
|
||||||
|
function getReserves()
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (
|
||||||
|
uint112 reserve0,
|
||||||
|
uint112 reserve1,
|
||||||
|
uint32 blockTimestampLast
|
||||||
|
);
|
||||||
|
}
|
@ -21,7 +21,7 @@ pragma solidity ^0.6.5;
|
|||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "../src/ZeroEx.sol";
|
import "../src/ZeroEx.sol";
|
||||||
import "../src/features/IBootstrapFeature.sol";
|
import "../src/features/interfaces/IBootstrapFeature.sol";
|
||||||
import "../src/migrations/InitialMigration.sol";
|
import "../src/migrations/InitialMigration.sol";
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@
|
|||||||
pragma solidity ^0.6.5;
|
pragma solidity ^0.6.5;
|
||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "../src/features/interfaces/IMetaTransactionsFeature.sol";
|
||||||
import "../src/features/NativeOrdersFeature.sol";
|
import "../src/features/NativeOrdersFeature.sol";
|
||||||
import "../src/features/IMetaTransactionsFeature.sol";
|
|
||||||
import "./TestFeeCollectorController.sol";
|
import "./TestFeeCollectorController.sol";
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@
|
|||||||
pragma solidity ^0.6.5;
|
pragma solidity ^0.6.5;
|
||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
import "../src/features/interfaces/IMetaTransactionsFeature.sol";
|
||||||
import "../src/features/TransformERC20Feature.sol";
|
import "../src/features/TransformERC20Feature.sol";
|
||||||
import "../src/features/IMetaTransactionsFeature.sol";
|
|
||||||
|
|
||||||
|
|
||||||
contract TestMetaTransactionsTransformERC20Feature is
|
contract TestMetaTransactionsTransformERC20Feature is
|
||||||
|
@ -21,7 +21,7 @@ pragma solidity ^0.6.5;
|
|||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "../src/migrations/LibMigrate.sol";
|
import "../src/migrations/LibMigrate.sol";
|
||||||
import "../src/features/IOwnableFeature.sol";
|
import "../src/features/interfaces/IOwnableFeature.sol";
|
||||||
|
|
||||||
|
|
||||||
contract TestMigrator {
|
contract TestMigrator {
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
pragma solidity ^0.6.5;
|
pragma solidity ^0.6.5;
|
||||||
pragma experimental ABIEncoderV2;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "../src/features/INativeOrdersFeature.sol";
|
import "../src/features/interfaces/INativeOrdersFeature.sol";
|
||||||
|
|
||||||
contract TestRfqOriginRegistration {
|
contract TestRfqOriginRegistration {
|
||||||
function registerAllowedRfqOrigins(
|
function registerAllowedRfqOrigins(
|
||||||
|
@ -41,9 +41,9 @@
|
|||||||
"rollback": "node ./lib/scripts/rollback.js"
|
"rollback": "node ./lib/scripts/rollback.js"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider",
|
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,PositiveSlippageFeeTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature,FeeCollectorController,FeeCollector,CurveLiquidityProvider,BatchFillNativeOrdersFeature,IBatchFillNativeOrdersFeature,MultiplexFeature,IMultiplexFeature",
|
||||||
"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|AllowanceTarget|BootstrapFeature|BridgeAdapter|BridgeSource|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|INativeOrdersFeature|IOwnableFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinDodo|MixinDodoV2|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|MooniswapLiquidityProvider|NativeOrdersFeature|OwnableFeature|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|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BatchFillNativeOrdersFeature|BootstrapFeature|BridgeAdapter|BridgeSource|CurveLiquidityProvider|FeeCollector|FeeCollectorController|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FixinTokenSpender|FlashWallet|FullMigration|IAllowanceTarget|IBatchFillNativeOrdersFeature|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IFeature|IFlashWallet|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IMooniswapPool|IMultiplexFeature|INativeOrdersEvents|INativeOrdersFeature|IOwnableFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IUniswapV2Pair|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibFeeCollector|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinBalancer|MixinBancor|MixinCoFiX|MixinCryptoCom|MixinCurve|MixinDodo|MixinDodoV2|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|MooniswapLiquidityProvider|MultiplexFeature|NativeOrdersCancellation|NativeOrdersFeature|NativeOrdersInfo|NativeOrdersProtocolFees|NativeOrdersSettlement|OwnableFeature|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|TestPermissionlessTransformerDeployerSuicidal|TestPermissionlessTransformerDeployerTransformer|TestRfqOriginRegistration|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx|ZeroExOptimized).json"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
import { ContractArtifact } from 'ethereum-types';
|
import { ContractArtifact } from 'ethereum-types';
|
||||||
|
|
||||||
import * as AffiliateFeeTransformer from '../generated-artifacts/AffiliateFeeTransformer.json';
|
import * as AffiliateFeeTransformer from '../generated-artifacts/AffiliateFeeTransformer.json';
|
||||||
|
import * as BatchFillNativeOrdersFeature from '../generated-artifacts/BatchFillNativeOrdersFeature.json';
|
||||||
import * as BridgeAdapter from '../generated-artifacts/BridgeAdapter.json';
|
import * as BridgeAdapter from '../generated-artifacts/BridgeAdapter.json';
|
||||||
import * as CurveLiquidityProvider from '../generated-artifacts/CurveLiquidityProvider.json';
|
import * as CurveLiquidityProvider from '../generated-artifacts/CurveLiquidityProvider.json';
|
||||||
import * as FeeCollector from '../generated-artifacts/FeeCollector.json';
|
import * as FeeCollector from '../generated-artifacts/FeeCollector.json';
|
||||||
@ -13,9 +14,11 @@ import * as FeeCollectorController from '../generated-artifacts/FeeCollectorCont
|
|||||||
import * as FillQuoteTransformer from '../generated-artifacts/FillQuoteTransformer.json';
|
import * as FillQuoteTransformer from '../generated-artifacts/FillQuoteTransformer.json';
|
||||||
import * as FullMigration from '../generated-artifacts/FullMigration.json';
|
import * as FullMigration from '../generated-artifacts/FullMigration.json';
|
||||||
import * as IAllowanceTarget from '../generated-artifacts/IAllowanceTarget.json';
|
import * as IAllowanceTarget from '../generated-artifacts/IAllowanceTarget.json';
|
||||||
|
import * as IBatchFillNativeOrdersFeature from '../generated-artifacts/IBatchFillNativeOrdersFeature.json';
|
||||||
import * as IERC20Transformer from '../generated-artifacts/IERC20Transformer.json';
|
import * as IERC20Transformer from '../generated-artifacts/IERC20Transformer.json';
|
||||||
import * as IFlashWallet from '../generated-artifacts/IFlashWallet.json';
|
import * as IFlashWallet from '../generated-artifacts/IFlashWallet.json';
|
||||||
import * as ILiquidityProviderFeature from '../generated-artifacts/ILiquidityProviderFeature.json';
|
import * as ILiquidityProviderFeature from '../generated-artifacts/ILiquidityProviderFeature.json';
|
||||||
|
import * as IMultiplexFeature from '../generated-artifacts/IMultiplexFeature.json';
|
||||||
import * as INativeOrdersFeature from '../generated-artifacts/INativeOrdersFeature.json';
|
import * as INativeOrdersFeature from '../generated-artifacts/INativeOrdersFeature.json';
|
||||||
import * as InitialMigration from '../generated-artifacts/InitialMigration.json';
|
import * as InitialMigration from '../generated-artifacts/InitialMigration.json';
|
||||||
import * as IOwnableFeature from '../generated-artifacts/IOwnableFeature.json';
|
import * as IOwnableFeature from '../generated-artifacts/IOwnableFeature.json';
|
||||||
@ -26,6 +29,7 @@ import * as IZeroEx from '../generated-artifacts/IZeroEx.json';
|
|||||||
import * as LiquidityProviderFeature from '../generated-artifacts/LiquidityProviderFeature.json';
|
import * as LiquidityProviderFeature from '../generated-artifacts/LiquidityProviderFeature.json';
|
||||||
import * as LogMetadataTransformer from '../generated-artifacts/LogMetadataTransformer.json';
|
import * as LogMetadataTransformer from '../generated-artifacts/LogMetadataTransformer.json';
|
||||||
import * as MetaTransactionsFeature from '../generated-artifacts/MetaTransactionsFeature.json';
|
import * as MetaTransactionsFeature from '../generated-artifacts/MetaTransactionsFeature.json';
|
||||||
|
import * as MultiplexFeature from '../generated-artifacts/MultiplexFeature.json';
|
||||||
import * as NativeOrdersFeature from '../generated-artifacts/NativeOrdersFeature.json';
|
import * as NativeOrdersFeature from '../generated-artifacts/NativeOrdersFeature.json';
|
||||||
import * as OwnableFeature from '../generated-artifacts/OwnableFeature.json';
|
import * as OwnableFeature from '../generated-artifacts/OwnableFeature.json';
|
||||||
import * as PayTakerTransformer from '../generated-artifacts/PayTakerTransformer.json';
|
import * as PayTakerTransformer from '../generated-artifacts/PayTakerTransformer.json';
|
||||||
@ -66,4 +70,8 @@ export const artifacts = {
|
|||||||
FeeCollectorController: FeeCollectorController as ContractArtifact,
|
FeeCollectorController: FeeCollectorController as ContractArtifact,
|
||||||
FeeCollector: FeeCollector as ContractArtifact,
|
FeeCollector: FeeCollector as ContractArtifact,
|
||||||
CurveLiquidityProvider: CurveLiquidityProvider as ContractArtifact,
|
CurveLiquidityProvider: CurveLiquidityProvider as ContractArtifact,
|
||||||
|
BatchFillNativeOrdersFeature: BatchFillNativeOrdersFeature as ContractArtifact,
|
||||||
|
IBatchFillNativeOrdersFeature: IBatchFillNativeOrdersFeature as ContractArtifact,
|
||||||
|
MultiplexFeature: MultiplexFeature as ContractArtifact,
|
||||||
|
IMultiplexFeature: IMultiplexFeature as ContractArtifact,
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
* -----------------------------------------------------------------------------
|
* -----------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
export * from '../generated-wrappers/affiliate_fee_transformer';
|
export * from '../generated-wrappers/affiliate_fee_transformer';
|
||||||
|
export * from '../generated-wrappers/batch_fill_native_orders_feature';
|
||||||
export * from '../generated-wrappers/bridge_adapter';
|
export * from '../generated-wrappers/bridge_adapter';
|
||||||
export * from '../generated-wrappers/curve_liquidity_provider';
|
export * from '../generated-wrappers/curve_liquidity_provider';
|
||||||
export * from '../generated-wrappers/fee_collector';
|
export * from '../generated-wrappers/fee_collector';
|
||||||
@ -11,9 +12,11 @@ export * from '../generated-wrappers/fee_collector_controller';
|
|||||||
export * from '../generated-wrappers/fill_quote_transformer';
|
export * from '../generated-wrappers/fill_quote_transformer';
|
||||||
export * from '../generated-wrappers/full_migration';
|
export * from '../generated-wrappers/full_migration';
|
||||||
export * from '../generated-wrappers/i_allowance_target';
|
export * from '../generated-wrappers/i_allowance_target';
|
||||||
|
export * from '../generated-wrappers/i_batch_fill_native_orders_feature';
|
||||||
export * from '../generated-wrappers/i_erc20_transformer';
|
export * from '../generated-wrappers/i_erc20_transformer';
|
||||||
export * from '../generated-wrappers/i_flash_wallet';
|
export * from '../generated-wrappers/i_flash_wallet';
|
||||||
export * from '../generated-wrappers/i_liquidity_provider_feature';
|
export * from '../generated-wrappers/i_liquidity_provider_feature';
|
||||||
|
export * from '../generated-wrappers/i_multiplex_feature';
|
||||||
export * from '../generated-wrappers/i_native_orders_feature';
|
export * from '../generated-wrappers/i_native_orders_feature';
|
||||||
export * from '../generated-wrappers/i_ownable_feature';
|
export * from '../generated-wrappers/i_ownable_feature';
|
||||||
export * from '../generated-wrappers/i_simple_function_registry_feature';
|
export * from '../generated-wrappers/i_simple_function_registry_feature';
|
||||||
@ -24,6 +27,7 @@ export * from '../generated-wrappers/initial_migration';
|
|||||||
export * from '../generated-wrappers/liquidity_provider_feature';
|
export * from '../generated-wrappers/liquidity_provider_feature';
|
||||||
export * from '../generated-wrappers/log_metadata_transformer';
|
export * from '../generated-wrappers/log_metadata_transformer';
|
||||||
export * from '../generated-wrappers/meta_transactions_feature';
|
export * from '../generated-wrappers/meta_transactions_feature';
|
||||||
|
export * from '../generated-wrappers/multiplex_feature';
|
||||||
export * from '../generated-wrappers/native_orders_feature';
|
export * from '../generated-wrappers/native_orders_feature';
|
||||||
export * from '../generated-wrappers/ownable_feature';
|
export * from '../generated-wrappers/ownable_feature';
|
||||||
export * from '../generated-wrappers/pay_taker_transformer';
|
export * from '../generated-wrappers/pay_taker_transformer';
|
||||||
|
@ -7,6 +7,7 @@ import { ContractArtifact } from 'ethereum-types';
|
|||||||
|
|
||||||
import * as AffiliateFeeTransformer from '../test/generated-artifacts/AffiliateFeeTransformer.json';
|
import * as AffiliateFeeTransformer from '../test/generated-artifacts/AffiliateFeeTransformer.json';
|
||||||
import * as AllowanceTarget from '../test/generated-artifacts/AllowanceTarget.json';
|
import * as AllowanceTarget from '../test/generated-artifacts/AllowanceTarget.json';
|
||||||
|
import * as BatchFillNativeOrdersFeature from '../test/generated-artifacts/BatchFillNativeOrdersFeature.json';
|
||||||
import * as BootstrapFeature from '../test/generated-artifacts/BootstrapFeature.json';
|
import * as BootstrapFeature from '../test/generated-artifacts/BootstrapFeature.json';
|
||||||
import * as BridgeAdapter from '../test/generated-artifacts/BridgeAdapter.json';
|
import * as BridgeAdapter from '../test/generated-artifacts/BridgeAdapter.json';
|
||||||
import * as BridgeSource from '../test/generated-artifacts/BridgeSource.json';
|
import * as BridgeSource from '../test/generated-artifacts/BridgeSource.json';
|
||||||
@ -22,6 +23,7 @@ import * as FixinTokenSpender from '../test/generated-artifacts/FixinTokenSpende
|
|||||||
import * as FlashWallet from '../test/generated-artifacts/FlashWallet.json';
|
import * as FlashWallet from '../test/generated-artifacts/FlashWallet.json';
|
||||||
import * as FullMigration from '../test/generated-artifacts/FullMigration.json';
|
import * as FullMigration from '../test/generated-artifacts/FullMigration.json';
|
||||||
import * as IAllowanceTarget from '../test/generated-artifacts/IAllowanceTarget.json';
|
import * as IAllowanceTarget from '../test/generated-artifacts/IAllowanceTarget.json';
|
||||||
|
import * as IBatchFillNativeOrdersFeature from '../test/generated-artifacts/IBatchFillNativeOrdersFeature.json';
|
||||||
import * as IBootstrapFeature from '../test/generated-artifacts/IBootstrapFeature.json';
|
import * as IBootstrapFeature from '../test/generated-artifacts/IBootstrapFeature.json';
|
||||||
import * as IBridgeAdapter from '../test/generated-artifacts/IBridgeAdapter.json';
|
import * as IBridgeAdapter from '../test/generated-artifacts/IBridgeAdapter.json';
|
||||||
import * as IERC20Bridge from '../test/generated-artifacts/IERC20Bridge.json';
|
import * as IERC20Bridge from '../test/generated-artifacts/IERC20Bridge.json';
|
||||||
@ -33,6 +35,8 @@ import * as ILiquidityProviderFeature from '../test/generated-artifacts/ILiquidi
|
|||||||
import * as ILiquidityProviderSandbox from '../test/generated-artifacts/ILiquidityProviderSandbox.json';
|
import * as ILiquidityProviderSandbox from '../test/generated-artifacts/ILiquidityProviderSandbox.json';
|
||||||
import * as IMetaTransactionsFeature from '../test/generated-artifacts/IMetaTransactionsFeature.json';
|
import * as IMetaTransactionsFeature from '../test/generated-artifacts/IMetaTransactionsFeature.json';
|
||||||
import * as IMooniswapPool from '../test/generated-artifacts/IMooniswapPool.json';
|
import * as IMooniswapPool from '../test/generated-artifacts/IMooniswapPool.json';
|
||||||
|
import * as IMultiplexFeature from '../test/generated-artifacts/IMultiplexFeature.json';
|
||||||
|
import * as INativeOrdersEvents from '../test/generated-artifacts/INativeOrdersEvents.json';
|
||||||
import * as INativeOrdersFeature from '../test/generated-artifacts/INativeOrdersFeature.json';
|
import * as INativeOrdersFeature from '../test/generated-artifacts/INativeOrdersFeature.json';
|
||||||
import * as InitialMigration from '../test/generated-artifacts/InitialMigration.json';
|
import * as InitialMigration from '../test/generated-artifacts/InitialMigration.json';
|
||||||
import * as IOwnableFeature from '../test/generated-artifacts/IOwnableFeature.json';
|
import * as IOwnableFeature from '../test/generated-artifacts/IOwnableFeature.json';
|
||||||
@ -42,6 +46,7 @@ import * as ITestSimpleFunctionRegistryFeature from '../test/generated-artifacts
|
|||||||
import * as ITokenSpenderFeature from '../test/generated-artifacts/ITokenSpenderFeature.json';
|
import * as ITokenSpenderFeature from '../test/generated-artifacts/ITokenSpenderFeature.json';
|
||||||
import * as ITransformERC20Feature from '../test/generated-artifacts/ITransformERC20Feature.json';
|
import * as ITransformERC20Feature from '../test/generated-artifacts/ITransformERC20Feature.json';
|
||||||
import * as IUniswapFeature from '../test/generated-artifacts/IUniswapFeature.json';
|
import * as IUniswapFeature from '../test/generated-artifacts/IUniswapFeature.json';
|
||||||
|
import * as IUniswapV2Pair from '../test/generated-artifacts/IUniswapV2Pair.json';
|
||||||
import * as IZeroEx from '../test/generated-artifacts/IZeroEx.json';
|
import * as IZeroEx from '../test/generated-artifacts/IZeroEx.json';
|
||||||
import * as LibBootstrap from '../test/generated-artifacts/LibBootstrap.json';
|
import * as LibBootstrap from '../test/generated-artifacts/LibBootstrap.json';
|
||||||
import * as LibCommonRichErrors from '../test/generated-artifacts/LibCommonRichErrors.json';
|
import * as LibCommonRichErrors from '../test/generated-artifacts/LibCommonRichErrors.json';
|
||||||
@ -90,7 +95,12 @@ import * as MixinUniswap from '../test/generated-artifacts/MixinUniswap.json';
|
|||||||
import * as MixinUniswapV2 from '../test/generated-artifacts/MixinUniswapV2.json';
|
import * as MixinUniswapV2 from '../test/generated-artifacts/MixinUniswapV2.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 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 NativeOrdersProtocolFees from '../test/generated-artifacts/NativeOrdersProtocolFees.json';
|
||||||
|
import * as NativeOrdersSettlement from '../test/generated-artifacts/NativeOrdersSettlement.json';
|
||||||
import * as OwnableFeature from '../test/generated-artifacts/OwnableFeature.json';
|
import * as OwnableFeature from '../test/generated-artifacts/OwnableFeature.json';
|
||||||
import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json';
|
import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json';
|
||||||
import * as PermissionlessTransformerDeployer from '../test/generated-artifacts/PermissionlessTransformerDeployer.json';
|
import * as PermissionlessTransformerDeployer from '../test/generated-artifacts/PermissionlessTransformerDeployer.json';
|
||||||
@ -167,27 +177,36 @@ export const artifacts = {
|
|||||||
LiquidityProviderSandbox: LiquidityProviderSandbox as ContractArtifact,
|
LiquidityProviderSandbox: LiquidityProviderSandbox as ContractArtifact,
|
||||||
PermissionlessTransformerDeployer: PermissionlessTransformerDeployer as ContractArtifact,
|
PermissionlessTransformerDeployer: PermissionlessTransformerDeployer as ContractArtifact,
|
||||||
TransformerDeployer: TransformerDeployer as ContractArtifact,
|
TransformerDeployer: TransformerDeployer as ContractArtifact,
|
||||||
|
BatchFillNativeOrdersFeature: BatchFillNativeOrdersFeature as ContractArtifact,
|
||||||
BootstrapFeature: BootstrapFeature as ContractArtifact,
|
BootstrapFeature: BootstrapFeature as ContractArtifact,
|
||||||
IBootstrapFeature: IBootstrapFeature as ContractArtifact,
|
|
||||||
IFeature: IFeature as ContractArtifact,
|
|
||||||
ILiquidityProviderFeature: ILiquidityProviderFeature as ContractArtifact,
|
|
||||||
IMetaTransactionsFeature: IMetaTransactionsFeature as ContractArtifact,
|
|
||||||
INativeOrdersFeature: INativeOrdersFeature as ContractArtifact,
|
|
||||||
IOwnableFeature: IOwnableFeature as ContractArtifact,
|
|
||||||
ISimpleFunctionRegistryFeature: ISimpleFunctionRegistryFeature as ContractArtifact,
|
|
||||||
ITokenSpenderFeature: ITokenSpenderFeature as ContractArtifact,
|
|
||||||
ITransformERC20Feature: ITransformERC20Feature as ContractArtifact,
|
|
||||||
IUniswapFeature: IUniswapFeature 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,
|
||||||
OwnableFeature: OwnableFeature as ContractArtifact,
|
OwnableFeature: OwnableFeature as ContractArtifact,
|
||||||
SimpleFunctionRegistryFeature: SimpleFunctionRegistryFeature as ContractArtifact,
|
SimpleFunctionRegistryFeature: SimpleFunctionRegistryFeature as ContractArtifact,
|
||||||
TokenSpenderFeature: TokenSpenderFeature as ContractArtifact,
|
TokenSpenderFeature: TokenSpenderFeature as ContractArtifact,
|
||||||
TransformERC20Feature: TransformERC20Feature as ContractArtifact,
|
TransformERC20Feature: TransformERC20Feature as ContractArtifact,
|
||||||
UniswapFeature: UniswapFeature as ContractArtifact,
|
UniswapFeature: UniswapFeature as ContractArtifact,
|
||||||
|
IBatchFillNativeOrdersFeature: IBatchFillNativeOrdersFeature as ContractArtifact,
|
||||||
|
IBootstrapFeature: IBootstrapFeature as ContractArtifact,
|
||||||
|
IFeature: IFeature as ContractArtifact,
|
||||||
|
ILiquidityProviderFeature: ILiquidityProviderFeature as ContractArtifact,
|
||||||
|
IMetaTransactionsFeature: IMetaTransactionsFeature as ContractArtifact,
|
||||||
|
IMultiplexFeature: IMultiplexFeature as ContractArtifact,
|
||||||
|
INativeOrdersEvents: INativeOrdersEvents as ContractArtifact,
|
||||||
|
INativeOrdersFeature: INativeOrdersFeature as ContractArtifact,
|
||||||
|
IOwnableFeature: IOwnableFeature as ContractArtifact,
|
||||||
|
ISimpleFunctionRegistryFeature: ISimpleFunctionRegistryFeature as ContractArtifact,
|
||||||
|
ITokenSpenderFeature: ITokenSpenderFeature as ContractArtifact,
|
||||||
|
ITransformERC20Feature: ITransformERC20Feature as ContractArtifact,
|
||||||
|
IUniswapFeature: IUniswapFeature as ContractArtifact,
|
||||||
LibNativeOrder: LibNativeOrder as ContractArtifact,
|
LibNativeOrder: LibNativeOrder as ContractArtifact,
|
||||||
LibSignature: LibSignature as ContractArtifact,
|
LibSignature: LibSignature as ContractArtifact,
|
||||||
|
NativeOrdersCancellation: NativeOrdersCancellation as ContractArtifact,
|
||||||
|
NativeOrdersInfo: NativeOrdersInfo as ContractArtifact,
|
||||||
|
NativeOrdersProtocolFees: NativeOrdersProtocolFees as ContractArtifact,
|
||||||
|
NativeOrdersSettlement: NativeOrdersSettlement as ContractArtifact,
|
||||||
FixinCommon: FixinCommon as ContractArtifact,
|
FixinCommon: FixinCommon as ContractArtifact,
|
||||||
FixinEIP712: FixinEIP712 as ContractArtifact,
|
FixinEIP712: FixinEIP712 as ContractArtifact,
|
||||||
FixinProtocolFees: FixinProtocolFees as ContractArtifact,
|
FixinProtocolFees: FixinProtocolFees as ContractArtifact,
|
||||||
@ -238,6 +257,7 @@ export const artifacts = {
|
|||||||
MixinZeroExBridge: MixinZeroExBridge as ContractArtifact,
|
MixinZeroExBridge: MixinZeroExBridge as ContractArtifact,
|
||||||
ILiquidityProvider: ILiquidityProvider as ContractArtifact,
|
ILiquidityProvider: ILiquidityProvider as ContractArtifact,
|
||||||
IMooniswapPool: IMooniswapPool as ContractArtifact,
|
IMooniswapPool: IMooniswapPool as ContractArtifact,
|
||||||
|
IUniswapV2Pair: IUniswapV2Pair as ContractArtifact,
|
||||||
IERC20Bridge: IERC20Bridge as ContractArtifact,
|
IERC20Bridge: IERC20Bridge as ContractArtifact,
|
||||||
IStaking: IStaking as ContractArtifact,
|
IStaking: IStaking as ContractArtifact,
|
||||||
ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact,
|
ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact,
|
||||||
|
479
contracts/zero-ex/test/features/batch_fill_native_orders_test.ts
Normal file
479
contracts/zero-ex/test/features/batch_fill_native_orders_test.ts
Normal file
@ -0,0 +1,479 @@
|
|||||||
|
import {
|
||||||
|
blockchainTests,
|
||||||
|
constants,
|
||||||
|
describe,
|
||||||
|
expect,
|
||||||
|
getRandomPortion,
|
||||||
|
verifyEventsFromLogs,
|
||||||
|
} from '@0x/contracts-test-utils';
|
||||||
|
import { LimitOrder, LimitOrderFields, OrderStatus, RevertErrors, RfqOrder, RfqOrderFields } from '@0x/protocol-utils';
|
||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { BatchFillNativeOrdersFeatureContract, IZeroExContract, IZeroExEvents } from '../../src/wrappers';
|
||||||
|
import { artifacts } from '../artifacts';
|
||||||
|
import { abis } from '../utils/abis';
|
||||||
|
import {
|
||||||
|
assertOrderInfoEquals,
|
||||||
|
computeLimitOrderFilledAmounts,
|
||||||
|
computeRfqOrderFilledAmounts,
|
||||||
|
createExpiry,
|
||||||
|
getRandomLimitOrder,
|
||||||
|
getRandomRfqOrder,
|
||||||
|
NativeOrdersTestEnvironment,
|
||||||
|
} from '../utils/orders';
|
||||||
|
import { TestMintableERC20TokenContract } from '../wrappers';
|
||||||
|
|
||||||
|
blockchainTests.resets('BatchFillNativeOrdersFeature', env => {
|
||||||
|
const { NULL_ADDRESS, ZERO_AMOUNT } = constants;
|
||||||
|
let maker: string;
|
||||||
|
let taker: string;
|
||||||
|
let zeroEx: IZeroExContract;
|
||||||
|
let feature: BatchFillNativeOrdersFeatureContract;
|
||||||
|
let verifyingContract: string;
|
||||||
|
let makerToken: TestMintableERC20TokenContract;
|
||||||
|
let takerToken: TestMintableERC20TokenContract;
|
||||||
|
let testUtils: NativeOrdersTestEnvironment;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
testUtils = await NativeOrdersTestEnvironment.createAsync(env);
|
||||||
|
maker = testUtils.maker;
|
||||||
|
taker = testUtils.taker;
|
||||||
|
zeroEx = testUtils.zeroEx;
|
||||||
|
makerToken = testUtils.makerToken;
|
||||||
|
takerToken = testUtils.takerToken;
|
||||||
|
|
||||||
|
verifyingContract = zeroEx.address;
|
||||||
|
const featureImpl = await BatchFillNativeOrdersFeatureContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.BatchFillNativeOrdersFeature,
|
||||||
|
env.provider,
|
||||||
|
env.txDefaults,
|
||||||
|
artifacts,
|
||||||
|
zeroEx.address,
|
||||||
|
);
|
||||||
|
const [owner] = await env.getAccountAddressesAsync();
|
||||||
|
await zeroEx
|
||||||
|
.migrate(featureImpl.address, featureImpl.migrate().getABIEncodedTransactionData(), owner)
|
||||||
|
.awaitTransactionSuccessAsync();
|
||||||
|
feature = new BatchFillNativeOrdersFeatureContract(
|
||||||
|
zeroEx.address,
|
||||||
|
env.provider,
|
||||||
|
{ ...env.txDefaults, gasPrice: testUtils.gasPrice },
|
||||||
|
abis,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function getTestLimitOrder(fields: Partial<LimitOrderFields> = {}): LimitOrder {
|
||||||
|
return getRandomLimitOrder({
|
||||||
|
maker,
|
||||||
|
verifyingContract,
|
||||||
|
chainId: 1337,
|
||||||
|
takerToken: takerToken.address,
|
||||||
|
makerToken: makerToken.address,
|
||||||
|
taker: NULL_ADDRESS,
|
||||||
|
sender: NULL_ADDRESS,
|
||||||
|
...fields,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function getTestRfqOrder(fields: Partial<RfqOrderFields> = {}): RfqOrder {
|
||||||
|
return getRandomRfqOrder({
|
||||||
|
maker,
|
||||||
|
verifyingContract,
|
||||||
|
chainId: 1337,
|
||||||
|
takerToken: takerToken.address,
|
||||||
|
makerToken: makerToken.address,
|
||||||
|
txOrigin: taker,
|
||||||
|
...fields,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('batchFillLimitOrders', () => {
|
||||||
|
async function assertExpectedFinalBalancesAsync(
|
||||||
|
orders: LimitOrder[],
|
||||||
|
takerTokenFillAmounts: BigNumber[] = orders.map(order => order.takerAmount),
|
||||||
|
takerTokenAlreadyFilledAmounts: BigNumber[] = orders.map(() => ZERO_AMOUNT),
|
||||||
|
receipt?: TransactionReceiptWithDecodedLogs,
|
||||||
|
): Promise<void> {
|
||||||
|
const expectedFeeRecipientBalances: { [feeRecipient: string]: BigNumber } = {};
|
||||||
|
const { makerTokenFilledAmount, takerTokenFilledAmount } = orders
|
||||||
|
.map((order, i) =>
|
||||||
|
computeLimitOrderFilledAmounts(order, takerTokenFillAmounts[i], takerTokenAlreadyFilledAmounts[i]),
|
||||||
|
)
|
||||||
|
.reduce(
|
||||||
|
(previous, current, i) => {
|
||||||
|
_.update(expectedFeeRecipientBalances, orders[i].feeRecipient, balance =>
|
||||||
|
(balance || ZERO_AMOUNT).plus(current.takerTokenFeeFilledAmount),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
makerTokenFilledAmount: previous.makerTokenFilledAmount.plus(
|
||||||
|
current.makerTokenFilledAmount,
|
||||||
|
),
|
||||||
|
takerTokenFilledAmount: previous.takerTokenFilledAmount.plus(
|
||||||
|
current.takerTokenFilledAmount,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{ makerTokenFilledAmount: ZERO_AMOUNT, takerTokenFilledAmount: ZERO_AMOUNT },
|
||||||
|
);
|
||||||
|
const makerBalance = await takerToken.balanceOf(maker).callAsync();
|
||||||
|
const takerBalance = await makerToken.balanceOf(taker).callAsync();
|
||||||
|
expect(makerBalance, 'maker token balance').to.bignumber.eq(takerTokenFilledAmount);
|
||||||
|
expect(takerBalance, 'taker token balance').to.bignumber.eq(makerTokenFilledAmount);
|
||||||
|
for (const [feeRecipient, expectedFeeRecipientBalance] of Object.entries(expectedFeeRecipientBalances)) {
|
||||||
|
const feeRecipientBalance = await takerToken.balanceOf(feeRecipient).callAsync();
|
||||||
|
expect(feeRecipientBalance, `fee recipient balance`).to.bignumber.eq(expectedFeeRecipientBalance);
|
||||||
|
}
|
||||||
|
if (receipt) {
|
||||||
|
const balanceOfTakerNow = await env.web3Wrapper.getBalanceInWeiAsync(taker);
|
||||||
|
const balanceOfTakerBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker, receipt.blockNumber - 1);
|
||||||
|
const protocolFees = testUtils.protocolFee.times(orders.length);
|
||||||
|
const totalCost = testUtils.gasPrice.times(receipt.gasUsed).plus(protocolFees);
|
||||||
|
expect(balanceOfTakerBefore.minus(totalCost), 'taker ETH balance').to.bignumber.eq(balanceOfTakerNow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Fully fills multiple orders', async () => {
|
||||||
|
const orders = [...new Array(3)].map(() => getTestLimitOrder({ takerTokenFeeAmount: ZERO_AMOUNT }));
|
||||||
|
const signatures = await Promise.all(
|
||||||
|
orders.map(order => order.getSignatureWithProviderAsync(env.provider)),
|
||||||
|
);
|
||||||
|
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||||
|
const value = testUtils.protocolFee.times(orders.length);
|
||||||
|
const tx = await feature
|
||||||
|
.batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), false)
|
||||||
|
.awaitTransactionSuccessAsync({ from: taker, value });
|
||||||
|
const [orderInfos] = await zeroEx.batchGetLimitOrderRelevantStates(orders, signatures).callAsync();
|
||||||
|
orderInfos.map((orderInfo, i) =>
|
||||||
|
assertOrderInfoEquals(orderInfo, {
|
||||||
|
status: OrderStatus.Filled,
|
||||||
|
orderHash: orders[i].getHash(),
|
||||||
|
takerTokenFilledAmount: orders[i].takerAmount,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
tx.logs,
|
||||||
|
orders.map(order => testUtils.createLimitOrderFilledEventArgs(order)),
|
||||||
|
IZeroExEvents.LimitOrderFilled,
|
||||||
|
);
|
||||||
|
return assertExpectedFinalBalancesAsync(orders);
|
||||||
|
});
|
||||||
|
it('Partially fills multiple orders', async () => {
|
||||||
|
const orders = [...new Array(3)].map(getTestLimitOrder);
|
||||||
|
const signatures = await Promise.all(
|
||||||
|
orders.map(order => order.getSignatureWithProviderAsync(env.provider)),
|
||||||
|
);
|
||||||
|
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||||
|
const value = testUtils.protocolFee.times(orders.length);
|
||||||
|
const fillAmounts = orders.map(order => getRandomPortion(order.takerAmount));
|
||||||
|
const tx = await feature
|
||||||
|
.batchFillLimitOrders(orders, signatures, fillAmounts, false)
|
||||||
|
.awaitTransactionSuccessAsync({ from: taker, value });
|
||||||
|
const [orderInfos] = await zeroEx.batchGetLimitOrderRelevantStates(orders, signatures).callAsync();
|
||||||
|
orderInfos.map((orderInfo, i) =>
|
||||||
|
assertOrderInfoEquals(orderInfo, {
|
||||||
|
status: OrderStatus.Fillable,
|
||||||
|
orderHash: orders[i].getHash(),
|
||||||
|
takerTokenFilledAmount: fillAmounts[i],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
tx.logs,
|
||||||
|
orders.map((order, i) => testUtils.createLimitOrderFilledEventArgs(order, fillAmounts[i])),
|
||||||
|
IZeroExEvents.LimitOrderFilled,
|
||||||
|
);
|
||||||
|
return assertExpectedFinalBalancesAsync(orders, fillAmounts);
|
||||||
|
});
|
||||||
|
it('Fills multiple orders and refunds excess ETH', async () => {
|
||||||
|
const orders = [...new Array(3)].map(() => getTestLimitOrder({ takerTokenFeeAmount: ZERO_AMOUNT }));
|
||||||
|
const signatures = await Promise.all(
|
||||||
|
orders.map(order => order.getSignatureWithProviderAsync(env.provider)),
|
||||||
|
);
|
||||||
|
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||||
|
const value = testUtils.protocolFee.times(orders.length).plus(420);
|
||||||
|
const tx = await feature
|
||||||
|
.batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), false)
|
||||||
|
.awaitTransactionSuccessAsync({ from: taker, value });
|
||||||
|
const [orderInfos] = await zeroEx.batchGetLimitOrderRelevantStates(orders, signatures).callAsync();
|
||||||
|
orderInfos.map((orderInfo, i) =>
|
||||||
|
assertOrderInfoEquals(orderInfo, {
|
||||||
|
status: OrderStatus.Filled,
|
||||||
|
orderHash: orders[i].getHash(),
|
||||||
|
takerTokenFilledAmount: orders[i].takerAmount,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
tx.logs,
|
||||||
|
orders.map(order => testUtils.createLimitOrderFilledEventArgs(order)),
|
||||||
|
IZeroExEvents.LimitOrderFilled,
|
||||||
|
);
|
||||||
|
return assertExpectedFinalBalancesAsync(orders);
|
||||||
|
});
|
||||||
|
it('Skips over unfillable orders and refunds excess ETH', async () => {
|
||||||
|
const fillableOrders = [...new Array(3)].map(() => getTestLimitOrder({ takerTokenFeeAmount: ZERO_AMOUNT }));
|
||||||
|
const expiredOrder = getTestLimitOrder({ expiry: createExpiry(-1), takerTokenFeeAmount: ZERO_AMOUNT });
|
||||||
|
const orders = [expiredOrder, ...fillableOrders];
|
||||||
|
const signatures = await Promise.all(
|
||||||
|
orders.map(order => order.getSignatureWithProviderAsync(env.provider)),
|
||||||
|
);
|
||||||
|
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||||
|
const value = testUtils.protocolFee.times(orders.length);
|
||||||
|
const tx = await feature
|
||||||
|
.batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), false)
|
||||||
|
.awaitTransactionSuccessAsync({ from: taker, value });
|
||||||
|
const [orderInfos] = await zeroEx.batchGetLimitOrderRelevantStates(orders, signatures).callAsync();
|
||||||
|
const [expiredOrderInfo, ...filledOrderInfos] = orderInfos;
|
||||||
|
assertOrderInfoEquals(expiredOrderInfo, {
|
||||||
|
status: OrderStatus.Expired,
|
||||||
|
orderHash: expiredOrder.getHash(),
|
||||||
|
takerTokenFilledAmount: ZERO_AMOUNT,
|
||||||
|
});
|
||||||
|
filledOrderInfos.map((orderInfo, i) =>
|
||||||
|
assertOrderInfoEquals(orderInfo, {
|
||||||
|
status: OrderStatus.Filled,
|
||||||
|
orderHash: fillableOrders[i].getHash(),
|
||||||
|
takerTokenFilledAmount: fillableOrders[i].takerAmount,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
tx.logs,
|
||||||
|
fillableOrders.map(order => testUtils.createLimitOrderFilledEventArgs(order)),
|
||||||
|
IZeroExEvents.LimitOrderFilled,
|
||||||
|
);
|
||||||
|
return assertExpectedFinalBalancesAsync(fillableOrders);
|
||||||
|
});
|
||||||
|
it('Fills multiple orders with revertIfIncomplete=true', async () => {
|
||||||
|
const orders = [...new Array(3)].map(() => getTestLimitOrder({ takerTokenFeeAmount: ZERO_AMOUNT }));
|
||||||
|
const signatures = await Promise.all(
|
||||||
|
orders.map(order => order.getSignatureWithProviderAsync(env.provider)),
|
||||||
|
);
|
||||||
|
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||||
|
const value = testUtils.protocolFee.times(orders.length);
|
||||||
|
const tx = await feature
|
||||||
|
.batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), true)
|
||||||
|
.awaitTransactionSuccessAsync({ from: taker, value });
|
||||||
|
const [orderInfos] = await zeroEx.batchGetLimitOrderRelevantStates(orders, signatures).callAsync();
|
||||||
|
orderInfos.map((orderInfo, i) =>
|
||||||
|
assertOrderInfoEquals(orderInfo, {
|
||||||
|
status: OrderStatus.Filled,
|
||||||
|
orderHash: orders[i].getHash(),
|
||||||
|
takerTokenFilledAmount: orders[i].takerAmount,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
tx.logs,
|
||||||
|
orders.map(order => testUtils.createLimitOrderFilledEventArgs(order)),
|
||||||
|
IZeroExEvents.LimitOrderFilled,
|
||||||
|
);
|
||||||
|
return assertExpectedFinalBalancesAsync(orders);
|
||||||
|
});
|
||||||
|
it('If revertIfIncomplete==true, reverts on an unfillable order', async () => {
|
||||||
|
const fillableOrders = [...new Array(3)].map(() => getTestLimitOrder({ takerTokenFeeAmount: ZERO_AMOUNT }));
|
||||||
|
const expiredOrder = getTestLimitOrder({ expiry: createExpiry(-1), takerTokenFeeAmount: ZERO_AMOUNT });
|
||||||
|
const orders = [expiredOrder, ...fillableOrders];
|
||||||
|
const signatures = await Promise.all(
|
||||||
|
orders.map(order => order.getSignatureWithProviderAsync(env.provider)),
|
||||||
|
);
|
||||||
|
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||||
|
const value = testUtils.protocolFee.times(orders.length);
|
||||||
|
const tx = feature
|
||||||
|
.batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), true)
|
||||||
|
.awaitTransactionSuccessAsync({ from: taker, value });
|
||||||
|
return expect(tx).to.revertWith(
|
||||||
|
new RevertErrors.NativeOrders.BatchFillIncompleteError(
|
||||||
|
expiredOrder.getHash(),
|
||||||
|
ZERO_AMOUNT,
|
||||||
|
expiredOrder.takerAmount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('If revertIfIncomplete==true, reverts on an incomplete fill ', async () => {
|
||||||
|
const fillableOrders = [...new Array(3)].map(() => getTestLimitOrder({ takerTokenFeeAmount: ZERO_AMOUNT }));
|
||||||
|
const partiallyFilledOrder = getTestLimitOrder({ takerTokenFeeAmount: ZERO_AMOUNT });
|
||||||
|
const partialFillAmount = getRandomPortion(partiallyFilledOrder.takerAmount);
|
||||||
|
await testUtils.fillLimitOrderAsync(partiallyFilledOrder, { fillAmount: partialFillAmount });
|
||||||
|
const orders = [partiallyFilledOrder, ...fillableOrders];
|
||||||
|
const signatures = await Promise.all(
|
||||||
|
orders.map(order => order.getSignatureWithProviderAsync(env.provider)),
|
||||||
|
);
|
||||||
|
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||||
|
const value = testUtils.protocolFee.times(orders.length);
|
||||||
|
const tx = feature
|
||||||
|
.batchFillLimitOrders(orders, signatures, orders.map(order => order.takerAmount), true)
|
||||||
|
.awaitTransactionSuccessAsync({ from: taker, value });
|
||||||
|
return expect(tx).to.revertWith(
|
||||||
|
new RevertErrors.NativeOrders.BatchFillIncompleteError(
|
||||||
|
partiallyFilledOrder.getHash(),
|
||||||
|
partiallyFilledOrder.takerAmount.minus(partialFillAmount),
|
||||||
|
partiallyFilledOrder.takerAmount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('batchFillRfqOrders', () => {
|
||||||
|
async function assertExpectedFinalBalancesAsync(
|
||||||
|
orders: RfqOrder[],
|
||||||
|
takerTokenFillAmounts: BigNumber[] = orders.map(order => order.takerAmount),
|
||||||
|
takerTokenAlreadyFilledAmounts: BigNumber[] = orders.map(() => ZERO_AMOUNT),
|
||||||
|
): Promise<void> {
|
||||||
|
const { makerTokenFilledAmount, takerTokenFilledAmount } = orders
|
||||||
|
.map((order, i) =>
|
||||||
|
computeRfqOrderFilledAmounts(order, takerTokenFillAmounts[i], takerTokenAlreadyFilledAmounts[i]),
|
||||||
|
)
|
||||||
|
.reduce((previous, current) => ({
|
||||||
|
makerTokenFilledAmount: previous.makerTokenFilledAmount.plus(current.makerTokenFilledAmount),
|
||||||
|
takerTokenFilledAmount: previous.takerTokenFilledAmount.plus(current.takerTokenFilledAmount),
|
||||||
|
}));
|
||||||
|
const makerBalance = await takerToken.balanceOf(maker).callAsync();
|
||||||
|
const takerBalance = await makerToken.balanceOf(taker).callAsync();
|
||||||
|
expect(makerBalance).to.bignumber.eq(takerTokenFilledAmount);
|
||||||
|
expect(takerBalance).to.bignumber.eq(makerTokenFilledAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Fully fills multiple orders', async () => {
|
||||||
|
const orders = [...new Array(3)].map(() => getTestRfqOrder());
|
||||||
|
const signatures = await Promise.all(
|
||||||
|
orders.map(order => order.getSignatureWithProviderAsync(env.provider)),
|
||||||
|
);
|
||||||
|
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||||
|
const tx = await feature
|
||||||
|
.batchFillRfqOrders(orders, signatures, orders.map(order => order.takerAmount), false)
|
||||||
|
.awaitTransactionSuccessAsync({ from: taker });
|
||||||
|
const [orderInfos] = await zeroEx.batchGetRfqOrderRelevantStates(orders, signatures).callAsync();
|
||||||
|
orderInfos.map((orderInfo, i) =>
|
||||||
|
assertOrderInfoEquals(orderInfo, {
|
||||||
|
status: OrderStatus.Filled,
|
||||||
|
orderHash: orders[i].getHash(),
|
||||||
|
takerTokenFilledAmount: orders[i].takerAmount,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
tx.logs,
|
||||||
|
orders.map(order => testUtils.createRfqOrderFilledEventArgs(order)),
|
||||||
|
IZeroExEvents.RfqOrderFilled,
|
||||||
|
);
|
||||||
|
return assertExpectedFinalBalancesAsync(orders);
|
||||||
|
});
|
||||||
|
it('Partially fills multiple orders', async () => {
|
||||||
|
const orders = [...new Array(3)].map(() => getTestRfqOrder());
|
||||||
|
const signatures = await Promise.all(
|
||||||
|
orders.map(order => order.getSignatureWithProviderAsync(env.provider)),
|
||||||
|
);
|
||||||
|
const fillAmounts = orders.map(order => getRandomPortion(order.takerAmount));
|
||||||
|
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||||
|
const tx = await feature
|
||||||
|
.batchFillRfqOrders(orders, signatures, fillAmounts, false)
|
||||||
|
.awaitTransactionSuccessAsync({ from: taker });
|
||||||
|
const [orderInfos] = await zeroEx.batchGetRfqOrderRelevantStates(orders, signatures).callAsync();
|
||||||
|
orderInfos.map((orderInfo, i) =>
|
||||||
|
assertOrderInfoEquals(orderInfo, {
|
||||||
|
status: OrderStatus.Fillable,
|
||||||
|
orderHash: orders[i].getHash(),
|
||||||
|
takerTokenFilledAmount: fillAmounts[i],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
tx.logs,
|
||||||
|
orders.map((order, i) => testUtils.createRfqOrderFilledEventArgs(order, fillAmounts[i])),
|
||||||
|
IZeroExEvents.RfqOrderFilled,
|
||||||
|
);
|
||||||
|
return assertExpectedFinalBalancesAsync(orders, fillAmounts);
|
||||||
|
});
|
||||||
|
it('Skips over unfillable orders', async () => {
|
||||||
|
const fillableOrders = [...new Array(3)].map(() => getTestRfqOrder());
|
||||||
|
const expiredOrder = getTestRfqOrder({ expiry: createExpiry(-1) });
|
||||||
|
const orders = [expiredOrder, ...fillableOrders];
|
||||||
|
const signatures = await Promise.all(
|
||||||
|
orders.map(order => order.getSignatureWithProviderAsync(env.provider)),
|
||||||
|
);
|
||||||
|
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||||
|
const tx = await feature
|
||||||
|
.batchFillRfqOrders(orders, signatures, orders.map(order => order.takerAmount), false)
|
||||||
|
.awaitTransactionSuccessAsync({ from: taker });
|
||||||
|
const [orderInfos] = await zeroEx.batchGetRfqOrderRelevantStates(orders, signatures).callAsync();
|
||||||
|
const [expiredOrderInfo, ...filledOrderInfos] = orderInfos;
|
||||||
|
assertOrderInfoEquals(expiredOrderInfo, {
|
||||||
|
status: OrderStatus.Expired,
|
||||||
|
orderHash: expiredOrder.getHash(),
|
||||||
|
takerTokenFilledAmount: ZERO_AMOUNT,
|
||||||
|
});
|
||||||
|
filledOrderInfos.map((orderInfo, i) =>
|
||||||
|
assertOrderInfoEquals(orderInfo, {
|
||||||
|
status: OrderStatus.Filled,
|
||||||
|
orderHash: fillableOrders[i].getHash(),
|
||||||
|
takerTokenFilledAmount: fillableOrders[i].takerAmount,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
tx.logs,
|
||||||
|
fillableOrders.map(order => testUtils.createRfqOrderFilledEventArgs(order)),
|
||||||
|
IZeroExEvents.RfqOrderFilled,
|
||||||
|
);
|
||||||
|
return assertExpectedFinalBalancesAsync(fillableOrders);
|
||||||
|
});
|
||||||
|
it('Fills multiple orders with revertIfIncomplete=true', async () => {
|
||||||
|
const orders = [...new Array(3)].map(() => getTestRfqOrder());
|
||||||
|
const signatures = await Promise.all(
|
||||||
|
orders.map(order => order.getSignatureWithProviderAsync(env.provider)),
|
||||||
|
);
|
||||||
|
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||||
|
const tx = await feature
|
||||||
|
.batchFillRfqOrders(orders, signatures, orders.map(order => order.takerAmount), true)
|
||||||
|
.awaitTransactionSuccessAsync({ from: taker });
|
||||||
|
const [orderInfos] = await zeroEx.batchGetRfqOrderRelevantStates(orders, signatures).callAsync();
|
||||||
|
orderInfos.map((orderInfo, i) =>
|
||||||
|
assertOrderInfoEquals(orderInfo, {
|
||||||
|
status: OrderStatus.Filled,
|
||||||
|
orderHash: orders[i].getHash(),
|
||||||
|
takerTokenFilledAmount: orders[i].takerAmount,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
tx.logs,
|
||||||
|
orders.map(order => testUtils.createRfqOrderFilledEventArgs(order)),
|
||||||
|
IZeroExEvents.RfqOrderFilled,
|
||||||
|
);
|
||||||
|
return assertExpectedFinalBalancesAsync(orders);
|
||||||
|
});
|
||||||
|
it('If revertIfIncomplete==true, reverts on an unfillable order', async () => {
|
||||||
|
const fillableOrders = [...new Array(3)].map(() => getTestRfqOrder());
|
||||||
|
const expiredOrder = getTestRfqOrder({ expiry: createExpiry(-1) });
|
||||||
|
const orders = [expiredOrder, ...fillableOrders];
|
||||||
|
const signatures = await Promise.all(
|
||||||
|
orders.map(order => order.getSignatureWithProviderAsync(env.provider)),
|
||||||
|
);
|
||||||
|
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||||
|
const tx = feature
|
||||||
|
.batchFillRfqOrders(orders, signatures, orders.map(order => order.takerAmount), true)
|
||||||
|
.awaitTransactionSuccessAsync({ from: taker });
|
||||||
|
return expect(tx).to.revertWith(
|
||||||
|
new RevertErrors.NativeOrders.BatchFillIncompleteError(
|
||||||
|
expiredOrder.getHash(),
|
||||||
|
ZERO_AMOUNT,
|
||||||
|
expiredOrder.takerAmount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('If revertIfIncomplete==true, reverts on an incomplete fill ', async () => {
|
||||||
|
const fillableOrders = [...new Array(3)].map(() => getTestRfqOrder());
|
||||||
|
const partiallyFilledOrder = getTestRfqOrder();
|
||||||
|
const partialFillAmount = getRandomPortion(partiallyFilledOrder.takerAmount);
|
||||||
|
await testUtils.fillRfqOrderAsync(partiallyFilledOrder, partialFillAmount);
|
||||||
|
const orders = [partiallyFilledOrder, ...fillableOrders];
|
||||||
|
const signatures = await Promise.all(
|
||||||
|
orders.map(order => order.getSignatureWithProviderAsync(env.provider)),
|
||||||
|
);
|
||||||
|
await testUtils.prepareBalancesForOrdersAsync(orders);
|
||||||
|
const tx = feature
|
||||||
|
.batchFillRfqOrders(orders, signatures, orders.map(order => order.takerAmount), true)
|
||||||
|
.awaitTransactionSuccessAsync({ from: taker });
|
||||||
|
return expect(tx).to.revertWith(
|
||||||
|
new RevertErrors.NativeOrders.BatchFillIncompleteError(
|
||||||
|
partiallyFilledOrder.getHash(),
|
||||||
|
partiallyFilledOrder.takerAmount.minus(partialFillAmount),
|
||||||
|
partiallyFilledOrder.takerAmount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
764
contracts/zero-ex/test/features/multiplex_test.ts
Normal file
764
contracts/zero-ex/test/features/multiplex_test.ts
Normal file
@ -0,0 +1,764 @@
|
|||||||
|
import {
|
||||||
|
artifacts as erc20Artifacts,
|
||||||
|
ERC20TokenContract,
|
||||||
|
WETH9Contract,
|
||||||
|
WETH9DepositEventArgs,
|
||||||
|
WETH9Events,
|
||||||
|
WETH9WithdrawalEventArgs,
|
||||||
|
} from '@0x/contracts-erc20';
|
||||||
|
import { blockchainTests, constants, expect, filterLogsToArguments, toBaseUnitAmount } from '@0x/contracts-test-utils';
|
||||||
|
import {
|
||||||
|
BridgeSource,
|
||||||
|
encodeFillQuoteTransformerData,
|
||||||
|
encodePayTakerTransformerData,
|
||||||
|
FillQuoteTransformerOrderType,
|
||||||
|
FillQuoteTransformerSide,
|
||||||
|
findTransformerNonce,
|
||||||
|
RfqOrder,
|
||||||
|
SIGNATURE_ABI,
|
||||||
|
} from '@0x/protocol-utils';
|
||||||
|
import { AbiEncoder, BigNumber, logUtils } from '@0x/utils';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { artifacts } from '../artifacts';
|
||||||
|
import { abis } from '../utils/abis';
|
||||||
|
import { getRandomRfqOrder } from '../utils/orders';
|
||||||
|
import {
|
||||||
|
BridgeAdapterBridgeFillEventArgs,
|
||||||
|
BridgeAdapterEvents,
|
||||||
|
IUniswapV2PairEvents,
|
||||||
|
IUniswapV2PairSwapEventArgs,
|
||||||
|
IZeroExContract,
|
||||||
|
IZeroExEvents,
|
||||||
|
IZeroExRfqOrderFilledEventArgs,
|
||||||
|
MultiplexFeatureContract,
|
||||||
|
MultiplexFeatureEvents,
|
||||||
|
MultiplexFeatureLiquidityProviderSwapEventArgs,
|
||||||
|
SimpleFunctionRegistryFeatureContract,
|
||||||
|
} from '../wrappers';
|
||||||
|
|
||||||
|
const HIGH_BIT = new BigNumber(2).pow(255);
|
||||||
|
function encodeFractionalFillAmount(frac: number): BigNumber {
|
||||||
|
return HIGH_BIT.plus(new BigNumber(frac).times('1e18').integerValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
const EP_GOVERNOR = '0x618f9c67ce7bf1a50afa1e7e0238422601b0ff6e';
|
||||||
|
const DAI_WALLET = '0xbe0eb53f46cd790cd13851d5eff43d12404d33e8';
|
||||||
|
const WETH_WALLET = '0x1e0447b19bb6ecfdae1e4ae1694b0c3659614e4e';
|
||||||
|
const USDC_WALLET = '0xbe0eb53f46cd790cd13851d5eff43d12404d33e8';
|
||||||
|
blockchainTests.configure({
|
||||||
|
fork: {
|
||||||
|
unlockedAccounts: [EP_GOVERNOR, DAI_WALLET, WETH_WALLET, USDC_WALLET],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface WrappedBatchCall {
|
||||||
|
selector: string;
|
||||||
|
sellAmount: BigNumber;
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockchainTests.fork.skip('Multiplex feature', env => {
|
||||||
|
const DAI_ADDRESS = '0x6b175474e89094c44da98b954eedeac495271d0f';
|
||||||
|
const dai = new ERC20TokenContract(DAI_ADDRESS, env.provider, env.txDefaults);
|
||||||
|
const ETH_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
|
||||||
|
const WETH_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
|
||||||
|
const weth = new WETH9Contract(WETH_ADDRESS, env.provider, env.txDefaults);
|
||||||
|
const USDC_ADDRESS = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
|
||||||
|
const USDT_ADDRESS = '0xdac17f958d2ee523a2206206994597c13d831ec7';
|
||||||
|
const usdt = new ERC20TokenContract(USDT_ADDRESS, env.provider, env.txDefaults);
|
||||||
|
const LON_ADDRESS = '0x0000000000095413afc295d19edeb1ad7b71c952';
|
||||||
|
const PLP_SANDBOX_ADDRESS = '0x407b4128e9ecad8769b2332312a9f655cb9f5f3a';
|
||||||
|
const WETH_DAI_PLP_ADDRESS = '0x1db681925786441ba82adefac7bf492089665ca0';
|
||||||
|
const WETH_USDC_PLP_ADDRESS = '0x8463c03c0c57ff19fa8b431e0d3a34e2df89888e';
|
||||||
|
const USDC_USDT_PLP_ADDRESS = '0xc340ef96449514cea4dfa11d847a06d7f03d437c';
|
||||||
|
const GREEDY_TOKENS_BLOOM_FILTER = '0x0000100800000480002c00401000000820000000000000020000001010800001';
|
||||||
|
const BALANCER_WETH_DAI = '0x8b6e6e7b5b3801fed2cafd4b22b8a16c2f2db21a';
|
||||||
|
const fqtNonce = findTransformerNonce(
|
||||||
|
'0xfa6282736af206cb4cfc5cb786d82aecdf1186f9',
|
||||||
|
'0x39dce47a67ad34344eab877eae3ef1fa2a1d50bb',
|
||||||
|
);
|
||||||
|
const payTakerNonce = findTransformerNonce(
|
||||||
|
'0x4638a7ebe75b911b995d0ec73a81e4f85f41f24e',
|
||||||
|
'0x39dce47a67ad34344eab877eae3ef1fa2a1d50bb',
|
||||||
|
);
|
||||||
|
|
||||||
|
let zeroEx: IZeroExContract;
|
||||||
|
let multiplex: MultiplexFeatureContract;
|
||||||
|
let rfqMaker: string;
|
||||||
|
let flashWalletAddress: string;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
const erc20Abis = _.mapValues(erc20Artifacts, v => v.compilerOutput.abi);
|
||||||
|
[rfqMaker] = await env.getAccountAddressesAsync();
|
||||||
|
zeroEx = new IZeroExContract('0xdef1c0ded9bec7f1a1670819833240f027b25eff', env.provider, env.txDefaults, {
|
||||||
|
...abis,
|
||||||
|
...erc20Abis,
|
||||||
|
});
|
||||||
|
flashWalletAddress = await zeroEx.getTransformWallet().callAsync();
|
||||||
|
const registry = new SimpleFunctionRegistryFeatureContract(zeroEx.address, env.provider, env.txDefaults, {
|
||||||
|
...abis,
|
||||||
|
...erc20Abis,
|
||||||
|
});
|
||||||
|
multiplex = new MultiplexFeatureContract(zeroEx.address, env.provider, env.txDefaults, {
|
||||||
|
...abis,
|
||||||
|
...erc20Abis,
|
||||||
|
});
|
||||||
|
const multiplexImpl = await MultiplexFeatureContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.MultiplexFeature,
|
||||||
|
env.provider,
|
||||||
|
env.txDefaults,
|
||||||
|
artifacts,
|
||||||
|
zeroEx.address,
|
||||||
|
WETH_ADDRESS,
|
||||||
|
PLP_SANDBOX_ADDRESS,
|
||||||
|
GREEDY_TOKENS_BLOOM_FILTER,
|
||||||
|
);
|
||||||
|
await registry
|
||||||
|
.extend(multiplex.getSelector('batchFill'), multiplexImpl.address)
|
||||||
|
.awaitTransactionSuccessAsync({ from: EP_GOVERNOR, gasPrice: 0 }, { shouldValidate: false });
|
||||||
|
await registry
|
||||||
|
.extend(multiplex.getSelector('multiHopFill'), multiplexImpl.address)
|
||||||
|
.awaitTransactionSuccessAsync({ from: EP_GOVERNOR, gasPrice: 0 }, { shouldValidate: false });
|
||||||
|
await dai
|
||||||
|
.approve(zeroEx.address, constants.MAX_UINT256)
|
||||||
|
.awaitTransactionSuccessAsync({ from: DAI_WALLET, gasPrice: 0 }, { shouldValidate: false });
|
||||||
|
await weth
|
||||||
|
.transfer(rfqMaker, toBaseUnitAmount(100))
|
||||||
|
.awaitTransactionSuccessAsync({ from: WETH_WALLET, gasPrice: 0 }, { shouldValidate: false });
|
||||||
|
await weth
|
||||||
|
.approve(zeroEx.address, constants.MAX_UINT256)
|
||||||
|
.awaitTransactionSuccessAsync({ from: rfqMaker, gasPrice: 0 }, { shouldValidate: false });
|
||||||
|
});
|
||||||
|
describe('batchFill', () => {
|
||||||
|
let rfqDataEncoder: AbiEncoder.DataType;
|
||||||
|
let uniswapCall: WrappedBatchCall;
|
||||||
|
let sushiswapCall: WrappedBatchCall;
|
||||||
|
let plpCall: WrappedBatchCall;
|
||||||
|
let rfqCall: WrappedBatchCall;
|
||||||
|
let rfqOrder: RfqOrder;
|
||||||
|
before(async () => {
|
||||||
|
rfqDataEncoder = AbiEncoder.create([
|
||||||
|
{ name: 'order', type: 'tuple', components: RfqOrder.STRUCT_ABI },
|
||||||
|
{ name: 'signature', type: 'tuple', components: SIGNATURE_ABI },
|
||||||
|
]);
|
||||||
|
rfqOrder = getRandomRfqOrder({
|
||||||
|
maker: rfqMaker,
|
||||||
|
verifyingContract: zeroEx.address,
|
||||||
|
chainId: 1,
|
||||||
|
takerToken: DAI_ADDRESS,
|
||||||
|
makerToken: WETH_ADDRESS,
|
||||||
|
makerAmount: toBaseUnitAmount(100),
|
||||||
|
takerAmount: toBaseUnitAmount(100),
|
||||||
|
txOrigin: DAI_WALLET,
|
||||||
|
});
|
||||||
|
rfqCall = {
|
||||||
|
selector: zeroEx.getSelector('_fillRfqOrder'),
|
||||||
|
sellAmount: toBaseUnitAmount(1),
|
||||||
|
data: rfqDataEncoder.encode({
|
||||||
|
order: rfqOrder,
|
||||||
|
signature: await rfqOrder.getSignatureWithProviderAsync(env.provider),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const uniswapDataEncoder = AbiEncoder.create([
|
||||||
|
{ name: 'tokens', type: 'address[]' },
|
||||||
|
{ name: 'isSushi', type: 'bool' },
|
||||||
|
]);
|
||||||
|
const plpDataEncoder = AbiEncoder.create([
|
||||||
|
{ name: 'provider', type: 'address' },
|
||||||
|
{ name: 'auxiliaryData', type: 'bytes' },
|
||||||
|
]);
|
||||||
|
uniswapCall = {
|
||||||
|
selector: multiplex.getSelector('_sellToUniswap'),
|
||||||
|
sellAmount: toBaseUnitAmount(1.01),
|
||||||
|
data: uniswapDataEncoder.encode({ tokens: [DAI_ADDRESS, WETH_ADDRESS], isSushi: false }),
|
||||||
|
};
|
||||||
|
sushiswapCall = {
|
||||||
|
selector: multiplex.getSelector('_sellToUniswap'),
|
||||||
|
sellAmount: toBaseUnitAmount(1.02),
|
||||||
|
data: uniswapDataEncoder.encode({ tokens: [DAI_ADDRESS, WETH_ADDRESS], isSushi: true }),
|
||||||
|
};
|
||||||
|
plpCall = {
|
||||||
|
selector: multiplex.getSelector('_sellToLiquidityProvider'),
|
||||||
|
sellAmount: toBaseUnitAmount(1.03),
|
||||||
|
data: plpDataEncoder.encode({
|
||||||
|
provider: WETH_DAI_PLP_ADDRESS,
|
||||||
|
auxiliaryData: constants.NULL_BYTES,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
it('MultiplexFeature.batchFill(RFQ, unused Uniswap fallback)', async () => {
|
||||||
|
const batchFillData = {
|
||||||
|
inputToken: DAI_ADDRESS,
|
||||||
|
outputToken: WETH_ADDRESS,
|
||||||
|
sellAmount: rfqCall.sellAmount,
|
||||||
|
calls: [rfqCall, uniswapCall],
|
||||||
|
};
|
||||||
|
const tx = await multiplex
|
||||||
|
.batchFill(batchFillData, constants.ZERO_AMOUNT)
|
||||||
|
.awaitTransactionSuccessAsync({ from: DAI_WALLET, gasPrice: 0 }, { shouldValidate: false });
|
||||||
|
logUtils.log(`${tx.gasUsed} gas used`);
|
||||||
|
|
||||||
|
const [rfqEvent] = filterLogsToArguments<IZeroExRfqOrderFilledEventArgs>(
|
||||||
|
tx.logs,
|
||||||
|
IZeroExEvents.RfqOrderFilled,
|
||||||
|
);
|
||||||
|
expect(rfqEvent.maker).to.equal(rfqMaker);
|
||||||
|
expect(rfqEvent.taker).to.equal(DAI_WALLET);
|
||||||
|
expect(rfqEvent.makerToken).to.equal(WETH_ADDRESS);
|
||||||
|
expect(rfqEvent.takerToken).to.equal(DAI_ADDRESS);
|
||||||
|
expect(rfqEvent.takerTokenFilledAmount).to.bignumber.equal(rfqCall.sellAmount);
|
||||||
|
expect(rfqEvent.makerTokenFilledAmount).to.bignumber.equal(rfqCall.sellAmount);
|
||||||
|
});
|
||||||
|
it('MultiplexFeature.batchFill(expired RFQ, Uniswap fallback)', async () => {
|
||||||
|
const expiredRfqOrder = getRandomRfqOrder({
|
||||||
|
maker: rfqMaker,
|
||||||
|
verifyingContract: zeroEx.address,
|
||||||
|
chainId: 1,
|
||||||
|
takerToken: DAI_ADDRESS,
|
||||||
|
makerToken: WETH_ADDRESS,
|
||||||
|
makerAmount: toBaseUnitAmount(100),
|
||||||
|
takerAmount: toBaseUnitAmount(100),
|
||||||
|
txOrigin: DAI_WALLET,
|
||||||
|
expiry: new BigNumber(0),
|
||||||
|
});
|
||||||
|
const expiredRfqCall = {
|
||||||
|
selector: zeroEx.getSelector('_fillRfqOrder'),
|
||||||
|
sellAmount: toBaseUnitAmount(1.23),
|
||||||
|
data: rfqDataEncoder.encode({
|
||||||
|
order: expiredRfqOrder,
|
||||||
|
signature: await expiredRfqOrder.getSignatureWithProviderAsync(env.provider),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const batchFillData = {
|
||||||
|
inputToken: DAI_ADDRESS,
|
||||||
|
outputToken: WETH_ADDRESS,
|
||||||
|
sellAmount: expiredRfqCall.sellAmount,
|
||||||
|
calls: [expiredRfqCall, uniswapCall],
|
||||||
|
};
|
||||||
|
const tx = await multiplex
|
||||||
|
.batchFill(batchFillData, constants.ZERO_AMOUNT)
|
||||||
|
.awaitTransactionSuccessAsync({ from: DAI_WALLET, gasPrice: 0 }, { shouldValidate: false });
|
||||||
|
logUtils.log(`${tx.gasUsed} gas used`);
|
||||||
|
const [uniswapEvent] = filterLogsToArguments<IUniswapV2PairSwapEventArgs>(
|
||||||
|
tx.logs,
|
||||||
|
IUniswapV2PairEvents.Swap,
|
||||||
|
);
|
||||||
|
expect(uniswapEvent.sender, 'Uniswap Swap event sender').to.equal(zeroEx.address);
|
||||||
|
expect(uniswapEvent.to, 'Uniswap Swap event to').to.equal(DAI_WALLET);
|
||||||
|
expect(
|
||||||
|
BigNumber.max(uniswapEvent.amount0In, uniswapEvent.amount1In),
|
||||||
|
'Uniswap Swap event input amount',
|
||||||
|
).to.bignumber.equal(uniswapCall.sellAmount);
|
||||||
|
expect(
|
||||||
|
BigNumber.max(uniswapEvent.amount0Out, uniswapEvent.amount1Out),
|
||||||
|
'Uniswap Swap event output amount',
|
||||||
|
).to.bignumber.gt(0);
|
||||||
|
});
|
||||||
|
it('MultiplexFeature.batchFill(expired RFQ, Balancer FQT fallback)', async () => {
|
||||||
|
const expiredRfqOrder = getRandomRfqOrder({
|
||||||
|
maker: rfqMaker,
|
||||||
|
verifyingContract: zeroEx.address,
|
||||||
|
chainId: 1,
|
||||||
|
takerToken: DAI_ADDRESS,
|
||||||
|
makerToken: WETH_ADDRESS,
|
||||||
|
makerAmount: toBaseUnitAmount(100),
|
||||||
|
takerAmount: toBaseUnitAmount(100),
|
||||||
|
txOrigin: DAI_WALLET,
|
||||||
|
expiry: new BigNumber(0),
|
||||||
|
});
|
||||||
|
const expiredRfqCall = {
|
||||||
|
selector: zeroEx.getSelector('_fillRfqOrder'),
|
||||||
|
sellAmount: toBaseUnitAmount(1.23),
|
||||||
|
data: rfqDataEncoder.encode({
|
||||||
|
order: expiredRfqOrder,
|
||||||
|
signature: await expiredRfqOrder.getSignatureWithProviderAsync(env.provider),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const poolEncoder = AbiEncoder.create([{ name: 'poolAddress', type: 'address' }]);
|
||||||
|
const fqtData = encodeFillQuoteTransformerData({
|
||||||
|
side: FillQuoteTransformerSide.Sell,
|
||||||
|
sellToken: DAI_ADDRESS,
|
||||||
|
buyToken: WETH_ADDRESS,
|
||||||
|
bridgeOrders: [
|
||||||
|
{
|
||||||
|
source: BridgeSource.Balancer,
|
||||||
|
takerTokenAmount: expiredRfqCall.sellAmount,
|
||||||
|
makerTokenAmount: expiredRfqCall.sellAmount,
|
||||||
|
bridgeData: poolEncoder.encode([BALANCER_WETH_DAI]),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
limitOrders: [],
|
||||||
|
rfqOrders: [],
|
||||||
|
fillSequence: [FillQuoteTransformerOrderType.Bridge],
|
||||||
|
fillAmount: expiredRfqCall.sellAmount,
|
||||||
|
refundReceiver: constants.NULL_ADDRESS,
|
||||||
|
});
|
||||||
|
const payTakerData = encodePayTakerTransformerData({
|
||||||
|
tokens: [WETH_ADDRESS],
|
||||||
|
amounts: [constants.MAX_UINT256],
|
||||||
|
});
|
||||||
|
const transformERC20Encoder = AbiEncoder.create([
|
||||||
|
{
|
||||||
|
name: 'transformations',
|
||||||
|
type: 'tuple[]',
|
||||||
|
components: [{ name: 'deploymentNonce', type: 'uint32' }, { name: 'data', type: 'bytes' }],
|
||||||
|
},
|
||||||
|
{ name: 'ethValue', type: 'uint256' },
|
||||||
|
]);
|
||||||
|
const balancerFqtCall = {
|
||||||
|
selector: zeroEx.getSelector('_transformERC20'),
|
||||||
|
sellAmount: expiredRfqCall.sellAmount,
|
||||||
|
data: transformERC20Encoder.encode({
|
||||||
|
transformations: [
|
||||||
|
{
|
||||||
|
deploymentNonce: fqtNonce,
|
||||||
|
data: fqtData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deploymentNonce: payTakerNonce,
|
||||||
|
data: payTakerData,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ethValue: constants.ZERO_AMOUNT,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const batchFillData = {
|
||||||
|
inputToken: DAI_ADDRESS,
|
||||||
|
outputToken: WETH_ADDRESS,
|
||||||
|
sellAmount: expiredRfqCall.sellAmount,
|
||||||
|
calls: [expiredRfqCall, balancerFqtCall],
|
||||||
|
};
|
||||||
|
const tx = await multiplex
|
||||||
|
.batchFill(batchFillData, constants.ZERO_AMOUNT)
|
||||||
|
.awaitTransactionSuccessAsync({ from: DAI_WALLET, gasPrice: 0 }, { shouldValidate: false });
|
||||||
|
logUtils.log(`${tx.gasUsed} gas used`);
|
||||||
|
const [bridgeFillEvent] = filterLogsToArguments<BridgeAdapterBridgeFillEventArgs>(
|
||||||
|
tx.logs,
|
||||||
|
BridgeAdapterEvents.BridgeFill,
|
||||||
|
);
|
||||||
|
expect(bridgeFillEvent.source).to.bignumber.equal(BridgeSource.Balancer);
|
||||||
|
expect(bridgeFillEvent.inputToken).to.equal(DAI_ADDRESS);
|
||||||
|
expect(bridgeFillEvent.outputToken).to.equal(WETH_ADDRESS);
|
||||||
|
expect(bridgeFillEvent.inputTokenAmount).to.bignumber.equal(expiredRfqCall.sellAmount);
|
||||||
|
expect(bridgeFillEvent.outputTokenAmount).to.bignumber.gt(0);
|
||||||
|
});
|
||||||
|
it('MultiplexFeature.batchFill(Sushiswap, PLP, Uniswap, RFQ)', async () => {
|
||||||
|
const batchFillData = {
|
||||||
|
inputToken: DAI_ADDRESS,
|
||||||
|
outputToken: WETH_ADDRESS,
|
||||||
|
sellAmount: BigNumber.sum(
|
||||||
|
sushiswapCall.sellAmount,
|
||||||
|
plpCall.sellAmount,
|
||||||
|
uniswapCall.sellAmount,
|
||||||
|
rfqCall.sellAmount,
|
||||||
|
),
|
||||||
|
calls: [sushiswapCall, plpCall, uniswapCall, rfqCall],
|
||||||
|
};
|
||||||
|
const tx = await multiplex
|
||||||
|
.batchFill(batchFillData, constants.ZERO_AMOUNT)
|
||||||
|
.awaitTransactionSuccessAsync({ from: DAI_WALLET, gasPrice: 0 }, { shouldValidate: false });
|
||||||
|
logUtils.log(`${tx.gasUsed} gas used`);
|
||||||
|
const [sushiswapEvent, uniswapEvent] = filterLogsToArguments<IUniswapV2PairSwapEventArgs>(
|
||||||
|
tx.logs,
|
||||||
|
IUniswapV2PairEvents.Swap,
|
||||||
|
);
|
||||||
|
expect(sushiswapEvent.sender, 'Sushiswap Swap event sender').to.equal(zeroEx.address);
|
||||||
|
expect(sushiswapEvent.to, 'Sushiswap Swap event to').to.equal(DAI_WALLET);
|
||||||
|
expect(
|
||||||
|
BigNumber.max(sushiswapEvent.amount0In, sushiswapEvent.amount1In),
|
||||||
|
'Sushiswap Swap event input amount',
|
||||||
|
).to.bignumber.equal(sushiswapCall.sellAmount);
|
||||||
|
expect(
|
||||||
|
BigNumber.max(sushiswapEvent.amount0Out, sushiswapEvent.amount1Out),
|
||||||
|
'Sushiswap Swap event output amount',
|
||||||
|
).to.bignumber.gt(0);
|
||||||
|
expect(uniswapEvent.sender, 'Uniswap Swap event sender').to.equal(zeroEx.address);
|
||||||
|
expect(uniswapEvent.to, 'Uniswap Swap event to').to.equal(DAI_WALLET);
|
||||||
|
expect(
|
||||||
|
BigNumber.max(uniswapEvent.amount0In, uniswapEvent.amount1In),
|
||||||
|
'Uniswap Swap event input amount',
|
||||||
|
).to.bignumber.equal(uniswapCall.sellAmount);
|
||||||
|
expect(
|
||||||
|
BigNumber.max(uniswapEvent.amount0Out, uniswapEvent.amount1Out),
|
||||||
|
'Uniswap Swap event output amount',
|
||||||
|
).to.bignumber.gt(0);
|
||||||
|
|
||||||
|
const [plpEvent] = filterLogsToArguments<MultiplexFeatureLiquidityProviderSwapEventArgs>(
|
||||||
|
tx.logs,
|
||||||
|
MultiplexFeatureEvents.LiquidityProviderSwap,
|
||||||
|
);
|
||||||
|
expect(plpEvent.inputToken, 'LiquidityProviderSwap event inputToken').to.equal(batchFillData.inputToken);
|
||||||
|
expect(plpEvent.outputToken, 'LiquidityProviderSwap event outputToken').to.equal(batchFillData.outputToken);
|
||||||
|
expect(plpEvent.inputTokenAmount, 'LiquidityProviderSwap event inputToken').to.bignumber.equal(
|
||||||
|
plpCall.sellAmount,
|
||||||
|
);
|
||||||
|
expect(plpEvent.outputTokenAmount, 'LiquidityProviderSwap event outputTokenAmount').to.bignumber.gt(0);
|
||||||
|
expect(plpEvent.provider, 'LiquidityProviderSwap event provider address').to.equal(WETH_DAI_PLP_ADDRESS);
|
||||||
|
expect(plpEvent.recipient, 'LiquidityProviderSwap event recipient address').to.equal(DAI_WALLET);
|
||||||
|
|
||||||
|
const [rfqEvent] = filterLogsToArguments<IZeroExRfqOrderFilledEventArgs>(
|
||||||
|
tx.logs,
|
||||||
|
IZeroExEvents.RfqOrderFilled,
|
||||||
|
);
|
||||||
|
expect(rfqEvent.maker).to.equal(rfqMaker);
|
||||||
|
expect(rfqEvent.taker).to.equal(DAI_WALLET);
|
||||||
|
expect(rfqEvent.makerToken).to.equal(WETH_ADDRESS);
|
||||||
|
expect(rfqEvent.takerToken).to.equal(DAI_ADDRESS);
|
||||||
|
expect(rfqEvent.takerTokenFilledAmount).to.bignumber.equal(rfqCall.sellAmount);
|
||||||
|
expect(rfqEvent.makerTokenFilledAmount).to.bignumber.equal(rfqCall.sellAmount);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('multiHopFill', () => {
|
||||||
|
let uniswapDataEncoder: AbiEncoder.DataType;
|
||||||
|
let plpDataEncoder: AbiEncoder.DataType;
|
||||||
|
let curveEncoder: AbiEncoder.DataType;
|
||||||
|
let transformERC20Encoder: AbiEncoder.DataType;
|
||||||
|
let batchFillEncoder: AbiEncoder.DataType;
|
||||||
|
let multiHopFillEncoder: AbiEncoder.DataType;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
uniswapDataEncoder = AbiEncoder.create([
|
||||||
|
{ name: 'tokens', type: 'address[]' },
|
||||||
|
{ name: 'isSushi', type: 'bool' },
|
||||||
|
]);
|
||||||
|
plpDataEncoder = AbiEncoder.create([
|
||||||
|
{ name: 'provider', type: 'address' },
|
||||||
|
{ name: 'auxiliaryData', type: 'bytes' },
|
||||||
|
]);
|
||||||
|
curveEncoder = AbiEncoder.create([
|
||||||
|
{ name: 'curveAddress', type: 'address' },
|
||||||
|
{ name: 'exchangeFunctionSelector', type: 'bytes4' },
|
||||||
|
{ name: 'fromTokenIdx', type: 'int128' },
|
||||||
|
{ name: 'toTokenIdx', type: 'int128' },
|
||||||
|
]);
|
||||||
|
transformERC20Encoder = AbiEncoder.create([
|
||||||
|
{
|
||||||
|
name: 'transformations',
|
||||||
|
type: 'tuple[]',
|
||||||
|
components: [{ name: 'deploymentNonce', type: 'uint32' }, { name: 'data', type: 'bytes' }],
|
||||||
|
},
|
||||||
|
{ name: 'ethValue', type: 'uint256' },
|
||||||
|
]);
|
||||||
|
batchFillEncoder = AbiEncoder.create([
|
||||||
|
{
|
||||||
|
name: 'calls',
|
||||||
|
type: 'tuple[]',
|
||||||
|
components: [
|
||||||
|
{ name: 'selector', type: 'bytes4' },
|
||||||
|
{ name: 'sellAmount', type: 'uint256' },
|
||||||
|
{ name: 'data', type: 'bytes' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: 'ethValue', type: 'uint256' },
|
||||||
|
]);
|
||||||
|
multiHopFillEncoder = AbiEncoder.create([
|
||||||
|
{ name: 'tokens', type: 'address[]' },
|
||||||
|
{
|
||||||
|
name: 'calls',
|
||||||
|
type: 'tuple[]',
|
||||||
|
components: [{ name: 'selector', type: 'bytes4' }, { name: 'data', type: 'bytes' }],
|
||||||
|
},
|
||||||
|
{ name: 'ethValue', type: 'uint256' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('MultiplexFeature.multiHopFill(DAI ––Curve––> USDC ––Uni––> WETH ––unwrap––> ETH)', async () => {
|
||||||
|
const sellAmount = toBaseUnitAmount(1000000); // 1M DAI
|
||||||
|
const fqtData = encodeFillQuoteTransformerData({
|
||||||
|
side: FillQuoteTransformerSide.Sell,
|
||||||
|
sellToken: DAI_ADDRESS,
|
||||||
|
buyToken: USDC_ADDRESS,
|
||||||
|
bridgeOrders: [
|
||||||
|
{
|
||||||
|
source: BridgeSource.Curve,
|
||||||
|
takerTokenAmount: sellAmount,
|
||||||
|
makerTokenAmount: sellAmount,
|
||||||
|
bridgeData: curveEncoder.encode([
|
||||||
|
'0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7', // 3-pool
|
||||||
|
'0x3df02124', // `exchange` selector
|
||||||
|
0, // DAI
|
||||||
|
1, // USDC
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
limitOrders: [],
|
||||||
|
rfqOrders: [],
|
||||||
|
fillSequence: [FillQuoteTransformerOrderType.Bridge],
|
||||||
|
fillAmount: sellAmount,
|
||||||
|
refundReceiver: constants.NULL_ADDRESS,
|
||||||
|
});
|
||||||
|
const payTakerData = encodePayTakerTransformerData({
|
||||||
|
tokens: [USDC_ADDRESS],
|
||||||
|
amounts: [constants.MAX_UINT256],
|
||||||
|
});
|
||||||
|
const curveFqtCall = {
|
||||||
|
selector: zeroEx.getSelector('_transformERC20'),
|
||||||
|
sellAmount,
|
||||||
|
data: transformERC20Encoder.encode({
|
||||||
|
transformations: [
|
||||||
|
{
|
||||||
|
deploymentNonce: fqtNonce,
|
||||||
|
data: fqtData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deploymentNonce: payTakerNonce,
|
||||||
|
data: payTakerData,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ethValue: constants.ZERO_AMOUNT,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const uniswapCall = {
|
||||||
|
selector: multiplex.getSelector('_sellToUniswap'),
|
||||||
|
data: uniswapDataEncoder.encode({ tokens: [USDC_ADDRESS, WETH_ADDRESS], isSushi: false }),
|
||||||
|
};
|
||||||
|
const unwrapEthCall = {
|
||||||
|
selector: weth.getSelector('withdraw'),
|
||||||
|
data: constants.NULL_BYTES,
|
||||||
|
};
|
||||||
|
const multiHopFillData = {
|
||||||
|
tokens: [DAI_ADDRESS, USDC_ADDRESS, WETH_ADDRESS, ETH_TOKEN_ADDRESS],
|
||||||
|
sellAmount,
|
||||||
|
calls: [curveFqtCall, uniswapCall, unwrapEthCall],
|
||||||
|
};
|
||||||
|
const tx = await multiplex
|
||||||
|
.multiHopFill(multiHopFillData, constants.ZERO_AMOUNT)
|
||||||
|
.awaitTransactionSuccessAsync({ from: DAI_WALLET, gasPrice: 0 }, { shouldValidate: false });
|
||||||
|
logUtils.log(`${tx.gasUsed} gas used`);
|
||||||
|
const [bridgeFillEvent] = filterLogsToArguments<BridgeAdapterBridgeFillEventArgs>(
|
||||||
|
tx.logs,
|
||||||
|
BridgeAdapterEvents.BridgeFill,
|
||||||
|
);
|
||||||
|
expect(bridgeFillEvent.source).to.bignumber.equal(BridgeSource.Curve);
|
||||||
|
expect(bridgeFillEvent.inputToken).to.equal(DAI_ADDRESS);
|
||||||
|
expect(bridgeFillEvent.outputToken).to.equal(USDC_ADDRESS);
|
||||||
|
expect(bridgeFillEvent.inputTokenAmount).to.bignumber.equal(sellAmount);
|
||||||
|
expect(bridgeFillEvent.outputTokenAmount).to.bignumber.gt(0);
|
||||||
|
const [uniswapEvent] = filterLogsToArguments<IUniswapV2PairSwapEventArgs>(
|
||||||
|
tx.logs,
|
||||||
|
IUniswapV2PairEvents.Swap,
|
||||||
|
);
|
||||||
|
expect(uniswapEvent.sender, 'Uniswap Swap event sender').to.equal(zeroEx.address);
|
||||||
|
expect(uniswapEvent.to, 'Uniswap Swap event to').to.equal(zeroEx.address);
|
||||||
|
const uniswapInputAmount = BigNumber.max(uniswapEvent.amount0In, uniswapEvent.amount1In);
|
||||||
|
expect(uniswapInputAmount, 'Uniswap Swap event input amount').to.bignumber.equal(
|
||||||
|
bridgeFillEvent.outputTokenAmount,
|
||||||
|
);
|
||||||
|
const uniswapOutputAmount = BigNumber.max(uniswapEvent.amount0Out, uniswapEvent.amount1Out);
|
||||||
|
expect(uniswapOutputAmount, 'Uniswap Swap event output amount').to.bignumber.gt(0);
|
||||||
|
const [wethWithdrawalEvent] = filterLogsToArguments<WETH9WithdrawalEventArgs>(
|
||||||
|
tx.logs,
|
||||||
|
WETH9Events.Withdrawal,
|
||||||
|
);
|
||||||
|
expect(wethWithdrawalEvent._owner, 'WETH Withdrawal event _owner').to.equal(zeroEx.address);
|
||||||
|
expect(wethWithdrawalEvent._value, 'WETH Withdrawal event _value').to.bignumber.equal(uniswapOutputAmount);
|
||||||
|
});
|
||||||
|
it('MultiplexFeature.multiHopFill(ETH ––wrap–-> WETH ––Uni––> USDC ––Curve––> DAI)', async () => {
|
||||||
|
const sellAmount = toBaseUnitAmount(1); // 1 ETH
|
||||||
|
const fqtData = encodeFillQuoteTransformerData({
|
||||||
|
side: FillQuoteTransformerSide.Sell,
|
||||||
|
sellToken: USDC_ADDRESS,
|
||||||
|
buyToken: DAI_ADDRESS,
|
||||||
|
bridgeOrders: [
|
||||||
|
{
|
||||||
|
source: BridgeSource.Curve,
|
||||||
|
takerTokenAmount: constants.MAX_UINT256,
|
||||||
|
makerTokenAmount: constants.MAX_UINT256,
|
||||||
|
bridgeData: curveEncoder.encode([
|
||||||
|
'0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7', // 3-pool
|
||||||
|
'0x3df02124', // `exchange` selector
|
||||||
|
1, // USDC
|
||||||
|
0, // DAI
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
limitOrders: [],
|
||||||
|
rfqOrders: [],
|
||||||
|
fillSequence: [FillQuoteTransformerOrderType.Bridge],
|
||||||
|
fillAmount: constants.MAX_UINT256,
|
||||||
|
refundReceiver: constants.NULL_ADDRESS,
|
||||||
|
});
|
||||||
|
const payTakerData = encodePayTakerTransformerData({
|
||||||
|
tokens: [DAI_ADDRESS],
|
||||||
|
amounts: [constants.MAX_UINT256],
|
||||||
|
});
|
||||||
|
const curveFqtCall = {
|
||||||
|
selector: zeroEx.getSelector('_transformERC20'),
|
||||||
|
data: transformERC20Encoder.encode({
|
||||||
|
transformations: [
|
||||||
|
{
|
||||||
|
deploymentNonce: fqtNonce,
|
||||||
|
data: fqtData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deploymentNonce: payTakerNonce,
|
||||||
|
data: payTakerData,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ethValue: constants.ZERO_AMOUNT,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const uniswapCall = {
|
||||||
|
selector: multiplex.getSelector('_sellToUniswap'),
|
||||||
|
data: uniswapDataEncoder.encode({ tokens: [WETH_ADDRESS, USDC_ADDRESS], isSushi: false }),
|
||||||
|
};
|
||||||
|
const wrapEthCall = {
|
||||||
|
selector: weth.getSelector('deposit'),
|
||||||
|
data: constants.NULL_BYTES,
|
||||||
|
};
|
||||||
|
const multiHopFillData = {
|
||||||
|
tokens: [ETH_TOKEN_ADDRESS, WETH_ADDRESS, USDC_ADDRESS, DAI_ADDRESS],
|
||||||
|
sellAmount,
|
||||||
|
calls: [wrapEthCall, uniswapCall, curveFqtCall],
|
||||||
|
};
|
||||||
|
const tx = await multiplex
|
||||||
|
.multiHopFill(multiHopFillData, constants.ZERO_AMOUNT)
|
||||||
|
.awaitTransactionSuccessAsync(
|
||||||
|
{ from: rfqMaker, gasPrice: 0, value: sellAmount },
|
||||||
|
{ shouldValidate: false },
|
||||||
|
);
|
||||||
|
logUtils.log(`${tx.gasUsed} gas used`);
|
||||||
|
|
||||||
|
const [wethDepositEvent] = filterLogsToArguments<WETH9DepositEventArgs>(tx.logs, WETH9Events.Deposit);
|
||||||
|
expect(wethDepositEvent._owner, 'WETH Deposit event _owner').to.equal(zeroEx.address);
|
||||||
|
expect(wethDepositEvent._value, 'WETH Deposit event _value').to.bignumber.equal(sellAmount);
|
||||||
|
|
||||||
|
const [uniswapEvent] = filterLogsToArguments<IUniswapV2PairSwapEventArgs>(
|
||||||
|
tx.logs,
|
||||||
|
IUniswapV2PairEvents.Swap,
|
||||||
|
);
|
||||||
|
expect(uniswapEvent.sender, 'Uniswap Swap event sender').to.equal(zeroEx.address);
|
||||||
|
expect(uniswapEvent.to, 'Uniswap Swap event to').to.equal(flashWalletAddress);
|
||||||
|
const uniswapInputAmount = BigNumber.max(uniswapEvent.amount0In, uniswapEvent.amount1In);
|
||||||
|
expect(uniswapInputAmount, 'Uniswap Swap event input amount').to.bignumber.equal(sellAmount);
|
||||||
|
const uniswapOutputAmount = BigNumber.max(uniswapEvent.amount0Out, uniswapEvent.amount1Out);
|
||||||
|
expect(uniswapOutputAmount, 'Uniswap Swap event output amount').to.bignumber.gt(0);
|
||||||
|
|
||||||
|
const [bridgeFillEvent] = filterLogsToArguments<BridgeAdapterBridgeFillEventArgs>(
|
||||||
|
tx.logs,
|
||||||
|
BridgeAdapterEvents.BridgeFill,
|
||||||
|
);
|
||||||
|
expect(bridgeFillEvent.source).to.bignumber.equal(BridgeSource.Curve);
|
||||||
|
expect(bridgeFillEvent.inputToken).to.equal(USDC_ADDRESS);
|
||||||
|
expect(bridgeFillEvent.outputToken).to.equal(DAI_ADDRESS);
|
||||||
|
expect(bridgeFillEvent.inputTokenAmount).to.bignumber.equal(uniswapOutputAmount);
|
||||||
|
expect(bridgeFillEvent.outputTokenAmount).to.bignumber.gt(0);
|
||||||
|
});
|
||||||
|
it.skip('MultiplexFeature.multiHopFill() complex scenario', async () => {
|
||||||
|
/*
|
||||||
|
|
||||||
|
/––––PLP–––> USDC
|
||||||
|
/ \
|
||||||
|
/ PLP
|
||||||
|
/––Uni (via USDC)–––\
|
||||||
|
/ V
|
||||||
|
ETH ––wrap––> WETH ––––Uni/Sushi–––> USDT ––Sushi––> LON
|
||||||
|
\ ^
|
||||||
|
––––––––––––––– Uni –––––––––––––––––/
|
||||||
|
*/
|
||||||
|
// Taker has to have approved the EP for the intermediate tokens :/
|
||||||
|
await weth
|
||||||
|
.approve(zeroEx.address, constants.MAX_UINT256)
|
||||||
|
.awaitTransactionSuccessAsync({ from: rfqMaker, gasPrice: 0 }, { shouldValidate: false });
|
||||||
|
await usdt
|
||||||
|
.approve(zeroEx.address, constants.MAX_UINT256)
|
||||||
|
.awaitTransactionSuccessAsync({ from: rfqMaker, gasPrice: 0 }, { shouldValidate: false });
|
||||||
|
|
||||||
|
const sellAmount = toBaseUnitAmount(1); // 1 ETH
|
||||||
|
const wethUsdcPlpCall = {
|
||||||
|
selector: multiplex.getSelector('_sellToLiquidityProvider'),
|
||||||
|
data: plpDataEncoder.encode({
|
||||||
|
provider: WETH_USDC_PLP_ADDRESS,
|
||||||
|
auxiliaryData: constants.NULL_BYTES,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const usdcUsdtPlpCall = {
|
||||||
|
selector: multiplex.getSelector('_sellToLiquidityProvider'),
|
||||||
|
data: plpDataEncoder.encode({
|
||||||
|
provider: USDC_USDT_PLP_ADDRESS,
|
||||||
|
auxiliaryData: constants.NULL_BYTES,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const wethUsdcUsdtMultiHopCall = {
|
||||||
|
selector: multiplex.getSelector('_multiHopFill'),
|
||||||
|
sellAmount: encodeFractionalFillAmount(0.25),
|
||||||
|
data: multiHopFillEncoder.encode({
|
||||||
|
tokens: [WETH_ADDRESS, USDC_ADDRESS, USDT_ADDRESS],
|
||||||
|
calls: [wethUsdcPlpCall, usdcUsdtPlpCall],
|
||||||
|
ethValue: constants.ZERO_AMOUNT,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const wethUsdcUsdtUniswapCall = {
|
||||||
|
selector: multiplex.getSelector('_sellToUniswap'),
|
||||||
|
sellAmount: encodeFractionalFillAmount(0.25),
|
||||||
|
data: uniswapDataEncoder.encode({ tokens: [WETH_ADDRESS, USDC_ADDRESS, USDT_ADDRESS], isSushi: false }),
|
||||||
|
};
|
||||||
|
const wethUsdtUniswapCall = {
|
||||||
|
selector: multiplex.getSelector('_sellToUniswap'),
|
||||||
|
sellAmount: encodeFractionalFillAmount(0.25),
|
||||||
|
data: uniswapDataEncoder.encode({ tokens: [WETH_ADDRESS, USDT_ADDRESS], isSushi: false }),
|
||||||
|
};
|
||||||
|
const wethUsdtSushiswapCall = {
|
||||||
|
selector: multiplex.getSelector('_sellToUniswap'),
|
||||||
|
sellAmount: encodeFractionalFillAmount(0.25),
|
||||||
|
data: uniswapDataEncoder.encode({ tokens: [WETH_ADDRESS, USDT_ADDRESS], isSushi: true }),
|
||||||
|
};
|
||||||
|
const wethUsdtBatchCall = {
|
||||||
|
selector: multiplex.getSelector('_batchFill'),
|
||||||
|
data: batchFillEncoder.encode({
|
||||||
|
calls: [
|
||||||
|
wethUsdcUsdtMultiHopCall,
|
||||||
|
wethUsdcUsdtUniswapCall,
|
||||||
|
wethUsdtUniswapCall,
|
||||||
|
wethUsdtSushiswapCall,
|
||||||
|
],
|
||||||
|
ethValue: constants.ZERO_AMOUNT,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const usdtLonSushiCall = {
|
||||||
|
selector: multiplex.getSelector('_sellToUniswap'),
|
||||||
|
data: uniswapDataEncoder.encode({ tokens: [USDT_ADDRESS, LON_ADDRESS], isSushi: true }),
|
||||||
|
};
|
||||||
|
const wethUsdtLonMultiHopCall = {
|
||||||
|
selector: multiplex.getSelector('_multiHopFill'),
|
||||||
|
sellAmount: encodeFractionalFillAmount(0.8),
|
||||||
|
data: multiHopFillEncoder.encode({
|
||||||
|
tokens: [WETH_ADDRESS, USDT_ADDRESS],
|
||||||
|
calls: [wethUsdtBatchCall, usdtLonSushiCall],
|
||||||
|
ethValue: constants.ZERO_AMOUNT,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const wethLonUniswapCall = {
|
||||||
|
selector: multiplex.getSelector('_sellToUniswap'),
|
||||||
|
sellAmount: encodeFractionalFillAmount(0.2),
|
||||||
|
data: uniswapDataEncoder.encode({ tokens: [WETH_ADDRESS, LON_ADDRESS], isSushi: false }),
|
||||||
|
};
|
||||||
|
|
||||||
|
const wethLonBatchFillCall = {
|
||||||
|
selector: multiplex.getSelector('_batchFill'),
|
||||||
|
data: batchFillEncoder.encode({
|
||||||
|
calls: [wethUsdtLonMultiHopCall, wethLonUniswapCall],
|
||||||
|
ethValue: constants.ZERO_AMOUNT,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const wrapEthCall = {
|
||||||
|
selector: weth.getSelector('deposit'),
|
||||||
|
data: constants.NULL_BYTES,
|
||||||
|
};
|
||||||
|
const multiHopFillData = {
|
||||||
|
tokens: [ETH_TOKEN_ADDRESS, WETH_ADDRESS, LON_ADDRESS],
|
||||||
|
sellAmount,
|
||||||
|
calls: [wrapEthCall, wethLonBatchFillCall],
|
||||||
|
};
|
||||||
|
const tx = await multiplex
|
||||||
|
.multiHopFill(multiHopFillData, constants.ZERO_AMOUNT)
|
||||||
|
.awaitTransactionSuccessAsync(
|
||||||
|
{ from: rfqMaker, gasPrice: 0, value: sellAmount },
|
||||||
|
{ shouldValidate: false },
|
||||||
|
);
|
||||||
|
logUtils.log(`${tx.gasUsed} gas used`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -3,25 +3,28 @@ import {
|
|||||||
constants,
|
constants,
|
||||||
describe,
|
describe,
|
||||||
expect,
|
expect,
|
||||||
|
getRandomPortion,
|
||||||
randomAddress,
|
randomAddress,
|
||||||
verifyEventsFromLogs,
|
verifyEventsFromLogs,
|
||||||
} from '@0x/contracts-test-utils';
|
} from '@0x/contracts-test-utils';
|
||||||
import {
|
import { LimitOrder, LimitOrderFields, OrderStatus, RevertErrors, RfqOrder, RfqOrderFields } from '@0x/protocol-utils';
|
||||||
LimitOrder,
|
|
||||||
LimitOrderFields,
|
|
||||||
OrderInfo,
|
|
||||||
OrderStatus,
|
|
||||||
RevertErrors,
|
|
||||||
RfqOrder,
|
|
||||||
RfqOrderFields,
|
|
||||||
} from '@0x/protocol-utils';
|
|
||||||
import { AnyRevertError, BigNumber } from '@0x/utils';
|
import { AnyRevertError, BigNumber } from '@0x/utils';
|
||||||
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||||
|
|
||||||
import { IZeroExContract, IZeroExEvents } from '../../src/wrappers';
|
import { IZeroExContract, IZeroExEvents } from '../../src/wrappers';
|
||||||
import { artifacts } from '../artifacts';
|
import { artifacts } from '../artifacts';
|
||||||
import { fullMigrateAsync } from '../utils/migration';
|
import { fullMigrateAsync } from '../utils/migration';
|
||||||
import { getRandomLimitOrder, getRandomRfqOrder } from '../utils/orders';
|
import {
|
||||||
|
assertOrderInfoEquals,
|
||||||
|
computeLimitOrderFilledAmounts,
|
||||||
|
computeRfqOrderFilledAmounts,
|
||||||
|
createExpiry,
|
||||||
|
getActualFillableTakerTokenAmount,
|
||||||
|
getFillableMakerTokenAmount,
|
||||||
|
getRandomLimitOrder,
|
||||||
|
getRandomRfqOrder,
|
||||||
|
NativeOrdersTestEnvironment,
|
||||||
|
} from '../utils/orders';
|
||||||
import { TestMintableERC20TokenContract, TestRfqOriginRegistrationContract } from '../wrappers';
|
import { TestMintableERC20TokenContract, TestRfqOriginRegistrationContract } from '../wrappers';
|
||||||
|
|
||||||
blockchainTests.resets('NativeOrdersFeature', env => {
|
blockchainTests.resets('NativeOrdersFeature', env => {
|
||||||
@ -39,6 +42,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
let takerToken: TestMintableERC20TokenContract;
|
let takerToken: TestMintableERC20TokenContract;
|
||||||
let wethToken: TestMintableERC20TokenContract;
|
let wethToken: TestMintableERC20TokenContract;
|
||||||
let testRfqOriginRegistration: TestRfqOriginRegistrationContract;
|
let testRfqOriginRegistration: TestRfqOriginRegistrationContract;
|
||||||
|
let testUtils: NativeOrdersTestEnvironment;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
let owner;
|
let owner;
|
||||||
@ -78,6 +82,16 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
env.txDefaults,
|
env.txDefaults,
|
||||||
artifacts,
|
artifacts,
|
||||||
);
|
);
|
||||||
|
testUtils = new NativeOrdersTestEnvironment(
|
||||||
|
maker,
|
||||||
|
taker,
|
||||||
|
makerToken,
|
||||||
|
takerToken,
|
||||||
|
zeroEx,
|
||||||
|
GAS_PRICE,
|
||||||
|
SINGLE_PROTOCOL_FEE,
|
||||||
|
env,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
function getTestLimitOrder(fields: Partial<LimitOrderFields> = {}): LimitOrder {
|
function getTestLimitOrder(fields: Partial<LimitOrderFields> = {}): LimitOrder {
|
||||||
@ -105,27 +119,6 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function prepareBalancesForOrderAsync(order: LimitOrder | RfqOrder, _taker: string = taker): Promise<void> {
|
|
||||||
await makerToken.mint(maker, order.makerAmount).awaitTransactionSuccessAsync();
|
|
||||||
if ('takerTokenFeeAmount' in order) {
|
|
||||||
await takerToken
|
|
||||||
.mint(_taker, order.takerAmount.plus(order.takerTokenFeeAmount))
|
|
||||||
.awaitTransactionSuccessAsync();
|
|
||||||
} else {
|
|
||||||
await takerToken.mint(_taker, order.takerAmount).awaitTransactionSuccessAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertOrderInfoEquals(actual: OrderInfo, expected: OrderInfo): void {
|
|
||||||
expect(actual.status).to.eq(expected.status);
|
|
||||||
expect(actual.orderHash).to.eq(expected.orderHash);
|
|
||||||
expect(actual.takerTokenFilledAmount).to.bignumber.eq(expected.takerTokenFilledAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createExpiry(deltaSeconds: number = 60): BigNumber {
|
|
||||||
return new BigNumber(Math.floor(Date.now() / 1000) + deltaSeconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('getProtocolFeeMultiplier()', () => {
|
describe('getProtocolFeeMultiplier()', () => {
|
||||||
it('returns the protocol fee multiplier', async () => {
|
it('returns the protocol fee multiplier', async () => {
|
||||||
const r = await zeroEx.getProtocolFeeMultiplier().callAsync();
|
const r = await zeroEx.getProtocolFeeMultiplier().callAsync();
|
||||||
@ -149,26 +142,6 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
async function fillLimitOrderAsync(
|
|
||||||
order: LimitOrder,
|
|
||||||
opts: Partial<{
|
|
||||||
fillAmount: BigNumber | number;
|
|
||||||
taker: string;
|
|
||||||
protocolFee?: BigNumber | number;
|
|
||||||
}> = {},
|
|
||||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
|
||||||
const { fillAmount, taker: _taker, protocolFee } = {
|
|
||||||
taker,
|
|
||||||
fillAmount: order.takerAmount,
|
|
||||||
...opts,
|
|
||||||
};
|
|
||||||
await prepareBalancesForOrderAsync(order, _taker);
|
|
||||||
const _protocolFee = protocolFee === undefined ? SINGLE_PROTOCOL_FEE : protocolFee;
|
|
||||||
return zeroEx
|
|
||||||
.fillLimitOrder(order, await order.getSignatureWithProviderAsync(env.provider), new BigNumber(fillAmount))
|
|
||||||
.awaitTransactionSuccessAsync({ from: _taker, value: _protocolFee });
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('getLimitOrderInfo()', () => {
|
describe('getLimitOrderInfo()', () => {
|
||||||
it('unfilled order', async () => {
|
it('unfilled order', async () => {
|
||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
@ -205,7 +178,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
const expiry = createExpiry(60);
|
const expiry = createExpiry(60);
|
||||||
const order = getTestLimitOrder({ expiry });
|
const order = getTestLimitOrder({ expiry });
|
||||||
// Fill the order first.
|
// Fill the order first.
|
||||||
await fillLimitOrderAsync(order);
|
await testUtils.fillLimitOrderAsync(order);
|
||||||
// Advance time to expire the order.
|
// Advance time to expire the order.
|
||||||
await env.web3Wrapper.increaseTimeAsync(61);
|
await env.web3Wrapper.increaseTimeAsync(61);
|
||||||
const info = await zeroEx.getLimitOrderInfo(order).callAsync();
|
const info = await zeroEx.getLimitOrderInfo(order).callAsync();
|
||||||
@ -219,7 +192,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
it('filled order', async () => {
|
it('filled order', async () => {
|
||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
// Fill the order first.
|
// Fill the order first.
|
||||||
await fillLimitOrderAsync(order);
|
await testUtils.fillLimitOrderAsync(order);
|
||||||
const info = await zeroEx.getLimitOrderInfo(order).callAsync();
|
const info = await zeroEx.getLimitOrderInfo(order).callAsync();
|
||||||
assertOrderInfoEquals(info, {
|
assertOrderInfoEquals(info, {
|
||||||
status: OrderStatus.Filled,
|
status: OrderStatus.Filled,
|
||||||
@ -232,7 +205,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
const fillAmount = order.takerAmount.minus(1);
|
const fillAmount = order.takerAmount.minus(1);
|
||||||
// Fill the order first.
|
// Fill the order first.
|
||||||
await fillLimitOrderAsync(order, { fillAmount });
|
await testUtils.fillLimitOrderAsync(order, { fillAmount });
|
||||||
const info = await zeroEx.getLimitOrderInfo(order).callAsync();
|
const info = await zeroEx.getLimitOrderInfo(order).callAsync();
|
||||||
assertOrderInfoEquals(info, {
|
assertOrderInfoEquals(info, {
|
||||||
status: OrderStatus.Fillable,
|
status: OrderStatus.Fillable,
|
||||||
@ -244,7 +217,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
it('filled then cancelled order', async () => {
|
it('filled then cancelled order', async () => {
|
||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
// Fill the order first.
|
// Fill the order first.
|
||||||
await fillLimitOrderAsync(order);
|
await testUtils.fillLimitOrderAsync(order);
|
||||||
await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
||||||
const info = await zeroEx.getLimitOrderInfo(order).callAsync();
|
const info = await zeroEx.getLimitOrderInfo(order).callAsync();
|
||||||
assertOrderInfoEquals(info, {
|
assertOrderInfoEquals(info, {
|
||||||
@ -258,7 +231,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
const fillAmount = order.takerAmount.minus(1);
|
const fillAmount = order.takerAmount.minus(1);
|
||||||
// Fill the order first.
|
// Fill the order first.
|
||||||
await fillLimitOrderAsync(order, { fillAmount });
|
await testUtils.fillLimitOrderAsync(order, { fillAmount });
|
||||||
await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
||||||
const info = await zeroEx.getLimitOrderInfo(order).callAsync();
|
const info = await zeroEx.getLimitOrderInfo(order).callAsync();
|
||||||
assertOrderInfoEquals(info, {
|
assertOrderInfoEquals(info, {
|
||||||
@ -269,17 +242,6 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
async function fillRfqOrderAsync(
|
|
||||||
order: RfqOrder,
|
|
||||||
fillAmount: BigNumber | number = order.takerAmount,
|
|
||||||
_taker: string = taker,
|
|
||||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
|
||||||
await prepareBalancesForOrderAsync(order, _taker);
|
|
||||||
return zeroEx
|
|
||||||
.fillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), new BigNumber(fillAmount))
|
|
||||||
.awaitTransactionSuccessAsync({ from: _taker });
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('getRfqOrderInfo()', () => {
|
describe('getRfqOrderInfo()', () => {
|
||||||
it('unfilled order', async () => {
|
it('unfilled order', async () => {
|
||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
@ -316,7 +278,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
it('filled then expired order', async () => {
|
it('filled then expired order', async () => {
|
||||||
const expiry = createExpiry(60);
|
const expiry = createExpiry(60);
|
||||||
const order = getTestRfqOrder({ expiry });
|
const order = getTestRfqOrder({ expiry });
|
||||||
await prepareBalancesForOrderAsync(order);
|
await testUtils.prepareBalancesForOrdersAsync([order]);
|
||||||
const sig = await order.getSignatureWithProviderAsync(env.provider);
|
const sig = await order.getSignatureWithProviderAsync(env.provider);
|
||||||
// Fill the order first.
|
// Fill the order first.
|
||||||
await zeroEx.fillRfqOrder(order, sig, order.takerAmount).awaitTransactionSuccessAsync({ from: taker });
|
await zeroEx.fillRfqOrder(order, sig, order.takerAmount).awaitTransactionSuccessAsync({ from: taker });
|
||||||
@ -333,7 +295,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
it('filled order', async () => {
|
it('filled order', async () => {
|
||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
// Fill the order first.
|
// Fill the order first.
|
||||||
await fillRfqOrderAsync(order, order.takerAmount, taker);
|
await testUtils.fillRfqOrderAsync(order, order.takerAmount, taker);
|
||||||
const info = await zeroEx.getRfqOrderInfo(order).callAsync();
|
const info = await zeroEx.getRfqOrderInfo(order).callAsync();
|
||||||
assertOrderInfoEquals(info, {
|
assertOrderInfoEquals(info, {
|
||||||
status: OrderStatus.Filled,
|
status: OrderStatus.Filled,
|
||||||
@ -346,7 +308,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
const fillAmount = order.takerAmount.minus(1);
|
const fillAmount = order.takerAmount.minus(1);
|
||||||
// Fill the order first.
|
// Fill the order first.
|
||||||
await fillRfqOrderAsync(order, fillAmount);
|
await testUtils.fillRfqOrderAsync(order, fillAmount);
|
||||||
const info = await zeroEx.getRfqOrderInfo(order).callAsync();
|
const info = await zeroEx.getRfqOrderInfo(order).callAsync();
|
||||||
assertOrderInfoEquals(info, {
|
assertOrderInfoEquals(info, {
|
||||||
status: OrderStatus.Fillable,
|
status: OrderStatus.Fillable,
|
||||||
@ -358,7 +320,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
it('filled then cancelled order', async () => {
|
it('filled then cancelled order', async () => {
|
||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
// Fill the order first.
|
// Fill the order first.
|
||||||
await fillRfqOrderAsync(order);
|
await testUtils.fillRfqOrderAsync(order);
|
||||||
await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
||||||
const info = await zeroEx.getRfqOrderInfo(order).callAsync();
|
const info = await zeroEx.getRfqOrderInfo(order).callAsync();
|
||||||
assertOrderInfoEquals(info, {
|
assertOrderInfoEquals(info, {
|
||||||
@ -372,7 +334,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
const fillAmount = order.takerAmount.minus(1);
|
const fillAmount = order.takerAmount.minus(1);
|
||||||
// Fill the order first.
|
// Fill the order first.
|
||||||
await fillRfqOrderAsync(order, fillAmount);
|
await testUtils.fillRfqOrderAsync(order, fillAmount);
|
||||||
await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
||||||
const info = await zeroEx.getRfqOrderInfo(order).callAsync();
|
const info = await zeroEx.getRfqOrderInfo(order).callAsync();
|
||||||
assertOrderInfoEquals(info, {
|
assertOrderInfoEquals(info, {
|
||||||
@ -408,7 +370,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
|
|
||||||
it('can cancel a fully filled order', async () => {
|
it('can cancel a fully filled order', async () => {
|
||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
await fillLimitOrderAsync(order);
|
await testUtils.fillLimitOrderAsync(order);
|
||||||
const receipt = await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
const receipt = await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
@ -421,7 +383,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
|
|
||||||
it('can cancel a partially filled order', async () => {
|
it('can cancel a partially filled order', async () => {
|
||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
await fillLimitOrderAsync(order, { fillAmount: order.takerAmount.minus(1) });
|
await testUtils.fillLimitOrderAsync(order, { fillAmount: order.takerAmount.minus(1) });
|
||||||
const receipt = await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
const receipt = await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
@ -482,7 +444,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
|
|
||||||
it('can cancel a fully filled order', async () => {
|
it('can cancel a fully filled order', async () => {
|
||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
await fillRfqOrderAsync(order);
|
await testUtils.fillRfqOrderAsync(order);
|
||||||
const receipt = await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
const receipt = await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
@ -495,7 +457,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
|
|
||||||
it('can cancel a partially filled order', async () => {
|
it('can cancel a partially filled order', async () => {
|
||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
await fillRfqOrderAsync(order, order.takerAmount.minus(1));
|
await testUtils.fillRfqOrderAsync(order, order.takerAmount.minus(1));
|
||||||
const receipt = await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
const receipt = await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
@ -747,63 +709,6 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
interface LimitOrderFilledAmounts {
|
|
||||||
makerTokenFilledAmount: BigNumber;
|
|
||||||
takerTokenFilledAmount: BigNumber;
|
|
||||||
takerTokenFeeFilledAmount: BigNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeLimitOrderFilledAmounts(
|
|
||||||
order: LimitOrder,
|
|
||||||
takerTokenFillAmount: BigNumber = order.takerAmount,
|
|
||||||
takerTokenAlreadyFilledAmount: BigNumber = ZERO_AMOUNT,
|
|
||||||
): LimitOrderFilledAmounts {
|
|
||||||
const fillAmount = BigNumber.min(
|
|
||||||
order.takerAmount,
|
|
||||||
takerTokenFillAmount,
|
|
||||||
order.takerAmount.minus(takerTokenAlreadyFilledAmount),
|
|
||||||
);
|
|
||||||
const makerTokenFilledAmount = fillAmount
|
|
||||||
.times(order.makerAmount)
|
|
||||||
.div(order.takerAmount)
|
|
||||||
.integerValue(BigNumber.ROUND_DOWN);
|
|
||||||
const takerTokenFeeFilledAmount = fillAmount
|
|
||||||
.times(order.takerTokenFeeAmount)
|
|
||||||
.div(order.takerAmount)
|
|
||||||
.integerValue(BigNumber.ROUND_DOWN);
|
|
||||||
return {
|
|
||||||
makerTokenFilledAmount,
|
|
||||||
takerTokenFilledAmount: fillAmount,
|
|
||||||
takerTokenFeeFilledAmount,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createLimitOrderFilledEventArgs(
|
|
||||||
order: LimitOrder,
|
|
||||||
takerTokenFillAmount: BigNumber = order.takerAmount,
|
|
||||||
takerTokenAlreadyFilledAmount: BigNumber = ZERO_AMOUNT,
|
|
||||||
): object {
|
|
||||||
const {
|
|
||||||
makerTokenFilledAmount,
|
|
||||||
takerTokenFilledAmount,
|
|
||||||
takerTokenFeeFilledAmount,
|
|
||||||
} = computeLimitOrderFilledAmounts(order, takerTokenFillAmount, takerTokenAlreadyFilledAmount);
|
|
||||||
const protocolFee = order.taker !== NULL_ADDRESS ? ZERO_AMOUNT : SINGLE_PROTOCOL_FEE;
|
|
||||||
return {
|
|
||||||
taker,
|
|
||||||
takerTokenFilledAmount,
|
|
||||||
makerTokenFilledAmount,
|
|
||||||
takerTokenFeeFilledAmount,
|
|
||||||
orderHash: order.getHash(),
|
|
||||||
maker: order.maker,
|
|
||||||
feeRecipient: order.feeRecipient,
|
|
||||||
makerToken: order.makerToken,
|
|
||||||
takerToken: order.takerToken,
|
|
||||||
protocolFeePaid: protocolFee,
|
|
||||||
pool: order.pool,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function assertExpectedFinalBalancesFromLimitOrderFillAsync(
|
async function assertExpectedFinalBalancesFromLimitOrderFillAsync(
|
||||||
order: LimitOrder,
|
order: LimitOrder,
|
||||||
opts: Partial<{
|
opts: Partial<{
|
||||||
@ -841,10 +746,10 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
describe('fillLimitOrder()', () => {
|
describe('fillLimitOrder()', () => {
|
||||||
it('can fully fill an order', async () => {
|
it('can fully fill an order', async () => {
|
||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
const receipt = await fillLimitOrderAsync(order);
|
const receipt = await testUtils.fillLimitOrderAsync(order);
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
[createLimitOrderFilledEventArgs(order)],
|
[testUtils.createLimitOrderFilledEventArgs(order)],
|
||||||
IZeroExEvents.LimitOrderFilled,
|
IZeroExEvents.LimitOrderFilled,
|
||||||
);
|
);
|
||||||
assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), {
|
assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), {
|
||||||
@ -858,10 +763,10 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
it('can partially fill an order', async () => {
|
it('can partially fill an order', async () => {
|
||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
const fillAmount = order.takerAmount.minus(1);
|
const fillAmount = order.takerAmount.minus(1);
|
||||||
const receipt = await fillLimitOrderAsync(order, { fillAmount });
|
const receipt = await testUtils.fillLimitOrderAsync(order, { fillAmount });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
[createLimitOrderFilledEventArgs(order, fillAmount)],
|
[testUtils.createLimitOrderFilledEventArgs(order, fillAmount)],
|
||||||
IZeroExEvents.LimitOrderFilled,
|
IZeroExEvents.LimitOrderFilled,
|
||||||
);
|
);
|
||||||
assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), {
|
assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), {
|
||||||
@ -869,24 +774,26 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
status: OrderStatus.Fillable,
|
status: OrderStatus.Fillable,
|
||||||
takerTokenFilledAmount: fillAmount,
|
takerTokenFilledAmount: fillAmount,
|
||||||
});
|
});
|
||||||
await assertExpectedFinalBalancesFromLimitOrderFillAsync(order, { takerTokenFillAmount: fillAmount });
|
await assertExpectedFinalBalancesFromLimitOrderFillAsync(order, {
|
||||||
|
takerTokenFillAmount: fillAmount,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can fully fill an order in two steps', async () => {
|
it('can fully fill an order in two steps', async () => {
|
||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
let fillAmount = order.takerAmount.dividedToIntegerBy(2);
|
let fillAmount = order.takerAmount.dividedToIntegerBy(2);
|
||||||
let receipt = await fillLimitOrderAsync(order, { fillAmount });
|
let receipt = await testUtils.fillLimitOrderAsync(order, { fillAmount });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
[createLimitOrderFilledEventArgs(order, fillAmount)],
|
[testUtils.createLimitOrderFilledEventArgs(order, fillAmount)],
|
||||||
IZeroExEvents.LimitOrderFilled,
|
IZeroExEvents.LimitOrderFilled,
|
||||||
);
|
);
|
||||||
const alreadyFilledAmount = fillAmount;
|
const alreadyFilledAmount = fillAmount;
|
||||||
fillAmount = order.takerAmount.minus(fillAmount);
|
fillAmount = order.takerAmount.minus(fillAmount);
|
||||||
receipt = await fillLimitOrderAsync(order, { fillAmount });
|
receipt = await testUtils.fillLimitOrderAsync(order, { fillAmount });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
[createLimitOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)],
|
[testUtils.createLimitOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)],
|
||||||
IZeroExEvents.LimitOrderFilled,
|
IZeroExEvents.LimitOrderFilled,
|
||||||
);
|
);
|
||||||
assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), {
|
assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), {
|
||||||
@ -899,10 +806,10 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
it('clamps fill amount to remaining available', async () => {
|
it('clamps fill amount to remaining available', async () => {
|
||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
const fillAmount = order.takerAmount.plus(1);
|
const fillAmount = order.takerAmount.plus(1);
|
||||||
const receipt = await fillLimitOrderAsync(order, { fillAmount });
|
const receipt = await testUtils.fillLimitOrderAsync(order, { fillAmount });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
[createLimitOrderFilledEventArgs(order, fillAmount)],
|
[testUtils.createLimitOrderFilledEventArgs(order, fillAmount)],
|
||||||
IZeroExEvents.LimitOrderFilled,
|
IZeroExEvents.LimitOrderFilled,
|
||||||
);
|
);
|
||||||
assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), {
|
assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), {
|
||||||
@ -910,24 +817,26 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
status: OrderStatus.Filled,
|
status: OrderStatus.Filled,
|
||||||
takerTokenFilledAmount: order.takerAmount,
|
takerTokenFilledAmount: order.takerAmount,
|
||||||
});
|
});
|
||||||
await assertExpectedFinalBalancesFromLimitOrderFillAsync(order, { takerTokenFillAmount: fillAmount });
|
await assertExpectedFinalBalancesFromLimitOrderFillAsync(order, {
|
||||||
|
takerTokenFillAmount: fillAmount,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clamps fill amount to remaining available in partial filled order', async () => {
|
it('clamps fill amount to remaining available in partial filled order', async () => {
|
||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
let fillAmount = order.takerAmount.dividedToIntegerBy(2);
|
let fillAmount = order.takerAmount.dividedToIntegerBy(2);
|
||||||
let receipt = await fillLimitOrderAsync(order, { fillAmount });
|
let receipt = await testUtils.fillLimitOrderAsync(order, { fillAmount });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
[createLimitOrderFilledEventArgs(order, fillAmount)],
|
[testUtils.createLimitOrderFilledEventArgs(order, fillAmount)],
|
||||||
IZeroExEvents.LimitOrderFilled,
|
IZeroExEvents.LimitOrderFilled,
|
||||||
);
|
);
|
||||||
const alreadyFilledAmount = fillAmount;
|
const alreadyFilledAmount = fillAmount;
|
||||||
fillAmount = order.takerAmount.minus(fillAmount).plus(1);
|
fillAmount = order.takerAmount.minus(fillAmount).plus(1);
|
||||||
receipt = await fillLimitOrderAsync(order, { fillAmount });
|
receipt = await testUtils.fillLimitOrderAsync(order, { fillAmount });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
[createLimitOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)],
|
[testUtils.createLimitOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)],
|
||||||
IZeroExEvents.LimitOrderFilled,
|
IZeroExEvents.LimitOrderFilled,
|
||||||
);
|
);
|
||||||
assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), {
|
assertOrderInfoEquals(await zeroEx.getLimitOrderInfo(order).callAsync(), {
|
||||||
@ -939,7 +848,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
|
|
||||||
it('cannot fill an expired order', async () => {
|
it('cannot fill an expired order', async () => {
|
||||||
const order = getTestLimitOrder({ expiry: createExpiry(-60) });
|
const order = getTestLimitOrder({ expiry: createExpiry(-60) });
|
||||||
const tx = fillLimitOrderAsync(order);
|
const tx = testUtils.fillLimitOrderAsync(order);
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Expired),
|
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Expired),
|
||||||
);
|
);
|
||||||
@ -948,7 +857,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
it('cannot fill a cancelled order', async () => {
|
it('cannot fill a cancelled order', async () => {
|
||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
await zeroEx.cancelLimitOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
||||||
const tx = fillLimitOrderAsync(order);
|
const tx = testUtils.fillLimitOrderAsync(order);
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Cancelled),
|
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Cancelled),
|
||||||
);
|
);
|
||||||
@ -959,7 +868,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
await zeroEx
|
await zeroEx
|
||||||
.cancelPairLimitOrders(makerToken.address, takerToken.address, order.salt.plus(1))
|
.cancelPairLimitOrders(makerToken.address, takerToken.address, order.salt.plus(1))
|
||||||
.awaitTransactionSuccessAsync({ from: maker });
|
.awaitTransactionSuccessAsync({ from: maker });
|
||||||
const tx = fillLimitOrderAsync(order);
|
const tx = testUtils.fillLimitOrderAsync(order);
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Cancelled),
|
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Cancelled),
|
||||||
);
|
);
|
||||||
@ -967,7 +876,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
|
|
||||||
it('non-taker cannot fill order', async () => {
|
it('non-taker cannot fill order', async () => {
|
||||||
const order = getTestLimitOrder({ taker });
|
const order = getTestLimitOrder({ taker });
|
||||||
const tx = fillLimitOrderAsync(order, { fillAmount: order.takerAmount, taker: notTaker });
|
const tx = testUtils.fillLimitOrderAsync(order, { fillAmount: order.takerAmount, taker: notTaker });
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new RevertErrors.NativeOrders.OrderNotFillableByTakerError(order.getHash(), notTaker, order.taker),
|
new RevertErrors.NativeOrders.OrderNotFillableByTakerError(order.getHash(), notTaker, order.taker),
|
||||||
);
|
);
|
||||||
@ -975,7 +884,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
|
|
||||||
it('non-sender cannot fill order', async () => {
|
it('non-sender cannot fill order', async () => {
|
||||||
const order = getTestLimitOrder({ sender: taker });
|
const order = getTestLimitOrder({ sender: taker });
|
||||||
const tx = fillLimitOrderAsync(order, { fillAmount: order.takerAmount, taker: notTaker });
|
const tx = testUtils.fillLimitOrderAsync(order, { fillAmount: order.takerAmount, taker: notTaker });
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new RevertErrors.NativeOrders.OrderNotFillableBySenderError(order.getHash(), notTaker, order.sender),
|
new RevertErrors.NativeOrders.OrderNotFillableBySenderError(order.getHash(), notTaker, order.sender),
|
||||||
);
|
);
|
||||||
@ -985,7 +894,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
// Overwrite chainId to result in a different hash and therefore different
|
// Overwrite chainId to result in a different hash and therefore different
|
||||||
// signature.
|
// signature.
|
||||||
const tx = fillLimitOrderAsync(order.clone({ chainId: 1234 }));
|
const tx = testUtils.fillLimitOrderAsync(order.clone({ chainId: 1234 }));
|
||||||
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),
|
||||||
);
|
);
|
||||||
@ -993,7 +902,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
|
|
||||||
it('fails if no protocol fee attached', async () => {
|
it('fails if no protocol fee attached', async () => {
|
||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
await prepareBalancesForOrderAsync(order);
|
await testUtils.prepareBalancesForOrdersAsync([order]);
|
||||||
const tx = zeroEx
|
const tx = zeroEx
|
||||||
.fillLimitOrder(
|
.fillLimitOrder(
|
||||||
order,
|
order,
|
||||||
@ -1008,62 +917,24 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
|
|
||||||
it('refunds excess protocol fee', async () => {
|
it('refunds excess protocol fee', async () => {
|
||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
const receipt = await fillLimitOrderAsync(order, { protocolFee: SINGLE_PROTOCOL_FEE.plus(1) });
|
const receipt = await testUtils.fillLimitOrderAsync(order, { protocolFee: SINGLE_PROTOCOL_FEE.plus(1) });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
[createLimitOrderFilledEventArgs(order)],
|
[testUtils.createLimitOrderFilledEventArgs(order)],
|
||||||
IZeroExEvents.LimitOrderFilled,
|
IZeroExEvents.LimitOrderFilled,
|
||||||
);
|
);
|
||||||
await assertExpectedFinalBalancesFromLimitOrderFillAsync(order, { receipt });
|
await assertExpectedFinalBalancesFromLimitOrderFillAsync(order, { receipt });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
interface RfqOrderFilledAmounts {
|
describe('registerAllowedRfqOrigins()', () => {
|
||||||
makerTokenFilledAmount: BigNumber;
|
it('cannot register through a contract', async () => {
|
||||||
takerTokenFilledAmount: BigNumber;
|
const tx = testRfqOriginRegistration
|
||||||
}
|
.registerAllowedRfqOrigins(zeroEx.address, [], true)
|
||||||
|
.awaitTransactionSuccessAsync();
|
||||||
function computeRfqOrderFilledAmounts(
|
expect(tx).to.revertWith('NativeOrdersFeature/NO_CONTRACT_ORIGINS');
|
||||||
order: RfqOrder,
|
});
|
||||||
takerTokenFillAmount: BigNumber = order.takerAmount,
|
});
|
||||||
takerTokenAlreadyFilledAmount: BigNumber = ZERO_AMOUNT,
|
|
||||||
): RfqOrderFilledAmounts {
|
|
||||||
const fillAmount = BigNumber.min(
|
|
||||||
order.takerAmount,
|
|
||||||
takerTokenFillAmount,
|
|
||||||
order.takerAmount.minus(takerTokenAlreadyFilledAmount),
|
|
||||||
);
|
|
||||||
const makerTokenFilledAmount = fillAmount
|
|
||||||
.times(order.makerAmount)
|
|
||||||
.div(order.takerAmount)
|
|
||||||
.integerValue(BigNumber.ROUND_DOWN);
|
|
||||||
return {
|
|
||||||
makerTokenFilledAmount,
|
|
||||||
takerTokenFilledAmount: fillAmount,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createRfqOrderFilledEventArgs(
|
|
||||||
order: RfqOrder,
|
|
||||||
takerTokenFillAmount: BigNumber = order.takerAmount,
|
|
||||||
takerTokenAlreadyFilledAmount: BigNumber = ZERO_AMOUNT,
|
|
||||||
): object {
|
|
||||||
const { makerTokenFilledAmount, takerTokenFilledAmount } = computeRfqOrderFilledAmounts(
|
|
||||||
order,
|
|
||||||
takerTokenFillAmount,
|
|
||||||
takerTokenAlreadyFilledAmount,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
taker,
|
|
||||||
takerTokenFilledAmount,
|
|
||||||
makerTokenFilledAmount,
|
|
||||||
orderHash: order.getHash(),
|
|
||||||
maker: order.maker,
|
|
||||||
makerToken: order.makerToken,
|
|
||||||
takerToken: order.takerToken,
|
|
||||||
pool: order.pool,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function assertExpectedFinalBalancesFromRfqOrderFillAsync(
|
async function assertExpectedFinalBalancesFromRfqOrderFillAsync(
|
||||||
order: RfqOrder,
|
order: RfqOrder,
|
||||||
@ -1081,20 +952,15 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
expect(takerBalance).to.bignumber.eq(makerTokenFilledAmount);
|
expect(takerBalance).to.bignumber.eq(makerTokenFilledAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('registerAllowedRfqOrigins()', () => {
|
|
||||||
it('cannot register through a contract', async () => {
|
|
||||||
const tx = testRfqOriginRegistration
|
|
||||||
.registerAllowedRfqOrigins(zeroEx.address, [], true)
|
|
||||||
.awaitTransactionSuccessAsync();
|
|
||||||
expect(tx).to.revertWith('NativeOrdersFeature/NO_CONTRACT_ORIGINS');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('fillRfqOrder()', () => {
|
describe('fillRfqOrder()', () => {
|
||||||
it('can fully fill an order', async () => {
|
it('can fully fill an order', async () => {
|
||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
const receipt = await fillRfqOrderAsync(order);
|
const receipt = await testUtils.fillRfqOrderAsync(order);
|
||||||
verifyEventsFromLogs(receipt.logs, [createRfqOrderFilledEventArgs(order)], IZeroExEvents.RfqOrderFilled);
|
verifyEventsFromLogs(
|
||||||
|
receipt.logs,
|
||||||
|
[testUtils.createRfqOrderFilledEventArgs(order)],
|
||||||
|
IZeroExEvents.RfqOrderFilled,
|
||||||
|
);
|
||||||
assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), {
|
assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), {
|
||||||
orderHash: order.getHash(),
|
orderHash: order.getHash(),
|
||||||
status: OrderStatus.Filled,
|
status: OrderStatus.Filled,
|
||||||
@ -1106,10 +972,10 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
it('can partially fill an order', async () => {
|
it('can partially fill an order', async () => {
|
||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
const fillAmount = order.takerAmount.minus(1);
|
const fillAmount = order.takerAmount.minus(1);
|
||||||
const receipt = await fillRfqOrderAsync(order, fillAmount);
|
const receipt = await testUtils.fillRfqOrderAsync(order, fillAmount);
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
[createRfqOrderFilledEventArgs(order, fillAmount)],
|
[testUtils.createRfqOrderFilledEventArgs(order, fillAmount)],
|
||||||
IZeroExEvents.RfqOrderFilled,
|
IZeroExEvents.RfqOrderFilled,
|
||||||
);
|
);
|
||||||
assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), {
|
assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), {
|
||||||
@ -1123,18 +989,18 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
it('can fully fill an order in two steps', async () => {
|
it('can fully fill an order in two steps', async () => {
|
||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
let fillAmount = order.takerAmount.dividedToIntegerBy(2);
|
let fillAmount = order.takerAmount.dividedToIntegerBy(2);
|
||||||
let receipt = await fillRfqOrderAsync(order, fillAmount);
|
let receipt = await testUtils.fillRfqOrderAsync(order, fillAmount);
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
[createRfqOrderFilledEventArgs(order, fillAmount)],
|
[testUtils.createRfqOrderFilledEventArgs(order, fillAmount)],
|
||||||
IZeroExEvents.RfqOrderFilled,
|
IZeroExEvents.RfqOrderFilled,
|
||||||
);
|
);
|
||||||
const alreadyFilledAmount = fillAmount;
|
const alreadyFilledAmount = fillAmount;
|
||||||
fillAmount = order.takerAmount.minus(fillAmount);
|
fillAmount = order.takerAmount.minus(fillAmount);
|
||||||
receipt = await fillRfqOrderAsync(order, fillAmount);
|
receipt = await testUtils.fillRfqOrderAsync(order, fillAmount);
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
[createRfqOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)],
|
[testUtils.createRfqOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)],
|
||||||
IZeroExEvents.RfqOrderFilled,
|
IZeroExEvents.RfqOrderFilled,
|
||||||
);
|
);
|
||||||
assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), {
|
assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), {
|
||||||
@ -1147,10 +1013,10 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
it('clamps fill amount to remaining available', async () => {
|
it('clamps fill amount to remaining available', async () => {
|
||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
const fillAmount = order.takerAmount.plus(1);
|
const fillAmount = order.takerAmount.plus(1);
|
||||||
const receipt = await fillRfqOrderAsync(order, fillAmount);
|
const receipt = await testUtils.fillRfqOrderAsync(order, fillAmount);
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
[createRfqOrderFilledEventArgs(order, fillAmount)],
|
[testUtils.createRfqOrderFilledEventArgs(order, fillAmount)],
|
||||||
IZeroExEvents.RfqOrderFilled,
|
IZeroExEvents.RfqOrderFilled,
|
||||||
);
|
);
|
||||||
assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), {
|
assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), {
|
||||||
@ -1164,18 +1030,18 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
it('clamps fill amount to remaining available in partial filled order', async () => {
|
it('clamps fill amount to remaining available in partial filled order', async () => {
|
||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
let fillAmount = order.takerAmount.dividedToIntegerBy(2);
|
let fillAmount = order.takerAmount.dividedToIntegerBy(2);
|
||||||
let receipt = await fillRfqOrderAsync(order, fillAmount);
|
let receipt = await testUtils.fillRfqOrderAsync(order, fillAmount);
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
[createRfqOrderFilledEventArgs(order, fillAmount)],
|
[testUtils.createRfqOrderFilledEventArgs(order, fillAmount)],
|
||||||
IZeroExEvents.RfqOrderFilled,
|
IZeroExEvents.RfqOrderFilled,
|
||||||
);
|
);
|
||||||
const alreadyFilledAmount = fillAmount;
|
const alreadyFilledAmount = fillAmount;
|
||||||
fillAmount = order.takerAmount.minus(fillAmount).plus(1);
|
fillAmount = order.takerAmount.minus(fillAmount).plus(1);
|
||||||
receipt = await fillRfqOrderAsync(order, fillAmount);
|
receipt = await testUtils.fillRfqOrderAsync(order, fillAmount);
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
[createRfqOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)],
|
[testUtils.createRfqOrderFilledEventArgs(order, fillAmount, alreadyFilledAmount)],
|
||||||
IZeroExEvents.RfqOrderFilled,
|
IZeroExEvents.RfqOrderFilled,
|
||||||
);
|
);
|
||||||
assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), {
|
assertOrderInfoEquals(await zeroEx.getRfqOrderInfo(order).callAsync(), {
|
||||||
@ -1187,7 +1053,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
|
|
||||||
it('cannot fill an order with wrong tx.origin', async () => {
|
it('cannot fill an order with wrong tx.origin', async () => {
|
||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
const tx = fillRfqOrderAsync(order, order.takerAmount, notTaker);
|
const tx = testUtils.fillRfqOrderAsync(order, order.takerAmount, notTaker);
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new RevertErrors.NativeOrders.OrderNotFillableByOriginError(order.getHash(), notTaker, taker),
|
new RevertErrors.NativeOrders.OrderNotFillableByOriginError(order.getHash(), notTaker, taker),
|
||||||
);
|
);
|
||||||
@ -1210,7 +1076,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
],
|
],
|
||||||
IZeroExEvents.RfqOrderOriginsAllowed,
|
IZeroExEvents.RfqOrderOriginsAllowed,
|
||||||
);
|
);
|
||||||
return fillRfqOrderAsync(order, order.takerAmount, notTaker);
|
return testUtils.fillRfqOrderAsync(order, order.takerAmount, notTaker);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cannot fill an order with registered then unregistered tx.origin', async () => {
|
it('cannot fill an order with registered then unregistered tx.origin', async () => {
|
||||||
@ -1232,7 +1098,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
IZeroExEvents.RfqOrderOriginsAllowed,
|
IZeroExEvents.RfqOrderOriginsAllowed,
|
||||||
);
|
);
|
||||||
|
|
||||||
const tx = fillRfqOrderAsync(order, order.takerAmount, notTaker);
|
const tx = testUtils.fillRfqOrderAsync(order, order.takerAmount, notTaker);
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new RevertErrors.NativeOrders.OrderNotFillableByOriginError(order.getHash(), notTaker, taker),
|
new RevertErrors.NativeOrders.OrderNotFillableByOriginError(order.getHash(), notTaker, taker),
|
||||||
);
|
);
|
||||||
@ -1240,7 +1106,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
|
|
||||||
it('cannot fill an order with a zero tx.origin', async () => {
|
it('cannot fill an order with a zero tx.origin', async () => {
|
||||||
const order = getTestRfqOrder({ txOrigin: NULL_ADDRESS });
|
const order = getTestRfqOrder({ txOrigin: NULL_ADDRESS });
|
||||||
const tx = fillRfqOrderAsync(order, order.takerAmount, notTaker);
|
const tx = testUtils.fillRfqOrderAsync(order, order.takerAmount, notTaker);
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Invalid),
|
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Invalid),
|
||||||
);
|
);
|
||||||
@ -1248,7 +1114,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
|
|
||||||
it('non-taker cannot fill order', async () => {
|
it('non-taker cannot fill order', async () => {
|
||||||
const order = getTestRfqOrder({ taker, txOrigin: notTaker });
|
const order = getTestRfqOrder({ taker, txOrigin: notTaker });
|
||||||
const tx = fillRfqOrderAsync(order, order.takerAmount, notTaker);
|
const tx = testUtils.fillRfqOrderAsync(order, order.takerAmount, notTaker);
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new RevertErrors.NativeOrders.OrderNotFillableByTakerError(order.getHash(), notTaker, order.taker),
|
new RevertErrors.NativeOrders.OrderNotFillableByTakerError(order.getHash(), notTaker, order.taker),
|
||||||
);
|
);
|
||||||
@ -1256,7 +1122,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
|
|
||||||
it('cannot fill an expired order', async () => {
|
it('cannot fill an expired order', async () => {
|
||||||
const order = getTestRfqOrder({ expiry: createExpiry(-60) });
|
const order = getTestRfqOrder({ expiry: createExpiry(-60) });
|
||||||
const tx = fillRfqOrderAsync(order);
|
const tx = testUtils.fillRfqOrderAsync(order);
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Expired),
|
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Expired),
|
||||||
);
|
);
|
||||||
@ -1265,7 +1131,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
it('cannot fill a cancelled order', async () => {
|
it('cannot fill a cancelled order', async () => {
|
||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
await zeroEx.cancelRfqOrder(order).awaitTransactionSuccessAsync({ from: maker });
|
||||||
const tx = fillRfqOrderAsync(order);
|
const tx = testUtils.fillRfqOrderAsync(order);
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Cancelled),
|
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Cancelled),
|
||||||
);
|
);
|
||||||
@ -1276,7 +1142,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
await zeroEx
|
await zeroEx
|
||||||
.cancelPairRfqOrders(makerToken.address, takerToken.address, order.salt.plus(1))
|
.cancelPairRfqOrders(makerToken.address, takerToken.address, order.salt.plus(1))
|
||||||
.awaitTransactionSuccessAsync({ from: maker });
|
.awaitTransactionSuccessAsync({ from: maker });
|
||||||
const tx = fillRfqOrderAsync(order);
|
const tx = testUtils.fillRfqOrderAsync(order);
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Cancelled),
|
new RevertErrors.NativeOrders.OrderNotFillableError(order.getHash(), OrderStatus.Cancelled),
|
||||||
);
|
);
|
||||||
@ -1286,7 +1152,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
// Overwrite chainId to result in a different hash and therefore different
|
// Overwrite chainId to result in a different hash and therefore different
|
||||||
// signature.
|
// signature.
|
||||||
const tx = fillRfqOrderAsync(order.clone({ chainId: 1234 }));
|
const tx = testUtils.fillRfqOrderAsync(order.clone({ chainId: 1234 }));
|
||||||
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),
|
||||||
);
|
);
|
||||||
@ -1294,7 +1160,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
|
|
||||||
it('fails if ETH is attached', async () => {
|
it('fails if ETH is attached', async () => {
|
||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
await prepareBalancesForOrderAsync(order, taker);
|
await testUtils.prepareBalancesForOrdersAsync([order], taker);
|
||||||
const tx = zeroEx
|
const tx = zeroEx
|
||||||
.fillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount)
|
.fillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount)
|
||||||
.awaitTransactionSuccessAsync({ from: taker, value: 1 });
|
.awaitTransactionSuccessAsync({ from: taker, value: 1 });
|
||||||
@ -1306,20 +1172,20 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
describe('fillOrKillLimitOrder()', () => {
|
describe('fillOrKillLimitOrder()', () => {
|
||||||
it('can fully fill an order', async () => {
|
it('can fully fill an order', async () => {
|
||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
await prepareBalancesForOrderAsync(order);
|
await testUtils.prepareBalancesForOrdersAsync([order]);
|
||||||
const receipt = await zeroEx
|
const receipt = await zeroEx
|
||||||
.fillOrKillLimitOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount)
|
.fillOrKillLimitOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount)
|
||||||
.awaitTransactionSuccessAsync({ from: taker, value: SINGLE_PROTOCOL_FEE });
|
.awaitTransactionSuccessAsync({ from: taker, value: SINGLE_PROTOCOL_FEE });
|
||||||
verifyEventsFromLogs(
|
verifyEventsFromLogs(
|
||||||
receipt.logs,
|
receipt.logs,
|
||||||
[createLimitOrderFilledEventArgs(order)],
|
[testUtils.createLimitOrderFilledEventArgs(order)],
|
||||||
IZeroExEvents.LimitOrderFilled,
|
IZeroExEvents.LimitOrderFilled,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reverts if cannot fill the exact amount', async () => {
|
it('reverts if cannot fill the exact amount', async () => {
|
||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
await prepareBalancesForOrderAsync(order);
|
await testUtils.prepareBalancesForOrdersAsync([order]);
|
||||||
const fillAmount = order.takerAmount.plus(1);
|
const fillAmount = order.takerAmount.plus(1);
|
||||||
const tx = zeroEx
|
const tx = zeroEx
|
||||||
.fillOrKillLimitOrder(order, await order.getSignatureWithProviderAsync(env.provider), fillAmount)
|
.fillOrKillLimitOrder(order, await order.getSignatureWithProviderAsync(env.provider), fillAmount)
|
||||||
@ -1331,7 +1197,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
|
|
||||||
it('refunds excess protocol fee', async () => {
|
it('refunds excess protocol fee', async () => {
|
||||||
const order = getTestLimitOrder();
|
const order = getTestLimitOrder();
|
||||||
await prepareBalancesForOrderAsync(order);
|
await testUtils.prepareBalancesForOrdersAsync([order]);
|
||||||
const takerBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker);
|
const takerBalanceBefore = await env.web3Wrapper.getBalanceInWeiAsync(taker);
|
||||||
const receipt = await zeroEx
|
const receipt = await zeroEx
|
||||||
.fillOrKillLimitOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount)
|
.fillOrKillLimitOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount)
|
||||||
@ -1345,16 +1211,20 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
describe('fillOrKillRfqOrder()', () => {
|
describe('fillOrKillRfqOrder()', () => {
|
||||||
it('can fully fill an order', async () => {
|
it('can fully fill an order', async () => {
|
||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
await prepareBalancesForOrderAsync(order);
|
await testUtils.prepareBalancesForOrdersAsync([order]);
|
||||||
const receipt = await zeroEx
|
const receipt = await zeroEx
|
||||||
.fillOrKillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount)
|
.fillOrKillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount)
|
||||||
.awaitTransactionSuccessAsync({ from: taker });
|
.awaitTransactionSuccessAsync({ from: taker });
|
||||||
verifyEventsFromLogs(receipt.logs, [createRfqOrderFilledEventArgs(order)], IZeroExEvents.RfqOrderFilled);
|
verifyEventsFromLogs(
|
||||||
|
receipt.logs,
|
||||||
|
[testUtils.createRfqOrderFilledEventArgs(order)],
|
||||||
|
IZeroExEvents.RfqOrderFilled,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reverts if cannot fill the exact amount', async () => {
|
it('reverts if cannot fill the exact amount', async () => {
|
||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
await prepareBalancesForOrderAsync(order);
|
await testUtils.prepareBalancesForOrdersAsync([order]);
|
||||||
const fillAmount = order.takerAmount.plus(1);
|
const fillAmount = order.takerAmount.plus(1);
|
||||||
const tx = zeroEx
|
const tx = zeroEx
|
||||||
.fillOrKillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), fillAmount)
|
.fillOrKillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), fillAmount)
|
||||||
@ -1366,7 +1236,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
|
|
||||||
it('fails if ETH is attached', async () => {
|
it('fails if ETH is attached', async () => {
|
||||||
const order = getTestRfqOrder();
|
const order = getTestRfqOrder();
|
||||||
await prepareBalancesForOrderAsync(order);
|
await testUtils.prepareBalancesForOrdersAsync([order]);
|
||||||
const tx = zeroEx
|
const tx = zeroEx
|
||||||
.fillOrKillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount)
|
.fillOrKillRfqOrder(order, await order.getSignatureWithProviderAsync(env.provider), order.takerAmount)
|
||||||
.awaitTransactionSuccessAsync({ from: taker, value: 1 });
|
.awaitTransactionSuccessAsync({ from: taker, value: 1 });
|
||||||
@ -1385,34 +1255,6 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
await makerToken.approve(zeroEx.address, allowance).awaitTransactionSuccessAsync({ from: maker });
|
await makerToken.approve(zeroEx.address, allowance).awaitTransactionSuccessAsync({ from: maker });
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFillableMakerTokenAmount(
|
|
||||||
order: LimitOrder | RfqOrder,
|
|
||||||
takerTokenFilledAmount: BigNumber = ZERO_AMOUNT,
|
|
||||||
): BigNumber {
|
|
||||||
return order.takerAmount
|
|
||||||
.minus(takerTokenFilledAmount)
|
|
||||||
.times(order.makerAmount)
|
|
||||||
.div(order.takerAmount)
|
|
||||||
.integerValue(BigNumber.ROUND_DOWN);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getActualFillableTakerTokenAmount(
|
|
||||||
order: LimitOrder | RfqOrder,
|
|
||||||
makerBalance: BigNumber = order.makerAmount,
|
|
||||||
makerAllowance: BigNumber = order.makerAmount,
|
|
||||||
takerTokenFilledAmount: BigNumber = ZERO_AMOUNT,
|
|
||||||
): BigNumber {
|
|
||||||
const fillableMakerTokenAmount = getFillableMakerTokenAmount(order, takerTokenFilledAmount);
|
|
||||||
return BigNumber.min(fillableMakerTokenAmount, makerBalance, makerAllowance)
|
|
||||||
.times(order.takerAmount)
|
|
||||||
.div(order.makerAmount)
|
|
||||||
.integerValue(BigNumber.ROUND_UP);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRandomFraction(precision: number = 2): string {
|
|
||||||
return Math.random().toPrecision(precision);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('getLimitOrderRelevantState()', () => {
|
describe('getLimitOrderRelevantState()', () => {
|
||||||
it('works with an empty order', async () => {
|
it('works with an empty order', async () => {
|
||||||
const order = getTestLimitOrder({
|
const order = getTestLimitOrder({
|
||||||
@ -1487,7 +1329,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
await takerToken
|
await takerToken
|
||||||
.mint(taker, order.takerAmount.plus(order.takerTokenFeeAmount))
|
.mint(taker, order.takerAmount.plus(order.takerTokenFeeAmount))
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
await fillLimitOrderAsync(order);
|
await testUtils.fillLimitOrderAsync(order);
|
||||||
// Partially fill the order.
|
// Partially fill the order.
|
||||||
const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx
|
const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx
|
||||||
.getLimitOrderRelevantState(order, await order.getSignatureWithProviderAsync(env.provider))
|
.getLimitOrderRelevantState(order, await order.getSignatureWithProviderAsync(env.provider))
|
||||||
@ -1509,12 +1351,12 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
.mint(taker, order.takerAmount.plus(order.takerTokenFeeAmount))
|
.mint(taker, order.takerAmount.plus(order.takerTokenFeeAmount))
|
||||||
.awaitTransactionSuccessAsync();
|
.awaitTransactionSuccessAsync();
|
||||||
// Partially fill the order.
|
// Partially fill the order.
|
||||||
const fillAmount = order.takerAmount.times(getRandomFraction()).integerValue();
|
const fillAmount = getRandomPortion(order.takerAmount);
|
||||||
await fillLimitOrderAsync(order, { fillAmount });
|
await testUtils.fillLimitOrderAsync(order, { fillAmount });
|
||||||
// Reduce maker funds to be < remaining.
|
// Reduce maker funds to be < remaining.
|
||||||
const remainingMakerAmount = getFillableMakerTokenAmount(order, fillAmount);
|
const remainingMakerAmount = getFillableMakerTokenAmount(order, fillAmount);
|
||||||
const balance = remainingMakerAmount.times(getRandomFraction()).integerValue();
|
const balance = getRandomPortion(remainingMakerAmount);
|
||||||
const allowance = remainingMakerAmount.times(getRandomFraction()).integerValue();
|
const allowance = getRandomPortion(remainingMakerAmount);
|
||||||
await fundOrderMakerAsync(order, balance, allowance);
|
await fundOrderMakerAsync(order, balance, allowance);
|
||||||
// Get order state.
|
// Get order state.
|
||||||
const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx
|
const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx
|
||||||
@ -1604,7 +1446,7 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
// Fully Fund maker and taker.
|
// Fully Fund maker and taker.
|
||||||
await fundOrderMakerAsync(order);
|
await fundOrderMakerAsync(order);
|
||||||
await takerToken.mint(taker, order.takerAmount);
|
await takerToken.mint(taker, order.takerAmount);
|
||||||
await fillRfqOrderAsync(order);
|
await testUtils.fillRfqOrderAsync(order);
|
||||||
// Partially fill the order.
|
// Partially fill the order.
|
||||||
const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx
|
const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx
|
||||||
.getRfqOrderRelevantState(order, await order.getSignatureWithProviderAsync(env.provider))
|
.getRfqOrderRelevantState(order, await order.getSignatureWithProviderAsync(env.provider))
|
||||||
@ -1624,12 +1466,12 @@ blockchainTests.resets('NativeOrdersFeature', env => {
|
|||||||
await fundOrderMakerAsync(order);
|
await fundOrderMakerAsync(order);
|
||||||
await takerToken.mint(taker, order.takerAmount).awaitTransactionSuccessAsync();
|
await takerToken.mint(taker, order.takerAmount).awaitTransactionSuccessAsync();
|
||||||
// Partially fill the order.
|
// Partially fill the order.
|
||||||
const fillAmount = order.takerAmount.times(getRandomFraction()).integerValue();
|
const fillAmount = getRandomPortion(order.takerAmount);
|
||||||
await fillRfqOrderAsync(order, fillAmount);
|
await testUtils.fillRfqOrderAsync(order, fillAmount);
|
||||||
// Reduce maker funds to be < remaining.
|
// Reduce maker funds to be < remaining.
|
||||||
const remainingMakerAmount = getFillableMakerTokenAmount(order, fillAmount);
|
const remainingMakerAmount = getFillableMakerTokenAmount(order, fillAmount);
|
||||||
const balance = remainingMakerAmount.times(getRandomFraction()).integerValue();
|
const balance = getRandomPortion(remainingMakerAmount);
|
||||||
const allowance = remainingMakerAmount.times(getRandomFraction()).integerValue();
|
const allowance = getRandomPortion(remainingMakerAmount);
|
||||||
await fundOrderMakerAsync(order, balance, allowance);
|
await fundOrderMakerAsync(order, balance, allowance);
|
||||||
// Get order state.
|
// Get order state.
|
||||||
const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx
|
const [orderInfo, fillableTakerAmount, isSignatureValid] = await zeroEx
|
||||||
|
@ -1,6 +1,181 @@
|
|||||||
import { getRandomInteger, randomAddress } from '@0x/contracts-test-utils';
|
import {
|
||||||
import { LimitOrder, LimitOrderFields, RfqOrder, RfqOrderFields } from '@0x/protocol-utils';
|
BlockchainTestsEnvironment,
|
||||||
|
constants,
|
||||||
|
expect,
|
||||||
|
getRandomInteger,
|
||||||
|
randomAddress,
|
||||||
|
} from '@0x/contracts-test-utils';
|
||||||
|
import { LimitOrder, LimitOrderFields, OrderBase, OrderInfo, RfqOrder, RfqOrderFields } from '@0x/protocol-utils';
|
||||||
import { BigNumber, hexUtils } from '@0x/utils';
|
import { BigNumber, hexUtils } from '@0x/utils';
|
||||||
|
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||||
|
|
||||||
|
import { IZeroExContract, IZeroExLimitOrderFilledEventArgs, IZeroExRfqOrderFilledEventArgs } from '../../src/wrappers';
|
||||||
|
import { artifacts } from '../artifacts';
|
||||||
|
import { fullMigrateAsync } from '../utils/migration';
|
||||||
|
import { TestMintableERC20TokenContract } from '../wrappers';
|
||||||
|
|
||||||
|
const { ZERO_AMOUNT: ZERO, NULL_ADDRESS } = constants;
|
||||||
|
|
||||||
|
interface RfqOrderFilledAmounts {
|
||||||
|
makerTokenFilledAmount: BigNumber;
|
||||||
|
takerTokenFilledAmount: BigNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LimitOrderFilledAmounts {
|
||||||
|
makerTokenFilledAmount: BigNumber;
|
||||||
|
takerTokenFilledAmount: BigNumber;
|
||||||
|
takerTokenFeeFilledAmount: BigNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NativeOrdersTestEnvironment {
|
||||||
|
public static async createAsync(
|
||||||
|
env: BlockchainTestsEnvironment,
|
||||||
|
gasPrice: BigNumber = new BigNumber('123e9'),
|
||||||
|
protocolFeeMultiplier: number = 70e3,
|
||||||
|
): Promise<NativeOrdersTestEnvironment> {
|
||||||
|
const [owner, maker, taker] = await env.getAccountAddressesAsync();
|
||||||
|
const [makerToken, takerToken] = await Promise.all(
|
||||||
|
[...new Array(2)].map(async () =>
|
||||||
|
TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.TestMintableERC20Token,
|
||||||
|
env.provider,
|
||||||
|
{ ...env.txDefaults, gasPrice },
|
||||||
|
artifacts,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults, {}, { protocolFeeMultiplier });
|
||||||
|
await makerToken.approve(zeroEx.address, constants.MAX_UINT256).awaitTransactionSuccessAsync({ from: maker });
|
||||||
|
await takerToken.approve(zeroEx.address, constants.MAX_UINT256).awaitTransactionSuccessAsync({ from: taker });
|
||||||
|
return new NativeOrdersTestEnvironment(
|
||||||
|
maker,
|
||||||
|
taker,
|
||||||
|
makerToken,
|
||||||
|
takerToken,
|
||||||
|
zeroEx,
|
||||||
|
gasPrice,
|
||||||
|
gasPrice.times(protocolFeeMultiplier),
|
||||||
|
env,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public readonly maker: string,
|
||||||
|
public readonly taker: string,
|
||||||
|
public readonly makerToken: TestMintableERC20TokenContract,
|
||||||
|
public readonly takerToken: TestMintableERC20TokenContract,
|
||||||
|
public readonly zeroEx: IZeroExContract,
|
||||||
|
public readonly gasPrice: BigNumber,
|
||||||
|
public readonly protocolFee: BigNumber,
|
||||||
|
private readonly _env: BlockchainTestsEnvironment,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async prepareBalancesForOrdersAsync(
|
||||||
|
orders: LimitOrder[] | RfqOrder[],
|
||||||
|
taker: string = this.taker,
|
||||||
|
): Promise<void> {
|
||||||
|
await this.makerToken
|
||||||
|
.mint(this.maker, BigNumber.sum(...(orders as OrderBase[]).map(order => order.makerAmount)))
|
||||||
|
.awaitTransactionSuccessAsync();
|
||||||
|
await this.takerToken
|
||||||
|
.mint(
|
||||||
|
taker,
|
||||||
|
BigNumber.sum(
|
||||||
|
...(orders as OrderBase[]).map(order =>
|
||||||
|
order.takerAmount.plus(order instanceof LimitOrder ? order.takerTokenFeeAmount : 0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async fillLimitOrderAsync(
|
||||||
|
order: LimitOrder,
|
||||||
|
opts: Partial<{
|
||||||
|
fillAmount: BigNumber | number;
|
||||||
|
taker: string;
|
||||||
|
protocolFee: BigNumber | number;
|
||||||
|
}> = {},
|
||||||
|
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||||
|
const { fillAmount, taker, protocolFee } = {
|
||||||
|
taker: this.taker,
|
||||||
|
fillAmount: order.takerAmount,
|
||||||
|
...opts,
|
||||||
|
};
|
||||||
|
await this.prepareBalancesForOrdersAsync([order], taker);
|
||||||
|
const value = protocolFee === undefined ? this.protocolFee : protocolFee;
|
||||||
|
return this.zeroEx
|
||||||
|
.fillLimitOrder(
|
||||||
|
order,
|
||||||
|
await order.getSignatureWithProviderAsync(this._env.provider),
|
||||||
|
new BigNumber(fillAmount),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ from: taker, value });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async fillRfqOrderAsync(
|
||||||
|
order: RfqOrder,
|
||||||
|
fillAmount: BigNumber | number = order.takerAmount,
|
||||||
|
taker: string = this.taker,
|
||||||
|
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||||
|
await this.prepareBalancesForOrdersAsync([order], taker);
|
||||||
|
return this.zeroEx
|
||||||
|
.fillRfqOrder(
|
||||||
|
order,
|
||||||
|
await order.getSignatureWithProviderAsync(this._env.provider),
|
||||||
|
new BigNumber(fillAmount),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ from: taker });
|
||||||
|
}
|
||||||
|
|
||||||
|
public createLimitOrderFilledEventArgs(
|
||||||
|
order: LimitOrder,
|
||||||
|
takerTokenFillAmount: BigNumber = order.takerAmount,
|
||||||
|
takerTokenAlreadyFilledAmount: BigNumber = ZERO,
|
||||||
|
): IZeroExLimitOrderFilledEventArgs {
|
||||||
|
const {
|
||||||
|
makerTokenFilledAmount,
|
||||||
|
takerTokenFilledAmount,
|
||||||
|
takerTokenFeeFilledAmount,
|
||||||
|
} = computeLimitOrderFilledAmounts(order, takerTokenFillAmount, takerTokenAlreadyFilledAmount);
|
||||||
|
const protocolFee = order.taker !== NULL_ADDRESS ? ZERO : this.protocolFee;
|
||||||
|
return {
|
||||||
|
takerTokenFilledAmount,
|
||||||
|
makerTokenFilledAmount,
|
||||||
|
takerTokenFeeFilledAmount,
|
||||||
|
orderHash: order.getHash(),
|
||||||
|
maker: order.maker,
|
||||||
|
taker: this.taker,
|
||||||
|
feeRecipient: order.feeRecipient,
|
||||||
|
makerToken: order.makerToken,
|
||||||
|
takerToken: order.takerToken,
|
||||||
|
protocolFeePaid: protocolFee,
|
||||||
|
pool: order.pool,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public createRfqOrderFilledEventArgs(
|
||||||
|
order: RfqOrder,
|
||||||
|
takerTokenFillAmount: BigNumber = order.takerAmount,
|
||||||
|
takerTokenAlreadyFilledAmount: BigNumber = ZERO,
|
||||||
|
): IZeroExRfqOrderFilledEventArgs {
|
||||||
|
const { makerTokenFilledAmount, takerTokenFilledAmount } = computeRfqOrderFilledAmounts(
|
||||||
|
order,
|
||||||
|
takerTokenFillAmount,
|
||||||
|
takerTokenAlreadyFilledAmount,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
takerTokenFilledAmount,
|
||||||
|
makerTokenFilledAmount,
|
||||||
|
orderHash: order.getHash(),
|
||||||
|
maker: order.maker,
|
||||||
|
taker: this.taker,
|
||||||
|
makerToken: order.makerToken,
|
||||||
|
takerToken: order.takerToken,
|
||||||
|
pool: order.pool,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a random limit order.
|
* Generate a random limit order.
|
||||||
@ -40,3 +215,105 @@ export function getRandomRfqOrder(fields: Partial<RfqOrderFields> = {}): RfqOrde
|
|||||||
...fields,
|
...fields,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts the fields of an OrderInfo object.
|
||||||
|
*/
|
||||||
|
export function assertOrderInfoEquals(actual: OrderInfo, expected: OrderInfo): void {
|
||||||
|
expect(actual.status, 'Order status').to.eq(expected.status);
|
||||||
|
expect(actual.orderHash, 'Order hash').to.eq(expected.orderHash);
|
||||||
|
expect(actual.takerTokenFilledAmount, 'Order takerTokenFilledAmount').to.bignumber.eq(
|
||||||
|
expected.takerTokenFilledAmount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an order expiry field.
|
||||||
|
*/
|
||||||
|
export function createExpiry(deltaSeconds: number = 60): BigNumber {
|
||||||
|
return new BigNumber(Math.floor(Date.now() / 1000) + deltaSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the maker, taker, and taker token fee amounts filled for
|
||||||
|
* the given limit order.
|
||||||
|
*/
|
||||||
|
export function computeLimitOrderFilledAmounts(
|
||||||
|
order: LimitOrder,
|
||||||
|
takerTokenFillAmount: BigNumber = order.takerAmount,
|
||||||
|
takerTokenAlreadyFilledAmount: BigNumber = ZERO,
|
||||||
|
): LimitOrderFilledAmounts {
|
||||||
|
const fillAmount = BigNumber.min(
|
||||||
|
order.takerAmount,
|
||||||
|
takerTokenFillAmount,
|
||||||
|
order.takerAmount.minus(takerTokenAlreadyFilledAmount),
|
||||||
|
);
|
||||||
|
const makerTokenFilledAmount = fillAmount
|
||||||
|
.times(order.makerAmount)
|
||||||
|
.div(order.takerAmount)
|
||||||
|
.integerValue(BigNumber.ROUND_DOWN);
|
||||||
|
const takerTokenFeeFilledAmount = fillAmount
|
||||||
|
.times(order.takerTokenFeeAmount)
|
||||||
|
.div(order.takerAmount)
|
||||||
|
.integerValue(BigNumber.ROUND_DOWN);
|
||||||
|
return {
|
||||||
|
makerTokenFilledAmount,
|
||||||
|
takerTokenFilledAmount: fillAmount,
|
||||||
|
takerTokenFeeFilledAmount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the maker and taker amounts filled for the given RFQ order.
|
||||||
|
*/
|
||||||
|
export function computeRfqOrderFilledAmounts(
|
||||||
|
order: RfqOrder,
|
||||||
|
takerTokenFillAmount: BigNumber = order.takerAmount,
|
||||||
|
takerTokenAlreadyFilledAmount: BigNumber = ZERO,
|
||||||
|
): RfqOrderFilledAmounts {
|
||||||
|
const fillAmount = BigNumber.min(
|
||||||
|
order.takerAmount,
|
||||||
|
takerTokenFillAmount,
|
||||||
|
order.takerAmount.minus(takerTokenAlreadyFilledAmount),
|
||||||
|
);
|
||||||
|
const makerTokenFilledAmount = fillAmount
|
||||||
|
.times(order.makerAmount)
|
||||||
|
.div(order.takerAmount)
|
||||||
|
.integerValue(BigNumber.ROUND_DOWN);
|
||||||
|
return {
|
||||||
|
makerTokenFilledAmount,
|
||||||
|
takerTokenFilledAmount: fillAmount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the remaining fillable amount in maker token for
|
||||||
|
* the given order.
|
||||||
|
*/
|
||||||
|
export function getFillableMakerTokenAmount(
|
||||||
|
order: LimitOrder | RfqOrder,
|
||||||
|
takerTokenFilledAmount: BigNumber = ZERO,
|
||||||
|
): BigNumber {
|
||||||
|
return order.takerAmount
|
||||||
|
.minus(takerTokenFilledAmount)
|
||||||
|
.times(order.makerAmount)
|
||||||
|
.div(order.takerAmount)
|
||||||
|
.integerValue(BigNumber.ROUND_DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the remaining fillable amnount in taker token, based on
|
||||||
|
* the amount already filled and the maker's balance/allowance.
|
||||||
|
*/
|
||||||
|
export function getActualFillableTakerTokenAmount(
|
||||||
|
order: LimitOrder | RfqOrder,
|
||||||
|
makerBalance: BigNumber = order.makerAmount,
|
||||||
|
makerAllowance: BigNumber = order.makerAmount,
|
||||||
|
takerTokenFilledAmount: BigNumber = ZERO,
|
||||||
|
): BigNumber {
|
||||||
|
const fillableMakerTokenAmount = getFillableMakerTokenAmount(order, takerTokenFilledAmount);
|
||||||
|
return BigNumber.min(fillableMakerTokenAmount, makerBalance, makerAllowance)
|
||||||
|
.times(order.takerAmount)
|
||||||
|
.div(order.makerAmount)
|
||||||
|
.integerValue(BigNumber.ROUND_UP);
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
export * from '../test/generated-wrappers/affiliate_fee_transformer';
|
export * from '../test/generated-wrappers/affiliate_fee_transformer';
|
||||||
export * from '../test/generated-wrappers/allowance_target';
|
export * from '../test/generated-wrappers/allowance_target';
|
||||||
|
export * from '../test/generated-wrappers/batch_fill_native_orders_feature';
|
||||||
export * from '../test/generated-wrappers/bootstrap_feature';
|
export * from '../test/generated-wrappers/bootstrap_feature';
|
||||||
export * from '../test/generated-wrappers/bridge_adapter';
|
export * from '../test/generated-wrappers/bridge_adapter';
|
||||||
export * from '../test/generated-wrappers/bridge_source';
|
export * from '../test/generated-wrappers/bridge_source';
|
||||||
@ -20,6 +21,7 @@ export * from '../test/generated-wrappers/fixin_token_spender';
|
|||||||
export * from '../test/generated-wrappers/flash_wallet';
|
export * from '../test/generated-wrappers/flash_wallet';
|
||||||
export * from '../test/generated-wrappers/full_migration';
|
export * from '../test/generated-wrappers/full_migration';
|
||||||
export * from '../test/generated-wrappers/i_allowance_target';
|
export * from '../test/generated-wrappers/i_allowance_target';
|
||||||
|
export * from '../test/generated-wrappers/i_batch_fill_native_orders_feature';
|
||||||
export * from '../test/generated-wrappers/i_bootstrap_feature';
|
export * from '../test/generated-wrappers/i_bootstrap_feature';
|
||||||
export * from '../test/generated-wrappers/i_bridge_adapter';
|
export * from '../test/generated-wrappers/i_bridge_adapter';
|
||||||
export * from '../test/generated-wrappers/i_erc20_bridge';
|
export * from '../test/generated-wrappers/i_erc20_bridge';
|
||||||
@ -31,6 +33,8 @@ export * from '../test/generated-wrappers/i_liquidity_provider_feature';
|
|||||||
export * from '../test/generated-wrappers/i_liquidity_provider_sandbox';
|
export * from '../test/generated-wrappers/i_liquidity_provider_sandbox';
|
||||||
export * from '../test/generated-wrappers/i_meta_transactions_feature';
|
export * from '../test/generated-wrappers/i_meta_transactions_feature';
|
||||||
export * from '../test/generated-wrappers/i_mooniswap_pool';
|
export * from '../test/generated-wrappers/i_mooniswap_pool';
|
||||||
|
export * from '../test/generated-wrappers/i_multiplex_feature';
|
||||||
|
export * from '../test/generated-wrappers/i_native_orders_events';
|
||||||
export * from '../test/generated-wrappers/i_native_orders_feature';
|
export * from '../test/generated-wrappers/i_native_orders_feature';
|
||||||
export * from '../test/generated-wrappers/i_ownable_feature';
|
export * from '../test/generated-wrappers/i_ownable_feature';
|
||||||
export * from '../test/generated-wrappers/i_simple_function_registry_feature';
|
export * from '../test/generated-wrappers/i_simple_function_registry_feature';
|
||||||
@ -39,6 +43,7 @@ export * from '../test/generated-wrappers/i_test_simple_function_registry_featur
|
|||||||
export * from '../test/generated-wrappers/i_token_spender_feature';
|
export * from '../test/generated-wrappers/i_token_spender_feature';
|
||||||
export * from '../test/generated-wrappers/i_transform_erc20_feature';
|
export * from '../test/generated-wrappers/i_transform_erc20_feature';
|
||||||
export * from '../test/generated-wrappers/i_uniswap_feature';
|
export * from '../test/generated-wrappers/i_uniswap_feature';
|
||||||
|
export * from '../test/generated-wrappers/i_uniswap_v2_pair';
|
||||||
export * from '../test/generated-wrappers/i_zero_ex';
|
export * from '../test/generated-wrappers/i_zero_ex';
|
||||||
export * from '../test/generated-wrappers/initial_migration';
|
export * from '../test/generated-wrappers/initial_migration';
|
||||||
export * from '../test/generated-wrappers/lib_bootstrap';
|
export * from '../test/generated-wrappers/lib_bootstrap';
|
||||||
@ -88,7 +93,12 @@ export * from '../test/generated-wrappers/mixin_uniswap';
|
|||||||
export * from '../test/generated-wrappers/mixin_uniswap_v2';
|
export * from '../test/generated-wrappers/mixin_uniswap_v2';
|
||||||
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/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_protocol_fees';
|
||||||
|
export * from '../test/generated-wrappers/native_orders_settlement';
|
||||||
export * from '../test/generated-wrappers/ownable_feature';
|
export * from '../test/generated-wrappers/ownable_feature';
|
||||||
export * from '../test/generated-wrappers/pay_taker_transformer';
|
export * from '../test/generated-wrappers/pay_taker_transformer';
|
||||||
export * from '../test/generated-wrappers/permissionless_transformer_deployer';
|
export * from '../test/generated-wrappers/permissionless_transformer_deployer';
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*", "./scripts/**/*"],
|
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*", "./scripts/**/*"],
|
||||||
"files": [
|
"files": [
|
||||||
"generated-artifacts/AffiliateFeeTransformer.json",
|
"generated-artifacts/AffiliateFeeTransformer.json",
|
||||||
|
"generated-artifacts/BatchFillNativeOrdersFeature.json",
|
||||||
"generated-artifacts/BridgeAdapter.json",
|
"generated-artifacts/BridgeAdapter.json",
|
||||||
"generated-artifacts/CurveLiquidityProvider.json",
|
"generated-artifacts/CurveLiquidityProvider.json",
|
||||||
"generated-artifacts/FeeCollector.json",
|
"generated-artifacts/FeeCollector.json",
|
||||||
@ -11,9 +12,11 @@
|
|||||||
"generated-artifacts/FillQuoteTransformer.json",
|
"generated-artifacts/FillQuoteTransformer.json",
|
||||||
"generated-artifacts/FullMigration.json",
|
"generated-artifacts/FullMigration.json",
|
||||||
"generated-artifacts/IAllowanceTarget.json",
|
"generated-artifacts/IAllowanceTarget.json",
|
||||||
|
"generated-artifacts/IBatchFillNativeOrdersFeature.json",
|
||||||
"generated-artifacts/IERC20Transformer.json",
|
"generated-artifacts/IERC20Transformer.json",
|
||||||
"generated-artifacts/IFlashWallet.json",
|
"generated-artifacts/IFlashWallet.json",
|
||||||
"generated-artifacts/ILiquidityProviderFeature.json",
|
"generated-artifacts/ILiquidityProviderFeature.json",
|
||||||
|
"generated-artifacts/IMultiplexFeature.json",
|
||||||
"generated-artifacts/INativeOrdersFeature.json",
|
"generated-artifacts/INativeOrdersFeature.json",
|
||||||
"generated-artifacts/IOwnableFeature.json",
|
"generated-artifacts/IOwnableFeature.json",
|
||||||
"generated-artifacts/ISimpleFunctionRegistryFeature.json",
|
"generated-artifacts/ISimpleFunctionRegistryFeature.json",
|
||||||
@ -24,6 +27,7 @@
|
|||||||
"generated-artifacts/LiquidityProviderFeature.json",
|
"generated-artifacts/LiquidityProviderFeature.json",
|
||||||
"generated-artifacts/LogMetadataTransformer.json",
|
"generated-artifacts/LogMetadataTransformer.json",
|
||||||
"generated-artifacts/MetaTransactionsFeature.json",
|
"generated-artifacts/MetaTransactionsFeature.json",
|
||||||
|
"generated-artifacts/MultiplexFeature.json",
|
||||||
"generated-artifacts/NativeOrdersFeature.json",
|
"generated-artifacts/NativeOrdersFeature.json",
|
||||||
"generated-artifacts/OwnableFeature.json",
|
"generated-artifacts/OwnableFeature.json",
|
||||||
"generated-artifacts/PayTakerTransformer.json",
|
"generated-artifacts/PayTakerTransformer.json",
|
||||||
@ -35,6 +39,7 @@
|
|||||||
"generated-artifacts/ZeroEx.json",
|
"generated-artifacts/ZeroEx.json",
|
||||||
"test/generated-artifacts/AffiliateFeeTransformer.json",
|
"test/generated-artifacts/AffiliateFeeTransformer.json",
|
||||||
"test/generated-artifacts/AllowanceTarget.json",
|
"test/generated-artifacts/AllowanceTarget.json",
|
||||||
|
"test/generated-artifacts/BatchFillNativeOrdersFeature.json",
|
||||||
"test/generated-artifacts/BootstrapFeature.json",
|
"test/generated-artifacts/BootstrapFeature.json",
|
||||||
"test/generated-artifacts/BridgeAdapter.json",
|
"test/generated-artifacts/BridgeAdapter.json",
|
||||||
"test/generated-artifacts/BridgeSource.json",
|
"test/generated-artifacts/BridgeSource.json",
|
||||||
@ -50,6 +55,7 @@
|
|||||||
"test/generated-artifacts/FlashWallet.json",
|
"test/generated-artifacts/FlashWallet.json",
|
||||||
"test/generated-artifacts/FullMigration.json",
|
"test/generated-artifacts/FullMigration.json",
|
||||||
"test/generated-artifacts/IAllowanceTarget.json",
|
"test/generated-artifacts/IAllowanceTarget.json",
|
||||||
|
"test/generated-artifacts/IBatchFillNativeOrdersFeature.json",
|
||||||
"test/generated-artifacts/IBootstrapFeature.json",
|
"test/generated-artifacts/IBootstrapFeature.json",
|
||||||
"test/generated-artifacts/IBridgeAdapter.json",
|
"test/generated-artifacts/IBridgeAdapter.json",
|
||||||
"test/generated-artifacts/IERC20Bridge.json",
|
"test/generated-artifacts/IERC20Bridge.json",
|
||||||
@ -61,6 +67,8 @@
|
|||||||
"test/generated-artifacts/ILiquidityProviderSandbox.json",
|
"test/generated-artifacts/ILiquidityProviderSandbox.json",
|
||||||
"test/generated-artifacts/IMetaTransactionsFeature.json",
|
"test/generated-artifacts/IMetaTransactionsFeature.json",
|
||||||
"test/generated-artifacts/IMooniswapPool.json",
|
"test/generated-artifacts/IMooniswapPool.json",
|
||||||
|
"test/generated-artifacts/IMultiplexFeature.json",
|
||||||
|
"test/generated-artifacts/INativeOrdersEvents.json",
|
||||||
"test/generated-artifacts/INativeOrdersFeature.json",
|
"test/generated-artifacts/INativeOrdersFeature.json",
|
||||||
"test/generated-artifacts/IOwnableFeature.json",
|
"test/generated-artifacts/IOwnableFeature.json",
|
||||||
"test/generated-artifacts/ISimpleFunctionRegistryFeature.json",
|
"test/generated-artifacts/ISimpleFunctionRegistryFeature.json",
|
||||||
@ -69,6 +77,7 @@
|
|||||||
"test/generated-artifacts/ITokenSpenderFeature.json",
|
"test/generated-artifacts/ITokenSpenderFeature.json",
|
||||||
"test/generated-artifacts/ITransformERC20Feature.json",
|
"test/generated-artifacts/ITransformERC20Feature.json",
|
||||||
"test/generated-artifacts/IUniswapFeature.json",
|
"test/generated-artifacts/IUniswapFeature.json",
|
||||||
|
"test/generated-artifacts/IUniswapV2Pair.json",
|
||||||
"test/generated-artifacts/IZeroEx.json",
|
"test/generated-artifacts/IZeroEx.json",
|
||||||
"test/generated-artifacts/InitialMigration.json",
|
"test/generated-artifacts/InitialMigration.json",
|
||||||
"test/generated-artifacts/LibBootstrap.json",
|
"test/generated-artifacts/LibBootstrap.json",
|
||||||
@ -118,7 +127,12 @@
|
|||||||
"test/generated-artifacts/MixinUniswapV2.json",
|
"test/generated-artifacts/MixinUniswapV2.json",
|
||||||
"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/NativeOrdersCancellation.json",
|
||||||
"test/generated-artifacts/NativeOrdersFeature.json",
|
"test/generated-artifacts/NativeOrdersFeature.json",
|
||||||
|
"test/generated-artifacts/NativeOrdersInfo.json",
|
||||||
|
"test/generated-artifacts/NativeOrdersProtocolFees.json",
|
||||||
|
"test/generated-artifacts/NativeOrdersSettlement.json",
|
||||||
"test/generated-artifacts/OwnableFeature.json",
|
"test/generated-artifacts/OwnableFeature.json",
|
||||||
"test/generated-artifacts/PayTakerTransformer.json",
|
"test/generated-artifacts/PayTakerTransformer.json",
|
||||||
"test/generated-artifacts/PermissionlessTransformerDeployer.json",
|
"test/generated-artifacts/PermissionlessTransformerDeployer.json",
|
||||||
|
@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "3.13.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Update IZeroEx artifact",
|
||||||
|
"pr": 140
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "3.12.0",
|
"version": "3.12.0",
|
||||||
"changes": [
|
"changes": [
|
||||||
|
@ -3,6 +3,16 @@
|
|||||||
"contractName": "IZeroEx",
|
"contractName": "IZeroEx",
|
||||||
"compilerOutput": {
|
"compilerOutput": {
|
||||||
"abi": [
|
"abi": [
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{ "indexed": false, "internalType": "bytes32", "name": "orderHash", "type": "bytes32" },
|
||||||
|
{ "indexed": false, "internalType": "address", "name": "maker", "type": "address" },
|
||||||
|
{ "indexed": false, "internalType": "uint64", "name": "expiry", "type": "uint64" }
|
||||||
|
],
|
||||||
|
"name": "ExpiredRfqOrder",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"anonymous": false,
|
"anonymous": false,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
@ -39,26 +49,11 @@
|
|||||||
{
|
{
|
||||||
"anonymous": false,
|
"anonymous": false,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{ "indexed": false, "internalType": "address", "name": "inputToken", "type": "address" },
|
||||||
"indexed": false,
|
{ "indexed": false, "internalType": "address", "name": "outputToken", "type": "address" },
|
||||||
"internalType": "contract IERC20TokenV06",
|
|
||||||
"name": "inputToken",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": false,
|
|
||||||
"internalType": "contract IERC20TokenV06",
|
|
||||||
"name": "outputToken",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{ "indexed": false, "internalType": "uint256", "name": "inputTokenAmount", "type": "uint256" },
|
{ "indexed": false, "internalType": "uint256", "name": "inputTokenAmount", "type": "uint256" },
|
||||||
{ "indexed": false, "internalType": "uint256", "name": "outputTokenAmount", "type": "uint256" },
|
{ "indexed": false, "internalType": "uint256", "name": "outputTokenAmount", "type": "uint256" },
|
||||||
{
|
{ "indexed": false, "internalType": "address", "name": "provider", "type": "address" },
|
||||||
"indexed": false,
|
|
||||||
"internalType": "contract ILiquidityProvider",
|
|
||||||
"name": "provider",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{ "indexed": false, "internalType": "address", "name": "recipient", "type": "address" }
|
{ "indexed": false, "internalType": "address", "name": "recipient", "type": "address" }
|
||||||
],
|
],
|
||||||
"name": "LiquidityProviderSwap",
|
"name": "LiquidityProviderSwap",
|
||||||
@ -444,6 +439,127 @@
|
|||||||
"stateMutability": "payable",
|
"stateMutability": "payable",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"components": [
|
||||||
|
{ "internalType": "contract IERC20TokenV06", "name": "inputToken", "type": "address" },
|
||||||
|
{ "internalType": "contract IERC20TokenV06", "name": "outputToken", "type": "address" },
|
||||||
|
{ "internalType": "uint256", "name": "sellAmount", "type": "uint256" },
|
||||||
|
{
|
||||||
|
"components": [
|
||||||
|
{ "internalType": "bytes4", "name": "selector", "type": "bytes4" },
|
||||||
|
{ "internalType": "uint256", "name": "sellAmount", "type": "uint256" },
|
||||||
|
{ "internalType": "bytes", "name": "data", "type": "bytes" }
|
||||||
|
],
|
||||||
|
"internalType": "struct IMultiplexFeature.WrappedBatchCall[]",
|
||||||
|
"name": "calls",
|
||||||
|
"type": "tuple[]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"internalType": "struct IMultiplexFeature.BatchFillData",
|
||||||
|
"name": "fillData",
|
||||||
|
"type": "tuple"
|
||||||
|
},
|
||||||
|
{ "internalType": "uint256", "name": "minBuyAmount", "type": "uint256" }
|
||||||
|
],
|
||||||
|
"name": "batchFill",
|
||||||
|
"outputs": [{ "internalType": "uint256", "name": "outputTokenAmount", "type": "uint256" }],
|
||||||
|
"stateMutability": "payable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"components": [
|
||||||
|
{ "internalType": "contract IERC20TokenV06", "name": "makerToken", "type": "address" },
|
||||||
|
{ "internalType": "contract IERC20TokenV06", "name": "takerToken", "type": "address" },
|
||||||
|
{ "internalType": "uint128", "name": "makerAmount", "type": "uint128" },
|
||||||
|
{ "internalType": "uint128", "name": "takerAmount", "type": "uint128" },
|
||||||
|
{ "internalType": "uint128", "name": "takerTokenFeeAmount", "type": "uint128" },
|
||||||
|
{ "internalType": "address", "name": "maker", "type": "address" },
|
||||||
|
{ "internalType": "address", "name": "taker", "type": "address" },
|
||||||
|
{ "internalType": "address", "name": "sender", "type": "address" },
|
||||||
|
{ "internalType": "address", "name": "feeRecipient", "type": "address" },
|
||||||
|
{ "internalType": "bytes32", "name": "pool", "type": "bytes32" },
|
||||||
|
{ "internalType": "uint64", "name": "expiry", "type": "uint64" },
|
||||||
|
{ "internalType": "uint256", "name": "salt", "type": "uint256" }
|
||||||
|
],
|
||||||
|
"internalType": "struct LibNativeOrder.LimitOrder[]",
|
||||||
|
"name": "orders",
|
||||||
|
"type": "tuple[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"internalType": "enum LibSignature.SignatureType",
|
||||||
|
"name": "signatureType",
|
||||||
|
"type": "uint8"
|
||||||
|
},
|
||||||
|
{ "internalType": "uint8", "name": "v", "type": "uint8" },
|
||||||
|
{ "internalType": "bytes32", "name": "r", "type": "bytes32" },
|
||||||
|
{ "internalType": "bytes32", "name": "s", "type": "bytes32" }
|
||||||
|
],
|
||||||
|
"internalType": "struct LibSignature.Signature[]",
|
||||||
|
"name": "signatures",
|
||||||
|
"type": "tuple[]"
|
||||||
|
},
|
||||||
|
{ "internalType": "uint128[]", "name": "takerTokenFillAmounts", "type": "uint128[]" },
|
||||||
|
{ "internalType": "bool", "name": "revertIfIncomplete", "type": "bool" }
|
||||||
|
],
|
||||||
|
"name": "batchFillLimitOrders",
|
||||||
|
"outputs": [
|
||||||
|
{ "internalType": "uint128[]", "name": "takerTokenFilledAmounts", "type": "uint128[]" },
|
||||||
|
{ "internalType": "uint128[]", "name": "makerTokenFilledAmounts", "type": "uint128[]" }
|
||||||
|
],
|
||||||
|
"stateMutability": "payable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"components": [
|
||||||
|
{ "internalType": "contract IERC20TokenV06", "name": "makerToken", "type": "address" },
|
||||||
|
{ "internalType": "contract IERC20TokenV06", "name": "takerToken", "type": "address" },
|
||||||
|
{ "internalType": "uint128", "name": "makerAmount", "type": "uint128" },
|
||||||
|
{ "internalType": "uint128", "name": "takerAmount", "type": "uint128" },
|
||||||
|
{ "internalType": "address", "name": "maker", "type": "address" },
|
||||||
|
{ "internalType": "address", "name": "taker", "type": "address" },
|
||||||
|
{ "internalType": "address", "name": "txOrigin", "type": "address" },
|
||||||
|
{ "internalType": "bytes32", "name": "pool", "type": "bytes32" },
|
||||||
|
{ "internalType": "uint64", "name": "expiry", "type": "uint64" },
|
||||||
|
{ "internalType": "uint256", "name": "salt", "type": "uint256" }
|
||||||
|
],
|
||||||
|
"internalType": "struct LibNativeOrder.RfqOrder[]",
|
||||||
|
"name": "orders",
|
||||||
|
"type": "tuple[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"internalType": "enum LibSignature.SignatureType",
|
||||||
|
"name": "signatureType",
|
||||||
|
"type": "uint8"
|
||||||
|
},
|
||||||
|
{ "internalType": "uint8", "name": "v", "type": "uint8" },
|
||||||
|
{ "internalType": "bytes32", "name": "r", "type": "bytes32" },
|
||||||
|
{ "internalType": "bytes32", "name": "s", "type": "bytes32" }
|
||||||
|
],
|
||||||
|
"internalType": "struct LibSignature.Signature[]",
|
||||||
|
"name": "signatures",
|
||||||
|
"type": "tuple[]"
|
||||||
|
},
|
||||||
|
{ "internalType": "uint128[]", "name": "takerTokenFillAmounts", "type": "uint128[]" },
|
||||||
|
{ "internalType": "bool", "name": "revertIfIncomplete", "type": "bool" }
|
||||||
|
],
|
||||||
|
"name": "batchFillRfqOrders",
|
||||||
|
"outputs": [
|
||||||
|
{ "internalType": "uint128[]", "name": "takerTokenFilledAmounts", "type": "uint128[]" },
|
||||||
|
{ "internalType": "uint128[]", "name": "makerTokenFilledAmounts", "type": "uint128[]" }
|
||||||
|
],
|
||||||
|
"stateMutability": "nonpayable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
@ -1221,6 +1337,33 @@
|
|||||||
"stateMutability": "nonpayable",
|
"stateMutability": "nonpayable",
|
||||||
"type": "function"
|
"type": "function"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"components": [
|
||||||
|
{ "internalType": "address[]", "name": "tokens", "type": "address[]" },
|
||||||
|
{ "internalType": "uint256", "name": "sellAmount", "type": "uint256" },
|
||||||
|
{
|
||||||
|
"components": [
|
||||||
|
{ "internalType": "bytes4", "name": "selector", "type": "bytes4" },
|
||||||
|
{ "internalType": "bytes", "name": "data", "type": "bytes" }
|
||||||
|
],
|
||||||
|
"internalType": "struct IMultiplexFeature.WrappedMultiHopCall[]",
|
||||||
|
"name": "calls",
|
||||||
|
"type": "tuple[]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"internalType": "struct IMultiplexFeature.MultiHopFillData",
|
||||||
|
"name": "fillData",
|
||||||
|
"type": "tuple"
|
||||||
|
},
|
||||||
|
{ "internalType": "uint256", "name": "minBuyAmount", "type": "uint256" }
|
||||||
|
],
|
||||||
|
"name": "multiHopFill",
|
||||||
|
"outputs": [{ "internalType": "uint256", "name": "outputTokenAmount", "type": "uint256" }],
|
||||||
|
"stateMutability": "payable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"inputs": [],
|
"inputs": [],
|
||||||
"name": "owner",
|
"name": "owner",
|
||||||
@ -1402,6 +1545,40 @@
|
|||||||
},
|
},
|
||||||
"returns": { "returnResults": "The ABI-encoded results of the underlying calls." }
|
"returns": { "returnResults": "The ABI-encoded results of the underlying calls." }
|
||||||
},
|
},
|
||||||
|
"batchFill((address,address,uint256,(bytes4,uint256,bytes)[]),uint256)": {
|
||||||
|
"details": "Executes a batch of fills selling `fillData.inputToken` for `fillData.outputToken` in sequence. Refer to the internal variant `_batchFill` for the allowed nested operations.",
|
||||||
|
"params": {
|
||||||
|
"fillData": "Encodes the input/output tokens, the sell amount, and the nested operations for this batch fill.",
|
||||||
|
"minBuyAmount": "The minimum amount of `fillData.outputToken` to buy. Reverts if this amount is not met."
|
||||||
|
},
|
||||||
|
"returns": { "outputTokenAmount": "The amount of the output token bought." }
|
||||||
|
},
|
||||||
|
"batchFillLimitOrders((address,address,uint128,uint128,uint128,address,address,address,address,bytes32,uint64,uint256)[],(uint8,uint8,bytes32,bytes32)[],uint128[],bool)": {
|
||||||
|
"details": "Fills multiple limit orders.",
|
||||||
|
"params": {
|
||||||
|
"orders": "Array of limit orders.",
|
||||||
|
"revertIfIncomplete": "If true, reverts if this function fails to fill the full fill amount for any individual order.",
|
||||||
|
"signatures": "Array of signatures corresponding to each order.",
|
||||||
|
"takerTokenFillAmounts": "Array of desired amounts to fill each order."
|
||||||
|
},
|
||||||
|
"returns": {
|
||||||
|
"makerTokenFilledAmounts": "Array of amounts filled, in maker token.",
|
||||||
|
"takerTokenFilledAmounts": "Array of amounts filled, in taker token."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"batchFillRfqOrders((address,address,uint128,uint128,address,address,address,bytes32,uint64,uint256)[],(uint8,uint8,bytes32,bytes32)[],uint128[],bool)": {
|
||||||
|
"details": "Fills multiple RFQ orders.",
|
||||||
|
"params": {
|
||||||
|
"orders": "Array of RFQ orders.",
|
||||||
|
"revertIfIncomplete": "If true, reverts if this function fails to fill the full fill amount for any individual order.",
|
||||||
|
"signatures": "Array of signatures corresponding to each order.",
|
||||||
|
"takerTokenFillAmounts": "Array of desired amounts to fill each order."
|
||||||
|
},
|
||||||
|
"returns": {
|
||||||
|
"makerTokenFilledAmounts": "Array of amounts filled, in maker token.",
|
||||||
|
"takerTokenFilledAmounts": "Array of amounts filled, in taker token."
|
||||||
|
}
|
||||||
|
},
|
||||||
"batchGetLimitOrderRelevantStates((address,address,uint128,uint128,uint128,address,address,address,address,bytes32,uint64,uint256)[],(uint8,uint8,bytes32,bytes32)[])": {
|
"batchGetLimitOrderRelevantStates((address,address,uint128,uint128,uint128,address,address,address,address,bytes32,uint64,uint256)[],(uint8,uint8,bytes32,bytes32)[])": {
|
||||||
"details": "Batch version of `getLimitOrderRelevantState()`, without reverting. Orders that would normally cause `getLimitOrderRelevantState()` to revert will have empty results.",
|
"details": "Batch version of `getLimitOrderRelevantState()`, without reverting. Orders that would normally cause `getLimitOrderRelevantState()` to revert will have empty results.",
|
||||||
"params": { "orders": "The limit orders.", "signatures": "The order signatures." },
|
"params": { "orders": "The limit orders.", "signatures": "The order signatures." },
|
||||||
@ -1600,6 +1777,14 @@
|
|||||||
"target": "The migrator contract address."
|
"target": "The migrator contract address."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"multiHopFill((address[],uint256,(bytes4,bytes)[]),uint256)": {
|
||||||
|
"details": "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.",
|
||||||
|
"params": {
|
||||||
|
"fillData": "Encodes the path of tokens, the sell amount, and the nested operations for this multi-hop fill.",
|
||||||
|
"minBuyAmount": "The minimum amount of the output token to buy. Reverts if this amount is not met."
|
||||||
|
},
|
||||||
|
"returns": { "outputTokenAmount": "The amount of the output token bought." }
|
||||||
|
},
|
||||||
"owner()": {
|
"owner()": {
|
||||||
"details": "The owner of this contract.",
|
"details": "The owner of this contract.",
|
||||||
"returns": { "ownerAddress": "The owner address." }
|
"returns": { "ownerAddress": "The owner address." }
|
||||||
|
@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "13.14.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Update IZeroExContract wrapper",
|
||||||
|
"pr": 140
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "13.13.0",
|
"version": "13.13.0",
|
||||||
"changes": [
|
"changes": [
|
||||||
|
@ -36,6 +36,7 @@ import * as ethers from 'ethers';
|
|||||||
// tslint:enable:no-unused-variable
|
// tslint:enable:no-unused-variable
|
||||||
|
|
||||||
export type IZeroExEventArgs =
|
export type IZeroExEventArgs =
|
||||||
|
| IZeroExExpiredRfqOrderEventArgs
|
||||||
| IZeroExLimitOrderFilledEventArgs
|
| IZeroExLimitOrderFilledEventArgs
|
||||||
| IZeroExLiquidityProviderSwapEventArgs
|
| IZeroExLiquidityProviderSwapEventArgs
|
||||||
| IZeroExMetaTransactionExecutedEventArgs
|
| IZeroExMetaTransactionExecutedEventArgs
|
||||||
@ -52,6 +53,7 @@ export type IZeroExEventArgs =
|
|||||||
| IZeroExTransformerDeployerUpdatedEventArgs;
|
| IZeroExTransformerDeployerUpdatedEventArgs;
|
||||||
|
|
||||||
export enum IZeroExEvents {
|
export enum IZeroExEvents {
|
||||||
|
ExpiredRfqOrder = 'ExpiredRfqOrder',
|
||||||
LimitOrderFilled = 'LimitOrderFilled',
|
LimitOrderFilled = 'LimitOrderFilled',
|
||||||
LiquidityProviderSwap = 'LiquidityProviderSwap',
|
LiquidityProviderSwap = 'LiquidityProviderSwap',
|
||||||
MetaTransactionExecuted = 'MetaTransactionExecuted',
|
MetaTransactionExecuted = 'MetaTransactionExecuted',
|
||||||
@ -68,6 +70,12 @@ export enum IZeroExEvents {
|
|||||||
TransformerDeployerUpdated = 'TransformerDeployerUpdated',
|
TransformerDeployerUpdated = 'TransformerDeployerUpdated',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IZeroExExpiredRfqOrderEventArgs extends DecodedLogArgs {
|
||||||
|
orderHash: string;
|
||||||
|
maker: string;
|
||||||
|
expiry: BigNumber;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IZeroExLimitOrderFilledEventArgs extends DecodedLogArgs {
|
export interface IZeroExLimitOrderFilledEventArgs extends DecodedLogArgs {
|
||||||
orderHash: string;
|
orderHash: string;
|
||||||
maker: string;
|
maker: string;
|
||||||
@ -284,6 +292,29 @@ export class IZeroExContract extends BaseContract {
|
|||||||
*/
|
*/
|
||||||
public static ABI(): ContractAbi {
|
public static ABI(): ContractAbi {
|
||||||
const abi = [
|
const abi = [
|
||||||
|
{
|
||||||
|
anonymous: false,
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: 'orderHash',
|
||||||
|
type: 'bytes32',
|
||||||
|
indexed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'maker',
|
||||||
|
type: 'address',
|
||||||
|
indexed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiry',
|
||||||
|
type: 'uint64',
|
||||||
|
indexed: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: 'ExpiredRfqOrder',
|
||||||
|
outputs: [],
|
||||||
|
type: 'event',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
anonymous: false,
|
anonymous: false,
|
||||||
inputs: [
|
inputs: [
|
||||||
@ -1193,6 +1224,253 @@ export class IZeroExContract extends BaseContract {
|
|||||||
stateMutability: 'payable',
|
stateMutability: 'payable',
|
||||||
type: 'function',
|
type: 'function',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: 'fillData',
|
||||||
|
type: 'tuple',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
name: 'inputToken',
|
||||||
|
type: 'address',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'outputToken',
|
||||||
|
type: 'address',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sellAmount',
|
||||||
|
type: 'uint256',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'calls',
|
||||||
|
type: 'tuple[]',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
name: 'selector',
|
||||||
|
type: 'bytes4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sellAmount',
|
||||||
|
type: 'uint256',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'data',
|
||||||
|
type: 'bytes',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'minBuyAmount',
|
||||||
|
type: 'uint256',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: 'batchFill',
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: 'outputTokenAmount',
|
||||||
|
type: 'uint256',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: 'payable',
|
||||||
|
type: 'function',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: 'orders',
|
||||||
|
type: 'tuple[]',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
name: 'makerToken',
|
||||||
|
type: 'address',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'takerToken',
|
||||||
|
type: 'address',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'makerAmount',
|
||||||
|
type: 'uint128',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'takerAmount',
|
||||||
|
type: 'uint128',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'takerTokenFeeAmount',
|
||||||
|
type: 'uint128',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'maker',
|
||||||
|
type: 'address',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'taker',
|
||||||
|
type: 'address',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sender',
|
||||||
|
type: 'address',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'feeRecipient',
|
||||||
|
type: 'address',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pool',
|
||||||
|
type: 'bytes32',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiry',
|
||||||
|
type: 'uint64',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'salt',
|
||||||
|
type: 'uint256',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'signatures',
|
||||||
|
type: 'tuple[]',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
name: 'signatureType',
|
||||||
|
type: 'uint8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'v',
|
||||||
|
type: 'uint8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'r',
|
||||||
|
type: 'bytes32',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 's',
|
||||||
|
type: 'bytes32',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'takerTokenFillAmounts',
|
||||||
|
type: 'uint128[]',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'revertIfIncomplete',
|
||||||
|
type: 'bool',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: 'batchFillLimitOrders',
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: 'takerTokenFilledAmounts',
|
||||||
|
type: 'uint128[]',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'makerTokenFilledAmounts',
|
||||||
|
type: 'uint128[]',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: 'payable',
|
||||||
|
type: 'function',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: 'orders',
|
||||||
|
type: 'tuple[]',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
name: 'makerToken',
|
||||||
|
type: 'address',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'takerToken',
|
||||||
|
type: 'address',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'makerAmount',
|
||||||
|
type: 'uint128',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'takerAmount',
|
||||||
|
type: 'uint128',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'maker',
|
||||||
|
type: 'address',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'taker',
|
||||||
|
type: 'address',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'txOrigin',
|
||||||
|
type: 'address',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pool',
|
||||||
|
type: 'bytes32',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiry',
|
||||||
|
type: 'uint64',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'salt',
|
||||||
|
type: 'uint256',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'signatures',
|
||||||
|
type: 'tuple[]',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
name: 'signatureType',
|
||||||
|
type: 'uint8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'v',
|
||||||
|
type: 'uint8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'r',
|
||||||
|
type: 'bytes32',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 's',
|
||||||
|
type: 'bytes32',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'takerTokenFillAmounts',
|
||||||
|
type: 'uint128[]',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'revertIfIncomplete',
|
||||||
|
type: 'bool',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: 'batchFillRfqOrders',
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: 'takerTokenFilledAmounts',
|
||||||
|
type: 'uint128[]',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'makerTokenFilledAmounts',
|
||||||
|
type: 'uint128[]',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: 'nonpayable',
|
||||||
|
type: 'function',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
@ -2804,6 +3082,51 @@ export class IZeroExContract extends BaseContract {
|
|||||||
stateMutability: 'nonpayable',
|
stateMutability: 'nonpayable',
|
||||||
type: 'function',
|
type: 'function',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: 'fillData',
|
||||||
|
type: 'tuple',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
name: 'tokens',
|
||||||
|
type: 'address[]',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sellAmount',
|
||||||
|
type: 'uint256',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'calls',
|
||||||
|
type: 'tuple[]',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
name: 'selector',
|
||||||
|
type: 'bytes4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'data',
|
||||||
|
type: 'bytes',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'minBuyAmount',
|
||||||
|
type: 'uint256',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: 'multiHopFill',
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: 'outputTokenAmount',
|
||||||
|
type: 'uint256',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: 'payable',
|
||||||
|
type: 'function',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
inputs: [],
|
inputs: [],
|
||||||
name: 'owner',
|
name: 'owner',
|
||||||
@ -3705,6 +4028,240 @@ export class IZeroExContract extends BaseContract {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
public batchFill(
|
||||||
|
fillData: {
|
||||||
|
inputToken: string;
|
||||||
|
outputToken: string;
|
||||||
|
sellAmount: BigNumber;
|
||||||
|
calls: Array<{ selector: string; sellAmount: BigNumber; data: string }>;
|
||||||
|
},
|
||||||
|
minBuyAmount: BigNumber,
|
||||||
|
): ContractTxFunctionObj<BigNumber> {
|
||||||
|
const self = (this as any) as IZeroExContract;
|
||||||
|
|
||||||
|
assert.isBigNumber('minBuyAmount', minBuyAmount);
|
||||||
|
const functionSignature = 'batchFill((address,address,uint256,(bytes4,uint256,bytes)[]),uint256)';
|
||||||
|
|
||||||
|
return {
|
||||||
|
async sendTransactionAsync(
|
||||||
|
txData?: Partial<TxData> | undefined,
|
||||||
|
opts: SendTransactionOpts = { shouldValidate: true },
|
||||||
|
): Promise<string> {
|
||||||
|
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
|
||||||
|
{ data: this.getABIEncodedTransactionData(), ...txData },
|
||||||
|
this.estimateGasAsync.bind(this),
|
||||||
|
);
|
||||||
|
if (opts.shouldValidate !== false) {
|
||||||
|
await this.callAsync(txDataWithDefaults);
|
||||||
|
}
|
||||||
|
return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults);
|
||||||
|
},
|
||||||
|
awaitTransactionSuccessAsync(
|
||||||
|
txData?: Partial<TxData>,
|
||||||
|
opts: AwaitTransactionSuccessOpts = { shouldValidate: true },
|
||||||
|
): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> {
|
||||||
|
return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts);
|
||||||
|
},
|
||||||
|
async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
|
||||||
|
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
|
||||||
|
data: this.getABIEncodedTransactionData(),
|
||||||
|
...txData,
|
||||||
|
});
|
||||||
|
return self._web3Wrapper.estimateGasAsync(txDataWithDefaults);
|
||||||
|
},
|
||||||
|
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<BigNumber> {
|
||||||
|
BaseContract._assertCallParams(callData, defaultBlock);
|
||||||
|
const rawCallResult = await self._performCallAsync(
|
||||||
|
{ data: this.getABIEncodedTransactionData(), ...callData },
|
||||||
|
defaultBlock,
|
||||||
|
);
|
||||||
|
const abiEncoder = self._lookupAbiEncoder(functionSignature);
|
||||||
|
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
|
||||||
|
return abiEncoder.strictDecodeReturnValue<BigNumber>(rawCallResult);
|
||||||
|
},
|
||||||
|
getABIEncodedTransactionData(): string {
|
||||||
|
return self._strictEncodeArguments(functionSignature, [fillData, minBuyAmount]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Fills multiple limit orders.
|
||||||
|
* @param orders Array of limit orders.
|
||||||
|
* @param signatures Array of signatures corresponding to each order.
|
||||||
|
* @param takerTokenFillAmounts Array of desired amounts to fill each order.
|
||||||
|
* @param revertIfIncomplete If true, reverts if this function fails to
|
||||||
|
* fill the full fill amount for any individual order.
|
||||||
|
*/
|
||||||
|
public batchFillLimitOrders(
|
||||||
|
orders: Array<{
|
||||||
|
makerToken: string;
|
||||||
|
takerToken: string;
|
||||||
|
makerAmount: BigNumber;
|
||||||
|
takerAmount: BigNumber;
|
||||||
|
takerTokenFeeAmount: BigNumber;
|
||||||
|
maker: string;
|
||||||
|
taker: string;
|
||||||
|
sender: string;
|
||||||
|
feeRecipient: string;
|
||||||
|
pool: string;
|
||||||
|
expiry: BigNumber;
|
||||||
|
salt: BigNumber;
|
||||||
|
}>,
|
||||||
|
signatures: Array<{ signatureType: number | BigNumber; v: number | BigNumber; r: string; s: string }>,
|
||||||
|
takerTokenFillAmounts: BigNumber[],
|
||||||
|
revertIfIncomplete: boolean,
|
||||||
|
): ContractTxFunctionObj<[BigNumber[], BigNumber[]]> {
|
||||||
|
const self = (this as any) as IZeroExContract;
|
||||||
|
assert.isArray('orders', orders);
|
||||||
|
assert.isArray('signatures', signatures);
|
||||||
|
assert.isArray('takerTokenFillAmounts', takerTokenFillAmounts);
|
||||||
|
assert.isBoolean('revertIfIncomplete', revertIfIncomplete);
|
||||||
|
const functionSignature =
|
||||||
|
'batchFillLimitOrders((address,address,uint128,uint128,uint128,address,address,address,address,bytes32,uint64,uint256)[],(uint8,uint8,bytes32,bytes32)[],uint128[],bool)';
|
||||||
|
|
||||||
|
return {
|
||||||
|
async sendTransactionAsync(
|
||||||
|
txData?: Partial<TxData> | undefined,
|
||||||
|
opts: SendTransactionOpts = { shouldValidate: true },
|
||||||
|
): Promise<string> {
|
||||||
|
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
|
||||||
|
{ data: this.getABIEncodedTransactionData(), ...txData },
|
||||||
|
this.estimateGasAsync.bind(this),
|
||||||
|
);
|
||||||
|
if (opts.shouldValidate !== false) {
|
||||||
|
await this.callAsync(txDataWithDefaults);
|
||||||
|
}
|
||||||
|
return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults);
|
||||||
|
},
|
||||||
|
awaitTransactionSuccessAsync(
|
||||||
|
txData?: Partial<TxData>,
|
||||||
|
opts: AwaitTransactionSuccessOpts = { shouldValidate: true },
|
||||||
|
): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> {
|
||||||
|
return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts);
|
||||||
|
},
|
||||||
|
async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
|
||||||
|
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
|
||||||
|
data: this.getABIEncodedTransactionData(),
|
||||||
|
...txData,
|
||||||
|
});
|
||||||
|
return self._web3Wrapper.estimateGasAsync(txDataWithDefaults);
|
||||||
|
},
|
||||||
|
async callAsync(
|
||||||
|
callData: Partial<CallData> = {},
|
||||||
|
defaultBlock?: BlockParam,
|
||||||
|
): Promise<[BigNumber[], BigNumber[]]> {
|
||||||
|
BaseContract._assertCallParams(callData, defaultBlock);
|
||||||
|
const rawCallResult = await self._performCallAsync(
|
||||||
|
{ data: this.getABIEncodedTransactionData(), ...callData },
|
||||||
|
defaultBlock,
|
||||||
|
);
|
||||||
|
const abiEncoder = self._lookupAbiEncoder(functionSignature);
|
||||||
|
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
|
||||||
|
return abiEncoder.strictDecodeReturnValue<[BigNumber[], BigNumber[]]>(rawCallResult);
|
||||||
|
},
|
||||||
|
getABIEncodedTransactionData(): string {
|
||||||
|
return self._strictEncodeArguments(functionSignature, [
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
takerTokenFillAmounts,
|
||||||
|
revertIfIncomplete,
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Fills multiple RFQ orders.
|
||||||
|
* @param orders Array of RFQ orders.
|
||||||
|
* @param signatures Array of signatures corresponding to each order.
|
||||||
|
* @param takerTokenFillAmounts Array of desired amounts to fill each order.
|
||||||
|
* @param revertIfIncomplete If true, reverts if this function fails to
|
||||||
|
* fill the full fill amount for any individual order.
|
||||||
|
*/
|
||||||
|
public batchFillRfqOrders(
|
||||||
|
orders: Array<{
|
||||||
|
makerToken: string;
|
||||||
|
takerToken: string;
|
||||||
|
makerAmount: BigNumber;
|
||||||
|
takerAmount: BigNumber;
|
||||||
|
maker: string;
|
||||||
|
taker: string;
|
||||||
|
txOrigin: string;
|
||||||
|
pool: string;
|
||||||
|
expiry: BigNumber;
|
||||||
|
salt: BigNumber;
|
||||||
|
}>,
|
||||||
|
signatures: Array<{ signatureType: number | BigNumber; v: number | BigNumber; r: string; s: string }>,
|
||||||
|
takerTokenFillAmounts: BigNumber[],
|
||||||
|
revertIfIncomplete: boolean,
|
||||||
|
): ContractTxFunctionObj<[BigNumber[], BigNumber[]]> {
|
||||||
|
const self = (this as any) as IZeroExContract;
|
||||||
|
assert.isArray('orders', orders);
|
||||||
|
assert.isArray('signatures', signatures);
|
||||||
|
assert.isArray('takerTokenFillAmounts', takerTokenFillAmounts);
|
||||||
|
assert.isBoolean('revertIfIncomplete', revertIfIncomplete);
|
||||||
|
const functionSignature =
|
||||||
|
'batchFillRfqOrders((address,address,uint128,uint128,address,address,address,bytes32,uint64,uint256)[],(uint8,uint8,bytes32,bytes32)[],uint128[],bool)';
|
||||||
|
|
||||||
|
return {
|
||||||
|
async sendTransactionAsync(
|
||||||
|
txData?: Partial<TxData> | undefined,
|
||||||
|
opts: SendTransactionOpts = { shouldValidate: true },
|
||||||
|
): Promise<string> {
|
||||||
|
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
|
||||||
|
{ data: this.getABIEncodedTransactionData(), ...txData },
|
||||||
|
this.estimateGasAsync.bind(this),
|
||||||
|
);
|
||||||
|
if (opts.shouldValidate !== false) {
|
||||||
|
await this.callAsync(txDataWithDefaults);
|
||||||
|
}
|
||||||
|
return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults);
|
||||||
|
},
|
||||||
|
awaitTransactionSuccessAsync(
|
||||||
|
txData?: Partial<TxData>,
|
||||||
|
opts: AwaitTransactionSuccessOpts = { shouldValidate: true },
|
||||||
|
): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> {
|
||||||
|
return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts);
|
||||||
|
},
|
||||||
|
async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
|
||||||
|
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
|
||||||
|
data: this.getABIEncodedTransactionData(),
|
||||||
|
...txData,
|
||||||
|
});
|
||||||
|
return self._web3Wrapper.estimateGasAsync(txDataWithDefaults);
|
||||||
|
},
|
||||||
|
async callAsync(
|
||||||
|
callData: Partial<CallData> = {},
|
||||||
|
defaultBlock?: BlockParam,
|
||||||
|
): Promise<[BigNumber[], BigNumber[]]> {
|
||||||
|
BaseContract._assertCallParams(callData, defaultBlock);
|
||||||
|
const rawCallResult = await self._performCallAsync(
|
||||||
|
{ data: this.getABIEncodedTransactionData(), ...callData },
|
||||||
|
defaultBlock,
|
||||||
|
);
|
||||||
|
const abiEncoder = self._lookupAbiEncoder(functionSignature);
|
||||||
|
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
|
||||||
|
return abiEncoder.strictDecodeReturnValue<[BigNumber[], BigNumber[]]>(rawCallResult);
|
||||||
|
},
|
||||||
|
getABIEncodedTransactionData(): string {
|
||||||
|
return self._strictEncodeArguments(functionSignature, [
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
takerTokenFillAmounts,
|
||||||
|
revertIfIncomplete,
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Batch version of `getLimitOrderRelevantState()`, without reverting.
|
* Batch version of `getLimitOrderRelevantState()`, without reverting.
|
||||||
* Orders that would normally cause `getLimitOrderRelevantState()`
|
* Orders that would normally cause `getLimitOrderRelevantState()`
|
||||||
@ -5679,6 +6236,67 @@ export class IZeroExContract extends BaseContract {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
public multiHopFill(
|
||||||
|
fillData: { tokens: string[]; sellAmount: BigNumber; calls: Array<{ selector: string; data: string }> },
|
||||||
|
minBuyAmount: BigNumber,
|
||||||
|
): ContractTxFunctionObj<BigNumber> {
|
||||||
|
const self = (this as any) as IZeroExContract;
|
||||||
|
|
||||||
|
assert.isBigNumber('minBuyAmount', minBuyAmount);
|
||||||
|
const functionSignature = 'multiHopFill((address[],uint256,(bytes4,bytes)[]),uint256)';
|
||||||
|
|
||||||
|
return {
|
||||||
|
async sendTransactionAsync(
|
||||||
|
txData?: Partial<TxData> | undefined,
|
||||||
|
opts: SendTransactionOpts = { shouldValidate: true },
|
||||||
|
): Promise<string> {
|
||||||
|
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
|
||||||
|
{ data: this.getABIEncodedTransactionData(), ...txData },
|
||||||
|
this.estimateGasAsync.bind(this),
|
||||||
|
);
|
||||||
|
if (opts.shouldValidate !== false) {
|
||||||
|
await this.callAsync(txDataWithDefaults);
|
||||||
|
}
|
||||||
|
return self._web3Wrapper.sendTransactionAsync(txDataWithDefaults);
|
||||||
|
},
|
||||||
|
awaitTransactionSuccessAsync(
|
||||||
|
txData?: Partial<TxData>,
|
||||||
|
opts: AwaitTransactionSuccessOpts = { shouldValidate: true },
|
||||||
|
): PromiseWithTransactionHash<TransactionReceiptWithDecodedLogs> {
|
||||||
|
return self._promiseWithTransactionHash(this.sendTransactionAsync(txData, opts), opts);
|
||||||
|
},
|
||||||
|
async estimateGasAsync(txData?: Partial<TxData> | undefined): Promise<number> {
|
||||||
|
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync({
|
||||||
|
data: this.getABIEncodedTransactionData(),
|
||||||
|
...txData,
|
||||||
|
});
|
||||||
|
return self._web3Wrapper.estimateGasAsync(txDataWithDefaults);
|
||||||
|
},
|
||||||
|
async callAsync(callData: Partial<CallData> = {}, defaultBlock?: BlockParam): Promise<BigNumber> {
|
||||||
|
BaseContract._assertCallParams(callData, defaultBlock);
|
||||||
|
const rawCallResult = await self._performCallAsync(
|
||||||
|
{ data: this.getABIEncodedTransactionData(), ...callData },
|
||||||
|
defaultBlock,
|
||||||
|
);
|
||||||
|
const abiEncoder = self._lookupAbiEncoder(functionSignature);
|
||||||
|
BaseContract._throwIfUnexpectedEmptyCallResult(rawCallResult, abiEncoder);
|
||||||
|
return abiEncoder.strictDecodeReturnValue<BigNumber>(rawCallResult);
|
||||||
|
},
|
||||||
|
getABIEncodedTransactionData(): string {
|
||||||
|
return self._strictEncodeArguments(functionSignature, [fillData, minBuyAmount]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* The owner of this contract.
|
* The owner of this contract.
|
||||||
*/
|
*/
|
||||||
|
@ -125,6 +125,7 @@ export {
|
|||||||
IZeroExContract,
|
IZeroExContract,
|
||||||
IZeroExEventArgs,
|
IZeroExEventArgs,
|
||||||
IZeroExEvents,
|
IZeroExEvents,
|
||||||
|
IZeroExExpiredRfqOrderEventArgs,
|
||||||
IZeroExLiquidityProviderSwapEventArgs,
|
IZeroExLiquidityProviderSwapEventArgs,
|
||||||
IZeroExMetaTransactionExecutedEventArgs,
|
IZeroExMetaTransactionExecutedEventArgs,
|
||||||
IZeroExMigratedEventArgs,
|
IZeroExMigratedEventArgs,
|
||||||
|
@ -110,6 +110,20 @@ export class OnlyOrderMakerAllowed extends RevertError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class BatchFillIncompleteError extends RevertError {
|
||||||
|
constructor(orderHash?: string, takerTokenFilledAmount?: Numberish, takerTokenFillAmount?: Numberish) {
|
||||||
|
super(
|
||||||
|
'BatchFillIncompleteError',
|
||||||
|
'BatchFillIncompleteError(bytes32 orderHash, uint256 takerTokenFilledAmount, uint256 takerTokenFillAmount)',
|
||||||
|
{
|
||||||
|
orderHash,
|
||||||
|
takerTokenFilledAmount,
|
||||||
|
takerTokenFillAmount,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const types = [
|
const types = [
|
||||||
ProtocolFeeRefundFailed,
|
ProtocolFeeRefundFailed,
|
||||||
OrderNotFillableByOriginError,
|
OrderNotFillableByOriginError,
|
||||||
@ -120,6 +134,7 @@ const types = [
|
|||||||
CancelSaltTooLowError,
|
CancelSaltTooLowError,
|
||||||
FillOrKillFailedError,
|
FillOrKillFailedError,
|
||||||
OnlyOrderMakerAllowed,
|
OnlyOrderMakerAllowed,
|
||||||
|
BatchFillIncompleteError,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Register the types we've defined.
|
// Register the types we've defined.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user