Style changes to atomic order matching

This commit is contained in:
Greg Hysen
2018-05-11 14:51:00 -07:00
parent fa7570352c
commit 5735095521
11 changed files with 332 additions and 350 deletions

View File

@@ -53,6 +53,20 @@ contract MixinExchangeCore is
////// Core exchange functions //////
/// @dev Cancels all orders reated by sender with a salt less than or equal to the specified salt value.
/// @param salt Orders created with a salt less or equal to this value will be cancelled.
function cancelOrdersUpTo(uint256 salt)
external
{
uint256 newMakerEpoch = salt + 1; // makerEpoch is initialized to 0, so to cancelUpTo we need salt + 1
require(
newMakerEpoch > makerEpoch[msg.sender], // epoch must be monotonically increasing
INVALID_NEW_MAKER_EPOCH
);
makerEpoch[msg.sender] = newMakerEpoch;
emit CancelUpTo(msg.sender, newMakerEpoch);
}
/// @dev Gets information about an order: status, hash, and amount filled.
/// @param order Order to gather information on.
/// @return status Status of order. Statuses are defined in the LibStatus.Status struct.
@@ -64,20 +78,11 @@ contract MixinExchangeCore is
returns (
uint8 orderStatus,
bytes32 orderHash,
uint256 takerAssetFilledAmount)
uint256 takerAssetFilledAmount
)
{
// Compute the order hash and fetch filled amount
// Compute the order hash
orderHash = getOrderHash(order);
takerAssetFilledAmount = filled[orderHash];
// If order.takerAssetAmount is zero, then the order will always
// be considered filled because 0 == takerAssetAmount == takerAssetFilledAmount
// Instead of distinguishing between unfilled and filled zero taker
// amount orders, we choose not to support them.
if (order.takerAssetAmount == 0) {
orderStatus = uint8(Status.ORDER_INVALID_TAKER_ASSET_AMOUNT);
return (orderStatus, orderHash, takerAssetFilledAmount);
}
// If order.makerAssetAmount is zero, we also reject the order.
// While the Exchange contract handles them correctly, they create
@@ -88,15 +93,18 @@ contract MixinExchangeCore is
return (orderStatus, orderHash, takerAssetFilledAmount);
}
// Validate order expiration
if (block.timestamp >= order.expirationTimeSeconds) {
orderStatus = uint8(Status.ORDER_EXPIRED);
// If order.takerAssetAmount is zero, then the order will always
// be considered filled because 0 == takerAssetAmount == takerAssetFilledAmount
// Instead of distinguishing between unfilled and filled zero taker
// amount orders, we choose not to support them.
if (order.takerAssetAmount == 0) {
orderStatus = uint8(Status.ORDER_INVALID_TAKER_ASSET_AMOUNT);
return (orderStatus, orderHash, takerAssetFilledAmount);
}
// Validate order availability
if (takerAssetFilledAmount >= order.takerAssetAmount) {
orderStatus = uint8(Status.ORDER_FULLY_FILLED);
// Validate order expiration
if (block.timestamp >= order.expirationTimeSeconds) {
orderStatus = uint8(Status.ORDER_EXPIRED);
return (orderStatus, orderHash, takerAssetFilledAmount);
}
@@ -110,6 +118,13 @@ contract MixinExchangeCore is
return (orderStatus, orderHash, takerAssetFilledAmount);
}
// Fetch filled amount and validate order availability
takerAssetFilledAmount = filled[orderHash];
if (takerAssetFilledAmount >= order.takerAssetAmount) {
orderStatus = uint8(Status.ORDER_FULLY_FILLED);
return (orderStatus, orderHash, takerAssetFilledAmount);
}
// All other statuses are ruled out: order is Fillable
orderStatus = uint8(Status.ORDER_FILLABLE);
return (orderStatus, orderHash, takerAssetFilledAmount);
@@ -119,18 +134,18 @@ contract MixinExchangeCore is
/// @param order to be filled.
/// @param orderStatus Status of order to be filled.
/// @param orderHash Hash of order to be filled.
/// @param takerAssetFilledAmount Amount of order already filled.
/// @param signature Proof that the orders was created by its maker.
/// @param takerAddress Address of order taker.
/// @param takerAssetFilledAmount Amount of order already filled.
/// @param takerAssetFillAmount Desired amount of order to fill by taker.
function validateFillOrderContextOrRevert(
/// @param signature Proof that the orders was created by its maker.
function validateFillOrRevert(
Order memory order,
uint8 orderStatus,
bytes32 orderHash,
uint256 takerAssetFilledAmount,
bytes memory signature,
address takerAddress,
uint256 takerAssetFillAmount)
uint256 takerAssetFilledAmount,
uint256 takerAssetFillAmount,
bytes memory signature)
internal
{
// Ensure order is valid
@@ -190,23 +205,24 @@ contract MixinExchangeCore is
pure
returns (
uint8 status,
FillResults memory fillResults)
FillResults memory fillResults
)
{
// Fill Amount must be greater than 0
if (takerAssetFillAmount <= 0) {
// Fill amount must be greater than 0
if (takerAssetFillAmount == 0) {
status = uint8(Status.TAKER_ASSET_FILL_AMOUNT_TOO_LOW);
return;
}
// Ensure the order is fillable
if (orderStatus != uint8(Status.ORDER_FILLABLE)) {
status = uint8(orderStatus);
status = orderStatus;
return;
}
// Compute takerAssetFilledAmount
uint256 remainingtakerAssetAmount = safeSub(order.takerAssetAmount, takerAssetFilledAmount);
fillResults.takerAssetFilledAmount = min256(takerAssetFillAmount, remainingtakerAssetAmount);
uint256 remainingTakerAssetAmount = safeSub(order.takerAssetAmount, takerAssetFilledAmount);
fillResults.takerAssetFilledAmount = min256(takerAssetFillAmount, remainingTakerAssetAmount);
// Validate fill order rounding
if (isRoundingError(
@@ -242,19 +258,32 @@ contract MixinExchangeCore is
/// @dev Updates state with results of a fill order.
/// @param order that was filled.
/// @param takerAddress Address of taker who filled the order.
/// @param takerAssetFilledAmount Amount of order already filled.
/// @return fillResults Amounts filled and fees paid by maker and taker.
function updateFilledState(
Order memory order,
address takerAddress,
bytes32 orderHash,
uint256 takerAssetFilledAmount,
FillResults memory fillResults)
internal
{
// Update state
filled[orderHash] = safeAdd(filled[orderHash], fillResults.takerAssetFilledAmount);
filled[orderHash] = safeAdd(takerAssetFilledAmount, fillResults.takerAssetFilledAmount);
// Log order
emitFillEvent(order, takerAddress, orderHash, fillResults);
emit Fill(
order.makerAddress,
takerAddress,
order.feeRecipientAddress,
fillResults.makerAssetFilledAmount,
fillResults.takerAssetFilledAmount,
fillResults.makerFeePaid,
fillResults.takerFeePaid,
orderHash,
order.makerAssetData,
order.takerAssetData
);
}
/// @dev Fills the input order.
@@ -279,7 +308,15 @@ contract MixinExchangeCore is
address takerAddress = getCurrentContextAddress();
// Either our context is valid or we revert
validateFillOrderContextOrRevert(order, orderStatus, orderHash, takerAssetFilledAmount, signature, takerAddress, takerAssetFillAmount);
validateFillOrRevert(
order,
orderStatus,
orderHash,
takerAddress,
takerAssetFilledAmount,
takerAssetFillAmount,
signature
);
// Compute proportional fill amounts
uint8 status;
@@ -290,11 +327,16 @@ contract MixinExchangeCore is
}
// Settle order
(fillResults.makerAssetFilledAmount, fillResults.makerFeePaid, fillResults.takerFeePaid) =
settleOrder(order, takerAddress, fillResults.takerAssetFilledAmount);
settleOrder(order, takerAddress, fillResults);
// Update exchange internal state
updateFilledState(order, takerAddress, orderHash, fillResults);
updateFilledState(
order,
takerAddress,
orderHash,
takerAssetFilledAmount,
fillResults
);
return fillResults;
}
@@ -302,7 +344,7 @@ contract MixinExchangeCore is
/// @param order that was cancelled.
/// @param orderStatus Status of order that was cancelled.
/// @param orderHash Hash of order that was cancelled.
function validateCancelOrderContextOrRevert(
function validateCancelOrRevert(
Order memory order,
uint8 orderStatus,
bytes32 orderHash)
@@ -386,50 +428,12 @@ contract MixinExchangeCore is
// Fetch current order status
bytes32 orderHash;
uint8 orderStatus;
uint256 takerAssetFilledAmount;
(orderStatus, orderHash, takerAssetFilledAmount) = getOrderInfo(order);
(orderStatus, orderHash, ) = getOrderInfo(order);
// Validate context
validateCancelOrderContextOrRevert(order, orderStatus, orderHash);
validateCancelOrRevert(order, orderStatus, orderHash);
// Perform cancel
return updateCancelledState(order, orderStatus, orderHash);
}
/// @dev Cancels all orders reated by sender with a salt less than or equal to the specified salt value.
/// @param salt Orders created with a salt less or equal to this value will be cancelled.
function cancelOrdersUpTo(uint256 salt)
external
{
uint256 newMakerEpoch = salt + 1; // makerEpoch is initialized to 0, so to cancelUpTo we need salt + 1
require(
newMakerEpoch > makerEpoch[msg.sender], // epoch must be monotonically increasing
INVALID_NEW_MAKER_EPOCH
);
makerEpoch[msg.sender] = newMakerEpoch;
emit CancelUpTo(msg.sender, newMakerEpoch);
}
/// @dev Logs a Fill event with the given arguments.
/// The sole purpose of this function is to get around the stack variable limit.
function emitFillEvent(
Order memory order,
address takerAddress,
bytes32 orderHash,
FillResults memory fillResults)
internal
{
emit Fill(
order.makerAddress,
takerAddress,
order.feeRecipientAddress,
fillResults.makerAssetFilledAmount,
fillResults.takerAssetFilledAmount,
fillResults.makerFeePaid,
fillResults.takerFeePaid,
orderHash,
order.makerAssetData,
order.takerAssetData
);
}
}

View File

@@ -36,175 +36,7 @@ contract MixinMatchOrders is
MMatchOrders,
MSettlement,
MTransactions
{
/// @dev Validates context for matchOrders. Succeeds or throws.
/// @param leftOrder First order to match.
/// @param rightOrder Second order to match.
function validateMatchOrdersContextOrRevert(
Order memory leftOrder,
Order memory rightOrder)
internal
{
// The leftOrder maker asset must be the same as the rightOrder taker asset.
require(
areBytesEqual(leftOrder.makerAssetData, rightOrder.takerAssetData),
ASSET_MISMATCH_MAKER_TAKER
);
// The leftOrder taker asset must be the same as the rightOrder maker asset.
require(
areBytesEqual(leftOrder.takerAssetData, rightOrder.makerAssetData),
ASSET_MISMATCH_TAKER_MAKER
);
// Make sure there is a positive spread.
// There is a positive spread iff the cost per unit bought (OrderA.MakerAmount/OrderA.TakerAmount) for each order is greater
// than the profit per unit sold of the matched order (OrderB.TakerAmount/OrderB.MakerAmount).
// This is satisfied by the equations below:
// <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 validateMatchedOrderFillResultsOrThrow(MatchedFillResults memory matchedFillResults)
internal
{
// The right order must spend at least as much as we're transferring to the left order.
require(
matchedFillResults.right.makerAssetFilledAmount >=
matchedFillResults.left.takerAssetFilledAmount,
MISCALCULATED_TRANSFER_AMOUNTS
);
// If the amount transferred from the right order is different than what is transferred, it is a rounding error amount.
// Ensure this difference is negligible by dividing the values with each other. The result should equal to ~1.
require(
!isRoundingError(
matchedFillResults.right.makerAssetFilledAmount,
matchedFillResults.left.takerAssetFilledAmount,
1),
ROUNDING_ERROR_TRANSFER_AMOUNTS
);
}
/// @dev Calculates partial value given a numerator and denominator.
/// Throws if there is a rounding error.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to calculate partial of.
/// @return Partial value of target.
function safeGetPartialAmount(
uint256 numerator,
uint256 denominator,
uint256 target)
internal pure
returns (uint256 partialAmount)
{
require(
!isRoundingError(numerator, denominator, target),
ROUNDING_ERROR_ON_PARTIAL_AMOUNT
);
return getPartialAmount(numerator, denominator, target);
}
/// @dev Calculates fill amounts for the matched orders.
/// Each order is filled at their respective price point. However, the calculations are
/// carried out as though the orders are both being filled at the right order's price point.
/// The profit made by the leftOrder order goes to the taker (who matched the two orders).
/// @param leftOrder First order to match.
/// @param rightOrder Second order to match.
/// @param leftOrderStatus Order status of left order.
/// @param rightOrderStatus Order status of right order.
/// @param leftOrderFilledAmount Amount of left order already filled.
/// @param rightOrderFilledAmount Amount of right order already filled.
/// @return status Return status of calculating fill amounts. Returns Status.SUCCESS on success.
/// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
function calculateMatchedFillResults(
Order memory leftOrder,
Order memory rightOrder,
uint8 leftOrderStatus,
uint8 rightOrderStatus,
uint256 leftOrderFilledAmount,
uint256 rightOrderFilledAmount)
internal
returns (
uint8 status,
MatchedFillResults memory matchedFillResults)
{
// We settle orders at the price point defined by the right order (profit goes to the order taker)
// The constraint can be either on the left or on the right.
// The constraint is on the left iff the amount required to fill the left order
// is less than or equal to the amount we can spend from the right order:
// <leftTakerAssetAmountRemaining> <= <rightTakerAssetAmountRemaining> * <rightMakerToTakerRatio>
// <leftTakerAssetAmountRemaining> <= <rightTakerAssetAmountRemaining> * <rightOrder.makerAssetAmount> / <rightOrder.takerAssetAmount>
// <leftTakerAssetAmountRemaining> * <rightOrder.takerAssetAmount> <= <rightTakerAssetAmountRemaining> * <rightOrder.makerAssetAmount>
uint256 rightTakerAssetAmountRemaining = safeSub(rightOrder.takerAssetAmount, rightOrderFilledAmount);
uint256 leftTakerAssetAmountRemaining = safeSub(leftOrder.takerAssetAmount, leftOrderFilledAmount);
uint256 leftOrderAmountToFill = 0;
uint256 rightOrderAmountToFill = 0;
if (
safeMul(leftTakerAssetAmountRemaining, rightOrder.takerAssetAmount) <=
safeMul(rightTakerAssetAmountRemaining, rightOrder.makerAssetAmount)
) {
// Left order is the constraint: maximally fill left
leftOrderAmountToFill = leftTakerAssetAmountRemaining;
// The right order receives an amount proportional to how much was spent.
// TODO: Can we ensure rounding error is in the correct direction?
rightOrderAmountToFill = safeGetPartialAmount(
rightOrder.takerAssetAmount,
rightOrder.makerAssetAmount,
leftOrderAmountToFill);
} else {
// Right order is the constraint: maximally fill right
rightOrderAmountToFill = rightTakerAssetAmountRemaining;
// The left order receives an amount proportional to how much was spent.
// TODO: Can we ensure rounding error is in the correct direction?
leftOrderAmountToFill = safeGetPartialAmount(
rightOrder.makerAssetAmount,
rightOrder.takerAssetAmount,
rightOrderAmountToFill);
}
// Calculate fill results for left order
( status,
matchedFillResults.left
) = calculateFillResults(
leftOrder,
leftOrderStatus,
leftOrderFilledAmount,
leftOrderAmountToFill);
if (status != uint8(Status.SUCCESS)) {
return (status, matchedFillResults);
}
// Calculate fill results for right order
( status,
matchedFillResults.right
) = calculateFillResults(
rightOrder,
rightOrderStatus,
rightOrderFilledAmount,
rightOrderAmountToFill);
if (status != uint8(Status.SUCCESS)) {
return (status, matchedFillResults);
}
// Validate the fill results
validateMatchedOrderFillResultsOrThrow(matchedFillResults);
// Return status & fill results
return (status, matchedFillResults);
}
{
/// @dev Match two complementary orders that have a positive spread.
/// Each order is filled at their respective price point. However, the calculations are
@@ -249,7 +81,7 @@ contract MixinMatchOrders is
address takerAddress = getCurrentContextAddress();
// Either our context is valid or we revert
validateMatchOrdersContextOrRevert(leftOrder, rightOrder);
validateMatchOrThrow(leftOrder, rightOrder);
// Compute proportional fill amounts
uint8 matchedFillAmountsStatus;
@@ -267,22 +99,24 @@ contract MixinMatchOrders is
}
// Validate fill contexts
validateFillOrderContextOrRevert(
validateFillOrRevert(
leftOrder,
leftOrderInfo.orderStatus,
leftOrderInfo.orderHash,
takerAddress,
leftOrderInfo.orderFilledAmount,
leftSignature,
rightOrder.makerAddress,
matchedFillResults.left.takerAssetFilledAmount);
validateFillOrderContextOrRevert(
matchedFillResults.left.takerAssetFilledAmount,
leftSignature
);
validateFillOrRevert(
rightOrder,
rightOrderInfo.orderStatus,
rightOrderInfo.orderHash,
takerAddress,
rightOrderInfo.orderFilledAmount,
rightSignature,
leftOrder.makerAddress,
matchedFillResults.right.takerAssetFilledAmount);
matchedFillResults.right.takerAssetFilledAmount,
rightSignature
);
// Settle matched orders. Succeeds or throws.
settleMatchedOrders(leftOrder, rightOrder, matchedFillResults, takerAddress);
@@ -290,18 +124,169 @@ contract MixinMatchOrders is
// Update exchange state
updateFilledState(
leftOrder,
rightOrder.makerAddress,
takerAddress,
leftOrderInfo.orderHash,
leftOrderInfo.orderFilledAmount,
matchedFillResults.left
);
updateFilledState(
rightOrder,
leftOrder.makerAddress,
takerAddress,
rightOrderInfo.orderHash,
rightOrderInfo.orderFilledAmount,
matchedFillResults.right
);
// Return results
return matchedFillResults;
}
/// @dev Validates context for matchOrders. Succeeds or throws.
/// @param leftOrder First order to match.
/// @param rightOrder Second order to match.
function validateMatchOrThrow(
Order memory leftOrder,
Order memory rightOrder)
internal
{
// The leftOrder maker asset must be the same as the rightOrder taker asset.
require(
areBytesEqual(leftOrder.makerAssetData, rightOrder.takerAssetData),
ASSET_MISMATCH_MAKER_TAKER
);
// The leftOrder taker asset must be the same as the rightOrder maker asset.
require(
areBytesEqual(leftOrder.takerAssetData, rightOrder.makerAssetData),
ASSET_MISMATCH_TAKER_MAKER
);
// Make sure there is a positive spread.
// There is a positive spread iff the cost per unit bought (OrderA.MakerAmount/OrderA.TakerAmount) for each order is greater
// than the profit per unit sold of the matched order (OrderB.TakerAmount/OrderB.MakerAmount).
// This is satisfied by the equations below:
// <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 validateMatchOrThrow(MatchedFillResults memory matchedFillResults)
internal
{
// The right order must spend at least as much as we're transferring to the left order.
require(
matchedFillResults.right.makerAssetFilledAmount >=
matchedFillResults.left.takerAssetFilledAmount,
MISCALCULATED_TRANSFER_AMOUNTS
);
// If the amount transferred from the right order is different than what is transferred, it is a rounding error amount.
// Ensure this difference is negligible by dividing the values with each other. The result should equal to ~1.
require(
!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.
/// @return status Return status of calculating fill amounts. Returns Status.SUCCESS on success.
/// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
function calculateMatchedFillResults(
Order memory leftOrder,
Order memory rightOrder,
uint8 leftOrderStatus,
uint8 rightOrderStatus,
uint256 leftOrderFilledAmount,
uint256 rightOrderFilledAmount)
internal
returns (
uint8 status,
MatchedFillResults memory matchedFillResults
)
{
// We settle orders at the price point defined by the right order (profit goes to the order taker)
// The constraint can be either on the left or on the right.
// The constraint is on the left iff the amount required to fill the left order
// is less than or equal to the amount we can spend from the right order:
// <leftTakerAssetAmountRemaining> <= <rightTakerAssetAmountRemaining> * <rightMakerToTakerRatio>
// <leftTakerAssetAmountRemaining> <= <rightTakerAssetAmountRemaining> * <rightOrder.makerAssetAmount> / <rightOrder.takerAssetAmount>
// <leftTakerAssetAmountRemaining> * <rightOrder.takerAssetAmount> <= <rightTakerAssetAmountRemaining> * <rightOrder.makerAssetAmount>
uint256 rightTakerAssetAmountRemaining = safeSub(rightOrder.takerAssetAmount, rightOrderFilledAmount);
uint256 leftTakerAssetAmountRemaining = safeSub(leftOrder.takerAssetAmount, leftOrderFilledAmount);
uint256 leftOrderAmountToFill;
uint256 rightOrderAmountToFill;
if (
safeMul(leftTakerAssetAmountRemaining, rightOrder.takerAssetAmount) <=
safeMul(rightTakerAssetAmountRemaining, rightOrder.makerAssetAmount)
) {
// Left order is the constraint: maximally fill left
leftOrderAmountToFill = leftTakerAssetAmountRemaining;
// The right order receives an amount proportional to how much was spent.
// TODO: Can we ensure rounding error is in the correct direction?
rightOrderAmountToFill = safeGetPartialAmount(
rightOrder.takerAssetAmount,
rightOrder.makerAssetAmount,
leftOrderAmountToFill
);
} else {
// Right order is the constraint: maximally fill right
rightOrderAmountToFill = rightTakerAssetAmountRemaining;
// The left order receives an amount proportional to how much was spent.
// TODO: Can we ensure rounding error is in the correct direction?
leftOrderAmountToFill = safeGetPartialAmount(
rightOrder.makerAssetAmount,
rightOrder.takerAssetAmount,
rightOrderAmountToFill
);
}
// Calculate fill results for left order
(status, matchedFillResults.left) = calculateFillResults(
leftOrder,
leftOrderStatus,
leftOrderFilledAmount,
leftOrderAmountToFill
);
if (status != uint8(Status.SUCCESS)) {
return (status, matchedFillResults);
}
// Calculate fill results for right order
(status, matchedFillResults.right) = calculateFillResults(
rightOrder,
rightOrderStatus,
rightOrderFilledAmount,
rightOrderAmountToFill
);
if (status != uint8(Status.SUCCESS)) {
return (status, matchedFillResults);
}
// Validate the fill results
validateMatchOrThrow(matchedFillResults);
// Return status & fill results
return (status, matchedFillResults);
}
}

View File

@@ -22,11 +22,13 @@ import "./mixins/MSettlement.sol";
import "./mixins/MAssetProxyDispatcher.sol";
import "./libs/LibOrder.sol";
import "./libs/LibMath.sol";
import "./libs/LibExchangeErrors.sol";
import "./libs/LibFillResults.sol";
import "./mixins/MMatchOrders.sol";
import "./mixins/LibExchangeErrors.sol";
contract MixinSettlement is
LibMath,
LibFillResults,
LibExchangeErrors,
MMatchOrders,
MSettlement,
@@ -58,47 +60,37 @@ contract MixinSettlement is
/// @dev Settles an order by transferring assets between counterparties.
/// @param order Order struct containing order specifications.
/// @param takerAddress Address selling takerAsset and buying makerAsset.
/// @param takerAssetFilledAmount The amount of takerAsset that will be transferred to the order's maker.
/// @return Amount filled by maker and fees paid by maker/taker.
/// @param fillResults Amounts to be filled and fees paid by maker and taker.
function settleOrder(
LibOrder.Order memory order,
address takerAddress,
uint256 takerAssetFilledAmount)
FillResults memory fillResults)
internal
returns (
uint256 makerAssetFilledAmount,
uint256 makerFeePaid,
uint256 takerFeePaid
)
{
makerAssetFilledAmount = getPartialAmount(takerAssetFilledAmount, order.takerAssetAmount, order.makerAssetAmount);
dispatchTransferFrom(
order.makerAssetData,
order.makerAddress,
takerAddress,
makerAssetFilledAmount
fillResults.makerAssetFilledAmount
);
dispatchTransferFrom(
order.takerAssetData,
takerAddress,
order.makerAddress,
takerAssetFilledAmount
fillResults.takerAssetFilledAmount
);
makerFeePaid = getPartialAmount(takerAssetFilledAmount, order.takerAssetAmount, order.makerFee);
dispatchTransferFrom(
ZRX_PROXY_DATA,
order.makerAddress,
order.feeRecipientAddress,
makerFeePaid
fillResults.makerFeePaid
);
takerFeePaid = getPartialAmount(takerAssetFilledAmount, order.takerAssetAmount, order.takerFee);
dispatchTransferFrom(
ZRX_PROXY_DATA,
takerAddress,
order.feeRecipientAddress,
takerFeePaid
fillResults.takerFeePaid
);
return (makerAssetFilledAmount, makerFeePaid, takerFeePaid);
}
/// @dev Settles matched order by transferring appropriate funds between order makers, taker, and fee recipient.

View File

@@ -55,5 +55,4 @@ contract LibExchangeErrors {
string constant NEGATIVE_SPREAD = "Matched orders must have a positive spread.";
string constant MISCALCULATED_TRANSFER_AMOUNTS = "A miscalculation occurred: the left maker would receive more than the right maker would spend.";
string constant ROUNDING_ERROR_TRANSFER_AMOUNTS = "A rounding error occurred when calculating transfer amounts for matched orders.";
string constant ROUNDING_ERROR_ON_PARTIAL_AMOUNT = "A rounding error occurred when calculating partial transfer amounts.";
}

View File

@@ -23,6 +23,7 @@ import "../../../utils/SafeMath/SafeMath.sol";
contract LibMath is
SafeMath
{
string constant ROUNDING_ERROR_ON_PARTIAL_AMOUNT = "A rounding error occurred when calculating partial transfer amounts.";
/// @dev Calculates partial value given a numerator and denominator.
/// @param numerator Numerator.
@@ -44,6 +45,26 @@ contract LibMath is
return partialAmount;
}
/// @dev Calculates partial value given a numerator and denominator.
/// Throws if there is a rounding error.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to calculate partial of.
/// @return Partial value of target.
function safeGetPartialAmount(
uint256 numerator,
uint256 denominator,
uint256 target)
internal pure
returns (uint256 partialAmount)
{
require(
!isRoundingError(numerator, denominator, target),
ROUNDING_ERROR_ON_PARTIAL_AMOUNT
);
return getPartialAmount(numerator, denominator, target);
}
/// @dev Checks if rounding error > 0.1%.
/// @param numerator Numerator.
/// @param denominator Denominator.

View File

@@ -56,14 +56,10 @@ contract MExchangeCore is
uint256 makerEpoch
);
/// @dev Logs a Fill event with the given arguments.
/// The sole purpose of this function is to get around the stack variable limit.
function emitFillEvent(
LibOrder.Order memory order,
address takerAddress,
bytes32 orderHash,
LibFillResults.FillResults memory fillResults)
internal;
/// @dev Cancels all orders reated by sender with a salt less than or equal to the specified salt value.
/// @param salt Orders created with a salt less or equal to this value will be cancelled.
function cancelOrdersUpTo(uint256 salt)
external;
/// @dev Gets information about an order: status, hash, and amount filled.
/// @param order Order to gather information on.
@@ -76,24 +72,25 @@ contract MExchangeCore is
returns (
uint8 orderStatus,
bytes32 orderHash,
uint256 takerAssetFilledAmount);
uint256 takerAssetFilledAmount
);
/// @dev Validates context for fillOrder. Succeeds or throws.
/// @param order to be filled.
/// @param orderStatus Status of order to be filled.
/// @param orderHash Hash of order to be filled.
/// @param takerAssetFilledAmount Amount of order already filled.
/// @param signature Proof that the orders was created by its maker.
/// @param takerAddress Address of order taker.
/// @param takerAssetFilledAmount Amount of order already filled.
/// @param takerAssetFillAmount Desired amount of order to fill by taker.
function validateFillOrderContextOrRevert(
/// @param signature Proof that the orders was created by its maker.
function validateFillOrRevert(
LibOrder.Order memory order,
uint8 orderStatus,
bytes32 orderHash,
uint256 takerAssetFilledAmount,
bytes memory signature,
address takerAddress,
uint256 takerAssetFillAmount)
uint256 takerAssetFilledAmount,
uint256 takerAssetFillAmount,
bytes memory signature)
internal;
/// @dev Calculates amounts filled and fees paid by maker and taker.
@@ -112,16 +109,19 @@ contract MExchangeCore is
pure
returns (
uint8 status,
LibFillResults.FillResults memory fillResults);
LibFillResults.FillResults memory fillResults
);
/// @dev Updates state with results of a fill order.
/// @param order that was filled.
/// @param takerAddress Address of taker who filled the order.
/// @param takerAssetFilledAmount Amount of order already filled.
/// @return fillResults Amounts filled and fees paid by maker and taker.
function updateFilledState(
LibOrder.Order memory order,
address takerAddress,
bytes32 orderHash,
uint256 takerAssetFilledAmount,
LibFillResults.FillResults memory fillResults)
internal;
@@ -141,7 +141,7 @@ contract MExchangeCore is
/// @param order that was cancelled.
/// @param orderStatus Status of order that was cancelled.
/// @param orderHash Hash of order that was cancelled.
function validateCancelOrderContextOrRevert(
function validateCancelOrRevert(
LibOrder.Order memory order,
uint8 orderStatus,
bytes32 orderHash)
@@ -162,15 +162,12 @@ contract MExchangeCore is
returns (bool stateUpdated);
/// @dev After calling, the order can not be filled anymore.
/// @param order Order struct containing order specifications.
/// Throws if order is invalid or sender does not have permission to cancel.
/// @param order Order to cancel. Order must be Status.FILLABLE.
/// @return True if the order state changed to cancelled.
/// False if the transaction was already cancelled or expired.
/// False if the order was order was in a valid, but
/// unfillable state (see LibStatus.STATUS for order states)
function cancelOrder(LibOrder.Order memory order)
public
returns (bool);
/// @dev Cancels all orders reated by sender with a salt less than or equal to the specified salt value.
/// @param salt Orders created with a salt less or equal to this value will be cancelled.
function cancelOrdersUpTo(uint256 salt)
external;
}

View File

@@ -37,32 +37,36 @@ contract MMatchOrders {
uint256 orderFilledAmount;
}
/// @dev Match two complementary orders that have a positive spread.
/// Each order is filled at their respective price point. However, the calculations are
/// carried out as though the orders are both being filled at the right order's price point.
/// The profit made by the left order goes to the taker (who matched the two orders).
/// @param leftOrder First order to match.
/// @param rightOrder Second order to match.
/// @param leftSignature Proof that order was created by the left maker.
/// @param rightSignature Proof that order was created by the right maker.
/// @return matchedFillResults Amounts filled and fees paid by maker and taker of matched orders.
function matchOrders(
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder,
bytes leftSignature,
bytes rightSignature)
public
returns (MatchedFillResults memory matchedFillResults);
/// @dev Validates context for matchOrders. Succeeds or throws.
/// @param leftOrder First order to match.
/// @param rightOrder Second order to match.
function validateMatchOrdersContextOrRevert(
function validateMatchOrThrow(
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder)
internal;
/// @dev Validates matched fill results. Succeeds or throws.
/// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
function validateMatchedOrderFillResultsOrThrow(MatchedFillResults memory matchedFillResults)
function validateMatchOrThrow(MatchedFillResults memory matchedFillResults)
internal;
/// @dev Calculates partial value given a numerator and denominator.
/// Throws if there is a rounding error.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to calculate partial of.
/// @return Partial value of target.
function safeGetPartialAmount(
uint256 numerator,
uint256 denominator,
uint256 target)
internal pure
returns (uint256 partialAmount);
/// @dev Calculates fill amounts for the matched orders.
/// Each order is filled at their respective price point. However, the calculations are
/// carried out as though the orders are both being filled at the right order's price point.
@@ -85,22 +89,6 @@ contract MMatchOrders {
internal
returns (
uint8 status,
MatchedFillResults memory matchedFillResults);
/// @dev Match two complementary orders that have a positive spread.
/// Each order is filled at their respective price point. However, the calculations are
/// carried out as though the orders are both being filled at the right order's price point.
/// The profit made by the left order goes to the taker (who matched the two orders).
/// @param leftOrder First order to match.
/// @param rightOrder Second order to match.
/// @param leftSignature Proof that order was created by the left maker.
/// @param rightSignature Proof that order was created by the right maker.
/// @return matchedFillResults Amounts filled and fees paid by maker and taker of matched orders.
function matchOrders(
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder,
bytes leftSignature,
bytes rightSignature)
public
returns (MatchedFillResults memory matchedFillResults);
MatchedFillResults memory matchedFillResults
);
}

View File

@@ -20,24 +20,19 @@ pragma solidity ^0.4.23;
import "../libs/LibOrder.sol";
import "./MMatchOrders.sol";
import "../libs/LibFillResults.sol";
contract MSettlement {
/// @dev Settles an order by transfering assets between counterparties.
/// @dev Settles an order by transferring assets between counterparties.
/// @param order Order struct containing order specifications.
/// @param takerAddress Address selling takerAsset and buying makerAsset.
/// @param takerAssetFilledAmount The amount of takerAsset that will be transfered to the order's maker.
/// @return Amount filled by maker and fees paid by maker/taker.
/// @param fillResults Amounts to be filled and fees paid by maker and taker.
function settleOrder(
LibOrder.Order memory order,
address takerAddress,
uint256 takerAssetFilledAmount)
internal
returns (
uint256 makerAssetFilledAmount,
uint256 makerFeePaid,
uint256 takerFeePaid
);
LibFillResults.FillResults memory fillResults)
internal;
/// @dev Settles matched order by transferring appropriate funds between order makers, taker, and fee recipient.
/// @param leftOrder First matched order.

View File

@@ -225,12 +225,6 @@ export class ExchangeWrapper {
const filledAmount = new BigNumber(await this._exchange.filled.callAsync(orderHashHex));
return filledAmount;
}
private async _getTxWithDecodedExchangeLogsAsync(txHash: string) {
const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash);
tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address);
tx.logs = _.map(tx.logs, log => this._logDecoder.decodeLogOrThrow(log));
return tx;
}
public async getOrderInfoAsync(
signedOrder: SignedOrder,
): Promise<[number /* orderStatus */, string /* orderHash */, BigNumber /* orderTakerAssetAmountFilled */]> {
@@ -251,6 +245,14 @@ export class ExchangeWrapper {
{ from },
);
const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address);
tx.logs = _.map(tx.logs, log => this._logDecoder.decodeLogOrThrow(log));
return tx;
}
private async _getTxWithDecodedExchangeLogsAsync(txHash: string) {
const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash);
tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address);
tx.logs = _.map(tx.logs, log => this._logDecoder.decodeLogOrThrow(log));
return tx;
}
}