Skip to content

Commit abd87c6

Browse files
authored
Merge pull request #808 from opentensor/hotfix/fix-emission-calc
reorder mul/div
2 parents adad4fd + 824c5f5 commit abd87c6

File tree

4 files changed

+327
-8
lines changed

4 files changed

+327
-8
lines changed

pallets/subtensor/src/coinbase/run_coinbase.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ impl<T: Config> Pallet<T> {
292292
// --- 8 Iterate over each nominator and get all viable stake.
293293
let mut total_viable_nominator_stake: u64 = total_hotkey_stake;
294294
for (nominator, nominator_stake) in Stake::<T>::iter_prefix(hotkey) {
295-
if LastAddStakeIncrease::<T>::get(hotkey, nominator) > last_emission_drain {
295+
if false && LastAddStakeIncrease::<T>::get(hotkey, nominator) > last_emission_drain {
296296
total_viable_nominator_stake =
297297
total_viable_nominator_stake.saturating_sub(nominator_stake);
298298
}
@@ -303,15 +303,18 @@ impl<T: Config> Pallet<T> {
303303
for (nominator, nominator_stake) in Stake::<T>::iter_prefix(hotkey) {
304304
// --- 10 Check if the stake was manually increased by the user since the last emission drain for this hotkey.
305305
// If it was, skip this nominator as they will not receive their proportion of the emission.
306-
if LastAddStakeIncrease::<T>::get(hotkey, nominator.clone()) > last_emission_drain {
306+
if false
307+
&& LastAddStakeIncrease::<T>::get(hotkey, nominator.clone())
308+
> last_emission_drain
309+
{
307310
continue;
308311
}
309312

310313
// --- 11 Calculate this nominator's share of the emission.
311-
let nominator_emission: I64F64 = I64F64::from_num(emission_minus_take)
312-
.saturating_mul(I64F64::from_num(nominator_stake))
314+
let nominator_emission: I64F64 = I64F64::from_num(nominator_stake)
313315
.checked_div(I64F64::from_num(total_viable_nominator_stake))
314-
.unwrap_or(I64F64::from_num(0));
316+
.unwrap_or(I64F64::from_num(0))
317+
.saturating_mul(I64F64::from_num(emission_minus_take));
315318

316319
// --- 12 Increase the stake for the nominator.
317320
Self::increase_stake_on_coldkey_hotkey_account(

pallets/subtensor/tests/coinbase.rs

Lines changed: 317 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#![allow(unused, clippy::indexing_slicing, clippy::panic, clippy::unwrap_used)]
22
use crate::mock::*;
33
mod mock;
4-
// use frame_support::{assert_err, assert_ok};
4+
use frame_support::assert_ok;
5+
use pallet_subtensor::LastAddStakeIncrease;
56
use sp_core::U256;
7+
use substrate_fixed::types::I64F64;
68

79
// Test the ability to hash all sorts of hotkeys.
810
#[test]
@@ -154,3 +156,317 @@ fn test_set_and_get_hotkey_emission_tempo() {
154156
assert_eq!(updated_tempo, new_tempo);
155157
});
156158
}
159+
160+
// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --test coinbase test_coinbase_nominator_drainage_overflow -- --nocapture
161+
#[test]
162+
fn test_coinbase_nominator_drainage_overflow() {
163+
new_test_ext(1).execute_with(|| {
164+
// 1. Set up the network and accounts
165+
let netuid: u16 = 1;
166+
let hotkey = U256::from(0);
167+
let coldkey = U256::from(3);
168+
let nominator1 = U256::from(1);
169+
let nominator2 = U256::from(2);
170+
171+
log::debug!("Setting up network with netuid: {}", netuid);
172+
log::debug!("Hotkey: {:?}, Coldkey: {:?}", hotkey, coldkey);
173+
log::debug!("Nominators: {:?}, {:?}", nominator1, nominator2);
174+
175+
// 2. Create network and register neuron
176+
add_network(netuid, 1, 0);
177+
register_ok_neuron(netuid, hotkey, coldkey, 100000);
178+
SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey);
179+
180+
log::debug!("Network created and neuron registered");
181+
182+
// 3. Set up balances and stakes
183+
SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000);
184+
SubtensorModule::add_balance_to_coldkey_account(&nominator1, 1500);
185+
SubtensorModule::add_balance_to_coldkey_account(&nominator2, 1500);
186+
187+
log::debug!("Balances added to accounts");
188+
189+
// 4. Make the hotkey a delegate
190+
let vali_take = (u16::MAX as u64 / 10);
191+
assert_ok!(SubtensorModule::do_become_delegate(
192+
RuntimeOrigin::signed(coldkey),
193+
hotkey,
194+
vali_take as u16
195+
));
196+
197+
log::debug!("Hotkey became a delegate with minimum take");
198+
199+
// Add stakes for nominators
200+
// Add the stake directly to their coldkey-hotkey account
201+
// This bypasses the accounting in stake delta
202+
SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator1, &hotkey, 5e9 as u64);
203+
SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator2, &hotkey, 5e9 as u64);
204+
let initial_stake = 5e9 as u64;
205+
206+
// Log the stakes for hotkey, nominator1, and nominator2
207+
log::debug!(
208+
"Initial stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}",
209+
SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey),
210+
SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey),
211+
SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey)
212+
);
213+
log::debug!("Stakes added for nominators");
214+
215+
// 5. Set emission and verify initial states
216+
let to_emit = 20_000e9 as u64;
217+
SubtensorModule::set_emission_values(&[netuid], vec![to_emit]).unwrap();
218+
assert_eq!(SubtensorModule::get_subnet_emission_value(netuid), to_emit);
219+
assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0);
220+
assert_eq!(
221+
SubtensorModule::get_total_stake_for_hotkey(&hotkey),
222+
initial_stake * 2
223+
);
224+
assert_eq!(SubtensorModule::get_pending_emission(netuid), 0);
225+
226+
log::debug!("Emission set and initial states verified");
227+
228+
// 6. Set hotkey emission tempo
229+
SubtensorModule::set_hotkey_emission_tempo(1);
230+
log::debug!("Hotkey emission tempo set to 1");
231+
232+
// 7. Simulate blocks and check emissions
233+
next_block();
234+
assert_eq!(SubtensorModule::get_pending_emission(netuid), to_emit);
235+
log::debug!(
236+
"After first block, pending emission: {}",
237+
SubtensorModule::get_pending_emission(netuid)
238+
);
239+
240+
next_block();
241+
assert_eq!(SubtensorModule::get_pending_emission(netuid), 0);
242+
assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0);
243+
log::debug!("After second block, pending emission drained");
244+
245+
// 8. Check final stakes
246+
let hotkey_stake = SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey);
247+
let nominator1_stake =
248+
SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey);
249+
let nominator2_stake =
250+
SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey);
251+
252+
log::debug!(
253+
"Final stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}",
254+
hotkey_stake,
255+
nominator1_stake,
256+
nominator2_stake
257+
);
258+
259+
// 9. Verify distribution
260+
let total_emission = to_emit * 2; // to_emit per block for 2 blocks
261+
let hotkey_emission = (I64F64::from_num(total_emission) / I64F64::from_num(u16::MAX)
262+
* I64F64::from_num(vali_take))
263+
.to_num::<u64>();
264+
let remaining_emission = total_emission - hotkey_emission;
265+
let nominator_emission = remaining_emission / 2;
266+
267+
log::debug!(
268+
"Calculated emissions - Hotkey: {}, Each Nominator: {}",
269+
hotkey_emission,
270+
nominator_emission
271+
);
272+
273+
// Debug: Print the actual stakes
274+
log::debug!("Actual hotkey stake: {}", hotkey_stake);
275+
log::debug!("Actual nominator1 stake: {}", nominator1_stake);
276+
log::debug!("Actual nominator2 stake: {}", nominator2_stake);
277+
278+
// Debug: Check the total stake for the hotkey
279+
let total_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey);
280+
log::debug!("Total stake for hotkey: {}", total_stake);
281+
282+
// Assertions
283+
let expected_hotkey_stake = 4_000e9 as u64;
284+
let eps = 0.5e9 as u64;
285+
assert!(
286+
hotkey_stake >= expected_hotkey_stake - eps
287+
&& hotkey_stake <= expected_hotkey_stake + eps,
288+
"Hotkey stake mismatch - expected: {}, actual: {}",
289+
expected_hotkey_stake,
290+
hotkey_stake
291+
);
292+
assert_eq!(
293+
nominator1_stake,
294+
initial_stake + nominator_emission,
295+
"Nominator1 stake mismatch"
296+
);
297+
assert_eq!(
298+
nominator2_stake,
299+
initial_stake + nominator_emission,
300+
"Nominator2 stake mismatch"
301+
);
302+
303+
// 10. Check total stake
304+
assert_eq!(
305+
total_stake,
306+
initial_stake + initial_stake + total_emission,
307+
"Total stake mismatch"
308+
);
309+
310+
log::debug!("Test completed");
311+
});
312+
}
313+
314+
// 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
315+
#[test]
316+
fn test_coinbase_nominator_drainage_no_affected_by_last_add_stake() {
317+
new_test_ext(1).execute_with(|| {
318+
// 1. Set up the network and accounts
319+
let netuid: u16 = 1;
320+
let hotkey = U256::from(0);
321+
let coldkey = U256::from(3);
322+
let nominator1 = U256::from(1);
323+
let nominator2 = U256::from(2);
324+
325+
log::debug!("Setting up network with netuid: {}", netuid);
326+
log::debug!("Hotkey: {:?}, Coldkey: {:?}", hotkey, coldkey);
327+
log::debug!("Nominators: {:?}, {:?}", nominator1, nominator2);
328+
329+
// 2. Create network and register neuron
330+
add_network(netuid, 1, 0);
331+
register_ok_neuron(netuid, hotkey, coldkey, 100000);
332+
SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey);
333+
334+
log::debug!("Network created and neuron registered");
335+
336+
// 3. Set up balances and stakes
337+
SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000);
338+
SubtensorModule::add_balance_to_coldkey_account(&nominator1, 1500);
339+
SubtensorModule::add_balance_to_coldkey_account(&nominator2, 1500);
340+
341+
log::debug!("Balances added to accounts");
342+
343+
// 4. Make the hotkey a delegate
344+
let vali_take = (u16::MAX as u64 / 10);
345+
assert_ok!(SubtensorModule::do_become_delegate(
346+
RuntimeOrigin::signed(coldkey),
347+
hotkey,
348+
vali_take as u16
349+
));
350+
351+
log::debug!("Hotkey became a delegate with minimum take");
352+
353+
// Add stakes for nominators
354+
// Add the stake directly to their coldkey-hotkey account
355+
// This bypasses the accounting in stake delta
356+
SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator1, &hotkey, 5e9 as u64);
357+
SubtensorModule::increase_stake_on_coldkey_hotkey_account(&nominator2, &hotkey, 5e9 as u64);
358+
let initial_stake = 5e9 as u64;
359+
360+
// Make add_stake call for nominator1
361+
// This should not affect the emission distribution
362+
363+
// Will be greater than the bock emission
364+
LastAddStakeIncrease::<Test>::insert(hotkey, nominator1, 100);
365+
366+
// Log the stakes for hotkey, nominator1, and nominator2
367+
log::debug!(
368+
"Initial stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}",
369+
SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey),
370+
SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey),
371+
SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey)
372+
);
373+
log::debug!("Stakes added for nominators");
374+
375+
// 5. Set emission and verify initial states
376+
let to_emit = 20_000e9 as u64;
377+
SubtensorModule::set_emission_values(&[netuid], vec![to_emit]).unwrap();
378+
assert_eq!(SubtensorModule::get_subnet_emission_value(netuid), to_emit);
379+
assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0);
380+
assert_eq!(
381+
SubtensorModule::get_total_stake_for_hotkey(&hotkey),
382+
initial_stake * 2
383+
);
384+
assert_eq!(SubtensorModule::get_pending_emission(netuid), 0);
385+
386+
log::debug!("Emission set and initial states verified");
387+
388+
// 6. Set hotkey emission tempo
389+
SubtensorModule::set_hotkey_emission_tempo(1);
390+
log::debug!("Hotkey emission tempo set to 1");
391+
392+
// 7. Simulate blocks and check emissions
393+
next_block();
394+
assert_eq!(SubtensorModule::get_pending_emission(netuid), to_emit);
395+
log::debug!(
396+
"After first block, pending emission: {}",
397+
SubtensorModule::get_pending_emission(netuid)
398+
);
399+
400+
next_block();
401+
assert_eq!(SubtensorModule::get_pending_emission(netuid), 0);
402+
assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0);
403+
log::debug!("After second block, pending emission drained");
404+
405+
// 8. Check final stakes
406+
let hotkey_stake = SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey);
407+
let nominator1_stake =
408+
SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator1, &hotkey);
409+
let nominator2_stake =
410+
SubtensorModule::get_stake_for_coldkey_and_hotkey(&nominator2, &hotkey);
411+
412+
log::debug!(
413+
"Final stakes - Hotkey: {}, Nominator1: {}, Nominator2: {}",
414+
hotkey_stake,
415+
nominator1_stake,
416+
nominator2_stake
417+
);
418+
419+
// 9. Verify distribution
420+
let total_emission = to_emit * 2; // to_emit per block for 2 blocks
421+
let hotkey_emission = (I64F64::from_num(total_emission) / I64F64::from_num(u16::MAX)
422+
* I64F64::from_num(vali_take))
423+
.to_num::<u64>();
424+
let remaining_emission = total_emission - hotkey_emission;
425+
let nominator_emission = remaining_emission / 2;
426+
427+
log::debug!(
428+
"Calculated emissions - Hotkey: {}, Each Nominator: {}",
429+
hotkey_emission,
430+
nominator_emission
431+
);
432+
433+
// Debug: Print the actual stakes
434+
log::debug!("Actual hotkey stake: {}", hotkey_stake);
435+
log::debug!("Actual nominator1 stake: {}", nominator1_stake);
436+
log::debug!("Actual nominator2 stake: {}", nominator2_stake);
437+
438+
// Debug: Check the total stake for the hotkey
439+
let total_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey);
440+
log::debug!("Total stake for hotkey: {}", total_stake);
441+
442+
// Assertions
443+
let expected_hotkey_stake = 4_000e9 as u64;
444+
let eps = 0.5e9 as u64;
445+
assert!(
446+
hotkey_stake >= expected_hotkey_stake - eps
447+
&& hotkey_stake <= expected_hotkey_stake + eps,
448+
"Hotkey stake mismatch - expected: {}, actual: {}",
449+
expected_hotkey_stake,
450+
hotkey_stake
451+
);
452+
assert_eq!(
453+
nominator1_stake,
454+
initial_stake + nominator_emission,
455+
"Nominator1 stake mismatch"
456+
);
457+
assert_eq!(
458+
nominator2_stake,
459+
initial_stake + nominator_emission,
460+
"Nominator2 stake mismatch"
461+
);
462+
463+
// 10. Check total stake
464+
assert_eq!(
465+
total_stake,
466+
initial_stake + initial_stake + total_emission,
467+
"Total stake mismatch"
468+
);
469+
470+
log::debug!("Test completed");
471+
});
472+
}

runtime/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
142142
// `spec_version`, and `authoring_version` are the same between Wasm and native.
143143
// This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use
144144
// the compatible custom types.
145-
spec_version: 196,
145+
spec_version: 201,
146146
impl_version: 1,
147147
apis: RUNTIME_API_VERSIONS,
148148
transaction_version: 1,

scripts/build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
cargo build --profile production --features "runtime-benchmarks metadata-hash"
1+
cargo build --profile production --features "metadata-hash"
22

0 commit comments

Comments
 (0)