diff --git a/examples/paraToRelayTransferMultiAsset.ts b/examples/paraToRelayTransferMultiAsset.ts new file mode 100644 index 00000000..edde9eb0 --- /dev/null +++ b/examples/paraToRelayTransferMultiAsset.ts @@ -0,0 +1,44 @@ +/** + * When importing from @substrate/asset-transfer-api it would look like the following + * + * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' + */ +import { AssetTransferApi, constructApiPromise } from '../src'; +import { TxResult } from '../src/types'; +import { GREEN, PURPLE, RESET } from './colors'; + +/** + * In this example we are creating a call to send KSM from a Moonriver (Parachain) account + * to a Kusama Relay chain account, where the `xcmVersion` is set to 3, and `isLimited` is false declaring that + * it will allow `unlimited` weight for the tx. + * + * NOTE: When `isLimited` is true it will expect for refTime and proofSize to be provided as additional arguments. + */ +const main = async () => { + const { api, specName, safeXcmVersion } = await constructApiPromise('wss://moonriver.public.blastapi.io'); + const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); + let callInfo: TxResult<'call'>; + try { + callInfo = await assetApi.createTransferTransaction( + '0', + '0xc4db7bcb733e117c0b34ac96354b10d47e84a006b9e7e66a229d174e8ff2a063', + ['KSM'], + ['1000000000000'], + { + format: 'call', + isLimited: false, + xcmVersion: 3, + } + ); + + console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`); + } catch (e) { + console.error(e); + throw Error(e as string); + } + + const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); + console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); +}; + +main().finally(() => process.exit()); diff --git a/src/AssetTransferApi.spec.ts b/src/AssetTransferApi.spec.ts index deecd164..3d7030d9 100644 --- a/src/AssetTransferApi.spec.ts +++ b/src/AssetTransferApi.spec.ts @@ -336,6 +336,22 @@ describe('AssetTransferAPI', () => { moonriverAssetsApi.registry ); + expect(assetCallType).toEqual('Reserve'); + }); + }); + describe('ParaToRelay', () => { + it('Should correctly return Reserve', () => { + const assetCallType = moonriverAssetsApi['fetchCallType']( + '2023', + '0', + ['KSM'], + Direction.ParaToRelay, + AssetType.Native, + false, + false, + moonriverAssetsApi.registry + ); + expect(assetCallType).toEqual('Reserve'); }); }); diff --git a/src/AssetTransferApi.ts b/src/AssetTransferApi.ts index e33f1371..2e0bad9f 100644 --- a/src/AssetTransferApi.ts +++ b/src/AssetTransferApi.ts @@ -306,12 +306,14 @@ export class AssetTransferApi { let txMethod: Methods; let transaction: SubmittableExtrinsic<'promise', ISubmittableResult>; - if ( (xcmPallet === XcmPalletName.xTokens || xcmPallet === XcmPalletName.xtokens) && - (xcmDirection === Direction.ParaToSystem || xcmDirection === Direction.ParaToPara) + (xcmDirection === Direction.ParaToSystem || + xcmDirection === Direction.ParaToPara || + xcmDirection === Direction.ParaToRelay) ) { - if (!paysWithFeeDest && assetIds.length < 2) { + // This ensures paraToRelay always uses `transferMultiAsset`. + if (xcmDirection === Direction.ParaToRelay || (!paysWithFeeDest && assetIds.length < 2)) { txMethod = 'transferMultiasset'; transaction = await transferMultiasset( _api, @@ -599,8 +601,6 @@ export class AssetTransferApi { * Check if the origin is a Parachain or Parathread */ if (originIsParachain && destIsRelayChain) { - throw new BaseError('ParaToRelay is not yet implemented', BaseErrorsEnum.NotImplemented); - return Direction.ParaToRelay; } diff --git a/src/createXcmCalls/util/establishXcmPallet.ts b/src/createXcmCalls/util/establishXcmPallet.ts index 70889613..d0211349 100644 --- a/src/createXcmCalls/util/establishXcmPallet.ts +++ b/src/createXcmCalls/util/establishXcmPallet.ts @@ -68,7 +68,9 @@ const isXTokensOriginNonForeignAssetsPalletTx = ( !isForeignAssetsTransfer && !isParachainPrimaryNativeAsset && direction && - (direction === Direction.ParaToSystem || direction === Direction.ParaToPara) && + (direction === Direction.ParaToSystem || + direction === Direction.ParaToPara || + direction === Direction.ParaToRelay) && xPallet ) { return true; diff --git a/src/createXcmTypes/ParaToRelay.spec.ts b/src/createXcmTypes/ParaToRelay.spec.ts new file mode 100644 index 00000000..c2cff2e0 --- /dev/null +++ b/src/createXcmTypes/ParaToRelay.spec.ts @@ -0,0 +1,250 @@ +// Copyright 2023 Parity Technologies (UK) Ltd. + +import { Registry } from '../registry'; +import { adjustedMockMoonriverParachainApi } from '../testHelpers/adjustedMockMoonriverParachainApi'; +import { ParaToRelay } from './ParaToRelay'; + +describe('ParaToRelay', () => { + const registry = new Registry('Moonriver', {}); + const assetOpts = { + registry, + isLiquidTokenTransfer: false, + isForeignAssetsTransfer: false, + api: adjustedMockMoonriverParachainApi, + }; + describe('Beneficiary', () => { + it('Should work for V2', () => { + const beneficiary = ParaToRelay.createBeneficiary( + '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', + 2 + ); + + const expectedRes = { + V2: { + parents: 0, + interior: { + X1: { + AccountId32: { + id: '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', + network: 'Any', + }, + }, + }, + }, + }; + + expect(beneficiary).toStrictEqual(expectedRes); + }); + it('Should work for V3', () => { + const beneficiary = ParaToRelay.createBeneficiary( + '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', + 3 + ); + + const expectedRes = { + V3: { + parents: 0, + interior: { + X1: { + AccountId32: { + id: '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', + }, + }, + }, + }, + }; + + expect(beneficiary).toStrictEqual(expectedRes); + }); + }); + describe('Dest', () => { + it('Should work for V2', () => { + const dest = ParaToRelay.createDest('', 2); + const expected = { + V2: { + parents: 1, + interior: { + Here: null, + }, + }, + }; + expect(dest).toStrictEqual(expected); + }); + it('Should work for V3', () => { + const dest = ParaToRelay.createDest('', 3); + const expected = { + V3: { + parents: 1, + interior: { + Here: null, + }, + }, + }; + expect(dest).toStrictEqual(expected); + }); + }); + describe('Assets', () => { + it('Should work for V2', async () => { + const asset = await ParaToRelay.createAssets(['1000000'], 2, 'Moonriver', ['ksm'], assetOpts); + const expected = { + V2: [ + { + fun: { + Fungible: '1000000', + }, + id: { + Concrete: { + interior: { + Here: '', + }, + parents: 1, + }, + }, + }, + ], + }; + expect(asset).toStrictEqual(expected); + }); + it('Should work for V3', async () => { + const asset = await ParaToRelay.createAssets(['1000000'], 3, 'Moonriver', ['ksm'], assetOpts); + const expected = { + V3: [ + { + fun: { + Fungible: '1000000', + }, + id: { + Concrete: { + interior: { + Here: '', + }, + parents: 1, + }, + }, + }, + ], + }; + expect(asset).toStrictEqual(expected); + }); + }); + describe('WeightLimit', () => { + it('Should work for unLimited', () => { + const weightLimit = ParaToRelay.createWeightLimit({ isLimited: true }); + const expected = { + Unlimited: null, + }; + expect(weightLimit).toStrictEqual(expected); + }); + it('Should work for a weightLimit', () => { + const weightLimit = ParaToRelay.createWeightLimit({ + isLimited: true, + weightLimit: { + refTime: '100000000', + proofSize: '10000', + }, + }); + const expected = { + Limited: { + refTime: '100000000', + proofSize: '10000', + }, + }; + expect(weightLimit).toStrictEqual(expected); + }); + }); + describe('FeeAssetItem', () => { + const opts = { + registry, + isLiquidTokenTransfer: false, + isForeignAssetsTransfer: false, + }; + it('Should return zero', async () => { + const feeAssetItem = await ParaToRelay.createFeeAssetItem(adjustedMockMoonriverParachainApi, opts); + expect(feeAssetItem).toStrictEqual(0); + }); + }); + describe('XTokensBeneficiaryDest', () => { + it('Should work for V2', () => { + if (ParaToRelay.createXTokensBeneficiary) { + const xTokensBeneficiary = ParaToRelay.createXTokensBeneficiary( + '', + '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', + 2 + ); + const expected = { + V2: { + parents: 1, + interior: { + X1: { AccountId32: { id: '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b' } }, + }, + }, + }; + + expect(xTokensBeneficiary).toStrictEqual(expected); + } + }); + it('Should work for V3', () => { + if (ParaToRelay.createXTokensBeneficiary) { + const xTokensBeneficiary = ParaToRelay.createXTokensBeneficiary( + '', + '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', + 3 + ); + const expected = { + V3: { + parents: 1, + interior: { + X1: { AccountId32: { id: '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b' } }, + }, + }, + }; + + expect(xTokensBeneficiary).toStrictEqual(expected); + } + }); + }); + describe('XTokensAsset', () => { + it('Should work for V2', async () => { + if (ParaToRelay.createXTokensAsset) { + const xTokensAsset = await ParaToRelay.createXTokensAsset('1000000', 2, 'Moonriver', 'KSM', assetOpts); + const expected = { + V2: { + id: { + Concrete: { + parents: 1, + interior: { + Here: null, + }, + }, + }, + fun: { + Fungible: { Fungible: '1000000' }, + }, + }, + }; + expect(xTokensAsset).toStrictEqual(expected); + } + }); + it('Should work for V3', async () => { + if (ParaToRelay.createXTokensAsset) { + const xTokensAsset = await ParaToRelay.createXTokensAsset('1000000', 3, 'Moonriver', 'KSM', assetOpts); + const expected = { + V3: { + id: { + Concrete: { + parents: 1, + interior: { + Here: null, + }, + }, + }, + fun: { + Fungible: { Fungible: '1000000' }, + }, + }, + }; + expect(xTokensAsset).toStrictEqual(expected); + } + }); + }); +}); diff --git a/src/createXcmTypes/ParaToRelay.ts b/src/createXcmTypes/ParaToRelay.ts new file mode 100644 index 00000000..41714516 --- /dev/null +++ b/src/createXcmTypes/ParaToRelay.ts @@ -0,0 +1,180 @@ +// Copyright 2023 Parity Technologies (UK) Ltd. + +import type { ApiPromise } from '@polkadot/api'; + +import { + CreateWeightLimitOpts, + ICreateXcmType, + UnionXcAssetsMultiAsset, + UnionXcmMultiAssets, + XcmDestBenificiary, + XcmDestBenificiaryXcAssets, + XcmMultiAsset, + XcmWeight, +} from './types'; + +export const ParaToRelay: ICreateXcmType = { + /** + * Create a XcmVersionedMultiLocation structured type for a beneficiary. + * + * @param accountId The accountId of the beneficiary. + * @param xcmVersion The accepted xcm version. + */ + createBeneficiary: (accountId: string, xcmVersion: number): XcmDestBenificiary => { + if (xcmVersion === 2) { + return { + V2: { + parents: 0, + interior: { + X1: { + AccountId32: { + network: 'Any', + id: accountId, + }, + }, + }, + }, + }; + } + + return { + V3: { + parents: 0, + interior: { + X1: { + AccountId32: { + id: accountId, + }, + }, + }, + }, + }; + }, + /** + * Create a XcmVersionedMultiLocation structured type for a destination. + * + * @param destId The destId in this case, which is the relay chain. + * @param xcmVersion The accepted xcm version. + */ + createDest: (_: string, xcmVersion: number): XcmDestBenificiary => { + if (xcmVersion === 2) { + return { + V2: { + parents: 1, + interior: { + Here: null, + }, + }, + }; + } + + return { + V3: { + parents: 1, + interior: { + Here: null, + }, + }, + }; + }, + /** + * Create a VersionedMultiAsset structured type. + * + * @param amounts The amount for a relay native asset. The length will always be one. + * @param xcmVersion The accepted xcm version. + */ + createAssets: (amounts: string[], xcmVersion: number): Promise => { + const multiAssets: XcmMultiAsset[] = []; + + const amount = amounts[0]; + const multiAsset = { + fun: { + Fungible: amount, + }, + id: { + Concrete: { + interior: { + Here: '', + }, + parents: 1, + }, + }, + } as XcmMultiAsset; + + multiAssets.push(multiAsset); + + if (xcmVersion === 2) { + return Promise.resolve({ + V2: multiAssets, + }); + } else { + return Promise.resolve({ + V3: multiAssets, + }); + } + }, + /** + * Create an Xcm WeightLimit structured type. + * + * @param opts Options that are used for WeightLimit. + */ + createWeightLimit: (opts: CreateWeightLimitOpts): XcmWeight => { + return opts.isLimited && opts.weightLimit?.refTime && opts.weightLimit?.proofSize + ? { + Limited: { + refTime: opts.weightLimit?.refTime, + proofSize: opts.weightLimit?.proofSize, + }, + } + : { Unlimited: null }; + }, + /** + * Return the correct feeAssetItem based on XCM direction. + * In this case it will always be zero since there is no `feeAssetItem` for this direction. + */ + createFeeAssetItem: async (_: ApiPromise): Promise => { + return await Promise.resolve(0); + }, + createXTokensBeneficiary: (_: string, accountId: string, xcmVersion: number): XcmDestBenificiaryXcAssets => { + if (xcmVersion === 2) { + return { + V2: { + parents: 1, + interior: { + X1: { AccountId32: { id: accountId } }, + }, + }, + }; + } + + return { + V3: { + parents: 1, + interior: { + X1: { AccountId32: { id: accountId } }, + }, + }, + }; + }, + createXTokensAsset: (amount: string, xcmVersion: number): Promise => { + const multiAsset = { + id: { + Concrete: { + parents: 1, + interior: { + Here: null, + }, + }, + }, + fun: { + Fungible: { Fungible: amount }, + }, + }; + + if (xcmVersion === 2) { + return Promise.resolve({ V2: multiAsset }); + } else { + return Promise.resolve({ V3: multiAsset }); + } + }, +}; diff --git a/src/createXcmTypes/index.ts b/src/createXcmTypes/index.ts index f1e7f490..250f0f11 100644 --- a/src/createXcmTypes/index.ts +++ b/src/createXcmTypes/index.ts @@ -2,6 +2,7 @@ import { Direction } from '../types'; import { ParaToPara } from './ParaToPara'; +import { ParaToRelay } from './ParaToRelay'; import { ParaToSystem } from './ParaToSystem'; import { RelayToPara } from './RelayToPara'; import { RelayToSystem } from './RelayToSystem'; @@ -21,6 +22,6 @@ export const createXcmTypes: ICreateXcmTypeLookup = { SystemToRelay, RelayToSystem, ParaToPara, - ParaToRelay: {} as ICreateXcmType, + ParaToRelay, ParaToSystem, }; diff --git a/src/errors/checkXcmTxInputs.spec.ts b/src/errors/checkXcmTxInputs.spec.ts index 10d98b27..7c2b2c3a 100644 --- a/src/errors/checkXcmTxInputs.spec.ts +++ b/src/errors/checkXcmTxInputs.spec.ts @@ -465,6 +465,86 @@ describe('checkAssetIds', () => { ); }).not.toThrow(); }); + it('Should not throw an error for ParaToRelay when its a valid', async () => { + const registry = new Registry('moonriver', {}); + const currentRegistry = registry.currentRelayRegistry; + // eslint-disable-next-line @typescript-eslint/await-thenable + await expect(async () => { + await checkAssetIdInput( + adjustedMockMoonriverParachainApi, + ['KSM'], + currentRegistry, + 'moonriver', + Direction.ParaToRelay, + registry, + 2, + false, + false + ); + }).not.toThrow(); + // eslint-disable-next-line @typescript-eslint/await-thenable + await expect(async () => { + await checkAssetIdInput( + adjustedMockMoonriverParachainApi, + ['42259045809535163221576417993425387648'], + currentRegistry, + 'moonriver', + Direction.ParaToRelay, + registry, + 2, + false, + false + ); + }).not.toThrow(); + // eslint-disable-next-line @typescript-eslint/await-thenable + await expect(async () => { + await checkAssetIdInput( + adjustedMockMoonriverParachainApi, + [''], + currentRegistry, + 'moonriver', + Direction.ParaToRelay, + registry, + 2, + false, + false + ); + }).not.toThrow(); + // eslint-disable-next-line @typescript-eslint/await-thenable + await expect(async () => { + await checkAssetIdInput( + adjustedMockMoonriverParachainApi, + [], + currentRegistry, + 'moonriver', + Direction.ParaToRelay, + registry, + 2, + false, + false + ); + }).not.toThrow(); + }); + it('Should error when an invalid assetId is inputted for ParaToRelay', async () => { + const registry = new Registry('karura', {}); + const currentRegistry = registry.currentRelayRegistry; + + await expect(async () => { + await checkAssetIdInput( + adjustedMockMoonriverParachainApi, + ['TEST'], + currentRegistry, + 'crust-collator', + Direction.ParaToRelay, + registry, + 2, + false, + false + ); + }).rejects.toThrowError( + "The current input for assetId's does not meet our specifications for ParaToRelay transfers." + ); + }); }); describe('checkIfNativeRelayChainAssetPresentInMultiAssetIdList', () => { diff --git a/src/errors/checkXcmTxInputs.ts b/src/errors/checkXcmTxInputs.ts index 857c3c98..9885a0b7 100644 --- a/src/errors/checkXcmTxInputs.ts +++ b/src/errors/checkXcmTxInputs.ts @@ -764,6 +764,55 @@ const checkSystemToSystemAssetId = async ( ); }; +/** + * Check the assets for ParaToRelay. + * + * @param assetId + * @param registry + * @param specName + */ +const checkParaToRelayAssetId = (assetId: string, registry: Registry, specName: string) => { + const relayRegistry = registry.currentRelayRegistry; + const relayNativeToken = relayRegistry[0].tokens[0]; + const nativeEqAssetId = relayNativeToken === assetId; + const curParaId = registry.lookupChainIdBySpecName(specName); + const curParaRegistry = relayRegistry[curParaId]; + const { xcAssetsData } = curParaRegistry; + + let assetIsRelayChainNativeAsset = false; + if (assetId === '') { + assetIsRelayChainNativeAsset = true; + } + + // If the asset equals the relays native asset exit. + if (nativeEqAssetId) { + assetIsRelayChainNativeAsset = true; + } + + const isValidInt = validateNumber(assetId); + if (isValidInt && xcAssetsData && xcAssetsData.length > 0) { + // We assume the first xcAsset will represent the relay chain + // since they are all sorted in the registry. + if (xcAssetsData[0].asset === assetId) { + assetIsRelayChainNativeAsset = true; + } + } + + const paraIncludesRelayNativeInTokens = curParaRegistry.tokens.includes(relayNativeToken); + const paraIncludesRelayNativeInXcAssets = xcAssetsData && xcAssetsData[0].symbol === relayNativeToken; + + if (paraIncludesRelayNativeInTokens || paraIncludesRelayNativeInXcAssets) { + assetIsRelayChainNativeAsset = true; + } + + if (!assetIsRelayChainNativeAsset) { + throw new BaseError( + "The current input for assetId's does not meet our specifications for ParaToRelay transfers.", + BaseErrorsEnum.InvalidAsset + ); + } +}; + /** * Checks to ensure that assetId's have a length no greater than MAX_ASSETS_FOR_TRANSFER, throws an error if greater than MAX_ASSETS_FOR_TRANSFER * @@ -975,6 +1024,10 @@ export const checkAssetIdInput = async ( if (xcmDirection === Direction.ParaToSystem || xcmDirection === Direction.ParaToPara) { await checkParaOriginAssetId(api, assetId, specName, registry); } + + if (xcmDirection === Direction.ParaToRelay) { + checkParaToRelayAssetId(assetId, registry, specName); + } } }; @@ -1096,4 +1149,9 @@ export const checkXcmTxInputs = async ( CheckXTokensPalletOriginIsNonForeignAssetTx(xcmDirection, xcmPallet, isForeignAssetsTransfer); checkAssetsAmountMatch(assetIds, amounts, isParachainPrimaryNativeAsset); } + + if (xcmDirection === Direction.ParaToRelay) { + checkRelayAssetIdLength(assetIds); + checkRelayAmountsLength(amounts); + } }; diff --git a/src/integrationTests/parachains/bifrost.spec.ts b/src/integrationTests/parachains/bifrost.spec.ts index 7f5fb84e..f893480f 100644 --- a/src/integrationTests/parachains/bifrost.spec.ts +++ b/src/integrationTests/parachains/bifrost.spec.ts @@ -1177,4 +1177,144 @@ describe('Bifrost', () => { }); }); }); + describe('ParaToRelay', () => { + describe('transferMultiasset', () => { + describe('XCM V2', () => { + it('Should correctly build xTokens transferMultiasset txs from Bifrost', async () => { + const tests: TestMultiassetWithFormat[] = [ + [ + '0', + 'xcKSM', + 'call', + { + dest: 'kusama', + origin: 'bifrost', + direction: 'ParaToRelay' as Direction, + format: 'call', + method: 'transferMultiasset', + tx: '0x460101000100000700e40b54020101010100f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b01a10f411f', + xcmVersion: 2, + }, + ], + [ + '0', + '42259045809535163221576417993425387648', + 'call', + { + dest: 'kusama', + origin: 'bifrost', + direction: 'ParaToRelay' as Direction, + format: 'call', + method: 'transferMultiasset', + tx: '0x460101000100000700e40b54020101010100f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b01a10f411f', + xcmVersion: 2, + }, + ], + [ + '0', + 'ksm', + 'payload', + { + dest: 'kusama', + origin: 'bifrost', + direction: 'ParaToRelay' as Direction, + format: 'payload', + method: 'transferMultiasset', + tx: '0xdc460101000100000700e40b54020101010100f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b01a10f411f45022800fe080000040000000000000000000000000000000000000000000000000000000000000000000000be2554aa8a0151eb4d706308c47d16996af391e4c5e499c7cbef24259b7d4503', + xcmVersion: 2, + }, + ], + ]; + + for (const test of tests) { + const [paraId, assetId, format, expectedResult] = test; + const res = await bifrostTransferMultiasset(bifrostATA, format as Format, 2, paraId, assetId, { + isLimited: true, + weightLimit: { + refTime: '1000', + proofSize: '2000', + }, + isForeignAssetsTransfer: false, + isLiquidTokenTransfer: false, + }); + + expect(res).toEqual(expectedResult); + } + }); + it('Should correctly build a V2 submittable transferMultiasset', async () => { + const res = await bifrostTransferMultiasset(bifrostATA, 'submittable', 2, '0', 'ksm', { + isLimited: true, + weightLimit: { + refTime: '1000', + proofSize: '2000', + }, + isForeignAssetsTransfer: false, + isLiquidTokenTransfer: false, + }); + expect(res.tx.toRawType()).toEqual('Extrinsic'); + }); + }); + describe('XCM V3', () => { + it('Should correctly build xTokens transferMultiasset txs from Bifrost', async () => { + const tests: TestMultiassetWithFormat[] = [ + [ + '0', + 'ksm', + 'call', + { + dest: 'kusama', + origin: 'bifrost', + direction: 'ParaToRelay' as Direction, + format: 'call', + method: 'transferMultiasset', + tx: '0x460103000100000700e40b54020301010100f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b01a10f411f', + xcmVersion: 3, + }, + ], + [ + '0', + '42259045809535163221576417993425387648', // SDN + 'payload', + { + dest: 'kusama', + origin: 'bifrost', + direction: 'ParaToRelay' as Direction, + format: 'payload', + method: 'transferMultiasset', + tx: '0xdc460103000100000700e40b54020301010100f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b01a10f411f45022800fe080000040000000000000000000000000000000000000000000000000000000000000000000000be2554aa8a0151eb4d706308c47d16996af391e4c5e499c7cbef24259b7d4503', + xcmVersion: 3, + }, + ], + ]; + + for (const test of tests) { + const [paraId, assetId, format, expectedResult] = test; + const res = await bifrostTransferMultiasset(bifrostATA, format as Format, 3, paraId, assetId, { + isLimited: true, + weightLimit: { + refTime: '1000', + proofSize: '2000', + }, + isForeignAssetsTransfer: false, + isLiquidTokenTransfer: false, + }); + + expect(res).toEqual(expectedResult); + } + }); + it('Should correctly build a V3 submittable transferMultiasset', async () => { + const res = await bifrostTransferMultiasset(bifrostATA, 'submittable', 3, '0', 'ksm', { + isLimited: true, + weightLimit: { + refTime: '1000', + proofSize: '2000', + }, + isForeignAssetsTransfer: false, + isLiquidTokenTransfer: false, + }); + expect(res.tx.toRawType()).toEqual('Extrinsic'); + }); + }); + }); + }); }); diff --git a/src/integrationTests/parachains/moonriver.spec.ts b/src/integrationTests/parachains/moonriver.spec.ts index 3e8d5f90..4fc3f6c6 100644 --- a/src/integrationTests/parachains/moonriver.spec.ts +++ b/src/integrationTests/parachains/moonriver.spec.ts @@ -1134,4 +1134,144 @@ describe('Moonriver', () => { }); }); }); + describe('ParaToRelay', () => { + describe('transferMultiasset', () => { + describe('XCM V2', () => { + it('Should correctly build xTokens transferMultiasset txs from Moonriver', async () => { + const tests: TestMultiassetWithFormat[] = [ + [ + '0', + 'xcKSM', + 'call', + { + dest: 'kusama', + origin: 'moonriver', + direction: 'ParaToRelay' as Direction, + format: 'call', + method: 'transferMultiasset', + tx: '0x6a0101000100000700e40b54020101010100f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b01a10f411f', + xcmVersion: 2, + }, + ], + [ + '0', + '42259045809535163221576417993425387648', + 'call', + { + dest: 'kusama', + origin: 'moonriver', + direction: 'ParaToRelay' as Direction, + format: 'call', + method: 'transferMultiasset', + tx: '0x6a0101000100000700e40b54020101010100f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b01a10f411f', + xcmVersion: 2, + }, + ], + [ + '0', + 'ksm', + 'payload', + { + dest: 'kusama', + origin: 'moonriver', + direction: 'ParaToRelay' as Direction, + format: 'payload', + method: 'transferMultiasset', + tx: '0xdc6a0101000100000700e40b54020101010100f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b01a10f411f45022800fe080000040000000000000000000000000000000000000000000000000000000000000000000000be2554aa8a0151eb4d706308c47d16996af391e4c5e499c7cbef24259b7d4503', + xcmVersion: 2, + }, + ], + ]; + + for (const test of tests) { + const [paraId, assetId, format, expectedResult] = test; + const res = await moonriverTransferMultiasset(moonriverATA, format as Format, 2, paraId, assetId, { + isLimited: true, + weightLimit: { + refTime: '1000', + proofSize: '2000', + }, + isForeignAssetsTransfer: false, + isLiquidTokenTransfer: false, + }); + + expect(res).toEqual(expectedResult); + } + }); + it('Should correctly build a V2 submittable transferMultiasset', async () => { + const res = await moonriverTransferMultiasset(moonriverATA, 'submittable', 2, '0', 'ksm', { + isLimited: true, + weightLimit: { + refTime: '1000', + proofSize: '2000', + }, + isForeignAssetsTransfer: false, + isLiquidTokenTransfer: false, + }); + expect(res.tx.toRawType()).toEqual('Extrinsic'); + }); + }); + describe('XCM V3', () => { + it('Should correctly build xTokens transferMultiasset txs from Moonriver', async () => { + const tests: TestMultiassetWithFormat[] = [ + [ + '0', + 'ksm', + 'call', + { + dest: 'kusama', + origin: 'moonriver', + direction: 'ParaToRelay' as Direction, + format: 'call', + method: 'transferMultiasset', + tx: '0x6a0103000100000700e40b54020301010100f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b01a10f411f', + xcmVersion: 3, + }, + ], + [ + '0', + '42259045809535163221576417993425387648', // SDN + 'payload', + { + dest: 'kusama', + origin: 'moonriver', + direction: 'ParaToRelay' as Direction, + format: 'payload', + method: 'transferMultiasset', + tx: '0xdc6a0103000100000700e40b54020301010100f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b01a10f411f45022800fe080000040000000000000000000000000000000000000000000000000000000000000000000000be2554aa8a0151eb4d706308c47d16996af391e4c5e499c7cbef24259b7d4503', + xcmVersion: 3, + }, + ], + ]; + + for (const test of tests) { + const [paraId, assetId, format, expectedResult] = test; + const res = await moonriverTransferMultiasset(moonriverATA, format as Format, 3, paraId, assetId, { + isLimited: true, + weightLimit: { + refTime: '1000', + proofSize: '2000', + }, + isForeignAssetsTransfer: false, + isLiquidTokenTransfer: false, + }); + + expect(res).toEqual(expectedResult); + } + }); + it('Should correctly build a V3 submittable transferMultiasset', async () => { + const res = await moonriverTransferMultiasset(moonriverATA, 'submittable', 3, '0', 'ksm', { + isLimited: true, + weightLimit: { + refTime: '1000', + proofSize: '2000', + }, + isForeignAssetsTransfer: false, + isLiquidTokenTransfer: false, + }); + expect(res.tx.toRawType()).toEqual('Extrinsic'); + }); + }); + }); + }); }); diff --git a/src/registry/Registry.ts b/src/registry/Registry.ts index ce2d8fd3..61e822fc 100644 --- a/src/registry/Registry.ts +++ b/src/registry/Registry.ts @@ -212,7 +212,7 @@ export class Registry { /** * Return the info for a parachain within a relay chains registry. * - * @param id + * @param id ParaId */ public lookupParachainInfo(id: string): ExpandedChainInfoKeys[] { const chainIds = Object.keys(this.currentRelayRegistry);