Skip to content

Commit

Permalink
Merge pull request #808 from opentensor/hotfix/fix-emission-calc
Browse files Browse the repository at this point in the history
reorder mul/div
  • Loading branch information
unconst authored Sep 12, 2024
2 parents adad4fd + 824c5f5 commit abd87c6
Show file tree
Hide file tree
Showing 4 changed files with 327 additions and 8 deletions.
13 changes: 8 additions & 5 deletions pallets/subtensor/src/coinbase/run_coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ impl<T: Config> Pallet<T> {
// --- 8 Iterate over each nominator and get all viable stake.
let mut total_viable_nominator_stake: u64 = total_hotkey_stake;
for (nominator, nominator_stake) in Stake::<T>::iter_prefix(hotkey) {
if LastAddStakeIncrease::<T>::get(hotkey, nominator) > last_emission_drain {
if false && LastAddStakeIncrease::<T>::get(hotkey, nominator) > last_emission_drain {
total_viable_nominator_stake =
total_viable_nominator_stake.saturating_sub(nominator_stake);
}
Expand All @@ -303,15 +303,18 @@ impl<T: Config> Pallet<T> {
for (nominator, nominator_stake) in Stake::<T>::iter_prefix(hotkey) {
// --- 10 Check if the stake was manually increased by the user since the last emission drain for this hotkey.
// If it was, skip this nominator as they will not receive their proportion of the emission.
if LastAddStakeIncrease::<T>::get(hotkey, nominator.clone()) > last_emission_drain {
if false
&& LastAddStakeIncrease::<T>::get(hotkey, nominator.clone())
> last_emission_drain
{
continue;
}

// --- 11 Calculate this nominator's share of the emission.
let nominator_emission: I64F64 = I64F64::from_num(emission_minus_take)
.saturating_mul(I64F64::from_num(nominator_stake))
let nominator_emission: I64F64 = I64F64::from_num(nominator_stake)
.checked_div(I64F64::from_num(total_viable_nominator_stake))
.unwrap_or(I64F64::from_num(0));
.unwrap_or(I64F64::from_num(0))
.saturating_mul(I64F64::from_num(emission_minus_take));

// --- 12 Increase the stake for the nominator.
Self::increase_stake_on_coldkey_hotkey_account(
Expand Down
318 changes: 317 additions & 1 deletion pallets/subtensor/tests/coinbase.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#![allow(unused, clippy::indexing_slicing, clippy::panic, clippy::unwrap_used)]
use crate::mock::*;
mod mock;
// use frame_support::{assert_err, assert_ok};
use frame_support::assert_ok;
use pallet_subtensor::LastAddStakeIncrease;
use sp_core::U256;
use substrate_fixed::types::I64F64;

// Test the ability to hash all sorts of hotkeys.
#[test]
Expand Down Expand Up @@ -154,3 +156,317 @@ fn test_set_and_get_hotkey_emission_tempo() {
assert_eq!(updated_tempo, new_tempo);
});
}

// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --test coinbase test_coinbase_nominator_drainage_overflow -- --nocapture
#[test]
fn test_coinbase_nominator_drainage_overflow() {
new_test_ext(1).execute_with(|| {
// 1. Set up the network and accounts
let netuid: u16 = 1;
let hotkey = U256::from(0);
let coldkey = U256::from(3);
let nominator1 = U256::from(1);
let nominator2 = U256::from(2);

log::debug!("Setting up network with netuid: {}", netuid);
log::debug!("Hotkey: {:?}, Coldkey: {:?}", hotkey, coldkey);
log::debug!("Nominators: {:?}, {:?}", nominator1, nominator2);

// 2. Create network and register neuron
add_network(netuid, 1, 0);
register_ok_neuron(netuid, hotkey, coldkey, 100000);
SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey);

log::debug!("Network created and neuron registered");

// 3. Set up balances and stakes
SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000);
SubtensorModule::add_balance_to_coldkey_account(&nominator1, 1500);
SubtensorModule::add_balance_to_coldkey_account(&nominator2, 1500);

log::debug!("Balances added to accounts");

// 4. Make the hotkey a delegate
let vali_take = (u16::MAX as u64 / 10);
assert_ok!(SubtensorModule::do_become_delegate(
RuntimeOrigin::signed(coldkey),
hotkey,
vali_take as u16
));

log::debug!("Hotkey became a delegate with minimum take");

// Add stakes for nominators
// Add the stake directly to their coldkey-hotkey account
// This bypasses the accounting in stake delta
SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator1, &hotkey, 5e9 as u64);
SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator2, &hotkey, 5e9 as u64);
let initial_stake = 5e9 as u64;

// Log the stakes for hotkey, nominator1, and nominator2
log::debug!(
"Initial stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}",
SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey),
SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey),
SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey)
);
log::debug!("Stakes added for nominators");

// 5. Set emission and verify initial states
let to_emit = 20_000e9 as u64;
SubtensorModule::set_emission_values(&[netuid], vec![to_emit]).unwrap();
assert_eq!(SubtensorModule::get_subnet_emission_value(netuid), to_emit);
assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0);
assert_eq!(
SubtensorModule::get_total_stake_for_hotkey(&hotkey),
initial_stake * 2
);
assert_eq!(SubtensorModule::get_pending_emission(netuid), 0);

log::debug!("Emission set and initial states verified");

// 6. Set hotkey emission tempo
SubtensorModule::set_hotkey_emission_tempo(1);
log::debug!("Hotkey emission tempo set to 1");

// 7. Simulate blocks and check emissions
next_block();
assert_eq!(SubtensorModule::get_pending_emission(netuid), to_emit);
log::debug!(
"After first block, pending emission: {}",
SubtensorModule::get_pending_emission(netuid)
);

next_block();
assert_eq!(SubtensorModule::get_pending_emission(netuid), 0);
assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0);
log::debug!("After second block, pending emission drained");

// 8. Check final stakes
let hotkey_stake = SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey);
let nominator1_stake =
SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey);
let nominator2_stake =
SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey);

log::debug!(
"Final stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}",
hotkey_stake,
nominator1_stake,
nominator2_stake
);

// 9. Verify distribution
let total_emission = to_emit * 2; // to_emit per block for 2 blocks
let hotkey_emission = (I64F64::from_num(total_emission) / I64F64::from_num(u16::MAX)
* I64F64::from_num(vali_take))
.to_num::<u64>();
let remaining_emission = total_emission - hotkey_emission;
let nominator_emission = remaining_emission / 2;

log::debug!(
"Calculated emissions - Hotkey: {}, Each Nominator: {}",
hotkey_emission,
nominator_emission
);

// Debug: Print the actual stakes
log::debug!("Actual hotkey stake: {}", hotkey_stake);
log::debug!("Actual nominator1 stake: {}", nominator1_stake);
log::debug!("Actual nominator2 stake: {}", nominator2_stake);

// Debug: Check the total stake for the hotkey
let total_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey);
log::debug!("Total stake for hotkey: {}", total_stake);

// Assertions
let expected_hotkey_stake = 4_000e9 as u64;
let eps = 0.5e9 as u64;
assert!(
hotkey_stake >= expected_hotkey_stake - eps
&& hotkey_stake <= expected_hotkey_stake + eps,
"Hotkey stake mismatch - expected: {}, actual: {}",
expected_hotkey_stake,
hotkey_stake
);
assert_eq!(
nominator1_stake,
initial_stake + nominator_emission,
"Nominator1 stake mismatch"
);
assert_eq!(
nominator2_stake,
initial_stake + nominator_emission,
"Nominator2 stake mismatch"
);

// 10. Check total stake
assert_eq!(
total_stake,
initial_stake + initial_stake + total_emission,
"Total stake mismatch"
);

log::debug!("Test completed");
});
}

// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --test coinbase test_coinbase_nominator_drainage_no_affected_by_last_add_stake -- --nocapture
#[test]
fn test_coinbase_nominator_drainage_no_affected_by_last_add_stake() {
new_test_ext(1).execute_with(|| {
// 1. Set up the network and accounts
let netuid: u16 = 1;
let hotkey = U256::from(0);
let coldkey = U256::from(3);
let nominator1 = U256::from(1);
let nominator2 = U256::from(2);

log::debug!("Setting up network with netuid: {}", netuid);
log::debug!("Hotkey: {:?}, Coldkey: {:?}", hotkey, coldkey);
log::debug!("Nominators: {:?}, {:?}", nominator1, nominator2);

// 2. Create network and register neuron
add_network(netuid, 1, 0);
register_ok_neuron(netuid, hotkey, coldkey, 100000);
SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey);

log::debug!("Network created and neuron registered");

// 3. Set up balances and stakes
SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000);
SubtensorModule::add_balance_to_coldkey_account(&nominator1, 1500);
SubtensorModule::add_balance_to_coldkey_account(&nominator2, 1500);

log::debug!("Balances added to accounts");

// 4. Make the hotkey a delegate
let vali_take = (u16::MAX as u64 / 10);
assert_ok!(SubtensorModule::do_become_delegate(
RuntimeOrigin::signed(coldkey),
hotkey,
vali_take as u16
));

log::debug!("Hotkey became a delegate with minimum take");

// Add stakes for nominators
// Add the stake directly to their coldkey-hotkey account
// This bypasses the accounting in stake delta
SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator1, &hotkey, 5e9 as u64);
SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator2, &hotkey, 5e9 as u64);
let initial_stake = 5e9 as u64;

// Make add_stake call for nominator1
// This should not affect the emission distribution

// Will be greater than the bock emission
LastAddStakeIncrease::<Test>::insert(hotkey, nominator1, 100);

// Log the stakes for hotkey, nominator1, and nominator2
log::debug!(
"Initial stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}",
SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey),
SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey),
SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey)
);
log::debug!("Stakes added for nominators");

// 5. Set emission and verify initial states
let to_emit = 20_000e9 as u64;
SubtensorModule::set_emission_values(&[netuid], vec![to_emit]).unwrap();
assert_eq!(SubtensorModule::get_subnet_emission_value(netuid), to_emit);
assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0);
assert_eq!(
SubtensorModule::get_total_stake_for_hotkey(&hotkey),
initial_stake * 2
);
assert_eq!(SubtensorModule::get_pending_emission(netuid), 0);

log::debug!("Emission set and initial states verified");

// 6. Set hotkey emission tempo
SubtensorModule::set_hotkey_emission_tempo(1);
log::debug!("Hotkey emission tempo set to 1");

// 7. Simulate blocks and check emissions
next_block();
assert_eq!(SubtensorModule::get_pending_emission(netuid), to_emit);
log::debug!(
"After first block, pending emission: {}",
SubtensorModule::get_pending_emission(netuid)
);

next_block();
assert_eq!(SubtensorModule::get_pending_emission(netuid), 0);
assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0);
log::debug!("After second block, pending emission drained");

// 8. Check final stakes
let hotkey_stake = SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey);
let nominator1_stake =
SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey);
let nominator2_stake =
SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey);

log::debug!(
"Final stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}",
hotkey_stake,
nominator1_stake,
nominator2_stake
);

// 9. Verify distribution
let total_emission = to_emit * 2; // to_emit per block for 2 blocks
let hotkey_emission = (I64F64::from_num(total_emission) / I64F64::from_num(u16::MAX)
* I64F64::from_num(vali_take))
.to_num::<u64>();
let remaining_emission = total_emission - hotkey_emission;
let nominator_emission = remaining_emission / 2;

log::debug!(
"Calculated emissions - Hotkey: {}, Each Nominator: {}",
hotkey_emission,
nominator_emission
);

// Debug: Print the actual stakes
log::debug!("Actual hotkey stake: {}", hotkey_stake);
log::debug!("Actual nominator1 stake: {}", nominator1_stake);
log::debug!("Actual nominator2 stake: {}", nominator2_stake);

// Debug: Check the total stake for the hotkey
let total_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey);
log::debug!("Total stake for hotkey: {}", total_stake);

// Assertions
let expected_hotkey_stake = 4_000e9 as u64;
let eps = 0.5e9 as u64;
assert!(
hotkey_stake >= expected_hotkey_stake - eps
&& hotkey_stake <= expected_hotkey_stake + eps,
"Hotkey stake mismatch - expected: {}, actual: {}",
expected_hotkey_stake,
hotkey_stake
);
assert_eq!(
nominator1_stake,
initial_stake + nominator_emission,
"Nominator1 stake mismatch"
);
assert_eq!(
nominator2_stake,
initial_stake + nominator_emission,
"Nominator2 stake mismatch"
);

// 10. Check total stake
assert_eq!(
total_stake,
initial_stake + initial_stake + total_emission,
"Total stake mismatch"
);

log::debug!("Test completed");
});
}
2 changes: 1 addition & 1 deletion runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,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: 196,
spec_version: 201,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
Expand Down
2 changes: 1 addition & 1 deletion scripts/build.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
cargo build --profile production --features "runtime-benchmarks metadata-hash"
cargo build --profile production --features "metadata-hash"

0 comments on commit abd87c6

Please sign in to comment.