Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -225,26 +225,3 @@ exports[`asset hub & polkadot > Teleport DOT from Polkadot to Asset Hub > tx eve
},
]
`;

exports[`polkadot & assetHub > Foreign asset spend from Relay treasury is reflected on AssetHub > payout events 1`] = `
[
{
"data": {
"index": "(rounded 160)",
"paymentId": "(rounded 210)",
},
"method": "Paid",
"section": "treasury",
},
]
`;

exports[`polkadot & assetHub > Foreign asset spend from Relay treasury is reflected on AssetHub > treasury spend approval events 1`] = `
[
{
"data": "(redacted)",
"method": "AssetSpendApproved",
"section": "treasury",
},
]
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

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",
},
]
`;
11 changes: 1 addition & 10 deletions packages/polkadot/src/assetHubPolkadot.polkadot.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import { describe, test } from 'vitest'
import { describe } from 'vitest'

import { defaultAccounts } from '@e2e-test/networks'
import { assetHubPolkadot, polkadot } from '@e2e-test/networks/chains'
import { setupNetworks } from '@e2e-test/shared'
import { treasurySpendForeignAssetTest } from '@e2e-test/shared'
import { query, tx } from '@e2e-test/shared/api'
import { runXcmPalletDown, runXcmPalletUp } from '@e2e-test/shared/xcm'

describe('polkadot & assetHub', async () => {
const [polkadotClient, assetHubClient] = await setupNetworks(polkadot, assetHubPolkadot)

test('Foreign asset spend from Relay treasury is reflected on AssetHub', async () => {
await treasurySpendForeignAssetTest(polkadotClient, assetHubClient)
})
})

describe('asset hub & polkadot', async () => {
const [polkadotClient, ahClient] = await setupNetworks(polkadot, assetHubPolkadot)

Expand Down
4 changes: 4 additions & 0 deletions packages/polkadot/src/polkadot.treasury.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { assetHubPolkadot, polkadot } from '@e2e-test/networks/chains'
import { treasuryE2ETests } from '@e2e-test/shared'

treasuryE2ETests(polkadot, assetHubPolkadot, { testSuiteName: 'Polkadot Treasury', addressEncoding: 0 })
63 changes: 2 additions & 61 deletions packages/shared/src/governance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BN } from 'bn.js'
import { assert, describe, expect, test } from 'vitest'
import { assert, describe, test } from 'vitest'

import { type Chain, type Client as NetworkClient, defaultAccountsSr25519 } from '@e2e-test/networks'
import { type Chain, defaultAccountsSr25519 } from '@e2e-test/networks'
import { type Client, setupNetworks } from '@e2e-test/shared'
import { check, checkEvents, checkSystemEvents, objectCmp, scheduleInlineCallWithOrigin } from './helpers/index.js'

Expand Down Expand Up @@ -893,62 +893,3 @@ export function governanceE2ETests<
})
})
}

export async function treasurySpendForeignAssetTest(relayClient: NetworkClient, assetHubClient: NetworkClient) {
await relayClient.dev.setStorage({
System: {
account: [
// give Alice some DOTs so that she can sign a payout transaction.
[[devAccounts.alice.address], { providers: 1, data: { free: 10000e10 } }],
],
},
})
const USDT_ID = 1984
const balanceBefore = await assetHubClient.api.query.assets.account(USDT_ID, devAccounts.alice.address)

// amount is encoded into the call
const amount = 123123123123n
const treasurySpendCall =
'0x130504000100a10f0002043205011f07b3c3b5aa1c0400010100d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d00'
await scheduleInlineCallWithOrigin(relayClient, treasurySpendCall, { system: 'Root' })
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({ hash: false, redactKeys: /data/ })
.toMatchSnapshot('treasury spend approval events')

// filter events to find an index to payout
const spendEvents = (await relayClient.api.query.system.events()).filter(
({ event }) => event.section === 'treasury' && event.method === 'AssetSpendApproved',
)
expect(spendEvents.length).toBe(1)
assert(relayClient.api.events.treasury.AssetSpendApproved.is(spendEvents[0].event))
const spendEvent = spendEvents[0]
const spendIndex = (spendEvent.event.data[0] as any).toNumber()

// payout
const payoutEvents = await sendTransaction(
relayClient.api.tx.treasury.payout(spendIndex).signAsync(devAccounts.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' }).toMatchSnapshot('payout events')
const events = (await relayClient.api.query.system.events()).filter(
({ event }) => event.section === 'treasury' && event.method === 'Paid',
)
expect(events.length).toBe(1)
assert(relayClient.api.events.treasury.Paid.is(events[0].event))
const payoutEvent = events[0]
const payoutIndex = (payoutEvent.event.data[0] as any).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, devAccounts.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)
}
1 change: 1 addition & 0 deletions packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './people.js'
export * from './proxy.js'
export * from './scheduler.js'
export * from './staking.js'
export * from './treasury.js'
export * from './vesting.js'

export * from './types.js'
Expand Down
98 changes: 98 additions & 0 deletions packages/shared/src/treasury.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { assert, describe, expect, test } from 'vitest'

import { sendTransaction } from '@acala-network/chopsticks-testing'
import { type Chain, defaultAccountsSr25519 as devAccounts } from '@e2e-test/networks'
import { setupNetworks } from '@e2e-test/shared'

import { checkEvents, checkSystemEvents, scheduleInlineCallWithOrigin } from './helpers/index.js'

/**
* 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<string, unknown> | undefined,
TInitStoragesRelay extends Record<string, Record<string, any>> | undefined,
TInitStoragesPara extends Record<string, Record<string, any>> | undefined,
>(relayChain: Chain<TCustom, TInitStoragesRelay>, ahChain: Chain<TCustom, TInitStoragesPara>) {
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.
[[devAccounts.alice.address], { providers: 1, data: { free: 10000e10 } }],
],
},
})
const USDT_ID = 1984
const balanceBefore = await assetHubClient.api.query.assets.account(USDT_ID, devAccounts.alice.address)

// amount is encoded into the call
const amount = 123123123123n
const treasurySpendCall =
'0x130504000100a10f0002043205011f07b3c3b5aa1c0400010100d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d00'
await scheduleInlineCallWithOrigin(relayClient, treasurySpendCall, { system: 'Root' })
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(devAccounts.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)
Comment on lines +65 to +74
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn’t one of these be sufficient? I may not fully understand the value that PET snapshots provide — if you could elaborate on that, I’d really appreciate it @rockbmb


// 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 balanceAfterAmount = balanceAfter.isNone ? 0n : balanceAfter.unwrap().balance.toBigInt()
const balanceBeforeAmount = balanceBefore.isNone ? 0n : balanceBefore.unwrap().balance.toBigInt()
expect(balanceAfterAmount - balanceBeforeAmount).toBe(amount)
}

export function treasuryE2ETests<
TCustom extends Record<string, unknown> | undefined,
TInitStoragesRelay extends Record<string, Record<string, any>> | undefined,
TInitStoragesPara extends Record<string, Record<string, any>> | undefined,
>(
relayChain: Chain<TCustom, TInitStoragesRelay>,
ahChain: Chain<TCustom, TInitStoragesPara>,
testConfig: { testSuiteName: string; addressEncoding: number },
) {
describe(testConfig.testSuiteName, () => {
test('Foreign asset spend from Relay treasury is reflected on AssetHub', async () => {
await treasurySpendForeignAssetTest(relayChain, ahChain)
})
})
}