-
Notifications
You must be signed in to change notification settings - Fork 465
Add marketBuy/SellOrdersFillOrKill() to Exchange #2075
Add marketBuy/SellOrdersFillOrKill() to Exchange #2075
Conversation
There was a problem hiding this 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(); |
There was a problem hiding this comment.
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)
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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[]; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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.
There was a problem hiding this 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); |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
4e7289f
to
2e51fac
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this 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.
There was a problem hiding this 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( |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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.
…de()` instead of `LibBytes`.
…now return sane values.
…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`.
…g a ganache provider.
…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.
…IncompleteFillError`.
…` rich error. `@0x/contracts-exchange`: Allow `marketSell/BuyOrdersNoThrow` to be destructive to orders again.
…from `MixinWrapperFUnctions`.
…st class in favor of `awaitTransactionSuccessAsync`.
65a9821
to
e9a4b07
Compare
Description
This PR introduces two new functions to the Exchange's
MixinWrapperFunctions
, which is the subject of ZEIP-50:marketSellOrdersFillOrKill()
marketSellOrdersNoThrow()
, ensures thattakerAssetFilledAmount > takerAssetFillAmount
.marketBuyOrdersFillOrKill()
marketBuyOrdersNoThrow()
, ensures thatmakerAssetFilledAmount > makerAssetFillAmount
.Other Changes
Re-introducing the
noThrow()
suffixIn PR #2042, the
marketBuyOrders()
andmarketSellOrders()
functions were replaced bymarketBuyOrdersNoThrow()
andmarketSellOrdersNoThrow()
, 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 fieldsThe 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 existingIncompleteFillError
rich revertMade sense.
Use
abi.decode()
inLibTransactionDecoder
LibTransactionDecoder
relied heavily onLibBytes
for decoding individual error parameters. This worked fine for simple types, butLibBytes
doesn't support reading abytes32[]
(which I needed), so I figured we might as well just be consistent just useabi.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 defaultweb3Wrapper
instance withallowUnlimitedContractSize
enabled if set totrue
.With the addition of the new methods, the
DevUtils
contract is no longer deployable under normal conditions because it is > 24KB. TheUNLIMITED_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
Checklist:
[WIP]
if necessary.