Skip to content

Commit

Permalink
Fixes after integration (#384)
Browse files Browse the repository at this point in the history
* adjustments after integrating XCM in the dapp

* normalize extrinsic build for xcm versions

* update test snapshot

* add tests for normalize functions

* remove unused import

* change wording and remove log

* Update packages/builder/src/extrinsic/ExtrinsicBuilder.utils.ts

Co-authored-by: Richard Kenigs <[email protected]>

* apply type inference and fix tyeps

---------

Co-authored-by: Richard Kenigs <[email protected]>
  • Loading branch information
mmaurello and Rihyx authored Nov 13, 2024
1 parent 82e55a4 commit 9ea36ad
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 96 deletions.
61 changes: 61 additions & 0 deletions packages/builder/src/extrinsic/ExtrinsicBuilder.utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { describe, expect, it } from 'vitest';
import { XcmVersion } from './ExtrinsicBuilder.interfaces';
import { normalizeConcrete, normalizeX1 } from './ExtrinsicBuilder.utils';

describe('normalizeX1', () => {
it('should return original object when xcmVersion < v4', () => {
const input = {
interior: { X1: { Parachain: 1000 } },
};

expect(normalizeX1(XcmVersion.v3, input)).toEqual(input);
});

it('should wrap X1 value in array for xcmVersion >= v4', () => {
const input = {
interior: { X1: { Parachain: 1000 } },
};
const expected = {
interior: { X1: [{ Parachain: 1000 }] },
};

expect(normalizeX1(XcmVersion.v4, input)).toEqual(expected);
});

it('should handle lowercase x1 key', () => {
const input = {
interior: { x1: { Parachain: 1000 } },
};
const expected = {
interior: { x1: [{ Parachain: 1000 }] },
};

expect(normalizeX1(XcmVersion.v4, input)).toEqual(expected);
});

it('should not modify object without X1/x1 key', () => {
const input = {
interior: { X2: [{ Parachain: 1000 }, { GeneralIndex: 1 }] },
};

expect(normalizeX1(XcmVersion.v4, input)).toEqual(input);
});
});

describe('normalizeConcrete', () => {
it('should return original object for xcmVersion >= v4', () => {
const input = { Parachain: 1000 };

expect(normalizeConcrete(XcmVersion.v4, input)).toBe(input);
expect(normalizeConcrete(XcmVersion.v5, input)).toBe(input);
});

it('should wrap object in Concrete for xcmVersion < v4', () => {
const input = { Parachain: 1000 };
const expected = { Concrete: { Parachain: 1000 } };

expect(normalizeConcrete(XcmVersion.v3, input)).toEqual(expected);
expect(normalizeConcrete(XcmVersion.v2, input)).toEqual(expected);
expect(normalizeConcrete(XcmVersion.v1, input)).toEqual(expected);
});
});
38 changes: 38 additions & 0 deletions packages/builder/src/extrinsic/ExtrinsicBuilder.utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { SubmittableExtrinsicFunction } from '@polkadot/api/types';
import { getTypeDef } from '@polkadot/types';
import type { AnyJson } from '@polkadot/types/types';
import { u8aToHex } from '@polkadot/util';
import { decodeAddress } from '@polkadot/util-crypto';
import { XcmVersion } from './ExtrinsicBuilder.interfaces';
Expand Down Expand Up @@ -61,3 +62,40 @@ export function getExtrinsicAccount(address: string) {
},
};
}

export function isXcmV4(xcmVersion: XcmVersion): boolean {
return xcmVersion >= XcmVersion.v4;
}

export function normalizeX1(
xcmVersion: XcmVersion,
versionedObject: Record<string, AnyJson | object>,
) {
if (!isXcmV4(xcmVersion)) return versionedObject;

const normalizedObject = { ...versionedObject };
const interior = normalizedObject.interior as object;

if ('X1' in interior && interior?.X1 && !Array.isArray(interior.X1)) {
interior.X1 = [interior.X1];
} else if ('x1' in interior && interior?.x1 && !Array.isArray(interior.x1)) {
interior.x1 = [interior.x1];
}

return normalizedObject;
}

export function normalizeConcrete(
xcmVersion: XcmVersion,
versionedObject: object,
) {
return isXcmV4(xcmVersion)
? versionedObject
: applyConcreteWrapper(versionedObject);
}

export function applyConcreteWrapper(versionedObject: object) {
return {
Concrete: { ...versionedObject },
};
}
97 changes: 49 additions & 48 deletions packages/builder/src/extrinsic/pallets/polkadotXcm/polkadotXcm.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { ExtrinsicConfig } from '../../../types/substrate/ExtrinsicConfig';
import type { ExtrinsicConfigBuilder } from '../../ExtrinsicBuilder.interfaces';
import {
getExtrinsicArgumentVersion,
normalizeConcrete,
} from '../../ExtrinsicBuilder.utils';
import {
getPolkadotXcmExtrinsicArgs,
shouldFeeAssetPrecedeAsset,
Expand Down Expand Up @@ -75,25 +79,24 @@ export function polkadotXcm() {
func,
getArgs: (extrinsicFunction) => {
const isAssetDifferent = !params.asset.isSame(params.fee);
const version = getExtrinsicArgumentVersion(extrinsicFunction);

const assets = [
{
id: {
Concrete: {
parents: 0,
interior: {
X2: [
{
PalletInstance:
params.asset.getAssetPalletInstance(),
},
{
GeneralIndex: params.asset.getAssetId(),
},
],
},
id: normalizeConcrete(version, {
parents: 0,
interior: {
X2: [
{
PalletInstance:
params.asset.getAssetPalletInstance(),
},
{
GeneralIndex: params.asset.getAssetId(),
},
],
},
},
}),
fun: {
Fungible: params.asset.amount,
},
Expand All @@ -105,22 +108,19 @@ export function polkadotXcm() {

if (isAssetDifferent) {
const feeAsset = {
id: {
Concrete: {
parents: 0,
interior: {
X2: [
{
PalletInstance:
params.fee.getAssetPalletInstance(),
},
{
GeneralIndex: params.fee.getAssetId(),
},
],
},
id: normalizeConcrete(version, {
parents: 0,
interior: {
X2: [
{
PalletInstance: params.fee.getAssetPalletInstance(),
},
{
GeneralIndex: params.fee.getAssetId(),
},
],
},
},
}),
fun: {
Fungible: params.fee.amount,
},
Expand Down Expand Up @@ -153,34 +153,35 @@ export function polkadotXcm() {
new ExtrinsicConfig({
module: pallet,
func,
getArgs: (extrinsicFunction) =>
getPolkadotXcmExtrinsicArgs({
getArgs: (extrinsicFunction) => {
const version = getExtrinsicArgumentVersion(extrinsicFunction);

return getPolkadotXcmExtrinsicArgs({
...params,
func: extrinsicFunction,
asset: [
{
id: {
Concrete: {
parents: 1,
interior: {
X2: [
{
Parachain: params.destination.parachainId,
},
{
PalletInstance:
params.asset.getAssetPalletInstance(),
},
],
},
id: normalizeConcrete(version, {
parents: 1,
interior: {
X2: [
{
Parachain: params.destination.parachainId,
},
{
PalletInstance:
params.asset.getAssetPalletInstance(),
},
],
},
},
}),
fun: {
Fungible: params.asset.amount,
},
},
],
}),
});
},
}),
}),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Parents } from '../../ExtrinsicBuilder.interfaces';
import {
getExtrinsicAccount,
getExtrinsicArgumentVersion,
normalizeX1,
} from '../../ExtrinsicBuilder.utils';

export interface GetExtrinsicParams extends BuilderParams {
Expand All @@ -27,22 +28,22 @@ export function getPolkadotXcmExtrinsicArgs({

return [
{
[version]: {
[version]: normalizeX1(version, {
parents,
interior: {
X1: {
Parachain: destination.parachainId,
},
},
},
}),
},
{
[version]: {
[version]: normalizeX1(version, {
parents: 0,
interior: {
X1: getExtrinsicAccount(destinationAddress),
},
},
}),
},
{
[version]: asset,
Expand Down
45 changes: 11 additions & 34 deletions packages/builder/src/fee/FeeBuilder.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type {
ChainAsset,
ChainAssetId,
} from '@moonbeam-network/xcm-types';
import { isHexString } from '@moonbeam-network/xcm-utils';
import type { ApiPromise } from '@polkadot/api';
import type { Option, Result, u128 } from '@polkadot/types';
import type {
Expand All @@ -12,6 +11,10 @@ import type {
} from '@polkadot/types/interfaces';
import type { AnyJson } from '@polkadot/types/types';
import { XcmVersion } from '../extrinsic';
import {
normalizeConcrete,
normalizeX1,
} from '../extrinsic/ExtrinsicBuilder.utils';
import type { MoonbeamRuntimeXcmConfigAssetType } from './FeeBuilder.interfaces';

const DEFAULT_AMOUNT = 10 ** 6;
Expand All @@ -24,23 +27,6 @@ function isXcmV4() {
return XCM_VERSION === XcmVersion.v4;
}

function normalizeX1(assetType: Record<string, AnyJson>) {
if (!isXcmV4()) {
return assetType;
}
const normalizedAssetType = { ...assetType };
if (
normalizedAssetType.interior &&
typeof normalizedAssetType.interior === 'object' &&
'x1' in normalizedAssetType.interior
) {
if (!Array.isArray(normalizedAssetType.interior.x1)) {
normalizedAssetType.interior.x1 = [normalizedAssetType.interior.x1];
}
}
return normalizedAssetType;
}

export function getWithdrawAssetInstruction(assetTypes: object[]) {
return {
WithdrawAsset: assetTypes.map((assetType) => ({
Expand Down Expand Up @@ -121,12 +107,6 @@ export function getSetTopicInstruction() {
};
}

function applyConcreteWrapper(id: object) {
return {
Concrete: { ...id },
};
}

// TODO this is for Moonbeam, when applying to all we have to
// configure the multilocation of the native asset in the chain
function getNativeAssetId(palletInstanceNumber: number | undefined): object {
Expand All @@ -145,8 +125,7 @@ function getNativeAssetId(palletInstanceNumber: number | undefined): object {
},
parents: '0',
};

return isXcmV4() ? id : applyConcreteWrapper(id);
return normalizeConcrete(XCM_VERSION, id);
}

function getConcreteAssetIdWithAccountKey20(
Expand Down Expand Up @@ -175,7 +154,7 @@ function getConcreteAssetIdWithAccountKey20(
},
parents: '0',
};
return isXcmV4() ? id : applyConcreteWrapper(id);
return normalizeConcrete(XCM_VERSION, id);
}

export async function getAssetIdType(
Expand All @@ -201,22 +180,20 @@ export async function getVersionedAssetId(
const assetId = asset.getAssetId();
const palletInstance = asset.getAssetPalletInstance();

if (assetId === chain.nativeAsset.address) {
if (assetId === chain.nativeAsset.originSymbol) {
return getNativeAssetId(palletInstance);
}

if (isHexString(assetId)) {
return getConcreteAssetIdWithAccountKey20(assetId, palletInstance);
if (asset.hasOnlyAddress()) {
return getConcreteAssetIdWithAccountKey20(asset.address, palletInstance);
}

const assetType = await getAssetIdType(api, assetId);
const assetTypeObject = assetType.unwrap().asXcm.toJSON();

const normalizedAssetTypeObject = normalizeX1(assetTypeObject);
const normalizedAssetTypeObject = normalizeX1(XCM_VERSION, assetTypeObject);

return isXcmV4()
? normalizedAssetTypeObject
: applyConcreteWrapper(normalizedAssetTypeObject);
return normalizeConcrete(XCM_VERSION, normalizedAssetTypeObject);
}

export async function getFeeForXcmInstructionsAndAsset(
Expand Down
Loading

0 comments on commit 9ea36ad

Please sign in to comment.