diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index a3cb7a692f..a055668f1a 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -147,7 +147,9 @@ mod hooks { // Migrate Lock Reduction Interval .saturating_add(migrations::migrate_network_lock_reduction_interval::migrate_network_lock_reduction_interval::()) // Migrate subnet locked balances - .saturating_add(migrations::migrate_subnet_locked::migrate_restore_subnet_locked::()); + .saturating_add(migrations::migrate_subnet_locked::migrate_restore_subnet_locked::()) + // Migrate subnet burn cost to 2500 + .saturating_add(migrations::migrate_network_lock_cost_2500::migrate_network_lock_cost_2500::()); weight } diff --git a/pallets/subtensor/src/migrations/migrate_network_lock_cost_2500.rs b/pallets/subtensor/src/migrations/migrate_network_lock_cost_2500.rs new file mode 100644 index 0000000000..e12356f6ba --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_network_lock_cost_2500.rs @@ -0,0 +1,48 @@ +use super::*; +use frame_support::{traits::Get, weights::Weight}; +use log; +use scale_info::prelude::string::String; + +pub fn migrate_network_lock_cost_2500() -> Weight { + const RAO_PER_TAO: u64 = 1_000_000_000; + const TARGET_COST_TAO: u64 = 2_500; + const NEW_LAST_LOCK_RAO: u64 = (TARGET_COST_TAO / 2) * RAO_PER_TAO; // 1,250 TAO + + let migration_name = b"migrate_network_lock_cost_2500".to_vec(); + let mut weight = T::DbWeight::get().reads(1); + + // Skip if already executed + if HasMigrationRun::::get(&migration_name) { + log::info!( + target: "runtime", + "Migration '{}' already run - skipping.", + String::from_utf8_lossy(&migration_name) + ); + return weight; + } + + // Use the current block; ensure it's non-zero so mult == 2 in get_network_lock_cost() + let current_block = Pallet::::get_current_block_as_u64(); + let block_to_set = if current_block == 0 { 1 } else { current_block }; + + // Set last_lock so that price = 2 * last_lock = 2,500 TAO at this block + Pallet::::set_network_last_lock(TaoCurrency::from(NEW_LAST_LOCK_RAO)); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + // Start decay from "now" (no backdated decay) + Pallet::::set_network_last_lock_block(block_to_set); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + // Mark migration done + HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + target: "runtime", + "Migration '{}' completed. lock_cost set to 2,500 TAO at block {}.", + String::from_utf8_lossy(&migration_name), + block_to_set + ); + + weight +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index ef2df8bdec..b036783d9d 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -21,6 +21,7 @@ pub mod migrate_fix_root_tao_and_alpha_in; pub mod migrate_identities_v2; pub mod migrate_init_total_issuance; pub mod migrate_network_immunity_period; +pub mod migrate_network_lock_cost_2500; pub mod migrate_network_lock_reduction_interval; pub mod migrate_orphaned_storage_items; pub mod migrate_populate_owned_hotkeys; diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index b37c25f04c..d6cc596d26 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -2044,3 +2044,115 @@ fn test_migrate_restore_subnet_locked_65_128() { ); }); } + +#[test] +fn test_migrate_network_lock_cost_2500_sets_price_and_decay() { + new_test_ext(0).execute_with(|| { + // ── constants ─────────────────────────────────────────────────────── + const RAO_PER_TAO: u64 = 1_000_000_000; + const TARGET_COST_TAO: u64 = 2_500; + const TARGET_COST_RAO: u64 = TARGET_COST_TAO * RAO_PER_TAO; + const NEW_LAST_LOCK_RAO: u64 = (TARGET_COST_TAO / 2) * RAO_PER_TAO; + + let migration_key = b"migrate_network_lock_cost_2500".to_vec(); + + // ── pre ────────────────────────────────────────────────────────────── + assert!( + !HasMigrationRun::::get(migration_key.clone()), + "HasMigrationRun should be false before migration" + ); + + // Ensure current_block > 0 so mult == 2 in get_network_lock_cost() + step_block(1); + let current_block_before = Pallet::::get_current_block_as_u64(); + + // Snapshot interval to ensure migration doesn't change it + let interval_before = NetworkLockReductionInterval::::get(); + + // ── run migration ──────────────────────────────────────────────────── + let weight = crate::migrations::migrate_network_lock_cost_2500::migrate_network_lock_cost_2500::(); + assert!(!weight.is_zero(), "migration weight should be > 0"); + + // ── asserts: params & flags ───────────────────────────────────────── + assert_eq!( + Pallet::::get_network_last_lock(), + NEW_LAST_LOCK_RAO.into(), + "last_lock should be set to 1,250 TAO (in rao)" + ); + assert_eq!( + Pallet::::get_network_last_lock_block(), + current_block_before, + "last_lock_block should be set to the current block" + ); + + // Lock cost should be exactly 2,500 TAO immediately after migration + let lock_cost_now = Pallet::::get_network_lock_cost(); + assert_eq!( + lock_cost_now, + TARGET_COST_RAO.into(), + "lock cost should be 2,500 TAO right after migration" + ); + + // Interval should be unchanged by this migration + assert_eq!( + NetworkLockReductionInterval::::get(), + interval_before, + "lock reduction interval should not be modified by this migration" + ); + + assert!( + HasMigrationRun::::get(migration_key.clone()), + "HasMigrationRun should be true after migration" + ); + + // ── decay check (1 block later) ───────────────────────────────────── + // Expected: cost = max(min_lock, 2*L - floor(L / eff_interval) * delta_blocks) + let eff_interval = Pallet::::get_lock_reduction_interval(); + let per_block_decrement: u64 = if eff_interval == 0 { + 0 + } else { + NEW_LAST_LOCK_RAO / eff_interval + }; + + let min_lock_rao: u64 = Pallet::::get_network_min_lock().to_u64(); + + step_block(1); + let expected_after_1: u64 = core::cmp::max( + min_lock_rao, + TARGET_COST_RAO.saturating_sub(per_block_decrement), + ); + let lock_cost_after_1 = Pallet::::get_network_lock_cost(); + assert_eq!( + lock_cost_after_1, + expected_after_1.into(), + "lock cost should decay by one per-block step after 1 block" + ); + + // ── idempotency: running the migration again should do nothing ────── + let last_lock_before_rerun = Pallet::::get_network_last_lock(); + let last_lock_block_before_rerun = Pallet::::get_network_last_lock_block(); + let cost_before_rerun = Pallet::::get_network_lock_cost(); + + let _weight2 = crate::migrations::migrate_network_lock_cost_2500::migrate_network_lock_cost_2500::(); + + assert!( + HasMigrationRun::::get(migration_key.clone()), + "HasMigrationRun remains true on second run" + ); + assert_eq!( + Pallet::::get_network_last_lock(), + last_lock_before_rerun, + "second run should not modify last_lock" + ); + assert_eq!( + Pallet::::get_network_last_lock_block(), + last_lock_block_before_rerun, + "second run should not modify last_lock_block" + ); + assert_eq!( + Pallet::::get_network_lock_cost(), + cost_before_rerun, + "second run should not change current lock cost" + ); + }); +}