Skip to content

Commit

Permalink
implement Batch contract builder for sending assets and xcm message w…
Browse files Browse the repository at this point in the history
…ith remote execution
  • Loading branch information
mmaurello committed Oct 16, 2024
1 parent 8cd1d05 commit 0a6face
Show file tree
Hide file tree
Showing 16 changed files with 867 additions and 113 deletions.
8 changes: 4 additions & 4 deletions examples/mrl-simple/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from '@moonbeam-network/xcm-config';
import type { Asset } from '@moonbeam-network/xcm-types';
import { Keyring } from '@polkadot/api';
import { cryptoWaitReady, mnemonicToMiniSecret } from '@polkadot/util-crypto';
import { cryptoWaitReady } from '@polkadot/util-crypto';
import { http, type Address, createWalletClient } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { moonbaseAlpha as moonbaseAlphaViem } from 'viem/chains';
Expand Down Expand Up @@ -56,9 +56,9 @@ main()
async function main() {
// await fromFantomToPeaq(ftm, 0.011);
// await fromFantomToMoonbase(ftm, 0.01);
// await fromMoonbaseToFantom(ftmwh, 0.01);
// await fromPeaqToFantom(agng, 1);
await fromPeaqEvmToFantom(ftmwh, 0.01);
// await fromMoonbaseToFantom(dev, 0.01);
// await fromPeaqToFantom(ftmwh, 0.1);
await fromPeaqEvmToFantom(ftmwh, 0.12);
}

async function fromFantomToPeaq(asset: Asset, amount: number) {
Expand Down
1 change: 1 addition & 0 deletions examples/mrl-simple/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"dependencies": {
"@moonbeam-network/mrl": "workspace:*",
"@moonbeam-network/xcm-config": "workspace:*",
"@moonbeam-network/xcm-types": "workspace:*",
"@moonbeam-network/xcm-utils": "workspace:*"
},
"devDependencies": {
Expand Down
29 changes: 29 additions & 0 deletions packages/builder/src/builder.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { type AnyParachain, EvmParachain } from '@moonbeam-network/xcm-types';
import { u8aToHex } from '@polkadot/util';
import { decodeAddress } from '@polkadot/util-crypto';
import type { Address } from 'viem';

export function getPrecompileDestinationInterior(
destination: AnyParachain,
address?: string,
): [Address, Address] | [Address] {
if (!address) {
return [`0x0000000${destination.parachainId.toString(16)}`];
}

/*
01: AccountId32
03: AccountKey20
https://docs.moonbeam.network/builders/interoperability/xcm/xc20/xtokens/#building-the-precompile-multilocation
*/
const accountType = EvmParachain.is(destination) ? '03' : '01';
const acc = `0x${accountType}${u8aToHex(
decodeAddress(address),
-1,
false,
)}00` as Address;

return destination.parachainId
? [`0x0000000${destination.parachainId.toString(16)}`, acc]
: [acc];
}
24 changes: 5 additions & 19 deletions packages/builder/src/contract/contracts/Xtokens/Xtokens.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { type AnyParachain, EvmParachain } from '@moonbeam-network/xcm-types';
import type { AnyParachain } from '@moonbeam-network/xcm-types';
import { formatAssetIdToERC20 } from '@moonbeam-network/xcm-utils';
import { u8aToHex } from '@polkadot/util';
import { decodeAddress, evmToAddress } from '@polkadot/util-crypto';
import { getPrecompileDestinationInterior } from '../../../builder.utils';
import { ContractConfig } from '../../../types/evm/ContractConfig';
import type { ContractConfigBuilder } from '../../ContractBuilder.interfaces';
import { XTOKENS_ABI } from './XtokensABI';
Expand Down Expand Up @@ -116,6 +117,7 @@ type DestinationMultilocation = [
),
];

// TODO test if this is needed
function getDestinationMultilocationForPrecompileDestination(
address: string,
destination: AnyParachain,
Expand Down Expand Up @@ -145,22 +147,6 @@ function getDestinationMultilocation(
address: string,
destination: AnyParachain,
): DestinationMultilocation {
/*
01: AccountId32
03: AccountKey20
https://docs.moonbeam.network/builders/interoperability/xcm/xc20/xtokens/#building-the-precompile-multilocation
*/
const accountType = EvmParachain.is(destination) ? '03' : '01';
const acc = `0x${accountType}${u8aToHex(
decodeAddress(address),
-1,
false,
)}00`;

return [
1,
destination.parachainId
? [`0x0000000${destination.parachainId.toString(16)}`, acc]
: [acc],
];
const interior = getPrecompileDestinationInterior(destination, address);
return [1, interior];
}
10 changes: 6 additions & 4 deletions packages/builder/src/extrinsic/pallets/xTokens/xTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export function xTokens() {
module: pallet,
func: 'transfer',
getArgs: (func) => {
const version = getExtrinsicArgumentVersion(func, 2);
const destIndex = 2;
const version = getExtrinsicArgumentVersion(func, destIndex);

return [
asset.getAssetId(),
Expand All @@ -29,6 +30,7 @@ export function xTokens() {
}),
transferMultiAsset: (originParachainId: number) => {
const funcName = 'transferMultiasset';
const destIndex = 1;

return {
here: (): ExtrinsicConfigBuilder => ({
Expand All @@ -37,7 +39,7 @@ export function xTokens() {
module: pallet,
func: funcName,
getArgs: (func) => {
const version = getExtrinsicArgumentVersion(func, 1);
const version = getExtrinsicArgumentVersion(func, destIndex);

return [
{
Expand Down Expand Up @@ -65,7 +67,7 @@ export function xTokens() {
module: pallet,
func: funcName,
getArgs: (func) => {
const version = getExtrinsicArgumentVersion(func, 1);
const version = getExtrinsicArgumentVersion(func, destIndex);

return [
{
Expand Down Expand Up @@ -97,7 +99,7 @@ export function xTokens() {
module: pallet,
func: funcName,
getArgs: (func) => {
const version = getExtrinsicArgumentVersion(func, 1);
const version = getExtrinsicArgumentVersion(func, destIndex);

return [
{
Expand Down
169 changes: 169 additions & 0 deletions packages/builder/src/mrl/providers/wormhole/contract/Batch/Batch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import {
type AssetAmount,
type ChainAsset,
EvmParachain,
type Parachain,
} from '@moonbeam-network/xcm-types';
import { getMultilocationDerivedAddresses } from '@moonbeam-network/xcm-utils';
import { _0n } from '@polkadot/util';
import { evmToAddress } from '@polkadot/util-crypto';
import { type Address, encodeFunctionData, maxUint64 } from 'viem';
import { getPrecompileDestinationInterior } from '../../../../../builder.utils';
import { ContractConfig } from '../../../../../contract';
import type { MrlConfigBuilder } from '../../../../MrlBuilder.interfaces';
import {
CROSS_CHAIN_FEE,
buildSendExtrinsic,
} from '../../extrinsic/polkadotXcm/polkadotXcm';
import { getAbisForChain } from './abi/abi.helpers';

const module = 'PeaqTest';
export const TEST_PEAQ_CALL: `0x${string}` =
'0x96e292b8000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000094000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000803000000000000000000000000000000000000000000000000000000000000080400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002c00000000000000000000000000000000000000000000000000000000000000244ab946323000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000ffffffffffffffff0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000ffffffff000000000000000000000000000003e8000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000ffffffff000000000000000000000000000003e900000000000000000000000000000000000000000000000000005af3107a400000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000500000003e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016033b6d6b531a9c2d2e1490f39ad2609ea9cd2f7b29000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000052498600e64000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000500000003e800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003f0031000040000010403001300008a5d78456301130000010403001300008a5d7845630100060107b81f98d005eecd03007d0e260001bd370f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008080000000000000000000000000000000000000000000000000000000000000000110d96e292b8000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000003200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000566c1cebc6a4afa1c122e039c4bebe77043148ee000000000000000000000000bc976d4b9d57e57c3ca52e1fd136c45ff7955a960000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000bc976d4b9d57e57c3ca52e1fd136c45ff7955a9600000000000000000000000000000000000000000000000000005af3107a40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c40f5287b0000000000000000000000000566c1cebc6a4afa1c122e039c4bebe77043148ee00000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000008480769599e23f626efff39b89f3137e9917a4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001608140d010204000103003b6d6b531a9c2d2e1490f39ad2609ea9cd2f7b2900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';

export function Batch() {
return {
transferAssetsAndMessage: (): MrlConfigBuilder => ({
build: ({
asset,
destination,
destinationAddress,
fee,
isAutomatic,
moonAsset,
moonChain,
moonApi,
source,
sourceAddress,
sourceApi,
transact,
}) => {
if (!EvmParachain.is(source)) {
throw new Error('Source chain needs to be an EVMParachain');
}

if (!sourceApi) {
throw new Error('Source API needs to be defined');
}

if (
!source.contracts?.Xtokens ||
!source.contracts?.XcmUtils ||
!source.contracts?.Batch
) {
throw new Error(
'Source chain needs to have the Xtokens, XcmUtils and Batch contract addresses configured',
);
}

const subMappedAddress = evmToAddress(sourceAddress);

const { BatchAbi, XcmUtilsAbi, XtokensAbi } = getAbisForChain(source);

const { address20: computedOriginAccount } =
getMultilocationDerivedAddresses({
address: subMappedAddress,
paraId: source.parachainId,
isParents: true,
});

const send = buildSendExtrinsic({
asset,
destination,
destinationAddress,
computedOriginAccount,
fee,
isAutomatic,
moonAsset,
moonChain,
moonApi,
source,
sourceAddress,
sourceApi,
transact,
});
// we keep only the message
const encodedXcmMessage = send.args[1].toHex();

const { destinationParachain, destinationParachainAndAddress } =
getDestinationInHex(moonChain, computedOriginAccount);

const { currencies, feeItem } = getCurrencies({
source,
moonAsset,
asset,
});
const weight = maxUint64;

const multiTransferTxData = encodeFunctionData({
abi: XtokensAbi,
functionName: 'transferMultiCurrencies',
args: [currencies, feeItem, destinationParachainAndAddress, weight],
});

const xcmSendTxData = encodeFunctionData({
abi: XcmUtilsAbi,
functionName: 'xcmSend',
args: [destinationParachain, encodedXcmMessage],
});

return new ContractConfig({
address: source.contracts.Batch,
abi: BatchAbi,
args: [
[source.contracts.Xtokens, source.contracts.XcmUtils],
[],
[multiTransferTxData, xcmSendTxData],
[],
],
func: 'batchAll',
module,
});
},
}),
};
}

function getDestinationInHex(
moonChain: EvmParachain,
computedOriginAccount: string,
) {
const destinationParachain = {
parents: 1,
interior: getPrecompileDestinationInterior(moonChain),
} as const;

const destinationParachainAndAddress = {
parents: 1,
interior: getPrecompileDestinationInterior(
moonChain,
computedOriginAccount,
),
} as const;

return {
destinationParachain,
destinationParachainAndAddress,
};
}

interface GetCurrenciesParams {
source: Parachain | EvmParachain;
moonAsset: ChainAsset;
asset: AssetAmount;
}

function getCurrencies({ source, moonAsset, asset }: GetCurrenciesParams) {
const currencies = [
{
currencyAddress: source.getChainAsset(moonAsset).address as Address,
amount: CROSS_CHAIN_FEE,
},
{
currencyAddress: asset.address as Address,
amount: asset.amount,
},
];
const feeItem = 0; // moonAsset
return { currencies, feeItem };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { EvmParachain } from '@moonbeam-network/xcm-types';
import { PEAQ_BATCH_ABI } from './peaq/PeaqBatchContractAbi';
import { PEAQ_XCM_UTILS_ABI } from './peaq/PeaqXcmUtilsContractAbi';
import { PEAQ_XTOKENS_ABI } from './peaq/PeaqXtokensContractAbi';

export function getAbisForChain(chain: EvmParachain) {
// TODO when we add more chains, find a way to handle Abis for the different chains, if the Abis differ
return {
BatchAbi: PEAQ_BATCH_ABI,
XcmUtilsAbi: PEAQ_XCM_UTILS_ABI,
XtokensAbi: PEAQ_XTOKENS_ABI,
};
}
Loading

0 comments on commit 0a6face

Please sign in to comment.