Jorge-Lopes / KREAd

KREAd is an application that uses and implements composable NFTs on Agoric, the application allows users to customize their own characters by equipping and unequipping items.
Apache License 2.0
0 stars 0 forks source link

Work Plan #1

Open Jorge-Lopes opened 7 months ago

Jorge-Lopes commented 7 months ago

Problem Definition

KREAd dapp require maintenance effort to:

We advise to explore the code analysis for a detailed description of the KREAd features, with the support of sequence diagrams.

Performance issues

The initial effort will be focused on the contract side, more specifically, address the following task:

The changes made to the KREAd contract has to ensure the following rules:

Proposed solution:

The solution designed will require multiple changes to the current implementation.

Wash Trades The first change intend to remove the need to, when minting a character, creating 2 identical characters (A and B), where the character A is allocated in the userSeat of the user who made the offer, and the character B, along with the default Items, will be allocated in the inventorySeat managed by the Kread contract.

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 noticed that the proposal will need a different keyword for the want and give Character amount.

Items Purse The second change intend to remove the need for the inventorySeat, that holds the duplicated Character along with the equipped Items.

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.

ERTP issuer To complement the change above, we wish to replace the ZCFMint created for both Character and Item into an IssuerKit. The reason behind this decision is based on the Vats where the Purses will be stored. When using the makeZCFMint method, the Purses created from the issuer returned will be stored in the Zoe Vat, while with the makeIssuerKit method, the Purses created will be stored in the KREAd Vat. We expect that this will reduce the load on Zoe and improve its performance.

Market Purse When an user wish to sell a Character or an Item, the asset will be escrowed in Zoe, which contributes for the performance issue described above. To address this issue, one additional change we intend to implement is to create a dedicated Purse for every sellRecord, that will hold the asset until the sell is executed or the user decides to cancel it. This Purse will be recorded in the contract state, by including it in the MarketEntryGuard, along with the seller Seat.

Design representation

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.

Mint Character

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

Equip Item

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

Unequip Item

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

Sell Character

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

Buy Character

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

Contract state

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
}

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;

We should 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).

Characters and Inventory

Regarding the steps to migrate the duplicated Character and equipped Items to the new version, the steps to follow should be:

Items not equipped

Regarding the unequipped Items, when the user tries to equip or swap the Item, it should:

Assets on sale at the market

The assets registered in the marketState can be separated into 2 categories, based on the seller seat:

For the Items owned by the KREAd, the migration process should remove those sell records from the marketState and exit the internalSellSeat. The publishItemCollection method could be invoked again at contract upgrade, which would mint the new collection with the updated KREAdITEM issuer and deposit it in the dedicated market Purse IMPORTANT: it is important to review the original collection used to remove any Items that is not listed in the market (that will prevent duplicating an Item that was already purchased by an user)

For the Characters and Items owned by users, the migration process could be, progressively:

Open Questions:

Jorge-Lopes commented 7 months ago

Work Items