From a4c821eb60c227df4512d6c24ce0e5239b8bb6ce Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 10 May 2018 14:22:29 -0700 Subject: Atomic Order Matching - Smart Contracts. --- .../current/protocol/Exchange/Exchange.sol | 14 +- .../protocol/Exchange/MixinExchangeCore.sol | 372 ++++++++++++++++----- .../current/protocol/Exchange/MixinMatchOrders.sol | 285 ++++++++++++++++ .../current/protocol/Exchange/MixinSettlement.sol | 85 +++++ .../protocol/Exchange/MixinWrapperFunctions.sol | 5 +- .../Exchange/interfaces/IWrapperFunctions.sol | 4 +- .../protocol/Exchange/libs/LibExchangeErrors.sol | 15 +- .../current/protocol/Exchange/libs/LibStatus.sol | 49 +++ .../Exchange/mixins/MAssetProxyDispatcher.sol | 1 + .../protocol/Exchange/mixins/MExchangeCore.sol | 119 +++++++ .../protocol/Exchange/mixins/MMatchOrders.sol | 106 ++++++ .../protocol/Exchange/mixins/MSettlement.sol | 14 +- .../TestSignatureValidator.sol | 2 +- 13 files changed, 957 insertions(+), 114 deletions(-) create mode 100644 packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol create mode 100644 packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol create mode 100644 packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol 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: + // / >= / + // AND + // / >= / + // 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: + // <= * + // <= * / + // * <= * + 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 +} -- cgit v1.2.3