diff --git a/integration-tests/chopsticks/.papi/descriptors/.gitignore b/integration-tests/chopsticks/.papi/descriptors/.gitignore deleted file mode 100644 index 46d96ea47..000000000 --- a/integration-tests/chopsticks/.papi/descriptors/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore -!package.json \ No newline at end of file diff --git a/integration-tests/chopsticks/.papi/descriptors/package.json b/integration-tests/chopsticks/.papi/descriptors/package.json deleted file mode 100644 index 08cffa71c..000000000 --- a/integration-tests/chopsticks/.papi/descriptors/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "version": "0.1.0-autogenerated.14907376514540113357", - "name": "@polkadot-api/descriptors", - "files": [ - "dist" - ], - "exports": { - ".": { - "types": "./dist/index.d.ts", - "module": "./dist/index.mjs", - "import": "./dist/index.mjs", - "require": "./dist/index.js" - }, - "./package.json": "./package.json" - }, - "main": "./dist/index.js", - "module": "./dist/index.mjs", - "browser": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "sideEffects": false, - "peerDependencies": { - "polkadot-api": "*" - } -} diff --git a/integration-tests/chopsticks/.papi/metadata/pah.scale b/integration-tests/chopsticks/.papi/metadata/pah.scale deleted file mode 100644 index b48010a4c..000000000 Binary files a/integration-tests/chopsticks/.papi/metadata/pah.scale and /dev/null differ diff --git a/integration-tests/chopsticks/.papi/metadata/polimec.scale b/integration-tests/chopsticks/.papi/metadata/polimec.scale deleted file mode 100644 index 8d2530c58..000000000 Binary files a/integration-tests/chopsticks/.papi/metadata/polimec.scale and /dev/null differ diff --git a/integration-tests/chopsticks/.papi/metadata/polkadot.scale b/integration-tests/chopsticks/.papi/metadata/polkadot.scale deleted file mode 100644 index 6d1a37047..000000000 Binary files a/integration-tests/chopsticks/.papi/metadata/polkadot.scale and /dev/null differ diff --git a/integration-tests/chopsticks/.papi/polkadot-api.json b/integration-tests/chopsticks/.papi/polkadot-api.json deleted file mode 100644 index 2bf9d8891..000000000 --- a/integration-tests/chopsticks/.papi/polkadot-api.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "version": 0, - "descriptorPath": ".papi/descriptors", - "entries": { - "polimec": { - "metadata": ".papi/metadata/polimec.scale" - }, - "polkadot": { - "wsUrl": "wss://rpc.ibp.network/polkadot", - "metadata": ".papi/metadata/polkadot.scale" - }, - "pah": { - "wsUrl": "wss://sys.ibp.network/statemint", - "metadata": ".papi/metadata/pah.scale" - }, - "bridge": { - "wsUrl": "wss://sys.ibp.network/bridgehub-polkadot", - "metadata": ".papi/metadata/bridge.scale" - } - } -} \ No newline at end of file diff --git a/integration-tests/chopsticks/src/managers/PolimecManager.ts b/integration-tests/chopsticks/src/managers/PolimecManager.ts index 44d9ff46c..630d46bca 100644 --- a/integration-tests/chopsticks/src/managers/PolimecManager.ts +++ b/integration-tests/chopsticks/src/managers/PolimecManager.ts @@ -50,6 +50,8 @@ export class PolimecManager extends BaseChainManager { case Asset.WETH: // Placeholder return AssetSourceRelation.Self; + case Asset.PLMC: + return AssetSourceRelation.Self; } } @@ -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; } } diff --git a/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts b/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts index dc6c7d894..075087ead 100644 --- a/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts +++ b/integration-tests/chopsticks/src/managers/PolkadotHubManager.ts @@ -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; } } diff --git a/integration-tests/chopsticks/src/setup.ts b/integration-tests/chopsticks/src/setup.ts index 42d9bbaa6..30748b495 100644 --- a/integration-tests/chopsticks/src/setup.ts +++ b/integration-tests/chopsticks/src/setup.ts @@ -100,6 +100,7 @@ export class ChainSetup { 'wasm-override': POLIMEC_WASM, 'import-storage': polimec_storage, 'build-block-mode': BuildBlockMode.Instant, + 'runtime-log-level': 5, }); } diff --git a/integration-tests/chopsticks/src/tests/polimec.test.ts b/integration-tests/chopsticks/src/tests/polimec.test.ts index 23ad70a4e..7348a8449 100644 --- a/integration-tests/chopsticks/src/tests/polimec.test.ts +++ b/integration-tests/chopsticks/src/tests/polimec.test.ts @@ -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 }, ); }); diff --git a/integration-tests/chopsticks/src/transfers/BaseTransfer.ts b/integration-tests/chopsticks/src/transfers/BaseTransfer.ts index efbfbe4b5..c37f4a30c 100644 --- a/integration-tests/chopsticks/src/transfers/BaseTransfer.ts +++ b/integration-tests/chopsticks/src/transfers/BaseTransfer.ts @@ -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 { @@ -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); diff --git a/integration-tests/chopsticks/src/transfers/PolimecToHub.ts b/integration-tests/chopsticks/src/transfers/PolimecToHub.ts index c6e347414..56c347184 100644 --- a/integration-tests/chopsticks/src/transfers/PolimecToHub.ts +++ b/integration-tests/chopsticks/src/transfers/PolimecToHub.ts @@ -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 { @@ -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(), @@ -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 }; @@ -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 { + 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; } } diff --git a/integration-tests/chopsticks/src/types.ts b/integration-tests/chopsticks/src/types.ts index 60fdddc60..91a45e792 100644 --- a/integration-tests/chopsticks/src/types.ts +++ b/integration-tests/chopsticks/src/types.ts @@ -89,13 +89,15 @@ export interface TransferDataParams { assets: XcmVersionedAssets; recv?: Accounts; isMultiHop?: boolean; + fee_asset_item: number; } export enum Asset { DOT = 10, USDC = 1337, USDT = 1984, - WETH = 10000, // Note: This is not the real Asset ID - we should improve this. + WETH = 10000, + PLMC = 3344, } export function AssetHubAssetLocation( @@ -175,8 +177,8 @@ export function AssetLocation( const baseLocation = asset === Asset.WETH ? EthereumAssetLocation(FixedSizeBinary.fromHex(WETH_ADDRESS)) - : asset === Asset.DOT - ? NativeAssetLocation(assetSourceRelation) + : asset === Asset.DOT || asset === Asset.PLMC + ? NativeAssetLocation(assetSourceRelation, asset) : AssetHubAssetLocation(BigInt(asset), assetSourceRelation); return baseLocation; diff --git a/integration-tests/chopsticks/src/utils.ts b/integration-tests/chopsticks/src/utils.ts index c45495330..86a79b2a2 100644 --- a/integration-tests/chopsticks/src/utils.ts +++ b/integration-tests/chopsticks/src/utils.ts @@ -20,6 +20,7 @@ import { XcmVersionedLocation, XcmVersionedXcm, } from '@polkadot-api/descriptors'; +import type { I5gi8h3e5lkbeq } from '@polkadot-api/descriptors/dist/common-types'; import { Enum, FixedSizeBinary } from 'polkadot-api'; const custom_xcm_on_dest = (): XcmVersionedXcm => { return XcmVersionedXcm.V3([ @@ -57,7 +58,12 @@ const custom_xcm_on_dest = (): XcmVersionedXcm => { ]); }; -export const createTransferData = ({ toChain, assets, recv }: TransferDataParams) => { +export const createTransferData = ({ + toChain, + assets, + recv, + fee_asset_item, +}: TransferDataParams): I5gi8h3e5lkbeq => { if (toChain === Chains.Polkadot) { throw new Error('Invalid chain'); } @@ -80,7 +86,7 @@ export const createTransferData = ({ toChain, assets, recv }: TransferDataParams dest, beneficiary, assets, - fee_asset_item: 0, + fee_asset_item, weight_limit: XcmV3WeightLimit.Unlimited(), }; }; diff --git a/integration-tests/src/tests/xcm_config.rs b/integration-tests/src/tests/xcm_config.rs deleted file mode 100644 index e69de29bb..000000000 diff --git a/pallets/funding/src/functions/5_settlement.rs b/pallets/funding/src/functions/5_settlement.rs index 57dd9106d..20f8d149c 100644 --- a/pallets/funding/src/functions/5_settlement.rs +++ b/pallets/funding/src/functions/5_settlement.rs @@ -320,6 +320,7 @@ impl Pallet { T::FundingCurrency::transfer(asset.id(), &project_pot, participant, amount, Preservation::Expendable)?; Ok(()) } + /// Helper function to release the PLMC bond to the participant fn release_participation_bond_for(participant: &AccountIdOf, amount: Balance) -> DispatchResult { if amount.is_zero() { diff --git a/runtimes/polimec/src/xcm_config.rs b/runtimes/polimec/src/xcm_config.rs index 3182a74f0..9830271ec 100644 --- a/runtimes/polimec/src/xcm_config.rs +++ b/runtimes/polimec/src/xcm_config.rs @@ -276,6 +276,23 @@ pub type Barrier = TrailingSetTopicAsId< /// for the following assets: DOT, USDT and USDC. pub type Reserves = AssetHubAssetsAsReserve; +pub struct PLMCToHubTeleport; +impl ContainsPair for PLMCToHubTeleport { + fn contains(asset: &Asset, location: &Location) -> bool { + // We only allow teleportation of PLMC to the AssetHub parachain. + asset.id.0 == Location::here() && location == &AssetHubLocation::get() + } +} + +pub struct TeleportFilter; +impl Contains<(Location, Vec)> for TeleportFilter { + fn contains(item: &(Location, Vec)) -> bool { + // We only allow teleportation of PLMC, but anyone can do it + let (_loc, assets) = item; + assets.iter().all(|asset| asset.id.0 == Location::here()) + } +} + /// Means for transacting assets on this chain. /// FungibleTransactor is a FungibleAdapter that allows for transacting PLMC. /// ForeignAssetsAdapter is a FungiblesAdapter that allows for transacting foreign assets. @@ -306,8 +323,7 @@ impl xcm_executor::Config for XcmConfig { type HrmpNewChannelOpenRequestHandler = (); /// Locations that we trust to act as reserves for specific assets. type IsReserve = Reserves; - /// Currently we do not support teleportation of PLMC or other assets. - type IsTeleporter = (); + type IsTeleporter = PLMCToHubTeleport; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type MessageExporter = (); type OriginConverter = XcmOriginToTransactDispatchOrigin; @@ -453,7 +469,7 @@ impl pallet_xcm::Config for Runtime { type XcmReserveTransferFilter = AssetHubAssetsAsReserve; type XcmRouter = XcmRouter; // We do not allow teleportation of PLMC or other assets. - type XcmTeleportFilter = Nothing; + type XcmTeleportFilter = TeleportFilter; const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; } diff --git a/runtimes/shared-configuration/src/funding.rs b/runtimes/shared-configuration/src/funding.rs index 9f59119e9..7815bedc3 100644 --- a/runtimes/shared-configuration/src/funding.rs +++ b/runtimes/shared-configuration/src/funding.rs @@ -22,16 +22,19 @@ use sp_runtime::Perquintill; use sp_std::{collections::btree_map::BTreeMap, vec, vec::Vec}; use xcm::v4::Location; +#[cfg(feature = "instant-mode")] +pub const EVALUATION_ROUND_DURATION: BlockNumber = 7; +#[cfg(feature = "fast-mode")] +pub const EVALUATION_ROUND_DURATION: BlockNumber = 10 * crate::MINUTES; #[cfg(not(any(feature = "fast-mode", feature = "instant-mode")))] pub const EVALUATION_ROUND_DURATION: BlockNumber = 7 * crate::DAYS; -#[cfg(not(any(feature = "fast-mode", feature = "instant-mode")))] -pub const AUCTION_ROUND_DURATION: BlockNumber = 14 * crate::DAYS; - -#[cfg(not(any(feature = "fast-mode", feature = "instant-mode")))] -pub const COMMUNITY_ROUND_DURATION: BlockNumber = 5 * crate::DAYS; +#[cfg(feature = "instant-mode")] +pub const AUCTION_ROUND_DURATION: BlockNumber = 7; +#[cfg(feature = "fast-mode")] +pub const AUCTION_ROUND_DURATION: BlockNumber = 30 * crate::MINUTES; #[cfg(not(any(feature = "fast-mode", feature = "instant-mode")))] -pub const REMAINDER_ROUND_DURATION: BlockNumber = 2 * crate::DAYS; +pub const AUCTION_ROUND_DURATION: BlockNumber = 7 * crate::DAYS; pub type ProjectIdentifier = u32;