-
Notifications
You must be signed in to change notification settings - Fork 499
feat: update to polkadot release stable2412
#1463
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
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great so far. Thanks
ipapandinas
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this deprecated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch, left over from copy-paste while writing the migration.
I will remove it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
forgot to push the commit, done
Dinonard
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, great work!
Want to check a few more things, but as it stands now, looks good!
Dinonard
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
ipapandinas
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
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