Skip to content

Commit

Permalink
feat: support relay to bridge tx construction (#409)
Browse files Browse the repository at this point in the history
  • Loading branch information
marshacb authored Jun 12, 2024
1 parent 54e8148 commit 436d78b
Show file tree
Hide file tree
Showing 15 changed files with 780 additions and 25 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ The below chart is focusing on what directions are supported for constructing as
| System to System ||||
| System to Bridge ||||
| System to Parachain ||||
| Relay to Parachain ||||
| Relay to Parachain ||||
| Relay to Bridge ||||
| Relay to System ||||
| Parachain to Parachain ||||
| Parachain to Relay ||||
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* 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 an `xcmPallet` `transferAssetsUsingTypeAndThen` call to send 1 ROC (asset with location `{"parents":"0","interior":{"Here":""}}`)
* from a Rococo (Relay Chain) account
* to a Westend Asset Hub account, where the `xcmVersion` is set to 4 and no `weightLimit` option is provided declaring that
* the tx will allow unlimited weight to be used for fees.
* The `paysWithFeeDest` value is set to pay fees with KSM and the values for `assetTransferType` and `feesTransferType`
* are both set to the `RemoteReserve` location of Rococo AssetHub, specifying that the reserve location to be used for transferring and fees is Rococo AssetHub.
*
* NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`.
*/
const main = async () => {
const { api, specName, safeXcmVersion } = await constructApiPromise('wss://rococo-rpc.polkadot.io');
const assetApi = new AssetTransferApi(api, specName, safeXcmVersion);

let callInfo: TxResult<'call'>;
try {
callInfo = await assetApi.createTransferTransaction(
`{"parents":"1","interior":{"X2":[{"GlobalConsensus":"Westend"},{"Parachain":"1000"}]}}`,
'13EoPU88424tufnjevEYbbvZ7sGV3q1uhuN4ZbUaoTsnLHYt',
[`{"parents":"0","interior":{"Here":""}}`],
['1000000000000'],
{
format: 'call',
xcmVersion: 4,
paysWithFeeDest: `{"parents":"0","interior":{"Here":""}}`,
assetTransferType: 'RemoteReserve',
remoteReserveAssetTransferTypeLocation: '{"parents":"0","interior":{"X1":{"Parachain":"1000"}}}',
feesTransferType: 'RemoteReserve',
remoteReserveFeesTransferTypeLocation: '{"parents":"0","interior":{"X1":{"Parachain":"1000"}}}',
},
);

console.log(callInfo);
} 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()
.catch((err) => console.error(err))
.finally(() => process.exit());
9 changes: 8 additions & 1 deletion src/AssetTransferApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,10 @@ export class AssetTransferApi {
return Direction.RelayToPara;
}

if (isOriginRelayChain && isDestBridge) {
return Direction.RelayToBridge;
}

/**
* Check if the origin is a Parachain or Parathread
*/
Expand Down Expand Up @@ -1125,7 +1129,6 @@ export class AssetTransferApi {
paysWithFeeDest?: string,
): Promise<ResolvedCallInfo> {
const { api } = baseArgs;

let txMethod: Methods | undefined = undefined;

const isXtokensPallet = xcmPallet === XcmPalletName.xTokens || xcmPallet === XcmPalletName.xtokens;
Expand Down Expand Up @@ -1159,6 +1162,10 @@ export class AssetTransferApi {
throw new BaseError(`Unable to resolve correct transfer call`, BaseErrorsEnum.InternalError);
}

if (!txMethod) {
throw new BaseError(`Unable to resolve correct transfer call`, BaseErrorsEnum.InternalError);
}

if (!callExistsInRuntime(api, txMethod, xcmPallet)) {
throw new BaseError(
`Did not find ${txMethod} from pallet ${xcmPallet} in the current runtime`,
Expand Down
209 changes: 209 additions & 0 deletions src/createXcmTypes/RelayToBridge.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// Copyright 2024 Parity Technologies (UK) Ltd.

import { Registry } from '../registry';
import { adjustedMockSystemApiV1011000 } from '../testHelpers/adjustedMockSystemApiV1011000';
import { RelayToBridge } from './RelayToBridge';

describe('RelayToBridge', () => {
const registry = new Registry('rococo', {});
const isForeignAssetsTransfer = true;
const isLiquidTokenTransfer = false;
describe('Beneficiary', () => {
it('Should work for V3', () => {
const beneficiary = RelayToBridge.createBeneficiary(
'0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b',
3,
);

const expectedRes = {
V3: {
parents: 0,
interior: {
X1: {
AccountId32: {
id: '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b',
},
},
},
},
};

expect(beneficiary).toStrictEqual(expectedRes);
});
it('Should work for V3 for an Ethereum Address', () => {
const beneficiary = RelayToBridge.createBeneficiary('0x96Bd611EbE3Af39544104e26764F4939924F6Ece', 3);

const expectedRes = {
V3: {
parents: 0,
interior: {
X1: {
AccountKey20: {
key: '0x96Bd611EbE3Af39544104e26764F4939924F6Ece',
},
},
},
},
};

expect(beneficiary).toStrictEqual(expectedRes);
});
it('Should work for V4', () => {
const beneficiary = RelayToBridge.createBeneficiary(
'0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b',
4,
);

const expectedRes = {
V4: {
parents: 0,
interior: {
X1: [
{
AccountId32: {
id: '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b',
},
},
],
},
},
};

expect(beneficiary).toStrictEqual(expectedRes);
});
it('Should work for V4 for an Ethereum Address', () => {
const beneficiary = RelayToBridge.createBeneficiary('0x96Bd611EbE3Af39544104e26764F4939924F6Ece', 4);

const expectedRes = {
V4: {
parents: 0,
interior: {
X1: [
{
AccountKey20: {
key: '0x96Bd611EbE3Af39544104e26764F4939924F6Ece',
},
},
],
},
},
};

expect(beneficiary).toStrictEqual(expectedRes);
});
});
describe('Destination', () => {
it('Should work for V3', () => {
const destId = `{"parents":"2","interior":{"X1":{"GlobalConsensus":{"Ethereum":{"chainId":"11155111"}}}}}`;
const destination = RelayToBridge.createDest(destId, 3);

const expectedRes = {
V3: {
parents: 1,
interior: {
X1: {
GlobalConsensus: {
Ethereum: {
chainId: '11155111',
},
},
},
},
},
};

expect(destination).toStrictEqual(expectedRes);
});
it('Should work for V4', () => {
const destId = `{"parents":"2","interior":{"X2":[{"GlobalConsensus":"Kusama"},{"Parachain":"1000"}]}}`;

const destination = RelayToBridge.createDest(destId, 4);

const expectedRes = {
V4: {
parents: 1,
interior: {
X2: [
{
GlobalConsensus: 'Kusama',
},
{
Parachain: '1000',
},
],
},
},
};

expect(destination).toStrictEqual(expectedRes);
});
});
describe('Assets', () => {
it('Should work for V3', async () => {
const assets = await RelayToBridge.createAssets(
['10000000000'],
3,
'rococo',
[`{"parents":"0","interior":{"Here":""}}`],
{
registry,
isForeignAssetsTransfer,
isLiquidTokenTransfer,
api: adjustedMockSystemApiV1011000,
},
);

const expectedRes = {
V3: [
{
id: {
Concrete: {
parents: 0,
interior: {
Here: '',
},
},
},
fun: {
Fungible: '10000000000',
},
},
],
};

expect(assets).toStrictEqual(expectedRes);
});
it('Should work for V4', async () => {
const assets = await RelayToBridge.createAssets(
['10000000000'],
4,
'rococo',
[`{"parents":"0","interior":{"Here":""}}`],
{
registry,
isForeignAssetsTransfer,
isLiquidTokenTransfer,
api: adjustedMockSystemApiV1011000,
},
);

const expectedRes = {
V4: [
{
id: {
parents: 0,
interior: {
Here: '',
},
},
fun: {
Fungible: '10000000000',
},
},
],
};

expect(assets).toStrictEqual(expectedRes);
});
});
});
Loading

0 comments on commit 436d78b

Please sign in to comment.