diff --git a/packages/kusama/src/__snapshots__/assetHubKusama.accounts.e2e.test.ts.snap b/packages/kusama/src/__snapshots__/assetHubKusama.accounts.e2e.test.ts.snap index b6e6fdb0f..6e5e679e1 100644 --- a/packages/kusama/src/__snapshots__/assetHubKusama.accounts.e2e.test.ts.snap +++ b/packages/kusama/src/__snapshots__/assetHubKusama.accounts.e2e.test.ts.snap @@ -191,6 +191,8 @@ exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction erro ] `; +exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via manual reserve and manual lock, triggered via referendum submission > liquidity restricted action events 1`] = `[]`; + exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via manual reserve and manual lock, triggered via referendum submission > liquidity restriction test skipped 1`] = `"Skipping test - required pallets not available: deposit=referendum submission"`; exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via manual reserve and vested transfer, triggered via multisig creation > liquidity restriction test skipped 1`] = `"Skipping test - required pallets not available: lock=vested transfer"`; @@ -199,10 +201,16 @@ exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction erro exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via manual reserve and vested transfer, triggered via referendum submission > liquidity restriction test skipped 1`] = `"Skipping test - required pallets not available: lock=vested transfer, deposit=referendum submission"`; +exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via nomination pool and manual lock, triggered via multisig creation > liquidity restricted action events 1`] = `[]`; + exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via nomination pool and manual lock, triggered via multisig creation > liquidity restriction test skipped 1`] = `"Skipping test - required pallets not available: reserve=nomination pool"`; +exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via nomination pool and manual lock, triggered via proxy addition > liquidity restricted action events 1`] = `[]`; + exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via nomination pool and manual lock, triggered via proxy addition > liquidity restriction test skipped 1`] = `"Skipping test - required pallets not available: reserve=nomination pool"`; +exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via nomination pool and manual lock, triggered via referendum submission > liquidity restricted action events 1`] = `[]`; + exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via nomination pool and manual lock, triggered via referendum submission > liquidity restriction test skipped 1`] = `"Skipping test - required pallets not available: reserve=nomination pool, deposit=referendum submission"`; exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via nomination pool and vested transfer, triggered via multisig creation > liquidity restriction test skipped 1`] = `"Skipping test - required pallets not available: reserve=nomination pool, lock=vested transfer"`; @@ -211,10 +219,29 @@ exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction erro exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via nomination pool and vested transfer, triggered via referendum submission > liquidity restriction test skipped 1`] = `"Skipping test - required pallets not available: reserve=nomination pool, lock=vested transfer, deposit=referendum submission"`; +exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via staking bond and manual lock, triggered via multisig creation > deposit action success events 1`] = ` +[ + { + "data": { + "amount": "(rounded 6700000000)", + "who": "D8ew585BL5H1ALhn4kmJoxhgqcgKDZLPc6xunJNv4mmrBns", + }, + "method": "Reserved", + "section": "balances", + }, +] +`; + +exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via staking bond and manual lock, triggered via multisig creation > liquidity restricted action events 1`] = `[]`; + exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via staking bond and manual lock, triggered via multisig creation > liquidity restriction test skipped 1`] = `"Skipping test - required pallets not available: reserve=staking bond"`; +exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via staking bond and manual lock, triggered via proxy addition > liquidity restricted action events 1`] = `[]`; + exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via staking bond and manual lock, triggered via proxy addition > liquidity restriction test skipped 1`] = `"Skipping test - required pallets not available: reserve=staking bond"`; +exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via staking bond and manual lock, triggered via referendum submission > liquidity restricted action events 1`] = `[]`; + exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via staking bond and manual lock, triggered via referendum submission > liquidity restriction test skipped 1`] = `"Skipping test - required pallets not available: reserve=staking bond, deposit=referendum submission"`; exports[`Kusama Asset Hub Accounts > currency tests > liquidity restriction error: funds locked via staking bond and vested transfer, triggered via multisig creation > liquidity restriction test skipped 1`] = `"Skipping test - required pallets not available: reserve=staking bond, lock=vested transfer"`; diff --git a/packages/kusama/src/__snapshots__/coretimeKusama.accounts.e2e.test.ts.snap b/packages/kusama/src/__snapshots__/coretimeKusama.accounts.e2e.test.ts.snap index 90ef669e4..f27a45a6e 100644 --- a/packages/kusama/src/__snapshots__/coretimeKusama.accounts.e2e.test.ts.snap +++ b/packages/kusama/src/__snapshots__/coretimeKusama.accounts.e2e.test.ts.snap @@ -141,6 +141,8 @@ exports[`Kusama Coretime Accounts > \`transfer_all\` > transfer all with keepAli ] `; +exports[`Kusama Coretime Accounts > currency tests > liquidity restriction error: funds locked via manual reserve and manual lock, triggered via multisig creation > deposit action success events 1`] = `[]`; + exports[`Kusama Coretime Accounts > currency tests > liquidity restriction error: funds locked via manual reserve and manual lock, triggered via multisig creation > liquidity restricted action events 1`] = ` [ { @@ -166,6 +168,8 @@ exports[`Kusama Coretime Accounts > currency tests > liquidity restriction error ] `; +exports[`Kusama Coretime Accounts > currency tests > liquidity restriction error: funds locked via manual reserve and manual lock, triggered via proxy addition > deposit action success events 1`] = `[]`; + exports[`Kusama Coretime Accounts > currency tests > liquidity restriction error: funds locked via manual reserve and manual lock, triggered via proxy addition > liquidity restricted action events 1`] = ` [ { diff --git a/packages/kusama/src/assetHubKusama.accounts.e2e.test.ts b/packages/kusama/src/assetHubKusama.accounts.e2e.test.ts index e6834fa80..c754e1f82 100644 --- a/packages/kusama/src/assetHubKusama.accounts.e2e.test.ts +++ b/packages/kusama/src/assetHubKusama.accounts.e2e.test.ts @@ -1,16 +1,32 @@ import { assetHubKusama, kusama } from '@e2e-test/networks/chains' -import { accountsE2ETests, registerTestTree } from '@e2e-test/shared' +import { + accountsE2ETests, + createAccountsConfig, + createDefaultDepositActions, + createDefaultLockActions, + createDefaultReserveActions, + registerTestTree, + type TestConfig, +} from '@e2e-test/shared' -registerTestTree( - accountsE2ETests( - assetHubKusama, - { - testSuiteName: 'Kusama Asset Hub Accounts', - addressEncoding: 2, - blockProvider: 'NonLocal', - asyncBacking: 'Enabled', - chainEd: 'LowEd', - }, - kusama, - ), -) +const testCfg: TestConfig = { + testSuiteName: 'Kusama Asset Hub Accounts', + addressEncoding: 2, + blockProvider: 'NonLocal', + asyncBacking: 'Enabled', + chainEd: 'LowEd', +} + +// When testing liquidity restrictions on Asset Hubs, to simulate frozen funds, vesting is skipped due to AHM. +const lockActions = createDefaultLockActions().filter((action) => !action.name.includes('vest')) + +const accountsCfg = createAccountsConfig({ + relayChain: kusama, + actions: { + reserveActions: createDefaultReserveActions(), + lockActions, + depositActions: createDefaultDepositActions(), + }, +}) + +registerTestTree(accountsE2ETests(assetHubKusama, testCfg, accountsCfg)) diff --git a/packages/kusama/src/bridgeHubKusama.accounts.e2e.test.ts b/packages/kusama/src/bridgeHubKusama.accounts.e2e.test.ts index 871aeb1aa..89d1b76d1 100644 --- a/packages/kusama/src/bridgeHubKusama.accounts.e2e.test.ts +++ b/packages/kusama/src/bridgeHubKusama.accounts.e2e.test.ts @@ -1,15 +1,15 @@ import { bridgeHubKusama, kusama } from '@e2e-test/networks/chains' -import { accountsE2ETests, registerTestTree } from '@e2e-test/shared' +import { accountsE2ETests, createAccountsConfig, registerTestTree, type TestConfig } from '@e2e-test/shared' -registerTestTree( - accountsE2ETests( - bridgeHubKusama, - { - testSuiteName: 'Kusama Bridge Hub Accounts', - addressEncoding: 2, - blockProvider: 'Local', - chainEd: 'LowEd', - }, - kusama, - ), -) +const testCfg: TestConfig = { + testSuiteName: 'Kusama Bridge Hub Accounts', + addressEncoding: 2, + blockProvider: 'Local', + chainEd: 'LowEd', +} + +const accountsCfg = createAccountsConfig({ + relayChain: kusama, +}) + +registerTestTree(accountsE2ETests(bridgeHubKusama, testCfg, accountsCfg)) diff --git a/packages/kusama/src/coretimeKusama.accounts.e2e.test.ts b/packages/kusama/src/coretimeKusama.accounts.e2e.test.ts index 269414a4d..589740ae5 100644 --- a/packages/kusama/src/coretimeKusama.accounts.e2e.test.ts +++ b/packages/kusama/src/coretimeKusama.accounts.e2e.test.ts @@ -1,15 +1,15 @@ import { coretimeKusama, kusama } from '@e2e-test/networks/chains' -import { accountsE2ETests, registerTestTree } from '@e2e-test/shared' +import { accountsE2ETests, createAccountsConfig, registerTestTree, type TestConfig } from '@e2e-test/shared' -registerTestTree( - accountsE2ETests( - coretimeKusama, - { - testSuiteName: 'Kusama Coretime Accounts', - addressEncoding: 2, - blockProvider: 'Local', - chainEd: 'LowEd', - }, - kusama, - ), -) +const testCfg: TestConfig = { + testSuiteName: 'Kusama Coretime Accounts', + addressEncoding: 2, + blockProvider: 'Local', + chainEd: 'LowEd', +} + +const accountsCfg = createAccountsConfig({ + relayChain: kusama, +}) + +registerTestTree(accountsE2ETests(coretimeKusama, testCfg, accountsCfg)) diff --git a/packages/kusama/src/peopleKusama.accounts.e2e.test.ts b/packages/kusama/src/peopleKusama.accounts.e2e.test.ts index 2a57120a9..1598a3c90 100644 --- a/packages/kusama/src/peopleKusama.accounts.e2e.test.ts +++ b/packages/kusama/src/peopleKusama.accounts.e2e.test.ts @@ -1,15 +1,15 @@ import { kusama, peopleKusama } from '@e2e-test/networks/chains' -import { accountsE2ETests, registerTestTree } from '@e2e-test/shared' +import { accountsE2ETests, createAccountsConfig, registerTestTree, type TestConfig } from '@e2e-test/shared' -registerTestTree( - accountsE2ETests( - peopleKusama, - { - testSuiteName: 'Kusama People Chain Accounts', - addressEncoding: 2, - blockProvider: 'Local', - chainEd: 'LowEd', - }, - kusama, - ), -) +const testCfg: TestConfig = { + testSuiteName: 'Kusama People Chain Accounts', + addressEncoding: 2, + blockProvider: 'Local', + chainEd: 'LowEd', +} + +const accountsCfg = createAccountsConfig({ + relayChain: kusama, +}) + +registerTestTree(accountsE2ETests(peopleKusama, testCfg, accountsCfg)) diff --git a/packages/polkadot/src/assetHubPolkadot.accounts.e2e.test.ts b/packages/polkadot/src/assetHubPolkadot.accounts.e2e.test.ts index 9a0d4291e..067b1bcad 100644 --- a/packages/polkadot/src/assetHubPolkadot.accounts.e2e.test.ts +++ b/packages/polkadot/src/assetHubPolkadot.accounts.e2e.test.ts @@ -1,16 +1,32 @@ import { assetHubPolkadot, polkadot } from '@e2e-test/networks/chains' -import { accountsE2ETests, registerTestTree } from '@e2e-test/shared' +import { + accountsE2ETests, + createAccountsConfig, + createDefaultDepositActions, + createDefaultLockActions, + createDefaultReserveActions, + registerTestTree, + type TestConfig, +} from '@e2e-test/shared' -registerTestTree( - accountsE2ETests( - assetHubPolkadot, - { - testSuiteName: 'Polkadot Asset Hub Accounts', - addressEncoding: 0, - blockProvider: 'NonLocal', - asyncBacking: 'Enabled', - chainEd: 'Normal', - }, - polkadot, - ), -) +const testCfg: TestConfig = { + testSuiteName: 'Polkadot Asset Hub Accounts', + addressEncoding: 0, + blockProvider: 'NonLocal', + asyncBacking: 'Enabled', + chainEd: 'Normal', +} + +// When testing liquidity restrictions on Asset Hubs, to simulate frozen funds, vesting is skipped due to AHM. +const lockActions = createDefaultLockActions().filter((action) => !action.name.includes('vest')) + +const accountsCfg = createAccountsConfig({ + relayChain: polkadot, + actions: { + reserveActions: createDefaultReserveActions(), + lockActions, + depositActions: createDefaultDepositActions(), + }, +}) + +registerTestTree(accountsE2ETests(assetHubPolkadot, testCfg, accountsCfg)) diff --git a/packages/polkadot/src/bridgeHubPolkadot.accounts.e2e.test.ts b/packages/polkadot/src/bridgeHubPolkadot.accounts.e2e.test.ts index 207518b23..6e85af8a4 100644 --- a/packages/polkadot/src/bridgeHubPolkadot.accounts.e2e.test.ts +++ b/packages/polkadot/src/bridgeHubPolkadot.accounts.e2e.test.ts @@ -1,15 +1,15 @@ import { bridgeHubPolkadot, polkadot } from '@e2e-test/networks/chains' -import { accountsE2ETests, registerTestTree } from '@e2e-test/shared' +import { accountsE2ETests, createAccountsConfig, registerTestTree, type TestConfig } from '@e2e-test/shared' -registerTestTree( - accountsE2ETests( - bridgeHubPolkadot, - { - testSuiteName: 'Polkadot Bridge Hub Accounts', - blockProvider: 'Local', - addressEncoding: 0, - chainEd: 'Normal', - }, - polkadot, - ), -) +const testCfg: TestConfig = { + testSuiteName: 'Polkadot Bridge Hub Accounts', + blockProvider: 'Local', + addressEncoding: 0, + chainEd: 'Normal', +} + +const accountsCfg = createAccountsConfig({ + relayChain: polkadot, +}) + +registerTestTree(accountsE2ETests(bridgeHubPolkadot, testCfg, accountsCfg)) diff --git a/packages/polkadot/src/coretimePolkadot.accounts.e2e.test.ts b/packages/polkadot/src/coretimePolkadot.accounts.e2e.test.ts index 3421c18da..a826fa1e9 100644 --- a/packages/polkadot/src/coretimePolkadot.accounts.e2e.test.ts +++ b/packages/polkadot/src/coretimePolkadot.accounts.e2e.test.ts @@ -1,16 +1,16 @@ import { coretimePolkadot, polkadot } from '@e2e-test/networks/chains' -import { accountsE2ETests, registerTestTree } from '@e2e-test/shared' +import { accountsE2ETests, createAccountsConfig, registerTestTree, type TestConfig } from '@e2e-test/shared' -registerTestTree( - accountsE2ETests( - coretimePolkadot, - { - testSuiteName: 'Polkadot Coretime Accounts', - addressEncoding: 0, - blockProvider: 'Local', - asyncBacking: 'Enabled', - chainEd: 'Normal', - }, - polkadot, - ), -) +const testCfg: TestConfig = { + testSuiteName: 'Polkadot Coretime Accounts', + addressEncoding: 0, + blockProvider: 'Local', + asyncBacking: 'Enabled', + chainEd: 'Normal', +} + +const accountsCfg = createAccountsConfig({ + relayChain: polkadot, +}) + +registerTestTree(accountsE2ETests(coretimePolkadot, testCfg, accountsCfg)) diff --git a/packages/polkadot/src/peoplePolkadot.accounts.e2e.test.ts b/packages/polkadot/src/peoplePolkadot.accounts.e2e.test.ts index 6006f3b85..85159bfc2 100644 --- a/packages/polkadot/src/peoplePolkadot.accounts.e2e.test.ts +++ b/packages/polkadot/src/peoplePolkadot.accounts.e2e.test.ts @@ -1,15 +1,15 @@ import { peoplePolkadot, polkadot } from '@e2e-test/networks/chains' -import { accountsE2ETests, registerTestTree } from '@e2e-test/shared' +import { accountsE2ETests, createAccountsConfig, registerTestTree, type TestConfig } from '@e2e-test/shared' -registerTestTree( - accountsE2ETests( - peoplePolkadot, - { - testSuiteName: 'Polkadot People Chain Accounts', - addressEncoding: 0, - blockProvider: 'Local', - chainEd: 'Normal', - }, - polkadot, - ), -) +const testCfg: TestConfig = { + testSuiteName: 'Polkadot People Chain Accounts', + addressEncoding: 0, + blockProvider: 'Local', + chainEd: 'Normal', +} + +const accountsCfg = createAccountsConfig({ + relayChain: polkadot, +}) + +registerTestTree(accountsE2ETests(peoplePolkadot, testCfg, accountsCfg)) diff --git a/packages/shared/src/accounts.ts b/packages/shared/src/accounts.ts index bd4adf1c9..698015df3 100644 --- a/packages/shared/src/accounts.ts +++ b/packages/shared/src/accounts.ts @@ -189,11 +189,12 @@ interface DepositAction< } /** - * Define the list of reserve actions to be used in the liquidity restriction tests. + * Create default reserve actions to be used in the liquidity restriction tests. * * Recall that if a network does not support one of these, it'll be skipped when generating the test cases. + * Networks can still override these by providing custom actions in their config. */ -function createReserveActions< +export function createDefaultReserveActions< TCustom extends Record, TInitStorages extends Record>, >(): ReserveAction[] { @@ -273,7 +274,7 @@ function createReserveActions< * * Recall that in the case a network does not support one of these, it'll be skipped when generating the test cases. */ -function createLockActions< +export function createDefaultLockActions< TCustom extends Record, TInitStorages extends Record>, >(): LockAction[] { @@ -293,12 +294,7 @@ function createLockActions< }) await sendTransaction(vestedTransferTx.signAsync(alice)) }, - isAvailable: (client) => { - // Vesting is filtered on Asset Hubs while the AHM is pending. - const chainName = client.config.name.toLowerCase() - if (chainName.includes('assethub')) return false - return !!client.api.tx.vesting - }, + isAvailable: (client) => !!client.api.tx.vesting, }, // This action manually sets storage to simulate an existing lock. // Helpful on networks where vesting is not available i.e. most of them. @@ -344,10 +340,12 @@ function createLockActions< * * Recall that if a network does not support one of these, it'll be skipped when generating the test cases. * - * On every network where this error is raised, proxy and multisig are available, so the test is guaranteed to run + * On almost every network, proxy and/or multisig are available, so the test is guaranteed to run * at least once each network. + * + * Even still, networks can override these action by providing custom actions in their config. */ -function createDepositActions< +export function createDefaultDepositActions< TCustom extends Record, TInitStorages extends Record>, >(): DepositAction[] { @@ -375,7 +373,7 @@ function createDepositActions< calculateDeposit: async (client) => { const depositBase = client.api.consts.multisig.depositBase.toBigInt() const depositFactor = client.api.consts.multisig.depositFactor.toBigInt() - return depositBase + depositFactor * 1n + return depositBase + depositFactor * 2n }, isAvailable: (client) => !!client.api.tx.multisig, }, @@ -467,6 +465,67 @@ async function transferInsufficientFundsTest< expect(aliceAccount.data.free.toBigInt()).toBe(totalBalance - txPaymentEventData.actualFee.toBigInt()) } +/** + * Expectation for the result of a liquidity restriction test. + * See https://github.com/open-web3-stack/polkadot-ecosystem-tests/issues/417. + * + * - If `success`, the running chain's runtime has been updated to include a fix to the + * `balances.LiquidityRestrictions` error some actions were incorrectly raising. + * Liqudity restriction tests should thus pass. + * - If `failure`, the running chain's runtime has not been updated yet, and the tests should expect + * the reserve-creating action they're using to fail. + */ +export type LiqRestrTestResExpectation = 'failure' | 'success' + +export interface AccountsTestConfig< + TCustom extends Record, + TInitStoragesBase extends Record>, + TInitStoragesRelay extends Record>, +> { + /** Expected behavior for liquidity restriction tests */ + expectation: LiqRestrTestResExpectation + /** Optional relay chain for XCM-based operations */ + relayChain?: Chain + /** Custom action lists - if not provided, defaults will be used */ + actions?: { + reserveActions: ReserveAction[] + lockActions: LockAction[] + depositActions: DepositAction[] + } +} + +/** + * Create default accounts test configuration + */ +export function createAccountsConfig< + TCustom extends Record, + TInitStoragesBase extends Record>, + TInitStoragesRelay extends Record>, +>( + overrides?: Partial>, +): AccountsTestConfig { + return { + ...defaultAccountsTestConfig(), + ...overrides, + } +} + +/** + * Default accounts E2E test configuration. + */ +const defaultAccountsTestConfig = < + TCustom extends Record, + TInitStoragesBase extends Record>, + TInitStoragesRelay extends Record>, +>(): AccountsTestConfig => ({ + expectation: 'failure', + actions: { + reserveActions: createDefaultReserveActions(), + lockActions: createDefaultLockActions(), + depositActions: createDefaultDepositActions(), + }, +}) + /// ----- /// Tests /// ----- @@ -840,7 +899,7 @@ async function transferAllowDeathWithReserveTest< expect(await isAccountReaped(client, bob.address)).toBe(true) // Create a reserve action - use the first available one - const reserveActions = createReserveActions() + const reserveActions = createDefaultReserveActions() const availableReserveAction = reserveActions.find((action) => action.isAvailable(client)) if (!availableReserveAction) { @@ -1438,7 +1497,7 @@ async function forceTransferWithReserveTest< expect(await isAccountReaped(baseClient, bob.address)).toBe(true) // Create a reserve action - use the first available one - const reserveActions = createReserveActions() + const reserveActions = createDefaultReserveActions() const availableReserveAction = reserveActions.find((action) => action.isAvailable(baseClient)) if (!availableReserveAction) { @@ -1892,7 +1951,7 @@ async function transferAllWithReserveTest< expect(await isAccountReaped(client, bob.address)).toBe(true) // Create a reserve action - use the first available one - const reserveActions = createReserveActions() + const reserveActions = createDefaultReserveActions() const availableReserveAction = reserveActions.find((action) => action.isAvailable(client)) if (!availableReserveAction) { @@ -3892,17 +3951,21 @@ async function burnDoubleAttemptTest< * 3. another action that internally uses `Currency::reserve` to reserve funds not exceeding the remaining free balance * * These actions (and tests) are generated at the test-tree level, so each network will have a different set of test - * cases, depending on the pallets it has available. - * See the {@link DepositAction}, {@link ReserveAction}, and {@link LockAction} interfaces for details. + * cases, depending on the pallets it has available, and whether it's been updated. + * See the {@link DepositAction}, {@link ReserveAction}, and {@link LockAction} interfaces for more. * * Overall test structure: * - * 1. Credits an account with 1_000_000 ED - * 2. Executes the provided reserve action for 900_000 ED - * 3. Executes the provided lock action for 900_000 ED - * 4. Tries to execute the provided deposit action - * 5. Checks that `balances.LiquidityRestrictions` is raised - * 6. Verify that the account has, in fact, funds to perform the operation + * 1. Credit an account with 1_000_000 ED + * 2. Execute the provided reserve action for 900_000 ED + * 3. Execute the provided lock action for 900_000 ED + * 4. Try to execute the provided deposit action + * Depending on whether the runtime has been upstreamed a fix: + * 5. Check that the transaction failed with the appropriate liquidity restriction error + * 6. Verify that the account did, in fact, have enough funds to perform the operation + * or + * 5. Check that the transaction succeeded + * 6. Verify that the deposit action placed the new expected reserve */ async function testLiquidityRestrictionForAction< TCustom extends Record, @@ -3913,6 +3976,7 @@ async function testLiquidityRestrictionForAction< reserveAction: ReserveAction, lockAction: LockAction, depositAction: DepositAction, + expectation: LiqRestrTestResExpectation, ) { const [client] = await setupNetworks(chain) @@ -3965,7 +4029,7 @@ async function testLiquidityRestrictionForAction< await updateCumulativeFees(client.api, cumulativeFees, testConfig.addressEncoding) - // Step 4: Try to execute the deposit action - this should fail due to liquidity restrictions + // Step 4: Try to execute the deposit action const actionTx = await depositAction.createTransaction(client) const actionEvents = await sendTransaction(actionTx.signAsync(alice)) @@ -3974,39 +4038,79 @@ async function testLiquidityRestrictionForAction< await updateCumulativeFees(client.api, cumulativeFees, testConfig.addressEncoding) - // Step 5: Check that the transaction failed with the appropriate error + // Step 5: Check the result of the transation - await checkEvents(actionEvents, { section: 'system', method: 'ExtrinsicFailed' }).toMatchSnapshot( - 'liquidity restricted action events', - ) + // Step 6: Verify account state post action: in case of success, check new balances. - const finalEvents = await client.api.query.system.events() - const failedEvent = finalEvents.find((record) => { - const { event } = record - return event.section === 'system' && event.method === 'ExtrinsicFailed' - }) + // Reminder: If the chain has not been upgraded, expect the deposit action to fail, and verify accordingly. + // If it has, the action should succeed. + match(expectation) + .with('failure', async () => { + // Step 5 - expect(failedEvent).toBeDefined() - assert(client.api.events.system.ExtrinsicFailed.is(failedEvent!.event)) - const dispatchError = failedEvent!.event.data.dispatchError + await checkEvents(actionEvents, { section: 'system', method: 'ExtrinsicFailed' }).toMatchSnapshot( + 'liquidity restricted action events', + ) - assert(dispatchError.isModule) - const moduleError = dispatchError.asModule - expect(client.api.errors.balances.LiquidityRestrictions.is(moduleError)).toBe(true) + const finalEvents = await client.api.query.system.events() + const failedEvent = finalEvents.find((record) => { + const { event } = record + return event.section === 'system' && event.method === 'ExtrinsicFailed' + }) - // Step 6: Verify account state should have allowed the operation which just failed + expect(failedEvent).toBeDefined() + assert(client.api.events.system.ExtrinsicFailed.is(failedEvent!.event)) + const dispatchError = failedEvent!.event.data.dispatchError - const account = await client.api.query.system.account(alice.address) - const actionDeposit = await depositAction.calculateDeposit(client) + assert(dispatchError.isModule) + const moduleError = dispatchError.asModule + expect(client.api.errors.balances.LiquidityRestrictions.is(moduleError)).toBe(true) - expect(account.data.free.toBigInt()).toBe( - totalBalance - lockAmount - cumulativeFees.get(encodeAddress(alice.address, testConfig.addressEncoding))!, - ) - expect(account.data.reserved.toBigInt()).toBe(reservedAmount) - expect(account.data.frozen.toBigInt()).toBe(lockAmount) + // Step 6 - // The operation failed, even though the account had enough funds to place the required deposit - expect(account.data.free.toBigInt()).toBeGreaterThanOrEqual(actionDeposit) + const account = await client.api.query.system.account(alice.address) + const actionDeposit = await depositAction.calculateDeposit(client) + + expect(account.data.free.toBigInt()).toBe( + totalBalance - lockAmount - cumulativeFees.get(encodeAddress(alice.address, testConfig.addressEncoding))!, + ) + expect(account.data.reserved.toBigInt()).toBe(reservedAmount) + expect(account.data.frozen.toBigInt()).toBe(lockAmount) + + // The operation failed, even though the account had enough funds to place the required deposit + expect(account.data.free.toBigInt()).toBeGreaterThanOrEqual(actionDeposit) + }) + .with('success', async () => { + // Step 5 + + await checkEvents(actionEvents, { section: 'balances', method: 'Reserved' }).toMatchSnapshot( + 'deposit action success events', + ) + + const finalEvents = await client.api.query.system.events() + const reservedEvent = finalEvents.find((record) => { + const { event } = record + return event.section === 'balances' && event.method === 'Reserved' + }) + expect(reservedEvent).toBeDefined() + assert(client.api.events.balances.Reserved.is(reservedEvent!.event)) + const reservedEventData = reservedEvent!.event.data + expect(reservedEventData.who.toString()).toBe(encodeAddress(alice.address, testConfig.addressEncoding)) + const actionDeposit = await depositAction.calculateDeposit(client) + expect(reservedEventData.amount.toBigInt()).toBe(actionDeposit) + + // Step 6 + const account = await client.api.query.system.account(alice.address) + + expect(account.data.free.toBigInt()).toBe( + totalBalance - + lockAmount - + actionDeposit - + cumulativeFees.get(encodeAddress(alice.address, testConfig.addressEncoding))!, + ) + expect(account.data.reserved.toBigInt()).toBe(reservedAmount + actionDeposit) + expect(account.data.frozen.toBigInt()).toBe(lockAmount) + }) } /// ---------- @@ -4180,11 +4284,11 @@ const burnNormalEDTests = (chain: Chain, testConfig: TestConfig): RootTestTree = export const accountsE2ETests = < TCustom extends Record, TInitStoragesBase extends Record>, - TInitStoragesRelay extends Record> | undefined, + TInitStoragesRelay extends Record>, >( chain: Chain, testConfig: TestConfig, - relayChain?: Chain, + accountsCfg: AccountsTestConfig = defaultAccountsTestConfig(), ): RootTestTree => ({ kind: 'describe', label: testConfig.testSuiteName, @@ -4201,22 +4305,22 @@ export const accountsE2ETests = < { kind: 'test' as const, label: 'force transferring origin below ED can kill it', - testFn: () => forceTransferKillTest(chain, testConfig, relayChain), + testFn: () => forceTransferKillTest(chain, testConfig, accountsCfg.relayChain), }, { kind: 'test' as const, label: 'force transfer below existential deposit fails', - testFn: () => forceTransferBelowExistentialDepositTest(chain, testConfig, relayChain), + testFn: () => forceTransferBelowExistentialDepositTest(chain, testConfig, accountsCfg.relayChain), }, { kind: 'test' as const, label: 'force transfer with insufficient funds fails', - testFn: () => forceTransferInsufficientFundsTest(chain, testConfig, relayChain), + testFn: () => forceTransferInsufficientFundsTest(chain, testConfig, accountsCfg.relayChain), }, { kind: 'test', label: 'account with reserves cannot be force transferred from', - testFn: () => forceTransferWithReserveTest(chain, testConfig, relayChain), + testFn: () => forceTransferWithReserveTest(chain, testConfig, accountsCfg.relayChain), }, { kind: 'test', @@ -4226,7 +4330,7 @@ export const accountsE2ETests = < { kind: 'test', label: 'self-transfer is a no-op', - testFn: () => forceTransferSelfTest(chain, testConfig, relayChain), + testFn: () => forceTransferSelfTest(chain, testConfig, accountsCfg.relayChain), }, ], }, @@ -4278,17 +4382,17 @@ export const accountsE2ETests = < { kind: 'test', label: 'unreserving 0 from account with no reserves is a no-op', - testFn: () => forceUnreserveNoReservesTest(chain, testConfig, relayChain), + testFn: () => forceUnreserveNoReservesTest(chain, testConfig, accountsCfg.relayChain), }, { kind: 'test', label: 'unreserving from non-existent account is a no-op', - testFn: () => forceUnreserveNonExistentAccountTest(chain, testConfig, relayChain), + testFn: () => forceUnreserveNonExistentAccountTest(chain, testConfig, accountsCfg.relayChain), }, { kind: 'test', label: 'unreserving from account with reserves works correctly', - testFn: () => forceUnreserveWithReservesTest(chain, testConfig, relayChain), + testFn: () => forceUnreserveWithReservesTest(chain, testConfig, accountsCfg.relayChain), }, ], }, @@ -4304,12 +4408,12 @@ export const accountsE2ETests = < { kind: 'test', label: 'successfully sets balance and and adjusts total issuance', - testFn: () => forceSetBalanceSuccessTest(chain, testConfig, relayChain), + testFn: () => forceSetBalanceSuccessTest(chain, testConfig, accountsCfg.relayChain), }, { kind: 'test', label: 'setting balance below ED reaps account and updates total issuance', - testFn: () => forceSetBalanceBelowEdTest(chain, testConfig, relayChain), + testFn: () => forceSetBalanceBelowEdTest(chain, testConfig, accountsCfg.relayChain), }, ], }, @@ -4325,12 +4429,12 @@ export const accountsE2ETests = < { kind: 'test', label: 'zero delta fails with DeltaZero error in both directions', - testFn: () => forceAdjustTotalIssuanceZeroDeltaTest(chain, testConfig, relayChain), + testFn: () => forceAdjustTotalIssuanceZeroDeltaTest(chain, testConfig, accountsCfg.relayChain), }, { kind: 'test', label: 'successful adjustments increase and decrease total issuance', - testFn: () => forceAdjustTotalIssuanceSuccessTest(chain, testConfig, relayChain), + testFn: () => forceAdjustTotalIssuanceSuccessTest(chain, testConfig, accountsCfg.relayChain), }, ], }, @@ -4343,24 +4447,27 @@ export const accountsE2ETests = < kind: 'describe', label: 'currency tests', children: (() => { - const reserveActions = createReserveActions() - const lockActions = createLockActions() - const depositActions = createDepositActions() - const testCases: Array<{ kind: 'test'; label: string; testFn: () => Promise }> = [] // Combinatorially generate test cases for as many combinations of reserves, locks and deposit actions that // trigger the liquidity restriction error. // If a network does not support any of the generated test cases, a log is shown, and the test is skipped. // At worst, this will require 3 roundtrips to the chopsticks local node; at best 1. - for (const reserveAction of reserveActions) { - for (const lockAction of lockActions) { - for (const depositAction of depositActions) { + for (const reserveAction of accountsCfg.actions?.reserveActions!) { + for (const lockAction of accountsCfg.actions?.lockActions!) { + for (const depositAction of accountsCfg.actions?.depositActions!) { testCases.push({ kind: 'test' as const, label: `liquidity restriction error: funds locked via ${reserveAction.name} and ${lockAction.name}, triggered via ${depositAction.name}`, testFn: () => - testLiquidityRestrictionForAction(chain, testConfig, reserveAction, lockAction, depositAction), + testLiquidityRestrictionForAction( + chain, + testConfig, + reserveAction, + lockAction, + depositAction, + accountsCfg.expectation, + ), }) } }