diff options
Diffstat (limited to 'packages/contracts/src')
56 files changed, 1297 insertions, 224 deletions
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 85e2bc47e..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"; @@ -25,24 +25,29 @@ 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"; + // Mixins are instantiated in the order they are inherited 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/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 82c7a2ddc..1c2420374 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -16,12 +16,13 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; 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,6 +51,8 @@ contract MixinExchangeCore is // Orders with a salt less than their maker's epoch are considered cancelled mapping (address => uint256) public makerEpoch; + ////// 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) @@ -70,42 +75,243 @@ contract MixinExchangeCore is function fillOrder( Order memory order, uint256 takerAssetFillAmount, - bytes memory signature) + bytes memory signature + ) public returns (FillResults memory fillResults) { + // Fetch order info + OrderInfo memory orderInfo = getOrderInfo(order); + + // Fetch taker address + address takerAddress = getCurrentContextAddress(); + + // Either our context is valid or we revert + assertValidFill( + order, + orderInfo.orderStatus, + orderInfo.orderHash, + takerAddress, + orderInfo.orderTakerAssetFilledAmount, + takerAssetFillAmount, + signature + ); + + // Compute proportional fill amounts + uint8 status; + (status, fillResults) = calculateFillResults( + order, + orderInfo.orderStatus, + orderInfo.orderTakerAssetFilledAmount, + takerAssetFillAmount + ); + if (status != uint8(Status.SUCCESS)) { + emit ExchangeStatus(uint8(status), orderInfo.orderHash); + return getNullFillResults(); + } + + // Settle order + settleOrder(order, takerAddress, fillResults); + + // Update exchange internal state + updateFilledState( + order, + takerAddress, + orderInfo.orderHash, + orderInfo.orderTakerAssetFilledAmount, + 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 valid, but in an + /// unfillable state (see LibStatus.STATUS for order states) + function cancelOrder(Order memory order) + public + returns (bool) + { + // Fetch current order status + OrderInfo memory orderInfo = getOrderInfo(order); + + // Validate context + assertValidCancel(order, orderInfo.orderStatus, orderInfo.orderHash); + + // Perform cancel + return updateCancelledState(order, orderInfo.orderStatus, orderInfo.orderHash); + } + + /// @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 - bytes32 orderHash = getOrderHash(order); + orderInfo.orderHash = getOrderHash(order); - // 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.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; } - // Check if order has been cancelled by orderHash - if (cancelled[orderHash]) { - 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 == orderTakerAssetFilledAmount + // 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 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 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.orderTakerAssetFilledAmount = filled[orderInfo.orderHash]; + if (orderInfo.orderTakerAssetFilledAmount >= 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. + /// @param order to be filled. + /// @param orderStatus Status of order to be 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 orderTakerAssetFilledAmount, + 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 (status, fillResults); + } + + // Ensure the order is fillable + if (orderStatus != uint8(Status.ORDER_FILLABLE)) { + status = orderStatus; + return (status, fillResults); + } + + // Compute takerAssetFilledAmount + uint256 remainingTakerAssetAmount = safeSub(order.takerAssetAmount, orderTakerAssetFilledAmount); + uint256 takerAssetFilledAmount = min256(takerAssetFillAmount, remainingTakerAssetAmount); + + // Validate fill order rounding + if (isRoundingError( + takerAssetFilledAmount, + order.takerAssetAmount, + order.makerAssetAmount)) + { + status = uint8(Status.ROUNDING_ERROR_TOO_LARGE); + return (status, fillResults); + } + + // Compute proportional transfer amounts + // TODO: All three are multiplied by the same fraction. This can + // potentially be optimized. + fillResults.takerAssetFilledAmount = takerAssetFilledAmount; + 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 (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 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( + Order memory order, + uint8 orderStatus, + bytes32 orderHash, + address takerAddress, + uint256 orderTakerAssetFilledAmount, + 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 (orderTakerAssetFilledAmount == 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 +321,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 +331,108 @@ 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; - } - - // Validate order availability - uint256 remainingTakerAssetFillAmount = safeSub(order.takerAssetAmount, filled[orderHash]); - if (remainingTakerAssetFillAmount == 0) { - emit ExchangeError(uint8(Errors.ORDER_FULLY_FILLED), orderHash); - return fillResults; - } - - // 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); - fillResults.takerAssetFilledAmount = 0; - return 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 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 orderTakerAssetFilledAmount, + 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); + filled[orderHash] = safeAdd(orderTakerAssetFilledAmount, fillResults.takerAssetFilledAmount); // Log order - emitFillEvent(order, takerAddress, orderHash, fillResults); - return fillResults; + emit Fill( + order.makerAddress, + takerAddress, + order.feeRecipientAddress, + fillResults.makerAssetFilledAmount, + fillResults.takerAssetFilledAmount, + fillResults.makerFeePaid, + fillResults.takerFeePaid, + orderHash, + order.makerAssetData, + order.takerAssetData + ); } - /// @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) + /// @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 assertValidCancel( + Order memory order, + uint8 orderStatus, + bytes32 orderHash + ) + internal { - // Compute the order hash - bytes32 orderHash = getOrderHash(order); - - // Validate the order + // Ensure order is valid + // 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( - 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) + // See LibStatus for a complete description of order statuses. + 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,29 +440,7 @@ contract MixinExchangeCore is order.makerAssetData, order.takerAssetData ); - return true; - } - /// @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 - ); + return stateUpdated; } } 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..9d8b521c0 --- /dev/null +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -0,0 +1,297 @@ +/* + 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.24; +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"; +import "./libs/LibExchangeErrors.sol"; + +contract MixinMatchOrders is + SafeMath, + LibBytes, + LibMath, + LibStatus, + LibOrder, + LibFillResults, + LibExchangeErrors, + MExchangeCore, + MMatchOrders, + MSettlement, + MTransactions +{ + + /// @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). + /// @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. + /// TODO: Make this function external once supported by Solidity (See Solidity Issues #3199, #1603) + function matchOrders( + Order memory leftOrder, + Order memory rightOrder, + bytes memory leftSignature, + bytes memory rightSignature + ) + public + returns (MatchedFillResults memory matchedFillResults) + { + // Get left & right order info + OrderInfo memory leftOrderInfo = getOrderInfo(leftOrder); + OrderInfo memory rightOrderInfo = getOrderInfo(rightOrder); + + // Fetch taker address + address takerAddress = getCurrentContextAddress(); + + // Either our context is valid or we revert + assertValidMatch(leftOrder, rightOrder); + + // Compute proportional fill amounts + matchedFillResults = calculateMatchedFillResults( + leftOrder, + rightOrder, + leftOrderInfo.orderStatus, + rightOrderInfo.orderStatus, + leftOrderInfo.orderTakerAssetFilledAmount, + rightOrderInfo.orderTakerAssetFilledAmount + ); + + // Validate fill contexts + assertValidFill( + leftOrder, + leftOrderInfo.orderStatus, + leftOrderInfo.orderHash, + takerAddress, + leftOrderInfo.orderTakerAssetFilledAmount, + matchedFillResults.left.takerAssetFilledAmount, + leftSignature + ); + assertValidFill( + rightOrder, + rightOrderInfo.orderStatus, + rightOrderInfo.orderHash, + takerAddress, + rightOrderInfo.orderTakerAssetFilledAmount, + matchedFillResults.right.takerAssetFilledAmount, + rightSignature + ); + + // Settle matched orders. Succeeds or throws. + settleMatchedOrders( + leftOrder, + rightOrder, + takerAddress, + matchedFillResults + ); + + // Update exchange state + updateFilledState( + leftOrder, + takerAddress, + leftOrderInfo.orderHash, + leftOrderInfo.orderTakerAssetFilledAmount, + matchedFillResults.left + ); + updateFilledState( + rightOrder, + takerAddress, + rightOrderInfo.orderHash, + rightOrderInfo.orderTakerAssetFilledAmount, + matchedFillResults.right + ); + + return matchedFillResults; + } + + /// @dev Validates context for matchOrders. Succeeds or throws. + /// @param leftOrder First order to match. + /// @param rightOrder Second order to match. + function assertValidMatch( + Order memory leftOrder, + Order memory rightOrder + ) + 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 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: + // <leftOrder.makerAssetAmount> / <leftOrder.takerAssetAmount> >= <rightOrder.takerAssetAmount> / <rightOrder.makerAssetAmount> + // AND + // <rightOrder.makerAssetAmount> / <rightOrder.takerAssetAmount> >= <leftOrder.takerAssetAmount> / <leftOrder.makerAssetAmount> + // These equations can be combined to get the following: + require( + safeMul(leftOrder.makerAssetAmount, rightOrder.makerAssetAmount) >= + safeMul(leftOrder.takerAssetAmount, rightOrder.takerAssetAmount), + NEGATIVE_SPREAD + ); + } + + /// @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 assertValidMatchResults(MatchedFillResults memory matchedFillResults) + internal + { + // 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( + !isRoundingError( + matchedFillResults.left.makerAssetFilledAmount, + amountSpentByLeft, + 1 + ), + ROUNDING_ERROR_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( + !isRoundingError( + matchedFillResults.right.makerAssetFilledAmount, + matchedFillResults.left.takerAssetFilledAmount, + 1 + ), + ROUNDING_ERROR_TRANSFER_AMOUNTS + ); + } + + /// @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. + /// @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 (MatchedFillResults memory matchedFillResults) + { + // 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: + // <leftTakerAssetAmountRemaining> <= <rightTakerAssetAmountRemaining> * <rightMakerToTakerRatio> + // <leftTakerAssetAmountRemaining> <= <rightTakerAssetAmountRemaining> * <rightOrder.makerAssetAmount> / <rightOrder.takerAssetAmount> + // <leftTakerAssetAmountRemaining> * <rightOrder.takerAssetAmount> <= <rightTakerAssetAmountRemaining> * <rightOrder.makerAssetAmount> + uint256 rightTakerAssetAmountRemaining = safeSub(rightOrder.takerAssetAmount, rightOrderFilledAmount); + uint256 leftTakerAssetAmountRemaining = safeSub(leftOrder.takerAssetAmount, leftOrderFilledAmount); + uint256 leftOrderAmountToFill; + uint256 rightOrderAmountToFill; + if ( + safeMul(leftTakerAssetAmountRemaining, rightOrder.takerAssetAmount) <= + safeMul(rightTakerAssetAmountRemaining, rightOrder.makerAssetAmount) + ) { + // Left order will be fully filled: maximally fill left + leftOrderAmountToFill = leftTakerAssetAmountRemaining; + + // The right order receives an amount proportional to how much was spent. + // TODO: Can we ensure rounding error is in the correct direction? + rightOrderAmountToFill = safeGetPartialAmount( + rightOrder.takerAssetAmount, + rightOrder.makerAssetAmount, + leftOrderAmountToFill + ); + } else { + // Right order will be fully filled: maximally fill right + rightOrderAmountToFill = rightTakerAssetAmountRemaining; + + // The left order receives an amount proportional to how much was spent. + // TODO: Can we ensure rounding error is in the correct direction? + leftOrderAmountToFill = safeGetPartialAmount( + rightOrder.makerAssetAmount, + rightOrder.takerAssetAmount, + rightOrderAmountToFill + ); + } + + // Calculate fill results for left order + uint8 status; + (status, matchedFillResults.left) = calculateFillResults( + leftOrder, + leftOrderStatus, + leftOrderFilledAmount, + leftOrderAmountToFill + ); + require( + status == uint8(Status.SUCCESS), + FAILED_TO_CALCULATE_FILL_RESULTS_FOR_LEFT_ORDER + ); + + // Calculate fill results for right order + (status, matchedFillResults.right) = calculateFillResults( + rightOrder, + rightOrderStatus, + rightOrderFilledAmount, + rightOrderAmountToFill + ); + require( + status == uint8(Status.SUCCESS), + FAILED_TO_CALCULATE_FILL_RESULTS_FOR_RIGHT_ORDER + ); + + // Calculate amount given to taker + matchedFillResults.takerFillAmount = safeSub( + matchedFillResults.left.makerAssetFilledAmount, + matchedFillResults.right.takerAssetFilledAmount + ); + + // Validate the fill results + assertValidMatchResults(matchedFillResults); + + // Return fill 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..7c03bde75 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol @@ -16,15 +16,22 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; +pragma experimental ABIEncoderV2; 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"; contract MixinSettlement is LibMath, + LibFillResults, + LibExchangeErrors, + MMatchOrders, MSettlement, MAssetProxyDispatcher { @@ -54,46 +61,111 @@ 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. + /// @param leftOrder First matched order. + /// @param rightOrder Second matched order. + /// @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, + address takerAddress, + MatchedFillResults memory matchedFillResults + ) + internal + { + // Order makers and taker + dispatchTransferFrom( + leftOrder.makerAssetData, + leftOrder.makerAddress, + rightOrder.makerAddress, + matchedFillResults.right.takerAssetFilledAmount + ); + dispatchTransferFrom( + rightOrder.makerAssetData, + rightOrder.makerAddress, + leftOrder.makerAddress, + matchedFillResults.left.takerAssetFilledAmount + ); + dispatchTransferFrom( + leftOrder.makerAssetData, + leftOrder.makerAddress, + takerAddress, + matchedFillResults.takerFillAmount + ); + + // 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 (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/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 7f12834a3..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.21; +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 42517221e..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"; @@ -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/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 9fba3491b..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,19 +16,23 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; 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 {} 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..fc0157d75 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"; @@ -48,4 +48,33 @@ 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 OrderInfo Information about the order and its state. + /// See LibOrder.OrderInfo for a complete description. + function getOrderInfo(LibOrder.Order memory order) + 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 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 orderTakerAssetFilledAmount, + uint256 takerAssetFillAmount + ) + public + pure + returns ( + uint8 status, + LibFillResults.FillResults memory fillResults + ); } 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..df009d063 --- /dev/null +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IMatchOrders.sol @@ -0,0 +1,44 @@ +/* + + 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.24; +pragma experimental ABIEncoderV2; + +import "../libs/LibOrder.sol"; +import "../libs/LibFillResults.sol"; + +contract IMatchOrders { + + /// @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). + /// @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. + /// TODO: Make this function external once supported by Solidity (See Solidity Issues #3199, #1603) + function matchOrders( + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder, + bytes memory leftSignature, + bytes memory rightSignature + ) + public + returns (LibFillResults.MatchedFillResults memory matchedFillResults); +} 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 d23170b1c..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,17 +16,17 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; 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..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,21 +16,10 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; 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."; @@ -55,4 +48,13 @@ 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 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/LibFillResults.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibFillResults.sol index 3b8f2acf9..aa54598fa 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"; @@ -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,19 @@ contract LibFillResults is totalFillResults.makerFeePaid = safeAdd(totalFillResults.makerFeePaid, singleFillResults.makerFeePaid); totalFillResults.takerFeePaid = safeAdd(totalFillResults.takerFeePaid, singleFillResults.takerFeePaid); } -}
\ No newline at end of file + + /// @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 + }); + } +} 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..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,13 +16,14 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; 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/libs/LibOrder.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibOrder.sol index 0fe8c8c96..7d8328c67 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 { @@ -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 orderTakerAssetFilledAmount; + } + /// @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/libs/LibStatus.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol new file mode 100644 index 000000000..f72b7d65f --- /dev/null +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibStatus.sol @@ -0,0 +1,51 @@ +/* + + 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.24; +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 /// + // 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 + 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..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,8 @@ */ -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 5b78e70da..ae1e50637 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,8 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; +pragma experimental ABIEncoderV2; import "../libs/LibOrder.sol"; import "../libs/LibFillResults.sol"; @@ -55,12 +56,62 @@ 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( + /// @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 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( + LibOrder.Order memory order, + uint8 orderStatus, + bytes32 orderHash, + address takerAddress, + uint256 orderTakerAssetFilledAmount, + uint256 takerAssetFillAmount, + bytes memory signature + ) + internal; + + /// @dev Updates state with results of a fill order. + /// @param order that was filled. + /// @param takerAddress Address of taker who filled the order. + /// @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, - LibFillResults.FillResults memory fillResults) + uint256 orderTakerAssetFilledAmount, + LibFillResults.FillResults memory fillResults + ) internal; + + /// @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 assertValidCancel( + 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); } 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..7dd608cf2 --- /dev/null +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MMatchOrders.sol @@ -0,0 +1,65 @@ +/* + + 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.24; +pragma experimental ABIEncoderV2; + +import "../libs/LibOrder.sol"; +import "../libs/LibFillResults.sol"; +import "./MExchangeCore.sol"; +import "../interfaces/IMatchOrders.sol"; + +contract MMatchOrders is + IMatchOrders +{ + + /// @dev Validates context for matchOrders. Succeeds or throws. + /// @param leftOrder First order to match. + /// @param rightOrder Second order to match. + function assertValidMatch( + 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 assertValidMatchResults(LibFillResults.MatchedFillResults memory matchedFillResults) + internal; + + /// @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. + /// @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 (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 5e2edbf99..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,26 +16,35 @@ */ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; 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. + /// @param rightOrder Second matched order. + /// @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, + address takerAddress, + LibFillResults.MatchedFillResults memory matchedFillResults + ) + internal; } - 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 20202cd7b..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"; @@ -38,4 +38,4 @@ contract TestSignatureValidator is MixinSignatureValidator { ); return isValid; } -}
\ No newline at end of file +} 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 { diff --git a/packages/contracts/src/utils/asset_proxy_utils.ts b/packages/contracts/src/utils/asset_proxy_utils.ts index dc31c3497..c042da5d0 100644 --- a/packages/contracts/src/utils/asset_proxy_utils.ts +++ b/packages/contracts/src/utils/asset_proxy_utils.ts @@ -2,12 +2,15 @@ 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 { 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,32 @@ export const assetProxyUtils = { const encodedMetadataHex = ethUtil.bufferToHex(encodedMetadata); return encodedMetadataHex; }, + decodeERC20ProxyData(proxyData: string): ERC20ProxyData { + 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); + const erc20ProxyData = { + assetProxyId, + tokenAddress, + }; + return erc20ProxyData; + }, encodeERC721ProxyData(tokenAddress: string, tokenId: BigNumber): string { const encodedAssetProxyId = assetProxyUtils.encodeAssetProxyId(AssetProxyId.ERC721); const encodedAddress = assetProxyUtils.encodeAddress(tokenAddress); @@ -36,4 +77,68 @@ export const assetProxyUtils = { const encodedMetadataHex = ethUtil.bufferToHex(encodedMetadata); return encodedMetadataHex; }, + decodeERC721ProxyData(proxyData: string): ERC721ProxyData { + 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); + const erc721ProxyData = { + assetProxyId, + tokenAddress, + tokenId, + }; + return erc721ProxyData; + }, + 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; + }, + 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/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts index 27fdd698f..46531fa3f 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,6 +225,26 @@ export class ExchangeWrapper { const filledAmount = new BigNumber(await this._exchange.filled.callAsync(orderHashHex)); return filledAmount; } + public async getOrderInfoAsync(signedOrder: SignedOrder): Promise<OrderInfo> { + const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo; + return orderInfo; + } + public async matchOrdersAsync( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + from: string, + ): Promise<TransactionReceiptWithDecodedLogs> { + 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; + } private async _getTxWithDecodedExchangeLogsAsync(txHash: string) { const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash); tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address); 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..8d81adece 100644 --- a/packages/contracts/src/utils/types.ts +++ b/packages/contracts/src/utils/types.ts @@ -74,12 +74,22 @@ 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 { + 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 { @@ -143,3 +153,47 @@ 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; +} + +export interface OrderInfo { + orderStatus: number; + orderHash: string; + orderTakerAssetFilledAmount: 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; +} |