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

fix: enable paysWithFeeDest for transferAssets #444

Merged
merged 2 commits into from
Oct 9, 2024
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
1 change: 1 addition & 0 deletions examples/paraToParaTransferMultiAsset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const main = async () => {
['1000000'],
{
format: 'call',
xcmPalletOverride: 'xTokens',
xcmVersion: safeXcmVersion,
},
);
Expand Down
1 change: 1 addition & 0 deletions examples/paraToParaTransferMultiAssetWithFee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const main = async () => {
refTime: '10000',
proofSize: '3000',
},
xcmPalletOverride: 'xTokens',
xcmVersion: safeXcmVersion,
// NOTE: for `xTokens` pallet `transferMultiassetWithFee` txs, `paysWithFeeDest` is the multiLocation of the asset that is intended to be used to pay for fees in the dest chain
paysWithFeeDest:
Expand Down
1 change: 1 addition & 0 deletions examples/paraToParaTransferMultiAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const main = async () => {
['1000000', '10000000000'],
{
format: 'call',
xcmPalletOverride: 'xTokens',
xcmVersion: safeXcmVersion,
},
);
Expand Down
1 change: 1 addition & 0 deletions examples/paraToRelayTransferMultiAsset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const main = async () => {
['1000000000000'],
{
format: 'call',
xcmPalletOverride: 'xTokens',
xcmVersion: safeXcmVersion,
},
);
Expand Down
1 change: 1 addition & 0 deletions examples/paraToSystemTransferMultiAsset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const main = async () => {
['1000000'],
{
format: 'call',
xcmPalletOverride: 'xTokens',
xcmVersion: safeXcmVersion,
},
);
Expand Down
1 change: 1 addition & 0 deletions examples/paraToSystemTransferMultiAssetWithFee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const main = async () => {
refTime: '10000',
proofSize: '3000',
},
xcmPalletOverride: 'xTokens',
xcmVersion: safeXcmVersion,
// NOTE: for xTokens `transferMultiassetWithFee` txs, paysWithFeeDest is the multiLocation of the asset that is intended to be used to pay for fees in the dest chain
paysWithFeeDest:
Expand Down
1 change: 1 addition & 0 deletions examples/paraToSystemTransferMultiAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const main = async () => {
['1000000', '10000000000'],
{
format: 'call',
xcmPalletOverride: 'xTokens',
xcmVersion: safeXcmVersion,
},
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* 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 `transferAssets` call to send WETH
* from a Bifrost Polkadot (Parachain) account
* to a Polkadot AssetHub (System Parachain) account, where the `xcmVersion` is set to 3 and no `weightLimit` is provided declaring that
* the allowable weight will be `unlimited` and `paysWithFeeDest` is asset ID `DOT` (Polkadot)
* declaring that `DOT` `should be used to pay for tx fees on the destination chain.
*
* 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 xcmVersion = 3;
const { api, specName } = await constructApiPromise('wss://bifrost-polkadot-rpc.dwellir.com/ws');
const assetApi = new AssetTransferApi(api, specName, xcmVersion);

let callInfo: TxResult<'call'>;
try {
callInfo = await assetApi.createTransferTransaction(
'1000',
'5EWNeodpcQ6iYibJ3jmWVe85nsok1EDG8Kk3aFg8ZzpfY1qX',
['WETH', 'DOT'],
['1000000000000', '10000000000'],
{
format: 'call',
xcmVersion,
paysWithFeeDest: 'DOT', // Asset to be used to pay for fees on destination chain
},
);

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());
8 changes: 4 additions & 4 deletions src/AssetTransferApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -851,14 +851,14 @@ describe('AssetTransferAPI', () => {

it('Should correctly set the feeAssetItem when paysWithFeeDest option is provided for a limitedReserveTransferAssets call', async () => {
const expected =
'{"args":{"dest":{"V3":{"parents":"1","interior":{"X1":{"Parachain":"2,000"}}}},"beneficiary":{"V3":{"parents":"0","interior":{"X1":{"AccountId32":{"network":null,"id":"0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b"}}}}},"assets":{"V3":[{"id":{"Concrete":{"parents":"0","interior":{"X2":[{"PalletInstance":"50"},{"GeneralIndex":"10"}]}}},"fun":{"Fungible":"2,000"}}]},"fee_asset_item":"0","weight_limit":"Unlimited"},"method":"limitedReserveTransferAssets","section":"polkadotXcm"}';
'{"args":{"dest":{"V3":{"parents":"1","interior":{"X1":{"Parachain":"2,000"}}}},"beneficiary":{"V3":{"parents":"0","interior":{"X1":{"AccountId32":{"network":null,"id":"0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b"}}}}},"assets":{"V3":[{"id":{"Concrete":{"parents":"0","interior":{"X2":[{"PalletInstance":"50"},{"GeneralIndex":"10"}]}}},"fun":{"Fungible":"2,000"}},{"id":{"Concrete":{"parents":"0","interior":{"X2":[{"PalletInstance":"50"},{"GeneralIndex":"11"}]}}},"fun":{"Fungible":"100,000"}}]},"fee_asset_item":"1","weight_limit":"Unlimited"},"method":"limitedReserveTransferAssets","section":"polkadotXcm"}';
const callTxResult = await systemAssetsApi.createTransferTransaction(
'2000',
'0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b',
['10'],
['2000'],
['10', '11'],
['2000', '100000'],
{
paysWithFeeDest: '10',
paysWithFeeDest: '11',
xcmVersion: 3,
format: 'call',
keepAlive: true,
Expand Down
5 changes: 5 additions & 0 deletions src/createXcmCalls/polkadotXcm/transferAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export const transferAssets = async (

const feeAssetItem = paysWithFeeDest
? await typeCreator.createFeeAssetItem(api, {
specName,
xcmVersion,
assetIds,
amounts,
paysWithFeeDest,
registry,
isForeignAssetsTransfer,
isLiquidTokenTransfer,
Expand Down
2 changes: 1 addition & 1 deletion src/createXcmTypes/ParaToSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export const ParaToSystem: ICreateXcmType = {
registry,
);

const assetIndex = getFeeAssetItemIndex(
const assetIndex = await getFeeAssetItemIndex(
api,
registry,
paysWithFeeDest,
Expand Down
31 changes: 3 additions & 28 deletions src/errors/checkXcmTxInputs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -629,50 +629,25 @@ describe('checkAssetIdsLengthIsValid', () => {
it('Should correctly error when more than 2 assetIds are passed in', () => {
const assetIds = ['ksm', '1984', '10'];
const assetTransferType = undefined;
const xcmPallet = XcmPalletName.polkadotXcm;

const err = () => checkAssetIdsLengthIsValid(assetIds, xcmPallet, assetTransferType);
const err = () => checkAssetIdsLengthIsValid(assetIds, assetTransferType);
expect(err).toThrow('Maximum number of assets allowed for transfer is 2. Found 3 assetIds');
});
it('Should correctly not error when 2 or fewer assetIds are passed in and assetTransferType is defined', () => {
const assetIds = ['ksm', '1984'];
const assetTransferType = 'RemoteReserve';
const xcmPallet = XcmPalletName.polkadotXcm;

const err = () => checkAssetIdsLengthIsValid(assetIds, xcmPallet, assetTransferType);
const err = () => checkAssetIdsLengthIsValid(assetIds, assetTransferType);
expect(err).not.toThrow('Maximum number of assets allowed for transfer is 2. Found 3 assetIds');
});
it('Should correctly throw an error when provided more than 1 asset id for a non `transferAssetsUsingTypeAndThen` `xcmPallet` call', () => {
const assetIds = [
`{"parents":"1","interior":{"Here":""}}`,
`{"parents":"2","interior":{"X1":{"GlobalConsensus":"Westend"}}}`,
];
const assetTransferType = undefined;
const xcmPallet = XcmPalletName.xcmPallet;

const err = () => checkAssetIdsLengthIsValid(assetIds, xcmPallet, assetTransferType);
expect(err).toThrow('transferAssets transactions cannot contain more than 1 asset location id. Found 2 assetIds');
});
it('Should correctly throw an error when provided more than 1 asset id for a non `transferAssetsUsingTypeAndThen` `polkadotXcm` call', () => {
const assetIds = [
`{"parents":"1","interior":{"Here":""}}`,
`{"parents":"2","interior":{"X1":{"GlobalConsensus":"Westend"}}}`,
];
const assetTransferType = undefined;
const xcmPallet = XcmPalletName.polkadotXcm;

const err = () => checkAssetIdsLengthIsValid(assetIds, xcmPallet, assetTransferType);
expect(err).toThrow('transferAssets transactions cannot contain more than 1 asset location id. Found 2 assetIds');
});
it('Should correctly not throw an error when provided more than 1 asset id for a `transferAssetsUsingTypeAndThen` call', () => {
const assetIds = [
`{"parents":"1","interior":{"Here":""}}`,
`{"parents":"2","interior":{"X1":{"GlobalConsensus":"Westend"}}}`,
];
const assetTransferType = 'RemoteReserve';
const xcmPallet = XcmPalletName.polkadotXcm;

const err = () => checkAssetIdsLengthIsValid(assetIds, xcmPallet, assetTransferType);
const err = () => checkAssetIdsLengthIsValid(assetIds, assetTransferType);
expect(err).not.toThrow();
});
});
Expand Down
19 changes: 2 additions & 17 deletions src/errors/checkXcmTxInputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -811,28 +811,13 @@ const checkParaToRelayAssetId = (assetId: string, registry: Registry, specName:
*
* @param assetIds
*/
export const checkAssetIdsLengthIsValid = (
assetIds: string[],
xcmPalletName: XcmPalletName,
assetTransferType: string | undefined,
) => {
export const checkAssetIdsLengthIsValid = (assetIds: string[], assetTransferType: string | undefined) => {
if (assetIds.length > MAX_ASSETS_FOR_TRANSFER && !assetTransferType) {
throw new BaseError(
`Maximum number of assets allowed for transfer is 2. Found ${assetIds.length} assetIds`,
BaseErrorsEnum.InvalidInput,
);
}

if (
assetIds.length > 1 &&
!assetTransferType &&
(xcmPalletName === XcmPalletName.polkadotXcm || xcmPalletName === XcmPalletName.xcmPallet)
) {
throw new BaseError(
`transferAssets transactions cannot contain more than 1 asset location id. Found ${assetIds.length} assetIds`,
BaseErrorsEnum.InvalidInput,
);
}
};

/**
Expand Down Expand Up @@ -1155,7 +1140,7 @@ export const checkXcmTxInputs = async (baseArgs: XcmBaseArgsWithPallet, opts: Ch
/**
* Checks to ensure that assetId's have a length no greater than MAX_ASSETS_FOR_TRANSFER
*/
checkAssetIdsLengthIsValid(assetIds, xcmPallet, assetTransferType);
checkAssetIdsLengthIsValid(assetIds, assetTransferType);

/**
* Checks to ensure that assetId's have no duplicate values
Expand Down
25 changes: 0 additions & 25 deletions src/integrationTests/AssetsTransferApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,31 +495,6 @@ describe('AssetTransferApi Integration Tests', () => {
);
}).rejects.toThrow('Bridge transactions require XCM version 3 or greater');
});
it('Should correctly throw an error when providing more than 1 asset for a `transferAssets` call', async () => {
await expect(async () => {
await bridgeBaseSystemCreateTx(
systemAssetsApiV1016000,
`{"parents":"2","interior":{"X2":[{"GlobalConsensus":"Polkadot"},{"Parachain":"1000"}]}}`,
[
`{"parents":"1","interior":{"Here":""}}`,
`{"parents":"2","interior":{"X1":{"GlobalConsensus":"Polkadot"}}}`,
],
['1000000000000', '2000000000000'],
'payload',
3,
{
isLiquidTokenTransfer: false,
isForeignAssetsTransfer: true,
weightLimit: {
refTime: '1000',
proofSize: '2000',
},
},
);
}).rejects.toThrow(
'transferAssets transactions cannot contain more than 1 asset location id. Found 2 assetIds',
);
});
});
describe('SystemToPara', () => {
const foreignBaseSystemCreateTx = async <T extends Format>(
Expand Down
15 changes: 9 additions & 6 deletions src/util/getFeeAssetItemIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ApiPromise } from '@polkadot/api';

import { FungibleStrAssetType } from '../createXcmTypes/types';
import { getAssetId } from '../createXcmTypes/util/getAssetId';
import { isParachain } from '../createXcmTypes/util/isParachain';
import { BaseError, BaseErrorsEnum } from '../errors';
import { Registry } from '../registry';
import { resolveMultiLocation } from '../util/resolveMultiLocation';
Expand All @@ -26,6 +27,8 @@ export const getFeeAssetItemIndex = async (
xcmVersion: number,
isForeignAssetsTransfer?: boolean,
): Promise<number> => {
const chainId = registry.lookupChainIdBySpecName(specName);
const isParaOrigin = isParachain(chainId);
let result = -1;

if (paysWithFeeDest) {
Expand All @@ -45,7 +48,7 @@ export const getFeeAssetItemIndex = async (

if (isRelayFeeAsset) {
// if the asset id is a relay asset, match Here interior
if (multiAssetInterior.Here || multiAssetInterior['here']) {
if ('Here' in multiAssetInterior) {
result = i;
break;
}
Expand All @@ -55,17 +58,17 @@ export const getFeeAssetItemIndex = async (
// if not a number, get the general index of the pays with fee asset
// to compare against the current multi asset
if (!isValidNumber) {
const paysWithFeeDestGeneralIndex = await getAssetId(
const paysWithFeeDestAssetLocationStr = await getAssetId(
api,
registry,
paysWithFeeDest,
specName,
xcmVersion,
isForeignAssetsTransfer,
);
// if isForeignAssetsTransfer, compare the multiAsset interior to the the paysWithFeeDestGeneralIndex as a multilocation
if (isForeignAssetsTransfer) {
const paysWithFeeDestMultiLocation = resolveMultiLocation(paysWithFeeDestGeneralIndex, xcmVersion);
// if isForeignAssetsTransfer or parachain origin, compare the multiAsset interior to the the paysWithFeeDestAssetLocationStr as a multilocation
if (isForeignAssetsTransfer || isParaOrigin) {
const paysWithFeeDestMultiLocation = resolveMultiLocation(paysWithFeeDestAssetLocationStr, xcmVersion);
const paysWithFeeDestMultiLocationInterior =
paysWithFeeDestMultiLocation.interior || paysWithFeeDestMultiLocation['Interior'];
if (JSON.stringify(multiAssetInterior) === JSON.stringify(paysWithFeeDestMultiLocationInterior)) {
Expand All @@ -82,7 +85,7 @@ export const getFeeAssetItemIndex = async (
if (
multiAssetInterior.X2 &&
(multiAssetInterior.X2[1].GeneralIndex || multiAssetInterior.X2[1]['Generalindex']) ===
paysWithFeeDestGeneralIndex
paysWithFeeDestAssetLocationStr
) {
result = i;
break;
Expand Down
14 changes: 14 additions & 0 deletions src/util/resolveMultiLocation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,18 @@ describe('resolveMultiLocation', () => {
'XcmVersion must be greater than 2 for MultiLocations that contain a GlobalConsensus junction.',
);
});
it('Should correctly resolve an xcmV1Multilocation values location', () => {
const str = `{"v1":{"parents":1,"interior":{"x2":[{"globalConsensus":{"ethereum":{"chainId":1}}},{"accountKey20":{"network":null,"key":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"}}]}}}`;
const exp = {
parents: 1,
interior: {
x2: [
{ globalConsensus: { ethereum: { chainId: 1 } } },
{ accountKey20: { network: null, key: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2' } },
],
},
};

expect(resolveMultiLocation(str, 3)).toStrictEqual(exp);
});
});
5 changes: 5 additions & 0 deletions src/util/resolveMultiLocation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ export const resolveMultiLocation = (multiLocation: AnyJson, xcmVersion: number)

let result = parseLocationStrToLocation(multiLocationStr);

// handle case where result is an xcmV1Multilocation from the registry
if ('v1' in result) {
result = result.v1 as UnionXcmMultiLocation;
}

if (xcmVersion > 3 && result.interior.X1) {
result = {
parents: result.parents,
Expand Down