feat: Update chain IDs & native token tickers for all 3 environments (#280)

Update the 3 DataHaven environments' chain IDs & native token ticker as
follows:

* **Mainnet**
  * **Chain ID**: 55930
  * **Ticker**: HAVE
* **TestNet**
  * **Chain ID**: 55931
  * **Ticker**: MOCK
* **Stagenet**
  * **Chain ID**: 55932
  * **Ticker**: STAGE

The PR includes a storage migration for the Stagenet & Testnet
environments, that are already live, to update the EVM Chain ID stored
in the `pallet-evm-chain-id` pallet.

Note: the token symbol will only be updated with the genesis config
presets or newly generated chain specs. For already live networks, the
existing chain spec must be updated (i.e. the tokenSymbol property
changed) and used by all nodes in the network. This change in the chain
spec will not alter the chain genesis so it safe to do (in the very
early stages of the chain obviously).

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Steve Degosserie 2025-11-07 12:14:28 +01:00 committed by GitHub
parent 45cc9101ea
commit a97f0547a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 213 additions and 27 deletions

1
operator/Cargo.lock generated
View file

@ -3171,6 +3171,7 @@ dependencies = [
"pallet-authorship",
"pallet-balances",
"pallet-evm",
"pallet-evm-chain-id",
"pallet-evm-precompile-proxy",
"pallet-migrations",
"pallet-safe-mode",

View file

@ -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";

View file

@ -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<ChainSpec, String> {
// Give the token a unit name and decimal places

View file

@ -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<ChainSpec, String> {
// Give the token a unit name and decimal places

View file

@ -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",

View file

@ -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<T, const NEW_CHAIN_ID: u64>(PhantomData<T>);
impl<T, const NEW_CHAIN_ID: u64> SteppedMigration for EvmChainIdMigration<T, NEW_CHAIN_ID>
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<Self::Cursor>,
meter: &mut WeightMeter,
) -> Result<Option<Self::Cursor>, 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::<T>::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<Vec<u8>, sp_runtime::TryRuntimeError> {
let old_chain_id = pallet_evm_chain_id::ChainId::<T>::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<u8>) -> 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::<T>::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<T>, Event<T>},
Timestamp: pallet_timestamp::{Pallet, Call, Storage},
EVM: pallet_evm::{Pallet, Call, Storage, Config<T>, Event<T>},
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::<TestRuntime>::default()
.build_storage()
.unwrap();
pallet_balances::GenesisConfig::<TestRuntime>::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::<TestRuntime>::put(OLD_CHAIN_ID);
assert_eq!(
pallet_evm_chain_id::ChainId::<TestRuntime>::get(),
OLD_CHAIN_ID
);
// Run the migration
let mut meter = WeightMeter::with_limit(Weight::MAX);
let result = EvmChainIdMigration::<TestRuntime, NEW_CHAIN_ID>::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::<TestRuntime>::get(),
NEW_CHAIN_ID
);
});
}
#[test]
fn evm_chain_id_migration_is_idempotent() {
use super::evm_chain_id::EvmChainIdMigration;
let mut storage = frame_system::GenesisConfig::<TestRuntime>::default()
.build_storage()
.unwrap();
pallet_balances::GenesisConfig::<TestRuntime>::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::<TestRuntime, NEW_CHAIN_ID>::step(None, &mut meter);
let mut meter = WeightMeter::with_limit(Weight::MAX);
let result2 = EvmChainIdMigration::<TestRuntime, NEW_CHAIN_ID>::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::<TestRuntime>::get(),
NEW_CHAIN_ID
);
});
}
}

View file

@ -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;
//╔═══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗

View file

@ -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(

View file

@ -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();
}

View file

@ -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<Runtime>;
type Migrations = (
datahaven_runtime_common::migrations::MultiBlockMigrationList<Runtime>,
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;

View file

@ -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(

View file

@ -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 {

View file

@ -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,
}
}

View file

@ -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();
}

View file

@ -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<Runtime>;
type Migrations = (
datahaven_runtime_common::migrations::MultiBlockMigrationList<Runtime>,
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;

View file

@ -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(

View file

@ -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 {

View file

@ -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,
}
}

Binary file not shown.

View file

@ -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<number>
]);
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