Skip to content

↔️ PLMC AH transfer test #455

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 0 additions & 3 deletions integration-tests/chopsticks/.papi/descriptors/.gitignore

This file was deleted.

24 changes: 0 additions & 24 deletions integration-tests/chopsticks/.papi/descriptors/package.json

This file was deleted.

Binary file not shown.
Binary file not shown.
Binary file not shown.
21 changes: 0 additions & 21 deletions integration-tests/chopsticks/.papi/polkadot-api.json

This file was deleted.

17 changes: 12 additions & 5 deletions integration-tests/chopsticks/src/managers/PolimecManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export class PolimecManager extends BaseChainManager {
case Asset.WETH:
// Placeholder
return AssetSourceRelation.Self;
case Asset.PLMC:
return AssetSourceRelation.Self;
}
}

Expand All @@ -69,12 +71,17 @@ export class PolimecManager extends BaseChainManager {
return 0n;
}

async getLocalXcmFee() {
async getXcmFee() {
const api = this.getApi(Chains.Polimec);
const events = await api.event.PolkadotXcm.FeesPaid.pull();
if (!events.length) return 0n;
const fees = events[0]?.payload?.fees;
if (!fees?.length) return 0n;
return (fees[0]?.fun?.value as bigint) || 0n;
console.dir(events, { depth: null });

return events[0]?.payload.fees?.[0]?.fun?.value ?? 0n;
}

async getTransactionFee() {
const api = this.getApi(Chains.Polimec);
const events = await api.event.TransactionPayment.TransactionFeePaid.pull();
return (events[0]?.payload.actual_fee as bigint) || 0n;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export class PolkadotHubManager extends BaseChainManager {
case Asset.WETH:
// This is not actually used, so we use Self as a placeholder
return AssetSourceRelation.Self;
case Asset.PLMC:
return AssetSourceRelation.Sibling;
}
}

Expand Down
1 change: 1 addition & 0 deletions integration-tests/chopsticks/src/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export class ChainSetup {
'wasm-override': POLIMEC_WASM,
'import-storage': polimec_storage,
'build-block-mode': BuildBlockMode.Instant,
'runtime-log-level': 5,
});
}

Expand Down
56 changes: 35 additions & 21 deletions integration-tests/chopsticks/src/tests/polimec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,47 @@ describe('Polimec -> Hub Transfer Tests', () => {
});
afterAll(async () => await chainSetup.cleanup());

test(
'Send USDC to Hub',
() =>
transferTest.testTransfer({
account: Accounts.BOB,
assets: [[Asset.USDC, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Sibling]],
}),
{ timeout: 25000 },
);
// test(
// 'Send USDC to Hub',
// () =>
// transferTest.testTransfer({
// account: Accounts.BOB,
// assets: [[Asset.USDC, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Sibling]],
// }),
// { timeout: 25000 },
// );
//
// test(
// 'Send USDT to Hub',
// () =>
// transferTest.testTransfer({
// account: Accounts.BOB,
// assets: [[Asset.USDT, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Sibling]],
// }),
// { timeout: 25000 },
// );

test(
'Send USDT to Hub',
() =>
transferTest.testTransfer({
account: Accounts.BOB,
assets: [[Asset.USDT, TRANSFER_AMOUNTS.TOKENS, AssetSourceRelation.Sibling]],
}),
{ timeout: 25000 },
);
// test(
// 'Send DOT to Hub',
// () =>
// transferTest.testTransfer({
// account: Accounts.BOB,
// assets: [[Asset.DOT, TRANSFER_AMOUNTS.NATIVE, AssetSourceRelation.Parent]],
// }),
// { timeout: 25000 },
// );

test(
'Send DOT to Hub',
'Send PLMC to Hub',
() =>
transferTest.testTransfer({
account: Accounts.BOB,
assets: [[Asset.DOT, TRANSFER_AMOUNTS.NATIVE, AssetSourceRelation.Parent]],
assets: [
[Asset.PLMC, TRANSFER_AMOUNTS.NATIVE, AssetSourceRelation.Self],
[Asset.DOT, TRANSFER_AMOUNTS.NATIVE, AssetSourceRelation.Parent],
],
fee_asset_item: 1,
}),
{ timeout: 25000 },
{ timeout: 25000000 },
);
});
2 changes: 2 additions & 0 deletions integration-tests/chopsticks/src/transfers/BaseTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { sleep } from 'bun';
export interface TransferOptions {
account: Accounts;
assets: [Asset, bigint, AssetSourceRelation][];
fee_asset_item?: number;
}

export abstract class BaseTransferTest {
Expand Down Expand Up @@ -63,6 +64,7 @@ export abstract class BaseTransferTest {
protected async verifyExecution() {
const events = await this.destManager.getMessageQueueEvents();

console.dir(events, { depth: null });
expect(events).not.toBeEmpty();
expect(events).toBeArray();
expect(events).toHaveLength(1);
Expand Down
160 changes: 141 additions & 19 deletions integration-tests/chopsticks/src/transfers/PolimecToHub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,22 @@ import { expect } from 'bun:test';
import { INITIAL_BALANCES } from '@/constants';
import type { PolimecManager } from '@/managers/PolimecManager';
import type { PolkadotHubManager } from '@/managers/PolkadotHubManager';
import { Asset, type BalanceCheck, Chains, getVersionedAssets } from '@/types';
import { createTransferData } from '@/utils';
import {
Asset,
AssetSourceRelation,
type BalanceCheck,
Chains,
ParaId,
type PolimecBalanceCheck,
getVersionedAssets,
} from '@/types';
import { createTransferData, unwrap } from '@/utils';
import {
DispatchRawOrigin,
XcmVersionedAssetId,
type XcmVersionedLocation,
type XcmVersionedXcm,
} from '@polkadot-api/descriptors';
import { BaseTransferTest, type TransferOptions } from './BaseTransfer';

export class PolimecToHubTransfer extends BaseTransferTest {
Expand All @@ -14,7 +28,7 @@ export class PolimecToHubTransfer extends BaseTransferTest {
super(sourceManager, destManager);
}

async executeTransfer({ account, assets }: TransferOptions) {
async executeTransfer({ account, assets, fee_asset_item }: TransferOptions) {
const [sourceBlock, destBlock] = await Promise.all([
this.sourceManager.getBlockNumber(),
this.destManager.getBlockNumber(),
Expand All @@ -25,12 +39,14 @@ export class PolimecToHubTransfer extends BaseTransferTest {
toChain: Chains.PolkadotHub,
assets: versioned_assets,
recv: account,
fee_asset_item: fee_asset_item ?? 0,
});

const res = await this.sourceManager
.getXcmPallet()
.transfer_assets(data)
.signAndSubmit(this.sourceManager.getSigner(account));
console.dir(res, { depth: null });

expect(res.ok).toBeTrue();
return { sourceBlock, destBlock };
Expand All @@ -48,23 +64,129 @@ export class PolimecToHubTransfer extends BaseTransferTest {
return { asset_balances: [{ source, destination }] };
}

verifyFinalBalances(
initialBalances: BalanceCheck[],
finalBalances: BalanceCheck[],
options: TransferOptions,
// verifyFinalBalances(
// initialBalances: BalanceCheck[],
// finalBalances: BalanceCheck[],
// options: TransferOptions,
// ) {
// // TODO: At the moment we exclude fees from the balance check since the PAPI team is wotking on some utilies to calculate fees.
// const initialBalance =
// options.assets[0][0] === Asset.DOT
// ? INITIAL_BALANCES.DOT
// : options.assets[0][0] === Asset.USDT
// ? INITIAL_BALANCES.USDT
// : INITIAL_BALANCES.USDC;
// for (let i = 0; i < options.assets.length; i++) {
// expect(initialBalances[i].destination).toBe(0n);
// expect(initialBalances[i].source).toBe(initialBalance);
// expect(finalBalances[i].source).toBeLessThan(initialBalances[i].source);
// expect(finalBalances[i].destination).toBeGreaterThan(initialBalances[i].destination);
// }
// }

async verifyFinalBalances(
assetInitialBalances: PolimecBalanceCheck[],
assetFinalBalances: PolimecBalanceCheck[],
transferOptions: TransferOptions,
) {
// TODO: At the moment we exclude fees from the balance check since the PAPI team is wotking on some utilies to calculate fees.
const initialBalance =
options.assets[0][0] === Asset.DOT
? INITIAL_BALANCES.DOT
: options.assets[0][0] === Asset.USDT
? INITIAL_BALANCES.USDT
: INITIAL_BALANCES.USDC;
for (let i = 0; i < options.assets.length; i++) {
expect(initialBalances[i].destination).toBe(0n);
expect(initialBalances[i].source).toBe(initialBalance);
expect(finalBalances[i].source).toBeLessThan(initialBalances[i].source);
expect(finalBalances[i].destination).toBeGreaterThan(initialBalances[i].destination);
const native_extrinsic_fee_amount = await this.sourceManager.getTransactionFee();
const source_xcm_asset_fee_amount = await this.sourceManager.getXcmFee();
const dest_xcm_asset_fee_amount = await this.calculatePolkadotHubXcmFee(transferOptions);

const fee_asset = transferOptions.assets[0][0];

for (let i = 0; i < transferOptions.assets.length; i++) {
const initialBalances = assetInitialBalances[i];
const finalBalances = assetFinalBalances[i];
const send_amount = transferOptions.assets[i][1];
const asset = transferOptions.assets[i][0];

let expectedSourceBalanceSpent = send_amount;
let expectedDestBalanceSpent = 0n;
let expectedTreasuryBalanceGained = 0n;

if (asset === Asset.PLMC) {
expectedSourceBalanceSpent += native_extrinsic_fee_amount + source_xcm_asset_fee_amount;
}
if (asset === fee_asset) {
expectedDestBalanceSpent += dest_xcm_asset_fee_amount;
expectedTreasuryBalanceGained += dest_xcm_asset_fee_amount;
}

expect(finalBalances.source).toBe(initialBalances.source - expectedSourceBalanceSpent);
expect(finalBalances.destination).toBe(
initialBalances.destination + send_amount - expectedDestBalanceSpent,
);
expect(finalBalances.treasury).toBe(initialBalances.treasury + expectedTreasuryBalanceGained);
}
}

async calculatePolkadotHubXcmFee(transferOptions: TransferOptions): Promise<bigint> {
let destinationExecutionFee: bigint;

const sourceApi = this.sourceManager.getApi(Chains.Polimec);
const destApi = this.destManager.getApi(Chains.PolkadotHub);

const versioned_assets = getVersionedAssets(transferOptions.assets);
const transferData = createTransferData({
toChain: Chains.Polimec,
assets: versioned_assets,
recv: transferOptions.account,
fee_asset_item: transferOptions.fee_asset_item ?? 0,
});

let remoteFeeAssetId: XcmVersionedAssetId;
const feeAsset = unwrap(transferOptions.assets.at(transferData.fee_asset_item));
if (feeAsset[2] === AssetSourceRelation.Self) {
feeAsset[2] = AssetSourceRelation.Sibling;
}
const versioned_asset = getVersionedAssets([feeAsset]);
if (versioned_asset.type === 'V4') {
remoteFeeAssetId = XcmVersionedAssetId.V4(unwrap(versioned_asset.value.at(0)).id);
} else {
throw new Error('Invalid versioned assets');
}

const localDryRunResult = await sourceApi.apis.DryRunApi.dry_run_call(
{ type: 'system', value: DispatchRawOrigin.Signed(transferOptions.account) },
{ type: 'PolkadotXcm', value: { type: 'transfer_assets', value: transferData } },
);

let forwardedXcms: [XcmVersionedLocation, XcmVersionedXcm[]][] = [];
if (localDryRunResult.success && localDryRunResult.value.forwarded_xcms) {
forwardedXcms = localDryRunResult.value.forwarded_xcms;
} else {
throw new Error('Dry run failed');
}

const xcmsToPHub = forwardedXcms.find(
([location, _]) =>
location.type === 'V4' &&
location.value.parents === 1 &&
location.value.interior.type === 'X1' &&
location.value.interior.value.type === 'Parachain' &&
location.value.interior.value.value === ParaId[Chains.PolkadotHub],
);
if (!xcmsToPHub) {
throw new Error('Could not find xcm to polimec');
}
const messages = xcmsToPHub[1];
const remoteXcm = messages[0];
const remoteXcmWeightResult = await destApi.apis.XcmPaymentApi.query_xcm_weight(remoteXcm);
if (remoteXcmWeightResult.success) {
const remoteExecutionFeesResult = await destApi.apis.XcmPaymentApi.query_weight_to_asset_fee(
remoteXcmWeightResult.value,
remoteFeeAssetId,
);
if (remoteExecutionFeesResult.success) {
destinationExecutionFee = remoteExecutionFeesResult.value;
} else {
throw new Error('Could not calculate destination xcm fee');
}
} else {
throw new Error('Could not calculate xcm weight');
}

return destinationExecutionFee;
}
}
Loading