From 0dd1706c2a633748341d3f799047d24f90f2dadd Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Thu, 9 Oct 2025 19:32:25 +0530 Subject: [PATCH 01/39] Basic treasury spend functionality test added with helper functions --- packages/shared/src/treasury.ts | 195 ++++++++++++++++++++++++++++++-- 1 file changed, 188 insertions(+), 7 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 03252d3db..675c3cae3 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -1,15 +1,129 @@ import { sendTransaction } from '@acala-network/chopsticks-testing' -import { type Chain, defaultAccountsSr25519 as devAccounts } from '@e2e-test/networks' -import { setupNetworks } from '@e2e-test/shared' +import { type Chain, testAccounts } from '@e2e-test/networks' +import { type Client, setupNetworks } from '@e2e-test/shared' import type { FrameSupportTokensFungibleUnionOfNativeOrWithId, XcmVersionedLocation } from '@polkadot/types/lookup' import { assert, expect } from 'vitest' +import { logAllEvents } from './helpers/helper_functions.js' import { checkEvents, checkSystemEvents, scheduleInlineCallWithOrigin } from './helpers/index.js' import type { RootTestTree } from './types.js' +/// ------- +/// Helpers +/// ------- + +// initial funding balance for accounts +const TEST_ACCOUNT_BALANCE_MULTIPLIER = 10000n // 10,000x existential deposit + +const SPEND_AMOUNT_MULTIPLIER = 1000n // 1000x existential deposit + +// AssetHub parachain ID +const ASSET_HUB_PARA_ID = 1000 + +// Assets pallet ID +const ASSETS_PALLET_ID = 50 + +// USDT asset ID +const USDT_ID = 1984 + +const ASSET_KIND = { + v4: { + location: { + parents: 0, + interior: { + x1: [ + { + parachain: ASSET_HUB_PARA_ID, + }, + ], + }, + }, + assetId: { + parents: 0, + interior: { + x2: [ + { + palletInstance: ASSETS_PALLET_ID, + }, + { + generalIndex: USDT_ID, + }, + ], + }, + }, + }, +} as unknown as FrameSupportTokensFungibleUnionOfNativeOrWithId + +// Beneficiary location(bob) for the treasury spend +const BENEFICIARY_LOCATION = { + v4: { + parents: 0, + interior: { + x1: [ + { + accountId32: { + network: null, + id: testAccounts.bob.addressRaw, + }, + }, + ], + }, + }, +} as unknown as XcmVersionedLocation + +/** + * Setup accounts with funds for testing + */ +export async function setupTestAccounts< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(relayClient: Client, accounts: string[] = ['alice', 'bob']) { + const accountMap = { + alice: testAccounts.alice.address, + bob: testAccounts.bob.address, + charlie: testAccounts.charlie.address, + dave: testAccounts.dave.address, + } + + const existentialDeposit = relayClient.api.consts.balances.existentialDeposit.toBigInt() + const testAccountBalance = TEST_ACCOUNT_BALANCE_MULTIPLIER * existentialDeposit + + const accountData = accounts + .filter((account) => accountMap[account as keyof typeof accountMap]) + .map((account) => [ + [accountMap[account as keyof typeof accountMap]], + { providers: 1, data: { free: testAccountBalance } }, + ]) + + await relayClient.dev.setStorage({ + System: { + account: accountData, + }, + }) +} + +async function getSpendCount< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(relayClient: Client): Promise { + return (await relayClient.api.query.treasury.spendCount()).toNumber() +} + +async function getSpendIndexFromEvent< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(relayClient: Client, eventType: 'AssetSpendApproved' | 'Paid'): Promise { + const [event] = (await relayClient.api.query.system.events()).filter( + ({ event }: any) => event.section === 'treasury' && event.method === eventType, + ) + expect(event).toBeTruthy() + assert(relayClient.api.events.treasury[eventType].is(event.event)) + return event.event.data.index.toNumber() +} + /** * Test that a foreign asset spend from the Relay treasury is reflected on the AssetHub. * @@ -28,14 +142,14 @@ export async function treasurySpendForeignAssetTest< System: { account: [ // give Alice some DOTs so that she can sign a payout transaction. - [[devAccounts.alice.address], { providers: 1, data: { free: 10000e10 } }], + [[testAccounts.alice.address], { providers: 1, data: { free: 10000e10 } }], ], }, }) const ASSET_HUB_PARA_ID = 1000 const ASSETS_PALLET_ID = 50 const USDT_ID = 1984 - const balanceBefore = await assetHubClient.api.query.assets.account(USDT_ID, devAccounts.alice.address) + const balanceBefore = await assetHubClient.api.query.assets.account(USDT_ID, testAccounts.alice.address) // amount is encoded into the call const amount = 123123123123n @@ -74,7 +188,7 @@ export async function treasurySpendForeignAssetTest< { accountId32: { network: null, - id: devAccounts.alice.addressRaw, + id: testAccounts.alice.addressRaw, }, }, ], @@ -106,7 +220,7 @@ export async function treasurySpendForeignAssetTest< // payout const payoutEvents = await sendTransaction( - relayClient.api.tx.treasury.payout(spendIndex).signAsync(devAccounts.alice), + relayClient.api.tx.treasury.payout(spendIndex).signAsync(testAccounts.alice), ) // create blocks on RC and AH to ensure that payout is properly processed @@ -124,12 +238,74 @@ export async function treasurySpendForeignAssetTest< // treasury spend does not emit any event on AH so we need to check that Alice's balance is increased by the `amount` directly await assetHubClient.dev.newBlock() - const balanceAfter = await assetHubClient.api.query.assets.account(USDT_ID, devAccounts.alice.address) + const balanceAfter = await assetHubClient.api.query.assets.account(USDT_ID, testAccounts.alice.address) const balanceAfterAmount = balanceAfter.isNone ? 0n : balanceAfter.unwrap().balance.toBigInt() const balanceBeforeAmount = balanceBefore.isNone ? 0n : balanceBefore.unwrap().balance.toBigInt() expect(balanceAfterAmount - balanceBeforeAmount).toBe(amount) } +/** + * Test: Basic treasury spend functionality + * + * Verifies that the treasury's foreign asset spending mechanism correctly processes spend proposals + * and maintains proper state tracking. This test ensures that when authorized users create spend + * proposals for foreign assets (like USDT on Asset Hub), the treasury system properly validates, + * stores, and schedules these spends for execution. + * + * Test Structure: + * 1. Create a treasury spend proposal for USDT on Asset Hub + * 2. Verify the spend is stored correctly in treasury state + * 3. Check that `AssetSpendApproved` event is emitted with correct data + * 4. Validate spend count increment and proper indexing + * 5. Confirm spend timing constraints (validFrom and expireAt periods) + */ +export async function treasurySpendBasicTest< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(relayChain: Chain) { + const [relayClient] = await setupNetworks(relayChain) + + // Setup test accounts + await setupTestAccounts(relayClient, ['alice', 'bob']) + + // Get initial spend count + const initialSpendCount = await getSpendCount(relayClient) + + // Create a spend proposal + const existentialDeposit = relayClient.api.consts.balances.existentialDeposit.toBigInt() + const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER + + const spendTx = relayClient.api.tx.treasury.spend(ASSET_KIND, spendAmount, BENEFICIARY_LOCATION, null) + const hexSpendTx = spendTx.method.toHex() + await scheduleInlineCallWithOrigin(relayClient, hexSpendTx, { Origins: 'BigSpender' }) + + await relayClient.dev.newBlock() + + // Check that AssetSpendApproved event was emitted + await checkSystemEvents(relayClient, { section: 'treasury', method: 'AssetSpendApproved' }) + .redact({ redactKeys: /expireAt|validFrom|index/ }) + .toMatchSnapshot('treasury spend approval events') + + // Verify spend count increased + const newSpendCount = await getSpendCount(relayClient) + expect(newSpendCount).toBe(initialSpendCount + 1) + + // Verify the spend was stored correctly + const spendIndex = await getSpendIndexFromEvent(relayClient, 'AssetSpendApproved') + const spend = await relayClient.api.query.treasury.spends(spendIndex) + + expect(spend.isSome).toBeTruthy() + const spendData = spend.unwrap() + expect(spendData.amount.toBigInt()).toBe(spendAmount) + expect(spendData.status.isPending).toBe(true) + + const validFrom = spendData.validFrom.toNumber() + const payoutPeriod = relayClient.api.consts.treasury.payoutPeriod.toNumber() + expect(spendData.expireAt.toNumber()).toBe(validFrom + payoutPeriod) + + await relayClient.teardown() +} + export function baseTreasuryE2ETests< TCustom extends Record | undefined, TInitStoragesRelay extends Record> | undefined, @@ -148,6 +324,11 @@ export function baseTreasuryE2ETests< label: 'Foreign asset spend from Relay treasury is reflected on AssetHub', testFn: async () => await treasurySpendForeignAssetTest(relayChain, ahChain), }, + { + kind: 'test', + label: 'Basic treasury spend functionality', + testFn: async () => await treasurySpendBasicTest(relayChain), + }, ], } } From 0d4f1142fdbeea1fd7ed291562e4ef83c05628d7 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Thu, 9 Oct 2025 19:42:57 +0530 Subject: [PATCH 02/39] test renamed --- packages/shared/src/treasury.ts | 44 +++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 675c3cae3..0f674a7fa 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -245,7 +245,7 @@ export async function treasurySpendForeignAssetTest< } /** - * Test: Basic treasury spend functionality + * Test: Propose and approve a spend of treasury funds. * * Verifies that the treasury's foreign asset spending mechanism correctly processes spend proposals * and maintains proper state tracking. This test ensures that when authorized users create spend @@ -306,6 +306,40 @@ export async function treasurySpendBasicTest< await relayClient.teardown() } +/** + * Test: Treasury spend proposal rejection + * + * Verifies that the treasury's foreign asset spending mechanism correctly rejects spend proposals + * and maintains proper state tracking. This test ensures that when unauthorized users create spend + * proposals for foreign assets (like USDT on Asset Hub), the treasury system properly rejects them. + */ +export async function treasurySpendProposalRejection< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(relayChain: Chain) { + const [relayClient] = await setupNetworks(relayChain) + + // Setup test accounts + await setupTestAccounts(relayClient, ['alice', 'bob']) + + // Get initial spend count + const initialSpendCount = await getSpendCount(relayClient) + + // Create a spend proposal + const existentialDeposit = relayClient.api.consts.balances.existentialDeposit.toBigInt() + const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER + + const spendTx = relayClient.api.tx.treasury.spend(ASSET_KIND, spendAmount, BENEFICIARY_LOCATION, null) + const hexSpendTx = spendTx.method.toHex() + await scheduleInlineCallWithOrigin(relayClient, hexSpendTx, { Origins: 'BigSpender' }) + await relayClient.dev.newBlock() + + // Check that AssetSpendRejected event was emitted + await checkSystemEvents(relayClient, { section: 'treasury', method: 'AssetSpendRejected' }) + .redact({ redactKeys: /expireAt|validFrom|index/ }) + .toMatchSnapshot('treasury spend rejection events') +} + export function baseTreasuryE2ETests< TCustom extends Record | undefined, TInitStoragesRelay extends Record> | undefined, @@ -326,9 +360,15 @@ export function baseTreasuryE2ETests< }, { kind: 'test', - label: 'Basic treasury spend functionality', + label: 'Propose and approve a spend of treasury funds.', testFn: async () => await treasurySpendBasicTest(relayChain), }, + // Treasury spend proposal rejection + { + kind: 'test', + label: 'Removal of approved Treasury spend proposal', + testFn: async () => await treasurySpendProposalRejection(relayChain), + }, ], } } From 59a141aaba0527f2fa763c795eae57637df18907 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Thu, 9 Oct 2025 20:06:02 +0530 Subject: [PATCH 03/39] test added: Void a previously approved proposal --- packages/shared/src/treasury.ts | 72 +++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 0f674a7fa..12d2a7f45 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -245,7 +245,7 @@ export async function treasurySpendForeignAssetTest< } /** - * Test: Propose and approve a spend of treasury funds. + * Test: Propose and approve a spend of treasury funds * * Verifies that the treasury's foreign asset spending mechanism correctly processes spend proposals * and maintains proper state tracking. This test ensures that when authorized users create spend @@ -307,13 +307,21 @@ export async function treasurySpendBasicTest< } /** - * Test: Treasury spend proposal rejection + * Test: Void a previously approved proposal * - * Verifies that the treasury's foreign asset spending mechanism correctly rejects spend proposals - * and maintains proper state tracking. This test ensures that when unauthorized users create spend - * proposals for foreign assets (like USDT on Asset Hub), the treasury system properly rejects them. + * Verifies that the treasury's spend voiding mechanism correctly cancels previously approved + * spend proposals and properly cleans up associated state. This test ensures that authorized + * users can cancel approved spends before they are executed, preventing unintended fund + * disbursements and maintaining proper treasury governance controls. + * + * Test Structure: + * 1. Create and approve a treasury spend proposal for USDT on Asset Hub + * 2. Verify the spend is properly stored and approved + * 3. Void the approved spend proposal using authorized origin + * 4. Check that AssetSpendVoided event is emitted with correct data + * 5. Confirm the spend is completely removed from treasury storage */ -export async function treasurySpendProposalRejection< +export async function voidApprovedTreasurySpendProposal< TCustom extends Record | undefined, TInitStoragesRelay extends Record> | undefined, >(relayChain: Chain) { @@ -332,12 +340,52 @@ export async function treasurySpendProposalRejection< const spendTx = relayClient.api.tx.treasury.spend(ASSET_KIND, spendAmount, BENEFICIARY_LOCATION, null) const hexSpendTx = spendTx.method.toHex() await scheduleInlineCallWithOrigin(relayClient, hexSpendTx, { Origins: 'BigSpender' }) + await relayClient.dev.newBlock() - // Check that AssetSpendRejected event was emitted - await checkSystemEvents(relayClient, { section: 'treasury', method: 'AssetSpendRejected' }) + // Check that AssetSpendApproved event was emitted + await checkSystemEvents(relayClient, { section: 'treasury', method: 'AssetSpendApproved' }) .redact({ redactKeys: /expireAt|validFrom|index/ }) - .toMatchSnapshot('treasury spend rejection events') + .toMatchSnapshot('treasury spend approval events') + + // Verify spend count increased + const newSpendCount = await getSpendCount(relayClient) + expect(newSpendCount).toBe(initialSpendCount + 1) + + // Verify the spend was stored correctly + const spendIndex = await getSpendIndexFromEvent(relayClient, 'AssetSpendApproved') + const spend = await relayClient.api.query.treasury.spends(spendIndex) + + expect(spend.isSome).toBeTruthy() + const spendData = spend.unwrap() + expect(spendData.amount.toBigInt()).toBe(spendAmount) + expect(spendData.status.isPending).toBe(true) + + const validFrom = spendData.validFrom.toNumber() + const payoutPeriod = relayClient.api.consts.treasury.payoutPeriod.toNumber() + expect(spendData.expireAt.toNumber()).toBe(validFrom + payoutPeriod) + + await relayClient.dev.newBlock() + + // Void a the approved proposal + const removeApprovedSpendTx = relayClient.api.tx.treasury.voidSpend(spendIndex) + const hexRemoveApprovedSpendTx = removeApprovedSpendTx.method.toHex() + await scheduleInlineCallWithOrigin(relayClient, hexRemoveApprovedSpendTx, { Origins: 'Treasurer' }) + + await relayClient.dev.newBlock() + + await logAllEvents(relayClient) + + // Check that AssetSpendVoided event was emitted + await checkSystemEvents(relayClient, { section: 'treasury', method: 'AssetSpendVoided' }) + .redact({ redactKeys: /index/ }) + .toMatchSnapshot('treasury spend voided events') + + // Verify the spend was removed from the storage + const spendAfter = await relayClient.api.query.treasury.spends(spendIndex) + expect(spendAfter.isNone).toBe(true) + + await relayClient.teardown() } export function baseTreasuryE2ETests< @@ -360,14 +408,14 @@ export function baseTreasuryE2ETests< }, { kind: 'test', - label: 'Propose and approve a spend of treasury funds.', + label: 'Propose and approve a spend of treasury funds', testFn: async () => await treasurySpendBasicTest(relayChain), }, // Treasury spend proposal rejection { kind: 'test', - label: 'Removal of approved Treasury spend proposal', - testFn: async () => await treasurySpendProposalRejection(relayChain), + label: 'Void previously approved spend', + testFn: async () => await voidApprovedTreasurySpendProposal(relayChain), }, ], } From 45b1da61fbd7d107b9ac74027ef315b2fef61f0e Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Thu, 9 Oct 2025 20:08:13 +0530 Subject: [PATCH 04/39] spend and reejct origin defined --- packages/shared/src/treasury.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 12d2a7f45..41fb75ce5 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -15,6 +15,10 @@ import type { RootTestTree } from './types.js' /// Helpers /// ------- +// Origins +const REJECT_ORIGIN = 'Treasurer' +const SPEND_ORIGIN = 'BigSpender' + // initial funding balance for accounts const TEST_ACCOUNT_BALANCE_MULTIPLIER = 10000n // 10,000x existential deposit @@ -277,7 +281,7 @@ export async function treasurySpendBasicTest< const spendTx = relayClient.api.tx.treasury.spend(ASSET_KIND, spendAmount, BENEFICIARY_LOCATION, null) const hexSpendTx = spendTx.method.toHex() - await scheduleInlineCallWithOrigin(relayClient, hexSpendTx, { Origins: 'BigSpender' }) + await scheduleInlineCallWithOrigin(relayClient, hexSpendTx, { Origins: SPEND_ORIGIN }) await relayClient.dev.newBlock() @@ -339,7 +343,7 @@ export async function voidApprovedTreasurySpendProposal< const spendTx = relayClient.api.tx.treasury.spend(ASSET_KIND, spendAmount, BENEFICIARY_LOCATION, null) const hexSpendTx = spendTx.method.toHex() - await scheduleInlineCallWithOrigin(relayClient, hexSpendTx, { Origins: 'BigSpender' }) + await scheduleInlineCallWithOrigin(relayClient, hexSpendTx, { Origins: SPEND_ORIGIN }) await relayClient.dev.newBlock() @@ -370,7 +374,7 @@ export async function voidApprovedTreasurySpendProposal< // Void a the approved proposal const removeApprovedSpendTx = relayClient.api.tx.treasury.voidSpend(spendIndex) const hexRemoveApprovedSpendTx = removeApprovedSpendTx.method.toHex() - await scheduleInlineCallWithOrigin(relayClient, hexRemoveApprovedSpendTx, { Origins: 'Treasurer' }) + await scheduleInlineCallWithOrigin(relayClient, hexRemoveApprovedSpendTx, { Origins: REJECT_ORIGIN }) await relayClient.dev.newBlock() From 622cef6d10799365d2dd282402ec7d300fd2592a Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Thu, 9 Oct 2025 21:36:15 +0530 Subject: [PATCH 05/39] test added: claim a spend --- packages/shared/src/treasury.ts | 98 +++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 5 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 41fb75ce5..20c7c1dae 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -22,7 +22,7 @@ const SPEND_ORIGIN = 'BigSpender' // initial funding balance for accounts const TEST_ACCOUNT_BALANCE_MULTIPLIER = 10000n // 10,000x existential deposit -const SPEND_AMOUNT_MULTIPLIER = 1000n // 1000x existential deposit +const SPEND_AMOUNT_MULTIPLIER = 100n // 100x existential deposit // AssetHub parachain ID const ASSET_HUB_PARA_ID = 1000 @@ -61,7 +61,7 @@ const ASSET_KIND = { }, } as unknown as FrameSupportTokensFungibleUnionOfNativeOrWithId -// Beneficiary location(bob) for the treasury spend +// Beneficiary location(alice) for the treasury spend const BENEFICIARY_LOCATION = { v4: { parents: 0, @@ -70,7 +70,7 @@ const BENEFICIARY_LOCATION = { { accountId32: { network: null, - id: testAccounts.bob.addressRaw, + id: testAccounts.alice.addressRaw, }, }, ], @@ -378,8 +378,6 @@ export async function voidApprovedTreasurySpendProposal< await relayClient.dev.newBlock() - await logAllEvents(relayClient) - // Check that AssetSpendVoided event was emitted await checkSystemEvents(relayClient, { section: 'treasury', method: 'AssetSpendVoided' }) .redact({ redactKeys: /index/ }) @@ -392,6 +390,90 @@ export async function voidApprovedTreasurySpendProposal< await relayClient.teardown() } +/** + * Test: Claim a spend + * + * Verifies that the treasury's spend claiming mechanism correctly processes approved spends + * and properly updates the spend status. This test ensures that authorized users can claim + * approved spends after the payout period has ended, ensuring proper fund disbursement and + * tracking of successful transactions. + * + * Test Structure: + * 1. Create and approve a treasury spend proposal for USDT on Asset Hub + * 2. Verify the spend is properly stored and approved + * 3. Claim the approved spend using Alice's account (beneficiary) + * 4. Check that Paid event is emitted with correct data + * 5. Confirm Alice's USDT balance increases by the spend amount on Asset Hub + */ +export async function claimTreasurySpend< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, + TInitStoragesPara extends Record> | undefined, +>(relayChain: Chain, ahChain: Chain) { + const [relayClient, assetHubClient] = await setupNetworks(relayChain, ahChain) + + // Setup test accounts + await setupTestAccounts(relayClient, ['alice', 'bob']) + + // Get initial spend count + const initialSpendCount = await getSpendCount(relayClient) + + // Create a spend proposal + const existentialDeposit = relayClient.api.consts.balances.existentialDeposit.toBigInt() + const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER + + const spendTx = relayClient.api.tx.treasury.spend(ASSET_KIND, spendAmount, BENEFICIARY_LOCATION, null) + const hexSpendTx = spendTx.method.toHex() + await scheduleInlineCallWithOrigin(relayClient, hexSpendTx, { Origins: SPEND_ORIGIN }) + + await relayClient.dev.newBlock() + + // Check that AssetSpendApproved event was emitted + await checkSystemEvents(relayClient, { section: 'treasury', method: 'AssetSpendApproved' }) + .redact({ redactKeys: /expireAt|validFrom|index/ }) + .toMatchSnapshot('treasury spend approval events') + + // Verify spend count increased + const newSpendCount = await getSpendCount(relayClient) + expect(newSpendCount).toBe(initialSpendCount + 1) + + // Verify the spend was stored correctly + const spendIndex = await getSpendIndexFromEvent(relayClient, 'AssetSpendApproved') + const spend = await relayClient.api.query.treasury.spends(spendIndex) + + expect(spend.isSome).toBeTruthy() + const spendData = spend.unwrap() + expect(spendData.amount.toBigInt()).toBe(spendAmount) + expect(spendData.status.isPending).toBe(true) + + // Alice's account should have some USDT balance on Asset Hub i.e her account should exist on Asset Hub for the payout to happen + const balanceBefore = await assetHubClient.api.query.assets.account(USDT_ID, testAccounts.alice.address) + await relayClient.dev.newBlock() + + // Claim the spend by the beneficiary i.e alice + const claimSpendTx = relayClient.api.tx.treasury.payout(spendIndex) + const payoutEvents = await sendTransaction(claimSpendTx.signAsync(testAccounts.alice)) + + await relayClient.dev.newBlock() + + await checkEvents(payoutEvents, { section: 'treasury', method: 'Paid' }) + .redact({ redactKeys: /paymentId|index/ }) + .toMatchSnapshot('payout events') + + const payoutIndex = await getSpendIndexFromEvent(relayClient, 'Paid') + expect(payoutIndex).toBe(spendIndex) + + // / treasury spend does not emit any event on AH so we need to check that Alice's balance is increased by the `amount` directly + await assetHubClient.dev.newBlock() + + const balanceAfter = await assetHubClient.api.query.assets.account(USDT_ID, testAccounts.alice.address) + const balanceAfterAmount = balanceAfter.isNone ? 0n : balanceAfter.unwrap().balance.toBigInt() + const balanceBeforeAmount = balanceBefore.isNone ? 0n : balanceBefore.unwrap().balance.toBigInt() + expect(balanceAfterAmount - balanceBeforeAmount).toBe(spendAmount) + + await relayClient.teardown() +} + export function baseTreasuryE2ETests< TCustom extends Record | undefined, TInitStoragesRelay extends Record> | undefined, @@ -421,6 +503,12 @@ export function baseTreasuryE2ETests< label: 'Void previously approved spend', testFn: async () => await voidApprovedTreasurySpendProposal(relayChain), }, + // Claim a spend + { + kind: 'test', + label: 'Claim a spend', + testFn: async () => await claimTreasurySpend(relayChain, ahChain), + }, ], } } From a76de6d567bd04663274bcdf62cc02c559d40a45 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Thu, 9 Oct 2025 21:39:14 +0530 Subject: [PATCH 06/39] testAccounts.alice.address funded with intiail usdt balance on assethub --- packages/networks/src/chains/assethub.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/networks/src/chains/assethub.ts b/packages/networks/src/chains/assethub.ts index f56ac24c3..4c0fe2c19 100644 --- a/packages/networks/src/chains/assethub.ts +++ b/packages/networks/src/chains/assethub.ts @@ -53,6 +53,7 @@ const getInitStorages = (config: typeof custom.assetHubPolkadot | typeof custom. Assets: { account: [ [[config.usdtIndex, defaultAccounts.alice.address], { balance: 1000e6 }], // USDT + [[config.usdtIndex, testAccounts.alice.address], { balance: 1000e6 }], // USDT ], }, ForeignAssets: { From ba9e8dba29410eb63c37cc76eb3ae9c6e696d590 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Thu, 9 Oct 2025 22:13:43 +0530 Subject: [PATCH 07/39] createSpendProposal() helper function added --- packages/shared/src/treasury.ts | 46 ++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 20c7c1dae..46eb37b5f 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -248,6 +248,18 @@ export async function treasurySpendForeignAssetTest< expect(balanceAfterAmount - balanceBeforeAmount).toBe(amount) } +/** + * Creates and schedules a treasury spend proposal + */ +export async function createSpendProposal< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(relayClient: Client, spendAmount: bigint) { + const spendTx = relayClient.api.tx.treasury.spend(ASSET_KIND, spendAmount, BENEFICIARY_LOCATION, null) + const hexSpendTx = spendTx.method.toHex() + await scheduleInlineCallWithOrigin(relayClient, hexSpendTx, { Origins: SPEND_ORIGIN }) +} + /** * Test: Propose and approve a spend of treasury funds * @@ -278,10 +290,7 @@ export async function treasurySpendBasicTest< // Create a spend proposal const existentialDeposit = relayClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - - const spendTx = relayClient.api.tx.treasury.spend(ASSET_KIND, spendAmount, BENEFICIARY_LOCATION, null) - const hexSpendTx = spendTx.method.toHex() - await scheduleInlineCallWithOrigin(relayClient, hexSpendTx, { Origins: SPEND_ORIGIN }) + await createSpendProposal(relayClient, spendAmount) await relayClient.dev.newBlock() @@ -341,9 +350,7 @@ export async function voidApprovedTreasurySpendProposal< const existentialDeposit = relayClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - const spendTx = relayClient.api.tx.treasury.spend(ASSET_KIND, spendAmount, BENEFICIARY_LOCATION, null) - const hexSpendTx = spendTx.method.toHex() - await scheduleInlineCallWithOrigin(relayClient, hexSpendTx, { Origins: SPEND_ORIGIN }) + await createSpendProposal(relayClient, spendAmount) await relayClient.dev.newBlock() @@ -422,9 +429,7 @@ export async function claimTreasurySpend< const existentialDeposit = relayClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - const spendTx = relayClient.api.tx.treasury.spend(ASSET_KIND, spendAmount, BENEFICIARY_LOCATION, null) - const hexSpendTx = spendTx.method.toHex() - await scheduleInlineCallWithOrigin(relayClient, hexSpendTx, { Origins: SPEND_ORIGIN }) + await createSpendProposal(relayClient, spendAmount) await relayClient.dev.newBlock() @@ -474,6 +479,21 @@ export async function claimTreasurySpend< await relayClient.teardown() } +/** + * Test: Check the status of the spend and remove it from the storage if processed + * + * Verifies that the treasury's spend status checking mechanism correctly processes approved spends + * and properly updates the spend status. This test ensures that authorized users can check the + * status of a spend and remove it from the storage if processed. + */ +export async function checkStatusOfTreasurySpend< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(relayChain: Chain) { + // const [relayClient] = await setupNetworks(relayChain) + console.log('checkStatusOfTreasurySpend') +} + export function baseTreasuryE2ETests< TCustom extends Record | undefined, TInitStoragesRelay extends Record> | undefined, @@ -509,6 +529,12 @@ export function baseTreasuryE2ETests< label: 'Claim a spend', testFn: async () => await claimTreasurySpend(relayChain, ahChain), }, + // Check the status of the spend and remove it from the storage if processed + { + kind: 'test', + label: 'Check status of a spend and remove it from the storage if processed', + testFn: async () => await checkStatusOfTreasurySpend(relayChain), + }, ], } } From a58c2b4b432696ae5aadce69856181ed6514b851 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Thu, 9 Oct 2025 22:24:14 +0530 Subject: [PATCH 08/39] helper function verifySystemEventAssetSpendApproved() added --- packages/shared/src/treasury.ts | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 46eb37b5f..d96b29104 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -251,7 +251,7 @@ export async function treasurySpendForeignAssetTest< /** * Creates and schedules a treasury spend proposal */ -export async function createSpendProposal< +async function createSpendProposal< TCustom extends Record | undefined, TInitStoragesRelay extends Record> | undefined, >(relayClient: Client, spendAmount: bigint) { @@ -260,6 +260,18 @@ export async function createSpendProposal< await scheduleInlineCallWithOrigin(relayClient, hexSpendTx, { Origins: SPEND_ORIGIN }) } +/** + * Verify that the AssetSpendApproved event was emitted + */ +async function verifySystemEventAssetSpendApproved< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(relayClient: Client) { + await checkSystemEvents(relayClient, { section: 'treasury', method: 'AssetSpendApproved' }) + .redact({ redactKeys: /expireAt|validFrom|index/ }) + .toMatchSnapshot('treasury spend approval events') +} + /** * Test: Propose and approve a spend of treasury funds * @@ -294,10 +306,8 @@ export async function treasurySpendBasicTest< await relayClient.dev.newBlock() - // Check that AssetSpendApproved event was emitted - await checkSystemEvents(relayClient, { section: 'treasury', method: 'AssetSpendApproved' }) - .redact({ redactKeys: /expireAt|validFrom|index/ }) - .toMatchSnapshot('treasury spend approval events') + // Verify that the AssetSpendApproved event was emitted + await verifySystemEventAssetSpendApproved(relayClient) // Verify spend count increased const newSpendCount = await getSpendCount(relayClient) @@ -354,10 +364,8 @@ export async function voidApprovedTreasurySpendProposal< await relayClient.dev.newBlock() - // Check that AssetSpendApproved event was emitted - await checkSystemEvents(relayClient, { section: 'treasury', method: 'AssetSpendApproved' }) - .redact({ redactKeys: /expireAt|validFrom|index/ }) - .toMatchSnapshot('treasury spend approval events') + // Verify that the AssetSpendApproved event was emitted + await verifySystemEventAssetSpendApproved(relayClient) // Verify spend count increased const newSpendCount = await getSpendCount(relayClient) @@ -433,10 +441,8 @@ export async function claimTreasurySpend< await relayClient.dev.newBlock() - // Check that AssetSpendApproved event was emitted - await checkSystemEvents(relayClient, { section: 'treasury', method: 'AssetSpendApproved' }) - .redact({ redactKeys: /expireAt|validFrom|index/ }) - .toMatchSnapshot('treasury spend approval events') + // Verify that the AssetSpendApproved event was emitted + await verifySystemEventAssetSpendApproved(relayClient) // Verify spend count increased const newSpendCount = await getSpendCount(relayClient) From b50d6d4eeed350397d8e8f6970b8a50fb372e329 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Thu, 9 Oct 2025 22:48:49 +0530 Subject: [PATCH 09/39] helper functions added : voidApprovedSpendProposal, verifySystemEventAssetSpendVoided, sendPayoutTx, verifyEventPaid --- packages/shared/src/treasury.ts | 63 ++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index d96b29104..713122824 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -3,7 +3,9 @@ import { sendTransaction } from '@acala-network/chopsticks-testing' import { type Chain, testAccounts } from '@e2e-test/networks' import { type Client, setupNetworks } from '@e2e-test/shared' +import type { KeyringPair } from '@polkadot/keyring/types' import type { FrameSupportTokensFungibleUnionOfNativeOrWithId, XcmVersionedLocation } from '@polkadot/types/lookup' +import type { Codec } from '@polkadot/types/types' import { assert, expect } from 'vitest' @@ -329,6 +331,30 @@ export async function treasurySpendBasicTest< await relayClient.teardown() } +/** + * Void a previously approved spend proposal + */ +async function voidApprovedSpendProposal< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(relayClient: Client, spendIndex: number) { + const removeApprovedSpendTx = relayClient.api.tx.treasury.voidSpend(spendIndex) + const hexRemoveApprovedSpendTx = removeApprovedSpendTx.method.toHex() + await scheduleInlineCallWithOrigin(relayClient, hexRemoveApprovedSpendTx, { Origins: REJECT_ORIGIN }) +} + +/** + * Verify that the AssetSpendVoided event was emitted + */ +async function verifySystemEventAssetSpendVoided< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(relayClient: Client) { + await checkSystemEvents(relayClient, { section: 'treasury', method: 'AssetSpendVoided' }) + .redact({ redactKeys: /index/ }) + .toMatchSnapshot('treasury spend voided events') +} + /** * Test: Void a previously approved proposal * @@ -386,17 +412,13 @@ export async function voidApprovedTreasurySpendProposal< await relayClient.dev.newBlock() - // Void a the approved proposal - const removeApprovedSpendTx = relayClient.api.tx.treasury.voidSpend(spendIndex) - const hexRemoveApprovedSpendTx = removeApprovedSpendTx.method.toHex() - await scheduleInlineCallWithOrigin(relayClient, hexRemoveApprovedSpendTx, { Origins: REJECT_ORIGIN }) + // Void the approved proposal + await voidApprovedSpendProposal(relayClient, spendIndex) await relayClient.dev.newBlock() // Check that AssetSpendVoided event was emitted - await checkSystemEvents(relayClient, { section: 'treasury', method: 'AssetSpendVoided' }) - .redact({ redactKeys: /index/ }) - .toMatchSnapshot('treasury spend voided events') + await verifySystemEventAssetSpendVoided(relayClient) // Verify the spend was removed from the storage const spendAfter = await relayClient.api.query.treasury.spends(spendIndex) @@ -405,6 +427,26 @@ export async function voidApprovedTreasurySpendProposal< await relayClient.teardown() } +/** + * Create a function sendPayoutTx by the beneficiary + */ +async function sendPayoutTx< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(relayClient: Client, spendIndex: number, beneficiary: KeyringPair) { + const payoutTx = relayClient.api.tx.treasury.payout(spendIndex) + return await sendTransaction(payoutTx.signAsync(beneficiary)) +} + +/** + * Verify that the Paid event was emitted + */ +async function verifyEventPaid(events: { events: Promise }) { + await checkEvents(events, { section: 'treasury', method: 'Paid' }) + .redact({ redactKeys: /paymentId|index/ }) + .toMatchSnapshot('payout events') +} + /** * Test: Claim a spend * @@ -462,14 +504,11 @@ export async function claimTreasurySpend< await relayClient.dev.newBlock() // Claim the spend by the beneficiary i.e alice - const claimSpendTx = relayClient.api.tx.treasury.payout(spendIndex) - const payoutEvents = await sendTransaction(claimSpendTx.signAsync(testAccounts.alice)) + const payoutEvents = await sendPayoutTx(relayClient, spendIndex, testAccounts.alice) await relayClient.dev.newBlock() - await checkEvents(payoutEvents, { section: 'treasury', method: 'Paid' }) - .redact({ redactKeys: /paymentId|index/ }) - .toMatchSnapshot('payout events') + await verifyEventPaid(payoutEvents) const payoutIndex = await getSpendIndexFromEvent(relayClient, 'Paid') expect(payoutIndex).toBe(spendIndex) From 6b957ae14f055bf37715118cec7cf4d85c556cdf Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Thu, 9 Oct 2025 23:01:57 +0530 Subject: [PATCH 10/39] added initial usdt funding to alice account on asset hub --- packages/shared/src/treasury.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 713122824..e2f8d59a9 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -251,7 +251,7 @@ export async function treasurySpendForeignAssetTest< } /** - * Creates and schedules a treasury spend proposal + * Helper: Creates and schedules a treasury spend proposal */ async function createSpendProposal< TCustom extends Record | undefined, @@ -263,7 +263,7 @@ async function createSpendProposal< } /** - * Verify that the AssetSpendApproved event was emitted + * Helper: Verify that the AssetSpendApproved event was emitted */ async function verifySystemEventAssetSpendApproved< TCustom extends Record | undefined, @@ -332,7 +332,7 @@ export async function treasurySpendBasicTest< } /** - * Void a previously approved spend proposal + * Helper: Void a previously approved spend proposal */ async function voidApprovedSpendProposal< TCustom extends Record | undefined, @@ -344,7 +344,7 @@ async function voidApprovedSpendProposal< } /** - * Verify that the AssetSpendVoided event was emitted + * Helper: Verify that the AssetSpendVoided event was emitted */ async function verifySystemEventAssetSpendVoided< TCustom extends Record | undefined, @@ -428,7 +428,7 @@ export async function voidApprovedTreasurySpendProposal< } /** - * Create a function sendPayoutTx by the beneficiary + * Helper: Create a function sendPayoutTx by the beneficiary */ async function sendPayoutTx< TCustom extends Record | undefined, @@ -439,7 +439,7 @@ async function sendPayoutTx< } /** - * Verify that the Paid event was emitted + * Helper: Verify that the Paid event was emitted */ async function verifyEventPaid(events: { events: Promise }) { await checkEvents(events, { section: 'treasury', method: 'Paid' }) @@ -472,6 +472,13 @@ export async function claimTreasurySpend< // Setup test accounts await setupTestAccounts(relayClient, ['alice', 'bob']) + // Ensure that Alice's account has some USDT balance on Asset Hub i.e her account should exist on Asset Hub for the payout to happen + await assetHubClient.dev.setStorage({ + Assets: { + account: [[[USDT_ID, testAccounts.alice.address], { balance: 1000e6 }]], + }, + }) + // Get initial spend count const initialSpendCount = await getSpendCount(relayClient) @@ -499,7 +506,6 @@ export async function claimTreasurySpend< expect(spendData.amount.toBigInt()).toBe(spendAmount) expect(spendData.status.isPending).toBe(true) - // Alice's account should have some USDT balance on Asset Hub i.e her account should exist on Asset Hub for the payout to happen const balanceBefore = await assetHubClient.api.query.assets.account(USDT_ID, testAccounts.alice.address) await relayClient.dev.newBlock() From a050fe1fb1682be8c30511ef2e850061e9556917 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Thu, 9 Oct 2025 23:02:48 +0530 Subject: [PATCH 11/39] Revert "testAccounts.alice.address funded with intiail usdt balance on assethub" This reverts commit f7bb7d5977a1d0d5757c5fe8636e53374de86aac. --- packages/networks/src/chains/assethub.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/networks/src/chains/assethub.ts b/packages/networks/src/chains/assethub.ts index 4c0fe2c19..f56ac24c3 100644 --- a/packages/networks/src/chains/assethub.ts +++ b/packages/networks/src/chains/assethub.ts @@ -53,7 +53,6 @@ const getInitStorages = (config: typeof custom.assetHubPolkadot | typeof custom. Assets: { account: [ [[config.usdtIndex, defaultAccounts.alice.address], { balance: 1000e6 }], // USDT - [[config.usdtIndex, testAccounts.alice.address], { balance: 1000e6 }], // USDT ], }, ForeignAssets: { From 1ce30d7ac5bf2d237a8e01194f8809388222ae14 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Thu, 9 Oct 2025 23:04:59 +0530 Subject: [PATCH 12/39] linter fix --- packages/shared/src/treasury.ts | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index e2f8d59a9..a6db6e58a 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -308,6 +308,8 @@ export async function treasurySpendBasicTest< await relayClient.dev.newBlock() + await logAllEvents(relayClient) + // Verify that the AssetSpendApproved event was emitted await verifySystemEventAssetSpendApproved(relayClient) @@ -530,21 +532,6 @@ export async function claimTreasurySpend< await relayClient.teardown() } -/** - * Test: Check the status of the spend and remove it from the storage if processed - * - * Verifies that the treasury's spend status checking mechanism correctly processes approved spends - * and properly updates the spend status. This test ensures that authorized users can check the - * status of a spend and remove it from the storage if processed. - */ -export async function checkStatusOfTreasurySpend< - TCustom extends Record | undefined, - TInitStoragesRelay extends Record> | undefined, ->(relayChain: Chain) { - // const [relayClient] = await setupNetworks(relayChain) - console.log('checkStatusOfTreasurySpend') -} - export function baseTreasuryE2ETests< TCustom extends Record | undefined, TInitStoragesRelay extends Record> | undefined, @@ -580,12 +567,6 @@ export function baseTreasuryE2ETests< label: 'Claim a spend', testFn: async () => await claimTreasurySpend(relayChain, ahChain), }, - // Check the status of the spend and remove it from the storage if processed - { - kind: 'test', - label: 'Check status of a spend and remove it from the storage if processed', - testFn: async () => await checkStatusOfTreasurySpend(relayChain), - }, ], } } From 5d9619bd79353033b0283b29d9f2ea58a6ee4ba9 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Thu, 9 Oct 2025 23:39:08 +0530 Subject: [PATCH 13/39] import fix --- packages/shared/src/treasury.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index a6db6e58a..0d9332507 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -9,7 +9,7 @@ import type { Codec } from '@polkadot/types/types' import { assert, expect } from 'vitest' -import { logAllEvents } from './helpers/helper_functions.js' +//import { logAllEvents } from './helpers/helper_functions.js' import { checkEvents, checkSystemEvents, scheduleInlineCallWithOrigin } from './helpers/index.js' import type { RootTestTree } from './types.js' @@ -308,8 +308,6 @@ export async function treasurySpendBasicTest< await relayClient.dev.newBlock() - await logAllEvents(relayClient) - // Verify that the AssetSpendApproved event was emitted await verifySystemEventAssetSpendApproved(relayClient) From b1c013d1a8c57169769b864f3f6d51326b57ed53 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Fri, 10 Oct 2025 00:15:39 +0530 Subject: [PATCH 14/39] helpers added getAssetHubBalanceAmount and setInitialUSDTBalanceOnAssetHub --- packages/shared/src/treasury.ts | 43 +++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 0d9332507..bef0fe1a0 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -447,6 +447,33 @@ async function verifyEventPaid(events: { events: Promise }) { .toMatchSnapshot('payout events') } +/** + * Helper: Get the balance amount of the account on Asset Hub + */ +async function getAssetHubBalanceAmount< + TCustom extends Record | undefined, + TInitStoragesPara extends Record> | undefined, +>(assetHubClient: Client, accountId: string): Promise { + const balance = await assetHubClient.api.query.assets.account(USDT_ID, accountId) + return balance.isNone ? 0n : balance.unwrap().balance.toBigInt() +} + +/** + * Helper: Set the initial balance amount of the account on Asset Hub + * + * This is required to ensure that the account exists on Asset Hub for the payout to happen + */ +async function setInitialUSDTBalanceOnAssetHub< + TCustom extends Record | undefined, + TInitStoragesPara extends Record> | undefined, +>(assetHubClient: Client, accountAddress: string): Promise { + await assetHubClient.dev.setStorage({ + Assets: { + account: [[[USDT_ID, accountAddress], { balance: 1000e6 }]], + }, + }) +} + /** * Test: Claim a spend * @@ -473,11 +500,7 @@ export async function claimTreasurySpend< await setupTestAccounts(relayClient, ['alice', 'bob']) // Ensure that Alice's account has some USDT balance on Asset Hub i.e her account should exist on Asset Hub for the payout to happen - await assetHubClient.dev.setStorage({ - Assets: { - account: [[[USDT_ID, testAccounts.alice.address], { balance: 1000e6 }]], - }, - }) + await setInitialUSDTBalanceOnAssetHub(assetHubClient, testAccounts.alice.address) // Get initial spend count const initialSpendCount = await getSpendCount(relayClient) @@ -506,7 +529,8 @@ export async function claimTreasurySpend< expect(spendData.amount.toBigInt()).toBe(spendAmount) expect(spendData.status.isPending).toBe(true) - const balanceBefore = await assetHubClient.api.query.assets.account(USDT_ID, testAccounts.alice.address) + const balanceAmountBefore = await getAssetHubBalanceAmount(assetHubClient, testAccounts.alice.address) + await relayClient.dev.newBlock() // Claim the spend by the beneficiary i.e alice @@ -522,10 +546,9 @@ export async function claimTreasurySpend< // / treasury spend does not emit any event on AH so we need to check that Alice's balance is increased by the `amount` directly await assetHubClient.dev.newBlock() - const balanceAfter = await assetHubClient.api.query.assets.account(USDT_ID, testAccounts.alice.address) - const balanceAfterAmount = balanceAfter.isNone ? 0n : balanceAfter.unwrap().balance.toBigInt() - const balanceBeforeAmount = balanceBefore.isNone ? 0n : balanceBefore.unwrap().balance.toBigInt() - expect(balanceAfterAmount - balanceBeforeAmount).toBe(spendAmount) + // Ensure that Alice's balance is increased by the `amount` + const balanceAmountAfter = await getAssetHubBalanceAmount(assetHubClient, testAccounts.alice.address) + expect(balanceAmountAfter - balanceAmountBefore).toBe(spendAmount) await relayClient.teardown() } From 24a57df58e91948b9ed24d1ea188c92ab92d00ce Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Fri, 10 Oct 2025 19:16:16 +0530 Subject: [PATCH 15/39] test added: Check status of a spend and remove it from the storage if processed --- packages/shared/src/treasury.ts | 125 ++++++++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 7 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index bef0fe1a0..e06f3e74c 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -448,18 +448,18 @@ async function verifyEventPaid(events: { events: Promise }) { } /** - * Helper: Get the balance amount of the account on Asset Hub + * Helper: Get the balance amount of the account on Asset Hub for USDT */ -async function getAssetHubBalanceAmount< +async function getAssetHubUSDTBalanceAmount< TCustom extends Record | undefined, TInitStoragesPara extends Record> | undefined, ->(assetHubClient: Client, accountId: string): Promise { - const balance = await assetHubClient.api.query.assets.account(USDT_ID, accountId) +>(assetHubClient: Client, accountAddress: string): Promise { + const balance = await assetHubClient.api.query.assets.account(USDT_ID, accountAddress) return balance.isNone ? 0n : balance.unwrap().balance.toBigInt() } /** - * Helper: Set the initial balance amount of the account on Asset Hub + * Helper: Set the initial balance amount of the account on Asset Hub for USDT * * This is required to ensure that the account exists on Asset Hub for the payout to happen */ @@ -529,7 +529,7 @@ export async function claimTreasurySpend< expect(spendData.amount.toBigInt()).toBe(spendAmount) expect(spendData.status.isPending).toBe(true) - const balanceAmountBefore = await getAssetHubBalanceAmount(assetHubClient, testAccounts.alice.address) + const balanceAmountBefore = await getAssetHubUSDTBalanceAmount(assetHubClient, testAccounts.alice.address) await relayClient.dev.newBlock() @@ -547,12 +547,117 @@ export async function claimTreasurySpend< await assetHubClient.dev.newBlock() // Ensure that Alice's balance is increased by the `amount` - const balanceAmountAfter = await getAssetHubBalanceAmount(assetHubClient, testAccounts.alice.address) + const balanceAmountAfter = await getAssetHubUSDTBalanceAmount(assetHubClient, testAccounts.alice.address) expect(balanceAmountAfter - balanceAmountBefore).toBe(spendAmount) await relayClient.teardown() } +async function verifyEventSpendProcessed(events: { events: Promise }) { + await checkEvents(events, { section: 'treasury', method: 'SpendProcessed' }) + .redact({ redactKeys: /index/ }) + .toMatchSnapshot('spend processed events') +} + +async function sendCheckStatusTx< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(relayClient: Client, spendIndex: number) { + const checkStatusTx = relayClient.api.tx.treasury.checkStatus(spendIndex) + return await sendTransaction(checkStatusTx.signAsync(testAccounts.alice)) +} + +/** + * Test: Check the status of a spend and remove it from the storage if processed + * + * Verifies that the treasury's spend status checking mechanism correctly processes approved spends + * and properly updates the spend status. This test ensures that users can check the + * status of a spend and remove it from the storage if processed. + * + * Test Structure: + * 1. Create and approve a treasury spend proposal for USDT on Asset Hub + * 2. Verify the spend is properly stored and approved + * 3. Check the status of the spend and remove it from the storage if processed + * 4. Verify that the SpendProcessed event was emitted + * 5. Verify that the spend is removed from the storage + */ +export async function checkStatusOfTreasurySpend< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, + TInitStoragesPara extends Record> | undefined, +>(relayChain: Chain, ahChain: Chain) { + const [relayClient, assetHubClient] = await setupNetworks(relayChain, ahChain) + + // Setup test accounts + await setupTestAccounts(relayClient, ['alice', 'bob']) + + // Ensure that Alice's account has some USDT balance on Asset Hub i.e her account should exist on Asset Hub for the payout to happen + await setInitialUSDTBalanceOnAssetHub(assetHubClient, testAccounts.alice.address) + + // Get initial spend count + const initialSpendCount = await getSpendCount(relayClient) + + // Create a spend proposal + const existentialDeposit = relayClient.api.consts.balances.existentialDeposit.toBigInt() + const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER + + await createSpendProposal(relayClient, spendAmount) + + await relayClient.dev.newBlock() + + // Verify that the AssetSpendApproved event was emitted + await verifySystemEventAssetSpendApproved(relayClient) + + // Verify spend count increased + const newSpendCount = await getSpendCount(relayClient) + expect(newSpendCount).toBe(initialSpendCount + 1) + + // Verify the spend was stored correctly + const spendIndex = await getSpendIndexFromEvent(relayClient, 'AssetSpendApproved') + const spend = await relayClient.api.query.treasury.spends(spendIndex) + + expect(spend.isSome).toBeTruthy() + const spendData = spend.unwrap() + expect(spendData.amount.toBigInt()).toBe(spendAmount) + expect(spendData.status.isPending).toBe(true) + + const balanceAmountBefore = await getAssetHubUSDTBalanceAmount(assetHubClient, testAccounts.alice.address) + + await relayClient.dev.newBlock() + + // Claim the spend by the beneficiary i.e alice + const payoutEvents = await sendPayoutTx(relayClient, spendIndex, testAccounts.alice) + + await relayClient.dev.newBlock() + + await verifyEventPaid(payoutEvents) + + const payoutIndex = await getSpendIndexFromEvent(relayClient, 'Paid') + expect(payoutIndex).toBe(spendIndex) + + // / treasury spend does not emit any event on AH so we need to check that Alice's balance is increased by the `amount` directly + await assetHubClient.dev.newBlock() + + // Ensure that Alice's balance is increased by the `amount` + const balanceAmountAfter = await getAssetHubUSDTBalanceAmount(assetHubClient, testAccounts.alice.address) + expect(balanceAmountAfter - balanceAmountBefore).toBe(spendAmount) + + await relayClient.dev.newBlock() + + const checkStatusEvents = await sendCheckStatusTx(relayClient, spendIndex) + + await relayClient.dev.newBlock() + + // verify SpendProcessed event + await verifyEventSpendProcessed(checkStatusEvents) + + // verify the spend is removed from the storage + const spendAfterCheckStatus = await relayClient.api.query.treasury.spends(spendIndex) + expect(spendAfterCheckStatus.isNone).toBe(true) + + await relayClient.teardown() +} + export function baseTreasuryE2ETests< TCustom extends Record | undefined, TInitStoragesRelay extends Record> | undefined, @@ -588,6 +693,12 @@ export function baseTreasuryE2ETests< label: 'Claim a spend', testFn: async () => await claimTreasurySpend(relayChain, ahChain), }, + // Check the status of the spend and remove it from the storage if processed + { + kind: 'test', + label: 'Check status of a spend and remove it from the storage if processed', + testFn: async () => await checkStatusOfTreasurySpend(relayChain, ahChain), + }, ], } } From 27436d88e4e5907ce33be35cef3a02c84b313765 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Fri, 10 Oct 2025 19:58:16 +0530 Subject: [PATCH 16/39] test added: Proposing a expired spend emits `SpendExpired` error --- packages/shared/src/treasury.ts | 64 +++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index e06f3e74c..410c80095 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -256,8 +256,8 @@ export async function treasurySpendForeignAssetTest< async function createSpendProposal< TCustom extends Record | undefined, TInitStoragesRelay extends Record> | undefined, ->(relayClient: Client, spendAmount: bigint) { - const spendTx = relayClient.api.tx.treasury.spend(ASSET_KIND, spendAmount, BENEFICIARY_LOCATION, null) +>(relayClient: Client, spendAmount: bigint, validFrom: number | null = null) { + const spendTx = relayClient.api.tx.treasury.spend(ASSET_KIND, spendAmount, BENEFICIARY_LOCATION, validFrom) const hexSpendTx = spendTx.method.toHex() await scheduleInlineCallWithOrigin(relayClient, hexSpendTx, { Origins: SPEND_ORIGIN }) } @@ -304,7 +304,7 @@ export async function treasurySpendBasicTest< // Create a spend proposal const existentialDeposit = relayClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - await createSpendProposal(relayClient, spendAmount) + await createSpendProposal(relayClient, spendAmount) // validFrom will default to null and the spend call will take current block number as validFrom await relayClient.dev.newBlock() @@ -658,6 +658,58 @@ export async function checkStatusOfTreasurySpend< await relayClient.teardown() } +/** + * Test: Proposing a expired spend emits `SpendExpired` error + * + * Verifies that the treasury's spend proposal mechanism correctly processes expired spends + * and emits `SpendExpired` error. + * + * Test Structure: + * 1. Create a spend proposal with a valid from that is in the past i.e expired + * 2. Verify that the `SpendExpired` error is emitted on the dispatched event + */ + +export async function proposeExpiredSpend< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(relayChain: Chain) { + const [relayClient] = await setupNetworks(relayChain) + + // Setup test accounts + await setupTestAccounts(relayClient, ['alice', 'bob']) + + // Create a spend proposal + const existentialDeposit = relayClient.api.consts.balances.existentialDeposit.toBigInt() + const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER + const currentBlockNumber = await relayClient.api.query.system.number() + const payoutPeriod = relayClient.api.consts.treasury.payoutPeriod.toNumber() + const validFrom = currentBlockNumber.toNumber() - payoutPeriod - 1 // subtracting any number to ensure that the spend is expired + await createSpendProposal(relayClient, spendAmount, validFrom) + + await relayClient.dev.newBlock() + + // check the result of dispatched event + const events = await relayClient.api.query.system.events() + // Find the Dispatched event from scheduler + const dispatchedEvent = events.find((record) => { + const { event } = record + return event.section === 'scheduler' && event.method === 'Dispatched' + }) + + assert(dispatchedEvent) + assert(relayClient.api.events.scheduler.Dispatched.is(dispatchedEvent.event)) + + const dispatchedData = dispatchedEvent.event.data + expect(dispatchedData.result.isErr).toBe(true) + + // Decode the module error to get human-readable details + const dispatchError = dispatchedData.result.asErr + assert(dispatchError.isModule) + expect(relayClient.api.errors.treasury.SpendExpired.is(dispatchError.asModule)).toBeTruthy() + + await relayClient.teardown() +} + export function baseTreasuryE2ETests< TCustom extends Record | undefined, TInitStoragesRelay extends Record> | undefined, @@ -699,6 +751,12 @@ export function baseTreasuryE2ETests< label: 'Check status of a spend and remove it from the storage if processed', testFn: async () => await checkStatusOfTreasurySpend(relayChain, ahChain), }, + // proposing a expired spend + { + kind: 'test', + label: 'Proposing a expired spend emits `SpendExpired` error', + testFn: async () => await proposeExpiredSpend(relayChain), + }, ], } } From db5a0b06cef43d8558347b28c6f13feefd040c9e Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Fri, 10 Oct 2025 20:13:56 +0530 Subject: [PATCH 17/39] test added: Smalltipper trying to spend more than the origin allows emits `InsufficientPermission` error --- packages/shared/src/treasury.ts | 66 +++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 410c80095..2a471d34d 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -20,6 +20,7 @@ import type { RootTestTree } from './types.js' // Origins const REJECT_ORIGIN = 'Treasurer' const SPEND_ORIGIN = 'BigSpender' +const SMALL_TIPPER_ORIGIN = 'SmallTipper' // initial funding balance for accounts const TEST_ACCOUNT_BALANCE_MULTIPLIER = 10000n // 10,000x existential deposit @@ -256,10 +257,15 @@ export async function treasurySpendForeignAssetTest< async function createSpendProposal< TCustom extends Record | undefined, TInitStoragesRelay extends Record> | undefined, ->(relayClient: Client, spendAmount: bigint, validFrom: number | null = null) { +>( + relayClient: Client, + spendAmount: bigint, + origin: string = SPEND_ORIGIN, + validFrom: number | null = null, +) { const spendTx = relayClient.api.tx.treasury.spend(ASSET_KIND, spendAmount, BENEFICIARY_LOCATION, validFrom) const hexSpendTx = spendTx.method.toHex() - await scheduleInlineCallWithOrigin(relayClient, hexSpendTx, { Origins: SPEND_ORIGIN }) + await scheduleInlineCallWithOrigin(relayClient, hexSpendTx, { Origins: origin }) } /** @@ -684,7 +690,7 @@ export async function proposeExpiredSpend< const currentBlockNumber = await relayClient.api.query.system.number() const payoutPeriod = relayClient.api.consts.treasury.payoutPeriod.toNumber() const validFrom = currentBlockNumber.toNumber() - payoutPeriod - 1 // subtracting any number to ensure that the spend is expired - await createSpendProposal(relayClient, spendAmount, validFrom) + await createSpendProposal(relayClient, spendAmount, SPEND_ORIGIN, validFrom) await relayClient.dev.newBlock() @@ -710,6 +716,54 @@ export async function proposeExpiredSpend< await relayClient.teardown() } +/** + * Test: Smalltipper trying to spend more than the origin allows emits `InsufficientPermission` error + * + * Verifies that the treasury's spend proposal mechanism correctly processes smalltipper trying to spend more than the origin allows + * and emits `InsufficientPermission` error. + * + * Test Structure: + * 1. Create a spend proposal with `SmallTipper` origin which does not have permission to spend the spendAmount + * 2. Verify that the `InsufficientPermission` error is emitted on the dispatched event + */ +export async function smalltipperTryingToSpendMoreThanTheOriginAllows< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, +>(relayChain: Chain) { + const [relayClient] = await setupNetworks(relayChain) + + // Setup test accounts + await setupTestAccounts(relayClient, ['alice', 'bob']) + + // Create a spend proposal + const existentialDeposit = relayClient.api.consts.balances.existentialDeposit.toBigInt() + const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER + await createSpendProposal(relayClient, spendAmount, SMALL_TIPPER_ORIGIN) // SmallTipper does not have permission to spend large amounts + + await relayClient.dev.newBlock() + + // check the result of dispatched event + const events = await relayClient.api.query.system.events() + // Find the Dispatched event from scheduler + const dispatchedEvent = events.find((record) => { + const { event } = record + return event.section === 'scheduler' && event.method === 'Dispatched' + }) + + assert(dispatchedEvent) + assert(relayClient.api.events.scheduler.Dispatched.is(dispatchedEvent.event)) + + const dispatchedData = dispatchedEvent.event.data + expect(dispatchedData.result.isErr).toBe(true) + + // Decode the module error to get human-readable details + const dispatchError = dispatchedData.result.asErr + assert(dispatchError.isModule) + expect(relayClient.api.errors.treasury.InsufficientPermission.is(dispatchError.asModule)).toBeTruthy() + + await relayClient.teardown() +} + export function baseTreasuryE2ETests< TCustom extends Record | undefined, TInitStoragesRelay extends Record> | undefined, @@ -757,6 +811,12 @@ export function baseTreasuryE2ETests< label: 'Proposing a expired spend emits `SpendExpired` error', testFn: async () => await proposeExpiredSpend(relayChain), }, + // smalltipper trying to spend more than the origin allows emits `InsufficientPermission` error + { + kind: 'test', + label: 'Smalltipper trying to spend more than the origin allows emits `InsufficientPermission` error', + testFn: async () => await smalltipperTryingToSpendMoreThanTheOriginAllows(relayChain), + }, ], } } From 4a136ed71d3c2652f35bfea7d6f7b588db6ea3b1 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Fri, 10 Oct 2025 20:34:14 +0530 Subject: [PATCH 18/39] updated snapshot --- .../polkadot.treasury.e2e.test.ts.snap | 349 ++++++++++++++++++ 1 file changed, 349 insertions(+) create mode 100644 packages/polkadot/src/__snapshots__/polkadot.treasury.e2e.test.ts.snap diff --git a/packages/polkadot/src/__snapshots__/polkadot.treasury.e2e.test.ts.snap b/packages/polkadot/src/__snapshots__/polkadot.treasury.e2e.test.ts.snap new file mode 100644 index 000000000..4e4649962 --- /dev/null +++ b/packages/polkadot/src/__snapshots__/polkadot.treasury.e2e.test.ts.snap @@ -0,0 +1,349 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Polkadot Treasury > Check status of a spend and remove it from the storage if processed > payout events 1`] = ` +[ + { + "data": { + "index": "(redacted)", + "paymentId": "(redacted)", + }, + "method": "Paid", + "section": "treasury", + }, +] +`; + +exports[`Polkadot Treasury > Check status of a spend and remove it from the storage if processed > spend processed events 1`] = ` +[ + { + "data": { + "index": "(redacted)", + }, + "method": "SpendProcessed", + "section": "treasury", + }, +] +`; + +exports[`Polkadot Treasury > Check status of a spend and remove it from the storage if processed > treasury spend approval events 1`] = ` +[ + { + "data": { + "amount": 1000000000000, + "assetKind": { + "V4": { + "assetId": { + "interior": { + "X2": [ + { + "PalletInstance": 50, + }, + { + "GeneralIndex": "(rounded 2000)", + }, + ], + }, + "parents": 0, + }, + "location": { + "interior": { + "X1": [ + { + "Parachain": 1000, + }, + ], + }, + "parents": 0, + }, + }, + }, + "beneficiary": { + "V4": { + "interior": { + "X1": [ + { + "AccountId32": { + "id": "(hash)", + "network": null, + }, + }, + ], + }, + "parents": 0, + }, + }, + "expireAt": "(redacted)", + "index": "(redacted)", + "validFrom": "(redacted)", + }, + "method": "AssetSpendApproved", + "section": "treasury", + }, +] +`; + +exports[`Polkadot Treasury > Claim a spend > payout events 1`] = ` +[ + { + "data": { + "index": "(redacted)", + "paymentId": "(redacted)", + }, + "method": "Paid", + "section": "treasury", + }, +] +`; + +exports[`Polkadot Treasury > Claim a spend > treasury spend approval events 1`] = ` +[ + { + "data": { + "amount": 1000000000000, + "assetKind": { + "V4": { + "assetId": { + "interior": { + "X2": [ + { + "PalletInstance": 50, + }, + { + "GeneralIndex": "(rounded 2000)", + }, + ], + }, + "parents": 0, + }, + "location": { + "interior": { + "X1": [ + { + "Parachain": 1000, + }, + ], + }, + "parents": 0, + }, + }, + }, + "beneficiary": { + "V4": { + "interior": { + "X1": [ + { + "AccountId32": { + "id": "(hash)", + "network": null, + }, + }, + ], + }, + "parents": 0, + }, + }, + "expireAt": "(redacted)", + "index": "(redacted)", + "validFrom": "(redacted)", + }, + "method": "AssetSpendApproved", + "section": "treasury", + }, +] +`; + +exports[`Polkadot Treasury > Foreign asset spend from Relay treasury is reflected on AssetHub > payout events 1`] = ` +[ + { + "data": { + "index": "(redacted)", + "paymentId": "(redacted)", + }, + "method": "Paid", + "section": "treasury", + }, +] +`; + +exports[`Polkadot Treasury > Foreign asset spend from Relay treasury is reflected on AssetHub > treasury spend approval events 1`] = ` +[ + { + "data": { + "amount": "123,123,123,123", + "assetKind": { + "V4": { + "assetId": { + "interior": { + "X2": [ + { + "PalletInstance": "50", + }, + { + "GeneralIndex": "1,984", + }, + ], + }, + "parents": "0", + }, + "location": { + "interior": { + "X1": [ + { + "Parachain": "1,000", + }, + ], + }, + "parents": "0", + }, + }, + }, + "beneficiary": { + "V4": { + "interior": { + "X1": [ + { + "AccountId32": { + "id": "(hash)", + "network": null, + }, + }, + ], + }, + "parents": "0", + }, + }, + "expireAt": "(redacted)", + "index": "(redacted)", + "validFrom": "(redacted)", + }, + "method": "AssetSpendApproved", + "section": "treasury", + }, +] +`; + +exports[`Polkadot Treasury > Propose and approve a spend of treasury funds > treasury spend approval events 1`] = ` +[ + { + "data": { + "amount": 1000000000000, + "assetKind": { + "V4": { + "assetId": { + "interior": { + "X2": [ + { + "PalletInstance": 50, + }, + { + "GeneralIndex": "(rounded 2000)", + }, + ], + }, + "parents": 0, + }, + "location": { + "interior": { + "X1": [ + { + "Parachain": 1000, + }, + ], + }, + "parents": 0, + }, + }, + }, + "beneficiary": { + "V4": { + "interior": { + "X1": [ + { + "AccountId32": { + "id": "(hash)", + "network": null, + }, + }, + ], + }, + "parents": 0, + }, + }, + "expireAt": "(redacted)", + "index": "(redacted)", + "validFrom": "(redacted)", + }, + "method": "AssetSpendApproved", + "section": "treasury", + }, +] +`; + +exports[`Polkadot Treasury > Void previously approved spend > treasury spend approval events 1`] = ` +[ + { + "data": { + "amount": 1000000000000, + "assetKind": { + "V4": { + "assetId": { + "interior": { + "X2": [ + { + "PalletInstance": 50, + }, + { + "GeneralIndex": "(rounded 2000)", + }, + ], + }, + "parents": 0, + }, + "location": { + "interior": { + "X1": [ + { + "Parachain": 1000, + }, + ], + }, + "parents": 0, + }, + }, + }, + "beneficiary": { + "V4": { + "interior": { + "X1": [ + { + "AccountId32": { + "id": "(hash)", + "network": null, + }, + }, + ], + }, + "parents": 0, + }, + }, + "expireAt": "(redacted)", + "index": "(redacted)", + "validFrom": "(redacted)", + }, + "method": "AssetSpendApproved", + "section": "treasury", + }, +] +`; + +exports[`Polkadot Treasury > Void previously approved spend > treasury spend voided events 1`] = ` +[ + { + "data": { + "index": "(redacted)", + }, + "method": "AssetSpendVoided", + "section": "treasury", + }, +] +`; From b05122c3dfa9286151f60c079989174dba8a02ca Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Wed, 22 Oct 2025 19:56:35 +0530 Subject: [PATCH 19/39] treasury module added in assetHubKusama --- .../kusama/src/assetHubKusama.treasury.e2e.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 packages/kusama/src/assetHubKusama.treasury.e2e.test.ts diff --git a/packages/kusama/src/assetHubKusama.treasury.e2e.test.ts b/packages/kusama/src/assetHubKusama.treasury.e2e.test.ts new file mode 100644 index 000000000..0cd7d24b2 --- /dev/null +++ b/packages/kusama/src/assetHubKusama.treasury.e2e.test.ts @@ -0,0 +1,11 @@ +import { assetHubKusama, kusama } from '@e2e-test/networks/chains' +import { baseTreasuryE2ETests, type ParaTestConfig, registerTestTree } from '@e2e-test/shared' + +const testConfig: ParaTestConfig = { + testSuiteName: 'Kusama Asset Hub Treasury', + addressEncoding: 2, + blockProvider: 'NonLocal', + asyncBacking: 'Enabled', +} + +registerTestTree(baseTreasuryE2ETests(kusama, assetHubKusama, testConfig)) From 82ae67ace2ea92bdb380a303901a910472148626 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Wed, 22 Oct 2025 19:58:44 +0530 Subject: [PATCH 20/39] test added: Check treasury payouts which are already approved can be paid --- packages/shared/src/treasury.ts | 79 ++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 2a471d34d..3918c0de8 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -761,7 +761,77 @@ export async function smalltipperTryingToSpendMoreThanTheOriginAllows< assert(dispatchError.isModule) expect(relayClient.api.errors.treasury.InsufficientPermission.is(dispatchError.asModule)).toBeTruthy() - await relayClient.teardown() + await assetHubClient.teardown() +} + +/** + * Test: Check treasury payouts which are already approved can be paid + * + * Verifies that the treasury's payout mechanism correctly processes already approved spends + * and properly disburses funds to beneficiaries. This test ensures that approved treasury + * spends can be successfully paid out + * + * Test Structure: + * 1. Get all the spends which are pending or failed and is neither expired nor early payout + * 2. Call payout tx for each pending or failed spend + * 3. Verify the Paid event is emitted + * 4. Verify the spend status is attempted + */ +export async function checkTreasuryPayoutsWhichAreAlreadyApprovedCanBePaid< + TCustom extends Record | undefined, + TInitStoragesRelay extends Record> | undefined, + TInitStoragesPara extends Record> | undefined, +>(relayChain: Chain, ahChain: Chain) { + const [assetHubClient, relayClient] = await setupNetworks(ahChain, relayChain) + + await setupTestAccounts(assetHubClient, ['alice', 'bob']) + + // get all the spends + const spends = await assetHubClient.api.query.treasury.spends.entries() + + // get current relay chain block number + const currentRelayChainBlockNumber = (await relayClient.api.query.system.number()).toNumber() + + // filter those spends which are pending or failed and is neither expired nor early payout + const pendingOrFailedSpends = spends.filter((spend) => { + const spendData = spend[1]?.unwrap() + return ( + (spendData?.status.isPending || spendData?.status.isFailed) && // not pending or failed + spendData?.validFrom.toNumber() < currentRelayChainBlockNumber && //not early payout + spendData?.expireAt.toNumber() > currentRelayChainBlockNumber // not expired + ) + }) + + await assetHubClient.dev.newBlock() + + const spendIndices: number[] = [] + + // call payout tx for each pending or failed spend + for (const spend of pendingOrFailedSpends) { + const spendIndex = spend[0].toHuman?.() as number + spendIndices.push(spendIndex) + const payoutTx = assetHubClient.api.tx.treasury.payout(spendIndex) + await sendTransaction(payoutTx.signAsync(testAccounts.alice)) + + await assetHubClient.dev.newBlock() + + // verify the Paid event is emitted + const treasuryEvents = await assetHubClient.api.query.system.events() + const paidEvent = treasuryEvents.find((record) => { + const { event } = record + return event.section === 'treasury' && event.method === 'Paid' + }) + assert(paidEvent) + assert(assetHubClient.api.events.treasury.Paid.is(paidEvent.event)) + } + + // verify the spends status is attempted + for (const spendIndex of spendIndices) { + const spend = await assetHubClient.api.query.treasury.spends(spendIndex) + expect(spend?.unwrap()?.status.isAttempted).toBe(true) + } + + await assetHubClient.teardown() } export function baseTreasuryE2ETests< @@ -815,7 +885,12 @@ export function baseTreasuryE2ETests< { kind: 'test', label: 'Smalltipper trying to spend more than the origin allows emits `InsufficientPermission` error', - testFn: async () => await smalltipperTryingToSpendMoreThanTheOriginAllows(relayChain), + testFn: async () => await smalltipperTryingToSpendMoreThanTheOriginAllows(ahChain), + }, + { + kind: 'test', + label: 'Check treasury payouts which are already approved can be paid', + testFn: async () => await checkTreasuryPayoutsWhichAreAlreadyApprovedCanBePaid(relayChain, ahChain), }, ], } From c1a98233f60ee8d60c264ad83cd3870ebbb7047d Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Wed, 22 Oct 2025 20:00:34 +0530 Subject: [PATCH 21/39] all tests ported to assetHub from relay chain --- packages/shared/src/treasury.ts | 272 ++++++++++++++++---------------- 1 file changed, 134 insertions(+), 138 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 3918c0de8..d30df571a 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -27,9 +27,6 @@ const TEST_ACCOUNT_BALANCE_MULTIPLIER = 10000n // 10,000x existential deposit const SPEND_AMOUNT_MULTIPLIER = 100n // 100x existential deposit -// AssetHub parachain ID -const ASSET_HUB_PARA_ID = 1000 - // Assets pallet ID const ASSETS_PALLET_ID = 50 @@ -40,13 +37,7 @@ const ASSET_KIND = { v4: { location: { parents: 0, - interior: { - x1: [ - { - parachain: ASSET_HUB_PARA_ID, - }, - ], - }, + interior: 'Here', // Asset is local to Asset Hub }, assetId: { parents: 0, @@ -114,20 +105,20 @@ export async function setupTestAccounts< async function getSpendCount< TCustom extends Record | undefined, - TInitStoragesRelay extends Record> | undefined, ->(relayClient: Client): Promise { - return (await relayClient.api.query.treasury.spendCount()).toNumber() + TInitStoragesPara extends Record> | undefined, +>(assetHubClient: Client): Promise { + return (await assetHubClient.api.query.treasury.spendCount()).toNumber() } async function getSpendIndexFromEvent< TCustom extends Record | undefined, - TInitStoragesRelay extends Record> | undefined, ->(relayClient: Client, eventType: 'AssetSpendApproved' | 'Paid'): Promise { - const [event] = (await relayClient.api.query.system.events()).filter( + TInitStoragesPara extends Record> | undefined, +>(assetHubClient: Client, eventType: 'AssetSpendApproved' | 'Paid'): Promise { + const [event] = (await assetHubClient.api.query.system.events()).filter( ({ event }: any) => event.section === 'treasury' && event.method === eventType, ) expect(event).toBeTruthy() - assert(relayClient.api.events.treasury[eventType].is(event.event)) + assert(assetHubClient.api.events.treasury[eventType].is(event.event)) return event.event.data.index.toNumber() } @@ -256,16 +247,16 @@ export async function treasurySpendForeignAssetTest< */ async function createSpendProposal< TCustom extends Record | undefined, - TInitStoragesRelay extends Record> | undefined, + TInitStoragesPara extends Record> | undefined, >( - relayClient: Client, + assetHubClient: Client, spendAmount: bigint, origin: string = SPEND_ORIGIN, validFrom: number | null = null, ) { - const spendTx = relayClient.api.tx.treasury.spend(ASSET_KIND, spendAmount, BENEFICIARY_LOCATION, validFrom) + const spendTx = assetHubClient.api.tx.treasury.spend(ASSET_KIND, spendAmount, BENEFICIARY_LOCATION, validFrom) const hexSpendTx = spendTx.method.toHex() - await scheduleInlineCallWithOrigin(relayClient, hexSpendTx, { Origins: origin }) + await scheduleInlineCallWithOrigin(assetHubClient, hexSpendTx, { Origins: origin }) } /** @@ -273,9 +264,9 @@ async function createSpendProposal< */ async function verifySystemEventAssetSpendApproved< TCustom extends Record | undefined, - TInitStoragesRelay extends Record> | undefined, ->(relayClient: Client) { - await checkSystemEvents(relayClient, { section: 'treasury', method: 'AssetSpendApproved' }) + TInitStoragesPara extends Record> | undefined, +>(assetHubClient: Client) { + await checkSystemEvents(assetHubClient, { section: 'treasury', method: 'AssetSpendApproved' }) .redact({ redactKeys: /expireAt|validFrom|index/ }) .toMatchSnapshot('treasury spend approval events') } @@ -297,33 +288,33 @@ async function verifySystemEventAssetSpendApproved< */ export async function treasurySpendBasicTest< TCustom extends Record | undefined, - TInitStoragesRelay extends Record> | undefined, ->(relayChain: Chain) { - const [relayClient] = await setupNetworks(relayChain) + TInitStoragesPara extends Record> | undefined, +>(ahChain: Chain) { + const [assetHubClient] = await setupNetworks(ahChain) // Setup test accounts - await setupTestAccounts(relayClient, ['alice', 'bob']) + await setupTestAccounts(assetHubClient, ['alice', 'bob']) // Get initial spend count - const initialSpendCount = await getSpendCount(relayClient) + const initialSpendCount = await getSpendCount(assetHubClient) // Create a spend proposal - const existentialDeposit = relayClient.api.consts.balances.existentialDeposit.toBigInt() + const existentialDeposit = assetHubClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - await createSpendProposal(relayClient, spendAmount) // validFrom will default to null and the spend call will take current block number as validFrom + await createSpendProposal(assetHubClient, spendAmount) // validFrom will default to null and the spend call will take current block number as validFrom block number - await relayClient.dev.newBlock() + await assetHubClient.dev.newBlock() // Verify that the AssetSpendApproved event was emitted - await verifySystemEventAssetSpendApproved(relayClient) + await verifySystemEventAssetSpendApproved(assetHubClient) // Verify spend count increased - const newSpendCount = await getSpendCount(relayClient) + const newSpendCount = await getSpendCount(assetHubClient) expect(newSpendCount).toBe(initialSpendCount + 1) // Verify the spend was stored correctly - const spendIndex = await getSpendIndexFromEvent(relayClient, 'AssetSpendApproved') - const spend = await relayClient.api.query.treasury.spends(spendIndex) + const spendIndex = await getSpendIndexFromEvent(assetHubClient, 'AssetSpendApproved') + const spend = await assetHubClient.api.query.treasury.spends(spendIndex) expect(spend.isSome).toBeTruthy() const spendData = spend.unwrap() @@ -331,10 +322,10 @@ export async function treasurySpendBasicTest< expect(spendData.status.isPending).toBe(true) const validFrom = spendData.validFrom.toNumber() - const payoutPeriod = relayClient.api.consts.treasury.payoutPeriod.toNumber() + const payoutPeriod = assetHubClient.api.consts.treasury.payoutPeriod.toNumber() expect(spendData.expireAt.toNumber()).toBe(validFrom + payoutPeriod) - await relayClient.teardown() + await assetHubClient.teardown() } /** @@ -342,11 +333,11 @@ export async function treasurySpendBasicTest< */ async function voidApprovedSpendProposal< TCustom extends Record | undefined, - TInitStoragesRelay extends Record> | undefined, ->(relayClient: Client, spendIndex: number) { - const removeApprovedSpendTx = relayClient.api.tx.treasury.voidSpend(spendIndex) + TInitStoragesPara extends Record> | undefined, +>(assetHubClient: Client, spendIndex: number) { + const removeApprovedSpendTx = assetHubClient.api.tx.treasury.voidSpend(spendIndex) const hexRemoveApprovedSpendTx = removeApprovedSpendTx.method.toHex() - await scheduleInlineCallWithOrigin(relayClient, hexRemoveApprovedSpendTx, { Origins: REJECT_ORIGIN }) + await scheduleInlineCallWithOrigin(assetHubClient, hexRemoveApprovedSpendTx, { Origins: REJECT_ORIGIN }) } /** @@ -354,9 +345,9 @@ async function voidApprovedSpendProposal< */ async function verifySystemEventAssetSpendVoided< TCustom extends Record | undefined, - TInitStoragesRelay extends Record> | undefined, ->(relayClient: Client) { - await checkSystemEvents(relayClient, { section: 'treasury', method: 'AssetSpendVoided' }) + TInitStoragesPara extends Record> | undefined, +>(assetHubClient: Client) { + await checkSystemEvents(assetHubClient, { section: 'treasury', method: 'AssetSpendVoided' }) .redact({ redactKeys: /index/ }) .toMatchSnapshot('treasury spend voided events') } @@ -378,34 +369,34 @@ async function verifySystemEventAssetSpendVoided< */ export async function voidApprovedTreasurySpendProposal< TCustom extends Record | undefined, - TInitStoragesRelay extends Record> | undefined, ->(relayChain: Chain) { - const [relayClient] = await setupNetworks(relayChain) + TInitStoragesPara extends Record> | undefined, +>(ahChain: Chain) { + const [assetHubClient] = await setupNetworks(ahChain) // Setup test accounts - await setupTestAccounts(relayClient, ['alice', 'bob']) + await setupTestAccounts(assetHubClient, ['alice', 'bob']) // Get initial spend count - const initialSpendCount = await getSpendCount(relayClient) + const initialSpendCount = await getSpendCount(assetHubClient) // Create a spend proposal - const existentialDeposit = relayClient.api.consts.balances.existentialDeposit.toBigInt() + const existentialDeposit = assetHubClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - await createSpendProposal(relayClient, spendAmount) + await createSpendProposal(assetHubClient, spendAmount) - await relayClient.dev.newBlock() + await assetHubClient.dev.newBlock() // Verify that the AssetSpendApproved event was emitted - await verifySystemEventAssetSpendApproved(relayClient) + await verifySystemEventAssetSpendApproved(assetHubClient) // Verify spend count increased - const newSpendCount = await getSpendCount(relayClient) + const newSpendCount = await getSpendCount(assetHubClient) expect(newSpendCount).toBe(initialSpendCount + 1) // Verify the spend was stored correctly - const spendIndex = await getSpendIndexFromEvent(relayClient, 'AssetSpendApproved') - const spend = await relayClient.api.query.treasury.spends(spendIndex) + const spendIndex = await getSpendIndexFromEvent(assetHubClient, 'AssetSpendApproved') + const spend = await assetHubClient.api.query.treasury.spends(spendIndex) expect(spend.isSome).toBeTruthy() const spendData = spend.unwrap() @@ -413,24 +404,24 @@ export async function voidApprovedTreasurySpendProposal< expect(spendData.status.isPending).toBe(true) const validFrom = spendData.validFrom.toNumber() - const payoutPeriod = relayClient.api.consts.treasury.payoutPeriod.toNumber() + const payoutPeriod = assetHubClient.api.consts.treasury.payoutPeriod.toNumber() expect(spendData.expireAt.toNumber()).toBe(validFrom + payoutPeriod) - await relayClient.dev.newBlock() + await assetHubClient.dev.newBlock() // Void the approved proposal - await voidApprovedSpendProposal(relayClient, spendIndex) + await voidApprovedSpendProposal(assetHubClient, spendIndex) - await relayClient.dev.newBlock() + await assetHubClient.dev.newBlock() // Check that AssetSpendVoided event was emitted - await verifySystemEventAssetSpendVoided(relayClient) + await verifySystemEventAssetSpendVoided(assetHubClient) // Verify the spend was removed from the storage - const spendAfter = await relayClient.api.query.treasury.spends(spendIndex) + const spendAfter = await assetHubClient.api.query.treasury.spends(spendIndex) expect(spendAfter.isNone).toBe(true) - await relayClient.teardown() + await assetHubClient.teardown() } /** @@ -438,9 +429,9 @@ export async function voidApprovedTreasurySpendProposal< */ async function sendPayoutTx< TCustom extends Record | undefined, - TInitStoragesRelay extends Record> | undefined, ->(relayClient: Client, spendIndex: number, beneficiary: KeyringPair) { - const payoutTx = relayClient.api.tx.treasury.payout(spendIndex) + TInitStoragesPara extends Record> | undefined, +>(assetHubClient: Client, spendIndex: number, beneficiary: KeyringPair) { + const payoutTx = assetHubClient.api.tx.treasury.payout(spendIndex) return await sendTransaction(payoutTx.signAsync(beneficiary)) } @@ -497,38 +488,49 @@ async function setInitialUSDTBalanceOnAssetHub< */ export async function claimTreasurySpend< TCustom extends Record | undefined, - TInitStoragesRelay extends Record> | undefined, TInitStoragesPara extends Record> | undefined, ->(relayChain: Chain, ahChain: Chain) { - const [relayClient, assetHubClient] = await setupNetworks(relayChain, ahChain) +>(ahChain: Chain) { + const [assetHubClient] = await setupNetworks(ahChain) // Setup test accounts - await setupTestAccounts(relayClient, ['alice', 'bob']) + await setupTestAccounts(assetHubClient, ['alice', 'bob']) // Ensure that Alice's account has some USDT balance on Asset Hub i.e her account should exist on Asset Hub for the payout to happen await setInitialUSDTBalanceOnAssetHub(assetHubClient, testAccounts.alice.address) // Get initial spend count - const initialSpendCount = await getSpendCount(relayClient) + // const initialSpendCount = await getSpendCount(assetHubClient) + const initialSpendCount = (await assetHubClient.api.query.treasury.spendCount()).toNumber() // Create a spend proposal - const existentialDeposit = relayClient.api.consts.balances.existentialDeposit.toBigInt() + const existentialDeposit = assetHubClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - await createSpendProposal(relayClient, spendAmount) + await createSpendProposal(assetHubClient, spendAmount) // Not working after moving to asset hub - await relayClient.dev.newBlock() + await assetHubClient.dev.newBlock() + + // check the result of dispatched event + const events = await assetHubClient.api.query.system.events() + + // Find the Dispatched event from scheduler + const dispatchedEvent = events.find((record) => { + const { event } = record + return event.section === 'scheduler' && event.method === 'Dispatched' + }) + + // assert(dispatchedEvent) not getting the dispatch event nor any extrinsic falied event or error. // Verify that the AssetSpendApproved event was emitted - await verifySystemEventAssetSpendApproved(relayClient) + await verifySystemEventAssetSpendApproved(assetHubClient) // Verify spend count increased - const newSpendCount = await getSpendCount(relayClient) + const newSpendCount = await getSpendCount(assetHubClient) expect(newSpendCount).toBe(initialSpendCount + 1) // Verify the spend was stored correctly - const spendIndex = await getSpendIndexFromEvent(relayClient, 'AssetSpendApproved') - const spend = await relayClient.api.query.treasury.spends(spendIndex) + const spendIndex = await getSpendIndexFromEvent(assetHubClient, 'AssetSpendApproved') + const spend = await assetHubClient.api.query.treasury.spends(spendIndex) expect(spend.isSome).toBeTruthy() const spendData = spend.unwrap() @@ -537,16 +539,16 @@ export async function claimTreasurySpend< const balanceAmountBefore = await getAssetHubUSDTBalanceAmount(assetHubClient, testAccounts.alice.address) - await relayClient.dev.newBlock() + await assetHubClient.dev.newBlock() // Claim the spend by the beneficiary i.e alice - const payoutEvents = await sendPayoutTx(relayClient, spendIndex, testAccounts.alice) + const payoutEvents = await sendPayoutTx(assetHubClient, spendIndex, testAccounts.alice) - await relayClient.dev.newBlock() + await assetHubClient.dev.newBlock() await verifyEventPaid(payoutEvents) - const payoutIndex = await getSpendIndexFromEvent(relayClient, 'Paid') + const payoutIndex = await getSpendIndexFromEvent(assetHubClient, 'Paid') expect(payoutIndex).toBe(spendIndex) // / treasury spend does not emit any event on AH so we need to check that Alice's balance is increased by the `amount` directly @@ -556,7 +558,7 @@ export async function claimTreasurySpend< const balanceAmountAfter = await getAssetHubUSDTBalanceAmount(assetHubClient, testAccounts.alice.address) expect(balanceAmountAfter - balanceAmountBefore).toBe(spendAmount) - await relayClient.teardown() + await assetHubClient.teardown() } async function verifyEventSpendProcessed(events: { events: Promise }) { @@ -589,38 +591,37 @@ async function sendCheckStatusTx< */ export async function checkStatusOfTreasurySpend< TCustom extends Record | undefined, - TInitStoragesRelay extends Record> | undefined, TInitStoragesPara extends Record> | undefined, ->(relayChain: Chain, ahChain: Chain) { - const [relayClient, assetHubClient] = await setupNetworks(relayChain, ahChain) +>(ahChain: Chain) { + const [assetHubClient] = await setupNetworks(ahChain) // Setup test accounts - await setupTestAccounts(relayClient, ['alice', 'bob']) + await setupTestAccounts(assetHubClient, ['alice', 'bob']) // Ensure that Alice's account has some USDT balance on Asset Hub i.e her account should exist on Asset Hub for the payout to happen await setInitialUSDTBalanceOnAssetHub(assetHubClient, testAccounts.alice.address) // Get initial spend count - const initialSpendCount = await getSpendCount(relayClient) + const initialSpendCount = await getSpendCount(assetHubClient) // Create a spend proposal - const existentialDeposit = relayClient.api.consts.balances.existentialDeposit.toBigInt() + const existentialDeposit = assetHubClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - await createSpendProposal(relayClient, spendAmount) + await createSpendProposal(assetHubClient, spendAmount) - await relayClient.dev.newBlock() + await assetHubClient.dev.newBlock() // Verify that the AssetSpendApproved event was emitted - await verifySystemEventAssetSpendApproved(relayClient) + await verifySystemEventAssetSpendApproved(assetHubClient) // Verify spend count increased - const newSpendCount = await getSpendCount(relayClient) + const newSpendCount = await getSpendCount(assetHubClient) expect(newSpendCount).toBe(initialSpendCount + 1) // Verify the spend was stored correctly - const spendIndex = await getSpendIndexFromEvent(relayClient, 'AssetSpendApproved') - const spend = await relayClient.api.query.treasury.spends(spendIndex) + const spendIndex = await getSpendIndexFromEvent(assetHubClient, 'AssetSpendApproved') + const spend = await assetHubClient.api.query.treasury.spends(spendIndex) expect(spend.isSome).toBeTruthy() const spendData = spend.unwrap() @@ -629,16 +630,16 @@ export async function checkStatusOfTreasurySpend< const balanceAmountBefore = await getAssetHubUSDTBalanceAmount(assetHubClient, testAccounts.alice.address) - await relayClient.dev.newBlock() + await assetHubClient.dev.newBlock() // Claim the spend by the beneficiary i.e alice - const payoutEvents = await sendPayoutTx(relayClient, spendIndex, testAccounts.alice) + const payoutEvents = await sendPayoutTx(assetHubClient, spendIndex, testAccounts.alice) - await relayClient.dev.newBlock() + await assetHubClient.dev.newBlock() await verifyEventPaid(payoutEvents) - const payoutIndex = await getSpendIndexFromEvent(relayClient, 'Paid') + const payoutIndex = await getSpendIndexFromEvent(assetHubClient, 'Paid') expect(payoutIndex).toBe(spendIndex) // / treasury spend does not emit any event on AH so we need to check that Alice's balance is increased by the `amount` directly @@ -648,20 +649,20 @@ export async function checkStatusOfTreasurySpend< const balanceAmountAfter = await getAssetHubUSDTBalanceAmount(assetHubClient, testAccounts.alice.address) expect(balanceAmountAfter - balanceAmountBefore).toBe(spendAmount) - await relayClient.dev.newBlock() + await assetHubClient.dev.newBlock() - const checkStatusEvents = await sendCheckStatusTx(relayClient, spendIndex) + const checkStatusEvents = await sendCheckStatusTx(assetHubClient, spendIndex) - await relayClient.dev.newBlock() + await assetHubClient.dev.newBlock() // verify SpendProcessed event await verifyEventSpendProcessed(checkStatusEvents) // verify the spend is removed from the storage - const spendAfterCheckStatus = await relayClient.api.query.treasury.spends(spendIndex) + const spendAfterCheckStatus = await assetHubClient.api.query.treasury.spends(spendIndex) expect(spendAfterCheckStatus.isNone).toBe(true) - await relayClient.teardown() + await assetHubClient.teardown() } /** @@ -677,25 +678,25 @@ export async function checkStatusOfTreasurySpend< export async function proposeExpiredSpend< TCustom extends Record | undefined, - TInitStoragesRelay extends Record> | undefined, ->(relayChain: Chain) { - const [relayClient] = await setupNetworks(relayChain) + TInitStoragesPara extends Record> | undefined, +>(ahChain: Chain) { + const [assetHubClient] = await setupNetworks(ahChain) // Setup test accounts - await setupTestAccounts(relayClient, ['alice', 'bob']) + await setupTestAccounts(assetHubClient, ['alice', 'bob']) // Create a spend proposal - const existentialDeposit = relayClient.api.consts.balances.existentialDeposit.toBigInt() + const existentialDeposit = assetHubClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - const currentBlockNumber = await relayClient.api.query.system.number() - const payoutPeriod = relayClient.api.consts.treasury.payoutPeriod.toNumber() + const currentBlockNumber = await assetHubClient.api.query.system.number() + const payoutPeriod = assetHubClient.api.consts.treasury.payoutPeriod.toNumber() const validFrom = currentBlockNumber.toNumber() - payoutPeriod - 1 // subtracting any number to ensure that the spend is expired - await createSpendProposal(relayClient, spendAmount, SPEND_ORIGIN, validFrom) + await createSpendProposal(assetHubClient, spendAmount, SPEND_ORIGIN, validFrom) - await relayClient.dev.newBlock() + await assetHubClient.dev.newBlock() // check the result of dispatched event - const events = await relayClient.api.query.system.events() + const events = await assetHubClient.api.query.system.events() // Find the Dispatched event from scheduler const dispatchedEvent = events.find((record) => { const { event } = record @@ -703,7 +704,7 @@ export async function proposeExpiredSpend< }) assert(dispatchedEvent) - assert(relayClient.api.events.scheduler.Dispatched.is(dispatchedEvent.event)) + assert(assetHubClient.api.events.scheduler.Dispatched.is(dispatchedEvent.event)) const dispatchedData = dispatchedEvent.event.data expect(dispatchedData.result.isErr).toBe(true) @@ -711,9 +712,9 @@ export async function proposeExpiredSpend< // Decode the module error to get human-readable details const dispatchError = dispatchedData.result.asErr assert(dispatchError.isModule) - expect(relayClient.api.errors.treasury.SpendExpired.is(dispatchError.asModule)).toBeTruthy() + expect(assetHubClient.api.errors.treasury.SpendExpired.is(dispatchError.asModule)).toBeTruthy() - await relayClient.teardown() + await assetHubClient.teardown() } /** @@ -728,22 +729,22 @@ export async function proposeExpiredSpend< */ export async function smalltipperTryingToSpendMoreThanTheOriginAllows< TCustom extends Record | undefined, - TInitStoragesRelay extends Record> | undefined, ->(relayChain: Chain) { - const [relayClient] = await setupNetworks(relayChain) + TInitStoragesPara extends Record> | undefined, +>(ahChain: Chain) { + const [assetHubClient] = await setupNetworks(ahChain) // Setup test accounts - await setupTestAccounts(relayClient, ['alice', 'bob']) + await setupTestAccounts(assetHubClient, ['alice', 'bob']) // Create a spend proposal - const existentialDeposit = relayClient.api.consts.balances.existentialDeposit.toBigInt() + const existentialDeposit = assetHubClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - await createSpendProposal(relayClient, spendAmount, SMALL_TIPPER_ORIGIN) // SmallTipper does not have permission to spend large amounts + await createSpendProposal(assetHubClient, spendAmount, SMALL_TIPPER_ORIGIN) // SmallTipper does not have permission to spend large amounts - await relayClient.dev.newBlock() + await assetHubClient.dev.newBlock() // check the result of dispatched event - const events = await relayClient.api.query.system.events() + const events = await assetHubClient.api.query.system.events() // Find the Dispatched event from scheduler const dispatchedEvent = events.find((record) => { const { event } = record @@ -751,7 +752,7 @@ export async function smalltipperTryingToSpendMoreThanTheOriginAllows< }) assert(dispatchedEvent) - assert(relayClient.api.events.scheduler.Dispatched.is(dispatchedEvent.event)) + assert(assetHubClient.api.events.scheduler.Dispatched.is(dispatchedEvent.event)) const dispatchedData = dispatchedEvent.event.data expect(dispatchedData.result.isErr).toBe(true) @@ -759,7 +760,7 @@ export async function smalltipperTryingToSpendMoreThanTheOriginAllows< // Decode the module error to get human-readable details const dispatchError = dispatchedData.result.asErr assert(dispatchError.isModule) - expect(relayClient.api.errors.treasury.InsufficientPermission.is(dispatchError.asModule)).toBeTruthy() + expect(assetHubClient.api.errors.treasury.InsufficientPermission.is(dispatchError.asModule)).toBeTruthy() await assetHubClient.teardown() } @@ -855,33 +856,28 @@ export function baseTreasuryE2ETests< { kind: 'test', label: 'Propose and approve a spend of treasury funds', - testFn: async () => await treasurySpendBasicTest(relayChain), + testFn: async () => await treasurySpendBasicTest(ahChain), }, - // Treasury spend proposal rejection { kind: 'test', label: 'Void previously approved spend', - testFn: async () => await voidApprovedTreasurySpendProposal(relayChain), + testFn: async () => await voidApprovedTreasurySpendProposal(ahChain), }, - // Claim a spend { kind: 'test', label: 'Claim a spend', - testFn: async () => await claimTreasurySpend(relayChain, ahChain), + testFn: async () => await claimTreasurySpend(ahChain), }, - // Check the status of the spend and remove it from the storage if processed { kind: 'test', label: 'Check status of a spend and remove it from the storage if processed', - testFn: async () => await checkStatusOfTreasurySpend(relayChain, ahChain), + testFn: async () => await checkStatusOfTreasurySpend(ahChain), }, - // proposing a expired spend { kind: 'test', label: 'Proposing a expired spend emits `SpendExpired` error', - testFn: async () => await proposeExpiredSpend(relayChain), + testFn: async () => await proposeExpiredSpend(ahChain), }, - // smalltipper trying to spend more than the origin allows emits `InsufficientPermission` error { kind: 'test', label: 'Smalltipper trying to spend more than the origin allows emits `InsufficientPermission` error', From b2cab2dc47b058aada53faf598bed1bde6f3a008 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Sun, 26 Oct 2025 16:27:21 +0530 Subject: [PATCH 22/39] blockProvide added for scheduleInlineCallWithOrigin --- packages/shared/src/treasury.ts | 45 ++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index d30df571a..5a409ed32 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -10,7 +10,7 @@ import type { Codec } from '@polkadot/types/types' import { assert, expect } from 'vitest' //import { logAllEvents } from './helpers/helper_functions.js' -import { checkEvents, checkSystemEvents, scheduleInlineCallWithOrigin } from './helpers/index.js' +import { checkEvents, checkSystemEvents, scheduleInlineCallWithOrigin, type TestConfig } from './helpers/index.js' import type { RootTestTree } from './types.js' /// ------- @@ -251,12 +251,13 @@ async function createSpendProposal< >( assetHubClient: Client, spendAmount: bigint, + testConfig: TestConfig, origin: string = SPEND_ORIGIN, validFrom: number | null = null, ) { const spendTx = assetHubClient.api.tx.treasury.spend(ASSET_KIND, spendAmount, BENEFICIARY_LOCATION, validFrom) const hexSpendTx = spendTx.method.toHex() - await scheduleInlineCallWithOrigin(assetHubClient, hexSpendTx, { Origins: origin }) + await scheduleInlineCallWithOrigin(assetHubClient, hexSpendTx, { Origins: origin }, testConfig.blockProvider) } /** @@ -289,7 +290,7 @@ async function verifySystemEventAssetSpendApproved< export async function treasurySpendBasicTest< TCustom extends Record | undefined, TInitStoragesPara extends Record> | undefined, ->(ahChain: Chain) { +>(ahChain: Chain, testConfig: TestConfig) { const [assetHubClient] = await setupNetworks(ahChain) // Setup test accounts @@ -301,7 +302,8 @@ export async function treasurySpendBasicTest< // Create a spend proposal const existentialDeposit = assetHubClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - await createSpendProposal(assetHubClient, spendAmount) // validFrom will default to null and the spend call will take current block number as validFrom block number + console.log('block provider: ', testConfig.blockProvider) + await createSpendProposal(assetHubClient, spendAmount, testConfig) // validFrom will default to null and the spend call will take current block number as validFrom block number await assetHubClient.dev.newBlock() @@ -370,7 +372,7 @@ async function verifySystemEventAssetSpendVoided< export async function voidApprovedTreasurySpendProposal< TCustom extends Record | undefined, TInitStoragesPara extends Record> | undefined, ->(ahChain: Chain) { +>(ahChain: Chain, testConfig: TestConfig) { const [assetHubClient] = await setupNetworks(ahChain) // Setup test accounts @@ -383,7 +385,7 @@ export async function voidApprovedTreasurySpendProposal< const existentialDeposit = assetHubClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - await createSpendProposal(assetHubClient, spendAmount) + await createSpendProposal(assetHubClient, spendAmount, testConfig) await assetHubClient.dev.newBlock() @@ -489,7 +491,7 @@ async function setInitialUSDTBalanceOnAssetHub< export async function claimTreasurySpend< TCustom extends Record | undefined, TInitStoragesPara extends Record> | undefined, ->(ahChain: Chain) { +>(ahChain: Chain, testConfig: TestConfig) { const [assetHubClient] = await setupNetworks(ahChain) // Setup test accounts @@ -506,7 +508,7 @@ export async function claimTreasurySpend< const existentialDeposit = assetHubClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - await createSpendProposal(assetHubClient, spendAmount) // Not working after moving to asset hub + await createSpendProposal(assetHubClient, spendAmount, testConfig) // Not working after moving to asset hub await assetHubClient.dev.newBlock() @@ -592,7 +594,7 @@ async function sendCheckStatusTx< export async function checkStatusOfTreasurySpend< TCustom extends Record | undefined, TInitStoragesPara extends Record> | undefined, ->(ahChain: Chain) { +>(ahChain: Chain, testConfig: TestConfig) { const [assetHubClient] = await setupNetworks(ahChain) // Setup test accounts @@ -608,7 +610,7 @@ export async function checkStatusOfTreasurySpend< const existentialDeposit = assetHubClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - await createSpendProposal(assetHubClient, spendAmount) + await createSpendProposal(assetHubClient, spendAmount, testConfig) await assetHubClient.dev.newBlock() @@ -679,7 +681,7 @@ export async function checkStatusOfTreasurySpend< export async function proposeExpiredSpend< TCustom extends Record | undefined, TInitStoragesPara extends Record> | undefined, ->(ahChain: Chain) { +>(ahChain: Chain, testConfig: TestConfig) { const [assetHubClient] = await setupNetworks(ahChain) // Setup test accounts @@ -691,7 +693,7 @@ export async function proposeExpiredSpend< const currentBlockNumber = await assetHubClient.api.query.system.number() const payoutPeriod = assetHubClient.api.consts.treasury.payoutPeriod.toNumber() const validFrom = currentBlockNumber.toNumber() - payoutPeriod - 1 // subtracting any number to ensure that the spend is expired - await createSpendProposal(assetHubClient, spendAmount, SPEND_ORIGIN, validFrom) + await createSpendProposal(assetHubClient, spendAmount, testConfig, SPEND_ORIGIN, validFrom) await assetHubClient.dev.newBlock() @@ -730,7 +732,7 @@ export async function proposeExpiredSpend< export async function smalltipperTryingToSpendMoreThanTheOriginAllows< TCustom extends Record | undefined, TInitStoragesPara extends Record> | undefined, ->(ahChain: Chain) { +>(ahChain: Chain, testConfig: TestConfig) { const [assetHubClient] = await setupNetworks(ahChain) // Setup test accounts @@ -739,7 +741,7 @@ export async function smalltipperTryingToSpendMoreThanTheOriginAllows< // Create a spend proposal const existentialDeposit = assetHubClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - await createSpendProposal(assetHubClient, spendAmount, SMALL_TIPPER_ORIGIN) // SmallTipper does not have permission to spend large amounts + await createSpendProposal(assetHubClient, spendAmount, testConfig, SMALL_TIPPER_ORIGIN) // SmallTipper does not have permission to spend large amounts await assetHubClient.dev.newBlock() @@ -842,7 +844,7 @@ export function baseTreasuryE2ETests< >( relayChain: Chain, ahChain: Chain, - testConfig: { testSuiteName: string; addressEncoding: number }, + testConfig: TestConfig, ): RootTestTree { return { kind: 'describe', @@ -853,35 +855,36 @@ export function baseTreasuryE2ETests< label: 'Foreign asset spend from Relay treasury is reflected on AssetHub', testFn: async () => await treasurySpendForeignAssetTest(relayChain, ahChain), }, + // yarn test treasury -t "Propose and approve a spend of treasury funds" { kind: 'test', label: 'Propose and approve a spend of treasury funds', - testFn: async () => await treasurySpendBasicTest(ahChain), + testFn: async () => await treasurySpendBasicTest(ahChain, testConfig), }, { kind: 'test', label: 'Void previously approved spend', - testFn: async () => await voidApprovedTreasurySpendProposal(ahChain), + testFn: async () => await voidApprovedTreasurySpendProposal(ahChain, testConfig), }, { kind: 'test', label: 'Claim a spend', - testFn: async () => await claimTreasurySpend(ahChain), + testFn: async () => await claimTreasurySpend(ahChain, testConfig), }, { kind: 'test', label: 'Check status of a spend and remove it from the storage if processed', - testFn: async () => await checkStatusOfTreasurySpend(ahChain), + testFn: async () => await checkStatusOfTreasurySpend(ahChain, testConfig), }, { kind: 'test', label: 'Proposing a expired spend emits `SpendExpired` error', - testFn: async () => await proposeExpiredSpend(ahChain), + testFn: async () => await proposeExpiredSpend(ahChain, testConfig), }, { kind: 'test', label: 'Smalltipper trying to spend more than the origin allows emits `InsufficientPermission` error', - testFn: async () => await smalltipperTryingToSpendMoreThanTheOriginAllows(ahChain), + testFn: async () => await smalltipperTryingToSpendMoreThanTheOriginAllows(ahChain, testConfig), }, { kind: 'test', From 8ae0eb4f4fba471990935fc825399c9af01238bf Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Tue, 28 Oct 2025 14:15:27 +0530 Subject: [PATCH 23/39] asset kind of spend changed to native --- packages/shared/src/treasury.ts | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 5a409ed32..75e2030a5 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -28,29 +28,17 @@ const TEST_ACCOUNT_BALANCE_MULTIPLIER = 10000n // 10,000x existential deposit const SPEND_AMOUNT_MULTIPLIER = 100n // 100x existential deposit // Assets pallet ID -const ASSETS_PALLET_ID = 50 +// const ASSETS_PALLET_ID = 50 // USDT asset ID const USDT_ID = 1984 +// Native asset kind for spend tests const ASSET_KIND = { v4: { location: { parents: 0, - interior: 'Here', // Asset is local to Asset Hub - }, - assetId: { - parents: 0, - interior: { - x2: [ - { - palletInstance: ASSETS_PALLET_ID, - }, - { - generalIndex: USDT_ID, - }, - ], - }, + interior: 'Here', // Native asset }, }, } as unknown as FrameSupportTokensFungibleUnionOfNativeOrWithId From 412e5b5f33c9946ccb08662f08230678db63d031 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Tue, 28 Oct 2025 14:16:04 +0530 Subject: [PATCH 24/39] sendCheckStatusTx ported to Asset hub --- packages/shared/src/treasury.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 75e2030a5..8721009b8 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -559,9 +559,9 @@ async function verifyEventSpendProcessed(events: { events: Promise | undefined, - TInitStoragesRelay extends Record> | undefined, ->(relayClient: Client, spendIndex: number) { - const checkStatusTx = relayClient.api.tx.treasury.checkStatus(spendIndex) + TInitStoragesAssetHub extends Record> | undefined, +>(assetHubClient: Client, spendIndex: number) { + const checkStatusTx = assetHubClient.api.tx.treasury.checkStatus(spendIndex) return await sendTransaction(checkStatusTx.signAsync(testAccounts.alice)) } From fe90b35bf0d84eb68cac8d8311922919027be812 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Tue, 28 Oct 2025 14:16:50 +0530 Subject: [PATCH 25/39] check_status called for treasury payouts --- packages/shared/src/treasury.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 8721009b8..c044ac3c5 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -822,6 +822,17 @@ export async function checkTreasuryPayoutsWhichAreAlreadyApprovedCanBePaid< expect(spend?.unwrap()?.status.isAttempted).toBe(true) } + // call check_status tx for each spend + for (const spendIndex of spendIndices) { + const checkStatusEvents = await sendCheckStatusTx(assetHubClient, spendIndex) + await assetHubClient.dev.newBlock() + await verifyEventSpendProcessed(checkStatusEvents) + + // verify the spend is removed from the storage + const spendAfterCheckStatus = await assetHubClient.api.query.treasury.spends(spendIndex) + expect(spendAfterCheckStatus.isNone).toBe(true) + } + await assetHubClient.teardown() } From 41c548b3effc08ddecd9cdbcf7a706d32605f0bc Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Tue, 28 Oct 2025 14:17:05 +0530 Subject: [PATCH 26/39] updated --- packages/shared/src/treasury.ts | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index c044ac3c5..b16f1df51 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -9,7 +9,7 @@ import type { Codec } from '@polkadot/types/types' import { assert, expect } from 'vitest' -//import { logAllEvents } from './helpers/helper_functions.js' +import { extractSchedulerErrorDetails, logAllEvents } from './helpers/helper_functions.js' import { checkEvents, checkSystemEvents, scheduleInlineCallWithOrigin, type TestConfig } from './helpers/index.js' import type { RootTestTree } from './types.js' @@ -295,6 +295,9 @@ export async function treasurySpendBasicTest< await assetHubClient.dev.newBlock() + // Extract and log detailed scheduler error information + await extractSchedulerErrorDetails(assetHubClient) + // Verify that the AssetSpendApproved event was emitted await verifySystemEventAssetSpendApproved(assetHubClient) @@ -500,17 +503,6 @@ export async function claimTreasurySpend< await assetHubClient.dev.newBlock() - // check the result of dispatched event - const events = await assetHubClient.api.query.system.events() - - // Find the Dispatched event from scheduler - const dispatchedEvent = events.find((record) => { - const { event } = record - return event.section === 'scheduler' && event.method === 'Dispatched' - }) - - // assert(dispatchedEvent) not getting the dispatch event nor any extrinsic falied event or error. - // Verify that the AssetSpendApproved event was emitted await verifySystemEventAssetSpendApproved(assetHubClient) @@ -885,6 +877,7 @@ export function baseTreasuryE2ETests< label: 'Smalltipper trying to spend more than the origin allows emits `InsufficientPermission` error', testFn: async () => await smalltipperTryingToSpendMoreThanTheOriginAllows(ahChain, testConfig), }, + // yarn test treasury -t "Check treasury payouts which are already approved can be paid" { kind: 'test', label: 'Check treasury payouts which are already approved can be paid', From d0ab95fc26b1ef400c466a50ba178a3d80c08e94 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Tue, 28 Oct 2025 16:56:48 +0530 Subject: [PATCH 27/39] ASSET_KIND and BENEFICIARY_LOCATION modified for assethub --- packages/shared/src/treasury.ts | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index b16f1df51..688416c3c 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -35,27 +35,37 @@ const USDT_ID = 1984 // Native asset kind for spend tests const ASSET_KIND = { - v4: { + v5: { location: { parents: 0, interior: 'Here', // Native asset }, + assetId: { + parents: 1, + interior: 'Here', + }, }, } as unknown as FrameSupportTokensFungibleUnionOfNativeOrWithId // Beneficiary location(alice) for the treasury spend const BENEFICIARY_LOCATION = { v4: { - parents: 0, - interior: { - x1: [ - { - accountId32: { - network: null, - id: testAccounts.alice.addressRaw, + location: { + parents: 0, + interior: { Here: null }, // Location is Here for same parachain + }, + accountId: { + parents: 0, + interior: { + x1: [ + { + accountId32: { + network: null, + id: testAccounts.alice.addressRaw, + }, }, - }, - ], + ], + }, }, }, } as unknown as XcmVersionedLocation From 0b2119072d433dd70ada3bbfbe68fd02fb5d668b Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Tue, 28 Oct 2025 16:57:18 +0530 Subject: [PATCH 28/39] largeSpendAmount with smalltipper --- packages/shared/src/treasury.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 688416c3c..9421e69a4 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -26,10 +26,7 @@ const SMALL_TIPPER_ORIGIN = 'SmallTipper' const TEST_ACCOUNT_BALANCE_MULTIPLIER = 10000n // 10,000x existential deposit const SPEND_AMOUNT_MULTIPLIER = 100n // 100x existential deposit - -// Assets pallet ID -// const ASSETS_PALLET_ID = 50 - +const LARGE_SPEND_AMOUNT_MULTIPLIER = 100_000_000n // 100,000x existential deposit // USDT asset ID const USDT_ID = 1984 @@ -730,8 +727,8 @@ export async function smalltipperTryingToSpendMoreThanTheOriginAllows< // Create a spend proposal const existentialDeposit = assetHubClient.api.consts.balances.existentialDeposit.toBigInt() - const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - await createSpendProposal(assetHubClient, spendAmount, testConfig, SMALL_TIPPER_ORIGIN) // SmallTipper does not have permission to spend large amounts + const largeSpendAmount = existentialDeposit * LARGE_SPEND_AMOUNT_MULTIPLIER + await createSpendProposal(assetHubClient, largeSpendAmount, testConfig, SMALL_TIPPER_ORIGIN) // SmallTipper does not have permission to spend large amounts await assetHubClient.dev.newBlock() From 798a799c1c2b94a74c07d460a019cf50dbec291b Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Tue, 28 Oct 2025 16:58:05 +0530 Subject: [PATCH 29/39] block provided added in voidSpend --- packages/shared/src/treasury.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 9421e69a4..9df568d3a 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -337,7 +337,12 @@ async function voidApprovedSpendProposal< >(assetHubClient: Client, spendIndex: number) { const removeApprovedSpendTx = assetHubClient.api.tx.treasury.voidSpend(spendIndex) const hexRemoveApprovedSpendTx = removeApprovedSpendTx.method.toHex() - await scheduleInlineCallWithOrigin(assetHubClient, hexRemoveApprovedSpendTx, { Origins: REJECT_ORIGIN }) + await scheduleInlineCallWithOrigin( + assetHubClient, + hexRemoveApprovedSpendTx, + { Origins: REJECT_ORIGIN }, + testConfig.blockProvider, + ) } /** From be17ac83039d6baa7105763829c8d01ee537047e Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Tue, 28 Oct 2025 16:59:41 +0530 Subject: [PATCH 30/39] usdt balance comparison replaced with native balance --- packages/shared/src/treasury.ts | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 9df568d3a..d1d9360ea 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -263,7 +263,7 @@ async function verifySystemEventAssetSpendApproved< TInitStoragesPara extends Record> | undefined, >(assetHubClient: Client) { await checkSystemEvents(assetHubClient, { section: 'treasury', method: 'AssetSpendApproved' }) - .redact({ redactKeys: /expireAt|validFrom|index/ }) + .redact({ redactKeys: /expireAt|validFrom|index|data/ }) .toMatchSnapshot('treasury spend approval events') } @@ -334,7 +334,7 @@ export async function treasurySpendBasicTest< async function voidApprovedSpendProposal< TCustom extends Record | undefined, TInitStoragesPara extends Record> | undefined, ->(assetHubClient: Client, spendIndex: number) { +>(assetHubClient: Client, spendIndex: number, testConfig: TestConfig) { const removeApprovedSpendTx = assetHubClient.api.tx.treasury.voidSpend(spendIndex) const hexRemoveApprovedSpendTx = removeApprovedSpendTx.method.toHex() await scheduleInlineCallWithOrigin( @@ -415,7 +415,7 @@ export async function voidApprovedTreasurySpendProposal< await assetHubClient.dev.newBlock() // Void the approved proposal - await voidApprovedSpendProposal(assetHubClient, spendIndex) + await voidApprovedSpendProposal(assetHubClient, spendIndex, testConfig) await assetHubClient.dev.newBlock() @@ -531,8 +531,8 @@ export async function claimTreasurySpend< expect(spendData.amount.toBigInt()).toBe(spendAmount) expect(spendData.status.isPending).toBe(true) - const balanceAmountBefore = await getAssetHubUSDTBalanceAmount(assetHubClient, testAccounts.alice.address) - + const balance = await assetHubClient.api.query.system.account(testAccounts.alice.address) + const balanceAmountBefore = balance.data.free.toBigInt() await assetHubClient.dev.newBlock() // Claim the spend by the beneficiary i.e alice @@ -545,12 +545,12 @@ export async function claimTreasurySpend< const payoutIndex = await getSpendIndexFromEvent(assetHubClient, 'Paid') expect(payoutIndex).toBe(spendIndex) - // / treasury spend does not emit any event on AH so we need to check that Alice's balance is increased by the `amount` directly await assetHubClient.dev.newBlock() - // Ensure that Alice's balance is increased by the `amount` - const balanceAmountAfter = await getAssetHubUSDTBalanceAmount(assetHubClient, testAccounts.alice.address) - expect(balanceAmountAfter - balanceAmountBefore).toBe(spendAmount) + // ensure that Alice's balance is increased + const balanceAfter = await assetHubClient.api.query.system.account(testAccounts.alice.address) + const balanceAmountAfter = balanceAfter.data.free.toBigInt() + expect(balanceAmountAfter - balanceAmountBefore).toBeGreaterThan(0n) await assetHubClient.teardown() } @@ -622,7 +622,8 @@ export async function checkStatusOfTreasurySpend< expect(spendData.amount.toBigInt()).toBe(spendAmount) expect(spendData.status.isPending).toBe(true) - const balanceAmountBefore = await getAssetHubUSDTBalanceAmount(assetHubClient, testAccounts.alice.address) + const balanceBefore = await assetHubClient.api.query.system.account(testAccounts.alice.address) + const balanceAmountBefore = balanceBefore.data.free.toBigInt() await assetHubClient.dev.newBlock() @@ -639,9 +640,10 @@ export async function checkStatusOfTreasurySpend< // / treasury spend does not emit any event on AH so we need to check that Alice's balance is increased by the `amount` directly await assetHubClient.dev.newBlock() - // Ensure that Alice's balance is increased by the `amount` - const balanceAmountAfter = await getAssetHubUSDTBalanceAmount(assetHubClient, testAccounts.alice.address) - expect(balanceAmountAfter - balanceAmountBefore).toBe(spendAmount) + // Ensure that Alice's balance is increased + const balanceAfter = await assetHubClient.api.query.system.account(testAccounts.alice.address) + const balanceAmountAfter = balanceAfter.data.free.toBigInt() + expect(balanceAmountAfter - balanceAmountBefore).toBeGreaterThan(0n) await assetHubClient.dev.newBlock() From fc797acc6d7d56b295900f1a618cccd5bcca12ba Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Tue, 28 Oct 2025 17:01:55 +0530 Subject: [PATCH 31/39] unused usdt funding functions removed --- packages/shared/src/treasury.ts | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index d1d9360ea..5b69f1e0a 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -297,14 +297,11 @@ export async function treasurySpendBasicTest< // Create a spend proposal const existentialDeposit = assetHubClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - console.log('block provider: ', testConfig.blockProvider) + await createSpendProposal(assetHubClient, spendAmount, testConfig) // validFrom will default to null and the spend call will take current block number as validFrom block number await assetHubClient.dev.newBlock() - // Extract and log detailed scheduler error information - await extractSchedulerErrorDetails(assetHubClient) - // Verify that the AssetSpendApproved event was emitted await verifySystemEventAssetSpendApproved(assetHubClient) @@ -449,17 +446,6 @@ async function verifyEventPaid(events: { events: Promise }) { .toMatchSnapshot('payout events') } -/** - * Helper: Get the balance amount of the account on Asset Hub for USDT - */ -async function getAssetHubUSDTBalanceAmount< - TCustom extends Record | undefined, - TInitStoragesPara extends Record> | undefined, ->(assetHubClient: Client, accountAddress: string): Promise { - const balance = await assetHubClient.api.query.assets.account(USDT_ID, accountAddress) - return balance.isNone ? 0n : balance.unwrap().balance.toBigInt() -} - /** * Helper: Set the initial balance amount of the account on Asset Hub for USDT * @@ -510,7 +496,6 @@ export async function claimTreasurySpend< // Create a spend proposal const existentialDeposit = assetHubClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - await createSpendProposal(assetHubClient, spendAmount, testConfig) // Not working after moving to asset hub await assetHubClient.dev.newBlock() @@ -592,9 +577,6 @@ export async function checkStatusOfTreasurySpend< // Setup test accounts await setupTestAccounts(assetHubClient, ['alice', 'bob']) - // Ensure that Alice's account has some USDT balance on Asset Hub i.e her account should exist on Asset Hub for the payout to happen - await setInitialUSDTBalanceOnAssetHub(assetHubClient, testAccounts.alice.address) - // Get initial spend count const initialSpendCount = await getSpendCount(assetHubClient) @@ -637,7 +619,6 @@ export async function checkStatusOfTreasurySpend< const payoutIndex = await getSpendIndexFromEvent(assetHubClient, 'Paid') expect(payoutIndex).toBe(spendIndex) - // / treasury spend does not emit any event on AH so we need to check that Alice's balance is increased by the `amount` directly await assetHubClient.dev.newBlock() // Ensure that Alice's balance is increased From 0faeb762c949a6d576b73e31df09cba1676ffef0 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Tue, 28 Oct 2025 17:02:44 +0530 Subject: [PATCH 32/39] initial usdt funding removed --- packages/shared/src/treasury.ts | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 5b69f1e0a..895c1e892 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -446,22 +446,6 @@ async function verifyEventPaid(events: { events: Promise }) { .toMatchSnapshot('payout events') } -/** - * Helper: Set the initial balance amount of the account on Asset Hub for USDT - * - * This is required to ensure that the account exists on Asset Hub for the payout to happen - */ -async function setInitialUSDTBalanceOnAssetHub< - TCustom extends Record | undefined, - TInitStoragesPara extends Record> | undefined, ->(assetHubClient: Client, accountAddress: string): Promise { - await assetHubClient.dev.setStorage({ - Assets: { - account: [[[USDT_ID, accountAddress], { balance: 1000e6 }]], - }, - }) -} - /** * Test: Claim a spend * @@ -486,12 +470,8 @@ export async function claimTreasurySpend< // Setup test accounts await setupTestAccounts(assetHubClient, ['alice', 'bob']) - // Ensure that Alice's account has some USDT balance on Asset Hub i.e her account should exist on Asset Hub for the payout to happen - await setInitialUSDTBalanceOnAssetHub(assetHubClient, testAccounts.alice.address) - // Get initial spend count - // const initialSpendCount = await getSpendCount(assetHubClient) - const initialSpendCount = (await assetHubClient.api.query.treasury.spendCount()).toNumber() + const initialSpendCount = await getSpendCount(assetHubClient) // Create a spend proposal const existentialDeposit = assetHubClient.api.consts.balances.existentialDeposit.toBigInt() From 53594e61efe7b1b4c91ba096abd8a3cc372da816 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Tue, 28 Oct 2025 17:05:18 +0530 Subject: [PATCH 33/39] old test treasurySpendForeignAssetTest for relay chain removed --- packages/shared/src/treasury.ts | 131 ++------------------------------ 1 file changed, 5 insertions(+), 126 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 895c1e892..3347e415e 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -9,7 +9,11 @@ import type { Codec } from '@polkadot/types/types' import { assert, expect } from 'vitest' -import { extractSchedulerErrorDetails, logAllEvents } from './helpers/helper_functions.js' +// import { +// extractExtrinsicFailedErrorDetails, +// extractSchedulerErrorDetails, +// logAllEvents, +// } from './helpers/helper_functions.js' import { checkEvents, checkSystemEvents, scheduleInlineCallWithOrigin, type TestConfig } from './helpers/index.js' import type { RootTestTree } from './types.js' @@ -117,126 +121,6 @@ async function getSpendIndexFromEvent< return event.event.data.index.toNumber() } -/** - * Test that a foreign asset spend from the Relay treasury is reflected on the AssetHub. - * - * 1. Approve a spend from the Relay treasury - * 2. Payout the spend from the Relay treasury - * 3. Check that the spend shows in the AssetHub - */ -export async function treasurySpendForeignAssetTest< - TCustom extends Record | undefined, - TInitStoragesRelay extends Record> | undefined, - TInitStoragesPara extends Record> | undefined, ->(relayChain: Chain, ahChain: Chain) { - const [relayClient, assetHubClient] = await setupNetworks(relayChain, ahChain) - - await relayClient.dev.setStorage({ - System: { - account: [ - // give Alice some DOTs so that she can sign a payout transaction. - [[testAccounts.alice.address], { providers: 1, data: { free: 10000e10 } }], - ], - }, - }) - const ASSET_HUB_PARA_ID = 1000 - const ASSETS_PALLET_ID = 50 - const USDT_ID = 1984 - const balanceBefore = await assetHubClient.api.query.assets.account(USDT_ID, testAccounts.alice.address) - - // amount is encoded into the call - const amount = 123123123123n - const assetKind = { - v4: { - location: { - parents: 0, - interior: { - x1: [ - { - parachain: ASSET_HUB_PARA_ID, - }, - ], - }, - }, - assetId: { - parents: 0, - interior: { - x2: [ - { - palletInstance: ASSETS_PALLET_ID, - }, - { - generalIndex: USDT_ID, - }, - ], - }, - }, - }, - } as unknown as FrameSupportTokensFungibleUnionOfNativeOrWithId - const beneficiary = { - v4: { - parents: 0, - interior: { - x1: [ - { - accountId32: { - network: null, - id: testAccounts.alice.addressRaw, - }, - }, - ], - }, - }, - } as unknown as XcmVersionedLocation - // validFrom - null, which means immediately. - const call = relayClient.api.tx.treasury.spend(assetKind, amount, beneficiary, null) - const hexCall = call.method.toHex() - await scheduleInlineCallWithOrigin(relayClient, hexCall, { Origins: 'BigSpender' }) - await relayClient.dev.newBlock() - await checkSystemEvents(relayClient, { section: 'treasury', method: 'AssetSpendApproved' }) - // values (e.g. index) inside data increase over time, - // PET framework often rounds them. - // Tests will be flaky if we don't redact them. - .redact({ - redactKeys: /expireAt|validFrom|index/, - number: false, - }) - .toMatchSnapshot('treasury spend approval events') - - // filter events to find an index to payout - const [assetSpendApprovedEvent] = (await relayClient.api.query.system.events()).filter( - ({ event }) => event.section === 'treasury' && event.method === 'AssetSpendApproved', - ) - expect(assetSpendApprovedEvent).toBeDefined() - assert(relayClient.api.events.treasury.AssetSpendApproved.is(assetSpendApprovedEvent.event)) - const spendIndex = assetSpendApprovedEvent.event.data.index.toNumber() - - // payout - const payoutEvents = await sendTransaction( - relayClient.api.tx.treasury.payout(spendIndex).signAsync(testAccounts.alice), - ) - - // create blocks on RC and AH to ensure that payout is properly processed - await relayClient.dev.newBlock() - await checkEvents(payoutEvents, { section: 'treasury', method: 'Paid' }) - .redact({ redactKeys: /paymentId|index/ }) - .toMatchSnapshot('payout events') - const [paidEvent] = (await relayClient.api.query.system.events()).filter( - ({ event }) => event.section === 'treasury' && event.method === 'Paid', - ) - expect(paidEvent).toBeDefined() - assert(relayClient.api.events.treasury.Paid.is(paidEvent.event)) - const payoutIndex = paidEvent.event.data.index.toNumber() - expect(payoutIndex).toBe(spendIndex) - - // treasury spend does not emit any event on AH so we need to check that Alice's balance is increased by the `amount` directly - await assetHubClient.dev.newBlock() - const balanceAfter = await assetHubClient.api.query.assets.account(USDT_ID, testAccounts.alice.address) - const balanceAfterAmount = balanceAfter.isNone ? 0n : balanceAfter.unwrap().balance.toBigInt() - const balanceBeforeAmount = balanceBefore.isNone ? 0n : balanceBefore.unwrap().balance.toBigInt() - expect(balanceAfterAmount - balanceBeforeAmount).toBe(amount) -} - /** * Helper: Creates and schedules a treasury spend proposal */ @@ -816,11 +700,6 @@ export function baseTreasuryE2ETests< kind: 'describe', label: testConfig.testSuiteName, children: [ - { - kind: 'test', - label: 'Foreign asset spend from Relay treasury is reflected on AssetHub', - testFn: async () => await treasurySpendForeignAssetTest(relayChain, ahChain), - }, // yarn test treasury -t "Propose and approve a spend of treasury funds" { kind: 'test', From 4d5838ab819322801cab2f347cca852be3634376 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Tue, 28 Oct 2025 17:07:46 +0530 Subject: [PATCH 34/39] lint fixed --- packages/shared/src/treasury.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 3347e415e..1690f982a 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -9,11 +9,6 @@ import type { Codec } from '@polkadot/types/types' import { assert, expect } from 'vitest' -// import { -// extractExtrinsicFailedErrorDetails, -// extractSchedulerErrorDetails, -// logAllEvents, -// } from './helpers/helper_functions.js' import { checkEvents, checkSystemEvents, scheduleInlineCallWithOrigin, type TestConfig } from './helpers/index.js' import type { RootTestTree } from './types.js' @@ -31,8 +26,6 @@ const TEST_ACCOUNT_BALANCE_MULTIPLIER = 10000n // 10,000x existential deposit const SPEND_AMOUNT_MULTIPLIER = 100n // 100x existential deposit const LARGE_SPEND_AMOUNT_MULTIPLIER = 100_000_000n // 100,000x existential deposit -// USDT asset ID -const USDT_ID = 1984 // Native asset kind for spend tests const ASSET_KIND = { @@ -723,15 +716,14 @@ export function baseTreasuryE2ETests< }, { kind: 'test', - label: 'Proposing a expired spend emits `SpendExpired` error', + label: 'Proposing a expired spend emits SpendExpired error', testFn: async () => await proposeExpiredSpend(ahChain, testConfig), }, { kind: 'test', - label: 'Smalltipper trying to spend more than the origin allows emits `InsufficientPermission` error', + label: 'Smalltipper trying to spend more than the origin allows emits InsufficientPermission error', testFn: async () => await smalltipperTryingToSpendMoreThanTheOriginAllows(ahChain, testConfig), }, - // yarn test treasury -t "Check treasury payouts which are already approved can be paid" { kind: 'test', label: 'Check treasury payouts which are already approved can be paid', From 1a29ecbde624a218bb9ea948837797299ba91b6f Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Tue, 28 Oct 2025 17:12:47 +0530 Subject: [PATCH 35/39] snapshot for treasury assethub pallet --- .../assetHubKusama.treasury.e2e.test.ts.snap | 103 ++++++++++++++++++ packages/shared/src/treasury.ts | 2 +- 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 packages/kusama/src/__snapshots__/assetHubKusama.treasury.e2e.test.ts.snap diff --git a/packages/kusama/src/__snapshots__/assetHubKusama.treasury.e2e.test.ts.snap b/packages/kusama/src/__snapshots__/assetHubKusama.treasury.e2e.test.ts.snap new file mode 100644 index 000000000..486c23fb3 --- /dev/null +++ b/packages/kusama/src/__snapshots__/assetHubKusama.treasury.e2e.test.ts.snap @@ -0,0 +1,103 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Kusama Asset Hub Treasury > Check status of a spend and remove it from the storage if processed > payout events 1`] = ` +[ + { + "data": { + "index": "(redacted)", + "paymentId": "(redacted)", + }, + "method": "Paid", + "section": "treasury", + }, +] +`; + +exports[`Kusama Asset Hub Treasury > Check status of a spend and remove it from the storage if processed > spend processed events 1`] = ` +[ + { + "data": { + "index": "(redacted)", + }, + "method": "SpendProcessed", + "section": "treasury", + }, +] +`; + +exports[`Kusama Asset Hub Treasury > Check status of a spend and remove it from the storage if processed > treasury spend approval events 1`] = ` +[ + { + "data": "(redacted)", + "method": "AssetSpendApproved", + "section": "treasury", + }, +] +`; + +exports[`Kusama Asset Hub Treasury > Check treasury payouts which are already approved can be paid > spend processed events 1`] = ` +[ + { + "data": { + "index": "(redacted)", + }, + "method": "SpendProcessed", + "section": "treasury", + }, +] +`; + +exports[`Kusama Asset Hub Treasury > Claim a spend > payout events 1`] = ` +[ + { + "data": { + "index": "(redacted)", + "paymentId": "(redacted)", + }, + "method": "Paid", + "section": "treasury", + }, +] +`; + +exports[`Kusama Asset Hub Treasury > Claim a spend > treasury spend approval events 1`] = ` +[ + { + "data": "(redacted)", + "method": "AssetSpendApproved", + "section": "treasury", + }, +] +`; + +exports[`Kusama Asset Hub Treasury > Propose and approve a spend of treasury funds > treasury spend approval events 1`] = ` +[ + { + "data": "(redacted)", + "method": "AssetSpendApproved", + "section": "treasury", + }, +] +`; + +exports[`Kusama Asset Hub Treasury > Void previously approved spend > treasury spend approval events 1`] = ` +[ + { + "data": "(redacted)", + "method": "AssetSpendApproved", + "section": "treasury", + }, +] +`; + +exports[`Kusama Asset Hub Treasury > Void previously approved spend > treasury spend voided events 1`] = ` +[ + { + "data": { + "index": "(redacted)", + }, + "method": "AssetSpendVoided", + "section": "treasury", + }, +] +`; diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 1690f982a..68750e27e 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -353,7 +353,7 @@ export async function claimTreasurySpend< // Create a spend proposal const existentialDeposit = assetHubClient.api.consts.balances.existentialDeposit.toBigInt() const spendAmount = existentialDeposit * SPEND_AMOUNT_MULTIPLIER - await createSpendProposal(assetHubClient, spendAmount, testConfig) // Not working after moving to asset hub + await createSpendProposal(assetHubClient, spendAmount, testConfig) await assetHubClient.dev.newBlock() From 1c46b4ba2e63795aefce392e87891244bccef322 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Tue, 28 Oct 2025 19:52:58 +0530 Subject: [PATCH 36/39] assetHubPolkadot e2e module added --- .../src/assetHubPolkadot.treasury.e2e.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 packages/polkadot/src/assetHubPolkadot.treasury.e2e.test.ts diff --git a/packages/polkadot/src/assetHubPolkadot.treasury.e2e.test.ts b/packages/polkadot/src/assetHubPolkadot.treasury.e2e.test.ts new file mode 100644 index 000000000..49438a6c9 --- /dev/null +++ b/packages/polkadot/src/assetHubPolkadot.treasury.e2e.test.ts @@ -0,0 +1,11 @@ +import { assetHubPolkadot, polkadot } from '@e2e-test/networks/chains' +import { baseTreasuryE2ETests, type ParaTestConfig, registerTestTree } from '@e2e-test/shared' + +const testConfig: ParaTestConfig = { + testSuiteName: 'Polkadot Asset Hub Treasury', + addressEncoding: 0, + blockProvider: 'NonLocal', + asyncBacking: 'Enabled', +} + +registerTestTree(baseTreasuryE2ETests(polkadot, assetHubPolkadot, testConfig)) From 69e21e525d8d43b065e83c07194e41f69e6ae4d0 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Wed, 29 Oct 2025 00:23:25 +0530 Subject: [PATCH 37/39] fixed tests failing on the PAH due to multiple dispatched events --- packages/shared/src/treasury.ts | 65 ++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 68750e27e..976889fb5 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -532,22 +532,31 @@ export async function proposeExpiredSpend< // check the result of dispatched event const events = await assetHubClient.api.query.system.events() // Find the Dispatched event from scheduler - const dispatchedEvent = events.find((record) => { + const dispatchedEvents = events.filter((record) => { const { event } = record return event.section === 'scheduler' && event.method === 'Dispatched' }) - assert(dispatchedEvent) - assert(assetHubClient.api.events.scheduler.Dispatched.is(dispatchedEvent.event)) - - const dispatchedData = dispatchedEvent.event.data - expect(dispatchedData.result.isErr).toBe(true) - - // Decode the module error to get human-readable details - const dispatchError = dispatchedData.result.asErr - assert(dispatchError.isModule) - expect(assetHubClient.api.errors.treasury.SpendExpired.is(dispatchError.asModule)).toBeTruthy() - + assert(dispatchedEvents.length > 0) + // the spend is expired, at least one of the dispatched events should be an error with SpendExpired + let foundSpendExpiredError = false + for (const dispatchedEvent of dispatchedEvents) { + assert(assetHubClient.api.events.scheduler.Dispatched.is(dispatchedEvent.event)) + const dispatchedData = dispatchedEvent.event.data + + // if the result is an error + if (dispatchedData.result.isErr) { + const dispatchError = dispatchedData.result.asErr + if (dispatchError.isModule) { + // Check if this is the SpendExpired error + if (assetHubClient.api.errors.treasury.SpendExpired.is(dispatchError.asModule)) { + foundSpendExpiredError = true + } + } + } + } + // Ensure at least one error was SpendExpired + assert(foundSpendExpiredError, 'Expected at least one Dispatched event to have a SpendExpired error') await assetHubClient.teardown() } @@ -580,21 +589,33 @@ export async function smalltipperTryingToSpendMoreThanTheOriginAllows< // check the result of dispatched event const events = await assetHubClient.api.query.system.events() // Find the Dispatched event from scheduler - const dispatchedEvent = events.find((record) => { + const dispatchedEvents = events.filter((record) => { const { event } = record return event.section === 'scheduler' && event.method === 'Dispatched' }) - assert(dispatchedEvent) - assert(assetHubClient.api.events.scheduler.Dispatched.is(dispatchedEvent.event)) - - const dispatchedData = dispatchedEvent.event.data - expect(dispatchedData.result.isErr).toBe(true) + assert(dispatchedEvents.length > 0) + let foundInsufficientPermissionError = false + // check if at least one of the dispatched events is an error with InsufficientPermission + for (const dispatchedEvent of dispatchedEvents) { + assert(assetHubClient.api.events.scheduler.Dispatched.is(dispatchedEvent.event)) + const dispatchedData = dispatchedEvent.event.data + + // if the result is an error + if (dispatchedData.result.isErr) { + const dispatchError = dispatchedData.result.asErr + assert(dispatchError.isModule) + if (assetHubClient.api.errors.treasury.InsufficientPermission.is(dispatchError.asModule)) { + foundInsufficientPermissionError = true + } + } + } - // Decode the module error to get human-readable details - const dispatchError = dispatchedData.result.asErr - assert(dispatchError.isModule) - expect(assetHubClient.api.errors.treasury.InsufficientPermission.is(dispatchError.asModule)).toBeTruthy() + // Ensure at least one error was InsufficientPermission + assert( + foundInsufficientPermissionError, + 'Expected at least one Dispatched event to have a InsufficientPermission error', + ) await assetHubClient.teardown() } From 68d482c897ba504da3041c4b2ba9f970807a350c Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Wed, 29 Oct 2025 00:26:14 +0530 Subject: [PATCH 38/39] refac --- packages/shared/src/treasury.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/shared/src/treasury.ts b/packages/shared/src/treasury.ts index 976889fb5..9ab63c577 100644 --- a/packages/shared/src/treasury.ts +++ b/packages/shared/src/treasury.ts @@ -651,11 +651,10 @@ export async function checkTreasuryPayoutsWhichAreAlreadyApprovedCanBePaid< // filter those spends which are pending or failed and is neither expired nor early payout const pendingOrFailedSpends = spends.filter((spend) => { const spendData = spend[1]?.unwrap() - return ( - (spendData?.status.isPending || spendData?.status.isFailed) && // not pending or failed - spendData?.validFrom.toNumber() < currentRelayChainBlockNumber && //not early payout - spendData?.expireAt.toNumber() > currentRelayChainBlockNumber // not expired - ) + const isSpendPendingOrFailed = spendData?.status.isPending || spendData?.status.isFailed + const isSpendNotEarlyPayout = spendData?.validFrom.toNumber() < currentRelayChainBlockNumber + const isSpendNotExpired = currentRelayChainBlockNumber < spendData?.expireAt.toNumber() + return isSpendPendingOrFailed && isSpendNotEarlyPayout && isSpendNotExpired }) await assetHubClient.dev.newBlock() @@ -714,7 +713,6 @@ export function baseTreasuryE2ETests< kind: 'describe', label: testConfig.testSuiteName, children: [ - // yarn test treasury -t "Propose and approve a spend of treasury funds" { kind: 'test', label: 'Propose and approve a spend of treasury funds', From befe367db3f53341af4adc4fec3d7d3479aca1a6 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Wed, 29 Oct 2025 00:28:20 +0530 Subject: [PATCH 39/39] snaps assetHubPolkadot treasury updated --- ...assetHubPolkadot.treasury.e2e.test.ts.snap | 91 +++++ .../polkadot.treasury.e2e.test.ts.snap | 349 ------------------ 2 files changed, 91 insertions(+), 349 deletions(-) create mode 100644 packages/polkadot/src/__snapshots__/assetHubPolkadot.treasury.e2e.test.ts.snap delete mode 100644 packages/polkadot/src/__snapshots__/polkadot.treasury.e2e.test.ts.snap diff --git a/packages/polkadot/src/__snapshots__/assetHubPolkadot.treasury.e2e.test.ts.snap b/packages/polkadot/src/__snapshots__/assetHubPolkadot.treasury.e2e.test.ts.snap new file mode 100644 index 000000000..b770b58a6 --- /dev/null +++ b/packages/polkadot/src/__snapshots__/assetHubPolkadot.treasury.e2e.test.ts.snap @@ -0,0 +1,91 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Polkadot Asset Hub Treasury > Check status of a spend and remove it from the storage if processed > payout events 1`] = ` +[ + { + "data": { + "index": "(redacted)", + "paymentId": "(redacted)", + }, + "method": "Paid", + "section": "treasury", + }, +] +`; + +exports[`Polkadot Asset Hub Treasury > Check status of a spend and remove it from the storage if processed > spend processed events 1`] = ` +[ + { + "data": { + "index": "(redacted)", + }, + "method": "SpendProcessed", + "section": "treasury", + }, +] +`; + +exports[`Polkadot Asset Hub Treasury > Check status of a spend and remove it from the storage if processed > treasury spend approval events 1`] = ` +[ + { + "data": "(redacted)", + "method": "AssetSpendApproved", + "section": "treasury", + }, +] +`; + +exports[`Polkadot Asset Hub Treasury > Claim a spend > payout events 1`] = ` +[ + { + "data": { + "index": "(redacted)", + "paymentId": "(redacted)", + }, + "method": "Paid", + "section": "treasury", + }, +] +`; + +exports[`Polkadot Asset Hub Treasury > Claim a spend > treasury spend approval events 1`] = ` +[ + { + "data": "(redacted)", + "method": "AssetSpendApproved", + "section": "treasury", + }, +] +`; + +exports[`Polkadot Asset Hub Treasury > Propose and approve a spend of treasury funds > treasury spend approval events 1`] = ` +[ + { + "data": "(redacted)", + "method": "AssetSpendApproved", + "section": "treasury", + }, +] +`; + +exports[`Polkadot Asset Hub Treasury > Void previously approved spend > treasury spend approval events 1`] = ` +[ + { + "data": "(redacted)", + "method": "AssetSpendApproved", + "section": "treasury", + }, +] +`; + +exports[`Polkadot Asset Hub Treasury > Void previously approved spend > treasury spend voided events 1`] = ` +[ + { + "data": { + "index": "(redacted)", + }, + "method": "AssetSpendVoided", + "section": "treasury", + }, +] +`; diff --git a/packages/polkadot/src/__snapshots__/polkadot.treasury.e2e.test.ts.snap b/packages/polkadot/src/__snapshots__/polkadot.treasury.e2e.test.ts.snap deleted file mode 100644 index 4e4649962..000000000 --- a/packages/polkadot/src/__snapshots__/polkadot.treasury.e2e.test.ts.snap +++ /dev/null @@ -1,349 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Polkadot Treasury > Check status of a spend and remove it from the storage if processed > payout events 1`] = ` -[ - { - "data": { - "index": "(redacted)", - "paymentId": "(redacted)", - }, - "method": "Paid", - "section": "treasury", - }, -] -`; - -exports[`Polkadot Treasury > Check status of a spend and remove it from the storage if processed > spend processed events 1`] = ` -[ - { - "data": { - "index": "(redacted)", - }, - "method": "SpendProcessed", - "section": "treasury", - }, -] -`; - -exports[`Polkadot Treasury > Check status of a spend and remove it from the storage if processed > treasury spend approval events 1`] = ` -[ - { - "data": { - "amount": 1000000000000, - "assetKind": { - "V4": { - "assetId": { - "interior": { - "X2": [ - { - "PalletInstance": 50, - }, - { - "GeneralIndex": "(rounded 2000)", - }, - ], - }, - "parents": 0, - }, - "location": { - "interior": { - "X1": [ - { - "Parachain": 1000, - }, - ], - }, - "parents": 0, - }, - }, - }, - "beneficiary": { - "V4": { - "interior": { - "X1": [ - { - "AccountId32": { - "id": "(hash)", - "network": null, - }, - }, - ], - }, - "parents": 0, - }, - }, - "expireAt": "(redacted)", - "index": "(redacted)", - "validFrom": "(redacted)", - }, - "method": "AssetSpendApproved", - "section": "treasury", - }, -] -`; - -exports[`Polkadot Treasury > Claim a spend > payout events 1`] = ` -[ - { - "data": { - "index": "(redacted)", - "paymentId": "(redacted)", - }, - "method": "Paid", - "section": "treasury", - }, -] -`; - -exports[`Polkadot Treasury > Claim a spend > treasury spend approval events 1`] = ` -[ - { - "data": { - "amount": 1000000000000, - "assetKind": { - "V4": { - "assetId": { - "interior": { - "X2": [ - { - "PalletInstance": 50, - }, - { - "GeneralIndex": "(rounded 2000)", - }, - ], - }, - "parents": 0, - }, - "location": { - "interior": { - "X1": [ - { - "Parachain": 1000, - }, - ], - }, - "parents": 0, - }, - }, - }, - "beneficiary": { - "V4": { - "interior": { - "X1": [ - { - "AccountId32": { - "id": "(hash)", - "network": null, - }, - }, - ], - }, - "parents": 0, - }, - }, - "expireAt": "(redacted)", - "index": "(redacted)", - "validFrom": "(redacted)", - }, - "method": "AssetSpendApproved", - "section": "treasury", - }, -] -`; - -exports[`Polkadot Treasury > Foreign asset spend from Relay treasury is reflected on AssetHub > payout events 1`] = ` -[ - { - "data": { - "index": "(redacted)", - "paymentId": "(redacted)", - }, - "method": "Paid", - "section": "treasury", - }, -] -`; - -exports[`Polkadot Treasury > Foreign asset spend from Relay treasury is reflected on AssetHub > treasury spend approval events 1`] = ` -[ - { - "data": { - "amount": "123,123,123,123", - "assetKind": { - "V4": { - "assetId": { - "interior": { - "X2": [ - { - "PalletInstance": "50", - }, - { - "GeneralIndex": "1,984", - }, - ], - }, - "parents": "0", - }, - "location": { - "interior": { - "X1": [ - { - "Parachain": "1,000", - }, - ], - }, - "parents": "0", - }, - }, - }, - "beneficiary": { - "V4": { - "interior": { - "X1": [ - { - "AccountId32": { - "id": "(hash)", - "network": null, - }, - }, - ], - }, - "parents": "0", - }, - }, - "expireAt": "(redacted)", - "index": "(redacted)", - "validFrom": "(redacted)", - }, - "method": "AssetSpendApproved", - "section": "treasury", - }, -] -`; - -exports[`Polkadot Treasury > Propose and approve a spend of treasury funds > treasury spend approval events 1`] = ` -[ - { - "data": { - "amount": 1000000000000, - "assetKind": { - "V4": { - "assetId": { - "interior": { - "X2": [ - { - "PalletInstance": 50, - }, - { - "GeneralIndex": "(rounded 2000)", - }, - ], - }, - "parents": 0, - }, - "location": { - "interior": { - "X1": [ - { - "Parachain": 1000, - }, - ], - }, - "parents": 0, - }, - }, - }, - "beneficiary": { - "V4": { - "interior": { - "X1": [ - { - "AccountId32": { - "id": "(hash)", - "network": null, - }, - }, - ], - }, - "parents": 0, - }, - }, - "expireAt": "(redacted)", - "index": "(redacted)", - "validFrom": "(redacted)", - }, - "method": "AssetSpendApproved", - "section": "treasury", - }, -] -`; - -exports[`Polkadot Treasury > Void previously approved spend > treasury spend approval events 1`] = ` -[ - { - "data": { - "amount": 1000000000000, - "assetKind": { - "V4": { - "assetId": { - "interior": { - "X2": [ - { - "PalletInstance": 50, - }, - { - "GeneralIndex": "(rounded 2000)", - }, - ], - }, - "parents": 0, - }, - "location": { - "interior": { - "X1": [ - { - "Parachain": 1000, - }, - ], - }, - "parents": 0, - }, - }, - }, - "beneficiary": { - "V4": { - "interior": { - "X1": [ - { - "AccountId32": { - "id": "(hash)", - "network": null, - }, - }, - ], - }, - "parents": 0, - }, - }, - "expireAt": "(redacted)", - "index": "(redacted)", - "validFrom": "(redacted)", - }, - "method": "AssetSpendApproved", - "section": "treasury", - }, -] -`; - -exports[`Polkadot Treasury > Void previously approved spend > treasury spend voided events 1`] = ` -[ - { - "data": { - "index": "(redacted)", - }, - "method": "AssetSpendVoided", - "section": "treasury", - }, -] -`;