Atomic Order Matching - Smart Contracts.

This commit is contained in:
Greg Hysen
2018-05-10 14:22:29 -07:00
parent c70540e7f4
commit a4c821eb60
13 changed files with 1022 additions and 179 deletions

View File

@@ -25,24 +25,28 @@ import "./MixinSettlement.sol";
import "./MixinWrapperFunctions.sol";
import "./MixinAssetProxyDispatcher.sol";
import "./MixinTransactions.sol";
import "./MixinMatchOrders.sol";
contract Exchange is
MixinWrapperFunctions,
MixinExchangeCore,
MixinMatchOrders,
MixinSettlement,
MixinSignatureValidator,
MixinTransactions,
MixinAssetProxyDispatcher
MixinAssetProxyDispatcher,
MixinWrapperFunctions
{
string constant public VERSION = "2.0.1-alpha";
constructor (bytes memory _zrxProxyData)
public
MixinExchangeCore()
MixinSignatureValidator()
MixinMatchOrders()
MixinSettlement(_zrxProxyData)
MixinWrapperFunctions()
MixinAssetProxyDispatcher()
MixinSignatureValidator()
MixinTransactions()
MixinAssetProxyDispatcher()
MixinWrapperFunctions()
{}
}

View File

@@ -22,6 +22,7 @@ pragma experimental ABIEncoderV2;
import "./libs/LibFillResults.sol";
import "./libs/LibOrder.sol";
import "./libs/LibMath.sol";
import "./libs/LibStatus.sol";
import "./libs/LibExchangeErrors.sol";
import "./mixins/MExchangeCore.sol";
import "./mixins/MSettlement.sol";
@@ -29,9 +30,11 @@ import "./mixins/MSignatureValidator.sol";
import "./mixins/MTransactions.sol";
contract MixinExchangeCore is
SafeMath,
LibMath,
LibStatus,
LibOrder,
LibFillResults,
LibMath,
LibExchangeErrors,
MExchangeCore,
MSettlement,
@@ -48,6 +51,347 @@ 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 Gets information about an order.
/// @param order Order to gather information on.
/// @return status Status of order. Statuses are defined in the LibStatus.Status struct.
/// @return orderHash Keccak-256 EIP712 hash of the order.
/// @return takerAssetFilledAmount Amount of order that has been filled.
function getOrderInfo(Order memory order)
public
view
returns (
uint8 orderStatus,
bytes32 orderHash,
uint256 takerAssetFilledAmount)
{
// Compute the order hash and fetch filled amount
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
// edge cases in the supporting infrastructure because they have
// an 'infinite' price when computed by a simple division.
if (order.makerAssetAmount == 0) {
orderStatus = uint8(Status.ORDER_INVALID_MAKER_ASSET_AMOUNT);
return (orderStatus, orderHash, takerAssetFilledAmount);
}
// Validate order expiration
if (block.timestamp >= order.expirationTimeSeconds) {
orderStatus = uint8(Status.ORDER_EXPIRED);
return (orderStatus, orderHash, takerAssetFilledAmount);
}
// Validate order availability
if (takerAssetFilledAmount >= order.takerAssetAmount) {
orderStatus = uint8(Status.ORDER_FULLY_FILLED);
return (orderStatus, orderHash, takerAssetFilledAmount);
}
// Check if order has been cancelled
if (cancelled[orderHash]) {
orderStatus = uint8(Status.ORDER_CANCELLED);
return (orderStatus, orderHash, takerAssetFilledAmount);
}
// Validate order is not cancelled
if (makerEpoch[order.makerAddress] > order.salt) {
orderStatus = uint8(Status.ORDER_CANCELLED);
return (orderStatus, orderHash, takerAssetFilledAmount);
}
// Order is Fillable
orderStatus = uint8(Status.ORDER_FILLABLE);
return (orderStatus, orderHash, takerAssetFilledAmount);
}
/// @dev Validates context for fillOrder. Succeeds or throws.
/// @param order to be filled.
/// @param orderStatus Status of order to be filled.
/// @param orderHash Hash of order to be filled.
/// @param takerAssetFilledAmount Amount of order already filled.
/// @param signature Proof that the orders was created by its maker.
/// @param takerAddress Address of order taker.
/// @param takerAssetFillAmount Desired amount of order to fill by taker.
function validateFillOrderContextOrRevert(
Order memory order,
uint8 orderStatus,
bytes32 orderHash,
uint256 takerAssetFilledAmount,
bytes memory signature,
address takerAddress,
uint256 takerAssetFillAmount)
internal
{
// Ensure order is valid
require(
orderStatus != uint8(Status.ORDER_INVALID_MAKER_ASSET_AMOUNT),
INVALID_ORDER_MAKER_ASSET_AMOUNT
);
require(
orderStatus != uint8(Status.ORDER_INVALID_TAKER_ASSET_AMOUNT),
INVALID_ORDER_TAKER_ASSET_AMOUNT
);
// Validate Maker signature (check only if first time seen)
if (takerAssetFilledAmount == 0) {
require(
isValidSignature(orderHash, order.makerAddress, signature),
SIGNATURE_VALIDATION_FAILED
);
}
// Validate sender is allowed to fill this order
if (order.senderAddress != address(0)) {
require(
order.senderAddress == msg.sender,
INVALID_SENDER
);
}
// Validate taker is allowed to fill this order
if (order.takerAddress != address(0)) {
require(
order.takerAddress == takerAddress,
INVALID_CONTEXT
);
}
require(
takerAssetFillAmount > 0,
GT_ZERO_AMOUNT_REQUIRED
);
}
/// @dev Calculates amounts filled and fees paid by maker and taker.
/// @param order to be filled.
/// @param orderStatus Status of order to be filled.
/// @param takerAssetFilledAmount Amount of order already filled.
/// @param takerAssetFillAmount Desired amount of order to fill by taker.
/// @return status Return status of calculating fill amounts. Returns Status.SUCCESS on success.
/// @return fillResults Amounts filled and fees paid by maker and taker.
function calculateFillResults(
Order memory order,
uint8 orderStatus,
uint256 takerAssetFilledAmount,
uint256 takerAssetFillAmount)
public
pure
returns (
uint8 status,
FillResults memory fillResults)
{
// Fill Amount must be greater than 0
if (takerAssetFillAmount <= 0) {
status = uint8(Status.TAKER_ASSET_FILL_AMOUNT_TOO_LOW);
return;
}
// Ensure the order is fillable
if (orderStatus != uint8(Status.ORDER_FILLABLE)) {
status = uint8(orderStatus);
return;
}
// Compute takerAssetFilledAmount
uint256 remainingtakerAssetAmount = safeSub(order.takerAssetAmount, takerAssetFilledAmount);
fillResults.takerAssetFilledAmount = min256(takerAssetFillAmount, remainingtakerAssetAmount);
// Validate fill order rounding
if (isRoundingError(
fillResults.takerAssetFilledAmount,
order.takerAssetAmount,
order.makerAssetAmount))
{
status = uint8(Status.ROUNDING_ERROR_TOO_LARGE);
fillResults.takerAssetFilledAmount = 0;
return;
}
// Compute proportional transfer amounts
// TODO: All three are multiplied by the same fraction. This can
// potentially be optimized.
fillResults.makerAssetFilledAmount = getPartialAmount(
fillResults.takerAssetFilledAmount,
order.takerAssetAmount,
order.makerAssetAmount);
fillResults.makerFeePaid = getPartialAmount(
fillResults.takerAssetFilledAmount,
order.takerAssetAmount,
order.makerFee);
fillResults.takerFeePaid = getPartialAmount(
fillResults.takerAssetFilledAmount,
order.takerAssetAmount,
order.takerFee);
status = uint8(Status.SUCCESS);
return;
}
/// @dev Updates state with results of a fill order.
/// @param order that was filled.
/// @param takerAddress Address of taker who filled the order.
/// @return fillResults Amounts filled and fees paid by maker and taker.
function updateFilledState(
Order memory order,
address takerAddress,
bytes32 orderHash,
FillResults memory fillResults)
internal
{
// Update state
filled[orderHash] = safeAdd(filled[orderHash], fillResults.takerAssetFilledAmount);
// Log order
emitFillEvent(order, takerAddress, orderHash, fillResults);
}
/// @dev Fills the input order.
/// @param order Order struct containing order specifications.
/// @param takerAssetFillAmount Desired amount of takerToken to sell.
/// @param signature Proof that order has been created by maker.
/// @return Amounts filled and fees paid by maker and taker.
function fillOrder(
Order memory order,
uint256 takerAssetFillAmount,
bytes memory signature)
public
returns (FillResults memory fillResults)
{
// Fetch current order status
bytes32 orderHash;
uint8 orderStatus;
uint256 takerAssetFilledAmount;
(orderStatus, orderHash, takerAssetFilledAmount) = getOrderInfo(order);
// Fetch taker address
address takerAddress = getCurrentContextAddress();
// Either our context is valid or we revert
validateFillOrderContextOrRevert(order, orderStatus, orderHash, takerAssetFilledAmount, signature, takerAddress, takerAssetFillAmount);
// Compute proportional fill amounts
uint8 status;
(status, fillResults) = calculateFillResults(order, orderStatus, takerAssetFilledAmount, takerAssetFillAmount);
if (status != uint8(Status.SUCCESS)) {
emit ExchangeStatus(uint8(status), orderHash);
return fillResults;
}
// Settle order
(fillResults.makerAssetFilledAmount, fillResults.makerFeePaid, fillResults.takerFeePaid) =
settleOrder(order, takerAddress, fillResults.takerAssetFilledAmount);
// Update exchange internal state
updateFilledState(order, takerAddress, orderHash, fillResults);
return fillResults;
}
/// @dev Validates context for cancelOrder. Succeeds or throws.
/// @param order that was cancelled.
/// @param orderStatus Status of order that was cancelled.
/// @param orderHash Hash of order that was cancelled.
function validateCancelOrderContextOrRevert(
Order memory order,
uint8 orderStatus,
bytes32 orderHash)
internal
{
// Ensure order is valid
require(
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 transaction signed by maker
address makerAddress = getCurrentContextAddress();
require(
order.makerAddress == makerAddress,
INVALID_CONTEXT
);
// Validate sender is allowed to cancel this order
if (order.senderAddress != address(0)) {
require(
order.senderAddress == msg.sender,
INVALID_SENDER
);
}
}
/// @dev Updates state with results of cancelling an order.
/// State is only updated if the order is currently fillable.
/// Otherwise, updating state would have no effect.
/// @param order that was cancelled.
/// @param orderStatus Status of order that was cancelled.
/// @param orderHash Hash of order that was cancelled.
/// @return stateUpdated Returns true only if state was updated.
function updateCancelledState(
Order memory order,
uint8 orderStatus,
bytes32 orderHash)
internal
returns (bool stateUpdated)
{
// Ensure order is fillable (otherwise cancelling does nothing)
if (orderStatus != uint8(Status.ORDER_FILLABLE)) {
emit ExchangeStatus(uint8(orderStatus), orderHash);
stateUpdated = false;
return stateUpdated;
}
// Perform cancel
cancelled[orderHash] = true;
stateUpdated = true;
// Log cancel
emit Cancel(
order.makerAddress,
order.feeRecipientAddress,
orderHash,
order.makerAssetData,
order.takerAssetData
);
return stateUpdated;
}
/// @dev After calling, the order can not be filled anymore.
/// @param order Order struct containing order specifications.
/// @return True if the order state changed to cancelled.
/// False if the transaction was already cancelled or expired.
function cancelOrder(Order memory order)
public
returns (bool)
{
// Fetch current order status
bytes32 orderHash;
uint8 orderStatus;
uint256 takerAssetFilledAmount;
(orderStatus, orderHash, takerAssetFilledAmount) = getOrderInfo(order);
// Validate context
validateCancelOrderContextOrRevert(order, orderStatus, orderHash);
// Perform cancel
return updateCancelledState(order, orderStatus, orderHash);
}
/// @dev Cancels all orders reated by sender with a salt less than or equal to the specified salt value.
/// @param salt Orders created with a salt less or equal to this value will be cancelled.
function cancelOrdersUpTo(uint256 salt)
@@ -62,162 +406,6 @@ contract MixinExchangeCore is
emit CancelUpTo(msg.sender, newMakerEpoch);
}
/// @dev Fills the input order.
/// @param order Order struct containing order specifications.
/// @param takerAssetFillAmount Desired amount of takerAsset to sell.
/// @param signature Proof that order has been created by maker.
/// @return Amounts filled and fees paid by maker and taker.
function fillOrder(
Order memory order,
uint256 takerAssetFillAmount,
bytes memory signature)
public
returns (FillResults memory fillResults)
{
// Compute the order hash
bytes32 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;
}
// Check if order has been cancelled by orderHash
if (cancelled[orderHash]) {
emit ExchangeError(uint8(Errors.ORDER_CANCELLED), orderHash);
return fillResults;
}
// 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
);
require(
isValidSignature(orderHash, order.makerAddress, signature),
SIGNATURE_VALIDATION_FAILED
);
}
// Validate sender is allowed to fill this order
if (order.senderAddress != address(0)) {
require(
order.senderAddress == msg.sender,
INVALID_SENDER
);
}
// Validate taker is allowed to fill this order
address takerAddress = getCurrentContextAddress();
if (order.takerAddress != address(0)) {
require(
order.takerAddress == takerAddress,
INVALID_CONTEXT
);
}
require(
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;
}
// Update state
filled[orderHash] = safeAdd(filled[orderHash], fillResults.takerAssetFilledAmount);
// Settle order
(fillResults.makerAssetFilledAmount, fillResults.makerFeePaid, fillResults.takerFeePaid) =
settleOrder(order, takerAddress, fillResults.takerAssetFilledAmount);
// Log order
emitFillEvent(order, takerAddress, orderHash, fillResults);
return fillResults;
}
/// @dev After calling, the order can not be filled anymore.
/// @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)
{
// Compute the order hash
bytes32 orderHash = getOrderHash(order);
// Validate the order
require(
order.makerAssetAmount > 0,
GT_ZERO_AMOUNT_REQUIRED
);
require(
order.takerAssetAmount > 0,
GT_ZERO_AMOUNT_REQUIRED
);
// 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;
}
if (cancelled[orderHash]) {
emit ExchangeError(uint8(Errors.ORDER_CANCELLED), orderHash);
return false;
}
cancelled[orderHash] = true;
emit Cancel(
order.makerAddress,
order.feeRecipientAddress,
orderHash,
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(

View File

@@ -0,0 +1,285 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.4.21;
pragma experimental ABIEncoderV2;
import "./mixins/MExchangeCore.sol";
import "./mixins/MMatchOrders.sol";
import "./mixins/MSettlement.sol";
import "./mixins/MTransactions.sol";
import "../../utils/SafeMath/SafeMath.sol";
import "./libs/LibMath.sol";
import "./libs/LibOrder.sol";
import "./libs/LibStatus.sol";
import "../../utils/LibBytes/LibBytes.sol";
contract MixinMatchOrders is
SafeMath,
LibBytes,
LibMath,
LibStatus,
LibOrder,
MExchangeCore,
MMatchOrders,
MSettlement,
MTransactions
{
/// @dev Validates context for matchOrders. Succeeds or throws.
/// @param leftOrder First order to match.
/// @param rightOrder Second order to match.
function validateMatchOrdersContextOrRevert(
Order memory leftOrder,
Order memory rightOrder)
internal
{
// The leftOrder maker asset must be the same as the rightOrder taker asset.
require(areBytesEqual(leftOrder.makerAssetData, rightOrder.takerAssetData));
// The leftOrder taker asset must be the same as the rightOrder maker asset.
require(areBytesEqual(leftOrder.takerAssetData, rightOrder.makerAssetData));
// Make sure there is a positive spread.
// There is a positive spread iff the cost per unit bought (OrderA.MakerAmount/OrderA.TakerAmount) for each order is greater
// than the profit per unit sold of the matched order (OrderB.TakerAmount/OrderB.MakerAmount).
// This is satisfied by the equations below:
// <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)
);
}
/// @dev Validates matched fill results. Succeeds or throws.
/// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
function validateMatchedOrderFillResultsOrThrow(MatchedFillResults memory matchedFillResults)
internal
{
// The right order must spend at least as much as we're transferring to the left order's maker.
// If the amount transferred from the right order is greater than what is transferred, it is a rounding error amount.
// Ensure this difference is negligible by dividing the values with each other. The result should equal to ~1.
require(matchedFillResults.right.makerAssetFilledAmount >= matchedFillResults.left.takerAssetFilledAmount);
require(!isRoundingError(matchedFillResults.right.makerAssetFilledAmount, matchedFillResults.left.takerAssetFilledAmount, 1));
}
/// @dev Calculates partial value given a numerator and denominator.
/// Throws if there is a rounding error.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to calculate partial of.
/// @return Partial value of target.
function safeGetPartialAmount(
uint256 numerator,
uint256 denominator,
uint256 target)
internal pure
returns (uint256 partialAmount)
{
require(!isRoundingError(numerator, denominator, target));
return getPartialAmount(numerator, denominator, target);
}
/// @dev Calculates fill amounts for the matched orders.
/// Each order is filled at their respective price point. However, the calculations are
/// carried out as though the orders are both being filled at the right order's price point.
/// The profit made by the leftOrder order goes to the taker (who matched the two orders).
/// @param leftOrder First order to match.
/// @param rightOrder Second order to match.
/// @param leftOrderStatus Order status of left order.
/// @param rightOrderStatus Order status of right order.
/// @param leftOrderFilledAmount Amount of left order already filled.
/// @param rightOrderFilledAmount Amount of right order already filled.
/// @return status Return status of calculating fill amounts. Returns Status.SUCCESS on success.
/// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
function calculateMatchedFillResults(
Order memory leftOrder,
Order memory rightOrder,
uint8 leftOrderStatus,
uint8 rightOrderStatus,
uint256 leftOrderFilledAmount,
uint256 rightOrderFilledAmount)
internal
returns (
uint8 status,
MatchedFillResults memory matchedFillResults)
{
// We settle orders at the price point defined by the right order (profit goes to the order taker)
// The constraint can be either on the left or on the right.
// The constraint is on the left iff the amount required to fill the left order
// is less than or equal to the amount we can spend from the right order:
// <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: Ensure rounding error is in the correct direction.
rightOrderAmountToFill = safeGetPartialAmount(
rightOrder.takerAssetAmount,
rightOrder.makerAssetAmount,
leftOrderAmountToFill);
} else {
// Right order is the constraint: maximally fill right
rightOrderAmountToFill = rightTakerAssetAmountRemaining;
// The left order receives an amount proportional to how much was spent.
// TODO: Ensure rounding error is in the correct direction.
leftOrderAmountToFill = safeGetPartialAmount(
rightOrder.makerAssetAmount,
rightOrder.takerAssetAmount,
rightOrderAmountToFill);
}
// Calculate fill results for left order
( status,
matchedFillResults.left
) = calculateFillResults(
leftOrder,
leftOrderStatus,
leftOrderFilledAmount,
leftOrderAmountToFill);
if (status != uint8(Status.SUCCESS)) {
return (status, matchedFillResults);
}
// Calculate fill results for right order
( status,
matchedFillResults.right
) = calculateFillResults(
rightOrder,
rightOrderStatus,
rightOrderFilledAmount,
rightOrderAmountToFill);
if (status != uint8(Status.SUCCESS)) {
return (status, matchedFillResults);
}
// Validate the fill results
validateMatchedOrderFillResultsOrThrow(matchedFillResults);
// Return status & fill results
return (status, matchedFillResults);
}
/// @dev Match two complementary orders that have a positive spread.
/// Each order is filled at their respective price point. However, the calculations are
/// carried out as though the orders are both being filled at the right order's price point.
/// The profit made by the left order goes to the taker (who matched the two orders).
/// @param leftOrder First order to match.
/// @param rightOrder Second order to match.
/// @param leftSignature Proof that order was created by the left maker.
/// @param rightSignature Proof that order was created by the right maker.
/// @return matchedFillResults Amounts filled and fees paid by maker and taker of matched orders.
function matchOrders(
Order memory leftOrder,
Order memory rightOrder,
bytes leftSignature,
bytes rightSignature)
public
returns (MatchedFillResults memory matchedFillResults)
{
// Get left status
OrderInfo memory leftOrderInfo;
( leftOrderInfo.orderStatus,
leftOrderInfo.orderHash,
leftOrderInfo.orderFilledAmount
) = getOrderInfo(leftOrder);
if (leftOrderInfo.orderStatus != uint8(Status.ORDER_FILLABLE)) {
emit ExchangeStatus(uint8(leftOrderInfo.orderStatus), leftOrderInfo.orderHash);
return matchedFillResults;
}
// Get right status
OrderInfo memory rightOrderInfo;
( rightOrderInfo.orderStatus,
rightOrderInfo.orderHash,
rightOrderInfo.orderFilledAmount
) = getOrderInfo(rightOrder);
if (rightOrderInfo.orderStatus != uint8(Status.ORDER_FILLABLE)) {
emit ExchangeStatus(uint8(rightOrderInfo.orderStatus), rightOrderInfo.orderHash);
return matchedFillResults;
}
// Fetch taker address
address takerAddress = getCurrentContextAddress();
// Either our context is valid or we revert
validateMatchOrdersContextOrRevert(leftOrder, rightOrder);
// Compute proportional fill amounts
uint8 matchedFillAmountsStatus;
( matchedFillAmountsStatus,
matchedFillResults
) = calculateMatchedFillResults(
leftOrder,
rightOrder,
leftOrderInfo.orderStatus,
rightOrderInfo.orderStatus,
leftOrderInfo.orderFilledAmount,
rightOrderInfo.orderFilledAmount);
if (matchedFillAmountsStatus != uint8(Status.SUCCESS)) {
return matchedFillResults;
}
// Validate fill contexts
validateFillOrderContextOrRevert(
leftOrder,
leftOrderInfo.orderStatus,
leftOrderInfo.orderHash,
leftOrderInfo.orderFilledAmount,
leftSignature,
rightOrder.makerAddress,
matchedFillResults.left.takerAssetFilledAmount);
validateFillOrderContextOrRevert(
rightOrder,
rightOrderInfo.orderStatus,
rightOrderInfo.orderHash,
rightOrderInfo.orderFilledAmount,
rightSignature,
leftOrder.makerAddress,
matchedFillResults.right.takerAssetFilledAmount);
// Settle matched orders. Succeeds or throws.
settleMatchedOrders(leftOrder, rightOrder, matchedFillResults, takerAddress);
// Update exchange state
updateFilledState(
leftOrder,
rightOrder.makerAddress,
leftOrderInfo.orderHash,
matchedFillResults.left
);
updateFilledState(
rightOrder,
leftOrder.makerAddress,
rightOrderInfo.orderHash,
matchedFillResults.right
);
// Return results
return matchedFillResults;
}
}

View File

@@ -22,9 +22,11 @@ import "./mixins/MSettlement.sol";
import "./mixins/MAssetProxyDispatcher.sol";
import "./libs/LibOrder.sol";
import "./libs/LibMath.sol";
import "./mixins/MMatchOrders.sol";
contract MixinSettlement is
LibMath,
MMatchOrders,
MSettlement,
MAssetProxyDispatcher
{
@@ -96,4 +98,87 @@ contract MixinSettlement is
);
return (makerAssetFilledAmount, makerFeePaid, takerFeePaid);
}
/// @dev Settles matched order by transferring appropriate funds between order makers, taker, and fee recipient.
/// @param leftOrder First matched order.
/// @param rightOrder Second matched order.
/// @param matchedFillResults Struct holding amounts to transfer between makers, taker, and fee recipients.
/// @param takerAddress Address that matched the orders. The taker receives the spread between orders as profit.
function settleMatchedOrders(
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder,
MatchedFillResults memory matchedFillResults,
address takerAddress)
internal
{
// Optimized for:
// * leftOrder.feeRecipient =?= rightOrder.feeRecipient
// Not optimized for:
// * {left, right}.{MakerAsset, TakerAsset} == ZRX
// * {left, right}.maker, takerAddress == {left, right}.feeRecipient
// leftOrder.MakerAsset == rightOrder.TakerAsset
// Taker should be left with a positive balance (the spread)
dispatchTransferFrom(
leftOrder.makerAssetData,
leftOrder.makerAddress,
takerAddress,
matchedFillResults.left.makerAssetFilledAmount);
dispatchTransferFrom(
leftOrder.makerAssetData,
takerAddress,
rightOrder.makerAddress,
matchedFillResults.right.takerAssetFilledAmount);
// rightOrder.MakerAsset == leftOrder.TakerAsset
// leftOrder.takerAssetFilledAmount ~ rightOrder.makerAssetFilledAmount
// The change goes to right, not to taker.
assert(matchedFillResults.right.makerAssetFilledAmount >= matchedFillResults.left.takerAssetFilledAmount);
dispatchTransferFrom(
rightOrder.makerAssetData,
rightOrder.makerAddress,
leftOrder.makerAddress,
matchedFillResults.right.makerAssetFilledAmount);
// Maker fees
dispatchTransferFrom(
ZRX_PROXY_DATA,
leftOrder.makerAddress,
leftOrder.feeRecipientAddress,
matchedFillResults.left.makerFeePaid);
dispatchTransferFrom(
ZRX_PROXY_DATA,
rightOrder.makerAddress,
rightOrder.feeRecipientAddress,
matchedFillResults.right.makerFeePaid);
// Taker fees
// If we assume distinct(left, right, takerAddress) and
// distinct(MakerAsset, TakerAsset, zrx) then the only remaining
// opportunity for optimization is when both feeRecipientAddress' are
// the same.
if (leftOrder.feeRecipientAddress == rightOrder.feeRecipientAddress) {
dispatchTransferFrom(
ZRX_PROXY_DATA,
takerAddress,
leftOrder.feeRecipientAddress,
safeAdd(
matchedFillResults.left.takerFeePaid,
matchedFillResults.right.takerFeePaid
)
);
} else {
dispatchTransferFrom(
ZRX_PROXY_DATA,
takerAddress,
leftOrder.feeRecipientAddress,
matchedFillResults.left.takerFeePaid);
dispatchTransferFrom(
ZRX_PROXY_DATA,
takerAddress,
rightOrder.feeRecipientAddress,
matchedFillResults.right.takerFeePaid);
}
}
}

View File

@@ -27,10 +27,11 @@ import "./libs/LibFillResults.sol";
import "./libs/LibExchangeErrors.sol";
contract MixinWrapperFunctions is
SafeMath,
LibBytes,
LibMath,
LibOrder,
LibFillResults,
LibMath,
LibBytes,
LibExchangeErrors,
MExchangeCore
{

View File

@@ -23,10 +23,10 @@ import "./libs/LibOrder.sol";
import "./libs/LibFillResults.sol";
contract IWrapperFunctions is
LibBytes,
LibMath,
LibOrder,
LibFillResults,
LibMath,
LibBytes,
LibExchangeErrors,
MExchangeCore
{

View File

@@ -20,17 +20,6 @@ pragma solidity ^0.4.23;
contract LibExchangeErrors {
// Error Codes
enum Errors {
ORDER_EXPIRED, // Order has already expired
ORDER_FULLY_FILLED, // Order has already been fully filled
ORDER_CANCELLED, // Order has already been cancelled
ROUNDING_ERROR_TOO_LARGE, // Rounding error too large
INSUFFICIENT_BALANCE_OR_ALLOWANCE // Insufficient balance or allowance for token transfer
}
event ExchangeError(uint8 indexed errorId, bytes32 indexed orderHash);
// Core revert reasons
string constant GT_ZERO_AMOUNT_REQUIRED = "Amount must be greater than 0.";
string constant SIGNATURE_VALIDATION_FAILED = "Signature validation failed.";
@@ -38,6 +27,10 @@ contract LibExchangeErrors {
string constant INVALID_CONTEXT = "Function called in an invalid context.";
string constant INVALID_NEW_MAKER_EPOCH = "Specified salt must be greater than or equal to existing makerEpoch.";
// Order revert reasons
string constant INVALID_ORDER_TAKER_ASSET_AMOUNT = "Invalid order taker asset amount: expected a non-zero value.";
string constant INVALID_ORDER_MAKER_ASSET_AMOUNT = "Invalid order maker asset amount: expected a non-zero value.";
// Transaction revert reasons
string constant DUPLICATE_TRANSACTION_HASH = "Transaction has already been executed.";
string constant TRANSACTION_EXECUTION_FAILED = "Transaction execution failed.";

View File

@@ -0,0 +1,49 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.4.21;
pragma experimental ABIEncoderV2;
contract LibStatus {
// Exchange Status Codes
enum Status {
/// Default Status ///
INVALID, // General invalid status
/// General Exchange Statuses ///
SUCCESS, // Indicates a successful operation
ROUNDING_ERROR_TOO_LARGE, // Rounding error too large
INSUFFICIENT_BALANCE_OR_ALLOWANCE, // Insufficient balance or allowance for token transfer
TAKER_ASSET_FILL_AMOUNT_TOO_LOW, // takerAssetFillAmount is <= 0
INVALID_SIGNATURE, // Invalid signature
INVALID_SENDER, // Invalid sender
INVALID_TAKER, // Invalid taker
INVALID_MAKER, // Invalid maker
/// Order State Statuses ///
ORDER_INVALID_MAKER_ASSET_AMOUNT, // Order does not have a valid maker asset amount
ORDER_INVALID_TAKER_ASSET_AMOUNT, // Order does not have a valid taker asset amount
ORDER_FILLABLE, // Order is fillable
ORDER_EXPIRED, // Order has already expired
ORDER_FULLY_FILLED, // Order is fully filled
ORDER_CANCELLED // Order has been cancelled
}
event ExchangeStatus(uint8 indexed statusId, bytes32 indexed orderHash);
}

View File

@@ -17,6 +17,7 @@
*/
pragma solidity ^0.4.23;
pragma experimental ABIEncoderV2;
import "../interfaces/IAssetProxyDispatcher.sol";

View File

@@ -17,6 +17,7 @@
*/
pragma solidity ^0.4.23;
pragma experimental ABIEncoderV2;
import "../libs/LibOrder.sol";
import "../libs/LibFillResults.sol";
@@ -63,4 +64,122 @@ contract MExchangeCore is
bytes32 orderHash,
LibFillResults.FillResults memory fillResults)
internal;
/// @dev Gets information about an order.
/// @param order Order to gather information on.
/// @return status Status of order. Statuses are defined in the LibStatus.Status struct.
/// @return orderHash Keccak-256 EIP712 hash of the order.
/// @return takerAssetFilledAmount Amount of order that has been filled.
function getOrderInfo(LibOrder.Order memory order)
public
view
returns (
uint8 orderStatus,
bytes32 orderHash,
uint256 takerAssetFilledAmount);
/// @dev Validates context for fillOrder. Succeeds or throws.
/// @param order to be filled.
/// @param orderStatus Status of order to be filled.
/// @param orderHash Hash of order to be filled.
/// @param takerAssetFilledAmount Amount of order already filled.
/// @param signature Proof that the orders was created by its maker.
/// @param takerAddress Address of order taker.
/// @param takerAssetFillAmount Desired amount of order to fill by taker.
function validateFillOrderContextOrRevert(
LibOrder.Order memory order,
uint8 orderStatus,
bytes32 orderHash,
uint256 takerAssetFilledAmount,
bytes memory signature,
address takerAddress,
uint256 takerAssetFillAmount)
internal;
/// @dev Calculates amounts filled and fees paid by maker and taker.
/// @param order to be filled.
/// @param orderStatus Status of order to be filled.
/// @param takerAssetFilledAmount Amount of order already filled.
/// @param takerAssetFillAmount Desired amount of order to fill by taker.
/// @return status Return status of calculating fill amounts. Returns Status.SUCCESS on success.
/// @return fillResults Amounts filled and fees paid by maker and taker.
function calculateFillResults(
LibOrder.Order memory order,
uint8 orderStatus,
uint256 takerAssetFilledAmount,
uint256 takerAssetFillAmount)
public
pure
returns (
uint8 status,
LibFillResults.FillResults memory fillResults);
/// @dev Updates state with results of a fill order.
/// @param order that was filled.
/// @param takerAddress Address of taker who filled the order.
/// @return fillResults Amounts filled and fees paid by maker and taker.
function updateFilledState(
LibOrder.Order memory order,
address takerAddress,
bytes32 orderHash,
LibFillResults.FillResults memory fillResults)
internal;
/// @dev Fills the input order.
/// @param order Order struct containing order specifications.
/// @param takerAssetFillAmount Desired amount of takerToken to sell.
/// @param signature Proof that order has been created by maker.
/// @return Amounts filled and fees paid by maker and taker.
function fillOrder(
LibOrder.Order memory order,
uint256 takerAssetFillAmount,
bytes memory signature)
public
returns (LibFillResults.FillResults memory fillResults);
/// @dev Validates context for cancelOrder. Succeeds or throws.
/// @param order that was cancelled.
/// @param orderStatus Status of order that was cancelled.
/// @param orderHash Hash of order that was cancelled.
function validateCancelOrderContextOrRevert(
LibOrder.Order memory order,
uint8 orderStatus,
bytes32 orderHash)
internal;
/// @dev Updates state with results of cancelling an order.
/// State is only updated if the order is currently fillable.
/// Otherwise, updating state would have no effect.
/// @param order that was cancelled.
/// @param orderStatus Status of order that was cancelled.
/// @param orderHash Hash of order that was cancelled.
/// @return stateUpdated Returns true only if state was updated.
function updateCancelledState(
LibOrder.Order memory order,
uint8 orderStatus,
bytes32 orderHash)
internal
returns (bool stateUpdated);
/// @dev After calling, the order can not be filled anymore.
/// @param order Order struct containing order specifications.
/// @return True if the order state changed to cancelled.
/// False if the transaction was already cancelled or expired.
function cancelOrder(LibOrder.Order memory order)
public
returns (bool);
/// @param salt Orders created with a salt less or equal to this value will be cancelled.
function cancelOrdersUpTo(uint256 salt)
external;
/*
/// @dev Checks if rounding error > 0.1%.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to multiply with numerator/denominator.
/// @return Rounding error is present.
function isRoundingError(uint256 numerator, uint256 denominator, uint256 target)
public pure
returns (bool isError); */
}

View File

@@ -0,0 +1,106 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.4.21;
pragma experimental ABIEncoderV2;
import "../libs/LibOrder.sol";
import "../libs/LibFillResults.sol";
import "./MExchangeCore.sol";
contract MMatchOrders {
struct MatchedFillResults {
LibFillResults.FillResults left;
LibFillResults.FillResults right;
}
/// This struct exists solely to avoid the stack limit constraint
/// in matchOrders
struct OrderInfo {
uint8 orderStatus;
bytes32 orderHash;
uint256 orderFilledAmount;
}
/// @dev Validates context for matchOrders. Succeeds or throws.
/// @param leftOrder First order to match.
/// @param rightOrder Second order to match.
function validateMatchOrdersContextOrRevert(
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder)
internal;
/// @dev Validates matched fill results. Succeeds or throws.
/// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
function validateMatchedOrderFillResultsOrThrow(MatchedFillResults memory matchedFillResults)
internal;
/// @dev Calculates partial value given a numerator and denominator.
/// Throws if there is a rounding error.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to calculate partial of.
/// @return Partial value of target.
function safeGetPartialAmount(
uint256 numerator,
uint256 denominator,
uint256 target)
internal pure
returns (uint256 partialAmount);
/// @dev Calculates fill amounts for the matched orders.
/// Each order is filled at their respective price point. However, the calculations are
/// carried out as though the orders are both being filled at the right order's price point.
/// The profit made by the leftOrder order goes to the taker (who matched the two orders).
/// @param leftOrder First order to match.
/// @param rightOrder Second order to match.
/// @param leftOrderStatus Order status of left order.
/// @param rightOrderStatus Order status of right order.
/// @param leftOrderFilledAmount Amount of left order already filled.
/// @param rightOrderFilledAmount Amount of right order already filled.
/// @return status Return status of calculating fill amounts. Returns Status.SUCCESS on success.
/// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
function calculateMatchedFillResults(
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder,
uint8 leftOrderStatus,
uint8 rightOrderStatus,
uint256 leftOrderFilledAmount,
uint256 rightOrderFilledAmount)
internal
returns (
uint8 status,
MatchedFillResults memory matchedFillResults);
/// @dev Match two complementary orders that have a positive spread.
/// Each order is filled at their respective price point. However, the calculations are
/// carried out as though the orders are both being filled at the right order's price point.
/// The profit made by the left order goes to the taker (who matched the two orders).
/// @param leftOrder First order to match.
/// @param rightOrder Second order to match.
/// @param leftSignature Proof that order was created by the left maker.
/// @param rightSignature Proof that order was created by the right maker.
/// @return matchedFillResults Amounts filled and fees paid by maker and taker of matched orders.
function matchOrders(
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder,
bytes leftSignature,
bytes rightSignature)
public
returns (MatchedFillResults memory matchedFillResults);
}

View File

@@ -19,6 +19,7 @@
pragma solidity ^0.4.23;
import "../libs/LibOrder.sol";
import "./MMatchOrders.sol";
contract MSettlement {
@@ -37,5 +38,16 @@ contract MSettlement {
uint256 makerFeePaid,
uint256 takerFeePaid
);
}
/// @dev Settles matched order by transferring appropriate funds between order makers, taker, and fee recipient.
/// @param leftOrder First matched order.
/// @param rightOrder Second matched order.
/// @param matchedFillResults Struct holding amounts to transfer between makers, taker, and fee recipients.
/// @param takerAddress Address that matched the orders. The taker receives the spread between orders as profit.
function settleMatchedOrders(
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder,
MMatchOrders.MatchedFillResults memory matchedFillResults,
address takerAddress)
internal;
}

View File

@@ -38,4 +38,4 @@ contract TestSignatureValidator is MixinSignatureValidator {
);
return isValid;
}
}
}