From a3ae81705957f8dcc36b7546701cb81b1bb3bfa0 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Mon, 27 Oct 2025 21:46:11 +0530 Subject: [PATCH 01/18] treasury payouts post migration test --- .../treasury_payout/treasury_payouts.ts | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 migration-tests/treasury_payout/treasury_payouts.ts diff --git a/migration-tests/treasury_payout/treasury_payouts.ts b/migration-tests/treasury_payout/treasury_payouts.ts new file mode 100644 index 00000000..65155c1e --- /dev/null +++ b/migration-tests/treasury_payout/treasury_payouts.ts @@ -0,0 +1,187 @@ +import '@polkadot/api-augment'; +import '@polkadot/types-augment'; +import { sendTransaction, setupNetworks } from '@acala-network/chopsticks-testing'; +import { Keyring } from '@polkadot/api'; +import { cryptoWaitReady } from '@polkadot/util-crypto'; +import { logger } from '../../shared/logger.js'; +import assert from 'assert'; + +// Wait for crypto to be ready before creating Keyring +await cryptoWaitReady(); +const keyring = new Keyring({ type: 'sr25519' }); +const alicePair = keyring.addFromUri('//Alice'); + +export interface NetworkConfig { + relayEndpoint: string; + relayPort: number; + assetHubEndpoint: string; + assetHubPort: number; +} + + +/** + * @param networkName - 'kusama' or 'polkadot' + * @param config - Network configuration with endpoints and ports + */ +async function testTreasuryPayouts(networkName: 'kusama' | 'polkadot', config: NetworkConfig): Promise { + logger.debug(`Starting treasury payouts test on forked ${networkName} network`); + + // Setup networks based on configuration + const networks = await setupNetworks({ + [networkName]: { + endpoint: config.relayEndpoint, + port: config.relayPort, + }, + [networkName === 'kusama' ? 'assetHubKusama' : 'assetHubPolkadot']: { + endpoint: config.assetHubEndpoint, + port: config.assetHubPort, + }, + }); + + const relayChain = networkName === 'kusama' + ? (networks as any).kusama + : (networks as any).polkadot; + const assetHub = networkName === 'kusama' + ? (networks as any).assetHubKusama + : (networks as any).assetHubPolkadot; + + try { + // Fund Alice account for transaction fees + const aliceAddress = '15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5'; + const fundingAmount = 1000e10; + + await assetHub.dev.setStorage({ + System: { + account: [ + [ + [aliceAddress], + { + providers: 1, + data: { + free: fundingAmount + } + } + ] + ] + } + }); + + logger.debug('✅ Alice account funded successfully'); + + // Get all spends from Asset Hub + const spends = await assetHub.api.query.treasury.spends.entries(); + logger.debug(`Found ${spends.length} total spends`); + + // Get current relay chain block number + const currentRelayChainBlockNumber = (await relayChain.api.query.system.number()).toNumber(); + logger.debug(`Current relay chain block number: ${currentRelayChainBlockNumber}`); + + // Filter spends which are pending or failed and are neither expired nor early payout + const pendingOrFailedSpends = spends.filter((spend: any) => { + const spendData = spend[1]?.unwrap(); + return ( + (spendData?.status.isPending || spendData?.status.isFailed) && // pending or failed + spendData?.validFrom.toNumber() < currentRelayChainBlockNumber && // not early payout + spendData?.expireAt.toNumber() > currentRelayChainBlockNumber // not expired + ); + }); + + logger.debug(`Found ${pendingOrFailedSpends.length} eligible spends for payout testing`); + + if (pendingOrFailedSpends.length === 0) { + logger.info('No eligible spends found for payout testing'); + return; + } + + // Test payout for each eligible spend + for (const spend of pendingOrFailedSpends) { + const spendIndex = spend[0].toHuman?.() as number; + + try { + // Create and sign the payout transaction + const payoutTx = assetHub.api.tx.treasury.payout(spendIndex); + await sendTransaction(payoutTx.signAsync(alicePair)); + + await assetHub.api.rpc('dev_newBlock', { count: 1 }); + + // Check for Paid event + const events = await assetHub.api.query.system.events(); + const paidEvent = events.find((record: any) => { + const { event } = record; + return event.section === 'treasury' && event.method === 'Paid'; + }); + + assert(paidEvent, `Paid event is not found for spend ${spendIndex} Event: ${events.map((record: any) => record.event.toHuman()).join(', ')}`); + assert(assetHub.api.events.treasury.Paid.is(paidEvent.event), `Paid event is not found for spend ${spendIndex} Event: ${paidEvent.event.toHuman()}`); + + // Verify the spend status changed to attempted + const spendAfter = await assetHub.api.query.treasury.spends(spendIndex); + const spendDataAfter = spendAfter?.unwrap(); + assert(spendDataAfter?.status.isAttempted, `Spend ${spendIndex} status is not attempted ${spendDataAfter?.status.toHuman()}`); + + // check status of the spend in the Asset Hub + const checkStatusTx = assetHub.api.tx.treasury.checkStatus(spendIndex); + await sendTransaction(checkStatusTx.signAsync(alicePair)); + + await assetHub.api.rpc('dev_newBlock', { count: 1 }); + + // check for SpendProcessed event + const eventsAfterCheckStatus = await assetHub.api.query.system.events(); + const spendProcessedEvent = eventsAfterCheckStatus.find((record: any) => { + const { event } = record; + return event.section === 'treasury' && event.method === 'SpendProcessed'; + }); + assert(spendProcessedEvent, `SpendProcessed event is not found for spend ${spendIndex} Event: ${eventsAfterCheckStatus.map((record: any) => record.event.toHuman()).join(', ')}`); + assert(assetHub.api.events.treasury.SpendProcessed.is(spendProcessedEvent.event), `SpendProcessed event is not found for spend ${spendIndex} Event: ${spendProcessedEvent.event.toHuman()}`); + } catch (error) { + logger.error(`❌ Failed to execute payout for spend ${spendIndex}:`, error); + continue; // continue with next spend + } + } + + logger.debug('✅ Treasury payouts test completed successfully'); + + } catch (error) { + logger.error('❌ Treasury payouts test failed:', error); + throw error; + } finally { + // Cleanup + await relayChain.teardown(); + await assetHub.teardown(); + } +} + + +const polkadot = () => { + return { + relayEndpoint: 'wss://polkadot-rpc.n.dwellir.com', + relayPort: 8008, + assetHubEndpoint: 'wss://asset-hub-polkadot-rpc.n.dwellir.com', + assetHubPort: 8009, + } +} + +const kusama = () => { + return { + relayEndpoint: 'wss://rpc.ibp.network/kusama', + relayPort: 8008, + assetHubEndpoint: 'wss://sys.ibp.network/asset-hub-kusama', + assetHubPort: 8009, + } +} +// Note: The test on polkadot will work only after treasury is migrated to asset hub on polkadot. +/** + * Main function to run treasury payout tests + * @param network - 'kusama' or 'polkadot' + */ +export async function runTreasuryPayoutTests(network: 'kusama' | 'polkadot'): Promise { + try { + const config = network === 'kusama' ? kusama() : polkadot(); + await testTreasuryPayouts(network, config); + logger.info(`✅ Treasury payout tests completed successfully for ${network}`); + } catch (error) { + logger.error(`❌ Treasury payout tests failed for ${network}:`, error); + throw error; + } +} + From 8364248563f10cc53cd132b951f633eeb3112815 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Mon, 27 Oct 2025 21:53:15 +0530 Subject: [PATCH 02/18] treasury payputs script added --- .../treasury_payout/run_treasury_payouts.ts | 24 +++++++++++++++++++ package.json | 1 + 2 files changed, 25 insertions(+) create mode 100644 migration-tests/treasury_payout/run_treasury_payouts.ts diff --git a/migration-tests/treasury_payout/run_treasury_payouts.ts b/migration-tests/treasury_payout/run_treasury_payouts.ts new file mode 100644 index 00000000..1b19c0e0 --- /dev/null +++ b/migration-tests/treasury_payout/run_treasury_payouts.ts @@ -0,0 +1,24 @@ +import { runTreasuryPayoutTests } from './treasury_payouts.js'; +import { logger } from '../../shared/logger.js'; + +const main = async () => { + const network = process.argv[2] || 'kusama'; + + if (network !== 'kusama' && network !== 'polkadot') { + logger.error('Invalid network. Please specify "kusama" or "polkadot"'); + process.exit(1); + } + + try { + await runTreasuryPayoutTests(network as 'kusama' | 'polkadot'); + process.exit(0); + } catch (error) { + logger.error('❌ Tests failed:', error); + process.exit(1); + } +}; + +main().catch((error) => { + logger.error('Unexpected error:', error); + process.exit(1); +}); diff --git a/package.json b/package.json index 9acada38..1ee6928d 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "authorize-upgrade": "node dist/zombie-bite-scripts/authorize_upgrade_ah.js", "report-account-migration-status": "node dist/zombie-bite-scripts/report_account_migration_status.js", "compare-state": "node dist/migration-tests/index.js", + "treasury-payouts": "node dist/migration-tests/treasury_payout/run_treasury_payouts.js", "find-rc-block-bite": "node dist/zombie-bite-scripts/find_rc_block_bite.js", "make-new-snapshot": "node dist/zombie-bite-scripts/make_new_snapshot.js", "create-migration-done-file": "node dist/zombie-bite-scripts/create_migration_done_file.js", From 08d7caf7150df643fb0545992cea8f4cb7f3a828 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Wed, 29 Oct 2025 17:51:10 +0530 Subject: [PATCH 03/18] just command added for treasury payouts --- justfiles/ahm.justfile | 13 +++++++++++++ .../treasury_payout/run_treasury_payouts.ts | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/justfiles/ahm.justfile b/justfiles/ahm.justfile index 322ca56d..397fa9b9 100644 --- a/justfiles/ahm.justfile +++ b/justfiles/ahm.justfile @@ -146,3 +146,16 @@ rust-test runtime base_path: --features {{ runtime }}-ahm \ --features try-runtime \ post_migration_checks_only -- --include-ignored --nocapture --test-threads 1 + +# Run treasury payout tests for a given network +treasury-payouts network="polkadot": + #!/usr/bin/env bash + set -ex + + if [[ "{{ network }}" != "kusama" && "{{ network }}" != "polkadot" ]]; then + echo "Error: network must be one of: kusama, polkadot" + exit 1 + fi + + just ahm _npm-build + node dist/migration-tests/treasury_payout/run_treasury_payouts.js {{ network }} diff --git a/migration-tests/treasury_payout/run_treasury_payouts.ts b/migration-tests/treasury_payout/run_treasury_payouts.ts index 1b19c0e0..cbcaa606 100644 --- a/migration-tests/treasury_payout/run_treasury_payouts.ts +++ b/migration-tests/treasury_payout/run_treasury_payouts.ts @@ -2,7 +2,7 @@ import { runTreasuryPayoutTests } from './treasury_payouts.js'; import { logger } from '../../shared/logger.js'; const main = async () => { - const network = process.argv[2] || 'kusama'; + const network = process.argv[2] || 'polkadot'; if (network !== 'kusama' && network !== 'polkadot') { logger.error('Invalid network. Please specify "kusama" or "polkadot"'); From 984edf8adc80d39f15b0777f4ff2d43aa8ce6adf Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Wed, 29 Oct 2025 19:18:04 +0530 Subject: [PATCH 04/18] first letter of chains capitalized --- justfiles/ahm.justfile | 6 +++--- .../treasury_payout/run_treasury_payouts.ts | 8 ++++---- .../treasury_payout/treasury_payouts.ts | 18 +++++++++--------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/justfiles/ahm.justfile b/justfiles/ahm.justfile index 397fa9b9..b1582693 100644 --- a/justfiles/ahm.justfile +++ b/justfiles/ahm.justfile @@ -148,12 +148,12 @@ rust-test runtime base_path: post_migration_checks_only -- --include-ignored --nocapture --test-threads 1 # Run treasury payout tests for a given network -treasury-payouts network="polkadot": +treasury-payouts network="Polkadot": #!/usr/bin/env bash set -ex - if [[ "{{ network }}" != "kusama" && "{{ network }}" != "polkadot" ]]; then - echo "Error: network must be one of: kusama, polkadot" + if [[ "{{ network }}" != "Kusama" && "{{ network }}" != "Polkadot" ]]; then + echo "Error: network must be one of: Kusama, Polkadot" exit 1 fi diff --git a/migration-tests/treasury_payout/run_treasury_payouts.ts b/migration-tests/treasury_payout/run_treasury_payouts.ts index cbcaa606..32419296 100644 --- a/migration-tests/treasury_payout/run_treasury_payouts.ts +++ b/migration-tests/treasury_payout/run_treasury_payouts.ts @@ -2,15 +2,15 @@ import { runTreasuryPayoutTests } from './treasury_payouts.js'; import { logger } from '../../shared/logger.js'; const main = async () => { - const network = process.argv[2] || 'polkadot'; + const network = process.argv[2] || 'Polkadot'; - if (network !== 'kusama' && network !== 'polkadot') { - logger.error('Invalid network. Please specify "kusama" or "polkadot"'); + if (network !== 'Kusama' && network !== 'Polkadot') { + logger.error('Invalid network. Please specify "Kusama" or "Polkadot"'); process.exit(1); } try { - await runTreasuryPayoutTests(network as 'kusama' | 'polkadot'); + await runTreasuryPayoutTests(network as 'Kusama' | 'Polkadot'); process.exit(0); } catch (error) { logger.error('❌ Tests failed:', error); diff --git a/migration-tests/treasury_payout/treasury_payouts.ts b/migration-tests/treasury_payout/treasury_payouts.ts index 65155c1e..9f8ae2f0 100644 --- a/migration-tests/treasury_payout/treasury_payouts.ts +++ b/migration-tests/treasury_payout/treasury_payouts.ts @@ -20,10 +20,10 @@ export interface NetworkConfig { /** - * @param networkName - 'kusama' or 'polkadot' + * @param networkName - 'Kusama' or 'Polkadot' * @param config - Network configuration with endpoints and ports */ -async function testTreasuryPayouts(networkName: 'kusama' | 'polkadot', config: NetworkConfig): Promise { +async function testTreasuryPayouts(networkName: 'Kusama' | 'Polkadot', config: NetworkConfig): Promise { logger.debug(`Starting treasury payouts test on forked ${networkName} network`); // Setup networks based on configuration @@ -32,16 +32,16 @@ async function testTreasuryPayouts(networkName: 'kusama' | 'polkadot', config: N endpoint: config.relayEndpoint, port: config.relayPort, }, - [networkName === 'kusama' ? 'assetHubKusama' : 'assetHubPolkadot']: { + [networkName === 'Kusama' ? 'assetHubKusama' : 'assetHubPolkadot']: { endpoint: config.assetHubEndpoint, port: config.assetHubPort, }, }); - const relayChain = networkName === 'kusama' + const relayChain = networkName === 'Kusama' ? (networks as any).kusama : (networks as any).polkadot; - const assetHub = networkName === 'kusama' + const assetHub = networkName === 'Kusama' ? (networks as any).assetHubKusama : (networks as any).assetHubPolkadot; @@ -169,14 +169,14 @@ const kusama = () => { assetHubPort: 8009, } } -// Note: The test on polkadot will work only after treasury is migrated to asset hub on polkadot. +// Note: The test on Polkadot will work only after treasury is migrated to asset hub on Polkadot. /** * Main function to run treasury payout tests - * @param network - 'kusama' or 'polkadot' + * @param network - 'Kusama' or 'Polkadot' */ -export async function runTreasuryPayoutTests(network: 'kusama' | 'polkadot'): Promise { +export async function runTreasuryPayoutTests(network: 'Kusama' | 'Polkadot'): Promise { try { - const config = network === 'kusama' ? kusama() : polkadot(); + const config = network === 'Kusama' ? kusama() : polkadot(); await testTreasuryPayouts(network, config); logger.info(`✅ Treasury payout tests completed successfully for ${network}`); } catch (error) { From e9d587667e3618b42a1bf41d7c732521d36388be Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Wed, 29 Oct 2025 19:48:40 +0530 Subject: [PATCH 05/18] fix --- migration-tests/treasury_payout/treasury_payouts.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migration-tests/treasury_payout/treasury_payouts.ts b/migration-tests/treasury_payout/treasury_payouts.ts index 9f8ae2f0..c2c360a1 100644 --- a/migration-tests/treasury_payout/treasury_payouts.ts +++ b/migration-tests/treasury_payout/treasury_payouts.ts @@ -39,8 +39,8 @@ async function testTreasuryPayouts(networkName: 'Kusama' | 'Polkadot', config: N }); const relayChain = networkName === 'Kusama' - ? (networks as any).kusama - : (networks as any).polkadot; + ? (networks as any).Kusama + : (networks as any).Polkadot; const assetHub = networkName === 'Kusama' ? (networks as any).assetHubKusama : (networks as any).assetHubPolkadot; From 7b2b362905ae89952cef28c6d5f6ff032e59e04b Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Mon, 3 Nov 2025 17:46:12 +0530 Subject: [PATCH 06/18] Update justfiles/ahm.justfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dónal Murray --- justfiles/ahm.justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfiles/ahm.justfile b/justfiles/ahm.justfile index b1582693..a5bdee6a 100644 --- a/justfiles/ahm.justfile +++ b/justfiles/ahm.justfile @@ -158,4 +158,4 @@ treasury-payouts network="Polkadot": fi just ahm _npm-build - node dist/migration-tests/treasury_payout/run_treasury_payouts.js {{ network }} + node dist/migration-tests/treasury_payout/run_treasury_payouts.ts {{ network }} From 274ace6d24e1a2bd99b6b3facfd869f095cab1de Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Mon, 3 Nov 2025 17:46:29 +0530 Subject: [PATCH 07/18] Update justfiles/ahm.justfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dónal Murray --- justfiles/ahm.justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfiles/ahm.justfile b/justfiles/ahm.justfile index a5bdee6a..31df7928 100644 --- a/justfiles/ahm.justfile +++ b/justfiles/ahm.justfile @@ -150,7 +150,7 @@ rust-test runtime base_path: # Run treasury payout tests for a given network treasury-payouts network="Polkadot": #!/usr/bin/env bash - set -ex + set -euxo pipefail if [[ "{{ network }}" != "Kusama" && "{{ network }}" != "Polkadot" ]]; then echo "Error: network must be one of: Kusama, Polkadot" From 750a6f3f05531483461ab179d979788c0ec2b996 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Mon, 3 Nov 2025 17:46:59 +0530 Subject: [PATCH 08/18] Update justfiles/ahm.justfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit remove redundant network check. Co-authored-by: Dónal Murray --- justfiles/ahm.justfile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/justfiles/ahm.justfile b/justfiles/ahm.justfile index 31df7928..ed0cd480 100644 --- a/justfiles/ahm.justfile +++ b/justfiles/ahm.justfile @@ -152,10 +152,6 @@ treasury-payouts network="Polkadot": #!/usr/bin/env bash set -euxo pipefail - if [[ "{{ network }}" != "Kusama" && "{{ network }}" != "Polkadot" ]]; then - echo "Error: network must be one of: Kusama, Polkadot" - exit 1 - fi just ahm _npm-build node dist/migration-tests/treasury_payout/run_treasury_payouts.ts {{ network }} From 06f692042c70e5c7e619fe2ac67114f66f883ad7 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Mon, 3 Nov 2025 17:47:27 +0530 Subject: [PATCH 09/18] Update package.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dónal Murray --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1ee6928d..0ac80c62 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "authorize-upgrade": "node dist/zombie-bite-scripts/authorize_upgrade_ah.js", "report-account-migration-status": "node dist/zombie-bite-scripts/report_account_migration_status.js", "compare-state": "node dist/migration-tests/index.js", - "treasury-payouts": "node dist/migration-tests/treasury_payout/run_treasury_payouts.js", + "treasury-payouts": "node dist/migration-tests/treasury_payout/run_treasury_payouts.ts", "find-rc-block-bite": "node dist/zombie-bite-scripts/find_rc_block_bite.js", "make-new-snapshot": "node dist/zombie-bite-scripts/make_new_snapshot.js", "create-migration-done-file": "node dist/zombie-bite-scripts/create_migration_done_file.js", From 42a6719bcbf03ebcc00d432441a6976f80a87b7b Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Mon, 3 Nov 2025 18:28:46 +0530 Subject: [PATCH 10/18] Revert "Update package.json" This reverts commit 06f692042c70e5c7e619fe2ac67114f66f883ad7. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0ac80c62..1ee6928d 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "authorize-upgrade": "node dist/zombie-bite-scripts/authorize_upgrade_ah.js", "report-account-migration-status": "node dist/zombie-bite-scripts/report_account_migration_status.js", "compare-state": "node dist/migration-tests/index.js", - "treasury-payouts": "node dist/migration-tests/treasury_payout/run_treasury_payouts.ts", + "treasury-payouts": "node dist/migration-tests/treasury_payout/run_treasury_payouts.js", "find-rc-block-bite": "node dist/zombie-bite-scripts/find_rc_block_bite.js", "make-new-snapshot": "node dist/zombie-bite-scripts/make_new_snapshot.js", "create-migration-done-file": "node dist/zombie-bite-scripts/create_migration_done_file.js", From 52662db9891c6086fe10763ab2b72985e67c215d Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Mon, 3 Nov 2025 19:10:06 +0530 Subject: [PATCH 11/18] alice addres fetched from the alicePair --- migration-tests/treasury_payout/treasury_payouts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migration-tests/treasury_payout/treasury_payouts.ts b/migration-tests/treasury_payout/treasury_payouts.ts index c2c360a1..4e43965a 100644 --- a/migration-tests/treasury_payout/treasury_payouts.ts +++ b/migration-tests/treasury_payout/treasury_payouts.ts @@ -47,7 +47,7 @@ async function testTreasuryPayouts(networkName: 'Kusama' | 'Polkadot', config: N try { // Fund Alice account for transaction fees - const aliceAddress = '15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5'; + const aliceAddress = alicePair.address; const fundingAmount = 1000e10; await assetHub.dev.setStorage({ From b4c6ef2253a7924c98bd0169671e5bc0136db79e Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Mon, 3 Nov 2025 19:20:58 +0530 Subject: [PATCH 12/18] Revert "Update justfiles/ahm.justfile" This reverts commit 7b2b362905ae89952cef28c6d5f6ff032e59e04b. --- justfiles/ahm.justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfiles/ahm.justfile b/justfiles/ahm.justfile index ed0cd480..270b1d31 100644 --- a/justfiles/ahm.justfile +++ b/justfiles/ahm.justfile @@ -154,4 +154,4 @@ treasury-payouts network="Polkadot": just ahm _npm-build - node dist/migration-tests/treasury_payout/run_treasury_payouts.ts {{ network }} + node dist/migration-tests/treasury_payout/run_treasury_payouts.js {{ network }} From 43409cf0c2f2a4e1cc95e5ae71030b00c33c9526 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Mon, 3 Nov 2025 21:40:53 +0530 Subject: [PATCH 13/18] removed the early payout filter from the spends --- migration-tests/treasury_payout/treasury_payouts.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/migration-tests/treasury_payout/treasury_payouts.ts b/migration-tests/treasury_payout/treasury_payouts.ts index 4e43965a..0bdff5f4 100644 --- a/migration-tests/treasury_payout/treasury_payouts.ts +++ b/migration-tests/treasury_payout/treasury_payouts.ts @@ -79,11 +79,9 @@ async function testTreasuryPayouts(networkName: 'Kusama' | 'Polkadot', config: N // Filter spends which are pending or failed and are neither expired nor early payout const pendingOrFailedSpends = spends.filter((spend: any) => { const spendData = spend[1]?.unwrap(); - return ( - (spendData?.status.isPending || spendData?.status.isFailed) && // 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 isSpendNotExpired = currentRelayChainBlockNumber < spendData?.expireAt.toNumber(); + return isSpendPendingOrFailed && isSpendNotExpired; }); logger.debug(`Found ${pendingOrFailedSpends.length} eligible spends for payout testing`); From 430669a86c29c7c1b156e26d89f6f24422b12cb8 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Mon, 3 Nov 2025 21:42:40 +0530 Subject: [PATCH 14/18] helper function to extract beneficiary address from the spend data --- .../treasury_payout/treasury_payouts.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/migration-tests/treasury_payout/treasury_payouts.ts b/migration-tests/treasury_payout/treasury_payouts.ts index 0bdff5f4..a7d95f6e 100644 --- a/migration-tests/treasury_payout/treasury_payouts.ts +++ b/migration-tests/treasury_payout/treasury_payouts.ts @@ -149,6 +149,45 @@ async function testTreasuryPayouts(networkName: 'Kusama' | 'Polkadot', config: N } } +const extractBeneficiaryAddress = (spendDataUnwrapped: any, spendIndex: number): string | null => { + const beneficiaryHuman = spendDataUnwrapped.beneficiary.toHuman() as any + let beneficiaryAddress: string | null = null + + // toHuman() uses uppercase keys: V4, X1, AccountId32 + // Navigate through the nested structure to find the accountId32 + if (beneficiaryHuman.V4?.accountId?.interior?.X1?.[0]?.AccountId32?.id) { + beneficiaryAddress = beneficiaryHuman.V4.accountId.interior.X1[0].AccountId32.id + } else if (beneficiaryHuman.V3?.accountId?.interior?.X1?.[0]?.AccountId32?.id) { + beneficiaryAddress = beneficiaryHuman.V3.accountId.interior.X1[0].AccountId32.id + } else if (beneficiaryHuman.V5?.accountId?.interior?.X1?.[0]?.AccountId32?.id) { + beneficiaryAddress = beneficiaryHuman.V5.accountId.interior.X1[0].AccountId32.id + } else if (beneficiaryHuman.V4?.accountId?.interior?.X2?.[1]?.AccountId32?.id) { + beneficiaryAddress = beneficiaryHuman.V4.accountId.interior.X2[1].AccountId32.id + } + + // Fallback: If still not found, try toJSON() which uses lowercase keys + if (!beneficiaryAddress) { + const beneficiaryJson = spendDataUnwrapped.beneficiary.toJSON() as any + + // toJSON() uses lowercase: v4, x1, accountId32 + if (beneficiaryJson.v4?.accountId?.interior?.x1?.[0]?.accountId32?.id) { + beneficiaryAddress = beneficiaryJson.v4.accountId.interior.x1[0].accountId32.id + } else if (beneficiaryJson.v3?.accountId?.interior?.x1?.[0]?.accountId32?.id) { + beneficiaryAddress = beneficiaryJson.v3.accountId.interior.x1[0].accountId32.id + } else if (beneficiaryJson.v5?.accountId?.interior?.x1?.[0]?.accountId32?.id) { + beneficiaryAddress = beneficiaryJson.v5.accountId.interior.x1[0].accountId32.id + } + } + + if (!beneficiaryAddress) { + console.error("Failed to extract beneficiary from spendIndex: ", spendIndex, ". Human structure:", JSON.stringify(beneficiaryHuman, null, 2)) + throw new Error('Could not extract beneficiary address from spend data') + } + + return beneficiaryAddress; +} + + const polkadot = () => { return { From 3b8bf962e3da675ba7b3d4b5cadd2e925633d885 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Mon, 3 Nov 2025 21:43:56 +0530 Subject: [PATCH 15/18] Added utility functions to extract asset type information from spendData --- .../treasury_payout/treasury_payouts.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/migration-tests/treasury_payout/treasury_payouts.ts b/migration-tests/treasury_payout/treasury_payouts.ts index a7d95f6e..68859b61 100644 --- a/migration-tests/treasury_payout/treasury_payouts.ts +++ b/migration-tests/treasury_payout/treasury_payouts.ts @@ -187,6 +187,33 @@ const extractBeneficiaryAddress = (spendDataUnwrapped: any, spendIndex: number): return beneficiaryAddress; } +/** + * Extract asset type information from asset kind JSON + * @param assetKindJson - The asset kind JSON object from spend data + * @returns An object containing assetId (number | null) and isNativeAsset (boolean) + */ +const extractAssetType = (assetKindJson: any): { assetId: number | null; isNativeAsset: boolean } => { + let assetId: number | null = null + let isNativeAsset = false + + // Check if it's a foreign asset (has palletInstance 50 and generalIndex) + if (assetKindJson.v5?.assetId?.interior?.x2) { + const x2 = assetKindJson.v5.assetId.interior.x2 + if (x2[0]?.palletInstance === 50 && x2[1]?.generalIndex) { + assetId = x2[1].generalIndex + isNativeAsset = false + } + } else if (assetKindJson.v5?.assetId?.parents === 1 && assetKindJson.v5?.assetId?.interior?.here === null) { + // Native asset (relay chain native asset, parents: 1 means relay chain) + isNativeAsset = true + } else if (assetKindJson.v5?.assetId?.parents === 0 && assetKindJson.v5?.assetId?.interior?.here === null) { + // Local native asset + isNativeAsset = true + } + + return { assetId, isNativeAsset } +} + const polkadot = () => { From c3923a4d7fa521df980d5835abd99735d6cdf514 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Mon, 3 Nov 2025 21:45:06 +0530 Subject: [PATCH 16/18] Created a helper function to retrieve beneficiary balances based on asset type. --- .../treasury_payout/treasury_payouts.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/migration-tests/treasury_payout/treasury_payouts.ts b/migration-tests/treasury_payout/treasury_payouts.ts index 68859b61..6f8c734b 100644 --- a/migration-tests/treasury_payout/treasury_payouts.ts +++ b/migration-tests/treasury_payout/treasury_payouts.ts @@ -214,7 +214,32 @@ const extractAssetType = (assetKindJson: any): { assetId: number | null; isNativ return { assetId, isNativeAsset } } - +/** + * Get the beneficiary balance based on asset type + * @param assetHub - The Asset Hub API instance + * @param beneficiaryAddress - The beneficiary's address + * @param isNativeAsset - Whether the asset is native + * @param assetId - The asset ID if it's a foreign asset, null otherwise + * @returns The beneficiary's balance as bigint + */ +const getBeneficiaryBalance = async ( + assetHub: any, + beneficiaryAddress: string | null, + isNativeAsset: boolean, + assetId: number | null +): Promise => { + if (isNativeAsset) { + const beneficiaryBalance = await assetHub.api.query.system.account(beneficiaryAddress) + return beneficiaryBalance.data.free.toBigInt() + } else if (assetId !== null) { + const assetBalance = await assetHub.api.query.assets.account(assetId, beneficiaryAddress) + return assetBalance.isSome ? assetBalance.unwrap().balance.toBigInt() : 0n + } else { + // Fallback to native balance if we can't determine asset type + const beneficiaryBalance = await assetHub.api.query.system.account(beneficiaryAddress) + return beneficiaryBalance.data.free.toBigInt() + } +} const polkadot = () => { return { From 984166f64dbbce0c15c542594d67787823a2ac22 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Mon, 3 Nov 2025 21:49:24 +0530 Subject: [PATCH 17/18] updated the validFrom block to a past block to make the spend eligible for payout --- .../treasury_payout/treasury_payouts.ts | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/migration-tests/treasury_payout/treasury_payouts.ts b/migration-tests/treasury_payout/treasury_payouts.ts index 6f8c734b..82d53ffe 100644 --- a/migration-tests/treasury_payout/treasury_payouts.ts +++ b/migration-tests/treasury_payout/treasury_payouts.ts @@ -18,6 +18,8 @@ export interface NetworkConfig { assetHubPort: number; } +// any past RC block number can be used to set the validFrom of the spendData to make it eligible for payout +const PAST_RC_BLOCK_NUMBER = 28_386_000; /** * @param networkName - 'Kusama' or 'Polkadot' @@ -91,10 +93,53 @@ async function testTreasuryPayouts(networkName: 'Kusama' | 'Polkadot', config: N return; } + // log the length of the pendingOrFailedSpends + logger.debug(`Length of pendingOrFailedSpends: ${pendingOrFailedSpends.length}`); + // Test payout for each eligible spend for (const spend of pendingOrFailedSpends) { const spendIndex = spend[0].toHuman?.() as number; + const spendData = spend[1]; + const spendDataUnwrapped = spendData?.unwrap(); + let beneficiaryAddress: string | null = extractBeneficiaryAddress(spendDataUnwrapped, spendIndex); + const spendAmount = spendDataUnwrapped.amount.toBigInt(); + // Determine asset type from spend data + const assetKindJson = spendDataUnwrapped.assetKind.toJSON() as any + const { assetId, isNativeAsset } = extractAssetType(assetKindJson) + + // Get the beneficiary balance before payout based on asset type + const beneficiaryBalanceBefore = await getBeneficiaryBalance( + assetHub, + beneficiaryAddress, + isNativeAsset, + assetId + ) + + // Use toJSON() and ensure numeric values remain as numbers (not strings) + const spendDataJson = spendDataUnwrapped.toJSON() as any + + // Create updated spend data by cloning the structure and updating only validFrom + const updatedSpendData = { + assetKind: fixNumericValues(spendDataJson.assetKind), + amount: typeof spendDataJson.amount === 'string' ? Number(spendDataJson.amount) : spendDataJson.amount, + beneficiary: fixNumericValues(spendDataJson.beneficiary), + validFrom: PAST_RC_BLOCK_NUMBER, + expireAt: typeof spendDataJson.expireAt === 'string' ? Number(spendDataJson.expireAt) : spendDataJson.expireAt, + status: spendDataJson.status, + } + + // Format: Spends is a StorageMap, so we pass [[key], value] format + await assetHub.dev.setStorage({ + Treasury: { + Spends: [[[spendIndex], updatedSpendData]], + }, + }) + + const updatedSpendDataStorage = await assetHub.api.query.treasury.spends(spendIndex) + const updatedSpendDataStorageUnwrapped = updatedSpendDataStorage?.unwrap() + assert(updatedSpendDataStorageUnwrapped?.validFrom.toNumber() === PAST_RC_BLOCK_NUMBER, `Updated spend data validFrom is not ${PAST_RC_BLOCK_NUMBER} but ${updatedSpendDataStorageUnwrapped?.validFrom.toNumber()}`); + try { // Create and sign the payout transaction const payoutTx = assetHub.api.tx.treasury.payout(spendIndex); @@ -241,6 +286,27 @@ const getBeneficiaryBalance = async ( } } +// Helper function to recursively fix numeric values in nested structures +const fixNumericValues = (obj: any): any => { + if (obj === null || obj === undefined) return obj + if (typeof obj === 'string' && /^-?\d+$/.test(obj)) { + // Convert numeric strings to numbers + const num = Number(obj) + if (!isNaN(num) && isFinite(num)) return num + } + if (Array.isArray(obj)) { + return obj.map(fixNumericValues) + } + if (typeof obj === 'object') { + const fixed: any = {} + for (const key in obj) { + fixed[key] = fixNumericValues(obj[key]) + } + return fixed + } + return obj +} + const polkadot = () => { return { relayEndpoint: 'wss://polkadot-rpc.n.dwellir.com', From 86d7b650231ca633c575b3b242208160be6a3526 Mon Sep 17 00:00:00 2001 From: Dhiraj Sah Date: Mon, 3 Nov 2025 21:50:31 +0530 Subject: [PATCH 18/18] Added validation to ensure beneficiary balance after payout increased by spend amount for the asset --- .../treasury_payout/treasury_payouts.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/migration-tests/treasury_payout/treasury_payouts.ts b/migration-tests/treasury_payout/treasury_payouts.ts index 82d53ffe..81638af0 100644 --- a/migration-tests/treasury_payout/treasury_payouts.ts +++ b/migration-tests/treasury_payout/treasury_payouts.ts @@ -176,6 +176,21 @@ async function testTreasuryPayouts(networkName: 'Kusama' | 'Polkadot', config: N }); assert(spendProcessedEvent, `SpendProcessed event is not found for spend ${spendIndex} Event: ${eventsAfterCheckStatus.map((record: any) => record.event.toHuman()).join(', ')}`); assert(assetHub.api.events.treasury.SpendProcessed.is(spendProcessedEvent.event), `SpendProcessed event is not found for spend ${spendIndex} Event: ${spendProcessedEvent.event.toHuman()}`); + + // Get beneficiary balance after payout based on asset type + const beneficiaryBalanceAfterValue = await getBeneficiaryBalance( + assetHub, + beneficiaryAddress, + isNativeAsset, + assetId + ) + + // ensure the diff of beneficiary balance after payout and before payout is equal to the spend amount + assert(beneficiaryBalanceAfterValue - beneficiaryBalanceBefore === spendAmount, + `The diff of beneficiary balance after payout and before payout is not equal to the spend amount. + Beneficiary balance before payout: ${beneficiaryBalanceBefore}, Spend amount: ${spendAmount}, + Beneficiary balance after payout: ${beneficiaryBalanceAfterValue}, + Diff: ${beneficiaryBalanceAfterValue - beneficiaryBalanceBefore}`); } catch (error) { logger.error(`❌ Failed to execute payout for spend ${spendIndex}:`, error); continue; // continue with next spend