Skip to content
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

feat: ParaToRelay #321

Merged
merged 21 commits into from
Nov 13, 2023
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
44 changes: 44 additions & 0 deletions examples/paraToRelayTransferMultiAsset.ts
Original file line number Diff line number Diff line change
@@ -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());
16 changes: 16 additions & 0 deletions src/AssetTransferApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
Expand Down
10 changes: 5 additions & 5 deletions src/AssetTransferApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
marshacb marked this conversation as resolved.
Show resolved Hide resolved
txMethod = 'transferMultiAsset';
transaction = await transferMultiAsset(
_api,
Expand Down Expand Up @@ -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;
}

Expand Down
4 changes: 3 additions & 1 deletion src/createXcmCalls/util/establishXcmPallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
250 changes: 250 additions & 0 deletions src/createXcmTypes/ParaToRelay.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
// Copyright 2023 Parity Technologies (UK) Ltd.

import { Registry } from '../registry';
import { adjustedMockParachainApi } from '../testHelpers/adjustedMockParachainApi';
import { ParaToRelay } from './ParaToRelay';

describe('ParaToRelay', () => {
const registry = new Registry('Moonriver', {});
const assetOpts = {
registry,
isLiquidTokenTransfer: false,
isForeignAssetsTransfer: false,
api: adjustedMockParachainApi,
};
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(adjustedMockParachainApi, 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);
}
});
});
});
Loading