From 3f105bc943b12f52d0eb4e8d270111d516100cd1 Mon Sep 17 00:00:00 2001 From: mmaurello <93129175+mmaurello@users.noreply.github.com> Date: Mon, 14 Oct 2024 11:45:55 +0200 Subject: [PATCH] Fix issues with transfers from all different sources and destinations (#368) * fix issues with transfers from all different sources and destinations * update tests * update tests --- .../builder/fixtures/builderParamsMock.ts | 23 ++- .../__snapshots__/polkadotXcm.test.ts.snap | 121 ++++++++++++- .../extrinsic/polkadotXcm/polkadotXcm.test.ts | 23 ++- .../extrinsic/polkadotXcm/polkadotXcm.ts | 167 +++++++++++++----- .../providers/wormhole/wormhole/wormhole.ts | 5 +- .../config/src/mrl-configs/fantomTestnet.ts | 2 +- .../config/src/mrl-configs/moonbaseAlpha.ts | 2 +- .../src/getTransferData/getMoonChainData.ts | 2 +- .../mrl/src/getTransferData/getSourceData.ts | 8 +- .../src/getTransferData/getTransferData.ts | 9 +- .../getTransferData/getTransferData.utils.ts | 6 +- .../services/wormhole/WormholeWagmiSigner.ts | 13 +- 12 files changed, 306 insertions(+), 75 deletions(-) diff --git a/packages/builder/fixtures/builderParamsMock.ts b/packages/builder/fixtures/builderParamsMock.ts index c5baee0a..4b14992e 100644 --- a/packages/builder/fixtures/builderParamsMock.ts +++ b/packages/builder/fixtures/builderParamsMock.ts @@ -13,7 +13,12 @@ import type { BuilderParams, MrlBuilderParams } from '../src'; export const apiMock = { tx: { polkadotXcm: { send: vi.fn(() => 'polkadotXcm.send => RESULT') }, - xTokens: { transfer: vi.fn(() => 'xTokens.transfer => RESULT') }, + xTokens: { + transfer: vi.fn(() => 'xTokens.transfer => RESULT'), + transferMulticurrencies: vi.fn( + () => 'xTokens.transferMulticurrencies => RESULT', + ), + }, }, // biome-ignore lint/suspicious/noExplicitAny: } as any; @@ -148,6 +153,22 @@ export const mrlBuildParamsMock: MrlBuilderParams = { }, }; +export const mrlBuildParamsSameAssetMock: MrlBuilderParams = { + ...buildParamsSameAssetMock, + isAutomatic: true, + moonApi: apiMock, + moonAsset: testAssetAmount, + moonChain: moonbaseAlphaMock, + moonGasLimit: 999_999n, + transact: { + call: '0x4d79207465787420737472696e67', + txWeight: { + refTime: 24_902_375_000n, + proofSize: 62_193n, + }, + }, +}; + export const mrlBuildParamsMock2: MrlBuilderParams = { ...buildParachainParamsMock, isAutomatic: true, diff --git a/packages/builder/src/mrl/providers/wormhole/extrinsic/polkadotXcm/__snapshots__/polkadotXcm.test.ts.snap b/packages/builder/src/mrl/providers/wormhole/extrinsic/polkadotXcm/__snapshots__/polkadotXcm.test.ts.snap index acd4285e..494545bb 100644 --- a/packages/builder/src/mrl/providers/wormhole/extrinsic/polkadotXcm/__snapshots__/polkadotXcm.test.ts.snap +++ b/packages/builder/src/mrl/providers/wormhole/extrinsic/polkadotXcm/__snapshots__/polkadotXcm.test.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`polkadotXcm > send > should be correct config 1`] = ` +exports[`polkadotXcm > send with transferMulticurrencies > should be correct config 1`] = ` ExtrinsicConfig { "func": "batchAll", "getArgs": [Function], @@ -8,7 +8,120 @@ ExtrinsicConfig { } `; -exports[`polkadotXcm > send > should get correct arguments 1`] = ` +exports[`polkadotXcm > send with transferMulticurrencies > should get correct arguments 1`] = ` +[ + [ + "xTokens.transferMulticurrencies => RESULT", + "polkadotXcm.send => RESULT", + ], +] +`; + +exports[`polkadotXcm > send with transferMulticurrencies > should get correct arguments 2`] = ` +[ + { + "V3": { + "interior": { + "X1": { + "Parachain": 1000, + }, + }, + "parents": 1, + }, + }, + { + "V3": [ + { + "WithdrawAsset": [ + { + "fun": { + "Fungible": 100000000000000000n, + }, + "id": { + "Concrete": { + "interior": { + "X1": { + "PalletInstance": 10, + }, + }, + "parents": 0, + }, + }, + }, + ], + }, + { + "BuyExecution": { + "fees": { + "fun": { + "Fungible": 100000000000000000n, + }, + "id": { + "Concrete": { + "interior": { + "X1": { + "PalletInstance": 10, + }, + }, + "parents": 0, + }, + }, + }, + "weightLimit": "Unlimited", + }, + }, + { + "Transact": { + "call": { + "encoded": "0x4d79207465787420737472696e67", + }, + "originKind": "SovereignAccount", + "requireWeightAtMost": { + "proofSize": 62193n, + "refTime": 24902375000n, + }, + }, + }, + { + "SetAppendix": [ + { + "RefundSurplus": {}, + }, + { + "DepositAsset": { + "assets": { + "Wild": { + "AllCounted": 1, + }, + }, + "beneficiary": { + "interior": { + "X1": { + "AccountKey20": { + "key": "0xcd93e4dcf4e6bb23f1c58109113266e9b79cd844", + }, + }, + }, + "parents": 0, + }, + }, + }, + ], + }, + ], + }, +] +`; + +exports[`polkadotXcm > send with transfers > should be correct config 1`] = ` +ExtrinsicConfig { + "func": "batchAll", + "getArgs": [Function], + "module": "utility", +} +`; + +exports[`polkadotXcm > send with transfers > should get correct arguments 1`] = ` [ [ "xTokens.transfer => RESULT", @@ -18,7 +131,7 @@ exports[`polkadotXcm > send > should get correct arguments 1`] = ` ] `; -exports[`polkadotXcm > send > should get correct arguments 2`] = ` +exports[`polkadotXcm > send with transfers > should get correct arguments 2`] = ` [ { "V3": { @@ -99,7 +212,7 @@ exports[`polkadotXcm > send > should get correct arguments 2`] = ` "interior": { "X1": { "AccountKey20": { - "key": "0x88275533b5d43292c86d05985c3a6e226fee2bae", + "key": "0xcd93e4dcf4e6bb23f1c58109113266e9b79cd844", }, }, }, diff --git a/packages/builder/src/mrl/providers/wormhole/extrinsic/polkadotXcm/polkadotXcm.test.ts b/packages/builder/src/mrl/providers/wormhole/extrinsic/polkadotXcm/polkadotXcm.test.ts index 01795e1f..2f17c4f5 100644 --- a/packages/builder/src/mrl/providers/wormhole/extrinsic/polkadotXcm/polkadotXcm.test.ts +++ b/packages/builder/src/mrl/providers/wormhole/extrinsic/polkadotXcm/polkadotXcm.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it, vi } from 'vitest'; -import { apiMock, mrlBuildParamsMock } from '../../../../../../fixtures'; +import { + apiMock, + mrlBuildParamsMock, + mrlBuildParamsSameAssetMock, +} from '../../../../../../fixtures'; import type { ExtrinsicConfig } from '../../../../../extrinsic'; import { XcmVersion } from '../../../../../extrinsic/ExtrinsicBuilder.interfaces'; import { polkadotXcm } from './polkadotXcm'; @@ -18,7 +22,7 @@ vi.mock( ); describe('polkadotXcm', () => { - describe('send', () => { + describe('send with transferMulticurrencies', () => { const extrinsic = polkadotXcm() .send() .build(mrlBuildParamsMock) as ExtrinsicConfig; @@ -32,4 +36,19 @@ describe('polkadotXcm', () => { expect(apiMock.tx.polkadotXcm.send.mock.lastCall).toMatchSnapshot(); }); }); + + describe('send with transfers', () => { + const extrinsic = polkadotXcm() + .send() + .build(mrlBuildParamsSameAssetMock) as ExtrinsicConfig; + + it('should be correct config', () => { + expect(extrinsic).toMatchSnapshot(); + }); + + it('should get correct arguments', () => { + expect(extrinsic.getArgs({} as any)).toMatchSnapshot(); + expect(apiMock.tx.polkadotXcm.send.mock.lastCall).toMatchSnapshot(); + }); + }); }); diff --git a/packages/builder/src/mrl/providers/wormhole/extrinsic/polkadotXcm/polkadotXcm.ts b/packages/builder/src/mrl/providers/wormhole/extrinsic/polkadotXcm/polkadotXcm.ts index 79839830..6df20e27 100644 --- a/packages/builder/src/mrl/providers/wormhole/extrinsic/polkadotXcm/polkadotXcm.ts +++ b/packages/builder/src/mrl/providers/wormhole/extrinsic/polkadotXcm/polkadotXcm.ts @@ -1,8 +1,18 @@ -import { type AnyParachain, AssetAmount } from '@moonbeam-network/xcm-types'; +import { + type AnyParachain, + AssetAmount, + Parachain, +} from '@moonbeam-network/xcm-types'; import { getMultilocationDerivedAddresses } from '@moonbeam-network/xcm-utils'; +import type { ApiPromise } from '@polkadot/api'; +import type { SubmittableExtrinsic } from '@polkadot/api/types'; +import type { ISubmittableResult } from '@polkadot/types/types'; import { ExtrinsicBuilder } from '../../../../../extrinsic/ExtrinsicBuilder'; import { ExtrinsicConfig } from '../../../../../types/substrate/ExtrinsicConfig'; -import type { MrlConfigBuilder } from '../../../../MrlBuilder.interfaces'; +import type { + MrlBuilderParams, + MrlConfigBuilder, +} from '../../../../MrlBuilder.interfaces'; // TODO: these have to come from the configs const BUY_EXECUTION_FEE = 100_000_000_000_000_000n; // moonChainFee @@ -14,7 +24,9 @@ export function polkadotXcm() { build: ({ asset, destination, + destinationAddress, fee, + isAutomatic, moonAsset, moonChain, moonApi, @@ -35,56 +47,31 @@ export function polkadotXcm() { throw new Error('Source API needs to be defined'); } + if (!Parachain.is(source)) { + throw new Error('Source chain needs to be a parachain'); + } + const { address20: computedOriginAccount } = getMultilocationDerivedAddresses({ address: sourceAddress, - paraId: moonChain.parachainId, + paraId: source.parachainId, isParents: true, }); - const { transfer } = sourceApi.tx.xTokens; - const builder = ExtrinsicBuilder().xTokens().transfer(); - - const assetTransferTx = transfer( - ...builder - .build({ - asset, - destination: moonChain, - destinationAddress: computedOriginAccount, - destinationApi: moonApi, - fee, - // TODO: This is a workaround. xTokens.transfer doesn't need source chain but the interfaces requires it. - // In this case we know that a source chain is not a Parachain. - source: source as AnyParachain, - sourceAddress, - sourceApi, - }) - .getArgs(transfer), - ); - /* - * TODO: Can we move it to AssetRoute and receive it in build params? - * In the future we could also check if wallet already has necessary DEV/GLMR to pay execution fees on Moonbase/Moonbeam. - * Also we need to move fees to AssetRoute. - */ - const feeAssetTransferTx = transfer( - ...builder - .build({ - asset: AssetAmount.fromChainAsset( - source.getChainAsset(moonAsset), - { - amount: CROSS_CHAIN_FEE + BUY_EXECUTION_FEE, - }, - ), - destination: moonChain, - destinationAddress: computedOriginAccount, - destinationApi: moonApi, - fee, - source: source as AnyParachain, - sourceAddress, - sourceApi, - }) - .getArgs(transfer), - ); + const assetTransferTxs = getAssetTransferTxs({ + asset, + computedOriginAccount, + destination, + destinationAddress, + fee, + isAutomatic, + moonApi, + moonAsset, + moonChain, + source, + sourceAddress, + sourceApi, + }); const send = sourceApi.tx.polkadotXcm.send( { @@ -166,9 +153,95 @@ export function polkadotXcm() { return new ExtrinsicConfig({ module: 'utility', func: 'batchAll', - getArgs: () => [[assetTransferTx, feeAssetTransferTx, send]], + getArgs: () => [[...assetTransferTxs, send]], }); }, }), }; } + +interface GetAssetTransferTxsParams extends MrlBuilderParams { + computedOriginAccount: string; + source: AnyParachain; + sourceApi: ApiPromise; +} + +function getAssetTransferTxs({ + asset, + computedOriginAccount, + fee, + moonApi, + moonAsset, + moonChain, + source, + sourceAddress, + sourceApi, +}: GetAssetTransferTxsParams): SubmittableExtrinsic< + 'promise', + ISubmittableResult +>[] { + const { transfer, transferMulticurrencies } = sourceApi.tx.xTokens; + const transferBuilder = ExtrinsicBuilder().xTokens().transfer(); + const transferMulticurrenciesBuilder = ExtrinsicBuilder() + .xTokens() + .transferMultiCurrencies(); + /** + * TODO here we should compare the asset with the cross chain fee asset. + * For example, FTM cannot pay for fees in Moonbase while AGNG can, so for FTM we have to send a transferMulticurrencies + * This "if" is a workaround, change when we implement properly the concept of cross-chain fee (different from moonChainFee) + */ + if (asset.isSame(fee)) { + const assetTransferTx = transfer( + ...transferBuilder + .build({ + asset: asset.copyWith({ + // TODO for the moment this is only applicable to peaq, AGNG pays for fees and we have to include the cross chain fee + // for this we have to add a new concept in the config (Cross Chain Fee), and get the value from there + amount: asset.amount + 10n * CROSS_CHAIN_FEE, + }), + destination: moonChain, + destinationAddress: computedOriginAccount, + destinationApi: moonApi, + fee, + source: source, + sourceAddress, + sourceApi, + }) + .getArgs(transfer), + ); + const feeAssetTransferTx = transfer( + ...transferBuilder + .build({ + asset: AssetAmount.fromChainAsset(source.getChainAsset(moonAsset), { + amount: CROSS_CHAIN_FEE + BUY_EXECUTION_FEE, + }), + destination: moonChain, + destinationAddress: computedOriginAccount, + destinationApi: moonApi, + fee, + source: source, + sourceAddress, + sourceApi, + }) + .getArgs(transfer), + ); + return [assetTransferTx, feeAssetTransferTx]; + } + const multiCurrenciesTransferTx = transferMulticurrencies( + ...transferMulticurrenciesBuilder + .build({ + asset, + destination: moonChain, + destinationAddress: computedOriginAccount, + destinationApi: moonApi, + fee: AssetAmount.fromChainAsset(source.getChainAsset(moonAsset), { + amount: CROSS_CHAIN_FEE + BUY_EXECUTION_FEE, + }), + source: source as AnyParachain, + sourceAddress, + sourceApi, + }) + .getArgs(transferMulticurrencies), + ); + return [multiCurrenciesTransferTx]; +} diff --git a/packages/builder/src/mrl/providers/wormhole/wormhole/wormhole.ts b/packages/builder/src/mrl/providers/wormhole/wormhole/wormhole.ts index 4575ade5..24960128 100644 --- a/packages/builder/src/mrl/providers/wormhole/wormhole/wormhole.ts +++ b/packages/builder/src/mrl/providers/wormhole/wormhole/wormhole.ts @@ -1,4 +1,4 @@ -import { EvmChain, EvmParachain } from '@moonbeam-network/xcm-types'; +import { EvmChain, EvmParachain, Parachain } from '@moonbeam-network/xcm-types'; import { getMultilocationDerivedAddresses } from '@moonbeam-network/xcm-utils'; import { evmToAddress } from '@polkadot/util-crypto/address'; import { Wormhole } from '@wormhole-foundation/sdk-connect'; @@ -26,6 +26,7 @@ export function wormhole() { source, sourceAddress, }): WormholeConfig => { + const isSourceParachain = Parachain.is(source); const isDestinationMoonChain = destination.isEqual(moonChain); const isDestinationEvmChain = EvmChain.is(destination); const isNativeAsset = asset.isSame( @@ -40,7 +41,7 @@ export function wormhole() { const { address20: computedOriginAccount } = getMultilocationDerivedAddresses({ address: sourceAddress, - paraId: moonChain.parachainId, + paraId: isSourceParachain ? source.parachainId : undefined, isParents: true, }); diff --git a/packages/config/src/mrl-configs/fantomTestnet.ts b/packages/config/src/mrl-configs/fantomTestnet.ts index 1f9f8739..fe6ceac8 100644 --- a/packages/config/src/mrl-configs/fantomTestnet.ts +++ b/packages/config/src/mrl-configs/fantomTestnet.ts @@ -92,7 +92,7 @@ export const fantomTestnetRoutes = new MrlChainRoutes({ }, }, mrl: { - isAutomaticPossible: true, + isAutomaticPossible: false, // TODO transfer: MrlBuilder().wormhole().wormhole().tokenTransfer(), moonChain: { asset: ftmwh, diff --git a/packages/config/src/mrl-configs/moonbaseAlpha.ts b/packages/config/src/mrl-configs/moonbaseAlpha.ts index 9538185e..7a03c035 100644 --- a/packages/config/src/mrl-configs/moonbaseAlpha.ts +++ b/packages/config/src/mrl-configs/moonbaseAlpha.ts @@ -29,7 +29,7 @@ export const moonbaseAlphaRoutes = new MrlChainRoutes({ }, }, mrl: { - isAutomaticPossible: true, + isAutomaticPossible: false, // TODO transfer: MrlBuilder().wormhole().wormhole().tokenTransfer(), moonChain: { asset: ftmwh, diff --git a/packages/mrl/src/getTransferData/getMoonChainData.ts b/packages/mrl/src/getTransferData/getMoonChainData.ts index a93d3a38..98c47eb1 100644 --- a/packages/mrl/src/getTransferData/getMoonChainData.ts +++ b/packages/mrl/src/getTransferData/getMoonChainData.ts @@ -49,7 +49,7 @@ export async function getMoonChainData({ if (Parachain.is(route.source.chain)) { const { address20 } = getMultilocationDerivedAddresses({ address: sourceAddress, - paraId: moonChain.parachainId, + paraId: route.source.chain.parachainId, isParents: true, }); diff --git a/packages/mrl/src/getTransferData/getSourceData.ts b/packages/mrl/src/getTransferData/getSourceData.ts index 7d1d1407..2f53318e 100644 --- a/packages/mrl/src/getTransferData/getSourceData.ts +++ b/packages/mrl/src/getTransferData/getSourceData.ts @@ -93,7 +93,7 @@ export async function getSourceData({ const transfer = await buildTransfer({ asset: balance, destinationAddress, - destinationFee, + feeAsset: feeBalance, route, sourceAddress, }); @@ -112,8 +112,8 @@ export async function getSourceData({ chain: source, transfer, asset: balance, + feeAsset: feeBalance, destinationAddress, - destinationFee, route, sourceAddress, }); @@ -201,7 +201,7 @@ export async function getRelayerFee({ asset, chain, destinationAddress, - destinationFee, + feeAsset, route, sourceAddress, transfer, @@ -214,7 +214,7 @@ export async function getRelayerFee({ const builderParams = await getMrlBuilderParams({ asset, destinationAddress, - destinationFee, + feeAsset, route, sourceAddress, }); diff --git a/packages/mrl/src/getTransferData/getTransferData.ts b/packages/mrl/src/getTransferData/getTransferData.ts index 3c5189ed..fd281b36 100644 --- a/packages/mrl/src/getTransferData/getTransferData.ts +++ b/packages/mrl/src/getTransferData/getTransferData.ts @@ -112,11 +112,16 @@ export async function getTransferData({ route.source.chain.getChainAsset(route.source.asset), { amount: bigintAmount }, ); - + const feeAsset = AssetAmount.fromChainAsset( + route.source.chain.getChainAsset( + route.source.fee?.asset || route.source.asset, + ), + { amount: sourceData.fee.amount }, + ); const transfer = await buildTransfer({ asset, destinationAddress, - destinationFee, + feeAsset, route, sourceAddress, }); diff --git a/packages/mrl/src/getTransferData/getTransferData.utils.ts b/packages/mrl/src/getTransferData/getTransferData.utils.ts index a7730c3d..f96da7cf 100644 --- a/packages/mrl/src/getTransferData/getTransferData.utils.ts +++ b/packages/mrl/src/getTransferData/getTransferData.utils.ts @@ -99,7 +99,7 @@ export function getMrlMin({ export interface BuildTransferParams { asset: AssetAmount; destinationAddress: string; - destinationFee: AssetAmount; + feeAsset: AssetAmount; route: AssetRoute; sourceAddress: string; } @@ -124,7 +124,7 @@ export async function buildTransfer(params: BuildTransferParams) { export async function getMrlBuilderParams({ asset, destinationAddress, - destinationFee, + feeAsset, route, sourceAddress, }: BuildTransferParams): Promise { @@ -150,7 +150,7 @@ export async function getMrlBuilderParams({ destination, destinationAddress, destinationApi, - fee: destinationFee, + fee: feeAsset, isAutomatic: route.mrl.isAutomaticPossible, moonApi, moonAsset: moonChain.nativeAsset, diff --git a/packages/mrl/src/services/wormhole/WormholeWagmiSigner.ts b/packages/mrl/src/services/wormhole/WormholeWagmiSigner.ts index 1c536c9e..cb4109f5 100644 --- a/packages/mrl/src/services/wormhole/WormholeWagmiSigner.ts +++ b/packages/mrl/src/services/wormhole/WormholeWagmiSigner.ts @@ -1,4 +1,3 @@ -import { wormholeFactory } from '@moonbeam-network/xcm-builder'; import type { EvmSigner } from '@moonbeam-network/xcm-sdk'; import type { EvmChain, EvmParachain } from '@moonbeam-network/xcm-types'; import type { @@ -7,7 +6,6 @@ import type { SignAndSendSigner, SignedTx, UnsignedTransaction, - Wormhole, } from '@wormhole-foundation/sdk-connect'; import { http, @@ -28,8 +26,6 @@ export class WormholeWagmiSigner< readonly #publicClient: PublicClient; - readonly #wh: Wormhole; - constructor(chain: EvmChain | EvmParachain, signer: EvmSigner) { this.#chain = chain; this.#signer = signer; @@ -37,12 +33,11 @@ export class WormholeWagmiSigner< chain: chain.getViemChain(), transport: http(), }); - this.#wh = wormholeFactory(chain) as Wormhole; } chain(): C { // biome-ignore lint/suspicious/noExplicitAny: need to fix types - return this.#wh.getChain(this.#chain.getWormholeName()) as any as C; + return this.#chain.getWormholeName() as any as C; } address(): Address { @@ -61,13 +56,17 @@ export class WormholeWagmiSigner< transactions: UnsignedTransaction[], ): Promise { const signed = []; + const signerChainID = await this.#signer.getChainId(); let nonce = await this.#publicClient.getTransactionCount({ address: this.address(), }); for (const tx of transactions) { - const { transaction, parallelizable } = tx; + const { transaction, description, parallelizable } = tx; + console.log( + `Signing: ${description} for ${this.address()}, chainId: ${signerChainID}`, + ); const signedTx = await this.#signer.sendTransaction({ ...transaction,