diff --git a/.github/workflows/verify-pr-commit.yml b/.github/workflows/verify-pr-commit.yml index ce072912fb..88c88818b8 100644 --- a/.github/workflows/verify-pr-commit.yml +++ b/.github/workflows/verify-pr-commit.yml @@ -184,7 +184,7 @@ jobs: uses: actions/checkout@v4 - name: Set Up Cargo Deny run: | - cargo install --force cargo-deny + cargo install --force --locked cargo-deny cargo generate-lockfile - name: Run Cargo Deny run: cargo deny check --hide-inclusion-graph -c deny.toml diff --git a/e2e/capacity/transactions.test.ts b/e2e/capacity/transactions.test.ts index f13b5c97f0..c650a559b6 100644 --- a/e2e/capacity/transactions.test.ts +++ b/e2e/capacity/transactions.test.ts @@ -32,7 +32,6 @@ import { getOrCreateAvroChatMessageItemizedSchema, getOrCreateParquetBroadcastSchema, getOrCreateAvroChatMessagePaginatedSchema, - generateItemizedSignaturePayloadV2, generatePaginatedUpsertSignaturePayloadV2, generatePaginatedDeleteSignaturePayloadV2, getCapacity, @@ -387,7 +386,7 @@ describe('Capacity Transactions', function () { const target_hash = await getCurrentItemizedHash(delegatorProviderId, itemizedSchemaId); const add_actions = [add_action, update_action]; - const payload = await generateItemizedSignaturePayloadV2({ + const payload = await generateItemizedSignaturePayload({ targetHash: target_hash, schemaId: itemizedSchemaId, actions: add_actions, diff --git a/e2e/package-lock.json b/e2e/package-lock.json index 7097bc48bd..17956b36ae 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -307,7 +307,7 @@ "node_modules/@frequency-chain/api-augment": { "version": "0.0.0", "resolved": "file:../js/api-augment/dist/frequency-chain-api-augment-0.0.0.tgz", - "integrity": "sha512-SMZfR2D7UP5SO7v8wX2Py2StCweXpq8CDhiLrk51jDEprC1FuQaLFTlrMlc3GrI1SnkE3MLU0LUr9gKxhcdWZw==", + "integrity": "sha512-TYffQ2eMmwREY6yoKEVmFnFu2rSWdzzYpoLDi+8E7ks9Xe0PFkO8JYEZOr8SP8POr2eJJ2EoSXjPgcWSMKczrA==", "license": "Apache-2.0", "dependencies": { "@polkadot/api": "^10.9.1", diff --git a/e2e/scaffolding/helpers.ts b/e2e/scaffolding/helpers.ts index 6916bbd294..64199affeb 100644 --- a/e2e/scaffolding/helpers.ts +++ b/e2e/scaffolding/helpers.ts @@ -1,6 +1,6 @@ import { Keyring } from '@polkadot/api'; import { KeyringPair } from '@polkadot/keyring/types'; -import { u16, u32, u64, Option } from '@polkadot/types'; +import { u16, u32, u64, Option, Bytes } from '@polkadot/types'; import type { FrameSystemAccountInfo, PalletCapacityCapacityDetails } from '@polkadot/types/lookup'; import { Codec } from '@polkadot/types/types'; import { u8aToHex, u8aWrapBytes } from '@polkadot/util'; @@ -24,6 +24,7 @@ import { MessageResponse, MessageSourceId, PageHash, + SchemaId, } from '@frequency-chain/api-augment/interfaces'; import assert from 'assert'; import { AVRO_GRAPH_CHANGE } from '../schemas/fixtures/avroGraphChangeSchemaType'; @@ -87,7 +88,7 @@ export async function generateAddKeyPayload( } export async function generateItemizedSignaturePayload( - payloadInputs: ItemizedSignaturePayload, + payloadInputs: ItemizedSignaturePayload | ItemizedSignaturePayloadV2, expirationOffset: number = 100, blockNumber?: number ): Promise { @@ -99,17 +100,63 @@ export async function generateItemizedSignaturePayload( }; } -export async function generateItemizedSignaturePayloadV2( - payloadInputs: ItemizedSignaturePayloadV2, - expirationOffset: number = 100, - blockNumber?: number -): Promise { - const { expiration, ...payload } = payloadInputs; +export function generateItemizedActions(items: { action: 'Add' | 'Update'; value: string }[]) { + return items.map(({ action, value }) => { + const actionObj = {}; + actionObj[action] = new Bytes(ExtrinsicHelper.api.registry, value); + return actionObj; + }); +} - return { - expiration: expiration || (blockNumber || (await getBlockNumber())) + expirationOffset, - ...payload, +export async function generateItemizedActionsPayloadAndSignature( + payloadInput: ItemizedSignaturePayload | ItemizedSignaturePayloadV2, + payloadType: 'PalletStatefulStorageItemizedSignaturePayload' | 'PalletStatefulStorageItemizedSignaturePayloadV2', + signingKeys: KeyringPair +) { + const payloadData = await generateItemizedSignaturePayload(payloadInput); + const payload = ExtrinsicHelper.api.registry.createType(payloadType, payloadData); + const signature = signPayloadSr25519(signingKeys, payload); + + return { payload: payloadData, signature }; +} + +export async function generateItemizedActionsSignedPayload( + actions: any[], + schemaId: SchemaId, + signingKeys: KeyringPair, + msaId: MessageSourceId +) { + const payloadInput: ItemizedSignaturePayload = { + msaId, + targetHash: await getCurrentItemizedHash(msaId, schemaId), + schemaId, + actions, + }; + + return generateItemizedActionsPayloadAndSignature( + payloadInput, + 'PalletStatefulStorageItemizedSignaturePayload', + signingKeys + ); +} + +export async function generateItemizedActionsSignedPayloadV2( + actions: any[], + schemaId: SchemaId, + signingKeys: KeyringPair, + msaId: MessageSourceId +) { + const payloadInput: ItemizedSignaturePayloadV2 = { + targetHash: await getCurrentItemizedHash(msaId, schemaId), + schemaId, + actions, }; + + return generateItemizedActionsPayloadAndSignature( + payloadInput, + 'PalletStatefulStorageItemizedSignaturePayloadV2', + signingKeys + ); } export async function generatePaginatedUpsertSignaturePayload( @@ -270,7 +317,7 @@ export async function createDelegator(source: KeyringPair, amount?: bigint): Pro export async function createDelegatorAndDelegation( source: KeyringPair, - schemaId: u16, + schemaId: u16 | u16[], providerId: u64, providerKeys: KeyringPair ): Promise<[KeyringPair, u64]> { @@ -280,7 +327,7 @@ export async function createDelegatorAndDelegation( // Grant delegation to the provider const payload = await generateDelegationPayload({ authorizedMsaId: providerId, - schemaIds: [schemaId], + schemaIds: Array.isArray(schemaId) ? schemaId : [schemaId], }); const addProviderData = ExtrinsicHelper.api.registry.createType('PalletMsaAddProvider', payload); diff --git a/e2e/stateful-pallet-storage/handleItemized.test.ts b/e2e/stateful-pallet-storage/handleItemized.test.ts index d0563ddde6..1fa31f5704 100644 --- a/e2e/stateful-pallet-storage/handleItemized.test.ts +++ b/e2e/stateful-pallet-storage/handleItemized.test.ts @@ -20,6 +20,7 @@ const fundingSource = getFundingSource('stateful-storage-handle-itemized'); describe('📗 Stateful Pallet Storage', function () { let schemaId_deletable: SchemaId; let schemaId_unsupported: SchemaId; + let delegatorKeys: KeyringPair; let msa_id: MessageSourceId; let providerId: MessageSourceId; let providerKeys: KeyringPair; @@ -46,7 +47,12 @@ describe('📗 Stateful Pallet Storage', function () { assert.notEqual(event2, undefined, 'setup should return a SchemaCreated event'); schemaId_unsupported = event2!.data.schemaId; // Create a MSA for the delegator and delegate to the provider - [, msa_id] = await createDelegatorAndDelegation(fundingSource, schemaId_deletable, providerId, providerKeys); + [delegatorKeys, msa_id] = await createDelegatorAndDelegation( + fundingSource, + schemaId_deletable, + providerId, + providerKeys + ); assert.notEqual(msa_id, undefined, 'setup should populate msa_id'); // Create an MSA that is not a provider to be used for testing failure cases [badMsaId] = await createMsa(fundingSource); @@ -104,7 +110,7 @@ describe('📗 Stateful Pallet Storage', function () { const add_actions = [add_action]; const fake_schema_id = new u16(ExtrinsicHelper.api.registry, 65_534); const itemized_add_result_1 = ExtrinsicHelper.applyItemActions( - providerKeys, + delegatorKeys, fake_schema_id, msa_id, add_actions, @@ -123,7 +129,7 @@ describe('📗 Stateful Pallet Storage', function () { }; const add_actions = [add_action]; const itemized_add_result_1 = ExtrinsicHelper.applyItemActions( - providerKeys, + delegatorKeys, schemaId_unsupported, msa_id, add_actions, @@ -224,7 +230,7 @@ describe('📗 Stateful Pallet Storage', function () { const remove_actions = [remove_action_1]; const fake_schema_id = new u16(ExtrinsicHelper.api.registry, 65_534); const itemized_remove_result_1 = ExtrinsicHelper.applyItemActions( - providerKeys, + delegatorKeys, fake_schema_id, msa_id, remove_actions, @@ -243,7 +249,7 @@ describe('📗 Stateful Pallet Storage', function () { }; const remove_actions = [remove_action_1]; const itemized_remove_result_1 = ExtrinsicHelper.applyItemActions( - providerKeys, + delegatorKeys, schemaId_unsupported, msa_id, remove_actions, diff --git a/e2e/stateful-pallet-storage/handlePaginated.test.ts b/e2e/stateful-pallet-storage/handlePaginated.test.ts index 1a369a9fc0..9f30b59bda 100644 --- a/e2e/stateful-pallet-storage/handlePaginated.test.ts +++ b/e2e/stateful-pallet-storage/handlePaginated.test.ts @@ -21,6 +21,7 @@ const fundingSource = getFundingSource('stateful-storage-handle-paginated'); describe('📗 Stateful Pallet Storage', function () { let schemaId: SchemaId; let schemaId_unsupported: SchemaId; + let delegatorKeys: KeyringPair; let msa_id: MessageSourceId; let providerId: MessageSourceId; let providerKeys: KeyringPair; @@ -44,7 +45,7 @@ describe('📗 Stateful Pallet Storage', function () { schemaId_unsupported = event2!.data.schemaId; // Create a MSA for the delegator and delegate to the provider - [, msa_id] = await createDelegatorAndDelegation(fundingSource, schemaId, providerId, providerKeys); + [delegatorKeys, msa_id] = await createDelegatorAndDelegation(fundingSource, schemaId, providerId, providerKeys); assert.notEqual(msa_id, undefined, 'setup should populate msa_id'); // Create an MSA that is not a provider to be used for testing failure cases @@ -142,7 +143,7 @@ describe('📗 Stateful Pallet Storage', function () { const payload_1 = new Bytes(ExtrinsicHelper.api.registry, 'Hello World From Frequency'); const fake_schema_id = new u16(ExtrinsicHelper.api.registry, badSchemaId); const paginated_add_result_1 = ExtrinsicHelper.upsertPage( - providerKeys, + delegatorKeys, fake_schema_id, msa_id, page_id, @@ -160,7 +161,7 @@ describe('📗 Stateful Pallet Storage', function () { const target_hash = await getCurrentPaginatedHash(msa_id, schemaId, page_id); const payload_1 = new Bytes(ExtrinsicHelper.api.registry, 'Hello World From Frequency'); const paginated_add_result_1 = ExtrinsicHelper.upsertPage( - providerKeys, + delegatorKeys, schemaId_unsupported, msa_id, page_id, @@ -207,7 +208,7 @@ describe('📗 Stateful Pallet Storage', function () { describe('Paginated Storage Removal Negative Tests 😊/😥', function () { it('🛑 should fail call to remove page with invalid schemaId', async function () { const page_id = 0; - const paginated_add_result_1 = ExtrinsicHelper.removePage(providerKeys, badSchemaId, msa_id, page_id, 0); + const paginated_add_result_1 = ExtrinsicHelper.removePage(delegatorKeys, badSchemaId, msa_id, page_id, 0); await assert.rejects(paginated_add_result_1.fundAndSend(fundingSource), { name: 'InvalidSchemaId', section: 'statefulStorage', @@ -216,7 +217,13 @@ describe('📗 Stateful Pallet Storage', function () { it('🛑 should fail call to remove page with invalid schema location', async function () { const page_id = 0; - const paginated_add_result_1 = ExtrinsicHelper.removePage(providerKeys, schemaId_unsupported, msa_id, page_id, 0); + const paginated_add_result_1 = ExtrinsicHelper.removePage( + delegatorKeys, + schemaId_unsupported, + msa_id, + page_id, + 0 + ); await assert.rejects(paginated_add_result_1.fundAndSend(fundingSource), { name: 'SchemaPayloadLocationMismatch', section: 'statefulStorage', diff --git a/e2e/stateful-pallet-storage/handleSignatureRequired.test.ts b/e2e/stateful-pallet-storage/handleSignatureRequired.test.ts index b4828688ef..3f6c5cbc64 100644 --- a/e2e/stateful-pallet-storage/handleSignatureRequired.test.ts +++ b/e2e/stateful-pallet-storage/handleSignatureRequired.test.ts @@ -3,10 +3,11 @@ import '@frequency-chain/api-augment'; import assert from 'assert'; import { DOLLARS, - createDelegator, + createDelegatorAndDelegation, createProviderKeysAndId, - generateItemizedSignaturePayload, - generateItemizedSignaturePayloadV2, + generateItemizedActions, + generateItemizedActionsSignedPayload, + generateItemizedActionsSignedPayloadV2, generatePaginatedDeleteSignaturePayload, generatePaginatedDeleteSignaturePayloadV2, generatePaginatedUpsertSignaturePayload, @@ -28,65 +29,184 @@ describe('📗 Stateful Pallet Storage Signature Required', function () { let itemizedSchemaId: SchemaId; let paginatedSchemaId: SchemaId; let msa_id: MessageSourceId; - let providerId: MessageSourceId; - let providerKeys: KeyringPair; + let undelegatedProviderId: MessageSourceId; + let undelegatedProviderKeys: KeyringPair; + let delegatedProviderId: MessageSourceId; + let delegatedProviderKeys: KeyringPair; let delegatorKeys: KeyringPair; before(async function () { + // Create a provider. This provider will NOT be granted delegations; + // methods requiring a payload signature do not require a delegation + [undelegatedProviderKeys, undelegatedProviderId] = await createProviderKeysAndId(fundingSource, 2n * DOLLARS); + assert.notEqual(undelegatedProviderId, undefined, 'setup should populate undelegatedProviderId'); + assert.notEqual(undelegatedProviderKeys, undefined, 'setup should populate undelegatedProviderKeys'); + // Create a provider for the MSA, the provider will be used to grant delegation - [providerKeys, providerId] = await createProviderKeysAndId(fundingSource, 2n * DOLLARS); - assert.notEqual(providerId, undefined, 'setup should populate providerId'); - assert.notEqual(providerKeys, undefined, 'setup should populate providerKeys'); + [delegatedProviderKeys, delegatedProviderId] = await createProviderKeysAndId(fundingSource, 2n * DOLLARS); + assert.notEqual(delegatedProviderId, undefined, 'setup should populate delegatedProviderId'); + assert.notEqual(delegatedProviderKeys, undefined, 'setup should populate delegatedProviderKeys'); // Create a schema for Itemized PayloadLocation - const createSchema = ExtrinsicHelper.createSchema(providerKeys, AVRO_CHAT_MESSAGE, 'AvroBinary', 'Itemized'); + const createSchema = ExtrinsicHelper.createSchemaV3( + undelegatedProviderKeys, + AVRO_CHAT_MESSAGE, + 'AvroBinary', + 'Itemized', + ['AppendOnly', 'SignatureRequired'], + 'test.ItemizedSignatureRequired' + ); const { target: event } = await createSchema.signAndSend(); itemizedSchemaId = event!.data.schemaId; // Create a schema for Paginated PayloadLocation - const createSchema2 = ExtrinsicHelper.createSchema(providerKeys, AVRO_CHAT_MESSAGE, 'AvroBinary', 'Paginated'); + const createSchema2 = ExtrinsicHelper.createSchemaV3( + undelegatedProviderKeys, + AVRO_CHAT_MESSAGE, + 'AvroBinary', + 'Paginated', + ['SignatureRequired'], + 'test.PaginatedSignatureRequired' + ); const { target: event2 } = await createSchema2.signAndSend(); assert.notEqual(event2, undefined, 'setup should return a SchemaCreated event'); paginatedSchemaId = event2!.data.schemaId; // Create a MSA for the delegator - [delegatorKeys, msa_id] = await createDelegator(fundingSource); + [delegatorKeys, msa_id] = await createDelegatorAndDelegation( + fundingSource, + [itemizedSchemaId, paginatedSchemaId], + delegatedProviderId, + delegatedProviderKeys + ); assert.notEqual(delegatorKeys, undefined, 'setup should populate delegator_key'); assert.notEqual(msa_id, undefined, 'setup should populate msa_id'); }); describe('Itemized With Signature Storage Tests', function () { - it('should be able to call applyItemizedActionWithSignature and apply actions', async function () { - // Add and update actions - const payload_1 = new Bytes(ExtrinsicHelper.api.registry, 'Hello World From Frequency'); + it('provider should be able to call applyItemizedActionWithSignature and apply actions', async function () { + const { payload, signature } = await generateItemizedActionsSignedPayload( + generateItemizedActions([ + { action: 'Add', value: 'Hello, world from Frequency' }, + { action: 'Add', value: 'Hello, world again from Frequency' }, + ]), + itemizedSchemaId, + delegatorKeys, + msa_id + ); - const add_action = { - Add: payload_1, - }; + const itemized_add_result_1 = ExtrinsicHelper.applyItemActionsWithSignature( + delegatorKeys, + undelegatedProviderKeys, + signature, + payload + ); + const { target: pageUpdateEvent1, eventMap: chainEvents } = + await itemized_add_result_1.fundAndSend(fundingSource); + assert.notEqual( + chainEvents['system.ExtrinsicSuccess'], + undefined, + 'should have returned an ExtrinsicSuccess event' + ); + assert.notEqual( + chainEvents['transactionPayment.TransactionFeePaid'], + undefined, + 'should have returned a TransactionFeePaid event' + ); + assert.notEqual( + pageUpdateEvent1, + undefined, + 'should have returned a PalletStatefulStorageItemizedActionApplied event' + ); + }); - const payload_2 = new Bytes(ExtrinsicHelper.api.registry, 'Hello World Again From Frequency'); + it('delegator (owner) should be able to call applyItemizedActionWithSignature and apply actions', async function () { + const { payload, signature } = await generateItemizedActionsSignedPayload( + generateItemizedActions([ + { action: 'Add', value: 'Hello, world from Frequency' }, + { action: 'Add', value: 'Hello, world again from Frequency' }, + ]), + itemizedSchemaId, + delegatorKeys, + msa_id + ); - const update_action = { - Add: payload_2, - }; + const itemized_add_result_1 = ExtrinsicHelper.applyItemActionsWithSignature( + delegatorKeys, + delegatorKeys, + signature, + payload + ); + const { target: pageUpdateEvent1, eventMap: chainEvents } = + await itemized_add_result_1.fundAndSend(fundingSource); + assert.notEqual( + chainEvents['system.ExtrinsicSuccess'], + undefined, + 'should have returned an ExtrinsicSuccess event' + ); + assert.notEqual( + chainEvents['transactionPayment.TransactionFeePaid'], + undefined, + 'should have returned a TransactionFeePaid event' + ); + assert.notEqual( + pageUpdateEvent1, + undefined, + 'should have returned a PalletStatefulStorageItemizedActionApplied event' + ); + }); - const target_hash = await getCurrentItemizedHash(msa_id, itemizedSchemaId); + it('provider should be able to call applyItemizedActionWithSignatureV2 and apply actions', async function () { + const { payload, signature } = await generateItemizedActionsSignedPayloadV2( + generateItemizedActions([ + { action: 'Add', value: 'Hello, world from Frequency' }, + { action: 'Add', value: 'Hello, world again from Frequency' }, + ]), + itemizedSchemaId, + delegatorKeys, + msa_id + ); - const add_actions = [add_action, update_action]; - const payload = await generateItemizedSignaturePayload({ - msaId: msa_id, - targetHash: target_hash, - schemaId: itemizedSchemaId, - actions: add_actions, - }); - const itemizedPayloadData = ExtrinsicHelper.api.registry.createType( - 'PalletStatefulStorageItemizedSignaturePayload', + const itemized_add_result_1 = ExtrinsicHelper.applyItemActionsWithSignatureV2( + delegatorKeys, + undelegatedProviderKeys, + signature, payload ); - const itemized_add_result_1 = ExtrinsicHelper.applyItemActionsWithSignature( + const { target: pageUpdateEvent1, eventMap: chainEvents } = + await itemized_add_result_1.fundAndSend(fundingSource); + assert.notEqual( + chainEvents['system.ExtrinsicSuccess'], + undefined, + 'should have returned an ExtrinsicSuccess event' + ); + assert.notEqual( + chainEvents['transactionPayment.TransactionFeePaid'], + undefined, + 'should have returned a TransactionFeePaid event' + ); + assert.notEqual( + pageUpdateEvent1, + undefined, + 'should have returned a PalletStatefulStorageItemizedActionApplied event' + ); + }); + + it('delegator (owner) should be able to call applyItemizedActionWithSignatureV2 and apply actions', async function () { + const { payload, signature } = await generateItemizedActionsSignedPayloadV2( + generateItemizedActions([ + { action: 'Add', value: 'Hello, world from Frequency' }, + { action: 'Add', value: 'Hello, world again from Frequency' }, + ]), + itemizedSchemaId, + delegatorKeys, + msa_id + ); + + const itemized_add_result_1 = ExtrinsicHelper.applyItemActionsWithSignatureV2( delegatorKeys, - providerKeys, - signPayloadSr25519(delegatorKeys, itemizedPayloadData), + delegatorKeys, + signature, payload ); const { target: pageUpdateEvent1, eventMap: chainEvents } = @@ -108,37 +228,47 @@ describe('📗 Stateful Pallet Storage Signature Required', function () { ); }); - it('should be able to call applyItemizedActionWithSignatureV2 and apply actions', async function () { - // Add and update actions - const payload_1 = new Bytes(ExtrinsicHelper.api.registry, 'Hello World From Frequency'); + it('provider should not be able to call applyItemizedAction', async function () { + const add_actions = generateItemizedActions([ + { action: 'Add', value: 'Hello, world from Frequency' }, + { action: 'Add', value: 'Hello, world again from Frequency' }, + ]); - const add_action = { - Add: payload_1, - }; + const target_hash = await getCurrentItemizedHash(msa_id, itemizedSchemaId); + + const itemized_add_result_1 = ExtrinsicHelper.applyItemActions( + undelegatedProviderKeys, + itemizedSchemaId, + msa_id, + add_actions, + target_hash + ); + await assert.rejects(itemized_add_result_1.fundAndSend(fundingSource), { name: 'UnauthorizedDelegate' }); - const payload_2 = new Bytes(ExtrinsicHelper.api.registry, 'Hello World Again From Frequency'); + const itemized_add_result_2 = ExtrinsicHelper.applyItemActions( + delegatedProviderKeys, + itemizedSchemaId, + msa_id, + add_actions, + target_hash + ); + await assert.rejects(itemized_add_result_2.fundAndSend(fundingSource), { name: 'UnsupportedOperationForSchema' }); + }); - const update_action = { - Add: payload_2, - }; + it('owner should be able to call applyItemizedAction', async function () { + const add_actions = generateItemizedActions([ + { action: 'Add', value: 'Hello, world from Frequency' }, + { action: 'Add', value: 'Hello, world again from Frequency' }, + ]); const target_hash = await getCurrentItemizedHash(msa_id, itemizedSchemaId); - const add_actions = [add_action, update_action]; - const payload = await generateItemizedSignaturePayloadV2({ - targetHash: target_hash, - schemaId: itemizedSchemaId, - actions: add_actions, - }); - const itemizedPayloadData = ExtrinsicHelper.api.registry.createType( - 'PalletStatefulStorageItemizedSignaturePayloadV2', - payload - ); - const itemized_add_result_1 = ExtrinsicHelper.applyItemActionsWithSignatureV2( + const itemized_add_result_1 = ExtrinsicHelper.applyItemActions( delegatorKeys, - providerKeys, - signPayloadSr25519(delegatorKeys, itemizedPayloadData), - payload + itemizedSchemaId, + msa_id, + add_actions, + target_hash ); const { target: pageUpdateEvent1, eventMap: chainEvents } = await itemized_add_result_1.fundAndSend(fundingSource); @@ -161,7 +291,84 @@ describe('📗 Stateful Pallet Storage Signature Required', function () { }); describe('Paginated With Signature Storage Tests', function () { - it('should be able to call upsert a page and delete it successfully', async function () { + it('provider should be able to call upsert a page and delete it successfully', async function () { + const page_id = new u16(ExtrinsicHelper.api.registry, 1); + + // Add and update actions + let target_hash = await getCurrentPaginatedHash(msa_id, paginatedSchemaId, page_id.toNumber()); + const upsertPayload = await generatePaginatedUpsertSignaturePayload({ + msaId: msa_id, + targetHash: target_hash, + schemaId: paginatedSchemaId, + pageId: page_id, + payload: new Bytes(ExtrinsicHelper.api.registry, 'Hello World From Frequency'), + }); + const upsertPayloadData = ExtrinsicHelper.api.registry.createType( + 'PalletStatefulStoragePaginatedUpsertSignaturePayload', + upsertPayload + ); + const upsert_result = ExtrinsicHelper.upsertPageWithSignature( + delegatorKeys, + undelegatedProviderKeys, + signPayloadSr25519(delegatorKeys, upsertPayloadData), + upsertPayload + ); + const { target: pageUpdateEvent, eventMap: chainEvents1 } = await upsert_result.fundAndSend(fundingSource); + assert.notEqual( + chainEvents1['system.ExtrinsicSuccess'], + undefined, + 'should have returned an ExtrinsicSuccess event' + ); + assert.notEqual( + chainEvents1['transactionPayment.TransactionFeePaid'], + undefined, + 'should have returned a TransactionFeePaid event' + ); + assert.notEqual( + pageUpdateEvent, + undefined, + 'should have returned a PalletStatefulStoragePaginatedPageUpdate event' + ); + + // Remove the page + target_hash = await getCurrentPaginatedHash(msa_id, paginatedSchemaId, page_id.toNumber()); + const deletePayload = await generatePaginatedDeleteSignaturePayload({ + msaId: msa_id, + targetHash: target_hash, + schemaId: paginatedSchemaId, + pageId: page_id, + }); + const deletePayloadData = ExtrinsicHelper.api.registry.createType( + 'PalletStatefulStoragePaginatedDeleteSignaturePayload', + deletePayload + ); + const remove_result = ExtrinsicHelper.deletePageWithSignature( + delegatorKeys, + undelegatedProviderKeys, + signPayloadSr25519(delegatorKeys, deletePayloadData), + deletePayload + ); + const { target: pageRemove, eventMap: chainEvents2 } = await remove_result.fundAndSend(fundingSource); + assert.notEqual( + chainEvents2['system.ExtrinsicSuccess'], + undefined, + 'should have returned an ExtrinsicSuccess event' + ); + assert.notEqual( + chainEvents2['transactionPayment.TransactionFeePaid'], + undefined, + 'should have returned a TransactionFeePaid event' + ); + assert.notEqual(pageRemove, undefined, 'should have returned a event'); + + // no pages should exist + const result = await ExtrinsicHelper.getPaginatedStorage(msa_id, paginatedSchemaId); + assert.notEqual(result, undefined, 'should have returned a valid response'); + const thePage = result.toArray().find((page) => page.page_id === page_id); + assert.equal(thePage, undefined, 'inserted page should not exist'); + }); + + it('delegator (owner) should be able to call upsert a page and delete it successfully', async function () { const page_id = new u16(ExtrinsicHelper.api.registry, 1); // Add and update actions @@ -179,7 +386,7 @@ describe('📗 Stateful Pallet Storage Signature Required', function () { ); const upsert_result = ExtrinsicHelper.upsertPageWithSignature( delegatorKeys, - providerKeys, + delegatorKeys, signPayloadSr25519(delegatorKeys, upsertPayloadData), upsertPayload ); @@ -214,7 +421,82 @@ describe('📗 Stateful Pallet Storage Signature Required', function () { ); const remove_result = ExtrinsicHelper.deletePageWithSignature( delegatorKeys, - providerKeys, + delegatorKeys, + signPayloadSr25519(delegatorKeys, deletePayloadData), + deletePayload + ); + const { target: pageRemove, eventMap: chainEvents2 } = await remove_result.fundAndSend(fundingSource); + assert.notEqual( + chainEvents2['system.ExtrinsicSuccess'], + undefined, + 'should have returned an ExtrinsicSuccess event' + ); + assert.notEqual( + chainEvents2['transactionPayment.TransactionFeePaid'], + undefined, + 'should have returned a TransactionFeePaid event' + ); + assert.notEqual(pageRemove, undefined, 'should have returned a event'); + + // no pages should exist + const result = await ExtrinsicHelper.getPaginatedStorage(msa_id, paginatedSchemaId); + assert.notEqual(result, undefined, 'should have returned a valid response'); + const thePage = result.toArray().find((page) => page.page_id === page_id); + assert.equal(thePage, undefined, 'inserted page should not exist'); + }); + + it('provider should be able to call upsertPageWithSignatureV2 a page and deletePageWithSignatureV2 it successfully', async function () { + const page_id = new u16(ExtrinsicHelper.api.registry, 1); + + // Add and update actions + let target_hash = await getCurrentPaginatedHash(msa_id, paginatedSchemaId, page_id.toNumber()); + const upsertPayload = await generatePaginatedUpsertSignaturePayloadV2({ + targetHash: target_hash, + schemaId: paginatedSchemaId, + pageId: page_id, + payload: new Bytes(ExtrinsicHelper.api.registry, 'Hello World From Frequency'), + }); + const upsertPayloadData = ExtrinsicHelper.api.registry.createType( + 'PalletStatefulStoragePaginatedUpsertSignaturePayloadV2', + upsertPayload + ); + const upsert_result = ExtrinsicHelper.upsertPageWithSignatureV2( + delegatorKeys, + undelegatedProviderKeys, + signPayloadSr25519(delegatorKeys, upsertPayloadData), + upsertPayload + ); + const { target: pageUpdateEvent, eventMap: chainEvents1 } = await upsert_result.fundAndSend(fundingSource); + assert.notEqual( + chainEvents1['system.ExtrinsicSuccess'], + undefined, + 'should have returned an ExtrinsicSuccess event' + ); + assert.notEqual( + chainEvents1['transactionPayment.TransactionFeePaid'], + undefined, + 'should have returned a TransactionFeePaid event' + ); + assert.notEqual( + pageUpdateEvent, + undefined, + 'should have returned a PalletStatefulStoragePaginatedPageUpdate event' + ); + + // Remove the page + target_hash = await getCurrentPaginatedHash(msa_id, paginatedSchemaId, page_id.toNumber()); + const deletePayload = await generatePaginatedDeleteSignaturePayloadV2({ + targetHash: target_hash, + schemaId: paginatedSchemaId, + pageId: page_id, + }); + const deletePayloadData = ExtrinsicHelper.api.registry.createType( + 'PalletStatefulStoragePaginatedDeleteSignaturePayloadV2', + deletePayload + ); + const remove_result = ExtrinsicHelper.deletePageWithSignatureV2( + delegatorKeys, + undelegatedProviderKeys, signPayloadSr25519(delegatorKeys, deletePayloadData), deletePayload ); @@ -234,10 +516,11 @@ describe('📗 Stateful Pallet Storage Signature Required', function () { // no pages should exist const result = await ExtrinsicHelper.getPaginatedStorage(msa_id, paginatedSchemaId); assert.notEqual(result, undefined, 'should have returned a valid response'); - assert.equal(result.length, 0, 'should returned no paginated pages'); + const thePage = result.toArray().find((page) => page.page_id === page_id); + assert.equal(thePage, undefined, 'inserted page should not exist'); }); - it('should be able to call upsertPageWithSignatureV2 a page and deletePageWithSignatureV2 it successfully', async function () { + it('delegator (owner) should be able to call upsertPageWithSignatureV2 a page and deletePageWithSignatureV2 it successfully', async function () { const page_id = new u16(ExtrinsicHelper.api.registry, 1); // Add and update actions @@ -254,7 +537,7 @@ describe('📗 Stateful Pallet Storage Signature Required', function () { ); const upsert_result = ExtrinsicHelper.upsertPageWithSignatureV2( delegatorKeys, - providerKeys, + delegatorKeys, signPayloadSr25519(delegatorKeys, upsertPayloadData), upsertPayload ); @@ -288,7 +571,7 @@ describe('📗 Stateful Pallet Storage Signature Required', function () { ); const remove_result = ExtrinsicHelper.deletePageWithSignatureV2( delegatorKeys, - providerKeys, + delegatorKeys, signPayloadSr25519(delegatorKeys, deletePayloadData), deletePayload ); @@ -308,7 +591,113 @@ describe('📗 Stateful Pallet Storage Signature Required', function () { // no pages should exist const result = await ExtrinsicHelper.getPaginatedStorage(msa_id, paginatedSchemaId); assert.notEqual(result, undefined, 'should have returned a valid response'); - assert.equal(result.length, 0, 'should returned no paginated pages'); + const thePage = result.toArray().find((page) => page.page_id === page_id); + assert.equal(thePage, undefined, 'inserted page should not exist'); + }); + + it('provider should not be able to call upsertPage directly', async function () { + const page_id = new u16(ExtrinsicHelper.api.registry, 1); + + const target_hash = await getCurrentPaginatedHash(msa_id, paginatedSchemaId, page_id.toNumber()); + + const upsert = ExtrinsicHelper.upsertPage( + undelegatedProviderKeys, + paginatedSchemaId, + msa_id, + page_id, + new Bytes(ExtrinsicHelper.api.registry, 'Hello World From Frequency'), + target_hash + ); + await assert.rejects(upsert.fundAndSend(fundingSource), { name: 'UnauthorizedDelegate' }); + + const upsert_2 = ExtrinsicHelper.upsertPage( + delegatedProviderKeys, + paginatedSchemaId, + msa_id, + page_id, + new Bytes(ExtrinsicHelper.api.registry, 'Hello World From Frequency'), + target_hash + ); + await assert.rejects(upsert_2.fundAndSend(fundingSource), { name: 'UnsupportedOperationForSchema' }); + }); + + it('owner should be able to call upsertPage directly', async function () { + const page_id = new u16(ExtrinsicHelper.api.registry, 1); + + const target_hash = await getCurrentPaginatedHash(msa_id, paginatedSchemaId, page_id.toNumber()); + + const upsert_result = ExtrinsicHelper.upsertPage( + delegatorKeys, + paginatedSchemaId, + msa_id, + page_id, + new Bytes(ExtrinsicHelper.api.registry, 'Hello World From Frequency'), + target_hash + ); + const { target: pageUpdateEvent, eventMap: chainEvents1 } = await upsert_result.fundAndSend(fundingSource); + assert.notEqual( + chainEvents1['system.ExtrinsicSuccess'], + undefined, + 'should have returned an ExtrinsicSuccess event' + ); + assert.notEqual( + chainEvents1['transactionPayment.TransactionFeePaid'], + undefined, + 'should have returned a TransactionFeePaid event' + ); + assert.notEqual( + pageUpdateEvent, + undefined, + 'should have returned a PalletStatefulStoragePaginatedPageUpdate event' + ); + }); + + it('delegate should not be able to call removePage directly', async function () { + const page_id = new u16(ExtrinsicHelper.api.registry, 1); + + const target_hash = await getCurrentPaginatedHash(msa_id, paginatedSchemaId, page_id.toNumber()); + + const remove_op = ExtrinsicHelper.removePage( + undelegatedProviderKeys, + paginatedSchemaId, + msa_id, + page_id, + target_hash + ); + await assert.rejects(remove_op.fundAndSend(fundingSource), { name: 'UnauthorizedDelegate' }); + + const remove_op_2 = ExtrinsicHelper.removePage( + delegatedProviderKeys, + paginatedSchemaId, + msa_id, + page_id, + target_hash + ); + await assert.rejects(remove_op_2.fundAndSend(fundingSource), { name: 'UnsupportedOperationForSchema' }); + }); + + it('owner should be able to call removePage directly', async function () { + const page_id = new u16(ExtrinsicHelper.api.registry, 1); + + const target_hash = await getCurrentPaginatedHash(msa_id, paginatedSchemaId, page_id.toNumber()); + + const remove_result = ExtrinsicHelper.removePage(delegatorKeys, paginatedSchemaId, msa_id, page_id, target_hash); + const { target: pageUpdateEvent, eventMap: chainEvents1 } = await remove_result.fundAndSend(fundingSource); + assert.notEqual( + chainEvents1['system.ExtrinsicSuccess'], + undefined, + 'should have returned an ExtrinsicSuccess event' + ); + assert.notEqual( + chainEvents1['transactionPayment.TransactionFeePaid'], + undefined, + 'should have returned a TransactionFeePaid event' + ); + assert.notEqual( + pageUpdateEvent, + undefined, + 'should have returned a PalletStatefulStoragePaginatedPageDeleted event' + ); }); }); }); diff --git a/pallets/stateful-storage/src/lib.rs b/pallets/stateful-storage/src/lib.rs index d94107e20d..6531fd67b0 100644 --- a/pallets/stateful-storage/src/lib.rs +++ b/pallets/stateful-storage/src/lib.rs @@ -252,6 +252,11 @@ pub mod pallet { /// Applies the Add or Delete Actions on the requested Itemized page. /// This is treated as a transaction so either all actions succeed or none will be executed. /// + /// Note: if called by the state owner, call may succeed even on `SignatureRequired` schemas. + /// The fact that the entire (signed) transaction is submitted by the owner's keypair is + /// considered equivalent to supplying a separate signature. Note in that case that a delegate + /// submitting this extrinsic on behalf of a user would fail. + /// /// # Events /// * [`Event::ItemizedPageUpdated`] /// * [`Event::ItemizedPageDeleted`] @@ -273,14 +278,25 @@ pub mod pallet { ) -> DispatchResult { let key = ensure_signed(origin)?; let is_pruning = actions.iter().any(|a| matches!(a, ItemAction::Delete { .. })); - Self::check_schema_for_write(schema_id, PayloadLocation::Itemized, false, is_pruning)?; - Self::check_msa_and_grants(key, state_owner_msa_id, schema_id)?; + let caller_msa_id = Self::check_msa_and_grants(key, state_owner_msa_id, schema_id)?; + let caller_is_state_owner = caller_msa_id == state_owner_msa_id; + Self::check_schema_for_write( + schema_id, + PayloadLocation::Itemized, + caller_is_state_owner, + is_pruning, + )?; Self::update_itemized(state_owner_msa_id, schema_id, target_hash, actions)?; Ok(()) } /// Creates or updates an Paginated storage with new payload /// + /// Note: if called by the state owner, call may succeed even on `SignatureRequired` schemas. + /// The fact that the entire (signed) transaction is submitted by the owner's keypair is + /// considered equivalent to supplying a separate signature. Note in that case that a delegate + /// submitting this extrinsic on behalf of a user would fail. + /// /// # Events /// * [`Event::PaginatedPageUpdated`] /// @@ -296,8 +312,15 @@ pub mod pallet { ) -> DispatchResult { let provider_key = ensure_signed(origin)?; ensure!(page_id <= T::MaxPaginatedPageId::get(), Error::::PageIdExceedsMaxAllowed); - Self::check_schema_for_write(schema_id, PayloadLocation::Paginated, false, false)?; - Self::check_msa_and_grants(provider_key, state_owner_msa_id, schema_id)?; + let caller_msa_id = + Self::check_msa_and_grants(provider_key, state_owner_msa_id, schema_id)?; + let caller_is_state_owner = caller_msa_id == state_owner_msa_id; + Self::check_schema_for_write( + schema_id, + PayloadLocation::Paginated, + caller_is_state_owner, + false, + )?; Self::update_paginated( state_owner_msa_id, schema_id, @@ -310,6 +333,11 @@ pub mod pallet { /// Deletes a Paginated storage /// + /// Note: if called by the state owner, call may succeed even on `SignatureRequired` schemas. + /// The fact that the entire (signed) transaction is submitted by the owner's keypair is + /// considered equivalent to supplying a separate signature. Note in that case that a delegate + /// submitting this extrinsic on behalf of a user would fail. + /// /// # Events /// * [`Event::PaginatedPageDeleted`] /// @@ -324,8 +352,15 @@ pub mod pallet { ) -> DispatchResult { let provider_key = ensure_signed(origin)?; ensure!(page_id <= T::MaxPaginatedPageId::get(), Error::::PageIdExceedsMaxAllowed); - Self::check_schema_for_write(schema_id, PayloadLocation::Paginated, false, true)?; - Self::check_msa_and_grants(provider_key, state_owner_msa_id, schema_id)?; + let caller_msa_id = + Self::check_msa_and_grants(provider_key, state_owner_msa_id, schema_id)?; + let caller_is_state_owner = caller_msa_id == state_owner_msa_id; + Self::check_schema_for_write( + schema_id, + PayloadLocation::Paginated, + caller_is_state_owner, + true, + )?; Self::delete_paginated(state_owner_msa_id, schema_id, page_id, target_hash)?; Ok(()) } diff --git a/pallets/stateful-storage/src/tests/apply_item_actions_tests.rs b/pallets/stateful-storage/src/tests/apply_item_actions_tests.rs index 31f527f95a..ee47fa85da 100644 --- a/pallets/stateful-storage/src/tests/apply_item_actions_tests.rs +++ b/pallets/stateful-storage/src/tests/apply_item_actions_tests.rs @@ -14,7 +14,7 @@ use parity_scale_codec::Encode; #[allow(unused_imports)] use pretty_assertions::{assert_eq, assert_ne, assert_str_eq}; use sp_core::{sr25519, Get, Pair}; -use sp_runtime::MultiSignature; +use sp_runtime::{AccountId32, MultiSignature}; #[test] fn apply_item_actions_with_invalid_msa_should_fail() { @@ -763,18 +763,22 @@ fn apply_item_actions_with_signature_having_corrupted_state_should_fail() { } #[test] -fn apply_item_actions_on_signature_schema_fails() { +fn apply_item_actions_on_signature_schema_fails_for_non_owner() { new_test_ext().execute_with(|| { // arrange - let caller_1 = test_public(1); - let msa_id = 1; + // Note: normal use case for this test would be called by a delegate; + // we don't bother setting up the delegation because the call should fail + // before we check the delegation, as long as the owner_msa_id != caller_msa_id + let (caller_msa_id, caller_keys) = get_signature_account(); + let owner_msa_id = caller_msa_id.saturating_add(1); + let caller_1: AccountId32 = caller_keys.public().into(); let schema_id = ITEMIZED_SIGNATURE_REQUIRED_SCHEMA; let payload = vec![1; 5]; let actions1 = vec![ItemAction::Add { data: payload.try_into().unwrap() }]; assert_err!( StatefulStoragePallet::apply_item_actions( RuntimeOrigin::signed(caller_1), - msa_id, + owner_msa_id, schema_id, NONEXISTENT_PAGE_HASH, BoundedVec::try_from(actions1).unwrap(), @@ -784,6 +788,25 @@ fn apply_item_actions_on_signature_schema_fails() { }); } +#[test] +fn apply_item_actions_on_signature_schema_succeeds_for_owner() { + new_test_ext().execute_with(|| { + // arrange + let (msa_id, caller_keys) = get_signature_account(); + let caller_1: AccountId32 = caller_keys.public().into(); + let schema_id = ITEMIZED_SIGNATURE_REQUIRED_SCHEMA; + let payload = vec![1; 5]; + let actions1 = vec![ItemAction::Add { data: payload.try_into().unwrap() }]; + assert_ok!(StatefulStoragePallet::apply_item_actions( + RuntimeOrigin::signed(caller_1), + msa_id, + schema_id, + NONEXISTENT_PAGE_HASH, + BoundedVec::try_from(actions1).unwrap(), + )); + }); +} + #[test] #[allow(deprecated)] fn apply_item_actions_with_signature_having_page_with_stale_hash_should_fail() { diff --git a/pallets/stateful-storage/src/tests/delete_page_tests.rs b/pallets/stateful-storage/src/tests/delete_page_tests.rs index 450a8f3ba7..4d8b76deee 100644 --- a/pallets/stateful-storage/src/tests/delete_page_tests.rs +++ b/pallets/stateful-storage/src/tests/delete_page_tests.rs @@ -11,7 +11,7 @@ use parity_scale_codec::Encode; #[allow(unused_imports)] use pretty_assertions::{assert_eq, assert_ne, assert_str_eq}; use sp_core::{sr25519, Get, Pair}; -use sp_runtime::MultiSignature; +use sp_runtime::{AccountId32, MultiSignature}; #[test] fn delete_page_id_out_of_bounds_errors() { @@ -1002,3 +1002,62 @@ fn delete_page_with_signature_v2_having_valid_inputs_should_remove_page() { ); }) } + +#[test] +fn delete_page_on_signature_schema_fails_for_non_owner() { + new_test_ext().execute_with(|| { + // arrange + // Note: normal use case for this test would be called by a delegate; + // we don't bother setting up the delegation because the call should fail + // before we check the delegation, as long as the owner_msa_id != caller_msa_id + let (caller_msa_id, caller_keys) = get_signature_account(); + let owner_msa_id = caller_msa_id.saturating_add(1); + let caller_1: AccountId32 = caller_keys.public().into(); + let schema_id = PAGINATED_SIGNED_SCHEMA; + let page_id = 1; + let page = generate_page::(Some(1), Some(100)); + ::write( + &owner_msa_id, + PALLET_STORAGE_PREFIX, + PAGINATED_STORAGE_PREFIX, + &(schema_id, page_id), + &page, + ); + assert_err!( + StatefulStoragePallet::delete_page( + RuntimeOrigin::signed(caller_1), + owner_msa_id, + schema_id, + page_id, + page.get_hash() + ), + Error::::UnsupportedOperationForSchema + ); + }); +} + +#[test] +fn delete_page_on_signature_schema_succeeds_for_owner() { + new_test_ext().execute_with(|| { + // arrange + let (msa_id, caller_keys) = get_signature_account(); + let caller_1: AccountId32 = caller_keys.public().into(); + let schema_id = PAGINATED_SIGNED_SCHEMA; + let page_id = 1; + let page = generate_page::(Some(1), Some(100)); + ::write( + &msa_id, + PALLET_STORAGE_PREFIX, + PAGINATED_STORAGE_PREFIX, + &(schema_id, page_id), + &page, + ); + assert_ok!(StatefulStoragePallet::delete_page( + RuntimeOrigin::signed(caller_1), + msa_id, + schema_id, + page_id, + page.get_hash() + )); + }); +} diff --git a/pallets/stateful-storage/src/tests/upsert_page_tests.rs b/pallets/stateful-storage/src/tests/upsert_page_tests.rs index f7f8366f8d..b1e4218973 100644 --- a/pallets/stateful-storage/src/tests/upsert_page_tests.rs +++ b/pallets/stateful-storage/src/tests/upsert_page_tests.rs @@ -15,7 +15,7 @@ use parity_scale_codec::Encode; #[allow(unused_imports)] use pretty_assertions::{assert_eq, assert_ne, assert_str_eq}; use sp_core::{sr25519, Get, Pair}; -use sp_runtime::MultiSignature; +use sp_runtime::{AccountId32, MultiSignature}; use sp_std::hash::Hasher; use twox_hash::XxHash64; @@ -671,11 +671,15 @@ fn upsert_page_with_signature_having_valid_inputs_should_work() { } #[test] -fn insert_page_fails_for_signature_schema() { +fn upsert_page_on_signature_schema_fails_for_non_owner() { new_test_ext().execute_with(|| { // setup - let caller_1 = test_public(1); - let msa_id = 1; + // Note: normal use case for this test would be called by a delegate; + // we don't bother setting up the delegation because the call should fail + // before we check the delegation, as long as the owner_msa_id != caller_msa_id + let (caller_msa_id, caller_keys) = get_signature_account(); + let caller_1: AccountId32 = caller_keys.public().into(); + let owner_msa_id = caller_msa_id.saturating_add(1); let schema_id = PAGINATED_SIGNED_SCHEMA; let page_id = 11; let payload = generate_payload_bytes::(None); @@ -684,7 +688,7 @@ fn insert_page_fails_for_signature_schema() { assert_err!( StatefulStoragePallet::upsert_page( RuntimeOrigin::signed(caller_1), - msa_id, + owner_msa_id, schema_id, page_id, NONEXISTENT_PAGE_HASH, @@ -695,6 +699,28 @@ fn insert_page_fails_for_signature_schema() { }); } +#[test] +fn upsert_page_on_signature_schema_succeeds_for_owner() { + new_test_ext().execute_with(|| { + // setup + let (msa_id, caller_keys) = get_signature_account(); + let caller_1: AccountId32 = caller_keys.public().into(); + let schema_id = PAGINATED_SIGNED_SCHEMA; + let page_id = 11; + let payload = generate_payload_bytes::(None); + + // assert + assert_ok!(StatefulStoragePallet::upsert_page( + RuntimeOrigin::signed(caller_1), + msa_id, + schema_id, + page_id, + NONEXISTENT_PAGE_HASH, + payload.into(), + )); + }); +} + #[test] fn upsert_page_with_signature_v2_having_page_id_out_of_bounds_should_fail() { new_test_ext().execute_with(|| { diff --git a/runtime/frequency/src/lib.rs b/runtime/frequency/src/lib.rs index 24121fb2fd..e51a975ca7 100644 --- a/runtime/frequency/src/lib.rs +++ b/runtime/frequency/src/lib.rs @@ -330,7 +330,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("frequency"), impl_name: create_runtime_str!("frequency"), authoring_version: 1, - spec_version: 69, + spec_version: 70, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -344,7 +344,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("frequency-rococo"), impl_name: create_runtime_str!("frequency"), authoring_version: 1, - spec_version: 69, + spec_version: 70, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1,