Skip to content

Commit

Permalink
fix: support xcm v6 config & delivery fee (#2829)
Browse files Browse the repository at this point in the history
  • Loading branch information
Asmadek authored Dec 17, 2024
1 parent b74d7d8 commit 308bba7
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 69 deletions.
83 changes: 43 additions & 40 deletions src/renderer/entities/transaction/ui/Fee/Fee.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type ApiPromise } from '@polkadot/api';
import { BN } from '@polkadot/util';
import { BN, BN_ZERO } from '@polkadot/util';
import { useUnit } from 'effector-react';
import { memo, useEffect, useState } from 'react';

Expand All @@ -15,55 +15,58 @@ type Props = {
asset: Asset;
transaction?: Transaction | null;
className?: string;
extraFee?: BN;
onFeeChange?: (fee: string) => void;
onFeeLoading?: (loading: boolean) => void;
};

export const Fee = memo(({ api, multiply = 1, asset, transaction, className, onFeeChange, onFeeLoading }: Props) => {
const fiatFlag = useUnit(priceProviderModel.$fiatFlag);
export const Fee = memo(
({ api, multiply = 1, asset, transaction, className, extraFee = BN_ZERO, onFeeChange, onFeeLoading }: Props) => {
const fiatFlag = useUnit(priceProviderModel.$fiatFlag);

const [fee, setFee] = useState('');
const [isLoading, setIsLoading] = useState(true);
const [fee, setFee] = useState('');
const [isLoading, setIsLoading] = useState(true);

const updateFee = (fee: string) => {
const totalFee = new BN(fee).muln(multiply).toString();
const updateFee = (fee: string) => {
const totalFee = new BN(fee).muln(multiply).add(extraFee).toString();

setFee(totalFee);
onFeeChange?.(totalFee);
};
setFee(totalFee);
onFeeChange?.(totalFee);
};

useEffect(() => {
onFeeLoading?.(isLoading);
}, [isLoading]);
useEffect(() => {
onFeeLoading?.(isLoading);
}, [isLoading]);

useEffect(() => {
setIsLoading(true);
useEffect(() => {
setIsLoading(true);

if (!api) return;
if (!api) return;

if (!transaction?.address) {
updateFee('0');
setIsLoading(false);
} else {
transactionService
.getTransactionFee(transaction, api)
.then(updateFee)
.catch((error) => {
updateFee('0');
console.info('Error getting fee - ', error);
})
.finally(() => setIsLoading(false));
}
}, [transaction, api]);
if (!transaction?.address) {
updateFee('0');
setIsLoading(false);
} else {
transactionService
.getTransactionFee(transaction, api)
.then(updateFee)
.catch((error) => {
updateFee('0');
console.info('Error getting fee - ', error);
})
.finally(() => setIsLoading(false));
}
}, [transaction, api]);

if (isLoading) {
return <FeeLoader fiatFlag={Boolean(fiatFlag)} />;
}
if (isLoading) {
return <FeeLoader fiatFlag={Boolean(fiatFlag)} />;
}

return (
<div className="flex flex-col items-end gap-y-0.5">
<AssetBalance value={fee} asset={asset} className={className} />
<AssetFiatBalance asset={asset} amount={fee} />
</div>
);
});
return (
<div className="flex flex-col items-end gap-y-0.5">
<AssetBalance value={fee} asset={asset} className={className} />
<AssetFiatBalance asset={asset} amount={fee} />
</div>
);
},
);
1 change: 1 addition & 0 deletions src/renderer/shared/api/xcm/__tests__/mock/xcmData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const CONFIG: XcmConfig = {
Action.DEPOSIT_ASSET,
],
},
networkDeliveryFee: {},
networkBaseWeight: {
b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe: '1000000000',
baf5aabe40646d11f0ee8abbdc64f4a4b7674925cba08e4a05ff9ebed6e2126b: '200000000',
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/shared/api/xcm/__tests__/xcm-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('shared/api/xcm/lib/xcm-utils', () => {
const location = xcmUtils.getDestinationLocation({ parentId: '0x00' }, 2000) as any;

expect(location.parents).toEqual(1);
expect(location.interior.X1.Parachain).toEqual(2000);
expect(location.interior.X1[0].Parachain).toEqual(2000);
});

test('should calculate correct location for parent parachain', () => {
Expand All @@ -54,13 +54,13 @@ describe('shared/api/xcm/lib/xcm-utils', () => {
const location = xcmUtils.getDestinationLocation({ parentId: '0x00' }, undefined, '0x00') as any;

expect(location.parents).toEqual(1);
expect(location.interior.X1.AccountId32.id).toEqual('0x00');
expect(location.interior.X1[0].AccountId32.id).toEqual('0x00');
});

test('should calculate correct location for child parachain', () => {
const location = xcmUtils.getDestinationLocation({ parentId: undefined }, 2000) as any;

expect(location.parents).toEqual(0);
expect(location.interior.X1.Parachain).toEqual(2000);
expect(location.interior.X1[0].Parachain).toEqual(2000);
});
});
7 changes: 6 additions & 1 deletion src/renderer/shared/api/xcm/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { BN } from '@polkadot/util';

import { TEST_ACCOUNTS } from '@/shared/lib/utils';

import { Action } from './types';

export const XCM_URL = 'https://raw.githubusercontent.com/novasamatech/nova-utils/master/xcm/v4/transfers.json';
export const XCM_URL = 'https://raw.githubusercontent.com/novasamatech/nova-utils/master/xcm/v6/transfers.json';
export const XCM_KEY = 'xcm-config';

export const SET_TOPIC_SIZE = new BN(33);
export const FACTOR_MULTIPLIER = new BN(18);

export const INSTRUCTION_OBJECT: Record<Action, (assetLocation: object, destLocation: object) => object> = {
[Action.WITHDRAW_ASSET]: (assetLocation: object) => {
return {
Expand Down
16 changes: 16 additions & 0 deletions src/renderer/shared/api/xcm/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type AssetName = string;
export type XcmConfig = {
assetsLocation: AssetsLocation;
instructions: Instructions;
networkDeliveryFee: NetworkDeliveryFee;
networkBaseWeight: NetworkBaseWeight;
chains: ChainXCM[];
};
Expand Down Expand Up @@ -43,6 +44,21 @@ export type NetworkBaseWeight = {
[chainId: string]: string;
};

export type DeliveryFeeConfig = {
type: 'exponential';
factorPallet: 'ParachainSystem' | 'XcmpQueue' | 'Dmp';
sizeBase: string;
sizeFactor: string;
alwaysHoldingPays: boolean;
};

export type NetworkDeliveryFee = {
[chainId: string]: {
toParent?: DeliveryFeeConfig;
toParachain?: DeliveryFeeConfig;
};
};

export type AssetXCM = {
assetId: number;
assetLocation: string;
Expand Down
26 changes: 15 additions & 11 deletions src/renderer/shared/api/xcm/lib/xcm-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,11 @@ function createJunctionFromObject(data: Record<string, unknown>) {

if (entries.length === 1) {
return {
X1: {
[JunctionType[entries[0][0] as JunctionTypeKey]]: entries[0][1],
},
X1: [
{
[JunctionType[entries[0][0] as JunctionTypeKey]]: entries[0][1],
},
],
};
}

Expand Down Expand Up @@ -179,12 +181,14 @@ function getAccountLocation(accountId?: AccountId) {
return {
parents: 0,
interior: {
X1: {
[isEthereum ? 'accountKey20' : 'accountId32']: {
network: 'Any',
[isEthereum ? 'key' : 'id']: accountId,
X1: [
{
[isEthereum ? 'accountKey20' : 'accountId32']: {
network: null,
[isEthereum ? 'key' : 'id']: accountId,
},
},
},
],
},
};
}
Expand All @@ -195,7 +199,7 @@ function getChildLocation(parachainId: number, accountId?: AccountId) {

if (accountId) {
location[isEthereum ? 'accountKey' : 'accountId'] = {
network: 'Any',
network: null,
[isEthereum ? 'key' : 'id']: accountId,
};
}
Expand All @@ -212,7 +216,7 @@ function getParentLocation(accountId?: AccountId) {

if (accountId) {
location[isEthereum ? 'accountKey' : 'accountId'] = {
network: 'Any',
network: null,
[isEthereum ? 'key' : 'id']: accountId,
};
}
Expand All @@ -229,7 +233,7 @@ function getSiblingLocation(parachainId: number, accountId?: AccountId) {

if (accountId) {
location[isEthereum ? 'accountKey' : 'accountId'] = {
network: 'Any',
network: null,
[isEthereum ? 'key' : 'id']: accountId,
};
}
Expand Down
58 changes: 54 additions & 4 deletions src/renderer/shared/api/xcm/service/xcmService.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { type ApiPromise } from '@polkadot/api';
import { BN } from '@polkadot/util';
import get from 'lodash/get';
import { BN, BN_TEN } from '@polkadot/util';
import { camelCase, get } from 'lodash';

import { type AccountId, type Chain, type ChainId, type HexString } from '@/shared/core';
import { getAssetId, getTypeName, getTypeVersion, toLocalChainId } from '@/shared/lib/utils';
import { type XTokenPalletTransferArgs, type XcmPalletTransferArgs } from '@/entities/transaction';
import { localStorageService } from '../../local-storage';
import { chainsService } from '../../network';
import { XCM_KEY, XCM_URL } from '../lib/constants';
import { FACTOR_MULTIPLIER, SET_TOPIC_SIZE, XCM_KEY, XCM_URL } from '../lib/constants';
import {
type AssetLocation,
type AssetName,
Expand All @@ -28,6 +28,7 @@ export const xcmService = {
getAvailableTransfers,
getEstimatedFee,
getEstimatedRequiredDestWeight,
getDeliveryFeeFromConfig,

getAssetLocation,
getVersionedDestinationLocation,
Expand All @@ -36,6 +37,8 @@ export const xcmService = {
parseXcmPalletExtrinsic,
parseXTokensExtrinsic,
decodeXcm,

getParentChain,
};

async function fetchXcmConfig(): Promise<XcmConfig> {
Expand Down Expand Up @@ -151,9 +154,9 @@ function getVersionedDestinationLocation(
destinationParaId?: number,
accountId?: AccountId,
) {
const location = xcmUtils.getDestinationLocation(originChain, destinationParaId, accountId);
const type = getTypeName(api, transferType, 'dest');
const version = getTypeVersion(api, type || '');
const location = xcmUtils.getDestinationLocation(originChain, destinationParaId, accountId);

if (!version) return location;

Expand Down Expand Up @@ -326,3 +329,50 @@ function decodeXcm(chainId: ChainId, data: XcmPalletPayload | XTokensPayload): D
dest: data.destAccountId,
};
}

function getParentChain(chain: Chain, chains: Record<ChainId, Chain>) {
if (!chain.parentId) return chain;

return chains[chain.parentId];
}

async function getDeliveryFeeFromConfig({
config,
originChain,
originApi,
parentApi,
destinationChainId,
txBytesLength,
}: {
config: XcmConfig;
originChain: string;
originApi: ApiPromise;
parentApi: ApiPromise;
destinationChainId: number;
txBytesLength: number;
}): Promise<BN> {
const RELAYCHAINS = [1000, 2000];
const direction = RELAYCHAINS.includes(destinationChainId) ? 'toParent' : 'toParachain';

const deliveryFeeConfig = config.networkDeliveryFee[originChain]?.[direction];

if (!deliveryFeeConfig) return new BN(0);

let deliveryFactor: string;

if (direction === 'toParent') {
deliveryFactor = (
await parentApi.query[camelCase(deliveryFeeConfig.factorPallet)].upwardDeliveryFeeFactor()
).toString();
} else {
deliveryFactor = (
await originApi.query[camelCase(deliveryFeeConfig.factorPallet)].deliveryFeeFactor(destinationChainId)
).toString();
}

const weight = new BN(txBytesLength).add(SET_TOPIC_SIZE);
const feeSize = new BN(deliveryFeeConfig.sizeBase).add(weight.mul(new BN(deliveryFeeConfig.sizeFactor)));
const deliveryFee = new BN(deliveryFactor).div(new BN(BN_TEN).pow(FACTOR_MULTIPLIER)).mul(feeSize);

return deliveryFee;
}
3 changes: 1 addition & 2 deletions src/renderer/shared/lib/utils/substrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ import { DEFAULT_TIME, ONE_DAY, THRESHOLD } from './constants';

export type TxMetadata = { registry: TypeRegistry; options: OptionsWithMeta; info: BaseTxInfo };

// TODO: Add V3, V4 support
const SUPPORTED_VERSIONS = ['V2'];
const SUPPORTED_VERSIONS = ['V3', 'V4'];
const UNUSED_LABEL = 'unused';

/**
Expand Down
Loading

0 comments on commit 308bba7

Please sign in to comment.