Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import {
createAbsoluteMultiLocation,
createHere,
createParachainJunction,
createRelativeMultiLocation,
} from '../location-types';
import { multiLocationService } from '../multi-location-service';

describe('shared/api/xcm/lib/multi-location-service', () => {
describe('reanchorAbsoluteLocation', () => {
test('reanchor global pov should remain unchanged', () => {
const initial = createAbsoluteMultiLocation(createParachainJunction(1000));
const pov = createAbsoluteMultiLocation(...createHere());
const expected = createRelativeMultiLocation(0, createParachainJunction(1000));

const result = multiLocationService.reanchorAbsoluteLocation(initial, pov);

expect(result).toEqual(expected);
});

test('reanchor no common junctions', () => {
const initial = createAbsoluteMultiLocation(createParachainJunction(1000));
const pov = createAbsoluteMultiLocation(createParachainJunction(2000));
const expected = createRelativeMultiLocation(1, createParachainJunction(1000));

const result = multiLocationService.reanchorAbsoluteLocation(initial, pov);

expect(result).toEqual(expected);
});

test('reanchor one common junction', () => {
const initial = createAbsoluteMultiLocation(createParachainJunction(1000), createParachainJunction(2000));
const pov = createAbsoluteMultiLocation(createParachainJunction(1000), createParachainJunction(3000));
const expected = createRelativeMultiLocation(1, createParachainJunction(2000));

const result = multiLocationService.reanchorAbsoluteLocation(initial, pov);

expect(result).toEqual(expected);
});

test('reanchor all common junction', () => {
const initial = createAbsoluteMultiLocation(createParachainJunction(1000), createParachainJunction(2000));
const pov = createAbsoluteMultiLocation(createParachainJunction(1000), createParachainJunction(2000));
const expected = createRelativeMultiLocation(0);

const result = multiLocationService.reanchorAbsoluteLocation(initial, pov);

expect(result).toEqual(expected);
});

test('reanchor global to global', () => {
const initial = createAbsoluteMultiLocation(...createHere());
const pov = createAbsoluteMultiLocation(...createHere());
const expected = createRelativeMultiLocation(0);

const result = multiLocationService.reanchorAbsoluteLocation(initial, pov);

expect(result).toEqual(expected);
});

test('reanchor pov is successor of initial', () => {
const initial = createAbsoluteMultiLocation(...createHere());
const pov = createAbsoluteMultiLocation(createParachainJunction(1000));
const expected = createRelativeMultiLocation(1);

const result = multiLocationService.reanchorAbsoluteLocation(initial, pov);

expect(result).toEqual(expected);
});

test('reanchor initial is successor of pov', () => {
const initial = createAbsoluteMultiLocation(createParachainJunction(1000), createParachainJunction(2000));
const pov = createAbsoluteMultiLocation(createParachainJunction(1000));
const expected = createRelativeMultiLocation(0, createParachainJunction(2000));

const result = multiLocationService.reanchorAbsoluteLocation(initial, pov);

expect(result).toEqual(expected);
});
});

describe('restoreAbsoluteLocation', () => {
test('restore global pov should remain unchanged', () => {
const expected = createAbsoluteMultiLocation(createParachainJunction(1000));
const pov = createAbsoluteMultiLocation(...createHere());

const relative = createRelativeMultiLocation(0, createParachainJunction(1000));

const restored = multiLocationService.restoreAbsoluteLocation(relative, pov);
expect(restored).toEqual(expected);
});

test('restore no common junctions', () => {
const expected = createAbsoluteMultiLocation(createParachainJunction(1000));
const pov = createAbsoluteMultiLocation(createParachainJunction(2000));

const relative = createRelativeMultiLocation(1, createParachainJunction(1000));

const restored = multiLocationService.restoreAbsoluteLocation(relative, pov);
expect(restored).toEqual(expected);
});

test('restore one common junction', () => {
const expected = createAbsoluteMultiLocation(createParachainJunction(1000), createParachainJunction(2000));
const pov = createAbsoluteMultiLocation(createParachainJunction(1000), createParachainJunction(3000));

const relative = createRelativeMultiLocation(1, createParachainJunction(2000));

const restored = multiLocationService.restoreAbsoluteLocation(relative, pov);
expect(restored).toEqual(expected);
});

test('restore all common junction', () => {
const expected = createAbsoluteMultiLocation(createParachainJunction(1000), createParachainJunction(2000));
const pov = createAbsoluteMultiLocation(createParachainJunction(1000), createParachainJunction(2000));

const relative = createRelativeMultiLocation(0);

const restored = multiLocationService.restoreAbsoluteLocation(relative, pov);
expect(restored).toEqual(expected);
});

test('restore global to global', () => {
const expected = createAbsoluteMultiLocation(...createHere());
const pov = createAbsoluteMultiLocation(...createHere());

const relative = createRelativeMultiLocation(0);

const restored = multiLocationService.restoreAbsoluteLocation(relative, pov);
expect(restored).toEqual(expected);
});

test('restore pov is successor of initial', () => {
const expected = createAbsoluteMultiLocation(...createHere());
const pov = createAbsoluteMultiLocation(createParachainJunction(1000));

// Target is ancestor of POV: go up 1, then Here
const relative = createRelativeMultiLocation(1);

const restored = multiLocationService.restoreAbsoluteLocation(relative, pov);
expect(restored).toEqual(expected);
});

test('restore initial is successor of pov', () => {
const expected = createAbsoluteMultiLocation(createParachainJunction(1000), createParachainJunction(2000));
const pov = createAbsoluteMultiLocation(createParachainJunction(1000));

const relative = createRelativeMultiLocation(0, createParachainJunction(2000));

const restored = multiLocationService.restoreAbsoluteLocation(relative, pov);
expect(restored).toEqual(expected);
});

test('should throw error for negative parents', () => {
const pov = createAbsoluteMultiLocation(createParachainJunction(1000));
const relative = createRelativeMultiLocation(-1, createParachainJunction(2000));

expect(() => {
multiLocationService.restoreAbsoluteLocation(relative, pov);
}).toThrow('Parents cannot be negative');
});

test('should throw error when parents exceed pov junctions', () => {
const pov = createAbsoluteMultiLocation(createParachainJunction(1000));
const relative = createRelativeMultiLocation(2, createParachainJunction(2000));

expect(() => {
multiLocationService.restoreAbsoluteLocation(relative, pov);
}).toThrow(
'Invalid relative location from given pov: Relative location has 2 parents whereas pov has only 1 junctions',
);
});

test('bidirectional test: reanchor then restore should return original', () => {
const original = createAbsoluteMultiLocation(createParachainJunction(1000), createParachainJunction(2000));
const pov = createAbsoluteMultiLocation(createParachainJunction(1000), createParachainJunction(3000));

// Reanchor to relative
const relative = multiLocationService.reanchorAbsoluteLocation(original, pov);

// Restore back to absolute
const restored = multiLocationService.restoreAbsoluteLocation(relative, pov);

expect(restored).toEqual(original);
});

test('bidirectional test: restore then reanchor should return original relative', () => {
const relative = createRelativeMultiLocation(1, createParachainJunction(2000));
const pov = createAbsoluteMultiLocation(createParachainJunction(1000), createParachainJunction(3000));

// Restore to absolute
const absolute = multiLocationService.restoreAbsoluteLocation(relative, pov);

// Reanchor back to relative
const reanchored = multiLocationService.reanchorAbsoluteLocation(absolute, pov);

expect(reanchored).toEqual(relative);
});
});
});
46 changes: 46 additions & 0 deletions src/renderer/shared/api/xcm/lib/location-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { type HexString } from '@/shared/core';

export type Junction =
| { Parachain: number }
| { AccountId32: { network?: string; id: HexString } }
| { AccountKey20: { network?: string; key: HexString } }
| { PalletInstance: number }
| { GeneralIndex: string }
| { GeneralKey: { length: number; data: HexString } };

export type RelativeMultiLocation = {
parents: number;
interior: Junction[];
};

export type AbsoluteMultiLocation = {
interior: Junction[];
};

export type XcmVersion = 2 | 3 | 4 | 5;

export type VersionedXcm<T> = {
xcm: T;
version: XcmVersion;
};

// Helper functions for creating multi-locations
export function createParachainJunction(id: number): Junction {
return { Parachain: id };
}

export function createAbsoluteMultiLocation(...junctions: Junction[]): AbsoluteMultiLocation {
return { interior: junctions };
}

export function createRelativeMultiLocation(parents: number, ...junctions: Junction[]): RelativeMultiLocation {
return { parents, interior: junctions };
}

export function createHere(): Junction[] {
return [];
}

export function createVersionedXcm<T>(xcm: T, version: XcmVersion): VersionedXcm<T> {
return { xcm, version };
}
68 changes: 68 additions & 0 deletions src/renderer/shared/api/xcm/lib/multi-location-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { type AbsoluteMultiLocation, type Junction, type RelativeMultiLocation } from './location-types';

function areJunctionsEqual(junction1: Junction, junction2: Junction): boolean {
return JSON.stringify(junction1) === JSON.stringify(junction2);
}

function findLastCommonJunctionIndex(
location1: AbsoluteMultiLocation,
location2: AbsoluteMultiLocation,
): number | null {
let lastCommonIndex = -1;

const minLength = Math.min(location1.interior.length, location2.interior.length);

for (let i = 0; i < minLength; i++) {
if (areJunctionsEqual(location1.interior[i], location2.interior[i])) {
lastCommonIndex = i;
} else {
break;
}
}

return lastCommonIndex >= 0 ? lastCommonIndex : null;
}

function reanchorAbsoluteLocation(
location: AbsoluteMultiLocation,
pointOfView: AbsoluteMultiLocation,
): RelativeMultiLocation {
const lastCommonIndex = findLastCommonJunctionIndex(location, pointOfView);
const firstDistinctIndex = lastCommonIndex !== null ? lastCommonIndex + 1 : 0;

const parents = pointOfView.interior.length - firstDistinctIndex;
const interior = location.interior.slice(firstDistinctIndex);

return {
parents,
interior,
};
}

function restoreAbsoluteLocation(
relativeLocation: RelativeMultiLocation,
pointOfView: AbsoluteMultiLocation,
): AbsoluteMultiLocation {
if (relativeLocation.parents < 0) {
throw new Error('Parents cannot be negative');
}

if (relativeLocation.parents > pointOfView.interior.length) {
throw new Error(
`Invalid relative location from given pov: ` +
`Relative location has ${relativeLocation.parents} parents whereas pov has only ${pointOfView.interior.length} junctions`,
);
}

const base = pointOfView.interior.slice(0, pointOfView.interior.length - relativeLocation.parents);
const resultJunctions = [...base, ...relativeLocation.interior];

return {
interior: resultJunctions,
};
}

export const multiLocationService = {
reanchorAbsoluteLocation,
restoreAbsoluteLocation,
};
Loading