Browse Source

query node - NFT issuance mappings update

ondratra 3 years ago
parent
commit
d897b639e9

File diff suppressed because it is too large
+ 0 - 0
chain-metadata.json


+ 2 - 2
cli/src/commands/account/chooseMember.ts → cli/src/commands/membership/chooseMember.ts

@@ -4,7 +4,7 @@ import chalk from 'chalk'
 import { flags } from '@oclif/command'
 import ExitCodes from '../../ExitCodes'
 
-export default class AccountChooseMember extends MembershipsCommandBase {
+export default class MembershipChooseMember extends MembershipsCommandBase {
   static description = 'Choose default member to use in the CLI'
   static flags = {
     memberId: flags.string({
@@ -16,7 +16,7 @@ export default class AccountChooseMember extends MembershipsCommandBase {
   }
 
   async run() {
-    const { memberId } = this.parse(AccountChooseMember).flags
+    const { memberId } = this.parse(MembershipChooseMember).flags
 
     const selectedMember = memberId
       ? await this.selectKnownMember(memberId)

+ 127 - 131
query-node/mappings/src/content/nft.ts

@@ -17,9 +17,11 @@ import {
   TransactionalStatusIdle,
   TransactionalStatusBuyNow,
   TransactionalStatusAuction,
+  TransactionalStatusUpdate,
   ContentActor,
   ContentActorMember,
   ContentActorCurator,
+  ContentActorLead,
   Curator,
 
   // events
@@ -133,7 +135,8 @@ async function resetNftTransactionalStatusFromVideo(
   store: DatabaseManager,
   videoId: string,
   errorMessage: string,
-  newOwner?: Membership
+  blockNumber: number,
+  newOwner?: Membership,
 ) {
   // load NFT
   const nft = await store.get(OwnedNft, { where: { id: videoId.toString() } as FindConditions<OwnedNft> })
@@ -143,15 +146,13 @@ async function resetNftTransactionalStatusFromVideo(
     return inconsistentState(errorMessage, videoId.toString())
   }
 
-  // reset transactional status
-  nft.transactionalStatus = new TransactionalStatusIdle()
-
   if (newOwner) {
     nft.ownerMember = newOwner
   }
 
-  // save NFT
-  await store.save<OwnedNft>(nft)
+  // reset transactional status
+  const transactionalStatus = new TransactionalStatusIdle()
+  await setNewNftTransactionalStatus(store, nft, transactionalStatus, blockNumber)
 }
 
 async function getRequiredExistingEntites<Type extends Video | Membership>(
@@ -215,13 +216,33 @@ async function convertContentActor(
     return result
   }
 
-  // contentActor.isLead not supported (not needed) now
+  if (contentActor.isLead) {
+    return new ContentActorLead()
+  }
 
   logger.error('Not implemented ContentActor type', { contentActor: contentActor.toString() })
   throw new Error('Not-implemented ContentActor type used')
 }
 
-async function finishAuction(store: DatabaseManager, videoId: number) {
+async function setNewNftTransactionalStatus(store: DatabaseManager, nft: OwnedNft, transactionalStatus: typeof TransactionalStatus, blockNumber: number) {
+  // update transactionalStatus
+  nft.transactionalStatus = transactionalStatus
+
+  // save NFT
+  await store.save<OwnedNft>(nft)
+
+  // create transactional status update record
+  const transactionalStatusUpdate = new TransactionalStatusUpdate({
+    nft,
+    transactionalStatus: nft.transactionalStatus,
+    changedAt: blockNumber,
+  })
+
+  // save update record
+  await store.save<TransactionalStatusUpdate>(transactionalStatusUpdate)
+}
+
+async function finishAuction(store: DatabaseManager, videoId: number, blockNumber: number) {
   // load video and auction
   const { video, auction } = await getCurrentAuctionFromVideo(
     store,
@@ -241,11 +262,12 @@ async function finishAuction(store: DatabaseManager, videoId: number) {
   )
 
   // update NFT's transactional status
-  await resetNftTransactionalStatusFromVideo(store, videoId.toString(), `Non-existing NFT's auction completed`, winner)
+  await resetNftTransactionalStatusFromVideo(store, videoId.toString(), `Non-existing NFT's auction completed`, blockNumber, winner)
 
   // update auction
   auction.isCompleted = true
   auction.winningMember = winner
+  auction.endedAtBlock = blockNumber
 
   // save auction
   await store.save<Auction>(auction)
@@ -302,28 +324,81 @@ async function createBid(
 export async function createNft(
   store: DatabaseManager,
   video: Video,
-  ownerMember,
-  creatorRoyalty: number | undefined,
-  metadata: string,
-  transactionalStatus: typeof TransactionalStatus
-) {
+  nftIssuanceParameters: joystreamTypes.NftIssuanceParameters,
+  blockNumber: number,
+): Promise<OwnedNft> {
+  // load owner
+  const ownerMember = nftIssuanceParameters.non_channel_owner.isSome
+    ? await getExistingEntity(store, Membership, nftIssuanceParameters.non_channel_owner.unwrap().toString())
+    : undefined
+
+  // calculate some values
+  const creatorRoyalty = nftIssuanceParameters.royalty.isSome ? nftIssuanceParameters.royalty.unwrap().toNumber() : undefined
+  const decodedMetadata = nftIssuanceParameters.nft_metadata.toString()
+
   // prepare nft record
-  const ownedNft = new OwnedNft({
+  const nft = new OwnedNft({
     id: video.id.toString(),
     video: video,
     ownerMember,
     creatorRoyalty,
-    metadata: metadata,
+    metadata: decodedMetadata,
+    // always start with Idle status to prevent egg-chicken problem between auction+nft; update it later if needed
     transactionalStatus: new TransactionalStatusIdle(),
   })
 
   // save nft
-  await store.save<OwnedNft>(ownedNft)
+  await store.save<OwnedNft>(nft)
+
+  // update NFT transactional status
+  const transactionalStatus = await convertTransactionalStatus(nftIssuanceParameters.init_transactional_status, store, nft, blockNumber)
+  await setNewNftTransactionalStatus(store, nft, transactionalStatus, blockNumber)
+
+  return nft
 }
 
-function convertTransactionalStatus(
-  transactionalStatus: joystreamTypes.InitTransactionalStatus
-): typeof TransactionalStatus {
+async function createAuction(
+  store: DatabaseManager,
+  nft: OwnedNft, // expects `nft.ownerMember` to be available
+  auctionParams: joystreamTypes.AuctionParams,
+  blockNumber: number
+): Promise<Auction> {
+  const whitelistedMembers = await getRequiredExistingEntites(
+    store,
+    Membership,
+    Array.from(auctionParams.whitelist.values()).map((item) => item.toString()),
+    'Non-existing members whitelisted'
+  )
+
+  // prepare auction record
+  const auction = new Auction({
+    nft: nft,
+    initialOwner: nft.ownerMember,
+    startingPrice: auctionParams.starting_price,
+    buyNowPrice: new BN(auctionParams.buy_now_price.toString()),
+    auctionType: createAuctionType(auctionParams.auction_type),
+    minimalBidStep: auctionParams.minimal_bid_step,
+    startsAtBlock: auctionParams.starts_at.isSome ? auctionParams.starts_at.unwrap().toNumber() : blockNumber,
+    plannedEndAtBlock: auctionParams.auction_type.isEnglish
+      ? blockNumber + auctionParams.auction_type.asEnglish.auction_duration.toNumber()
+      : undefined,
+    isCanceled: false,
+    isCompleted: false,
+    whitelistedMembers,
+  })
+
+  // save auction
+  await store.save<Auction>(auction)
+
+  return auction
+}
+
+export async function convertTransactionalStatus(
+  transactionalStatus: joystreamTypes.InitTransactionalStatus,
+  store: DatabaseManager,
+  nft: OwnedNft,
+  blockNumber: number,
+): Promise<typeof TransactionalStatus> {
   if (transactionalStatus.isIdle) {
     return new TransactionalStatusIdle()
   }
@@ -339,7 +414,11 @@ function convertTransactionalStatus(
   }
 
   if (transactionalStatus.isAuction) {
-    // TODO: auction
+    const auctionParams = transactionalStatus.asAuction
+    const status = new TransactionalStatusAuction()
+    status.auction = await createAuction(store, nft, auctionParams, blockNumber)
+
+    return status
   }
 
   logger.error('Not implemented TransactionalStatus type', { contentActor: transactionalStatus.toString() })
@@ -349,7 +428,7 @@ function convertTransactionalStatus(
 export async function contentNft_AuctionStarted({ event, store }: EventContext & StoreContext): Promise<void> {
   // common event processing
 
-  const [ownerActor, videoId, auctionParams] = new Content.AuctionStartedEvent(event).params
+  const [contentActor, videoId, auctionParams] = new Content.AuctionStartedEvent(event).params
 
   // specific event processing
 
@@ -359,7 +438,7 @@ export async function contentNft_AuctionStarted({ event, store }: EventContext &
     Video,
     videoId.toString(),
     `Non-existing video's auction started`,
-    ['nft']
+    ['nft', 'nft.ownerMember']
   )
 
   // ensure NFT has been issued
@@ -367,64 +446,21 @@ export async function contentNft_AuctionStarted({ event, store }: EventContext &
     return inconsistentState('Non-existing NFT auctioned', video.id.toString())
   }
 
-  // member seems to be only actor that can own NFT right now
-  if (!ownerActor.isMember) {
-    throw new Error(`Not implemented NFT owner type "${ownerActor}"`)
-  }
-  const ownerId = ownerActor.asMember
-
-  // load member
-  const member = await getRequiredExistingEntity(
-    store,
-    Membership,
-    ownerId.toString(),
-    'Non-existing member started video auction'
-  )
-
-  const whitelistedMembers = await getRequiredExistingEntites(
-    store,
-    Membership,
-    Array.from(auctionParams.whitelist.values()).map((item) => item.toString()),
-    'Non-existing members whitelisted'
-  )
-
-  // prepare auction record
-  const auction = new Auction({
-    nft: video.nft,
-    initialOwner: member,
-    startingPrice: auctionParams.starting_price,
-    buyNowPrice: new BN(auctionParams.buy_now_price.toString()),
-    auctionType: createAuctionType(auctionParams.auction_type),
-    minimalBidStep: auctionParams.minimal_bid_step,
-    startsAtBlock: auctionParams.starts_at.isSome ? auctionParams.starts_at.unwrap().toNumber() : event.blockNumber,
-    plannedEndAtBlock: auctionParams.auction_type.isEnglish
-      ? event.blockNumber + auctionParams.auction_type.asEnglish.auction_duration.toNumber()
-      : undefined,
-    isCanceled: false,
-    isCompleted: false,
-    whitelistedMembers,
-  })
+  const nft = video.nft
 
-  // save auction
-  await store.save<Auction>(auction)
+  const auction = await createAuction(store, nft, auctionParams, event.blockNumber)
 
+  // update NFT transactional status
   const transactionalStatus = new TransactionalStatusAuction()
   transactionalStatus.auctionId = auction.id
-
-  video.nft.transactionalStatus = transactionalStatus
-
-  // save NFT
-  await store.save<OwnedNft>(video.nft)
+  await setNewNftTransactionalStatus(store, nft, transactionalStatus, event.blockNumber)
 
   // common event processing - second
 
-  const actor = new ContentActorMember()
-  actor.member = new Membership({ id: ownerId.toString() })
-
   const announcingPeriodStartedEvent = new AuctionStartedEvent({
     ...genericEventFields(event),
 
-    actor,
+    actor: await convertContentActor(store, contentActor),
     video,
     auction,
   })
@@ -457,7 +493,7 @@ function createAuctionType(rawAuctionType: joystreamTypes.AuctionType): typeof A
 export async function contentNft_NftIssued({ event, store }: EventContext & StoreContext): Promise<void> {
   // common event processing
 
-  const [actor, videoId, royalty, metadata, mbNewOwnerId, initTransactionalStatus] = new Content.NftIssuedEvent(
+  const [actor, videoId, nftIssuanceParameters] = new Content.NftIssuedEvent(
     event
   ).params
 
@@ -466,28 +502,9 @@ export async function contentNft_NftIssued({ event, store }: EventContext & Stor
   // load video
   const video = await getRequiredExistingEntity(store, Video, videoId.toString(), 'NFT for non-existing video issed')
 
-  // load owner
-  const newOwner = await getExistingEntity(store, Membership, mbNewOwnerId.toString())
-
-  const creatorRoyalty = royalty.isSome ? royalty.unwrap().toNumber() : undefined
-  const transactionalStatus = convertTransactionalStatus(initTransactionalStatus)
-
-  await createNft(store, video, newOwner, creatorRoyalty, metadata.toString(), transactionalStatus)
+  // prepare and save nft record
+  const nft = await createNft(store, video, nftIssuanceParameters, event.blockNumber)
 
-  /*
-  // prepare nft record
-  const ownedNft = new OwnedNft({
-    id: video.id.toString(),
-    video,
-    ownerMember: newOwner,
-    creatorRoyalty,
-    metadata: metadata.toString(),
-    transactionalStatus: new TransactionalStatusIdle(),
-  })
-
-  // save nft
-  await store.save<OwnedNft>(ownedNft)
-*/
   // common event processing - second
 
   const announcingPeriodStartedEvent = new NftIssuedEvent({
@@ -495,9 +512,9 @@ export async function contentNft_NftIssued({ event, store }: EventContext & Stor
 
     contentActor: await convertContentActor(store, actor),
     video,
-    royalty: creatorRoyalty,
-    metadata: metadata.toString(),
-    newOwner,
+    royalty: nft.creatorRoyalty,
+    metadata: nft.metadata,
+    newOwner: nft.ownerMember,
   })
 
   await store.save<NftIssuedEvent>(announcingPeriodStartedEvent)
@@ -587,7 +604,7 @@ export async function contentNft_AuctionCanceled({ event, store }: EventContext
   )
 
   // update NFT's transactional status
-  await resetNftTransactionalStatusFromVideo(store, videoId.toString(), `Non-existing NFT's auction canceled`)
+  await resetNftTransactionalStatusFromVideo(store, videoId.toString(), `Non-existing NFT's auction canceled`, event.blockNumber)
 
   // mark auction as canceled
   auction.isCanceled = true
@@ -615,7 +632,7 @@ export async function contentNft_EnglishAuctionCompleted({ event, store }: Event
 
   // specific event processing
 
-  const { winner, video } = await finishAuction(store, videoId.toNumber())
+  const { winner, video } = await finishAuction(store, videoId.toNumber(), event.blockNumber)
 
   // common event processing - second
 
@@ -644,7 +661,7 @@ export async function contentNft_BidMadeCompletingAuction({
   await createBid(event, store, memberId.toNumber(), videoId.toNumber())
 
   // winish auction and transfer ownership
-  const { winner: member, video } = await finishAuction(store, videoId.toNumber())
+  const { winner: member, video } = await finishAuction(store, videoId.toNumber(), event.blockNumber)
 
   // common event processing - second
 
@@ -665,7 +682,7 @@ export async function contentNft_OpenAuctionBidAccepted({ event, store }: EventC
 
   // specific event processing
 
-  const { video } = await finishAuction(store, videoId.toNumber())
+  const { video } = await finishAuction(store, videoId.toNumber(), event.blockNumber)
 
   // common event processing - second
 
@@ -694,16 +711,11 @@ export async function contentNft_OfferStarted({ event, store }: EventContext & S
     'Non-existing nft was offered'
   )
 
-  // create offer
+  // update NFT transactional status
   const transactionalStatus = new TransactionalStatusInitiatedOfferToMember()
   transactionalStatus.memberId = memberId.toNumber()
   transactionalStatus.price = price.unwrapOr(undefined)
-
-  // update NFT
-  nft.transactionalStatus = transactionalStatus
-
-  // save NFT
-  await store.save<OwnedNft>(nft)
+  await setNewNftTransactionalStatus(store, nft, transactionalStatus, event.blockNumber)
 
   // common event processing - second
 
@@ -738,12 +750,8 @@ export async function contentNft_OfferAccepted({ event, store }: EventContext &
   const memberId = (nft.transactionalStatus as TransactionalStatusInitiatedOfferToMember).memberId
   const member = new Membership({ id: memberId.toString() })
 
-  // update NFT
-  nft.transactionalStatus = new TransactionalStatusIdle()
-  nft.ownerMember = member
-
-  // save NFT
-  await store.save<OwnedNft>(nft)
+  // update NFT's transactional status
+  await resetNftTransactionalStatusFromVideo(store, videoId.toString(), `Non-existing NFT's offer accepted`, event.blockNumber, member)
 
   // common event processing - second
 
@@ -771,11 +779,8 @@ export async function contentNft_OfferCanceled({ event, store }: EventContext &
     'Non-existing nft sell offer was canceled'
   )
 
-  // update NFT
-  nft.transactionalStatus = new TransactionalStatusIdle()
-
-  // save NFT
-  await store.save<OwnedNft>(nft)
+  // update NFT's transactional status
+  await resetNftTransactionalStatusFromVideo(store, videoId.toString(), `Non-existing NFT's offer canceled`, event.blockNumber)
 
   // common event processing - second
 
@@ -804,15 +809,10 @@ export async function contentNft_NftSellOrderMade({ event, store }: EventContext
     'Non-existing nft was offered'
   )
 
-  // create buy now offer
+  // update NFT transactional status
   const transactionalStatus = new TransactionalStatusBuyNow()
   transactionalStatus.price = price
-
-  // update NFT
-  nft.transactionalStatus = transactionalStatus
-
-  // save NFT
-  await store.save<OwnedNft>(nft)
+  await setNewNftTransactionalStatus(store, nft, transactionalStatus, event.blockNumber)
 
   // common event processing - second
 
@@ -846,7 +846,7 @@ export async function contentNft_NftBought({ event, store }: EventContext & Stor
   const winner = new Membership({ id: memberId.toString() })
 
   // update NFT's transactional status
-  await resetNftTransactionalStatusFromVideo(store, videoId.toString(), `Non-existing NFT's auction completed`, winner)
+  await resetNftTransactionalStatusFromVideo(store, videoId.toString(), `Non-existing NFT's auction completed`, event.blockNumber, winner)
 
   // common event processing - second
 
@@ -875,11 +875,7 @@ export async function contentNft_BuyNowCanceled({ event, store }: EventContext &
     'Non-existing nft was bought'
   )
 
-  // update NFT
-  nft.transactionalStatus = new TransactionalStatusIdle()
-
-  // save NFT
-  await store.save<OwnedNft>(nft)
+  await resetNftTransactionalStatusFromVideo(store, videoId.toString(), `Non-existing NFT's buy-now canceled`, event.blockNumber)
 
   // common event processing - second
 

+ 4 - 4
query-node/mappings/src/content/video.ts

@@ -12,11 +12,11 @@ import {
   videoRelationsForCountersBare,
   videoRelationsForCounters,
 } from './utils'
-import { Channel, Video, VideoCategory } from 'query-node/dist/model'
+import { Channel, OwnedNft, Video, VideoCategory } from 'query-node/dist/model'
 import { VideoMetadata, VideoCategoryMetadata } from '@joystream/metadata-protobuf'
 import { integrateMeta } from '@joystream/metadata-protobuf/utils'
 import _ from 'lodash'
-import { createNft } from './nft'
+import { createNft, convertTransactionalStatus } from './nft'
 
 export async function content_VideoCategoryCreated({ store, event }: EventContext & StoreContext): Promise<void> {
   // read event data
@@ -105,7 +105,7 @@ export async function content_VideoCreated(ctx: EventContext & StoreContext): Pr
   // load channel
   const channel = await store.get(Channel, {
     where: { id: channelId.toString() },
-    relations: ['category', 'ownerMember'],
+    relations: ['category'],
   })
 
   // ensure channel exists
@@ -134,7 +134,7 @@ export async function content_VideoCreated(ctx: EventContext & StoreContext): Pr
   if (videoCreationParameters.auto_issue_nft.isSome) {
     const issuanceParameters = videoCreationParameters.auto_issue_nft.unwrap()
 
-    // TODO: await createNft(store, video, channel.ownerMember, )
+    await createNft(store, video, issuanceParameters, event.blockNumber)
   }
 
   // update video active counters (if needed)

+ 17 - 6
query-node/schemas/contentNft.graphql

@@ -62,22 +62,33 @@ type OwnedNft @entity { # NFT in name can't be UPPERCASE because it causes codeg
   "Auctions done for this NFT"
   auctions: [Auction!]! @derivedFrom(field: "nft")
 
-  "Member owning the NFT (if any)"
+  "Member owning the NFT. Channel owner owns the NFT if not set."
   ownerMember: Membership
 
-  "Curator group owning the NFT (if any)"
-  ownerCuratorGroup: CuratorGroup
-
   "NFT's metadata"
   metadata: String!
 
   "NFT transactional status"
   transactionalStatus: TransactionalStatus!
 
+  "History of transacional status changes"
+  transactionalStatusUpdates: [TransactionalStatusUpdate!]! @derivedFrom(field: "nft")
+
   "Creator royalty"
   creatorRoyalty: Float
 }
 
+type TransactionalStatusUpdate @entity {
+  "Video NFT details"
+  nft: OwnedNft!
+
+  "NFT transactional status"
+  transactionalStatus: TransactionalStatus!
+
+  "Block number at which change happened"
+  changedAt: Int!
+}
+
 "NFT transactional state"
 union TransactionalStatus = TransactionalStatusIdle
   | TransactionalStatusInitiatedOfferToMember
@@ -136,8 +147,8 @@ type Auction @entity {
   "Auctioned NFT"
   nft: OwnedNft!
 
-  "Member starting NFT auction"
-  initialOwner: Membership!
+  "Member starting NFT auction. If not set channel owner started auction"
+  initialOwner: Membership
 
   "Member that won this auction"
   winningMember: Membership

+ 3 - 13
runtime-modules/content/src/lib.rs

@@ -1351,10 +1351,7 @@ decl_module! {
             Self::deposit_event(RawEvent::NftIssued(
                 actor,
                 video_id,
-                params.royalty,
-                params.nft_metadata,
-                params.non_channel_owner,
-                params.init_transactional_status
+                params,
             ));
         }
 
@@ -2243,7 +2240,7 @@ decl_event!(
             CurrencyOf<T>,
             <T as common::MembershipTypes>::MemberId,
         >,
-        InitTransactionalStatus = InitTransactionalStatus<T>,
+        NftIssuanceParameters = NftIssuanceParameters<T>,
         Balance = BalanceOf<T>,
         CurrencyAmount = CurrencyOf<T>,
         ChannelCreationParameters = ChannelCreationParameters<T>,
@@ -2327,14 +2324,7 @@ decl_event!(
         MinCashoutUpdated(Balance),
         // Nft auction
         AuctionStarted(ContentActor, VideoId, AuctionParams),
-        NftIssued(
-            ContentActor,
-            VideoId,
-            Option<Royalty>,
-            NftMetadata,
-            Option<MemberId>,
-            InitTransactionalStatus,
-        ),
+        NftIssued(ContentActor, VideoId, NftIssuanceParameters),
         AuctionBidMade(MemberId, VideoId, CurrencyAmount, IsExtended),
         AuctionBidCanceled(MemberId, VideoId),
         AuctionCanceled(ContentActor, VideoId),

+ 1 - 4
runtime-modules/content/src/tests/nft/issue_nft.rs

@@ -51,10 +51,7 @@ fn issue_nft() {
             MetaEvent::content(RawEvent::NftIssued(
                 ContentActor::Member(DEFAULT_MEMBER_ID),
                 video_id,
-                nft_issue_params.royalty,
-                nft_issue_params.nft_metadata,
-                nft_issue_params.non_channel_owner,
-                nft_issue_params.init_transactional_status,
+                nft_issue_params,
             )),
             number_of_events_before_call + 1,
         );

+ 1 - 1
tests/integration-tests/src/cli/joystream.ts

@@ -93,7 +93,7 @@ export class JoystreamCLI extends CLI {
     Selects active member for CLI commands.
   */
   async chooseMemberAccount(memberId: MemberId) {
-    const { stderr } = await this.run('account:chooseMember', ['--memberId', memberId.toString()])
+    const { stderr } = await this.run('membership:chooseMember', ['--memberId', memberId.toString()])
 
     if (stderr && !stderr.match(/^\s*Member switched to id/)) {
       throw new Error(`Unexpected CLI failure on choosing account: "${stderr}"`)

Some files were not shown because too many files changed in this diff