feat: update to polkadot release stable2412#1463
Conversation
|
/bench astar-dev,shibuya-dev,shiden-dev all |
|
Invalid runtime. It should be 'shibuya', 'shiden', or 'astar'. |
|
Runtime upgrade test is scheduled at https://github.com/AstarNetwork/Astar/actions/runs/14821411304. |
|
Runtime upgrade test is scheduled at https://github.com/AstarNetwork/Astar/actions/runs/14821412390. |
|
/runtime-upgrade-test shibuya |
|
Runtime upgrade test is scheduled at https://github.com/AstarNetwork/Astar/actions/runs/14821429515. |
|
Runtime upgrade test finished: yarn run v1.22.22 RUN v2.1.9 /home/runner/work/Astar/Astar/tests/e2e ✓ tests/runtime-upgrade.test.ts (1 test) 81077ms Test Files 1 passed (1) Done in 86.81s. |
|
@PierreOssun For XCM, better check our XCM precompile. Just spawn zombienet and run the script and it will take care of encoding, etc Zombienet config [relaychain]
default_command = "./polkadot"
default_args = [
"--no-hardware-benchmarks",
"-l=parachain=debug,xcm=trace",
"--database=paritydb",
]
chain = "rococo-local"
[[relaychain.nodes]]
name = "alice"
validator = true
rpc_port = 9500
[[relaychain.nodes]]
name = "bob"
validator = true
[[relaychain.nodes]]
name = "charlie"
validator = true
[[parachains]]
id = 2000
chain = "shibuya-dev"
cumulus_based = true
[[parachains.collators]]
name = "shibuya1"
command = "./astar-1500"
rpc_port = 8545
args = [
"--enable-evm-rpc",
"--pool-type=fork-aware",
"-l=xcm=trace",
"-lbasic-authorship=debug",
"-ltxpool=debug",
"-lxcm-precompile:assets_withdraw=trace",
"-lruntime::xc-asset-config=trace",
]
[[parachains.collators]]
name = "shibuya2"
command = "./astar-1500"
rpc_port = 8546
args = [
"--enable-evm-rpc",
"--pool-type=single-state",
"-l=xcm=trace",
"-lbasic-authorship=debug",
"-ltxpool=debug",
"-lxcm-precompile:assets_withdraw=trace",
"-lruntime::xc-asset-config=trace",
]
[[parachains.collators]]
name = "shibuya3"
command = "./astar-1500"
args = [
"--enable-evm-rpc",
"--pool-type=fork-aware",
"-l=xcm=trace",
"-lbasic-authorship=debug",
"-ltxpool=debug",
"-lxcm-precompile:assets_withdraw=trace",
"-lruntime::xc-asset-config=trace",
]
# For this one you can download or build some other para and run it.
# In this example, `astar-collator` is reused but `shiden-dev` chain is used
[[parachains]]
id = 3000
chain = "shiden-dev"
cumulus_based = true
[[parachains.collators]]
name = "shiden1"
command = "./astar-1500"
rpc_port = 9545
args = [
"--enable-evm-rpc",
"--pool-type=fork-aware",
"-l=xcm=trace",
"-lbasic-authorship=debug",
"-ltxpool=debug",
"-lxcm-precompile:assets_withdraw=trace",
"-lruntime::xc-asset-config=trace",
]
[[parachains.collators]]
name = "shiden2"
command = "./astar-1500"
args = [
"--enable-evm-rpc",
"--pool-type=fork-aware",
"-l=xcm=trace",
"-lbasic-authorship=debug",
"-ltxpool=debug",
"-lxcm-precompile:assets_withdraw=trace",
"-lruntime::xc-asset-config=trace",
]
[[parachains.collators]]
name = "shiden3"
command = "./astar-1500"
args = [
"--enable-evm-rpc",
"--pool-type=fork-aware",
"-l=xcm=trace",
"-lbasic-authorship=debug",
"-ltxpool=debug",
"-lxcm-precompile:assets_withdraw=trace",
"-lruntime::xc-asset-config=trace",
]
[settings]
timeout = 1000
Script to test precompile const { ApiPromise, WsProvider, Keyring } = require('@polkadot/api');
const { ethers, assert } = require('ethers');
const { u8aToHex, hexToU8a, isHex, bnToHex } = require('@polkadot/util');
const { decodeAddress, encodeAddress } = require('@polkadot/keyring');
const { blake2AsU8a, evmToAddress, cryptoWaitReady } = require('@polkadot/util-crypto');
const { Metadata, TypeRegistry } = require('@polkadot/types');
const XCM_V2_ABI = [
"struct Multilocation { uint8 parents; bytes[] interior }",
"struct WeightV2 { uint64 ref_time; uint64 proof_size }",
"struct MultiAsset { tuple(uint8 parents, bytes[] interior) location; uint256 amount }",
"struct Currency { address currencyAddress; uint256 amount }",
"function transfer(address currencyAddress, uint256 amount, tuple(uint8 parents, bytes[] interior) memory destination, tuple(uint64 ref_time, uint64 proof_size) memory weight) external returns (bool)",
"function transfer_with_fee(address currencyAddress, uint256 amount, uint256 fee, tuple(uint8 parents, bytes[] interior) memory destination, tuple(uint64 ref_time, uint64 proof_size) memory weight) external returns (bool)",
"function transfer_multiasset(tuple(uint8 parents, bytes[] interior) memory asset, uint256 amount, tuple(uint8 parents, bytes[] interior) memory destination, tuple(uint64 ref_time, uint64 proof_size) memory weight) external returns (bool)",
"function transfer_multiasset_with_fee(tuple(uint8 parents, bytes[] interior) memory asset, uint256 amount, uint256 fee, tuple(uint8 parents, bytes[] interior) memory destination, tuple(uint64 ref_time, uint64 proof_size) memory weight) external returns (bool)",
"function transfer_multi_currencies(tuple(address currencyAddress, uint256 amount)[] memory currencies, uint32 feeItem, tuple(uint8 parents, bytes[] interior) memory destination, tuple(uint64 ref_time, uint64 proof_size) memory weight) external returns (bool)",
"function transfer_multi_assets(tuple(tuple(uint8 parents, bytes[] interior) location, uint256 amount)[] memory assets, uint32 feeItem, tuple(uint8 parents, bytes[] interior) memory destination, tuple(uint64 ref_time, uint64 proof_size) memory weight) external returns (bool)",
"function send_xcm(tuple(uint8 parents, bytes[] interior) memory destination, bytes memory xcm_call) external returns (bool)"
];
const XCM_ABI = [
"function assets_withdraw(address[] calldata asset_id, uint256[] calldata asset_amount, bytes32 recipient_account_id, bool is_relay, uint256 parachain_id, uint256 fee_index) external returns (bool)",
"function assets_withdraw(address[] calldata asset_id, uint256[] calldata asset_amount, address recipient_account_id, bool is_relay, uint256 parachain_id, uint256 fee_index) external returns (bool)",
"function remote_transact(uint256 parachain_id, bool is_relay, address payment_asset_id, uint256 payment_amount, bytes calldata call, uint64 transact_weight) external returns (bool)",
"function assets_reserve_transfer(address[] calldata asset_id, uint256[] calldata asset_amount, bytes32 recipient_account_id, bool is_relay, uint256 parachain_id, uint256 fee_index) external returns (bool)",
"function assets_reserve_transfer(address[] calldata asset_id, uint256[] calldata asset_amount, address recipient_account_id, bool is_relay, uint256 parachain_id, uint256 fee_index) external returns (bool)"
];
// --- Configuration ---
const RELAY_WS = 'ws://127.0.0.1:9500';
const PARA1_WS = 'ws://127.0.0.1:8545';
const PARA2_WS = 'ws://127.0.0.1:9545';
const PARA1_EVM_RPC = 'ws://127.0.0.1:8545';
const PARA2_EVM_RPC = 'ws://127.0.0.1:9545';
const XCM_V2_PRECOMPILE_ADDR = '0x0000000000000000000000000000000000005004';
// Parachain IDs
const PARA1_ID = 2000;
const PARA2_ID = 3000;
// Asset IDs ]
const PARA1_ASSET_ID = 20010;
const PARA2_ASSET_ID = 30010;
const PARA1_NATIVE_ASSET_ID_ON_PARA2 = 23001;
const PARA2_NATIVE_ASSET_ID_ON_PARA1 = 32001;
const PARA1_ASSET_ID_ON_PARA2 = 23011;
const PARA2_ASSET_ID_ON_PARA1 = 32011;
const DOT_ASSET_ID = 10003;
const SUDO_KEY = '//Alice';
// Public PKs
const ALICE_EVM_PRIVKEY = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';
const BOB_EVM_PRIVKEY = '0x79c3b7fc0b7697b9414cb87adcb37317d1cab32818ae18c0e97ad76395d1fdcf';
const ASTAR_SS58_PREFIX = 5;
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function submitTx(tx, signer, api, label) {
const txHash = tx.hash.toHex();
console.log(`Submitting transaction: ${label}, Hash: ${txHash}`);
const nonce = await api.rpc.system.accountNextIndex(signer.address);
console.log(` [${label}] Using nonce: ${nonce}, Hash: ${txHash}`);
return new Promise((resolve, reject) => {
tx.signAndSend(signer, { nonce }, ({ status, events = [], dispatchError }) => {
console.log(` [${label}] Status: ${status.type}, Hash: ${txHash}`);
if (status.isInBlock) {
console.log(` [${label}] Included in block: ${status.asInBlock.toHex()}, Hash: ${txHash}`);
} else if (status.isFinalized) {
console.log(` [${label}] Finalized in block: ${status.asFinalized.toHex()}, Hash: ${txHash}`);
if (dispatchError) {
if (dispatchError.isModule) {
const decoded = api.registry.findMetaError(dispatchError.asModule);
const { docs, name, section } = decoded;
const errorMsg = `[${label}] Error: ${section}.${name}: ${docs.join(' ')}, Hash: ${txHash}`;
console.error(errorMsg);
reject(new Error(errorMsg));
} else {
const errorMsg = `[${label}] Error: ${dispatchError.toString()}, Hash: ${txHash}`;
console.error(errorMsg);
reject(new Error(errorMsg));
}
} else {
// Check for success event
const successEvent = events.find(({ event }) => api.events.system.ExtrinsicSuccess.is(event));
if (successEvent) {
console.log(` [${label}] Success. Hash: ${txHash}`);
resolve({ blockHash: status.asFinalized.toHex(), txHash });
} else {
// Check for specific failure events if needed
const failEvent = events.find(({ event }) => api.events.system.ExtrinsicFailed.is(event));
if (failEvent) {
const dispatchErrorFromEvent = failEvent.event.data[0];
if (dispatchErrorFromEvent.isModule) {
const decoded = api.registry.findMetaError(dispatchErrorFromEvent.asModule);
const { docs, name, section } = decoded;
const errorMsg = `[${label}] Failed (ExtrinsicFailed): ${section}.${name}: ${docs.join(' ')}, Hash: ${txHash}`;
console.error(errorMsg);
reject(new Error(errorMsg));
} else {
const errorMsg = `[${label}] Failed (ExtrinsicFailed): ${dispatchErrorFromEvent.toString()}, Hash: ${txHash}`;
console.error(errorMsg);
reject(new Error(errorMsg));
}
} else {
const errorMsg = `[${label}] Failed (No ExtrinsicSuccess / ExtrinsicFailed event found in finalized block). Hash: ${txHash}`;
console.error(errorMsg);
reject(new Error(errorMsg));
}
}
}
} else if (status.isInvalid || status.isDropped || status.isUsurped || status.isRetracted) {
const errorMsg = `[${label}] Transaction status error: ${status.type}. Hash: ${txHash}`;
console.log(errorMsg);
reject(new Error(errorMsg));
}
}).catch(error => {
const errorMsg = `[${label}] SignAndSend error: ${error.toString()}, Hash: ${txHash}`;
console.error(errorMsg);
console.error(`[${label}] Full SignAndSend error object:`, error);
reject(new Error(errorMsg));
});
});
}
// Creates an asset using sudo
async function createAsset(api, sudoSigner, assetId, minBalance = 1) {
console.log(`Checking if asset ${assetId} exists on ${api.runtimeChain.toString()}...`);
const existingAsset = await api.query.assets.asset(assetId);
if (existingAsset.isSome) {
console.log(`Asset ${assetId} already exists on ${api.runtimeChain.toString()}. Owner: ${existingAsset.unwrap().owner}. Skipping creation.`);
return;
}
console.log(`Asset ${assetId} does not exist. Creating asset ${assetId} on ${api.runtimeChain.toString()} with owner ${sudoSigner.address} and minBalance ${minBalance}...`);
const createTx = api.tx.sudo.sudo(
api.tx.assets.forceCreate(assetId, sudoSigner.address, true, minBalance)
);
await submitTx(createTx, sudoSigner, api, `Create Asset ${assetId}`);
console.log(`Asset ${assetId} creation initiated.`);
}
// Mints an asset using sudo
async function mintAsset(api, sudoSigner, assetId, recipientAccountId32, recipientEvmAddress, ethProvider, amount) {
let initialBalance = await getAssetBalance(api, recipientAccountId32, assetId);
if (BigInt(initialBalance) >= BigInt(amount)) {
console.log(`Initial balance of ${recipientAccountId32} is ${initialBalance}. Skipping minting.`);
return;
}
const recipientSs58 = encodeAddress(recipientAccountId32, ASTAR_SS58_PREFIX);
console.log(`Minting ${amount} of asset ${assetId} to EVM-derived SS58: ${recipientSs58} (AccountId: ${u8aToHex(recipientAccountId32)}, EVM: ${recipientEvmAddress}) on ${api.runtimeChain.toString()}...`);
const mintTx = api.tx.sudo.sudoAs(
sudoSigner.address,
api.tx.assets.mint(assetId, recipientAccountId32, amount)
);
await submitTx(mintTx, sudoSigner, api, `Mint Asset ${assetId} to ${recipientSs58.substring(0, 10)}...`);
let balance = await getAssetBalance_erc20Precompile(assetId, recipientEvmAddress, ethProvider);
assert(balance >= amount, `Native Minting failed. Expected more than ${amount} on EVM address ${recipientEvmAddress}. Found ${balance}.`);
}
// Mints an asset using sudo
async function mintNative(api, sudoSigner, recipientAccountId32, recipientEvmAddress, ethProvider, amount) {
let initialBalance = await getNativeBalance_evmRpc(recipientEvmAddress, ethProvider);
if (BigInt(initialBalance) + BigInt(1000000) >= BigInt(amount)) {
console.log(`Initial balance of ${recipientEvmAddress} is ${initialBalance}. Skipping minting.`);
return;
}
const recipientSs58 = encodeAddress(recipientAccountId32, ASTAR_SS58_PREFIX);
console.log(`Minting ${amount} of native to EVM-derived SS58: ${recipientSs58} (AccountId: ${u8aToHex(recipientAccountId32)}, EVM: ${recipientEvmAddress}) on ${api.runtimeChain.toString()}...`);
const mintTx = api.tx.sudo.sudo(
api.tx.balances.forceSetBalance(recipientAccountId32, amount)
);
await submitTx(mintTx, sudoSigner, api, `Mint Native Asset to ${recipientSs58.substring(0, 10)}...`);
let balance = await getNativeBalance_evmRpc(recipientEvmAddress, ethProvider);
assert(balance + 1000000n >= amount, `Native Minting failed. Expected more than ${amount} on EVM address ${recipientEvmAddress}. Found ${balance}.`);
}
async function registerXcAsset(api, sudoSigner, assetId, locationObject, unitsPerSecond = 1) {
const chain = api.runtimeChain.toString();
console.log(`Registering asset location for ID ${assetId} on ${chain}...`);
const location = api.createType('VersionedMultiLocation', locationObject);
let query = (await api.query.xcAssetConfig.assetIdToLocation(assetId)).toHuman();
if (query !== null) {
console.log(`Asset ${assetId} already registered on ${chain} as ${JSON.stringify(query)}. Skipping registration.`);
return;
}
const registerTx = api.tx.sudo.sudo(
api.tx.xcAssetConfig.registerAssetLocation(location, assetId)
);
await submitTx(registerTx, sudoSigner, api, `Register Asset Location ${assetId} on ${chain}`);
console.log(`Setting units per second for asset ID ${assetId} on ${chain}...`);
const setUnitsTx = api.tx.sudo.sudo(
api.tx.xcAssetConfig.setAssetUnitsPerSecond(location, unitsPerSecond)
);
await submitTx(setUnitsTx, sudoSigner, api, `Set UnitsPerSecond ${assetId} on ${chain}`);
console.log(`Asset ${assetId} registered on ${chain}.`);
}
// Gets Substrate asset balance
async function getAssetBalance(api, accountId32, assetId) {
const accountSs58 = encodeAddress(accountId32, ASTAR_SS58_PREFIX);
try {
const balance = await api.query.assets.account(assetId, accountId32);
if (balance.isSome) {
return balance.unwrap().balance.toBigInt();
}
return 0n;
} catch (e) {
console.warn(`Could not get balance for asset ${assetId} for account ${accountSs58}: ${e}`);
return 0n;
}
}
// Based on frontier/precompiles/src/solidity/codec/xcm.rs
// very raw hacky
function encodeJunction(junction) {
let encoded = '0x';
if (junction.Parachain !== undefined) { // Index 0
encoded += '00';
// Encode u32 as hex, little-endian, padded to 4 bytes (8 hex chars)
const paraIdHex = junction.Parachain.toString(16).padStart(8, '0');
encoded += paraIdHex;
} else if (junction.AccountId32 !== undefined) { // Index 1
encoded += '01';
const accountBytes = isHex(junction.AccountId32.id) ? junction.AccountId32.id : u8aToHex(decodeAddress(junction.AccountId32.id), -1, false);
encoded += accountBytes.startsWith('0x') ? accountBytes.substring(2) : accountBytes; // 32 bytes id
encoded += '00'; // NetworkId::Any/None selector
} else if (junction.AccountKey20 !== undefined) { // Index 3
encoded += '03';
const keyBytes = isHex(junction.AccountKey20.key) ? junction.AccountKey20.key : junction.AccountKey20.key;
encoded += keyBytes.startsWith('0x') ? keyBytes.substring(2) : keyBytes; // 20 bytes key
encoded += '00'; // NetworkId::Any/None selector
} else if (junction.PalletInstance !== undefined) { // Index 4
encoded += '04';
encoded += junction.PalletInstance.toString(16).padStart(2, '0'); // u8 -> 1 byte
} else if (junction.GeneralIndex !== undefined) { // Index 5
encoded += '05';
const indexBigInt = BigInt(junction.GeneralIndex);
let indexHex = indexBigInt.toString(16).padStart(32, '0');
let littleEndianHex = '';
for (let i = 0; i < 16; i++) {
littleEndianHex += indexHex.substring(30 - 2 * i, 32 - 2 * i);
}
encoded += littleEndianHex;
}
else {
console.error("Unsupported Junction for encoding:", junction);
throw new Error(`Unsupported Junction type for encoding`);
}
console.log("Encoded Junction:", encoded, "from", junction); // Debug log
return encoded;
}
function encodeInterior(interior) {
if (!interior || interior === 'Here') {
return [];
}
// interior should be like { X1: [Junction] } or { X2: [Junction, Junction] }, etc.
const junctions = Object.values(interior)[0];
if (!Array.isArray(junctions)) {
throw new Error("Invalid interior format for encoding: " + JSON.stringify(interior));
}
return junctions.map(j => encodeJunction(j));
}
async function getHrmpChannel(relayApi, sender, recipient) {
console.log(`Checking HRMP channel from ${sender} to ${recipient} on ${relayApi.runtimeChain}...`);
const channel = await relayApi.query.hrmp.hrmpChannels([sender, recipient]);
if (channel.isSome) {
const details = channel.unwrap();
console.log(` Channel from ${sender} to ${recipient} exists. Max Capacity: ${details.maxCapacity}, Max Message Size: ${details.maxMessageSize}`);
return details;
}
console.log(` Channel from ${sender} to ${recipient} does not exist or is not open.`);
return null;
}
async function forceOpenHrmpChannel(relayApi, sudoSigner, sender, recipient, maxCapacity, maxMessageSize) {
console.log(`Forcibly opening HRMP channel from ${sender} to ${recipient} on ${relayApi.runtimeChain} with sudo...`);
const openTx = relayApi.tx.sudo.sudo(
relayApi.tx.hrmp.forceOpenHrmpChannel(sender, recipient, maxCapacity, maxMessageSize)
);
await submitTx(openTx, sudoSigner, relayApi, `Force Open HRMP ${sender}->${recipient}`);
console.log(`HRMP channel opening initiated for ${sender} -> ${recipient}. It might take some time to confirm.`);
}
async function ensureHrmpChannelsAreOpen(relayApi, sudoSigner) {
console.log("\n--- Ensuring HRMP Channels are Open ---");
const channelsToOpen = [
{ sender: PARA1_ID, recipient: PARA2_ID, maxCapacity: 8, maxMessageSize: 512 },
{ sender: PARA2_ID, recipient: PARA1_ID, maxCapacity: 8, maxMessageSize: 512 },
];
let hrmpActionTaken = false;
for (const ch of channelsToOpen) {
const existingChannel = await getHrmpChannel(relayApi, ch.sender, ch.recipient);
if (existingChannel &&
existingChannel.maxCapacity.toNumber() === ch.maxCapacity &&
existingChannel.maxMessageSize.toNumber() === ch.maxMessageSize) {
console.log(` Channel ${ch.sender}->${ch.recipient} already exists and is configured correctly. Skipping force open.`);
continue;
} else if (existingChannel) {
console.log(` Channel ${ch.sender}->${ch.recipient} exists but parameters differ (Current: Cap ${existingChannel.maxCapacity}, Size ${existingChannel.maxMessageSize}. Desired: Cap ${ch.maxCapacity}, Size ${ch.maxMessageSize}). Will attempt to force open.`);
} else {
console.log(` Channel ${ch.sender}->${ch.recipient} does not exist. Will attempt to force open.`);
}
await forceOpenHrmpChannel(relayApi, sudoSigner, ch.sender, ch.recipient, ch.maxCapacity, ch.maxMessageSize);
hrmpActionTaken = true;
}
if (hrmpActionTaken) {
console.log("Waiting for HRMP channels to be processed (e.g., 2 relay chain blocks ~12s)...");
await delay(15000);
console.log("Re-checking HRMP channel status after actions:");
for (const ch of channelsToOpen) {
await getHrmpChannel(relayApi, ch.sender, ch.recipient);
}
} else {
console.log("All HRMP channels were already configured correctly. No action taken.");
}
console.log("HRMP channel setup process completed.");
}
async function getNativeBalance_evmRpc(address, provider) {
if (!provider) {
console.error("Ethers.js provider is not available for getNativeBalance_evmRpc.");
throw new Error("Provider not set for getNativeBalance_evmRpc");
}
console.log(`Querying native balance for ${address} via EVM RPC...`);
const balance = await provider.getBalance(address);
console.log(`Native balance for ${address}: ${ethers.formatEther(balance)}`);
return balance;
}
function computeAssetPrecompileAddress(assetId) {
const assetIdBN = ethers.toBigInt(assetId);
const assetIdHex = bnToHex(assetIdBN);
const assetIdHexBytes = ethers.zeroPadValue(assetIdHex, 16).substring(2); // remove 0x prefix
const precompileAddress = `0xFFFFFFFF${assetIdHexBytes}`;
return ethers.getAddress(precompileAddress.toLowerCase());
}
async function getAssetBalance_erc20Precompile(assetId, targetAddress, provider) {
if (!provider) {
console.error("Ethers.js provider is not available for getAssetBalance_erc20Precompile.");
throw new Error("Provider not set for getAssetBalance_erc20Precompile");
}
const precompileAddress = computeAssetPrecompileAddress(assetId);
console.log(`Querying balance of asset ${assetId} for ${targetAddress} via ERC20 precompile at ${precompileAddress}...`);
const erc20Abi = [
"function balanceOf(address account) view returns (uint256)",
"function decimals() view returns (uint8)"
];
const tokenContract = new ethers.Contract(precompileAddress, erc20Abi, provider);
try {
const balance = await tokenContract.balanceOf(targetAddress);
let decimals = 18; // Default decimals
try {
decimals = await tokenContract.decimals();
} catch (decError) {
console.warn(`Could not fetch decimals for asset ${assetId} at ${precompileAddress}, defaulting to 18. Error: ${decError.message}`);
}
console.log(`Asset ${assetId} balance for ${targetAddress}: ${ethers.formatUnits(balance, decimals)} (raw: ${balance.toString()})`);
return balance;
} catch (error) {
console.error(`Error fetching balance for asset ${assetId} from ${precompileAddress} for account ${targetAddress}. Error: ${error.message}`);
return 0n;
}
}
// --- Main Script ---
async function main() {
await cryptoWaitReady();
console.log("Crypto WASM ready.");
// 1. Initialization
console.log("Connecting to chains...");
const keyring = new Keyring({ type: 'sr25519' });
const aliceSudo = keyring.addFromUri(SUDO_KEY); // Alice for Sudo operations
const relayApi = await ApiPromise.create({ provider: new WsProvider(RELAY_WS), noInitWarn: true, });
const para1Api = await ApiPromise.create({ provider: new WsProvider(PARA1_WS), noInitWarn: true });
const para2Api = await ApiPromise.create({ provider: new WsProvider(PARA2_WS), noInitWarn: true });
const para1Provider = new ethers.WebSocketProvider(PARA1_EVM_RPC);
const para2Provider = new ethers.WebSocketProvider(PARA2_EVM_RPC);
// Ensure EVM private keys are set correctly
if (!ALICE_EVM_PRIVKEY || !BOB_EVM_PRIVKEY) {
console.error("EVM private keys for Alice and Bob are not set in the script!");
process.exit(1);
}
const aliceSigner1 = new ethers.Wallet(ALICE_EVM_PRIVKEY, para1Provider);
const aliceSigner2 = new ethers.Wallet(ALICE_EVM_PRIVKEY, para2Provider);
const bobSigner1 = new ethers.Wallet(BOB_EVM_PRIVKEY, para1Provider);
const bobSigner2 = new ethers.Wallet(BOB_EVM_PRIVKEY, para2Provider);
const aliceEvmAddress = aliceSigner1.address;
const aliceSs58 = evmToAddress(aliceEvmAddress, ASTAR_SS58_PREFIX);
const aliceAccountId32 = decodeAddress(aliceSs58);
const bobEvmAddress = bobSigner1.address;
const bobSs58 = evmToAddress(bobEvmAddress, ASTAR_SS58_PREFIX);
const bobAccountId32 = decodeAddress(bobSs58);
console.log(`Alice Sudo Substrate Address: ${aliceSudo.address}`);
console.log(`Alice EVM Address: ${aliceEvmAddress}`);
console.log(`Alice Derived Substrate SS58: ${aliceSs58}`);
console.log(`Alice Derived Substrate AccountId32: ${u8aToHex(aliceAccountId32)}`);
console.log(`Bob EVM Address: ${bobEvmAddress}`);
console.log(`Bob Derived Substrate SS58: ${bobSs58}`);
console.log(`Bob Derived Substrate AccountId32: ${u8aToHex(bobAccountId32)}`);
await Promise.all([relayApi.isReady, para1Api.isReady, para2Api.isReady]);
console.log("Connections established.");
console.log(`Relay: ${relayApi.runtimeChain} | Para1: ${para1Api.runtimeChain} | Para2: ${para2Api.runtimeChain}`);
// Ensure HRMP channels are open before proceeding
await ensureHrmpChannelsAreOpen(relayApi, aliceSudo);
// 2. Asset Setup
console.log("\n--- Asset Setup ---");
try {
// Mint initial balances (e.g., to Alice and Bob derived accounts)
const initialMintAmount = 1_000_000_000_000_000_000_000_000_000n;
// fund alice
await Promise.all([
mintNative(para1Api, aliceSudo, aliceAccountId32, aliceSigner1.address, para1Provider, initialMintAmount),
mintNative(para2Api, aliceSudo, aliceAccountId32, aliceSigner1.address, para2Provider, initialMintAmount),
]);
// fund bob
await Promise.all([
mintNative(para1Api, aliceSudo, bobAccountId32, bobSigner1.address, para1Provider, initialMintAmount),
mintNative(para2Api, aliceSudo, bobAccountId32, bobSigner1.address, para2Provider, initialMintAmount),
]);
// create assets in para
await Promise.all([
createAsset(para1Api, aliceSudo, PARA1_ASSET_ID),
createAsset(para2Api, aliceSudo, PARA2_ASSET_ID),
]);
// create placeholder forgein native assets in paras
await Promise.all([
createAsset(para1Api, aliceSudo, PARA2_NATIVE_ASSET_ID_ON_PARA1),
createAsset(para2Api, aliceSudo, PARA1_NATIVE_ASSET_ID_ON_PARA2),
]);
// create placeholder forgein native assets in paras
await Promise.all([
createAsset(para1Api, aliceSudo, PARA2_ASSET_ID_ON_PARA1),
createAsset(para2Api, aliceSudo, PARA1_ASSET_ID_ON_PARA2),
]);
// mint assets for alice
await Promise.all([
mintAsset(para1Api, aliceSudo, PARA1_ASSET_ID, aliceAccountId32, aliceSigner1.address, para1Provider, initialMintAmount),
mintAsset(para2Api, aliceSudo, PARA2_ASSET_ID, aliceAccountId32, aliceSigner2.address, para2Provider, initialMintAmount),
]);
// mint assets for bob
await Promise.all([
mintAsset(para1Api, aliceSudo, PARA1_ASSET_ID, bobAccountId32, bobSigner1.address, para1Provider, initialMintAmount),
mintAsset(para2Api, aliceSudo, PARA2_ASSET_ID, bobAccountId32, bobSigner2.address, para2Provider, initialMintAmount),
]);
console.log("Native assets created and minted.");
} catch (error) {
console.error("Error during asset setup:", error);
process.exit(1);
}
// 3. Asset Registration (xcAssetConfig)
console.log("\n--- Asset Registration (xcAssetConfig) ---");
// For some unknow reasons js api does not like V5
const dotLocation = { V4: { parents: 1, interior: 'Here' } }; // DOT on Relay
const para1Location = { V4: { parents: 1, interior: { X1: [{ Parachain: PARA1_ID }] } } }; // PARA1 native on Para1
const para2Location = { V4: { parents: 1, interior: { X1: [{ Parachain: PARA2_ID }] } } }; // PARA2 native on Para2
try {
// Register relay on Parachains
await Promise.all([
registerXcAsset(para1Api, aliceSudo, DOT_ASSET_ID, dotLocation),
registerXcAsset(para2Api, aliceSudo, DOT_ASSET_ID, dotLocation),
]);
// Register para assets on Parachain
await Promise.all([
registerXcAsset(para2Api, aliceSudo, PARA1_NATIVE_ASSET_ID_ON_PARA2, para1Location),
registerXcAsset(para1Api, aliceSudo, PARA2_NATIVE_ASSET_ID_ON_PARA1, para2Location)
]);
console.log("Foreign assets registered.");
} catch (error) {
console.error("Error during asset registration:", error);
process.exit(1);
}
// 4. Precompile Testing
console.log("\n--- Testing XCM Precompiles ---");
// Instantiate Precompile Contracts
// Use Bob for transfers
const xcmV2Contract1 = new ethers.Contract(XCM_V2_PRECOMPILE_ADDR, XCM_V2_ABI, bobSigner1);
// --- Test Case 1: XCM_V2.transfer_multiasset (PARA1 from Para1 to Para2) ---
console.log("\nTest Case 1: Transfer PARA1 from Para1 to Para2 (Bob -> Bob)");
const transferAmount = 100_000_000_000n;
// Get initial balances
const bobPara1BalanceBefore = await getNativeBalance_evmRpc(bobSigner1.address, para1Provider) + BigInt(1000000); // extra added for ED
const bobPara2BalanceBefore = await getAssetBalance(para2Api, bobAccountId32, PARA1_NATIVE_ASSET_ID_ON_PARA2); // Foreign asset balance
console.log(`Bob's initial PARA1 balance on Para1 (SS58: ${bobSs58}): ${bobPara1BalanceBefore}`);
console.log(`Bob's initial PARA1 balance on Para2 (SS58: ${bobSs58}): ${bobPara2BalanceBefore}`);
const assetLocationPara1ForTransfer = {
parents: 0,
interior: encodeInterior('Here')
};
const destinationPara2Bob = {
parents: 1,
interior: encodeInterior({
X2: [
{ Parachain: PARA2_ID },
{ AccountId32: { network: null, id: u8aToHex(bobAccountId32) } }
]
})
};
const weightLimit = { ref_time: 1_000_000_000n, proof_size: 65536n };
try {
console.log("Executing transfer_multiasset via precompile...");
console.log("Asset Location:", JSON.stringify(assetLocationPara1ForTransfer));
console.log("Destination:", JSON.stringify(destinationPara2Bob));
console.log("Amount:", transferAmount.toString());
console.log("Weight:", weightLimit);
const tx = await xcmV2Contract1.transfer_multiasset(
assetLocationPara1ForTransfer,
transferAmount,
destinationPara2Bob,
weightLimit,
{ gasLimit: 3000000 }
);
console.log(`Transaction hash on Para1: ${tx.hash}`);
const receipt = await tx.wait();
console.log(`Transaction confirmed on Para1. Gas used: ${receipt.gasUsed.toString()}`);
// Wait for XCM execution on Para2
console.log("Waiting for XCM execution on Para2 (approx 15-30s)...");
await delay(30000); // Wait 30 seconds
// Verify balances
const bobPara1BalanceAfter = await getNativeBalance_evmRpc(bobSigner1.address, para1Provider) + BigInt(1000000);
const bobPara2BalanceAfter = await getAssetBalance(para2Api, bobAccountId32, PARA1_NATIVE_ASSET_ID_ON_PARA2);
console.log(`Bob's final PARA1 balance on Para1 (SS58: ${bobSs58}): ${bobPara1BalanceAfter}`);
console.log(`Bob's final PARA1 balance on Para2 (SS58: ${bobSs58}): ${bobPara2BalanceAfter}`);
// --- Verification ---
const expectedPara1Balance = bobPara1BalanceBefore - transferAmount;
const expectedPara2Balance = bobPara2BalanceBefore + transferAmount;
if (bobPara1BalanceAfter < expectedPara1Balance) {
console.log("✅ Para1 balance check PASSED");
} else {
console.error(`❌ Para1 balance check FAILED. Expected: ${expectedPara1Balance} to be less than ${bobPara1BalanceBefore} Got: ${bobPara1BalanceAfter}`);
}
if (bobPara2BalanceAfter === expectedPara2Balance) {
console.log("✅ Para2 balance check PASSED");
} else {
console.error(`❌ Para2 balance check FAILED. Expected: ${expectedPara2Balance}, Got: ${bobPara2BalanceAfter}`);
console.log(" (Note: XCM execution might take longer or have failed silently on destination)");
}
} catch (error) {
console.error("Error during transfer_multiasset test:", error);
// Investigate error - could be gas issues, encoding problems, precompile revert, etc.
}
// 5. Disconnect
console.log("\nDisconnecting...");
await relayApi.disconnect();
await para1Api.disconnect();
await para2Api.disconnect();
console.log("Done.");
}
main().catch(error => {
console.error("Script failed:", error);
process.exit(1);
}); |
PierreOssun
left a comment
There was a problem hiding this comment.
Great so far. Thanks
ipapandinas
left a comment
There was a problem hiding this comment.
Big work with a good summary explaining the changes, kudos!
LGTM, I just have minor questions
| impl<const XCM_VERSION: u32, T: Config> UncheckedOnRuntimeUpgrade | ||
| for UncheckedMigrationXcmVersion<XCM_VERSION, T> | ||
| { | ||
| #[allow(deprecated)] |
There was a problem hiding this comment.
Why is this deprecated?
There was a problem hiding this comment.
good catch, left over from copy-paste while writing the migration.
I will remove it
There was a problem hiding this comment.
forgot to push the commit, done
Dinonard
left a comment
There was a problem hiding this comment.
Nice, great work!
Want to check a few more things, but as it stands now, looks good!
Minimum allowed line rate is |
Fixes: #1405 #1404
(Also partially address #982)
Pull Request Summary
Uplifts dependencies from
polkadot-stable2407topolkadot-2412-4. The release diff includes following releasespolkadot-2412-4polkadot-2412-3polkadot-2412-2polkadot-2412-1polkadot-2412polkadot-2409-2polkadot-2409-1polkadot-2409This uplift pin all the deps to tag
polkadot-stable2412-4Client
--pool-typeargBasicPool, thus needed change as wellfork-awaretxpool polkadot-evm/frontier#1653SelectCore- elastic scaling: add core selector to cumulus paritytech/polkadot-sdk#5372Runtime
XCMv5
require_weight_at_mostfrom transact, new InitiateTransfer instr,incompatible_versioned_multilocations_are_not_ok, as v2 is removed and all xcm v3/4 locations can be converted to v5 (and vice versa)new improved extrinsic version v5 - FRAME: Reintroduce
TransactionExtensionas a replacement forSignedExtensionparitytech/polkadot-sdk#3685pallet-transaction-paymentand all extensionsUpdate
RuntimeVerisontype and usesystem_versionto derive extrinsics rootStateVersioninstead ofV0paritytech/polkadot-sdk#4257New XCM runtime api to query if location is trusted reserve or teleporter for asset- Added Trusted Query API calls paritytech/polkadot-sdk#6039
Click here to check the API trait
[Identity] Decouple usernames from identities paritytech/polkadot-sdk#5554⚠️ Migration
new
transfer_allextrinsic inpallet_assets- [Assets] Call implementation fortransfer_allparitytech/polkadot-sdk#4527Frontier
SELFDESTRUCTop code, thuspallet-evm::Suicidedstorage item has been removedGasLimitStorageGrowthRatioconfig inpallet_evmblockHash: Will now be null since the transaction has not been added to any block yet. Previously0x0000000000000000000000000000000000000000000000000000000000000000was returned.to: The address of the receiver. Now null when its a contract creation transaction. Previously0x0000000000000000000000000000000000000000was returned.AccountProviderconfig - Support external account provider polkadot-evm/frontier#1329Migrations
pallet_identity::migration::v2::LazyMigrationV1ToV2<Runtime>([Identity] Decouple usernames from identities paritytech/polkadot-sdk#5554)pallet_xc_asset_config::migrations::versioned::V3ToV4<Runtime>- for XCMv5Final TODO
asset-registry- Investigate and replacexc-asset-configpallet with ormlasset-registry#1468Check list