diff --git a/packages/kusama/src/assetHubKusama.system.e2e.test.ts b/packages/kusama/src/assetHubKusama.system.e2e.test.ts new file mode 100644 index 000000000..c431dda53 --- /dev/null +++ b/packages/kusama/src/assetHubKusama.system.e2e.test.ts @@ -0,0 +1,20 @@ +import { assetHubKusama } from '@e2e-test/networks/chains' +import { type ParaTestConfig, registerTestTree, systemE2ETestsForParaWithScheduler } from '@e2e-test/shared' + +// TODO: Uncomment after Kusama 2.0+ release due to polkadot-fellows/runtimes#957 +// const testConfig: ParaTestConfig = { +// testSuiteName: 'Kusama AssetHub System', +// addressEncoding: 2, +// blockProvider: 'Local', +// asyncBacking: 'Enabled', +// } +// registerTestTree(systemE2ETestsViaRemoteScheduler(kusama, assetHubKusama, testConfig)) + +const testConfigForLocalScheduler: ParaTestConfig = { + testSuiteName: 'Kusama AssetHub System', + addressEncoding: 2, + blockProvider: 'NonLocal', + asyncBacking: 'Enabled', +} + +registerTestTree(systemE2ETestsForParaWithScheduler(assetHubKusama, testConfigForLocalScheduler)) diff --git a/packages/kusama/src/bridgeHubKusama.system.e2e.test.ts b/packages/kusama/src/bridgeHubKusama.system.e2e.test.ts new file mode 100644 index 000000000..2c5edaf3a --- /dev/null +++ b/packages/kusama/src/bridgeHubKusama.system.e2e.test.ts @@ -0,0 +1,21 @@ +import { assetHubKusama, bridgeHubKusama } from '@e2e-test/networks/chains' +import { type ParaTestConfig, registerTestTree, systemE2ETestsViaRemoteScheduler } from '@e2e-test/shared' + +// TODO: Uncomment after Kusama 2.0+ release due to polkadot-fellows/runtimes#957 +// const testConfig: ParaTestConfig = { +// testSuiteName: 'Kusama BridgeHub System', +// addressEncoding: 2, +// blockProvider: 'Local', +// asyncBacking: 'Enabled', +// } + +// registerTestTree(systemE2ETestsViaRemoteScheduler(kusama, bridgeHubKusama, testConfig)) + +const testConfigForAssetHub: ParaTestConfig = { + testSuiteName: 'Kusama BridgeHub System', + addressEncoding: 2, + blockProvider: 'NonLocal', + asyncBacking: 'Enabled', +} + +registerTestTree(systemE2ETestsViaRemoteScheduler(assetHubKusama, bridgeHubKusama, testConfigForAssetHub)) diff --git a/packages/kusama/src/coretimeKusama.system.e2e.test.ts b/packages/kusama/src/coretimeKusama.system.e2e.test.ts new file mode 100644 index 000000000..49270670d --- /dev/null +++ b/packages/kusama/src/coretimeKusama.system.e2e.test.ts @@ -0,0 +1,21 @@ +import { assetHubKusama, coretimeKusama } from '@e2e-test/networks/chains' +import { type ParaTestConfig, registerTestTree, systemE2ETestsViaRemoteScheduler } from '@e2e-test/shared' + +// TODO: Uncomment after Kusama 2.0+ release due to polkadot-fellows/runtimes#957 +// const testConfig: ParaTestConfig = { +// testSuiteName: 'Kusama Coretime System', +// addressEncoding: 0, +// blockProvider: 'Local', +// asyncBacking: 'Enabled', +// } + +// registerTestTree(systemE2ETestsViaRemoteScheduler(kusama, coretimeKusama, testConfig)) + +const testConfigForAssetHub: ParaTestConfig = { + testSuiteName: 'Kusama Coretime System', + addressEncoding: 2, + blockProvider: 'NonLocal', + asyncBacking: 'Enabled', +} + +registerTestTree(systemE2ETestsViaRemoteScheduler(assetHubKusama, coretimeKusama, testConfigForAssetHub)) diff --git a/packages/kusama/src/encointerKusama.system.e2e.test.ts b/packages/kusama/src/encointerKusama.system.e2e.test.ts new file mode 100644 index 000000000..5f4215c24 --- /dev/null +++ b/packages/kusama/src/encointerKusama.system.e2e.test.ts @@ -0,0 +1,21 @@ +import { assetHubKusama, encointerKusama } from '@e2e-test/networks/chains' +import { type ParaTestConfig, registerTestTree, systemE2ETestsViaRemoteScheduler } from '@e2e-test/shared' + +// TODO: Uncomment after Kusama 2.0+ release due to polkadot-fellows/runtimes#957 +// const testConfig: ParaTestConfig = { +// testSuiteName: 'Kusama Encointer System', +// addressEncoding: 0, +// blockProvider: 'Local', +// asyncBacking: 'Enabled', +// } + +// registerTestTree(systemE2ETestsViaRemoteScheduler(kusama, encointerKusama, testConfig)) + +const testConfigForAssetHub: ParaTestConfig = { + testSuiteName: 'Kusama Encointer System', + addressEncoding: 2, + blockProvider: 'NonLocal', + asyncBacking: 'Enabled', +} + +registerTestTree(systemE2ETestsViaRemoteScheduler(assetHubKusama, encointerKusama, testConfigForAssetHub)) diff --git a/packages/kusama/src/kusama.system.e2e.test.ts b/packages/kusama/src/kusama.system.e2e.test.ts new file mode 100644 index 000000000..08e67524c --- /dev/null +++ b/packages/kusama/src/kusama.system.e2e.test.ts @@ -0,0 +1,20 @@ +import { assetHubKusama, kusama } from '@e2e-test/networks/chains' +import { type ParaTestConfig, registerTestTree, systemE2ETestsViaRemoteScheduler } from '@e2e-test/shared' + +// TODO: Uncomment after Kusama 2.0+ release due to polkadot-fellows/runtimes#957 +// const testConfig: RelayTestConfig = { +// testSuiteName: 'Kusama System', +// addressEncoding: 2, +// blockProvider: 'Local', +// } + +// registerTestTree(systemE2ETests(kusama, testConfig)) + +const testConfigForAssetHub: ParaTestConfig = { + testSuiteName: 'Kusama System', + addressEncoding: 2, + blockProvider: 'NonLocal', + asyncBacking: 'Enabled', +} + +registerTestTree(systemE2ETestsViaRemoteScheduler(assetHubKusama, kusama, testConfigForAssetHub)) diff --git a/packages/kusama/src/peopleKusama.system.e2e.test.ts b/packages/kusama/src/peopleKusama.system.e2e.test.ts new file mode 100644 index 000000000..8eb2074c5 --- /dev/null +++ b/packages/kusama/src/peopleKusama.system.e2e.test.ts @@ -0,0 +1,21 @@ +import { assetHubKusama, peopleKusama } from '@e2e-test/networks/chains' +import { type ParaTestConfig, registerTestTree, systemE2ETestsViaRemoteScheduler } from '@e2e-test/shared' + +// TODO: Uncomment after Kusama 2.0+ release due to polkadot-fellows/runtimes#957 +// const testConfig: ParaTestConfig = { +// testSuiteName: 'Kusama People System', +// addressEncoding: 0, +// blockProvider: 'Local', +// asyncBacking: 'Enabled', +// } + +// registerTestTree(systemE2ETestsViaRemoteScheduler(kusama, peopleKusama, testConfig)) + +const testConfigForAssetHub: ParaTestConfig = { + testSuiteName: 'Kusama People System', + addressEncoding: 2, + blockProvider: 'NonLocal', + asyncBacking: 'Enabled', +} + +registerTestTree(systemE2ETestsViaRemoteScheduler(assetHubKusama, peopleKusama, testConfigForAssetHub)) diff --git a/packages/polkadot/src/assetHubPolkadot.system.e2e.test.ts b/packages/polkadot/src/assetHubPolkadot.system.e2e.test.ts new file mode 100644 index 000000000..41402c209 --- /dev/null +++ b/packages/polkadot/src/assetHubPolkadot.system.e2e.test.ts @@ -0,0 +1,22 @@ +import { assetHubPolkadot, polkadot } from '@e2e-test/networks/chains' +import { type ParaTestConfig, registerTestTree, systemE2ETestsViaRemoteScheduler } from '@e2e-test/shared' + +const testConfig: ParaTestConfig = { + testSuiteName: 'Polkadot AssetHub System', + addressEncoding: 0, + blockProvider: 'Local', + asyncBacking: 'Enabled', +} + +registerTestTree(systemE2ETestsViaRemoteScheduler(polkadot, assetHubPolkadot, testConfig)) + +// TODO: Uncomment Post-AHM on Polkadot + +// const testConfigForLocalScheduler: ParaTestConfig = { +// testSuiteName: 'Polkadot AssetHub System', +// addressEncoding: 0, +// blockProvider: 'NonLocal', +// asyncBacking: 'Enabled', +// } + +// registerTestTree(systemE2ETestsForParaWithScheduler(assetHubPolkadot, testConfigForLocalScheduler)) diff --git a/packages/polkadot/src/bridgeHubPolkadot.system.e2e.test.ts b/packages/polkadot/src/bridgeHubPolkadot.system.e2e.test.ts new file mode 100644 index 000000000..adf3ad2eb --- /dev/null +++ b/packages/polkadot/src/bridgeHubPolkadot.system.e2e.test.ts @@ -0,0 +1,22 @@ +import { bridgeHubPolkadot, polkadot } from '@e2e-test/networks/chains' +import { type ParaTestConfig, registerTestTree, systemE2ETestsViaRemoteScheduler } from '@e2e-test/shared' + +const testConfig: ParaTestConfig = { + testSuiteName: 'Polkadot BridgeHub System', + addressEncoding: 0, + blockProvider: 'Local', + asyncBacking: 'Enabled', +} + +registerTestTree(systemE2ETestsViaRemoteScheduler(polkadot, bridgeHubPolkadot, testConfig)) + +// TODO: Uncomment Post-AHM on Polkadot + +// const testConfigForAssetHub: ParaTestConfig = { +// testSuiteName: 'Polkadot BridgeHub System', +// addressEncoding: 0, +// blockProvider: 'NonLocal', +// asyncBacking: 'Enabled', +// } + +// registerTestTree(systemE2ETestsViaRemoteScheduler(assetHubPolkadot, bridgeHubPolkadot, testConfigForAssetHub)) diff --git a/packages/polkadot/src/collectivesPolkadot.system.e2e.test.ts b/packages/polkadot/src/collectivesPolkadot.system.e2e.test.ts new file mode 100644 index 000000000..39ad1aaea --- /dev/null +++ b/packages/polkadot/src/collectivesPolkadot.system.e2e.test.ts @@ -0,0 +1,28 @@ +import { collectivesPolkadot, polkadot } from '@e2e-test/networks/chains' +import { + type ParaTestConfig, + registerTestTree, + systemE2ETestsForParaWithScheduler, + systemE2ETestsViaRemoteScheduler, +} from '@e2e-test/shared' + +const testConfig: ParaTestConfig = { + testSuiteName: 'Polkadot Collectives System', + addressEncoding: 0, + blockProvider: 'Local', + asyncBacking: 'Enabled', +} + +registerTestTree(systemE2ETestsViaRemoteScheduler(polkadot, collectivesPolkadot, testConfig)) +registerTestTree(systemE2ETestsForParaWithScheduler(collectivesPolkadot, testConfig)) + +// TODO: Uncomment Post-AHM on Polkadot + +// const testConfigForAssetHub: ParaTestConfig = { +// testSuiteName: 'Polkadot Collectives System', +// addressEncoding: 0, +// blockProvider: 'NonLocal', +// asyncBacking: 'Enabled', +// } + +// registerTestTree(systemE2ETestsViaRemoteScheduler(assetHubPolkadot, collectivesPolkadot, testConfigForAssetHub)) diff --git a/packages/polkadot/src/coretimePolkadot.system.e2e.test.ts b/packages/polkadot/src/coretimePolkadot.system.e2e.test.ts new file mode 100644 index 000000000..100ce5e71 --- /dev/null +++ b/packages/polkadot/src/coretimePolkadot.system.e2e.test.ts @@ -0,0 +1,22 @@ +import { coretimePolkadot, polkadot } from '@e2e-test/networks/chains' +import { type ParaTestConfig, registerTestTree, systemE2ETestsViaRemoteScheduler } from '@e2e-test/shared' + +const testConfig: ParaTestConfig = { + testSuiteName: 'Polkadot Coretime System', + addressEncoding: 0, + blockProvider: 'Local', + asyncBacking: 'Enabled', +} + +registerTestTree(systemE2ETestsViaRemoteScheduler(polkadot, coretimePolkadot, testConfig)) + +// TODO: Uncomment Post-AHM on Polkadot + +// const testConfigForAssetHub: ParaTestConfig = { +// testSuiteName: 'Polkadot Coretime System', +// addressEncoding: 0, +// blockProvider: 'NonLocal', +// asyncBacking: 'Enabled', +// } + +// registerTestTree(systemE2ETestsViaRemoteScheduler(assetHubPolkadot, coretimePolkadot, testConfigForAssetHub)) diff --git a/packages/polkadot/src/peoplePolkadot.system.e2e.test.ts b/packages/polkadot/src/peoplePolkadot.system.e2e.test.ts new file mode 100644 index 000000000..9772f0b34 --- /dev/null +++ b/packages/polkadot/src/peoplePolkadot.system.e2e.test.ts @@ -0,0 +1,22 @@ +import { peoplePolkadot, polkadot } from '@e2e-test/networks/chains' +import { type ParaTestConfig, registerTestTree, systemE2ETestsViaRemoteScheduler } from '@e2e-test/shared' + +const testConfig: ParaTestConfig = { + testSuiteName: 'Polkadot People System', + addressEncoding: 0, + blockProvider: 'Local', + asyncBacking: 'Enabled', +} + +registerTestTree(systemE2ETestsViaRemoteScheduler(polkadot, peoplePolkadot, testConfig)) + +// TODO: Uncomment Post-AHM on Polkadot + +// const testConfigForAssetHub: ParaTestConfig = { +// testSuiteName: 'Polkadot People System', +// addressEncoding: 0, +// blockProvider: 'NonLocal', +// asyncBacking: 'Enabled', +// } + +// registerTestTree(systemE2ETestsViaRemoteScheduler(assetHubPolkadot, peoplePolkadot, testConfigForAssetHub)) diff --git a/packages/polkadot/src/polkadot.system.e2e.test.ts b/packages/polkadot/src/polkadot.system.e2e.test.ts new file mode 100644 index 000000000..a7ed937b9 --- /dev/null +++ b/packages/polkadot/src/polkadot.system.e2e.test.ts @@ -0,0 +1,21 @@ +import { polkadot } from '@e2e-test/networks/chains' +import { type RelayTestConfig, registerTestTree, systemE2ETests } from '@e2e-test/shared' + +const testConfig: RelayTestConfig = { + testSuiteName: 'Polkadot System', + addressEncoding: 0, + blockProvider: 'Local', +} + +registerTestTree(systemE2ETests(polkadot, testConfig)) + +// TODO: Uncomment Post-AHM on Polkadot + +// const testConfigForAssetHub: ParaTestConfig = { +// testSuiteName: 'Polkadot System', +// addressEncoding: 0, +// blockProvider: 'NonLocal', +// asyncBacking: 'Enabled', +// } + +// registerTestTree(systemE2ETestsViaRemoteScheduler(assetHubPolkadot, polkadot, testConfigForAssetHub)) diff --git a/packages/shared/src/helpers/index.ts b/packages/shared/src/helpers/index.ts index 0d96b9757..a17efdb13 100644 --- a/packages/shared/src/helpers/index.ts +++ b/packages/shared/src/helpers/index.ts @@ -1,12 +1,15 @@ import type { StorageValues } from '@acala-network/chopsticks' import { sendTransaction, setupCheck } from '@acala-network/chopsticks-testing' -import { defaultAccounts } from '@e2e-test/networks' +import { type Chain, defaultAccounts } from '@e2e-test/networks' import type { ApiPromise } from '@polkadot/api' import { encodeAddress } from '@polkadot/keyring' import type { KeyringPair } from '@polkadot/keyring/types' +import type { EventRecord } from '@polkadot/types/interfaces' import type { PalletStakingValidatorPrefs } from '@polkadot/types/lookup' +import type { IsEvent } from '@polkadot/types/metadata/decorate/types' +import type { AnyTuple, Codec, IEvent } from '@polkadot/types/types' import type { HexString } from '@polkadot/util/types' import { assert, expect } from 'vitest' @@ -492,3 +495,127 @@ export interface ParaTestConfig { * Union type for all test configurations, whether relay or parachain. */ export type TestConfig = RelayTestConfig | ParaTestConfig + +/** + * Matcher for an event argument. + * Can be a literal (compared via `.toString()`) or a function `(actual) => boolean`. + */ +type ArgMatcher = unknown | ((actual: unknown) => boolean) + +/** + * Criteria to match a specific Substrate event. + * - `type`: event constructor (e.g. `api.events.system.ExtrinsicSuccess`) + * - `args` (optional): map of argument names to `ArgMatcher`s + * + * Examples: + * { type: api.events.balances.Transfer, args: { from: ALICE, to: BOB } } + * { type: api.events.scheduler.Dispatched, args: { result: (r) => r.isErr } } + */ +type EventMatchCriteria = { + type: IsEvent + args?: { [K in keyof N]?: ArgMatcher } +} + +/** + * Assert that specific Substrate events were emitted. + * + * Usage: + * - `type`: the event constructor (e.g. `api.events.system.CodeUpdated`) + * - `args` (optional): matchers for event fields + * - literal: compared via `.toString()` + * - function: `(value) => boolean` + * + * Examples: + * + * // Match by literal + * assertExpectedEvents(await api.query.system.events(), [ + * { type: api.events.preimage.Noted, args: { hash_: preimageHash } } + * ]) + * + * // Match with function + * assertExpectedEvents(await api.query.system.events(), [ + * { type: api.events.scheduler.Dispatched, args: { result: (r) => r.isErr } } + * ]) + * + * // Match by type only + * assertExpectedEvents(await api.query.system.events(), [ + * { type: api.events.system.CodeUpdated } + * ]) + */ +export function assertExpectedEvents(actualEvents: EventRecord[], expectedEvents: EventMatchCriteria[]): void { + const missing: string[] = [] + + for (const expected of expectedEvents) { + const { type, args: expectedArgs } = expected + + const match = actualEvents.find(({ event }) => { + if (!type.is(event)) return false + + if (!expectedArgs) return true + + const namedArgs = (event as IEvent).data as unknown as Record + + for (const [key, matcher] of Object.entries(expectedArgs)) { + const actualValue = namedArgs[key] + + if (typeof matcher === 'function') { + if (!matcher(actualValue)) { + return false + } + } else { + if (matcher?.toString?.() !== actualValue?.toString?.()) { + return false + } + } + } + + return true + }) + + if (!match) { + const name = type.meta?.name.toString() ?? '[unknown event]' + const argDesc = expectedArgs + ? `${JSON.stringify( + Object.fromEntries( + Object.entries(expectedArgs).map(([k, v]) => [k, typeof v === 'function' ? '[Function]' : v?.toString()]), + ), + )}` + : '' + missing.push(`Event type "${name}" with expected args: ${argDesc}`) + } + } + + if (missing.length > 0) { + throw new Error(`Expected events not found:\n- ${missing.join('\n- ')}`) + } +} + +/** + * Util to build the XCM `MultiLocation` describing the route from one chain to another. + * + * Determines how to reach `to` from `from` within a relay–parachain topology: + * - Relay → Parachain: `{ parents: 0, interior: { X1: [{ Parachain: id }] } }` + * - Parachain → Relay: `{ parents: 1, interior: "Here" }` + * + * @param from - The chain sending the XCM message. + * @param to - The target chain receiving the XCM message. + * @returns The computed `MultiLocation` route. + */ +export function getXcmRoute(from: Chain, to: Chain) { + let parents: number + let interior: any + + if (from.isRelayChain) { + parents = 0 + } else { + parents = 1 + } + + if (to.isRelayChain) { + interior = 'Here' + } else { + interior = { X1: [{ Parachain: to.paraId }] } + } + + return { parents, interior } +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 3174481db..9d8f3172f 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -12,6 +12,7 @@ export * from './proxy.js' export * from './scheduler.js' export * from './setup.js' export * from './staking.js' +export * from './system.js' export * from './treasury.js' export * from './types.js' export * from './vesting.js' diff --git a/packages/shared/src/system.ts b/packages/shared/src/system.ts new file mode 100644 index 000000000..3fc4e7b24 --- /dev/null +++ b/packages/shared/src/system.ts @@ -0,0 +1,763 @@ +import { sendTransaction } from '@acala-network/chopsticks-testing' + +import { type Chain, type Client, defaultAccountsSr25519 as devAccounts } from '@e2e-test/networks' +import { setupNetworks } from '@e2e-test/shared' + +import type { SubmittableExtrinsic } from '@polkadot/api/types' +import type { Null, Result } from '@polkadot/types' +import type { SpRuntimeDispatchError } from '@polkadot/types/lookup' +import type { IU8a } from '@polkadot/types/types' +import { bufferToU8a, compactAddLength, stringToU8a } from '@polkadot/util' +import type { HexString } from '@polkadot/util/types' + +import { expect } from 'vitest' + +import { + assertExpectedEvents, + createXcmTransactSend, + getXcmRoute, + scheduleInlineCallWithOrigin, + scheduleLookupCallWithOrigin, + type TestConfig, +} from './helpers/index.js' +import type { RootTestTree } from './types.js' + +type SetCodeFn = (code: Uint8Array | HexString) => SubmittableExtrinsic<'promise'> +type AuthorizeUpgradeFn = (codeHash: string | Uint8Array) => SubmittableExtrinsic<'promise'> +type ExpectedEvents = Parameters[1] + +/** + * Runs the authorize upgrade + apply authorized upgrade scenario + * Scenario will fetch WASM from :code storage thus effectively trying to upgrade to the same WASM as currently used + * + * Focus of this test is solely the RU's authorization + application process + * + * Calls are run locally via scheduler to impersonate Root account + * + * via `call` param allows to either use `authorizeUpgrade` or `authorizeUpgradeWithoutChecks` + * + * 1. Fetches current runtime WASM and hashes it. + * 2. Schedules an authorizeUpgrade call as Root using the data from step 1. + * 4. Applies the upgrade with applyAuthorizedUpgrade using Alice account (non-root account). + * 5. Verifies expected events as given by the param `expectedAfterApply` + */ +async function runAuthorizeUpgradeScenario( + client: Client, + testConfig: TestConfig, + params: { + call: AuthorizeUpgradeFn + expectedAfterApply: (hash: IU8a) => ExpectedEvents + }, +) { + const alice = devAccounts.alice + + const currentWasm = bufferToU8a(Buffer.from((await client.chain.head.wasm).slice(2), 'hex')) + const currentWasmHash = client.api.registry.hash(currentWasm) + + const call = params.call(currentWasmHash).method + await scheduleInlineCallWithOrigin(client, call.toHex(), { system: 'Root' }, testConfig.blockProvider) + + await client.dev.newBlock({ count: 1 }) + + assertExpectedEvents(await client.api.query.system.events(), [ + { + type: client.api.events.system.UpgradeAuthorized, + args: { codeHash: currentWasmHash }, + }, + ]) + + const authorizedUpgrade = (await client.api.query.system.authorizedUpgrade()).value + expect(authorizedUpgrade.codeHash.toHex()).toEqual(currentWasmHash.toHex()) + + const applyCall = client.api.tx.system.applyAuthorizedUpgrade(compactAddLength(currentWasm)) + await sendTransaction(applyCall.signAsync(alice)) + + await client.dev.newBlock({ count: 1 }) + + if (client.config.isRelayChain) { + assertExpectedEvents(await client.api.query.system.events(), params.expectedAfterApply(currentWasmHash)) + } else { + const eventsAfterFirstBlock = await client.api.query.system.events() + await client.dev.newBlock({ count: 1 }) + const eventsAfterSecondBlock = await client.api.query.system.events() + assertExpectedEvents( + eventsAfterFirstBlock.concat(eventsAfterSecondBlock), + params.expectedAfterApply(currentWasmHash), + ) + } +} + +/** + * Runs the authorize upgrade + apply authorized upgrade scenario + * Scenario will fetch WASM from :code storage thus effectively trying to upgrade to the same WASM as currently used + * Calls are run via scheduler on Relay using XCM Transact to send the actual call to the destination parachain + * + * Focus of this test is solely the RU's authorization + application process + * + * via `call` param allows to either use `authorizeUpgrade` or `authorizeUpgradeWithoutChecks` + * + * 1. Fetches current runtime WASM and hashes it. + * 2. Creates XCM Transact call with the authorizeUpgrade call as payload using wasm data from step 1 + * 3. Applies the upgrade locally on the destination parachain + * with applyAuthorizedUpgrade using Alice account (non-root account). + * 5. Verifies expected events as given by the param `expectedAfterApply` + */ +async function runAuthorizeUpgradeScenarioViaRemoteScheduler( + governanceClient: Client, + toBeUpgradedClient: Client, + testConfig: TestConfig, + params: { + call: AuthorizeUpgradeFn + expectedAfterApply: (hash: IU8a) => ExpectedEvents + }, +) { + const alice = devAccounts.alice + + const currentWasm = bufferToU8a(Buffer.from((await toBeUpgradedClient.chain.head.wasm).slice(2), 'hex')) + const currentWasmHash = toBeUpgradedClient.api.registry.hash(currentWasm) + + const call = params.call(currentWasmHash).method + + const dest = getXcmRoute(governanceClient.config, toBeUpgradedClient.config) + const xcmTx = createXcmTransactSend(governanceClient, dest, call.toHex(), 'Superuser').method + + await scheduleInlineCallWithOrigin(governanceClient, xcmTx.toHex(), { system: 'Root' }, testConfig.blockProvider) + await governanceClient.dev.newBlock({ count: 1 }) + await toBeUpgradedClient.dev.newBlock({ count: 1 }) + + const authorizedUpgrade = (await toBeUpgradedClient.api.query.system.authorizedUpgrade()).value + expect(authorizedUpgrade.codeHash.toHex()).toEqual(currentWasmHash.toHex()) + + const applyCall = toBeUpgradedClient.api.tx.system.applyAuthorizedUpgrade(compactAddLength(currentWasm)) + await sendTransaction(applyCall.signAsync(alice)) + + await toBeUpgradedClient.dev.newBlock({ count: 1 }) + const eventsAfterFirstBlock = await toBeUpgradedClient.api.query.system.events() + await toBeUpgradedClient.dev.newBlock({ count: 1 }) + const eventsAfterSecondBlock = await toBeUpgradedClient.api.query.system.events() + + assertExpectedEvents(eventsAfterFirstBlock.concat(eventsAfterSecondBlock), params.expectedAfterApply(currentWasmHash)) +} + +/** + * Runs multiple authorizeUpgrade calls to confirm possibility to override previously authorized code + * Calls are run locally via scheduler to impersonate Root account + * + * via `call` param allows to either use `authorizeUpgrade` or `authorizeUpgradeWithoutChecks` + * + * 1. Schedules an authorizeUpgrade call using some hash (exact hash is not important) + * 2. Asserts expected `UpgradeAuthorized` event and `authorizedUpgrade` storage against expected hash + * 3. Schedules another authorizeUpgrade call using some hash different than hash from step 1 + * 4. Asserts expected `UpgradeAuthorized` event and `authorizedUpgrade` storage against expected different hash + */ +async function runAuthorizeUpgradeAllowToOverrideScenario( + client: Client, + testConfig: TestConfig, + params: { + call: AuthorizeUpgradeFn + }, +) { + const authorizeHash = async (someHash) => { + const call = params.call(someHash).method + await scheduleInlineCallWithOrigin(client, call.toHex(), { system: 'Root' }, testConfig.blockProvider) + + await client.dev.newBlock({ count: 1 }) + assertExpectedEvents(await client.api.query.system.events(), [ + { + type: client.api.events.system.UpgradeAuthorized, + args: { codeHash: someHash }, + }, + ]) + + const authorizedUpgrade = (await client.api.query.system.authorizedUpgrade()).value + expect(authorizedUpgrade.codeHash.toHex()).toEqual(someHash.toHex()) + } + + const someHash = client.api.registry.hash(stringToU8a('some data')) + const someOtherHash = client.api.registry.hash(stringToU8a('some other data')) + + await authorizeHash(someHash) // authorize some hash + await authorizeHash(someOtherHash) // then authorize different hash and expect the latter to be set +} + +/** + * Runs multiple authorizeUpgrade calls to confirm possibility to override previously authorized code + * Calls are run via scheduler on Governance Chain using XCM Transact to send the actual call to the destination parachain + * + * Focus of this test is solely the RU's authorization + application process + * + * via `call` param allows to either use `authorizeUpgrade` or `authorizeUpgradeWithoutChecks` + * + * 1. Schedules an authorizeUpgrade call using some hash (exact hash is not important) + * 2. Asserts expected `UpgradeAuthorized` event and `authorizedUpgrade` storage against expected hash + * 3. Schedules another authorizeUpgrade call using some hash different than hash from step 1 + * 4. Asserts expected `UpgradeAuthorized` event and `authorizedUpgrade` storage against expected different hash + */ +async function runAuthorizeUpgradeAllowToOverrideScenarioViaRemoteScheduler( + governanceChain: Client, + toBeUpgradedClient: Client, + testConfig: TestConfig, + params: { + call: AuthorizeUpgradeFn + }, +) { + const authorizeHash = async (someHash) => { + const call = params.call(someHash).method + const dest = getXcmRoute(governanceChain.config, toBeUpgradedClient.config) + const xcmTx = createXcmTransactSend(governanceChain, dest, call.toHex(), 'Superuser').method + await scheduleInlineCallWithOrigin(governanceChain, xcmTx.toHex(), { system: 'Root' }, testConfig.blockProvider) + + await governanceChain.dev.newBlock({ count: 1 }) + await toBeUpgradedClient.dev.newBlock({ count: 1 }) + + assertExpectedEvents(await toBeUpgradedClient.api.query.system.events(), [ + { + type: toBeUpgradedClient.api.events.system.UpgradeAuthorized, + args: { codeHash: someHash }, + }, + ]) + + const authorizedUpgrade = (await toBeUpgradedClient.api.query.system.authorizedUpgrade()).value + expect(authorizedUpgrade.codeHash.toHex()).toEqual(someHash.toHex()) + } + + const someHash = toBeUpgradedClient.api.registry.hash(stringToU8a('some data')) + const someOtherHash = toBeUpgradedClient.api.registry.hash(stringToU8a('some other data')) + + await authorizeHash(someHash) // authorize some hash + await authorizeHash(someOtherHash) // then authorize different hash and expect the latter to be set +} + +/** + * Runs upgrade scenario via direct set_code + * Scenario will fetch WASM from :code storage thus effectively trying to upgrade to the same WASM as currently used + * Calls are run locally via scheduler to impersonate Root account + * + * Focus of this test is to solely verify working of direct runtime upgrade (as opposed to 2-step apply+authorize method) + * + * via `call` param allows to either use `setCode` or `setCodeWithoutChecks` + * + * 1. Fetches current runtime WASM and hashes it. + * 2. Schedules an setCode call as Root using the data from step 1. + * 3. Verifies expected events as given by the param `expectedAfterSchedule` + * 4. Executes remark (could be any other) extrinsic to confirm WASM is still functional after enacted + */ +async function runSetCodeScenario( + client: Client, + testConfig: TestConfig, + params: { + call: SetCodeFn + expectedAfterSchedule: ExpectedEvents + }, +) { + const alice = devAccounts.alice + + // fund Alice (preimage for WASM is expensive) + await client.dev.setStorage({ + System: { + account: [[[alice.address], { providers: 1, data: { free: 100000 * 1e10 } }]], + }, + }) + + const currentWasm = Buffer.from((await client.chain.head.wasm).slice(2), 'hex') + + const call = params.call(compactAddLength(currentWasm)).method + + const preimageTx = client.api.tx.preimage.notePreimage(call.toHex()) + const preimageHash = call.hash + await sendTransaction(preimageTx.signAsync(alice)) + await client.dev.newBlock({ count: 1 }) + + assertExpectedEvents(await client.api.query.system.events(), [ + { type: client.api.events.preimage.Noted, args: { hash_: preimageHash } }, + ]) + + await scheduleLookupCallWithOrigin( + client, + { hash: preimageHash, len: call.encodedLength }, + { system: 'Root' }, + testConfig.blockProvider, + ) + await client.dev.newBlock({ count: 1 }) + + assertExpectedEvents(await client.api.query.system.events(), params.expectedAfterSchedule) + + // sanity: extrinsic still works after (failed/successful) upgrade attempt + const remarkContent = stringToU8a('still working') + const remarkCall = client.api.tx.system.remarkWithEvent(compactAddLength(remarkContent)) + await sendTransaction(remarkCall.signAsync(alice)) + await client.dev.newBlock({ count: 1 }) + + assertExpectedEvents(await client.api.query.system.events(), [ + { + type: client.api.events.system.Remarked, + args: { hash_: client.api.registry.hash(remarkContent) }, + }, + ]) +} + +/** + * Runs upgrade scenario via scheduler on remote chain (that can also act as root) + * Scenario will fetch WASM from :code storage thus effectively trying to upgrade to the same WASM as currently used + * Calls are run via scheduler on chosen chain using XCM Transact to send the actual call to the destination parachain + * + * Focus of this test is to verify that XCM containing WASM blob is too big to be sent between chosen scheduling chain and parachains + * + * via `call` param allows to either use `setCode` or `setCodeWithoutChecks` + * + * 1. Fetches current runtime WASM and hashes it. + * 2. Schedules an setCode call as Root using the data from step 1. + * 3. Verifies expected events as given by the param `expectedAfterSchedule` + */ +async function runSetCodeScenarioViaRemoteScheduler( + governanceClient: Client, + toBeUpgradedClient: Client, + testConfig: TestConfig, + params: { + call: SetCodeFn + expectedAfterSchedule: ExpectedEvents + }, +) { + const alice = devAccounts.alice + + // fund Alice (preimage for WASM is expensive) + await governanceClient.dev.setStorage({ + System: { + account: [[[alice.address], { providers: 1, data: { free: 100000 * 1e10 } }]], + }, + }) + + const currentWasm = Buffer.from((await toBeUpgradedClient.chain.head.wasm).slice(2), 'hex') + + const call = params.call(compactAddLength(currentWasm)).method + + const dest = getXcmRoute(governanceClient.config, toBeUpgradedClient.config) + const xcmTx = createXcmTransactSend(governanceClient, dest, call.toHex(), 'Superuser').method + + const preimageTx = governanceClient.api.tx.preimage.notePreimage(xcmTx.toHex()) + const preimageHash = xcmTx.hash + await sendTransaction(preimageTx.signAsync(alice)) + await governanceClient.dev.newBlock({ count: 1 }) + + assertExpectedEvents(await governanceClient.api.query.system.events(), [ + { type: governanceClient.api.events.preimage.Noted, args: { hash_: preimageHash } }, + ]) + + await scheduleLookupCallWithOrigin( + governanceClient, + { hash: preimageHash, len: xcmTx.encodedLength }, + { system: 'Root' }, + testConfig.blockProvider, + ) + await governanceClient.dev.newBlock({ count: 1 }) + + assertExpectedEvents(await governanceClient.api.query.system.events(), params.expectedAfterSchedule) +} + +/** + * Tests `setCode` flow — ensures runtime upgrade to the same WASM fails. + */ +export async function setCodeTests< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(chain: Chain, testConfig: TestConfig) { + const [client] = await setupNetworks(chain) + return runSetCodeScenario(client, testConfig, { + call: client.api.tx.system.setCode, + expectedAfterSchedule: [ + { + type: client.api.events.scheduler.Dispatched, + args: { + result: (r: Result) => + r.isErr && client.api.errors.system.SpecVersionNeedsToIncrease.is(r.asErr.asModule), + }, // expected failure + }, + ], + }) +} + +/** + * Tests `setCode` flow — ensures runtime upgrade to the same WASM fails due to XCM exceeding message size limits + */ +export async function setCodeViaRemoteSchedulerTests< + TCustomRelay extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, + TCustomPara extends Record | undefined, + TInitStoragesPara extends Record> | undefined, +>( + governanceChain: Chain, + toBeUpgradedChain: Chain, + testConfig: TestConfig, +) { + const [governanceClient, toBeUpgradedClient] = await setupNetworks(governanceChain, toBeUpgradedChain) + return runSetCodeScenarioViaRemoteScheduler(governanceClient, toBeUpgradedClient, testConfig, { + call: toBeUpgradedClient.api.tx.system.setCode, + expectedAfterSchedule: [ + { + type: governanceClient.api.events.scheduler.Dispatched, + args: { + result: (r: Result) => + r.isErr && + (governanceClient.api.errors.xcmPallet || governanceClient.api.errors.polkadotXcm).SendFailure.is( + r.asErr.asModule, + ), + }, // expected failure + }, + ], + }) +} + +/** + * Tests `setCodeWithoutChecks` flow — ensures upgrade to same WASM succeeds. + */ +export async function setCodeWithoutChecksTests< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(chain: Chain, testConfig: TestConfig) { + const [client] = await setupNetworks(chain) + return runSetCodeScenario(client, testConfig, { + call: client.api.tx.system.setCodeWithoutChecks, + expectedAfterSchedule: [ + { + type: client.api.events.scheduler.Dispatched, + args: { result: (r: Result) => r.isOk }, // expected success + }, + { type: client.api.events.system.CodeUpdated }, + ], + }) +} + +/** + * Tests `authorizeUpgrade` flow — upgrade to same WASM should fail validation. + */ +export async function authorizeUpgradeTests< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(chain: Chain, testConfig: TestConfig) { + const [client] = await setupNetworks(chain) + return runAuthorizeUpgradeScenario(client, testConfig, { + call: client.api.tx.system.authorizeUpgrade, + expectedAfterApply: (hash) => [ + { + type: client.api.events.system.RejectedInvalidAuthorizedUpgrade, + args: { + codeHash: hash, + error: (r: SpRuntimeDispatchError) => client.api.errors.system.SpecVersionNeedsToIncrease.is(r.asModule), + }, + }, + ], + }) +} + +/** + * Tests `authorizeUpgradeWithoutChecks` — upgrade to same WASM should succeed. + */ +export async function authorizeUpgradeWithoutChecksTests< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(chain: Chain, testConfig: TestConfig) { + const [client] = await setupNetworks(chain) + + let expectedEvents: ExpectedEvents = [] + if (chain.isRelayChain) { + expectedEvents = [{ type: client.api.events.system.CodeUpdated }] + } else { + expectedEvents = [ + { type: client.api.events.parachainSystem.ValidationFunctionStored }, + { type: client.api.events.parachainSystem.ValidationFunctionApplied }, + { type: client.api.events.system.CodeUpdated }, + ] + } + + return runAuthorizeUpgradeScenario(client, testConfig, { + call: client.api.tx.system.authorizeUpgradeWithoutChecks, + expectedAfterApply: () => expectedEvents, + }) +} + +/** + * Tests `authorizeUpgrade` executed via Governance Chain (XCM Transact) — upgrade to same WASM should fail validation. + */ +export async function authorizeUpgradeViaRemoteSchedulerTests< + TCustomRelay extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, + TCustomPara extends Record | undefined, + TInitStoragesPara extends Record> | undefined, +>( + governanceChain: Chain, + toBeUpgradedChain: Chain, + testConfig: TestConfig, +) { + const [governanceClient, toBeUpgradedClient] = await setupNetworks(governanceChain, toBeUpgradedChain) + return runAuthorizeUpgradeScenarioViaRemoteScheduler(governanceClient, toBeUpgradedClient, testConfig, { + call: toBeUpgradedClient.api.tx.system.authorizeUpgrade, + expectedAfterApply: (hash) => [ + { + type: toBeUpgradedClient.api.events.system.RejectedInvalidAuthorizedUpgrade, + args: { + codeHash: hash, + error: (r: SpRuntimeDispatchError) => + toBeUpgradedClient.api.errors.system.SpecVersionNeedsToIncrease.is(r.asModule), + }, + }, + ], + }) +} + +/** + * Tests `authorizeUpgrade` executed via Governance Chain (XCM Transact) — upgrade to same WASM should succeed. + */ +export async function authorizeUpgradeWithoutChecksViaRemoteSchedulerTests< + TCustomRelay extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, + TCustomPara extends Record | undefined, + TInitStoragesPara extends Record> | undefined, +>( + governanceChain: Chain, + toBeUpgradedChain: Chain, + testConfig: TestConfig, +) { + const [governanceClient, toBeUpgradedClient] = await setupNetworks(governanceChain, toBeUpgradedChain) + + let expectedEvents: ExpectedEvents = [] + if (toBeUpgradedChain.isRelayChain) { + expectedEvents = [{ type: toBeUpgradedClient.api.events.system.CodeUpdated }] + } else { + expectedEvents = [ + { type: toBeUpgradedClient.api.events.parachainSystem.ValidationFunctionStored }, + { type: toBeUpgradedClient.api.events.parachainSystem.ValidationFunctionApplied }, + { type: toBeUpgradedClient.api.events.system.CodeUpdated }, + ] + } + return runAuthorizeUpgradeScenarioViaRemoteScheduler(governanceClient, toBeUpgradedClient, testConfig, { + call: toBeUpgradedClient.api.tx.system.authorizeUpgradeWithoutChecks, + expectedAfterApply: () => expectedEvents, + }) +} + +/** + * Tests`authorizeUpgrade` ability to override previously authorized upgrade executed via Relay Chain (XCM Transact) + */ +export async function authorizeUpgradeAllowToOverrideViaRemoteSchedulerTests< + TCustomRelay extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, + TCustomPara extends Record | undefined, + TInitStoragesPara extends Record> | undefined, +>( + governanceChain: Chain, + toBeUpgradedChain: Chain, + testConfig: TestConfig, +) { + const [governanceClient, toBeUpgradedClient] = await setupNetworks(governanceChain, toBeUpgradedChain) + return runAuthorizeUpgradeAllowToOverrideScenarioViaRemoteScheduler( + governanceClient, + toBeUpgradedClient, + testConfig, + { + call: toBeUpgradedClient.api.tx.system.authorizeUpgrade, + }, + ) +} + +/** + * Tests `authorizeUpgradeWithoutChecks` ability to override previously authorized upgrade executed via Governance Chain (XCM Transact) + */ +export async function authorizeUpgradeWithoutChecksAllowToOverrideViaRemoteSchedulerTests< + TCustomRelay extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, + TCustomPara extends Record | undefined, + TInitStoragesPara extends Record> | undefined, +>( + governanceChain: Chain, + toBeUpgradedChain: Chain, + testConfig: TestConfig, +) { + const [governanceClient, toBeUpgradedClient] = await setupNetworks(governanceChain, toBeUpgradedChain) + return runAuthorizeUpgradeAllowToOverrideScenarioViaRemoteScheduler( + governanceClient, + toBeUpgradedClient, + testConfig, + { + call: toBeUpgradedClient.api.tx.system.authorizeUpgradeWithoutChecks, + }, + ) +} + +/** + * Tests `authorizeUpgrade` ability to override previously authorized upgrade. + */ +export async function authorizeUpgradeAllowToOverride< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(chain: Chain, testConfig: TestConfig) { + const [client] = await setupNetworks(chain) + return runAuthorizeUpgradeAllowToOverrideScenario(client, testConfig, { + call: client.api.tx.system.authorizeUpgrade, + }) +} + +/** + * Tests `authorizeUpgradeWithoutChecks` ability to override previously authorized upgrade. + */ +export async function authorizeUpgradeWithoutChecksAllowToOverride< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(chain: Chain, testConfig: TestConfig) { + const [client] = await setupNetworks(chain) + return runAuthorizeUpgradeAllowToOverrideScenario(client, testConfig, { + call: client.api.tx.system.authorizeUpgradeWithoutChecks, + }) +} + +/** + * System upgrade scenarios for relay chains + * + * To be used by chains with local Scheduler and Preimage pallets available + */ +export function systemE2ETests< + TCustom extends Record | undefined, + TInitStorages extends Record> | undefined, +>(chain: Chain, testConfig: TestConfig): RootTestTree { + return { + kind: 'describe', + label: testConfig.testSuiteName, + children: [ + { + kind: 'test', + label: 'set_code doesnt allow upgrade to the same wasm', + testFn: async () => await setCodeTests(chain, testConfig), + }, + { + kind: 'test', + label: 'set_code_without_checks allows upgrade to the same wasm', + testFn: async () => await setCodeWithoutChecksTests(chain, testConfig), + }, + { + kind: 'test', + label: 'authorize_upgrade_without_checks allows upgrade to the same wasm', + testFn: async () => await authorizeUpgradeWithoutChecksTests(chain, testConfig), + }, + { + kind: 'test', + label: 'authorize_upgrade doesnt allow upgrade to the same wasm', + testFn: async () => await authorizeUpgradeTests(chain, testConfig), + }, + { + kind: 'test', + label: 'authorize_upgrade allows to override previously authorized one', + testFn: async () => await authorizeUpgradeAllowToOverride(chain, testConfig), + }, + { + kind: 'test', + label: 'authorize_upgrade_without_checks allows to override previously authorized one', + testFn: async () => await authorizeUpgradeWithoutChecksAllowToOverride(chain, testConfig), + }, + ], + } +} + +/** + * Set of system upgrade scenarios prepared for parachains + * + * To be used by chains with local Scheduler and Preimage pallets available + */ +export function systemE2ETestsForParaWithScheduler< + TCustom extends Record | undefined, + TInitStorages extends Record> | undefined, +>(chain: Chain, testConfig: TestConfig): RootTestTree { + return { + kind: 'describe', + label: testConfig.testSuiteName, + children: [ + // TODO: Commented out tests dont work, not sure if thats expected or not + // (scheduler.Dispatched error: parachainSystem.ValidationDataNotAvailable) + // { + // kind: 'test', + // label: 'set_code doesnt allow upgrade to the same wasm', + // testFn: async () => await setCodeTests(chain, testConfig), + // }, + // { + // kind: 'test', + // label: 'set_code_without_checks allows upgrade to the same wasm', + // testFn: async () => await setCodeWithoutChecksTests(chain, testConfig), + // }, + { + kind: 'test', + label: 'authorize_upgrade_without_checks allows upgrade to the same wasm', + testFn: async () => await authorizeUpgradeWithoutChecksTests(chain, testConfig), + }, + { + kind: 'test', + label: 'authorize_upgrade doesnt allow upgrade to the same wasm', + testFn: async () => await authorizeUpgradeTests(chain, testConfig), + }, + { + kind: 'test', + label: 'authorize_upgrade allows to override previously authorized one', + testFn: async () => await authorizeUpgradeAllowToOverride(chain, testConfig), + }, + { + kind: 'test', + label: 'authorize_upgrade_without_checks allows to override previously authorized one', + testFn: async () => await authorizeUpgradeWithoutChecksAllowToOverride(chain, testConfig), + }, + ], + } +} + +/** + * System upgrade scenarios using other chain's scheduler + * + * To be used by chains that doesn't provide Scheduler or Preimage pallet locally + * and trust Governance Chain to execute calls as Root origin + */ +export function systemE2ETestsViaRemoteScheduler< + TCustomRelay extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, + TCustomPara extends Record | undefined, + TInitStoragesPara extends Record> | undefined, +>( + governanceChain: Chain, + toBeUpgradedChain: Chain, + testConfig: TestConfig, +): RootTestTree { + return { + kind: 'describe', + label: testConfig.testSuiteName, + children: [ + { + kind: 'test', + label: `authorize_upgrade doesnt allow upgrade to the same wasm (via ${governanceChain.name})`, + testFn: async () => + await authorizeUpgradeViaRemoteSchedulerTests(governanceChain, toBeUpgradedChain, testConfig), + }, + { + kind: 'test', + label: `authorize_upgrade_without_checks allows upgrade to the same wasm (via ${governanceChain.name})`, + testFn: async () => + await authorizeUpgradeWithoutChecksViaRemoteSchedulerTests(governanceChain, toBeUpgradedChain, testConfig), + }, + { + kind: 'test', + label: `authorize_upgrade allows to override previously authorized one (via ${governanceChain.name})`, + testFn: async () => + await authorizeUpgradeAllowToOverrideViaRemoteSchedulerTests(governanceChain, toBeUpgradedChain, testConfig), + }, + { + kind: 'test', + label: `authorize_upgrade_without_checks allows to override previously authorized one (via ${governanceChain.name})`, + testFn: async () => + await authorizeUpgradeWithoutChecksAllowToOverrideViaRemoteSchedulerTests( + governanceChain, + toBeUpgradedChain, + testConfig, + ), + }, + { + kind: 'test', + label: `expecting set_code to fail as sending WASM from relay to para should exceed XCM limits (via ${governanceChain.name})`, + testFn: async () => await setCodeViaRemoteSchedulerTests(governanceChain, toBeUpgradedChain, testConfig), + }, + ], + } +} diff --git a/packages/shared/src/upgrade.ts b/packages/shared/src/upgrade.ts index 04fe2aa99..a7ce6ef34 100644 --- a/packages/shared/src/upgrade.ts +++ b/packages/shared/src/upgrade.ts @@ -1,38 +1,19 @@ import { sendTransaction } from '@acala-network/chopsticks-testing' -import { type Chain, type Client, defaultAccounts } from '@e2e-test/networks' +import { type Client, defaultAccounts } from '@e2e-test/networks' import { sendWhitelistCallViaXcmTransact } from '@e2e-test/shared' import type { HexString } from '@polkadot/util/types' import { assert } from 'vitest' -import { checkEvents, checkSystemEvents, createXcmTransactSend, scheduleInlineCallWithOrigin } from './helpers/index.js' - -/** - * Computes the XCM `MultiLocation` route from a source chain to a destination chain. - * - * @param from - The source chain (the chain initiating the XCM message). - * @param to - The destination chain (the chain intended to receive and execute the XCM message). - */ -function getXcmRoute(from: Chain, to: Chain) { - let parents: number - let interior: any - - if (from.isRelayChain) { - parents = 0 - } else { - parents = 1 - } - - if (to.isRelayChain) { - interior = 'Here' - } else { - interior = { X1: [{ Parachain: to.paraId }] } - } - - return { parents, interior } -} +import { + checkEvents, + checkSystemEvents, + createXcmTransactSend, + getXcmRoute, + scheduleInlineCallWithOrigin, +} from './helpers/index.js' /** * Constructs an XCM forceBatch transaction that authorizes a runtime upgrade on a destination chain.