Closed willwarren89 closed 4 years ago
Looks great! Have you given any thought to set indexing? One option could be to use EIP-712:
setSchema := keccak256(abi.encodePacked(<Erc721Token>(type1 attribute1, type2 attribute2, ..., typeN attributeN))
setId := keccak256(abi.encodePacked(setSchema, attribute1, attribute2, ..., attributeN))
Standardizing this could also serve as a generalized way to store and query tokens by attribute.
Edit:
A solution specific to Gods Unchained could probably just concat the attributes together since they're just uint8
and together won't exceed 256 bits 🤷♀.
Thanks for writing up a detailed proposal! We've been thinking about grouping liquidity by attributes as well internally and definitely think it'd help trade volumes (provided that buying behaviour is utility based and not speculative).
From a product point of view, it seems to be that the proposal goes from the maker requesting the property type at a certain price, then the taker filling the order should their card match that property. My only concern with this flow is that it isn't modelled to the current experience of sellers deciding to sell an asset at a particular price and then buyers deciding to buy it or not. Please correct me if I'm wrong in my thinking over here!
Thinking out loud: I wonder if it might be worth exploring an option where when a card is listed, the ERC1155 token is minted but not deposited into the contract. This would technically create an on-chain copy of the seller's card that would require active off-chain management to destroy if the card is transferred to another user. However the big benefit I see with this is that it creates a much larger pool of on-chain assets grouped by properties that can also be used in AMM orderbooks. To ensure that the off-chain management is taken care of, a small gas reward can be provided by the taker as well.
Also dumb question - in your example:
Maker creates an order to purchase any wrapped Underbrush Boar card for 0.1 WETH.
Taker wraps their Underbrush Boar card, making it fungible.
Taker fills the order:
Taker receives 0.1 WETH from maker.
Maker receives the wrapped Underbrush Boar card from taker.
Why do you need step 2 in the first place? I was speaking to some coworkers about this and they mentioned that some marketplaces already have what's known as "bounties"/"buying options" for cards. The matching of a card based on properties essentially just happens off-chain. Unless the value proposition is to aggregate liquidity at 0x, however the incentive to do so for other marketplaces isn't that high since they'd want to retain liquidity rather than share it.
Have you given any thought to set indexing? One option could be to use EIP-712
A generalized solution that makes use of EIP-712 would be awesome. Still wrapping my head around how this could be implemented in Solidity in a clean way that allows different schemas to be supported and displayed. Any ideas @Recmo ?
My only concern with this flow is that it isn't modeled to the current experience of sellers deciding to sell an asset at a particular price and then buyers deciding to buy it or not. Please correct me if I'm wrong in my thinking over here!
Yep, you’re correct @kermankohli ! This contract would introduce a new user flow that works alongside the current one.
The good news is that it makes it easier for market makers to establish prices for cards and for players to find a buyer quickly. Interestingly, there are already market makers attempting to make markets for GU cards on OpenSea by submitting bids en masse to individual accounts that own items (see here). With the wrapper contract, a single order can be used to establish a price floor for all cards of a given {prototype, quality}.
Why do you need step 2 in the first place?
Not a dumb question, my explanation for this part wasn’t clear.
setId
for 0.1 WETH. setId
for the card they want to purchase, and the number of copies to buy.tokenIds
for the unwrapped ERC721 cards they will end up receiving if their order is filled, but they do know that the unwrapped cards will be of the {prototype, quality} that corresponds with the setId
they have specified in their order.The order itself is behaving like a filter. The Taker must prove that their card has all of the attributes set out by the order by performing a transformation to attribute-space before filling the order. Does this make more sense?
In my analysis of #43 I concluded that without major changes, there is no way for a taker to specify which of several valid assets are used to fill the trade during the trade. The loophole is to specify it in advance and store it somewhere, or to compute it during the transfer. This proposal does both: you specify in advance which assets qualify by wrapping them, and then during a transfer the actual asset is computed last-in-first-out from the specified set.
Some thoughts:
It doesn't seem necessary to transfer the assets to the wrapper. If the ERC1155 wrapper has approval rights to the ERC721 NFT contract, then the wrapper's transfer function can simply delegate and call the NFTs transferFrom method. This could solve some of the integration and TX flow issues. (Observe that this essentially turns the wrapper contract into an AssetProxy, and conversely some of our asset proxies like ERC20Proxy could also be implemented as an ERC115 contract with approval instead. Something to think about. You could also think of wrappers as deposit-withdrawl based asset-proxies.)
The restriction that a given token is part of only one set is unnecessary, but makes things easier. In the most general case, the wrapper (or proxy) contract would simply know all your tokens, and when asked to transfer from a particular set, it would enumerate them and transfer the first token that is in that set. (If you have a lot of tokens there are more efficient ways to implement this, but this should give you the idea.) So, if you add your "Underbrush Boar card with Gold shine" to the wrapper/proxy it will increase your balances for "Underbrush Boar", "Gold shine" and "Underbrush Boar with Gold shine" all by one. If you transfer one of these assets, the other balances also decrease. It's a little weird to have a single transfer affect multiple balances, but the exchange contracts should be able to handle it, since we can not assume any 'normal' behavior anyway for random tokens that are used for trading.
We could try to make the ERC1155 contract stateless, but I imagine users would want to specify which tokens are currently available for trading and which ones are not, so some state is required either way. It also seems that the GU NFTs are not enumerable (See https://github.com/immutable/gods-unchained-contracts/blob/master/contracts/gods-unchained/contracts/token/BatchToken.sol#L6), which is a requirement for a stateless ERC1155 to practically pick a valid token. Even if they where, I'm assuming users have sufficiently many tokens that enumeration becomes impractical.
Superseded by https://github.com/0xProject/ZEIPs/issues/75
Summary
The GUAttributeWrapper contract re-indexes GU cards from non-fungible ERC721 tokens with unique tokenIds into ERC1155 tokens where cards with shared attributes are grouped together under the same setID and fungible within that set. The GUAttributeWrapper contract makes it possible to create a bid to purchase cards with specific attributes via 0x protocol. By integrating the GUAttributeWrapper contract into the 0x protocol pipeline, users can enjoy a seamless experience where cards are synchronously wrapped, traded based on their attributes via 0x, and then unwrapped for the recipient on the other end.
Motivation
0x protocol allows traders to bid on individual Gods Unchained cards, but there currently isn't a way to post a bid that universally applies to every card with a specific set of attributes. In the scenario where a player wants to buy a specific card to finish out their deck they must either (1) accept the price that is currently available on the market for a copy of that card or (2) set their own price by bidding on individual copies of the desired card, hoping one of the owners notices and accepts the bid. The more bids submitted by the player, the higher probability that one of the bids is accepted, but this also increases the probability that the player accidentally buys multiple copies of the card if more than one of their bids is accepted.
Current state: "I'll pay 0.01 ETH for your Underbrush Boar card with this specific ID number."
Desired state: "I'll pay 0.01 ETH for any Underbrush Boar card.“ "I'll pay 0.01 ETH for any Underbrush Boar card, and I am willing to buy up to 5 copies at this price." "I'll pay 0.03 ETH for any Underbrush Boar card with Gold shine." "I'll pay 0.03 ETH for any Underbrush Boar card with Gold shine, and I am willing to buy up to 5 copies at this price."
Bonus points: “I’ll pay 0.05 ETH for any Legendary card.” “I’ll pay 0.2 ETH for any Legendary card with Diamond shine.”
This proposal describes a token wrapper contract, conceptually similar to wrapped ether (WETH), that re-indexes GU cards from non-fungible ERC721 tokens with unique tokenIds into ERC1155 tokens, where cards with shared attributes are grouped together into sets and fungible within that set. The ERC1155 token standard is ideal for our wrapper contract, because it allows a single smart contract to maintain ledgers for an entire universe of related assets.
Specification
The GUAttributeWrapper interface is an extension of ERC1155, ERC1155MintBurn, and ERC721TokenReciever.
Deposit
Cards can be deposited into the wrapper contract by way of the ERC721 safeTransferFrom function with a callback, by explicitly giving the wrapper contract access to card balances via the setApprovalForAll function, or by extending access permissions from the 0x ERC721AssetProxy contract to the wrapper contract.
ERC1155MintBurn(GUAttributeWrapper).mint(depositor, setID, 1)
deposits(setID)[N+1] = tokenId
All cards deposited to the same set are fungible within that set. One could deposit and wrap a meteorite Underbrush Boar with tokenIdA and later unwrap and withdraw a meteorite Underbrush Boar with tokenIdB if other users have deposited or withdrawn cards from that same set during the intervening period.
Withdraw
User specifies the setID for which they have a balance and the number of cards they would like to unwrap and withdraw from that set.
GUAttributeWrapper contract burns 1 unit of setID from the user’s balance.
ERC1155MintBurn(GUAttributeWrapper).burn(owner, setID, 1)
GUAttributeWrapper contract pops off the tokenId from the bottom of the deposits(setID)[] array and transfers the ERC721 card with that tokenId to the user.
Repeat steps 2 and 3 until the specified number of ERC721 cards has been transferred to the user or until the user’s balance for that setID reaches 0.
Trading
Wrapped GU cards behave like any other ERC1155 token when traded via 0x protocol.
We have a problem: when the maker receives the wrapped Underbrush Boar card, it won’t show up in the GU game client or in their wallet, and they are unlikely to know how to handle this card abstraction independently. Instead, the default system behavior should be that the maker receives the unwrapped ERC721 Underbrush Boar card. The system described below abstracts wrapping/unwrapping cards away from end users altogether.
It probably doesn’t make sense for the GUAttributeWrapper to block users from keeping their cards wrapped, but persistent wrapping should be discouraged for all but advanced users because:
The desired transaction flow is shown in the diagram below:
There are three distinct actions that that we would like to take place. They can be completed independently in three separate transactions, but we would like to find a way to string them together such that they happen synchronously in a single transaction.
TODO: Provide example order assetData.
Notes
This proposal is similar to Property Based Orders, but takes an approach that is bottoms up rather than top down.
Rationale
Appendix