aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contracts')
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol14
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol372
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol285
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol85
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol5
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IWrapperFunctions.sol4
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol15
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol49
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol1
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol119
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol106
-rw-r--r--packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSettlement.sol14
-rw-r--r--packages/contracts/src/contracts/current/test/TestSignatureValidator/TestSignatureValidator.sol2
13 files changed, 957 insertions, 114 deletions
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol b/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol
index 85e2bc47e..438d50ecf 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol
@@ -25,24 +25,28 @@ import "./MixinSettlement.sol";
import "./MixinWrapperFunctions.sol";
import "./MixinAssetProxyDispatcher.sol";
import "./MixinTransactions.sol";
+import "./MixinMatchOrders.sol";
contract Exchange is
- MixinWrapperFunctions,
MixinExchangeCore,
+ MixinMatchOrders,
MixinSettlement,
MixinSignatureValidator,
MixinTransactions,
- MixinAssetProxyDispatcher
+ MixinAssetProxyDispatcher,
+ MixinWrapperFunctions
{
+
string constant public VERSION = "2.0.1-alpha";
constructor (bytes memory _zrxProxyData)
public
MixinExchangeCore()
- MixinSignatureValidator()
+ MixinMatchOrders()
MixinSettlement(_zrxProxyData)
- MixinWrapperFunctions()
- MixinAssetProxyDispatcher()
+ MixinSignatureValidator()
MixinTransactions()
+ MixinAssetProxyDispatcher()
+ MixinWrapperFunctions()
{}
}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
index 82c7a2ddc..58b5fa6b1 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol
@@ -22,6 +22,7 @@ pragma experimental ABIEncoderV2;
import "./libs/LibFillResults.sol";
import "./libs/LibOrder.sol";
import "./libs/LibMath.sol";
+import "./libs/LibStatus.sol";
import "./libs/LibExchangeErrors.sol";
import "./mixins/MExchangeCore.sol";
import "./mixins/MSettlement.sol";
@@ -29,9 +30,11 @@ import "./mixins/MSignatureValidator.sol";
import "./mixins/MTransactions.sol";
contract MixinExchangeCore is
+ SafeMath,
+ LibMath,
+ LibStatus,
LibOrder,
LibFillResults,
- LibMath,
LibExchangeErrors,
MExchangeCore,
MSettlement,
@@ -48,64 +51,108 @@ contract MixinExchangeCore is
// Orders with a salt less than their maker's epoch are considered cancelled
mapping (address => uint256) public makerEpoch;
- /// @dev Cancels all orders reated by sender with a salt less than or equal to the specified salt value.
- /// @param salt Orders created with a salt less or equal to this value will be cancelled.
- function cancelOrdersUpTo(uint256 salt)
- external
- {
- uint256 newMakerEpoch = salt + 1; // makerEpoch is initialized to 0, so to cancelUpTo we need salt + 1
- require(
- newMakerEpoch > makerEpoch[msg.sender], // epoch must be monotonically increasing
- INVALID_NEW_MAKER_EPOCH
- );
- makerEpoch[msg.sender] = newMakerEpoch;
- emit CancelUpTo(msg.sender, newMakerEpoch);
- }
+ ////// Core exchange functions //////
- /// @dev Fills the input order.
- /// @param order Order struct containing order specifications.
- /// @param takerAssetFillAmount Desired amount of takerAsset to sell.
- /// @param signature Proof that order has been created by maker.
- /// @return Amounts filled and fees paid by maker and taker.
- function fillOrder(
- Order memory order,
- uint256 takerAssetFillAmount,
- bytes memory signature)
+ /// @dev Gets information about an order.
+ /// @param order Order to gather information on.
+ /// @return status Status of order. Statuses are defined in the LibStatus.Status struct.
+ /// @return orderHash Keccak-256 EIP712 hash of the order.
+ /// @return takerAssetFilledAmount Amount of order that has been filled.
+ function getOrderInfo(Order memory order)
public
- returns (FillResults memory fillResults)
+ view
+ returns (
+ uint8 orderStatus,
+ bytes32 orderHash,
+ uint256 takerAssetFilledAmount)
{
- // Compute the order hash
- bytes32 orderHash = getOrderHash(order);
+ // Compute the order hash and fetch filled amount
+ orderHash = getOrderHash(order);
+ takerAssetFilledAmount = filled[orderHash];
- // Check if order has been cancelled by salt value
- if (order.salt < makerEpoch[order.makerAddress]) {
- emit ExchangeError(uint8(Errors.ORDER_CANCELLED), orderHash);
- return fillResults;
+ // If order.takerAssetAmount is zero, then the order will always
+ // be considered filled because 0 == takerAssetAmount == takerAssetFilledAmount
+ // Instead of distinguishing between unfilled and filled zero taker
+ // amount orders, we choose not to support them.
+ if (order.takerAssetAmount == 0) {
+ orderStatus = uint8(Status.ORDER_INVALID_TAKER_ASSET_AMOUNT);
+ return (orderStatus, orderHash, takerAssetFilledAmount);
+ }
+
+ // If order.makerAssetAmount is zero, we also reject the order.
+ // While the Exchange contract handles them correctly, they create
+ // edge cases in the supporting infrastructure because they have
+ // an 'infinite' price when computed by a simple division.
+ if (order.makerAssetAmount == 0) {
+ orderStatus = uint8(Status.ORDER_INVALID_MAKER_ASSET_AMOUNT);
+ return (orderStatus, orderHash, takerAssetFilledAmount);
+ }
+
+ // Validate order expiration
+ if (block.timestamp >= order.expirationTimeSeconds) {
+ orderStatus = uint8(Status.ORDER_EXPIRED);
+ return (orderStatus, orderHash, takerAssetFilledAmount);
}
- // Check if order has been cancelled by orderHash
+ // Validate order availability
+ if (takerAssetFilledAmount >= order.takerAssetAmount) {
+ orderStatus = uint8(Status.ORDER_FULLY_FILLED);
+ return (orderStatus, orderHash, takerAssetFilledAmount);
+ }
+
+ // Check if order has been cancelled
if (cancelled[orderHash]) {
- emit ExchangeError(uint8(Errors.ORDER_CANCELLED), orderHash);
- return fillResults;
+ orderStatus = uint8(Status.ORDER_CANCELLED);
+ return (orderStatus, orderHash, takerAssetFilledAmount);
}
- // Validate order and maker only if first time seen
- // TODO: Read filled and cancelled only once
- if (filled[orderHash] == 0) {
- require(
- order.makerAssetAmount > 0,
- GT_ZERO_AMOUNT_REQUIRED
- );
- require(
- order.takerAssetAmount > 0,
- GT_ZERO_AMOUNT_REQUIRED
- );
+ // Validate order is not cancelled
+ if (makerEpoch[order.makerAddress] > order.salt) {
+ orderStatus = uint8(Status.ORDER_CANCELLED);
+ return (orderStatus, orderHash, takerAssetFilledAmount);
+ }
+
+ // Order is Fillable
+ orderStatus = uint8(Status.ORDER_FILLABLE);
+ return (orderStatus, orderHash, takerAssetFilledAmount);
+ }
+
+ /// @dev Validates context for fillOrder. Succeeds or throws.
+ /// @param order to be filled.
+ /// @param orderStatus Status of order to be filled.
+ /// @param orderHash Hash of order to be filled.
+ /// @param takerAssetFilledAmount Amount of order already filled.
+ /// @param signature Proof that the orders was created by its maker.
+ /// @param takerAddress Address of order taker.
+ /// @param takerAssetFillAmount Desired amount of order to fill by taker.
+ function validateFillOrderContextOrRevert(
+ Order memory order,
+ uint8 orderStatus,
+ bytes32 orderHash,
+ uint256 takerAssetFilledAmount,
+ bytes memory signature,
+ address takerAddress,
+ uint256 takerAssetFillAmount)
+ internal
+ {
+ // Ensure order is valid
+ require(
+ orderStatus != uint8(Status.ORDER_INVALID_MAKER_ASSET_AMOUNT),
+ INVALID_ORDER_MAKER_ASSET_AMOUNT
+ );
+ require(
+ orderStatus != uint8(Status.ORDER_INVALID_TAKER_ASSET_AMOUNT),
+ INVALID_ORDER_TAKER_ASSET_AMOUNT
+ );
+
+ // Validate Maker signature (check only if first time seen)
+ if (takerAssetFilledAmount == 0) {
require(
isValidSignature(orderHash, order.makerAddress, signature),
SIGNATURE_VALIDATION_FAILED
);
}
-
+
// Validate sender is allowed to fill this order
if (order.senderAddress != address(0)) {
require(
@@ -115,7 +162,6 @@ contract MixinExchangeCore is
}
// Validate taker is allowed to fill this order
- address takerAddress = getCurrentContextAddress();
if (order.takerAddress != address(0)) {
require(
order.takerAddress == takerAddress,
@@ -126,88 +172,194 @@ contract MixinExchangeCore is
takerAssetFillAmount > 0,
GT_ZERO_AMOUNT_REQUIRED
);
+ }
- // Validate order expiration
- if (block.timestamp >= order.expirationTimeSeconds) {
- emit ExchangeError(uint8(Errors.ORDER_EXPIRED), orderHash);
- return fillResults;
+ /// @dev Calculates amounts filled and fees paid by maker and taker.
+ /// @param order to be filled.
+ /// @param orderStatus Status of order to be filled.
+ /// @param takerAssetFilledAmount Amount of order already filled.
+ /// @param takerAssetFillAmount Desired amount of order to fill by taker.
+ /// @return status Return status of calculating fill amounts. Returns Status.SUCCESS on success.
+ /// @return fillResults Amounts filled and fees paid by maker and taker.
+ function calculateFillResults(
+ Order memory order,
+ uint8 orderStatus,
+ uint256 takerAssetFilledAmount,
+ uint256 takerAssetFillAmount)
+ public
+ pure
+ returns (
+ uint8 status,
+ FillResults memory fillResults)
+ {
+ // Fill Amount must be greater than 0
+ if (takerAssetFillAmount <= 0) {
+ status = uint8(Status.TAKER_ASSET_FILL_AMOUNT_TOO_LOW);
+ return;
}
- // Validate order availability
- uint256 remainingTakerAssetFillAmount = safeSub(order.takerAssetAmount, filled[orderHash]);
- if (remainingTakerAssetFillAmount == 0) {
- emit ExchangeError(uint8(Errors.ORDER_FULLY_FILLED), orderHash);
- return fillResults;
+ // Ensure the order is fillable
+ if (orderStatus != uint8(Status.ORDER_FILLABLE)) {
+ status = uint8(orderStatus);
+ return;
}
+ // Compute takerAssetFilledAmount
+ uint256 remainingtakerAssetAmount = safeSub(order.takerAssetAmount, takerAssetFilledAmount);
+ fillResults.takerAssetFilledAmount = min256(takerAssetFillAmount, remainingtakerAssetAmount);
+
// Validate fill order rounding
- fillResults.takerAssetFilledAmount = min256(takerAssetFillAmount, remainingTakerAssetFillAmount);
- if (isRoundingError(fillResults.takerAssetFilledAmount, order.takerAssetAmount, order.makerAssetAmount)) {
- emit ExchangeError(uint8(Errors.ROUNDING_ERROR_TOO_LARGE), orderHash);
+ if (isRoundingError(
+ fillResults.takerAssetFilledAmount,
+ order.takerAssetAmount,
+ order.makerAssetAmount))
+ {
+ status = uint8(Status.ROUNDING_ERROR_TOO_LARGE);
fillResults.takerAssetFilledAmount = 0;
- return fillResults;
+ return;
}
+ // Compute proportional transfer amounts
+ // TODO: All three are multiplied by the same fraction. This can
+ // potentially be optimized.
+ fillResults.makerAssetFilledAmount = getPartialAmount(
+ fillResults.takerAssetFilledAmount,
+ order.takerAssetAmount,
+ order.makerAssetAmount);
+ fillResults.makerFeePaid = getPartialAmount(
+ fillResults.takerAssetFilledAmount,
+ order.takerAssetAmount,
+ order.makerFee);
+ fillResults.takerFeePaid = getPartialAmount(
+ fillResults.takerAssetFilledAmount,
+ order.takerAssetAmount,
+ order.takerFee);
+
+ status = uint8(Status.SUCCESS);
+ return;
+ }
+
+ /// @dev Updates state with results of a fill order.
+ /// @param order that was filled.
+ /// @param takerAddress Address of taker who filled the order.
+ /// @return fillResults Amounts filled and fees paid by maker and taker.
+ function updateFilledState(
+ Order memory order,
+ address takerAddress,
+ bytes32 orderHash,
+ FillResults memory fillResults)
+ internal
+ {
// Update state
filled[orderHash] = safeAdd(filled[orderHash], fillResults.takerAssetFilledAmount);
- // Settle order
- (fillResults.makerAssetFilledAmount, fillResults.makerFeePaid, fillResults.takerFeePaid) =
- settleOrder(order, takerAddress, fillResults.takerAssetFilledAmount);
-
// Log order
emitFillEvent(order, takerAddress, orderHash, fillResults);
- return fillResults;
}
- /// @dev After calling, the order can not be filled anymore.
+ /// @dev Fills the input order.
/// @param order Order struct containing order specifications.
- /// @return True if the order state changed to cancelled.
- /// False if the transaction was already cancelled or expired.
- function cancelOrder(Order memory order)
+ /// @param takerAssetFillAmount Desired amount of takerToken to sell.
+ /// @param signature Proof that order has been created by maker.
+ /// @return Amounts filled and fees paid by maker and taker.
+ function fillOrder(
+ Order memory order,
+ uint256 takerAssetFillAmount,
+ bytes memory signature)
public
- returns (bool)
+ returns (FillResults memory fillResults)
{
- // Compute the order hash
- bytes32 orderHash = getOrderHash(order);
+ // Fetch current order status
+ bytes32 orderHash;
+ uint8 orderStatus;
+ uint256 takerAssetFilledAmount;
+ (orderStatus, orderHash, takerAssetFilledAmount) = getOrderInfo(order);
- // Validate the order
+ // Fetch taker address
+ address takerAddress = getCurrentContextAddress();
+
+ // Either our context is valid or we revert
+ validateFillOrderContextOrRevert(order, orderStatus, orderHash, takerAssetFilledAmount, signature, takerAddress, takerAssetFillAmount);
+
+ // Compute proportional fill amounts
+ uint8 status;
+ (status, fillResults) = calculateFillResults(order, orderStatus, takerAssetFilledAmount, takerAssetFillAmount);
+ if (status != uint8(Status.SUCCESS)) {
+ emit ExchangeStatus(uint8(status), orderHash);
+ return fillResults;
+ }
+
+ // Settle order
+ (fillResults.makerAssetFilledAmount, fillResults.makerFeePaid, fillResults.takerFeePaid) =
+ settleOrder(order, takerAddress, fillResults.takerAssetFilledAmount);
+
+ // Update exchange internal state
+ updateFilledState(order, takerAddress, orderHash, fillResults);
+ return fillResults;
+ }
+
+ /// @dev Validates context for cancelOrder. Succeeds or throws.
+ /// @param order that was cancelled.
+ /// @param orderStatus Status of order that was cancelled.
+ /// @param orderHash Hash of order that was cancelled.
+ function validateCancelOrderContextOrRevert(
+ Order memory order,
+ uint8 orderStatus,
+ bytes32 orderHash)
+ internal
+ {
+ // Ensure order is valid
require(
- order.makerAssetAmount > 0,
- GT_ZERO_AMOUNT_REQUIRED
+ orderStatus != uint8(Status.ORDER_INVALID_MAKER_ASSET_AMOUNT),
+ INVALID_ORDER_MAKER_ASSET_AMOUNT
);
require(
- order.takerAssetAmount > 0,
- GT_ZERO_AMOUNT_REQUIRED
+ orderStatus != uint8(Status.ORDER_INVALID_TAKER_ASSET_AMOUNT),
+ INVALID_ORDER_TAKER_ASSET_AMOUNT
);
- // Validate sender is allowed to cancel this order
- if (order.senderAddress != address(0)) {
- require(
- order.senderAddress == msg.sender,
- INVALID_SENDER
- );
- }
-
// Validate transaction signed by maker
address makerAddress = getCurrentContextAddress();
require(
order.makerAddress == makerAddress,
INVALID_CONTEXT
);
-
- if (block.timestamp >= order.expirationTimeSeconds) {
- emit ExchangeError(uint8(Errors.ORDER_EXPIRED), orderHash);
- return false;
+
+ // Validate sender is allowed to cancel this order
+ if (order.senderAddress != address(0)) {
+ require(
+ order.senderAddress == msg.sender,
+ INVALID_SENDER
+ );
}
+ }
- if (cancelled[orderHash]) {
- emit ExchangeError(uint8(Errors.ORDER_CANCELLED), orderHash);
- return false;
+ /// @dev Updates state with results of cancelling an order.
+ /// State is only updated if the order is currently fillable.
+ /// Otherwise, updating state would have no effect.
+ /// @param order that was cancelled.
+ /// @param orderStatus Status of order that was cancelled.
+ /// @param orderHash Hash of order that was cancelled.
+ /// @return stateUpdated Returns true only if state was updated.
+ function updateCancelledState(
+ Order memory order,
+ uint8 orderStatus,
+ bytes32 orderHash)
+ internal
+ returns (bool stateUpdated)
+ {
+ // Ensure order is fillable (otherwise cancelling does nothing)
+ if (orderStatus != uint8(Status.ORDER_FILLABLE)) {
+ emit ExchangeStatus(uint8(orderStatus), orderHash);
+ stateUpdated = false;
+ return stateUpdated;
}
+ // Perform cancel
cancelled[orderHash] = true;
+ stateUpdated = true;
+ // Log cancel
emit Cancel(
order.makerAddress,
order.feeRecipientAddress,
@@ -215,7 +367,43 @@ contract MixinExchangeCore is
order.makerAssetData,
order.takerAssetData
);
- return true;
+
+ return stateUpdated;
+ }
+
+ /// @dev After calling, the order can not be filled anymore.
+ /// @param order Order struct containing order specifications.
+ /// @return True if the order state changed to cancelled.
+ /// False if the transaction was already cancelled or expired.
+ function cancelOrder(Order memory order)
+ public
+ returns (bool)
+ {
+ // Fetch current order status
+ bytes32 orderHash;
+ uint8 orderStatus;
+ uint256 takerAssetFilledAmount;
+ (orderStatus, orderHash, takerAssetFilledAmount) = getOrderInfo(order);
+
+ // Validate context
+ validateCancelOrderContextOrRevert(order, orderStatus, orderHash);
+
+ // Perform cancel
+ return updateCancelledState(order, orderStatus, orderHash);
+ }
+
+ /// @dev Cancels all orders reated by sender with a salt less than or equal to the specified salt value.
+ /// @param salt Orders created with a salt less or equal to this value will be cancelled.
+ function cancelOrdersUpTo(uint256 salt)
+ external
+ {
+ uint256 newMakerEpoch = salt + 1; // makerEpoch is initialized to 0, so to cancelUpTo we need salt + 1
+ require(
+ newMakerEpoch > makerEpoch[msg.sender], // epoch must be monotonically increasing
+ INVALID_NEW_MAKER_EPOCH
+ );
+ makerEpoch[msg.sender] = newMakerEpoch;
+ emit CancelUpTo(msg.sender, newMakerEpoch);
}
/// @dev Logs a Fill event with the given arguments.
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol
new file mode 100644
index 000000000..863128a06
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol
@@ -0,0 +1,285 @@
+/*
+ Copyright 2018 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.4.21;
+pragma experimental ABIEncoderV2;
+
+import "./mixins/MExchangeCore.sol";
+import "./mixins/MMatchOrders.sol";
+import "./mixins/MSettlement.sol";
+import "./mixins/MTransactions.sol";
+import "../../utils/SafeMath/SafeMath.sol";
+import "./libs/LibMath.sol";
+import "./libs/LibOrder.sol";
+import "./libs/LibStatus.sol";
+import "../../utils/LibBytes/LibBytes.sol";
+
+contract MixinMatchOrders is
+ SafeMath,
+ LibBytes,
+ LibMath,
+ LibStatus,
+ LibOrder,
+ MExchangeCore,
+ MMatchOrders,
+ MSettlement,
+ MTransactions
+ {
+
+ /// @dev Validates context for matchOrders. Succeeds or throws.
+ /// @param leftOrder First order to match.
+ /// @param rightOrder Second order to match.
+ function validateMatchOrdersContextOrRevert(
+ Order memory leftOrder,
+ Order memory rightOrder)
+ internal
+ {
+ // The leftOrder maker asset must be the same as the rightOrder taker asset.
+ require(areBytesEqual(leftOrder.makerAssetData, rightOrder.takerAssetData));
+
+ // The leftOrder taker asset must be the same as the rightOrder maker asset.
+ require(areBytesEqual(leftOrder.takerAssetData, rightOrder.makerAssetData));
+
+ // Make sure there is a positive spread.
+ // There is a positive spread iff the cost per unit bought (OrderA.MakerAmount/OrderA.TakerAmount) for each order is greater
+ // than the profit per unit sold of the matched order (OrderB.TakerAmount/OrderB.MakerAmount).
+ // This is satisfied by the equations below:
+ // <leftOrder.makerAssetAmount> / <leftOrder.takerAssetAmount> >= <rightOrder.takerAssetAmount> / <rightOrder.makerAssetAmount>
+ // AND
+ // <rightOrder.makerAssetAmount> / <rightOrder.takerAssetAmount> >= <leftOrder.takerAssetAmount> / <leftOrder.makerAssetAmount>
+ // These equations can be combined to get the following:
+ require(
+ safeMul(leftOrder.makerAssetAmount, rightOrder.makerAssetAmount) >=
+ safeMul(leftOrder.takerAssetAmount, rightOrder.takerAssetAmount)
+ );
+ }
+
+ /// @dev Validates matched fill results. Succeeds or throws.
+ /// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
+ function validateMatchedOrderFillResultsOrThrow(MatchedFillResults memory matchedFillResults)
+ internal
+ {
+ // The right order must spend at least as much as we're transferring to the left order's maker.
+ // If the amount transferred from the right order is greater than what is transferred, it is a rounding error amount.
+ // Ensure this difference is negligible by dividing the values with each other. The result should equal to ~1.
+ require(matchedFillResults.right.makerAssetFilledAmount >= matchedFillResults.left.takerAssetFilledAmount);
+ require(!isRoundingError(matchedFillResults.right.makerAssetFilledAmount, matchedFillResults.left.takerAssetFilledAmount, 1));
+ }
+
+ /// @dev Calculates partial value given a numerator and denominator.
+ /// Throws if there is a rounding error.
+ /// @param numerator Numerator.
+ /// @param denominator Denominator.
+ /// @param target Value to calculate partial of.
+ /// @return Partial value of target.
+ function safeGetPartialAmount(
+ uint256 numerator,
+ uint256 denominator,
+ uint256 target)
+ internal pure
+ returns (uint256 partialAmount)
+ {
+ require(!isRoundingError(numerator, denominator, target));
+ return getPartialAmount(numerator, denominator, target);
+ }
+
+ /// @dev Calculates fill amounts for the matched orders.
+ /// Each order is filled at their respective price point. However, the calculations are
+ /// carried out as though the orders are both being filled at the right order's price point.
+ /// The profit made by the leftOrder order goes to the taker (who matched the two orders).
+ /// @param leftOrder First order to match.
+ /// @param rightOrder Second order to match.
+ /// @param leftOrderStatus Order status of left order.
+ /// @param rightOrderStatus Order status of right order.
+ /// @param leftOrderFilledAmount Amount of left order already filled.
+ /// @param rightOrderFilledAmount Amount of right order already filled.
+ /// @return status Return status of calculating fill amounts. Returns Status.SUCCESS on success.
+ /// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
+ function calculateMatchedFillResults(
+ Order memory leftOrder,
+ Order memory rightOrder,
+ uint8 leftOrderStatus,
+ uint8 rightOrderStatus,
+ uint256 leftOrderFilledAmount,
+ uint256 rightOrderFilledAmount)
+ internal
+ returns (
+ uint8 status,
+ MatchedFillResults memory matchedFillResults)
+ {
+ // We settle orders at the price point defined by the right order (profit goes to the order taker)
+ // The constraint can be either on the left or on the right.
+ // The constraint is on the left iff the amount required to fill the left order
+ // is less than or equal to the amount we can spend from the right order:
+ // <leftTakerAssetAmountRemaining> <= <rightTakerAssetAmountRemaining> * <rightMakerToTakerRatio>
+ // <leftTakerAssetAmountRemaining> <= <rightTakerAssetAmountRemaining> * <rightOrder.makerAssetAmount> / <rightOrder.takerAssetAmount>
+ // <leftTakerAssetAmountRemaining> * <rightOrder.takerAssetAmount> <= <rightTakerAssetAmountRemaining> * <rightOrder.makerAssetAmount>
+ uint256 rightTakerAssetAmountRemaining = safeSub(rightOrder.takerAssetAmount, rightOrderFilledAmount);
+ uint256 leftTakerAssetAmountRemaining = safeSub(leftOrder.takerAssetAmount, leftOrderFilledAmount);
+ uint256 leftOrderAmountToFill = 0;
+ uint256 rightOrderAmountToFill = 0;
+ if (
+ safeMul(leftTakerAssetAmountRemaining, rightOrder.takerAssetAmount) <=
+ safeMul(rightTakerAssetAmountRemaining, rightOrder.makerAssetAmount)
+ ) {
+ // Left order is the constraint: maximally fill left
+ leftOrderAmountToFill = leftTakerAssetAmountRemaining;
+
+ // The right order receives an amount proportional to how much was spent.
+ // TODO: Ensure rounding error is in the correct direction.
+ rightOrderAmountToFill = safeGetPartialAmount(
+ rightOrder.takerAssetAmount,
+ rightOrder.makerAssetAmount,
+ leftOrderAmountToFill);
+ } else {
+ // Right order is the constraint: maximally fill right
+ rightOrderAmountToFill = rightTakerAssetAmountRemaining;
+
+ // The left order receives an amount proportional to how much was spent.
+ // TODO: Ensure rounding error is in the correct direction.
+ leftOrderAmountToFill = safeGetPartialAmount(
+ rightOrder.makerAssetAmount,
+ rightOrder.takerAssetAmount,
+ rightOrderAmountToFill);
+ }
+
+ // Calculate fill results for left order
+ ( status,
+ matchedFillResults.left
+ ) = calculateFillResults(
+ leftOrder,
+ leftOrderStatus,
+ leftOrderFilledAmount,
+ leftOrderAmountToFill);
+ if (status != uint8(Status.SUCCESS)) {
+ return (status, matchedFillResults);
+ }
+
+ // Calculate fill results for right order
+ ( status,
+ matchedFillResults.right
+ ) = calculateFillResults(
+ rightOrder,
+ rightOrderStatus,
+ rightOrderFilledAmount,
+ rightOrderAmountToFill);
+ if (status != uint8(Status.SUCCESS)) {
+ return (status, matchedFillResults);
+ }
+
+ // Validate the fill results
+ validateMatchedOrderFillResultsOrThrow(matchedFillResults);
+
+ // Return status & fill results
+ return (status, matchedFillResults);
+ }
+
+ /// @dev Match two complementary orders that have a positive spread.
+ /// Each order is filled at their respective price point. However, the calculations are
+ /// carried out as though the orders are both being filled at the right order's price point.
+ /// The profit made by the left order goes to the taker (who matched the two orders).
+ /// @param leftOrder First order to match.
+ /// @param rightOrder Second order to match.
+ /// @param leftSignature Proof that order was created by the left maker.
+ /// @param rightSignature Proof that order was created by the right maker.
+ /// @return matchedFillResults Amounts filled and fees paid by maker and taker of matched orders.
+ function matchOrders(
+ Order memory leftOrder,
+ Order memory rightOrder,
+ bytes leftSignature,
+ bytes rightSignature)
+ public
+ returns (MatchedFillResults memory matchedFillResults)
+ {
+ // Get left status
+ OrderInfo memory leftOrderInfo;
+ ( leftOrderInfo.orderStatus,
+ leftOrderInfo.orderHash,
+ leftOrderInfo.orderFilledAmount
+ ) = getOrderInfo(leftOrder);
+ if (leftOrderInfo.orderStatus != uint8(Status.ORDER_FILLABLE)) {
+ emit ExchangeStatus(uint8(leftOrderInfo.orderStatus), leftOrderInfo.orderHash);
+ return matchedFillResults;
+ }
+
+ // Get right status
+ OrderInfo memory rightOrderInfo;
+ ( rightOrderInfo.orderStatus,
+ rightOrderInfo.orderHash,
+ rightOrderInfo.orderFilledAmount
+ ) = getOrderInfo(rightOrder);
+ if (rightOrderInfo.orderStatus != uint8(Status.ORDER_FILLABLE)) {
+ emit ExchangeStatus(uint8(rightOrderInfo.orderStatus), rightOrderInfo.orderHash);
+ return matchedFillResults;
+ }
+
+ // Fetch taker address
+ address takerAddress = getCurrentContextAddress();
+
+ // Either our context is valid or we revert
+ validateMatchOrdersContextOrRevert(leftOrder, rightOrder);
+
+ // Compute proportional fill amounts
+ uint8 matchedFillAmountsStatus;
+ ( matchedFillAmountsStatus,
+ matchedFillResults
+ ) = calculateMatchedFillResults(
+ leftOrder,
+ rightOrder,
+ leftOrderInfo.orderStatus,
+ rightOrderInfo.orderStatus,
+ leftOrderInfo.orderFilledAmount,
+ rightOrderInfo.orderFilledAmount);
+ if (matchedFillAmountsStatus != uint8(Status.SUCCESS)) {
+ return matchedFillResults;
+ }
+
+ // Validate fill contexts
+ validateFillOrderContextOrRevert(
+ leftOrder,
+ leftOrderInfo.orderStatus,
+ leftOrderInfo.orderHash,
+ leftOrderInfo.orderFilledAmount,
+ leftSignature,
+ rightOrder.makerAddress,
+ matchedFillResults.left.takerAssetFilledAmount);
+ validateFillOrderContextOrRevert(
+ rightOrder,
+ rightOrderInfo.orderStatus,
+ rightOrderInfo.orderHash,
+ rightOrderInfo.orderFilledAmount,
+ rightSignature,
+ leftOrder.makerAddress,
+ matchedFillResults.right.takerAssetFilledAmount);
+
+ // Settle matched orders. Succeeds or throws.
+ settleMatchedOrders(leftOrder, rightOrder, matchedFillResults, takerAddress);
+
+ // Update exchange state
+ updateFilledState(
+ leftOrder,
+ rightOrder.makerAddress,
+ leftOrderInfo.orderHash,
+ matchedFillResults.left
+ );
+ updateFilledState(
+ rightOrder,
+ leftOrder.makerAddress,
+ rightOrderInfo.orderHash,
+ matchedFillResults.right
+ );
+
+ // Return results
+ return matchedFillResults;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol
index 30f1bb49b..3b67051d2 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol
@@ -22,9 +22,11 @@ import "./mixins/MSettlement.sol";
import "./mixins/MAssetProxyDispatcher.sol";
import "./libs/LibOrder.sol";
import "./libs/LibMath.sol";
+import "./mixins/MMatchOrders.sol";
contract MixinSettlement is
LibMath,
+ MMatchOrders,
MSettlement,
MAssetProxyDispatcher
{
@@ -96,4 +98,87 @@ contract MixinSettlement is
);
return (makerAssetFilledAmount, makerFeePaid, takerFeePaid);
}
+
+ /// @dev Settles matched order by transferring appropriate funds between order makers, taker, and fee recipient.
+ /// @param leftOrder First matched order.
+ /// @param rightOrder Second matched order.
+ /// @param matchedFillResults Struct holding amounts to transfer between makers, taker, and fee recipients.
+ /// @param takerAddress Address that matched the orders. The taker receives the spread between orders as profit.
+ function settleMatchedOrders(
+ LibOrder.Order memory leftOrder,
+ LibOrder.Order memory rightOrder,
+ MatchedFillResults memory matchedFillResults,
+ address takerAddress)
+ internal
+ {
+ // Optimized for:
+ // * leftOrder.feeRecipient =?= rightOrder.feeRecipient
+
+ // Not optimized for:
+ // * {left, right}.{MakerAsset, TakerAsset} == ZRX
+ // * {left, right}.maker, takerAddress == {left, right}.feeRecipient
+
+ // leftOrder.MakerAsset == rightOrder.TakerAsset
+ // Taker should be left with a positive balance (the spread)
+ dispatchTransferFrom(
+ leftOrder.makerAssetData,
+ leftOrder.makerAddress,
+ takerAddress,
+ matchedFillResults.left.makerAssetFilledAmount);
+ dispatchTransferFrom(
+ leftOrder.makerAssetData,
+ takerAddress,
+ rightOrder.makerAddress,
+ matchedFillResults.right.takerAssetFilledAmount);
+
+ // rightOrder.MakerAsset == leftOrder.TakerAsset
+ // leftOrder.takerAssetFilledAmount ~ rightOrder.makerAssetFilledAmount
+ // The change goes to right, not to taker.
+ assert(matchedFillResults.right.makerAssetFilledAmount >= matchedFillResults.left.takerAssetFilledAmount);
+ dispatchTransferFrom(
+ rightOrder.makerAssetData,
+ rightOrder.makerAddress,
+ leftOrder.makerAddress,
+ matchedFillResults.right.makerAssetFilledAmount);
+
+ // Maker fees
+ dispatchTransferFrom(
+ ZRX_PROXY_DATA,
+ leftOrder.makerAddress,
+ leftOrder.feeRecipientAddress,
+ matchedFillResults.left.makerFeePaid);
+ dispatchTransferFrom(
+ ZRX_PROXY_DATA,
+ rightOrder.makerAddress,
+ rightOrder.feeRecipientAddress,
+ matchedFillResults.right.makerFeePaid);
+
+ // Taker fees
+ // If we assume distinct(left, right, takerAddress) and
+ // distinct(MakerAsset, TakerAsset, zrx) then the only remaining
+ // opportunity for optimization is when both feeRecipientAddress' are
+ // the same.
+ if (leftOrder.feeRecipientAddress == rightOrder.feeRecipientAddress) {
+ dispatchTransferFrom(
+ ZRX_PROXY_DATA,
+ takerAddress,
+ leftOrder.feeRecipientAddress,
+ safeAdd(
+ matchedFillResults.left.takerFeePaid,
+ matchedFillResults.right.takerFeePaid
+ )
+ );
+ } else {
+ dispatchTransferFrom(
+ ZRX_PROXY_DATA,
+ takerAddress,
+ leftOrder.feeRecipientAddress,
+ matchedFillResults.left.takerFeePaid);
+ dispatchTransferFrom(
+ ZRX_PROXY_DATA,
+ takerAddress,
+ rightOrder.feeRecipientAddress,
+ matchedFillResults.right.takerFeePaid);
+ }
+ }
}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol
index 42517221e..04018e09d 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol
@@ -27,10 +27,11 @@ import "./libs/LibFillResults.sol";
import "./libs/LibExchangeErrors.sol";
contract MixinWrapperFunctions is
+ SafeMath,
+ LibBytes,
+ LibMath,
LibOrder,
LibFillResults,
- LibMath,
- LibBytes,
LibExchangeErrors,
MExchangeCore
{
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IWrapperFunctions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IWrapperFunctions.sol
index d23170b1c..3aaa9de9e 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IWrapperFunctions.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IWrapperFunctions.sol
@@ -23,10 +23,10 @@ import "./libs/LibOrder.sol";
import "./libs/LibFillResults.sol";
contract IWrapperFunctions is
+ LibBytes,
+ LibMath,
LibOrder,
LibFillResults,
- LibMath,
- LibBytes,
LibExchangeErrors,
MExchangeCore
{
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol
index 6ffbdfdca..bf048e815 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol
@@ -20,17 +20,6 @@ pragma solidity ^0.4.23;
contract LibExchangeErrors {
- // Error Codes
- enum Errors {
- ORDER_EXPIRED, // Order has already expired
- ORDER_FULLY_FILLED, // Order has already been fully filled
- ORDER_CANCELLED, // Order has already been cancelled
- ROUNDING_ERROR_TOO_LARGE, // Rounding error too large
- INSUFFICIENT_BALANCE_OR_ALLOWANCE // Insufficient balance or allowance for token transfer
- }
-
- event ExchangeError(uint8 indexed errorId, bytes32 indexed orderHash);
-
// Core revert reasons
string constant GT_ZERO_AMOUNT_REQUIRED = "Amount must be greater than 0.";
string constant SIGNATURE_VALIDATION_FAILED = "Signature validation failed.";
@@ -38,6 +27,10 @@ contract LibExchangeErrors {
string constant INVALID_CONTEXT = "Function called in an invalid context.";
string constant INVALID_NEW_MAKER_EPOCH = "Specified salt must be greater than or equal to existing makerEpoch.";
+ // Order revert reasons
+ string constant INVALID_ORDER_TAKER_ASSET_AMOUNT = "Invalid order taker asset amount: expected a non-zero value.";
+ string constant INVALID_ORDER_MAKER_ASSET_AMOUNT = "Invalid order maker asset amount: expected a non-zero value.";
+
// Transaction revert reasons
string constant DUPLICATE_TRANSACTION_HASH = "Transaction has already been executed.";
string constant TRANSACTION_EXECUTION_FAILED = "Transaction execution failed.";
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol
new file mode 100644
index 000000000..42b9cb65d
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol
@@ -0,0 +1,49 @@
+/*
+
+ Copyright 2018 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.4.21;
+pragma experimental ABIEncoderV2;
+
+contract LibStatus {
+
+ // Exchange Status Codes
+ enum Status {
+ /// Default Status ///
+ INVALID, // General invalid status
+
+ /// General Exchange Statuses ///
+ SUCCESS, // Indicates a successful operation
+ ROUNDING_ERROR_TOO_LARGE, // Rounding error too large
+ INSUFFICIENT_BALANCE_OR_ALLOWANCE, // Insufficient balance or allowance for token transfer
+ TAKER_ASSET_FILL_AMOUNT_TOO_LOW, // takerAssetFillAmount is <= 0
+ INVALID_SIGNATURE, // Invalid signature
+ INVALID_SENDER, // Invalid sender
+ INVALID_TAKER, // Invalid taker
+ INVALID_MAKER, // Invalid maker
+
+ /// Order State Statuses ///
+ ORDER_INVALID_MAKER_ASSET_AMOUNT, // Order does not have a valid maker asset amount
+ ORDER_INVALID_TAKER_ASSET_AMOUNT, // Order does not have a valid taker asset amount
+ ORDER_FILLABLE, // Order is fillable
+ ORDER_EXPIRED, // Order has already expired
+ ORDER_FULLY_FILLED, // Order is fully filled
+ ORDER_CANCELLED // Order has been cancelled
+ }
+
+ event ExchangeStatus(uint8 indexed statusId, bytes32 indexed orderHash);
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol
index 77ee77797..67752d3c8 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol
@@ -17,6 +17,7 @@
*/
pragma solidity ^0.4.23;
+pragma experimental ABIEncoderV2;
import "../interfaces/IAssetProxyDispatcher.sol";
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol
index 5b78e70da..eb97be8b0 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol
@@ -17,6 +17,7 @@
*/
pragma solidity ^0.4.23;
+pragma experimental ABIEncoderV2;
import "../libs/LibOrder.sol";
import "../libs/LibFillResults.sol";
@@ -63,4 +64,122 @@ contract MExchangeCore is
bytes32 orderHash,
LibFillResults.FillResults memory fillResults)
internal;
+
+ /// @dev Gets information about an order.
+ /// @param order Order to gather information on.
+ /// @return status Status of order. Statuses are defined in the LibStatus.Status struct.
+ /// @return orderHash Keccak-256 EIP712 hash of the order.
+ /// @return takerAssetFilledAmount Amount of order that has been filled.
+ function getOrderInfo(LibOrder.Order memory order)
+ public
+ view
+ returns (
+ uint8 orderStatus,
+ bytes32 orderHash,
+ uint256 takerAssetFilledAmount);
+
+ /// @dev Validates context for fillOrder. Succeeds or throws.
+ /// @param order to be filled.
+ /// @param orderStatus Status of order to be filled.
+ /// @param orderHash Hash of order to be filled.
+ /// @param takerAssetFilledAmount Amount of order already filled.
+ /// @param signature Proof that the orders was created by its maker.
+ /// @param takerAddress Address of order taker.
+ /// @param takerAssetFillAmount Desired amount of order to fill by taker.
+ function validateFillOrderContextOrRevert(
+ LibOrder.Order memory order,
+ uint8 orderStatus,
+ bytes32 orderHash,
+ uint256 takerAssetFilledAmount,
+ bytes memory signature,
+ address takerAddress,
+ uint256 takerAssetFillAmount)
+ internal;
+
+ /// @dev Calculates amounts filled and fees paid by maker and taker.
+ /// @param order to be filled.
+ /// @param orderStatus Status of order to be filled.
+ /// @param takerAssetFilledAmount Amount of order already filled.
+ /// @param takerAssetFillAmount Desired amount of order to fill by taker.
+ /// @return status Return status of calculating fill amounts. Returns Status.SUCCESS on success.
+ /// @return fillResults Amounts filled and fees paid by maker and taker.
+ function calculateFillResults(
+ LibOrder.Order memory order,
+ uint8 orderStatus,
+ uint256 takerAssetFilledAmount,
+ uint256 takerAssetFillAmount)
+ public
+ pure
+ returns (
+ uint8 status,
+ LibFillResults.FillResults memory fillResults);
+
+ /// @dev Updates state with results of a fill order.
+ /// @param order that was filled.
+ /// @param takerAddress Address of taker who filled the order.
+ /// @return fillResults Amounts filled and fees paid by maker and taker.
+ function updateFilledState(
+ LibOrder.Order memory order,
+ address takerAddress,
+ bytes32 orderHash,
+ LibFillResults.FillResults memory fillResults)
+ internal;
+
+ /// @dev Fills the input order.
+ /// @param order Order struct containing order specifications.
+ /// @param takerAssetFillAmount Desired amount of takerToken to sell.
+ /// @param signature Proof that order has been created by maker.
+ /// @return Amounts filled and fees paid by maker and taker.
+ function fillOrder(
+ LibOrder.Order memory order,
+ uint256 takerAssetFillAmount,
+ bytes memory signature)
+ public
+ returns (LibFillResults.FillResults memory fillResults);
+
+ /// @dev Validates context for cancelOrder. Succeeds or throws.
+ /// @param order that was cancelled.
+ /// @param orderStatus Status of order that was cancelled.
+ /// @param orderHash Hash of order that was cancelled.
+ function validateCancelOrderContextOrRevert(
+ LibOrder.Order memory order,
+ uint8 orderStatus,
+ bytes32 orderHash)
+ internal;
+
+ /// @dev Updates state with results of cancelling an order.
+ /// State is only updated if the order is currently fillable.
+ /// Otherwise, updating state would have no effect.
+ /// @param order that was cancelled.
+ /// @param orderStatus Status of order that was cancelled.
+ /// @param orderHash Hash of order that was cancelled.
+ /// @return stateUpdated Returns true only if state was updated.
+ function updateCancelledState(
+ LibOrder.Order memory order,
+ uint8 orderStatus,
+ bytes32 orderHash)
+ internal
+ returns (bool stateUpdated);
+
+ /// @dev After calling, the order can not be filled anymore.
+ /// @param order Order struct containing order specifications.
+ /// @return True if the order state changed to cancelled.
+ /// False if the transaction was already cancelled or expired.
+ function cancelOrder(LibOrder.Order memory order)
+ public
+ returns (bool);
+
+ /// @param salt Orders created with a salt less or equal to this value will be cancelled.
+ function cancelOrdersUpTo(uint256 salt)
+ external;
+
+/*
+ /// @dev Checks if rounding error > 0.1%.
+ /// @param numerator Numerator.
+ /// @param denominator Denominator.
+ /// @param target Value to multiply with numerator/denominator.
+ /// @return Rounding error is present.
+ function isRoundingError(uint256 numerator, uint256 denominator, uint256 target)
+ public pure
+ returns (bool isError); */
}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol
new file mode 100644
index 000000000..abd15182e
--- /dev/null
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol
@@ -0,0 +1,106 @@
+/*
+
+ Copyright 2018 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.4.21;
+pragma experimental ABIEncoderV2;
+
+import "../libs/LibOrder.sol";
+import "../libs/LibFillResults.sol";
+import "./MExchangeCore.sol";
+
+contract MMatchOrders {
+
+ struct MatchedFillResults {
+ LibFillResults.FillResults left;
+ LibFillResults.FillResults right;
+ }
+
+ /// This struct exists solely to avoid the stack limit constraint
+ /// in matchOrders
+ struct OrderInfo {
+ uint8 orderStatus;
+ bytes32 orderHash;
+ uint256 orderFilledAmount;
+ }
+
+ /// @dev Validates context for matchOrders. Succeeds or throws.
+ /// @param leftOrder First order to match.
+ /// @param rightOrder Second order to match.
+ function validateMatchOrdersContextOrRevert(
+ LibOrder.Order memory leftOrder,
+ LibOrder.Order memory rightOrder)
+ internal;
+
+ /// @dev Validates matched fill results. Succeeds or throws.
+ /// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
+ function validateMatchedOrderFillResultsOrThrow(MatchedFillResults memory matchedFillResults)
+ internal;
+
+ /// @dev Calculates partial value given a numerator and denominator.
+ /// Throws if there is a rounding error.
+ /// @param numerator Numerator.
+ /// @param denominator Denominator.
+ /// @param target Value to calculate partial of.
+ /// @return Partial value of target.
+ function safeGetPartialAmount(
+ uint256 numerator,
+ uint256 denominator,
+ uint256 target)
+ internal pure
+ returns (uint256 partialAmount);
+
+ /// @dev Calculates fill amounts for the matched orders.
+ /// Each order is filled at their respective price point. However, the calculations are
+ /// carried out as though the orders are both being filled at the right order's price point.
+ /// The profit made by the leftOrder order goes to the taker (who matched the two orders).
+ /// @param leftOrder First order to match.
+ /// @param rightOrder Second order to match.
+ /// @param leftOrderStatus Order status of left order.
+ /// @param rightOrderStatus Order status of right order.
+ /// @param leftOrderFilledAmount Amount of left order already filled.
+ /// @param rightOrderFilledAmount Amount of right order already filled.
+ /// @return status Return status of calculating fill amounts. Returns Status.SUCCESS on success.
+ /// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
+ function calculateMatchedFillResults(
+ LibOrder.Order memory leftOrder,
+ LibOrder.Order memory rightOrder,
+ uint8 leftOrderStatus,
+ uint8 rightOrderStatus,
+ uint256 leftOrderFilledAmount,
+ uint256 rightOrderFilledAmount)
+ internal
+ returns (
+ uint8 status,
+ MatchedFillResults memory matchedFillResults);
+
+ /// @dev Match two complementary orders that have a positive spread.
+ /// Each order is filled at their respective price point. However, the calculations are
+ /// carried out as though the orders are both being filled at the right order's price point.
+ /// The profit made by the left order goes to the taker (who matched the two orders).
+ /// @param leftOrder First order to match.
+ /// @param rightOrder Second order to match.
+ /// @param leftSignature Proof that order was created by the left maker.
+ /// @param rightSignature Proof that order was created by the right maker.
+ /// @return matchedFillResults Amounts filled and fees paid by maker and taker of matched orders.
+ function matchOrders(
+ LibOrder.Order memory leftOrder,
+ LibOrder.Order memory rightOrder,
+ bytes leftSignature,
+ bytes rightSignature)
+ public
+ returns (MatchedFillResults memory matchedFillResults);
+}
diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSettlement.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSettlement.sol
index 5e2edbf99..ac38ede95 100644
--- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSettlement.sol
+++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSettlement.sol
@@ -19,6 +19,7 @@
pragma solidity ^0.4.23;
import "../libs/LibOrder.sol";
+import "./MMatchOrders.sol";
contract MSettlement {
@@ -37,5 +38,16 @@ contract MSettlement {
uint256 makerFeePaid,
uint256 takerFeePaid
);
-}
+ /// @dev Settles matched order by transferring appropriate funds between order makers, taker, and fee recipient.
+ /// @param leftOrder First matched order.
+ /// @param rightOrder Second matched order.
+ /// @param matchedFillResults Struct holding amounts to transfer between makers, taker, and fee recipients.
+ /// @param takerAddress Address that matched the orders. The taker receives the spread between orders as profit.
+ function settleMatchedOrders(
+ LibOrder.Order memory leftOrder,
+ LibOrder.Order memory rightOrder,
+ MMatchOrders.MatchedFillResults memory matchedFillResults,
+ address takerAddress)
+ internal;
+}
diff --git a/packages/contracts/src/contracts/current/test/TestSignatureValidator/TestSignatureValidator.sol b/packages/contracts/src/contracts/current/test/TestSignatureValidator/TestSignatureValidator.sol
index 20202cd7b..44565b361 100644
--- a/packages/contracts/src/contracts/current/test/TestSignatureValidator/TestSignatureValidator.sol
+++ b/packages/contracts/src/contracts/current/test/TestSignatureValidator/TestSignatureValidator.sol
@@ -38,4 +38,4 @@ contract TestSignatureValidator is MixinSignatureValidator {
);
return isValid;
}
-} \ No newline at end of file
+}