Update Exchange statuses, revert instead of emmitting event on fill/cancel failures, and remove redundant logic in matchOrders
This commit is contained in:
committed by
Jacob Evans
parent
79472552aa
commit
342432dc76
@@ -4,7 +4,7 @@
|
||||
"compilerSettings": {
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 0
|
||||
"runs": 200
|
||||
},
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
|
||||
@@ -20,9 +20,9 @@ pragma solidity ^0.4.24;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "../../utils/LibBytes/LibBytes.sol";
|
||||
import "../../tokens/ERC20Token/IERC20Token.sol";
|
||||
import "./MixinAssetProxy.sol";
|
||||
import "./MixinAuthorizable.sol";
|
||||
import "../../tokens/ERC20Token/IERC20Token.sol";
|
||||
|
||||
contract ERC20Proxy is
|
||||
LibBytes,
|
||||
@@ -33,11 +33,6 @@ contract ERC20Proxy is
|
||||
// Id of this proxy.
|
||||
uint8 constant PROXY_ID = 1;
|
||||
|
||||
// Revert reasons
|
||||
string constant INVALID_METADATA_LENGTH = "Metadata must have a length of 21.";
|
||||
string constant TRANSFER_FAILED = "Transfer failed.";
|
||||
string constant PROXY_ID_MISMATCH = "Proxy id in metadata does not match this proxy id.";
|
||||
|
||||
/// @dev Internal version of `transferFrom`.
|
||||
/// @param assetMetadata Encoded byte array.
|
||||
/// @param from Address to transfer asset from.
|
||||
@@ -56,12 +51,12 @@ contract ERC20Proxy is
|
||||
|
||||
require(
|
||||
length == 21,
|
||||
INVALID_METADATA_LENGTH
|
||||
LENGTH_21_REQUIRED
|
||||
);
|
||||
|
||||
// TODO: Is this too inflexible in the future?
|
||||
require(
|
||||
uint8(assetMetadata[length - 1]) == PROXY_ID,
|
||||
PROXY_ID_MISMATCH
|
||||
ASSET_PROXY_ID_MISMATCH
|
||||
);
|
||||
|
||||
// Decode metadata.
|
||||
@@ -70,7 +65,7 @@ contract ERC20Proxy is
|
||||
// Transfer tokens.
|
||||
bool success = IERC20Token(token).transferFrom(from, to, amount);
|
||||
require(
|
||||
success == true,
|
||||
success,
|
||||
TRANSFER_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ pragma solidity ^0.4.24;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "../../utils/LibBytes/LibBytes.sol";
|
||||
import "../../tokens/ERC721Token/ERC721Token.sol";
|
||||
import "./MixinAssetProxy.sol";
|
||||
import "./MixinAuthorizable.sol";
|
||||
import "../../tokens/ERC721Token/ERC721Token.sol";
|
||||
|
||||
contract ERC721Proxy is
|
||||
LibBytes,
|
||||
@@ -33,11 +33,6 @@ contract ERC721Proxy is
|
||||
// Id of this proxy.
|
||||
uint8 constant PROXY_ID = 2;
|
||||
|
||||
// Revert reasons
|
||||
string constant INVALID_TRANSFER_AMOUNT = "Transfer amount must equal 1.";
|
||||
string constant INVALID_METADATA_LENGTH = "Metadata must have a length of 53.";
|
||||
string constant PROXY_ID_MISMATCH = "Proxy id in metadata does not match this proxy id.";
|
||||
|
||||
/// @dev Internal version of `transferFrom`.
|
||||
/// @param assetMetadata Encoded byte array.
|
||||
/// @param from Address to transfer asset from.
|
||||
@@ -56,18 +51,19 @@ contract ERC721Proxy is
|
||||
|
||||
require(
|
||||
length == 53,
|
||||
INVALID_METADATA_LENGTH
|
||||
LENGTH_53_REQUIRED
|
||||
);
|
||||
|
||||
// TODO: Is this too inflexible in the future?
|
||||
require(
|
||||
uint8(assetMetadata[length - 1]) == PROXY_ID,
|
||||
PROXY_ID_MISMATCH
|
||||
ASSET_PROXY_ID_MISMATCH
|
||||
);
|
||||
|
||||
// There exists only 1 of each token.
|
||||
require(
|
||||
amount == 1,
|
||||
INVALID_TRANSFER_AMOUNT
|
||||
INVALID_AMOUNT
|
||||
);
|
||||
|
||||
// Decode metadata
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
pragma solidity ^0.4.24;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./mixins/MAssetProxy.sol";
|
||||
import "./mixins/MAuthorizable.sol";
|
||||
import "./mixins/MAssetProxy.sol";
|
||||
|
||||
contract MixinAssetProxy is
|
||||
contract MixinAssetProxy is
|
||||
MAuthorizable,
|
||||
MAssetProxy
|
||||
{
|
||||
|
||||
@@ -19,21 +19,16 @@
|
||||
pragma solidity ^0.4.24;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./mixins/MAuthorizable.sol";
|
||||
import "./libs/LibAssetProxyErrors.sol";
|
||||
import "../../utils/Ownable/Ownable.sol";
|
||||
import "./mixins/MAuthorizable.sol";
|
||||
|
||||
contract MixinAuthorizable is
|
||||
LibAssetProxyErrors,
|
||||
Ownable,
|
||||
MAuthorizable
|
||||
{
|
||||
|
||||
// Revert reasons
|
||||
string constant SENDER_NOT_AUTHORIZED = "Sender not authorized to call this method.";
|
||||
string constant TARGET_NOT_AUTHORIZED = "Target address must be authorized.";
|
||||
string constant TARGET_ALREADY_AUTHORIZED = "Target must not already be authorized.";
|
||||
string constant INDEX_OUT_OF_BOUNDS = "Specified array index is out of bounds.";
|
||||
string constant INDEX_ADDRESS_MISMATCH = "Address found at index does not match target address.";
|
||||
|
||||
/// @dev Only authorized addresses can invoke functions with this modifier.
|
||||
modifier onlyAuthorized {
|
||||
require(
|
||||
@@ -99,7 +94,7 @@ contract MixinAuthorizable is
|
||||
);
|
||||
require(
|
||||
authorities[index] == target,
|
||||
INDEX_ADDRESS_MISMATCH
|
||||
AUTHORIZED_ADDRESS_MISMATCH
|
||||
);
|
||||
|
||||
delete authorized[target];
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
|
||||
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;
|
||||
|
||||
contract LibAssetProxyErrors {
|
||||
/// Authorizable errors ///
|
||||
string constant SENDER_NOT_AUTHORIZED = "SENDER_NOT_AUTHORIZED"; // Sender not authorized to call this method.
|
||||
string constant TARGET_NOT_AUTHORIZED = "TARGET_NOT_AUTHORIZED"; // Target address not authorized to call this method.
|
||||
string constant TARGET_ALREADY_AUTHORIZED = "TARGET_ALREADY_AUTHORIZED"; // Target address must not already be authorized.
|
||||
string constant INDEX_OUT_OF_BOUNDS = "INDEX_OUT_OF_BOUNDS"; // Specified array index is out of bounds.
|
||||
string constant AUTHORIZED_ADDRESS_MISMATCH = "AUTHORIZED_ADDRESS_MISMATCH"; // Address at index does not match given target address.
|
||||
|
||||
/// AssetProxy errors ///
|
||||
string constant ASSET_PROXY_ID_MISMATCH = "ASSET_PROXY_ID_MISMATCH"; // Proxy id in metadata does not match this proxy id.
|
||||
string constant INVALID_AMOUNT = "INVALID_AMOUNT"; // Transfer amount must equal 1.
|
||||
string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Transfer failed.
|
||||
|
||||
/// Length validation errors ///
|
||||
string constant LENGTH_21_REQUIRED = "LENGTH_21_REQUIRED"; // Byte array must have a length of 21.
|
||||
string constant LENGTH_53_REQUIRED = "LENGTH_53_REQUIRED"; // Byte array must have a length of 53.
|
||||
}
|
||||
@@ -19,13 +19,13 @@
|
||||
pragma solidity ^0.4.24;
|
||||
|
||||
import "../../utils/Ownable/Ownable.sol";
|
||||
import "../AssetProxy/interfaces/IAssetProxy.sol";
|
||||
import "./libs/LibExchangeErrors.sol";
|
||||
import "./mixins/MAssetProxyDispatcher.sol";
|
||||
import "../AssetProxy/interfaces/IAssetProxy.sol";
|
||||
|
||||
contract MixinAssetProxyDispatcher is
|
||||
LibExchangeErrors,
|
||||
Ownable,
|
||||
LibExchangeErrors,
|
||||
MAssetProxyDispatcher
|
||||
{
|
||||
// Mapping from Asset Proxy Id's to their respective Asset Proxy
|
||||
@@ -45,9 +45,10 @@ contract MixinAssetProxyDispatcher is
|
||||
onlyOwner
|
||||
{
|
||||
// Ensure the existing asset proxy is not unintentionally overwritten
|
||||
address currentAssetProxy = address(assetProxies[assetProxyId]);
|
||||
require(
|
||||
oldAssetProxy == address(assetProxies[assetProxyId]),
|
||||
OLD_ASSET_PROXY_MISMATCH
|
||||
oldAssetProxy == currentAssetProxy,
|
||||
ASSET_PROXY_MISMATCH
|
||||
);
|
||||
|
||||
IAssetProxy assetProxy = IAssetProxy(newAssetProxy);
|
||||
@@ -57,7 +58,7 @@ contract MixinAssetProxyDispatcher is
|
||||
uint8 newAssetProxyId = assetProxy.getProxyId();
|
||||
require(
|
||||
newAssetProxyId == assetProxyId,
|
||||
NEW_ASSET_PROXY_MISMATCH
|
||||
ASSET_PROXY_ID_MISMATCH
|
||||
);
|
||||
}
|
||||
|
||||
@@ -98,7 +99,7 @@ contract MixinAssetProxyDispatcher is
|
||||
uint256 length = assetMetadata.length;
|
||||
require(
|
||||
length > 0,
|
||||
GT_ZERO_LENGTH_REQUIRED
|
||||
LENGTH_GT_0_REQUIRED
|
||||
);
|
||||
uint8 assetProxyId = uint8(assetMetadata[length - 1]);
|
||||
IAssetProxy assetProxy = assetProxies[assetProxyId];
|
||||
|
||||
@@ -22,7 +22,6 @@ 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";
|
||||
@@ -30,9 +29,7 @@ import "./mixins/MSignatureValidator.sol";
|
||||
import "./mixins/MTransactions.sol";
|
||||
|
||||
contract MixinExchangeCore is
|
||||
SafeMath,
|
||||
LibMath,
|
||||
LibStatus,
|
||||
LibOrder,
|
||||
LibFillResults,
|
||||
LibExchangeErrors,
|
||||
@@ -58,13 +55,21 @@ contract MixinExchangeCore is
|
||||
function cancelOrdersUpTo(uint256 salt)
|
||||
external
|
||||
{
|
||||
uint256 newMakerEpoch = salt + 1; // makerEpoch is initialized to 0, so to cancelUpTo we need salt + 1
|
||||
address makerAddress = getCurrentContextAddress();
|
||||
|
||||
// makerEpoch is initialized to 0, so to cancelUpTo we need salt + 1
|
||||
uint256 newMakerEpoch = salt + 1;
|
||||
uint256 oldMakerEpoch = makerEpoch[makerAddress];
|
||||
|
||||
// Ensure makerEpoch is monotonically increasing
|
||||
require(
|
||||
newMakerEpoch > makerEpoch[msg.sender], // epoch must be monotonically increasing
|
||||
newMakerEpoch > oldMakerEpoch,
|
||||
INVALID_NEW_MAKER_EPOCH
|
||||
);
|
||||
makerEpoch[msg.sender] = newMakerEpoch;
|
||||
emit CancelUpTo(msg.sender, newMakerEpoch);
|
||||
|
||||
// Update makerEpoch
|
||||
makerEpoch[makerAddress] = newMakerEpoch;
|
||||
emit CancelUpTo(makerAddress, newMakerEpoch);
|
||||
}
|
||||
|
||||
/// @dev Fills the input order.
|
||||
@@ -86,29 +91,22 @@ contract MixinExchangeCore is
|
||||
// Fetch taker address
|
||||
address takerAddress = getCurrentContextAddress();
|
||||
|
||||
// Either our context is valid or we revert
|
||||
// Get amount of takerAsset to fill
|
||||
uint256 remainingTakerAssetAmount = safeSub(order.takerAssetAmount, orderInfo.orderTakerAssetFilledAmount);
|
||||
uint256 takerAssetFilledAmount = min256(takerAssetFillAmount, remainingTakerAssetAmount);
|
||||
|
||||
// Validate context
|
||||
assertValidFill(
|
||||
order,
|
||||
orderInfo.orderStatus,
|
||||
orderInfo.orderHash,
|
||||
orderInfo,
|
||||
takerAddress,
|
||||
orderInfo.orderTakerAssetFilledAmount,
|
||||
takerAssetFillAmount,
|
||||
takerAssetFilledAmount,
|
||||
signature
|
||||
);
|
||||
|
||||
// Compute proportional fill amounts
|
||||
uint8 status;
|
||||
(status, fillResults) = calculateFillResults(
|
||||
order,
|
||||
orderInfo.orderStatus,
|
||||
orderInfo.orderTakerAssetFilledAmount,
|
||||
takerAssetFillAmount
|
||||
);
|
||||
if (status != uint8(Status.SUCCESS)) {
|
||||
emit ExchangeStatus(uint8(status), orderInfo.orderHash);
|
||||
return getNullFillResults();
|
||||
}
|
||||
fillResults = calculateFillResults(order, takerAssetFilledAmount);
|
||||
|
||||
// Settle order
|
||||
settleOrder(order, takerAddress, fillResults);
|
||||
@@ -126,32 +124,28 @@ contract MixinExchangeCore is
|
||||
|
||||
/// @dev After calling, the order can not be filled anymore.
|
||||
/// Throws if order is invalid or sender does not have permission to cancel.
|
||||
/// @param order Order to cancel. Order must be Status.FILLABLE.
|
||||
/// @return True if the order state changed to cancelled.
|
||||
/// False if the order was valid, but in an
|
||||
/// unfillable state (see LibStatus.STATUS for order states)
|
||||
/// @param order Order to cancel. Order must be OrderStatus.FILLABLE.
|
||||
function cancelOrder(Order memory order)
|
||||
public
|
||||
returns (bool)
|
||||
{
|
||||
// Fetch current order status
|
||||
OrderInfo memory orderInfo = getOrderInfo(order);
|
||||
|
||||
// Validate context
|
||||
assertValidCancel(order, orderInfo.orderStatus, orderInfo.orderHash);
|
||||
assertValidCancel(order, orderInfo);
|
||||
|
||||
// Perform cancel
|
||||
return updateCancelledState(order, orderInfo.orderStatus, orderInfo.orderHash);
|
||||
updateCancelledState(order, orderInfo.orderHash);
|
||||
}
|
||||
|
||||
/// @dev Gets information about an order: status, hash, and amount filled.
|
||||
/// @param order Order to gather information on.
|
||||
/// @return OrderInfo Information about the order and its state.
|
||||
/// See LibOrder.OrderInfo for a complete description.
|
||||
/// See LibOrder.OrderInfo for a complete description.
|
||||
function getOrderInfo(Order memory order)
|
||||
public
|
||||
view
|
||||
returns (LibOrder.OrderInfo memory orderInfo)
|
||||
returns (OrderInfo memory orderInfo)
|
||||
{
|
||||
// Compute the order hash
|
||||
orderInfo.orderHash = getOrderHash(order);
|
||||
@@ -161,7 +155,7 @@ contract MixinExchangeCore is
|
||||
// edge cases in the supporting infrastructure because they have
|
||||
// an 'infinite' price when computed by a simple division.
|
||||
if (order.makerAssetAmount == 0) {
|
||||
orderInfo.orderStatus = uint8(Status.ORDER_INVALID_MAKER_ASSET_AMOUNT);
|
||||
orderInfo.orderStatus = uint8(OrderStatus.INVALID_MAKER_ASSET_AMOUNT);
|
||||
return orderInfo;
|
||||
}
|
||||
|
||||
@@ -170,169 +164,38 @@ contract MixinExchangeCore is
|
||||
// Instead of distinguishing between unfilled and filled zero taker
|
||||
// amount orders, we choose not to support them.
|
||||
if (order.takerAssetAmount == 0) {
|
||||
orderInfo.orderStatus = uint8(Status.ORDER_INVALID_TAKER_ASSET_AMOUNT);
|
||||
orderInfo.orderStatus = uint8(OrderStatus.INVALID_TAKER_ASSET_AMOUNT);
|
||||
return orderInfo;
|
||||
}
|
||||
|
||||
// Validate order expiration
|
||||
if (block.timestamp >= order.expirationTimeSeconds) {
|
||||
orderInfo.orderStatus = uint8(Status.ORDER_EXPIRED);
|
||||
orderInfo.orderStatus = uint8(OrderStatus.EXPIRED);
|
||||
return orderInfo;
|
||||
}
|
||||
|
||||
// Check if order has been cancelled
|
||||
if (cancelled[orderInfo.orderHash]) {
|
||||
orderInfo.orderStatus = uint8(Status.ORDER_CANCELLED);
|
||||
orderInfo.orderStatus = uint8(OrderStatus.CANCELLED);
|
||||
return orderInfo;
|
||||
}
|
||||
if (makerEpoch[order.makerAddress] > order.salt) {
|
||||
orderInfo.orderStatus = uint8(Status.ORDER_CANCELLED);
|
||||
orderInfo.orderStatus = uint8(OrderStatus.CANCELLED);
|
||||
return orderInfo;
|
||||
}
|
||||
|
||||
// Fetch filled amount and validate order availability
|
||||
orderInfo.orderTakerAssetFilledAmount = filled[orderInfo.orderHash];
|
||||
if (orderInfo.orderTakerAssetFilledAmount >= order.takerAssetAmount) {
|
||||
orderInfo.orderStatus = uint8(Status.ORDER_FULLY_FILLED);
|
||||
orderInfo.orderStatus = uint8(OrderStatus.FULLY_FILLED);
|
||||
return orderInfo;
|
||||
}
|
||||
|
||||
// All other statuses are ruled out: order is Fillable
|
||||
orderInfo.orderStatus = uint8(Status.ORDER_FILLABLE);
|
||||
orderInfo.orderStatus = uint8(OrderStatus.FILLABLE);
|
||||
return orderInfo;
|
||||
}
|
||||
|
||||
/// @dev Calculates amounts filled and fees paid by maker and taker.
|
||||
/// @param order to be filled.
|
||||
/// @param orderStatus Status of order to be filled.
|
||||
/// @param orderTakerAssetFilledAmount Amount of order already filled.
|
||||
/// @param takerAssetFillAmount Desired amount of order to fill by taker.
|
||||
/// @return status Return status of calculating fill amounts. Returns Status.SUCCESS on success.
|
||||
/// @return fillResults Amounts filled and fees paid by maker and taker.
|
||||
function calculateFillResults(
|
||||
Order memory order,
|
||||
uint8 orderStatus,
|
||||
uint256 orderTakerAssetFilledAmount,
|
||||
uint256 takerAssetFillAmount
|
||||
)
|
||||
public
|
||||
pure
|
||||
returns (
|
||||
uint8 status,
|
||||
FillResults memory fillResults
|
||||
)
|
||||
{
|
||||
// Fill amount must be greater than 0
|
||||
if (takerAssetFillAmount == 0) {
|
||||
status = uint8(Status.TAKER_ASSET_FILL_AMOUNT_TOO_LOW);
|
||||
return (status, fillResults);
|
||||
}
|
||||
|
||||
// Ensure the order is fillable
|
||||
if (orderStatus != uint8(Status.ORDER_FILLABLE)) {
|
||||
status = orderStatus;
|
||||
return (status, fillResults);
|
||||
}
|
||||
|
||||
// Compute takerAssetFilledAmount
|
||||
uint256 remainingTakerAssetAmount = safeSub(order.takerAssetAmount, orderTakerAssetFilledAmount);
|
||||
uint256 takerAssetFilledAmount = min256(takerAssetFillAmount, remainingTakerAssetAmount);
|
||||
|
||||
// Validate fill order rounding
|
||||
if (isRoundingError(
|
||||
takerAssetFilledAmount,
|
||||
order.takerAssetAmount,
|
||||
order.makerAssetAmount))
|
||||
{
|
||||
status = uint8(Status.ROUNDING_ERROR_TOO_LARGE);
|
||||
return (status, fillResults);
|
||||
}
|
||||
|
||||
// Compute proportional transfer amounts
|
||||
// TODO: All three are multiplied by the same fraction. This can
|
||||
// potentially be optimized.
|
||||
fillResults.takerAssetFilledAmount = takerAssetFilledAmount;
|
||||
fillResults.makerAssetFilledAmount = getPartialAmount(
|
||||
fillResults.takerAssetFilledAmount,
|
||||
order.takerAssetAmount,
|
||||
order.makerAssetAmount
|
||||
);
|
||||
fillResults.makerFeePaid = getPartialAmount(
|
||||
fillResults.takerAssetFilledAmount,
|
||||
order.takerAssetAmount,
|
||||
order.makerFee
|
||||
);
|
||||
fillResults.takerFeePaid = getPartialAmount(
|
||||
fillResults.takerAssetFilledAmount,
|
||||
order.takerAssetAmount,
|
||||
order.takerFee
|
||||
);
|
||||
|
||||
status = uint8(Status.SUCCESS);
|
||||
return (status, fillResults);
|
||||
}
|
||||
|
||||
/// @dev Validates context for fillOrder. Succeeds or throws.
|
||||
/// @param order to be filled.
|
||||
/// @param orderStatus Status of order to be filled.
|
||||
/// @param orderHash Hash of order to be filled.
|
||||
/// @param takerAddress Address of order taker.
|
||||
/// @param orderTakerAssetFilledAmount Amount of order already filled.
|
||||
/// @param takerAssetFillAmount Desired amount of order to fill by taker.
|
||||
/// @param signature Proof that the orders was created by its maker.
|
||||
function assertValidFill(
|
||||
Order memory order,
|
||||
uint8 orderStatus,
|
||||
bytes32 orderHash,
|
||||
address takerAddress,
|
||||
uint256 orderTakerAssetFilledAmount,
|
||||
uint256 takerAssetFillAmount,
|
||||
bytes memory signature
|
||||
)
|
||||
internal
|
||||
{
|
||||
// Ensure order is valid
|
||||
// An order can only be filled if its status is FILLABLE;
|
||||
// however, only invalid statuses result in a throw.
|
||||
// See LibStatus for a complete description of order statuses.
|
||||
require(
|
||||
orderStatus != uint8(Status.ORDER_INVALID_MAKER_ASSET_AMOUNT),
|
||||
INVALID_ORDER_MAKER_ASSET_AMOUNT
|
||||
);
|
||||
require(
|
||||
orderStatus != uint8(Status.ORDER_INVALID_TAKER_ASSET_AMOUNT),
|
||||
INVALID_ORDER_TAKER_ASSET_AMOUNT
|
||||
);
|
||||
|
||||
// Validate Maker signature (check only if first time seen)
|
||||
if (orderTakerAssetFilledAmount == 0) {
|
||||
require(
|
||||
isValidSignature(orderHash, order.makerAddress, signature),
|
||||
SIGNATURE_VALIDATION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
// Validate sender is allowed to fill this order
|
||||
if (order.senderAddress != address(0)) {
|
||||
require(
|
||||
order.senderAddress == msg.sender,
|
||||
INVALID_SENDER
|
||||
);
|
||||
}
|
||||
|
||||
// Validate taker is allowed to fill this order
|
||||
if (order.takerAddress != address(0)) {
|
||||
require(
|
||||
order.takerAddress == takerAddress,
|
||||
INVALID_CONTEXT
|
||||
);
|
||||
}
|
||||
require(
|
||||
takerAssetFillAmount > 0,
|
||||
GT_ZERO_AMOUNT_REQUIRED
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Updates state with results of a fill order.
|
||||
/// @param order that was filled.
|
||||
/// @param takerAddress Address of taker who filled the order.
|
||||
@@ -365,72 +228,19 @@ contract MixinExchangeCore is
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Validates context for cancelOrder. Succeeds or throws.
|
||||
/// @param order that was cancelled.
|
||||
/// @param orderStatus Status of order that was cancelled.
|
||||
/// @param orderHash Hash of order that was cancelled.
|
||||
function assertValidCancel(
|
||||
Order memory order,
|
||||
uint8 orderStatus,
|
||||
bytes32 orderHash
|
||||
)
|
||||
internal
|
||||
{
|
||||
// Ensure order is valid
|
||||
// An order can only be cancelled if its status is FILLABLE;
|
||||
// however, only invalid statuses result in a throw.
|
||||
// See LibStatus for a complete description of order statuses.
|
||||
require(
|
||||
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)
|
||||
// See LibStatus for a complete description of order statuses.
|
||||
if (orderStatus != uint8(Status.ORDER_FILLABLE)) {
|
||||
emit ExchangeStatus(uint8(orderStatus), orderHash);
|
||||
stateUpdated = false;
|
||||
return stateUpdated;
|
||||
}
|
||||
|
||||
// Perform cancel
|
||||
cancelled[orderHash] = true;
|
||||
stateUpdated = true;
|
||||
|
||||
// Log cancel
|
||||
emit Cancel(
|
||||
@@ -440,7 +250,138 @@ contract MixinExchangeCore is
|
||||
order.makerAssetData,
|
||||
order.takerAssetData
|
||||
);
|
||||
}
|
||||
|
||||
return stateUpdated;
|
||||
/// @dev Validates context for fillOrder. Succeeds or throws.
|
||||
/// @param order to be filled.
|
||||
/// @param orderInfo OrderStatus, orderHash, and amount already filled of order.
|
||||
/// @param takerAddress Address of order taker.
|
||||
/// @param takerAssetFillAmount Desired amount of order to fill by taker.
|
||||
/// @param takerAssetFilledAmount Amount of takerAsset that will be filled.
|
||||
/// @param signature Proof that the orders was created by its maker.
|
||||
function assertValidFill(
|
||||
Order memory order,
|
||||
OrderInfo memory orderInfo,
|
||||
address takerAddress,
|
||||
uint256 takerAssetFillAmount,
|
||||
uint256 takerAssetFilledAmount,
|
||||
bytes memory signature
|
||||
)
|
||||
internal
|
||||
view
|
||||
{
|
||||
// An order can only be filled if its status is FILLABLE.
|
||||
require(
|
||||
orderInfo.orderStatus == uint8(OrderStatus.FILLABLE),
|
||||
ORDER_UNFILLABLE
|
||||
);
|
||||
|
||||
// Revert if fill amount is invalid
|
||||
require(
|
||||
takerAssetFillAmount != 0,
|
||||
INVALID_TAKER_AMOUNT
|
||||
);
|
||||
|
||||
// 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_TAKER
|
||||
);
|
||||
}
|
||||
|
||||
// Validate Maker signature (check only if first time seen)
|
||||
if (orderInfo.orderTakerAssetFilledAmount == 0) {
|
||||
require(
|
||||
isValidSignature(orderInfo.orderHash, order.makerAddress, signature),
|
||||
INVALID_ORDER_SIGNATURE
|
||||
);
|
||||
}
|
||||
|
||||
// Validate fill order rounding
|
||||
require(
|
||||
!isRoundingError(
|
||||
takerAssetFilledAmount,
|
||||
order.takerAssetAmount,
|
||||
order.makerAssetAmount
|
||||
),
|
||||
ROUNDING_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Validates context for cancelOrder. Succeeds or throws.
|
||||
/// @param order to be cancelled.
|
||||
/// @param orderInfo OrderStatus, orderHash, and amount already filled of order.
|
||||
function assertValidCancel(
|
||||
Order memory order,
|
||||
OrderInfo memory orderInfo
|
||||
)
|
||||
internal
|
||||
view
|
||||
{
|
||||
// Ensure order is valid
|
||||
// An order can only be cancelled if its status is FILLABLE.
|
||||
require(
|
||||
orderInfo.orderStatus == uint8(OrderStatus.FILLABLE),
|
||||
ORDER_UNFILLABLE
|
||||
);
|
||||
|
||||
// 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_MAKER
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Calculates amounts filled and fees paid by maker and taker.
|
||||
/// @param order to be filled.
|
||||
/// @param takerAssetFilledAmount Amount of takerAsset that will be filled.
|
||||
/// @return fillResults Amounts filled and fees paid by maker and taker.
|
||||
function calculateFillResults(
|
||||
Order memory order,
|
||||
uint256 takerAssetFilledAmount
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (FillResults memory fillResults)
|
||||
{
|
||||
// Compute proportional transfer amounts
|
||||
// TODO: All three are multiplied by the same fraction. This can
|
||||
// potentially be optimized.
|
||||
fillResults.takerAssetFilledAmount = takerAssetFilledAmount;
|
||||
fillResults.makerAssetFilledAmount = getPartialAmount(
|
||||
fillResults.takerAssetFilledAmount,
|
||||
order.takerAssetAmount,
|
||||
order.makerAssetAmount
|
||||
);
|
||||
fillResults.makerFeePaid = getPartialAmount(
|
||||
fillResults.takerAssetFilledAmount,
|
||||
order.takerAssetAmount,
|
||||
order.makerFee
|
||||
);
|
||||
fillResults.takerFeePaid = getPartialAmount(
|
||||
fillResults.takerAssetFilledAmount,
|
||||
order.takerAssetAmount,
|
||||
order.takerFee
|
||||
);
|
||||
|
||||
return fillResults;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,24 +14,19 @@
|
||||
pragma solidity ^0.4.24;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "../../utils/LibBytes/LibBytes.sol";
|
||||
import "./libs/LibMath.sol";
|
||||
import "./libs/LibOrder.sol";
|
||||
import "./libs/LibFillResults.sol";
|
||||
import "./libs/LibExchangeErrors.sol";
|
||||
import "./mixins/MExchangeCore.sol";
|
||||
import "./mixins/MMatchOrders.sol";
|
||||
import "./mixins/MSettlement.sol";
|
||||
import "./mixins/MTransactions.sol";
|
||||
import "../../utils/SafeMath/SafeMath.sol";
|
||||
import "./libs/LibMath.sol";
|
||||
import "./libs/LibOrder.sol";
|
||||
import "./libs/LibStatus.sol";
|
||||
import "../../utils/LibBytes/LibBytes.sol";
|
||||
import "./libs/LibExchangeErrors.sol";
|
||||
|
||||
contract MixinMatchOrders is
|
||||
SafeMath,
|
||||
LibBytes,
|
||||
LibMath,
|
||||
LibStatus,
|
||||
LibOrder,
|
||||
LibFillResults,
|
||||
LibExchangeErrors,
|
||||
MExchangeCore,
|
||||
MMatchOrders,
|
||||
@@ -50,17 +45,22 @@ contract MixinMatchOrders is
|
||||
/// @return matchedFillResults Amounts filled and fees paid by maker and taker of matched orders.
|
||||
/// TODO: Make this function external once supported by Solidity (See Solidity Issues #3199, #1603)
|
||||
function matchOrders(
|
||||
Order memory leftOrder,
|
||||
Order memory rightOrder,
|
||||
LibOrder.Order memory leftOrder,
|
||||
LibOrder.Order memory rightOrder,
|
||||
bytes memory leftSignature,
|
||||
bytes memory rightSignature
|
||||
)
|
||||
public
|
||||
returns (MatchedFillResults memory matchedFillResults)
|
||||
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
|
||||
OrderInfo memory leftOrderInfo = getOrderInfo(leftOrder);
|
||||
OrderInfo memory rightOrderInfo = getOrderInfo(rightOrder);
|
||||
LibOrder.OrderInfo memory leftOrderInfo = getOrderInfo(leftOrder);
|
||||
LibOrder.OrderInfo memory rightOrderInfo = getOrderInfo(rightOrder);
|
||||
|
||||
// Fetch taker address
|
||||
address takerAddress = getCurrentContextAddress();
|
||||
@@ -72,8 +72,6 @@ contract MixinMatchOrders is
|
||||
matchedFillResults = calculateMatchedFillResults(
|
||||
leftOrder,
|
||||
rightOrder,
|
||||
leftOrderInfo.orderStatus,
|
||||
rightOrderInfo.orderStatus,
|
||||
leftOrderInfo.orderTakerAssetFilledAmount,
|
||||
rightOrderInfo.orderTakerAssetFilledAmount
|
||||
);
|
||||
@@ -81,19 +79,17 @@ contract MixinMatchOrders is
|
||||
// Validate fill contexts
|
||||
assertValidFill(
|
||||
leftOrder,
|
||||
leftOrderInfo.orderStatus,
|
||||
leftOrderInfo.orderHash,
|
||||
leftOrderInfo,
|
||||
takerAddress,
|
||||
leftOrderInfo.orderTakerAssetFilledAmount,
|
||||
matchedFillResults.left.takerAssetFilledAmount,
|
||||
matchedFillResults.left.takerAssetFilledAmount,
|
||||
leftSignature
|
||||
);
|
||||
assertValidFill(
|
||||
rightOrder,
|
||||
rightOrderInfo.orderStatus,
|
||||
rightOrderInfo.orderHash,
|
||||
rightOrderInfo,
|
||||
takerAddress,
|
||||
rightOrderInfo.orderTakerAssetFilledAmount,
|
||||
matchedFillResults.right.takerAssetFilledAmount,
|
||||
matchedFillResults.right.takerAssetFilledAmount,
|
||||
rightSignature
|
||||
);
|
||||
@@ -129,25 +125,12 @@ contract MixinMatchOrders is
|
||||
/// @param leftOrder First order to match.
|
||||
/// @param rightOrder Second order to match.
|
||||
function assertValidMatch(
|
||||
Order memory leftOrder,
|
||||
Order memory rightOrder
|
||||
LibOrder.Order memory leftOrder,
|
||||
LibOrder.Order memory rightOrder
|
||||
)
|
||||
internal
|
||||
pure
|
||||
{
|
||||
// The leftOrder maker asset must be the same as the rightOrder taker asset.
|
||||
// TODO: Can we safely assume equality and expect a later failure otherwise?
|
||||
require(
|
||||
areBytesEqual(leftOrder.makerAssetData, rightOrder.takerAssetData),
|
||||
ASSET_MISMATCH_MAKER_TAKER
|
||||
);
|
||||
|
||||
// The leftOrder taker asset must be the same as the rightOrder maker asset.
|
||||
// TODO: Can we safely assume equality and expect a later failure otherwise?
|
||||
require(
|
||||
areBytesEqual(leftOrder.takerAssetData, rightOrder.makerAssetData),
|
||||
ASSET_MISMATCH_TAKER_MAKER
|
||||
);
|
||||
|
||||
// Make sure there is a profitable spread.
|
||||
// There is a profitable spread iff the cost per unit bought (OrderA.MakerAmount/OrderA.TakerAmount) for each order is greater
|
||||
// than the profit per unit sold of the matched order (OrderB.TakerAmount/OrderB.MakerAmount).
|
||||
@@ -159,39 +142,7 @@ contract MixinMatchOrders is
|
||||
require(
|
||||
safeMul(leftOrder.makerAssetAmount, rightOrder.makerAssetAmount) >=
|
||||
safeMul(leftOrder.takerAssetAmount, rightOrder.takerAssetAmount),
|
||||
NEGATIVE_SPREAD
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Validates matched fill results. Succeeds or throws.
|
||||
/// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
|
||||
function assertValidMatchResults(MatchedFillResults memory matchedFillResults)
|
||||
internal
|
||||
{
|
||||
// If the amount transferred from the left order is different than what is transferred, it is a rounding error amount.
|
||||
// Ensure this difference is negligible by dividing the values with each other. The result should equal to ~1.
|
||||
uint256 amountSpentByLeft = safeAdd(
|
||||
matchedFillResults.right.takerAssetFilledAmount,
|
||||
matchedFillResults.takerFillAmount
|
||||
);
|
||||
require(
|
||||
!isRoundingError(
|
||||
matchedFillResults.left.makerAssetFilledAmount,
|
||||
amountSpentByLeft,
|
||||
1
|
||||
),
|
||||
ROUNDING_ERROR_TRANSFER_AMOUNTS
|
||||
);
|
||||
|
||||
// If the amount transferred from the right order is different than what is transferred, it is a rounding error amount.
|
||||
// Ensure this difference is negligible by dividing the values with each other. The result should equal to ~1.
|
||||
require(
|
||||
!isRoundingError(
|
||||
matchedFillResults.right.makerAssetFilledAmount,
|
||||
matchedFillResults.left.takerAssetFilledAmount,
|
||||
1
|
||||
),
|
||||
ROUNDING_ERROR_TRANSFER_AMOUNTS
|
||||
NEGATIVE_SPREAD_REQUIRED
|
||||
);
|
||||
}
|
||||
|
||||
@@ -201,21 +152,18 @@ contract MixinMatchOrders is
|
||||
/// The profit made by the leftOrder order goes to the taker (who matched the two orders).
|
||||
/// @param leftOrder First order to match.
|
||||
/// @param rightOrder Second order to match.
|
||||
/// @param leftOrderStatus Order status of left order.
|
||||
/// @param rightOrderStatus Order status of right order.
|
||||
/// @param leftOrderFilledAmount Amount of left order already filled.
|
||||
/// @param rightOrderFilledAmount Amount of right order already filled.
|
||||
/// @param 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(
|
||||
Order memory leftOrder,
|
||||
Order memory rightOrder,
|
||||
uint8 leftOrderStatus,
|
||||
uint8 rightOrderStatus,
|
||||
uint256 leftOrderFilledAmount,
|
||||
uint256 rightOrderFilledAmount
|
||||
LibOrder.Order memory leftOrder,
|
||||
LibOrder.Order memory rightOrder,
|
||||
uint256 leftOrderTakerAssetFilledAmount,
|
||||
uint256 rightOrderTakerAssetFilledAmount
|
||||
)
|
||||
internal
|
||||
returns (MatchedFillResults memory matchedFillResults)
|
||||
pure
|
||||
returns (LibFillResults.MatchedFillResults memory matchedFillResults)
|
||||
{
|
||||
// We settle orders at the exchange rate of the right order.
|
||||
// The amount saved by the left maker goes to the taker.
|
||||
@@ -226,71 +174,55 @@ contract MixinMatchOrders is
|
||||
// <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;
|
||||
uint256 leftTakerAssetAmountRemaining = safeSub(leftOrder.takerAssetAmount, leftOrderTakerAssetFilledAmount);
|
||||
uint256 rightTakerAssetAmountRemaining = safeSub(rightOrder.takerAssetAmount, rightOrderTakerAssetFilledAmount);
|
||||
uint256 leftTakerAssetFilledAmount;
|
||||
uint256 rightTakerAssetFilledAmount;
|
||||
if (
|
||||
safeMul(leftTakerAssetAmountRemaining, rightOrder.takerAssetAmount) <=
|
||||
safeMul(rightTakerAssetAmountRemaining, rightOrder.makerAssetAmount)
|
||||
) {
|
||||
// Left order will be fully filled: maximally fill left
|
||||
leftOrderAmountToFill = leftTakerAssetAmountRemaining;
|
||||
leftTakerAssetFilledAmount = 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(
|
||||
rightTakerAssetFilledAmount = getPartialAmount(
|
||||
rightOrder.takerAssetAmount,
|
||||
rightOrder.makerAssetAmount,
|
||||
leftOrderAmountToFill
|
||||
leftTakerAssetFilledAmount
|
||||
);
|
||||
} else {
|
||||
// Right order will be fully filled: maximally fill right
|
||||
rightOrderAmountToFill = rightTakerAssetAmountRemaining;
|
||||
rightTakerAssetFilledAmount = 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(
|
||||
leftTakerAssetFilledAmount = getPartialAmount(
|
||||
rightOrder.makerAssetAmount,
|
||||
rightOrder.takerAssetAmount,
|
||||
rightOrderAmountToFill
|
||||
rightTakerAssetFilledAmount
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate fill results for left order
|
||||
uint8 status;
|
||||
(status, matchedFillResults.left) = calculateFillResults(
|
||||
matchedFillResults.left = calculateFillResults(
|
||||
leftOrder,
|
||||
leftOrderStatus,
|
||||
leftOrderFilledAmount,
|
||||
leftOrderAmountToFill
|
||||
);
|
||||
require(
|
||||
status == uint8(Status.SUCCESS),
|
||||
FAILED_TO_CALCULATE_FILL_RESULTS_FOR_LEFT_ORDER
|
||||
leftTakerAssetFilledAmount
|
||||
);
|
||||
|
||||
// Calculate fill results for right order
|
||||
(status, matchedFillResults.right) = calculateFillResults(
|
||||
matchedFillResults.right = calculateFillResults(
|
||||
rightOrder,
|
||||
rightOrderStatus,
|
||||
rightOrderFilledAmount,
|
||||
rightOrderAmountToFill
|
||||
);
|
||||
require(
|
||||
status == uint8(Status.SUCCESS),
|
||||
FAILED_TO_CALCULATE_FILL_RESULTS_FOR_RIGHT_ORDER
|
||||
rightTakerAssetFilledAmount
|
||||
);
|
||||
|
||||
// Calculate amount given to taker
|
||||
matchedFillResults.takerFillAmount = safeSub(
|
||||
matchedFillResults.leftMakerAssetSpreadAmount = safeSub(
|
||||
matchedFillResults.left.makerAssetFilledAmount,
|
||||
matchedFillResults.right.takerAssetFilledAmount
|
||||
);
|
||||
|
||||
// Validate the fill results
|
||||
assertValidMatchResults(matchedFillResults);
|
||||
|
||||
// Return fill results
|
||||
return matchedFillResults;
|
||||
}
|
||||
|
||||
@@ -19,17 +19,16 @@
|
||||
pragma solidity ^0.4.24;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./libs/LibMath.sol";
|
||||
import "./libs/LibFillResults.sol";
|
||||
import "./libs/LibOrder.sol";
|
||||
import "./libs/LibExchangeErrors.sol";
|
||||
import "./mixins/MMatchOrders.sol";
|
||||
import "./mixins/MSettlement.sol";
|
||||
import "./mixins/MAssetProxyDispatcher.sol";
|
||||
import "./libs/LibOrder.sol";
|
||||
import "./libs/LibMath.sol";
|
||||
import "./libs/LibExchangeErrors.sol";
|
||||
import "./libs/LibFillResults.sol";
|
||||
import "./mixins/MMatchOrders.sol";
|
||||
|
||||
contract MixinSettlement is
|
||||
LibMath,
|
||||
LibFillResults,
|
||||
LibExchangeErrors,
|
||||
MMatchOrders,
|
||||
MSettlement,
|
||||
@@ -65,7 +64,7 @@ contract MixinSettlement is
|
||||
function settleOrder(
|
||||
LibOrder.Order memory order,
|
||||
address takerAddress,
|
||||
FillResults memory fillResults
|
||||
LibFillResults.FillResults memory fillResults
|
||||
)
|
||||
internal
|
||||
{
|
||||
@@ -104,7 +103,7 @@ contract MixinSettlement is
|
||||
LibOrder.Order memory leftOrder,
|
||||
LibOrder.Order memory rightOrder,
|
||||
address takerAddress,
|
||||
MatchedFillResults memory matchedFillResults
|
||||
LibFillResults.MatchedFillResults memory matchedFillResults
|
||||
)
|
||||
internal
|
||||
{
|
||||
@@ -125,7 +124,7 @@ contract MixinSettlement is
|
||||
leftOrder.makerAssetData,
|
||||
leftOrder.makerAddress,
|
||||
takerAddress,
|
||||
matchedFillResults.takerFillAmount
|
||||
matchedFillResults.leftMakerAssetSpreadAmount
|
||||
);
|
||||
|
||||
// Maker fees
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
|
||||
pragma solidity ^0.4.24;
|
||||
|
||||
import "../../utils/LibBytes/LibBytes.sol";
|
||||
import "./libs/LibExchangeErrors.sol";
|
||||
import "./mixins/MSignatureValidator.sol";
|
||||
import "./mixins/MTransactions.sol";
|
||||
import "./interfaces/IWallet.sol";
|
||||
import "./interfaces/IValidator.sol";
|
||||
import "./libs/LibExchangeErrors.sol";
|
||||
import "../../utils/LibBytes/LibBytes.sol";
|
||||
|
||||
contract MixinSignatureValidator is
|
||||
LibBytes,
|
||||
@@ -31,6 +31,9 @@ contract MixinSignatureValidator is
|
||||
MSignatureValidator,
|
||||
MTransactions
|
||||
{
|
||||
// Personal message headers
|
||||
string constant ETH_PERSONAL_MESSAGE = "\x19Ethereum Signed Message:\n32";
|
||||
string constant TREZOR_PERSONAL_MESSAGE = "\x19Ethereum Signed Message:\n\x41";
|
||||
|
||||
// Mapping of hash => signer => signed
|
||||
mapping (bytes32 => mapping (address => bool)) public preSigned;
|
||||
@@ -51,7 +54,7 @@ contract MixinSignatureValidator is
|
||||
{
|
||||
require(
|
||||
isValidSignature(hash, signer, signature),
|
||||
SIGNATURE_VALIDATION_FAILED
|
||||
INVALID_SIGNATURE
|
||||
);
|
||||
preSigned[hash][signer] = true;
|
||||
}
|
||||
@@ -85,8 +88,8 @@ contract MixinSignatureValidator is
|
||||
{
|
||||
// TODO: Domain separation: make hash depend on role. (Taker sig should not be valid as maker sig, etc.)
|
||||
require(
|
||||
signature.length >= 1,
|
||||
INVALID_SIGNATURE_LENGTH
|
||||
signature.length > 0,
|
||||
LENGTH_GT_0_REQUIRED
|
||||
);
|
||||
|
||||
// Pop last byte off of signature byte array.
|
||||
@@ -104,7 +107,7 @@ contract MixinSignatureValidator is
|
||||
// it an explicit option. This aids testing and analysis. It is
|
||||
// also the initialization value for the enum type.
|
||||
if (signatureType == SignatureType.Illegal) {
|
||||
revert(ILLEGAL_SIGNATURE_TYPE);
|
||||
revert(SIGNATURE_ILLEGAL);
|
||||
|
||||
// Always invalid signature.
|
||||
// Like Illegal, this is always implicitly available and therefore
|
||||
@@ -113,7 +116,7 @@ contract MixinSignatureValidator is
|
||||
} else if (signatureType == SignatureType.Invalid) {
|
||||
require(
|
||||
signature.length == 0,
|
||||
INVALID_SIGNATURE_LENGTH
|
||||
LENGTH_0_REQUIRED
|
||||
);
|
||||
isValid = false;
|
||||
return isValid;
|
||||
@@ -122,7 +125,7 @@ contract MixinSignatureValidator is
|
||||
} else if (signatureType == SignatureType.EIP712) {
|
||||
require(
|
||||
signature.length == 65,
|
||||
INVALID_SIGNATURE_LENGTH
|
||||
LENGTH_65_REQUIRED
|
||||
);
|
||||
v = uint8(signature[0]);
|
||||
r = readBytes32(signature, 1);
|
||||
@@ -135,13 +138,13 @@ contract MixinSignatureValidator is
|
||||
} else if (signatureType == SignatureType.EthSign) {
|
||||
require(
|
||||
signature.length == 65,
|
||||
INVALID_SIGNATURE_LENGTH
|
||||
LENGTH_65_REQUIRED
|
||||
);
|
||||
v = uint8(signature[0]);
|
||||
r = readBytes32(signature, 1);
|
||||
s = readBytes32(signature, 33);
|
||||
recovered = ecrecover(
|
||||
keccak256("\x19Ethereum Signed Message:\n32", hash),
|
||||
keccak256(abi.encodePacked(ETH_PERSONAL_MESSAGE, hash)),
|
||||
v,
|
||||
r,
|
||||
s
|
||||
@@ -160,7 +163,7 @@ contract MixinSignatureValidator is
|
||||
} else if (signatureType == SignatureType.Caller) {
|
||||
require(
|
||||
signature.length == 0,
|
||||
INVALID_SIGNATURE_LENGTH
|
||||
LENGTH_0_REQUIRED
|
||||
);
|
||||
isValid = signer == msg.sender;
|
||||
return isValid;
|
||||
@@ -208,13 +211,13 @@ contract MixinSignatureValidator is
|
||||
} else if (signatureType == SignatureType.Trezor) {
|
||||
require(
|
||||
signature.length == 65,
|
||||
INVALID_SIGNATURE_LENGTH
|
||||
LENGTH_65_REQUIRED
|
||||
);
|
||||
v = uint8(signature[0]);
|
||||
r = readBytes32(signature, 1);
|
||||
s = readBytes32(signature, 33);
|
||||
recovered = ecrecover(
|
||||
keccak256("\x19Ethereum Signed Message:\n\x41", hash),
|
||||
keccak256(abi.encodePacked(TREZOR_PERSONAL_MESSAGE, hash)),
|
||||
v,
|
||||
r,
|
||||
s
|
||||
@@ -233,6 +236,6 @@ contract MixinSignatureValidator is
|
||||
// that we currently support. In this case returning false
|
||||
// may lead the caller to incorrectly believe that the
|
||||
// signature was invalid.)
|
||||
revert(UNSUPPORTED_SIGNATURE_TYPE);
|
||||
revert(SIGNATURE_UNSUPPORTED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
*/
|
||||
pragma solidity ^0.4.24;
|
||||
|
||||
import "./libs/LibExchangeErrors.sol";
|
||||
import "./mixins/MSignatureValidator.sol";
|
||||
import "./mixins/MTransactions.sol";
|
||||
import "./libs/LibExchangeErrors.sol";
|
||||
|
||||
contract MixinTransactions is
|
||||
LibExchangeErrors,
|
||||
@@ -50,29 +50,29 @@ contract MixinTransactions is
|
||||
// Prevent reentrancy
|
||||
require(
|
||||
currentContextAddress == address(0),
|
||||
REENTRANCY_NOT_ALLOWED
|
||||
REENTRANCY_ILLEGAL
|
||||
);
|
||||
|
||||
// Calculate transaction hash
|
||||
bytes32 transactionHash = keccak256(
|
||||
bytes32 transactionHash = keccak256(abi.encodePacked(
|
||||
address(this),
|
||||
signer,
|
||||
salt,
|
||||
data
|
||||
);
|
||||
));
|
||||
|
||||
// Validate transaction has not been executed
|
||||
require(
|
||||
!transactions[transactionHash],
|
||||
DUPLICATE_TRANSACTION_HASH
|
||||
INVALID_TX_HASH
|
||||
);
|
||||
|
||||
// TODO: is SignatureType.Caller necessary if we make this check?
|
||||
// Transaction always valid if signer is sender of transaction
|
||||
if (signer != msg.sender) {
|
||||
// Validate signature
|
||||
require(
|
||||
isValidSignature(transactionHash, signer, signature),
|
||||
SIGNATURE_VALIDATION_FAILED
|
||||
INVALID_TX_SIGNATURE
|
||||
);
|
||||
|
||||
// Set the current transaction signer
|
||||
@@ -83,7 +83,7 @@ contract MixinTransactions is
|
||||
transactions[transactionHash] = true;
|
||||
require(
|
||||
address(this).delegatecall(data),
|
||||
TRANSACTION_EXECUTION_FAILED
|
||||
FAILED_EXECUTION
|
||||
);
|
||||
|
||||
// Reset current transaction signer
|
||||
|
||||
@@ -20,17 +20,15 @@ pragma solidity ^0.4.24;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "../../utils/LibBytes/LibBytes.sol";
|
||||
import "./mixins/MExchangeCore.sol";
|
||||
import "./libs/LibMath.sol";
|
||||
import "./libs/LibOrder.sol";
|
||||
import "./libs/LibFillResults.sol";
|
||||
import "./libs/LibExchangeErrors.sol";
|
||||
import "./mixins/MExchangeCore.sol";
|
||||
|
||||
contract MixinWrapperFunctions is
|
||||
SafeMath,
|
||||
LibBytes,
|
||||
LibMath,
|
||||
LibOrder,
|
||||
LibFillResults,
|
||||
LibExchangeErrors,
|
||||
MExchangeCore
|
||||
@@ -40,7 +38,7 @@ contract MixinWrapperFunctions is
|
||||
/// @param takerAssetFillAmount Desired amount of takerAsset to sell.
|
||||
/// @param signature Proof that order has been created by maker.
|
||||
function fillOrKillOrder(
|
||||
Order memory order,
|
||||
LibOrder.Order memory order,
|
||||
uint256 takerAssetFillAmount,
|
||||
bytes memory signature)
|
||||
public
|
||||
@@ -65,7 +63,7 @@ contract MixinWrapperFunctions is
|
||||
/// @param signature Proof that order has been created by maker.
|
||||
/// @return Amounts filled and fees paid by maker and taker.
|
||||
function fillOrderNoThrow(
|
||||
Order memory order,
|
||||
LibOrder.Order memory order,
|
||||
uint256 takerAssetFillAmount,
|
||||
bytes memory signature)
|
||||
public
|
||||
@@ -264,7 +262,7 @@ contract MixinWrapperFunctions is
|
||||
/// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders.
|
||||
/// @param signatures Proofs that orders have been created by makers.
|
||||
function batchFillOrders(
|
||||
Order[] memory orders,
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256[] memory takerAssetFillAmounts,
|
||||
bytes[] memory signatures)
|
||||
public
|
||||
@@ -283,7 +281,7 @@ contract MixinWrapperFunctions is
|
||||
/// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders.
|
||||
/// @param signatures Proofs that orders have been created by makers.
|
||||
function batchFillOrKillOrders(
|
||||
Order[] memory orders,
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256[] memory takerAssetFillAmounts,
|
||||
bytes[] memory signatures)
|
||||
public
|
||||
@@ -303,7 +301,7 @@ contract MixinWrapperFunctions is
|
||||
/// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders.
|
||||
/// @param signatures Proofs that orders have been created by makers.
|
||||
function batchFillOrdersNoThrow(
|
||||
Order[] memory orders,
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256[] memory takerAssetFillAmounts,
|
||||
bytes[] memory signatures)
|
||||
public
|
||||
@@ -323,7 +321,7 @@ contract MixinWrapperFunctions is
|
||||
/// @param signatures Proofs that orders have been created by makers.
|
||||
/// @return Amounts filled and fees paid by makers and taker.
|
||||
function marketSellOrders(
|
||||
Order[] memory orders,
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256 takerAssetFillAmount,
|
||||
bytes[] memory signatures)
|
||||
public
|
||||
@@ -366,7 +364,7 @@ contract MixinWrapperFunctions is
|
||||
/// @param signatures Proofs that orders have been signed by makers.
|
||||
/// @return Amounts filled and fees paid by makers and taker.
|
||||
function marketSellOrdersNoThrow(
|
||||
Order[] memory orders,
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256 takerAssetFillAmount,
|
||||
bytes[] memory signatures)
|
||||
public
|
||||
@@ -408,7 +406,7 @@ contract MixinWrapperFunctions is
|
||||
/// @param signatures Proofs that orders have been signed by makers.
|
||||
/// @return Amounts filled and fees paid by makers and taker.
|
||||
function marketBuyOrders(
|
||||
Order[] memory orders,
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256 makerAssetFillAmount,
|
||||
bytes[] memory signatures)
|
||||
public
|
||||
@@ -459,7 +457,7 @@ contract MixinWrapperFunctions is
|
||||
/// @param signatures Proofs that orders have been signed by makers.
|
||||
/// @return Amounts filled and fees paid by makers and taker.
|
||||
function marketBuyOrdersNoThrow(
|
||||
Order[] memory orders,
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256 makerAssetFillAmount,
|
||||
bytes[] memory signatures)
|
||||
public
|
||||
@@ -505,7 +503,7 @@ contract MixinWrapperFunctions is
|
||||
|
||||
/// @dev Synchronously cancels multiple orders in a single transaction.
|
||||
/// @param orders Array of order specifications.
|
||||
function batchCancelOrders(Order[] memory orders)
|
||||
function batchCancelOrders(LibOrder.Order[] memory orders)
|
||||
public
|
||||
{
|
||||
for (uint256 i = 0; i < orders.length; i++) {
|
||||
|
||||
@@ -37,17 +37,15 @@ contract IExchangeCore {
|
||||
function fillOrder(
|
||||
LibOrder.Order memory order,
|
||||
uint256 takerAssetFillAmount,
|
||||
bytes memory signature)
|
||||
bytes memory signature
|
||||
)
|
||||
public
|
||||
returns (LibFillResults.FillResults memory 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(LibOrder.Order memory order)
|
||||
public
|
||||
returns (bool);
|
||||
public;
|
||||
|
||||
/// @dev Gets information about an order: status, hash, and amount filled.
|
||||
/// @param order Order to gather information on.
|
||||
@@ -57,24 +55,4 @@ contract IExchangeCore {
|
||||
public
|
||||
view
|
||||
returns (LibOrder.OrderInfo memory orderInfo);
|
||||
|
||||
/// @dev Calculates amounts filled and fees paid by maker and taker.
|
||||
/// @param order to be filled.
|
||||
/// @param orderStatus Status of order to be filled.
|
||||
/// @param orderTakerAssetFilledAmount Amount of order already filled.
|
||||
/// @param takerAssetFillAmount Desired amount of order to fill by taker.
|
||||
/// @return status Return status of calculating fill amounts. Returns Status.SUCCESS on success.
|
||||
/// @return fillResults Amounts filled and fees paid by maker and taker.
|
||||
function calculateFillResults(
|
||||
LibOrder.Order memory order,
|
||||
uint8 orderStatus,
|
||||
uint256 orderTakerAssetFilledAmount,
|
||||
uint256 takerAssetFillAmount
|
||||
)
|
||||
public
|
||||
pure
|
||||
returns (
|
||||
uint8 status,
|
||||
LibFillResults.FillResults memory fillResults
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,43 +19,44 @@
|
||||
pragma solidity ^0.4.24;
|
||||
|
||||
contract LibExchangeErrors {
|
||||
/// Order validation errors ///
|
||||
string constant ORDER_UNFILLABLE = "ORDER_UNFILLABLE"; // Order cannot be filled.
|
||||
string constant INVALID_MAKER = "INVALID_MAKER"; // Invalid makerAddress.
|
||||
string constant INVALID_TAKER = "INVALID_TAKER"; // Invalid takerAddress.
|
||||
string constant INVALID_SENDER = "INVALID_SENDER"; // Invalid `msg.sender`.
|
||||
string constant INVALID_ORDER_SIGNATURE = "INVALID_ORDER_SIGNATURE"; // Signature validation failed.
|
||||
string constant ASSET_DATA_MISMATCH = "ASSET_DATA_MISMATCH"; // Asset data must be the same for each order.
|
||||
|
||||
/// fillOrder validation errors ///
|
||||
string constant INVALID_TAKER_AMOUNT = "INVALID_TAKER_AMOUNT"; // takerAssetFillAmount cannot equal 0.
|
||||
string constant ROUNDING_ERROR = "ROUNDING_ERROR"; // Rounding error greater than 0.1% of takerAssetFillAmount.
|
||||
|
||||
/// Signature validation errors ///
|
||||
string constant INVALID_SIGNATURE = "INVALID_SIGNATURE"; // Signature validation failed.
|
||||
string constant SIGNATURE_ILLEGAL = "SIGNATURE_ILLEGAL"; // Signature type is illegal.
|
||||
string constant SIGNATURE_UNSUPPORTED = "SIGNATURE_UNSUPPORTED"; // Signature type unsupported.
|
||||
|
||||
/// cancelOrdersUptTo errors ///
|
||||
string constant INVALID_NEW_MAKER_EPOCH = "INVALID_NEW_MAKER_EPOCH"; // Specified salt must be greater than or equal to existing makerEpoch.
|
||||
|
||||
// Core revert reasons
|
||||
string constant GT_ZERO_AMOUNT_REQUIRED = "Amount must be greater than 0.";
|
||||
string constant SIGNATURE_VALIDATION_FAILED = "Signature validation failed.";
|
||||
string constant INVALID_SENDER = "Invalid `msg.sender`.";
|
||||
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.";
|
||||
/// fillOrKillOrder errors ///
|
||||
string constant COMPLETE_FILL_FAILED = "COMPLETE_FILL_FAILED"; // Desired takerAssetFillAmount could not be completely filled.
|
||||
|
||||
// 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.";
|
||||
/// matchOrders errors ///
|
||||
string constant NEGATIVE_SPREAD_REQUIRED = "NEGATIVE_SPREAD_REQUIRED"; // Matched orders must have a negative spread.
|
||||
|
||||
// Transaction revert reasons
|
||||
string constant REENTRANCY_NOT_ALLOWED = "`executeTransaction` is not allowed to call itself recursively.";
|
||||
string constant DUPLICATE_TRANSACTION_HASH = "Transaction has already been executed.";
|
||||
string constant TRANSACTION_EXECUTION_FAILED = "Transaction execution failed.";
|
||||
/// Transaction errors ///
|
||||
string constant REENTRANCY_ILLEGAL = "REENTRANCY_ILLEGAL"; // Recursive reentrancy is not allowed.
|
||||
string constant INVALID_TX_HASH = "INVALID_TX_HASH"; // Transaction has already been executed.
|
||||
string constant INVALID_TX_SIGNATURE = "INVALID_TX_SIGNATURE"; // Signature validation failed.
|
||||
string constant FAILED_EXECUTION = "FAILED_EXECUTION"; // Transaction execution failed.
|
||||
|
||||
/// registerAssetProxy errors ///
|
||||
string constant ASSET_PROXY_MISMATCH = "ASSET_PROXY_MISMATCH"; // oldAssetProxy proxy does not match currentAssetProxy.
|
||||
string constant ASSET_PROXY_ID_MISMATCH = "ASSET_PROXY_ID_MISMATCH"; // newAssetProxyId does not match given assetProxyId.
|
||||
|
||||
// Wrapper revert reasons
|
||||
string constant COMPLETE_FILL_FAILED = "Desired fill amount could not be completely filled.";
|
||||
string constant ASSET_DATA_MISMATCH = "Asset data must be the same for each order.";
|
||||
|
||||
// Asset proxy dispatcher revert reasons
|
||||
string constant GT_ZERO_LENGTH_REQUIRED = "Length must be greater than 0.";
|
||||
string constant OLD_ASSET_PROXY_MISMATCH = "Old asset proxy does not match asset proxy at given id.";
|
||||
string constant NEW_ASSET_PROXY_MISMATCH = "New asset proxy id does not match given id.";
|
||||
|
||||
// Signature validator revert reasons
|
||||
string constant INVALID_SIGNATURE_LENGTH = "Invalid signature length.";
|
||||
string constant ILLEGAL_SIGNATURE_TYPE = "Illegal signature type.";
|
||||
string constant UNSUPPORTED_SIGNATURE_TYPE = "Unsupported signature type.";
|
||||
|
||||
// Order matching revert reasons
|
||||
string constant ASSET_MISMATCH_MAKER_TAKER = "Left order maker asset is different from right order taker asset.";
|
||||
string constant ASSET_MISMATCH_TAKER_MAKER = "Left order taker asset is different from right order maker asset.";
|
||||
string constant NEGATIVE_SPREAD = "Matched orders must have a positive spread.";
|
||||
string constant MISCALCULATED_TRANSFER_AMOUNTS = "A miscalculation occurred: the left maker would receive more than the right maker would spend.";
|
||||
string constant ROUNDING_ERROR_TRANSFER_AMOUNTS = "A rounding error occurred when calculating transfer amounts for matched orders.";
|
||||
string constant FAILED_TO_CALCULATE_FILL_RESULTS_FOR_LEFT_ORDER = "Failed to calculate fill results for left order.";
|
||||
string constant FAILED_TO_CALCULATE_FILL_RESULTS_FOR_RIGHT_ORDER = "Failed to calculate fill results for right order.";
|
||||
/// Length validation errors ///
|
||||
string constant LENGTH_GT_0_REQUIRED = "LENGTH_GT_0_REQUIRED"; // Byte array must have a length greater than 0.
|
||||
string constant LENGTH_0_REQUIRED = "LENGTH_1_REQUIRED"; // Byte array must have a length of 1.
|
||||
string constant LENGTH_65_REQUIRED = "LENGTH_66_REQUIRED"; // Byte array must have a length of 66.
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ contract LibFillResults is
|
||||
}
|
||||
|
||||
struct MatchedFillResults {
|
||||
LibFillResults.FillResults left;
|
||||
LibFillResults.FillResults right;
|
||||
uint256 takerFillAmount;
|
||||
FillResults left;
|
||||
FillResults right;
|
||||
uint256 leftMakerAssetSpreadAmount;
|
||||
}
|
||||
|
||||
/// @dev Adds properties of both FillResults instances.
|
||||
@@ -50,19 +50,4 @@ contract LibFillResults is
|
||||
totalFillResults.makerFeePaid = safeAdd(totalFillResults.makerFeePaid, singleFillResults.makerFeePaid);
|
||||
totalFillResults.takerFeePaid = safeAdd(totalFillResults.takerFeePaid, singleFillResults.takerFeePaid);
|
||||
}
|
||||
|
||||
/// @dev Returns a null fill results struct
|
||||
function getNullFillResults()
|
||||
internal
|
||||
pure
|
||||
returns (FillResults memory)
|
||||
{
|
||||
// returns zeroed out FillResults instance
|
||||
return FillResults({
|
||||
makerAssetFilledAmount: 0,
|
||||
takerAssetFilledAmount: 0,
|
||||
makerFeePaid: 0,
|
||||
takerFeePaid: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,26 +45,6 @@ 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.
|
||||
|
||||
@@ -20,11 +20,11 @@ pragma solidity ^0.4.24;
|
||||
|
||||
contract LibOrder {
|
||||
|
||||
bytes32 constant DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(
|
||||
bytes32 constant DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(abi.encodePacked(
|
||||
"DomainSeparator(address contract)"
|
||||
);
|
||||
));
|
||||
|
||||
bytes32 constant ORDER_SCHEMA_HASH = keccak256(
|
||||
bytes32 constant ORDER_SCHEMA_HASH = keccak256(abi.encodePacked(
|
||||
"Order(",
|
||||
"address makerAddress,",
|
||||
"address takerAddress,",
|
||||
@@ -39,7 +39,19 @@ contract LibOrder {
|
||||
"bytes makerAssetData,",
|
||||
"bytes takerAssetData,",
|
||||
")"
|
||||
);
|
||||
));
|
||||
|
||||
// A valid order remains fillable until it is expired, fully filled, or cancelled.
|
||||
// An order's state is unaffected by external factors, like account balances.
|
||||
enum OrderStatus {
|
||||
INVALID, // Default value
|
||||
INVALID_MAKER_ASSET_AMOUNT, // Order does not have a valid maker asset amount
|
||||
INVALID_TAKER_ASSET_AMOUNT, // Order does not have a valid taker asset amount
|
||||
FILLABLE, // Order is fillable
|
||||
EXPIRED, // Order has already expired
|
||||
FULLY_FILLED, // Order is fully filled
|
||||
CANCELLED // Order has been cancelled
|
||||
}
|
||||
|
||||
struct Order {
|
||||
address makerAddress;
|
||||
@@ -75,11 +87,11 @@ contract LibOrder {
|
||||
{
|
||||
// TODO: EIP712 is not finalized yet
|
||||
// Source: https://github.com/ethereum/EIPs/pull/712
|
||||
orderHash = keccak256(
|
||||
orderHash = keccak256(abi.encodePacked(
|
||||
DOMAIN_SEPARATOR_SCHEMA_HASH,
|
||||
keccak256(address(this)),
|
||||
keccak256(abi.encodePacked(address(this))),
|
||||
ORDER_SCHEMA_HASH,
|
||||
keccak256(
|
||||
keccak256(abi.encodePacked(
|
||||
order.makerAddress,
|
||||
order.takerAddress,
|
||||
order.feeRecipientAddress,
|
||||
@@ -90,10 +102,10 @@ contract LibOrder {
|
||||
order.takerFee,
|
||||
order.expirationTimeSeconds,
|
||||
order.salt,
|
||||
keccak256(order.makerAssetData),
|
||||
keccak256(order.takerAssetData)
|
||||
)
|
||||
);
|
||||
keccak256(abi.encodePacked(order.makerAssetData)),
|
||||
keccak256(abi.encodePacked(order.takerAssetData))
|
||||
))
|
||||
));
|
||||
return orderHash;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.4.24;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
contract LibStatus {
|
||||
|
||||
// Exchange Status Codes
|
||||
enum Status {
|
||||
/// Default Status ///
|
||||
INVALID, // General invalid status
|
||||
|
||||
/// General Exchange Statuses ///
|
||||
SUCCESS, // Indicates a successful operation
|
||||
ROUNDING_ERROR_TOO_LARGE, // Rounding error too large
|
||||
INSUFFICIENT_BALANCE_OR_ALLOWANCE, // Insufficient balance or allowance for token transfer
|
||||
TAKER_ASSET_FILL_AMOUNT_TOO_LOW, // takerAssetFillAmount is <= 0
|
||||
INVALID_SIGNATURE, // Invalid signature
|
||||
INVALID_SENDER, // Invalid sender
|
||||
INVALID_TAKER, // Invalid taker
|
||||
INVALID_MAKER, // Invalid maker
|
||||
|
||||
/// Order State Statuses ///
|
||||
// A valid order remains fillable until it is expired, fully filled, or cancelled.
|
||||
// An order's state is unaffected by external factors, like account balances.
|
||||
ORDER_INVALID_MAKER_ASSET_AMOUNT, // Order does not have a valid maker asset amount
|
||||
ORDER_INVALID_TAKER_ASSET_AMOUNT, // Order does not have a valid taker asset amount
|
||||
ORDER_FILLABLE, // Order is fillable
|
||||
ORDER_EXPIRED, // Order has already expired
|
||||
ORDER_FULLY_FILLED, // Order is fully filled
|
||||
ORDER_CANCELLED // Order has been cancelled
|
||||
}
|
||||
|
||||
event ExchangeStatus(uint8 indexed statusId, bytes32 indexed orderHash);
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import "../interfaces/IExchangeCore.sol";
|
||||
contract MExchangeCore is
|
||||
IExchangeCore
|
||||
{
|
||||
|
||||
// Fill event is emitted whenever an order is filled.
|
||||
event Fill(
|
||||
address indexed makerAddress,
|
||||
@@ -56,25 +55,6 @@ contract MExchangeCore is
|
||||
uint256 makerEpoch
|
||||
);
|
||||
|
||||
/// @dev Validates context for fillOrder. Succeeds or throws.
|
||||
/// @param order to be filled.
|
||||
/// @param orderStatus Status of order to be filled.
|
||||
/// @param orderHash Hash of order to be filled.
|
||||
/// @param takerAddress Address of order taker.
|
||||
/// @param orderTakerAssetFilledAmount Amount of order already filled.
|
||||
/// @param takerAssetFillAmount Desired amount of order to fill by taker.
|
||||
/// @param signature Proof that the orders was created by its maker.
|
||||
function assertValidFill(
|
||||
LibOrder.Order memory order,
|
||||
uint8 orderStatus,
|
||||
bytes32 orderHash,
|
||||
address takerAddress,
|
||||
uint256 orderTakerAssetFilledAmount,
|
||||
uint256 takerAssetFillAmount,
|
||||
bytes memory signature
|
||||
)
|
||||
internal;
|
||||
|
||||
/// @dev Updates state with results of a fill order.
|
||||
/// @param order that was filled.
|
||||
/// @param takerAddress Address of taker who filled the order.
|
||||
@@ -89,29 +69,55 @@ contract MExchangeCore is
|
||||
)
|
||||
internal;
|
||||
|
||||
/// @dev Validates context for cancelOrder. Succeeds or throws.
|
||||
/// @param order that was cancelled.
|
||||
/// @param orderStatus Status of order that was cancelled.
|
||||
/// @param orderHash Hash of order that was cancelled.
|
||||
function assertValidCancel(
|
||||
LibOrder.Order memory order,
|
||||
uint8 orderStatus,
|
||||
bytes32 orderHash
|
||||
)
|
||||
internal;
|
||||
|
||||
/// @dev Updates state with results of cancelling an order.
|
||||
/// State is only updated if the order is currently fillable.
|
||||
/// Otherwise, updating state would have no effect.
|
||||
/// @param order that was cancelled.
|
||||
/// @param orderStatus Status of order that was cancelled.
|
||||
/// @param orderHash Hash of order that was cancelled.
|
||||
/// @return stateUpdated Returns true only if state was updated.
|
||||
function updateCancelledState(
|
||||
LibOrder.Order memory order,
|
||||
uint8 orderStatus,
|
||||
bytes32 orderHash
|
||||
)
|
||||
internal;
|
||||
|
||||
/// @dev Validates context for fillOrder. Succeeds or throws.
|
||||
/// @param order to be filled.
|
||||
/// @param orderInfo Status, orderHash, and amount already filled of order.
|
||||
/// @param takerAddress Address of order taker.
|
||||
/// @param takerAssetFillAmount Desired amount of order to fill by taker.
|
||||
/// @param takerAssetFilledAmount Amount of takerAsset that will be filled.
|
||||
/// @param signature Proof that the orders was created by its maker.
|
||||
function assertValidFill(
|
||||
LibOrder.Order memory order,
|
||||
LibOrder.OrderInfo memory orderInfo,
|
||||
address takerAddress,
|
||||
uint256 takerAssetFillAmount,
|
||||
uint256 takerAssetFilledAmount,
|
||||
bytes memory signature
|
||||
)
|
||||
internal
|
||||
returns (bool stateUpdated);
|
||||
view;
|
||||
|
||||
|
||||
/// @dev Validates context for cancelOrder. Succeeds or throws.
|
||||
/// @param order to be cancelled.
|
||||
/// @param orderInfo OrderStatus, orderHash, and amount already filled of order.
|
||||
function assertValidCancel(
|
||||
LibOrder.Order memory order,
|
||||
LibOrder.OrderInfo memory orderInfo
|
||||
)
|
||||
internal
|
||||
view;
|
||||
|
||||
/// @dev Calculates amounts filled and fees paid by maker and taker.
|
||||
/// @param order to be filled.
|
||||
/// @param takerAssetFilledAmount Amount of takerAsset that will be filled.
|
||||
/// @return fillResults Amounts filled and fees paid by maker and taker.
|
||||
function calculateFillResults(
|
||||
LibOrder.Order memory order,
|
||||
uint256 takerAssetFilledAmount
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (LibFillResults.FillResults memory fillResults);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ pragma experimental ABIEncoderV2;
|
||||
|
||||
import "../libs/LibOrder.sol";
|
||||
import "../libs/LibFillResults.sol";
|
||||
import "./MExchangeCore.sol";
|
||||
import "../interfaces/IMatchOrders.sol";
|
||||
|
||||
contract MMatchOrders is
|
||||
@@ -34,12 +33,8 @@ contract MMatchOrders is
|
||||
LibOrder.Order memory leftOrder,
|
||||
LibOrder.Order memory rightOrder
|
||||
)
|
||||
internal;
|
||||
|
||||
/// @dev Validates matched fill results. Succeeds or throws.
|
||||
/// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
|
||||
function assertValidMatchResults(LibFillResults.MatchedFillResults memory matchedFillResults)
|
||||
internal;
|
||||
internal
|
||||
pure;
|
||||
|
||||
/// @dev Calculates fill amounts for the matched orders.
|
||||
/// Each order is filled at their respective price point. However, the calculations are
|
||||
@@ -47,19 +42,16 @@ contract MMatchOrders is
|
||||
/// The profit made by the leftOrder order goes to the taker (who matched the two orders).
|
||||
/// @param leftOrder First order to match.
|
||||
/// @param rightOrder Second order to match.
|
||||
/// @param leftOrderStatus Order status of left order.
|
||||
/// @param rightOrderStatus Order status of right order.
|
||||
/// @param leftOrderFilledAmount Amount of left order already filled.
|
||||
/// @param rightOrderFilledAmount Amount of right order already filled.
|
||||
/// @param 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,
|
||||
uint8 leftOrderStatus,
|
||||
uint8 rightOrderStatus,
|
||||
uint256 leftOrderFilledAmount,
|
||||
uint256 rightOrderFilledAmount
|
||||
uint256 leftOrderTakerAssetFilledAmount,
|
||||
uint256 rightOrderTakerAssetFilledAmount
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (LibFillResults.MatchedFillResults memory matchedFillResults);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
pragma solidity ^0.4.24;
|
||||
|
||||
import "../libs/LibOrder.sol";
|
||||
import "./MMatchOrders.sol";
|
||||
import "../libs/LibFillResults.sol";
|
||||
|
||||
contract MSettlement {
|
||||
|
||||
@@ -71,7 +71,7 @@ contract TestLibs is
|
||||
|
||||
function getOrderSchemaHash()
|
||||
public
|
||||
view
|
||||
pure
|
||||
returns (bytes32)
|
||||
{
|
||||
return ORDER_SCHEMA_HASH;
|
||||
@@ -79,7 +79,7 @@ contract TestLibs is
|
||||
|
||||
function getDomainSeparatorSchemaHash()
|
||||
public
|
||||
view
|
||||
pure
|
||||
returns (bytes32)
|
||||
{
|
||||
return DOMAIN_SEPARATOR_SCHEMA_HASH;
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
ContractName,
|
||||
ERC20BalancesByOwner,
|
||||
ERC721TokenIdsByOwner,
|
||||
ExchangeStatus,
|
||||
TransferAmountsByMatchOrders as TransferAmounts,
|
||||
} from '../utils/types';
|
||||
import { provider, web3Wrapper } from '../utils/web3_wrapper';
|
||||
|
||||
@@ -69,22 +69,14 @@ export interface Token {
|
||||
swarmHash: string;
|
||||
}
|
||||
|
||||
export enum ExchangeStatus {
|
||||
export enum OrderStatus {
|
||||
INVALID,
|
||||
SUCCESS,
|
||||
ROUNDING_ERROR_TOO_LARGE,
|
||||
INSUFFICIENT_BALANCE_OR_ALLOWANCE,
|
||||
TAKER_ASSET_FILL_AMOUNT_TOO_LOW,
|
||||
INVALID_SIGNATURE,
|
||||
INVALID_SENDER,
|
||||
INVALID_TAKER,
|
||||
INVALID_MAKER,
|
||||
ORDER_INVALID_MAKER_ASSET_AMOUNT,
|
||||
ORDER_INVALID_TAKER_ASSET_AMOUNT,
|
||||
ORDER_FILLABLE,
|
||||
ORDER_EXPIRED,
|
||||
ORDER_FULLY_FILLED,
|
||||
ORDER_CANCELLED,
|
||||
INVALID_MAKER_ASSET_AMOUNT,
|
||||
INVALID_TAKER_ASSET_AMOUNT,
|
||||
FILLABLE,
|
||||
EXPIRED,
|
||||
FULLY_FILLED,
|
||||
CANCELLED,
|
||||
}
|
||||
|
||||
export enum ContractName {
|
||||
|
||||
@@ -19,6 +19,7 @@ describe('Authorizable', () => {
|
||||
let notOwner: string;
|
||||
let address: string;
|
||||
let authorizable: MixinAuthorizableContract;
|
||||
|
||||
before(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
|
||||
@@ -15,7 +15,6 @@ import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c
|
||||
import {
|
||||
CancelContractEventArgs,
|
||||
ExchangeContract,
|
||||
ExchangeStatusContractEventArgs,
|
||||
FillContractEventArgs,
|
||||
} from '../../src/contract_wrappers/generated/exchange';
|
||||
import { artifacts } from '../../src/utils/artifacts';
|
||||
@@ -25,6 +24,7 @@ import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
|
||||
import { ERC721Wrapper } from '../../src/utils/erc721_wrapper';
|
||||
import { ExchangeWrapper } from '../../src/utils/exchange_wrapper';
|
||||
import { OrderFactory } from '../../src/utils/order_factory';
|
||||
import { orderUtils } from '../../src/utils/order_utils';
|
||||
import { ContractName, ERC20BalancesByOwner, ExchangeStatus } from '../../src/utils/types';
|
||||
import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
|
||||
|
||||
@@ -132,6 +132,7 @@ describe('Exchange core', () => {
|
||||
signedOrder = orderFactory.newSignedOrder();
|
||||
});
|
||||
|
||||
<<<<<<< HEAD
|
||||
it('should create an unfillable order', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
makerAssetAmount: new BigNumber(1001),
|
||||
@@ -164,6 +165,8 @@ describe('Exchange core', () => {
|
||||
expect(takerAssetFilledAmountAfter2).to.be.bignumber.equal(takerAssetFilledAmountAfter1);
|
||||
});
|
||||
|
||||
=======
|
||||
>>>>>>> Update Exchange statuses, revert instead of emmitting event on fill/cancel failures, and remove redundant logic in matchOrders
|
||||
it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
|
||||
@@ -550,37 +553,21 @@ describe('Exchange core', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should not change erc20Balances if an order is expired', async () => {
|
||||
it('should throw if an order is expired', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
|
||||
});
|
||||
await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress);
|
||||
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
expect(newBalances).to.be.deep.equal(erc20Balances);
|
||||
return expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith(
|
||||
constants.REVERT,
|
||||
);
|
||||
});
|
||||
|
||||
it('should log an error event if an order is expired', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
|
||||
});
|
||||
|
||||
const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress);
|
||||
expect(res.logs).to.have.length(1);
|
||||
const log = res.logs[0] as LogWithDecodedArgs<ExchangeStatusContractEventArgs>;
|
||||
const statusCode = log.args.statusId;
|
||||
expect(statusCode).to.be.equal(ExchangeStatus.ORDER_EXPIRED);
|
||||
});
|
||||
|
||||
it('should log an error event if no value is filled', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({});
|
||||
it('should throw if no value is filled', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder();
|
||||
await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress);
|
||||
|
||||
const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress);
|
||||
expect(res.logs).to.have.length(1);
|
||||
const log = res.logs[0] as LogWithDecodedArgs<ExchangeStatusContractEventArgs>;
|
||||
const statusCode = log.args.statusId;
|
||||
expect(statusCode).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
|
||||
return expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith(
|
||||
constants.REVERT,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -618,12 +605,11 @@ describe('Exchange core', () => {
|
||||
|
||||
it('should be able to cancel a full order', async () => {
|
||||
await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress);
|
||||
await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
|
||||
takerAssetFillAmount: signedOrder.takerAssetAmount.div(2),
|
||||
});
|
||||
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
expect(newBalances).to.be.deep.equal(erc20Balances);
|
||||
return expect(
|
||||
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
|
||||
takerAssetFillAmount: signedOrder.takerAssetAmount.div(2),
|
||||
}),
|
||||
).to.be.rejectedWith(constants.REVERT);
|
||||
});
|
||||
|
||||
it('should log 1 event with correct arguments', async () => {
|
||||
@@ -641,26 +627,39 @@ describe('Exchange core', () => {
|
||||
expect(orderHashUtils.getOrderHashHex(signedOrder)).to.be.equal(logArgs.orderHash);
|
||||
});
|
||||
|
||||
it('should log an error if already cancelled', async () => {
|
||||
it('should throw if already cancelled', async () => {
|
||||
await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress);
|
||||
|
||||
const res = await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress);
|
||||
expect(res.logs).to.have.length(1);
|
||||
const log = res.logs[0] as LogWithDecodedArgs<ExchangeStatusContractEventArgs>;
|
||||
const statusCode = log.args.statusId;
|
||||
expect(statusCode).to.be.equal(ExchangeStatus.ORDER_CANCELLED);
|
||||
return expect(exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress)).to.be.rejectedWith(
|
||||
constants.REVERT,
|
||||
);
|
||||
});
|
||||
|
||||
it('should log error if order is expired', async () => {
|
||||
it('should throw if order is expired', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
|
||||
});
|
||||
return expect(exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress)).to.be.rejectedWith(
|
||||
constants.REVERT,
|
||||
);
|
||||
});
|
||||
|
||||
const res = await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress);
|
||||
expect(res.logs).to.have.length(1);
|
||||
const log = res.logs[0] as LogWithDecodedArgs<ExchangeStatusContractEventArgs>;
|
||||
const statusCode = log.args.statusId;
|
||||
expect(statusCode).to.be.equal(ExchangeStatus.ORDER_EXPIRED);
|
||||
it('should throw if rounding error is greater than 0.1%', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
makerAssetAmount: new BigNumber(1001),
|
||||
takerAssetAmount: new BigNumber(3),
|
||||
});
|
||||
|
||||
const fillTakerAssetAmount1 = new BigNumber(2);
|
||||
await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
|
||||
takerAssetFillAmount: fillTakerAssetAmount1,
|
||||
});
|
||||
|
||||
const fillTakerAssetAmount2 = new BigNumber(1);
|
||||
return expect(
|
||||
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
|
||||
takerAssetFillAmount: fillTakerAssetAmount2,
|
||||
}),
|
||||
).to.be.rejectedWith(constants.REVERT);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c
|
||||
import {
|
||||
CancelContractEventArgs,
|
||||
ExchangeContract,
|
||||
ExchangeStatusContractEventArgs,
|
||||
FillContractEventArgs,
|
||||
} from '../../src/contract_wrappers/generated/exchange';
|
||||
import { artifacts } from '../../src/utils/artifacts';
|
||||
@@ -30,8 +29,8 @@ import {
|
||||
ContractName,
|
||||
ERC20BalancesByOwner,
|
||||
ERC721TokenIdsByOwner,
|
||||
ExchangeStatus,
|
||||
OrderInfo,
|
||||
OrderStatus,
|
||||
} from '../../src/utils/types';
|
||||
import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
|
||||
|
||||
@@ -186,10 +185,10 @@ describe('matchOrders', () => {
|
||||
);
|
||||
// Verify left order was fully filled
|
||||
const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
|
||||
expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
|
||||
expect(leftOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
|
||||
// Verify right order was fully filled
|
||||
const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
|
||||
expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
|
||||
expect(rightOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
|
||||
});
|
||||
|
||||
it('should transfer the correct amounts when orders completely fill each other and taker doesnt take a profit', async () => {
|
||||
@@ -227,10 +226,10 @@ describe('matchOrders', () => {
|
||||
);
|
||||
// Verify left order was fully filled
|
||||
const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
|
||||
expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
|
||||
expect(leftOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
|
||||
// Verify right order was fully filled
|
||||
const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
|
||||
expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
|
||||
expect(rightOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
|
||||
// Verify taker did not take a profit
|
||||
expect(takerInitialBalances).to.be.deep.equal(
|
||||
newERC20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress],
|
||||
@@ -265,10 +264,10 @@ describe('matchOrders', () => {
|
||||
);
|
||||
// Verify left order was fully filled
|
||||
const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
|
||||
expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
|
||||
expect(leftOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
|
||||
// Verify right order was partially filled
|
||||
const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
|
||||
expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
|
||||
expect(rightOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FILLABLE);
|
||||
});
|
||||
|
||||
it('should transfer the correct amounts when right order is completely filled and left order is partially filled', async () => {
|
||||
@@ -299,10 +298,10 @@ describe('matchOrders', () => {
|
||||
);
|
||||
// Verify left order was partially filled
|
||||
const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
|
||||
expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
|
||||
expect(leftOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FILLABLE);
|
||||
// Verify right order was fully filled
|
||||
const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
|
||||
expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
|
||||
expect(rightOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
|
||||
});
|
||||
|
||||
it('should transfer the correct amounts when consecutive calls are used to completely fill the left order', async () => {
|
||||
@@ -338,10 +337,10 @@ describe('matchOrders', () => {
|
||||
);
|
||||
// Verify left order was partially filled
|
||||
const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
|
||||
expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
|
||||
expect(leftOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FILLABLE);
|
||||
// Verify right order was fully filled
|
||||
const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
|
||||
expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
|
||||
expect(rightOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
|
||||
// Construct second right order
|
||||
// Note: This order needs makerAssetAmount=90/takerAssetAmount=[anything <= 45] to fully fill the right order.
|
||||
// However, we use 100/50 to ensure a partial fill as we want to go down the "left fill"
|
||||
@@ -368,10 +367,10 @@ describe('matchOrders', () => {
|
||||
);
|
||||
// Verify left order was fully filled
|
||||
const leftOrderInfo2: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
|
||||
expect(leftOrderInfo2.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
|
||||
expect(leftOrderInfo2.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
|
||||
// Verify second right order was partially filled
|
||||
const rightOrderInfo2: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight2);
|
||||
expect(rightOrderInfo2.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
|
||||
expect(rightOrderInfo2.orderStatus as OrderStatus).to.be.equal(OrderStatus.FILLABLE);
|
||||
});
|
||||
|
||||
it('should transfer the correct amounts when consecutive calls are used to completely fill the right order', async () => {
|
||||
@@ -408,10 +407,10 @@ describe('matchOrders', () => {
|
||||
);
|
||||
// Verify left order was partially filled
|
||||
const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
|
||||
expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
|
||||
expect(leftOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
|
||||
// Verify right order was fully filled
|
||||
const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
|
||||
expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
|
||||
expect(rightOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FILLABLE);
|
||||
// Create second left order
|
||||
// Note: This order needs makerAssetAmount=96/takerAssetAmount=48 to fully fill the right order.
|
||||
// However, we use 100/50 to ensure a partial fill as we want to go down the "right fill"
|
||||
@@ -441,10 +440,10 @@ describe('matchOrders', () => {
|
||||
);
|
||||
// Verify second left order was partially filled
|
||||
const leftOrderInfo2: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft2);
|
||||
expect(leftOrderInfo2.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
|
||||
expect(leftOrderInfo2.orderStatus as OrderStatus).to.be.equal(OrderStatus.FILLABLE);
|
||||
// Verify right order was fully filled
|
||||
const rightOrderInfo2: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
|
||||
expect(rightOrderInfo2.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
|
||||
expect(rightOrderInfo2.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
|
||||
});
|
||||
|
||||
it('should transfer the correct amounts if fee recipient is the same across both matched orders', async () => {
|
||||
@@ -790,10 +789,10 @@ describe('matchOrders', () => {
|
||||
);
|
||||
// Verify left order was fully filled
|
||||
const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
|
||||
expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
|
||||
expect(leftOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
|
||||
// Verify right order was fully filled
|
||||
const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
|
||||
expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
|
||||
expect(rightOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
|
||||
});
|
||||
|
||||
it('should transfer correct amounts when right order maker asset is an ERC721 token', async () => {
|
||||
@@ -825,10 +824,10 @@ describe('matchOrders', () => {
|
||||
);
|
||||
// Verify left order was fully filled
|
||||
const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
|
||||
expect(leftOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
|
||||
expect(leftOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
|
||||
// Verify right order was fully filled
|
||||
const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
|
||||
expect(rightOrderInfo.orderStatus as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
|
||||
expect(rightOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
|
||||
});
|
||||
});
|
||||
}); // tslint:disable-line:max-file-line-count
|
||||
|
||||
@@ -194,9 +194,9 @@ describe('Exchange transactions', () => {
|
||||
|
||||
it('should cancel the order when signed by maker and called by sender', async () => {
|
||||
await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress);
|
||||
const res = await exchangeWrapper.fillOrderAsync(signedOrder, senderAddress);
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
expect(newBalances).to.deep.equal(erc20Balances);
|
||||
return expect(exchangeWrapper.fillOrderAsync(signedOrder, senderAddress)).to.be.rejectedWith(
|
||||
constants.REVERT,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -959,7 +959,7 @@ describe('Exchange wrappers', () => {
|
||||
const takerAssetCancelAmounts = _.map(signedOrders, signedOrder => signedOrder.takerAssetAmount);
|
||||
await exchangeWrapper.batchCancelOrdersAsync(signedOrders, makerAddress);
|
||||
|
||||
await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, {
|
||||
await exchangeWrapper.batchFillOrdersNoThrowAsync(signedOrders, takerAddress, {
|
||||
takerAssetFillAmounts: takerAssetCancelAmounts,
|
||||
});
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
|
||||
Reference in New Issue
Block a user