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

Test XCM transfer and pay fees with foreign asset #3047

Merged
merged 20 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
94 changes: 78 additions & 16 deletions test/helpers/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
import type { AccountId20 } from "@polkadot/types/interfaces/runtime";
import { encodeFunctionData, parseAbi, keccak256 } from "viem";
import { ApiPromise, WsProvider } from "@polkadot/api";
import { alith } from "@moonwall/util";

export const EVM_FOREIGN_ASSETS_PALLET_ACCOUNT = "0x6d6f646c666f7267617373740000000000000000";
export const ARBITRARY_ASSET_ID = 42259045809535163221576417993425387648n;
Expand All @@ -20,19 +21,21 @@ export const DUMMY_REVERT_BYTECODE = "0x60006000fd";
export const RELAY_SOURCE_LOCATION = { Xcm: { parents: 1, interior: "Here" } };
export const RELAY_SOURCE_LOCATION2 = { Xcm: { parents: 2, interior: "Here" } };
export const RELAY_V3_SOURCE_LOCATION = { V3: { parents: 1, interior: "Here" } } as any;
export const PARA_1000_SOURCE_LOCATION_V3 = {
export const PARA_1000_SOURCE_LOCATION = {
Xcm: { parents: 1, interior: { X1: { Parachain: 1000 } } },
};
export const PARA_2000_SOURCE_LOCATION = {
Xcm: { parents: 1, interior: { X1: { Parachain: 2000 } } },
};
export const PARA_1001_SOURCE_LOCATION = {
Xcm: { parents: 1, interior: { X1: { Parachain: 1001 } } },
};
export const PARA_2000_SOURCE_LOCATION = {
Xcm: { parents: 1, interior: { X1: { Parachain: 2000 } } },
};

// XCM V4 Locations
export const RELAY_SOURCE_LOCATION_V4 = { parents: 1, interior: { here: null } };
export const PARA_1000_SOURCE_LOCATION_V4 = { parents: 1, interior: { X1: [{ Parachain: 1000 }] } };
export const PARA_1001_SOURCE_LOCATION_V4 = { parents: 1, interior: { X1: [{ Parachain: 1001 }] } };

export interface AssetMetadata {
name: string;
symbol: string;
Expand All @@ -47,12 +50,31 @@ export const relayAssetMetadata: AssetMetadata = {
isFrozen: false,
};

export interface TestAsset {
// The asset id as required by pallet - moonbeam - foreign - assets
id: bigint | string;
// The asset's XCM location (preferably a v4)
location: any;
// The asset's metadata
metadata: AssetMetadata;
// The asset's relative price as required by pallet - xcm - weight - trader
relativePrice?: bigint;
}

export function assetContractAddress(assetId: bigint | string): `0x${string}` {
return `0xffffffff${BigInt(assetId).toString(16)}`;
// Prefix as defined in pallet - moonbeam - foreign - assets (4 bytes)
const contractBaseAddress = "0xffffffff";
// Asset part (padded to 16 bytes)
const assetAddressBytes = BigInt(assetId).toString(16).padStart(32, "0");
return `${contractBaseAddress}${assetAddressBytes}`;
}

export const patchLocationV4recursively = (value: any) => {
// e.g. Convert this: { X1: { Parachain: 1000 } } to { X1: [ { Parachain: 1000 } ] }
// Also, will remove the Xcm key if it exists.
if (value && value.Xcm !== undefined) {
value = value.Xcm;
}
if (value && typeof value == "object") {
if (Array.isArray(value)) {
return value.map(patchLocationV4recursively);
Expand Down Expand Up @@ -191,7 +213,7 @@ export async function calculateRelativePrice(
return relativePrice;
}

function getSupportedAssedStorageKey(asset: any, context: any) {
function getSupportedAssetStorageKey(asset: any, context: any) {
const assetV4 = patchLocationV4recursively(asset);

const module = xxhashAsU8a(new TextEncoder().encode("XcmWeightTrader"), 128);
Expand All @@ -207,14 +229,25 @@ function getSupportedAssedStorageKey(asset: any, context: any) {
return new Uint8Array([...module, ...method, ...blake2concatStagingXcmV4Location]);
}

export async function addAssetToWeightTrader(asset: any, relativePrice: number, context: any) {
/**
* Adds asset to the pallet - xcm - weight - trader, with the relative provided.
* If the relative price is 0, the asset will be added with a placeholder price.
*
* @param assetLocation XCM v4 location of asset
* @param relativePrice the pallet requires 18 decimals balance needed to equal 1 unit of native
* asset. For example, if the asset price is twice as low as the GLMR price,
* the relative price should be 500_000_000_000_000_000n.
*
* @param context
*/
export async function addAssetToWeightTrader(asset: any, relativePrice: bigint, context: any) {
const assetV4 = patchLocationV4recursively(asset.Xcm);

if (relativePrice == 0) {
if (relativePrice == 0n) {
const addAssetWithPlaceholderPrice = context
.polkadotJs()
.tx.sudo.sudo(context.polkadotJs().tx.xcmWeightTrader.addAsset(assetV4, 1n));
const overallAssetKey = getSupportedAssedStorageKey(assetV4, context);
const overallAssetKey = getSupportedAssetStorageKey(assetV4, context);

const overrideAssetPrice = context.polkadotJs().tx.sudo.sudo(
context.polkadotJs().tx.system.setStorage([
Expand Down Expand Up @@ -288,7 +321,7 @@ export async function registerOldForeignAsset(
.polkadotJs()
.tx.sudo.sudo(context.polkadotJs().tx.xcmWeightTrader.addAsset(assetV4, relativePrice)),
{
expectEvents: [context.polkadotJs().events.xcmWeightTrader.SupportedAssetAdded],
expectEvents: [context.polkadotJs().events.xcmWeightTrader.SupportedAssetAdded as any],
allowFailures: false,
}
);
Expand Down Expand Up @@ -344,9 +377,16 @@ export async function registerOldForeignAsset(
}

/**
* This registers a foreign asset via the moonbeam-foreign-assets pallet.
*
* This registers a foreign asset via the moonbeam - foreign - assets pallet.
* This call will deploy the contract and make the erc20 contract of the asset available
* in the following address: 0xffffffff + metadata.id
* in the following address: 0xffffffff + assetId
*
* @param context
* @param assetId a bigint representing the assetId (it can be arbitrary for tests)
* @param xcmLocation the XCM location of the asset in Polkadot
* @param metadata
* @returns
*/
export async function registerForeignAsset(
context: DevModeContext,
Expand All @@ -355,6 +395,10 @@ export async function registerForeignAsset(
metadata: AssetMetadata
) {
const { decimals, name, symbol } = metadata;

// Sanitize Xcm Location
xcmLocation = patchLocationV4recursively(xcmLocation);

const { result } = await context.createBlock(
context
.polkadotJs()
Expand Down Expand Up @@ -400,13 +444,10 @@ export async function mockAssetBalance(
context: DevModeContext,
assetBalance: bigint,
assetId: bigint,
assetLocation: any,
sudoAccount: KeyringPair,
account: string | AccountId20
account: `0x${string}`
) {
const api = context.polkadotJs();
// Register the asset
await registerForeignAsset(context, assetId, RELAY_SOURCE_LOCATION, relayAssetMetadata);

const xcmTransaction = {
V2: {
Expand All @@ -433,6 +474,27 @@ export async function mockAssetBalance(
return;
}

/* Registers foreign asset and calls mock asset balance */
export async function registerAndFundAsset(
context: any,
asset: TestAsset,
amount: bigint,
address: `0x${string}`
) {
const { registeredAssetId } = await registerForeignAsset(
context,
BigInt(asset.id),
asset.location,
asset.metadata
);

await addAssetToWeightTrader(asset.location, asset.relativePrice || 0n, context);

await mockAssetBalance(context, amount, BigInt(asset.id), alith, address);

return registeredAssetId;
}

// Mock balance for old foreign assets
// DEPRECATED: Please don't use for new tests
export async function mockOldAssetBalance(
Expand Down
30 changes: 30 additions & 0 deletions test/helpers/xcm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,36 @@ export class XcmFragment {
return this;
}

// Add a `DepositAsset` instruction for specific beneficiary and token
deposit_asset_definite(
location: any,
amount: bigint,
beneficiary: `0x${string}`,
network: XcmV3JunctionNetworkId["type"] | null = null
): this {
this.instructions.push({
DepositAsset: {
assets: {
Definite: [
{
id: {
Concrete: location,
},
fun: {
Fungible: amount,
},
},
],
},
beneficiary: {
parents: 0,
interior: { X1: { AccountKey20: { network, key: beneficiary } } },
},
},
});
return this;
}

// Add a `SetErrorHandler` instruction, appending all the nested instructions
set_error_handler_with(callbacks: XcmCallback[]): this {
const error_instructions: any[] = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
RELAY_SOURCE_LOCATION_V4,
foreignAssetBalance,
mockAssetBalance,
registerForeignAsset,
relayAssetMetadata,
} from "../../../../helpers";

describeSuite({
Expand All @@ -22,7 +24,10 @@ describeSuite({
const assetLocation = RELAY_SOURCE_LOCATION_V4;
const assetId = ARBITRARY_ASSET_ID;

await mockAssetBalance(context, someBalance, assetId, assetLocation, alith, ALITH_ADDRESS);
// Register the asset
await registerForeignAsset(context, assetId, assetLocation, relayAssetMetadata);
// Mock asset balance
await mockAssetBalance(context, someBalance, assetId, alith, ALITH_ADDRESS);

const newBalance = await foreignAssetBalance(context, assetId, ALITH_ADDRESS);
expect(newBalance).toBe(someBalance);
Expand Down
Loading
Loading