Open anilhelvaci opened 7 months ago
prepared by: @anilhelvaci
As stated in the problems targeted section, we are trying to relieve zoe from having to deal with KreadCharacter
and KreadItem
purses.
issuer.makeEmptyPurse()
where issuer is coming from a ZCFMint
(as is the case with KREAd) is going to be living in vat-zoe
which is still going to be responsible for the add
and subtract
operations for copyBag
items. This is still an expensive approach and we want to move it to a vat related to KREAd. So we need a separate the vat that holds the references to the purses.Moving the issuerKit
to a vat other than vat-zoe
will put the execution costs on KREAd and not on zoe. This is a requirement mentioned in https://github.com/Agoric/agoric-sdk/issues/8862 and in the quote:
...and our charge-for-execution plans need a way to assign the costs to v63-kread
wash trades
as long as we use different keywords for give
and want
part of the proposal. As referenced in https://github.com/Agoric/agoric-sdk/issues/8859This approach presents a solution with the current features;
KreadInventory
KreadItem
using ERTP's makeIssuerKit
methodpurse
created using kreadItemIssuer.makeEmptyPurse()
. vat-inventory
) there will be no reason for keeping an inventorySeat
.vat-kread(original KREAd contract)
whether it is mint
, equip
, unequip
or swap
, KREAd contract makes another offer to this vat-inventory
to fulfill user's offer.vat-kread
and vat-inventory
will happen via offerTo
method of zoeHelpers
APImint
with this solutionsequenceDiagram
box Blue User
participant Usw as SmartWallet
end
participant Zoe
box Black KREAd
participant Contract
participant State
end
participant Zoe1 as Zoe
box Green KREAdInventory
participant Isw as KREAdInventoryContract
end
Isw ->> Isw: create KreadItem issuer kit
Usw->>Zoe: mint offerSpec
Zoe->>Contract: Proposal - give Price
Zoe-->>Usw: UserSeat
Contract->>Contract: create Character amount
rect rgb(125, 0, 0)
critical create Inventory Account
Contract->>+Zoe1: registerCharacter offerSpec
Zoe1-->>Contract: { userSeat, deposited }
Zoe1 ->> Isw: registerCharacterHandler
Isw ->> Isw: create purse for equipped items
Isw ->> Isw: update character -> purse table
Isw-->>Zoe1: mintItemsIntoPurseInvitation
Zoe1-->>- Contract: offerResult<mintItemsIntoPurseInvitation>
Contract ->> Contract: userSeat.getOfferResult()
end
end
Contract->>Contract: mint Character
rect rgb(79, 29, 2)
critical create Inventory Account
Contract->>+Zoe1: mintItemsIntoPurse offerSpec
Zoe1-->>Contract: { userSeat, deposited }
Zoe1 ->> Isw: mintItemsIntoPurseHandler
Isw ->> Isw: find items purse for character
Isw ->> Isw: mint defaultItems into the purse
Isw-->>Zoe1: "Items successfully minted"
Zoe1-->>- Contract: offerResult< "Items successfully minted">
Contract ->> Contract: userSeat.getOfferResult()
end
end
Contract->>Zoe: reallocate Character
Contract->>Contract: exit user zcfSeat
Zoe-->>Usw: Character payout
Contract->>Contract: build new Character object (including the Inventory SW)
Contract->>State: Add the new Character to Entries storage
equip
with this solutionsequenceDiagram
box Blue User
participant Usw as SmartWallet
end
participant Zoe
box Black KREAd
participant Contract
participant State
end
participant Zoe1 as Zoe
box Green KREAdInventory
participant Isw as KREAdInventoryContract
end
Usw->>Zoe: equip offerSpec
Zoe->>Contract: Proposal - { give CharacterIn + Item } { want CharacterOut }
Zoe-->>Usw: UserSeat
rect rgb(125, 0, 0)
critical create Inventory Account
Contract->>+Zoe1: equipItem offerSpec
Zoe1-->>Contract: { userSeat, deposited }
Zoe1 ->> Isw: equipItemHandler
Isw ->> Isw: find items purse for character
Isw ->> Isw: add new Item to the purse
Isw-->>Zoe1: "Item successfully added"
Zoe1-->>- Contract: offerResult<"Item successfully added">
Contract ->> Contract: userSeat.getOfferResult()
end
end
Contract -->> Zoe: "Item successfully added"
Zoe-->>Usw: offerResult<"Item successfully added">
Contract->>Contract: update storage with character and new items
There are users who already minted characters and items with the existing way. We must implement a mechanism to migrate those users to the latest version. This means;
KREAdITEM
will changeinventorySeat
KREAdITEM
with the exact amounts that are equipped to the charactervat-inventory
by registering user's character and creating an item purse for itinventorySeat
We should probably perform this migration as old users try to interact with the application after the upgrade instead of trying to move all characters and items at once(for the sake of Zoe). So we end two main ways to do this;
migration
offer from the user if their character IS NOT registered in vat-inventory
equip
, unequip
, ..etc) will not take longer timeequip
, unequip
, ..etc).
equip
, unequip
, ..etc) that include migration(we'll have manually look for in the usual operation includes any migration or not).vat-kread
and vat-inventory
?
vat-inventory
to a virtual object that lives under vat-kread
when overall KREAd performance accounted?vat-kread
ApproachPrepared by: @Jorge-Lopes
Since the purpose of the next diagrams are to display the changes we intend to do to the original design, we will omit some details of the normal flow of the KREAd features that are not relevant.
sequenceDiagram
box Blue User
participant Usw as SmartWallet
end
participant Zoe
box Green KREAd
participant kreadKit
participant State
end
kreadKit ->> kreadKit: itemKit = makeIssuerKit()
Usw->>Zoe: mint offerSpec
Zoe->>kreadKit: Proposal { give Price }
Zoe-->>Usw: UserSeat
kreadKit->>kreadKit: characterMint.mintPayment(baseCharacter)
kreadKit->>kreadKit: itemMint.mintPayment(baseItems)
create participant Purse
kreadKit->>Purse: itemsIssuer.makeEmptyPurse()
Purse-->>kreadKit: provide inventoryPurse
create participant Seat
kreadKit->>Seat: zcf.makeEmptySeatKit()
Seat-->>kreadKit: provide userSeat and zcfSeat
kreadKit->>Zoe: transfer Character
kreadKit->>Seat: transfer Items
kreadKit->>Zoe: exit userSeat
kreadKit->>Seat: exit inventorySeat
kreadKit->>Seat: inventorySeat.getPayouts()
destroy Seat
Seat-->>kreadKit: return Items payment
kreadKit->>Purse: deposit Items into Purse
Zoe-->>Usw: Character payout
kreadKit->>State: remove baseCharacter from character.base
note over kreadKit: the new Character has the attribute Items purse
kreadKit->>State: add the new Character to character.entries
kreadKit->>State: remove baseItems from items.base
kreadKit->>State: add Purse to items.entries
On this diagram, we can observe an inventorySeat being created, the reason for that is to ensure the offer safety required for this process. More specifically, we need to ensure that the user is providing the respective Item payment that was specified in the proposal.
sequenceDiagram
box Blue User
participant Usw as SmartWallet
end
participant Zoe
box Green KREAd
participant kreadKit
participant State
end
Usw->>Zoe: equip offerSpec
Zoe->>kreadKit: Proposal {give CharacterIn + Item } {want CharacterOut}
Zoe-->>Usw: UserSeat
kreadKit->>kreadKit: validate Character
kreadKit->>State: get characterRecord from character.entries
State-->>kreadKit: return characterRecord
kreadKit->>State: get respective Purse from items.entries
create participant Purse
State->>Purse: get Purse
Purse-->>kreadKit: provide inventoryPurse
create participant Seat
kreadKit->>Seat: zcf.makeEmptySeatKit()
Seat-->>kreadKit: provide userSeat and zcfSeat
kreadKit->>Zoe: transfer Character
kreadKit->>Seat: transfer Items
kreadKit->>Zoe: exit userSeat
kreadKit->>Seat: exit inventorySeat
kreadKit->>Seat: inventorySeat.getPayouts()
destroy Seat
Seat-->>kreadKit: return Items payment
kreadKit->>Purse: deposit Items into Purse
Zoe-->>Usw: Character payout
sequenceDiagram
box Blue User
participant Usw as SmartWallet
end
participant Zoe
box Green KREAd
participant kreadKit
participant State
end
Usw->>Zoe: unequip offerSpec
Zoe->>kreadKit: Proposal {give CharacterIn} {want CharacterOut + Item}
Zoe-->>Usw: UserSeat
kreadKit->>kreadKit: validate Character
kreadKit->>State: get characterRecord from character.entries
State-->>kreadKit: return characterRecord
create participant Purse
kreadKit->>Purse: get respective Purse
Purse-->>kreadKit: provide inventoryPurse
kreadKit->>Purse: Purse.withdraw(Item)
Purse-->>kreadKit: return Item payment
kreadKit->>Zoe: transfer Character + Item
kreadKit->>Zoe: exit userSeat
Zoe-->>Usw: Character + Item payout
sequenceDiagram
box Blue User
participant Usw as SmartWallet
end
participant Zoe
box Green KREAd
participant kreadKit
participant State
end
Usw->>Zoe: sellCharacter offerSpec
Zoe->>kreadKit: Proposal {give Character} {want Price}
Zoe-->>Usw: UserSeat
kreadKit->>kreadKit: validate Character
create participant Purse
kreadKit->>Purse: itemsIssuer.makeEmptyPurse()
Note right of kreadKit: Why purse from item?
Purse-->>kreadKit: provide characterMarketPurse
create participant Seat
kreadKit->>Seat: zcf.makeEmptySeatKit()
Seat-->>kreadKit: provide userSeat and zcfSeat
kreadKit->>Seat: transfer Character
kreadKit->>Seat: exit inventorySeat
kreadKit->>Seat: inventorySeat.getPayouts()
destroy Seat
Seat-->>kreadKit: return payment
kreadKit->>Purse: deposit Character into Purse
note over kreadKit: the marketEntryGuard will include the Purse
kreadKit->>State: add new sell entry to market.characterEntries
sequenceDiagram
box Blue User
participant Usw as SmartWallet
end
participant Zoe
box Green KREAd
participant kreadKit
participant State
end
Usw->>Zoe: buyCharacter offerSpec
Zoe->>kreadKit: Proposal {give Price} {want Character}
Zoe-->>Usw: UserSeat
kreadKit->>kreadKit: validate Character
kreadKit->>State: get sell record from market.characterEntries
State-->>kreadKit: return sell record
create participant Purse
kreadKit->>Purse: get respective Purse
Purse-->>kreadKit: provide characterMarketPurse
kreadKit->>Purse: purse.withdraw(Character)
Purse-->>kreadKit: return Character payment
create participant Seat
kreadKit->>Seat: get respective seller seat
Seat-->>kreadKit: provide userSeat
kreadKit->>Seat: transfer Price
kreadKit->>Zoe: transfer Character
destroy Seat
kreadKit->>Seat: exit sellerSeat
kreadKit->>Zoe: exit userSeat
kreadKit->>State: remove sellRecord to market.characterEntries
The major difference in the contact state is the inventory Purse that was included into the CharacterEntryGuard
classDiagram
baggage *-- contractState
contractState *-- charactersState
contractState *-- itemsState
contractState *-- marketState
charactersState *-- characters
charactersState *-- baseCharacters
itemsState *-- items
itemsState *-- baseItems
marketState *-- characterMarket
marketState *-- itemMarket
marketState *-- marketMetrics
baseCharacters o-- BaseCharacterGuard
characters o-- CharacterEntryGuard
items o-- ItemRecorderGuard
baseItems o-- ItemGuard
characterMarket o-- MarketEntryGuard
itemMarket o-- MarketEntryGuard
marketMetrics o-- MarketMetricsGuard
CharacterEntryGuard o-- HistoryGuard
ItemRecorderGuard o-- HistoryGuard
CharacterEntryGuard o-- CharacterGuard
ItemRecorderGuard o-- ItemGuard
MarketEntryGuard o-- CharacterGuard
MarketEntryGuard o-- ItemGuard
class characters {
+String keyShape
+CharacterEntryGuard valueShape
}
class baseCharacters {
+Number keyShape
+BaseCharacterGuard valueShape
}
class items {
+Number keyShape
+ItemRecorderGuard valueShape
}
class baseItems {
+String keyShape
+List~ItemGuard~ valueShape
}
class characterMarket {
+String keyShape
+MarketEntryGuard valueShape
}
class itemMarket {
+Number keyShape
+MarketEntryGuard valueShape
}
class marketMetrics {
+String keyShape
+MarketMetricsGuard valueShape
}
class BaseCharacterGuard {
+String title
+String description
+String origin
+Number level
+String artistMetadata
+String image
+String characterTraits
}
class CharacterEntryGuard {
+String name
+CharacterGuard character
+Purse inventory
+RecorderKit inventoryKit
+List~HistoryGuard~ history
}
class ItemRecorderGuard {
+Number id
+ItemGuard item
+List~HistoryGuard~ history
}
class MarketEntryGuard {
+String or Number id
+Seat seat
+Purse purse
+RecorderKit recorderKit
+Amount askingPrice
+Amount royalty
+Amount platformFee
+CharacterGuard or ItemGuard asset
+bool isFirstSale
}
class MarketMetricsGuard {
+Number amountSold
+Number collectionSize
+Number averageLevel
+Number marketplaceAverageLevel
+Number latestSalePrice
+Number putForSaleCount
}
class HistoryGuard {
+String type
+Any data
+Timestamp timestamp
}
class CharacterGuard {
+String title
+String description
+String origin
+Number level
+String artistMetadata
+String image
+String characterTraits
+String name
+Number keyId
+Number id
+timestamp date
}
class ItemGuard {
+String name
+String category
+String description
+bool functional
+String origin
+String image
+String thumbnail
+Number rarity
+Number level
+Number filtering
+Number weight
+Number sense
+Number reserves
+Number durability
+String colors
+List~String~ artistMetadata
}
Design Proposition For KREAd Performance Issue
Context
See https://github.com/Agoric/agoric-sdk/issues/8862
Problems targeted
zoe
vatcopyBag
AmountMath
operations withcopyBag
causes too much computationAlternative Inventory
The current KREAd implementation uses multiple
ZCFSeat
s to store equipped items and a duplicate of user's characterto ensure users can interact with the contract under Zoe's protection. What we propose has two main improvements to this implementation;
Wash Trades Get rid of the duplicate character. Based on this comment from Chris Hibbert, it is explained that we can indeed execute "wash trades", wish means that the user can give and request the same asset when building an offer. Although It is important to notice that the proposal will need a different keyword for the want and give Character amount.
Dedicated purse over open ZCFSeat To remove the dependency on the inventorySeat, we intend to use the Items issuer to make an empty Purse for each Character that will hold the equipped Items. This purse will be included in the Character record, replacing the inventory seat attribute. This way we can deposit and withdraw the required Items to assure the normal flow of KREAd without the performance load of the previous design.
Replace
ITEMS
issuer To complement the change above, we wish to replace the ZCFMint created for Item into an IssuerKit. The reason behind this decision is based on the Vats where the Purses will be living in. When using themakeZCFMint
method, the Purses created from the issuer returned, will live invat-zoe
, while with themakeIssuerKit
method, the Purses created will be stored invat-kread
. We expect that this will reduce the load on Zoe and improve its performance.vat-zoe
does not create a problem for Zoe, we can consider skipping this feature.Implementation Approaches
Both approaches below takes the idea above and implements them with different considerations.
vat-kread
from allcopyBag
computations(add, subtract).vat-kread
Approach Implements alternative inventory approach inside thekreadKit
(or a dedicated virtual object, TBD) to avoid vat-to-vat communication overhead.Migration
There are users who already minted characters and items with the existing way. We must implement a mechanism to migrate those users to the latest version. This means;
KREAdITEM
will changeITEMS
issuer, this is omittedinventorySeat
KREAdITEM
with the exact amounts that are equipped to the characterITEMS
issuer, this is omittedinventorySeat
We should probably perform this migration as old users try to interact with the application after the upgrade instead of trying to move all characters and items at once(for the sake of Zoe). So we end two main ways to do this;
migration
offer from the user if their character DOES NOT have a registered purseequip
,unequip
, ..etc) will not take longer timeITEMS
issuer changes) We can structure the offer in the frontend in a way that users include their unequipped items in their migration offers so that we have a plan for how to get rid of assets that still use oldITEMS
issuer.equip
,unequip
, ..etc).equip
,unequip
, ..etc) that include migration(we'll have manually look for in the usual operation includes any migration or not).How to migrate characters/items that are on sale?
This question has its own heading simply because it's a pretty big one and we don't have a high level of confidence in the possible directions we're considering to take.
The KREAd market place has currently 524 items and 35 characters in sale. All sales are managed with an open
ZCFSeat
. There are two types of item sales, (1) First sale, means items minted by KREAd and being sold directly by the protocol, (2) Secondary sale, means users are selling the items they own. All character sales are done by the users, meaning they are similar to secondary item sales. From Zoe's point of view, it makes no difference whether the sale is secondary or not because all payments escrowed in various number ofZCFSeat
s are managed by the same purse. However during the migration, we might have to deal with first and secondary sales differently. The reason being, secondary sales actually have a user expecting a payout when first sales send the payout to some protocol account. Which means, in order not to break customer experience we must take careful approach in this. This problem has both a product and technical aspect to it. The product side is more related to the secondary sales whereas technical side is going to care about the first sales. This is simply because the majority of 524 items on sale are first sales. Therefore, most of the load is coming from there. The ideas we've considered so far for the first item sales;internalSellSeat
;numberOfItemsInPurse
will decreaseOther Open Questions
ITEMS
issuer)