diff --git a/operator/Cargo.lock b/operator/Cargo.lock index f1b38094..97906260 100644 --- a/operator/Cargo.lock +++ b/operator/Cargo.lock @@ -3171,6 +3171,7 @@ dependencies = [ "pallet-authorship", "pallet-balances", "pallet-evm", + "pallet-evm-chain-id", "pallet-evm-precompile-proxy", "pallet-migrations", "pallet-safe-mode", diff --git a/operator/node/src/chain_spec/mainnet.rs b/operator/node/src/chain_spec/mainnet.rs index 9e1949fd..058d412c 100644 --- a/operator/node/src/chain_spec/mainnet.rs +++ b/operator/node/src/chain_spec/mainnet.rs @@ -3,7 +3,7 @@ use sc_service::ChainType; use super::ChainSpec; -const EVM_CHAIN_ID: u64 = 1289; +const EVM_CHAIN_ID: u64 = 55930; const SS58_FORMAT: u16 = EVM_CHAIN_ID as u16; const TOKEN_DECIMALS: u8 = 18; const TOKEN_SYMBOL: &str = "HAVE"; diff --git a/operator/node/src/chain_spec/stagenet.rs b/operator/node/src/chain_spec/stagenet.rs index 87ae6b0e..a456b2b9 100644 --- a/operator/node/src/chain_spec/stagenet.rs +++ b/operator/node/src/chain_spec/stagenet.rs @@ -3,10 +3,10 @@ use sc_service::ChainType; use super::ChainSpec; -const EVM_CHAIN_ID: u64 = 1283; +const EVM_CHAIN_ID: u64 = 55932; const SS58_FORMAT: u16 = EVM_CHAIN_ID as u16; const TOKEN_DECIMALS: u8 = 18; -const TOKEN_SYMBOL: &str = "HAVE"; +const TOKEN_SYMBOL: &str = "STAGE"; pub fn development_chain_spec() -> Result { // Give the token a unit name and decimal places diff --git a/operator/node/src/chain_spec/testnet.rs b/operator/node/src/chain_spec/testnet.rs index 92c371dc..d0c4b352 100644 --- a/operator/node/src/chain_spec/testnet.rs +++ b/operator/node/src/chain_spec/testnet.rs @@ -3,10 +3,10 @@ use sc_service::ChainType; use super::ChainSpec; -const EVM_CHAIN_ID: u64 = 1288; +const EVM_CHAIN_ID: u64 = 55931; const SS58_FORMAT: u16 = EVM_CHAIN_ID as u16; const TOKEN_DECIMALS: u8 = 18; -const TOKEN_SYMBOL: &str = "HAVE"; +const TOKEN_SYMBOL: &str = "MOCK"; pub fn development_chain_spec() -> Result { // Give the token a unit name and decimal places diff --git a/operator/runtime/common/Cargo.toml b/operator/runtime/common/Cargo.toml index b9f3268d..8c72f790 100644 --- a/operator/runtime/common/Cargo.toml +++ b/operator/runtime/common/Cargo.toml @@ -15,6 +15,7 @@ pallet-authorship = { workspace = true } pallet-balances = { workspace = true } pallet-timestamp = { workspace = true } pallet-evm = { workspace = true } +pallet-evm-chain-id = { workspace = true } pallet-evm-precompile-proxy = { workspace = true } pallet-migrations = { workspace = true } pallet-safe-mode = { workspace = true } @@ -40,6 +41,7 @@ std = [ "pallet-balances/std", "pallet-timestamp/std", "pallet-evm/std", + "pallet-evm-chain-id/std", "pallet-evm-precompile-proxy/std", "pallet-migrations/std", "pallet-safe-mode/std", diff --git a/operator/runtime/common/src/migrations.rs b/operator/runtime/common/src/migrations.rs index 45b3c238..c1695952 100644 --- a/operator/runtime/common/src/migrations.rs +++ b/operator/runtime/common/src/migrations.rs @@ -32,6 +32,101 @@ pub type MigrationStatusHandler = (); /// Default handler triggered on migration failures. pub type FailedMigrationHandler = frame_support::migrations::FreezeChainOnFailedMigration; +/// Multi-block migration for updating the EVM chain ID to the new value. +pub mod evm_chain_id { + use core::marker::PhantomData; + use frame_support::{ + migrations::{MigrationId, SteppedMigration, SteppedMigrationError}, + pallet_prelude::*, + weights::WeightMeter, + }; + + #[cfg(feature = "try-runtime")] + use codec::Encode; + + /// Multi-block migration that updates the stored EVM chain ID to match the new configuration. + pub struct EvmChainIdMigration(PhantomData); + + impl SteppedMigration for EvmChainIdMigration + where + T: pallet_evm_chain_id::Config, + { + type Cursor = (); + type Identifier = MigrationId<20>; + + fn id() -> Self::Identifier { + MigrationId { + pallet_id: *b"dh-evm-chain-id-v1 ", + version_from: 0, + version_to: 1, + } + } + + fn step( + cursor: Option, + meter: &mut WeightMeter, + ) -> Result, SteppedMigrationError> { + // This migration completes in a single step + if cursor.is_some() { + return Ok(None); + } + + let required = T::DbWeight::get().reads_writes(1, 1); + if meter.try_consume(required).is_err() { + return Err(SteppedMigrationError::InsufficientWeight { required }); + } + + log::info!( + "🔄 [EVM Chain ID Migration] Updating chain ID to {}", + NEW_CHAIN_ID + ); + + // Update the chain ID storage + pallet_evm_chain_id::ChainId::::put(NEW_CHAIN_ID); + + log::info!( + "✅ [EVM Chain ID Migration] Successfully updated chain ID to {}", + NEW_CHAIN_ID + ); + + Ok(None) + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + let old_chain_id = pallet_evm_chain_id::ChainId::::get(); + log::info!( + "📋 [EVM Chain ID Migration] Current chain ID: {}", + old_chain_id + ); + Ok(old_chain_id.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + use codec::Decode; + + let old_chain_id = + u64::decode(&mut &state[..]).map_err(|_| "Failed to decode old chain ID")?; + let new_chain_id = pallet_evm_chain_id::ChainId::::get(); + + log::info!( + "🔍 [EVM Chain ID Migration] Chain ID updated from {} to {}", + old_chain_id, + new_chain_id + ); + + if new_chain_id != NEW_CHAIN_ID { + return Err(sp_runtime::TryRuntimeError::Other( + "Chain ID was not updated correctly", + )); + } + + Ok(()) + } + } +} + /// Multi-block migration for renaming the EVM pallet alias. pub mod evm_alias { use core::marker::PhantomData; @@ -232,6 +327,7 @@ mod tests { Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Timestamp: pallet_timestamp::{Pallet, Call, Storage}, EVM: pallet_evm::{Pallet, Call, Storage, Config, Event}, + EvmChainId: pallet_evm_chain_id::{Pallet, Storage}, } ); @@ -280,6 +376,8 @@ mod tests { type PrecompilesValue = MockPrecompiles; } + impl pallet_evm_chain_id::Config for TestRuntime {} + pub struct FixedGasPrice; impl pallet_evm::FeeCalculator for FixedGasPrice { fn min_gas_price() -> (U256, Weight) { @@ -375,4 +473,77 @@ mod tests { assert_eq!(count_keys(&old_storage_prefix(b"AccountStorages")[..]), 0); }); } + + #[test] + fn evm_chain_id_migration_updates_storage() { + use super::evm_chain_id::EvmChainIdMigration; + + let mut storage = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + pallet_balances::GenesisConfig::::default() + .assimilate_storage(&mut storage) + .unwrap(); + let mut ext = sp_io::TestExternalities::new(storage); + + ext.execute_with(|| { + // Set an old chain ID value + const OLD_CHAIN_ID: u64 = 12345; + const NEW_CHAIN_ID: u64 = 55931; + + pallet_evm_chain_id::ChainId::::put(OLD_CHAIN_ID); + assert_eq!( + pallet_evm_chain_id::ChainId::::get(), + OLD_CHAIN_ID + ); + + // Run the migration + let mut meter = WeightMeter::with_limit(Weight::MAX); + let result = EvmChainIdMigration::::step(None, &mut meter); + + // Verify migration succeeded and completed in one step + assert!(result.is_ok()); + assert_eq!(result.unwrap(), None); + + // Verify the chain ID was updated + assert_eq!( + pallet_evm_chain_id::ChainId::::get(), + NEW_CHAIN_ID + ); + }); + } + + #[test] + fn evm_chain_id_migration_is_idempotent() { + use super::evm_chain_id::EvmChainIdMigration; + + let mut storage = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + pallet_balances::GenesisConfig::::default() + .assimilate_storage(&mut storage) + .unwrap(); + let mut ext = sp_io::TestExternalities::new(storage); + + ext.execute_with(|| { + const NEW_CHAIN_ID: u64 = 55932; + + // Run the migration twice + let mut meter = WeightMeter::with_limit(Weight::MAX); + let result1 = EvmChainIdMigration::::step(None, &mut meter); + + let mut meter = WeightMeter::with_limit(Weight::MAX); + let result2 = EvmChainIdMigration::::step(None, &mut meter); + + // Both should succeed + assert!(result1.is_ok()); + assert!(result2.is_ok()); + + // Chain ID should be set correctly + assert_eq!( + pallet_evm_chain_id::ChainId::::get(), + NEW_CHAIN_ID + ); + }); + } } diff --git a/operator/runtime/mainnet/src/configs/mod.rs b/operator/runtime/mainnet/src/configs/mod.rs index 805552a1..7f3ba6c1 100644 --- a/operator/runtime/mainnet/src/configs/mod.rs +++ b/operator/runtime/mainnet/src/configs/mod.rs @@ -164,7 +164,7 @@ use datahaven_runtime_common::benchmarking::BenchmarkHelper; pub(crate) use crate::weights as mainnet_weights; -const EVM_CHAIN_ID: u64 = 1289; +const EVM_CHAIN_ID: u64 = 55930; const SS58_FORMAT: u16 = EVM_CHAIN_ID as u16; //╔═══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗ diff --git a/operator/runtime/mainnet/src/genesis_config_presets.rs b/operator/runtime/mainnet/src/genesis_config_presets.rs index 85143732..ccf4634e 100644 --- a/operator/runtime/mainnet/src/genesis_config_presets.rs +++ b/operator/runtime/mainnet/src/genesis_config_presets.rs @@ -16,7 +16,7 @@ use sp_core::{ecdsa, Pair, Public}; use sp_genesis_builder::{self, PresetId}; use sp_runtime::traits::{IdentifyAccount, Verify}; -const MAINNET_EVM_CHAIN_ID: u64 = 1289; +const MAINNET_EVM_CHAIN_ID: u64 = 55930; // Returns the genesis config presets populated with given parameters. fn testnet_genesis( diff --git a/operator/runtime/stagenet/build.rs b/operator/runtime/stagenet/build.rs index 6fba03d0..b65087b1 100644 --- a/operator/runtime/stagenet/build.rs +++ b/operator/runtime/stagenet/build.rs @@ -1,7 +1,7 @@ #[cfg(all(feature = "std", feature = "metadata-hash"))] fn main() { substrate_wasm_builder::WasmBuilder::init_with_defaults() - .enable_metadata_hash("HAVE", 18) + .enable_metadata_hash("STAGE", 18) .build(); } diff --git a/operator/runtime/stagenet/src/configs/mod.rs b/operator/runtime/stagenet/src/configs/mod.rs index d1d37a88..4a7766c5 100644 --- a/operator/runtime/stagenet/src/configs/mod.rs +++ b/operator/runtime/stagenet/src/configs/mod.rs @@ -164,7 +164,7 @@ use datahaven_runtime_common::benchmarking::BenchmarkHelper; pub(crate) use crate::weights as stagenet_weights; -const EVM_CHAIN_ID: u64 = 1283; +const EVM_CHAIN_ID: u64 = 55932; const SS58_FORMAT: u16 = EVM_CHAIN_ID as u16; //╔═══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗ @@ -855,7 +855,13 @@ impl pallet_parameters::Config for Runtime { impl pallet_migrations::Config for Runtime { type RuntimeEvent = RuntimeEvent; #[cfg(not(feature = "runtime-benchmarks"))] - type Migrations = datahaven_runtime_common::migrations::MultiBlockMigrationList; + type Migrations = ( + datahaven_runtime_common::migrations::MultiBlockMigrationList, + datahaven_runtime_common::migrations::evm_chain_id::EvmChainIdMigration< + Runtime, + EVM_CHAIN_ID, + >, + ); #[cfg(feature = "runtime-benchmarks")] type Migrations = datahaven_runtime_common::migrations::MultiBlockMigrationList; type CursorMaxLen = MigrationCursorMaxLen; diff --git a/operator/runtime/stagenet/src/genesis_config_presets.rs b/operator/runtime/stagenet/src/genesis_config_presets.rs index 844ade99..7c243615 100644 --- a/operator/runtime/stagenet/src/genesis_config_presets.rs +++ b/operator/runtime/stagenet/src/genesis_config_presets.rs @@ -16,7 +16,7 @@ use sp_core::{ecdsa, Pair, Public}; use sp_genesis_builder::{self, PresetId}; use sp_runtime::traits::{IdentifyAccount, Verify}; -const STAGENET_EVM_CHAIN_ID: u64 = 1283; +const STAGENET_EVM_CHAIN_ID: u64 = 55932; // Returns the genesis config presets populated with given parameters. fn testnet_genesis( diff --git a/operator/runtime/stagenet/src/precompiles.rs b/operator/runtime/stagenet/src/precompiles.rs index 3b2919eb..3530b77b 100644 --- a/operator/runtime/stagenet/src/precompiles.rs +++ b/operator/runtime/stagenet/src/precompiles.rs @@ -41,11 +41,11 @@ pub struct NativeErc20Metadata; impl Erc20Metadata for NativeErc20Metadata { fn name() -> &'static str { - "HAVE" + "STAGE" } fn symbol() -> &'static str { - "HAVE" + "STAGE" } fn decimals() -> u8 { diff --git a/operator/runtime/stagenet/tests/common.rs b/operator/runtime/stagenet/tests/common.rs index c20bcef9..05e5d62a 100644 --- a/operator/runtime/stagenet/tests/common.rs +++ b/operator/runtime/stagenet/tests/common.rs @@ -205,8 +205,8 @@ pub fn root_origin() -> RuntimeOrigin { #[allow(dead_code)] pub fn datahaven_token_metadata() -> snowbridge_core::AssetMetadata { snowbridge_core::AssetMetadata { - name: b"HAVE".to_vec().try_into().unwrap(), - symbol: b"wHAVE".to_vec().try_into().unwrap(), + name: b"STAGE".to_vec().try_into().unwrap(), + symbol: b"wSTAGE".to_vec().try_into().unwrap(), decimals: 18, } } diff --git a/operator/runtime/testnet/build.rs b/operator/runtime/testnet/build.rs index 6fba03d0..b7af7f28 100644 --- a/operator/runtime/testnet/build.rs +++ b/operator/runtime/testnet/build.rs @@ -1,7 +1,7 @@ #[cfg(all(feature = "std", feature = "metadata-hash"))] fn main() { substrate_wasm_builder::WasmBuilder::init_with_defaults() - .enable_metadata_hash("HAVE", 18) + .enable_metadata_hash("MOCK", 18) .build(); } diff --git a/operator/runtime/testnet/src/configs/mod.rs b/operator/runtime/testnet/src/configs/mod.rs index 67df3642..39cdbd78 100644 --- a/operator/runtime/testnet/src/configs/mod.rs +++ b/operator/runtime/testnet/src/configs/mod.rs @@ -164,7 +164,7 @@ use bridge_hub_common::AggregateMessageOrigin; #[cfg(feature = "runtime-benchmarks")] use datahaven_runtime_common::benchmarking::BenchmarkHelper; -const EVM_CHAIN_ID: u64 = 1288; +const EVM_CHAIN_ID: u64 = 55931; const SS58_FORMAT: u16 = EVM_CHAIN_ID as u16; //╔═══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗ @@ -858,7 +858,13 @@ impl pallet_parameters::Config for Runtime { impl pallet_migrations::Config for Runtime { type RuntimeEvent = RuntimeEvent; #[cfg(not(feature = "runtime-benchmarks"))] - type Migrations = datahaven_runtime_common::migrations::MultiBlockMigrationList; + type Migrations = ( + datahaven_runtime_common::migrations::MultiBlockMigrationList, + datahaven_runtime_common::migrations::evm_chain_id::EvmChainIdMigration< + Runtime, + EVM_CHAIN_ID, + >, + ); #[cfg(feature = "runtime-benchmarks")] type Migrations = datahaven_runtime_common::migrations::MultiBlockMigrationList; type CursorMaxLen = MigrationCursorMaxLen; diff --git a/operator/runtime/testnet/src/genesis_config_presets.rs b/operator/runtime/testnet/src/genesis_config_presets.rs index 9f23b0dd..932fc961 100644 --- a/operator/runtime/testnet/src/genesis_config_presets.rs +++ b/operator/runtime/testnet/src/genesis_config_presets.rs @@ -16,7 +16,7 @@ use sp_core::{ecdsa, Pair, Public}; use sp_genesis_builder::{self, PresetId}; use sp_runtime::traits::{IdentifyAccount, Verify}; -const TESTNET_EVM_CHAIN_ID: u64 = 1288; +const TESTNET_EVM_CHAIN_ID: u64 = 55931; // Returns the genesis config presets populated with given parameters. fn testnet_genesis( diff --git a/operator/runtime/testnet/src/precompiles.rs b/operator/runtime/testnet/src/precompiles.rs index 5c8920c2..675a03f0 100644 --- a/operator/runtime/testnet/src/precompiles.rs +++ b/operator/runtime/testnet/src/precompiles.rs @@ -41,11 +41,11 @@ pub struct NativeErc20Metadata; impl Erc20Metadata for NativeErc20Metadata { fn name() -> &'static str { - "HAVE" + "MOCK" } fn symbol() -> &'static str { - "HAVE" + "MOCK" } fn decimals() -> u8 { diff --git a/operator/runtime/testnet/tests/common.rs b/operator/runtime/testnet/tests/common.rs index 2405bf93..56a8b875 100644 --- a/operator/runtime/testnet/tests/common.rs +++ b/operator/runtime/testnet/tests/common.rs @@ -205,8 +205,8 @@ pub fn root_origin() -> RuntimeOrigin { #[allow(dead_code)] pub fn datahaven_token_metadata() -> snowbridge_core::AssetMetadata { snowbridge_core::AssetMetadata { - name: b"HAVE".to_vec().try_into().unwrap(), - symbol: b"wHAVE".to_vec().try_into().unwrap(), + name: b"MOCK".to_vec().try_into().unwrap(), + symbol: b"wMOCK".to_vec().try_into().unwrap(), decimals: 18, } } diff --git a/test/.papi/metadata/datahaven.scale b/test/.papi/metadata/datahaven.scale index 08f0c18c..8d66c3f9 100644 Binary files a/test/.papi/metadata/datahaven.scale and b/test/.papi/metadata/datahaven.scale differ diff --git a/test/suites/native-token-transfer.test.ts b/test/suites/native-token-transfer.test.ts index f7bd1d15..21afa225 100644 --- a/test/suites/native-token-transfer.test.ts +++ b/test/suites/native-token-transfer.test.ts @@ -146,8 +146,8 @@ describe("Native Token Transfer", () => { sender: { type: "V5", value: { parents: 0, interior: { type: "Here", value: undefined } } }, asset_id: { type: "V5", value: { parents: 0, interior: { type: "Here", value: undefined } } }, metadata: { - name: Binary.fromText("HAVE"), - symbol: Binary.fromText("wHAVE"), + name: Binary.fromText("STAGE"), + symbol: Binary.fromText("wSTAGE"), decimals: 18 } }); @@ -220,8 +220,8 @@ describe("Native Token Transfer", () => { }) as Promise ]); - expect(tokenName).toBe("HAVE"); - expect(tokenSymbol).toBe("wHAVE"); + expect(tokenName).toBe("STAGE"); + expect(tokenSymbol).toBe("wSTAGE"); expect(tokenDecimals).toBe(18); }, 300_000); // 5 minute timeout for registration