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

@0x/contracts-broker: Property-based Gods Unchained orders #2455

Merged
merged 15 commits into from
Feb 4, 2020

Conversation

moodlezoup
Copy link
Contributor

@moodlezoup moodlezoup commented Jan 24, 2020

Description

Implements the Broker and GodsUnchainedValidator contracts, as described in 0xProject/ZEIPs#75
Also moves LibAssetDataTransfer from @0x/contracts-exchange-forwarder to @0x/contracts-exchange-libs.

A couple of things to note:

  • The Broker currently doesn't support orders with taker fees. This is because, like the Forwarder, the Broker serves as the taker of the order, so the taker fee asset would need to be transferred to the Broker prior to the fill (more approvals to be set by the actual taker, etc.). You can't mix and match property-based and vanilla orders in batchBrokerTrade for the same reason.
  • Could add msg.sender checks to the fallback and safeBatchTransferFrom.
  • The way that the takerAssetAmount and the nested 1155 amounts work together is somewhat interesting for brokered assets. Refer to the comment above encodeBrokerAssetData for details
  • Right now safeBatchTransferFrom is fairly opinionated in that it enforces that from == address(this) and assumes the brokered asset is 721. Generalizing this could open up a few interesting use cases, but this can wait for a later iteration.

Testing instructions

Unit tests for the GodsUnchainedValidator contract are found in the new @0x/contracts-broker package, while Broker <> GodsUnchainedValidator integration tests are in @0x/contracts-integrations

Types of changes

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.

@moodlezoup moodlezoup changed the title @0x/contracts-broker: Property-based Gods Unchained orders [placeholder] @0x/contracts-broker: Property-based Gods Unchained orders Jan 24, 2020
@moodlezoup moodlezoup force-pushed the feature/broker/gods-unchained branch 2 times, most recently from 19e2559 to bebf90f Compare January 24, 2020 00:45
@buildsize
Copy link

buildsize bot commented Jan 24, 2020

File name Previous Size New Size Change
init.py 60.43 KB 60.43 KB 0 bytes (0%)
abi_gen_dummy.ts 85.45 KB [deleted]
lib_dummy.ts 7.13 KB [deleted]
test_lib_dummy.ts 10.56 KB [deleted]
environment.pickle 1.72 MB 1.72 MB 0 bytes (0%)
index.doctree 436.61 KB 436.61 KB 0 bytes (0%)
.buildinfo 230 bytes 230 bytes 0 bytes (0%)
genindex.html 31.42 KB 31.42 KB 0 bytes (0%)
index.html 4.69 KB 4.69 KB 0 bytes (0%)
objects.inv 1 KB 1 KB 0 bytes (0%)
py-modindex.html 3.07 KB 3.07 KB 0 bytes (0%)
search.html 2.84 KB 2.84 KB 0 bytes (0%)
searchindex.js 11.97 KB 11.97 KB 0 bytes (0%)
index.rst.txt 2.22 KB 2.22 KB 0 bytes (0%)
alabaster.css 10.92 KB 10.92 KB 0 bytes (0%)
basic.css 11.97 KB 11.97 KB 0 bytes (0%)
custom.css 42 bytes 42 bytes 0 bytes (0%)
doctools.js 9.05 KB 9.05 KB 0 bytes (0%)
documentation_options.js 324 bytes 324 bytes 0 bytes (0%)
file.png 286 bytes 286 bytes 0 bytes (0%)
jquery-[version].js 273.79 KB 273.79 KB 0 bytes (0%)
jquery.js 86.08 KB 86.08 KB 0 bytes (0%)
language_data.js 10.59 KB 10.59 KB 0 bytes (0%)
minus.png 90 bytes 90 bytes 0 bytes (0%)
plus.png 90 bytes 90 bytes 0 bytes (0%)
pygments.css 4.69 KB 4.69 KB 0 bytes (0%)
searchtools.js 15.65 KB 15.65 KB 0 bytes (0%)
underscore-[version].js 34.34 KB 34.34 KB 0 bytes (0%)
underscore.js 11.86 KB 11.86 KB 0 bytes (0%)
contract_addresses.html 16.8 KB 16.8 KB 0 bytes (0%)
contract_artifacts.html 8.24 KB 8.24 KB 0 bytes (0%)
json_schemas.html 12.56 KB 12.56 KB 0 bytes (0%)
order_utils.html 47.16 KB 47.16 KB 0 bytes (0%)
erc20_token.html 93.56 KB 93.56 KB 0 bytes (0%)
exchange.html 730.72 KB 730.72 KB 0 bytes (0%)
tx_params.html 9.41 KB 9.41 KB 0 bytes (0%)
local_message_signer.html 15.08 KB 15.08 KB 0 bytes (0%)
asset_data_utils.html 22.66 KB 22.66 KB 0 bytes (0%)
asset_proxy_owner.html 337.39 KB 337.39 KB 0 bytes (0%)
coordinator.html 128.72 KB 128.72 KB 0 bytes (0%)
coordinator_registry.html 39.59 KB 39.59 KB 0 bytes (0%)
dutch_auction.html 66.13 KB 66.13 KB 0 bytes (0%)
erc20_proxy.html 111.83 KB 111.83 KB 0 bytes (0%)
erc721_proxy.html 111.95 KB 111.95 KB 0 bytes (0%)
erc721_token.html 150.2 KB 150.2 KB 0 bytes (0%)
forwarder.html 130.08 KB 130.08 KB 0 bytes (0%)
i_asset_proxy.html 40.18 KB 40.18 KB 0 bytes (0%)
i_validator.html 27.06 KB 27.06 KB 0 bytes (0%)
i_wallet.html 24.9 KB 24.9 KB 0 bytes (0%)
multi_asset_proxy.html 146.81 KB 146.81 KB 0 bytes (0%)
order_validator.html 107.69 KB 107.69 KB 0 bytes (0%)
weth9.html 133.39 KB 133.39 KB 0 bytes (0%)
zrx_token.html 107.61 KB 107.61 KB 0 bytes (0%)
dev_utils.html 593.43 KB 593.43 KB 0 bytes (0%)
types.html 8.54 KB 8.54 KB 0 bytes (0%)
erc1155_mintable.html 276.51 KB 276.51 KB 0 bytes (0%)
erc1155_proxy.html 133.15 KB 133.15 KB 0 bytes (0%)
static_call_proxy.html 34.04 KB 34.04 KB 0 bytes (0%)
relayer_api.html 113.37 KB 113.37 KB 0 bytes (0%)
relayer_api_asset_data_pairs_response_schema.html 13.86 KB 13.86 KB 0 bytes (0%)
relayer_api_asset_data_trade_info_schema.html 23.09 KB 23.09 KB 0 bytes (0%)
relayer_api_error_response_schema.html 19.74 KB 19.74 KB 0 bytes (0%)
relayer_api_error_response_schema_validation_errors.html 19.92 KB 19.92 KB 0 bytes (0%)
relayer_api_fee_recipients_response_schema.html 13.83 KB 13.83 KB 0 bytes (0%)
relayer_api_order_config_payload_schema.html 39.52 KB 39.52 KB 0 bytes (0%)
relayer_api_order_config_response_schema.html 25.23 KB 25.23 KB 0 bytes (0%)
relayer_api_order_schema.html 15.88 KB 15.88 KB 0 bytes (0%)
relayer_api_orderbook_response_schema.html 16.2 KB 16.2 KB 0 bytes (0%)
relayer_api_orders_channel_subscribe_payload_schema.html 35.06 KB 35.06 KB 0 bytes (0%)
relayer_api_orders_channel_subscribe_schema.html 22.98 KB 22.98 KB 0 bytes (0%)
relayer_api_orders_channel_update_schema.html 22.83 KB 22.83 KB 0 bytes (0%)
relayer_api_orders_response_schema.html 13.75 KB 13.75 KB 0 bytes (0%)
signed_order_schema.html 14.36 KB 14.36 KB 0 bytes (0%)

@coveralls
Copy link

coveralls commented Jan 24, 2020

Coverage Status

Coverage increased (+0.1%) to 79.739% when pulling 0691cc7 on feature/broker/gods-unchained into f9c9b9f on development.

@moodlezoup moodlezoup changed the title [placeholder] @0x/contracts-broker: Property-based Gods Unchained orders [WIP] @0x/contracts-broker: Property-based Gods Unchained orders Jan 27, 2020
@moodlezoup moodlezoup force-pushed the feature/broker/gods-unchained branch 2 times, most recently from 9be5661 to 2a4a419 Compare January 28, 2020 20:13
@moodlezoup moodlezoup force-pushed the feature/broker/gods-unchained branch 6 times, most recently from 9992650 to 1fc449b Compare January 29, 2020 03:17
@moodlezoup moodlezoup changed the title [WIP] @0x/contracts-broker: Property-based Gods Unchained orders @0x/contracts-broker: Property-based Gods Unchained orders Jan 29, 2020
@moodlezoup moodlezoup marked this pull request as ready for review January 29, 2020 03:31
@moodlezoup moodlezoup force-pushed the feature/broker/gods-unchained branch from 1fc449b to c5267b6 Compare January 29, 2020 04:44
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! I really like the simplicity of this design.

)
external
{
if (from != address(this)) {
Copy link
Member

Choose a reason for hiding this comment

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

Why not just ignore the from parameter and always use address(this)?

Copy link
Contributor Author

@moodlezoup moodlezoup Jan 30, 2020

Choose a reason for hiding this comment

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

I put this in to handle the case where e.g, makerFeeAssetData = takerAssetData both point to the Broker. Without this check, the Broker would send one of the taker's assets to the fee recipient, but this would be disguised as a maker fee

Copy link
Contributor

Choose a reason for hiding this comment

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

Could this also be an issue for the taker fee? There's nothing explicitly preventing filling an order w/ a taker fee right?

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 suppose so, but at least if the takerFee is non-zero the taker should expect to be sending something to the maker, so it's kinda on them/the front-end to surface what that thing is. Also I can't really think of an easy way for the Broker to distinguish between a taker -> maker transfer vs a taker -> feeRecipient transfer

contracts/broker/contracts/src/Broker.sol Outdated Show resolved Hide resolved
contracts/broker/contracts/src/Broker.sol Outdated Show resolved Hide resolved
contracts/broker/contracts/src/Broker.sol Outdated Show resolved Hide resolved
uint256[] calldata amounts,
bytes calldata data
)
external
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 add a modifier that checks to make sure msg.sender == ERC1155Proxy here.

Copy link
Contributor Author

@moodlezoup moodlezoup Jan 30, 2020

Choose a reason for hiding this comment

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

should we check that msg.sender == _EXCHANGE in the fallback while we're at 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.

or I guess if we go with the WETH wrapping/unwrapping approach, msg.sender == _WETH

contracts/broker/contracts/src/Broker.sol Outdated Show resolved Hide resolved
contracts/broker/contracts/src/Broker.sol Outdated Show resolved Hide resolved
/// @param fillFunctionSelector The selector for either `fillOrder` or `fillOrKillOrder`.
/// @return fillResults Amounts filled and fees paid by the maker and taker.
function brokerTrade(
bytes[] memory brokeredAssets,
Copy link
Member

Choose a reason for hiding this comment

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

Since these will always be 721 tokens, do we actually need any information other than the token IDs? It would be much cheaper to only cache a uint256[] if we could get away with it.

Copy link
Contributor Author

@moodlezoup moodlezoup Jan 30, 2020

Choose a reason for hiding this comment

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

yeah, we can encode the token address in the 1155 asset data at the cost of a little generality
alternatively, I suppose we could save one storage word per asset while retaining full generality by using abi.encode(tokenAddress, tokenId) instead of the assetData


// Clear storage
delete _cachedAssetData;
_cacheIndex = 0;
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 actually need this for a single fill right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do in the case that the order's takerAssetAmount > 1.

Copy link
Contributor

@dorothy-zbornak dorothy-zbornak left a comment

Choose a reason for hiding this comment

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

This is really elegant! Still gonna think about it a bit more before approving but I'm really digging it so far.

Integration tests even look nice or w/e 🙄

Comment on lines 40 to 43
bytes[] internal _cachedAssetData;
uint256 internal _cacheIndex;
address internal _sender;
address internal _EXCHANGE; // solhint-disable-line var-name-mixedcase
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we get comments for these?

contracts/broker/contracts/src/Broker.sol Outdated Show resolved Hide resolved
@abandeali1
Copy link
Member

abandeali1 commented Jan 30, 2020

Another thought - if this contract approves the StakingProxy to spend its WETH balance, then filling WETH denominated orders wouldn't require ETH to be sent with the transaction in order to cover the protocol fee. This seems like it could be a pretty large UX improvement.

@moodlezoup
Copy link
Contributor Author

Another thought - if this contract approves the StakingProxy to spend it's WETH balance, then filling WETH denominated orders wouldn't require ETH to be sent with the transaction. This seems like it could be a pretty large UX improvement.

hm, that's an interesting thought... I guess this would look like:

  • Broker wraps msg.value into WETH, if any ETH is provided in the call
  • All protocol fees are paid in WETH (either supplied by the taker or acquired from the maker during settlement)
  • And at the end:
for (uint256 i = 0; i < orders.length; i++) {
    if (orders[i].makerAssetData != WETH) {
        orders[i].makerAssetData.transferOut(fillResults[i].makerAssetFilledAmount);
    }
}
ETHER_TOKEN.withdraw(remainingWethBalance);
msg.sender.transfer(remainingWethBalance);

@abandeali1
Copy link
Member

abandeali1 commented Jan 30, 2020

I don't think you'd actually need to do any wrapping/unwrapping. The Broker receives WETH as part of the trade and then uses that to pay protocol fees automatically if msg.value is 0. The only change we'd really need to change is subtract protocolFeePaid in the transferOut call when makerAssetData == wethAssetData && msg.value == 0.

@moodlezoup
Copy link
Contributor Author

I don't think you'd actually need to do any wrapping/unwrapping. The Broker receives WETH as part of the trade and then uses that to pay protocol fees automatically if msg.value is 0. The only change we'd really need to make is subtract protocolFeePaid in the transferOut call when makerAssetData == wethAssetData && msg.value == 0.

This would cause problems if msg.value = 1 or e.g. if the makerAssetAmount of WETH is not enough to cover all the protocol fees in the transaction and the taker needs to supply some ETH to make up the difference

@abandeali1
Copy link
Member

True, but nothing is stopping anyone from including ETH in the transaction if the makerAssetAmount < protocolFee. Also msg.value = 1 already breaks protocol fees (msg.value == protocolFee || msg.value == 0 must be true).

@moodlezoup
Copy link
Contributor Author

moodlezoup commented Jan 30, 2020

Not sure if I follow –– If 0 < msg.value < protocolFee the Exchange/StakingProxy will fall back to paying the protocol fee in WETH. So if this is the case, and the Broker uses WETH acquired from the makerAssetData to pay the protocol fee, we'd need to subtract protocolFeePaid from the transferOut amount even though msg.value > 0.

@abandeali1
Copy link
Member

Nvm you're right. I was thinking about how the StakingProxy handles receiving protocol fees, not how the Exchange handles sending them to the StakingProxy.

Wrapping/unwrapping seems like the most logical approach to handle all of the edge cases then. Pretty much exactly what the Forwarder does.

@moodlezoup
Copy link
Contributor Author

I guess at that point we might as well approve the ERC20Proxy as well, as this would enable WETH taker fees and filling orders with takerAssetData = WETH

/// @param to This should be the maker of the order.
/// @param amounts Should be an array of just one `uint256`, specifying the amount of the brokered assets to transfer.
/// @param data Encodes the validator contract address and any auxiliary data it needs for property validation.
function safeBatchTransferFrom(
Copy link
Contributor

@dorothy-zbornak dorothy-zbornak Jan 31, 2020

Choose a reason for hiding this comment

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

OK, so here is my admittedly kind of lame attack vector for this and why we should probably be checking that msg.sender == ERC1155Proxy:

  • Maker creates one of these orders with makerFeeAssetData = ERC1155(...) and feeRecipient == maker.
  • Taker is like, w/e not my fee not my problem so I'll fill this order.
  • Taker is also like, my sloppy ass will pass in more ERC721s than the fill/order wants because the broker shouldn't use them anyway.
  • Exchange fills taker -> maker, which leaves behind the excess ERC721s "in" the broker contract.
  • Exchange then fills maker fee, which is an 1155 so it calls onERC1155BatchReceived() on maker which, surprise, is a smart contract.
  • In onERC1155BatchReceived() the maker calls broker.safeBatchTransferFrom(address(broker), maker, [], [1], hex"0x...")
  • Maker walks away with an extra ERC721.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is actually the scenario that the if (from != address(this)) protects against. But yeah, I'm going to add a msg.sender == ERC1155Proxy check just in case we're missing an edge case

)
external
{
if (from != address(this)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this also be an issue for the taker fee? There's nothing explicitly preventing filling an order w/ a taker fee right?

@moodlezoup moodlezoup force-pushed the feature/broker/gods-unchained branch from 259c389 to f02dc29 Compare February 1, 2020 01:23
@moodlezoup
Copy link
Contributor Author

Added forwarder-like affiliate fees and WETH support
Refactored the Forwarder a good deal, most notably moved LibAssetDataTransfer and MixinWeth into @0x/contracts-extensions so that they can be reused between the Broker and Forwarder. (Also I deleted everything that was previously in the extensions🚶)

@moodlezoup moodlezoup force-pushed the feature/broker/gods-unchained branch from f02dc29 to efe200e Compare February 1, 2020 01:28
@moodlezoup moodlezoup requested a review from abandeali1 February 1, 2020 01:28
@moodlezoup moodlezoup force-pushed the feature/broker/gods-unchained branch from efe200e to d0865b2 Compare February 1, 2020 01:39
Copy link
Contributor

@dorothy-zbornak dorothy-zbornak left a comment

Choose a reason for hiding this comment

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

Seems legit! 👍

"version": "6.0.0",
"changes": [
{
"note": "New year, new me: remove everything, add MixinWethUtils and LibAssetDataTransfer",
Copy link
Contributor

Choose a reason for hiding this comment

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

03a19fb28d17cb42a4a4d97338e96a0c

PKG=@0x/contracts-extensions yarn build
PKG=@0x/contracts-dev-utils yarn build
Copy link
Contributor

Choose a reason for hiding this comment

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

😲

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.

Awesome, this is is really good shape! The refactors to the Forwarder are also 🔥 .

/// @param initialWethAmount Amount of WETH available after transferring affiliate fees.
/// @param wethSpent Amount of WETH spent when filling orders.
/// @dev Unwraps and refunds WETH to msg.sender.
/// @param refundAmount Amount of WETH balance to refund.
function _transferEthRefund(
Copy link
Member

Choose a reason for hiding this comment

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

Nit: now that this is in a more generic package, it probably makes sense to give this a more generic name. The first name that comes to mind is withdrawAndTransferEth.

@moodlezoup moodlezoup force-pushed the feature/broker/gods-unchained branch 2 times, most recently from f44c2e4 to eb4c697 Compare February 4, 2020 04:48
@moodlezoup moodlezoup force-pushed the feature/broker/gods-unchained branch from eb4c697 to 0691cc7 Compare February 4, 2020 18:13
@moodlezoup moodlezoup merged commit 93dcb68 into development Feb 4, 2020
@moodlezoup moodlezoup deleted the feature/broker/gods-unchained branch February 4, 2020 18:53
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.

4 participants