EP Native Orders (#27)

* `@0x/contracts-zero-ex`: add limit orders feature
`@0x/contracts-utils`: add `uint128` functions to `LibSafeMathV06`

* `@0x/contract-addresses`: Update ganache snapshot addresses

* `@0x/contracts-zero-ex`: Mask EIP712 struct hash values.

* `@0x/contracts-zero-ex`: Add more limit order tests

* `@0x/contracts-zero-ex`: Fix typos

* `@0x/contracts-zero-ex`: Compute fee collector address after protocol fee zero check.

* `@0x/contracts-zero-ex`: Remove WETH payment logic from fee collector fixin

* `@0x/contracts-zero-ex`: Convert all ETH to WETH in `FeeCollector`.

* `@0x/contracts-zero-ex`: Address review feedback

* `@0x/contracts-zero-ex`: Export more utils

* `@0x/contracts-zero-ex`: Rename `LimitOrdersFeatures`, `LibLimitOrders`, etc. into `*NativeOrders*`.
`@0x/contracts-zero-ex`: Emit `protocolFeePaid` in native order fill events.
`@0x/contracts-zero-ex`: Refactor to get around stack limits.
`@0x/contracts-zero-ex`: Use different storage mappings for RFQ and limit order pair cancellations.

* `@0x/contracts-zero-ex`: Add `getProtocolFeeMultiplier()` and `transferProtocolFeesForPools()` to `NativeOrdersFeature`.

* `@0x/contracts-zero-ex`: Fix broken tests

* update orders docs

* `@0x/contracts-zero-ex`: Add more tests to `NativeOrdersFeature`

* rebuild after rebase

* `@0x/contract-addresses`: Fix changelog booboo

* `@0x/contracts-zero-ex`: Add method selectors output to generated artifacts

* `@0x/contracts-zero-ex`: Add maker address to order cancel events.
`@0x/contracts-zreo-ex`: Remove `UpTo` suffix from order pair cancellation functions.
`@0x/contracts-zreo-ex`: Address misc review comments.

* `@0x/contracts-zero-ex`: More SafeMath in native orders

* update orders docs

Co-authored-by: Lawrence Forman <me@merklejerk.com>
This commit is contained in:
Lawrence Forman 2020-11-17 15:39:40 -05:00 committed by GitHub
parent 4f82543bdf
commit 561b60a24d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 4275 additions and 348 deletions

View File

@ -1,4 +1,13 @@
[
{
"version": "4.6.0",
"changes": [
{
"note": "Add `uint128` functions to `LibSafeMathV06`",
"pr": 27
}
]
},
{
"timestamp": 1605302002,
"version": "4.5.8",

View File

@ -105,4 +105,86 @@ library LibSafeMathV06 {
{
return a < b ? a : b;
}
function safeMul128(uint128 a, uint128 b)
internal
pure
returns (uint128)
{
if (a == 0) {
return 0;
}
uint128 c = a * b;
if (c / a != b) {
LibRichErrorsV06.rrevert(LibSafeMathRichErrorsV06.Uint256BinOpError(
LibSafeMathRichErrorsV06.BinOpErrorCodes.MULTIPLICATION_OVERFLOW,
a,
b
));
}
return c;
}
function safeDiv128(uint128 a, uint128 b)
internal
pure
returns (uint128)
{
if (b == 0) {
LibRichErrorsV06.rrevert(LibSafeMathRichErrorsV06.Uint256BinOpError(
LibSafeMathRichErrorsV06.BinOpErrorCodes.DIVISION_BY_ZERO,
a,
b
));
}
uint128 c = a / b;
return c;
}
function safeSub128(uint128 a, uint128 b)
internal
pure
returns (uint128)
{
if (b > a) {
LibRichErrorsV06.rrevert(LibSafeMathRichErrorsV06.Uint256BinOpError(
LibSafeMathRichErrorsV06.BinOpErrorCodes.SUBTRACTION_UNDERFLOW,
a,
b
));
}
return a - b;
}
function safeAdd128(uint128 a, uint128 b)
internal
pure
returns (uint128)
{
uint128 c = a + b;
if (c < a) {
LibRichErrorsV06.rrevert(LibSafeMathRichErrorsV06.Uint256BinOpError(
LibSafeMathRichErrorsV06.BinOpErrorCodes.ADDITION_OVERFLOW,
a,
b
));
}
return c;
}
function max128(uint128 a, uint128 b)
internal
pure
returns (uint128)
{
return a >= b ? a : b;
}
function min128(uint128 a, uint128 b)
internal
pure
returns (uint128)
{
return a < b ? a : b;
}
}

View File

@ -55,6 +55,10 @@
{
"note": "Add `LibSignature` library",
"pr": 21
},
{
"note": "Add `LimitOrdersFeature`",
"pr": 27
}
],
"timestamp": 1604355662

View File

@ -19,7 +19,8 @@
"evm.bytecode.object",
"evm.bytecode.sourceMap",
"evm.deployedBytecode.object",
"evm.deployedBytecode.sourceMap"
"evm.deployedBytecode.sourceMap",
"evm.methodIdentifiers"
]
}
}

View File

@ -27,6 +27,7 @@ import "./features/ITransformERC20Feature.sol";
import "./features/IMetaTransactionsFeature.sol";
import "./features/IUniswapFeature.sol";
import "./features/ILiquidityProviderFeature.sol";
import "./features/INativeOrdersFeature.sol";
/// @dev Interface for a fully featured Exchange Proxy.
@ -38,7 +39,8 @@ interface IZeroEx is
ITransformERC20Feature,
IMetaTransactionsFeature,
IUniswapFeature,
ILiquidityProviderFeature
ILiquidityProviderFeature,
INativeOrdersFeature
{
// solhint-disable state-visibility

View File

@ -0,0 +1,172 @@
/*
Copyright 2020 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;
library LibNativeOrdersRichErrors {
// solhint-disable func-name-mixedcase
function ProtocolFeeRefundFailed(
address receiver,
uint256 refundAmount
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("ProtocolFeeRefundFailed(address,uint256)")),
receiver,
refundAmount
);
}
function OrderNotFillableByOriginError(
bytes32 orderHash,
address txOrigin,
address orderTxOrigin
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("OrderNotFillableByOriginError(bytes32,address,address)")),
orderHash,
txOrigin,
orderTxOrigin
);
}
function OrderNotFillableError(
bytes32 orderHash,
uint8 orderStatus
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("OrderNotFillableError(bytes32,uint8)")),
orderHash,
orderStatus
);
}
function OrderNotSignedByMakerError(
bytes32 orderHash,
address signer,
address maker
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("OrderNotSignedByMakerError(bytes32,address,address)")),
orderHash,
signer,
maker
);
}
function OrderNotFillableBySenderError(
bytes32 orderHash,
address sender,
address orderSender
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("OrderNotFillableBySenderError(bytes32,address,address)")),
orderHash,
sender,
orderSender
);
}
function OrderNotFillableByTakerError(
bytes32 orderHash,
address taker,
address orderTaker
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("OrderNotFillableByTakerError(bytes32,address,address)")),
orderHash,
taker,
orderTaker
);
}
function CancelSaltTooLowError(
uint256 minValidSalt,
uint256 oldMinValidSalt
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("CancelSaltTooLowError(uint256,uint256)")),
minValidSalt,
oldMinValidSalt
);
}
function FillOrKillFailedError(
bytes32 orderHash,
uint256 takerTokenFilledAmount,
uint256 takerTokenFillAmount
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("FillOrKillFailedError(bytes32,uint256,uint256)")),
orderHash,
takerTokenFilledAmount,
takerTokenFillAmount
);
}
function OnlyOrderMakerAllowed(
bytes32 orderHash,
address sender,
address maker
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
bytes4(keccak256("OnlyOrderMakerAllowed(bytes32,address,address)")),
orderHash,
sender,
maker
);
}
}

View File

@ -58,8 +58,8 @@ contract FeeCollector is AuthorizableV06 {
onlyAuthorized
{
// Leave 1 wei behind to avoid expensive zero-->non-zero state change.
if (address(this).balance > 1) {
weth.deposit{value: address(this).balance - 1}();
if (address(this).balance > 0) {
weth.deposit{value: address(this).balance}();
}
}
}

View File

@ -0,0 +1,328 @@
/*
Copyright 2020 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 Feature for interacting with limit orders.
interface INativeOrdersFeature {
/// @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 protocolFeePaid How much protocol fee was paid.
/// @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,
uint256 protocolFeePaid,
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 or 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 PairOrdersCancelled(
address maker,
address makerToken,
address takerToken,
uint256 minValidSalt
);
/// @dev Transfers protocol fees from the `FeeCollector` pools into
/// the staking contract.
/// @param poolIds Staking pool IDs
function transferProtocolFeesForPools(bytes32[] calldata poolIds)
external;
/// @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 calldata order,
LibSignature.Signature calldata signature,
uint128 takerTokenFillAmount
)
external
payable
returns (uint128 takerTokenFilledAmount, uint128 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 calldata order,
LibSignature.Signature calldata signature,
uint128 takerTokenFillAmount
)
external
payable
returns (uint128 takerTokenFilledAmount, uint128 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 calldata order,
LibSignature.Signature calldata signature,
uint128 takerTokenFillAmount
)
external
payable
returns (uint128 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 calldata order,
LibSignature.Signature calldata signature,
uint128 takerTokenFillAmount
)
external
payable
returns (uint128 makerTokenFilledAmount);
/// @dev Fill a limit 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 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 calldata order,
LibSignature.Signature calldata signature,
uint128 takerTokenFillAmount,
address taker,
address sender
)
external
payable
returns (uint128 takerTokenFilledAmount, uint128 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 calldata order,
LibSignature.Signature calldata signature,
uint128 takerTokenFillAmount,
address taker
)
external
payable
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount);
/// @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 calldata order)
external;
/// @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 calldata order)
external;
/// @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[] calldata orders)
external;
/// @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[] calldata orders)
external;
/// @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
)
external;
/// @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[] calldata makerTokens,
IERC20TokenV06[] calldata takerTokens,
uint256[] calldata minValidSalts
)
external;
/// @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
)
external;
/// @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[] calldata makerTokens,
IERC20TokenV06[] calldata takerTokens,
uint256[] calldata minValidSalts
)
external;
/// @dev Get the order info for a limit order.
/// @param order The limit order.
/// @return orderInfo Info about the order.
function getLimitOrderInfo(LibNativeOrder.LimitOrder calldata order)
external
view
returns (LibNativeOrder.OrderInfo memory orderInfo);
/// @dev Get the order info for an RFQ order.
/// @param order The RFQ order.
/// @return orderInfo Info about the order.
function getRfqOrderInfo(LibNativeOrder.RfqOrder calldata order)
external
view
returns (LibNativeOrder.OrderInfo memory orderInfo);
/// @dev Get the canonical hash of a limit order.
/// @param order The limit order.
/// @return orderHash The order hash.
function getLimitOrderHash(LibNativeOrder.LimitOrder calldata order)
external
view
returns (bytes32 orderHash);
/// @dev Get the canonical hash of an RFQ order.
/// @param order The RFQ order.
/// @return orderHash The order hash.
function getRfqOrderHash(LibNativeOrder.RfqOrder calldata order)
external
view
returns (bytes32 orderHash);
/// @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);
}

View File

@ -0,0 +1,984 @@
/*
Copyright 2020 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-erc20/contracts/src/v06/LibERC20TokenV06.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 "../fixins/FixinCommon.sol";
import "../fixins/FixinProtocolFees.sol";
import "../fixins/FixinEIP712.sol";
import "../errors/LibNativeOrdersRichErrors.sol";
import "../migrations/LibMigrate.sol";
import "../storage/LibNativeOrdersStorage.sol";
import "../vendor/v3/IStaking.sol";
import "./libs/LibTokenSpender.sol";
import "./libs/LibSignature.sol";
import "./libs/LibNativeOrder.sol";
import "./INativeOrdersFeature.sol";
import "./IFeature.sol";
/// @dev Feature for interacting with limit orders.
contract NativeOrdersFeature is
IFeature,
INativeOrdersFeature,
FixinCommon,
FixinProtocolFees,
FixinEIP712
{
using LibSafeMathV06 for uint256;
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;
}
/// @dev Name of this feature.
string public constant override FEATURE_NAME = "LimitOrders";
/// @dev Version of this feature.
uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0);
/// @dev Highest bit of a uint256, used to flag cancelled orders.
uint256 private constant HIGH_BIT = 1 << 255;
constructor(
address zeroExAddress,
IEtherTokenV06 weth,
IStaking staking,
uint32 protocolFeeMultiplier
)
public
FixinEIP712(zeroExAddress)
FixinProtocolFees(weth, staking, protocolFeeMultiplier)
{
// 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.transferProtocolFeesForPools.selector);
_registerFeatureFunction(this.fillLimitOrder.selector);
_registerFeatureFunction(this.fillRfqOrder.selector);
_registerFeatureFunction(this.fillOrKillLimitOrder.selector);
_registerFeatureFunction(this.fillOrKillRfqOrder.selector);
_registerFeatureFunction(this._fillLimitOrder.selector);
_registerFeatureFunction(this._fillRfqOrder.selector);
_registerFeatureFunction(this.cancelLimitOrder.selector);
_registerFeatureFunction(this.cancelRfqOrder.selector);
_registerFeatureFunction(this.batchCancelLimitOrders.selector);
_registerFeatureFunction(this.batchCancelRfqOrders.selector);
_registerFeatureFunction(this.cancelPairLimitOrders.selector);
_registerFeatureFunction(this.batchCancelPairLimitOrders.selector);
_registerFeatureFunction(this.cancelPairRfqOrders.selector);
_registerFeatureFunction(this.batchCancelPairRfqOrders.selector);
_registerFeatureFunction(this.getLimitOrderInfo.selector);
_registerFeatureFunction(this.getRfqOrderInfo.selector);
_registerFeatureFunction(this.getLimitOrderHash.selector);
_registerFeatureFunction(this.getRfqOrderHash.selector);
_registerFeatureFunction(this.getProtocolFeeMultiplier.selector);
return LibMigrate.MIGRATE_SUCCESS;
}
/// @dev Transfers protocol fees from the `FeeCollector` pools into
/// the staking contract.
/// @param poolIds Staking pool IDs
function transferProtocolFeesForPools(bytes32[] calldata poolIds)
external
override
{
for (uint256 i = 0; i < poolIds.length; ++i) {
_transferFeesForPool(poolIds[i]);
}
}
/// @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
override
payable
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
FillNativeOrderResults memory results =
_fillLimitOrderPrivate(FillLimitOrderPrivateParams({
order: order,
signature: signature,
takerTokenFillAmount: takerTokenFillAmount,
taker: msg.sender,
sender: msg.sender
}));
_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
override
payable
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
FillNativeOrderResults memory results =
_fillRfqOrderPrivate(
order,
signature,
takerTokenFillAmount,
msg.sender
);
_refundExcessProtocolFeeToSender(results.ethProtocolFeePaid);
(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
override
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();
}
_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
override
payable
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();
}
_refundExcessProtocolFeeToSender(results.ethProtocolFeePaid);
makerTokenFilledAmount = results.makerTokenFilledAmount;
}
/// @dev Fill a limit 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 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
override
payable
onlySelf
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
FillNativeOrderResults memory results =
_fillLimitOrderPrivate(FillLimitOrderPrivateParams({
order: order,
signature: signature,
takerTokenFillAmount: takerTokenFillAmount,
taker: taker,
sender: sender
}));
_refundExcessProtocolFeeToSender(results.ethProtocolFeePaid);
(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
override
payable
onlySelf
returns (uint128 takerTokenFilledAmount, uint128 makerTokenFilledAmount)
{
FillNativeOrderResults memory results =
_fillRfqOrderPrivate(
order,
signature,
takerTokenFillAmount,
taker
);
_refundExcessProtocolFeeToSender(results.ethProtocolFeePaid);
(takerTokenFilledAmount, makerTokenFilledAmount) = (
results.takerTokenFilledAmount,
results.makerTokenFilledAmount
);
}
/// @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
override
{
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
override
{
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
override
{
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
override
{
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
override
{
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 PairOrdersCancelled(
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
override
{
require(
makerTokens.length == takerTokens.length &&
makerTokens.length == minValidSalts.length,
"LimitOrdersFeature/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
override
{
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 PairOrdersCancelled(
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
override
{
require(
makerTokens.length == takerTokens.length &&
makerTokens.length == minValidSalts.length,
"LimitOrdersFeature/MISMATCHED_PAIR_ORDERS_ARRAY_LENGTHS"
);
for (uint256 i = 0; i < makerTokens.length; ++i) {
cancelPairRfqOrders(
makerTokens[i],
takerTokens[i],
minValidSalts[i]
);
}
}
/// @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
override
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
override
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
);
}
/// @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
override
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
override
view
returns (bytes32 orderHash)
{
return _getEIP712Hash(
LibNativeOrder.getRfqOrderStructHash(order)
);
}
/// @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
override
view
returns (uint32 multiplier)
{
return PROTOCOL_FEE_MULTIPLIER;
}
/// @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 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, msg.sender);
}
/// @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
));
LibTokenSpender.spendERC20Tokens(
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();
}
// Must be fillable by the tx.origin.
if (order.txOrigin != address(0) && order.txOrigin != tx.origin) {
LibNativeOrdersRichErrors.OrderNotFillableByOriginError(
orderInfo.orderHash,
tx.origin,
order.txOrigin
).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();
}
}
// Pay the protocol fee.
results.ethProtocolFeePaid = _collectProtocolFee(order.pool);
// 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,
results.ethProtocolFeePaid,
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.
LibTokenSpender.spendERC20Tokens(
settleInfo.takerToken,
settleInfo.taker,
settleInfo.maker,
takerTokenFilledAmount
);
// Transfer maker -> taker.
LibTokenSpender.spendERC20Tokens(
settleInfo.makerToken,
settleInfo.maker,
settleInfo.taker,
makerTokenFilledAmount
);
}
/// @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)
private
{
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();
}
}
}
}

View File

@ -0,0 +1,213 @@
/*
Copyright 2020 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";
/// @dev A library for common native order operations.
library LibNativeOrder {
enum OrderStatus {
INVALID,
FILLABLE,
FILLED,
CANCELLED,
EXPIRED
}
/// @dev A standard OTC or OO limit order.
struct LimitOrder {
IERC20TokenV06 makerToken;
IERC20TokenV06 takerToken;
uint128 makerAmount;
uint128 takerAmount;
uint128 takerTokenFeeAmount;
address maker;
address taker;
address sender;
address feeRecipient;
bytes32 pool;
uint64 expiry;
uint256 salt;
}
/// @dev An RFQ limit order.
struct RfqOrder {
IERC20TokenV06 makerToken;
IERC20TokenV06 takerToken;
uint128 makerAmount;
uint128 takerAmount;
address maker;
address txOrigin;
bytes32 pool;
uint64 expiry;
uint256 salt;
}
/// @dev Info on a limit or RFQ order.
struct OrderInfo {
bytes32 orderHash;
OrderStatus status;
uint128 takerTokenFilledAmount;
}
uint256 private constant UINT_128_MASK = (1 << 128) - 1;
uint256 private constant UINT_64_MASK = (1 << 64) - 1;
uint256 private constant ADDRESS_MASK = (1 << 160) - 1;
// The type hash for limit orders, which is:
// keccak256(abi.encodePacked(
// "LimitOrder(",
// "address makerToken,",
// "address takerToken,",
// "uint128 makerAmount,",
// "uint128 takerAmount,",
// "uint128 takerTokenFeeAmount,",
// "address maker,",
// "address taker,",
// "address sender,",
// "address feeRecipient,",
// "bytes32 pool,",
// "uint64 expiry,",
// "uint256 salt"
// ")"
// ))
uint256 private constant _LIMIT_ORDER_TYPEHASH =
0xce918627cb55462ddbb85e73de69a8b322f2bc88f4507c52fcad6d4c33c29d49;
// The type hash for RFQ orders, which is:
// keccak256(abi.encodePacked(
// "RfqOrder(",
// "address makerToken,",
// "address takerToken,",
// "uint128 makerAmount,",
// "uint128 takerAmount,",
// "address maker,",
// "address txOrigin,",
// "bytes32 pool,",
// "uint64 expiry,",
// "uint256 salt"
// ")"
// ))
uint256 private constant _RFQ_ORDER_TYPEHASH =
0xc6b3034376598bc7f28b05e81db7ed88486dcdb6b4a6c7300353fffc5f31f382;
/// @dev Get the struct hash of a limit order.
/// @param order The limit order.
/// @return structHash The struct hash of the order.
function getLimitOrderStructHash(LimitOrder memory order)
internal
pure
returns (bytes32 structHash)
{
// The struct hash is:
// keccak256(abi.encode(
// TYPE_HASH,
// order.makerToken,
// order.takerToken,
// order.makerAmount,
// order.takerAmount,
// order.takerTokenFeeAmount,
// order.maker,
// order.taker,
// order.sender,
// order.feeRecipient,
// order.pool,
// order.expiry,
// order.salt,
// ))
assembly {
let mem := mload(0x40)
mstore(mem, _LIMIT_ORDER_TYPEHASH)
// order.makerToken;
mstore(add(mem, 0x20), and(ADDRESS_MASK, mload(order)))
// order.takerToken;
mstore(add(mem, 0x40), and(ADDRESS_MASK, mload(add(order, 0x20))))
// order.makerAmount;
mstore(add(mem, 0x60), and(UINT_128_MASK, mload(add(order, 0x40))))
// order.takerAmount;
mstore(add(mem, 0x80), and(UINT_128_MASK, mload(add(order, 0x60))))
// order.takerTokenFeeAmount;
mstore(add(mem, 0xA0), and(UINT_128_MASK, mload(add(order, 0x80))))
// order.maker;
mstore(add(mem, 0xC0), and(ADDRESS_MASK, mload(add(order, 0xA0))))
// order.taker;
mstore(add(mem, 0xE0), and(ADDRESS_MASK, mload(add(order, 0xC0))))
// order.sender;
mstore(add(mem, 0x100), and(ADDRESS_MASK, mload(add(order, 0xE0))))
// order.feeRecipient;
mstore(add(mem, 0x120), and(ADDRESS_MASK, mload(add(order, 0x100))))
// order.pool;
mstore(add(mem, 0x140), mload(add(order, 0x120)))
// order.expiry;
mstore(add(mem, 0x160), and(UINT_64_MASK, mload(add(order, 0x140))))
// order.salt;
mstore(add(mem, 0x180), mload(add(order, 0x160)))
structHash := keccak256(mem, 0x1A0)
}
}
/// @dev Get the struct hash of a RFQ order.
/// @param order The RFQ order.
/// @return structHash The struct hash of the order.
function getRfqOrderStructHash(RfqOrder memory order)
internal
pure
returns (bytes32 structHash)
{
// The struct hash is:
// keccak256(abi.encode(
// TYPE_HASH,
// order.makerToken,
// order.takerToken,
// order.makerAmount,
// order.takerAmount,
// order.maker,
// order.txOrigin,
// order.pool,
// order.expiry,
// order.salt,
// ))
assembly {
let mem := mload(0x40)
mstore(mem, _RFQ_ORDER_TYPEHASH)
// order.makerToken;
mstore(add(mem, 0x20), and(ADDRESS_MASK, mload(order)))
// order.takerToken;
mstore(add(mem, 0x40), and(ADDRESS_MASK, mload(add(order, 0x20))))
// order.makerAmount;
mstore(add(mem, 0x60), and(UINT_128_MASK, mload(add(order, 0x40))))
// order.takerAmount;
mstore(add(mem, 0x80), and(UINT_128_MASK, mload(add(order, 0x60))))
// order.maker;
mstore(add(mem, 0xA0), and(ADDRESS_MASK, mload(add(order, 0x80))))
// order.txOrigin;
mstore(add(mem, 0xC0), and(ADDRESS_MASK, mload(add(order, 0xA0))))
// order.pool;
mstore(add(mem, 0xE0), mload(add(order, 0xC0)))
// order.expiry;
mstore(add(mem, 0x100), and(UINT_64_MASK, mload(add(order, 0xE0))))
// order.salt;
mstore(add(mem, 0x120), mload(add(order, 0x100)))
structHash := keccak256(mem, 0x140)
}
}
}

View File

@ -22,48 +22,55 @@ pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
import "../external/FeeCollector.sol";
import "../features/libs/LibTokenSpender.sol";
import "../vendor/v3/IStaking.sol";
/// @dev Helpers for collecting protocol fees.
abstract contract FixinProtocolFees {
bytes32 immutable feeCollectorCodeHash;
/// @dev The protocol fee multiplier.
uint32 public immutable PROTOCOL_FEE_MULTIPLIER;
/// @dev Hash of the fee collector init code.
bytes32 private immutable FEE_COLLECTOR_INIT_CODE_HASH;
/// @dev The WETH token contract.
IEtherTokenV06 private immutable WETH;
/// @dev The staking contract.
IStaking private immutable STAKING;
constructor() internal {
feeCollectorCodeHash = keccak256(type(FeeCollector).creationCode);
}
/// @dev Collect the specified protocol fee in either WETH or ETH. If
/// msg.value is non-zero, the fee will be paid in ETH. Otherwise,
/// this function attempts to transfer the fee in WETH. Either way,
/// The fee is stored in a per-pool fee collector contract.
/// @param poolId The pool ID for which a fee is being collected.
/// @param amount The amount of ETH/WETH to be collected.
/// @param weth The WETH token contract.
function _collectProtocolFee(
bytes32 poolId,
uint256 amount,
IERC20TokenV06 weth
constructor(
IEtherTokenV06 weth,
IStaking staking,
uint32 protocolFeeMultiplier
)
internal
{
FeeCollector feeCollector = _getFeeCollector(poolId);
FEE_COLLECTOR_INIT_CODE_HASH = keccak256(type(FeeCollector).creationCode);
WETH = weth;
STAKING = staking;
PROTOCOL_FEE_MULTIPLIER = protocolFeeMultiplier;
}
if (msg.value == 0) {
// WETH
LibTokenSpender.spendERC20Tokens(weth, msg.sender, address(feeCollector), amount);
} else {
// ETH
(bool success,) = address(feeCollector).call{value: amount}("");
require(success, "FixinProtocolFees/ETHER_TRANSFER_FALIED");
/// @dev Collect the specified protocol fee in ETH.
/// The fee is stored in a per-pool fee collector contract.
/// @param poolId The pool ID for which a fee is being collected.
/// @return ethProtocolFeePaid How much protocol fee was collected in ETH.
function _collectProtocolFee(bytes32 poolId)
internal
returns (uint256 ethProtocolFeePaid)
{
uint256 protocolFeePaid = _getSingleProtocolFee();
if (protocolFeePaid == 0) {
// Nothing to do.
return 0;
}
FeeCollector feeCollector = _getFeeCollector(poolId);
(bool success,) = address(feeCollector).call{value: protocolFeePaid}("");
require(success, "FixinProtocolFees/ETHER_TRANSFER_FALIED");
return protocolFeePaid;
}
/// @dev Transfer fees for a given pool to the staking contract.
/// @param poolId Identifies the pool whose fees are being paid.
function _transferFeesForPool(
bytes32 poolId,
IStaking staking,
IEtherTokenV06 weth
)
function _transferFeesForPool(bytes32 poolId)
internal
{
FeeCollector feeCollector = _getFeeCollector(poolId);
@ -75,18 +82,18 @@ abstract contract FixinProtocolFees {
if (codeSize == 0) {
// Create and initialize the contract if necessary.
new FeeCollector{salt: poolId}();
feeCollector.initialize(weth, staking, poolId);
new FeeCollector{salt: bytes32(poolId)}();
feeCollector.initialize(WETH, STAKING, poolId);
}
if (address(feeCollector).balance > 1) {
feeCollector.convertToWeth(weth);
feeCollector.convertToWeth(WETH);
}
uint256 bal = weth.balanceOf(address(feeCollector));
uint256 bal = WETH.balanceOf(address(feeCollector));
if (bal > 1) {
// Leave 1 wei behind to avoid high SSTORE cost of zero-->non-zero.
staking.payProtocolFee(
STAKING.payProtocolFee(
address(feeCollector),
address(feeCollector),
bal - 1);
@ -95,9 +102,7 @@ abstract contract FixinProtocolFees {
/// @dev Compute the CREATE2 address for a fee collector.
/// @param poolId The fee collector's pool ID.
function _getFeeCollector(
bytes32 poolId
)
function _getFeeCollector(bytes32 poolId)
internal
view
returns (FeeCollector)
@ -107,8 +112,18 @@ abstract contract FixinProtocolFees {
byte(0xff),
address(this),
poolId, // pool ID is salt
feeCollectorCodeHash
FEE_COLLECTOR_INIT_CODE_HASH
))));
return FeeCollector(addr);
}
/// @dev Get the cost of a single protocol fee.
/// @return protocolFeeAmount The protocol fee amount, in ETH/WETH.
function _getSingleProtocolFee()
internal
view
returns (uint256 protocolFeeAmount)
{
return uint256(PROTOCOL_FEE_MULTIPLIER) * tx.gasprice;
}
}

View File

@ -25,6 +25,7 @@ import "../features/TokenSpenderFeature.sol";
import "../features/TransformERC20Feature.sol";
import "../features/SignatureValidatorFeature.sol";
import "../features/MetaTransactionsFeature.sol";
import "../features/NativeOrdersFeature.sol";
import "../external/AllowanceTarget.sol";
import "./InitialMigration.sol";
@ -42,6 +43,7 @@ contract FullMigration {
TransformERC20Feature transformERC20;
SignatureValidatorFeature signatureValidator;
MetaTransactionsFeature metaTransactions;
NativeOrdersFeature nativeOrders;
}
/// @dev Parameters needed to initialize features.
@ -84,7 +86,7 @@ contract FullMigration {
/// @param features Features to add to the proxy.
/// @return _zeroEx The configured ZeroEx contract. Same as the `zeroEx` parameter.
/// @param migrateOpts Parameters needed to initialize features.
function initializeZeroEx(
function migrateZeroEx(
address payable owner,
ZeroEx zeroEx,
Features memory features,
@ -195,5 +197,16 @@ contract FullMigration {
address(this)
);
}
// NativeOrdersFeature
{
// Register the feature.
ownable.migrate(
address(features.nativeOrders),
abi.encodeWithSelector(
NativeOrdersFeature.migrate.selector
),
address(this)
);
}
}
}

View File

@ -0,0 +1,54 @@
/*
Copyright 2020 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 "./LibStorage.sol";
/// @dev Storage helpers for `NativeOrdersFeature`.
library LibNativeOrdersStorage {
/// @dev Storage bucket for this feature.
struct Storage {
// How much taker token has been filled in order.
// The lower `uint128` is the taker token fill amount.
// The high bit will be `1` if the order was directly cancelled.
mapping(bytes32 => uint256) orderHashToTakerTokenFilledAmount;
// The minimum valid order salt for a given maker and order pair (maker, taker)
// for limit orders.
mapping(address => mapping(address => mapping(address => uint256)))
limitOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt;
// The minimum valid order salt for a given maker and order pair (maker, taker)
// for RFQ orders.
mapping(address => mapping(address => mapping(address => uint256)))
rfqOrdersMakerToMakerTokenToTakerTokenToMinValidOrderSalt;
}
/// @dev Get the storage bucket for this contract.
function getStorage() internal pure returns (Storage storage stor) {
uint256 storageSlot = LibStorage.getStorageSlot(
LibStorage.StorageId.NativeOrders
);
// Dip into assembly to change the slot pointed to by the local
// variable `stor`.
// See https://solidity.readthedocs.io/en/v0.6.8/assembly.html?highlight=slot#access-to-external-variables-functions-and-libraries
assembly { stor_slot := storageSlot }
}
}

View File

@ -36,7 +36,8 @@ library LibStorage {
TokenSpender,
TransformERC20,
MetaTransactions,
ReentrancyGuard
ReentrancyGuard,
NativeOrders
}
/// @dev Get the storage slot given a storage ID. We assign unique, well-spaced

View File

@ -37,14 +37,13 @@ contract TestBridge is
/// @param from Address to transfer asset from.
/// @param to Address to transfer asset to.
/// @param amount Amount of asset to transfer.
/// @param bridgeData Arbitrary asset data needed by the bridge contract.
/// @return success The magic bytes `0xdc1600f3` if successful.
function bridgeTransferFrom(
address tokenAddress,
address from,
address to,
uint256 amount,
bytes calldata bridgeData
bytes calldata /* bridgeData */
)
external
override

View File

@ -21,26 +21,31 @@ pragma experimental ABIEncoderV2;
import "../src/fixins/FixinProtocolFees.sol";
contract TestProtocolFees is FixinProtocolFees {
function collectProtocolFee(
bytes32 poolId,
uint256 amount,
IERC20TokenV06 weth
contract TestFixinProtocolFees is
FixinProtocolFees
{
constructor(
IEtherTokenV06 weth,
IStaking staking,
uint32 protocolFeeMultiplier
)
public
FixinProtocolFees(weth, staking, protocolFeeMultiplier)
{
// solhint-disalbe no-empty-blocks
}
function collectProtocolFee(bytes32 poolId)
external
payable
{
_collectProtocolFee(poolId, amount, weth);
_collectProtocolFee(poolId);
}
function transferFeesForPool(
bytes32 poolId,
IStaking staking,
IEtherTokenV06 weth
)
function transferFeesForPool(bytes32 poolId)
external
{
_transferFeesForPool(poolId, staking, weth);
_transferFeesForPool(poolId);
}
function getFeeCollector(
@ -52,4 +57,12 @@ contract TestProtocolFees is FixinProtocolFees {
{
return _getFeeCollector(poolId);
}
function getSingleProtocolFee()
external
view
returns (uint256 protocolFeeAmount)
{
return _getSingleProtocolFee();
}
}

View File

@ -0,0 +1,24 @@
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "../src/features/libs/LibNativeOrder.sol";
contract TestLibNativeOrder {
function getLimitOrderStructHash(LibNativeOrder.LimitOrder calldata order)
external
pure
returns (bytes32 structHash)
{
return LibNativeOrder.getLimitOrderStructHash(order);
}
function getRfqOrderStructHash(LibNativeOrder.RfqOrder calldata order)
external
pure
returns (bytes32 structHash)
{
return LibNativeOrder.getRfqOrderStructHash(order);
}
}

View File

@ -0,0 +1,24 @@
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
import "../src/features/NativeOrdersFeature.sol";
contract TestNativeOrdersFeature is
NativeOrdersFeature
{
constructor(
address zeroExAddress,
IEtherTokenV06 weth,
IStaking staking,
uint32 protocolFeeMultiplier
)
public
NativeOrdersFeature(zeroExAddress, weth, staking, protocolFeeMultiplier)
{
// solhint-disable no-empty-blocks
}
modifier onlySelf() override {
_;
}
}

View File

@ -28,7 +28,7 @@ contract TestTransformerBase is
IERC20Transformer,
Transformer
{
function transform(TransformContext calldata context)
function transform(TransformContext calldata /* context */)
external
override
returns (bytes4 success)

View File

@ -40,9 +40,9 @@
"publish:private": "yarn build && gitpkg publish"
},
"config": {
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,SignatureValidatorFeature,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProvider",
"publicInterfaceContracts": "IZeroEx,ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnableFeature,ISimpleFunctionRegistryFeature,ITokenSpenderFeature,ITransformERC20Feature,FillQuoteTransformer,PayTakerTransformer,WethTransformer,OwnableFeature,SimpleFunctionRegistryFeature,TransformERC20Feature,TokenSpenderFeature,AffiliateFeeTransformer,SignatureValidatorFeature,MetaTransactionsFeature,LogMetadataTransformer,BridgeAdapter,LiquidityProviderFeature,ILiquidityProviderFeature,NativeOrdersFeature,INativeOrdersFeature",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|FeeCollector|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibOrderHash|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpender|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAdapterAddresses|MixinBalancer|MixinCurve|MixinDodo|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|OwnableFeature|PayTakerTransformer|SignatureValidatorFeature|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestLibSignature|TestLibTokenSpender|TestLiquidityProvider|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestProtocolFees|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx).json"
"abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|BootstrapFeature|BridgeAdapter|FeeCollector|FillQuoteTransformer|FixinCommon|FixinEIP712|FixinProtocolFees|FixinReentrancyGuard|FlashWallet|FullMigration|IAllowanceTarget|IBootstrapFeature|IBridgeAdapter|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|ILiquidityProvider|ILiquidityProviderFeature|ILiquidityProviderSandbox|IMetaTransactionsFeature|INativeOrdersFeature|IOwnableFeature|ISignatureValidatorFeature|ISimpleFunctionRegistryFeature|IStaking|ITestSimpleFunctionRegistryFeature|ITokenSpenderFeature|ITransformERC20Feature|IUniswapFeature|IZeroEx|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibLiquidityProviderRichErrors|LibMetaTransactionsRichErrors|LibMetaTransactionsStorage|LibMigrate|LibNativeOrder|LibNativeOrdersRichErrors|LibNativeOrdersStorage|LibOrderHash|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibReentrancyGuardStorage|LibSignature|LibSignatureRichErrors|LibSignedCallData|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpender|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|LiquidityProviderFeature|LiquidityProviderSandbox|LogMetadataTransformer|MetaTransactionsFeature|MixinAdapterAddresses|MixinBalancer|MixinCurve|MixinDodo|MixinKyber|MixinMStable|MixinMooniswap|MixinOasis|MixinShell|MixinSushiswap|MixinUniswap|MixinUniswapV2|MixinZeroExBridge|NativeOrdersFeature|OwnableFeature|PayTakerTransformer|SignatureValidatorFeature|SimpleFunctionRegistryFeature|TestBridge|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFixinProtocolFees|TestFullMigration|TestInitialMigration|TestLibNativeOrder|TestLibSignature|TestLibTokenSpender|TestLiquidityProvider|TestMetaTransactionsTransformERC20Feature|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestNativeOrdersFeature|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestStaking|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpenderFeature|TransformERC20Feature|Transformer|TransformerDeployer|UniswapFeature|WethTransformer|ZeroEx).json"
},
"repository": {
"type": "git",

View File

@ -12,7 +12,8 @@ import * as FullMigration from '../generated-artifacts/FullMigration.json';
import * as IAllowanceTarget from '../generated-artifacts/IAllowanceTarget.json';
import * as IERC20Transformer from '../generated-artifacts/IERC20Transformer.json';
import * as IFlashWallet from '../generated-artifacts/IFlashWallet.json';
import * as ILiquidityProvider from '../generated-artifacts/ILiquidityProvider.json';
import * as ILiquidityProviderFeature from '../generated-artifacts/ILiquidityProviderFeature.json';
import * as INativeOrdersFeature from '../generated-artifacts/INativeOrdersFeature.json';
import * as InitialMigration from '../generated-artifacts/InitialMigration.json';
import * as IOwnableFeature from '../generated-artifacts/IOwnableFeature.json';
import * as ISimpleFunctionRegistryFeature from '../generated-artifacts/ISimpleFunctionRegistryFeature.json';
@ -22,6 +23,7 @@ import * as IZeroEx from '../generated-artifacts/IZeroEx.json';
import * as LiquidityProviderFeature from '../generated-artifacts/LiquidityProviderFeature.json';
import * as LogMetadataTransformer from '../generated-artifacts/LogMetadataTransformer.json';
import * as MetaTransactionsFeature from '../generated-artifacts/MetaTransactionsFeature.json';
import * as NativeOrdersFeature from '../generated-artifacts/NativeOrdersFeature.json';
import * as OwnableFeature from '../generated-artifacts/OwnableFeature.json';
import * as PayTakerTransformer from '../generated-artifacts/PayTakerTransformer.json';
import * as SignatureValidatorFeature from '../generated-artifacts/SignatureValidatorFeature.json';
@ -55,5 +57,7 @@ export const artifacts = {
LogMetadataTransformer: LogMetadataTransformer as ContractArtifact,
BridgeAdapter: BridgeAdapter as ContractArtifact,
LiquidityProviderFeature: LiquidityProviderFeature as ContractArtifact,
ILiquidityProvider: ILiquidityProvider as ContractArtifact,
ILiquidityProviderFeature: ILiquidityProviderFeature as ContractArtifact,
NativeOrdersFeature: NativeOrdersFeature as ContractArtifact,
INativeOrdersFeature: INativeOrdersFeature as ContractArtifact,
};

View File

@ -0,0 +1,70 @@
import { hexUtils, NULL_ADDRESS } from '@0x/utils';
export interface EIP712Domain {
name: string;
version: string;
chainId: number;
verifyingContract: string;
}
export const EIP712_DOMAIN_PARAMETERS = [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
];
const EXCHANGE_PROXY_EIP712_DOMAIN_DEFAULT = {
chainId: 1,
verifyingContract: NULL_ADDRESS,
name: 'ZeroEx',
version: '1.0.0',
};
const EXCHANGE_PROXY_DOMAIN_TYPEHASH = hexUtils.hash(
hexUtils.toHex(
Buffer.from(
[
'EIP712Domain(',
['string name', 'string version', 'uint256 chainId', 'address verifyingContract'].join(','),
')',
].join(''),
),
),
);
/**
* Create an exchange proxy EIP712 domain.
*/
export function createExchangeProxyEIP712Domain(chainId?: number, verifyingContract?: string): EIP712Domain {
return {
...EXCHANGE_PROXY_EIP712_DOMAIN_DEFAULT,
...(chainId ? { chainId } : {}),
...(verifyingContract ? { verifyingContract } : {}),
};
}
/**
* Get the hash of the exchange proxy EIP712 domain.
*/
export function getExchangeProxyEIP712DomainHash(chainId?: number, verifyingContract?: string): string {
const domain = createExchangeProxyEIP712Domain(chainId, verifyingContract);
return hexUtils.hash(
hexUtils.concat(
EXCHANGE_PROXY_DOMAIN_TYPEHASH,
hexUtils.hash(hexUtils.toHex(Buffer.from(domain.name))),
hexUtils.hash(hexUtils.toHex(Buffer.from(domain.version))),
hexUtils.leftPad(domain.chainId),
hexUtils.leftPad(domain.verifyingContract),
),
);
}
/**
* Compute a complete EIP712 hash given a struct hash.
*/
export function getExchangeProxyEIP712Hash(structHash: string, chainId?: number, verifyingContract?: string): string {
return hexUtils.hash(
hexUtils.concat('0x1901', getExchangeProxyEIP712DomainHash(chainId, verifyingContract), structHash),
);
}

View File

@ -33,6 +33,9 @@ export * from './migration';
export * from './nonce_utils';
export * from './signed_call_data';
export * from './signature_utils';
export * from './orders';
export * from './eip712_utils';
export * from './revert_errors';
export {
AffiliateFeeTransformerContract,
BridgeAdapterContract,
@ -49,6 +52,5 @@ export {
WethTransformerContract,
ZeroExContract,
} from './wrappers';
export * from './revert_errors';
export { EIP712TypedData } from '@0x/types';
export { SupportedProvider } from '@0x/subproviders';

View File

@ -1,4 +1,6 @@
import { SupportedProvider } from '@0x/subproviders';
import { SimpleContractArtifact } from '@0x/types';
import { NULL_ADDRESS } from '@0x/utils';
import { TxData } from 'ethereum-types';
import * as _ from 'lodash';
@ -8,6 +10,7 @@ import {
InitialMigrationContract,
IZeroExContract,
MetaTransactionsFeatureContract,
NativeOrdersFeatureContract,
OwnableFeatureContract,
SignatureValidatorFeatureContract,
SimpleFunctionRegistryFeatureContract,
@ -26,6 +29,19 @@ export interface BootstrapFeatures {
ownable: string;
}
/**
* Artifacts to use when deploying bootstrap features.
*/
export interface BootstrapFeatureArtifacts {
registry: SimpleContractArtifact;
ownable: SimpleContractArtifact;
}
const DEFAULT_BOOTSTRAP_FEATURE_ARTIFACTS = {
registry: artifacts.SimpleFunctionRegistryFeature,
ownable: artifacts.OwnableFeature,
};
/**
* Deploy the minimum features of the Exchange Proxy.
*/
@ -33,12 +49,17 @@ export async function deployBootstrapFeaturesAsync(
provider: SupportedProvider,
txDefaults: Partial<TxData>,
features: Partial<BootstrapFeatures> = {},
featureArtifacts: Partial<BootstrapFeatureArtifacts> = {},
): Promise<BootstrapFeatures> {
const _featureArtifacts = {
...DEFAULT_BOOTSTRAP_FEATURE_ARTIFACTS,
...featureArtifacts,
};
return {
registry:
features.registry ||
(await SimpleFunctionRegistryFeatureContract.deployFrom0xArtifactAsync(
artifacts.SimpleFunctionRegistryFeature,
_featureArtifacts.registry,
provider,
txDefaults,
artifacts,
@ -46,7 +67,7 @@ export async function deployBootstrapFeaturesAsync(
ownable:
features.ownable ||
(await OwnableFeatureContract.deployFrom0xArtifactAsync(
artifacts.OwnableFeature,
_featureArtifacts.ownable,
provider,
txDefaults,
artifacts,
@ -90,30 +111,73 @@ export interface FullFeatures extends BootstrapFeatures {
transformERC20: string;
signatureValidator: string;
metaTransactions: string;
nativeOrders: string;
}
/**
* Extra configuration options for a full migration of the Exchange Proxy.
* Artifacts to use when deploying full features.
*/
export interface FullMigrationOpts {
transformerDeployer: string;
export interface FullFeatureArtifacts extends BootstrapFeatureArtifacts {
tokenSpender: SimpleContractArtifact;
transformERC20: SimpleContractArtifact;
signatureValidator: SimpleContractArtifact;
metaTransactions: SimpleContractArtifact;
nativeOrders: SimpleContractArtifact;
}
/**
* Configuration for deploying full features..
*/
export interface FullFeaturesDeployConfig {
zeroExAddress: string;
wethAddress: string;
stakingAddress: string;
protocolFeeMultiplier: number;
}
/**
* Configuration options for a full migration of the Exchange Proxy.
*/
export interface FullMigrationConfig extends FullFeaturesDeployConfig {
transformerDeployer?: string;
}
const DEFAULT_FULL_FEATURES_DEPLOY_CONFIG = {
zeroExAddress: NULL_ADDRESS,
wethAddress: NULL_ADDRESS,
stakingAddress: NULL_ADDRESS,
protocolFeeMultiplier: 70e3,
};
const DEFAULT_FULL_FEATURES_ARTIFACTS = {
tokenSpender: artifacts.TokenSpenderFeature,
transformERC20: artifacts.TransformERC20Feature,
signatureValidator: artifacts.SignatureValidatorFeature,
metaTransactions: artifacts.MetaTransactionsFeature,
nativeOrders: artifacts.NativeOrdersFeature,
};
/**
* Deploy all the features for a full Exchange Proxy.
*/
export async function deployFullFeaturesAsync(
provider: SupportedProvider,
txDefaults: Partial<TxData>,
zeroExAddress: string,
config: Partial<FullFeaturesDeployConfig> = {},
features: Partial<FullFeatures> = {},
featureArtifacts: Partial<FullFeatureArtifacts> = {},
): Promise<FullFeatures> {
const _config = { ...DEFAULT_FULL_FEATURES_DEPLOY_CONFIG, ...config };
const _featureArtifacts = {
...DEFAULT_FULL_FEATURES_ARTIFACTS,
...featureArtifacts,
};
return {
...(await deployBootstrapFeaturesAsync(provider, txDefaults)),
tokenSpender:
features.tokenSpender ||
(await TokenSpenderFeatureContract.deployFrom0xArtifactAsync(
artifacts.TokenSpenderFeature,
_featureArtifacts.tokenSpender,
provider,
txDefaults,
artifacts,
@ -121,7 +185,7 @@ export async function deployFullFeaturesAsync(
transformERC20:
features.transformERC20 ||
(await TransformERC20FeatureContract.deployFrom0xArtifactAsync(
artifacts.TransformERC20Feature,
_featureArtifacts.transformERC20,
provider,
txDefaults,
artifacts,
@ -129,7 +193,7 @@ export async function deployFullFeaturesAsync(
signatureValidator:
features.signatureValidator ||
(await SignatureValidatorFeatureContract.deployFrom0xArtifactAsync(
artifacts.SignatureValidatorFeature,
_featureArtifacts.signatureValidator,
provider,
txDefaults,
artifacts,
@ -137,11 +201,23 @@ export async function deployFullFeaturesAsync(
metaTransactions:
features.metaTransactions ||
(await MetaTransactionsFeatureContract.deployFrom0xArtifactAsync(
artifacts.MetaTransactionsFeature,
_featureArtifacts.metaTransactions,
provider,
txDefaults,
artifacts,
zeroExAddress,
_config.zeroExAddress,
)).address,
nativeOrders:
features.nativeOrders ||
(await NativeOrdersFeatureContract.deployFrom0xArtifactAsync(
_featureArtifacts.nativeOrders,
provider,
txDefaults,
artifacts,
_config.zeroExAddress,
_config.wethAddress,
_config.stakingAddress,
_config.protocolFeeMultiplier,
)).address,
};
}
@ -154,7 +230,8 @@ export async function fullMigrateAsync(
provider: SupportedProvider,
txDefaults: Partial<TxData>,
features: Partial<FullFeatures> = {},
opts: Partial<FullMigrationOpts> = {},
config: Partial<FullMigrationConfig> = {},
featureArtifacts: Partial<FullFeatureArtifacts> = {},
): Promise<IZeroExContract> {
const migrator = await FullMigrationContract.deployFrom0xArtifactAsync(
artifacts.FullMigration,
@ -170,11 +247,12 @@ export async function fullMigrateAsync(
artifacts,
await migrator.getBootstrapper().callAsync(),
);
const _features = await deployFullFeaturesAsync(provider, txDefaults, zeroEx.address, features);
const _opts = {
const _config = { ...config, zeroExAddress: zeroEx.address };
const _features = await deployFullFeaturesAsync(provider, txDefaults, _config, features, featureArtifacts);
const migrateOpts = {
transformerDeployer: txDefaults.from as string,
...opts,
..._config,
};
await migrator.initializeZeroEx(owner, zeroEx.address, _features, _opts).awaitTransactionSuccessAsync();
await migrator.migrateZeroEx(owner, zeroEx.address, _features, migrateOpts).awaitTransactionSuccessAsync();
return new IZeroExContract(zeroEx.address, provider, txDefaults);
}

View File

@ -0,0 +1,333 @@
// tslint:disable: max-classes-per-file
import { SupportedProvider } from '@0x/subproviders';
import { EIP712TypedData } from '@0x/types';
import { BigNumber, hexUtils, NULL_ADDRESS } from '@0x/utils';
import { createExchangeProxyEIP712Domain, EIP712_DOMAIN_PARAMETERS, getExchangeProxyEIP712Hash } from './eip712_utils';
import {
eip712SignTypedDataWithKey,
eip712SignTypedDataWithProviderAsync,
ethSignHashWithKey,
ethSignHashWithProviderAsync,
Signature,
SignatureType,
} from './signature_utils';
const ZERO = new BigNumber(0);
const COMMON_ORDER_DEFAULT_VALUES = {
makerToken: NULL_ADDRESS,
takerToken: NULL_ADDRESS,
makerAmount: ZERO,
takerAmount: ZERO,
maker: NULL_ADDRESS,
pool: hexUtils.leftPad(0),
expiry: ZERO,
salt: ZERO,
chainId: 1,
verifyingContract: '0xdef1c0ded9bec7f1a1670819833240f027b25eff',
};
const LIMIT_ORDER_DEFAULT_VALUES = {
...COMMON_ORDER_DEFAULT_VALUES,
takerTokenFeeAmount: ZERO,
taker: NULL_ADDRESS,
sender: NULL_ADDRESS,
feeRecipient: NULL_ADDRESS,
};
const RFQ_ORDER_DEFAULT_VALUES = {
...COMMON_ORDER_DEFAULT_VALUES,
txOrigin: NULL_ADDRESS,
};
export type CommonOrderFields = typeof COMMON_ORDER_DEFAULT_VALUES;
export type LimitOrderFields = typeof LIMIT_ORDER_DEFAULT_VALUES;
export type RfqOrderFields = typeof RFQ_ORDER_DEFAULT_VALUES;
export enum OrderStatus {
Invalid = 0,
Fillable = 1,
Filled = 2,
Cancelled = 3,
Expired = 4,
}
export interface OrderInfo {
status: OrderStatus;
orderHash: string;
takerTokenFilledAmount: BigNumber;
}
export abstract class OrderBase {
public makerToken: string;
public takerToken: string;
public makerAmount: BigNumber;
public takerAmount: BigNumber;
public maker: string;
public pool: string;
public expiry: BigNumber;
public salt: BigNumber;
public chainId: number;
public verifyingContract: string;
protected constructor(fields: Partial<CommonOrderFields> = {}) {
const _fields = { ...COMMON_ORDER_DEFAULT_VALUES, ...fields };
this.makerToken = _fields.makerToken;
this.takerToken = _fields.takerToken;
this.makerAmount = _fields.makerAmount;
this.takerAmount = _fields.takerAmount;
this.maker = _fields.maker;
this.pool = _fields.pool;
this.expiry = _fields.expiry;
this.salt = _fields.salt;
this.chainId = _fields.chainId;
this.verifyingContract = _fields.verifyingContract;
}
public abstract getStructHash(): string;
public abstract getEIP712TypedData(): EIP712TypedData;
public getHash(): string {
return getExchangeProxyEIP712Hash(this.getStructHash(), this.chainId, this.verifyingContract);
}
public async getSignatureWithProviderAsync(
provider: SupportedProvider,
type: SignatureType = SignatureType.EthSign,
): Promise<Signature> {
switch (type) {
case SignatureType.EIP712:
return eip712SignTypedDataWithProviderAsync(this.getEIP712TypedData(), this.maker, provider);
case SignatureType.EthSign:
return ethSignHashWithProviderAsync(this.getHash(), this.maker, provider);
default:
throw new Error(`Cannot sign with signature type: ${type}`);
}
}
public getSignatureWithKey(key: string, type: SignatureType = SignatureType.EthSign): Signature {
switch (type) {
case SignatureType.EIP712:
return eip712SignTypedDataWithKey(this.getEIP712TypedData(), key);
case SignatureType.EthSign:
return ethSignHashWithKey(this.getHash(), key);
default:
throw new Error(`Cannot sign with signature type: ${type}`);
}
}
}
export class LimitOrder extends OrderBase {
public static readonly TYPE_HASH = hexUtils.hash(
hexUtils.toHex(
Buffer.from(
[
'LimitOrder(',
[
'address makerToken',
'address takerToken',
'uint128 makerAmount',
'uint128 takerAmount',
'uint128 takerTokenFeeAmount',
'address maker',
'address taker',
'address sender',
'address feeRecipient',
'bytes32 pool',
'uint64 expiry',
'uint256 salt',
].join(','),
')',
].join(''),
),
),
);
public takerTokenFeeAmount: BigNumber;
public taker: string;
public sender: string;
public feeRecipient: string;
constructor(fields: Partial<LimitOrderFields> = {}) {
const _fields = { ...LIMIT_ORDER_DEFAULT_VALUES, ...fields };
super(_fields);
this.takerTokenFeeAmount = _fields.takerTokenFeeAmount;
this.taker = _fields.taker;
this.sender = _fields.sender;
this.feeRecipient = _fields.feeRecipient;
}
public clone(fields: Partial<LimitOrderFields> = {}): LimitOrder {
return new LimitOrder({
makerToken: this.makerToken,
takerToken: this.takerToken,
makerAmount: this.makerAmount,
takerAmount: this.takerAmount,
takerTokenFeeAmount: this.takerTokenFeeAmount,
maker: this.maker,
taker: this.taker,
sender: this.sender,
feeRecipient: this.feeRecipient,
pool: this.pool,
expiry: this.expiry,
salt: this.salt,
chainId: this.chainId,
verifyingContract: this.verifyingContract,
...fields,
});
}
public getStructHash(): string {
return hexUtils.hash(
hexUtils.concat(
hexUtils.leftPad(LimitOrder.TYPE_HASH),
hexUtils.leftPad(this.makerToken),
hexUtils.leftPad(this.takerToken),
hexUtils.leftPad(this.makerAmount),
hexUtils.leftPad(this.takerAmount),
hexUtils.leftPad(this.takerTokenFeeAmount),
hexUtils.leftPad(this.maker),
hexUtils.leftPad(this.taker),
hexUtils.leftPad(this.sender),
hexUtils.leftPad(this.feeRecipient),
hexUtils.leftPad(this.pool),
hexUtils.leftPad(this.expiry),
hexUtils.leftPad(this.salt),
),
);
}
public getEIP712TypedData(): EIP712TypedData {
return {
types: {
EIP712Domain: EIP712_DOMAIN_PARAMETERS,
LimitOrder: [
{ type: 'address', name: 'makerToken' },
{ type: 'address', name: 'takerToken' },
{ type: 'uint128', name: 'makerAmount' },
{ type: 'uint128', name: 'takerAmount' },
{ type: 'uint128', name: 'takerTokenFeeAmount' },
{ type: 'address', name: 'maker' },
{ type: 'address', name: 'taker' },
{ type: 'address', name: 'sender' },
{ type: 'address', name: 'feeRecipient' },
{ type: 'bytes32', name: 'pool' },
{ type: 'uint64', name: 'expiry' },
{ type: 'uint256', name: 'salt' },
],
},
domain: createExchangeProxyEIP712Domain(this.chainId, this.verifyingContract) as any,
primaryType: 'LimitOrder',
message: {
makerToken: this.makerToken,
takerToken: this.takerToken,
makerAmount: this.makerAmount.toString(10),
takerAmount: this.takerAmount.toString(10),
takerTokenFeeAmount: this.takerTokenFeeAmount.toString(10),
maker: this.maker,
taker: this.taker,
sender: this.sender,
feeRecipient: this.feeRecipient,
pool: this.pool,
expiry: this.expiry.toString(10),
salt: this.salt.toString(10),
},
};
}
}
export class RfqOrder extends OrderBase {
public static readonly TYPE_HASH = hexUtils.hash(
hexUtils.toHex(
Buffer.from(
[
'RfqOrder(',
[
'address makerToken',
'address takerToken',
'uint128 makerAmount',
'uint128 takerAmount',
'address maker',
'address txOrigin',
'bytes32 pool',
'uint64 expiry',
'uint256 salt',
].join(','),
')',
].join(''),
),
),
);
public txOrigin: string;
constructor(fields: Partial<RfqOrderFields> = {}) {
const _fields = { ...RFQ_ORDER_DEFAULT_VALUES, ...fields };
super(_fields);
this.txOrigin = _fields.txOrigin;
}
public clone(fields: Partial<RfqOrderFields> = {}): RfqOrder {
return new RfqOrder({
makerToken: this.makerToken,
takerToken: this.takerToken,
makerAmount: this.makerAmount,
takerAmount: this.takerAmount,
maker: this.maker,
txOrigin: this.txOrigin,
pool: this.pool,
expiry: this.expiry,
salt: this.salt,
chainId: this.chainId,
verifyingContract: this.verifyingContract,
...fields,
});
}
public getStructHash(): string {
return hexUtils.hash(
hexUtils.concat(
hexUtils.leftPad(RfqOrder.TYPE_HASH),
hexUtils.leftPad(this.makerToken),
hexUtils.leftPad(this.takerToken),
hexUtils.leftPad(this.makerAmount),
hexUtils.leftPad(this.takerAmount),
hexUtils.leftPad(this.maker),
hexUtils.leftPad(this.txOrigin),
hexUtils.leftPad(this.pool),
hexUtils.leftPad(this.expiry),
hexUtils.leftPad(this.salt),
),
);
}
public getEIP712TypedData(): EIP712TypedData {
return {
types: {
EIP712Domain: EIP712_DOMAIN_PARAMETERS,
RfqOrder: [
{ type: 'address', name: 'makerToken' },
{ type: 'address', name: 'takerToken' },
{ type: 'uint128', name: 'makerAmount' },
{ type: 'uint128', name: 'takerAmount' },
{ type: 'address', name: 'maker' },
{ type: 'address', name: 'txOrigin' },
{ type: 'bytes32', name: 'pool' },
{ type: 'uint64', name: 'expiry' },
{ type: 'uint256', name: 'salt' },
],
},
domain: createExchangeProxyEIP712Domain(this.chainId, this.verifyingContract) as any,
primaryType: 'RfqOrder',
message: {
makerToken: this.makerToken,
takerToken: this.takerToken,
makerAmount: this.makerAmount.toString(10),
takerAmount: this.takerAmount.toString(10),
maker: this.maker,
txOrigin: this.txOrigin,
pool: this.pool,
expiry: this.expiry.toString(10),
salt: this.salt.toString(10),
},
};
}
}

View File

@ -1,7 +1,9 @@
// TODO(dorothy-zbornak): Move these into `@0x/protocol-utils` whenever that
// becomes a thing.
// tslint:disable:max-classes-per-file
import { RevertError } from '@0x/utils';
import { Numberish, RevertError } from '@0x/utils';
import { OrderStatus } from './orders';
export enum SignatureValidationErrorCodes {
AlwaysInvalid = 0,
@ -12,7 +14,6 @@ export enum SignatureValidationErrorCodes {
BadSignatureData = 5,
}
// tslint:disable:max-classes-per-file
export class SignatureValidationError extends RevertError {
constructor(code?: SignatureValidationErrorCodes, hash?: string) {
super('SignatureValidationError', 'SignatureValidationError(uint8 code, bytes32 hash)', {
@ -22,7 +23,125 @@ export class SignatureValidationError extends RevertError {
}
}
const types = [SignatureValidationError];
export class ProtocolFeeRefundFailed extends RevertError {
constructor(receiver?: string, refundAmount?: Numberish) {
super('ProtocolFeeRefundFailed', 'ProtocolFeeRefundFailed(address receiver, uint256 refundAmount)', {
receiver,
refundAmount,
});
}
}
export class OrderNotFillableByOriginError extends RevertError {
constructor(orderHash?: string, txOrigin?: string, orderTxOrigin?: string) {
super(
'OrderNotFillableByOriginError',
'OrderNotFillableByOriginError(bytes32 orderHash, address txOrigin, address orderTxOrigin)',
{
orderHash,
txOrigin,
orderTxOrigin,
},
);
}
}
export class OrderNotFillableError extends RevertError {
constructor(orderHash?: string, orderStatus?: OrderStatus) {
super('OrderNotFillableError', 'OrderNotFillableError(bytes32 orderHash, uint8 orderStatus)', {
orderHash,
orderStatus,
});
}
}
export class OrderNotSignedByMakerError extends RevertError {
constructor(orderHash?: string, signer?: string, maker?: string) {
super(
'OrderNotSignedByMakerError',
'OrderNotSignedByMakerError(bytes32 orderHash, address signer, address maker)',
{
orderHash,
signer,
maker,
},
);
}
}
export class OrderNotFillableBySenderError extends RevertError {
constructor(orderHash?: string, sender?: string, orderSender?: string) {
super(
'OrderNotFillableBySenderError',
'OrderNotFillableBySenderError(bytes32 orderHash, address sender, address orderSender)',
{
orderHash,
sender,
orderSender,
},
);
}
}
export class OrderNotFillableByTakerError extends RevertError {
constructor(orderHash?: string, taker?: string, orderTaker?: string) {
super(
'OrderNotFillableByTakerError',
'OrderNotFillableByTakerError(bytes32 orderHash, address taker, address orderTaker)',
{
orderHash,
taker,
orderTaker,
},
);
}
}
export class CancelSaltTooLowError extends RevertError {
constructor(minValidSalt?: Numberish, oldMinValidSalt?: Numberish) {
super('CancelSaltTooLowError', 'CancelSaltTooLowError(uint256 minValidSalt, uint256 oldMinValidSalt)', {
minValidSalt,
oldMinValidSalt,
});
}
}
export class FillOrKillFailedError extends RevertError {
constructor(orderHash?: string, takerTokenFilledAmount?: Numberish, takerTokenFillAmount?: Numberish) {
super(
'FillOrKillFailedError',
'FillOrKillFailedError(bytes32 orderHash, uint256 takerTokenFilledAmount, uint256 takerTokenFillAmount)',
{
orderHash,
takerTokenFilledAmount,
takerTokenFillAmount,
},
);
}
}
export class OnlyOrderMakerAllowed extends RevertError {
constructor(orderHash?: string, sender?: string, maker?: string) {
super('OnlyOrderMakerAllowed', 'OnlyOrderMakerAllowed(bytes32 orderHash, address sender, address maker)', {
orderHash,
sender,
maker,
});
}
}
const types = [
SignatureValidationError,
ProtocolFeeRefundFailed,
OrderNotFillableByOriginError,
OrderNotFillableError,
OrderNotSignedByMakerError,
OrderNotFillableBySenderError,
OrderNotFillableByTakerError,
CancelSaltTooLowError,
FillOrKillFailedError,
OnlyOrderMakerAllowed,
];
// Register the types we've defined.
for (const type of types) {

View File

@ -1,7 +1,7 @@
import { signatureUtils } from '@0x/order-utils';
import { SupportedProvider } from '@0x/subproviders';
import { EIP712TypedData } from '@0x/types';
import { hexUtils, signTypedDataUtils } from '@0x/utils';
import { hexUtils, providerUtils, signTypedDataUtils } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as ethjs from 'ethereumjs-util';
/**
@ -33,15 +33,17 @@ export interface Signature extends ECSignature {
/**
* Sign a hash with the EthSign signature type on a provider.
*/
export async function ethSignHashFromProviderAsync(
signer: string,
export async function ethSignHashWithProviderAsync(
hash: string,
signer: string,
provider: SupportedProvider,
): Promise<Signature> {
const signatureBytes = await signatureUtils.ecSignHashAsync(provider, hash, signer);
const parsed = parsePackedSignatureBytes(signatureBytes);
assertSignatureType(parsed, SignatureType.EthSign);
return parsed;
const w3w = new Web3Wrapper(providerUtils.standardizeOrThrow(provider));
const rpcSig = await w3w.signMessageAsync(signer, hash);
return {
...parseRpcSignature(rpcSig),
signatureType: SignatureType.EthSign,
};
}
/**
@ -57,6 +59,22 @@ export function ethSignHashWithKey(hash: string, key: string): Signature {
};
}
/**
* Sign a typed data object with the EIP712 signature type on a provider.
*/
export async function eip712SignTypedDataWithProviderAsync(
data: EIP712TypedData,
signer: string,
provider: SupportedProvider,
): Promise<Signature> {
const w3w = new Web3Wrapper(providerUtils.standardizeOrThrow(provider));
const rpcSig = await w3w.signTypedDataAsync(signer, data);
return {
...parseRpcSignature(rpcSig),
signatureType: SignatureType.EIP712,
};
}
/**
* Sign a typed data object with the EIP712 signature type, given a private key.
*/
@ -90,24 +108,34 @@ export function ecSignHashWithKey(hash: string, key: string): ECSignature {
};
}
function assertSignatureType(signature: Signature, expectedType: SignatureType): void {
if (signature.signatureType !== expectedType) {
throw new Error(`Expected signature type to be ${expectedType} but received ${signature.signatureType}.`);
// Parse a hex signature returned by an RPC call into an `ECSignature`.
function parseRpcSignature(rpcSig: string): ECSignature {
if (hexUtils.size(rpcSig) !== 65) {
throw new Error(`Invalid RPC signature length: "${rpcSig}"`);
}
}
function parsePackedSignatureBytes(signatureBytes: string): Signature {
if (hexUtils.size(signatureBytes) !== 66) {
throw new Error(`Expected packed signatureBytes to be 66 bytes long: ${signatureBytes}`);
// Some providers encode V as 0,1 instead of 27,28.
const VALID_V_VALUES = [0, 1, 27, 28];
// Some providers return the signature packed as V,R,S and others R,S,V.
// Try to guess which encoding it is (with a slight preference for R,S,V).
let v = parseInt(rpcSig.slice(-2), 16);
if (VALID_V_VALUES.includes(v)) {
// Format is R,S,V
v = v >= 27 ? v : v + 27;
return {
r: hexUtils.slice(rpcSig, 0, 32),
s: hexUtils.slice(rpcSig, 32, 64),
v,
};
}
const typeId = parseInt(signatureBytes.slice(-2), 16) as SignatureType;
if (!Object.values(SignatureType).includes(typeId)) {
throw new Error(`Invalid signatureBytes type ID detected: ${typeId}`);
// Format should be V,R,S
v = parseInt(rpcSig.slice(2, 4), 16);
if (!VALID_V_VALUES.includes(v)) {
throw new Error(`Cannot determine RPC signature layout from V value: "${rpcSig}"`);
}
v = v >= 27 ? v : v + 27;
return {
signatureType: typeId,
v: parseInt(signatureBytes.slice(2, 4), 16),
r: hexUtils.slice(signatureBytes, 1, 33),
s: hexUtils.slice(signatureBytes, 33),
v,
r: hexUtils.slice(rpcSig, 1, 33),
s: hexUtils.slice(rpcSig, 33, 65),
};
}

View File

@ -10,7 +10,8 @@ export * from '../generated-wrappers/full_migration';
export * from '../generated-wrappers/i_allowance_target';
export * from '../generated-wrappers/i_erc20_transformer';
export * from '../generated-wrappers/i_flash_wallet';
export * from '../generated-wrappers/i_liquidity_provider';
export * from '../generated-wrappers/i_liquidity_provider_feature';
export * from '../generated-wrappers/i_native_orders_feature';
export * from '../generated-wrappers/i_ownable_feature';
export * from '../generated-wrappers/i_simple_function_registry_feature';
export * from '../generated-wrappers/i_token_spender_feature';
@ -20,6 +21,7 @@ export * from '../generated-wrappers/initial_migration';
export * from '../generated-wrappers/liquidity_provider_feature';
export * from '../generated-wrappers/log_metadata_transformer';
export * from '../generated-wrappers/meta_transactions_feature';
export * from '../generated-wrappers/native_orders_feature';
export * from '../generated-wrappers/ownable_feature';
export * from '../generated-wrappers/pay_taker_transformer';
export * from '../generated-wrappers/signature_validator_feature';

View File

@ -30,6 +30,7 @@ import * as ILiquidityProvider from '../test/generated-artifacts/ILiquidityProvi
import * as ILiquidityProviderFeature from '../test/generated-artifacts/ILiquidityProviderFeature.json';
import * as ILiquidityProviderSandbox from '../test/generated-artifacts/ILiquidityProviderSandbox.json';
import * as IMetaTransactionsFeature from '../test/generated-artifacts/IMetaTransactionsFeature.json';
import * as INativeOrdersFeature from '../test/generated-artifacts/INativeOrdersFeature.json';
import * as InitialMigration from '../test/generated-artifacts/InitialMigration.json';
import * as IOwnableFeature from '../test/generated-artifacts/IOwnableFeature.json';
import * as ISignatureValidatorFeature from '../test/generated-artifacts/ISignatureValidatorFeature.json';
@ -47,6 +48,9 @@ import * as LibLiquidityProviderRichErrors from '../test/generated-artifacts/Lib
import * as LibMetaTransactionsRichErrors from '../test/generated-artifacts/LibMetaTransactionsRichErrors.json';
import * as LibMetaTransactionsStorage from '../test/generated-artifacts/LibMetaTransactionsStorage.json';
import * as LibMigrate from '../test/generated-artifacts/LibMigrate.json';
import * as LibNativeOrder from '../test/generated-artifacts/LibNativeOrder.json';
import * as LibNativeOrdersRichErrors from '../test/generated-artifacts/LibNativeOrdersRichErrors.json';
import * as LibNativeOrdersStorage from '../test/generated-artifacts/LibNativeOrdersStorage.json';
import * as LibOrderHash from '../test/generated-artifacts/LibOrderHash.json';
import * as LibOwnableRichErrors from '../test/generated-artifacts/LibOwnableRichErrors.json';
import * as LibOwnableStorage from '../test/generated-artifacts/LibOwnableStorage.json';
@ -82,6 +86,7 @@ import * as MixinSushiswap from '../test/generated-artifacts/MixinSushiswap.json
import * as MixinUniswap from '../test/generated-artifacts/MixinUniswap.json';
import * as MixinUniswapV2 from '../test/generated-artifacts/MixinUniswapV2.json';
import * as MixinZeroExBridge from '../test/generated-artifacts/MixinZeroExBridge.json';
import * as NativeOrdersFeature from '../test/generated-artifacts/NativeOrdersFeature.json';
import * as OwnableFeature from '../test/generated-artifacts/OwnableFeature.json';
import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json';
import * as SignatureValidatorFeature from '../test/generated-artifacts/SignatureValidatorFeature.json';
@ -92,8 +97,10 @@ import * as TestDelegateCaller from '../test/generated-artifacts/TestDelegateCal
import * as TestFillQuoteTransformerBridge from '../test/generated-artifacts/TestFillQuoteTransformerBridge.json';
import * as TestFillQuoteTransformerExchange from '../test/generated-artifacts/TestFillQuoteTransformerExchange.json';
import * as TestFillQuoteTransformerHost from '../test/generated-artifacts/TestFillQuoteTransformerHost.json';
import * as TestFixinProtocolFees from '../test/generated-artifacts/TestFixinProtocolFees.json';
import * as TestFullMigration from '../test/generated-artifacts/TestFullMigration.json';
import * as TestInitialMigration from '../test/generated-artifacts/TestInitialMigration.json';
import * as TestLibNativeOrder from '../test/generated-artifacts/TestLibNativeOrder.json';
import * as TestLibSignature from '../test/generated-artifacts/TestLibSignature.json';
import * as TestLibTokenSpender from '../test/generated-artifacts/TestLibTokenSpender.json';
import * as TestLiquidityProvider from '../test/generated-artifacts/TestLiquidityProvider.json';
@ -101,7 +108,7 @@ import * as TestMetaTransactionsTransformERC20Feature from '../test/generated-ar
import * as TestMigrator from '../test/generated-artifacts/TestMigrator.json';
import * as TestMintableERC20Token from '../test/generated-artifacts/TestMintableERC20Token.json';
import * as TestMintTokenERC20Transformer from '../test/generated-artifacts/TestMintTokenERC20Transformer.json';
import * as TestProtocolFees from '../test/generated-artifacts/TestProtocolFees.json';
import * as TestNativeOrdersFeature from '../test/generated-artifacts/TestNativeOrdersFeature.json';
import * as TestSimpleFunctionRegistryFeatureImpl1 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl1.json';
import * as TestSimpleFunctionRegistryFeatureImpl2 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json';
import * as TestStaking from '../test/generated-artifacts/TestStaking.json';
@ -127,6 +134,7 @@ export const artifacts = {
LibCommonRichErrors: LibCommonRichErrors as ContractArtifact,
LibLiquidityProviderRichErrors: LibLiquidityProviderRichErrors as ContractArtifact,
LibMetaTransactionsRichErrors: LibMetaTransactionsRichErrors as ContractArtifact,
LibNativeOrdersRichErrors: LibNativeOrdersRichErrors as ContractArtifact,
LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact,
LibProxyRichErrors: LibProxyRichErrors as ContractArtifact,
LibSignatureRichErrors: LibSignatureRichErrors as ContractArtifact,
@ -147,6 +155,7 @@ export const artifacts = {
IFeature: IFeature as ContractArtifact,
ILiquidityProviderFeature: ILiquidityProviderFeature as ContractArtifact,
IMetaTransactionsFeature: IMetaTransactionsFeature as ContractArtifact,
INativeOrdersFeature: INativeOrdersFeature as ContractArtifact,
IOwnableFeature: IOwnableFeature as ContractArtifact,
ISignatureValidatorFeature: ISignatureValidatorFeature as ContractArtifact,
ISimpleFunctionRegistryFeature: ISimpleFunctionRegistryFeature as ContractArtifact,
@ -155,12 +164,14 @@ export const artifacts = {
IUniswapFeature: IUniswapFeature as ContractArtifact,
LiquidityProviderFeature: LiquidityProviderFeature as ContractArtifact,
MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact,
NativeOrdersFeature: NativeOrdersFeature as ContractArtifact,
OwnableFeature: OwnableFeature as ContractArtifact,
SignatureValidatorFeature: SignatureValidatorFeature as ContractArtifact,
SimpleFunctionRegistryFeature: SimpleFunctionRegistryFeature as ContractArtifact,
TokenSpenderFeature: TokenSpenderFeature as ContractArtifact,
TransformERC20Feature: TransformERC20Feature as ContractArtifact,
UniswapFeature: UniswapFeature as ContractArtifact,
LibNativeOrder: LibNativeOrder as ContractArtifact,
LibSignature: LibSignature as ContractArtifact,
LibSignedCallData: LibSignedCallData as ContractArtifact,
LibTokenSpender: LibTokenSpender as ContractArtifact,
@ -173,6 +184,7 @@ export const artifacts = {
LibBootstrap: LibBootstrap as ContractArtifact,
LibMigrate: LibMigrate as ContractArtifact,
LibMetaTransactionsStorage: LibMetaTransactionsStorage as ContractArtifact,
LibNativeOrdersStorage: LibNativeOrdersStorage as ContractArtifact,
LibOwnableStorage: LibOwnableStorage as ContractArtifact,
LibProxyStorage: LibProxyStorage as ContractArtifact,
LibReentrancyGuardStorage: LibReentrancyGuardStorage as ContractArtifact,
@ -216,8 +228,10 @@ export const artifacts = {
TestFillQuoteTransformerBridge: TestFillQuoteTransformerBridge as ContractArtifact,
TestFillQuoteTransformerExchange: TestFillQuoteTransformerExchange as ContractArtifact,
TestFillQuoteTransformerHost: TestFillQuoteTransformerHost as ContractArtifact,
TestFixinProtocolFees: TestFixinProtocolFees as ContractArtifact,
TestFullMigration: TestFullMigration as ContractArtifact,
TestInitialMigration: TestInitialMigration as ContractArtifact,
TestLibNativeOrder: TestLibNativeOrder as ContractArtifact,
TestLibSignature: TestLibSignature as ContractArtifact,
TestLibTokenSpender: TestLibTokenSpender as ContractArtifact,
TestLiquidityProvider: TestLiquidityProvider as ContractArtifact,
@ -225,7 +239,7 @@ export const artifacts = {
TestMigrator: TestMigrator as ContractArtifact,
TestMintTokenERC20Transformer: TestMintTokenERC20Transformer as ContractArtifact,
TestMintableERC20Token: TestMintableERC20Token as ContractArtifact,
TestProtocolFees: TestProtocolFees as ContractArtifact,
TestNativeOrdersFeature: TestNativeOrdersFeature as ContractArtifact,
TestSimpleFunctionRegistryFeatureImpl1: TestSimpleFunctionRegistryFeatureImpl1 as ContractArtifact,
TestSimpleFunctionRegistryFeatureImpl2: TestSimpleFunctionRegistryFeatureImpl2 as ContractArtifact,
TestStaking: TestStaking as ContractArtifact,

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@ import { deployFullFeaturesAsync, FullFeatures } from './utils/migration';
import {
AllowanceTargetContract,
IMetaTransactionsFeatureContract,
INativeOrdersFeatureContract,
IOwnableFeatureContract,
ISignatureValidatorFeatureContract,
ISimpleFunctionRegistryFeatureContract,
@ -45,9 +46,9 @@ blockchainTests.resets('Full migration', env => {
artifacts,
await migrator.getBootstrapper().callAsync(),
);
features = await deployFullFeaturesAsync(env.provider, env.txDefaults, zeroEx.address);
features = await deployFullFeaturesAsync(env.provider, env.txDefaults, { zeroExAddress: zeroEx.address });
await migrator
.initializeZeroEx(owner, zeroEx.address, features, { transformerDeployer })
.migrateZeroEx(owner, zeroEx.address, features, { transformerDeployer })
.awaitTransactionSuccessAsync();
registry = new ISimpleFunctionRegistryFeatureContract(zeroEx.address, env.provider, env.txDefaults);
});
@ -63,10 +64,10 @@ blockchainTests.resets('Full migration', env => {
expect(dieRecipient).to.eq(owner);
});
it('Non-deployer cannot call initializeZeroEx()', async () => {
it('Non-deployer cannot call migrateZeroEx()', async () => {
const notDeployer = randomAddress();
const tx = migrator
.initializeZeroEx(owner, zeroEx.address, features, { transformerDeployer })
.migrateZeroEx(owner, zeroEx.address, features, { transformerDeployer })
.callAsync({ from: notDeployer });
return expect(tx).to.revertWith('FullMigration/INVALID_SENDER');
});
@ -103,6 +104,31 @@ blockchainTests.resets('Full migration', env => {
'getMetaTransactionHash',
],
},
LimitOrdersFeature: {
contractType: INativeOrdersFeatureContract,
fns: [
'transferProtocolFeesForPools',
'fillLimitOrder',
'fillRfqOrder',
'fillOrKillLimitOrder',
'fillOrKillRfqOrder',
'_fillLimitOrder',
'_fillRfqOrder',
'cancelLimitOrder',
'cancelRfqOrder',
'batchCancelLimitOrders',
'batchCancelRfqOrders',
'cancelPairLimitOrders',
'batchCancelPairLimitOrders',
'cancelPairRfqOrders',
'batchCancelPairRfqOrders',
'getLimitOrderInfo',
'getRfqOrderInfo',
'getLimitOrderHash',
'getRfqOrderHash',
'getProtocolFeeMultiplier',
],
},
};
function createFakeInputs(inputs: DataItem[] | DataItem): any | any[] {
@ -139,6 +165,11 @@ blockchainTests.resets('Full migration', env => {
return hexUtils.random(parseInt(/\d+$/.exec(item.type)![0], 10));
}
if (/^uint\d+$/.test(item.type)) {
if (item.type === 'uint8') {
// Solidity will revert if enum values are out of range, so
// play it safe and pick zero.
return 0;
}
return new BigNumber(hexUtils.random(parseInt(/\d+$/.exec(item.type)![0], 10) / 8));
}
if (/^int\d+$/.test(item.type)) {

View File

@ -0,0 +1,34 @@
import { blockchainTests, describe, expect } from '@0x/contracts-test-utils';
import { artifacts } from './artifacts';
import { getRandomLimitOrder, getRandomRfqOrder } from './utils/orders';
import { TestLibNativeOrderContract } from './wrappers';
blockchainTests('LibLimitOrder tests', env => {
let testContract: TestLibNativeOrderContract;
before(async () => {
testContract = await TestLibNativeOrderContract.deployFrom0xArtifactAsync(
artifacts.TestLibNativeOrder,
env.provider,
env.txDefaults,
artifacts,
);
});
describe('getLimitOrderStructHash()', () => {
it('returns the correct hash', async () => {
const order = getRandomLimitOrder();
const structHash = await testContract.getLimitOrderStructHash(order).callAsync();
expect(structHash).to.eq(order.getStructHash());
});
});
describe('getRfqOrderStructHash()', () => {
it('returns the correct hash', async () => {
const order = getRandomRfqOrder();
const structHash = await testContract.getRfqOrderStructHash(order).callAsync();
expect(structHash).to.eq(order.getStructHash());
});
});
});

View File

@ -1,25 +1,20 @@
import { blockchainTests, constants, expect } from '@0x/contracts-test-utils';
import { AuthorizableRevertErrors, BigNumber, hexUtils, ZeroExRevertErrors } from '@0x/utils';
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import { blockchainTests, expect } from '@0x/contracts-test-utils';
import { AuthorizableRevertErrors, BigNumber, hexUtils } from '@0x/utils';
import { artifacts } from './artifacts';
import { FeeCollectorContract, TestProtocolFeesContract, TestStakingContract, TestWethContract } from './wrappers';
import { FeeCollectorContract, TestFixinProtocolFeesContract, TestStakingContract, TestWethContract } from './wrappers';
blockchainTests.resets('ProtocolFees', env => {
let payer: string;
const FEE_MULTIPLIER = 70e3;
let taker: string;
let unauthorized: string;
let protocolFees: TestProtocolFeesContract;
let protocolFees: TestFixinProtocolFeesContract;
let staking: TestStakingContract;
let weth: TestWethContract;
let singleFeeAmount: BigNumber;
before(async () => {
[payer, unauthorized] = await env.getAccountAddressesAsync();
protocolFees = await TestProtocolFeesContract.deployFrom0xArtifactAsync(
artifacts.TestProtocolFees,
env.provider,
env.txDefaults,
artifacts,
);
[taker, unauthorized] = await env.getAccountAddressesAsync();
weth = await TestWethContract.deployFrom0xArtifactAsync(
artifacts.TestWeth,
env.provider,
@ -33,30 +28,26 @@ blockchainTests.resets('ProtocolFees', env => {
artifacts,
weth.address,
);
await weth.mint(payer, constants.ONE_ETHER).awaitTransactionSuccessAsync();
await weth.approve(protocolFees.address, constants.ONE_ETHER).awaitTransactionSuccessAsync({ from: payer });
protocolFees = await TestFixinProtocolFeesContract.deployFrom0xArtifactAsync(
artifacts.TestFixinProtocolFees,
env.provider,
{ ...env.txDefaults, from: taker },
artifacts,
weth.address,
staking.address,
FEE_MULTIPLIER,
);
singleFeeAmount = await protocolFees.getSingleProtocolFee().callAsync();
await weth.mint(taker, singleFeeAmount).awaitTransactionSuccessAsync();
await weth.approve(protocolFees.address, singleFeeAmount).awaitTransactionSuccessAsync({ from: taker });
});
async function collectAsync(
poolId: string,
amount: BigNumber,
etherValue: BigNumber,
): Promise<TransactionReceiptWithDecodedLogs> {
return protocolFees
.collectProtocolFee(poolId, amount, weth.address)
.awaitTransactionSuccessAsync({ from: payer, value: etherValue });
}
async function transferFeesAsync(poolId: string): Promise<TransactionReceiptWithDecodedLogs> {
return protocolFees.transferFeesForPool(poolId, staking.address, weth.address).awaitTransactionSuccessAsync();
}
describe('FeeCollector', () => {
it('should disallow unauthorized initialization', async () => {
const pool = hexUtils.random();
await collectAsync(pool, constants.ONE_ETHER, constants.ZERO_AMOUNT);
await transferFeesAsync(pool);
await protocolFees.collectProtocolFee(pool).awaitTransactionSuccessAsync({ value: singleFeeAmount });
await protocolFees.transferFeesForPool(pool).awaitTransactionSuccessAsync();
const feeCollector = new FeeCollectorContract(
await protocolFees.getFeeCollector(pool).callAsync(),
@ -74,91 +65,70 @@ blockchainTests.resets('ProtocolFees', env => {
describe('_collectProtocolFee()', () => {
const pool1 = hexUtils.random();
const pool2 = hexUtils.random();
let feeCollector1Address: string;
let feeCollector2Address: string;
it('should revert if WETH transfer fails', async () => {
const tooMuch = constants.ONE_ETHER.plus(1);
const tx = collectAsync(pool1, constants.ONE_ETHER.plus(1), constants.ZERO_AMOUNT);
return expect(tx).to.revertWith(
new ZeroExRevertErrors.Spender.SpenderERC20TransferFromFailedError(
weth.address,
payer,
undefined,
tooMuch,
undefined,
),
);
before(async () => {
feeCollector1Address = await protocolFees.getFeeCollector(pool1).callAsync();
feeCollector2Address = await protocolFees.getFeeCollector(pool2).callAsync();
});
it('should revert if insufficient ETH transferred', async () => {
const tooLittle = constants.ONE_ETHER.minus(1);
const tx = collectAsync(pool1, constants.ONE_ETHER, tooLittle);
const tooLittle = singleFeeAmount.minus(1);
const tx = protocolFees.collectProtocolFee(pool1).awaitTransactionSuccessAsync({ value: tooLittle });
return expect(tx).to.revertWith('FixinProtocolFees/ETHER_TRANSFER_FALIED');
});
it('should accept WETH fee', async () => {
const beforeWETH = await weth.balanceOf(payer).callAsync();
await collectAsync(pool1, constants.ONE_ETHER, constants.ZERO_AMOUNT);
const afterWETH = await weth.balanceOf(payer).callAsync();
return expect(beforeWETH.minus(afterWETH)).to.bignumber.eq(constants.ONE_ETHER);
});
it('should accept ETH fee', async () => {
const beforeWETH = await weth.balanceOf(payer).callAsync();
const beforeETH = await env.web3Wrapper.getBalanceInWeiAsync(payer);
await collectAsync(pool1, constants.ONE_ETHER, constants.ONE_ETHER);
const afterWETH = await weth.balanceOf(payer).callAsync();
const afterETH = await env.web3Wrapper.getBalanceInWeiAsync(payer);
const beforeETH = await env.web3Wrapper.getBalanceInWeiAsync(taker);
await protocolFees.collectProtocolFee(pool1).awaitTransactionSuccessAsync({ value: singleFeeAmount });
const afterETH = await env.web3Wrapper.getBalanceInWeiAsync(taker);
// We check for greater than 1 ether spent to allow for spending on gas.
await expect(beforeETH.minus(afterETH)).to.bignumber.gt(constants.ONE_ETHER);
return expect(beforeWETH).to.bignumber.eq(afterWETH);
});
// We check for greater than fee spent to allow for spending on gas.
await expect(beforeETH.minus(afterETH)).to.bignumber.gt(singleFeeAmount);
it('should transfer both ETH and WETH', async () => {
await collectAsync(pool1, constants.ONE_ETHER, constants.ZERO_AMOUNT);
await collectAsync(pool1, constants.ONE_ETHER, constants.ONE_ETHER);
await transferFeesAsync(pool1);
const balanceWETH = await weth.balanceOf(staking.address).callAsync();
// We leave 1 wei behind of both ETH and WETH.
return expect(balanceWETH).to.bignumber.eq(constants.ONE_ETHER.times(2).minus(2));
await expect(await env.web3Wrapper.getBalanceInWeiAsync(feeCollector1Address)).to.bignumber.eq(
singleFeeAmount,
);
});
it('should accept ETH after first transfer', async () => {
await collectAsync(pool1, constants.ONE_ETHER, constants.ONE_ETHER);
await transferFeesAsync(pool1);
await collectAsync(pool1, constants.ONE_ETHER, constants.ONE_ETHER);
await transferFeesAsync(pool1);
await protocolFees.collectProtocolFee(pool1).awaitTransactionSuccessAsync({ value: singleFeeAmount });
await protocolFees.transferFeesForPool(pool1).awaitTransactionSuccessAsync();
await protocolFees.collectProtocolFee(pool1).awaitTransactionSuccessAsync({ value: singleFeeAmount });
await protocolFees.transferFeesForPool(pool1).awaitTransactionSuccessAsync();
const balanceWETH = await weth.balanceOf(staking.address).callAsync();
// We leave 1 wei behind of both ETH and WETH
return expect(balanceWETH).to.bignumber.eq(constants.ONE_ETHER.times(2).minus(2));
// We leave 1 wei of WETH behind.
await expect(balanceWETH).to.bignumber.eq(singleFeeAmount.times(2).minus(1));
await expect(await weth.balanceOf(feeCollector1Address).callAsync()).to.bignumber.equal(1);
// And no ETH.
await expect(await env.web3Wrapper.getBalanceInWeiAsync(feeCollector1Address)).to.bignumber.eq(0);
});
it('should attribute fees correctly', async () => {
const pool1Amount = new BigNumber(12345);
const pool2Amount = new BigNumber(45678);
await collectAsync(pool1, pool1Amount, pool1Amount); // ETH
await transferFeesAsync(pool1);
await collectAsync(pool2, pool2Amount, constants.ZERO_AMOUNT); // WETH
await transferFeesAsync(pool2);
await protocolFees.collectProtocolFee(pool1).awaitTransactionSuccessAsync({ value: singleFeeAmount });
await protocolFees.transferFeesForPool(pool1).awaitTransactionSuccessAsync();
await protocolFees.collectProtocolFee(pool2).awaitTransactionSuccessAsync({ value: singleFeeAmount });
await protocolFees.transferFeesForPool(pool2).awaitTransactionSuccessAsync();
const pool1Balance = await staking.balanceForPool(pool1).callAsync();
const pool2Balance = await staking.balanceForPool(pool2).callAsync();
const balanceWETH = await weth.balanceOf(staking.address).callAsync();
await expect(balanceWETH).to.bignumber.equal(pool1Balance.plus(pool2Balance));
await expect(balanceWETH).to.bignumber.equal(singleFeeAmount.times(2).minus(2));
// We leave 1 wei behind of both ETH and WETH.
await expect(pool1Balance).to.bignumber.equal(pool1Amount.minus(2));
// Here we paid in WETH, so there's just 1 wei of WETH held back.
return expect(pool2Balance).to.bignumber.equal(pool2Amount.minus(1));
// We leave 1 wei of WETH behind.
await expect(pool1Balance).to.bignumber.equal(singleFeeAmount.minus(1));
await expect(pool2Balance).to.bignumber.equal(singleFeeAmount.minus(1));
await expect(await weth.balanceOf(feeCollector1Address).callAsync()).to.bignumber.equal(1);
await expect(await weth.balanceOf(feeCollector2Address).callAsync()).to.bignumber.equal(1);
await expect(pool2Balance).to.bignumber.equal(singleFeeAmount.minus(1));
// And no ETH.
await expect(await env.web3Wrapper.getBalanceInWeiAsync(feeCollector1Address)).to.bignumber.eq(0);
await expect(await env.web3Wrapper.getBalanceInWeiAsync(feeCollector2Address)).to.bignumber.eq(0);
});
});
});

View File

@ -4,6 +4,7 @@ export {
deployFullFeaturesAsync,
initialMigrateAsync,
fullMigrateAsync,
FullMigrationOpts,
FullMigrationConfig,
FullFeaturesDeployConfig,
FullFeatures,
} from '../../src/migration';

View File

@ -0,0 +1,43 @@
import { getRandomInteger, randomAddress } from '@0x/contracts-test-utils';
import { BigNumber, hexUtils } from '@0x/utils';
import { LimitOrder, LimitOrderFields, RfqOrder, RfqOrderFields } from '../../src/orders';
/**
* Generate a random limit order.
*/
export function getRandomLimitOrder(fields: Partial<LimitOrderFields> = {}): LimitOrder {
return new LimitOrder({
makerToken: randomAddress(),
takerToken: randomAddress(),
makerAmount: getRandomInteger('1e18', '100e18'),
takerAmount: getRandomInteger('1e6', '100e6'),
takerTokenFeeAmount: getRandomInteger('0.01e18', '1e18'),
maker: randomAddress(),
taker: randomAddress(),
sender: randomAddress(),
feeRecipient: randomAddress(),
pool: hexUtils.random(),
expiry: new BigNumber(Math.floor(Date.now() / 1000 + 60)),
salt: new BigNumber(hexUtils.random()),
...fields,
});
}
/**
* Generate a random RFQ order.
*/
export function getRandomRfqOrder(fields: Partial<RfqOrderFields> = {}): RfqOrder {
return new RfqOrder({
makerToken: randomAddress(),
takerToken: randomAddress(),
makerAmount: getRandomInteger('1e18', '100e18'),
takerAmount: getRandomInteger('1e6', '100e6'),
maker: randomAddress(),
txOrigin: randomAddress(),
pool: hexUtils.random(),
expiry: new BigNumber(Math.floor(Date.now() / 1000 + 60)),
salt: new BigNumber(hexUtils.random()),
...fields,
});
}

View File

@ -28,6 +28,7 @@ export * from '../test/generated-wrappers/i_liquidity_provider';
export * from '../test/generated-wrappers/i_liquidity_provider_feature';
export * from '../test/generated-wrappers/i_liquidity_provider_sandbox';
export * from '../test/generated-wrappers/i_meta_transactions_feature';
export * from '../test/generated-wrappers/i_native_orders_feature';
export * from '../test/generated-wrappers/i_ownable_feature';
export * from '../test/generated-wrappers/i_signature_validator_feature';
export * from '../test/generated-wrappers/i_simple_function_registry_feature';
@ -45,6 +46,9 @@ export * from '../test/generated-wrappers/lib_liquidity_provider_rich_errors';
export * from '../test/generated-wrappers/lib_meta_transactions_rich_errors';
export * from '../test/generated-wrappers/lib_meta_transactions_storage';
export * from '../test/generated-wrappers/lib_migrate';
export * from '../test/generated-wrappers/lib_native_order';
export * from '../test/generated-wrappers/lib_native_orders_rich_errors';
export * from '../test/generated-wrappers/lib_native_orders_storage';
export * from '../test/generated-wrappers/lib_order_hash';
export * from '../test/generated-wrappers/lib_ownable_rich_errors';
export * from '../test/generated-wrappers/lib_ownable_storage';
@ -80,6 +84,7 @@ export * from '../test/generated-wrappers/mixin_sushiswap';
export * from '../test/generated-wrappers/mixin_uniswap';
export * from '../test/generated-wrappers/mixin_uniswap_v2';
export * from '../test/generated-wrappers/mixin_zero_ex_bridge';
export * from '../test/generated-wrappers/native_orders_feature';
export * from '../test/generated-wrappers/ownable_feature';
export * from '../test/generated-wrappers/pay_taker_transformer';
export * from '../test/generated-wrappers/signature_validator_feature';
@ -90,8 +95,10 @@ export * from '../test/generated-wrappers/test_delegate_caller';
export * from '../test/generated-wrappers/test_fill_quote_transformer_bridge';
export * from '../test/generated-wrappers/test_fill_quote_transformer_exchange';
export * from '../test/generated-wrappers/test_fill_quote_transformer_host';
export * from '../test/generated-wrappers/test_fixin_protocol_fees';
export * from '../test/generated-wrappers/test_full_migration';
export * from '../test/generated-wrappers/test_initial_migration';
export * from '../test/generated-wrappers/test_lib_native_order';
export * from '../test/generated-wrappers/test_lib_signature';
export * from '../test/generated-wrappers/test_lib_token_spender';
export * from '../test/generated-wrappers/test_liquidity_provider';
@ -99,7 +106,7 @@ export * from '../test/generated-wrappers/test_meta_transactions_transform_erc20
export * from '../test/generated-wrappers/test_migrator';
export * from '../test/generated-wrappers/test_mint_token_erc20_transformer';
export * from '../test/generated-wrappers/test_mintable_erc20_token';
export * from '../test/generated-wrappers/test_protocol_fees';
export * from '../test/generated-wrappers/test_native_orders_feature';
export * from '../test/generated-wrappers/test_simple_function_registry_feature_impl1';
export * from '../test/generated-wrappers/test_simple_function_registry_feature_impl2';
export * from '../test/generated-wrappers/test_staking';

View File

@ -10,7 +10,8 @@
"generated-artifacts/IAllowanceTarget.json",
"generated-artifacts/IERC20Transformer.json",
"generated-artifacts/IFlashWallet.json",
"generated-artifacts/ILiquidityProvider.json",
"generated-artifacts/ILiquidityProviderFeature.json",
"generated-artifacts/INativeOrdersFeature.json",
"generated-artifacts/IOwnableFeature.json",
"generated-artifacts/ISimpleFunctionRegistryFeature.json",
"generated-artifacts/ITokenSpenderFeature.json",
@ -20,6 +21,7 @@
"generated-artifacts/LiquidityProviderFeature.json",
"generated-artifacts/LogMetadataTransformer.json",
"generated-artifacts/MetaTransactionsFeature.json",
"generated-artifacts/NativeOrdersFeature.json",
"generated-artifacts/OwnableFeature.json",
"generated-artifacts/PayTakerTransformer.json",
"generated-artifacts/SignatureValidatorFeature.json",
@ -53,6 +55,7 @@
"test/generated-artifacts/ILiquidityProviderFeature.json",
"test/generated-artifacts/ILiquidityProviderSandbox.json",
"test/generated-artifacts/IMetaTransactionsFeature.json",
"test/generated-artifacts/INativeOrdersFeature.json",
"test/generated-artifacts/IOwnableFeature.json",
"test/generated-artifacts/ISignatureValidatorFeature.json",
"test/generated-artifacts/ISimpleFunctionRegistryFeature.json",
@ -70,6 +73,9 @@
"test/generated-artifacts/LibMetaTransactionsRichErrors.json",
"test/generated-artifacts/LibMetaTransactionsStorage.json",
"test/generated-artifacts/LibMigrate.json",
"test/generated-artifacts/LibNativeOrder.json",
"test/generated-artifacts/LibNativeOrdersRichErrors.json",
"test/generated-artifacts/LibNativeOrdersStorage.json",
"test/generated-artifacts/LibOrderHash.json",
"test/generated-artifacts/LibOwnableRichErrors.json",
"test/generated-artifacts/LibOwnableStorage.json",
@ -105,6 +111,7 @@
"test/generated-artifacts/MixinUniswap.json",
"test/generated-artifacts/MixinUniswapV2.json",
"test/generated-artifacts/MixinZeroExBridge.json",
"test/generated-artifacts/NativeOrdersFeature.json",
"test/generated-artifacts/OwnableFeature.json",
"test/generated-artifacts/PayTakerTransformer.json",
"test/generated-artifacts/SignatureValidatorFeature.json",
@ -115,8 +122,10 @@
"test/generated-artifacts/TestFillQuoteTransformerBridge.json",
"test/generated-artifacts/TestFillQuoteTransformerExchange.json",
"test/generated-artifacts/TestFillQuoteTransformerHost.json",
"test/generated-artifacts/TestFixinProtocolFees.json",
"test/generated-artifacts/TestFullMigration.json",
"test/generated-artifacts/TestInitialMigration.json",
"test/generated-artifacts/TestLibNativeOrder.json",
"test/generated-artifacts/TestLibSignature.json",
"test/generated-artifacts/TestLibTokenSpender.json",
"test/generated-artifacts/TestLiquidityProvider.json",
@ -124,7 +133,7 @@
"test/generated-artifacts/TestMigrator.json",
"test/generated-artifacts/TestMintTokenERC20Transformer.json",
"test/generated-artifacts/TestMintableERC20Token.json",
"test/generated-artifacts/TestProtocolFees.json",
"test/generated-artifacts/TestNativeOrdersFeature.json",
"test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl1.json",
"test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json",
"test/generated-artifacts/TestStaking.json",

View File

@ -2,11 +2,7 @@
Orders
######
An order is a message passed into the 0x Protocol to facilitate an ERC20->ERC20 trade. There are currently two types of orders in 0x V4: Limit, RFQ.
.. note::
As of v4 of the protocol, the maker address is no longer explicitly defined in limit orders. The maker is instead recovered from the signature of the order's EIP712 hash.
An order is a message passed into the 0x Protocol to facilitate an ERC20->ERC20 trade. There are currently two types of orders in 0x V4: **Limit** and **RFQ**.
.. note::
0x Orders currently support the exchange of ERC20 Tokens. Other asset classes, like ERC721,
@ -22,48 +18,45 @@ Structure
The ``LimitOrder`` struct has the following fields:
+------------------+-------------+-----------------------------------------------------------------------------+
| Field | Type | Description |
+==================+=============+=============================================================================+
| ``makerToken`` | ``address`` | The ERC20 token the maker is selling and the maker is selling to the taker. |
+------------------+-------------+-----------------------------------------------------------------------------+
| ``takerToken`` | ``address`` | The ERC20 token the taker is selling and the taker is selling to the maker. |
+------------------+-------------+-----------------------------------------------------------------------------+
| ``makerAmount`` | ``uint128`` | The amount of makerToken being sold by the maker. |
+------------------+-------------+-----------------------------------------------------------------------------+
| ``takerAmount`` | ``uint128`` | The amount of takerToken being sold by the taker. |
+------------------+-------------+-----------------------------------------------------------------------------+
| ``feeRecipient`` | ``address`` | Recipient of maker token or taker token fees (if non-zero). |
+------------------+-------------+-----------------------------------------------------------------------------+
| ``feeAmount`` | ``uint128`` | Amount of takerToken paid by the taker to the feeRecipient. |
+------------------+-------------+-----------------------------------------------------------------------------+
| ``taker`` | ``address`` | Allowed taker address. Set to zero to allow any taker. |
+------------------+-------------+-----------------------------------------------------------------------------+
| ``sender`` | ``address`` | Allowed address to directly call ``fillLimitOrder()`` (``msg.sender``). |
| | | This is distinct from ``taker`` in meta-transactions. |
| | | Set to zero to allow any caller. |
+------------------+-------------+-----------------------------------------------------------------------------+
| ``pool`` | ``uint256`` | The staking pool to attribute the 0x protocol fee from this order. |
| | | Set to zero to attribute to the default pool, not owned by anyone. |
+------------------+-------------+-----------------------------------------------------------------------------+
| ``expiry`` | ``uint64`` | The Unix timestamp in seconds when this order expires. |
+------------------+-------------+-----------------------------------------------------------------------------+
| ``salt`` | ``uint256`` | Arbitrary number to enforce uniqueness of the order's hash. |
+------------------+-------------+-----------------------------------------------------------------------------+
+--------------------------+-------------+-----------------------------------------------------------------------------+
| Field | Type | Description |
+==========================+=============+=============================================================================+
| ``makerToken`` | ``address`` | The ERC20 token the maker is selling and the maker is selling to the taker. |
+--------------------------+-------------+-----------------------------------------------------------------------------+
| ``takerToken`` | ``address`` | The ERC20 token the taker is selling and the taker is selling to the maker. |
+--------------------------+-------------+-----------------------------------------------------------------------------+
| ``makerAmount`` | ``uint128`` | The amount of makerToken being sold by the maker. |
+--------------------------+-------------+-----------------------------------------------------------------------------+
| ``takerAmount`` | ``uint128`` | The amount of takerToken being sold by the taker. |
+--------------------------+-------------+-----------------------------------------------------------------------------+
| ``takerTokenFeeAmount`` | ``uint128`` | Amount of takerToken paid by the taker to the feeRecipient. |
+--------------------------+-------------+-----------------------------------------------------------------------------+
| ``maker`` | ``address`` | The address of the maker, and signer, of this order. |
+--------------------------+-------------+-----------------------------------------------------------------------------+
| ``taker`` | ``address`` | Allowed taker address. Set to zero to allow any taker. |
+--------------------------+-------------+-----------------------------------------------------------------------------+
| ``sender`` | ``address`` | Allowed address to directly call ``fillLimitOrder()`` (``msg.sender``). |
| | | This is distinct from ``taker`` in meta-transactions. |
| | | Set to zero to allow any caller. |
+--------------------------+-------------+-----------------------------------------------------------------------------+
| ``feeRecipient`` | ``address`` | Recipient of maker token or taker token fees (if non-zero). |
+--------------------------+-------------+-----------------------------------------------------------------------------+
| ``pool`` | ``bytes32`` | The staking pool to attribute the 0x protocol fee from this order. |
| | | Set to zero to attribute to the default pool, not owned by anyone. |
+--------------------------+-------------+-----------------------------------------------------------------------------+
| ``expiry`` | ``uint64`` | The Unix timestamp in seconds when this order expires. |
+--------------------------+-------------+-----------------------------------------------------------------------------+
| ``salt`` | ``uint256`` | Arbitrary number to enforce uniqueness of the order's hash. |
+--------------------------+-------------+-----------------------------------------------------------------------------+
Hashing limit orders
--------------------
There are two hashes associated with limit orders: the signature hash and the fill hash. The signature hash is what gets signed during the signing step. The fill hash is the hash used to uniquely identify an order inside the protocol and can be considered the "canonical" hash of the order.
Computing the signature hash
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The signature hash is the hash of the order struct, following the `EIP712 spec <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md>`_. In solidity, the signature hash is computed as:
The hash of the order is used to uniquely identify an order inside the protocol. It is computed following the `EIP712 spec <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md>`_ standard. In solidity, the hash is computed as:
.. code-block:: solidity
bytes32 signatureHash = keccak256(abi.encodePacked(
bytes32 orderHash = keccak256(abi.encodePacked(
'\x19\x01',
// The domain separator.
keccak256(abi.encode(
@ -90,11 +83,12 @@ The signature hash is the hash of the order struct, following the `EIP712 spec <
'address takerToken,',
'uint128 makerAmount,',
'uint128 takerAmount,',
'address feeRecipient,',
'uint128 feeAmount,',
'uint128 takerTokenFeeAmount,',
'address taker,',
'address maker,',
'address sender,',
'uint256 pool,',
'address feeRecipient,',
'bytes32 pool,',
'uint64 expiry,',
'uint256 salt)'
)),
@ -103,34 +97,22 @@ The signature hash is the hash of the order struct, following the `EIP712 spec <
order.takerToken,
order.makerAmount,
order.takerAmount,
order.feeRecipient,
order.feeAmount,
order.takerTokenFeeAmount,
order.maker,
order.taker,
order.sender,
order.feeRecipient,
order.pool,
order.expiry,
order.salt
))
));
Computing the fill hash
^^^^^^^^^^^^^^^^^^^^^^^^^
The fill hash simply hashes the previous signature hash with the maker's address, which can be recovered from the order's signature if not already known.
Alternatively, the Exchange Proxy contract can be used to retrieve the hash given an order.
.. code-block:: solidity
// For EthSign signatures, the signatureHash would need to be replaced with
// keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", signatureHash))
address makerAddress = ecrecover(signatureHash, signature.v, signature.r, signature.s);
bytes32 fillHash = keccak256(abi.encode(signatureHash, makerAddress));
Alternatively, the Exchange Proxy contract can be used to retrieve these hashes given an order and signature.
.. code-block:: solidity
bytes32 signatureHash = IZeroEx(0xDef1C0ded9bec7F1a1670819833240f027b25EfF).getLimitOrderSignatureHash(order);
bytes32 fillHash = IZeroEx(0xDef1C0ded9bec7F1a1670819833240f027b25EfF).getLimitOrderFillHash(order, signature);
bytes32 orderHash = IZeroEx(0xDef1C0ded9bec7F1a1670819833240f027b25EfF).getLimitOrderHash(order);
Signing limit orders
--------------------
@ -153,21 +135,26 @@ There are two types of signatures supported: ``EIP712`` and ``EthSign``.
* The ``EIP712`` signature type is best for web frontends that present an order to be signed through Metamask in a human-readable format. It relies on the `eth_signTypedData <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md#specification-of-the-eth_signtypeddata-json-rpc>`_ JSON-RPC method exposed by MetaMask. This signature has the ``signatureType`` of ``2``.
* The ``EthSign`` signature is best for use with headless providers, such as when using a geth node. This relies on the ``eth_sign`` JSON-RPC method common to all nodes. This signature has the ``signatureType`` of ``3``.
In both cases, the ``@0x/order-utils`` package simplifies generating these signatures.
In both cases, the ``@0x/protocol-utils`` package simplifies generating these signatures.
.. code-block:: javascript
:linenos:
const orderUtils = require('@0x/order-utils');
const order = new orderUtils.LimitOrder({
const utils = require('@0x/protocol-utils');
const order = new utils.LimitOrder({
makerToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI
takerToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
... // Other fields
});
// Generate an EIP712 signature
const signature = await order.signTypedDataAsync(web3.currentProvider, makerAddress);
const signature = await order.eip712SignTypedDataWithProviderAsync(
web3.currentProvider,
makerAddress,
);
// Generate an EthSign signature
const signature = await order.sign(web3.currentProvider, makerAddress);
const signature = await order.ethSignHashWithProviderAsync(
web3.currentProvider,
makerAddress,
);
Filling limit orders
--------------------
@ -185,12 +172,12 @@ Limit orders can be filled with the ``fillLimitOrder()`` or ``fillOrKillLimitOrd
// The signature
Signature calldata signature,
// How much taker token to fill the order with
uint256 takerTokenFillAmount
uint128 takerTokenFillAmount
)
external
payable
// How much maker token from the order the taker received.
returns (uint256 makerTokenFillAmount);
returns (uint128 takerTokenFillAmount, uint128 makerTokenFillAmount);
``fillOrKillLimitOrder()`` fills a single limit order for **exactly** ``takerTokenFillAmount``:
@ -202,17 +189,17 @@ Limit orders can be filled with the ``fillLimitOrder()`` or ``fillOrKillLimitOrd
// The signature
Signature calldata signature,
// How much taker token to fill the order with
uint256 takerTokenFillAmount
uint128 takerTokenFillAmount
)
external
payable
// How much maker token from the order the taker received.
returns (uint256 makerTokenFillAmount);
returns (uint128 makerTokenFillAmount);
Cancelling a limit order
------------------------
Because there is no way to un-sign an order that has been distributed, limit orders must be cancelled on-chain through ``cancelLimitOrder()``, ``batchCancelLimitOrders()`` or ``cancelLimitOrdersUpTo()`` functions. They can only be called by the order's maker.
Because there is no way to un-sign an order that has been distributed, limit orders must be cancelled on-chain through one of several functions. They can only be called by the order's maker.
``cancelLimitOrder()`` cancels a single limit order created by the caller:
@ -234,15 +221,28 @@ Because there is no way to un-sign an order that has been distributed, limit ord
)
external;
``cancelLimitOrdersUpTo()`` will cancel limit orders created by the caller with a ``salt`` field <= the value provided. Subsequent calls to this function must provide a ``salt`` >= the last call to succeed.
``cancelLimitPairOrders()`` will cancel all limit orders created by the caller with with a maker and taker token pair and a ``salt`` field < the ``salt`` provided. Subsequent calls to this function with the same tokens must provide a ``salt`` >= the last call to succeed.
.. code-block:: solidity
function cancelLimitOrdersUpTo(
function cancelLimitPairLimitOrders(
address makerToken,
address takerToken,
uint256 salt;
)
external;
``batchCancelLimitPairOrders()`` performs multiple ``cancelLimitPairOrders()`` at once. Each respective index across arrays is equivalent to a single call.
.. code-block:: solidity
function batchCancelLimitPairOrders(
address[] makerTokens,
address[] takerTokens,
uint256[] salts;
)
external;
Getting the status of a limit order
-----------------------------------
@ -250,31 +250,30 @@ The Exchange Proxy exposes a function ``getLimitOrderInfo()`` to query informati
.. code-block:: solidity
enum OrderState {
enum OrderStatus {
INVALID,
CANCELLED,
FILLABLE,
FILLED
FILLED,
CANCELLED,
EXPIRED
}
struct OrderInfo {
// The fill hash.
bytes32 fillHash;
// The order hash.
bytes32 orderHash;
// Current state of the order.
OrderState state;
OrderStatus status;
// How much taker token has been filled in the order.
uint256 takerTokenFilledAmount;
uint128 takerTokenFilledAmount;
}
function getLimitOrderInfo(
// The order
LimitOrder calldata order,
// The signature
Signature calldata signature
LimitOrder calldata order
)
external
view
returns (OrderInfo memory status);
returns (OrderInfo memory orderInfo);
RFQ Orders
==========
@ -303,9 +302,11 @@ The ``RFQOrder`` struct has the following fields:
+-----------------+-------------+-----------------------------------------------------------------------------+
| ``takerAmount`` | ``uint128`` | The amount of takerToken being sold by the taker. |
+-----------------+-------------+-----------------------------------------------------------------------------+
| ``maker`` | ``address`` | The address of the maker, and signer, of this order. |
+-----------------+-------------+-----------------------------------------------------------------------------+
| ``txOrigin`` | ``address`` | The allowed address of the EOA that submitted the Ethereum transaction. |
+-----------------+-------------+-----------------------------------------------------------------------------+
| ``pool`` | ``uint256`` | The staking pool to attribute the 0x protocol fee from this order. |
| ``pool`` | ``bytes32`` | The staking pool to attribute the 0x protocol fee from this order. |
| | | Set to zero to attribute to the default pool, not owned by anyone. |
+-----------------+-------------+-----------------------------------------------------------------------------+
| ``expiry`` | ``uint64`` | The Unix timestamp in seconds when this order expires. |
@ -316,16 +317,11 @@ The ``RFQOrder`` struct has the following fields:
Hashing RFQ orders
------------------
There are two hashes associated with RFQ orders: the signature hash and the fill hash. The signature hash is what gets signed during the signing step. The fill hash is the hash used to uniquely identify an order inside the protocol and can be considered the "canonical" hash of the order.
Computing the signature hash
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The signature hash is the hash of the order struct, following the `EIP712 spec <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md>`_. In solidity, the signature hash is computed as:
The hash of the order is used to uniquely identify an order inside the protocol. It is computed following the `EIP712 spec <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md>`_ standard. In solidity, the hash is computed as:
.. code-block:: solidity
bytes32 signatureHash = keccak256(abi.encodePacked(
bytes32 orderHash = keccak256(abi.encodePacked(
'\x19\x01',
// The domain separator.
keccak256(abi.encode(
@ -352,8 +348,9 @@ The signature hash is the hash of the order struct, following the `EIP712 spec <
'address takerToken,',
'uint128 makerAmount,',
'uint128 takerAmount,',
'address maker,'
'address txOrigin,'
'uint256 pool,',
'bytes32 pool,',
'uint64 expiry,',
'uint256 salt)'
)),
@ -362,6 +359,7 @@ The signature hash is the hash of the order struct, following the `EIP712 spec <
order.takerToken,
order.makerAmount,
order.takerAmount,
order.maker,
order.txOrigin,
order.pool,
order.expiry,
@ -369,32 +367,11 @@ The signature hash is the hash of the order struct, following the `EIP712 spec <
))
));
Computing the fill hash
^^^^^^^^^^^^^^^^^^^^^^^
The fill hash simply hashes the previous signature hash with the maker's address, which can be recovered from the order's signature if not already known.
Alternatively, the Exchange Proxy contract can be used to retrieve the hash given an order.
.. code-block:: solidity
// For EthSign signatures, the signatureHash would need to be replaced with
// keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", signatureHash))
address makerAddress = ecrecover(
keccak256(abi.encodePacked(
'\x19Ethereum Signed Message:\n32',
signatureHash
)),
signature.v,
signature.r,
signature.s
);
bytes32 fillHash = keccak256(abi.encode(signatureHash, makerAddress));
Alternatively, the Exchange Proxy contract can be used to retrieve these hashes given an order and signature.
.. code-block:: solidity
bytes32 signatureHash = IZeroEx(0xDef1C0ded9bec7F1a1670819833240f027b25EfF).getRfqOrderSignatureHash(order);
bytes32 fillHash = IZeroEx(0xDef1C0ded9bec7F1a1670819833240f027b25EfF).getRfqOrderFillHash(order, signature);
bytes32 orderHash = IZeroEx(0xDef1C0ded9bec7F1a1670819833240f027b25EfF).getLimitOrderHash(order);
Signing RFQ orders
------------------
@ -411,18 +388,21 @@ The protocol accepts signatures defined by the following struct:
bytes32 s; // Signature data.
}
The ``@0x/order-utils`` node package simplifies the process of creating a valid signature object.
The ``@0x/protocol-utils`` node package simplifies the process of creating a valid signature object.
.. code-block:: javascript
:linenos:
const orderUtils = require('@0x/order-utils');
const order = new orderUtils.RfqOrder({
const utils = require('@0x/protocol-utils');
const order = new utils.RfqOrder({
makerToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI
takerToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
... // Other fields
});
const signature = await order.sign(web3.currentProvider, makerAddress);
// Generate an EthSign signature
const signature = await order.ethSignHashWithProviderAsync(
web3.currentProvider,
makerAddress,
);
Filling RFQ Orders
------------------
@ -439,12 +419,12 @@ RFQ orders can be filled with the ``fillRfqOrder()`` or ``fillOrKillRfqOrder()``
// The signature
Signature calldata signature,
// How much taker token to fill the order with
uint256 takerTokenFillAmount
uint128 takerTokenFillAmount
)
external
payable
// How much maker token from the order the taker received.
returns (uint256 makerTokenFillAmount);
returns (uint128 takerTokenFillAmount, uint128 makerTokenFillAmount);
``fillOrKillRfqOrder()`` fills a single RFQ order for **exactly** ``takerTokenFillAmount``:
@ -456,17 +436,17 @@ RFQ orders can be filled with the ``fillRfqOrder()`` or ``fillOrKillRfqOrder()``
// The signature
Signature calldata signature,
// How much taker token to fill the order with
uint256 takerTokenFillAmount
uint128 takerTokenFillAmount
)
external
payable
// How much maker token from the order the taker received.
returns (uint256 makerTokenFillAmount);
returns (uint128 makerTokenFillAmount);
Cancelling an RFQ order
-----------------------
Similar to limit orders, RFQ orders can be cancelled on-chain through ``cancelRfqOrder()`` or ``batchCancelRfqOrders()`` (but there is no ``...UpTo()`` variant). Both can only be called by the order's maker.
Similar to limit orders, RFQ orders can be cancelled on-chain through a variety of functions, which can only be called by the order's maker.
``cancelRfqOrder()`` cancels a single RFQ order created by the caller:
@ -488,6 +468,28 @@ Similar to limit orders, RFQ orders can be cancelled on-chain through ``cancelRf
)
external;
``cancelPairRfqOrders()`` will cancel all RFQ orders created by the caller with with a maker and taker token pair and a ``salt`` field < the ``salt`` provided. Subsequent calls to this function with the same tokens must provide a ``salt`` >= the last call to succeed.
.. code-block:: solidity
function cancelPairRfqOrders(
address makerToken,
address takerToken,
uint256 salt;
)
external;
``batchCancelPairRfqOrders()`` performs multiple ``cancelPairRfqOrders()`` at once. Each respective index across arrays is equivalent to a single call.
.. code-block:: solidity
function batchCancelPairRfqOrders(
address[] makerTokens,
address[] takerTokens,
uint256[] salts;
)
external;
Getting the status of an RFQ order
----------------------------------
@ -495,28 +497,27 @@ The Exchange Proxy exposes a function ``getRfqOrderInfo()`` to query information
.. code-block:: solidity
enum OrderState {
enum OrderStatus {
INVALID,
CANCELLED,
FILLABLE,
FILLED
FILLED,
CANCELLED,
EXPIRED
}
struct OrderInfo {
// The fill hash.
bytes32 fillHash;
// The order hash.
bytes32 orderHash;
// Current state of the order.
OrderState state;
OrderStatus status;
// How much taker token has been filled in the order.
uint256 takerTokenFilledAmount;
uint128 takerTokenFilledAmount;
}
function getRfqOrderInfo(
// The order
RfqOrder calldata order,
// The signature
Signature calldata signature
RfqOrder calldata order
)
external
view
returns (OrderInfo memory status);
returns (OrderInfo memory orderInfo);

View File

@ -1,4 +1,13 @@
[
{
"version": "5.4.0",
"changes": [
{
"note": "Update ganache snapshot addresses",
"pr": 27
}
]
},
{
"version": "5.3.0",
"changes": [

View File

@ -198,10 +198,10 @@
"exchangeProxyFlashWallet": "0xb9682a8e7920b431f1d412b8510f0077410c8faa",
"exchangeProxyLiquidityProviderSandbox": "0x0000000000000000000000000000000000000000",
"transformers": {
"wethTransformer": "0xc6b0d3c45a6b5092808196cb00df5c357d55e1d5",
"payTakerTransformer": "0x7209185959d7227fb77274e1e88151d7c4c368d3",
"affiliateFeeTransformer": "0x3f16ca81691dab9184cb4606c361d73c4fd2510a",
"fillQuoteTransformer": "0x99356167edba8fbdc36959e3f5d0c43d1ba9c6db"
"wethTransformer": "0x7209185959d7227fb77274e1e88151d7c4c368d3",
"payTakerTransformer": "0x3f16ca81691dab9184cb4606c361d73c4fd2510a",
"affiliateFeeTransformer": "0x99356167edba8fbdc36959e3f5d0c43d1ba9c6db",
"fillQuoteTransformer": "0x45b3a72221e571017c0f0ec42189e11d149d0ace"
}
}
}