Skip to content

Commit 3706c1d

Browse files
committed
Merge branch 'devnet-ready' into rework-coldkey-swap
2 parents e3d4585 + be47139 commit 3706c1d

File tree

11 files changed

+201
-27
lines changed

11 files changed

+201
-27
lines changed

pallets/subtensor/src/coinbase/root.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,8 @@ impl<T: Config> Pallet<T> {
298298
SubnetTaoInEmission::<T>::remove(netuid);
299299
SubnetVolume::<T>::remove(netuid);
300300
SubnetMovingPrice::<T>::remove(netuid);
301+
SubnetTaoFlow::<T>::remove(netuid);
302+
SubnetEmaTaoFlow::<T>::remove(netuid);
301303
SubnetTaoProvided::<T>::remove(netuid);
302304

303305
// --- 13. Token / mechanism / registration toggles.

pallets/subtensor/src/epoch/math.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,8 +1592,3 @@ pub fn mat_ema_alpha(
15921592
pub fn safe_ln(value: I32F32) -> I32F32 {
15931593
ln(value).unwrap_or(I32F32::saturating_from_num(0.0))
15941594
}
1595-
1596-
/// Safe exp function, returns 0 if value is 0.
1597-
pub fn safe_exp(value: I32F32) -> I32F32 {
1598-
exp(value).unwrap_or(I32F32::saturating_from_num(0.0))
1599-
}

pallets/subtensor/src/epoch/run_epoch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1477,7 +1477,7 @@ impl<T: Config> Pallet<T> {
14771477

14781478
// sigmoid = 1. / (1. + e^(-steepness * (combined_diff - 0.5)))
14791479
let sigmoid = one.saturating_div(
1480-
one.saturating_add(safe_exp(
1480+
one.saturating_add(exp_safe(
14811481
alpha_sigmoid_steepness
14821482
.saturating_div(I32F32::from_num(-100))
14831483
.saturating_mul(combined_diff.saturating_sub(I32F32::from_num(0.5))),

pallets/subtensor/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,9 +330,9 @@ pub mod pallet {
330330
/// Enum for the per-coldkey root claim setting.
331331
pub enum RootClaimTypeEnum {
332332
/// Swap any alpha emission for TAO.
333+
#[default]
333334
Swap,
334335
/// Keep all alpha emission.
335-
#[default]
336336
Keep,
337337
/// Keep all alpha emission for specified subnets.
338338
KeepSubnets {

pallets/subtensor/src/macros/errors.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,8 @@ mod errors {
268268
InvalidRootClaimThreshold,
269269
/// Exceeded subnet limit number or zero.
270270
InvalidSubnetNumber,
271+
/// Unintended precision loss when unstaking alpha
272+
PrecisionLoss,
271273
/// Deprecated call.
272274
Deprecated,
273275
}

pallets/subtensor/src/staking/recycle_alpha.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,10 @@ impl<T: Config> Pallet<T> {
5555
&hotkey, &coldkey, netuid, amount,
5656
);
5757

58+
ensure!(actual_alpha_decrease <= amount, Error::<T>::PrecisionLoss);
59+
5860
// Recycle means we should decrease the alpha issuance tracker.
59-
Self::recycle_subnet_alpha(netuid, amount);
61+
Self::recycle_subnet_alpha(netuid, actual_alpha_decrease);
6062

6163
Self::deposit_event(Event::AlphaRecycled(
6264
coldkey,
@@ -120,7 +122,9 @@ impl<T: Config> Pallet<T> {
120122
&hotkey, &coldkey, netuid, amount,
121123
);
122124

123-
Self::burn_subnet_alpha(netuid, amount);
125+
ensure!(actual_alpha_decrease <= amount, Error::<T>::PrecisionLoss);
126+
127+
Self::burn_subnet_alpha(netuid, actual_alpha_decrease);
124128

125129
// Deposit event
126130
Self::deposit_event(Event::AlphaBurned(

pallets/subtensor/src/tests/claim_root.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1816,6 +1816,6 @@ fn test_claim_root_default_mode_keep() {
18161816
new_test_ext(1).execute_with(|| {
18171817
let coldkey = U256::from(1003);
18181818

1819-
assert_eq!(RootClaimType::<Test>::get(coldkey), RootClaimTypeEnum::Keep);
1819+
assert_eq!(RootClaimType::<Test>::get(coldkey), RootClaimTypeEnum::Swap);
18201820
});
18211821
}

pallets/subtensor/src/tests/networks.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,10 @@ fn dissolve_clears_all_per_subnet_storages() {
368368
SubnetTaoProvided::<Test>::insert(net, TaoCurrency::from(1));
369369
SubnetAlphaInProvided::<Test>::insert(net, AlphaCurrency::from(1));
370370

371+
// TAO Flow
372+
SubnetTaoFlow::<Test>::insert(net, 0i64);
373+
SubnetEmaTaoFlow::<Test>::insert(net, (0u64, substrate_fixed::types::I64F64::from_num(0)));
374+
371375
// Subnet locks
372376
TransferToggle::<Test>::insert(net, true);
373377
SubnetLocked::<Test>::insert(net, TaoCurrency::from(1));
@@ -500,6 +504,10 @@ fn dissolve_clears_all_per_subnet_storages() {
500504
assert!(!SubnetTaoInEmission::<Test>::contains_key(net));
501505
assert!(!SubnetVolume::<Test>::contains_key(net));
502506

507+
// TAO Flow
508+
assert!(!SubnetTaoFlow::<Test>::contains_key(net));
509+
assert!(!SubnetEmaTaoFlow::<Test>::contains_key(net));
510+
503511
// These are now REMOVED
504512
assert!(!SubnetAlphaIn::<Test>::contains_key(net));
505513
assert!(!SubnetAlphaOut::<Test>::contains_key(net));

pallets/subtensor/src/tests/recycle_alpha.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use approx::assert_abs_diff_eq;
22
use frame_support::{assert_noop, assert_ok, traits::Currency};
33
use sp_core::U256;
4+
use substrate_fixed::types::U64F64;
45
use subtensor_runtime_common::{AlphaCurrency, Currency as CurrencyT};
56

67
use super::mock;
@@ -543,3 +544,77 @@ fn test_burn_errors() {
543544
);
544545
});
545546
}
547+
548+
#[test]
549+
fn test_recycle_precision_loss() {
550+
new_test_ext(1).execute_with(|| {
551+
let coldkey = U256::from(1);
552+
let hotkey = U256::from(2);
553+
554+
let netuid = add_dynamic_network(&hotkey, &coldkey);
555+
556+
Balances::make_free_balance_be(&coldkey, 1_000_000_000);
557+
// sanity check
558+
assert!(SubtensorModule::if_subnet_exist(netuid));
559+
560+
// add stake to coldkey-hotkey pair so we can recycle it
561+
let stake = 200_000;
562+
increase_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, stake.into(), netuid);
563+
564+
// amount to recycle
565+
let recycle_amount = AlphaCurrency::from(stake / 2);
566+
567+
// Modify the alpha pool denominator so it's low-precision
568+
let denominator = U64F64::from_num(0.00000001);
569+
TotalHotkeyShares::<Test>::insert(hotkey, netuid, denominator);
570+
Alpha::<Test>::insert((&hotkey, &coldkey, netuid), denominator);
571+
572+
// recycle, expect error due to precision loss
573+
assert_noop!(
574+
SubtensorModule::recycle_alpha(
575+
RuntimeOrigin::signed(coldkey),
576+
hotkey,
577+
recycle_amount,
578+
netuid
579+
),
580+
Error::<Test>::PrecisionLoss
581+
);
582+
});
583+
}
584+
585+
#[test]
586+
fn test_burn_precision_loss() {
587+
new_test_ext(1).execute_with(|| {
588+
let coldkey = U256::from(1);
589+
let hotkey = U256::from(2);
590+
591+
let netuid = add_dynamic_network(&hotkey, &coldkey);
592+
593+
Balances::make_free_balance_be(&coldkey, 1_000_000_000);
594+
// sanity check
595+
assert!(SubtensorModule::if_subnet_exist(netuid));
596+
597+
// add stake to coldkey-hotkey pair so we can recycle it
598+
let stake = 200_000;
599+
increase_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, stake.into(), netuid);
600+
601+
// amount to recycle
602+
let burn_amount = AlphaCurrency::from(stake / 2);
603+
604+
// Modify the alpha pool denominator so it's low-precision
605+
let denominator = U64F64::from_num(0.00000001);
606+
TotalHotkeyShares::<Test>::insert(hotkey, netuid, denominator);
607+
Alpha::<Test>::insert((&hotkey, &coldkey, netuid), denominator);
608+
609+
// burn, expect error due to precision loss
610+
assert_noop!(
611+
SubtensorModule::burn_alpha(
612+
RuntimeOrigin::signed(coldkey),
613+
hotkey,
614+
burn_amount,
615+
netuid
616+
),
617+
Error::<Test>::PrecisionLoss
618+
);
619+
});
620+
}

runtime/src/lib.rs

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ use sp_runtime::{
6161
AccountIdLookup, BlakeTwo256, Block as BlockT, DispatchInfoOf, Dispatchable, One,
6262
PostDispatchInfoOf, UniqueSaturatedInto, Verify,
6363
},
64-
transaction_validity::{TransactionSource, TransactionValidity, TransactionValidityError},
64+
transaction_validity::{
65+
TransactionPriority, TransactionSource, TransactionValidity, TransactionValidityError,
66+
},
6567
};
6668
use sp_std::cmp::Ordering;
6769
use sp_std::prelude::*;
@@ -239,7 +241,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
239241
// `spec_version`, and `authoring_version` are the same between Wasm and native.
240242
// This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use
241243
// the compatible custom types.
242-
spec_version: 364,
244+
spec_version: 365,
243245
impl_version: 1,
244246
apis: RUNTIME_API_VERSIONS,
245247
transaction_version: 1,
@@ -1221,6 +1223,10 @@ impl<F: FindAuthor<u32>> FindAuthor<H160> for FindAuthorTruncated<F> {
12211223
}
12221224

12231225
const BLOCK_GAS_LIMIT: u64 = 75_000_000;
1226+
pub const NORMAL_DISPATCH_BASE_PRIORITY: TransactionPriority = 1;
1227+
pub const OPERATIONAL_DISPATCH_PRIORITY: TransactionPriority = 10_000_000_000;
1228+
const EVM_TRANSACTION_BASE_PRIORITY: TransactionPriority = NORMAL_DISPATCH_BASE_PRIORITY;
1229+
const EVM_LOG_TARGET: &str = "runtime::ethereum";
12241230

12251231
/// `WeightPerGas` is an approximate ratio of the amount of Weight per Gas.
12261232
///
@@ -1384,6 +1390,35 @@ impl<B: BlockT> fp_rpc::ConvertTransaction<<B as BlockT>::Extrinsic> for Transac
13841390
}
13851391
}
13861392

1393+
fn adjust_evm_priority_and_warn(
1394+
validity: &mut Option<TransactionValidity>,
1395+
priority_fee: Option<U256>,
1396+
info: &H160,
1397+
) {
1398+
if let Some(Ok(valid_transaction)) = validity.as_mut() {
1399+
let original_priority = valid_transaction.priority;
1400+
valid_transaction.priority = EVM_TRANSACTION_BASE_PRIORITY;
1401+
1402+
let has_priority_fee = priority_fee.is_some_and(|fee| !fee.is_zero());
1403+
if has_priority_fee {
1404+
log::warn!(
1405+
target: EVM_LOG_TARGET,
1406+
"Priority fee/tip from {:?} (max_priority_fee_per_gas: {:?}) is ignored for transaction ordering",
1407+
info,
1408+
priority_fee.unwrap_or_default(),
1409+
);
1410+
} else if original_priority > EVM_TRANSACTION_BASE_PRIORITY {
1411+
log::warn!(
1412+
target: EVM_LOG_TARGET,
1413+
"EVM transaction priority from {:?} reduced from {} to {}; priority tips are ignored for ordering",
1414+
info,
1415+
original_priority,
1416+
EVM_TRANSACTION_BASE_PRIORITY,
1417+
);
1418+
}
1419+
}
1420+
}
1421+
13871422
impl fp_self_contained::SelfContainedCall for RuntimeCall {
13881423
type SignedInfo = H160;
13891424

@@ -1408,7 +1443,21 @@ impl fp_self_contained::SelfContainedCall for RuntimeCall {
14081443
len: usize,
14091444
) -> Option<TransactionValidity> {
14101445
match self {
1411-
RuntimeCall::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len),
1446+
RuntimeCall::Ethereum(call) => {
1447+
let priority_fee = match call {
1448+
pallet_ethereum::Call::transact { transaction } => match transaction {
1449+
EthereumTransaction::EIP1559(tx) => Some(tx.max_priority_fee_per_gas),
1450+
EthereumTransaction::EIP7702(tx) => Some(tx.max_priority_fee_per_gas),
1451+
_ => None,
1452+
},
1453+
_ => None,
1454+
};
1455+
1456+
let mut validity = call.validate_self_contained(info, dispatch_info, len);
1457+
adjust_evm_priority_and_warn(&mut validity, priority_fee, info);
1458+
1459+
validity
1460+
}
14121461
_ => None,
14131462
}
14141463
}
@@ -2619,6 +2668,52 @@ fn test_into_substrate_balance_zero_value() {
26192668
assert_eq!(result, Some(expected_substrate_balance));
26202669
}
26212670

2671+
#[test]
2672+
fn evm_priority_overrides_tip_to_base() {
2673+
let mut validity: Option<TransactionValidity> =
2674+
Some(Ok(sp_runtime::transaction_validity::ValidTransaction {
2675+
priority: 99,
2676+
requires: vec![],
2677+
provides: vec![],
2678+
longevity: sp_runtime::transaction_validity::TransactionLongevity::MAX,
2679+
propagate: true,
2680+
}));
2681+
2682+
let signer = H160::repeat_byte(1);
2683+
adjust_evm_priority_and_warn(&mut validity, Some(U256::from(10)), &signer);
2684+
2685+
let adjusted_priority = validity
2686+
.as_ref()
2687+
.and_then(|v| v.as_ref().ok())
2688+
.map(|v| v.priority);
2689+
2690+
assert_eq!(adjusted_priority, Some(EVM_TRANSACTION_BASE_PRIORITY));
2691+
}
2692+
2693+
#[test]
2694+
fn evm_priority_cannot_overtake_unstake() {
2695+
// Unstake is a normal-class extrinsic (priority = NORMAL_DISPATCH_BASE_PRIORITY).
2696+
let unstake_priority: TransactionPriority = NORMAL_DISPATCH_BASE_PRIORITY;
2697+
let evm_priority: TransactionPriority = EVM_TRANSACTION_BASE_PRIORITY;
2698+
2699+
// Clamp guarantees the EVM tx is never above the unstake priority.
2700+
assert!(evm_priority <= unstake_priority);
2701+
2702+
// If both arrive with equal priority, arrival order keeps unstake first.
2703+
let mut queue: Vec<(&str, TransactionPriority, usize)> = vec![
2704+
("unstake", unstake_priority, 0), // arrives first
2705+
("evm", evm_priority, 1), // arrives later
2706+
];
2707+
2708+
queue.sort_by(|a, b| {
2709+
b.1.cmp(&a.1) // higher priority first
2710+
.then_with(|| a.2.cmp(&b.2)) // earlier arrival first when equal
2711+
});
2712+
2713+
let first = queue.first().map(|entry| entry.0);
2714+
assert_eq!(first, Some("unstake"));
2715+
}
2716+
26222717
#[test]
26232718
fn test_into_evm_balance_valid() {
26242719
// Valid conversion from Substrate to EVM

0 commit comments

Comments
 (0)