diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index 43a40ec32..99c11b716 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -86,7 +86,7 @@ parameter_types! { pub const InitialImmunityPeriod: u16 = 2; pub const InitialMaxAllowedUids: u16 = 2; pub const InitialBondsMovingAverage: u64 = 900_000; - pub const InitialBondsPenalty: u16 = 0; + pub const InitialBondsPenalty: u16 = u16::MAX; pub const InitialStakePruningMin: u16 = 0; pub const InitialFoundationDistribution: u64 = 0; pub const InitialDefaultDelegateTake: u16 = 11_796; // 18% honest number. diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index e2a08b390..1ff8b2760 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -37,17 +37,24 @@ impl Pallet { let current_block: u64 = Self::get_current_block_as_u64(); log::debug!("Current block: {:?}", current_block); - // --- 1. Get all netuids (filter out root and new subnet without first emission block) + // --- 1. Get all netuids (filter out root) let subnets: Vec = Self::get_all_subnet_netuids() .into_iter() .filter(|netuid| *netuid != 0) - .filter(|netuid| FirstEmissionBlockNumber::::get(*netuid).is_some()) .collect(); log::debug!("All subnet netuids: {:?}", subnets); + // Filter out subnets with no first emission block number. + let subnets_to_emit_to: Vec = subnets + .clone() + .into_iter() + .filter(|netuid| FirstEmissionBlockNumber::::get(*netuid).is_some()) + .collect(); + log::debug!("Subnets to emit to: {:?}", subnets_to_emit_to); // --- 2. Get sum of tao reserves ( in a later version we will switch to prices. ) let mut total_moving_prices: I96F32 = I96F32::saturating_from_num(0.0); - for netuid_i in subnets.iter() { + // Only get price EMA for subnets that we emit to. + for netuid_i in subnets_to_emit_to.iter() { // Get and update the moving price of each subnet adding the total together. total_moving_prices = total_moving_prices.saturating_add(Self::get_moving_alpha_price(*netuid_i)); @@ -59,7 +66,8 @@ impl Pallet { let mut tao_in: BTreeMap = BTreeMap::new(); let mut alpha_in: BTreeMap = BTreeMap::new(); let mut alpha_out: BTreeMap = BTreeMap::new(); - for netuid_i in subnets.iter() { + // Only calculate for subnets that we are emitting to. + for netuid_i in subnets_to_emit_to.iter() { // Get subnet price. let price_i: I96F32 = Self::get_alpha_price(*netuid_i); log::debug!("price_i: {:?}", price_i); @@ -104,7 +112,7 @@ impl Pallet { // --- 4. Injection. // Actually perform the injection of alpha_in, alpha_out and tao_in into the subnet pool. // This operation changes the pool liquidity each block. - for netuid_i in subnets.iter() { + for netuid_i in subnets_to_emit_to.iter() { // Inject Alpha in. let alpha_in_i: u64 = tou64!(*alpha_in.get(netuid_i).unwrap_or(&asfloat!(0))); SubnetAlphaInEmission::::insert(*netuid_i, alpha_in_i); @@ -136,7 +144,7 @@ impl Pallet { // Owner cuts are accumulated and then fed to the drain at the end of this func. let cut_percent: I96F32 = Self::get_float_subnet_owner_cut(); let mut owner_cuts: BTreeMap = BTreeMap::new(); - for netuid_i in subnets.iter() { + for netuid_i in subnets_to_emit_to.iter() { // Get alpha out. let alpha_out_i: I96F32 = *alpha_out.get(netuid_i).unwrap_or(&asfloat!(0)); log::debug!("alpha_out_i: {:?}", alpha_out_i); @@ -155,7 +163,7 @@ impl Pallet { // --- 6. Seperate out root dividends in alpha and sell them into tao. // Then accumulate those dividends for later. - for netuid_i in subnets.iter() { + for netuid_i in subnets_to_emit_to.iter() { // Get remaining alpha out. let alpha_out_i: I96F32 = *alpha_out.get(netuid_i).unwrap_or(&asfloat!(0.0)); log::debug!("alpha_out_i: {:?}", alpha_out_i); @@ -200,12 +208,14 @@ impl Pallet { } // --- 7 Update moving prices after using them in the emission calculation. - for netuid_i in subnets.iter() { + // Only update price EMA for subnets that we emit to. + for netuid_i in subnets_to_emit_to.iter() { // Update moving prices after using them above. Self::update_moving_price(*netuid_i); } // --- 7. Drain pending emission through the subnet based on tempo. + // Run the epoch for *all* subnets, even if we don't emit anything. for &netuid in subnets.iter() { // Pass on subnets that have not reached their tempo. if Self::should_run_epoch(netuid, current_block) { diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 48af2df59..0a95f6dd6 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -1889,3 +1889,245 @@ fn test_drain_pending_emission_no_miners_all_drained() { assert_abs_diff_eq!(new_stake, emission.saturating_add(init_stake), epsilon = 1); }); } + +#[test] +fn test_drain_pending_emission_zero_emission() { + new_test_ext(1).execute_with(|| { + let netuid = add_dynamic_network(&U256::from(1), &U256::from(2)); + let hotkey = U256::from(3); + let coldkey = U256::from(4); + let miner_hk = U256::from(5); + let miner_ck = U256::from(6); + let init_stake: u64 = 100_000_000_000_000; + let tempo = 2; + SubtensorModule::set_tempo(netuid, tempo); + // Set weight-set limit to 0. + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + register_ok_neuron(netuid, hotkey, coldkey, 0); + register_ok_neuron(netuid, miner_hk, miner_ck, 0); + // Give non-zero stake + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid, init_stake, + ); + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey), + init_stake + ); + + // Set the weight of root TAO to be 0%, so only alpha is effective. + SubtensorModule::set_tao_weight(0); + + run_to_block_no_epoch(netuid, 50); + + // Run epoch for initial setup. + SubtensorModule::epoch(netuid, 0); + + // Set weights on miner + assert_ok!(SubtensorModule::set_weights( + RuntimeOrigin::signed(hotkey), + netuid, + vec![0, 1, 2], + vec![0, 0, 1], + 0, + )); + + run_to_block_no_epoch(netuid, 50); + + // Clear incentive and dividends. + Incentive::::remove(netuid); + Dividends::::remove(netuid); + + // Set the emission to be ZERO. + SubtensorModule::drain_pending_emission(netuid, 0, 0, 0, 0); + + // Get the new stake of the hotkey. + let new_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); + // We expect the stake to remain unchanged. + assert_eq!(new_stake, init_stake); + + // Check that the incentive and dividends are set by epoch. + assert!(Incentive::::get(netuid).iter().sum::() > 0); + assert!(Dividends::::get(netuid).iter().sum::() > 0); + }); +} + +#[test] +fn test_run_coinbase_not_started() { + new_test_ext(1).execute_with(|| { + let netuid = 1; + let tempo = 2; + + let sn_owner_hk = U256::from(7); + let sn_owner_ck = U256::from(8); + + add_network_without_emission_block(netuid, tempo, 0); + assert_eq!(FirstEmissionBlockNumber::::get(netuid), None); + + SubnetOwner::::insert(netuid, sn_owner_ck); + SubnetOwnerHotkey::::insert(netuid, sn_owner_hk); + + let hotkey = U256::from(3); + let coldkey = U256::from(4); + let miner_hk = U256::from(5); + let miner_ck = U256::from(6); + let init_stake: u64 = 100_000_000_000_000; + let tempo = 2; + SubtensorModule::set_tempo(netuid, tempo); + // Set weight-set limit to 0. + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + register_ok_neuron(netuid, hotkey, coldkey, 0); + register_ok_neuron(netuid, miner_hk, miner_ck, 0); + register_ok_neuron(netuid, sn_owner_hk, sn_owner_ck, 0); + // Give non-zero stake + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid, init_stake, + ); + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey), + init_stake + ); + + // Set the weight of root TAO to be 0%, so only alpha is effective. + SubtensorModule::set_tao_weight(0); + + run_to_block_no_epoch(netuid, 30); + + // Run epoch for initial setup. + SubtensorModule::epoch(netuid, 0); + + // Set weights on miner + assert_ok!(SubtensorModule::set_weights( + RuntimeOrigin::signed(hotkey), + netuid, + vec![0, 1, 2], + vec![0, 0, 1], + 0, + )); + + // Clear incentive and dividends. + Incentive::::remove(netuid); + Dividends::::remove(netuid); + + // Step so tempo should run. + next_block_no_epoch(netuid); + next_block_no_epoch(netuid); + next_block_no_epoch(netuid); + let current_block = System::block_number(); + assert!(SubtensorModule::should_run_epoch(netuid, current_block)); + + // Run coinbase with emission. + SubtensorModule::run_coinbase(I96F32::saturating_from_num(100_000_000)); + + // We expect that the epoch ran. + assert_eq!(BlocksSinceLastStep::::get(netuid), 0); + + // Get the new stake of the hotkey. We expect no emissions. + let new_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); + // We expect the stake to remain unchanged. + assert_eq!(new_stake, init_stake); + + // Check that the incentive and dividends are set. + assert!(Incentive::::get(netuid).iter().sum::() > 0); + assert!(Dividends::::get(netuid).iter().sum::() > 0); + }); +} + +#[test] +fn test_run_coinbase_not_started_start_after() { + new_test_ext(1).execute_with(|| { + let netuid = 1; + let tempo = 2; + + let sn_owner_hk = U256::from(7); + let sn_owner_ck = U256::from(8); + + add_network_without_emission_block(netuid, tempo, 0); + assert_eq!(FirstEmissionBlockNumber::::get(netuid), None); + + SubnetOwner::::insert(netuid, sn_owner_ck); + SubnetOwnerHotkey::::insert(netuid, sn_owner_hk); + + let hotkey = U256::from(3); + let coldkey = U256::from(4); + let miner_hk = U256::from(5); + let miner_ck = U256::from(6); + let init_stake: u64 = 100_000_000_000_000; + let tempo = 2; + SubtensorModule::set_tempo(netuid, tempo); + // Set weight-set limit to 0. + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + register_ok_neuron(netuid, hotkey, coldkey, 0); + register_ok_neuron(netuid, miner_hk, miner_ck, 0); + register_ok_neuron(netuid, sn_owner_hk, sn_owner_ck, 0); + // Give non-zero stake + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid, init_stake, + ); + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey), + init_stake + ); + + // Set the weight of root TAO to be 0%, so only alpha is effective. + SubtensorModule::set_tao_weight(0); + + run_to_block_no_epoch(netuid, 30); + + // Run epoch for initial setup. + SubtensorModule::epoch(netuid, 0); + + // Set weights on miner + assert_ok!(SubtensorModule::set_weights( + RuntimeOrigin::signed(hotkey), + netuid, + vec![0, 1, 2], + vec![0, 0, 1], + 0, + )); + + // Clear incentive and dividends. + Incentive::::remove(netuid); + Dividends::::remove(netuid); + + // Step so tempo should run. + next_block_no_epoch(netuid); + next_block_no_epoch(netuid); + next_block_no_epoch(netuid); + let current_block = System::block_number(); + assert!(SubtensorModule::should_run_epoch(netuid, current_block)); + + // Run coinbase with emission. + SubtensorModule::run_coinbase(I96F32::saturating_from_num(100_000_000)); + // We expect that the epoch ran. + assert_eq!(BlocksSinceLastStep::::get(netuid), 0); + + let block_number = DurationOfStartCall::get(); + run_to_block_no_epoch(netuid, block_number); + + let current_block = System::block_number(); + + // Run start call. + assert_ok!(SubtensorModule::start_call( + RuntimeOrigin::signed(sn_owner_ck), + netuid + )); + assert_eq!( + FirstEmissionBlockNumber::::get(netuid), + Some(current_block + 1) + ); + + // Run coinbase with emission. + SubtensorModule::run_coinbase(I96F32::saturating_from_num(100_000_000)); + // We expect that the epoch ran. + assert_eq!(BlocksSinceLastStep::::get(netuid), 0); + + // Get the new stake of the hotkey. We expect no emissions. + let new_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey); + // We expect the stake to remain unchanged. + assert!(new_stake > init_stake); + log::info!("new_stake: {}", new_stake); + }); +} diff --git a/pallets/subtensor/src/tests/epoch.rs b/pallets/subtensor/src/tests/epoch.rs index 1bd998e7a..aaaf93e08 100644 --- a/pallets/subtensor/src/tests/epoch.rs +++ b/pallets/subtensor/src/tests/epoch.rs @@ -983,28 +983,6 @@ fn test_512_graph_random_weights() { // }); // } -fn next_block_no_epoch(netuid: u16) -> u64 { - // high tempo to skip automatic epochs in on_initialize - let high_tempo: u16 = u16::MAX - 1; - let old_tempo: u16 = SubtensorModule::get_tempo(netuid); - - SubtensorModule::set_tempo(netuid, high_tempo); - let new_block = next_block(); - SubtensorModule::set_tempo(netuid, old_tempo); - - new_block -} - -fn run_to_block_no_epoch(netuid: u16, n: u64) { - // high tempo to skip automatic epochs in on_initialize - let high_tempo: u16 = u16::MAX - 1; - let old_tempo: u16 = SubtensorModule::get_tempo(netuid); - - SubtensorModule::set_tempo(netuid, high_tempo); - run_to_block(n); - SubtensorModule::set_tempo(netuid, old_tempo); -} - // Test bonds exponential moving average over a sequence of epochs. #[test] fn test_bonds() { diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index ea5e3d449..cf0f14ea7 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -138,7 +138,7 @@ parameter_types! { pub const InitialImmunityPeriod: u16 = 2; pub const InitialMaxAllowedUids: u16 = 2; pub const InitialBondsMovingAverage: u64 = 900_000; - pub const InitialBondsPenalty:u16 = 0; + pub const InitialBondsPenalty:u16 = u16::MAX; pub const InitialStakePruningMin: u16 = 0; pub const InitialFoundationDistribution: u64 = 0; pub const InitialDefaultDelegateTake: u16 = 11_796; // 18%, same as in production @@ -594,6 +594,30 @@ pub(crate) fn run_to_block(n: u64) { } } +#[allow(dead_code)] +pub(crate) fn next_block_no_epoch(netuid: u16) -> u64 { + // high tempo to skip automatic epochs in on_initialize + let high_tempo: u16 = u16::MAX - 1; + let old_tempo: u16 = SubtensorModule::get_tempo(netuid); + + SubtensorModule::set_tempo(netuid, high_tempo); + let new_block = next_block(); + SubtensorModule::set_tempo(netuid, old_tempo); + + new_block +} + +#[allow(dead_code)] +pub(crate) fn run_to_block_no_epoch(netuid: u16, n: u64) { + // high tempo to skip automatic epochs in on_initialize + let high_tempo: u16 = u16::MAX - 1; + let old_tempo: u16 = SubtensorModule::get_tempo(netuid); + + SubtensorModule::set_tempo(netuid, high_tempo); + run_to_block(n); + SubtensorModule::set_tempo(netuid, old_tempo); +} + #[allow(dead_code)] pub(crate) fn step_epochs(count: u16, netuid: u16) { for _ in 0..count { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 22659c130..ee920ef93 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -207,7 +207,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 254, + spec_version: 255, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -1010,7 +1010,7 @@ parameter_types! { pub const SubtensorInitialMaxRegistrationsPerBlock: u16 = 1; pub const SubtensorInitialPruningScore : u16 = u16::MAX; pub const SubtensorInitialBondsMovingAverage: u64 = 900_000; - pub const SubtensorInitialBondsPenalty: u16 = 0; + pub const SubtensorInitialBondsPenalty: u16 = u16::MAX; pub const SubtensorInitialDefaultTake: u16 = 11_796; // 18% honest number. pub const SubtensorInitialMinDelegateTake: u16 = 0; // Allow 0% delegate take pub const SubtensorInitialDefaultChildKeyTake: u16 = 0; // Allow 0% childkey take @@ -1044,7 +1044,11 @@ parameter_types! { pub const InitialDissolveNetworkScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days pub const SubtensorInitialTaoWeight: u64 = 971_718_665_099_567_868; // 0.05267697438728329% tao weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks - pub const DurationOfStartCall: u64 = 7 * 24 * 60 * 60 / 12; // 7 days + pub const DurationOfStartCall: u64 = if cfg!(feature = "fast-blocks") { + 10 // Only 10 blocks for fast blocks + } else { + 7 * 24 * 60 * 60 / 12 // 7 days + }; } impl pallet_subtensor::Config for Runtime {