ZEIP-23: trade bundles of assets

Vote on ZEIP23 with ZRX on February 18, 2019

Will Warren
11 min readFeb 11, 2019

Update: ZEIP-23 has been approved and implemented on mainnet!

Key Points

  • MultiAssetProxy (MAP) is a smart contract that adds the ability to trade arbitrary bundles of ERC20 and ERC721 tokens via 0x protocol.
  • MAP development is complete, the code has passed a security audit, but integration requires a “hot upgrade” to 0x smart contracts, which needs community oversight.
  • As the final step in the ZEIP process, ZRX token holders will approve or reject the MAP integration via a token vote to begin February 18, 2019. If approved, the 0x version number will be bumped from v2.0 to v2.1.
  • A centralized upgrade mechanism will be used to trigger these types of integrations until it is safe to transition to a more advanced binding on-chain governance system.
  • ZEIP23 will be our first exercise in community governance; while the process is naive, the stakes are high. We encourage the community to take the process seriously and to commit to improving it with us over time. First, we need help establishing communication norms that ensure the community is fully informed before voting on a proposal.

Preface

The 0x Improvement Proposal (ZEIP) process is a work in progress and will need to improve in practically every way. However, our first goal is to establish ZEIP communication guidelines that will lead to a well informed community. Describing interactions between complex systems of contracts leaves plenty of room for misunderstanding and miscommunication.

It’s easy to view Ethereum contracts as toys and, often, they are. Every week within the Ethereum community there is another wave of whimsical hackathon projects, flashy NFT items, and overt Ponzi schemes. These projects are genuinely fun to tinker with and inspire a great deal of creative innovation. 0x is also an exciting tool and catalyst for creative innovation, but 0x is not a toy. Modifying 0x protocol’s live system of contracts — which has access to large sums of digital assets — is a safety-critical process and we must take the same amount of precaution as we would when writing software that controls a nuclear power plant or commercial aircraft.

The first ZEIP we are putting to the community is not complex: it introduces a new Ethereum contract that consists of a few hundred lines of code and that provides one highly requested capability. The purpose of this post isn’t just to call on the community to inspect the code/audit/bug bounty, it’s to provide a clear and comprehensive explanation for the proposal both technically and from the standpoint of practical implications for users. At a bare minimum, readers with different levels of familiarity with Ethereum smart contracts should be able to come away with the ability to ask logical questions and productively engage in conversation with other members of the community. The following is our first and best effort, but I hope the community will identify methods of communication that are most helpful so future ZEIPs can be communicated more clearly.

What is the motivation behind ZEIP23?

The non-fungible token (NFT) space quickly expanded after ERC721 was formally adopted by the Ethereum community, leading to a vibrant ecosystem of blockchain-based games, collectibles, and marketplaces. While 0x protocol v2.0 supports the exchange of ERC721 tokens, one of the most commonly requested features is the ability to trade bundles of assets in a single atomic transaction. ZEIP23 provides 0x with native support for trading any arbitrary bundle of ERC20/ERC721 tokens. The feature supports a variety of use cases:

  • Sell 20-card booster packs for your ERC721 trading card game.
  • Buy a cluster of neighboring LAND parcels in Decentraland to grow your virtual estate.
  • Open a short position within a categorical prediction market. For example, use Augur or Veil to bet against a specific political party winning the 2020 US presidential election by going long all of the other political parties.
Figure 1: trading a bundle of CryptoKitties for a bundle containing an Axie and some Dai. Image courtesy of Boxswap.

Background and work completed

ZEIP23 was developed following a conversation with the team behind OpenSea, the largest Ethereum NFT marketplace by volume at the time of writing. They pointed out that NFT projects and enthusiasts require the ability to trade bundles of assets, and that a settlement layer such as 0x must natively support bundles to provide a smooth user experience. The core team explored a number of ways to add support for bundle trades to 0x protocol and collected feedback from Ethereum devs with subject matter expertise, including Philippe from HorizonGames.

Ultimately, Amir proposed a solution that is extensible and allows bundles of assets to be described in an intuitive way within the 0x order schema. Amir’s proposal became ZEIP23 and was implemented and internally audited over the following two weeks. The ConsenSys Diligence team completed an independent security audit in early December 2018. The code is now in a state where the core team is comfortable moving forward with integration.

The core team sponsors an ongoing bug bounty program for 0x smart contracts. The MultiAssetProxy (MAP) contract associated with ZEIP23 is now included in the bug bounty program, which pays up to $100,000 for critical bugs. Instructions may be found here.

Next steps

The final step before the MAP contract can be integrated into the 0x pipeline is a confirmation vote by ZRX token holders. A confirmation vote is necessary because ZEIP23 would be the first “hot upgrade” to the 0x pipeline; it requires us to modify access controls for 0x’s existing smart contracts, which have access to live digital assets. Stakeholders must have veto power over ZEIP23 and all subsequent proposals that require a hot upgrade. Typically, disagreement with respect to a proposal’s merit or implementation details should be sussed out early on in the ZEIP process, before a security audit has taken place. The token vote is a final backstop that gives stakeholders the power to block proposals that haven’t been adequately vetted or that cut corners with respect to security.

The vote for ZEIP23 will begin on February 18, 2019, run for one week, and be determined by a simple majority of ZRX token-weighted votes. Individual members of the 0x core team may participate in the vote, but ZRX tokens held by the organization are ineligible. If ZEIP23 is approved, the integration will immediately be initiated on-chain. Following a two-week grace period that is enforced via smart contract, the MAP integration will be completed. If ZEIP23 is rejected, a post-mortem will take place in the 0x forum for voters to discuss the misalignment, figure out if a more favorable version of the proposal can be created, and/or if additional security measures are needed.

Figure 2: important dates for the upcoming vote on ZEIP23.

We will initially (and begrudgingly) be using a centralized off-chain voting mechanism that is similar to Carbon Vote. While we are actively pursuing a more robust solution, it is too early and risky to put a binding on-chain governance system in place for these types of upgrades today. That being said, we believe the community and core team need to set a precedent that hot upgrades require community vetting and approval every time. Even if the governance process isn’t yet bound by blockchain consensus and the upgrades aren’t contentious, our practices today must be aligned with our end goal where 0x protocol is a public good maintained by the community.

Technical Overview

TL;DR ZEIP23 introduces the MultiAssetProxy (MAP) contract. The MAP accepts data which specifies a list of assets and corresponding amounts. It loops through those lists and transfers the requested amount of each asset by delegating to the appropriate AssetProxy contract.

AssetIds and AssetProxy contracts

The MAP is an AssetProxy contract. Before discussing the MAP contract in detail, let’s walk through the 0x message format and the process of trade settlement to see how AssetProxy contracts work at a high level.

0x protocol’s message format describes the assets on either side of a trade as ABIv2 encoded AssetData which is similar in structure to calldata; the payload passed to an Ethereum contract when attempting to call one of its external functions. The the first four bytes of calldata is a function selector that indicates the function to route the payload to. Similarly for AssetData, the first four bytes consist of an AssetId that indicates the asset type (i.e. ERC20, ERC721, etc).

Figure 3: the data used to describe the assets on either side of a 0x trade includes a four byte AssetId which indicates the asset type.

The Exchange contract — which acts as the entry point for 0x orders — maps the AssetId to a corresponding AssetProxy contract and forwards the encoded payload to that contract to decode and process, resulting in a state change. The only state changes possible today are balance transfers from one party to another, but since AssetData can be any length, any state change could conceivably be supported by new AssetProxy contracts in the future.

Figure 4: when a 0x order is passed into the Exchange contract, it uses the AssetId to determine which AssetProxy contract to forward the remaining AssetData to.

Today, the 0x pipeline includes two AssetProxy contracts which interface with ERC20 and ERC721 tokens, respectively.

Name: ERC20AssetProxy
ENS: erc20.assetproxy.eth
Address: 0x2240dab907db71e64d3e0dba4800c83b5c502d4e
AssetId: bytes4(keccak256("ERC20Token(address)")) = 0xf47261b0

Name: ERC721AssetProxy
ENS: erc721.assetproxy.eth
Address: 0x208e41fb445f1bb1b6780d58356e81405f3e6127
AssetId: bytes4(keccak256("ERC721Token(address,uint256)")) = 0x02571792

In summary, AssetProxy contracts allow 0x's modular system of smart contracts to be upgraded without bringing markets to a halt, to easily support new token standards without leaking asset-specific business logic into other parts of the system, and to place constraints on how the system interacts with users' digital assets to prevent unintended or malicious behavior e.g. labelling ERC20 tokens as ERC721 tokens and vice versa.

How does the MultiAssetProxy contract work?

The MAP contract is also identified by an AssetId, but instead of executing an asset transfer, it accepts an array of AssetDatas and corresponding amounts that represent a bundle of assets.

AssetId: bytes4(keccak256("MultiAsset(uint256[],bytes[])")) = 0x94cfcdd7

The MAP contract iterates through the array, reads the AssetId associated with each asset, and forwards the encoded payload to the appropriate AssetProxy contract. The trade remains atomic: all legs of a trade must be completed successfully or the entire transaction is reverted. Partial fills are not possible.

Figure 5: the MAP contract forwards AssetData for a bundle containing two ERC721 tokens and a balance of ERC20 tokens. The figure only shows one side of the trade being completed.

Source code for the MAP contract can be found here. Though simple in terms of behavior, it has been optimized using assembly to keep the gas costs as low as possible. We invite all members of the community to review the implementation and participate in our ongoing bug bounty program.

Name: MultiAssetProxy
ENS: multi.assetproxy.eth
Addresses:

  • Mainnet: 0x8a13e81fa50eca62fdec7f5d16e513a86e95481b
  • Kovan: 0xf6313a772c222f51c28f2304c0703b8cf5428fd8
  • Rinkeby: 0xb34cde0ad3a83d04abebc0b66e75196f22216621
  • Ropsten: 0xab8fbd189c569ccdee3a4d929bb7f557be4028f6

AssetId: bytes4(keccak256("MultiAsset(uint256[],bytes[])")) = 0x94cfcdd7

Technical Overview: MAP integration

AssetProxyOwner

Each contract within 0x protocol’s pipeline has one or more sets of access controls. For AssetProxy contracts, all incoming function calls are blocked except for requests sent by explicitly approved addresses (i.e. the Exchange contract). Each AssetProxy stores a list of approved “callers” and specifies a single “owner” address that is responsible for managing the list of callers.

Name: AssetProxyOwner
ENS: owner.assetproxy.eth
Address: 0x17992e4fFB22730138e4B62aaa6367FA9D3699a6

All AssetProxy contracts are owned by the AssetProxyOwner contract. Therefore, AssetProxyOwner is responsible for orchestrating all changes to the 0x pipeline. Today, AssetProxyOwner is a Gnosis multisig, modified such that there is a two week delay between the time any transaction is initiated and when it is executed. This grace period gives users time to identify and react to a malicious update before any damage can occur. The only transaction that does not require a two week delay is a transaction that shuts the entire 0x pipeline down, a precaution in case we encounter a bug or unintended behavior.

It is important to note that the current AssetProxyOwner contract that is controlling the pipeline will be replaced by a sequence of increasingly decentralized governance systems over time. We acknowledge that using a multi-sig for something this mission critical is not in the spirit of decentralization. However, the transition to more robust governance systems should not be rushed.

Initiating and executing MAP integration

The following transactions comprise the steps required to integrate the MAP contract into the 0x pipeline. Please note that the order in which the following transactions is carried out is important. The first five transactions configure the MAP contract and may executed without approval from the AssetProxyOwner contract [Kovan]:

  1. Deploy MAP from Externally Owned Account
  2. Call MAP.registerAssetProxy(erc20Proxy.address)
  3. Call MAP.registerAssetProxy(erc721Proxy.address)
  4. Call MAP.addAuthorizedAddress(exchange.address)
  5. Call MAP.transferOwnership(assetProxyOwner.address)

The final four transactions require updating the state of existing contracts within the 0x pipeline and therefore require approval from the AssetProxyOwner contract (and must each pass a two week timelock before going into effect):

  1. Call erc20Proxy.addAuthorizedAddress(MAP.address) from assetProxyOwner [Kovan]
  2. Call erc721Proxy.addAuthorizedAddress(MAP.address) from assetProxyOwner [Kovan]
  3. Call exchange.registerAssetProxy(MAP.address) from assetProxyOwner [Kovan]
  4. Call assetProxyOwner.registerAssetProxy(MAP.address, true) from assetProxyOwner

Kovan test trade #1: (ERC721 + ERC20) <> ERC20.
Kovan test trade #2: ERC20 <> (ERC721 + ERC721 + ERC721 + ERC721).

Given that ZEIP23 is a hot upgrade that modifies 0x’s live contracts, the process comes with risks. The first line of defense is clear communication and the second line of defense is providing voters with the ability to independently verify all proposed state changes. The MAP contract is deployed to mainnet and its source code has been verified on Etherscan; further the MAP contract’s deployed bytecode can be cross referenced with the contract artifacts in the 0x monorepo. Voters may also use Etherscan’s “Read Contract” tab to verify that the MAP contract’s only authorized caller is the mainnet Exchange contract. If ZEIP23 is accepted by voters, the four final transactions described above will be initiated within the AssetProxyOwner contract. At that time, users may verify that the initiated transactions are correct by inspecting the ABI encoded data passed into the AssetProxyOwner contract is as follows:

// AssetProxyOwner
// submitTransaction(address,uint256,bytes) bytes4 0xc6427474
// ERC20Proxy (0x2240dab907db71e64d3e0dba4800c83b5c502d4e) addAuthorisedAddress(MAP.address)
0xc64274740000000000000000000000002240dab907db71e64d3e0dba4800c83b5c502d4e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002442f1181e0000000000000000000000008a13e81fa50eca62fdec7f5d16e513a86e95481b00000000000000000000000000000000000000000000000000000000
// ERC721Proxy (0x208e41fb445f1bb1b6780d58356e81405f3e6127) addAuthorisedAddress(MAP.address)
0xc6427474000000000000000000000000208e41fb445f1bb1b6780d58356e81405f3e612700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002442f1181e0000000000000000000000008a13e81fa50eca62fdec7f5d16e513a86e95481b00000000000000000000000000000000000000000000000000000000
// Exchange registerAssetProxy(MAP.address)
0xc64274740000000000000000000000004f833a24e1f95d70f028921e27040ca56e09ab0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024c585bb930000000000000000000000008a13e81fa50eca62fdec7f5d16e513a86e95481b00000000000000000000000000000000000000000000000000000000
// AssetProxyOwner registerAssetProxy(MAP.address,true)
0xc642747400000000000000000000000017992e4ffb22730138e4b62aaa6367fa9d3699a60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000445a1a66af0000000000000000000000008a13e81fa50eca62fdec7f5d16e513a86e95481b000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000

Now What?

The vote will start on February 18th and end on February 25th, 2019 (exact block numbers forthcoming). All ZRX token holders may vote on ZEIP23. We will be adding a voting landing page to 0x.org before the 18th. We will also be hosting an AMA on the 0x subreddit tomorrow (February 12, 2019) at 11am PT where our community can ask questions and/or submit comments regarding ZEIP23, the voting process, and how it may be improved in the future. If you want to discuss the proposal, join our Discord chat.

Written in collaboration with Jacob Evans and Amir Bandeali.

Learn more about 0x and join our community

Website | Blog | Twitter | Discord Chat | Facebook | Reddit | LinkedIn | Subscribe to our newsletter for updates in the 0x ecosystem

--

--

Will Warren

Co-founder @0xProject. Previously research @LosAlamosNatLab, MechE @UCSanDiego.