336 lines
14 KiB
Solidity
336 lines
14 KiB
Solidity
/*
|
|
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 "../../utils/ReentrancyGuard/ReentrancyGuard.sol";
|
|
import "./libs/LibConstants.sol";
|
|
import "./libs/LibMath.sol";
|
|
import "./libs/LibOrder.sol";
|
|
import "./libs/LibFillResults.sol";
|
|
import "./mixins/MExchangeCore.sol";
|
|
import "./mixins/MMatchOrders.sol";
|
|
import "./mixins/MTransactions.sol";
|
|
import "./mixins/MAssetProxyDispatcher.sol";
|
|
|
|
|
|
contract MixinMatchOrders is
|
|
ReentrancyGuard,
|
|
LibConstants,
|
|
LibMath,
|
|
MAssetProxyDispatcher,
|
|
MExchangeCore,
|
|
MMatchOrders,
|
|
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.
|
|
function matchOrders(
|
|
LibOrder.Order memory leftOrder,
|
|
LibOrder.Order memory rightOrder,
|
|
bytes memory leftSignature,
|
|
bytes memory rightSignature
|
|
)
|
|
public
|
|
nonReentrant
|
|
returns (LibFillResults.MatchedFillResults memory matchedFillResults)
|
|
{
|
|
// We assume that rightOrder.takerAssetData == leftOrder.makerAssetData and rightOrder.makerAssetData == leftOrder.takerAssetData.
|
|
// If this assumption isn't true, the match will fail at signature validation.
|
|
rightOrder.makerAssetData = leftOrder.takerAssetData;
|
|
rightOrder.takerAssetData = leftOrder.makerAssetData;
|
|
|
|
// Get left & right order info
|
|
LibOrder.OrderInfo memory leftOrderInfo = getOrderInfo(leftOrder);
|
|
LibOrder.OrderInfo memory rightOrderInfo = getOrderInfo(rightOrder);
|
|
|
|
// Fetch taker address
|
|
address takerAddress = getCurrentContextAddress();
|
|
|
|
// Either our context is valid or we revert
|
|
assertFillableOrder(
|
|
leftOrder,
|
|
leftOrderInfo,
|
|
takerAddress,
|
|
leftSignature
|
|
);
|
|
assertFillableOrder(
|
|
rightOrder,
|
|
rightOrderInfo,
|
|
takerAddress,
|
|
rightSignature
|
|
);
|
|
assertValidMatch(leftOrder, rightOrder);
|
|
|
|
// Compute proportional fill amounts
|
|
matchedFillResults = calculateMatchedFillResults(
|
|
leftOrder,
|
|
rightOrder,
|
|
leftOrderInfo.orderTakerAssetFilledAmount,
|
|
rightOrderInfo.orderTakerAssetFilledAmount
|
|
);
|
|
|
|
// Validate fill contexts
|
|
assertValidFill(
|
|
leftOrder,
|
|
leftOrderInfo,
|
|
matchedFillResults.left.takerAssetFilledAmount,
|
|
matchedFillResults.left.takerAssetFilledAmount,
|
|
matchedFillResults.left.makerAssetFilledAmount
|
|
);
|
|
assertValidFill(
|
|
rightOrder,
|
|
rightOrderInfo,
|
|
matchedFillResults.right.takerAssetFilledAmount,
|
|
matchedFillResults.right.takerAssetFilledAmount,
|
|
matchedFillResults.right.makerAssetFilledAmount
|
|
);
|
|
|
|
// Update exchange state
|
|
updateFilledState(
|
|
leftOrder,
|
|
takerAddress,
|
|
leftOrderInfo.orderHash,
|
|
leftOrderInfo.orderTakerAssetFilledAmount,
|
|
matchedFillResults.left
|
|
);
|
|
updateFilledState(
|
|
rightOrder,
|
|
takerAddress,
|
|
rightOrderInfo.orderHash,
|
|
rightOrderInfo.orderTakerAssetFilledAmount,
|
|
matchedFillResults.right
|
|
);
|
|
|
|
// Settle matched orders. Succeeds or throws.
|
|
settleMatchedOrders(
|
|
leftOrder,
|
|
rightOrder,
|
|
takerAddress,
|
|
matchedFillResults
|
|
);
|
|
|
|
return matchedFillResults;
|
|
}
|
|
|
|
/// @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
|
|
pure
|
|
{
|
|
// 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_REQUIRED"
|
|
);
|
|
}
|
|
|
|
/// @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 leftOrderTakerAssetFilledAmount Amount of left order already filled.
|
|
/// @param rightOrderTakerAssetFilledAmount 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,
|
|
uint256 leftOrderTakerAssetFilledAmount,
|
|
uint256 rightOrderTakerAssetFilledAmount
|
|
)
|
|
internal
|
|
pure
|
|
returns (LibFillResults.MatchedFillResults memory matchedFillResults)
|
|
{
|
|
// Derive maker asset amounts for left & right orders, given store taker assert amounts
|
|
uint256 leftTakerAssetAmountRemaining = safeSub(leftOrder.takerAssetAmount, leftOrderTakerAssetFilledAmount);
|
|
uint256 leftMakerAssetAmountRemaining = getPartialAmountFloor(
|
|
leftOrder.makerAssetAmount,
|
|
leftOrder.takerAssetAmount,
|
|
leftTakerAssetAmountRemaining
|
|
);
|
|
uint256 rightTakerAssetAmountRemaining = safeSub(rightOrder.takerAssetAmount, rightOrderTakerAssetFilledAmount);
|
|
uint256 rightMakerAssetAmountRemaining = getPartialAmountFloor(
|
|
rightOrder.makerAssetAmount,
|
|
rightOrder.takerAssetAmount,
|
|
rightTakerAssetAmountRemaining
|
|
);
|
|
|
|
// Calculate fill results for maker and taker assets: at least one order will be fully filled.
|
|
// The maximum amount the left maker can buy is `leftTakerAssetAmountRemaining`
|
|
// The maximum amount the right maker can sell is `rightMakerAssetAmountRemaining`
|
|
// We have two distinct cases for calculating the fill results:
|
|
// Case 1.
|
|
// If the left maker can buy more than the right maker can sell, then only the right order is fully filled.
|
|
// If the left maker can buy exactly what the right maker can sell, then both orders are fully filled.
|
|
// Case 2.
|
|
// If the left maker cannot buy more than the right maker can sell, then only the left order is fully filled.
|
|
if (leftTakerAssetAmountRemaining >= rightMakerAssetAmountRemaining) {
|
|
// Case 1: Right order is fully filled
|
|
matchedFillResults.right.makerAssetFilledAmount = rightMakerAssetAmountRemaining;
|
|
matchedFillResults.right.takerAssetFilledAmount = rightTakerAssetAmountRemaining;
|
|
matchedFillResults.left.takerAssetFilledAmount = matchedFillResults.right.makerAssetFilledAmount;
|
|
// Round down to ensure the maker's exchange rate does not exceed the price specified by the order.
|
|
// We favor the maker when the exchange rate must be rounded.
|
|
matchedFillResults.left.makerAssetFilledAmount = getPartialAmountFloor(
|
|
leftOrder.makerAssetAmount,
|
|
leftOrder.takerAssetAmount,
|
|
matchedFillResults.left.takerAssetFilledAmount
|
|
);
|
|
} else {
|
|
// Case 2: Left order is fully filled
|
|
matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining;
|
|
matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining;
|
|
matchedFillResults.right.makerAssetFilledAmount = matchedFillResults.left.takerAssetFilledAmount;
|
|
// Round up to ensure the maker's exchange rate does not exceed the price specified by the order.
|
|
// We favor the maker when the exchange rate must be rounded.
|
|
matchedFillResults.right.takerAssetFilledAmount = getPartialAmountCeil(
|
|
rightOrder.takerAssetAmount,
|
|
rightOrder.makerAssetAmount,
|
|
matchedFillResults.right.makerAssetFilledAmount
|
|
);
|
|
}
|
|
|
|
// Calculate amount given to taker
|
|
matchedFillResults.leftMakerAssetSpreadAmount = safeSub(
|
|
matchedFillResults.left.makerAssetFilledAmount,
|
|
matchedFillResults.right.takerAssetFilledAmount
|
|
);
|
|
|
|
// Compute fees for left order
|
|
matchedFillResults.left.makerFeePaid = getPartialAmountFloor(
|
|
matchedFillResults.left.makerAssetFilledAmount,
|
|
leftOrder.makerAssetAmount,
|
|
leftOrder.makerFee
|
|
);
|
|
matchedFillResults.left.takerFeePaid = getPartialAmountFloor(
|
|
matchedFillResults.left.takerAssetFilledAmount,
|
|
leftOrder.takerAssetAmount,
|
|
leftOrder.takerFee
|
|
);
|
|
|
|
// Compute fees for right order
|
|
matchedFillResults.right.makerFeePaid = getPartialAmountFloor(
|
|
matchedFillResults.right.makerAssetFilledAmount,
|
|
rightOrder.makerAssetAmount,
|
|
rightOrder.makerFee
|
|
);
|
|
matchedFillResults.right.takerFeePaid = getPartialAmountFloor(
|
|
matchedFillResults.right.takerAssetFilledAmount,
|
|
rightOrder.takerAssetAmount,
|
|
rightOrder.takerFee
|
|
);
|
|
|
|
// Return fill results
|
|
return matchedFillResults;
|
|
}
|
|
|
|
/// @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
|
|
)
|
|
private
|
|
{
|
|
bytes memory zrxAssetData = ZRX_ASSET_DATA;
|
|
// 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.leftMakerAssetSpreadAmount
|
|
);
|
|
|
|
// Maker fees
|
|
dispatchTransferFrom(
|
|
zrxAssetData,
|
|
leftOrder.makerAddress,
|
|
leftOrder.feeRecipientAddress,
|
|
matchedFillResults.left.makerFeePaid
|
|
);
|
|
dispatchTransferFrom(
|
|
zrxAssetData,
|
|
rightOrder.makerAddress,
|
|
rightOrder.feeRecipientAddress,
|
|
matchedFillResults.right.makerFeePaid
|
|
);
|
|
|
|
// Taker fees
|
|
if (leftOrder.feeRecipientAddress == rightOrder.feeRecipientAddress) {
|
|
dispatchTransferFrom(
|
|
zrxAssetData,
|
|
takerAddress,
|
|
leftOrder.feeRecipientAddress,
|
|
safeAdd(
|
|
matchedFillResults.left.takerFeePaid,
|
|
matchedFillResults.right.takerFeePaid
|
|
)
|
|
);
|
|
} else {
|
|
dispatchTransferFrom(
|
|
zrxAssetData,
|
|
takerAddress,
|
|
leftOrder.feeRecipientAddress,
|
|
matchedFillResults.left.takerFeePaid
|
|
);
|
|
dispatchTransferFrom(
|
|
zrxAssetData,
|
|
takerAddress,
|
|
rightOrder.feeRecipientAddress,
|
|
matchedFillResults.right.takerFeePaid
|
|
);
|
|
}
|
|
}
|
|
}
|