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

Add marketBuy/SellOrdersFillOrKill() to Exchange #2075

Merged
merged 28 commits into from
Aug 21, 2019

Conversation

dorothy-zbornak
Copy link
Contributor

@dorothy-zbornak dorothy-zbornak commented Aug 16, 2019

Description

This PR introduces two new functions to the Exchange's MixinWrapperFunctions, which is the subject of ZEIP-50:

  • marketSellOrdersFillOrKill()
    • After calling marketSellOrdersNoThrow(), ensures that takerAssetFilledAmount > takerAssetFillAmount.
  • marketBuyOrdersFillOrKill()
    • After calling marketBuyOrdersNoThrow(), ensures that makerAssetFilledAmount > makerAssetFillAmount.

Other Changes

Re-introducing the noThrow() suffix

In PR #2042, the marketBuyOrders() and marketSellOrders() functions were replaced by marketBuyOrdersNoThrow() and marketSellOrdersNoThrow(), assuming the formers' names.

This was probably a bad idea because the function signatures were identical, meaning it was very possible that a dapp would call those functions expecting the old behavior.

So I've added the noThrow() suffixes back to those functions in this PR.

RevertError types can now take array fields

The new marketBuy/SellOrdersFillOrKill() functions needed to throw a rich revert that holds the order hashes of all the orders passed. So now this is a thing.

Added a fillAmount field to existing IncompleteFillError rich revert

Made sense.

Use abi.decode() in LibTransactionDecoder

LibTransactionDecoder relied heavily on LibBytes for decoding individual error parameters. This worked fine for simple types, but LibBytes doesn't support reading a bytes32[] (which I needed), so I figured we might as well just be consistent just use abi.decode() throughout that contract.

Added an UNLIMITED_CONTRACT_SIZE environment variable, @0x/contracts-dev-utils tests now run with it.

This environment variable is picked up by @0x/contracts-test-utils and creates the default web3Wrapper instance with allowUnlimitedContractSize enabled if set to true.

With the addition of the new methods, the DevUtils contract is no longer deployable under normal conditions because it is > 24KB. The UNLIMITED_CONTRACT_SIZE is a temporary fix for our tests, but we need to re-architecture this contract to be smaller/deployable inevitably.

Testing instructions

You know the drill, so DRILL IT.

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • Prefix PR title with [WIP] if necessary.
  • Add tests to cover changes as needed.
  • Update documentation as needed.
  • Add new entries to the relevant CHANGELOG.jsons.

@coveralls
Copy link

coveralls commented Aug 17, 2019

Coverage Status

Coverage remained the same at 75.804% when pulling e9a4b07 on feature/3.0/exchange/market-fill-or-kill into 0fad6a6 on 3.0.

Copy link
Contributor

@jalextowle jalextowle left a comment

Choose a reason for hiding this comment

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

A few nits, but looks great. Great job on getting solid test coverage! I'll approve once the unlimited contract size issue gets addressed, and once you've had a change to respond to the nits.

const strings = ['foo', 'bar'];
const revert1 = new ArrayRevertError(strings);
const revert2 = new ArrayRevertError(strings);
expect(revert1.equals(revert2)).to.be.true();
Copy link
Contributor

Choose a reason for hiding this comment

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

Style question: Is there a reason to use expect(revert1.equals(revert2)).to.be.false() instead of expect(revert1).to.be.deep.eq(revert2)?

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, there's a semantic difference, but it doesn't really matter in this case. equals() will ignore missing fields.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good to know 👍

@@ -9,7 +9,7 @@ import { BigNumber } from './configured_bignumber';

// tslint:disable: max-classes-per-file

type ArgTypes = string | BigNumber | number | boolean;
type ArgTypes = string | BigNumber | number | boolean | BigNumber[] | string[] | number[] | boolean[];
Copy link
Contributor

Choose a reason for hiding this comment

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

🔥

@@ -388,14 +388,16 @@ function declarationToAbi(declaration: string): RevertErrorAbi {
const [name, args] = m.slice(1);
const argList: string[] = _.filter(args.split(','));
const argData: DataItem[] = _.map(argList, (a: string) => {
m = /^\s*([_a-z][a-z0-9_]*)\s+([_a-z][a-z0-9_]*)\s*$/i.exec(a);
m = /^\s*(([_a-z][a-z0-9_]*)(\[\d*\])*)\s+([_a-z][a-z0-9_]*)\s*$/i.exec(a);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Could you add a comment to this regex? It's getting a bit long, and it is not immediately obvious that this regex just matches an identifier, possibly ending with an array bracket, separated from another identifier by whitespace?

return normalizeBytes(lhs as string) === normalizeBytes(rhs as string);
} else if (type === 'string') {
return lhs === rhs;
} else if (/\[\d*\]$/.test(type)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Very clean! Might be good to add a comment on the larger regexs, but they aren't too bad yet.

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.

I have few comments on the Solidity that I think would simplify things. Will review the tests tomorrow!

uint256 ordersLength = orders.length;
orderHashes = new bytes32[](ordersLength);
for (uint256 i = 0; i != ordersLength; i++) {
orderHashes[i] = orders[i].getTypedDataHash(EIP712_EXCHANGE_DOMAIN_HASH);
Copy link
Member

Choose a reason for hiding this comment

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

We can set EIP712_EXCHANGE_DOMAIN_HASH to a stack variable to save a bunch of sloads.

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 be in favor of just removing this completely though. The order hashes here aren't really relevant to why the fill failed, and they can all be easily calculated since we have all of the orders already.

@@ -308,9 +360,25 @@ contract MixinWrapperFunctions is
);
if (fillResults.takerAssetFilledAmount != takerAssetFillAmount) {
LibRichErrors.rrevert(LibExchangeRichErrors.IncompleteFillError(
takerAssetFillAmount,
Copy link
Member

Choose a reason for hiding this comment

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

It feels like the filled amount is probably more useful than the fill amount, since we already know the fill amount when the function is called.

@@ -584,7 +609,38 @@ library LibExchangeRichErrors {
{
return abi.encodeWithSelector(
INCOMPLETE_FILL_ERROR_SELECTOR,
takerAssetFillAmount,
Copy link
Member

Choose a reason for hiding this comment

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

The takerAssetFilledAmount feels more useful than the takerAssetFillAmount that gets passed into fillOrKillOrder, since we already know what that is when we make the function call.

That being said, I kind of like passing in both amounts (expectedFillAmount and actualFillAmount).

return abi.encodeWithSelector(
INCOMPLETE_MARKET_SELL_ERROR_SELECTOR,
takerAssetFillAmount,
orderHashes
Copy link
Member

Choose a reason for hiding this comment

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

Are these orderHashes really that useful? If we add an errorCode param to this, we can consolidate all 3 fillOrKill errors into a single rich revert reason. Something like:

function IncompleteFillError(
    FillOrKillErrorCode errorCode,
    uint256 actualFillAmount,
    uint256 expectedFillAmount
)

// Attempt to sell the remaining amount of takerAsset
LibFillResults.FillResults memory singleFillResults = fillOrderNoThrow(
orders[i],
remainingTakerAssetFillAmount,
signatures[i]
);

// Restore the original `makerAssetData` so we're non-destructive.
orders[i].makerAssetData = originalMakerAssetData;
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, this change doesn't feel super necessary to me since this isn't intended to be a library or used in external contracts. We also do something similar in matchOrders, right? Is there anywhere where we actually reuse the in-memory orders after calling these functions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was to preserve the order hashes in the event of a rich revert, but we won't need it anymore if we aren't returning order hashes.

@dorothy-zbornak dorothy-zbornak force-pushed the feature/3.0/exchange/market-fill-or-kill branch from 4e7289f to 2e51fac Compare August 20, 2019 17:07
Copy link
Contributor

@jalextowle jalextowle left a comment

Choose a reason for hiding this comment

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

LGTM

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.

Nice work on this! Love the new testing format.

Copy link
Contributor

@hysz hysz left a comment

Choose a reason for hiding this comment

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

Looks great! Just one minor change now that we've merged development into 3.0.

from: string,
opts: { takerAssetFillAmount: BigNumber; gas?: number },
): Promise<TransactionReceiptWithDecodedLogs> {
const txHash = await this._exchange.marketSellOrdersFillOrKill.sendTransactionAsync(
Copy link
Contributor

Choose a reason for hiding this comment

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

If we use awaitTransactionSuccessAsync here then we don't need the log decoder :)

from: string,
opts: { makerAssetFillAmount: BigNumber; gas?: number },
): Promise<TransactionReceiptWithDecodedLogs> {
const txHash = await this._exchange.marketBuyOrdersFillOrKill.sendTransactionAsync(
Copy link
Contributor

Choose a reason for hiding this comment

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

Same note

…lError` type.

`@0x/order-utils`: Add `IncompleteMarketSellError` and `IncompleteMarketBuyError` `RevertError` types.
…ers` back to `marketSellOrdersNoThrow` and `marketBuyOrdersNoThrow`.

`@0x/contracts-exchange`: Introduce new `marketSellOrdersFillOrKill` and `marketBuyOrdersFillOrKill` functions.
`@0x/contracts-exchange`: Add new rich error types: `IncompleteMarketBuyError` and `IncompleteMarketSellError`.
`@0x/contracts-exchange`: Use `abi.decode()` in `LibExchangeRichErrorDecoder` over `LibBytes`.
…ketBuy/SellOrdersFillOrKill` to `LibTransactionDecoder`.
… work with `marketBuy/SellFillOrKill()` functions.
…OT be destructive to the orders.

`@0x/contracts-exchange`: Fix wrapper unit tests to use the actual order hash algorithm, since it can't be overridden anymore.
…onfigurable via `Web3Config`.

`@0x/dev-utils`: Add `UnlimitedContractSize` to `EnvVars`.
…wUnlimitedContractSize` if `UNLIMITED_CONTRACT_SIZE` environment variable is set.
…MITED_CONTRACT_SIZE=true` environment variable.
@dorothy-zbornak dorothy-zbornak force-pushed the feature/3.0/exchange/market-fill-or-kill branch from 65a9821 to e9a4b07 Compare August 21, 2019 14:59
@dorothy-zbornak dorothy-zbornak merged commit 7407890 into 3.0 Aug 21, 2019
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