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, "timestamp": 1605302002,
"version": "4.5.8", "version": "4.5.8",

View File

@ -105,4 +105,86 @@ library LibSafeMathV06 {
{ {
return a < b ? a : b; 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", "note": "Add `LibSignature` library",
"pr": 21 "pr": 21
},
{
"note": "Add `LimitOrdersFeature`",
"pr": 27
} }
], ],
"timestamp": 1604355662 "timestamp": 1604355662

View File

@ -19,7 +19,8 @@
"evm.bytecode.object", "evm.bytecode.object",
"evm.bytecode.sourceMap", "evm.bytecode.sourceMap",
"evm.deployedBytecode.object", "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/IMetaTransactionsFeature.sol";
import "./features/IUniswapFeature.sol"; import "./features/IUniswapFeature.sol";
import "./features/ILiquidityProviderFeature.sol"; import "./features/ILiquidityProviderFeature.sol";
import "./features/INativeOrdersFeature.sol";
/// @dev Interface for a fully featured Exchange Proxy. /// @dev Interface for a fully featured Exchange Proxy.
@ -38,7 +39,8 @@ interface IZeroEx is
ITransformERC20Feature, ITransformERC20Feature,
IMetaTransactionsFeature, IMetaTransactionsFeature,
IUniswapFeature, IUniswapFeature,
ILiquidityProviderFeature ILiquidityProviderFeature,
INativeOrdersFeature
{ {
// solhint-disable state-visibility // 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 onlyAuthorized
{ {
// Leave 1 wei behind to avoid expensive zero-->non-zero state change. // Leave 1 wei behind to avoid expensive zero-->non-zero state change.
if (address(this).balance > 1) { if (address(this).balance > 0) {
weth.deposit{value: address(this).balance - 1}(); 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 "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
import "../external/FeeCollector.sol"; import "../external/FeeCollector.sol";
import "../features/libs/LibTokenSpender.sol"; import "../features/libs/LibTokenSpender.sol";
import "../vendor/v3/IStaking.sol";
/// @dev Helpers for collecting protocol fees. /// @dev Helpers for collecting protocol fees.
abstract contract FixinProtocolFees { 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 { constructor(
feeCollectorCodeHash = keccak256(type(FeeCollector).creationCode); IEtherTokenV06 weth,
} IStaking staking,
uint32 protocolFeeMultiplier
/// @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
) )
internal 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) { /// @dev Collect the specified protocol fee in ETH.
// WETH /// The fee is stored in a per-pool fee collector contract.
LibTokenSpender.spendERC20Tokens(weth, msg.sender, address(feeCollector), amount); /// @param poolId The pool ID for which a fee is being collected.
} else { /// @return ethProtocolFeePaid How much protocol fee was collected in ETH.
// ETH function _collectProtocolFee(bytes32 poolId)
(bool success,) = address(feeCollector).call{value: amount}(""); internal
require(success, "FixinProtocolFees/ETHER_TRANSFER_FALIED"); 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. /// @dev Transfer fees for a given pool to the staking contract.
/// @param poolId Identifies the pool whose fees are being paid. /// @param poolId Identifies the pool whose fees are being paid.
function _transferFeesForPool( function _transferFeesForPool(bytes32 poolId)
bytes32 poolId,
IStaking staking,
IEtherTokenV06 weth
)
internal internal
{ {
FeeCollector feeCollector = _getFeeCollector(poolId); FeeCollector feeCollector = _getFeeCollector(poolId);
@ -75,18 +82,18 @@ abstract contract FixinProtocolFees {
if (codeSize == 0) { if (codeSize == 0) {
// Create and initialize the contract if necessary. // Create and initialize the contract if necessary.
new FeeCollector{salt: poolId}(); new FeeCollector{salt: bytes32(poolId)}();
feeCollector.initialize(weth, staking, poolId); feeCollector.initialize(WETH, STAKING, poolId);
} }
if (address(feeCollector).balance > 1) { 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) { if (bal > 1) {
// Leave 1 wei behind to avoid high SSTORE cost of zero-->non-zero. // Leave 1 wei behind to avoid high SSTORE cost of zero-->non-zero.
staking.payProtocolFee( STAKING.payProtocolFee(
address(feeCollector), address(feeCollector),
address(feeCollector), address(feeCollector),
bal - 1); bal - 1);
@ -95,9 +102,7 @@ abstract contract FixinProtocolFees {
/// @dev Compute the CREATE2 address for a fee collector. /// @dev Compute the CREATE2 address for a fee collector.
/// @param poolId The fee collector's pool ID. /// @param poolId The fee collector's pool ID.
function _getFeeCollector( function _getFeeCollector(bytes32 poolId)
bytes32 poolId
)
internal internal
view view
returns (FeeCollector) returns (FeeCollector)
@ -107,8 +112,18 @@ abstract contract FixinProtocolFees {
byte(0xff), byte(0xff),
address(this), address(this),
poolId, // pool ID is salt poolId, // pool ID is salt
feeCollectorCodeHash FEE_COLLECTOR_INIT_CODE_HASH
)))); ))));
return FeeCollector(addr); 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/TransformERC20Feature.sol";
import "../features/SignatureValidatorFeature.sol"; import "../features/SignatureValidatorFeature.sol";
import "../features/MetaTransactionsFeature.sol"; import "../features/MetaTransactionsFeature.sol";
import "../features/NativeOrdersFeature.sol";
import "../external/AllowanceTarget.sol"; import "../external/AllowanceTarget.sol";
import "./InitialMigration.sol"; import "./InitialMigration.sol";
@ -42,6 +43,7 @@ contract FullMigration {
TransformERC20Feature transformERC20; TransformERC20Feature transformERC20;
SignatureValidatorFeature signatureValidator; SignatureValidatorFeature signatureValidator;
MetaTransactionsFeature metaTransactions; MetaTransactionsFeature metaTransactions;
NativeOrdersFeature nativeOrders;
} }
/// @dev Parameters needed to initialize features. /// @dev Parameters needed to initialize features.
@ -84,7 +86,7 @@ contract FullMigration {
/// @param features Features to add to the proxy. /// @param features Features to add to the proxy.
/// @return _zeroEx The configured ZeroEx contract. Same as the `zeroEx` parameter. /// @return _zeroEx The configured ZeroEx contract. Same as the `zeroEx` parameter.
/// @param migrateOpts Parameters needed to initialize features. /// @param migrateOpts Parameters needed to initialize features.
function initializeZeroEx( function migrateZeroEx(
address payable owner, address payable owner,
ZeroEx zeroEx, ZeroEx zeroEx,
Features memory features, Features memory features,
@ -195,5 +197,16 @@ contract FullMigration {
address(this) 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, TokenSpender,
TransformERC20, TransformERC20,
MetaTransactions, MetaTransactions,
ReentrancyGuard ReentrancyGuard,
NativeOrders
} }
/// @dev Get the storage slot given a storage ID. We assign unique, well-spaced /// @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 from Address to transfer asset from.
/// @param to Address to transfer asset to. /// @param to Address to transfer asset to.
/// @param amount Amount of asset to transfer. /// @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. /// @return success The magic bytes `0xdc1600f3` if successful.
function bridgeTransferFrom( function bridgeTransferFrom(
address tokenAddress, address tokenAddress,
address from, address from,
address to, address to,
uint256 amount, uint256 amount,
bytes calldata bridgeData bytes calldata /* bridgeData */
) )
external external
override override

View File

@ -21,26 +21,31 @@ pragma experimental ABIEncoderV2;
import "../src/fixins/FixinProtocolFees.sol"; import "../src/fixins/FixinProtocolFees.sol";
contract TestProtocolFees is FixinProtocolFees { contract TestFixinProtocolFees is
function collectProtocolFee( FixinProtocolFees
bytes32 poolId, {
uint256 amount, constructor(
IERC20TokenV06 weth IEtherTokenV06 weth,
IStaking staking,
uint32 protocolFeeMultiplier
) )
public
FixinProtocolFees(weth, staking, protocolFeeMultiplier)
{
// solhint-disalbe no-empty-blocks
}
function collectProtocolFee(bytes32 poolId)
external external
payable payable
{ {
_collectProtocolFee(poolId, amount, weth); _collectProtocolFee(poolId);
} }
function transferFeesForPool( function transferFeesForPool(bytes32 poolId)
bytes32 poolId,
IStaking staking,
IEtherTokenV06 weth
)
external external
{ {
_transferFeesForPool(poolId, staking, weth); _transferFeesForPool(poolId);
} }
function getFeeCollector( function getFeeCollector(
@ -52,4 +57,12 @@ contract TestProtocolFees is FixinProtocolFees {
{ {
return _getFeeCollector(poolId); 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, IERC20Transformer,
Transformer Transformer
{ {
function transform(TransformContext calldata context) function transform(TransformContext calldata /* context */)
external external
override override
returns (bytes4 success) returns (bytes4 success)

View File

@ -40,9 +40,9 @@
"publish:private": "yarn build && gitpkg publish" "publish:private": "yarn build && gitpkg publish"
}, },
"config": { "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: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": { "repository": {
"type": "git", "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 IAllowanceTarget from '../generated-artifacts/IAllowanceTarget.json';
import * as IERC20Transformer from '../generated-artifacts/IERC20Transformer.json'; import * as IERC20Transformer from '../generated-artifacts/IERC20Transformer.json';
import * as IFlashWallet from '../generated-artifacts/IFlashWallet.json'; import * as IFlashWallet from '../generated-artifacts/IFlashWallet.json';
import * as 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 InitialMigration from '../generated-artifacts/InitialMigration.json';
import * as IOwnableFeature from '../generated-artifacts/IOwnableFeature.json'; import * as IOwnableFeature from '../generated-artifacts/IOwnableFeature.json';
import * as ISimpleFunctionRegistryFeature from '../generated-artifacts/ISimpleFunctionRegistryFeature.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 LiquidityProviderFeature from '../generated-artifacts/LiquidityProviderFeature.json';
import * as LogMetadataTransformer from '../generated-artifacts/LogMetadataTransformer.json'; import * as LogMetadataTransformer from '../generated-artifacts/LogMetadataTransformer.json';
import * as MetaTransactionsFeature from '../generated-artifacts/MetaTransactionsFeature.json'; import * as MetaTransactionsFeature from '../generated-artifacts/MetaTransactionsFeature.json';
import * as NativeOrdersFeature from '../generated-artifacts/NativeOrdersFeature.json';
import * as OwnableFeature from '../generated-artifacts/OwnableFeature.json'; import * as OwnableFeature from '../generated-artifacts/OwnableFeature.json';
import * as PayTakerTransformer from '../generated-artifacts/PayTakerTransformer.json'; import * as PayTakerTransformer from '../generated-artifacts/PayTakerTransformer.json';
import * as SignatureValidatorFeature from '../generated-artifacts/SignatureValidatorFeature.json'; import * as SignatureValidatorFeature from '../generated-artifacts/SignatureValidatorFeature.json';
@ -55,5 +57,7 @@ export const artifacts = {
LogMetadataTransformer: LogMetadataTransformer as ContractArtifact, LogMetadataTransformer: LogMetadataTransformer as ContractArtifact,
BridgeAdapter: BridgeAdapter as ContractArtifact, BridgeAdapter: BridgeAdapter as ContractArtifact,
LiquidityProviderFeature: LiquidityProviderFeature 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 './nonce_utils';
export * from './signed_call_data'; export * from './signed_call_data';
export * from './signature_utils'; export * from './signature_utils';
export * from './orders';
export * from './eip712_utils';
export * from './revert_errors';
export { export {
AffiliateFeeTransformerContract, AffiliateFeeTransformerContract,
BridgeAdapterContract, BridgeAdapterContract,
@ -49,6 +52,5 @@ export {
WethTransformerContract, WethTransformerContract,
ZeroExContract, ZeroExContract,
} from './wrappers'; } from './wrappers';
export * from './revert_errors';
export { EIP712TypedData } from '@0x/types'; export { EIP712TypedData } from '@0x/types';
export { SupportedProvider } from '@0x/subproviders'; export { SupportedProvider } from '@0x/subproviders';

View File

@ -1,4 +1,6 @@
import { SupportedProvider } from '@0x/subproviders'; import { SupportedProvider } from '@0x/subproviders';
import { SimpleContractArtifact } from '@0x/types';
import { NULL_ADDRESS } from '@0x/utils';
import { TxData } from 'ethereum-types'; import { TxData } from 'ethereum-types';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -8,6 +10,7 @@ import {
InitialMigrationContract, InitialMigrationContract,
IZeroExContract, IZeroExContract,
MetaTransactionsFeatureContract, MetaTransactionsFeatureContract,
NativeOrdersFeatureContract,
OwnableFeatureContract, OwnableFeatureContract,
SignatureValidatorFeatureContract, SignatureValidatorFeatureContract,
SimpleFunctionRegistryFeatureContract, SimpleFunctionRegistryFeatureContract,
@ -26,6 +29,19 @@ export interface BootstrapFeatures {
ownable: string; 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. * Deploy the minimum features of the Exchange Proxy.
*/ */
@ -33,12 +49,17 @@ export async function deployBootstrapFeaturesAsync(
provider: SupportedProvider, provider: SupportedProvider,
txDefaults: Partial<TxData>, txDefaults: Partial<TxData>,
features: Partial<BootstrapFeatures> = {}, features: Partial<BootstrapFeatures> = {},
featureArtifacts: Partial<BootstrapFeatureArtifacts> = {},
): Promise<BootstrapFeatures> { ): Promise<BootstrapFeatures> {
const _featureArtifacts = {
...DEFAULT_BOOTSTRAP_FEATURE_ARTIFACTS,
...featureArtifacts,
};
return { return {
registry: registry:
features.registry || features.registry ||
(await SimpleFunctionRegistryFeatureContract.deployFrom0xArtifactAsync( (await SimpleFunctionRegistryFeatureContract.deployFrom0xArtifactAsync(
artifacts.SimpleFunctionRegistryFeature, _featureArtifacts.registry,
provider, provider,
txDefaults, txDefaults,
artifacts, artifacts,
@ -46,7 +67,7 @@ export async function deployBootstrapFeaturesAsync(
ownable: ownable:
features.ownable || features.ownable ||
(await OwnableFeatureContract.deployFrom0xArtifactAsync( (await OwnableFeatureContract.deployFrom0xArtifactAsync(
artifacts.OwnableFeature, _featureArtifacts.ownable,
provider, provider,
txDefaults, txDefaults,
artifacts, artifacts,
@ -90,30 +111,73 @@ export interface FullFeatures extends BootstrapFeatures {
transformERC20: string; transformERC20: string;
signatureValidator: string; signatureValidator: string;
metaTransactions: 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 { export interface FullFeatureArtifacts extends BootstrapFeatureArtifacts {
transformerDeployer: string; 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. * Deploy all the features for a full Exchange Proxy.
*/ */
export async function deployFullFeaturesAsync( export async function deployFullFeaturesAsync(
provider: SupportedProvider, provider: SupportedProvider,
txDefaults: Partial<TxData>, txDefaults: Partial<TxData>,
zeroExAddress: string, config: Partial<FullFeaturesDeployConfig> = {},
features: Partial<FullFeatures> = {}, features: Partial<FullFeatures> = {},
featureArtifacts: Partial<FullFeatureArtifacts> = {},
): Promise<FullFeatures> { ): Promise<FullFeatures> {
const _config = { ...DEFAULT_FULL_FEATURES_DEPLOY_CONFIG, ...config };
const _featureArtifacts = {
...DEFAULT_FULL_FEATURES_ARTIFACTS,
...featureArtifacts,
};
return { return {
...(await deployBootstrapFeaturesAsync(provider, txDefaults)), ...(await deployBootstrapFeaturesAsync(provider, txDefaults)),
tokenSpender: tokenSpender:
features.tokenSpender || features.tokenSpender ||
(await TokenSpenderFeatureContract.deployFrom0xArtifactAsync( (await TokenSpenderFeatureContract.deployFrom0xArtifactAsync(
artifacts.TokenSpenderFeature, _featureArtifacts.tokenSpender,
provider, provider,
txDefaults, txDefaults,
artifacts, artifacts,
@ -121,7 +185,7 @@ export async function deployFullFeaturesAsync(
transformERC20: transformERC20:
features.transformERC20 || features.transformERC20 ||
(await TransformERC20FeatureContract.deployFrom0xArtifactAsync( (await TransformERC20FeatureContract.deployFrom0xArtifactAsync(
artifacts.TransformERC20Feature, _featureArtifacts.transformERC20,
provider, provider,
txDefaults, txDefaults,
artifacts, artifacts,
@ -129,7 +193,7 @@ export async function deployFullFeaturesAsync(
signatureValidator: signatureValidator:
features.signatureValidator || features.signatureValidator ||
(await SignatureValidatorFeatureContract.deployFrom0xArtifactAsync( (await SignatureValidatorFeatureContract.deployFrom0xArtifactAsync(
artifacts.SignatureValidatorFeature, _featureArtifacts.signatureValidator,
provider, provider,
txDefaults, txDefaults,
artifacts, artifacts,
@ -137,11 +201,23 @@ export async function deployFullFeaturesAsync(
metaTransactions: metaTransactions:
features.metaTransactions || features.metaTransactions ||
(await MetaTransactionsFeatureContract.deployFrom0xArtifactAsync( (await MetaTransactionsFeatureContract.deployFrom0xArtifactAsync(
artifacts.MetaTransactionsFeature, _featureArtifacts.metaTransactions,
provider, provider,
txDefaults, txDefaults,
artifacts, 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, )).address,
}; };
} }
@ -154,7 +230,8 @@ export async function fullMigrateAsync(
provider: SupportedProvider, provider: SupportedProvider,
txDefaults: Partial<TxData>, txDefaults: Partial<TxData>,
features: Partial<FullFeatures> = {}, features: Partial<FullFeatures> = {},
opts: Partial<FullMigrationOpts> = {}, config: Partial<FullMigrationConfig> = {},
featureArtifacts: Partial<FullFeatureArtifacts> = {},
): Promise<IZeroExContract> { ): Promise<IZeroExContract> {
const migrator = await FullMigrationContract.deployFrom0xArtifactAsync( const migrator = await FullMigrationContract.deployFrom0xArtifactAsync(
artifacts.FullMigration, artifacts.FullMigration,
@ -170,11 +247,12 @@ export async function fullMigrateAsync(
artifacts, artifacts,
await migrator.getBootstrapper().callAsync(), await migrator.getBootstrapper().callAsync(),
); );
const _features = await deployFullFeaturesAsync(provider, txDefaults, zeroEx.address, features); const _config = { ...config, zeroExAddress: zeroEx.address };
const _opts = { const _features = await deployFullFeaturesAsync(provider, txDefaults, _config, features, featureArtifacts);
const migrateOpts = {
transformerDeployer: txDefaults.from as string, 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); 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 // TODO(dorothy-zbornak): Move these into `@0x/protocol-utils` whenever that
// becomes a thing. // becomes a thing.
// tslint:disable:max-classes-per-file // tslint:disable:max-classes-per-file
import { RevertError } from '@0x/utils'; import { Numberish, RevertError } from '@0x/utils';
import { OrderStatus } from './orders';
export enum SignatureValidationErrorCodes { export enum SignatureValidationErrorCodes {
AlwaysInvalid = 0, AlwaysInvalid = 0,
@ -12,7 +14,6 @@ export enum SignatureValidationErrorCodes {
BadSignatureData = 5, BadSignatureData = 5,
} }
// tslint:disable:max-classes-per-file
export class SignatureValidationError extends RevertError { export class SignatureValidationError extends RevertError {
constructor(code?: SignatureValidationErrorCodes, hash?: string) { constructor(code?: SignatureValidationErrorCodes, hash?: string) {
super('SignatureValidationError', 'SignatureValidationError(uint8 code, bytes32 hash)', { 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. // Register the types we've defined.
for (const type of types) { for (const type of types) {

View File

@ -1,7 +1,7 @@
import { signatureUtils } from '@0x/order-utils';
import { SupportedProvider } from '@0x/subproviders'; import { SupportedProvider } from '@0x/subproviders';
import { EIP712TypedData } from '@0x/types'; 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'; 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. * Sign a hash with the EthSign signature type on a provider.
*/ */
export async function ethSignHashFromProviderAsync( export async function ethSignHashWithProviderAsync(
signer: string,
hash: string, hash: string,
signer: string,
provider: SupportedProvider, provider: SupportedProvider,
): Promise<Signature> { ): Promise<Signature> {
const signatureBytes = await signatureUtils.ecSignHashAsync(provider, hash, signer); const w3w = new Web3Wrapper(providerUtils.standardizeOrThrow(provider));
const parsed = parsePackedSignatureBytes(signatureBytes); const rpcSig = await w3w.signMessageAsync(signer, hash);
assertSignatureType(parsed, SignatureType.EthSign); return {
return parsed; ...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. * 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 { // Parse a hex signature returned by an RPC call into an `ECSignature`.
if (signature.signatureType !== expectedType) { function parseRpcSignature(rpcSig: string): ECSignature {
throw new Error(`Expected signature type to be ${expectedType} but received ${signature.signatureType}.`); if (hexUtils.size(rpcSig) !== 65) {
throw new Error(`Invalid RPC signature length: "${rpcSig}"`);
} }
} // Some providers encode V as 0,1 instead of 27,28.
const VALID_V_VALUES = [0, 1, 27, 28];
function parsePackedSignatureBytes(signatureBytes: string): Signature { // Some providers return the signature packed as V,R,S and others R,S,V.
if (hexUtils.size(signatureBytes) !== 66) { // Try to guess which encoding it is (with a slight preference for R,S,V).
throw new Error(`Expected packed signatureBytes to be 66 bytes long: ${signatureBytes}`); 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; // Format should be V,R,S
if (!Object.values(SignatureType).includes(typeId)) { v = parseInt(rpcSig.slice(2, 4), 16);
throw new Error(`Invalid signatureBytes type ID detected: ${typeId}`); 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 { return {
signatureType: typeId, v,
v: parseInt(signatureBytes.slice(2, 4), 16), r: hexUtils.slice(rpcSig, 1, 33),
r: hexUtils.slice(signatureBytes, 1, 33), s: hexUtils.slice(rpcSig, 33, 65),
s: hexUtils.slice(signatureBytes, 33),
}; };
} }

View File

@ -10,7 +10,8 @@ export * from '../generated-wrappers/full_migration';
export * from '../generated-wrappers/i_allowance_target'; export * from '../generated-wrappers/i_allowance_target';
export * from '../generated-wrappers/i_erc20_transformer'; export * from '../generated-wrappers/i_erc20_transformer';
export * from '../generated-wrappers/i_flash_wallet'; export * from '../generated-wrappers/i_flash_wallet';
export * from '../generated-wrappers/i_liquidity_provider'; 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_ownable_feature';
export * from '../generated-wrappers/i_simple_function_registry_feature'; export * from '../generated-wrappers/i_simple_function_registry_feature';
export * from '../generated-wrappers/i_token_spender_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/liquidity_provider_feature';
export * from '../generated-wrappers/log_metadata_transformer'; export * from '../generated-wrappers/log_metadata_transformer';
export * from '../generated-wrappers/meta_transactions_feature'; export * from '../generated-wrappers/meta_transactions_feature';
export * from '../generated-wrappers/native_orders_feature';
export * from '../generated-wrappers/ownable_feature'; export * from '../generated-wrappers/ownable_feature';
export * from '../generated-wrappers/pay_taker_transformer'; export * from '../generated-wrappers/pay_taker_transformer';
export * from '../generated-wrappers/signature_validator_feature'; 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 ILiquidityProviderFeature from '../test/generated-artifacts/ILiquidityProviderFeature.json';
import * as ILiquidityProviderSandbox from '../test/generated-artifacts/ILiquidityProviderSandbox.json'; import * as ILiquidityProviderSandbox from '../test/generated-artifacts/ILiquidityProviderSandbox.json';
import * as IMetaTransactionsFeature from '../test/generated-artifacts/IMetaTransactionsFeature.json'; import * as IMetaTransactionsFeature from '../test/generated-artifacts/IMetaTransactionsFeature.json';
import * as INativeOrdersFeature from '../test/generated-artifacts/INativeOrdersFeature.json';
import * as InitialMigration from '../test/generated-artifacts/InitialMigration.json'; import * as InitialMigration from '../test/generated-artifacts/InitialMigration.json';
import * as IOwnableFeature from '../test/generated-artifacts/IOwnableFeature.json'; import * as IOwnableFeature from '../test/generated-artifacts/IOwnableFeature.json';
import * as ISignatureValidatorFeature from '../test/generated-artifacts/ISignatureValidatorFeature.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 LibMetaTransactionsRichErrors from '../test/generated-artifacts/LibMetaTransactionsRichErrors.json';
import * as LibMetaTransactionsStorage from '../test/generated-artifacts/LibMetaTransactionsStorage.json'; import * as LibMetaTransactionsStorage from '../test/generated-artifacts/LibMetaTransactionsStorage.json';
import * as LibMigrate from '../test/generated-artifacts/LibMigrate.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 LibOrderHash from '../test/generated-artifacts/LibOrderHash.json';
import * as LibOwnableRichErrors from '../test/generated-artifacts/LibOwnableRichErrors.json'; import * as LibOwnableRichErrors from '../test/generated-artifacts/LibOwnableRichErrors.json';
import * as LibOwnableStorage from '../test/generated-artifacts/LibOwnableStorage.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 MixinUniswap from '../test/generated-artifacts/MixinUniswap.json';
import * as MixinUniswapV2 from '../test/generated-artifacts/MixinUniswapV2.json'; import * as MixinUniswapV2 from '../test/generated-artifacts/MixinUniswapV2.json';
import * as MixinZeroExBridge from '../test/generated-artifacts/MixinZeroExBridge.json'; import * as MixinZeroExBridge from '../test/generated-artifacts/MixinZeroExBridge.json';
import * as NativeOrdersFeature from '../test/generated-artifacts/NativeOrdersFeature.json';
import * as OwnableFeature from '../test/generated-artifacts/OwnableFeature.json'; import * as OwnableFeature from '../test/generated-artifacts/OwnableFeature.json';
import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json'; import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json';
import * as SignatureValidatorFeature from '../test/generated-artifacts/SignatureValidatorFeature.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 TestFillQuoteTransformerBridge from '../test/generated-artifacts/TestFillQuoteTransformerBridge.json';
import * as TestFillQuoteTransformerExchange from '../test/generated-artifacts/TestFillQuoteTransformerExchange.json'; import * as TestFillQuoteTransformerExchange from '../test/generated-artifacts/TestFillQuoteTransformerExchange.json';
import * as TestFillQuoteTransformerHost from '../test/generated-artifacts/TestFillQuoteTransformerHost.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 TestFullMigration from '../test/generated-artifacts/TestFullMigration.json';
import * as TestInitialMigration from '../test/generated-artifacts/TestInitialMigration.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 TestLibSignature from '../test/generated-artifacts/TestLibSignature.json';
import * as TestLibTokenSpender from '../test/generated-artifacts/TestLibTokenSpender.json'; import * as TestLibTokenSpender from '../test/generated-artifacts/TestLibTokenSpender.json';
import * as TestLiquidityProvider from '../test/generated-artifacts/TestLiquidityProvider.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 TestMigrator from '../test/generated-artifacts/TestMigrator.json';
import * as TestMintableERC20Token from '../test/generated-artifacts/TestMintableERC20Token.json'; import * as TestMintableERC20Token from '../test/generated-artifacts/TestMintableERC20Token.json';
import * as TestMintTokenERC20Transformer from '../test/generated-artifacts/TestMintTokenERC20Transformer.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 TestSimpleFunctionRegistryFeatureImpl1 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl1.json';
import * as TestSimpleFunctionRegistryFeatureImpl2 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json'; import * as TestSimpleFunctionRegistryFeatureImpl2 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json';
import * as TestStaking from '../test/generated-artifacts/TestStaking.json'; import * as TestStaking from '../test/generated-artifacts/TestStaking.json';
@ -127,6 +134,7 @@ export const artifacts = {
LibCommonRichErrors: LibCommonRichErrors as ContractArtifact, LibCommonRichErrors: LibCommonRichErrors as ContractArtifact,
LibLiquidityProviderRichErrors: LibLiquidityProviderRichErrors as ContractArtifact, LibLiquidityProviderRichErrors: LibLiquidityProviderRichErrors as ContractArtifact,
LibMetaTransactionsRichErrors: LibMetaTransactionsRichErrors as ContractArtifact, LibMetaTransactionsRichErrors: LibMetaTransactionsRichErrors as ContractArtifact,
LibNativeOrdersRichErrors: LibNativeOrdersRichErrors as ContractArtifact,
LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact, LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact,
LibProxyRichErrors: LibProxyRichErrors as ContractArtifact, LibProxyRichErrors: LibProxyRichErrors as ContractArtifact,
LibSignatureRichErrors: LibSignatureRichErrors as ContractArtifact, LibSignatureRichErrors: LibSignatureRichErrors as ContractArtifact,
@ -147,6 +155,7 @@ export const artifacts = {
IFeature: IFeature as ContractArtifact, IFeature: IFeature as ContractArtifact,
ILiquidityProviderFeature: ILiquidityProviderFeature as ContractArtifact, ILiquidityProviderFeature: ILiquidityProviderFeature as ContractArtifact,
IMetaTransactionsFeature: IMetaTransactionsFeature as ContractArtifact, IMetaTransactionsFeature: IMetaTransactionsFeature as ContractArtifact,
INativeOrdersFeature: INativeOrdersFeature as ContractArtifact,
IOwnableFeature: IOwnableFeature as ContractArtifact, IOwnableFeature: IOwnableFeature as ContractArtifact,
ISignatureValidatorFeature: ISignatureValidatorFeature as ContractArtifact, ISignatureValidatorFeature: ISignatureValidatorFeature as ContractArtifact,
ISimpleFunctionRegistryFeature: ISimpleFunctionRegistryFeature as ContractArtifact, ISimpleFunctionRegistryFeature: ISimpleFunctionRegistryFeature as ContractArtifact,
@ -155,12 +164,14 @@ export const artifacts = {
IUniswapFeature: IUniswapFeature as ContractArtifact, IUniswapFeature: IUniswapFeature as ContractArtifact,
LiquidityProviderFeature: LiquidityProviderFeature as ContractArtifact, LiquidityProviderFeature: LiquidityProviderFeature as ContractArtifact,
MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact, MetaTransactionsFeature: MetaTransactionsFeature as ContractArtifact,
NativeOrdersFeature: NativeOrdersFeature as ContractArtifact,
OwnableFeature: OwnableFeature as ContractArtifact, OwnableFeature: OwnableFeature as ContractArtifact,
SignatureValidatorFeature: SignatureValidatorFeature as ContractArtifact, SignatureValidatorFeature: SignatureValidatorFeature as ContractArtifact,
SimpleFunctionRegistryFeature: SimpleFunctionRegistryFeature as ContractArtifact, SimpleFunctionRegistryFeature: SimpleFunctionRegistryFeature as ContractArtifact,
TokenSpenderFeature: TokenSpenderFeature as ContractArtifact, TokenSpenderFeature: TokenSpenderFeature as ContractArtifact,
TransformERC20Feature: TransformERC20Feature as ContractArtifact, TransformERC20Feature: TransformERC20Feature as ContractArtifact,
UniswapFeature: UniswapFeature as ContractArtifact, UniswapFeature: UniswapFeature as ContractArtifact,
LibNativeOrder: LibNativeOrder as ContractArtifact,
LibSignature: LibSignature as ContractArtifact, LibSignature: LibSignature as ContractArtifact,
LibSignedCallData: LibSignedCallData as ContractArtifact, LibSignedCallData: LibSignedCallData as ContractArtifact,
LibTokenSpender: LibTokenSpender as ContractArtifact, LibTokenSpender: LibTokenSpender as ContractArtifact,
@ -173,6 +184,7 @@ export const artifacts = {
LibBootstrap: LibBootstrap as ContractArtifact, LibBootstrap: LibBootstrap as ContractArtifact,
LibMigrate: LibMigrate as ContractArtifact, LibMigrate: LibMigrate as ContractArtifact,
LibMetaTransactionsStorage: LibMetaTransactionsStorage as ContractArtifact, LibMetaTransactionsStorage: LibMetaTransactionsStorage as ContractArtifact,
LibNativeOrdersStorage: LibNativeOrdersStorage as ContractArtifact,
LibOwnableStorage: LibOwnableStorage as ContractArtifact, LibOwnableStorage: LibOwnableStorage as ContractArtifact,
LibProxyStorage: LibProxyStorage as ContractArtifact, LibProxyStorage: LibProxyStorage as ContractArtifact,
LibReentrancyGuardStorage: LibReentrancyGuardStorage as ContractArtifact, LibReentrancyGuardStorage: LibReentrancyGuardStorage as ContractArtifact,
@ -216,8 +228,10 @@ export const artifacts = {
TestFillQuoteTransformerBridge: TestFillQuoteTransformerBridge as ContractArtifact, TestFillQuoteTransformerBridge: TestFillQuoteTransformerBridge as ContractArtifact,
TestFillQuoteTransformerExchange: TestFillQuoteTransformerExchange as ContractArtifact, TestFillQuoteTransformerExchange: TestFillQuoteTransformerExchange as ContractArtifact,
TestFillQuoteTransformerHost: TestFillQuoteTransformerHost as ContractArtifact, TestFillQuoteTransformerHost: TestFillQuoteTransformerHost as ContractArtifact,
TestFixinProtocolFees: TestFixinProtocolFees as ContractArtifact,
TestFullMigration: TestFullMigration as ContractArtifact, TestFullMigration: TestFullMigration as ContractArtifact,
TestInitialMigration: TestInitialMigration as ContractArtifact, TestInitialMigration: TestInitialMigration as ContractArtifact,
TestLibNativeOrder: TestLibNativeOrder as ContractArtifact,
TestLibSignature: TestLibSignature as ContractArtifact, TestLibSignature: TestLibSignature as ContractArtifact,
TestLibTokenSpender: TestLibTokenSpender as ContractArtifact, TestLibTokenSpender: TestLibTokenSpender as ContractArtifact,
TestLiquidityProvider: TestLiquidityProvider as ContractArtifact, TestLiquidityProvider: TestLiquidityProvider as ContractArtifact,
@ -225,7 +239,7 @@ export const artifacts = {
TestMigrator: TestMigrator as ContractArtifact, TestMigrator: TestMigrator as ContractArtifact,
TestMintTokenERC20Transformer: TestMintTokenERC20Transformer as ContractArtifact, TestMintTokenERC20Transformer: TestMintTokenERC20Transformer as ContractArtifact,
TestMintableERC20Token: TestMintableERC20Token as ContractArtifact, TestMintableERC20Token: TestMintableERC20Token as ContractArtifact,
TestProtocolFees: TestProtocolFees as ContractArtifact, TestNativeOrdersFeature: TestNativeOrdersFeature as ContractArtifact,
TestSimpleFunctionRegistryFeatureImpl1: TestSimpleFunctionRegistryFeatureImpl1 as ContractArtifact, TestSimpleFunctionRegistryFeatureImpl1: TestSimpleFunctionRegistryFeatureImpl1 as ContractArtifact,
TestSimpleFunctionRegistryFeatureImpl2: TestSimpleFunctionRegistryFeatureImpl2 as ContractArtifact, TestSimpleFunctionRegistryFeatureImpl2: TestSimpleFunctionRegistryFeatureImpl2 as ContractArtifact,
TestStaking: TestStaking 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 { import {
AllowanceTargetContract, AllowanceTargetContract,
IMetaTransactionsFeatureContract, IMetaTransactionsFeatureContract,
INativeOrdersFeatureContract,
IOwnableFeatureContract, IOwnableFeatureContract,
ISignatureValidatorFeatureContract, ISignatureValidatorFeatureContract,
ISimpleFunctionRegistryFeatureContract, ISimpleFunctionRegistryFeatureContract,
@ -45,9 +46,9 @@ blockchainTests.resets('Full migration', env => {
artifacts, artifacts,
await migrator.getBootstrapper().callAsync(), 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 await migrator
.initializeZeroEx(owner, zeroEx.address, features, { transformerDeployer }) .migrateZeroEx(owner, zeroEx.address, features, { transformerDeployer })
.awaitTransactionSuccessAsync(); .awaitTransactionSuccessAsync();
registry = new ISimpleFunctionRegistryFeatureContract(zeroEx.address, env.provider, env.txDefaults); registry = new ISimpleFunctionRegistryFeatureContract(zeroEx.address, env.provider, env.txDefaults);
}); });
@ -63,10 +64,10 @@ blockchainTests.resets('Full migration', env => {
expect(dieRecipient).to.eq(owner); expect(dieRecipient).to.eq(owner);
}); });
it('Non-deployer cannot call initializeZeroEx()', async () => { it('Non-deployer cannot call migrateZeroEx()', async () => {
const notDeployer = randomAddress(); const notDeployer = randomAddress();
const tx = migrator const tx = migrator
.initializeZeroEx(owner, zeroEx.address, features, { transformerDeployer }) .migrateZeroEx(owner, zeroEx.address, features, { transformerDeployer })
.callAsync({ from: notDeployer }); .callAsync({ from: notDeployer });
return expect(tx).to.revertWith('FullMigration/INVALID_SENDER'); return expect(tx).to.revertWith('FullMigration/INVALID_SENDER');
}); });
@ -103,6 +104,31 @@ blockchainTests.resets('Full migration', env => {
'getMetaTransactionHash', '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[] { 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)); return hexUtils.random(parseInt(/\d+$/.exec(item.type)![0], 10));
} }
if (/^uint\d+$/.test(item.type)) { 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)); return new BigNumber(hexUtils.random(parseInt(/\d+$/.exec(item.type)![0], 10) / 8));
} }
if (/^int\d+$/.test(item.type)) { 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 { blockchainTests, expect } from '@0x/contracts-test-utils';
import { AuthorizableRevertErrors, BigNumber, hexUtils, ZeroExRevertErrors } from '@0x/utils'; import { AuthorizableRevertErrors, BigNumber, hexUtils } from '@0x/utils';
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import { artifacts } from './artifacts'; import { artifacts } from './artifacts';
import { FeeCollectorContract, TestProtocolFeesContract, TestStakingContract, TestWethContract } from './wrappers'; import { FeeCollectorContract, TestFixinProtocolFeesContract, TestStakingContract, TestWethContract } from './wrappers';
blockchainTests.resets('ProtocolFees', env => { blockchainTests.resets('ProtocolFees', env => {
let payer: string; const FEE_MULTIPLIER = 70e3;
let taker: string;
let unauthorized: string; let unauthorized: string;
let protocolFees: TestProtocolFeesContract; let protocolFees: TestFixinProtocolFeesContract;
let staking: TestStakingContract; let staking: TestStakingContract;
let weth: TestWethContract; let weth: TestWethContract;
let singleFeeAmount: BigNumber;
before(async () => { before(async () => {
[payer, unauthorized] = await env.getAccountAddressesAsync(); [taker, unauthorized] = await env.getAccountAddressesAsync();
protocolFees = await TestProtocolFeesContract.deployFrom0xArtifactAsync(
artifacts.TestProtocolFees,
env.provider,
env.txDefaults,
artifacts,
);
weth = await TestWethContract.deployFrom0xArtifactAsync( weth = await TestWethContract.deployFrom0xArtifactAsync(
artifacts.TestWeth, artifacts.TestWeth,
env.provider, env.provider,
@ -33,30 +28,26 @@ blockchainTests.resets('ProtocolFees', env => {
artifacts, artifacts,
weth.address, weth.address,
); );
await weth.mint(payer, constants.ONE_ETHER).awaitTransactionSuccessAsync(); protocolFees = await TestFixinProtocolFeesContract.deployFrom0xArtifactAsync(
await weth.approve(protocolFees.address, constants.ONE_ETHER).awaitTransactionSuccessAsync({ from: payer }); 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', () => { describe('FeeCollector', () => {
it('should disallow unauthorized initialization', async () => { it('should disallow unauthorized initialization', async () => {
const pool = hexUtils.random(); const pool = hexUtils.random();
await collectAsync(pool, constants.ONE_ETHER, constants.ZERO_AMOUNT); await protocolFees.collectProtocolFee(pool).awaitTransactionSuccessAsync({ value: singleFeeAmount });
await transferFeesAsync(pool); await protocolFees.transferFeesForPool(pool).awaitTransactionSuccessAsync();
const feeCollector = new FeeCollectorContract( const feeCollector = new FeeCollectorContract(
await protocolFees.getFeeCollector(pool).callAsync(), await protocolFees.getFeeCollector(pool).callAsync(),
@ -74,91 +65,70 @@ blockchainTests.resets('ProtocolFees', env => {
describe('_collectProtocolFee()', () => { describe('_collectProtocolFee()', () => {
const pool1 = hexUtils.random(); const pool1 = hexUtils.random();
const pool2 = hexUtils.random(); const pool2 = hexUtils.random();
let feeCollector1Address: string;
let feeCollector2Address: string;
it('should revert if WETH transfer fails', async () => { before(async () => {
const tooMuch = constants.ONE_ETHER.plus(1); feeCollector1Address = await protocolFees.getFeeCollector(pool1).callAsync();
const tx = collectAsync(pool1, constants.ONE_ETHER.plus(1), constants.ZERO_AMOUNT); feeCollector2Address = await protocolFees.getFeeCollector(pool2).callAsync();
return expect(tx).to.revertWith(
new ZeroExRevertErrors.Spender.SpenderERC20TransferFromFailedError(
weth.address,
payer,
undefined,
tooMuch,
undefined,
),
);
}); });
it('should revert if insufficient ETH transferred', async () => { it('should revert if insufficient ETH transferred', async () => {
const tooLittle = constants.ONE_ETHER.minus(1); const tooLittle = singleFeeAmount.minus(1);
const tx = collectAsync(pool1, constants.ONE_ETHER, tooLittle); const tx = protocolFees.collectProtocolFee(pool1).awaitTransactionSuccessAsync({ value: tooLittle });
return expect(tx).to.revertWith('FixinProtocolFees/ETHER_TRANSFER_FALIED'); 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 () => { it('should accept ETH fee', async () => {
const beforeWETH = await weth.balanceOf(payer).callAsync(); const beforeETH = await env.web3Wrapper.getBalanceInWeiAsync(taker);
const beforeETH = await env.web3Wrapper.getBalanceInWeiAsync(payer); await protocolFees.collectProtocolFee(pool1).awaitTransactionSuccessAsync({ value: singleFeeAmount });
await collectAsync(pool1, constants.ONE_ETHER, constants.ONE_ETHER); const afterETH = await env.web3Wrapper.getBalanceInWeiAsync(taker);
const afterWETH = await weth.balanceOf(payer).callAsync();
const afterETH = await env.web3Wrapper.getBalanceInWeiAsync(payer);
// We check for greater than 1 ether spent to allow for spending on gas. // We check for greater than fee spent to allow for spending on gas.
await expect(beforeETH.minus(afterETH)).to.bignumber.gt(constants.ONE_ETHER); await expect(beforeETH.minus(afterETH)).to.bignumber.gt(singleFeeAmount);
return expect(beforeWETH).to.bignumber.eq(afterWETH);
});
it('should transfer both ETH and WETH', async () => { await expect(await env.web3Wrapper.getBalanceInWeiAsync(feeCollector1Address)).to.bignumber.eq(
await collectAsync(pool1, constants.ONE_ETHER, constants.ZERO_AMOUNT); singleFeeAmount,
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));
}); });
it('should accept ETH after first transfer', async () => { it('should accept ETH after first transfer', async () => {
await collectAsync(pool1, constants.ONE_ETHER, constants.ONE_ETHER); await protocolFees.collectProtocolFee(pool1).awaitTransactionSuccessAsync({ value: singleFeeAmount });
await transferFeesAsync(pool1); await protocolFees.transferFeesForPool(pool1).awaitTransactionSuccessAsync();
await collectAsync(pool1, constants.ONE_ETHER, constants.ONE_ETHER); await protocolFees.collectProtocolFee(pool1).awaitTransactionSuccessAsync({ value: singleFeeAmount });
await transferFeesAsync(pool1); await protocolFees.transferFeesForPool(pool1).awaitTransactionSuccessAsync();
const balanceWETH = await weth.balanceOf(staking.address).callAsync(); const balanceWETH = await weth.balanceOf(staking.address).callAsync();
// We leave 1 wei behind of both ETH and WETH // We leave 1 wei of WETH behind.
return expect(balanceWETH).to.bignumber.eq(constants.ONE_ETHER.times(2).minus(2)); 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 () => { it('should attribute fees correctly', async () => {
const pool1Amount = new BigNumber(12345); await protocolFees.collectProtocolFee(pool1).awaitTransactionSuccessAsync({ value: singleFeeAmount });
const pool2Amount = new BigNumber(45678); await protocolFees.transferFeesForPool(pool1).awaitTransactionSuccessAsync();
await protocolFees.collectProtocolFee(pool2).awaitTransactionSuccessAsync({ value: singleFeeAmount });
await collectAsync(pool1, pool1Amount, pool1Amount); // ETH await protocolFees.transferFeesForPool(pool2).awaitTransactionSuccessAsync();
await transferFeesAsync(pool1);
await collectAsync(pool2, pool2Amount, constants.ZERO_AMOUNT); // WETH
await transferFeesAsync(pool2);
const pool1Balance = await staking.balanceForPool(pool1).callAsync(); const pool1Balance = await staking.balanceForPool(pool1).callAsync();
const pool2Balance = await staking.balanceForPool(pool2).callAsync(); const pool2Balance = await staking.balanceForPool(pool2).callAsync();
const balanceWETH = await weth.balanceOf(staking.address).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. // We leave 1 wei of WETH behind.
await expect(pool1Balance).to.bignumber.equal(pool1Amount.minus(2)); await expect(pool1Balance).to.bignumber.equal(singleFeeAmount.minus(1));
await expect(pool2Balance).to.bignumber.equal(singleFeeAmount.minus(1));
// Here we paid in WETH, so there's just 1 wei of WETH held back. await expect(await weth.balanceOf(feeCollector1Address).callAsync()).to.bignumber.equal(1);
return expect(pool2Balance).to.bignumber.equal(pool2Amount.minus(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, deployFullFeaturesAsync,
initialMigrateAsync, initialMigrateAsync,
fullMigrateAsync, fullMigrateAsync,
FullMigrationOpts, FullMigrationConfig,
FullFeaturesDeployConfig,
FullFeatures, FullFeatures,
} from '../../src/migration'; } 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_feature';
export * from '../test/generated-wrappers/i_liquidity_provider_sandbox'; export * from '../test/generated-wrappers/i_liquidity_provider_sandbox';
export * from '../test/generated-wrappers/i_meta_transactions_feature'; export * from '../test/generated-wrappers/i_meta_transactions_feature';
export * from '../test/generated-wrappers/i_native_orders_feature';
export * from '../test/generated-wrappers/i_ownable_feature'; export * from '../test/generated-wrappers/i_ownable_feature';
export * from '../test/generated-wrappers/i_signature_validator_feature'; export * from '../test/generated-wrappers/i_signature_validator_feature';
export * from '../test/generated-wrappers/i_simple_function_registry_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_rich_errors';
export * from '../test/generated-wrappers/lib_meta_transactions_storage'; export * from '../test/generated-wrappers/lib_meta_transactions_storage';
export * from '../test/generated-wrappers/lib_migrate'; 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_order_hash';
export * from '../test/generated-wrappers/lib_ownable_rich_errors'; export * from '../test/generated-wrappers/lib_ownable_rich_errors';
export * from '../test/generated-wrappers/lib_ownable_storage'; 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';
export * from '../test/generated-wrappers/mixin_uniswap_v2'; export * from '../test/generated-wrappers/mixin_uniswap_v2';
export * from '../test/generated-wrappers/mixin_zero_ex_bridge'; export * from '../test/generated-wrappers/mixin_zero_ex_bridge';
export * from '../test/generated-wrappers/native_orders_feature';
export * from '../test/generated-wrappers/ownable_feature'; export * from '../test/generated-wrappers/ownable_feature';
export * from '../test/generated-wrappers/pay_taker_transformer'; export * from '../test/generated-wrappers/pay_taker_transformer';
export * from '../test/generated-wrappers/signature_validator_feature'; 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_bridge';
export * from '../test/generated-wrappers/test_fill_quote_transformer_exchange'; 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_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_full_migration';
export * from '../test/generated-wrappers/test_initial_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_signature';
export * from '../test/generated-wrappers/test_lib_token_spender'; export * from '../test/generated-wrappers/test_lib_token_spender';
export * from '../test/generated-wrappers/test_liquidity_provider'; 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_migrator';
export * from '../test/generated-wrappers/test_mint_token_erc20_transformer'; 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_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_impl1';
export * from '../test/generated-wrappers/test_simple_function_registry_feature_impl2'; export * from '../test/generated-wrappers/test_simple_function_registry_feature_impl2';
export * from '../test/generated-wrappers/test_staking'; export * from '../test/generated-wrappers/test_staking';

View File

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

View File

@ -2,11 +2,7 @@
Orders 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. 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::
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.
.. note:: .. note::
0x Orders currently support the exchange of ERC20 Tokens. Other asset classes, like ERC721, 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: The ``LimitOrder`` struct has the following fields:
+------------------+-------------+-----------------------------------------------------------------------------+ +--------------------------+-------------+-----------------------------------------------------------------------------+
| Field | Type | Description | | Field | Type | Description |
+==================+=============+=============================================================================+ +==========================+=============+=============================================================================+
| ``makerToken`` | ``address`` | The ERC20 token the maker is selling and the maker is selling to the taker. | | ``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. | | ``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. | | ``makerAmount`` | ``uint128`` | The amount of makerToken being sold by the maker. |
+------------------+-------------+-----------------------------------------------------------------------------+ +--------------------------+-------------+-----------------------------------------------------------------------------+
| ``takerAmount`` | ``uint128`` | The amount of takerToken being sold by the taker. | | ``takerAmount`` | ``uint128`` | The amount of takerToken being sold by the taker. |
+------------------+-------------+-----------------------------------------------------------------------------+ +--------------------------+-------------+-----------------------------------------------------------------------------+
| ``feeRecipient`` | ``address`` | Recipient of maker token or taker token fees (if non-zero). | | ``takerTokenFeeAmount`` | ``uint128`` | Amount of takerToken paid by the taker to the feeRecipient. |
+------------------+-------------+-----------------------------------------------------------------------------+ +--------------------------+-------------+-----------------------------------------------------------------------------+
| ``feeAmount`` | ``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. | | ``taker`` | ``address`` | Allowed taker address. Set to zero to allow any taker. |
+------------------+-------------+-----------------------------------------------------------------------------+ +--------------------------+-------------+-----------------------------------------------------------------------------+
| ``sender`` | ``address`` | Allowed address to directly call ``fillLimitOrder()`` (``msg.sender``). | | ``sender`` | ``address`` | Allowed address to directly call ``fillLimitOrder()`` (``msg.sender``). |
| | | This is distinct from ``taker`` in meta-transactions. | | | | This is distinct from ``taker`` in meta-transactions. |
| | | Set to zero to allow any caller. | | | | Set to zero to allow any caller. |
+------------------+-------------+-----------------------------------------------------------------------------+ +--------------------------+-------------+-----------------------------------------------------------------------------+
| ``pool`` | ``uint256`` | The staking pool to attribute the 0x protocol fee from this order. | | ``feeRecipient`` | ``address`` | Recipient of maker token or taker token fees (if non-zero). |
| | | Set to zero to attribute to the default pool, not owned by anyone. | +--------------------------+-------------+-----------------------------------------------------------------------------+
+------------------+-------------+-----------------------------------------------------------------------------+ | ``pool`` | ``bytes32`` | The staking pool to attribute the 0x protocol fee from this order. |
| ``expiry`` | ``uint64`` | The Unix timestamp in seconds when this order expires. | | | | Set to zero to attribute to the default pool, not owned by anyone. |
+------------------+-------------+-----------------------------------------------------------------------------+ +--------------------------+-------------+-----------------------------------------------------------------------------+
| ``salt`` | ``uint256`` | Arbitrary number to enforce uniqueness of the order's hash. | | ``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 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. 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:
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:
.. code-block:: solidity .. code-block:: solidity
bytes32 signatureHash = keccak256(abi.encodePacked( bytes32 orderHash = keccak256(abi.encodePacked(
'\x19\x01', '\x19\x01',
// The domain separator. // The domain separator.
keccak256(abi.encode( keccak256(abi.encode(
@ -90,11 +83,12 @@ The signature hash is the hash of the order struct, following the `EIP712 spec <
'address takerToken,', 'address takerToken,',
'uint128 makerAmount,', 'uint128 makerAmount,',
'uint128 takerAmount,', 'uint128 takerAmount,',
'address feeRecipient,', 'uint128 takerTokenFeeAmount,',
'uint128 feeAmount,',
'address taker,', 'address taker,',
'address maker,',
'address sender,', 'address sender,',
'uint256 pool,', 'address feeRecipient,',
'bytes32 pool,',
'uint64 expiry,', 'uint64 expiry,',
'uint256 salt)' 'uint256 salt)'
)), )),
@ -103,34 +97,22 @@ The signature hash is the hash of the order struct, following the `EIP712 spec <
order.takerToken, order.takerToken,
order.makerAmount, order.makerAmount,
order.takerAmount, order.takerAmount,
order.feeRecipient, order.takerTokenFeeAmount,
order.feeAmount, order.maker,
order.taker, order.taker,
order.sender, order.sender,
order.feeRecipient,
order.pool, order.pool,
order.expiry, order.expiry,
order.salt order.salt
)) ))
)); ));
Computing the fill hash Alternatively, the Exchange Proxy contract can be used to retrieve the hash given an order.
^^^^^^^^^^^^^^^^^^^^^^^^^
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.
.. code-block:: solidity .. code-block:: solidity
// For EthSign signatures, the signatureHash would need to be replaced with bytes32 orderHash = IZeroEx(0xDef1C0ded9bec7F1a1670819833240f027b25EfF).getLimitOrderHash(order);
// 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);
Signing limit orders 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 ``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``. * 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 .. code-block:: javascript
:linenos:
const orderUtils = require('@0x/order-utils'); const utils = require('@0x/protocol-utils');
const order = new orderUtils.LimitOrder({ const order = new utils.LimitOrder({
makerToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI makerToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI
takerToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH takerToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
... // Other fields ... // Other fields
}); });
// Generate an EIP712 signature // Generate an EIP712 signature
const signature = await order.signTypedDataAsync(web3.currentProvider, makerAddress); const signature = await order.eip712SignTypedDataWithProviderAsync(
web3.currentProvider,
makerAddress,
);
// Generate an EthSign signature // Generate an EthSign signature
const signature = await order.sign(web3.currentProvider, makerAddress); const signature = await order.ethSignHashWithProviderAsync(
web3.currentProvider,
makerAddress,
);
Filling limit orders Filling limit orders
-------------------- --------------------
@ -185,12 +172,12 @@ Limit orders can be filled with the ``fillLimitOrder()`` or ``fillOrKillLimitOrd
// The signature // The signature
Signature calldata signature, Signature calldata signature,
// How much taker token to fill the order with // How much taker token to fill the order with
uint256 takerTokenFillAmount uint128 takerTokenFillAmount
) )
external external
payable payable
// How much maker token from the order the taker received. // 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``: ``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 // The signature
Signature calldata signature, Signature calldata signature,
// How much taker token to fill the order with // How much taker token to fill the order with
uint256 takerTokenFillAmount uint128 takerTokenFillAmount
) )
external external
payable payable
// How much maker token from the order the taker received. // How much maker token from the order the taker received.
returns (uint256 makerTokenFillAmount); returns (uint128 makerTokenFillAmount);
Cancelling a limit order 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: ``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; 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 .. code-block:: solidity
function cancelLimitOrdersUpTo( function cancelLimitPairLimitOrders(
address makerToken,
address takerToken,
uint256 salt; uint256 salt;
) )
external; 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 Getting the status of a limit order
----------------------------------- -----------------------------------
@ -250,31 +250,30 @@ The Exchange Proxy exposes a function ``getLimitOrderInfo()`` to query informati
.. code-block:: solidity .. code-block:: solidity
enum OrderState { enum OrderStatus {
INVALID, INVALID,
CANCELLED,
FILLABLE, FILLABLE,
FILLED FILLED,
CANCELLED,
EXPIRED
} }
struct OrderInfo { struct OrderInfo {
// The fill hash. // The order hash.
bytes32 fillHash; bytes32 orderHash;
// Current state of the order. // Current state of the order.
OrderState state; OrderStatus status;
// How much taker token has been filled in the order. // How much taker token has been filled in the order.
uint256 takerTokenFilledAmount; uint128 takerTokenFilledAmount;
} }
function getLimitOrderInfo( function getLimitOrderInfo(
// The order // The order
LimitOrder calldata order, LimitOrder calldata order
// The signature
Signature calldata signature
) )
external external
view view
returns (OrderInfo memory status); returns (OrderInfo memory orderInfo);
RFQ Orders RFQ Orders
========== ==========
@ -303,9 +302,11 @@ The ``RFQOrder`` struct has the following fields:
+-----------------+-------------+-----------------------------------------------------------------------------+ +-----------------+-------------+-----------------------------------------------------------------------------+
| ``takerAmount`` | ``uint128`` | The amount of takerToken being sold by the taker. | | ``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. | | ``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. | | | | Set to zero to attribute to the default pool, not owned by anyone. |
+-----------------+-------------+-----------------------------------------------------------------------------+ +-----------------+-------------+-----------------------------------------------------------------------------+
| ``expiry`` | ``uint64`` | The Unix timestamp in seconds when this order expires. | | ``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 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. 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:
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:
.. code-block:: solidity .. code-block:: solidity
bytes32 signatureHash = keccak256(abi.encodePacked( bytes32 orderHash = keccak256(abi.encodePacked(
'\x19\x01', '\x19\x01',
// The domain separator. // The domain separator.
keccak256(abi.encode( keccak256(abi.encode(
@ -352,8 +348,9 @@ The signature hash is the hash of the order struct, following the `EIP712 spec <
'address takerToken,', 'address takerToken,',
'uint128 makerAmount,', 'uint128 makerAmount,',
'uint128 takerAmount,', 'uint128 takerAmount,',
'address maker,'
'address txOrigin,' 'address txOrigin,'
'uint256 pool,', 'bytes32 pool,',
'uint64 expiry,', 'uint64 expiry,',
'uint256 salt)' 'uint256 salt)'
)), )),
@ -362,6 +359,7 @@ The signature hash is the hash of the order struct, following the `EIP712 spec <
order.takerToken, order.takerToken,
order.makerAmount, order.makerAmount,
order.takerAmount, order.takerAmount,
order.maker,
order.txOrigin, order.txOrigin,
order.pool, order.pool,
order.expiry, order.expiry,
@ -369,32 +367,11 @@ The signature hash is the hash of the order struct, following the `EIP712 spec <
)) ))
)); ));
Computing the fill hash Alternatively, the Exchange Proxy contract can be used to retrieve the hash given an order.
^^^^^^^^^^^^^^^^^^^^^^^
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.
.. code-block:: solidity .. code-block:: solidity
// For EthSign signatures, the signatureHash would need to be replaced with bytes32 orderHash = IZeroEx(0xDef1C0ded9bec7F1a1670819833240f027b25EfF).getLimitOrderHash(order);
// 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);
Signing RFQ orders Signing RFQ orders
------------------ ------------------
@ -411,18 +388,21 @@ The protocol accepts signatures defined by the following struct:
bytes32 s; // Signature data. 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 .. code-block:: javascript
:linenos:
const orderUtils = require('@0x/order-utils'); const utils = require('@0x/protocol-utils');
const order = new orderUtils.RfqOrder({ const order = new utils.RfqOrder({
makerToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI makerToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI
takerToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH takerToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
... // Other fields ... // 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 Filling RFQ Orders
------------------ ------------------
@ -439,12 +419,12 @@ RFQ orders can be filled with the ``fillRfqOrder()`` or ``fillOrKillRfqOrder()``
// The signature // The signature
Signature calldata signature, Signature calldata signature,
// How much taker token to fill the order with // How much taker token to fill the order with
uint256 takerTokenFillAmount uint128 takerTokenFillAmount
) )
external external
payable payable
// How much maker token from the order the taker received. // 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``: ``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 // The signature
Signature calldata signature, Signature calldata signature,
// How much taker token to fill the order with // How much taker token to fill the order with
uint256 takerTokenFillAmount uint128 takerTokenFillAmount
) )
external external
payable payable
// How much maker token from the order the taker received. // How much maker token from the order the taker received.
returns (uint256 makerTokenFillAmount); returns (uint128 makerTokenFillAmount);
Cancelling an RFQ order 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: ``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; 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 Getting the status of an RFQ order
---------------------------------- ----------------------------------
@ -495,28 +497,27 @@ The Exchange Proxy exposes a function ``getRfqOrderInfo()`` to query information
.. code-block:: solidity .. code-block:: solidity
enum OrderState { enum OrderStatus {
INVALID, INVALID,
CANCELLED,
FILLABLE, FILLABLE,
FILLED FILLED,
CANCELLED,
EXPIRED
} }
struct OrderInfo { struct OrderInfo {
// The fill hash. // The order hash.
bytes32 fillHash; bytes32 orderHash;
// Current state of the order. // Current state of the order.
OrderState state; OrderStatus status;
// How much taker token has been filled in the order. // How much taker token has been filled in the order.
uint256 takerTokenFilledAmount; uint128 takerTokenFilledAmount;
} }
function getRfqOrderInfo( function getRfqOrderInfo(
// The order // The order
RfqOrder calldata order, RfqOrder calldata order
// The signature
Signature calldata signature
) )
external external
view 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", "version": "5.3.0",
"changes": [ "changes": [

View File

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