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 (limited to 'packages/contracts') 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 From 9b1015bbce81b3f2a245e3dab6eea7c9028ce93b Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 10 May 2018 14:22:49 -0700 Subject: Atomic Order Matching - Tests --- packages/contracts/src/utils/asset_proxy_utils.ts | 74 ++ packages/contracts/src/utils/exchange_wrapper.ts | 22 + packages/contracts/src/utils/order_utils.ts | 9 + packages/contracts/src/utils/types.ts | 48 +- packages/contracts/test/exchange/core.ts | 30 +- packages/contracts/test/exchange/match_orders.ts | 873 +++++++++++++++++++++ packages/contracts/test/exchange/transactions.ts | 4 +- .../contracts/test/utils/match_order_tester.ts | 353 +++++++++ 8 files changed, 1390 insertions(+), 23 deletions(-) create mode 100644 packages/contracts/test/exchange/match_orders.ts create mode 100644 packages/contracts/test/utils/match_order_tester.ts (limited to 'packages/contracts') diff --git a/packages/contracts/src/utils/asset_proxy_utils.ts b/packages/contracts/src/utils/asset_proxy_utils.ts index dc31c3497..9a26a9ca7 100644 --- a/packages/contracts/src/utils/asset_proxy_utils.ts +++ b/packages/contracts/src/utils/asset_proxy_utils.ts @@ -8,6 +8,9 @@ export const assetProxyUtils = { encodeAssetProxyId(assetProxyId: AssetProxyId): Buffer { return ethUtil.toBuffer(assetProxyId); }, + decodeAssetProxyId(encodedAssetProxyId: Buffer): AssetProxyId { + return ethUtil.bufferToInt(encodedAssetProxyId); + }, encodeAddress(address: string): Buffer { if (!ethUtil.isValidAddress(address)) { throw new Error(`Invalid Address: ${address}`); @@ -15,12 +18,24 @@ export const assetProxyUtils = { const encodedAddress = ethUtil.toBuffer(address); return encodedAddress; }, + decodeAddress(encodedAddress: Buffer): string { + const address = ethUtil.bufferToHex(encodedAddress); + if (!ethUtil.isValidAddress(address)) { + throw new Error(`Invalid Address: ${address}`); + } + return address; + }, encodeUint256(value: BigNumber): Buffer { const formattedValue = new BN(value.toString(10)); const encodedValue = ethUtil.toBuffer(formattedValue); const paddedValue = ethUtil.setLengthLeft(encodedValue, 32); return paddedValue; }, + decodeUint256(encodedValue: Buffer): BigNumber { + const formattedValue = ethUtil.bufferToHex(encodedValue); + const value = new BigNumber(formattedValue, 16); + return value; + }, encodeERC20ProxyData(tokenAddress: string): string { const encodedAssetProxyId = assetProxyUtils.encodeAssetProxyId(AssetProxyId.ERC20); const encodedAddress = assetProxyUtils.encodeAddress(tokenAddress); @@ -28,6 +43,28 @@ export const assetProxyUtils = { const encodedMetadataHex = ethUtil.bufferToHex(encodedMetadata); return encodedMetadataHex; }, + decodeERC20ProxyData(proxyData: string): string /* tokenAddress */ { + const encodedProxyMetadata = ethUtil.toBuffer(proxyData); + if (encodedProxyMetadata.byteLength !== 21) { + throw new Error( + `Could not decode ERC20 Proxy Data. Expected length of encoded data to be 21. Got ${ + encodedProxyMetadata.byteLength + }`, + ); + } + const encodedAssetProxyId = encodedProxyMetadata.slice(0, 1); + const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId); + if (assetProxyId !== AssetProxyId.ERC20) { + throw new Error( + `Could not decode ERC20 Proxy Data. Expected Asset Proxy Id to be ERC20 (${ + AssetProxyId.ERC20 + }), but got ${assetProxyId}`, + ); + } + const encodedTokenAddress = encodedProxyMetadata.slice(1, 21); + const tokenAddress = assetProxyUtils.decodeAddress(encodedTokenAddress); + return tokenAddress; + }, encodeERC721ProxyData(tokenAddress: string, tokenId: BigNumber): string { const encodedAssetProxyId = assetProxyUtils.encodeAssetProxyId(AssetProxyId.ERC721); const encodedAddress = assetProxyUtils.encodeAddress(tokenAddress); @@ -36,4 +73,41 @@ export const assetProxyUtils = { const encodedMetadataHex = ethUtil.bufferToHex(encodedMetadata); return encodedMetadataHex; }, + decodeERC721ProxyData(proxyData: string): [string /* tokenAddress */, BigNumber /* tokenId */] { + const encodedProxyMetadata = ethUtil.toBuffer(proxyData); + if (encodedProxyMetadata.byteLength !== 53) { + throw new Error( + `Could not decode ERC20 Proxy Data. Expected length of encoded data to be 53. Got ${ + encodedProxyMetadata.byteLength + }`, + ); + } + const encodedAssetProxyId = encodedProxyMetadata.slice(0, 1); + const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId); + if (assetProxyId !== AssetProxyId.ERC721) { + throw new Error( + `Could not decode ERC721 Proxy Data. Expected Asset Proxy Id to be ERC721 (${ + AssetProxyId.ERC721 + }), but got ${assetProxyId}`, + ); + } + const encodedTokenAddress = encodedProxyMetadata.slice(1, 21); + const tokenAddress = assetProxyUtils.decodeAddress(encodedTokenAddress); + const encodedTokenId = encodedProxyMetadata.slice(21, 53); + const tokenId = assetProxyUtils.decodeUint256(encodedTokenId); + return [tokenAddress, tokenId]; + }, + decodeProxyDataId(proxyData: string): AssetProxyId { + const encodedProxyMetadata = ethUtil.toBuffer(proxyData); + if (encodedProxyMetadata.byteLength < 1) { + throw new Error( + `Could not decode Proxy Data. Expected length of encoded data to be at least 1. Got ${ + encodedProxyMetadata.byteLength + }`, + ); + } + const encodedAssetProxyId = encodedProxyMetadata.slice(0, 1); + const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId); + return assetProxyId; + }, }; diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts index 27fdd698f..6d36198f2 100644 --- a/packages/contracts/src/utils/exchange_wrapper.ts +++ b/packages/contracts/src/utils/exchange_wrapper.ts @@ -231,4 +231,26 @@ export class ExchangeWrapper { tx.logs = _.map(tx.logs, log => this._logDecoder.decodeLogOrThrow(log)); return tx; } + public async getOrderInfoAsync( + signedOrder: SignedOrder, + ): Promise<[number /* orderStatus */, string /* orderHash */, BigNumber /* orderTakerAssetAmountFilled */]> { + const orderInfo: [number, string, BigNumber] = await this._exchange.getOrderInfo.callAsync(signedOrder); + return orderInfo; + } + public async matchOrdersAsync( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + from: string, + ): Promise { + const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight); + const txHash = await this._exchange.matchOrders.sendTransactionAsync( + params.left, + params.right, + params.leftSignature, + params.rightSignature, + { from }, + ); + const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash); + return tx; + } } diff --git a/packages/contracts/src/utils/order_utils.ts b/packages/contracts/src/utils/order_utils.ts index 10bbf4f7c..7a482ad9e 100644 --- a/packages/contracts/src/utils/order_utils.ts +++ b/packages/contracts/src/utils/order_utils.ts @@ -80,4 +80,13 @@ export const orderUtils = { const orderHashHex = `0x${orderHashBuff.toString('hex')}`; return orderHashHex; }, + createMatchOrders(signedOrderLeft: SignedOrder, signedOrderRight: SignedOrder) { + const fill = { + left: orderUtils.getOrderStruct(signedOrderLeft), + right: orderUtils.getOrderStruct(signedOrderRight), + leftSignature: signedOrderLeft.signature, + rightSignature: signedOrderRight.signature, + }; + return fill; + }, }; diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts index 234b14ef9..ce7fbcbe1 100644 --- a/packages/contracts/src/utils/types.ts +++ b/packages/contracts/src/utils/types.ts @@ -74,12 +74,27 @@ export interface Token { swarmHash: string; } -export enum ExchangeContractErrs { - ERROR_ORDER_EXPIRED, - ERROR_ORDER_FULLY_FILLED, - ERROR_ORDER_CANCELLED, - ERROR_ROUNDING_ERROR_TOO_LARGE, - ERROR_INSUFFICIENT_BALANCE_OR_ALLOWANCE, +export enum ExchangeStatus { + /// 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 } export enum ContractName { @@ -143,3 +158,24 @@ export interface SignedTransaction { data: string; signature: string; } + +export interface TransferAmountsByMatchOrders { + // Left Maker + amountBoughtByLeftMaker: BigNumber; + amountSoldByLeftMaker: BigNumber; + amountReceivedByLeftMaker: BigNumber; + feePaidByLeftMaker: BigNumber; + // Right Maker + amountBoughtByRightMaker: BigNumber; + amountSoldByRightMaker: BigNumber; + amountReceivedByRightMaker: BigNumber; + feePaidByRightMaker: BigNumber; + // Taker + amountReceivedByTaker: BigNumber; + feePaidByTakerLeft: BigNumber; + feePaidByTakerRight: BigNumber; + totalFeePaidByTaker: BigNumber; + // Fee Recipients + feeReceivedLeft: BigNumber; + feeReceivedRight: BigNumber; +} diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts index 6c190d4da..e9c51ea18 100644 --- a/packages/contracts/test/exchange/core.ts +++ b/packages/contracts/test/exchange/core.ts @@ -12,7 +12,7 @@ import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c import { CancelContractEventArgs, ExchangeContract, - ExchangeErrorContractEventArgs, + ExchangeStatusContractEventArgs, FillContractEventArgs, } from '../../src/contract_wrappers/generated/exchange'; import { artifacts } from '../../src/utils/artifacts'; @@ -25,8 +25,8 @@ import { ERC721Wrapper } from '../../src/utils/erc721_wrapper'; import { ExchangeWrapper } from '../../src/utils/exchange_wrapper'; import { OrderFactory } from '../../src/utils/order_factory'; import { orderUtils } from '../../src/utils/order_utils'; -import { AssetProxyId, ERC20BalancesByOwner, ExchangeContractErrs, SignedOrder } from '../../src/utils/types'; -import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; +import { AssetProxyId, ContractName, ERC20BalancesByOwner, ExchangeStatus, SignedOrder } from '../../src/utils/types'; +import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; chaiSetup.configure(); const expect = chai.expect; @@ -556,9 +556,9 @@ describe('Exchange core', () => { const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); expect(res.logs).to.have.length(1); - const log = res.logs[0] as LogWithDecodedArgs; - const errCode = log.args.errorId; - expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_EXPIRED); + const log = res.logs[0] as LogWithDecodedArgs; + const statusCode = log.args.statusId; + expect(statusCode).to.be.equal(ExchangeStatus.ORDER_EXPIRED); }); it('should log an error event if no value is filled', async () => { @@ -567,9 +567,9 @@ describe('Exchange core', () => { const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); expect(res.logs).to.have.length(1); - const log = res.logs[0] as LogWithDecodedArgs; - const errCode = log.args.errorId; - expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_FULLY_FILLED); + const log = res.logs[0] as LogWithDecodedArgs; + const statusCode = log.args.statusId; + expect(statusCode).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); }); }); @@ -635,9 +635,9 @@ describe('Exchange core', () => { const res = await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); expect(res.logs).to.have.length(1); - const log = res.logs[0] as LogWithDecodedArgs; - const errCode = log.args.errorId; - expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_CANCELLED); + const log = res.logs[0] as LogWithDecodedArgs; + const statusCode = log.args.statusId; + expect(statusCode).to.be.equal(ExchangeStatus.ORDER_CANCELLED); }); it('should log error if order is expired', async () => { @@ -647,9 +647,9 @@ describe('Exchange core', () => { const res = await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); expect(res.logs).to.have.length(1); - const log = res.logs[0] as LogWithDecodedArgs; - const errCode = log.args.errorId; - expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_EXPIRED); + const log = res.logs[0] as LogWithDecodedArgs; + const statusCode = log.args.statusId; + expect(statusCode).to.be.equal(ExchangeStatus.ORDER_EXPIRED); }); }); diff --git a/packages/contracts/test/exchange/match_orders.ts b/packages/contracts/test/exchange/match_orders.ts new file mode 100644 index 000000000..a114d92ac --- /dev/null +++ b/packages/contracts/test/exchange/match_orders.ts @@ -0,0 +1,873 @@ +import { LogWithDecodedArgs, ZeroEx } from '0x.js'; +import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { BigNumber } from '@0xproject/utils'; +import * as chai from 'chai'; +import ethUtil = require('ethereumjs-util'); +import * as _ from 'lodash'; + +import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token'; +import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token'; +import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy'; +import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy'; +import { + CancelContractEventArgs, + ExchangeContract, + ExchangeStatusContractEventArgs, + FillContractEventArgs, +} from '../../src/contract_wrappers/generated/exchange'; +import { assetProxyUtils } from '../../src/utils/asset_proxy_utils'; +import { constants } from '../../src/utils/constants'; +import { crypto } from '../../src/utils/crypto'; +import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; +import { ERC721Wrapper } from '../../src/utils/erc721_wrapper'; +import { ExchangeWrapper } from '../../src/utils/exchange_wrapper'; +import { OrderFactory } from '../../src/utils/order_factory'; +import { orderUtils } from '../../src/utils/order_utils'; +import { + AssetProxyId, + ContractName, + ERC20BalancesByOwner, + ERC721TokenIdsByOwner, + ExchangeStatus, + SignedOrder, +} from '../../src/utils/types'; +import { chaiSetup } from '../utils/chai_setup'; +import { deployer } from '../utils/deployer'; +import { provider, web3Wrapper } from '../utils/web3_wrapper'; + +import { MatchOrderTester } from '../utils/match_order_tester'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +describe('matchOrdersAndVerifyBalancesAsync', () => { + let makerAddressLeft: string; + let makerAddressRight: string; + let owner: string; + let takerAddress: string; + let feeRecipientAddressLeft: string; + let feeRecipientAddressRight: string; + + let erc20TokenA: DummyERC20TokenContract; + let erc20TokenB: DummyERC20TokenContract; + let zrxToken: DummyERC20TokenContract; + let erc721Token: DummyERC721TokenContract; + let exchange: ExchangeContract; + let erc20Proxy: ERC20ProxyContract; + let erc721Proxy: ERC721ProxyContract; + + let erc20BalancesByOwner: ERC20BalancesByOwner; + let erc721TokenIdsByOwner: ERC721TokenIdsByOwner; + let exchangeWrapper: ExchangeWrapper; + let erc20Wrapper: ERC20Wrapper; + let erc721Wrapper: ERC721Wrapper; + let orderFactoryLeft: OrderFactory; + let orderFactoryRight: OrderFactory; + + let erc721LeftMakerAssetIds: BigNumber[]; + let erc721RightMakerAssetIds: BigNumber[]; + let erc721TakerAssetIds: BigNumber[]; + + let defaultERC20MakerAssetAddress: string; + let defaultERC20TakerAssetAddress: string; + let defaultERC721AssetAddress: string; + + let matchOrderTester: MatchOrderTester; + + let zeroEx: ZeroEx; + + before(async () => { + // Create accounts + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const usedAddresses = ([ + owner, + makerAddressLeft, + makerAddressRight, + takerAddress, + feeRecipientAddressLeft, + feeRecipientAddressRight, + ] = accounts); + // Create wrappers + erc20Wrapper = new ERC20Wrapper(deployer, provider, usedAddresses, owner); + erc721Wrapper = new ERC721Wrapper(deployer, provider, usedAddresses, owner); + // Deploy ERC20 token & ERC20 proxy + [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(); + erc20Proxy = await erc20Wrapper.deployProxyAsync(); + await erc20Wrapper.setBalancesAndAllowancesAsync(); + // Deploy ERC721 token and proxy + [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); + erc721Proxy = await erc721Wrapper.deployProxyAsync(); + await erc721Wrapper.setBalancesAndAllowancesAsync(); + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + erc721LeftMakerAssetIds = erc721Balances[makerAddressLeft][erc721Token.address]; + erc721RightMakerAssetIds = erc721Balances[makerAddressRight][erc721Token.address]; + erc721TakerAssetIds = erc721Balances[takerAddress][erc721Token.address]; + // Depoy exchange + const exchangeInstance = await deployer.deployAsync(ContractName.Exchange, [ + assetProxyUtils.encodeERC20ProxyData(zrxToken.address), + ]); + exchange = new ExchangeContract(exchangeInstance.abi, exchangeInstance.address, provider); + zeroEx = new ZeroEx(provider, { + exchangeContractAddress: exchange.address, + networkId: constants.TESTRPC_NETWORK_ID, + }); + exchangeWrapper = new ExchangeWrapper(exchange, zeroEx); + await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner); + await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC721, erc721Proxy.address, owner); + // Authorize ERC20 and ERC721 trades by exchange + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { + from: owner, + }); + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { + from: owner, + }); + // Set default addresses + defaultERC20MakerAssetAddress = erc20TokenA.address; + defaultERC20TakerAssetAddress = erc20TokenB.address; + defaultERC721AssetAddress = erc721Token.address; + // Create default order parameters + const defaultOrderParams = { + ...constants.STATIC_ORDER_PARAMS, + exchangeAddress: exchange.address, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + }; + const privateKeyLeft = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressLeft)]; + orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParams); + const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)]; + orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParams); + // Set match order tester + matchOrderTester = new MatchOrderTester(exchangeWrapper, erc20Wrapper, erc721Wrapper); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('matchOrders', () => { + beforeEach(async () => { + erc20BalancesByOwner = await erc20Wrapper.getBalancesAsync(); + erc721TokenIdsByOwner = await erc721Wrapper.getBalancesAsync(); + }); + + it('should transfer the correct amounts when orders completely fill each other', async () => { + // Create orders to match + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Match signedOrderLeft with signedOrderRight + await matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight, + zrxToken.address, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + ); + // Verify left order was fully filled + const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + // Verify right order was fully filled + const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( + signedOrderRight, + ); + expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + }); + + it('should transfer the correct amounts when orders completely fill each other and taker doesnt take a profit', async () => { + // Create orders to match + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Store original taker balance + const takerInitialBalances = _.cloneDeep(erc20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress]); + // Match signedOrderLeft with signedOrderRight + let newERC20BalancesByOwner: ERC20BalancesByOwner; + let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner; + [ + newERC20BalancesByOwner, + newERC721TokenIdsByOwner, + ] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight, + zrxToken.address, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + ); + // Verify left order was fully filled + const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + // Verify right order was fully filled + const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( + signedOrderRight, + ); + expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + // Verify taker did not take a profit + expect(takerInitialBalances).to.be.deep.equal( + newERC20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress], + ); + }); + + it('should transfer the correct amounts when left order is completely filled and right order is partially filled', async () => { + // Create orders to match + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(20), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(4), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Match orders + await matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight, + zrxToken.address, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + ); + // Verify left order was fully filled + const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + // Verify right order was partially filled + const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( + signedOrderRight, + ); + expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); + }); + + it('should transfer the correct amounts when right order is completely filled and left order is partially filled', async () => { + // Create orders to match + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(50), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Match orders + await matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight, + zrxToken.address, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + ); + // Verify left order was partially filled + const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); + // Verify right order was fully filled + const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( + signedOrderRight, + ); + expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + }); + + it('should transfer the correct amounts when consecutive calls are used to completely fill the left order', async () => { + // Create orders to match + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(50), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Match orders + let newERC20BalancesByOwner: ERC20BalancesByOwner; + let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner; + [ + newERC20BalancesByOwner, + newERC721TokenIdsByOwner, + ] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight, + zrxToken.address, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + ); + // Verify left order was partially filled + const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); + // Verify right order was fully filled + const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( + signedOrderRight, + ); + expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + // Construct second right order + // Note: This order needs makerAssetAmount=90/takerAssetAmount=[anything <= 45] to fully fill the right order. + // However, we use 100/50 to ensure a partial fill as we want to go down the "left fill" + // branch in the contract twice for this test. + const signedOrderRight2 = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(50), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Match signedOrderLeft with signedOrderRight2 + const leftTakerAssetFilledAmount = signedOrderRight.makerAssetAmount; + const rightTakerAssetFilledAmount = new BigNumber(0); + await matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight2, + zrxToken.address, + takerAddress, + newERC20BalancesByOwner, + erc721TokenIdsByOwner, + leftTakerAssetFilledAmount, + rightTakerAssetFilledAmount, + ); + // Verify left order was fully filled + const leftOrderInfo2: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( + signedOrderLeft, + ); + expect(leftOrderInfo2[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + // Verify second right order was partially filled + const rightOrderInfo2: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( + signedOrderRight2, + ); + expect(rightOrderInfo2[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); + }); + + it('should transfer the correct amounts when consecutive calls are used to completely fill the right order', async () => { + // Create orders to match + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(50), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Match orders + let newERC20BalancesByOwner: ERC20BalancesByOwner; + let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner; + [ + newERC20BalancesByOwner, + newERC721TokenIdsByOwner, + ] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight, + zrxToken.address, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + ); + // Verify left order was partially filled + const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + // Verify right order was fully filled + const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( + signedOrderRight, + ); + expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); + // Create second left order + // Note: This order needs makerAssetAmount=96/takerAssetAmount=48 to fully fill the right order. + // However, we use 100/50 to ensure a partial fill as we want to go down the "right fill" + // branch in the contract twice for this test. + const signedOrderLeft2 = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(50), 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + // Match signedOrderLeft2 with signedOrderRight + const leftTakerAssetFilledAmount = new BigNumber(0); + const takerAmountReceived = newERC20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress].minus( + erc20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress], + ); + const rightTakerAssetFilledAmount = signedOrderLeft.makerAssetAmount.minus(takerAmountReceived); + await matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft2, + signedOrderRight, + zrxToken.address, + takerAddress, + newERC20BalancesByOwner, + erc721TokenIdsByOwner, + leftTakerAssetFilledAmount, + rightTakerAssetFilledAmount, + ); + // Verify second left order was partially filled + const leftOrderInfo2: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( + signedOrderLeft2, + ); + expect(leftOrderInfo2[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); + // Verify right order was fully filled + const rightOrderInfo2: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( + signedOrderRight, + ); + expect(rightOrderInfo2[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + }); + + it('should transfer the correct amounts if fee recipient is the same across both matched orders', async () => { + const feeRecipientAddress = feeRecipientAddressLeft; + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + feeRecipientAddress, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18), + feeRecipientAddress, + }); + // Match orders + await matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight, + zrxToken.address, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + ); + }); + + it('should transfer the correct amounts if taker is also the left order maker', async () => { + // Create orders to match + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Match orders + takerAddress = signedOrderLeft.makerAddress; + await matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight, + zrxToken.address, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + ); + }); + + it('should transfer the correct amounts if taker is also the right order maker', async () => { + // Create orders to match + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Match orders + takerAddress = signedOrderRight.makerAddress; + await matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight, + zrxToken.address, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + ); + }); + + it('should transfer the correct amounts if taker is also the left fee recipient', async () => { + // Create orders to match + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Match orders + takerAddress = feeRecipientAddressLeft; + await matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight, + zrxToken.address, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + ); + }); + + it('should transfer the correct amounts if taker is also the right fee recipient', async () => { + // Create orders to match + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Match orders + takerAddress = feeRecipientAddressRight; + await matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight, + zrxToken.address, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + ); + }); + + it('should transfer the correct amounts if left maker is the left fee recipient and right maker is the right fee recipient', async () => { + // Create orders to match + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + feeRecipientAddress: makerAddressLeft, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18), + feeRecipientAddress: makerAddressRight, + }); + // Match orders + await matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight, + zrxToken.address, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + ); + }); + + it('Should not transfer any amounts if left order is not fillable', async () => { + // Create orders to match + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Cancel left order + await exchangeWrapper.cancelOrderAsync(signedOrderLeft, signedOrderLeft.makerAddress); + // Match orders + await exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress); + // Verify balances did not change + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20BalancesByOwner); + }); + + it('Should not transfer any amounts if right order is not fillable', async () => { + // Create orders to match + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Cancel right order + await exchangeWrapper.cancelOrderAsync(signedOrderRight, signedOrderRight.makerAddress); + // Match orders + await exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress); + // Verify balances did not change + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20BalancesByOwner); + }); + + it('should throw if there is not a positive spread', async () => { + // Create orders to match + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Match orders + return expect( + matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight, + zrxToken.address, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + ), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if the left maker asset is not equal to the right taker asset ', async () => { + // Create orders to match + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Match orders + return expect( + matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight, + zrxToken.address, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + ), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if the right maker asset is not equal to the left taker asset', async () => { + // Create orders to match + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Match orders + return expect( + matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight, + zrxToken.address, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + ), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should transfer correct amounts when left order maker asset is an ERC721 token', async () => { + // Create orders to match + const erc721TokenToTransfer = erc721LeftMakerAssetIds[0]; + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC721ProxyData(defaultERC721AssetAddress, erc721TokenToTransfer), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetAmount: new BigNumber(1), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC721ProxyData(defaultERC721AssetAddress, erc721TokenToTransfer), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: new BigNumber(1), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Match orders + await matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight, + zrxToken.address, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + ); + // Verify left order was fully filled + const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + // Verify right order was fully filled + const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( + signedOrderRight, + ); + expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + }); + + it('should transfer correct amounts when right order maker asset is an ERC721 token', async () => { + // Create orders to match + const erc721TokenToTransfer = erc721RightMakerAssetIds[0]; + const signedOrderLeft = orderFactoryLeft.newSignedOrder({ + makerAddress: makerAddressLeft, + makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC721ProxyData(defaultERC721AssetAddress, erc721TokenToTransfer), + makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: new BigNumber(1), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = orderFactoryRight.newSignedOrder({ + makerAddress: makerAddressRight, + makerAssetData: assetProxyUtils.encodeERC721ProxyData(defaultERC721AssetAddress, erc721TokenToTransfer), + takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetAmount: new BigNumber(1), + takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Match orders + await matchOrderTester.matchOrdersAndVerifyBalancesAsync( + signedOrderLeft, + signedOrderRight, + zrxToken.address, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + ); + // Verify left order was fully filled + const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + // Verify right order was fully filled + const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( + signedOrderRight, + ); + expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + }); + }); +}); // tslint:disable-line:max-file-line-count diff --git a/packages/contracts/test/exchange/transactions.ts b/packages/contracts/test/exchange/transactions.ts index a71b50a61..482475554 100644 --- a/packages/contracts/test/exchange/transactions.ts +++ b/packages/contracts/test/exchange/transactions.ts @@ -21,7 +21,7 @@ import { TransactionFactory } from '../../src/utils/transaction_factory'; import { AssetProxyId, ERC20BalancesByOwner, - ExchangeContractErrs, + ExchangeStatus, OrderStruct, SignatureType, SignedOrder, @@ -197,7 +197,7 @@ describe('Exchange transactions', () => { it('should cancel the order when signed by maker and called by sender', async () => { await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); + const res = await exchangeWrapper.fillOrderAsync(signedOrder, senderAddress); const newBalances = await erc20Wrapper.getBalancesAsync(); expect(newBalances).to.deep.equal(erc20Balances); }); diff --git a/packages/contracts/test/utils/match_order_tester.ts b/packages/contracts/test/utils/match_order_tester.ts new file mode 100644 index 000000000..91c2ab0a3 --- /dev/null +++ b/packages/contracts/test/utils/match_order_tester.ts @@ -0,0 +1,353 @@ +import { LogWithDecodedArgs, ZeroEx } from '0x.js'; +import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { BigNumber } from '@0xproject/utils'; +import * as chai from 'chai'; +import ethUtil = require('ethereumjs-util'); +import * as _ from 'lodash'; + +import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token'; +import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token'; +import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy'; +import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy'; +import { + CancelContractEventArgs, + ExchangeContract, + FillContractEventArgs, +} from '../../src/contract_wrappers/generated/exchange'; +import { assetProxyUtils } from '../../src/utils/asset_proxy_utils'; +import { constants } from '../../src/utils/constants'; +import { crypto } from '../../src/utils/crypto'; +import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; +import { ERC721Wrapper } from '../../src/utils/erc721_wrapper'; +import { ExchangeWrapper } from '../../src/utils/exchange_wrapper'; +import { OrderFactory } from '../../src/utils/order_factory'; +import { orderUtils } from '../../src/utils/order_utils'; +import { + AssetProxyId, + ContractName, + ERC20BalancesByOwner, + ERC721TokenIdsByOwner, + ExchangeStatus, + SignedOrder, + TransferAmountsByMatchOrders as TransferAmounts, +} from '../../src/utils/types'; +import { chaiSetup } from '../utils/chai_setup'; +import { deployer } from '../utils/deployer'; +import { provider, web3Wrapper } from '../utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +export class MatchOrderTester { + private _exchangeWrapper: ExchangeWrapper; + private _erc20Wrapper: ERC20Wrapper; + private _erc721Wrapper: ERC721Wrapper; + + /// @dev Calculates the expected balances of order makers, fee recipients, and the taker, + /// as a result of matching two orders. + /// @param signedOrderLeft First matched order. + /// @param signedOrderRight Second matched order. + /// @param feeTokenAddress Address of ERC20 fee token. + /// @param takerAddress Address of taker (the address who matched the two orders) + /// @param erc20BalancesByOwner Current ERC20 balances. + /// @param erc721TokenIdsByOwner Current ERC721 token owners. + /// @param expectedTransferAmounts A struct containing the expected transfer amounts. + /// @return Expected ERC20 balances & ERC721 token owners after orders have been matched. + private static _calculateExpectedBalances( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + feeTokenAddress: string, + takerAddress: string, + erc20BalancesByOwner: ERC20BalancesByOwner, + erc721TokenIdsByOwner: ERC721TokenIdsByOwner, + expectedTransferAmounts: TransferAmounts, + ): [ERC20BalancesByOwner, ERC721TokenIdsByOwner] { + const makerAddressLeft = signedOrderLeft.makerAddress; + const makerAddressRight = signedOrderRight.makerAddress; + const feeRecipientAddressLeft = signedOrderLeft.feeRecipientAddress; + const feeRecipientAddressRight = signedOrderRight.feeRecipientAddress; + // Operations are performed on copies of the balances + const expectedNewERC20BalancesByOwner = _.cloneDeep(erc20BalancesByOwner); + const expectedNewERC721TokenIdsByOwner = _.cloneDeep(erc721TokenIdsByOwner); + // Left Maker Asset (Right Taker Asset) + const makerAssetProxyIdLeft = assetProxyUtils.decodeProxyDataId(signedOrderLeft.makerAssetData); + if (makerAssetProxyIdLeft === AssetProxyId.ERC20) { + // Decode asset data + const makerAssetAddressLeft = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.makerAssetData); + const takerAssetAddressRight = makerAssetAddressLeft; + // Left Maker + expectedNewERC20BalancesByOwner[makerAddressLeft][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ + makerAddressLeft + ][makerAssetAddressLeft].minus(expectedTransferAmounts.amountSoldByLeftMaker); + // Right Maker + expectedNewERC20BalancesByOwner[makerAddressRight][ + takerAssetAddressRight + ] = expectedNewERC20BalancesByOwner[makerAddressRight][takerAssetAddressRight].add( + expectedTransferAmounts.amountReceivedByRightMaker, + ); + // Taker + expectedNewERC20BalancesByOwner[takerAddress][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ + takerAddress + ][makerAssetAddressLeft].add(expectedTransferAmounts.amountReceivedByTaker); + } else if (makerAssetProxyIdLeft === AssetProxyId.ERC721) { + // Decode asset data + let makerAssetAddressLeft; + let makerAssetIdLeft; + [makerAssetAddressLeft, makerAssetIdLeft] = assetProxyUtils.decodeERC721ProxyData( + signedOrderLeft.makerAssetData, + ); + const takerAssetAddressRight = makerAssetAddressLeft; + const takerAssetIdRight = makerAssetIdLeft; + // Left Maker + _.remove(expectedNewERC721TokenIdsByOwner[makerAddressLeft][makerAssetAddressLeft], makerAssetIdLeft); + // Right Maker + expectedNewERC721TokenIdsByOwner[makerAddressRight][takerAssetAddressRight].push(takerAssetIdRight); + // Taker: Since there is only 1 asset transferred, the taker does not receive any of the left maker asset. + } + // Left Taker Asset (Right Maker Asset) + // Note: This exchange is only between the order makers: the Taker does not receive any of the left taker asset. + const takerAssetProxyIdLeft = assetProxyUtils.decodeProxyDataId(signedOrderLeft.takerAssetData); + if (takerAssetProxyIdLeft === AssetProxyId.ERC20) { + // Decode asset data + const takerAssetAddressLeft = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.takerAssetData); + const makerAssetAddressRight = takerAssetAddressLeft; + // Left Maker + expectedNewERC20BalancesByOwner[makerAddressLeft][takerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ + makerAddressLeft + ][takerAssetAddressLeft].add(expectedTransferAmounts.amountReceivedByLeftMaker); + // Right Maker + expectedNewERC20BalancesByOwner[makerAddressRight][ + makerAssetAddressRight + ] = expectedNewERC20BalancesByOwner[makerAddressRight][makerAssetAddressRight].minus( + expectedTransferAmounts.amountSoldByRightMaker, + ); + } else if (takerAssetProxyIdLeft === AssetProxyId.ERC721) { + // Decode asset data + let makerAssetAddressRight; + let makerAssetIdRight; + [makerAssetAddressRight, makerAssetIdRight] = assetProxyUtils.decodeERC721ProxyData( + signedOrderRight.makerAssetData, + ); + const takerAssetAddressLeft = makerAssetAddressRight; + const takerAssetIdLeft = makerAssetIdRight; + // Right Maker + _.remove(expectedNewERC721TokenIdsByOwner[makerAddressRight][makerAssetAddressRight], makerAssetIdRight); + // Left Maker + expectedNewERC721TokenIdsByOwner[makerAddressLeft][takerAssetAddressLeft].push(takerAssetIdLeft); + } + // Left Maker Fees + expectedNewERC20BalancesByOwner[makerAddressLeft][feeTokenAddress] = expectedNewERC20BalancesByOwner[ + makerAddressLeft + ][feeTokenAddress].minus(expectedTransferAmounts.feePaidByLeftMaker); + // Right Maker Fees + expectedNewERC20BalancesByOwner[makerAddressRight][feeTokenAddress] = expectedNewERC20BalancesByOwner[ + makerAddressRight + ][feeTokenAddress].minus(expectedTransferAmounts.feePaidByRightMaker); + // Taker Fees + expectedNewERC20BalancesByOwner[takerAddress][feeTokenAddress] = expectedNewERC20BalancesByOwner[takerAddress][ + feeTokenAddress + ].minus(expectedTransferAmounts.totalFeePaidByTaker); + // Left Fee Recipient Fees + expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][feeTokenAddress] = expectedNewERC20BalancesByOwner[ + feeRecipientAddressLeft + ][feeTokenAddress].add(expectedTransferAmounts.feeReceivedLeft); + // Right Fee Recipient Fees + expectedNewERC20BalancesByOwner[feeRecipientAddressRight][feeTokenAddress] = expectedNewERC20BalancesByOwner[ + feeRecipientAddressRight + ][feeTokenAddress].add(expectedTransferAmounts.feeReceivedRight); + + return [expectedNewERC20BalancesByOwner, expectedNewERC721TokenIdsByOwner]; + } + + /// @dev Compares a pair of ERC20 balances and a pair of ERC721 token owners. + /// @param expectedNewERC20BalancesByOwner Expected ERC20 balances. + /// @param realERC20BalancesByOwner Actual ERC20 balances. + /// @param expectedNewERC721TokenIdsByOwner Expected ERC721 token owners. + /// @param realERC721TokenIdsByOwner Actual ERC20 token owners. + /// @return True only if ERC20 balances match and ERC721 token owners match. + private static _compareExpectedAndRealBalances( + expectedNewERC20BalancesByOwner: ERC20BalancesByOwner, + realERC20BalancesByOwner: ERC20BalancesByOwner, + expectedNewERC721TokenIdsByOwner: ERC721TokenIdsByOwner, + realERC721TokenIdsByOwner: ERC721TokenIdsByOwner, + ) { + // ERC20 Balances + const erc20BalancesMatch = _.isEqual(expectedNewERC20BalancesByOwner, realERC20BalancesByOwner); + if (!erc20BalancesMatch) { + return false; + } + // ERC721 Token Ids + const sortedExpectedNewERC721TokenIdsByOwner = _.mapValues( + expectedNewERC721TokenIdsByOwner, + tokenIdsByOwner => { + _.mapValues(tokenIdsByOwner, tokenIds => { + _.sortBy(tokenIds); + }); + }, + ); + const sortedNewERC721TokenIdsByOwner = _.mapValues(realERC721TokenIdsByOwner, tokenIdsByOwner => { + _.mapValues(tokenIdsByOwner, tokenIds => { + _.sortBy(tokenIds); + }); + }); + const erc721TokenIdsMatch = _.isEqual(sortedExpectedNewERC721TokenIdsByOwner, sortedNewERC721TokenIdsByOwner); + return erc721TokenIdsMatch; + } + + /// @dev Constructs new MatchOrderTester. + /// @param exchangeWrapper Used to call to the Exchange. + /// @param erc20Wrapper Used to fetch ERC20 balances. + /// @param erc721Wrapper Used to fetch ERC721 token owners. + constructor(exchangeWrapper: ExchangeWrapper, erc20Wrapper: ERC20Wrapper, erc721Wrapper: ERC721Wrapper) { + this._exchangeWrapper = exchangeWrapper; + this._erc20Wrapper = erc20Wrapper; + this._erc721Wrapper = erc721Wrapper; + } + + /// @dev Matches two complementary orders and validates results. + /// Validation either succeeds or throws. + /// @param signedOrderLeft First matched order. + /// @param signedOrderRight Second matched order. + /// @param feeTokenAddress Address of ERC20 fee token. + /// @param takerAddress Address of taker (the address who matched the two orders) + /// @param erc20BalancesByOwner Current ERC20 balances. + /// @param erc721TokenIdsByOwner Current ERC721 token owners. + /// @param initialTakerAssetFilledAmountLeft Current amount the left order has been filled. + /// @param initialTakerAssetFilledAmountRight Current amount the right order has been filled. + /// @return New ERC20 balances & ERC721 token owners. + public async matchOrdersAndVerifyBalancesAsync( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + feeTokenAddress: string, + takerAddress: string, + erc20BalancesByOwner: ERC20BalancesByOwner, + erc721TokenIdsByOwner: ERC721TokenIdsByOwner, + initialTakerAssetFilledAmountLeft?: BigNumber, + initialTakerAssetFilledAmountRight?: BigNumber, + ): Promise<[ERC20BalancesByOwner, ERC721TokenIdsByOwner]> { + // Test setup & verify preconditions + const makerAddressLeft = signedOrderLeft.makerAddress; + const makerAddressRight = signedOrderRight.makerAddress; + const feeRecipientAddressLeft = signedOrderLeft.feeRecipientAddress; + const feeRecipientAddressRight = signedOrderRight.feeRecipientAddress; + // Verify Left order preconditions + const takerAssetFilledAmountBeforeLeft = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( + orderUtils.getOrderHashHex(signedOrderLeft), + ); + const expectedLeftOrderFillAmoutBeforeMatch = initialTakerAssetFilledAmountLeft + ? initialTakerAssetFilledAmountLeft + : new BigNumber(0); + expect(takerAssetFilledAmountBeforeLeft).to.be.bignumber.equal(expectedLeftOrderFillAmoutBeforeMatch); + // Verify Right order preconditions + const takerAssetFilledAmountBeforeRight = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( + orderUtils.getOrderHashHex(signedOrderRight), + ); + const expectedRightOrderFillAmoutBeforeMatch = initialTakerAssetFilledAmountRight + ? initialTakerAssetFilledAmountRight + : new BigNumber(0); + expect(takerAssetFilledAmountBeforeRight).to.be.bignumber.equal(expectedRightOrderFillAmoutBeforeMatch); + // Match left & right orders + await this._exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress); + const newERC20BalancesByOwner = await this._erc20Wrapper.getBalancesAsync(); + const newERC721TokenIdsByOwner = await this._erc721Wrapper.getBalancesAsync(); + // Calculate expected balance changes + const expectedTransferAmounts = await this._calculateExpectedTransferAmountsAsync( + signedOrderLeft, + signedOrderRight, + expectedLeftOrderFillAmoutBeforeMatch, + expectedRightOrderFillAmoutBeforeMatch, + ); + let expectedERC20BalancesByOwner: ERC20BalancesByOwner; + let expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner; + [expectedERC20BalancesByOwner, expectedERC721TokenIdsByOwner] = MatchOrderTester._calculateExpectedBalances( + signedOrderLeft, + signedOrderRight, + feeTokenAddress, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + // Assert our expected balances are equal to the actual balances + const expectedBalancesMatchRealBalances = MatchOrderTester._compareExpectedAndRealBalances( + expectedERC20BalancesByOwner, + newERC20BalancesByOwner, + expectedERC721TokenIdsByOwner, + newERC721TokenIdsByOwner, + ); + expect(expectedBalancesMatchRealBalances).to.be.true(); + return [newERC20BalancesByOwner, newERC721TokenIdsByOwner]; + } + + /// @dev Calculates expected transfer amounts between order makers, fee recipients, and + /// the taker when two orders are matched. + /// @param signedOrderLeft First matched order. + /// @param signedOrderRight Second matched order. + /// @param expectedLeftOrderFillAmoutBeforeMatch How much we expect the left order has been filled, prior to matching orders. + /// @param expectedRightOrderFillAmoutBeforeMatch How much we expect the right order has been filled, prior to matching orders. + /// @return TransferAmounts A struct containing the expected transfer amounts. + private async _calculateExpectedTransferAmountsAsync( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + expectedLeftOrderFillAmoutBeforeMatch: BigNumber, + expectedRightOrderFillAmoutBeforeMatch: BigNumber, + ): Promise { + let amountBoughtByLeftMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( + orderUtils.getOrderHashHex(signedOrderLeft), + ); + amountBoughtByLeftMaker = amountBoughtByLeftMaker.minus(expectedLeftOrderFillAmoutBeforeMatch); + const amountSoldByLeftMaker = amountBoughtByLeftMaker + .times(signedOrderLeft.makerAssetAmount) + .dividedToIntegerBy(signedOrderLeft.takerAssetAmount); + const amountReceivedByRightMaker = amountBoughtByLeftMaker + .times(signedOrderRight.takerAssetAmount) + .dividedToIntegerBy(signedOrderRight.makerAssetAmount); + const amountReceivedByTaker = amountSoldByLeftMaker.minus(amountReceivedByRightMaker); + let amountBoughtByRightMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( + orderUtils.getOrderHashHex(signedOrderRight), + ); + amountBoughtByRightMaker = amountBoughtByRightMaker.minus(expectedRightOrderFillAmoutBeforeMatch); + const amountSoldByRightMaker = amountBoughtByRightMaker + .times(signedOrderRight.makerAssetAmount) + .dividedToIntegerBy(signedOrderRight.takerAssetAmount); + const amountReceivedByLeftMaker = amountSoldByRightMaker; + const feePaidByLeftMaker = signedOrderLeft.makerFee + .times(amountSoldByLeftMaker) + .dividedToIntegerBy(signedOrderLeft.makerAssetAmount); + const feePaidByRightMaker = signedOrderRight.makerFee + .times(amountSoldByRightMaker) + .dividedToIntegerBy(signedOrderRight.makerAssetAmount); + const feePaidByTakerLeft = signedOrderLeft.takerFee + .times(amountSoldByLeftMaker) + .dividedToIntegerBy(signedOrderLeft.makerAssetAmount); + const feePaidByTakerRight = signedOrderRight.takerFee + .times(amountSoldByRightMaker) + .dividedToIntegerBy(signedOrderRight.makerAssetAmount); + const totalFeePaidByTaker = feePaidByTakerLeft.add(feePaidByTakerRight); + const feeReceivedLeft = feePaidByLeftMaker.add(feePaidByTakerLeft); + const feeReceivedRight = feePaidByRightMaker.add(feePaidByTakerRight); + // Return values + const expectedTransferAmounts = { + // Left Maker + amountBoughtByLeftMaker, + amountSoldByLeftMaker, + amountReceivedByLeftMaker, + feePaidByLeftMaker, + // Right Maker + amountBoughtByRightMaker, + amountSoldByRightMaker, + amountReceivedByRightMaker, + feePaidByRightMaker, + // Taker + amountReceivedByTaker, + feePaidByTakerLeft, + feePaidByTakerRight, + totalFeePaidByTaker, + // Fee Recipients + feeReceivedLeft, + feeReceivedRight, + }; + return expectedTransferAmounts; + } +} -- cgit v1.2.3 From 68fa7ae2a3ab66d6798985d2d89d236dc4ec42dc Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 10 May 2018 14:25:17 -0700 Subject: Removed isRoundingError from mixin header --- .../current/protocol/Exchange/mixins/MExchangeCore.sol | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) (limited to 'packages/contracts') 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 eb97be8b0..416cf426d 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol @@ -169,17 +169,8 @@ contract MExchangeCore is public returns (bool); + /// @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; - -/* - /// @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); */ } -- cgit v1.2.3 From f378406d155bbb7c4679904756c897ed2f4388c1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 10 May 2018 14:52:06 -0700 Subject: Updated remaining contracts to v0.4.23 --- .../src/contracts/current/protocol/Exchange/MixinMatchOrders.sol | 2 +- .../src/contracts/current/protocol/Exchange/MixinTransactions.sol | 2 +- .../src/contracts/current/protocol/Exchange/libs/LibStatus.sol | 2 +- .../src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index 863128a06..76b021919 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -11,7 +11,7 @@ limitations under the License. */ -pragma solidity ^0.4.21; +pragma solidity ^0.4.23; pragma experimental ABIEncoderV2; import "./mixins/MExchangeCore.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol index 7f12834a3..b7d70682e 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol @@ -15,7 +15,7 @@ limitations under the License. */ -pragma solidity ^0.4.21; +pragma solidity ^0.4.23; import "./mixins/MSignatureValidator.sol"; import "./mixins/MTransactions.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol index 42b9cb65d..fea58f64e 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.21; +pragma solidity ^0.4.23; pragma experimental ABIEncoderV2; contract LibStatus { diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol index abd15182e..2aedeb3e6 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol @@ -15,7 +15,7 @@ limitations under the License. */ -pragma solidity ^0.4.21; +pragma solidity ^0.4.23; pragma experimental ABIEncoderV2; import "../libs/LibOrder.sol"; -- cgit v1.2.3 From fa7570352ce65dc58df6969c5a24cf3f487e738f Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 11 May 2018 11:32:12 -0700 Subject: Added require reasons to MixinMatchOrders and cleaned up some comments. --- .../protocol/Exchange/MixinExchangeCore.sol | 18 +++++---- .../current/protocol/Exchange/MixinMatchOrders.sol | 44 ++++++++++++++++------ .../current/protocol/Exchange/MixinSettlement.sol | 8 +++- .../protocol/Exchange/libs/LibExchangeErrors.sol | 8 ++++ .../protocol/Exchange/mixins/MExchangeCore.sol | 2 +- packages/contracts/src/utils/types.ts | 35 ++++++++--------- 6 files changed, 75 insertions(+), 40 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index 58b5fa6b1..981c5984e 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -53,7 +53,7 @@ contract MixinExchangeCore is ////// Core exchange functions ////// - /// @dev Gets information about an order. + /// @dev Gets information about an order: status, hash, and amount filled. /// @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. @@ -105,14 +105,12 @@ contract MixinExchangeCore is orderStatus = uint8(Status.ORDER_CANCELLED); return (orderStatus, orderHash, takerAssetFilledAmount); } - - // Validate order is not cancelled if (makerEpoch[order.makerAddress] > order.salt) { orderStatus = uint8(Status.ORDER_CANCELLED); return (orderStatus, orderHash, takerAssetFilledAmount); } - // Order is Fillable + // All other statuses are ruled out: order is Fillable orderStatus = uint8(Status.ORDER_FILLABLE); return (orderStatus, orderHash, takerAssetFilledAmount); } @@ -136,6 +134,8 @@ contract MixinExchangeCore is internal { // Ensure order is valid + // An order can only be filled if it is Status.FILLABLE; + // however, only invalid statuses result in a throw. require( orderStatus != uint8(Status.ORDER_INVALID_MAKER_ASSET_AMOUNT), INVALID_ORDER_MAKER_ASSET_AMOUNT @@ -269,7 +269,7 @@ contract MixinExchangeCore is public returns (FillResults memory fillResults) { - // Fetch current order status + // Fetch order info bytes32 orderHash; uint8 orderStatus; uint256 takerAssetFilledAmount; @@ -309,6 +309,8 @@ contract MixinExchangeCore is internal { // Ensure order is valid + // An order can only be cancelled if it is Status.FILLABLE; + // however, only invalid statuses result in a throw. require( orderStatus != uint8(Status.ORDER_INVALID_MAKER_ASSET_AMOUNT), INVALID_ORDER_MAKER_ASSET_AMOUNT @@ -372,9 +374,11 @@ contract MixinExchangeCore is } /// @dev After calling, the order can not be filled anymore. - /// @param order Order struct containing order specifications. + /// Throws if order is invalid or sender does not have permission to cancel. + /// @param order Order to cancel. Order must be Status.FILLABLE. /// @return True if the order state changed to cancelled. - /// False if the transaction was already cancelled or expired. + /// False if the order was order was in a valid, but + /// unfillable state (see LibStatus.STATUS for order states) function cancelOrder(Order memory order) public returns (bool) diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index 76b021919..a333b8221 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -23,6 +23,7 @@ import "./libs/LibMath.sol"; import "./libs/LibOrder.sol"; import "./libs/LibStatus.sol"; import "../../utils/LibBytes/LibBytes.sol"; +import "./libs/LibExchangeErrors.sol"; contract MixinMatchOrders is SafeMath, @@ -30,6 +31,7 @@ contract MixinMatchOrders is LibMath, LibStatus, LibOrder, + LibExchangeErrors, MExchangeCore, MMatchOrders, MSettlement, @@ -45,22 +47,29 @@ contract MixinMatchOrders is internal { // The leftOrder maker asset must be the same as the rightOrder taker asset. - require(areBytesEqual(leftOrder.makerAssetData, rightOrder.takerAssetData)); + require( + areBytesEqual(leftOrder.makerAssetData, rightOrder.takerAssetData), + ASSET_MISMATCH_MAKER_TAKER + ); // The leftOrder taker asset must be the same as the rightOrder maker asset. - require(areBytesEqual(leftOrder.takerAssetData, rightOrder.makerAssetData)); + require( + areBytesEqual(leftOrder.takerAssetData, rightOrder.makerAssetData), + ASSET_MISMATCH_TAKER_MAKER + ); // 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) + safeMul(leftOrder.takerAssetAmount, rightOrder.takerAssetAmount), + NEGATIVE_SPREAD ); } @@ -69,11 +78,21 @@ contract MixinMatchOrders is 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. + // The right order must spend at least as much as we're transferring to the left order. + require( + matchedFillResults.right.makerAssetFilledAmount >= + matchedFillResults.left.takerAssetFilledAmount, + MISCALCULATED_TRANSFER_AMOUNTS + ); + // If the amount transferred from the right order is different 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)); + require( + !isRoundingError( + matchedFillResults.right.makerAssetFilledAmount, + matchedFillResults.left.takerAssetFilledAmount, + 1), + ROUNDING_ERROR_TRANSFER_AMOUNTS + ); } /// @dev Calculates partial value given a numerator and denominator. @@ -89,7 +108,10 @@ contract MixinMatchOrders is internal pure returns (uint256 partialAmount) { - require(!isRoundingError(numerator, denominator, target)); + require( + !isRoundingError(numerator, denominator, target), + ROUNDING_ERROR_ON_PARTIAL_AMOUNT + ); return getPartialAmount(numerator, denominator, target); } @@ -136,7 +158,7 @@ contract MixinMatchOrders is leftOrderAmountToFill = leftTakerAssetAmountRemaining; // The right order receives an amount proportional to how much was spent. - // TODO: Ensure rounding error is in the correct direction. + // TODO: Can we ensure rounding error is in the correct direction? rightOrderAmountToFill = safeGetPartialAmount( rightOrder.takerAssetAmount, rightOrder.makerAssetAmount, @@ -146,7 +168,7 @@ contract MixinMatchOrders is rightOrderAmountToFill = rightTakerAssetAmountRemaining; // The left order receives an amount proportional to how much was spent. - // TODO: Ensure rounding error is in the correct direction. + // TODO: Can we ensure rounding error is in the correct direction? leftOrderAmountToFill = safeGetPartialAmount( rightOrder.makerAssetAmount, rightOrder.takerAssetAmount, diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol index 3b67051d2..cde64ba74 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol @@ -23,9 +23,11 @@ import "./mixins/MAssetProxyDispatcher.sol"; import "./libs/LibOrder.sol"; import "./libs/LibMath.sol"; import "./mixins/MMatchOrders.sol"; +import "./mixins/LibExchangeErrors.sol"; contract MixinSettlement is LibMath, + LibExchangeErrors, MMatchOrders, MSettlement, MAssetProxyDispatcher @@ -134,7 +136,11 @@ contract MixinSettlement is // rightOrder.MakerAsset == leftOrder.TakerAsset // leftOrder.takerAssetFilledAmount ~ rightOrder.makerAssetFilledAmount // The change goes to right, not to taker. - assert(matchedFillResults.right.makerAssetFilledAmount >= matchedFillResults.left.takerAssetFilledAmount); + require( + matchedFillResults.right.makerAssetFilledAmount >= + matchedFillResults.left.takerAssetFilledAmount, + MISCALCULATED_TRANSFER_AMOUNTS + ); dispatchTransferFrom( rightOrder.makerAssetData, rightOrder.makerAddress, 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 bf048e815..ba055dd8e 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol @@ -48,4 +48,12 @@ contract LibExchangeErrors { string constant INVALID_SIGNATURE_LENGTH = "Invalid signature length."; string constant ILLEGAL_SIGNATURE_TYPE = "Illegal signature type."; string constant UNSUPPORTED_SIGNATURE_TYPE = "Unsupported signature type."; + + // Order matching revert reasons + string constant ASSET_MISMATCH_MAKER_TAKER = "Left order maker asset is different from right order taker asset."; + string constant ASSET_MISMATCH_TAKER_MAKER = "Left order taker asset is different from right order maker asset."; + string constant NEGATIVE_SPREAD = "Matched orders must have a positive spread."; + string constant MISCALCULATED_TRANSFER_AMOUNTS = "A miscalculation occurred: the left maker would receive more than the right maker would spend."; + string constant ROUNDING_ERROR_TRANSFER_AMOUNTS = "A rounding error occurred when calculating transfer amounts for matched orders."; + string constant ROUNDING_ERROR_ON_PARTIAL_AMOUNT = "A rounding error occurred when calculating partial transfer amounts."; } 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 416cf426d..cfd2678ba 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol @@ -65,7 +65,7 @@ contract MExchangeCore is LibFillResults.FillResults memory fillResults) internal; - /// @dev Gets information about an order. + /// @dev Gets information about an order: status, hash, and amount filled. /// @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. diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts index ce7fbcbe1..0e3b2c9a8 100644 --- a/packages/contracts/src/utils/types.ts +++ b/packages/contracts/src/utils/types.ts @@ -75,26 +75,21 @@ export interface Token { } export enum ExchangeStatus { - /// 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 + INVALID, + SUCCESS, + ROUNDING_ERROR_TOO_LARGE, + INSUFFICIENT_BALANCE_OR_ALLOWANCE, + TAKER_ASSET_FILL_AMOUNT_TOO_LOW, + INVALID_SIGNATURE, + INVALID_SENDER, + INVALID_TAKER, + INVALID_MAKER, + ORDER_INVALID_MAKER_ASSET_AMOUNT, + ORDER_INVALID_TAKER_ASSET_AMOUNT, + ORDER_FILLABLE, + ORDER_EXPIRED, + ORDER_FULLY_FILLED, + ORDER_CANCELLED, } export enum ContractName { -- cgit v1.2.3 From 5735095521aeda75b257236e34d6ec76c16df8ab Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 11 May 2018 14:51:00 -0700 Subject: Style changes to atomic order matching --- .../protocol/Exchange/MixinExchangeCore.sol | 158 +++++++------ .../current/protocol/Exchange/MixinMatchOrders.sol | 257 ++++++++++----------- .../current/protocol/Exchange/MixinSettlement.sol | 26 +-- .../protocol/Exchange/libs/LibExchangeErrors.sol | 1 - .../current/protocol/Exchange/libs/LibMath.sol | 21 ++ .../protocol/Exchange/mixins/MExchangeCore.sol | 45 ++-- .../protocol/Exchange/mixins/MMatchOrders.sol | 54 ++--- .../protocol/Exchange/mixins/MSettlement.sol | 15 +- packages/contracts/src/utils/exchange_wrapper.ts | 14 +- packages/contracts/test/exchange/match_orders.ts | 2 +- .../contracts/test/utils/match_order_tester.ts | 1 - 11 files changed, 288 insertions(+), 306 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index 981c5984e..2c23575c7 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -53,6 +53,20 @@ contract MixinExchangeCore is ////// Core exchange functions ////// + /// @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 Gets information about an order: status, hash, and amount filled. /// @param order Order to gather information on. /// @return status Status of order. Statuses are defined in the LibStatus.Status struct. @@ -64,20 +78,11 @@ contract MixinExchangeCore is returns ( uint8 orderStatus, bytes32 orderHash, - uint256 takerAssetFilledAmount) + uint256 takerAssetFilledAmount + ) { - // Compute the order hash and fetch filled amount + // Compute the order hash orderHash = getOrderHash(order); - takerAssetFilledAmount = filled[orderHash]; - - // 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 @@ -88,15 +93,18 @@ contract MixinExchangeCore is return (orderStatus, orderHash, takerAssetFilledAmount); } - // Validate order expiration - if (block.timestamp >= order.expirationTimeSeconds) { - orderStatus = uint8(Status.ORDER_EXPIRED); + // 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); } - // Validate order availability - if (takerAssetFilledAmount >= order.takerAssetAmount) { - orderStatus = uint8(Status.ORDER_FULLY_FILLED); + // Validate order expiration + if (block.timestamp >= order.expirationTimeSeconds) { + orderStatus = uint8(Status.ORDER_EXPIRED); return (orderStatus, orderHash, takerAssetFilledAmount); } @@ -110,6 +118,13 @@ contract MixinExchangeCore is return (orderStatus, orderHash, takerAssetFilledAmount); } + // Fetch filled amount and validate order availability + takerAssetFilledAmount = filled[orderHash]; + if (takerAssetFilledAmount >= order.takerAssetAmount) { + orderStatus = uint8(Status.ORDER_FULLY_FILLED); + return (orderStatus, orderHash, takerAssetFilledAmount); + } + // All other statuses are ruled out: order is Fillable orderStatus = uint8(Status.ORDER_FILLABLE); return (orderStatus, orderHash, takerAssetFilledAmount); @@ -119,18 +134,18 @@ contract MixinExchangeCore is /// @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 takerAssetFilledAmount Amount of order already filled. /// @param takerAssetFillAmount Desired amount of order to fill by taker. - function validateFillOrderContextOrRevert( + /// @param signature Proof that the orders was created by its maker. + function validateFillOrRevert( Order memory order, uint8 orderStatus, bytes32 orderHash, - uint256 takerAssetFilledAmount, - bytes memory signature, address takerAddress, - uint256 takerAssetFillAmount) + uint256 takerAssetFilledAmount, + uint256 takerAssetFillAmount, + bytes memory signature) internal { // Ensure order is valid @@ -190,23 +205,24 @@ contract MixinExchangeCore is pure returns ( uint8 status, - FillResults memory fillResults) + FillResults memory fillResults + ) { - // Fill Amount must be greater than 0 - if (takerAssetFillAmount <= 0) { + // Fill amount must be greater than 0 + if (takerAssetFillAmount == 0) { status = uint8(Status.TAKER_ASSET_FILL_AMOUNT_TOO_LOW); return; } // Ensure the order is fillable if (orderStatus != uint8(Status.ORDER_FILLABLE)) { - status = uint8(orderStatus); + status = orderStatus; return; } // Compute takerAssetFilledAmount - uint256 remainingtakerAssetAmount = safeSub(order.takerAssetAmount, takerAssetFilledAmount); - fillResults.takerAssetFilledAmount = min256(takerAssetFillAmount, remainingtakerAssetAmount); + uint256 remainingTakerAssetAmount = safeSub(order.takerAssetAmount, takerAssetFilledAmount); + fillResults.takerAssetFilledAmount = min256(takerAssetFillAmount, remainingTakerAssetAmount); // Validate fill order rounding if (isRoundingError( @@ -242,19 +258,32 @@ contract MixinExchangeCore is /// @dev Updates state with results of a fill order. /// @param order that was filled. /// @param takerAddress Address of taker who filled the order. + /// @param takerAssetFilledAmount Amount of order already filled. /// @return fillResults Amounts filled and fees paid by maker and taker. function updateFilledState( Order memory order, address takerAddress, bytes32 orderHash, + uint256 takerAssetFilledAmount, FillResults memory fillResults) internal { // Update state - filled[orderHash] = safeAdd(filled[orderHash], fillResults.takerAssetFilledAmount); + filled[orderHash] = safeAdd(takerAssetFilledAmount, fillResults.takerAssetFilledAmount); // Log order - emitFillEvent(order, takerAddress, orderHash, fillResults); + emit Fill( + order.makerAddress, + takerAddress, + order.feeRecipientAddress, + fillResults.makerAssetFilledAmount, + fillResults.takerAssetFilledAmount, + fillResults.makerFeePaid, + fillResults.takerFeePaid, + orderHash, + order.makerAssetData, + order.takerAssetData + ); } /// @dev Fills the input order. @@ -279,7 +308,15 @@ contract MixinExchangeCore is address takerAddress = getCurrentContextAddress(); // Either our context is valid or we revert - validateFillOrderContextOrRevert(order, orderStatus, orderHash, takerAssetFilledAmount, signature, takerAddress, takerAssetFillAmount); + validateFillOrRevert( + order, + orderStatus, + orderHash, + takerAddress, + takerAssetFilledAmount, + takerAssetFillAmount, + signature + ); // Compute proportional fill amounts uint8 status; @@ -290,11 +327,16 @@ contract MixinExchangeCore is } // Settle order - (fillResults.makerAssetFilledAmount, fillResults.makerFeePaid, fillResults.takerFeePaid) = - settleOrder(order, takerAddress, fillResults.takerAssetFilledAmount); + settleOrder(order, takerAddress, fillResults); // Update exchange internal state - updateFilledState(order, takerAddress, orderHash, fillResults); + updateFilledState( + order, + takerAddress, + orderHash, + takerAssetFilledAmount, + fillResults + ); return fillResults; } @@ -302,7 +344,7 @@ contract MixinExchangeCore is /// @param order that was cancelled. /// @param orderStatus Status of order that was cancelled. /// @param orderHash Hash of order that was cancelled. - function validateCancelOrderContextOrRevert( + function validateCancelOrRevert( Order memory order, uint8 orderStatus, bytes32 orderHash) @@ -386,50 +428,12 @@ contract MixinExchangeCore is // Fetch current order status bytes32 orderHash; uint8 orderStatus; - uint256 takerAssetFilledAmount; - (orderStatus, orderHash, takerAssetFilledAmount) = getOrderInfo(order); + (orderStatus, orderHash, ) = getOrderInfo(order); // Validate context - validateCancelOrderContextOrRevert(order, orderStatus, orderHash); + validateCancelOrRevert(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. - /// The sole purpose of this function is to get around the stack variable limit. - function emitFillEvent( - Order memory order, - address takerAddress, - bytes32 orderHash, - FillResults memory fillResults) - internal - { - emit Fill( - order.makerAddress, - takerAddress, - order.feeRecipientAddress, - fillResults.makerAssetFilledAmount, - fillResults.takerAssetFilledAmount, - fillResults.makerFeePaid, - fillResults.takerFeePaid, - orderHash, - order.makerAssetData, - order.takerAssetData - ); - } } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index a333b8221..77ba0a089 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -36,12 +36,115 @@ contract MixinMatchOrders is MMatchOrders, MSettlement, MTransactions +{ + + /// @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 + validateMatchOrThrow(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 + validateFillOrRevert( + leftOrder, + leftOrderInfo.orderStatus, + leftOrderInfo.orderHash, + takerAddress, + leftOrderInfo.orderFilledAmount, + matchedFillResults.left.takerAssetFilledAmount, + leftSignature + ); + validateFillOrRevert( + rightOrder, + rightOrderInfo.orderStatus, + rightOrderInfo.orderHash, + takerAddress, + rightOrderInfo.orderFilledAmount, + matchedFillResults.right.takerAssetFilledAmount, + rightSignature + ); + + // Settle matched orders. Succeeds or throws. + settleMatchedOrders(leftOrder, rightOrder, matchedFillResults, takerAddress); + + // Update exchange state + updateFilledState( + leftOrder, + takerAddress, + leftOrderInfo.orderHash, + leftOrderInfo.orderFilledAmount, + matchedFillResults.left + ); + updateFilledState( + rightOrder, + takerAddress, + rightOrderInfo.orderHash, + rightOrderInfo.orderFilledAmount, + matchedFillResults.right + ); + + // Return results + return matchedFillResults; + } /// @dev Validates context for matchOrders. Succeeds or throws. /// @param leftOrder First order to match. /// @param rightOrder Second order to match. - function validateMatchOrdersContextOrRevert( + function validateMatchOrThrow( Order memory leftOrder, Order memory rightOrder) internal @@ -62,7 +165,7 @@ contract MixinMatchOrders is // 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: @@ -75,7 +178,7 @@ contract MixinMatchOrders is /// @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) + function validateMatchOrThrow(MatchedFillResults memory matchedFillResults) internal { // The right order must spend at least as much as we're transferring to the left order. @@ -95,26 +198,6 @@ contract MixinMatchOrders is ); } - /// @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), - ROUNDING_ERROR_ON_PARTIAL_AMOUNT - ); - 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. @@ -137,7 +220,8 @@ contract MixinMatchOrders is internal returns ( uint8 status, - MatchedFillResults memory matchedFillResults) + 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. @@ -148,8 +232,8 @@ contract MixinMatchOrders is // * <= * uint256 rightTakerAssetAmountRemaining = safeSub(rightOrder.takerAssetAmount, rightOrderFilledAmount); uint256 leftTakerAssetAmountRemaining = safeSub(leftOrder.takerAssetAmount, leftOrderFilledAmount); - uint256 leftOrderAmountToFill = 0; - uint256 rightOrderAmountToFill = 0; + uint256 leftOrderAmountToFill; + uint256 rightOrderAmountToFill; if ( safeMul(leftTakerAssetAmountRemaining, rightOrder.takerAssetAmount) <= safeMul(rightTakerAssetAmountRemaining, rightOrder.makerAssetAmount) @@ -162,7 +246,8 @@ contract MixinMatchOrders is rightOrderAmountToFill = safeGetPartialAmount( rightOrder.takerAssetAmount, rightOrder.makerAssetAmount, - leftOrderAmountToFill); + leftOrderAmountToFill + ); } else { // Right order is the constraint: maximally fill right rightOrderAmountToFill = rightTakerAssetAmountRemaining; @@ -172,136 +257,36 @@ contract MixinMatchOrders is leftOrderAmountToFill = safeGetPartialAmount( rightOrder.makerAssetAmount, rightOrder.takerAssetAmount, - rightOrderAmountToFill); + rightOrderAmountToFill + ); } // Calculate fill results for left order - ( status, - matchedFillResults.left - ) = calculateFillResults( + (status, matchedFillResults.left) = calculateFillResults( leftOrder, leftOrderStatus, leftOrderFilledAmount, - leftOrderAmountToFill); + leftOrderAmountToFill + ); if (status != uint8(Status.SUCCESS)) { return (status, matchedFillResults); } // Calculate fill results for right order - ( status, - matchedFillResults.right - ) = calculateFillResults( + (status, matchedFillResults.right) = calculateFillResults( rightOrder, rightOrderStatus, rightOrderFilledAmount, - rightOrderAmountToFill); + rightOrderAmountToFill + ); if (status != uint8(Status.SUCCESS)) { return (status, matchedFillResults); } // Validate the fill results - validateMatchedOrderFillResultsOrThrow(matchedFillResults); + validateMatchOrThrow(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 cde64ba74..cb96bbc3e 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol @@ -22,11 +22,13 @@ import "./mixins/MSettlement.sol"; import "./mixins/MAssetProxyDispatcher.sol"; import "./libs/LibOrder.sol"; import "./libs/LibMath.sol"; +import "./libs/LibExchangeErrors.sol"; +import "./libs/LibFillResults.sol"; import "./mixins/MMatchOrders.sol"; -import "./mixins/LibExchangeErrors.sol"; contract MixinSettlement is LibMath, + LibFillResults, LibExchangeErrors, MMatchOrders, MSettlement, @@ -58,47 +60,37 @@ contract MixinSettlement is /// @dev Settles an order by transferring assets between counterparties. /// @param order Order struct containing order specifications. /// @param takerAddress Address selling takerAsset and buying makerAsset. - /// @param takerAssetFilledAmount The amount of takerAsset that will be transferred to the order's maker. - /// @return Amount filled by maker and fees paid by maker/taker. + /// @param fillResults Amounts to be filled and fees paid by maker and taker. function settleOrder( LibOrder.Order memory order, address takerAddress, - uint256 takerAssetFilledAmount) + FillResults memory fillResults) internal - returns ( - uint256 makerAssetFilledAmount, - uint256 makerFeePaid, - uint256 takerFeePaid - ) { - makerAssetFilledAmount = getPartialAmount(takerAssetFilledAmount, order.takerAssetAmount, order.makerAssetAmount); dispatchTransferFrom( order.makerAssetData, order.makerAddress, takerAddress, - makerAssetFilledAmount + fillResults.makerAssetFilledAmount ); dispatchTransferFrom( order.takerAssetData, takerAddress, order.makerAddress, - takerAssetFilledAmount + fillResults.takerAssetFilledAmount ); - makerFeePaid = getPartialAmount(takerAssetFilledAmount, order.takerAssetAmount, order.makerFee); dispatchTransferFrom( ZRX_PROXY_DATA, order.makerAddress, order.feeRecipientAddress, - makerFeePaid + fillResults.makerFeePaid ); - takerFeePaid = getPartialAmount(takerAssetFilledAmount, order.takerAssetAmount, order.takerFee); dispatchTransferFrom( ZRX_PROXY_DATA, takerAddress, order.feeRecipientAddress, - takerFeePaid + fillResults.takerFeePaid ); - return (makerAssetFilledAmount, makerFeePaid, takerFeePaid); } /// @dev Settles matched order by transferring appropriate funds between order makers, taker, and fee recipient. 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 ba055dd8e..5ef0a52c5 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol @@ -55,5 +55,4 @@ contract LibExchangeErrors { string constant NEGATIVE_SPREAD = "Matched orders must have a positive spread."; string constant MISCALCULATED_TRANSFER_AMOUNTS = "A miscalculation occurred: the left maker would receive more than the right maker would spend."; string constant ROUNDING_ERROR_TRANSFER_AMOUNTS = "A rounding error occurred when calculating transfer amounts for matched orders."; - string constant ROUNDING_ERROR_ON_PARTIAL_AMOUNT = "A rounding error occurred when calculating partial transfer amounts."; } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol index b48602d19..6bae03ff2 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol @@ -23,6 +23,7 @@ import "../../../utils/SafeMath/SafeMath.sol"; contract LibMath is SafeMath { + string constant ROUNDING_ERROR_ON_PARTIAL_AMOUNT = "A rounding error occurred when calculating partial transfer amounts."; /// @dev Calculates partial value given a numerator and denominator. /// @param numerator Numerator. @@ -44,6 +45,26 @@ contract LibMath is return partialAmount; } + /// @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), + ROUNDING_ERROR_ON_PARTIAL_AMOUNT + ); + return getPartialAmount(numerator, denominator, target); + } + /// @dev Checks if rounding error > 0.1%. /// @param numerator Numerator. /// @param denominator Denominator. 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 cfd2678ba..6f7d99c48 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol @@ -56,14 +56,10 @@ contract MExchangeCore is uint256 makerEpoch ); - /// @dev Logs a Fill event with the given arguments. - /// The sole purpose of this function is to get around the stack variable limit. - function emitFillEvent( - LibOrder.Order memory order, - address takerAddress, - bytes32 orderHash, - LibFillResults.FillResults memory fillResults) - internal; + /// @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; /// @dev Gets information about an order: status, hash, and amount filled. /// @param order Order to gather information on. @@ -76,24 +72,25 @@ contract MExchangeCore is returns ( uint8 orderStatus, bytes32 orderHash, - uint256 takerAssetFilledAmount); + 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 takerAssetFilledAmount Amount of order already filled. /// @param takerAssetFillAmount Desired amount of order to fill by taker. - function validateFillOrderContextOrRevert( + /// @param signature Proof that the orders was created by its maker. + function validateFillOrRevert( LibOrder.Order memory order, uint8 orderStatus, bytes32 orderHash, - uint256 takerAssetFilledAmount, - bytes memory signature, address takerAddress, - uint256 takerAssetFillAmount) + uint256 takerAssetFilledAmount, + uint256 takerAssetFillAmount, + bytes memory signature) internal; /// @dev Calculates amounts filled and fees paid by maker and taker. @@ -112,16 +109,19 @@ contract MExchangeCore is pure returns ( uint8 status, - LibFillResults.FillResults memory fillResults); + 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. + /// @param takerAssetFilledAmount Amount of order already filled. /// @return fillResults Amounts filled and fees paid by maker and taker. function updateFilledState( LibOrder.Order memory order, address takerAddress, bytes32 orderHash, + uint256 takerAssetFilledAmount, LibFillResults.FillResults memory fillResults) internal; @@ -141,7 +141,7 @@ contract MExchangeCore is /// @param order that was cancelled. /// @param orderStatus Status of order that was cancelled. /// @param orderHash Hash of order that was cancelled. - function validateCancelOrderContextOrRevert( + function validateCancelOrRevert( LibOrder.Order memory order, uint8 orderStatus, bytes32 orderHash) @@ -162,15 +162,12 @@ contract MExchangeCore is returns (bool stateUpdated); /// @dev After calling, the order can not be filled anymore. - /// @param order Order struct containing order specifications. + /// Throws if order is invalid or sender does not have permission to cancel. + /// @param order Order to cancel. Order must be Status.FILLABLE. /// @return True if the order state changed to cancelled. - /// False if the transaction was already cancelled or expired. + /// False if the order was order was in a valid, but + /// unfillable state (see LibStatus.STATUS for order states) function cancelOrder(LibOrder.Order memory order) public returns (bool); - - /// @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; } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol index 2aedeb3e6..a1e3f28a1 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol @@ -37,32 +37,36 @@ contract MMatchOrders { uint256 orderFilledAmount; } + /// @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); + /// @dev Validates context for matchOrders. Succeeds or throws. /// @param leftOrder First order to match. /// @param rightOrder Second order to match. - function validateMatchOrdersContextOrRevert( + function validateMatchOrThrow( 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) + function validateMatchOrThrow(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. @@ -85,22 +89,6 @@ contract MMatchOrders { 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); + 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 ac38ede95..bd9042f62 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSettlement.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSettlement.sol @@ -20,24 +20,19 @@ pragma solidity ^0.4.23; import "../libs/LibOrder.sol"; import "./MMatchOrders.sol"; +import "../libs/LibFillResults.sol"; contract MSettlement { - /// @dev Settles an order by transfering assets between counterparties. + /// @dev Settles an order by transferring assets between counterparties. /// @param order Order struct containing order specifications. /// @param takerAddress Address selling takerAsset and buying makerAsset. - /// @param takerAssetFilledAmount The amount of takerAsset that will be transfered to the order's maker. - /// @return Amount filled by maker and fees paid by maker/taker. + /// @param fillResults Amounts to be filled and fees paid by maker and taker. function settleOrder( LibOrder.Order memory order, address takerAddress, - uint256 takerAssetFilledAmount) - internal - returns ( - uint256 makerAssetFilledAmount, - uint256 makerFeePaid, - uint256 takerFeePaid - ); + LibFillResults.FillResults memory fillResults) + internal; /// @dev Settles matched order by transferring appropriate funds between order makers, taker, and fee recipient. /// @param leftOrder First matched order. diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts index 6d36198f2..5b026fce0 100644 --- a/packages/contracts/src/utils/exchange_wrapper.ts +++ b/packages/contracts/src/utils/exchange_wrapper.ts @@ -225,12 +225,6 @@ export class ExchangeWrapper { const filledAmount = new BigNumber(await this._exchange.filled.callAsync(orderHashHex)); return filledAmount; } - private async _getTxWithDecodedExchangeLogsAsync(txHash: string) { - const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash); - tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address); - tx.logs = _.map(tx.logs, log => this._logDecoder.decodeLogOrThrow(log)); - return tx; - } public async getOrderInfoAsync( signedOrder: SignedOrder, ): Promise<[number /* orderStatus */, string /* orderHash */, BigNumber /* orderTakerAssetAmountFilled */]> { @@ -251,6 +245,14 @@ export class ExchangeWrapper { { from }, ); const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash); + tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address); + tx.logs = _.map(tx.logs, log => this._logDecoder.decodeLogOrThrow(log)); + return tx; + } + private async _getTxWithDecodedExchangeLogsAsync(txHash: string) { + const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash); + tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address); + tx.logs = _.map(tx.logs, log => this._logDecoder.decodeLogOrThrow(log)); return tx; } } diff --git a/packages/contracts/test/exchange/match_orders.ts b/packages/contracts/test/exchange/match_orders.ts index a114d92ac..6ba88b5fb 100644 --- a/packages/contracts/test/exchange/match_orders.ts +++ b/packages/contracts/test/exchange/match_orders.ts @@ -41,7 +41,7 @@ chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -describe('matchOrdersAndVerifyBalancesAsync', () => { +describe('matchOrders', () => { let makerAddressLeft: string; let makerAddressRight: string; let owner: string; diff --git a/packages/contracts/test/utils/match_order_tester.ts b/packages/contracts/test/utils/match_order_tester.ts index 91c2ab0a3..4e3459734 100644 --- a/packages/contracts/test/utils/match_order_tester.ts +++ b/packages/contracts/test/utils/match_order_tester.ts @@ -32,7 +32,6 @@ import { TransferAmountsByMatchOrders as TransferAmounts, } from '../../src/utils/types'; import { chaiSetup } from '../utils/chai_setup'; -import { deployer } from '../utils/deployer'; import { provider, web3Wrapper } from '../utils/web3_wrapper'; chaiSetup.configure(); -- cgit v1.2.3 From 1dd7688bddd1b1328ad1dea46129ff489d7ea403 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 11 May 2018 17:22:10 -0700 Subject: Reordered fund transfers for matched orders, plus added an extra sanity check to order matching calculations --- .../current/protocol/Exchange/MixinMatchOrders.sol | 33 ++++++++++++-- .../current/protocol/Exchange/MixinSettlement.sol | 50 ++++++++-------------- .../protocol/Exchange/mixins/MMatchOrders.sol | 1 + 3 files changed, 49 insertions(+), 35 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index 77ba0a089..ab4768d02 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -84,8 +84,8 @@ contract MixinMatchOrders is validateMatchOrThrow(leftOrder, rightOrder); // Compute proportional fill amounts - uint8 matchedFillAmountsStatus; - ( matchedFillAmountsStatus, + uint8 matchedFillResultsStatus; + ( matchedFillResultsStatus, matchedFillResults ) = calculateMatchedFillResults( leftOrder, @@ -94,7 +94,7 @@ contract MixinMatchOrders is rightOrderInfo.orderStatus, leftOrderInfo.orderFilledAmount, rightOrderInfo.orderFilledAmount); - if (matchedFillAmountsStatus != uint8(Status.SUCCESS)) { + if (matchedFillResultsStatus != uint8(Status.SUCCESS)) { return matchedFillResults; } @@ -181,6 +181,27 @@ contract MixinMatchOrders is function validateMatchOrThrow(MatchedFillResults memory matchedFillResults) internal { + // The left order must spend at least as much as we're sending to the combined + // amounts being sent to the right order and taker + uint256 amountSpentByLeft = safeAdd( + matchedFillResults.right.takerAssetFilledAmount, + matchedFillResults.takerFillAmount + ); + require( + matchedFillResults.left.makerAssetFilledAmount >= + amountSpentByLeft, + MISCALCULATED_TRANSFER_AMOUNTS + ); + // If the amount transferred from the left order is different 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( + !isRoundingError( + matchedFillResults.left.makerAssetFilledAmount, + amountSpentByLeft, + 1), + ROUNDING_ERROR_TRANSFER_AMOUNTS + ); + // The right order must spend at least as much as we're transferring to the left order. require( matchedFillResults.right.makerAssetFilledAmount >= @@ -283,6 +304,12 @@ contract MixinMatchOrders is return (status, matchedFillResults); } + // Calculate amount given to taker + matchedFillResults.takerFillAmount = safeSub( + matchedFillResults.left.makerAssetFilledAmount, + matchedFillResults.right.takerAssetFilledAmount + ); + // Validate the fill results validateMatchOrThrow(matchedFillResults); diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol index cb96bbc3e..4911c62b5 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol @@ -105,57 +105,41 @@ contract MixinSettlement is 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) + // Order makers and taker 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. - require( - matchedFillResults.right.makerAssetFilledAmount >= - matchedFillResults.left.takerAssetFilledAmount, - MISCALCULATED_TRANSFER_AMOUNTS + matchedFillResults.right.takerAssetFilledAmount ); dispatchTransferFrom( rightOrder.makerAssetData, rightOrder.makerAddress, leftOrder.makerAddress, - matchedFillResults.right.makerAssetFilledAmount); + matchedFillResults.left.takerAssetFilledAmount + ); + dispatchTransferFrom( + leftOrder.makerAssetData, + leftOrder.makerAddress, + takerAddress, + matchedFillResults.takerFillAmount + ); // Maker fees dispatchTransferFrom( ZRX_PROXY_DATA, leftOrder.makerAddress, leftOrder.feeRecipientAddress, - matchedFillResults.left.makerFeePaid); + matchedFillResults.left.makerFeePaid + ); dispatchTransferFrom( ZRX_PROXY_DATA, rightOrder.makerAddress, rightOrder.feeRecipientAddress, - matchedFillResults.right.makerFeePaid); + 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, @@ -171,12 +155,14 @@ contract MixinSettlement is ZRX_PROXY_DATA, takerAddress, leftOrder.feeRecipientAddress, - matchedFillResults.left.takerFeePaid); + matchedFillResults.left.takerFeePaid + ); dispatchTransferFrom( ZRX_PROXY_DATA, takerAddress, rightOrder.feeRecipientAddress, - matchedFillResults.right.takerFeePaid); + matchedFillResults.right.takerFeePaid + ); } } } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol index a1e3f28a1..17b6cd92d 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol @@ -27,6 +27,7 @@ contract MMatchOrders { struct MatchedFillResults { LibFillResults.FillResults left; LibFillResults.FillResults right; + uint256 takerFillAmount; } /// This struct exists solely to avoid the stack limit constraint -- cgit v1.2.3 From bb73963421eaf154194f4e32a933b50cb72ededc Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Sat, 12 May 2018 13:59:12 -0700 Subject: Changes based on style guidelines put out by Amir --- .../protocol/Exchange/MixinExchangeCore.sol | 184 +++++++++++---------- .../current/protocol/Exchange/MixinMatchOrders.sol | 23 ++- .../current/protocol/Exchange/MixinSettlement.sol | 6 +- .../protocol/Exchange/interfaces/IExchangeCore.sol | 14 ++ .../protocol/Exchange/interfaces/IMatchOrders.sol | 43 +++++ .../protocol/Exchange/libs/LibFillResults.sol | 8 +- .../protocol/Exchange/mixins/MExchangeCore.sol | 58 ++----- .../protocol/Exchange/mixins/MMatchOrders.sol | 38 ++--- .../protocol/Exchange/mixins/MSettlement.sol | 8 +- 9 files changed, 210 insertions(+), 172 deletions(-) create mode 100644 packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index 2c23575c7..3e14e4cb4 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -130,6 +130,88 @@ contract MixinExchangeCore is return (orderStatus, orderHash, takerAssetFilledAmount); } + /// @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( + Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + public + returns (FillResults memory fillResults) + { + // Fetch order info + bytes32 orderHash; + uint8 orderStatus; + uint256 takerAssetFilledAmount; + (orderStatus, orderHash, takerAssetFilledAmount) = getOrderInfo(order); + + // Fetch taker address + address takerAddress = getCurrentContextAddress(); + + // Either our context is valid or we revert + validateFillOrRevert( + order, + orderStatus, + orderHash, + takerAddress, + takerAssetFilledAmount, + takerAssetFillAmount, + signature + ); + + // 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 + settleOrder(order, takerAddress, fillResults); + + // Update exchange internal state + updateFilledState( + order, + takerAddress, + orderHash, + takerAssetFilledAmount, + fillResults + ); + return fillResults; + } + + /// @dev After calling, the order can not be filled anymore. + /// Throws if order is invalid or sender does not have permission to cancel. + /// @param order Order to cancel. Order must be Status.FILLABLE. + /// @return True if the order state changed to cancelled. + /// False if the order was order was in a valid, but + /// unfillable state (see LibStatus.STATUS for order states) + function cancelOrder(Order memory order) + public + returns (bool) + { + // Fetch current order status + bytes32 orderHash; + uint8 orderStatus; + (orderStatus, orderHash, ) = getOrderInfo(order); + + // Validate context + validateCancelOrRevert(order, orderStatus, orderHash); + + // Perform cancel + return updateCancelledState(order, orderStatus, orderHash); + } + /// @dev Validates context for fillOrder. Succeeds or throws. /// @param order to be filled. /// @param orderStatus Status of order to be filled. @@ -145,7 +227,8 @@ contract MixinExchangeCore is address takerAddress, uint256 takerAssetFilledAmount, uint256 takerAssetFillAmount, - bytes memory signature) + bytes memory signature + ) internal { // Ensure order is valid @@ -200,8 +283,9 @@ contract MixinExchangeCore is Order memory order, uint8 orderStatus, uint256 takerAssetFilledAmount, - uint256 takerAssetFillAmount) - public + uint256 takerAssetFillAmount + ) + internal pure returns ( uint8 status, @@ -241,15 +325,18 @@ contract MixinExchangeCore is fillResults.makerAssetFilledAmount = getPartialAmount( fillResults.takerAssetFilledAmount, order.takerAssetAmount, - order.makerAssetAmount); + order.makerAssetAmount + ); fillResults.makerFeePaid = getPartialAmount( fillResults.takerAssetFilledAmount, order.takerAssetAmount, - order.makerFee); + order.makerFee + ); fillResults.takerFeePaid = getPartialAmount( fillResults.takerAssetFilledAmount, order.takerAssetAmount, - order.takerFee); + order.takerFee + ); status = uint8(Status.SUCCESS); return; @@ -265,7 +352,8 @@ contract MixinExchangeCore is address takerAddress, bytes32 orderHash, uint256 takerAssetFilledAmount, - FillResults memory fillResults) + FillResults memory fillResults + ) internal { // Update state @@ -286,60 +374,6 @@ contract MixinExchangeCore is ); } - /// @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( - Order memory order, - uint256 takerAssetFillAmount, - bytes memory signature) - public - returns (FillResults memory fillResults) - { - // Fetch order info - bytes32 orderHash; - uint8 orderStatus; - uint256 takerAssetFilledAmount; - (orderStatus, orderHash, takerAssetFilledAmount) = getOrderInfo(order); - - // Fetch taker address - address takerAddress = getCurrentContextAddress(); - - // Either our context is valid or we revert - validateFillOrRevert( - order, - orderStatus, - orderHash, - takerAddress, - takerAssetFilledAmount, - takerAssetFillAmount, - signature - ); - - // 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 - settleOrder(order, takerAddress, fillResults); - - // Update exchange internal state - updateFilledState( - order, - takerAddress, - orderHash, - takerAssetFilledAmount, - fillResults - ); - return fillResults; - } - /// @dev Validates context for cancelOrder. Succeeds or throws. /// @param order that was cancelled. /// @param orderStatus Status of order that was cancelled. @@ -347,7 +381,8 @@ contract MixinExchangeCore is function validateCancelOrRevert( Order memory order, uint8 orderStatus, - bytes32 orderHash) + bytes32 orderHash + ) internal { // Ensure order is valid @@ -388,7 +423,8 @@ contract MixinExchangeCore is function updateCancelledState( Order memory order, uint8 orderStatus, - bytes32 orderHash) + bytes32 orderHash + ) internal returns (bool stateUpdated) { @@ -414,26 +450,4 @@ contract MixinExchangeCore is return stateUpdated; } - - /// @dev After calling, the order can not be filled anymore. - /// Throws if order is invalid or sender does not have permission to cancel. - /// @param order Order to cancel. Order must be Status.FILLABLE. - /// @return True if the order state changed to cancelled. - /// False if the order was order was in a valid, but - /// unfillable state (see LibStatus.STATUS for order states) - function cancelOrder(Order memory order) - public - returns (bool) - { - // Fetch current order status - bytes32 orderHash; - uint8 orderStatus; - (orderStatus, orderHash, ) = getOrderInfo(order); - - // Validate context - validateCancelOrRevert(order, orderStatus, orderHash); - - // Perform cancel - return updateCancelledState(order, orderStatus, orderHash); - } } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index ab4768d02..b20bc14ad 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -31,6 +31,7 @@ contract MixinMatchOrders is LibMath, LibStatus, LibOrder, + LibFillResults, LibExchangeErrors, MExchangeCore, MMatchOrders, @@ -51,7 +52,8 @@ contract MixinMatchOrders is Order memory leftOrder, Order memory rightOrder, bytes leftSignature, - bytes rightSignature) + bytes rightSignature + ) public returns (MatchedFillResults memory matchedFillResults) { @@ -119,7 +121,12 @@ contract MixinMatchOrders is ); // Settle matched orders. Succeeds or throws. - settleMatchedOrders(leftOrder, rightOrder, matchedFillResults, takerAddress); + settleMatchedOrders( + leftOrder, + rightOrder, + matchedFillResults, + takerAddress + ); // Update exchange state updateFilledState( @@ -146,7 +153,8 @@ contract MixinMatchOrders is /// @param rightOrder Second order to match. function validateMatchOrThrow( Order memory leftOrder, - Order memory rightOrder) + Order memory rightOrder + ) internal { // The leftOrder maker asset must be the same as the rightOrder taker asset. @@ -198,7 +206,8 @@ contract MixinMatchOrders is !isRoundingError( matchedFillResults.left.makerAssetFilledAmount, amountSpentByLeft, - 1), + 1 + ), ROUNDING_ERROR_TRANSFER_AMOUNTS ); @@ -214,7 +223,8 @@ contract MixinMatchOrders is !isRoundingError( matchedFillResults.right.makerAssetFilledAmount, matchedFillResults.left.takerAssetFilledAmount, - 1), + 1 + ), ROUNDING_ERROR_TRANSFER_AMOUNTS ); } @@ -237,7 +247,8 @@ contract MixinMatchOrders is uint8 leftOrderStatus, uint8 rightOrderStatus, uint256 leftOrderFilledAmount, - uint256 rightOrderFilledAmount) + uint256 rightOrderFilledAmount + ) internal returns ( uint8 status, diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol index 4911c62b5..7e324a5d2 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol @@ -64,7 +64,8 @@ contract MixinSettlement is function settleOrder( LibOrder.Order memory order, address takerAddress, - FillResults memory fillResults) + FillResults memory fillResults + ) internal { dispatchTransferFrom( @@ -102,7 +103,8 @@ contract MixinSettlement is LibOrder.Order memory leftOrder, LibOrder.Order memory rightOrder, MatchedFillResults memory matchedFillResults, - address takerAddress) + address takerAddress + ) internal { // Order makers and taker diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol index 0f19525ca..f97c74123 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol @@ -29,6 +29,20 @@ contract IExchangeCore { function cancelOrdersUpTo(uint256 salt) external; + /// @dev Gets information about an order: status, hash, and amount filled. + /// @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 Fills the input order. /// @param order Order struct containing order specifications. /// @param takerAssetFillAmount Desired amount of takerAsset to sell. diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol new file mode 100644 index 000000000..fe8cee5f1 --- /dev/null +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol @@ -0,0 +1,43 @@ +/* + + 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.23; +pragma experimental ABIEncoderV2; + +import "../libs/LibOrder.sol"; +import "../libs/LibFillResults.sol"; + +contract IMatchOrders { + + /// @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 (LibFillResults.MatchedFillResults memory matchedFillResults); +} diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol index 3b8f2acf9..c4647c44f 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol @@ -31,6 +31,12 @@ contract LibFillResults is uint256 takerFeePaid; } + struct MatchedFillResults { + LibFillResults.FillResults left; + LibFillResults.FillResults right; + uint256 takerFillAmount; + } + /// @dev Adds properties of both FillResults instances. /// Modifies the first FillResults instance specified. /// @param totalFillResults Fill results instance that will be added onto. @@ -44,4 +50,4 @@ contract LibFillResults is totalFillResults.makerFeePaid = safeAdd(totalFillResults.makerFeePaid, singleFillResults.makerFeePaid); totalFillResults.takerFeePaid = safeAdd(totalFillResults.takerFeePaid, singleFillResults.takerFeePaid); } -} \ No newline at end of file +} 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 6f7d99c48..f166abb71 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol @@ -56,25 +56,6 @@ contract MExchangeCore is uint256 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; - - /// @dev Gets information about an order: status, hash, and amount filled. - /// @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. @@ -90,7 +71,8 @@ contract MExchangeCore is address takerAddress, uint256 takerAssetFilledAmount, uint256 takerAssetFillAmount, - bytes memory signature) + bytes memory signature + ) internal; /// @dev Calculates amounts filled and fees paid by maker and taker. @@ -104,8 +86,9 @@ contract MExchangeCore is LibOrder.Order memory order, uint8 orderStatus, uint256 takerAssetFilledAmount, - uint256 takerAssetFillAmount) - public + uint256 takerAssetFillAmount + ) + internal pure returns ( uint8 status, @@ -122,21 +105,10 @@ contract MExchangeCore is address takerAddress, bytes32 orderHash, uint256 takerAssetFilledAmount, - LibFillResults.FillResults memory fillResults) + 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. @@ -144,7 +116,8 @@ contract MExchangeCore is function validateCancelOrRevert( LibOrder.Order memory order, uint8 orderStatus, - bytes32 orderHash) + bytes32 orderHash + ) internal; /// @dev Updates state with results of cancelling an order. @@ -157,17 +130,8 @@ contract MExchangeCore is function updateCancelledState( LibOrder.Order memory order, uint8 orderStatus, - bytes32 orderHash) + bytes32 orderHash + ) internal returns (bool stateUpdated); - - /// @dev After calling, the order can not be filled anymore. - /// Throws if order is invalid or sender does not have permission to cancel. - /// @param order Order to cancel. Order must be Status.FILLABLE. - /// @return True if the order state changed to cancelled. - /// False if the order was order was in a valid, but - /// unfillable state (see LibStatus.STATUS for order states) - function cancelOrder(LibOrder.Order memory order) - public - returns (bool); } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol index 17b6cd92d..60a266250 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol @@ -21,14 +21,11 @@ pragma experimental ABIEncoderV2; import "../libs/LibOrder.sol"; import "../libs/LibFillResults.sol"; import "./MExchangeCore.sol"; +import "../interfaces/IMatchOrders.sol"; -contract MMatchOrders { - - struct MatchedFillResults { - LibFillResults.FillResults left; - LibFillResults.FillResults right; - uint256 takerFillAmount; - } +contract MMatchOrders is + IMatchOrders +{ /// This struct exists solely to avoid the stack limit constraint /// in matchOrders @@ -38,34 +35,18 @@ contract MMatchOrders { uint256 orderFilledAmount; } - /// @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); - /// @dev Validates context for matchOrders. Succeeds or throws. /// @param leftOrder First order to match. /// @param rightOrder Second order to match. function validateMatchOrThrow( LibOrder.Order memory leftOrder, - LibOrder.Order memory rightOrder) + 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 validateMatchOrThrow(MatchedFillResults memory matchedFillResults) + function validateMatchOrThrow(LibFillResults.MatchedFillResults memory matchedFillResults) internal; /// @dev Calculates fill amounts for the matched orders. @@ -86,10 +67,11 @@ contract MMatchOrders { uint8 leftOrderStatus, uint8 rightOrderStatus, uint256 leftOrderFilledAmount, - uint256 rightOrderFilledAmount) + uint256 rightOrderFilledAmount + ) internal returns ( uint8 status, - MatchedFillResults memory matchedFillResults + LibFillResults.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 bd9042f62..d0de385db 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSettlement.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSettlement.sol @@ -31,7 +31,8 @@ contract MSettlement { function settleOrder( LibOrder.Order memory order, address takerAddress, - LibFillResults.FillResults memory fillResults) + LibFillResults.FillResults memory fillResults + ) internal; /// @dev Settles matched order by transferring appropriate funds between order makers, taker, and fee recipient. @@ -42,7 +43,8 @@ contract MSettlement { function settleMatchedOrders( LibOrder.Order memory leftOrder, LibOrder.Order memory rightOrder, - MMatchOrders.MatchedFillResults memory matchedFillResults, - address takerAddress) + LibFillResults.MatchedFillResults memory matchedFillResults, + address takerAddress + ) internal; } -- cgit v1.2.3 From 12d8c2398fbe6b6b46ad15e51e1763c988f41c73 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 14 May 2018 11:26:30 -0700 Subject: Reordered functions in mixin exchange core -- getOrderInfo is at the bottom --- .../protocol/Exchange/MixinExchangeCore.sol | 126 ++++++++++----------- .../protocol/Exchange/interfaces/IExchangeCore.sol | 28 ++--- 2 files changed, 77 insertions(+), 77 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index 3e14e4cb4..5ad36f501 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -67,69 +67,6 @@ contract MixinExchangeCore is emit CancelUpTo(msg.sender, newMakerEpoch); } - /// @dev Gets information about an order: status, hash, and amount filled. - /// @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 - view - returns ( - uint8 orderStatus, - bytes32 orderHash, - uint256 takerAssetFilledAmount - ) - { - // Compute the order hash - orderHash = getOrderHash(order); - - // 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); - } - - // 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); - } - - // Validate order expiration - if (block.timestamp >= order.expirationTimeSeconds) { - orderStatus = uint8(Status.ORDER_EXPIRED); - return (orderStatus, orderHash, takerAssetFilledAmount); - } - - // Check if order has been cancelled - if (cancelled[orderHash]) { - orderStatus = uint8(Status.ORDER_CANCELLED); - return (orderStatus, orderHash, takerAssetFilledAmount); - } - if (makerEpoch[order.makerAddress] > order.salt) { - orderStatus = uint8(Status.ORDER_CANCELLED); - return (orderStatus, orderHash, takerAssetFilledAmount); - } - - // Fetch filled amount and validate order availability - takerAssetFilledAmount = filled[orderHash]; - if (takerAssetFilledAmount >= order.takerAssetAmount) { - orderStatus = uint8(Status.ORDER_FULLY_FILLED); - return (orderStatus, orderHash, takerAssetFilledAmount); - } - - // All other statuses are ruled out: order is Fillable - orderStatus = uint8(Status.ORDER_FILLABLE); - return (orderStatus, orderHash, takerAssetFilledAmount); - } - /// @dev Fills the input order. /// @param order Order struct containing order specifications. /// @param takerAssetFillAmount Desired amount of takerToken to sell. @@ -450,4 +387,67 @@ contract MixinExchangeCore is return stateUpdated; } + + /// @dev Gets information about an order: status, hash, and amount filled. + /// @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 + view + returns ( + uint8 orderStatus, + bytes32 orderHash, + uint256 takerAssetFilledAmount + ) + { + // Compute the order hash + orderHash = getOrderHash(order); + + // 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); + } + + // 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); + } + + // Validate order expiration + if (block.timestamp >= order.expirationTimeSeconds) { + orderStatus = uint8(Status.ORDER_EXPIRED); + return (orderStatus, orderHash, takerAssetFilledAmount); + } + + // Check if order has been cancelled + if (cancelled[orderHash]) { + orderStatus = uint8(Status.ORDER_CANCELLED); + return (orderStatus, orderHash, takerAssetFilledAmount); + } + if (makerEpoch[order.makerAddress] > order.salt) { + orderStatus = uint8(Status.ORDER_CANCELLED); + return (orderStatus, orderHash, takerAssetFilledAmount); + } + + // Fetch filled amount and validate order availability + takerAssetFilledAmount = filled[orderHash]; + if (takerAssetFilledAmount >= order.takerAssetAmount) { + orderStatus = uint8(Status.ORDER_FULLY_FILLED); + return (orderStatus, orderHash, takerAssetFilledAmount); + } + + // All other statuses are ruled out: order is Fillable + orderStatus = uint8(Status.ORDER_FILLABLE); + return (orderStatus, orderHash, takerAssetFilledAmount); + } } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol index f97c74123..d62690933 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol @@ -29,20 +29,6 @@ contract IExchangeCore { function cancelOrdersUpTo(uint256 salt) external; - /// @dev Gets information about an order: status, hash, and amount filled. - /// @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 Fills the input order. /// @param order Order struct containing order specifications. /// @param takerAssetFillAmount Desired amount of takerAsset to sell. @@ -62,4 +48,18 @@ contract IExchangeCore { function cancelOrder(LibOrder.Order memory order) public returns (bool); + + /// @dev Gets information about an order: status, hash, and amount filled. + /// @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 + ); } -- cgit v1.2.3 From 3e6e7fb2725a3a1bb9f4ac2f371c0cc8e751b8a1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 14 May 2018 11:28:28 -0700 Subject: Token -> Asset in fillOrder spec --- .../src/contracts/current/protocol/Exchange/MixinExchangeCore.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index 5ad36f501..29f0e17cf 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -69,7 +69,7 @@ contract MixinExchangeCore is /// @dev Fills the input order. /// @param order Order struct containing order specifications. - /// @param takerAssetFillAmount Desired amount of takerToken to sell. + /// @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( -- cgit v1.2.3 From 061facdcceddbc68620ae710c1f2fb2c99e4d3f3 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 14 May 2018 14:19:53 -0700 Subject: Removed redundant status checks in matchOrders. Saves gas, plus follows pattern of fillOrder more closely. --- .../src/contracts/current/protocol/Exchange/MixinMatchOrders.sol | 8 -------- 1 file changed, 8 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index b20bc14ad..9ea44beab 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -63,10 +63,6 @@ contract MixinMatchOrders is 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; @@ -74,10 +70,6 @@ contract MixinMatchOrders is 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(); -- cgit v1.2.3 From 93087324d9b57c87d48d0b7b3cbb28437927ffeb Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 15 May 2018 16:11:20 -0700 Subject: Throw if the left or right orders do not compute the correct fill results. I like this better than just logging an error and failing silently. --- .../current/protocol/Exchange/MixinMatchOrders.sol | 36 +++++++++------------- .../protocol/Exchange/libs/LibExchangeErrors.sol | 2 ++ .../current/protocol/Exchange/libs/LibMath.sol | 2 +- .../protocol/Exchange/mixins/MMatchOrders.sol | 6 +--- packages/contracts/test/exchange/match_orders.ts | 18 +++++------ 5 files changed, 27 insertions(+), 37 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index 9ea44beab..9f9ad6a52 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -78,19 +78,14 @@ contract MixinMatchOrders is validateMatchOrThrow(leftOrder, rightOrder); // Compute proportional fill amounts - uint8 matchedFillResultsStatus; - ( matchedFillResultsStatus, - matchedFillResults - ) = calculateMatchedFillResults( + matchedFillResults = calculateMatchedFillResults( leftOrder, rightOrder, leftOrderInfo.orderStatus, rightOrderInfo.orderStatus, leftOrderInfo.orderFilledAmount, - rightOrderInfo.orderFilledAmount); - if (matchedFillResultsStatus != uint8(Status.SUCCESS)) { - return matchedFillResults; - } + rightOrderInfo.orderFilledAmount + ); // Validate fill contexts validateFillOrRevert( @@ -231,7 +226,6 @@ contract MixinMatchOrders is /// @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, @@ -242,10 +236,7 @@ contract MixinMatchOrders is uint256 rightOrderFilledAmount ) internal - returns ( - uint8 status, - MatchedFillResults memory matchedFillResults - ) + returns (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. @@ -286,15 +277,17 @@ contract MixinMatchOrders is } // Calculate fill results for left order + uint8 status; (status, matchedFillResults.left) = calculateFillResults( leftOrder, leftOrderStatus, leftOrderFilledAmount, leftOrderAmountToFill ); - if (status != uint8(Status.SUCCESS)) { - return (status, matchedFillResults); - } + require( + status == uint8(Status.SUCCESS), + FAILED_TO_CALCULATE_FILL_RESULTS_FOR_LEFT_ORDER + ); // Calculate fill results for right order (status, matchedFillResults.right) = calculateFillResults( @@ -303,9 +296,10 @@ contract MixinMatchOrders is rightOrderFilledAmount, rightOrderAmountToFill ); - if (status != uint8(Status.SUCCESS)) { - return (status, matchedFillResults); - } + require( + status == uint8(Status.SUCCESS), + FAILED_TO_CALCULATE_FILL_RESULTS_FOR_RIGHT_ORDER + ); // Calculate amount given to taker matchedFillResults.takerFillAmount = safeSub( @@ -316,7 +310,7 @@ contract MixinMatchOrders is // Validate the fill results validateMatchOrThrow(matchedFillResults); - // Return status & fill results - return (status, matchedFillResults); + // Return fill results + return matchedFillResults; } } 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 5ef0a52c5..84f621f64 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol @@ -55,4 +55,6 @@ contract LibExchangeErrors { string constant NEGATIVE_SPREAD = "Matched orders must have a positive spread."; string constant MISCALCULATED_TRANSFER_AMOUNTS = "A miscalculation occurred: the left maker would receive more than the right maker would spend."; string constant ROUNDING_ERROR_TRANSFER_AMOUNTS = "A rounding error occurred when calculating transfer amounts for matched orders."; + string constant FAILED_TO_CALCULATE_FILL_RESULTS_FOR_LEFT_ORDER = "Failed to calculate fill results for left order."; + string constant FAILED_TO_CALCULATE_FILL_RESULTS_FOR_RIGHT_ORDER = "Failed to calculate fill results for right order."; } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol index 6bae03ff2..27d65c69f 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol @@ -61,7 +61,7 @@ contract LibMath is require( !isRoundingError(numerator, denominator, target), ROUNDING_ERROR_ON_PARTIAL_AMOUNT - ); + ); return getPartialAmount(numerator, denominator, target); } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol index 60a266250..d8b9a22c3 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol @@ -59,7 +59,6 @@ contract MMatchOrders is /// @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, @@ -70,8 +69,5 @@ contract MMatchOrders is uint256 rightOrderFilledAmount ) internal - returns ( - uint8 status, - LibFillResults.MatchedFillResults memory matchedFillResults - ); + returns (LibFillResults.MatchedFillResults memory matchedFillResults); } diff --git a/packages/contracts/test/exchange/match_orders.ts b/packages/contracts/test/exchange/match_orders.ts index 6ba88b5fb..74e4f6760 100644 --- a/packages/contracts/test/exchange/match_orders.ts +++ b/packages/contracts/test/exchange/match_orders.ts @@ -647,7 +647,7 @@ describe('matchOrders', () => { ); }); - it('Should not transfer any amounts if left order is not fillable', async () => { + it('Should throw if left order is not fillable', async () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, @@ -668,13 +668,12 @@ describe('matchOrders', () => { // Cancel left order await exchangeWrapper.cancelOrderAsync(signedOrderLeft, signedOrderLeft.makerAddress); // Match orders - await exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress); - // Verify balances did not change - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20BalancesByOwner); + return expect( + exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), + ).to.be.rejectedWith(constants.REVERT); }); - it('Should not transfer any amounts if right order is not fillable', async () => { + it('Should throw if right order is not fillable', async () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, @@ -695,10 +694,9 @@ describe('matchOrders', () => { // Cancel right order await exchangeWrapper.cancelOrderAsync(signedOrderRight, signedOrderRight.makerAddress); // Match orders - await exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress); - // Verify balances did not change - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20BalancesByOwner); + return expect( + exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), + ).to.be.rejectedWith(constants.REVERT); }); it('should throw if there is not a positive spread', async () => { -- cgit v1.2.3 From 71483e28656842a30c06a6fdc7dccea2e62ffcc8 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 15 May 2018 22:06:12 -0700 Subject: Renamed "validate" functions to "assert" in mixin match. --- .../contracts/current/protocol/Exchange/Exchange.sol | 1 + .../current/protocol/Exchange/MixinExchangeCore.sol | 20 ++++++++++---------- .../current/protocol/Exchange/MixinMatchOrders.sol | 13 ++++++------- .../protocol/Exchange/mixins/MExchangeCore.sol | 4 ++-- .../protocol/Exchange/mixins/MMatchOrders.sol | 4 ++-- 5 files changed, 21 insertions(+), 21 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol b/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol index 438d50ecf..2d1729b1c 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol @@ -39,6 +39,7 @@ contract Exchange is string constant public VERSION = "2.0.1-alpha"; + // Mixins are instantiated in the order they are inherited constructor (bytes memory _zrxProxyData) public MixinExchangeCore() diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index 29f0e17cf..7bd88a82c 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -90,7 +90,7 @@ contract MixinExchangeCore is address takerAddress = getCurrentContextAddress(); // Either our context is valid or we revert - validateFillOrRevert( + assertValidFill( order, orderStatus, orderHash, @@ -131,7 +131,7 @@ contract MixinExchangeCore is /// Throws if order is invalid or sender does not have permission to cancel. /// @param order Order to cancel. Order must be Status.FILLABLE. /// @return True if the order state changed to cancelled. - /// False if the order was order was in a valid, but + /// False if the order was valid, but in an /// unfillable state (see LibStatus.STATUS for order states) function cancelOrder(Order memory order) public @@ -143,7 +143,7 @@ contract MixinExchangeCore is (orderStatus, orderHash, ) = getOrderInfo(order); // Validate context - validateCancelOrRevert(order, orderStatus, orderHash); + assertValidCancel(order, orderStatus, orderHash); // Perform cancel return updateCancelledState(order, orderStatus, orderHash); @@ -157,7 +157,7 @@ contract MixinExchangeCore is /// @param takerAssetFilledAmount Amount of order already filled. /// @param takerAssetFillAmount Desired amount of order to fill by taker. /// @param signature Proof that the orders was created by its maker. - function validateFillOrRevert( + function assertValidFill( Order memory order, uint8 orderStatus, bytes32 orderHash, @@ -243,22 +243,22 @@ contract MixinExchangeCore is // Compute takerAssetFilledAmount uint256 remainingTakerAssetAmount = safeSub(order.takerAssetAmount, takerAssetFilledAmount); - fillResults.takerAssetFilledAmount = min256(takerAssetFillAmount, remainingTakerAssetAmount); + uint256 newTakerAssetFilledAmount = min256(takerAssetFillAmount, remainingTakerAssetAmount); // Validate fill order rounding if (isRoundingError( - fillResults.takerAssetFilledAmount, + newTakerAssetFilledAmount, order.takerAssetAmount, order.makerAssetAmount)) { status = uint8(Status.ROUNDING_ERROR_TOO_LARGE); - fillResults.takerAssetFilledAmount = 0; - return; + return (status, fillResults); } // Compute proportional transfer amounts // TODO: All three are multiplied by the same fraction. This can // potentially be optimized. + fillResults.takerAssetFilledAmount = newTakerAssetFilledAmount; fillResults.makerAssetFilledAmount = getPartialAmount( fillResults.takerAssetFilledAmount, order.takerAssetAmount, @@ -276,7 +276,7 @@ contract MixinExchangeCore is ); status = uint8(Status.SUCCESS); - return; + return (status, fillResults); } /// @dev Updates state with results of a fill order. @@ -315,7 +315,7 @@ contract MixinExchangeCore is /// @param order that was cancelled. /// @param orderStatus Status of order that was cancelled. /// @param orderHash Hash of order that was cancelled. - function validateCancelOrRevert( + function assertValidCancel( Order memory order, uint8 orderStatus, bytes32 orderHash diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index 9f9ad6a52..744262da2 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -75,7 +75,7 @@ contract MixinMatchOrders is address takerAddress = getCurrentContextAddress(); // Either our context is valid or we revert - validateMatchOrThrow(leftOrder, rightOrder); + assertValidMatch(leftOrder, rightOrder); // Compute proportional fill amounts matchedFillResults = calculateMatchedFillResults( @@ -88,7 +88,7 @@ contract MixinMatchOrders is ); // Validate fill contexts - validateFillOrRevert( + assertValidFill( leftOrder, leftOrderInfo.orderStatus, leftOrderInfo.orderHash, @@ -97,7 +97,7 @@ contract MixinMatchOrders is matchedFillResults.left.takerAssetFilledAmount, leftSignature ); - validateFillOrRevert( + assertValidFill( rightOrder, rightOrderInfo.orderStatus, rightOrderInfo.orderHash, @@ -131,14 +131,13 @@ contract MixinMatchOrders is matchedFillResults.right ); - // Return results return matchedFillResults; } /// @dev Validates context for matchOrders. Succeeds or throws. /// @param leftOrder First order to match. /// @param rightOrder Second order to match. - function validateMatchOrThrow( + function assertValidMatch( Order memory leftOrder, Order memory rightOrder ) @@ -173,7 +172,7 @@ contract MixinMatchOrders is /// @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 validateMatchOrThrow(MatchedFillResults memory matchedFillResults) + function assertValidMatch(MatchedFillResults memory matchedFillResults) internal { // The left order must spend at least as much as we're sending to the combined @@ -308,7 +307,7 @@ contract MixinMatchOrders is ); // Validate the fill results - validateMatchOrThrow(matchedFillResults); + assertValidMatch(matchedFillResults); // Return fill results return matchedFillResults; 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 f166abb71..c88dd92db 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol @@ -64,7 +64,7 @@ contract MExchangeCore is /// @param takerAssetFilledAmount Amount of order already filled. /// @param takerAssetFillAmount Desired amount of order to fill by taker. /// @param signature Proof that the orders was created by its maker. - function validateFillOrRevert( + function assertValidFill( LibOrder.Order memory order, uint8 orderStatus, bytes32 orderHash, @@ -113,7 +113,7 @@ contract MExchangeCore is /// @param order that was cancelled. /// @param orderStatus Status of order that was cancelled. /// @param orderHash Hash of order that was cancelled. - function validateCancelOrRevert( + function assertValidCancel( LibOrder.Order memory order, uint8 orderStatus, bytes32 orderHash diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol index d8b9a22c3..7c9a481f1 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol @@ -38,7 +38,7 @@ contract MMatchOrders is /// @dev Validates context for matchOrders. Succeeds or throws. /// @param leftOrder First order to match. /// @param rightOrder Second order to match. - function validateMatchOrThrow( + function assertValidMatch( LibOrder.Order memory leftOrder, LibOrder.Order memory rightOrder ) @@ -46,7 +46,7 @@ contract MMatchOrders is /// @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 validateMatchOrThrow(LibFillResults.MatchedFillResults memory matchedFillResults) + function assertValidMatch(LibFillResults.MatchedFillResults memory matchedFillResults) internal; /// @dev Calculates fill amounts for the matched orders. -- cgit v1.2.3 From 80114edc71dae53d44352177da5ede55ba6977b0 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 16 May 2018 14:04:22 -0700 Subject: Comments for readability in exchange core and mixin match orders --- .../contracts/current/protocol/Exchange/MixinExchangeCore.sol | 9 ++++++--- .../src/contracts/current/protocol/Exchange/MixinMatchOrders.sol | 1 + .../current/protocol/Exchange/interfaces/IExchangeCore.sol | 2 +- .../current/protocol/Exchange/interfaces/IMatchOrders.sol | 1 + .../src/contracts/current/protocol/Exchange/libs/LibStatus.sol | 2 ++ 5 files changed, 11 insertions(+), 4 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index 7bd88a82c..d311505bd 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -169,8 +169,9 @@ contract MixinExchangeCore is internal { // Ensure order is valid - // An order can only be filled if it is Status.FILLABLE; + // An order can only be filled if its status is FILLABLE; // however, only invalid statuses result in a throw. + // See LibStatus for a complete description of order statuses. require( orderStatus != uint8(Status.ORDER_INVALID_MAKER_ASSET_AMOUNT), INVALID_ORDER_MAKER_ASSET_AMOUNT @@ -323,8 +324,9 @@ contract MixinExchangeCore is internal { // Ensure order is valid - // An order can only be cancelled if it is Status.FILLABLE; + // An order can only be cancelled if its status is FILLABLE; // however, only invalid statuses result in a throw. + // See LibStatus for a complete description of order statuses. require( orderStatus != uint8(Status.ORDER_INVALID_MAKER_ASSET_AMOUNT), INVALID_ORDER_MAKER_ASSET_AMOUNT @@ -366,6 +368,7 @@ contract MixinExchangeCore is returns (bool stateUpdated) { // Ensure order is fillable (otherwise cancelling does nothing) + // See LibStatus for a complete description of order statuses. if (orderStatus != uint8(Status.ORDER_FILLABLE)) { emit ExchangeStatus(uint8(orderStatus), orderHash); stateUpdated = false; @@ -390,7 +393,7 @@ contract MixinExchangeCore is /// @dev Gets information about an order: status, hash, and amount filled. /// @param order Order to gather information on. - /// @return status Status of order. Statuses are defined in the LibStatus.Status struct. + /// @return status Status of order. See LibStatus for a complete description of order statuses. /// @return orderHash Keccak-256 EIP712 hash of the order. /// @return takerAssetFilledAmount Amount of order that has been filled. function getOrderInfo(Order memory order) diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index 744262da2..35d65c213 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -48,6 +48,7 @@ contract MixinMatchOrders is /// @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. + /// TODO: Make this function external once supported by Solidity (See Solidity Issues #3199, #1603) function matchOrders( Order memory leftOrder, Order memory rightOrder, diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol index d62690933..e8dbf473b 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol @@ -51,7 +51,7 @@ contract IExchangeCore { /// @dev Gets information about an order: status, hash, and amount filled. /// @param order Order to gather information on. - /// @return status Status of order. Statuses are defined in the LibStatus.Status struct. + /// @return status Status of order. See LibStatus for a complete description of order statuses. /// @return orderHash Keccak-256 EIP712 hash of the order. /// @return takerAssetFilledAmount Amount of order that has been filled. function getOrderInfo(LibOrder.Order memory order) diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol index fe8cee5f1..f4ad20790 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol @@ -32,6 +32,7 @@ contract IMatchOrders { /// @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. + /// TODO: Make this function external once supported by Solidity (See Solidity Issues #3199, #1603) function matchOrders( LibOrder.Order memory leftOrder, LibOrder.Order memory rightOrder, diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol index fea58f64e..c29eb2284 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol @@ -37,6 +37,8 @@ contract LibStatus { INVALID_MAKER, // Invalid maker /// Order State Statuses /// + // A valid order remains fillable until it is expired, fully filled, or cancelled. + // An order's state is unaffected by external factors, like account balances. 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 -- cgit v1.2.3 From c79f3501cd412bc6dfe294c3c9a5164055bab548 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 16 May 2018 14:29:51 -0700 Subject: Renamed takerAssetFilledAmount to orderFilledAmount to more easily distinguish between fill results and order state --- .../protocol/Exchange/MixinExchangeCore.sol | 58 +++++++++++----------- .../protocol/Exchange/interfaces/IExchangeCore.sol | 4 +- .../protocol/Exchange/mixins/MExchangeCore.sol | 12 ++--- 3 files changed, 37 insertions(+), 37 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index d311505bd..9d455246d 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -83,8 +83,8 @@ contract MixinExchangeCore is // Fetch order info bytes32 orderHash; uint8 orderStatus; - uint256 takerAssetFilledAmount; - (orderStatus, orderHash, takerAssetFilledAmount) = getOrderInfo(order); + uint256 orderFilledAmount; + (orderStatus, orderHash, orderFilledAmount) = getOrderInfo(order); // Fetch taker address address takerAddress = getCurrentContextAddress(); @@ -95,7 +95,7 @@ contract MixinExchangeCore is orderStatus, orderHash, takerAddress, - takerAssetFilledAmount, + orderFilledAmount, takerAssetFillAmount, signature ); @@ -105,7 +105,7 @@ contract MixinExchangeCore is (status, fillResults) = calculateFillResults( order, orderStatus, - takerAssetFilledAmount, + orderFilledAmount, takerAssetFillAmount ); if (status != uint8(Status.SUCCESS)) { @@ -121,7 +121,7 @@ contract MixinExchangeCore is order, takerAddress, orderHash, - takerAssetFilledAmount, + orderFilledAmount, fillResults ); return fillResults; @@ -154,7 +154,7 @@ contract MixinExchangeCore is /// @param orderStatus Status of order to be filled. /// @param orderHash Hash of order to be filled. /// @param takerAddress Address of order taker. - /// @param takerAssetFilledAmount Amount of order already filled. + /// @param orderFilledAmount Amount of order already filled. /// @param takerAssetFillAmount Desired amount of order to fill by taker. /// @param signature Proof that the orders was created by its maker. function assertValidFill( @@ -162,7 +162,7 @@ contract MixinExchangeCore is uint8 orderStatus, bytes32 orderHash, address takerAddress, - uint256 takerAssetFilledAmount, + uint256 orderFilledAmount, uint256 takerAssetFillAmount, bytes memory signature ) @@ -182,7 +182,7 @@ contract MixinExchangeCore is ); // Validate Maker signature (check only if first time seen) - if (takerAssetFilledAmount == 0) { + if (orderFilledAmount == 0) { require( isValidSignature(orderHash, order.makerAddress, signature), SIGNATURE_VALIDATION_FAILED @@ -213,14 +213,14 @@ contract MixinExchangeCore is /// @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 orderFilledAmount 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 orderFilledAmount, uint256 takerAssetFillAmount ) internal @@ -243,12 +243,12 @@ contract MixinExchangeCore is } // Compute takerAssetFilledAmount - uint256 remainingTakerAssetAmount = safeSub(order.takerAssetAmount, takerAssetFilledAmount); - uint256 newTakerAssetFilledAmount = min256(takerAssetFillAmount, remainingTakerAssetAmount); + uint256 remainingTakerAssetAmount = safeSub(order.takerAssetAmount, orderFilledAmount); + uint256 takerAssetFilledAmount = min256(takerAssetFillAmount, remainingTakerAssetAmount); // Validate fill order rounding if (isRoundingError( - newTakerAssetFilledAmount, + takerAssetFilledAmount, order.takerAssetAmount, order.makerAssetAmount)) { @@ -259,7 +259,7 @@ contract MixinExchangeCore is // Compute proportional transfer amounts // TODO: All three are multiplied by the same fraction. This can // potentially be optimized. - fillResults.takerAssetFilledAmount = newTakerAssetFilledAmount; + fillResults.takerAssetFilledAmount = takerAssetFilledAmount; fillResults.makerAssetFilledAmount = getPartialAmount( fillResults.takerAssetFilledAmount, order.takerAssetAmount, @@ -283,19 +283,19 @@ contract MixinExchangeCore is /// @dev Updates state with results of a fill order. /// @param order that was filled. /// @param takerAddress Address of taker who filled the order. - /// @param takerAssetFilledAmount Amount of order already filled. + /// @param orderFilledAmount Amount of order already filled. /// @return fillResults Amounts filled and fees paid by maker and taker. function updateFilledState( Order memory order, address takerAddress, bytes32 orderHash, - uint256 takerAssetFilledAmount, + uint256 orderFilledAmount, FillResults memory fillResults ) internal { // Update state - filled[orderHash] = safeAdd(takerAssetFilledAmount, fillResults.takerAssetFilledAmount); + filled[orderHash] = safeAdd(orderFilledAmount, fillResults.takerAssetFilledAmount); // Log order emit Fill( @@ -395,14 +395,14 @@ contract MixinExchangeCore is /// @param order Order to gather information on. /// @return status Status of order. See LibStatus for a complete description of order statuses. /// @return orderHash Keccak-256 EIP712 hash of the order. - /// @return takerAssetFilledAmount Amount of order that has been filled. + /// @return orderFilledAmount Amount of order that has been filled. function getOrderInfo(Order memory order) public view returns ( uint8 orderStatus, bytes32 orderHash, - uint256 takerAssetFilledAmount + uint256 orderFilledAmount ) { // Compute the order hash @@ -414,43 +414,43 @@ contract MixinExchangeCore is // 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); + return (orderStatus, orderHash, orderFilledAmount); } // If order.takerAssetAmount is zero, then the order will always - // be considered filled because 0 == takerAssetAmount == takerAssetFilledAmount + // be considered filled because 0 == takerAssetAmount == orderFilledAmount // 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); + return (orderStatus, orderHash, orderFilledAmount); } // Validate order expiration if (block.timestamp >= order.expirationTimeSeconds) { orderStatus = uint8(Status.ORDER_EXPIRED); - return (orderStatus, orderHash, takerAssetFilledAmount); + return (orderStatus, orderHash, orderFilledAmount); } // Check if order has been cancelled if (cancelled[orderHash]) { orderStatus = uint8(Status.ORDER_CANCELLED); - return (orderStatus, orderHash, takerAssetFilledAmount); + return (orderStatus, orderHash, orderFilledAmount); } if (makerEpoch[order.makerAddress] > order.salt) { orderStatus = uint8(Status.ORDER_CANCELLED); - return (orderStatus, orderHash, takerAssetFilledAmount); + return (orderStatus, orderHash, orderFilledAmount); } // Fetch filled amount and validate order availability - takerAssetFilledAmount = filled[orderHash]; - if (takerAssetFilledAmount >= order.takerAssetAmount) { + orderFilledAmount = filled[orderHash]; + if (orderFilledAmount >= order.takerAssetAmount) { orderStatus = uint8(Status.ORDER_FULLY_FILLED); - return (orderStatus, orderHash, takerAssetFilledAmount); + return (orderStatus, orderHash, orderFilledAmount); } // All other statuses are ruled out: order is Fillable orderStatus = uint8(Status.ORDER_FILLABLE); - return (orderStatus, orderHash, takerAssetFilledAmount); + return (orderStatus, orderHash, orderFilledAmount); } } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol index e8dbf473b..2ee957faa 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol @@ -53,13 +53,13 @@ contract IExchangeCore { /// @param order Order to gather information on. /// @return status Status of order. See LibStatus for a complete description of order statuses. /// @return orderHash Keccak-256 EIP712 hash of the order. - /// @return takerAssetFilledAmount Amount of order that has been filled. + /// @return orderFilledAmount Amount of order that has been filled. function getOrderInfo(LibOrder.Order memory order) public view returns ( uint8 orderStatus, bytes32 orderHash, - uint256 takerAssetFilledAmount + uint256 orderFilledAmount ); } 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 c88dd92db..fe6c155a3 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol @@ -61,7 +61,7 @@ contract MExchangeCore is /// @param orderStatus Status of order to be filled. /// @param orderHash Hash of order to be filled. /// @param takerAddress Address of order taker. - /// @param takerAssetFilledAmount Amount of order already filled. + /// @param orderFilledAmount Amount of order already filled. /// @param takerAssetFillAmount Desired amount of order to fill by taker. /// @param signature Proof that the orders was created by its maker. function assertValidFill( @@ -69,7 +69,7 @@ contract MExchangeCore is uint8 orderStatus, bytes32 orderHash, address takerAddress, - uint256 takerAssetFilledAmount, + uint256 orderFilledAmount, uint256 takerAssetFillAmount, bytes memory signature ) @@ -78,14 +78,14 @@ contract MExchangeCore is /// @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 orderFilledAmount 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 orderFilledAmount, uint256 takerAssetFillAmount ) internal @@ -98,13 +98,13 @@ contract MExchangeCore is /// @dev Updates state with results of a fill order. /// @param order that was filled. /// @param takerAddress Address of taker who filled the order. - /// @param takerAssetFilledAmount Amount of order already filled. + /// @param orderFilledAmount Amount of order already filled. /// @return fillResults Amounts filled and fees paid by maker and taker. function updateFilledState( LibOrder.Order memory order, address takerAddress, bytes32 orderHash, - uint256 takerAssetFilledAmount, + uint256 orderFilledAmount, LibFillResults.FillResults memory fillResults ) internal; -- cgit v1.2.3 From ce177ae6f6fd5a4f23a85c97a44f2c1ca020ddff Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 16 May 2018 16:16:19 -0700 Subject: IExchange inherits from all other interfaces (in the same order as Exchange inherits Mixins) --- .../current/protocol/Exchange/interfaces/IExchange.sol | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchange.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchange.sol index 9fba3491b..20cdfa57e 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchange.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchange.sol @@ -20,15 +20,19 @@ pragma solidity ^0.4.23; pragma experimental ABIEncoderV2; import "./IExchangeCore.sol"; -import "./ISignatureValidator.sol"; -import "./IAssetProxyDispatcher.sol"; -import "./ITransactions.sol"; -import "./IWrapperFunctions.sol"; +import "./IMatchOrders"; +import "./ISettlement"; +import "./ISignatureValidator"; +import "./ITransactions"; +import "./IAssetProxyDispatcher"; +import "./IWrapperFunctions"; contract IExchange is - IWrapperFunctions, IExchangeCore, + IMatchOrders, + ISettlement, ISignatureValidator, ITransactions, - IAssetProxyDispatcher + IAssetProxyDispatcher, + IWrapperFunctions {} -- cgit v1.2.3 From c8f65a1bf9c664b21ee21b08a3a91881d7f3dce1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 16 May 2018 16:19:28 -0700 Subject: Updated order of settleMatchedOrders to align with settleOrder --- .../src/contracts/current/protocol/Exchange/MixinMatchOrders.sol | 4 ++-- .../src/contracts/current/protocol/Exchange/MixinSettlement.sol | 6 +++--- .../src/contracts/current/protocol/Exchange/mixins/MSettlement.sol | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index 35d65c213..70b6b8ff8 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -112,8 +112,8 @@ contract MixinMatchOrders is settleMatchedOrders( leftOrder, rightOrder, - matchedFillResults, - takerAddress + takerAddress, + matchedFillResults ); // Update exchange state diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol index 7e324a5d2..6bd9edae5 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol @@ -97,13 +97,13 @@ contract MixinSettlement is /// @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. + /// @param matchedFillResults Struct holding amounts to transfer between makers, taker, and fee recipients. function settleMatchedOrders( LibOrder.Order memory leftOrder, LibOrder.Order memory rightOrder, - MatchedFillResults memory matchedFillResults, - address takerAddress + address takerAddress, + MatchedFillResults memory matchedFillResults ) internal { 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 d0de385db..b78718c0c 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSettlement.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSettlement.sol @@ -38,13 +38,13 @@ contract MSettlement { /// @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. + /// @param matchedFillResults Struct holding amounts to transfer between makers, taker, and fee recipients. function settleMatchedOrders( LibOrder.Order memory leftOrder, LibOrder.Order memory rightOrder, - LibFillResults.MatchedFillResults memory matchedFillResults, - address takerAddress + address takerAddress, + LibFillResults.MatchedFillResults memory matchedFillResults ) internal; } -- cgit v1.2.3 From 636dae6a797ecdbdea186c1590ee35eec4521f41 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 16 May 2018 16:57:02 -0700 Subject: The OrderInfo struct is now returned by the getOrderInfo function --- .../protocol/Exchange/MixinExchangeCore.sol | 76 +++++++-------- .../current/protocol/Exchange/MixinMatchOrders.sol | 16 +--- .../protocol/Exchange/interfaces/IExchangeCore.sol | 11 +-- .../current/protocol/Exchange/libs/LibOrder.sol | 9 ++ .../protocol/Exchange/mixins/MMatchOrders.sol | 8 -- packages/contracts/src/utils/exchange_wrapper.ts | 8 +- packages/contracts/src/utils/types.ts | 6 ++ packages/contracts/test/exchange/match_orders.ts | 105 ++++++++------------- 8 files changed, 98 insertions(+), 141 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index 9d455246d..7e9c4f8fa 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -81,10 +81,7 @@ contract MixinExchangeCore is returns (FillResults memory fillResults) { // Fetch order info - bytes32 orderHash; - uint8 orderStatus; - uint256 orderFilledAmount; - (orderStatus, orderHash, orderFilledAmount) = getOrderInfo(order); + OrderInfo memory orderInfo = getOrderInfo(order); // Fetch taker address address takerAddress = getCurrentContextAddress(); @@ -92,10 +89,10 @@ contract MixinExchangeCore is // Either our context is valid or we revert assertValidFill( order, - orderStatus, - orderHash, + orderInfo.orderStatus, + orderInfo.orderHash, takerAddress, - orderFilledAmount, + orderInfo.orderFilledAmount, takerAssetFillAmount, signature ); @@ -104,12 +101,12 @@ contract MixinExchangeCore is uint8 status; (status, fillResults) = calculateFillResults( order, - orderStatus, - orderFilledAmount, + orderInfo.orderStatus, + orderInfo.orderFilledAmount, takerAssetFillAmount ); if (status != uint8(Status.SUCCESS)) { - emit ExchangeStatus(uint8(status), orderHash); + emit ExchangeStatus(uint8(status), orderInfo.orderHash); return fillResults; } @@ -120,8 +117,8 @@ contract MixinExchangeCore is updateFilledState( order, takerAddress, - orderHash, - orderFilledAmount, + orderInfo.orderHash, + orderInfo.orderFilledAmount, fillResults ); return fillResults; @@ -138,15 +135,13 @@ contract MixinExchangeCore is returns (bool) { // Fetch current order status - bytes32 orderHash; - uint8 orderStatus; - (orderStatus, orderHash, ) = getOrderInfo(order); + OrderInfo memory orderInfo = getOrderInfo(order); // Validate context - assertValidCancel(order, orderStatus, orderHash); + assertValidCancel(order, orderInfo.orderStatus, orderInfo.orderHash); // Perform cancel - return updateCancelledState(order, orderStatus, orderHash); + return updateCancelledState(order, orderInfo.orderStatus, orderInfo.orderHash); } /// @dev Validates context for fillOrder. Succeeds or throws. @@ -393,28 +388,23 @@ contract MixinExchangeCore is /// @dev Gets information about an order: status, hash, and amount filled. /// @param order Order to gather information on. - /// @return status Status of order. See LibStatus for a complete description of order statuses. - /// @return orderHash Keccak-256 EIP712 hash of the order. - /// @return orderFilledAmount Amount of order that has been filled. + /// @return OrderInfo Information about the order and its state. + /// See LibOrder.OrderInfo for a complete description. function getOrderInfo(Order memory order) public view - returns ( - uint8 orderStatus, - bytes32 orderHash, - uint256 orderFilledAmount - ) + returns (LibOrder.OrderInfo memory orderInfo) { // Compute the order hash - orderHash = getOrderHash(order); + orderInfo.orderHash = getOrderHash(order); // 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, orderFilledAmount); + orderInfo.orderStatus = uint8(Status.ORDER_INVALID_MAKER_ASSET_AMOUNT); + return orderInfo; } // If order.takerAssetAmount is zero, then the order will always @@ -422,35 +412,35 @@ contract MixinExchangeCore is // 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, orderFilledAmount); + orderInfo.orderStatus = uint8(Status.ORDER_INVALID_TAKER_ASSET_AMOUNT); + return orderInfo; } // Validate order expiration if (block.timestamp >= order.expirationTimeSeconds) { - orderStatus = uint8(Status.ORDER_EXPIRED); - return (orderStatus, orderHash, orderFilledAmount); + orderInfo.orderStatus = uint8(Status.ORDER_EXPIRED); + return orderInfo; } // Check if order has been cancelled - if (cancelled[orderHash]) { - orderStatus = uint8(Status.ORDER_CANCELLED); - return (orderStatus, orderHash, orderFilledAmount); + if (cancelled[orderInfo.orderHash]) { + orderInfo.orderStatus = uint8(Status.ORDER_CANCELLED); + return orderInfo; } if (makerEpoch[order.makerAddress] > order.salt) { - orderStatus = uint8(Status.ORDER_CANCELLED); - return (orderStatus, orderHash, orderFilledAmount); + orderInfo.orderStatus = uint8(Status.ORDER_CANCELLED); + return orderInfo; } // Fetch filled amount and validate order availability - orderFilledAmount = filled[orderHash]; - if (orderFilledAmount >= order.takerAssetAmount) { - orderStatus = uint8(Status.ORDER_FULLY_FILLED); - return (orderStatus, orderHash, orderFilledAmount); + orderInfo.orderFilledAmount = filled[orderInfo.orderHash]; + if (orderInfo.orderFilledAmount >= order.takerAssetAmount) { + orderInfo.orderStatus = uint8(Status.ORDER_FULLY_FILLED); + return orderInfo; } // All other statuses are ruled out: order is Fillable - orderStatus = uint8(Status.ORDER_FILLABLE); - return (orderStatus, orderHash, orderFilledAmount); + orderInfo.orderStatus = uint8(Status.ORDER_FILLABLE); + return orderInfo; } } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index 70b6b8ff8..d53653fbf 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -58,19 +58,9 @@ contract MixinMatchOrders is public returns (MatchedFillResults memory matchedFillResults) { - // Get left status - OrderInfo memory leftOrderInfo; - ( leftOrderInfo.orderStatus, - leftOrderInfo.orderHash, - leftOrderInfo.orderFilledAmount - ) = getOrderInfo(leftOrder); - - // Get right status - OrderInfo memory rightOrderInfo; - ( rightOrderInfo.orderStatus, - rightOrderInfo.orderHash, - rightOrderInfo.orderFilledAmount - ) = getOrderInfo(rightOrder); + // Get left & right order info + OrderInfo memory leftOrderInfo = getOrderInfo(leftOrder); + OrderInfo memory rightOrderInfo = getOrderInfo(rightOrder); // Fetch taker address address takerAddress = getCurrentContextAddress(); diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol index 2ee957faa..958a89c39 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol @@ -51,15 +51,10 @@ contract IExchangeCore { /// @dev Gets information about an order: status, hash, and amount filled. /// @param order Order to gather information on. - /// @return status Status of order. See LibStatus for a complete description of order statuses. - /// @return orderHash Keccak-256 EIP712 hash of the order. - /// @return orderFilledAmount Amount of order that has been filled. + /// @return OrderInfo Information about the order and its state. + /// See LibOrder.OrderInfo for a complete description. function getOrderInfo(LibOrder.Order memory order) public view - returns ( - uint8 orderStatus, - bytes32 orderHash, - uint256 orderFilledAmount - ); + returns (LibOrder.OrderInfo memory orderInfo); } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol index 0fe8c8c96..5f00b87f0 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol @@ -51,6 +51,15 @@ contract LibOrder { bytes takerAssetData; } + struct OrderInfo { + // See LibStatus for a complete description of order statuses + uint8 orderStatus; + // Keccak-256 EIP712 hash of the order + bytes32 orderHash; + // Amount of order that has been filled + uint256 orderFilledAmount; + } + /// @dev Calculates Keccak-256 hash of the order. /// @param order The order structure. /// @return Keccak-256 EIP712 hash of the order. diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol index 7c9a481f1..d71f4e120 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol @@ -27,14 +27,6 @@ contract MMatchOrders is IMatchOrders { - /// 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. diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts index 5b026fce0..7032db386 100644 --- a/packages/contracts/src/utils/exchange_wrapper.ts +++ b/packages/contracts/src/utils/exchange_wrapper.ts @@ -9,7 +9,7 @@ import { constants } from './constants'; import { formatters } from './formatters'; import { LogDecoder } from './log_decoder'; import { orderUtils } from './order_utils'; -import { AssetProxyId, SignedOrder, SignedTransaction } from './types'; +import { AssetProxyId, OrderInfo, SignedOrder, SignedTransaction } from './types'; export class ExchangeWrapper { private _exchange: ExchangeContract; @@ -225,10 +225,8 @@ export class ExchangeWrapper { const filledAmount = new BigNumber(await this._exchange.filled.callAsync(orderHashHex)); return filledAmount; } - public async getOrderInfoAsync( - signedOrder: SignedOrder, - ): Promise<[number /* orderStatus */, string /* orderHash */, BigNumber /* orderTakerAssetAmountFilled */]> { - const orderInfo: [number, string, BigNumber] = await this._exchange.getOrderInfo.callAsync(signedOrder); + public async getOrderInfoAsync(signedOrder: SignedOrder): Promise { + const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo; return orderInfo; } public async matchOrdersAsync( diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts index 0e3b2c9a8..518776214 100644 --- a/packages/contracts/src/utils/types.ts +++ b/packages/contracts/src/utils/types.ts @@ -174,3 +174,9 @@ export interface TransferAmountsByMatchOrders { feeReceivedLeft: BigNumber; feeReceivedRight: BigNumber; } + +export interface OrderInfo { + orderStatus: number; + orderHash: string; + orderFilledAmount: BigNumber; +} diff --git a/packages/contracts/test/exchange/match_orders.ts b/packages/contracts/test/exchange/match_orders.ts index 74e4f6760..cc1486298 100644 --- a/packages/contracts/test/exchange/match_orders.ts +++ b/packages/contracts/test/exchange/match_orders.ts @@ -29,6 +29,7 @@ import { ERC20BalancesByOwner, ERC721TokenIdsByOwner, ExchangeStatus, + OrderInfo, SignedOrder, } from '../../src/utils/types'; import { chaiSetup } from '../utils/chai_setup'; @@ -180,13 +181,11 @@ describe('matchOrders', () => { erc721TokenIdsByOwner, ); // Verify left order was fully filled - const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); - expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); // Verify right order was fully filled - const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( - signedOrderRight, - ); - expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight); + expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); }); it('should transfer the correct amounts when orders completely fill each other and taker doesnt take a profit', async () => { @@ -224,13 +223,11 @@ describe('matchOrders', () => { erc721TokenIdsByOwner, ); // Verify left order was fully filled - const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); - expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); // Verify right order was fully filled - const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( - signedOrderRight, - ); - expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight); + expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); // Verify taker did not take a profit expect(takerInitialBalances).to.be.deep.equal( newERC20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress], @@ -265,13 +262,11 @@ describe('matchOrders', () => { erc721TokenIdsByOwner, ); // Verify left order was fully filled - const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); - expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); // Verify right order was partially filled - const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( - signedOrderRight, - ); - expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); + const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight); + expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); }); it('should transfer the correct amounts when right order is completely filled and left order is partially filled', async () => { @@ -302,13 +297,11 @@ describe('matchOrders', () => { erc721TokenIdsByOwner, ); // Verify left order was partially filled - const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); - expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); + const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); // Verify right order was fully filled - const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( - signedOrderRight, - ); - expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight); + expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); }); it('should transfer the correct amounts when consecutive calls are used to completely fill the left order', async () => { @@ -344,13 +337,11 @@ describe('matchOrders', () => { erc721TokenIdsByOwner, ); // Verify left order was partially filled - const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); - expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); + const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); // Verify right order was fully filled - const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( - signedOrderRight, - ); - expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight); + expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); // Construct second right order // Note: This order needs makerAssetAmount=90/takerAssetAmount=[anything <= 45] to fully fill the right order. // However, we use 100/50 to ensure a partial fill as we want to go down the "left fill" @@ -377,15 +368,11 @@ describe('matchOrders', () => { rightTakerAssetFilledAmount, ); // Verify left order was fully filled - const leftOrderInfo2: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( - signedOrderLeft, - ); - expect(leftOrderInfo2[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + const leftOrderInfo2: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + expect(leftOrderInfo2.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); // Verify second right order was partially filled - const rightOrderInfo2: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( - signedOrderRight2, - ); - expect(rightOrderInfo2[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); + const rightOrderInfo2: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight2); + expect(rightOrderInfo2.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); }); it('should transfer the correct amounts when consecutive calls are used to completely fill the right order', async () => { @@ -422,13 +409,11 @@ describe('matchOrders', () => { erc721TokenIdsByOwner, ); // Verify left order was partially filled - const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); - expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); // Verify right order was fully filled - const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( - signedOrderRight, - ); - expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); + const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight); + expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); // Create second left order // Note: This order needs makerAssetAmount=96/takerAssetAmount=48 to fully fill the right order. // However, we use 100/50 to ensure a partial fill as we want to go down the "right fill" @@ -458,15 +443,11 @@ describe('matchOrders', () => { rightTakerAssetFilledAmount, ); // Verify second left order was partially filled - const leftOrderInfo2: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( - signedOrderLeft2, - ); - expect(leftOrderInfo2[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); + const leftOrderInfo2: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft2); + expect(leftOrderInfo2.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE); // Verify right order was fully filled - const rightOrderInfo2: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( - signedOrderRight, - ); - expect(rightOrderInfo2[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + const rightOrderInfo2: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight); + expect(rightOrderInfo2.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); }); it('should transfer the correct amounts if fee recipient is the same across both matched orders', async () => { @@ -821,13 +802,11 @@ describe('matchOrders', () => { erc721TokenIdsByOwner, ); // Verify left order was fully filled - const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); - expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); // Verify right order was fully filled - const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( - signedOrderRight, - ); - expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight); + expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); }); it('should transfer correct amounts when right order maker asset is an ERC721 token', async () => { @@ -859,13 +838,11 @@ describe('matchOrders', () => { erc721TokenIdsByOwner, ); // Verify left order was fully filled - const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); - expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); // Verify right order was fully filled - const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync( - signedOrderRight, - ); - expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); + const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight); + expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED); }); }); }); // tslint:disable-line:max-file-line-count -- cgit v1.2.3 From 28bb11217ca004aa01f56d38a3281b6f313c9a16 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 16 May 2018 16:59:50 -0700 Subject: Removed redundant log decode call --- packages/contracts/src/utils/exchange_wrapper.ts | 2 -- 1 file changed, 2 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts index 7032db386..46531fa3f 100644 --- a/packages/contracts/src/utils/exchange_wrapper.ts +++ b/packages/contracts/src/utils/exchange_wrapper.ts @@ -243,8 +243,6 @@ export class ExchangeWrapper { { from }, ); const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash); - tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address); - tx.logs = _.map(tx.logs, log => this._logDecoder.decodeLogOrThrow(log)); return tx; } private async _getTxWithDecodedExchangeLogsAsync(txHash: string) { -- cgit v1.2.3 From e748e1891b6af5c5f60f5c1fc45482ace6a3b2ad Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 16 May 2018 17:27:43 -0700 Subject: Style improvements to the match orders typescript test class --- .../contracts/test/utils/match_order_tester.ts | 32 ++++++++++------------ 1 file changed, 14 insertions(+), 18 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/test/utils/match_order_tester.ts b/packages/contracts/test/utils/match_order_tester.ts index 4e3459734..467b65927 100644 --- a/packages/contracts/test/utils/match_order_tester.ts +++ b/packages/contracts/test/utils/match_order_tester.ts @@ -158,7 +158,6 @@ export class MatchOrderTester { return [expectedNewERC20BalancesByOwner, expectedNewERC721TokenIdsByOwner]; } - /// @dev Compares a pair of ERC20 balances and a pair of ERC721 token owners. /// @param expectedNewERC20BalancesByOwner Expected ERC20 balances. /// @param realERC20BalancesByOwner Actual ERC20 balances. @@ -193,7 +192,6 @@ export class MatchOrderTester { const erc721TokenIdsMatch = _.isEqual(sortedExpectedNewERC721TokenIdsByOwner, sortedNewERC721TokenIdsByOwner); return erc721TokenIdsMatch; } - /// @dev Constructs new MatchOrderTester. /// @param exchangeWrapper Used to call to the Exchange. /// @param erc20Wrapper Used to fetch ERC20 balances. @@ -203,7 +201,6 @@ export class MatchOrderTester { this._erc20Wrapper = erc20Wrapper; this._erc721Wrapper = erc721Wrapper; } - /// @dev Matches two complementary orders and validates results. /// Validation either succeeds or throws. /// @param signedOrderLeft First matched order. @@ -231,21 +228,21 @@ export class MatchOrderTester { const feeRecipientAddressLeft = signedOrderLeft.feeRecipientAddress; const feeRecipientAddressRight = signedOrderRight.feeRecipientAddress; // Verify Left order preconditions - const takerAssetFilledAmountBeforeLeft = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( + const orderFilledAmountLeft = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( orderUtils.getOrderHashHex(signedOrderLeft), ); - const expectedLeftOrderFillAmoutBeforeMatch = initialTakerAssetFilledAmountLeft + const expectedOrderFilledAmountLeft = initialTakerAssetFilledAmountLeft ? initialTakerAssetFilledAmountLeft : new BigNumber(0); - expect(takerAssetFilledAmountBeforeLeft).to.be.bignumber.equal(expectedLeftOrderFillAmoutBeforeMatch); + expect(expectedOrderFilledAmountLeft).to.be.bignumber.equal(orderFilledAmountLeft); // Verify Right order preconditions - const takerAssetFilledAmountBeforeRight = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( + const orderFilledAmountRight = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( orderUtils.getOrderHashHex(signedOrderRight), ); - const expectedRightOrderFillAmoutBeforeMatch = initialTakerAssetFilledAmountRight + const expectedOrderFilledAmountRight = initialTakerAssetFilledAmountRight ? initialTakerAssetFilledAmountRight : new BigNumber(0); - expect(takerAssetFilledAmountBeforeRight).to.be.bignumber.equal(expectedRightOrderFillAmoutBeforeMatch); + expect(expectedOrderFilledAmountRight).to.be.bignumber.equal(orderFilledAmountRight); // Match left & right orders await this._exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress); const newERC20BalancesByOwner = await this._erc20Wrapper.getBalancesAsync(); @@ -254,8 +251,8 @@ export class MatchOrderTester { const expectedTransferAmounts = await this._calculateExpectedTransferAmountsAsync( signedOrderLeft, signedOrderRight, - expectedLeftOrderFillAmoutBeforeMatch, - expectedRightOrderFillAmoutBeforeMatch, + orderFilledAmountLeft, + orderFilledAmountRight, ); let expectedERC20BalancesByOwner: ERC20BalancesByOwner; let expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner; @@ -278,24 +275,23 @@ export class MatchOrderTester { expect(expectedBalancesMatchRealBalances).to.be.true(); return [newERC20BalancesByOwner, newERC721TokenIdsByOwner]; } - /// @dev Calculates expected transfer amounts between order makers, fee recipients, and /// the taker when two orders are matched. /// @param signedOrderLeft First matched order. /// @param signedOrderRight Second matched order. - /// @param expectedLeftOrderFillAmoutBeforeMatch How much we expect the left order has been filled, prior to matching orders. - /// @param expectedRightOrderFillAmoutBeforeMatch How much we expect the right order has been filled, prior to matching orders. + /// @param orderFilledAmountLeft How much left order has been filled, prior to matching orders. + /// @param orderFilledAmountRight How much the right order has been filled, prior to matching orders. /// @return TransferAmounts A struct containing the expected transfer amounts. private async _calculateExpectedTransferAmountsAsync( signedOrderLeft: SignedOrder, signedOrderRight: SignedOrder, - expectedLeftOrderFillAmoutBeforeMatch: BigNumber, - expectedRightOrderFillAmoutBeforeMatch: BigNumber, + orderFilledAmountLeft: BigNumber, + orderFilledAmountRight: BigNumber, ): Promise { let amountBoughtByLeftMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( orderUtils.getOrderHashHex(signedOrderLeft), ); - amountBoughtByLeftMaker = amountBoughtByLeftMaker.minus(expectedLeftOrderFillAmoutBeforeMatch); + amountBoughtByLeftMaker = amountBoughtByLeftMaker.minus(orderFilledAmountLeft); const amountSoldByLeftMaker = amountBoughtByLeftMaker .times(signedOrderLeft.makerAssetAmount) .dividedToIntegerBy(signedOrderLeft.takerAssetAmount); @@ -306,7 +302,7 @@ export class MatchOrderTester { let amountBoughtByRightMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( orderUtils.getOrderHashHex(signedOrderRight), ); - amountBoughtByRightMaker = amountBoughtByRightMaker.minus(expectedRightOrderFillAmoutBeforeMatch); + amountBoughtByRightMaker = amountBoughtByRightMaker.minus(orderFilledAmountRight); const amountSoldByRightMaker = amountBoughtByRightMaker .times(signedOrderRight.makerAssetAmount) .dividedToIntegerBy(signedOrderRight.takerAssetAmount); -- cgit v1.2.3 From 2eb5819851252eb90207765ed64a119ad138c0ad Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 16 May 2018 17:55:50 -0700 Subject: Cleaned up interface for decoding proxy data. Added a general decoder, which should be useful for the forwarding contract code. --- packages/contracts/src/utils/asset_proxy_utils.ts | 41 +++++++++++++++++++--- packages/contracts/src/utils/types.ts | 17 +++++++++ .../contracts/test/utils/match_order_tester.ts | 22 ++++++------ 3 files changed, 63 insertions(+), 17 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/utils/asset_proxy_utils.ts b/packages/contracts/src/utils/asset_proxy_utils.ts index 9a26a9ca7..c042da5d0 100644 --- a/packages/contracts/src/utils/asset_proxy_utils.ts +++ b/packages/contracts/src/utils/asset_proxy_utils.ts @@ -2,7 +2,7 @@ import { BigNumber } from '@0xproject/utils'; import BN = require('bn.js'); import ethUtil = require('ethereumjs-util'); -import { AssetProxyId } from './types'; +import { AssetProxyId, ERC20ProxyData, ERC721ProxyData, ProxyData } from './types'; export const assetProxyUtils = { encodeAssetProxyId(assetProxyId: AssetProxyId): Buffer { @@ -43,7 +43,7 @@ export const assetProxyUtils = { const encodedMetadataHex = ethUtil.bufferToHex(encodedMetadata); return encodedMetadataHex; }, - decodeERC20ProxyData(proxyData: string): string /* tokenAddress */ { + decodeERC20ProxyData(proxyData: string): ERC20ProxyData { const encodedProxyMetadata = ethUtil.toBuffer(proxyData); if (encodedProxyMetadata.byteLength !== 21) { throw new Error( @@ -63,7 +63,11 @@ export const assetProxyUtils = { } const encodedTokenAddress = encodedProxyMetadata.slice(1, 21); const tokenAddress = assetProxyUtils.decodeAddress(encodedTokenAddress); - return tokenAddress; + const erc20ProxyData = { + assetProxyId, + tokenAddress, + }; + return erc20ProxyData; }, encodeERC721ProxyData(tokenAddress: string, tokenId: BigNumber): string { const encodedAssetProxyId = assetProxyUtils.encodeAssetProxyId(AssetProxyId.ERC721); @@ -73,7 +77,7 @@ export const assetProxyUtils = { const encodedMetadataHex = ethUtil.bufferToHex(encodedMetadata); return encodedMetadataHex; }, - decodeERC721ProxyData(proxyData: string): [string /* tokenAddress */, BigNumber /* tokenId */] { + decodeERC721ProxyData(proxyData: string): ERC721ProxyData { const encodedProxyMetadata = ethUtil.toBuffer(proxyData); if (encodedProxyMetadata.byteLength !== 53) { throw new Error( @@ -95,7 +99,12 @@ export const assetProxyUtils = { const tokenAddress = assetProxyUtils.decodeAddress(encodedTokenAddress); const encodedTokenId = encodedProxyMetadata.slice(21, 53); const tokenId = assetProxyUtils.decodeUint256(encodedTokenId); - return [tokenAddress, tokenId]; + const erc721ProxyData = { + assetProxyId, + tokenAddress, + tokenId, + }; + return erc721ProxyData; }, decodeProxyDataId(proxyData: string): AssetProxyId { const encodedProxyMetadata = ethUtil.toBuffer(proxyData); @@ -110,4 +119,26 @@ export const assetProxyUtils = { const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId); return assetProxyId; }, + decodeProxyData(proxyData: string): ProxyData { + const assetProxyId = assetProxyUtils.decodeProxyDataId(proxyData); + switch (assetProxyId) { + case AssetProxyId.ERC20: + const erc20ProxyData = assetProxyUtils.decodeERC20ProxyData(proxyData); + const generalizedERC20ProxyData = { + assetProxyId, + tokenAddress: erc20ProxyData.tokenAddress, + }; + return generalizedERC20ProxyData; + case AssetProxyId.ERC721: + const erc721ProxyData = assetProxyUtils.decodeERC721ProxyData(proxyData); + const generaliedERC721ProxyData = { + assetProxyId, + tokenAddress: erc721ProxyData.tokenAddress, + data: erc721ProxyData.tokenId, + }; + return generaliedERC721ProxyData; + default: + throw new Error(`Unrecognized asset proxy id: ${assetProxyId}`); + } + }, }; diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts index 518776214..ba1243672 100644 --- a/packages/contracts/src/utils/types.ts +++ b/packages/contracts/src/utils/types.ts @@ -180,3 +180,20 @@ export interface OrderInfo { orderHash: string; orderFilledAmount: BigNumber; } + +export interface ERC20ProxyData { + assetProxyId: AssetProxyId; + tokenAddress: string; +} + +export interface ERC721ProxyData { + assetProxyId: AssetProxyId; + tokenAddress: string; + tokenId: BigNumber; +} + +export interface ProxyData { + assetProxyId: AssetProxyId; + tokenAddress?: string; + data?: any; +} diff --git a/packages/contracts/test/utils/match_order_tester.ts b/packages/contracts/test/utils/match_order_tester.ts index 467b65927..2e15b5e1e 100644 --- a/packages/contracts/test/utils/match_order_tester.ts +++ b/packages/contracts/test/utils/match_order_tester.ts @@ -73,7 +73,8 @@ export class MatchOrderTester { const makerAssetProxyIdLeft = assetProxyUtils.decodeProxyDataId(signedOrderLeft.makerAssetData); if (makerAssetProxyIdLeft === AssetProxyId.ERC20) { // Decode asset data - const makerAssetAddressLeft = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.makerAssetData); + const erc20ProxyData = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.makerAssetData); + const makerAssetAddressLeft = erc20ProxyData.tokenAddress; const takerAssetAddressRight = makerAssetAddressLeft; // Left Maker expectedNewERC20BalancesByOwner[makerAddressLeft][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ @@ -91,11 +92,9 @@ export class MatchOrderTester { ][makerAssetAddressLeft].add(expectedTransferAmounts.amountReceivedByTaker); } else if (makerAssetProxyIdLeft === AssetProxyId.ERC721) { // Decode asset data - let makerAssetAddressLeft; - let makerAssetIdLeft; - [makerAssetAddressLeft, makerAssetIdLeft] = assetProxyUtils.decodeERC721ProxyData( - signedOrderLeft.makerAssetData, - ); + const erc721ProxyData = assetProxyUtils.decodeERC721ProxyData(signedOrderLeft.makerAssetData); + const makerAssetAddressLeft = erc721ProxyData.tokenAddress; + const makerAssetIdLeft = erc721ProxyData.tokenId; const takerAssetAddressRight = makerAssetAddressLeft; const takerAssetIdRight = makerAssetIdLeft; // Left Maker @@ -109,7 +108,8 @@ export class MatchOrderTester { const takerAssetProxyIdLeft = assetProxyUtils.decodeProxyDataId(signedOrderLeft.takerAssetData); if (takerAssetProxyIdLeft === AssetProxyId.ERC20) { // Decode asset data - const takerAssetAddressLeft = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.takerAssetData); + const erc20ProxyData = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.takerAssetData); + const takerAssetAddressLeft = erc20ProxyData.tokenAddress; const makerAssetAddressRight = takerAssetAddressLeft; // Left Maker expectedNewERC20BalancesByOwner[makerAddressLeft][takerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ @@ -123,11 +123,9 @@ export class MatchOrderTester { ); } else if (takerAssetProxyIdLeft === AssetProxyId.ERC721) { // Decode asset data - let makerAssetAddressRight; - let makerAssetIdRight; - [makerAssetAddressRight, makerAssetIdRight] = assetProxyUtils.decodeERC721ProxyData( - signedOrderRight.makerAssetData, - ); + const erc721ProxyData = assetProxyUtils.decodeERC721ProxyData(signedOrderRight.makerAssetData); + const makerAssetAddressRight = erc721ProxyData.tokenAddress; + const makerAssetIdRight = erc721ProxyData.tokenId; const takerAssetAddressLeft = makerAssetAddressRight; const takerAssetIdLeft = makerAssetIdRight; // Right Maker -- cgit v1.2.3 From 80285a300d61bb45688c850d103df94ae589d5cc Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 16 May 2018 18:44:49 -0700 Subject: Wording of calculateMatchedFillResults --- .../current/protocol/Exchange/MixinMatchOrders.sol | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index d53653fbf..e353b19e8 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -228,13 +228,15 @@ contract MixinMatchOrders is internal returns (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: - // <= * - // <= * / - // * <= * + // We settle orders at the exchange rate of the right order. + // The amount saved by the left maker goes to the taker. + // Either the left or right order will be fully filled; possibly both. + // The left order is fully filled iff the right order can sell more than left can buy. + // That is: 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; @@ -243,7 +245,7 @@ contract MixinMatchOrders is safeMul(leftTakerAssetAmountRemaining, rightOrder.takerAssetAmount) <= safeMul(rightTakerAssetAmountRemaining, rightOrder.makerAssetAmount) ) { - // Left order is the constraint: maximally fill left + // Left order will be fully filled: maximally fill left leftOrderAmountToFill = leftTakerAssetAmountRemaining; // The right order receives an amount proportional to how much was spent. @@ -254,7 +256,7 @@ contract MixinMatchOrders is leftOrderAmountToFill ); } else { - // Right order is the constraint: maximally fill right + // Right order will be fully filled: maximally fill right rightOrderAmountToFill = rightTakerAssetAmountRemaining; // The left order receives an amount proportional to how much was spent. -- cgit v1.2.3 From dbbd32d2ce0329f5219f2c477589e8a2b9a73472 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 17 May 2018 13:15:19 -0700 Subject: Moved feeTokenAddress to MatchOrderTester constructor. Since it is constant, we dont need to pass it in on each call. --- packages/contracts/test/exchange/match_orders.ts | 21 +- .../contracts/test/utils/match_order_tester.ts | 243 +++++++++++---------- 2 files changed, 126 insertions(+), 138 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/test/exchange/match_orders.ts b/packages/contracts/test/exchange/match_orders.ts index cc1486298..073ef4136 100644 --- a/packages/contracts/test/exchange/match_orders.ts +++ b/packages/contracts/test/exchange/match_orders.ts @@ -139,7 +139,7 @@ describe('matchOrders', () => { const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)]; orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParams); // Set match order tester - matchOrderTester = new MatchOrderTester(exchangeWrapper, erc20Wrapper, erc721Wrapper); + matchOrderTester = new MatchOrderTester(exchangeWrapper, erc20Wrapper, erc721Wrapper, zrxToken.address); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -175,7 +175,6 @@ describe('matchOrders', () => { await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, - zrxToken.address, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, @@ -217,7 +216,6 @@ describe('matchOrders', () => { ] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, - zrxToken.address, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, @@ -256,7 +254,6 @@ describe('matchOrders', () => { await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, - zrxToken.address, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, @@ -291,7 +288,6 @@ describe('matchOrders', () => { await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, - zrxToken.address, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, @@ -331,7 +327,6 @@ describe('matchOrders', () => { ] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, - zrxToken.address, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, @@ -360,7 +355,6 @@ describe('matchOrders', () => { await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight2, - zrxToken.address, takerAddress, newERC20BalancesByOwner, erc721TokenIdsByOwner, @@ -403,7 +397,6 @@ describe('matchOrders', () => { ] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, - zrxToken.address, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, @@ -435,7 +428,6 @@ describe('matchOrders', () => { await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft2, signedOrderRight, - zrxToken.address, takerAddress, newERC20BalancesByOwner, erc721TokenIdsByOwner, @@ -472,7 +464,6 @@ describe('matchOrders', () => { await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, - zrxToken.address, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, @@ -502,7 +493,6 @@ describe('matchOrders', () => { await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, - zrxToken.address, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, @@ -532,7 +522,6 @@ describe('matchOrders', () => { await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, - zrxToken.address, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, @@ -562,7 +551,6 @@ describe('matchOrders', () => { await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, - zrxToken.address, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, @@ -592,7 +580,6 @@ describe('matchOrders', () => { await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, - zrxToken.address, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, @@ -621,7 +608,6 @@ describe('matchOrders', () => { await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, - zrxToken.address, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, @@ -703,7 +689,6 @@ describe('matchOrders', () => { matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, - zrxToken.address, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, @@ -734,7 +719,6 @@ describe('matchOrders', () => { matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, - zrxToken.address, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, @@ -765,7 +749,6 @@ describe('matchOrders', () => { matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, - zrxToken.address, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, @@ -796,7 +779,6 @@ describe('matchOrders', () => { await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, - zrxToken.address, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, @@ -832,7 +814,6 @@ describe('matchOrders', () => { await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, - zrxToken.address, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, diff --git a/packages/contracts/test/utils/match_order_tester.ts b/packages/contracts/test/utils/match_order_tester.ts index 2e15b5e1e..ba6610b68 100644 --- a/packages/contracts/test/utils/match_order_tester.ts +++ b/packages/contracts/test/utils/match_order_tester.ts @@ -42,120 +42,8 @@ export class MatchOrderTester { private _exchangeWrapper: ExchangeWrapper; private _erc20Wrapper: ERC20Wrapper; private _erc721Wrapper: ERC721Wrapper; + private _feeTokenAddress: string; - /// @dev Calculates the expected balances of order makers, fee recipients, and the taker, - /// as a result of matching two orders. - /// @param signedOrderLeft First matched order. - /// @param signedOrderRight Second matched order. - /// @param feeTokenAddress Address of ERC20 fee token. - /// @param takerAddress Address of taker (the address who matched the two orders) - /// @param erc20BalancesByOwner Current ERC20 balances. - /// @param erc721TokenIdsByOwner Current ERC721 token owners. - /// @param expectedTransferAmounts A struct containing the expected transfer amounts. - /// @return Expected ERC20 balances & ERC721 token owners after orders have been matched. - private static _calculateExpectedBalances( - signedOrderLeft: SignedOrder, - signedOrderRight: SignedOrder, - feeTokenAddress: string, - takerAddress: string, - erc20BalancesByOwner: ERC20BalancesByOwner, - erc721TokenIdsByOwner: ERC721TokenIdsByOwner, - expectedTransferAmounts: TransferAmounts, - ): [ERC20BalancesByOwner, ERC721TokenIdsByOwner] { - const makerAddressLeft = signedOrderLeft.makerAddress; - const makerAddressRight = signedOrderRight.makerAddress; - const feeRecipientAddressLeft = signedOrderLeft.feeRecipientAddress; - const feeRecipientAddressRight = signedOrderRight.feeRecipientAddress; - // Operations are performed on copies of the balances - const expectedNewERC20BalancesByOwner = _.cloneDeep(erc20BalancesByOwner); - const expectedNewERC721TokenIdsByOwner = _.cloneDeep(erc721TokenIdsByOwner); - // Left Maker Asset (Right Taker Asset) - const makerAssetProxyIdLeft = assetProxyUtils.decodeProxyDataId(signedOrderLeft.makerAssetData); - if (makerAssetProxyIdLeft === AssetProxyId.ERC20) { - // Decode asset data - const erc20ProxyData = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.makerAssetData); - const makerAssetAddressLeft = erc20ProxyData.tokenAddress; - const takerAssetAddressRight = makerAssetAddressLeft; - // Left Maker - expectedNewERC20BalancesByOwner[makerAddressLeft][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ - makerAddressLeft - ][makerAssetAddressLeft].minus(expectedTransferAmounts.amountSoldByLeftMaker); - // Right Maker - expectedNewERC20BalancesByOwner[makerAddressRight][ - takerAssetAddressRight - ] = expectedNewERC20BalancesByOwner[makerAddressRight][takerAssetAddressRight].add( - expectedTransferAmounts.amountReceivedByRightMaker, - ); - // Taker - expectedNewERC20BalancesByOwner[takerAddress][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ - takerAddress - ][makerAssetAddressLeft].add(expectedTransferAmounts.amountReceivedByTaker); - } else if (makerAssetProxyIdLeft === AssetProxyId.ERC721) { - // Decode asset data - const erc721ProxyData = assetProxyUtils.decodeERC721ProxyData(signedOrderLeft.makerAssetData); - const makerAssetAddressLeft = erc721ProxyData.tokenAddress; - const makerAssetIdLeft = erc721ProxyData.tokenId; - const takerAssetAddressRight = makerAssetAddressLeft; - const takerAssetIdRight = makerAssetIdLeft; - // Left Maker - _.remove(expectedNewERC721TokenIdsByOwner[makerAddressLeft][makerAssetAddressLeft], makerAssetIdLeft); - // Right Maker - expectedNewERC721TokenIdsByOwner[makerAddressRight][takerAssetAddressRight].push(takerAssetIdRight); - // Taker: Since there is only 1 asset transferred, the taker does not receive any of the left maker asset. - } - // Left Taker Asset (Right Maker Asset) - // Note: This exchange is only between the order makers: the Taker does not receive any of the left taker asset. - const takerAssetProxyIdLeft = assetProxyUtils.decodeProxyDataId(signedOrderLeft.takerAssetData); - if (takerAssetProxyIdLeft === AssetProxyId.ERC20) { - // Decode asset data - const erc20ProxyData = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.takerAssetData); - const takerAssetAddressLeft = erc20ProxyData.tokenAddress; - const makerAssetAddressRight = takerAssetAddressLeft; - // Left Maker - expectedNewERC20BalancesByOwner[makerAddressLeft][takerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ - makerAddressLeft - ][takerAssetAddressLeft].add(expectedTransferAmounts.amountReceivedByLeftMaker); - // Right Maker - expectedNewERC20BalancesByOwner[makerAddressRight][ - makerAssetAddressRight - ] = expectedNewERC20BalancesByOwner[makerAddressRight][makerAssetAddressRight].minus( - expectedTransferAmounts.amountSoldByRightMaker, - ); - } else if (takerAssetProxyIdLeft === AssetProxyId.ERC721) { - // Decode asset data - const erc721ProxyData = assetProxyUtils.decodeERC721ProxyData(signedOrderRight.makerAssetData); - const makerAssetAddressRight = erc721ProxyData.tokenAddress; - const makerAssetIdRight = erc721ProxyData.tokenId; - const takerAssetAddressLeft = makerAssetAddressRight; - const takerAssetIdLeft = makerAssetIdRight; - // Right Maker - _.remove(expectedNewERC721TokenIdsByOwner[makerAddressRight][makerAssetAddressRight], makerAssetIdRight); - // Left Maker - expectedNewERC721TokenIdsByOwner[makerAddressLeft][takerAssetAddressLeft].push(takerAssetIdLeft); - } - // Left Maker Fees - expectedNewERC20BalancesByOwner[makerAddressLeft][feeTokenAddress] = expectedNewERC20BalancesByOwner[ - makerAddressLeft - ][feeTokenAddress].minus(expectedTransferAmounts.feePaidByLeftMaker); - // Right Maker Fees - expectedNewERC20BalancesByOwner[makerAddressRight][feeTokenAddress] = expectedNewERC20BalancesByOwner[ - makerAddressRight - ][feeTokenAddress].minus(expectedTransferAmounts.feePaidByRightMaker); - // Taker Fees - expectedNewERC20BalancesByOwner[takerAddress][feeTokenAddress] = expectedNewERC20BalancesByOwner[takerAddress][ - feeTokenAddress - ].minus(expectedTransferAmounts.totalFeePaidByTaker); - // Left Fee Recipient Fees - expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][feeTokenAddress] = expectedNewERC20BalancesByOwner[ - feeRecipientAddressLeft - ][feeTokenAddress].add(expectedTransferAmounts.feeReceivedLeft); - // Right Fee Recipient Fees - expectedNewERC20BalancesByOwner[feeRecipientAddressRight][feeTokenAddress] = expectedNewERC20BalancesByOwner[ - feeRecipientAddressRight - ][feeTokenAddress].add(expectedTransferAmounts.feeReceivedRight); - - return [expectedNewERC20BalancesByOwner, expectedNewERC721TokenIdsByOwner]; - } /// @dev Compares a pair of ERC20 balances and a pair of ERC721 token owners. /// @param expectedNewERC20BalancesByOwner Expected ERC20 balances. /// @param realERC20BalancesByOwner Actual ERC20 balances. @@ -194,16 +82,22 @@ export class MatchOrderTester { /// @param exchangeWrapper Used to call to the Exchange. /// @param erc20Wrapper Used to fetch ERC20 balances. /// @param erc721Wrapper Used to fetch ERC721 token owners. - constructor(exchangeWrapper: ExchangeWrapper, erc20Wrapper: ERC20Wrapper, erc721Wrapper: ERC721Wrapper) { + /// @param feeTokenAddress Address of ERC20 fee token. + constructor( + exchangeWrapper: ExchangeWrapper, + erc20Wrapper: ERC20Wrapper, + erc721Wrapper: ERC721Wrapper, + feeTokenAddress: string, + ) { this._exchangeWrapper = exchangeWrapper; this._erc20Wrapper = erc20Wrapper; this._erc721Wrapper = erc721Wrapper; + this._feeTokenAddress = feeTokenAddress; } /// @dev Matches two complementary orders and validates results. /// Validation either succeeds or throws. /// @param signedOrderLeft First matched order. /// @param signedOrderRight Second matched order. - /// @param feeTokenAddress Address of ERC20 fee token. /// @param takerAddress Address of taker (the address who matched the two orders) /// @param erc20BalancesByOwner Current ERC20 balances. /// @param erc721TokenIdsByOwner Current ERC721 token owners. @@ -213,7 +107,6 @@ export class MatchOrderTester { public async matchOrdersAndVerifyBalancesAsync( signedOrderLeft: SignedOrder, signedOrderRight: SignedOrder, - feeTokenAddress: string, takerAddress: string, erc20BalancesByOwner: ERC20BalancesByOwner, erc721TokenIdsByOwner: ERC721TokenIdsByOwner, @@ -254,10 +147,9 @@ export class MatchOrderTester { ); let expectedERC20BalancesByOwner: ERC20BalancesByOwner; let expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner; - [expectedERC20BalancesByOwner, expectedERC721TokenIdsByOwner] = MatchOrderTester._calculateExpectedBalances( + [expectedERC20BalancesByOwner, expectedERC721TokenIdsByOwner] = this._calculateExpectedBalances( signedOrderLeft, signedOrderRight, - feeTokenAddress, takerAddress, erc20BalancesByOwner, erc721TokenIdsByOwner, @@ -343,4 +235,119 @@ export class MatchOrderTester { }; return expectedTransferAmounts; } + /// @dev Calculates the expected balances of order makers, fee recipients, and the taker, + /// as a result of matching two orders. + /// @param signedOrderLeft First matched order. + /// @param signedOrderRight Second matched order. + /// @param takerAddress Address of taker (the address who matched the two orders) + /// @param erc20BalancesByOwner Current ERC20 balances. + /// @param erc721TokenIdsByOwner Current ERC721 token owners. + /// @param expectedTransferAmounts A struct containing the expected transfer amounts. + /// @return Expected ERC20 balances & ERC721 token owners after orders have been matched. + private _calculateExpectedBalances( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + takerAddress: string, + erc20BalancesByOwner: ERC20BalancesByOwner, + erc721TokenIdsByOwner: ERC721TokenIdsByOwner, + expectedTransferAmounts: TransferAmounts, + ): [ERC20BalancesByOwner, ERC721TokenIdsByOwner] { + const makerAddressLeft = signedOrderLeft.makerAddress; + const makerAddressRight = signedOrderRight.makerAddress; + const feeRecipientAddressLeft = signedOrderLeft.feeRecipientAddress; + const feeRecipientAddressRight = signedOrderRight.feeRecipientAddress; + // Operations are performed on copies of the balances + const expectedNewERC20BalancesByOwner = _.cloneDeep(erc20BalancesByOwner); + const expectedNewERC721TokenIdsByOwner = _.cloneDeep(erc721TokenIdsByOwner); + // Left Maker Asset (Right Taker Asset) + const makerAssetProxyIdLeft = assetProxyUtils.decodeProxyDataId(signedOrderLeft.makerAssetData); + if (makerAssetProxyIdLeft === AssetProxyId.ERC20) { + // Decode asset data + const erc20ProxyData = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.makerAssetData); + const makerAssetAddressLeft = erc20ProxyData.tokenAddress; + const takerAssetAddressRight = makerAssetAddressLeft; + // Left Maker + expectedNewERC20BalancesByOwner[makerAddressLeft][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ + makerAddressLeft + ][makerAssetAddressLeft].minus(expectedTransferAmounts.amountSoldByLeftMaker); + // Right Maker + expectedNewERC20BalancesByOwner[makerAddressRight][ + takerAssetAddressRight + ] = expectedNewERC20BalancesByOwner[makerAddressRight][takerAssetAddressRight].add( + expectedTransferAmounts.amountReceivedByRightMaker, + ); + // Taker + expectedNewERC20BalancesByOwner[takerAddress][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ + takerAddress + ][makerAssetAddressLeft].add(expectedTransferAmounts.amountReceivedByTaker); + } else if (makerAssetProxyIdLeft === AssetProxyId.ERC721) { + // Decode asset data + const erc721ProxyData = assetProxyUtils.decodeERC721ProxyData(signedOrderLeft.makerAssetData); + const makerAssetAddressLeft = erc721ProxyData.tokenAddress; + const makerAssetIdLeft = erc721ProxyData.tokenId; + const takerAssetAddressRight = makerAssetAddressLeft; + const takerAssetIdRight = makerAssetIdLeft; + // Left Maker + _.remove(expectedNewERC721TokenIdsByOwner[makerAddressLeft][makerAssetAddressLeft], makerAssetIdLeft); + // Right Maker + expectedNewERC721TokenIdsByOwner[makerAddressRight][takerAssetAddressRight].push(takerAssetIdRight); + // Taker: Since there is only 1 asset transferred, the taker does not receive any of the left maker asset. + } + // Left Taker Asset (Right Maker Asset) + // Note: This exchange is only between the order makers: the Taker does not receive any of the left taker asset. + const takerAssetProxyIdLeft = assetProxyUtils.decodeProxyDataId(signedOrderLeft.takerAssetData); + if (takerAssetProxyIdLeft === AssetProxyId.ERC20) { + // Decode asset data + const erc20ProxyData = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.takerAssetData); + const takerAssetAddressLeft = erc20ProxyData.tokenAddress; + const makerAssetAddressRight = takerAssetAddressLeft; + // Left Maker + expectedNewERC20BalancesByOwner[makerAddressLeft][takerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ + makerAddressLeft + ][takerAssetAddressLeft].add(expectedTransferAmounts.amountReceivedByLeftMaker); + // Right Maker + expectedNewERC20BalancesByOwner[makerAddressRight][ + makerAssetAddressRight + ] = expectedNewERC20BalancesByOwner[makerAddressRight][makerAssetAddressRight].minus( + expectedTransferAmounts.amountSoldByRightMaker, + ); + } else if (takerAssetProxyIdLeft === AssetProxyId.ERC721) { + // Decode asset data + const erc721ProxyData = assetProxyUtils.decodeERC721ProxyData(signedOrderRight.makerAssetData); + const makerAssetAddressRight = erc721ProxyData.tokenAddress; + const makerAssetIdRight = erc721ProxyData.tokenId; + const takerAssetAddressLeft = makerAssetAddressRight; + const takerAssetIdLeft = makerAssetIdRight; + // Right Maker + _.remove(expectedNewERC721TokenIdsByOwner[makerAddressRight][makerAssetAddressRight], makerAssetIdRight); + // Left Maker + expectedNewERC721TokenIdsByOwner[makerAddressLeft][takerAssetAddressLeft].push(takerAssetIdLeft); + } + // Left Maker Fees + expectedNewERC20BalancesByOwner[makerAddressLeft][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[ + makerAddressLeft + ][this._feeTokenAddress].minus(expectedTransferAmounts.feePaidByLeftMaker); + // Right Maker Fees + expectedNewERC20BalancesByOwner[makerAddressRight][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[ + makerAddressRight + ][this._feeTokenAddress].minus(expectedTransferAmounts.feePaidByRightMaker); + // Taker Fees + expectedNewERC20BalancesByOwner[takerAddress][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[ + takerAddress + ][this._feeTokenAddress].minus(expectedTransferAmounts.totalFeePaidByTaker); + // Left Fee Recipient Fees + expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][ + this._feeTokenAddress + ] = expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][this._feeTokenAddress].add( + expectedTransferAmounts.feeReceivedLeft, + ); + // Right Fee Recipient Fees + expectedNewERC20BalancesByOwner[feeRecipientAddressRight][ + this._feeTokenAddress + ] = expectedNewERC20BalancesByOwner[feeRecipientAddressRight][this._feeTokenAddress].add( + expectedTransferAmounts.feeReceivedRight, + ); + + return [expectedNewERC20BalancesByOwner, expectedNewERC721TokenIdsByOwner]; + } } -- cgit v1.2.3 From bb74789b42b3605f0ec9fb8daba95b6b28a9f263 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 17 May 2018 14:02:21 -0700 Subject: Changed calculateFillResults to public visibility so that it can be used by the Forwarding Contract. --- .../protocol/Exchange/MixinExchangeCore.sol | 222 ++++++++++----------- .../protocol/Exchange/interfaces/IExchangeCore.sol | 20 ++ .../protocol/Exchange/mixins/MExchangeCore.sol | 20 -- 3 files changed, 131 insertions(+), 131 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index 7e9c4f8fa..29d03fdb8 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -144,65 +144,62 @@ contract MixinExchangeCore is return updateCancelledState(order, orderInfo.orderStatus, orderInfo.orderHash); } - /// @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 takerAddress Address of order taker. - /// @param orderFilledAmount Amount of order already filled. - /// @param takerAssetFillAmount Desired amount of order to fill by taker. - /// @param signature Proof that the orders was created by its maker. - function assertValidFill( - Order memory order, - uint8 orderStatus, - bytes32 orderHash, - address takerAddress, - uint256 orderFilledAmount, - uint256 takerAssetFillAmount, - bytes memory signature - ) - internal + /// @dev Gets information about an order: status, hash, and amount filled. + /// @param order Order to gather information on. + /// @return OrderInfo Information about the order and its state. + /// See LibOrder.OrderInfo for a complete description. + function getOrderInfo(Order memory order) + public + view + returns (LibOrder.OrderInfo memory orderInfo) { - // Ensure order is valid - // An order can only be filled if its status is FILLABLE; - // however, only invalid statuses result in a throw. - // See LibStatus for a complete description of order statuses. - 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 - ); + // Compute the order hash + orderInfo.orderHash = getOrderHash(order); - // Validate Maker signature (check only if first time seen) - if (orderFilledAmount == 0) { - require( - isValidSignature(orderHash, order.makerAddress, signature), - SIGNATURE_VALIDATION_FAILED - ); + // 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) { + orderInfo.orderStatus = uint8(Status.ORDER_INVALID_MAKER_ASSET_AMOUNT); + return orderInfo; } - // Validate sender is allowed to fill this order - if (order.senderAddress != address(0)) { - require( - order.senderAddress == msg.sender, - INVALID_SENDER - ); + // If order.takerAssetAmount is zero, then the order will always + // be considered filled because 0 == takerAssetAmount == orderFilledAmount + // Instead of distinguishing between unfilled and filled zero taker + // amount orders, we choose not to support them. + if (order.takerAssetAmount == 0) { + orderInfo.orderStatus = uint8(Status.ORDER_INVALID_TAKER_ASSET_AMOUNT); + return orderInfo; } - // Validate taker is allowed to fill this order - if (order.takerAddress != address(0)) { - require( - order.takerAddress == takerAddress, - INVALID_CONTEXT - ); + // Validate order expiration + if (block.timestamp >= order.expirationTimeSeconds) { + orderInfo.orderStatus = uint8(Status.ORDER_EXPIRED); + return orderInfo; } - require( - takerAssetFillAmount > 0, - GT_ZERO_AMOUNT_REQUIRED - ); + + // Check if order has been cancelled + if (cancelled[orderInfo.orderHash]) { + orderInfo.orderStatus = uint8(Status.ORDER_CANCELLED); + return orderInfo; + } + if (makerEpoch[order.makerAddress] > order.salt) { + orderInfo.orderStatus = uint8(Status.ORDER_CANCELLED); + return orderInfo; + } + + // Fetch filled amount and validate order availability + orderInfo.orderFilledAmount = filled[orderInfo.orderHash]; + if (orderInfo.orderFilledAmount >= order.takerAssetAmount) { + orderInfo.orderStatus = uint8(Status.ORDER_FULLY_FILLED); + return orderInfo; + } + + // All other statuses are ruled out: order is Fillable + orderInfo.orderStatus = uint8(Status.ORDER_FILLABLE); + return orderInfo; } /// @dev Calculates amounts filled and fees paid by maker and taker. @@ -218,7 +215,7 @@ contract MixinExchangeCore is uint256 orderFilledAmount, uint256 takerAssetFillAmount ) - internal + public pure returns ( uint8 status, @@ -275,6 +272,67 @@ contract MixinExchangeCore is return (status, fillResults); } + /// @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 takerAddress Address of order taker. + /// @param orderFilledAmount Amount of order already filled. + /// @param takerAssetFillAmount Desired amount of order to fill by taker. + /// @param signature Proof that the orders was created by its maker. + function assertValidFill( + Order memory order, + uint8 orderStatus, + bytes32 orderHash, + address takerAddress, + uint256 orderFilledAmount, + uint256 takerAssetFillAmount, + bytes memory signature + ) + internal + { + // Ensure order is valid + // An order can only be filled if its status is FILLABLE; + // however, only invalid statuses result in a throw. + // See LibStatus for a complete description of order statuses. + 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 (orderFilledAmount == 0) { + require( + isValidSignature(orderHash, order.makerAddress, signature), + SIGNATURE_VALIDATION_FAILED + ); + } + + // Validate sender is allowed to fill this order + if (order.senderAddress != address(0)) { + require( + order.senderAddress == msg.sender, + INVALID_SENDER + ); + } + + // Validate taker is allowed to fill this order + if (order.takerAddress != address(0)) { + require( + order.takerAddress == takerAddress, + INVALID_CONTEXT + ); + } + require( + takerAssetFillAmount > 0, + GT_ZERO_AMOUNT_REQUIRED + ); + } + /// @dev Updates state with results of a fill order. /// @param order that was filled. /// @param takerAddress Address of taker who filled the order. @@ -385,62 +443,4 @@ contract MixinExchangeCore is return stateUpdated; } - - /// @dev Gets information about an order: status, hash, and amount filled. - /// @param order Order to gather information on. - /// @return OrderInfo Information about the order and its state. - /// See LibOrder.OrderInfo for a complete description. - function getOrderInfo(Order memory order) - public - view - returns (LibOrder.OrderInfo memory orderInfo) - { - // Compute the order hash - orderInfo.orderHash = getOrderHash(order); - - // 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) { - orderInfo.orderStatus = uint8(Status.ORDER_INVALID_MAKER_ASSET_AMOUNT); - return orderInfo; - } - - // If order.takerAssetAmount is zero, then the order will always - // be considered filled because 0 == takerAssetAmount == orderFilledAmount - // Instead of distinguishing between unfilled and filled zero taker - // amount orders, we choose not to support them. - if (order.takerAssetAmount == 0) { - orderInfo.orderStatus = uint8(Status.ORDER_INVALID_TAKER_ASSET_AMOUNT); - return orderInfo; - } - - // Validate order expiration - if (block.timestamp >= order.expirationTimeSeconds) { - orderInfo.orderStatus = uint8(Status.ORDER_EXPIRED); - return orderInfo; - } - - // Check if order has been cancelled - if (cancelled[orderInfo.orderHash]) { - orderInfo.orderStatus = uint8(Status.ORDER_CANCELLED); - return orderInfo; - } - if (makerEpoch[order.makerAddress] > order.salt) { - orderInfo.orderStatus = uint8(Status.ORDER_CANCELLED); - return orderInfo; - } - - // Fetch filled amount and validate order availability - orderInfo.orderFilledAmount = filled[orderInfo.orderHash]; - if (orderInfo.orderFilledAmount >= order.takerAssetAmount) { - orderInfo.orderStatus = uint8(Status.ORDER_FULLY_FILLED); - return orderInfo; - } - - // All other statuses are ruled out: order is Fillable - orderInfo.orderStatus = uint8(Status.ORDER_FILLABLE); - return orderInfo; - } } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol index 958a89c39..8a0a73556 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol @@ -57,4 +57,24 @@ contract IExchangeCore { public view returns (LibOrder.OrderInfo memory orderInfo); + + /// @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 orderFilledAmount 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 orderFilledAmount, + uint256 takerAssetFillAmount + ) + public + pure + returns ( + uint8 status, + LibFillResults.FillResults memory fillResults + ); } 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 fe6c155a3..2f928372b 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol @@ -75,26 +75,6 @@ contract MExchangeCore is ) 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 orderFilledAmount 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 orderFilledAmount, - uint256 takerAssetFillAmount - ) - internal - 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. -- cgit v1.2.3 From 8c1ae350885e290af6071b98f065dffb39f19506 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 18 May 2018 11:31:27 -0700 Subject: Rebased against v2-prototype --- packages/contracts/compiler.json | 2 +- packages/contracts/package.json | 2 +- .../current/protocol/Exchange/MixinSettlement.sol | 1 + packages/contracts/test/exchange/core.ts | 2 +- packages/contracts/test/exchange/match_orders.ts | 18 ++++++++++-------- packages/contracts/test/utils/match_order_tester.ts | 4 ++-- 6 files changed, 16 insertions(+), 13 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/compiler.json b/packages/contracts/compiler.json index 9b0a1e161..bf6cc2376 100644 --- a/packages/contracts/compiler.json +++ b/packages/contracts/compiler.json @@ -4,7 +4,7 @@ "compilerSettings": { "optimizer": { "enabled": true, - "runs": 200 + "runs": 0 }, "outputSelection": { "*": { diff --git a/packages/contracts/package.json b/packages/contracts/package.json index b10ed3c44..6fba881b7 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -12,7 +12,7 @@ }, "scripts": { "watch": "tsc -w", - "prebuild": "run-s clean copy_artifacts generate_contract_wrappers", + "prebuild": "run-s clean compile copy_artifacts generate_contract_wrappers", "copy_artifacts": "copyfiles -u 4 '../migrations/artifacts/2.0.0/**/*' ./lib/src/artifacts;", "build": "tsc", "test": "run-s build run_mocha", diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol index 6bd9edae5..c2d6b931f 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol @@ -17,6 +17,7 @@ */ pragma solidity ^0.4.23; +pragma experimental ABIEncoderV2; import "./mixins/MSettlement.sol"; import "./mixins/MAssetProxyDispatcher.sol"; diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts index e9c51ea18..5633fcd7e 100644 --- a/packages/contracts/test/exchange/core.ts +++ b/packages/contracts/test/exchange/core.ts @@ -26,7 +26,7 @@ import { ExchangeWrapper } from '../../src/utils/exchange_wrapper'; import { OrderFactory } from '../../src/utils/order_factory'; import { orderUtils } from '../../src/utils/order_utils'; import { AssetProxyId, ContractName, ERC20BalancesByOwner, ExchangeStatus, SignedOrder } from '../../src/utils/types'; -import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; +import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; chaiSetup.configure(); const expect = chai.expect; diff --git a/packages/contracts/test/exchange/match_orders.ts b/packages/contracts/test/exchange/match_orders.ts index 073ef4136..94cdf4598 100644 --- a/packages/contracts/test/exchange/match_orders.ts +++ b/packages/contracts/test/exchange/match_orders.ts @@ -15,7 +15,9 @@ import { ExchangeStatusContractEventArgs, FillContractEventArgs, } from '../../src/contract_wrappers/generated/exchange'; +import { artifacts } from '../../src/utils/artifacts'; import { assetProxyUtils } from '../../src/utils/asset_proxy_utils'; +import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { crypto } from '../../src/utils/crypto'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; @@ -32,9 +34,7 @@ import { OrderInfo, SignedOrder, } from '../../src/utils/types'; -import { chaiSetup } from '../utils/chai_setup'; -import { deployer } from '../utils/deployer'; -import { provider, web3Wrapper } from '../utils/web3_wrapper'; +import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; import { MatchOrderTester } from '../utils/match_order_tester'; @@ -90,8 +90,8 @@ describe('matchOrders', () => { feeRecipientAddressRight, ] = accounts); // Create wrappers - erc20Wrapper = new ERC20Wrapper(deployer, provider, usedAddresses, owner); - erc721Wrapper = new ERC721Wrapper(deployer, provider, usedAddresses, owner); + erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); + erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); // Deploy ERC20 token & ERC20 proxy [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(); erc20Proxy = await erc20Wrapper.deployProxyAsync(); @@ -105,10 +105,12 @@ describe('matchOrders', () => { erc721RightMakerAssetIds = erc721Balances[makerAddressRight][erc721Token.address]; erc721TakerAssetIds = erc721Balances[takerAddress][erc721Token.address]; // Depoy exchange - const exchangeInstance = await deployer.deployAsync(ContractName.Exchange, [ + exchange = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + provider, + txDefaults, assetProxyUtils.encodeERC20ProxyData(zrxToken.address), - ]); - exchange = new ExchangeContract(exchangeInstance.abi, exchangeInstance.address, provider); + ); zeroEx = new ZeroEx(provider, { exchangeContractAddress: exchange.address, networkId: constants.TESTRPC_NETWORK_ID, diff --git a/packages/contracts/test/utils/match_order_tester.ts b/packages/contracts/test/utils/match_order_tester.ts index ba6610b68..f95d8fb34 100644 --- a/packages/contracts/test/utils/match_order_tester.ts +++ b/packages/contracts/test/utils/match_order_tester.ts @@ -15,6 +15,7 @@ import { FillContractEventArgs, } from '../../src/contract_wrappers/generated/exchange'; import { assetProxyUtils } from '../../src/utils/asset_proxy_utils'; +import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { crypto } from '../../src/utils/crypto'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; @@ -31,8 +32,7 @@ import { SignedOrder, TransferAmountsByMatchOrders as TransferAmounts, } from '../../src/utils/types'; -import { chaiSetup } from '../utils/chai_setup'; -import { provider, web3Wrapper } from '../utils/web3_wrapper'; +import { provider, web3Wrapper } from '../../src/utils/web3_wrapper'; chaiSetup.configure(); const expect = chai.expect; -- cgit v1.2.3 From 89abd765702313c9c22b41e92729880850e44c92 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 18 May 2018 14:13:42 -0700 Subject: Upgraded to Solidity 0.4.24 --- packages/contracts/package.json | 2 +- .../src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol | 2 +- .../src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol | 2 +- .../src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol | 2 +- .../src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol | 2 +- .../contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol | 2 +- .../current/protocol/AssetProxy/interfaces/IAuthorizable.sol | 2 +- .../src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol | 2 +- .../contracts/current/protocol/AssetProxy/mixins/MAuthorizable.sol | 2 +- .../contracts/src/contracts/current/protocol/Exchange/Exchange.sol | 2 +- .../contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol | 2 +- .../src/contracts/current/protocol/Exchange/MixinExchangeCore.sol | 2 +- .../src/contracts/current/protocol/Exchange/MixinMatchOrders.sol | 2 +- .../src/contracts/current/protocol/Exchange/MixinSettlement.sol | 2 +- .../contracts/current/protocol/Exchange/MixinSignatureValidator.sol | 2 +- .../src/contracts/current/protocol/Exchange/MixinTransactions.sol | 2 +- .../src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol | 2 +- .../current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol | 2 +- .../src/contracts/current/protocol/Exchange/interfaces/IExchange.sol | 2 +- .../contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol | 2 +- .../contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol | 2 +- .../current/protocol/Exchange/interfaces/ISignatureValidator.sol | 2 +- .../src/contracts/current/protocol/Exchange/interfaces/ISigner.sol | 2 +- .../contracts/current/protocol/Exchange/interfaces/ITransactions.sol | 2 +- .../current/protocol/Exchange/interfaces/IWrapperFunctions.sol | 2 +- .../contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol | 2 +- .../src/contracts/current/protocol/Exchange/libs/LibFillResults.sol | 2 +- .../src/contracts/current/protocol/Exchange/libs/LibMath.sol | 2 +- .../src/contracts/current/protocol/Exchange/libs/LibOrder.sol | 2 +- .../src/contracts/current/protocol/Exchange/libs/LibStatus.sol | 2 +- .../current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol | 2 +- .../src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol | 2 +- .../src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol | 2 +- .../src/contracts/current/protocol/Exchange/mixins/MSettlement.sol | 2 +- .../current/protocol/Exchange/mixins/MSignatureValidator.sol | 2 +- .../src/contracts/current/protocol/Exchange/mixins/MTransactions.sol | 2 +- .../src/contracts/current/test/DummyERC20Token/DummyERC20Token.sol | 2 +- .../src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol | 2 +- packages/contracts/src/contracts/current/test/Mintable/Mintable.sol | 2 +- .../test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol | 2 +- .../src/contracts/current/test/TestLibBytes/TestLibBytes.sol | 2 +- packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol | 4 ++-- .../current/test/TestSignatureValidator/TestSignatureValidator.sol | 2 +- .../contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol | 2 +- .../contracts/src/contracts/current/tokens/ERC20Token/IERC20Token.sol | 2 +- .../src/contracts/current/tokens/ERC721Token/ERC721Token.sol | 4 ++-- .../src/contracts/current/tokens/ERC721Token/IERC721Receiver.sol | 4 ++-- .../src/contracts/current/tokens/ERC721Token/IERC721Token.sol | 4 ++-- .../tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol | 2 +- packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol | 2 +- packages/contracts/src/contracts/current/utils/Ownable/IOwnable.sol | 2 +- packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol | 2 +- packages/contracts/src/contracts/current/utils/SafeMath/SafeMath.sol | 2 +- 53 files changed, 57 insertions(+), 57 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 6fba881b7..a253e76d4 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -57,7 +57,7 @@ "npm-run-all": "^4.1.2", "prettier": "^1.11.1", "shx": "^0.2.2", - "solc": "^0.4.23", + "solc": "^0.4.24", "tslint": "5.8.0", "typescript": "2.7.1", "yargs": "^10.0.3" diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol index c02536d67..ee0c66fdc 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../../utils/LibBytes/LibBytes.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol index 475359087..94aab9139 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../../utils/LibBytes/LibBytes.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol index d58cfc2dd..4ec31304f 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "./mixins/MAssetProxy.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol index b66b783ea..0bbd3b318 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "./mixins/MAuthorizable.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol index eca6524e5..8b30dfabb 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "./IAuthorizable.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAuthorizable.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAuthorizable.sol index 3120be7ec..d6fe03898 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAuthorizable.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAuthorizable.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../../../utils/Ownable/IOwnable.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol index e0ec8c4e1..3800bf04c 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../interfaces/IAssetProxy.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAuthorizable.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAuthorizable.sol index 71d1910e5..cdf60bdee 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAuthorizable.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAuthorizable.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../interfaces/IAuthorizable.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol b/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol index 2d1729b1c..b7b308069 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "./MixinExchangeCore.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol index 308dace32..3b38d1f37 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; import "../../utils/Ownable/Ownable.sol"; import "../AssetProxy/interfaces/IAssetProxy.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index 29d03fdb8..b57376fa6 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "./libs/LibFillResults.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index e353b19e8..f1093631f 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -11,7 +11,7 @@ limitations under the License. */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "./mixins/MExchangeCore.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol index c2d6b931f..7c03bde75 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "./mixins/MSettlement.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol index 2322625d9..f7fcd36b6 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; import "./mixins/MSignatureValidator.sol"; import "./interfaces/ISigner.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol index b7d70682e..f93a80705 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol @@ -15,7 +15,7 @@ limitations under the License. */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; import "./mixins/MSignatureValidator.sol"; import "./mixins/MTransactions.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol index 04018e09d..15f1a2e0b 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../../utils/LibBytes/LibBytes.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol index 4e4ed6be8..3ce5ef157 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; contract IAssetProxyDispatcher { diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchange.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchange.sol index 20cdfa57e..fc428e9c0 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchange.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchange.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "./IExchangeCore.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol index 8a0a73556..eae2c8091 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../libs/LibOrder.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol index f4ad20790..9676ca657 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol @@ -15,7 +15,7 @@ limitations under the License. */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../libs/LibOrder.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISignatureValidator.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISignatureValidator.sol index b4a238472..65ff45f7b 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISignatureValidator.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISignatureValidator.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; contract ISignatureValidator { diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISigner.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISigner.sol index e065cfd8a..53c41d331 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISigner.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ISigner.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; contract ISigner { diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ITransactions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ITransactions.sol index 76e4cf2fe..d973bf001 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ITransactions.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ITransactions.sol @@ -15,7 +15,7 @@ limitations under the License. */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; contract ITransactions { 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 3aaa9de9e..1eb1233ed 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IWrapperFunctions.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IWrapperFunctions.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "./libs/LibOrder.sol"; 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 84f621f64..4712ee36c 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; contract LibExchangeErrors { diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol index c4647c44f..ce30e3c0f 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; import "../../../utils/SafeMath/SafeMath.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol index 27d65c69f..ea8c138d6 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; import "../../../utils/SafeMath/SafeMath.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol index 5f00b87f0..538b455d0 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; contract LibOrder { diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol index c29eb2284..f72b7d65f 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; contract LibStatus { 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 67752d3c8..ccc960d6e 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; 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 2f928372b..cd8335568 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../libs/LibOrder.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol index d71f4e120..904647ec2 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol @@ -15,7 +15,7 @@ limitations under the License. */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../libs/LibOrder.sol"; 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 b78718c0c..50b62e79f 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSettlement.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSettlement.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; import "../libs/LibOrder.sol"; import "./MMatchOrders.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSignatureValidator.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSignatureValidator.sol index ba26c07f6..3658e7c6f 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSignatureValidator.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MSignatureValidator.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; import "../interfaces/ISignatureValidator.sol"; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MTransactions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MTransactions.sol index 159ed1527..e2f89de01 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MTransactions.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MTransactions.sol @@ -15,7 +15,7 @@ limitations under the License. */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; import "../interfaces/ITransactions.sol"; diff --git a/packages/contracts/src/contracts/current/test/DummyERC20Token/DummyERC20Token.sol b/packages/contracts/src/contracts/current/test/DummyERC20Token/DummyERC20Token.sol index ab5311e0c..0c7b18c0c 100644 --- a/packages/contracts/src/contracts/current/test/DummyERC20Token/DummyERC20Token.sol +++ b/packages/contracts/src/contracts/current/test/DummyERC20Token/DummyERC20Token.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../Mintable/Mintable.sol"; diff --git a/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol b/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol index 22ebbd3c1..369a2950d 100644 --- a/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol +++ b/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../../tokens/ERC721Token/ERC721Token.sol"; diff --git a/packages/contracts/src/contracts/current/test/Mintable/Mintable.sol b/packages/contracts/src/contracts/current/test/Mintable/Mintable.sol index fd944f244..a91bfee9e 100644 --- a/packages/contracts/src/contracts/current/test/Mintable/Mintable.sol +++ b/packages/contracts/src/contracts/current/test/Mintable/Mintable.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../../tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol"; diff --git a/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol index 93ec3cef3..11ca0617d 100644 --- a/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol +++ b/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../../protocol/Exchange/MixinAssetProxyDispatcher.sol"; diff --git a/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol b/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol index 1597ff8d5..ac4602933 100644 --- a/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol +++ b/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../../utils/LibBytes/LibBytes.sol"; diff --git a/packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol b/packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol index 0dc7785b2..b8fc90af1 100644 --- a/packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol +++ b/packages/contracts/src/contracts/current/test/TestLibs/TestLibs.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../../protocol/Exchange/libs/LibMath.sol"; @@ -77,4 +77,4 @@ contract TestLibs is addFillResults(totalFillResults, singleFillResults); return totalFillResults; } -} \ No newline at end of file +} diff --git a/packages/contracts/src/contracts/current/test/TestSignatureValidator/TestSignatureValidator.sol b/packages/contracts/src/contracts/current/test/TestSignatureValidator/TestSignatureValidator.sol index 44565b361..15d9ca189 100644 --- a/packages/contracts/src/contracts/current/test/TestSignatureValidator/TestSignatureValidator.sol +++ b/packages/contracts/src/contracts/current/test/TestSignatureValidator/TestSignatureValidator.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../../protocol/Exchange/MixinSignatureValidator.sol"; diff --git a/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol b/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol index 6497d3c7a..f0bcdafef 100644 --- a/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol +++ b/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "./IERC20Token.sol"; diff --git a/packages/contracts/src/contracts/current/tokens/ERC20Token/IERC20Token.sol b/packages/contracts/src/contracts/current/tokens/ERC20Token/IERC20Token.sol index 537f5a83d..eb879b6a8 100644 --- a/packages/contracts/src/contracts/current/tokens/ERC20Token/IERC20Token.sol +++ b/packages/contracts/src/contracts/current/tokens/ERC20Token/IERC20Token.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; contract IERC20Token { diff --git a/packages/contracts/src/contracts/current/tokens/ERC721Token/ERC721Token.sol b/packages/contracts/src/contracts/current/tokens/ERC721Token/ERC721Token.sol index b3493bc99..41ba149e3 100644 --- a/packages/contracts/src/contracts/current/tokens/ERC721Token/ERC721Token.sol +++ b/packages/contracts/src/contracts/current/tokens/ERC721Token/ERC721Token.sol @@ -23,7 +23,7 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; import "./IERC721Token.sol"; import "./IERC721Receiver.sol"; @@ -403,4 +403,4 @@ contract ERC721Token is assembly { size := extcodesize(addr) } // solium-disable-line security/no-inline-assembly return size > 0; } -} \ No newline at end of file +} diff --git a/packages/contracts/src/contracts/current/tokens/ERC721Token/IERC721Receiver.sol b/packages/contracts/src/contracts/current/tokens/ERC721Token/IERC721Receiver.sol index 3484bf824..b0fff3c90 100644 --- a/packages/contracts/src/contracts/current/tokens/ERC721Token/IERC721Receiver.sol +++ b/packages/contracts/src/contracts/current/tokens/ERC721Token/IERC721Receiver.sol @@ -23,7 +23,7 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; /** * @title ERC721 token receiver interface @@ -57,4 +57,4 @@ contract IERC721Receiver { bytes _data) public returns (bytes4); -} \ No newline at end of file +} diff --git a/packages/contracts/src/contracts/current/tokens/ERC721Token/IERC721Token.sol b/packages/contracts/src/contracts/current/tokens/ERC721Token/IERC721Token.sol index 81e1b97af..345712d67 100644 --- a/packages/contracts/src/contracts/current/tokens/ERC721Token/IERC721Token.sol +++ b/packages/contracts/src/contracts/current/tokens/ERC721Token/IERC721Token.sol @@ -23,7 +23,7 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; /** * @title ERC721 Non-Fungible Token Standard basic interface @@ -102,4 +102,4 @@ contract IERC721Token { uint256 _tokenId, bytes _data) public; -} \ No newline at end of file +} diff --git a/packages/contracts/src/contracts/current/tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol b/packages/contracts/src/contracts/current/tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol index 395e5d356..f62602ab3 100644 --- a/packages/contracts/src/contracts/current/tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol +++ b/packages/contracts/src/contracts/current/tokens/UnlimitedAllowanceToken/UnlimitedAllowanceToken.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "../ERC20Token/ERC20Token.sol"; diff --git a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol index 3c5531e35..2c5d9e756 100644 --- a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol +++ b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol @@ -16,7 +16,7 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; contract LibBytes { diff --git a/packages/contracts/src/contracts/current/utils/Ownable/IOwnable.sol b/packages/contracts/src/contracts/current/utils/Ownable/IOwnable.sol index 63b04945f..e77680903 100644 --- a/packages/contracts/src/contracts/current/utils/Ownable/IOwnable.sol +++ b/packages/contracts/src/contracts/current/utils/Ownable/IOwnable.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; /* diff --git a/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol b/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol index 933aa168a..296c6c856 100644 --- a/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol +++ b/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; /* diff --git a/packages/contracts/src/contracts/current/utils/SafeMath/SafeMath.sol b/packages/contracts/src/contracts/current/utils/SafeMath/SafeMath.sol index 1ab27eebc..e137f6ca5 100644 --- a/packages/contracts/src/contracts/current/utils/SafeMath/SafeMath.sol +++ b/packages/contracts/src/contracts/current/utils/SafeMath/SafeMath.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; contract SafeMath { -- cgit v1.2.3 From d13c08cc0db1c211bd354bcdd15a2071aa1cd0e0 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 18 May 2018 16:37:50 -0700 Subject: Style improvements to order matching --- .../protocol/Exchange/MixinExchangeCore.sol | 4 +-- .../current/protocol/Exchange/MixinMatchOrders.sol | 33 ++++++++-------------- .../protocol/Exchange/interfaces/IMatchOrders.sol | 6 ++-- .../protocol/Exchange/mixins/MMatchOrders.sol | 2 +- 4 files changed, 17 insertions(+), 28 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index b57376fa6..b9738519f 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -225,13 +225,13 @@ contract MixinExchangeCore is // Fill amount must be greater than 0 if (takerAssetFillAmount == 0) { status = uint8(Status.TAKER_ASSET_FILL_AMOUNT_TOO_LOW); - return; + return (status, fillResults); } // Ensure the order is fillable if (orderStatus != uint8(Status.ORDER_FILLABLE)) { status = orderStatus; - return; + return (status, fillResults); } // Compute takerAssetFilledAmount diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index f1093631f..ac382d719 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -39,7 +39,7 @@ contract MixinMatchOrders is MTransactions { - /// @dev Match two complementary orders that have a positive spread. + /// @dev Match two complementary orders that have a profitable 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). @@ -52,8 +52,8 @@ contract MixinMatchOrders is function matchOrders( Order memory leftOrder, Order memory rightOrder, - bytes leftSignature, - bytes rightSignature + bytes memory leftSignature, + bytes memory rightSignature ) public returns (MatchedFillResults memory matchedFillResults) @@ -135,19 +135,21 @@ contract MixinMatchOrders is internal { // The leftOrder maker asset must be the same as the rightOrder taker asset. + // TODO: Can we safely assume equality and expect a later failure otherwise? require( areBytesEqual(leftOrder.makerAssetData, rightOrder.takerAssetData), ASSET_MISMATCH_MAKER_TAKER ); // The leftOrder taker asset must be the same as the rightOrder maker asset. + // TODO: Can we safely assume equality and expect a later failure otherwise? require( areBytesEqual(leftOrder.takerAssetData, rightOrder.makerAssetData), ASSET_MISMATCH_TAKER_MAKER ); - // 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 + // Make sure there is a profitable spread. + // There is a profitable 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: // / >= / @@ -163,22 +165,15 @@ contract MixinMatchOrders is /// @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 assertValidMatch(MatchedFillResults memory matchedFillResults) + function assertValidMatchResults(MatchedFillResults memory matchedFillResults) internal { - // The left order must spend at least as much as we're sending to the combined - // amounts being sent to the right order and taker + // If the amount transferred from the left order is different 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. uint256 amountSpentByLeft = safeAdd( matchedFillResults.right.takerAssetFilledAmount, matchedFillResults.takerFillAmount ); - require( - matchedFillResults.left.makerAssetFilledAmount >= - amountSpentByLeft, - MISCALCULATED_TRANSFER_AMOUNTS - ); - // If the amount transferred from the left order is different 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( !isRoundingError( matchedFillResults.left.makerAssetFilledAmount, @@ -188,12 +183,6 @@ contract MixinMatchOrders is ROUNDING_ERROR_TRANSFER_AMOUNTS ); - // The right order must spend at least as much as we're transferring to the left order. - require( - matchedFillResults.right.makerAssetFilledAmount >= - matchedFillResults.left.takerAssetFilledAmount, - MISCALCULATED_TRANSFER_AMOUNTS - ); // If the amount transferred from the right order is different 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( @@ -300,7 +289,7 @@ contract MixinMatchOrders is ); // Validate the fill results - assertValidMatch(matchedFillResults); + assertValidMatchResults(matchedFillResults); // Return fill results return matchedFillResults; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol index 9676ca657..df009d063 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol @@ -23,7 +23,7 @@ import "../libs/LibFillResults.sol"; contract IMatchOrders { - /// @dev Match two complementary orders that have a positive spread. + /// @dev Match two complementary orders that have a profitable 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). @@ -36,8 +36,8 @@ contract IMatchOrders { function matchOrders( LibOrder.Order memory leftOrder, LibOrder.Order memory rightOrder, - bytes leftSignature, - bytes rightSignature + bytes memory leftSignature, + bytes memory rightSignature ) public returns (LibFillResults.MatchedFillResults memory matchedFillResults); diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol index 904647ec2..7dd608cf2 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol @@ -38,7 +38,7 @@ contract MMatchOrders is /// @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 assertValidMatch(LibFillResults.MatchedFillResults memory matchedFillResults) + function assertValidMatchResults(LibFillResults.MatchedFillResults memory matchedFillResults) internal; /// @dev Calculates fill amounts for the matched orders. -- cgit v1.2.3 From b5bcfc8fe7680b094091823483d2416a73da9be9 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 18 May 2018 16:38:01 -0700 Subject: orderFilledAmount -> orderTakerAssetFilledAmount --- .../protocol/Exchange/MixinExchangeCore.sol | 30 +++++++++++----------- .../current/protocol/Exchange/MixinMatchOrders.sol | 12 ++++----- .../protocol/Exchange/interfaces/IExchangeCore.sol | 4 +-- .../current/protocol/Exchange/libs/LibOrder.sol | 2 +- .../protocol/Exchange/mixins/MExchangeCore.sol | 8 +++--- packages/contracts/src/utils/types.ts | 2 +- .../contracts/test/utils/match_order_tester.ts | 24 ++++++++--------- 7 files changed, 41 insertions(+), 41 deletions(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index b9738519f..4c45f06b5 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -92,7 +92,7 @@ contract MixinExchangeCore is orderInfo.orderStatus, orderInfo.orderHash, takerAddress, - orderInfo.orderFilledAmount, + orderInfo.orderTakerAssetFilledAmount, takerAssetFillAmount, signature ); @@ -102,7 +102,7 @@ contract MixinExchangeCore is (status, fillResults) = calculateFillResults( order, orderInfo.orderStatus, - orderInfo.orderFilledAmount, + orderInfo.orderTakerAssetFilledAmount, takerAssetFillAmount ); if (status != uint8(Status.SUCCESS)) { @@ -118,7 +118,7 @@ contract MixinExchangeCore is order, takerAddress, orderInfo.orderHash, - orderInfo.orderFilledAmount, + orderInfo.orderTakerAssetFilledAmount, fillResults ); return fillResults; @@ -166,7 +166,7 @@ contract MixinExchangeCore is } // If order.takerAssetAmount is zero, then the order will always - // be considered filled because 0 == takerAssetAmount == orderFilledAmount + // be considered filled because 0 == takerAssetAmount == orderTakerAssetFilledAmount // Instead of distinguishing between unfilled and filled zero taker // amount orders, we choose not to support them. if (order.takerAssetAmount == 0) { @@ -191,8 +191,8 @@ contract MixinExchangeCore is } // Fetch filled amount and validate order availability - orderInfo.orderFilledAmount = filled[orderInfo.orderHash]; - if (orderInfo.orderFilledAmount >= order.takerAssetAmount) { + orderInfo.orderTakerAssetFilledAmount = filled[orderInfo.orderHash]; + if (orderInfo.orderTakerAssetFilledAmount >= order.takerAssetAmount) { orderInfo.orderStatus = uint8(Status.ORDER_FULLY_FILLED); return orderInfo; } @@ -205,14 +205,14 @@ contract MixinExchangeCore is /// @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 orderFilledAmount Amount of order already filled. + /// @param orderTakerAssetFilledAmount 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 orderFilledAmount, + uint256 orderTakerAssetFilledAmount, uint256 takerAssetFillAmount ) public @@ -235,7 +235,7 @@ contract MixinExchangeCore is } // Compute takerAssetFilledAmount - uint256 remainingTakerAssetAmount = safeSub(order.takerAssetAmount, orderFilledAmount); + uint256 remainingTakerAssetAmount = safeSub(order.takerAssetAmount, orderTakerAssetFilledAmount); uint256 takerAssetFilledAmount = min256(takerAssetFillAmount, remainingTakerAssetAmount); // Validate fill order rounding @@ -277,7 +277,7 @@ contract MixinExchangeCore is /// @param orderStatus Status of order to be filled. /// @param orderHash Hash of order to be filled. /// @param takerAddress Address of order taker. - /// @param orderFilledAmount Amount of order already filled. + /// @param orderTakerAssetFilledAmount Amount of order already filled. /// @param takerAssetFillAmount Desired amount of order to fill by taker. /// @param signature Proof that the orders was created by its maker. function assertValidFill( @@ -285,7 +285,7 @@ contract MixinExchangeCore is uint8 orderStatus, bytes32 orderHash, address takerAddress, - uint256 orderFilledAmount, + uint256 orderTakerAssetFilledAmount, uint256 takerAssetFillAmount, bytes memory signature ) @@ -305,7 +305,7 @@ contract MixinExchangeCore is ); // Validate Maker signature (check only if first time seen) - if (orderFilledAmount == 0) { + if (orderTakerAssetFilledAmount == 0) { require( isValidSignature(orderHash, order.makerAddress, signature), SIGNATURE_VALIDATION_FAILED @@ -336,19 +336,19 @@ contract MixinExchangeCore is /// @dev Updates state with results of a fill order. /// @param order that was filled. /// @param takerAddress Address of taker who filled the order. - /// @param orderFilledAmount Amount of order already filled. + /// @param orderTakerAssetFilledAmount Amount of order already filled. /// @return fillResults Amounts filled and fees paid by maker and taker. function updateFilledState( Order memory order, address takerAddress, bytes32 orderHash, - uint256 orderFilledAmount, + uint256 orderTakerAssetFilledAmount, FillResults memory fillResults ) internal { // Update state - filled[orderHash] = safeAdd(orderFilledAmount, fillResults.takerAssetFilledAmount); + filled[orderHash] = safeAdd(orderTakerAssetFilledAmount, fillResults.takerAssetFilledAmount); // Log order emit Fill( diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index ac382d719..9d8b521c0 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -74,8 +74,8 @@ contract MixinMatchOrders is rightOrder, leftOrderInfo.orderStatus, rightOrderInfo.orderStatus, - leftOrderInfo.orderFilledAmount, - rightOrderInfo.orderFilledAmount + leftOrderInfo.orderTakerAssetFilledAmount, + rightOrderInfo.orderTakerAssetFilledAmount ); // Validate fill contexts @@ -84,7 +84,7 @@ contract MixinMatchOrders is leftOrderInfo.orderStatus, leftOrderInfo.orderHash, takerAddress, - leftOrderInfo.orderFilledAmount, + leftOrderInfo.orderTakerAssetFilledAmount, matchedFillResults.left.takerAssetFilledAmount, leftSignature ); @@ -93,7 +93,7 @@ contract MixinMatchOrders is rightOrderInfo.orderStatus, rightOrderInfo.orderHash, takerAddress, - rightOrderInfo.orderFilledAmount, + rightOrderInfo.orderTakerAssetFilledAmount, matchedFillResults.right.takerAssetFilledAmount, rightSignature ); @@ -111,14 +111,14 @@ contract MixinMatchOrders is leftOrder, takerAddress, leftOrderInfo.orderHash, - leftOrderInfo.orderFilledAmount, + leftOrderInfo.orderTakerAssetFilledAmount, matchedFillResults.left ); updateFilledState( rightOrder, takerAddress, rightOrderInfo.orderHash, - rightOrderInfo.orderFilledAmount, + rightOrderInfo.orderTakerAssetFilledAmount, matchedFillResults.right ); diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol index eae2c8091..fc0157d75 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IExchangeCore.sol @@ -61,14 +61,14 @@ contract IExchangeCore { /// @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 orderFilledAmount Amount of order already filled. + /// @param orderTakerAssetFilledAmount 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 orderFilledAmount, + uint256 orderTakerAssetFilledAmount, uint256 takerAssetFillAmount ) public diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol index 538b455d0..7d8328c67 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol @@ -57,7 +57,7 @@ contract LibOrder { // Keccak-256 EIP712 hash of the order bytes32 orderHash; // Amount of order that has been filled - uint256 orderFilledAmount; + uint256 orderTakerAssetFilledAmount; } /// @dev Calculates Keccak-256 hash of the order. 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 cd8335568..ae1e50637 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MExchangeCore.sol @@ -61,7 +61,7 @@ contract MExchangeCore is /// @param orderStatus Status of order to be filled. /// @param orderHash Hash of order to be filled. /// @param takerAddress Address of order taker. - /// @param orderFilledAmount Amount of order already filled. + /// @param orderTakerAssetFilledAmount Amount of order already filled. /// @param takerAssetFillAmount Desired amount of order to fill by taker. /// @param signature Proof that the orders was created by its maker. function assertValidFill( @@ -69,7 +69,7 @@ contract MExchangeCore is uint8 orderStatus, bytes32 orderHash, address takerAddress, - uint256 orderFilledAmount, + uint256 orderTakerAssetFilledAmount, uint256 takerAssetFillAmount, bytes memory signature ) @@ -78,13 +78,13 @@ contract MExchangeCore is /// @dev Updates state with results of a fill order. /// @param order that was filled. /// @param takerAddress Address of taker who filled the order. - /// @param orderFilledAmount Amount of order already filled. + /// @param orderTakerAssetFilledAmount Amount of order already filled. /// @return fillResults Amounts filled and fees paid by maker and taker. function updateFilledState( LibOrder.Order memory order, address takerAddress, bytes32 orderHash, - uint256 orderFilledAmount, + uint256 orderTakerAssetFilledAmount, LibFillResults.FillResults memory fillResults ) internal; diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts index ba1243672..8d81adece 100644 --- a/packages/contracts/src/utils/types.ts +++ b/packages/contracts/src/utils/types.ts @@ -178,7 +178,7 @@ export interface TransferAmountsByMatchOrders { export interface OrderInfo { orderStatus: number; orderHash: string; - orderFilledAmount: BigNumber; + orderTakerAssetFilledAmount: BigNumber; } export interface ERC20ProxyData { diff --git a/packages/contracts/test/utils/match_order_tester.ts b/packages/contracts/test/utils/match_order_tester.ts index f95d8fb34..14930de08 100644 --- a/packages/contracts/test/utils/match_order_tester.ts +++ b/packages/contracts/test/utils/match_order_tester.ts @@ -119,21 +119,21 @@ export class MatchOrderTester { const feeRecipientAddressLeft = signedOrderLeft.feeRecipientAddress; const feeRecipientAddressRight = signedOrderRight.feeRecipientAddress; // Verify Left order preconditions - const orderFilledAmountLeft = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( + const orderTakerAssetFilledAmountLeft = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( orderUtils.getOrderHashHex(signedOrderLeft), ); const expectedOrderFilledAmountLeft = initialTakerAssetFilledAmountLeft ? initialTakerAssetFilledAmountLeft : new BigNumber(0); - expect(expectedOrderFilledAmountLeft).to.be.bignumber.equal(orderFilledAmountLeft); + expect(expectedOrderFilledAmountLeft).to.be.bignumber.equal(orderTakerAssetFilledAmountLeft); // Verify Right order preconditions - const orderFilledAmountRight = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( + const orderTakerAssetFilledAmountRight = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( orderUtils.getOrderHashHex(signedOrderRight), ); const expectedOrderFilledAmountRight = initialTakerAssetFilledAmountRight ? initialTakerAssetFilledAmountRight : new BigNumber(0); - expect(expectedOrderFilledAmountRight).to.be.bignumber.equal(orderFilledAmountRight); + expect(expectedOrderFilledAmountRight).to.be.bignumber.equal(orderTakerAssetFilledAmountRight); // Match left & right orders await this._exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress); const newERC20BalancesByOwner = await this._erc20Wrapper.getBalancesAsync(); @@ -142,8 +142,8 @@ export class MatchOrderTester { const expectedTransferAmounts = await this._calculateExpectedTransferAmountsAsync( signedOrderLeft, signedOrderRight, - orderFilledAmountLeft, - orderFilledAmountRight, + orderTakerAssetFilledAmountLeft, + orderTakerAssetFilledAmountRight, ); let expectedERC20BalancesByOwner: ERC20BalancesByOwner; let expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner; @@ -169,19 +169,19 @@ export class MatchOrderTester { /// the taker when two orders are matched. /// @param signedOrderLeft First matched order. /// @param signedOrderRight Second matched order. - /// @param orderFilledAmountLeft How much left order has been filled, prior to matching orders. - /// @param orderFilledAmountRight How much the right order has been filled, prior to matching orders. + /// @param orderTakerAssetFilledAmountLeft How much left order has been filled, prior to matching orders. + /// @param orderTakerAssetFilledAmountRight How much the right order has been filled, prior to matching orders. /// @return TransferAmounts A struct containing the expected transfer amounts. private async _calculateExpectedTransferAmountsAsync( signedOrderLeft: SignedOrder, signedOrderRight: SignedOrder, - orderFilledAmountLeft: BigNumber, - orderFilledAmountRight: BigNumber, + orderTakerAssetFilledAmountLeft: BigNumber, + orderTakerAssetFilledAmountRight: BigNumber, ): Promise { let amountBoughtByLeftMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( orderUtils.getOrderHashHex(signedOrderLeft), ); - amountBoughtByLeftMaker = amountBoughtByLeftMaker.minus(orderFilledAmountLeft); + amountBoughtByLeftMaker = amountBoughtByLeftMaker.minus(orderTakerAssetFilledAmountLeft); const amountSoldByLeftMaker = amountBoughtByLeftMaker .times(signedOrderLeft.makerAssetAmount) .dividedToIntegerBy(signedOrderLeft.takerAssetAmount); @@ -192,7 +192,7 @@ export class MatchOrderTester { let amountBoughtByRightMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( orderUtils.getOrderHashHex(signedOrderRight), ); - amountBoughtByRightMaker = amountBoughtByRightMaker.minus(orderFilledAmountRight); + amountBoughtByRightMaker = amountBoughtByRightMaker.minus(orderTakerAssetFilledAmountRight); const amountSoldByRightMaker = amountBoughtByRightMaker .times(signedOrderRight.makerAssetAmount) .dividedToIntegerBy(signedOrderRight.takerAssetAmount); -- cgit v1.2.3 From f4ebbfabf48088a1b935a2fe688819be1781d1f9 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 18 May 2018 16:55:39 -0700 Subject: Added getNullFillResults --- .../current/protocol/Exchange/MixinExchangeCore.sol | 2 +- .../current/protocol/Exchange/libs/LibFillResults.sol | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) (limited to 'packages/contracts') diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index 4c45f06b5..1c2420374 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -107,7 +107,7 @@ contract MixinExchangeCore is ); if (status != uint8(Status.SUCCESS)) { emit ExchangeStatus(uint8(status), orderInfo.orderHash); - return fillResults; + return getNullFillResults(); } // Settle order diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol index ce30e3c0f..aa54598fa 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol @@ -50,4 +50,19 @@ contract LibFillResults is totalFillResults.makerFeePaid = safeAdd(totalFillResults.makerFeePaid, singleFillResults.makerFeePaid); totalFillResults.takerFeePaid = safeAdd(totalFillResults.takerFeePaid, singleFillResults.takerFeePaid); } + + /// @dev Returns a null fill results struct + function getNullFillResults() + internal + pure + returns (FillResults memory) + { + // returns zeroed out FillResults instance + return FillResults({ + makerAssetFilledAmount: 0, + takerAssetFilledAmount: 0, + makerFeePaid: 0, + takerFeePaid: 0 + }); + } } -- cgit v1.2.3