Skip to content
This repository was archived by the owner on Jul 9, 2021. It is now read-only.

Atomic Order Matching #572

Merged
merged 31 commits into from
May 21, 2018
Merged

Atomic Order Matching #572

merged 31 commits into from
May 21, 2018

Conversation

hysz
Copy link
Contributor

@hysz hysz commented May 5, 2018

Description

Atomic Order Matching - Work in Progress.

Match two complementary orders that have a positive spread. Each order is filled at their respective price point. However, the calculations are carried out as though the orders are both being filled at the right order's price point. The profit made by the left order goes to the taker (who matched the two orders).

This PR is based on two earlier PR's by Remco:

In this PR:

  • Refactoring of exchange core
  • Atomic order matching mixin
  • Lots of test cases for atomic order matching: ERC20 / ERC721
  • Several detailed comments inline
  • Rebased against Solidity v0.4.23

Todo:

  • I think we have a good test set for the order matching. With the refactoring in this PR, we could write more granular test cases for the exchange core.

Motivation and Context

The exchange core functions were getting really long. It was good to refactor/split these up. The refactor was also necessary to write a clean implementation of order matching, since there's a lot of overlap between functions.

Atomic order matching is useful for arbitrage and exchanges that take profit from the spread.

How Has This Been Tested?

Test cases were created for new functionality. We did not change any existing functionality. Tests still pass.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • Change requires a change to the documentation.
  • Added tests to cover my changes.
  • Added new entries to the relevant CHANGELOG.jsons.
  • Labeled this PR with the 'WIP' label if it is a work in progress.
  • Labeled this PR with the labels corresponding to the changed package.

Copy link
Member

@abandeali1 abandeali1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work! Going to have to give this another pass tomorrow.

/// @return status Status of order. Statuses are defined in the LibStatus.Status struct.
/// @return orderHash Keccak-256 EIP712 hash of the order.
/// @return filledAmount Amount of order that has been filled.
function getOrderInfo(Order memory order)
public
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be internal.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally set it up this way so that it could be used by our tests -- the atomic order matching tests make extensive use of it. Is there any harm in keeping it public?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ Could also be used by the forwarding contract. It should probably also be moved into LibOrder. Then we could use the [ContractName]Test pattern you used in the Solidity 0.4.23 PR.

returns (
uint8 orderStatus,
bytes32 orderHash,
uint256 filledAmount)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename this takerAssetFilledAmount for consistency.

{
// Ensure order status is not invalid
if (orderStatus == uint8(Status.INVALID)) {
emit ExchangeStatus(uint8(orderStatus), orderHash);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert reverts all state, including events, so this patten won't work. Instead, let's just use a require with a reason.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I'll replace with require(..., [message]) after rebasing against v0.4.23

/// @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 calculateFillAmounts(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rename this to calculateFillResults.

FillResults memory fillResults)
{
// Fill Amount must be greater than 0
if(takerAssetFillAmount <= 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing a space after if here.

status = uint8(Status.ROUNDING_ERROR_TOO_LARGE);
return (status, matchedFillOrderAmounts);
}
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of duplicate logic in this else block. Can we break this out into another function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Order memory leftOrder,
Order memory rightOrder,
MatchedOrderFillAmounts memory matchedFillOrderAmounts,
address taker)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

takerAddress

bytes rightSignature)
public
returns (
MatchedOrderFillAmounts memory matchedFillOrderAmounts)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we put this on the same line as returns?

/// @param rightSignature Proof that order was created by the right maker.
/// @return leftFillResults Amounts filled and fees paid by maker and taker of left order.
/// @return leftFillResults Amounts filled and fees paid by maker and taker of right order.
function matchOrders(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! This function is very readable/easy to understand.

MatchedOrderFillAmounts memory matchedFillOrderAmounts)
{
// Get left status
OrderInfo memory leftOrderInfo;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a struct because of the stack variable limitations?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah =\ ... I left a note on the struct definition.

@hysz hysz force-pushed the feature/contracts/atomicMatching branch from 6d754f8 to 68f6816 Compare May 10, 2018 21:43
@hysz hysz changed the title [WIP] Atomic Order Matching Atomic Order Matching May 11, 2018
@@ -48,64 +51,108 @@ contract MixinExchangeCore is
// Orders with a salt less than their maker's epoch are considered cancelled
mapping (address => uint256) public makerEpoch;

/// @dev Cancels all orders reated by sender with a salt less than or equal to the specified salt value.
/// @param salt Orders created with a salt less or equal to this value will be cancelled.
function cancelOrdersUpTo(uint256 salt)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The order for functions within our contracts should be external, public, and then internal. WIthin each visibility section, they should be ordered by how frequently we think they will be used.

/// @return status Status of order. Statuses are defined in the LibStatus.Status struct.
/// @return orderHash Keccak-256 EIP712 hash of the order.
/// @return takerAssetFilledAmount Amount of order that has been filled.
function getOrderInfo(Order memory order)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this internal.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Knowing the amount remaining to be filled is needed in the forwarding contract. Is there an alternative? In the mean time I have been keeping getUnavailable around in my PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dekz - I chatted offline with @abandeali1 about this & we decided to keep it public. Hoping it can be useful in the forwarding contract, for getting amount to fill & order state.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep either this or calculateFillResults would be ideal. If it was calculateFillResults it requires getOrderInfo, but would save also save us duplicating this calculation (and and edge cases) in the forwarding contract.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be in favor of making calculateFillResults public if it can simplify the forwarding contract. @abandeali1 Thoughts?

bytes32 orderHash = getOrderHash(order);
// Compute the order hash and fetch filled amount
orderHash = getOrderHash(order);
takerAssetFilledAmount = filled[orderHash];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make this check right before we actually need it. For the sake of efficiency, the checks should be ordered:

  1. makerAssetAmount
  2. takerAssetAmount
  3. block.timestamp
  4. cancelled
  5. makerEpoch
    (For all of the above, takerAssetFilledAmount will be 0)
  6. takerAssetFilledAmount (don't actually check the filled mapping until right before this check)

/// @param takerAddress Address of order taker.
/// @param takerAssetFillAmount Desired amount of order to fill by taker.
function validateFillOrderContextOrRevert(
Order memory order,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's group these params by type.

/// @param signature Proof that the orders was created by its maker.
/// @param takerAddress Address of order taker.
/// @param takerAssetFillAmount Desired amount of order to fill by taker.
function validateFillOrderContextOrRevert(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this name is a little confusing. Maybe just validateFillOrRevert?

rightOrderFilledAmount,
rightOrderAmountToFill);
if (status != uint8(Status.SUCCESS)) {
return (status, matchedFillResults);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, if the left status is successful but the right is not, matchedFillResults will be inaccurate here.

) = getOrderInfo(rightOrder);
if (rightOrderInfo.orderStatus != uint8(Status.ORDER_FILLABLE)) {
emit ExchangeStatus(uint8(rightOrderInfo.orderStatus), rightOrderInfo.orderHash);
return matchedFillResults;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above - matchedFillResults can be inaccurate here.

// Update exchange state
updateFilledState(
leftOrder,
rightOrder.makerAddress,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should just use takerAddress for both updateFilledState calls.


// leftOrder.MakerAsset == rightOrder.TakerAsset
// Taker should be left with a positive balance (the spread)
dispatchTransferFrom(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, transfers look like:
leftMaker => taker
taker => rightMaker
rightMaker => leftMaker
The spread is always coming from the leftMaker.

However, we never want the taker to actually take custody of the tokens (other than the spread). So what would be more desirable would be:
leftMaker => rightMaker
rightMaker => leftMaker
leftMaker => taker

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, this feels cleaner 👍

returns (
uint8 orderStatus,
bytes32 orderHash,
uint256 takerAssetFilledAmount);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ending parenthesis should be on a newline when we have multiple line returns.

@hysz hysz force-pushed the feature/contracts/atomicMatching branch 2 times, most recently from d8e0153 to aca55fb Compare May 12, 2018 21:04
MixinTransactions()
MixinAssetProxyDispatcher()
MixinWrapperFunctions()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we perhaps order these alphabetically?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So there actually is (now) a method to this ordering: contracts are instantiated in the order they are inherited. This makes it easy to update by simply copying the inheritance list to the constructor list. It also makes it easy to verify that all constructors are being called.

I've added a comment above the constructor to clarify this.

(also worth noting that the inheritance list must be ordered by least-to-most derived)

if (cancelled[orderHash]) {
emit ExchangeError(uint8(Errors.ORDER_CANCELLED), orderHash);
// Either our context is valid or we revert
validateFillOrRevert(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is analogous to an assertion, do we want to rename it to assertValidFill?

order.takerAssetAmount,
order.makerAssetAmount))
{
status = uint8(Status.ROUNDING_ERROR_TOO_LARGE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or perhaps don't set it to fillResults.takerAssetFilledAmount until before returning below?

/// 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 order was in a valid, but
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wording: if the order was order was

(orderStatus, orderHash, ) = getOrderInfo(order);

// Validate context
validateCancelOrRevert(order, orderStatus, orderHash);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertValidCancel

matchedFillResults.right
);

// Return results
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think we can remove this comment.

@@ -225,6 +225,30 @@ export class ExchangeWrapper {
const filledAmount = new BigNumber(await this._exchange.filled.callAsync(orderHashHex));
return filledAmount;
}
public async getOrderInfoAsync(
signedOrder: SignedOrder,
): Promise<[number /* orderStatus */, string /* orderHash */, BigNumber /* orderTakerAssetAmountFilled */]> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these comments show up in-line in text editors? If not, why don't we marshal the array into an object with named parameters? I don't think keeping the exact same interface as the contract is important here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented above about this contract function returning an OrderInfo struct, if it did it would also be typed in the generated contract wrappers and would bubble up here.

private static _calculateExpectedBalances(
signedOrderLeft: SignedOrder,
signedOrderRight: SignedOrder,
feeTokenAddress: string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't the feeTokenAddress to retrieved from the SignedOrder?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Negative; the fee token address is not currently included the order struct.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yep, you're right. Isn't it always ZRX address? Why not hard-code it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually yeah that's a good point, we could just pass it into the constructor once rather than to each call. (Updated!)

expect(expectedBalancesMatchRealBalances).to.be.true();
return [newERC20BalancesByOwner, newERC721TokenIdsByOwner];
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Omit newline

signedOrderLeft: SignedOrder,
signedOrderRight: SignedOrder,
expectedLeftOrderFillAmoutBeforeMatch: BigNumber,
expectedRightOrderFillAmoutBeforeMatch: BigNumber,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I fully understand this variable name. So it's the expected fill amount but before the match?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that is a confusing name... I've updated to simply orderFilledAmountLeft and orderFilledAmountRight. These seem more sensible and align with the naming scheme in the smart contracts.

/// @dev Fills the input order.
/// @param order Order struct containing order specifications.
/// @param takerAssetFillAmount Desired amount of takerAsset to sell.
/// @param takerAssetFillAmount Desired amount of takerToken to sell.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be takerAsset, maybe from the older PR?

Order memory order,
address takerAddress,
bytes32 orderHash,
uint256 takerAssetFilledAmount,
Copy link
Member

@dekz dekz May 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this naming confused me a bit, though the above comment does help.
How about orderFilledAmount to differentiate fillResults.takerAssetFilledAmount, that seems to match OrderInfo.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yeah that is confusing. I've updated the wording here and elsewhere for consistency.


// Validate the order
// Ensure order is valid
// An order can only be cancelled if it is Status.FILLABLE;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't check balances in the contract so it should still be FILLABLE even if the funds were moved.

For context @hysz in 0x.js we often determine the state based off two or more inputs, state(order,maker,taker). So we do get the scenario where the order is fillable, but the maker/taker make it unfillable due to insufficient funds.

/// @return status Status of order. Statuses are defined in the LibStatus.Status struct.
/// @return orderHash Keccak-256 EIP712 hash of the order.
/// @return takerAssetFilledAmount Amount of order that has been filled.
function getOrderInfo(Order memory order)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep either this or calculateFillResults would be ideal. If it was calculateFillResults it requires getOrderInfo, but would save also save us duplicating this calculation (and and edge cases) in the forwarding contract.

bytes leftSignature,
bytes rightSignature
)
public
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be external

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solidity does not yet support structs as parameters for external functions.
See Solidity Issues #3199 and #1603. I've added a todo in the comments.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ohh mannnnn

LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder,
LibFillResults.MatchedFillResults memory matchedFillResults,
address takerAddress
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we refactor this to have a similar parameter order to settleOrder, so takerAddress before fillResults?

encodeERC20ProxyData(tokenAddress: string): string {
const encodedAssetProxyId = assetProxyUtils.encodeAssetProxyId(AssetProxyId.ERC20);
const encodedAddress = assetProxyUtils.encodeAddress(tokenAddress);
const encodedMetadata = Buffer.concat([encodedAssetProxyId, encodedAddress]);
const encodedMetadataHex = ethUtil.bufferToHex(encodedMetadata);
return encodedMetadataHex;
},
decodeERC20ProxyData(proxyData: string): string /* tokenAddress */ {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like if this had a type which includes the asset proxy Id. And also include a decodeProxyData which returns id, token address (optional) and data (optional). Forwarding wrapper currently switches on the asset id and has to pull it out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated decodeERC20ProxyData & decodeERC721ProxyData accordingly. Also see the newly created function decodeProxyData, which uses the generalized form you requested.

function getOrderInfo(LibOrder.Order memory order)
public
view
returns (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason this doesn't return an OrderInfo struct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea. This struct was originally created as a workaround for the stack limit. But yeah, it totally makes sense to just return it rather than reinterpret the output.

@@ -225,6 +225,30 @@ export class ExchangeWrapper {
const filledAmount = new BigNumber(await this._exchange.filled.callAsync(orderHashHex));
return filledAmount;
}
public async getOrderInfoAsync(
signedOrder: SignedOrder,
): Promise<[number /* orderStatus */, string /* orderHash */, BigNumber /* orderTakerAssetAmountFilled */]> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented above about this contract function returning an OrderInfo struct, if it did it would also be typed in the generated contract wrappers and would bubble up here.

{ from },
);
const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't needed as it is performed in _getTxWithDecodedExchangeLogsAsync

Copy link
Member

@abandeali1 abandeali1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple small changes, but this looks soooo clean overall.

if (cancelled[orderHash]) {
emit ExchangeError(uint8(Errors.ORDER_CANCELLED), orderHash);
// Compute proportional fill amounts
uint8 status;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 0.4.24, we can do (uint8 status, fillResult) = ...
Might just be worth updating to the newest version so that we don't have to adjust the styling again later.

takerAssetFillAmount
);
if (status != uint8(Status.SUCCESS)) {
emit ExchangeStatus(uint8(status), orderInfo.orderHash);
return fillResults;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idea I just thought of - in order to be explicit here, maybe it's worth returning a null fillResults struct.

Would need something like this (until Solidity adds support for constant structs):

function getNullFillResults() internal returns (FillResults memory) {
    // returns zeroed out FillResults instance
    return FillResults({
        ...
    });
}

internal
returns (bool stateUpdated)
{
// Ensure order is fillable (otherwise cancelling does nothing)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple reasons why we have this:

  • Cheaper to not update state for an invalid cancel.
  • Could see external contracts using the filled/cancelled mappings as on-chain proofs down the line, and having fake cancels could potentially complicate that.

Don't think it's really a big deal either way, but I'm in favor of leaving it in since it is a bit more efficient.

);

// Compute proportional fill amounts
uint8 status;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Solidity 0.4.24, we can write (uint8 status, fillResults) = ...

Might as well upgrade so we don't have to change this later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this only works if we are declaring all variables simultaneously in the tuple.


// Fetch filled amount and validate order availability
orderInfo.orderFilledAmount = filled[orderInfo.orderHash];
if (orderInfo.orderFilledAmount >= order.takerAssetAmount) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there ever a case where orderInfo.orderFilledAmount > order.takerAssetAmount?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory there should not be. But in practice there could be a rounding error amount.


/// @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 assertValidMatch(MatchedFillResults memory matchedFillResults)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer not to overload this function. How about assertValidMatchOrders and assertValidMatchResults?

}

// Calculate fill results for left order
uint8 status;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Combine these lines with Solidity 0.4.24

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this only works if we are declaring all variables simultaneously in the tuple.

matchedFillResults.right.takerAssetFilledAmount,
matchedFillResults.takerFillAmount
);
require(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What miscalculation needs to happen in order for this to evaluate to false? Is this something we expect to ever hit, or are we just being extra safe?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we expect this to sometimes be hit, we should add tests for that scenario.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a sanity check. And it's actually covered by the subsequent rounding error test. I'll remove this.

function matchOrders(
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder,
bytes leftSignature,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bytes memory again

// Keccak-256 EIP712 hash of the order
bytes32 orderHash;
// Amount of order that has been filled
uint256 orderFilledAmount;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's verbose, but I prefer orderTakerAssetFilledAmount here.

@hysz hysz force-pushed the feature/contracts/atomicMatching branch from 0aa81e4 to cc16e2c Compare May 18, 2018 18:39
hysz added 19 commits May 18, 2018 17:01
…ults. I like this better than just logging an error and failing silently.
…stinguish between fill results and order state
…, which should be useful for the forwarding contract code.
…nstant, we dont need to pass it in on each call.
@hysz hysz force-pushed the feature/contracts/atomicMatching branch from 8b4565a to f4ebbfa Compare May 19, 2018 00:08
function assertValidCancel(
Order memory order,
uint8 orderStatus,
bytes32 orderHash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertValidFill takes in the taker address, thoughts on passing it via parameters for consistency in assertValidCancel?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be cleaner. It would also make the function pure. assertValidFill is also pure, except for calling isValidSignature.

hysz added 2 commits May 21, 2018 13:54
@hysz hysz merged commit d460c0e into v2-prototype May 21, 2018
@recmo recmo mentioned this pull request May 28, 2018
9 tasks
@hysz hysz deleted the feature/contracts/atomicMatching branch March 16, 2019 04:36
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants