Browse Source

Merge branch 'olympia-proposals-mappings' into olympia-staging

Leszek Wiesner 3 years ago
parent
commit
62438503f0

+ 9 - 8
cli/src/graphql/generated/schema.ts

@@ -15,8 +15,6 @@ export type Scalars = {
   BigInt: any
   /** The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
   JSONObject: any
-  /** GraphQL representation of Bytes */
-  Bytes: any
 }
 
 export type AmendConstitutionProposalDetails = {
@@ -12170,16 +12168,16 @@ export enum RewardPaymentType {
 }
 
 export type RuntimeUpgradeProposalDetails = {
-  /** Runtime upgrade WASM bytecode */
-  wasmBytecode: Scalars['Bytes']
+  /** Runtime upgrade WASM bytecode hash */
+  wasmBytecodeHash: Scalars['String']
 }
 
 export type RuntimeUpgradeProposalDetailsCreateInput = {
-  wasmBytecode: Scalars['Bytes']
+  wasmBytecodeHash: Scalars['String']
 }
 
 export type RuntimeUpgradeProposalDetailsUpdateInput = {
-  wasmBytecode?: Maybe<Scalars['Bytes']>
+  wasmBytecodeHash?: Maybe<Scalars['String']>
 }
 
 export type RuntimeUpgradeProposalDetailsWhereInput = {
@@ -12207,8 +12205,11 @@ export type RuntimeUpgradeProposalDetailsWhereInput = {
   deletedAt_gte?: Maybe<Scalars['DateTime']>
   deletedById_eq?: Maybe<Scalars['ID']>
   deletedById_in?: Maybe<Array<Scalars['ID']>>
-  wasmBytecode_eq?: Maybe<Scalars['Bytes']>
-  wasmBytecode_in?: Maybe<Array<Scalars['Bytes']>>
+  wasmBytecodeHash_eq?: Maybe<Scalars['String']>
+  wasmBytecodeHash_contains?: Maybe<Scalars['String']>
+  wasmBytecodeHash_startsWith?: Maybe<Scalars['String']>
+  wasmBytecodeHash_endsWith?: Maybe<Scalars['String']>
+  wasmBytecodeHash_in?: Maybe<Array<Scalars['String']>>
   AND?: Maybe<Array<RuntimeUpgradeProposalDetailsWhereInput>>
   OR?: Maybe<Array<RuntimeUpgradeProposalDetailsWhereInput>>
 }

+ 26 - 9
query-node/mappings/proposals.ts

@@ -55,10 +55,12 @@ import {
   ProposalVotedEvent,
   ProposalVoteKind,
   ProposalCancelledEvent,
+  ProposalCreatedEvent,
 } from 'query-node/dist/model'
 import { bytesToString, genericEventFields, getWorkingGroupModuleName, perpareString } from './common'
 import { ProposalsEngine, ProposalsCodex } from './generated/types'
 import { createWorkingGroupOpeningMetadata } from './workingGroups'
+import { blake2AsHex } from '@polkadot/util-crypto'
 
 // FIXME: https://github.com/Joystream/joystream/issues/2457
 type ProposalsMappingsMemoryCache = {
@@ -95,7 +97,7 @@ async function parseProposalDetails(
   else if (proposalDetails.isRuntimeUpgrade) {
     const details = new RuntimeUpgradeProposalDetails()
     const specificDetails = proposalDetails.asRuntimeUpgrade
-    details.wasmBytecode = Buffer.from(specificDetails.toU8a(true))
+    details.wasmBytecodeHash = blake2AsHex(Buffer.from(specificDetails.toU8a(true)))
     return details
   }
   // FundingRequestProposalDetails:
@@ -263,25 +265,34 @@ async function parseProposalDetails(
   else if (proposalDetails.isCreateBlogPost) {
     const details = new CreateBlogPostProposalDetails()
     const specificDetails = proposalDetails.asCreateBlogPost
-    // TODO:
+    const [title, body] = specificDetails
+    details.title = perpareString(title.toString())
+    details.body = perpareString(body.toString())
+    return details
   }
   // EditBlogPostProposalDetails:
   else if (proposalDetails.isEditBlogPost) {
     const details = new EditBlogPostProposalDetails()
     const specificDetails = proposalDetails.asEditBlogPost
-    // TODO:
+    const [postId, optTitle, optBody] = specificDetails
+    details.blogPost = postId.toString()
+    details.newTitle = optTitle.isSome ? perpareString(optTitle.unwrap().toString()) : undefined
+    details.newBody = optBody.isSome ? perpareString(optBody.unwrap().toString()) : undefined
+    return details
   }
   // LockBlogPostProposalDetails:
   else if (proposalDetails.isLockBlogPost) {
     const details = new LockBlogPostProposalDetails()
-    const specificDetails = proposalDetails.asLockBlogPost
-    // TODO:
+    const postId = proposalDetails.asLockBlogPost
+    details.blogPost = postId.toString()
+    return details
   }
   // UnlockBlogPostProposalDetails:
   else if (proposalDetails.isUnlockBlogPost) {
     const details = new UnlockBlogPostProposalDetails()
-    const specificDetails = proposalDetails.asUnlockBlogPost
-    // TODO:
+    const postId = proposalDetails.asUnlockBlogPost
+    details.blogPost = postId.toString()
+    return details
   }
   // VetoProposalDetails:
   else if (proposalDetails.isVetoProposal) {
@@ -289,9 +300,9 @@ async function parseProposalDetails(
     const specificDetails = proposalDetails.asVetoProposal
     details.proposalId = specificDetails.toString()
     return details
+  } else {
+    throw new Error(`Unspported proposal details type: ${proposalDetails.type}`)
   }
-
-  throw new Error(`Unspported proposal details type: ${proposalDetails.type}`)
 }
 
 export async function proposalsEngine_ProposalCreated({ event }: EventContext & StoreContext): Promise<void> {
@@ -326,6 +337,12 @@ export async function proposalsCodex_ProposalCreated({ store, event }: EventCont
     statusSetAtTime: eventTime,
   })
   await store.save<Proposal>(proposal)
+
+  const proposalCreatedEvent = new ProposalCreatedEvent({
+    ...genericEventFields(event),
+    proposal: proposal,
+  })
+  await store.save<ProposalCreatedEvent>(proposalCreatedEvent)
 }
 
 export async function proposalsEngine_ProposalStatusUpdated({

+ 2 - 2
query-node/schemas/proposals.graphql

@@ -153,8 +153,8 @@ type SignalProposalDetails @variant {
 }
 
 type RuntimeUpgradeProposalDetails @variant {
-  "Runtime upgrade WASM bytecode"
-  wasmBytecode: Bytes!
+  "Runtime upgrade WASM bytecode hash"
+  wasmBytecodeHash: String!
 }
 
 type FundingRequestDestination @entity {

+ 1 - 1
tests/integration-tests/proposal-parameters.json

@@ -4,7 +4,7 @@
       "grace_period": 0
   },
   "runtime_upgrade_proposal": {
-    "voting_period": 20,
+    "voting_period": 100,
     "grace_period": 20,
     "constitutionality": 2
   },

+ 7 - 0
tests/integration-tests/run-test-scenario.sh

@@ -9,5 +9,12 @@ SCENARIO=$1
 # fallback if scenario if not specified
 SCENARIO=${SCENARIO:=full}
 
+if [ -n "$RUNTIME_UPGRADE_TARGET_IMAGE_TAG" ]; then
+  export RUNTIME_UPGRADE_TARGET_WASM_PATH=${RUNTIME_UPGRADE_TARGET_WASM_PATH:="./target-runtime.wasm"}
+  id=`docker create joystream/node:${RUNTIME_UPGRADE_TARGET_IMAGE_TAG}`
+  docker cp $id:/joystream/runtime.compact.wasm $RUNTIME_UPGRADE_TARGET_WASM_PATH
+  docker rm $id
+fi
+
 # Execute the tests
 time DEBUG=* yarn workspace integration-tests node-ts-strict src/scenarios/${SCENARIO}.ts

+ 1 - 1
tests/integration-tests/src/Api.ts

@@ -299,7 +299,7 @@ export class Api {
       return
     }
 
-    const blockHash = status.asInBlock.toString()
+    const blockHash = (status.isInBlock ? status.asInBlock : status.asFinalized).toString()
     const blockNumber = (await this.api.rpc.chain.getHeader(blockHash)).number.toNumber()
     const blockTimestamp = (await this.api.query.timestamp.now.at(blockHash)).toNumber()
     const blockEvents = await this.api.query.system.events.at(blockHash)

+ 1 - 1
tests/integration-tests/src/QueryNodeApi.ts

@@ -287,7 +287,7 @@ export class QueryNodeApi {
     query: () => Promise<QueryResultT>,
     assertResultIsValid: (res: QueryResultT) => void,
     retryTimeMs = BLOCKTIME * 3,
-    retries = 3
+    retries = 6
   ): Promise<QueryResultT> {
     const label = query.toString().replace(/^.*\.([A-za-z0-9]+\(.*\))$/g, '$1')
     const debug = this.tryDebug.extend(label)

+ 1 - 1
tests/integration-tests/src/fixtures/proposals/AllProposalsOutcomesFixture.ts

@@ -62,7 +62,7 @@ export class AllProposalsOutcomesFixture extends BaseFixture {
     let batch: ProposalTestCase[]
     let n = 0
     while ((batch = testCases.slice(n * proposalsPerBatch, (n + 1) * proposalsPerBatch)).length) {
-      await api.untilProposalsCanBeCreated(proposalsPerBatch)
+      await api.untilProposalsCanBeCreated(batch.length)
       const createProposalsParams: ProposalCreationParams[] = batch.map(({ type, details, decisionStatus }, i) => ({
         asMember: memberIds[i],
         title: `${_.startCase(type)}`,

+ 17 - 10
tests/integration-tests/src/fixtures/proposals/CreateProposalsFixture.ts

@@ -13,6 +13,7 @@ import { AddStakingAccountsHappyCaseFixture } from '../membership'
 import { getWorkingGroupModuleName } from '../../consts'
 import { assertQueriedOpeningMetadataIsValid } from '../workingGroups/utils'
 import { OpeningMetadata } from '@joystream/metadata-protobuf'
+import { blake2AsHex } from '@polkadot/util-crypto'
 
 export type ProposalCreationParams<T extends ProposalType = ProposalType> = {
   asMember: MemberId
@@ -115,7 +116,9 @@ export class CreateProposalsFixture extends StandardizedFixture {
       case 'CreateBlogPost': {
         Utils.assert(qProposal.details.__typename === 'CreateBlogPostProposalDetails')
         const details = proposalDetails.asType('CreateBlogPost')
-        // TODO
+        const [title, body] = details
+        assert.equal(qProposal.details.title, title.toString())
+        assert.equal(qProposal.details.body, body.toString())
         break
       }
       case 'CreateWorkingGroupLeadOpening': {
@@ -144,7 +147,10 @@ export class CreateProposalsFixture extends StandardizedFixture {
       case 'EditBlogPost': {
         Utils.assert(qProposal.details.__typename === 'EditBlogPostProposalDetails')
         const details = proposalDetails.asType('EditBlogPost')
-        // TODO
+        const [postId, newTitle, newBody] = details
+        assert.equal(qProposal.details.blogPost, postId.toString())
+        assert.equal(qProposal.details.newTitle, newTitle.unwrapOr(undefined)?.toString())
+        assert.equal(qProposal.details.newBody, newBody.unwrapOr(undefined)?.toString())
         break
       }
       case 'FillWorkingGroupLeadOpening': {
@@ -169,14 +175,14 @@ export class CreateProposalsFixture extends StandardizedFixture {
       }
       case 'LockBlogPost': {
         Utils.assert(qProposal.details.__typename === 'LockBlogPostProposalDetails')
-        const details = proposalDetails.asType('LockBlogPost')
-        // TODO
+        const postId = proposalDetails.asType('LockBlogPost')
+        assert.equal(qProposal.details.blogPost, postId.toString())
         break
       }
       case 'RuntimeUpgrade': {
         Utils.assert(qProposal.details.__typename === 'RuntimeUpgradeProposalDetails')
         const details = proposalDetails.asType('RuntimeUpgrade')
-        // TODO
+        assert.equal(qProposal.details.wasmBytecodeHash, blake2AsHex(details))
         break
       }
       case 'SetCouncilBudgetIncrement': {
@@ -261,8 +267,8 @@ export class CreateProposalsFixture extends StandardizedFixture {
       }
       case 'UnlockBlogPost': {
         Utils.assert(qProposal.details.__typename === 'UnlockBlogPostProposalDetails')
-        const details = proposalDetails.asType('UnlockBlogPost')
-        // TODO
+        const postId = proposalDetails.asType('UnlockBlogPost')
+        assert.equal(qProposal.details.blogPost, postId.toString())
         break
       }
       case 'UpdateWorkingGroupBudget': {
@@ -296,19 +302,20 @@ export class CreateProposalsFixture extends StandardizedFixture {
       assert.equal(qProposal.status.__typename, 'ProposalStatusDeciding')
       assert.equal(qProposal.statusSetAtBlock, e.blockNumber)
       assert.equal(new Date(qProposal.statusSetAtTime).getTime(), e.blockTimestamp)
+      assert.equal(qProposal.createdInEvent.inBlock, e.blockNumber)
+      assert.equal(qProposal.createdInEvent.inExtrinsic, this.extrinsics[i].hash.toString())
     })
   }
 
   protected assertQueryNodeEventIsValid(qEvent: ProposalCreatedEventFieldsFragment, i: number): void {
-    // TODO
+    // TODO: https://github.com/Joystream/joystream/issues/2457
   }
 
   async runQueryNodeChecks(): Promise<void> {
     await super.runQueryNodeChecks()
-    // TODO: Events
 
     // Query the proposals
-    const qProposals = await this.query.tryQueryWithTimeout(
+    await this.query.tryQueryWithTimeout(
       () => this.query.getProposalsByIds(this.events.map((e) => e.proposalId)),
       (result) => this.assertQueriedProposalsAreValid(result)
     )

+ 5 - 1
tests/integration-tests/src/fixtures/proposals/DecideOnProposalStatusFixture.ts

@@ -179,7 +179,11 @@ export class DecideOnProposalStatusFixture extends BaseQueryNodeFixture {
           await this.api.untilBlock(qProposal.statusSetAtBlock + proposal.parameters.gracePeriod.toNumber())
           ;[qProposal] = await this.query.tryQueryWithTimeout(
             () => this.query.getProposalsByIds([this.params[i].proposalId]),
-            ([p]) => p.status.__typename === 'ProposalStatusExecuted'
+            ([p]) =>
+              assert.equal(
+                p.status.__typename,
+                this.params[i].expectExecutionFailure ? 'ProposalStatusExecutionFailed' : 'ProposalStatusExecuted'
+              )
           )
           await this.postExecutionChecks(qProposal)
         }

+ 27 - 22
tests/integration-tests/src/flows/proposals/index.ts

@@ -11,14 +11,6 @@ import {
 import { OpeningMetadata } from '@joystream/metadata-protobuf'
 import { AllProposalsOutcomesFixture, TestedProposal } from '../../fixtures/proposals/AllProposalsOutcomesFixture'
 
-//   // TODO:
-//   // RuntimeUpgradeProposal
-//   // TODO: Blog-related proposals:
-//   // CreateBlogPost
-//   // EditBlogPostProposal
-//   // LockBlogPostProposal
-//   // UnlockBlogPostProposal
-
 export default async function creatingProposals({ api, query }: FlowProps): Promise<void> {
   const debug = Debugger('flow:creating-proposals')
   debug('Started')
@@ -28,7 +20,7 @@ export default async function creatingProposals({ api, query }: FlowProps): Prom
   const createLeadOpeningsFixture = new CreateOpeningsFixture(
     api,
     query,
-    'storageWorkingGroup',
+    'membershipWorkingGroup',
     [DEFAULT_OPENING_PARAMS, DEFAULT_OPENING_PARAMS],
     true
   )
@@ -45,7 +37,7 @@ export default async function creatingProposals({ api, query }: FlowProps): Prom
   ])
   await new FixtureRunner(addStakingAccountsFixture).run()
 
-  const applyOnOpeningFixture = new ApplyOnOpeningsHappyCaseFixture(api, query, 'storageWorkingGroup', [
+  const applyOnOpeningFixture = new ApplyOnOpeningsHappyCaseFixture(api, query, 'membershipWorkingGroup', [
     {
       openingId: openingToFillId,
       applicants: [
@@ -67,17 +59,22 @@ export default async function creatingProposals({ api, query }: FlowProps): Prom
   const accountsToFund = (await api.createKeyPairs(5)).map((key) => key.address)
   const proposalsToTest: TestedProposal[] = [
     { details: { AmendConstitution: 'New constitution' } },
-    { details: { FundingRequest: accountsToFund.map((a, i) => ({ account: a, amount: (i + 1) * 1000 })) } },
+    {
+      details: { FundingRequest: accountsToFund.map((a, i) => ({ account: a, amount: (i + 1) * 1000 })) },
+      expectExecutionFailure: true, // InsufficientFunds
+    },
     { details: { Signal: 'Text' } },
     { details: { SetCouncilBudgetIncrement: 1_000_000 } },
     { details: { SetCouncilorReward: 100 } },
     { details: { SetInitialInvitationBalance: 10 } },
     { details: { SetInitialInvitationCount: 5 } },
     { details: { SetMaxValidatorCount: 100 } },
-    { details: { SetMembershipLeadInvitationQuota: 50 } },
     { details: { SetMembershipPrice: 500 } },
     { details: { SetReferralCut: 25 } },
-    { details: { UpdateWorkingGroupBudget: [10_000_000, 'Content', 'Negative'] }, expectExecutionFailure: true },
+    {
+      details: { UpdateWorkingGroupBudget: [10_000_000, 'Content', 'Negative'] },
+      expectExecutionFailure: true, // InsufficientFunds
+    },
     {
       details: {
         CreateWorkingGroupLeadOpening: {
@@ -87,38 +84,46 @@ export default async function creatingProposals({ api, query }: FlowProps): Prom
             leaving_unstaking_period: DEFAULT_OPENING_PARAMS.unstakingPeriod,
             stake_amount: DEFAULT_OPENING_PARAMS.stake,
           },
-          working_group: 'Storage',
+          working_group: 'Membership',
         },
       },
     },
-    { details: { CancelWorkingGroupLeadOpening: [openingToCancelId, 'Storage'] } },
+    { details: { CancelWorkingGroupLeadOpening: [openingToCancelId, 'Membership'] } },
     {
       details: {
         FillWorkingGroupLeadOpening: {
           opening_id: openingToFillId,
           successful_application_id: applicationId,
-          working_group: 'Storage',
+          working_group: 'Membership',
         },
       },
     },
+    { details: { CreateBlogPost: ['Blog title', 'Blog text'] } },
+    // Blogs not supported yet, so we currently use invalid id and expect failure
+    { details: { EditBlogPost: [999, 'New title', 'New text'] }, expectExecutionFailure: true },
+    { details: { LockBlogPost: 999 }, expectExecutionFailure: true },
+    { details: { UnlockBlogPost: 999 }, expectExecutionFailure: true },
   ]
 
   const testAllOutcomesFixture = new AllProposalsOutcomesFixture(api, query, proposalsToTest)
   await new FixtureRunner(testAllOutcomesFixture).run()
 
-  // The storage lead should be hired at this point, so we can test lead-related proposals
+  // The membership lead should be hired at this point, so we can test lead-related proposals
 
-  const leadId = (await api.query.storageWorkingGroup.currentLead()).unwrap()
+  const leadId = (await api.query.membershipWorkingGroup.currentLead()).unwrap()
   const leadProposalsToTest: TestedProposal[] = [
-    { details: { DecreaseWorkingGroupLeadStake: [leadId, 100, 'Storage'] } },
-    { details: { SetWorkingGroupLeadReward: [leadId, 50, 'Storage'] } },
-    { details: { SlashWorkingGroupLead: [leadId, 100, 'Storage'] } },
+    { details: { SetMembershipLeadInvitationQuota: 50 } },
+    { details: { DecreaseWorkingGroupLeadStake: [leadId, 100, 'Membership'] } },
+    { details: { SetWorkingGroupLeadReward: [leadId, 50, 'Membership'] } },
+    { details: { SlashWorkingGroupLead: [leadId, 100, 'Membership'] } },
   ]
   const leadProposalsOutcomesFixture = new AllProposalsOutcomesFixture(api, query, leadProposalsToTest)
   await new FixtureRunner(leadProposalsOutcomesFixture).run()
 
   const terminateLeadProposalOutcomesFixture = new AllProposalsOutcomesFixture(api, query, [
-    { details: { TerminateWorkingGroupLead: { worker_id: leadId, working_group: 'Storage', slashing_amount: 100 } } },
+    {
+      details: { TerminateWorkingGroupLead: { worker_id: leadId, working_group: 'Membership', slashing_amount: 100 } },
+    },
   ])
   await new FixtureRunner(terminateLeadProposalOutcomesFixture).run()
 

+ 70 - 0
tests/integration-tests/src/flows/proposals/runtimeUpgradeProposal.ts

@@ -0,0 +1,70 @@
+import { FlowProps } from '../../Flow'
+import Debugger from 'debug'
+import { FixtureRunner } from '../../Fixture'
+import { AllProposalsOutcomesFixture, TestedProposal } from '../../fixtures/proposals/AllProposalsOutcomesFixture'
+import { Utils } from '../../utils'
+import fs from 'fs'
+import { CreateProposalsFixture, DecideOnProposalStatusFixture } from '../../fixtures/proposals'
+import { BuyMembershipHappyCaseFixture } from '../../fixtures/membership'
+import { assert } from 'chai'
+
+export default async function runtimeUpgradeProposal({ api, query, env }: FlowProps): Promise<void> {
+  const debug = Debugger('flow:runtime-upgrade-proposal')
+  debug('Started')
+  api.enableVerboseTxLogs()
+
+  const runtimeUpgradeWasmPath = env.RUNTIME_UPGRADE_TARGET_WASM_PATH
+
+  Utils.assert(
+    runtimeUpgradeWasmPath && fs.existsSync(runtimeUpgradeWasmPath),
+    'Invalid RUNTIME_UPGRADE_TARGET_WASM_PATH'
+  )
+
+  // Proposals to be "CancelledByRuntime"
+  const [memberAcc] = (await api.createKeyPairs(1)).map((kp) => kp.address)
+  const buyMembershipFixture = new BuyMembershipHappyCaseFixture(api, query, [memberAcc])
+  await new FixtureRunner(buyMembershipFixture).run()
+  const [memberId] = buyMembershipFixture.getCreatedMembers()
+  const createProposalsFixture = new CreateProposalsFixture(api, query, [
+    {
+      type: 'RuntimeUpgrade',
+      details: Utils.readRuntimeFromFile(runtimeUpgradeWasmPath),
+      asMember: memberId,
+      title: 'To be cancelled by runtime',
+      description: 'Proposal to be cancelled by runtime',
+    },
+  ])
+  await new FixtureRunner(createProposalsFixture).run()
+  const [toBeCanceledByRuntimeProposalId] = createProposalsFixture.getCreatedProposalsIds()
+  // Currently we use constitutionality === 2 for runtime upgrade, so we need to approve the proposal
+  // in order for it not to get "Rejected" during new council election
+  const decideOnProposalStatusFixture = new DecideOnProposalStatusFixture(api, query, [
+    { proposalId: toBeCanceledByRuntimeProposalId, status: 'Approved' },
+  ])
+  await new FixtureRunner(decideOnProposalStatusFixture).run()
+
+  // Runtime upgrade proposal
+  const testedProposals: TestedProposal[] = [
+    { details: { RuntimeUpgrade: Utils.readRuntimeFromFile(runtimeUpgradeWasmPath) } },
+  ]
+  const testAllOutcomesFixture = new AllProposalsOutcomesFixture(api, query, testedProposals)
+  await new FixtureRunner(testAllOutcomesFixture).run()
+
+  // Check the "CancelledByRuntime" proposal status
+  await query.tryQueryWithTimeout(
+    () => query.getProposalsByIds([toBeCanceledByRuntimeProposalId]),
+    ([proposal]) => {
+      Utils.assert(
+        proposal.status.__typename === 'ProposalStatusCanceledByRuntime',
+        `Proposal expected to be CanceledByRuntime. Actual status: ${proposal.status.__typename}`
+      )
+      Utils.assert(proposal.status.proposalDecisionMadeEvent, 'Missing proposalDecisionMadeEvent reference')
+      assert.equal(
+        proposal.status.proposalDecisionMadeEvent.decisionStatus.__typename,
+        'ProposalStatusCanceledByRuntime'
+      )
+    }
+  )
+
+  debug('Done')
+}

+ 45 - 6
tests/integration-tests/src/graphql/generated/queries.ts

@@ -818,7 +818,7 @@ type ProposalDetailsFields_SignalProposalDetails_Fragment = { __typename: 'Signa
 
 type ProposalDetailsFields_RuntimeUpgradeProposalDetails_Fragment = {
   __typename: 'RuntimeUpgradeProposalDetails'
-  wasmBytecode: any
+  wasmBytecodeHash: string
 }
 
 type ProposalDetailsFields_FundingRequestProposalDetails_Fragment = {
@@ -921,13 +921,28 @@ type ProposalDetailsFields_SetReferralCutProposalDetails_Fragment = {
   newReferralCut: number
 }
 
-type ProposalDetailsFields_CreateBlogPostProposalDetails_Fragment = { __typename: 'CreateBlogPostProposalDetails' }
+type ProposalDetailsFields_CreateBlogPostProposalDetails_Fragment = {
+  __typename: 'CreateBlogPostProposalDetails'
+  title: string
+  body: string
+}
 
-type ProposalDetailsFields_EditBlogPostProposalDetails_Fragment = { __typename: 'EditBlogPostProposalDetails' }
+type ProposalDetailsFields_EditBlogPostProposalDetails_Fragment = {
+  __typename: 'EditBlogPostProposalDetails'
+  blogPost: string
+  newTitle?: Types.Maybe<string>
+  newBody?: Types.Maybe<string>
+}
 
-type ProposalDetailsFields_LockBlogPostProposalDetails_Fragment = { __typename: 'LockBlogPostProposalDetails' }
+type ProposalDetailsFields_LockBlogPostProposalDetails_Fragment = {
+  __typename: 'LockBlogPostProposalDetails'
+  blogPost: string
+}
 
-type ProposalDetailsFields_UnlockBlogPostProposalDetails_Fragment = { __typename: 'UnlockBlogPostProposalDetails' }
+type ProposalDetailsFields_UnlockBlogPostProposalDetails_Fragment = {
+  __typename: 'UnlockBlogPostProposalDetails'
+  blogPost: string
+}
 
 type ProposalDetailsFields_VetoProposalDetails_Fragment = {
   __typename: 'VetoProposalDetails'
@@ -1011,6 +1026,7 @@ export type ProposalFieldsFragment = {
     | ProposalStatusFields_ProposalStatusExpired_Fragment
     | ProposalStatusFields_ProposalStatusCancelled_Fragment
     | ProposalStatusFields_ProposalStatusCanceledByRuntime_Fragment
+  createdInEvent: { id: string; inBlock: number; inExtrinsic?: Types.Maybe<string> }
 }
 
 export type GetProposalsByIdsQueryVariables = Types.Exact<{
@@ -2381,7 +2397,7 @@ export const ProposalDetailsFields = gql`
       text
     }
     ... on RuntimeUpgradeProposalDetails {
-      wasmBytecode
+      wasmBytecodeHash
     }
     ... on FundingRequestProposalDetails {
       destinationsList {
@@ -2472,6 +2488,24 @@ export const ProposalDetailsFields = gql`
     ... on SetReferralCutProposalDetails {
       newReferralCut
     }
+    ... on SetReferralCutProposalDetails {
+      newReferralCut
+    }
+    ... on CreateBlogPostProposalDetails {
+      title
+      body
+    }
+    ... on EditBlogPostProposalDetails {
+      blogPost
+      newTitle
+      newBody
+    }
+    ... on LockBlogPostProposalDetails {
+      blogPost
+    }
+    ... on UnlockBlogPostProposalDetails {
+      blogPost
+    }
     ... on VetoProposalDetails {
       proposal {
         id
@@ -2596,6 +2630,11 @@ export const ProposalFields = gql`
     }
     statusSetAtBlock
     statusSetAtTime
+    createdInEvent {
+      id
+      inBlock
+      inExtrinsic
+    }
   }
   ${ProposalDetailsFields}
   ${ProposalStatusFields}

+ 9 - 8
tests/integration-tests/src/graphql/generated/schema.ts

@@ -15,8 +15,6 @@ export type Scalars = {
   BigInt: any
   /** The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
   JSONObject: any
-  /** GraphQL representation of Bytes */
-  Bytes: any
 }
 
 export type AmendConstitutionProposalDetails = {
@@ -12170,16 +12168,16 @@ export enum RewardPaymentType {
 }
 
 export type RuntimeUpgradeProposalDetails = {
-  /** Runtime upgrade WASM bytecode */
-  wasmBytecode: Scalars['Bytes']
+  /** Runtime upgrade WASM bytecode hash */
+  wasmBytecodeHash: Scalars['String']
 }
 
 export type RuntimeUpgradeProposalDetailsCreateInput = {
-  wasmBytecode: Scalars['Bytes']
+  wasmBytecodeHash: Scalars['String']
 }
 
 export type RuntimeUpgradeProposalDetailsUpdateInput = {
-  wasmBytecode?: Maybe<Scalars['Bytes']>
+  wasmBytecodeHash?: Maybe<Scalars['String']>
 }
 
 export type RuntimeUpgradeProposalDetailsWhereInput = {
@@ -12207,8 +12205,11 @@ export type RuntimeUpgradeProposalDetailsWhereInput = {
   deletedAt_gte?: Maybe<Scalars['DateTime']>
   deletedById_eq?: Maybe<Scalars['ID']>
   deletedById_in?: Maybe<Array<Scalars['ID']>>
-  wasmBytecode_eq?: Maybe<Scalars['Bytes']>
-  wasmBytecode_in?: Maybe<Array<Scalars['Bytes']>>
+  wasmBytecodeHash_eq?: Maybe<Scalars['String']>
+  wasmBytecodeHash_contains?: Maybe<Scalars['String']>
+  wasmBytecodeHash_startsWith?: Maybe<Scalars['String']>
+  wasmBytecodeHash_endsWith?: Maybe<Scalars['String']>
+  wasmBytecodeHash_in?: Maybe<Array<Scalars['String']>>
   AND?: Maybe<Array<RuntimeUpgradeProposalDetailsWhereInput>>
   OR?: Maybe<Array<RuntimeUpgradeProposalDetailsWhereInput>>
 }

+ 28 - 2
tests/integration-tests/src/graphql/queries/proposals.graphql

@@ -99,7 +99,7 @@ fragment ProposalDetailsFields on ProposalDetails {
     text
   }
   ... on RuntimeUpgradeProposalDetails {
-    wasmBytecode
+    wasmBytecodeHash
   }
   ... on FundingRequestProposalDetails {
     destinationsList {
@@ -203,7 +203,28 @@ fragment ProposalDetailsFields on ProposalDetails {
     newReferralCut
   }
 
-  # TODO: Blog proposals
+  ... on SetReferralCutProposalDetails {
+    newReferralCut
+  }
+
+  ... on CreateBlogPostProposalDetails {
+    title
+    body
+  }
+
+  ... on EditBlogPostProposalDetails {
+    blogPost
+    newTitle
+    newBody
+  }
+
+  ... on LockBlogPostProposalDetails {
+    blogPost
+  }
+
+  ... on UnlockBlogPostProposalDetails {
+    blogPost
+  }
 
   ... on VetoProposalDetails {
     proposal {
@@ -236,6 +257,11 @@ fragment ProposalFields on Proposal {
   }
   statusSetAtBlock
   statusSetAtTime
+  createdInEvent {
+    id
+    inBlock
+    inExtrinsic
+  }
 }
 
 query getProposalsByIds($ids: [ID!]) {

+ 16 - 6
tests/integration-tests/src/scenarios/full.ts

@@ -20,14 +20,25 @@ import proposals from '../flows/proposals'
 import cancellingProposals from '../flows/proposals/cancellingProposal'
 import vetoProposal from '../flows/proposals/vetoProposal'
 import electCouncil from '../flows/council/elect'
+import runtimeUpgradeProposal from '../flows/proposals/runtimeUpgradeProposal'
 import { scenario } from '../Scenario'
 
-scenario(async ({ job }) => {
-  // Membership:
-  const membershipSystemJob = job('membership system', membershipSystem)
+scenario(async ({ job, env }) => {
+  // Runtime upgrade should always be first job
+  // (except councilJob, which is required for voting and should probably depend on the "source" runtime)
+  const councilJob = job('electing council', electCouncil)
+  const runtimeUpgradeProposalJob = env.RUNTIME_UPGRADE_TARGET_IMAGE_TAG
+    ? job('runtime upgrade proposal', runtimeUpgradeProposal).requires(councilJob)
+    : undefined
+
+  const membershipSystemJob = job('membership system', membershipSystem).requires(
+    runtimeUpgradeProposalJob || councilJob
+  )
 
   // All other jobs should be executed after membershipSystemJob,
   // otherwise changing membershipPrice etc. may break them
+
+  // Membership:
   job('creating members', creatingMemberships).after(membershipSystemJob)
   job('updating member profile', updatingMemberProfile).after(membershipSystemJob)
   job('updating member accounts', updatingMemberAccounts).after(membershipSystemJob)
@@ -36,10 +47,9 @@ scenario(async ({ job }) => {
   job('managing staking accounts', managingStakingAccounts).after(membershipSystemJob)
 
   // Proposals:
-  const councilJob = job('electing council', electCouncil).after(membershipSystemJob)
-  const proposalsJob = job('proposals', [proposals, cancellingProposals, vetoProposal]).requires(councilJob)
+  const proposalsJob = job('proposals', [proposals, cancellingProposals, vetoProposal]).requires(membershipSystemJob)
 
-  // Working groups:
+  // Working groups
   const sudoHireLead = job('sudo lead opening', leadOpening).after(proposalsJob)
   job('openings and applications', openingsAndApplications).requires(sudoHireLead)
   job('upcoming openings', upcomingOpenings).requires(sudoHireLead)

+ 6 - 2
tests/integration-tests/src/scenarios/proposals.ts

@@ -2,9 +2,13 @@ import proposals from '../flows/proposals'
 import cancellingProposals from '../flows/proposals/cancellingProposal'
 import vetoProposal from '../flows/proposals/vetoProposal'
 import electCouncil from '../flows/council/elect'
+import runtimeUpgradeProposal from '../flows/proposals/runtimeUpgradeProposal'
 import { scenario } from '../Scenario'
 
-scenario(async ({ job }) => {
+scenario(async ({ job, env }) => {
   const councilJob = job('electing council', electCouncil)
-  job('proposals', [proposals, cancellingProposals, vetoProposal]).requires(councilJob)
+  const runtimeUpgradeProposalJob = env.RUNTIME_UPGRADE_TARGET_IMAGE_TAG
+    ? job('runtime upgrade proposal', runtimeUpgradeProposal).requires(councilJob)
+    : undefined
+  job('proposals', [proposals, cancellingProposals, vetoProposal]).requires(runtimeUpgradeProposalJob || councilJob)
 })

+ 1 - 1
tests/integration-tests/src/sender.ts

@@ -66,7 +66,7 @@ export class Sender {
         process.exit(-1)
       }
 
-      if (!result.status.isInBlock) {
+      if (!(result.status.isInBlock || result.status.isFinalized)) {
         return
       }