Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions pallets/subtensor/src/extensions/subtensor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use sp_runtime::traits::{
AsSystemOriginSigner, DispatchInfoOf, Dispatchable, Implication, TransactionExtension,
ValidateResult,
};
use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError};
use sp_runtime::{
impl_tx_ext_default,
transaction_validity::{TransactionSource, TransactionValidity, ValidTransaction},
Expand All @@ -17,8 +16,6 @@ use sp_std::vec::Vec;
use subtensor_macros::freeze_struct;
use subtensor_runtime_common::{CustomTransactionError, NetUid, NetUidStorageIndex};

const ADD_STAKE_BURN_PRIORITY_BOOST: u64 = 100;

type CallOf<T> = <T as frame_system::Config>::RuntimeCall;
type OriginOf<T> = <T as frame_system::Config>::RuntimeOrigin;

Expand Down Expand Up @@ -262,13 +259,6 @@ where
.map_err(|_| CustomTransactionError::EvmKeyAssociateRateLimitExceeded)?;
Ok((Default::default(), (), origin))
}
Some(Call::add_stake_burn { netuid, .. }) => {
Pallet::<T>::ensure_subnet_owner(origin.clone(), *netuid).map_err(|_| {
TransactionValidityError::Invalid(InvalidTransaction::BadSigner)
})?;

Ok((Self::validity_ok(ADD_STAKE_BURN_PRIORITY_BOOST), (), origin))
}
_ => Ok((Default::default(), (), origin)),
}
}
Expand Down
2 changes: 2 additions & 0 deletions pallets/subtensor/src/macros/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ mod hooks {
.saturating_add(migrations::migrate_rate_limiting_last_blocks::migrate_obsolete_rate_limiting_last_blocks_storage::<T>())
// Re-encode rate limit keys after introducing OwnerHyperparamUpdate variant
.saturating_add(migrations::migrate_rate_limit_keys::migrate_rate_limit_keys::<T>())
// Remove AddStakeBurn entries from LastRateLimitedBlock
.saturating_add(migrations::migrate_remove_add_stake_burn_rate_limit::migrate_remove_add_stake_burn_rate_limit::<T>())
// Migrate remove network modality
.saturating_add(migrations::migrate_remove_network_modality::migrate_remove_network_modality::<T>())
// Migrate Immunity Period
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use alloc::string::String;
use alloc::vec::Vec;
use frame_support::{traits::Get, weights::Weight};

use crate::{Config, HasMigrationRun, LastRateLimitedBlock, RateLimitKey};

const MIGRATION_NAME: &[u8] = b"migrate_remove_add_stake_burn_rate_limit";

pub fn migrate_remove_add_stake_burn_rate_limit<T: Config>() -> Weight {
let mut weight = T::DbWeight::get().reads(1);

if HasMigrationRun::<T>::get(MIGRATION_NAME) {
log::info!(
"Migration '{}' already executed - skipping",
String::from_utf8_lossy(MIGRATION_NAME)
);
return weight;
}

log::info!(
"Running migration '{}'",
String::from_utf8_lossy(MIGRATION_NAME)
);

let mut scanned_count = 0u64;
let keys_to_remove = LastRateLimitedBlock::<T>::iter_keys()
.filter_map(|key| {
scanned_count = scanned_count.saturating_add(1);
matches!(key, RateLimitKey::AddStakeBurn(_)).then_some(key)
})
.collect::<Vec<_>>();
let removed_count = keys_to_remove.len() as u64;

weight = weight.saturating_add(T::DbWeight::get().reads(scanned_count));

for key in &keys_to_remove {
LastRateLimitedBlock::<T>::remove(key);
}

weight = weight.saturating_add(T::DbWeight::get().writes(removed_count));

HasMigrationRun::<T>::insert(MIGRATION_NAME, true);
weight = weight.saturating_add(T::DbWeight::get().writes(1));

log::info!(
"Migration '{}' completed. scanned_entries={}, removed_add_stake_burn_entries={}",
String::from_utf8_lossy(MIGRATION_NAME),
scanned_count,
removed_count
);

weight
}
1 change: 1 addition & 0 deletions pallets/subtensor/src/migrations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub mod migrate_populate_owned_hotkeys;
pub mod migrate_rao;
pub mod migrate_rate_limit_keys;
pub mod migrate_rate_limiting_last_blocks;
pub mod migrate_remove_add_stake_burn_rate_limit;
pub mod migrate_remove_commitments_rate_limit;
pub mod migrate_remove_network_modality;
pub mod migrate_remove_old_identity_maps;
Expand Down
41 changes: 6 additions & 35 deletions pallets/subtensor/src/staking/recycle_alpha.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use super::*;
use crate::{Error, system::ensure_signed};
use frame_support::storage::{TransactionOutcome, transactional};
use subtensor_runtime_common::{AlphaBalance, NetUid};

impl<T: Config> Pallet<T> {
Expand Down Expand Up @@ -125,24 +124,14 @@ impl<T: Config> Pallet<T> {

Ok(amount)
}

pub(crate) fn do_add_stake_burn(
origin: OriginFor<T>,
hotkey: T::AccountId,
netuid: NetUid,
amount: TaoBalance,
limit: Option<TaoBalance>,
) -> DispatchResult {
Self::ensure_subnet_owner(origin.clone(), netuid)?;

let current_block = Self::get_current_block_as_u64();
let last_block = Self::get_rate_limited_last_block(&RateLimitKey::AddStakeBurn(netuid));
let rate_limit = TransactionType::AddStakeBurn.rate_limit_on_subnet::<T>(netuid);

ensure!(
last_block.is_zero() || current_block.saturating_sub(last_block) >= rate_limit,
Error::<T>::AddStakeBurnRateLimitExceeded
);

let alpha = if let Some(limit) = limit {
Self::do_add_stake_limit(origin.clone(), hotkey.clone(), netuid, amount, limit, false)?
} else {
Expand All @@ -151,8 +140,6 @@ impl<T: Config> Pallet<T> {

Self::do_burn_alpha(origin, hotkey.clone(), alpha, netuid)?;

Self::set_rate_limited_last_block(&RateLimitKey::AddStakeBurn(netuid), current_block);

Self::deposit_event(Event::AddStakeBurn {
netuid,
hotkey,
Expand All @@ -173,36 +160,20 @@ impl<T: Config> Pallet<T> {
netuid: NetUid,
amount: TaoBalance,
) -> Result<AlphaBalance, DispatchError> {
transactional::with_transaction(|| {
let alpha = match Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount) {
Ok(a) => a,
Err(e) => return TransactionOutcome::Rollback(Err(e)),
};
match Self::do_recycle_alpha(origin, hotkey, alpha, netuid) {
Ok(real_alpha) => TransactionOutcome::Commit(Ok(real_alpha)),
Err(e) => TransactionOutcome::Rollback(Err(e)),
}
})
let alpha = Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount)?;
Self::do_recycle_alpha(origin, hotkey, alpha, netuid)
}

/// Atomically stakes TAO and burns the resulting alpha. Permissionless
/// counterpart to `do_add_stake_burn`: no subnet-owner guard and no rate
/// counterpart to `do_add_stake_burn`: return the amount of alpha burned.
/// limit. Used by the chain extension.
pub fn do_add_stake_burn_permissionless(
origin: OriginFor<T>,
hotkey: T::AccountId,
netuid: NetUid,
amount: TaoBalance,
) -> Result<AlphaBalance, DispatchError> {
transactional::with_transaction(|| {
let alpha = match Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount) {
Ok(a) => a,
Err(e) => return TransactionOutcome::Rollback(Err(e)),
};
match Self::do_burn_alpha(origin, hotkey, alpha, netuid) {
Ok(real_alpha) => TransactionOutcome::Commit(Ok(real_alpha)),
Err(e) => TransactionOutcome::Rollback(Err(e)),
}
})
let alpha = Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount)?;
Self::do_burn_alpha(origin, hotkey, alpha, netuid)
}
}
39 changes: 39 additions & 0 deletions pallets/subtensor/src/tests/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1123,6 +1123,45 @@ fn test_migrate_rate_limit_keys() {
});
}

#[test]
fn test_migrate_remove_add_stake_burn_rate_limit() {
new_test_ext(1).execute_with(|| {
const MIGRATION_NAME: &[u8] = b"migrate_remove_add_stake_burn_rate_limit";
let netuid = NetUid::from(1);
let other_netuid = NetUid::from(2);
let preserved_netuid = NetUid::from(3);
let add_stake_burn_key = RateLimitKey::AddStakeBurn(netuid);
let other_add_stake_burn_key = RateLimitKey::AddStakeBurn(other_netuid);
let preserved_key = RateLimitKey::SetSNOwnerHotkey(preserved_netuid);

SubtensorModule::set_rate_limited_last_block(&add_stake_burn_key, 100);
SubtensorModule::set_rate_limited_last_block(&other_add_stake_burn_key, 200);
SubtensorModule::set_rate_limited_last_block(&preserved_key, 300);

let weight =
crate::migrations::migrate_remove_add_stake_burn_rate_limit::migrate_remove_add_stake_burn_rate_limit::<Test>();

assert!(
HasMigrationRun::<Test>::get(MIGRATION_NAME.to_vec()),
"Migration should be marked as executed"
);
assert!(!weight.is_zero(), "Migration weight should be non-zero");

assert_eq!(
SubtensorModule::get_rate_limited_last_block(&add_stake_burn_key),
0
);
assert_eq!(
SubtensorModule::get_rate_limited_last_block(&other_add_stake_burn_key),
0
);
assert_eq!(
SubtensorModule::get_rate_limited_last_block(&preserved_key),
300
);
});
}

#[test]
fn test_migrate_fix_staking_hot_keys() {
new_test_ext(1).execute_with(|| {
Expand Down
98 changes: 24 additions & 74 deletions pallets/subtensor/src/tests/recycle_alpha.rs
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ fn test_add_stake_burn_with_limit_success() {
}

#[test]
fn test_add_stake_burn_non_owner_fails() {
fn test_add_stake_burn_non_owner_succeeds() {
new_test_ext(1).execute_with(|| {
let hotkey_account_id = U256::from(1);
let coldkey_account_id = U256::from(2);
Expand All @@ -793,17 +793,30 @@ fn test_add_stake_burn_non_owner_fails() {
// Give non-owner some balance
add_balance_to_coldkey_account(&non_owner_coldkey, amount.into());

// Non-owner trying to call add_stake_burn should fail with BadOrigin
assert_noop!(
SubtensorModule::add_stake_burn(
RuntimeOrigin::signed(non_owner_coldkey),
hotkey_account_id,
netuid,
amount.into(),
None,
// Any signed origin can atomically stake and burn.
assert_ok!(SubtensorModule::add_stake_burn(
RuntimeOrigin::signed(non_owner_coldkey),
hotkey_account_id,
netuid,
amount.into(),
None,
));

assert_eq!(
SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(
&hotkey_account_id,
&non_owner_coldkey,
netuid
),
DispatchError::BadOrigin
AlphaBalance::ZERO
);

assert!(System::events().iter().any(|e| {
matches!(
&e.event,
RuntimeEvent::SubtensorModule(Event::AddStakeBurn { .. })
)
}));
});
}

Expand All @@ -827,7 +840,7 @@ fn test_add_stake_burn_nonexistent_subnet_fails() {
amount.into(),
None,
),
DispatchError::BadOrigin
Error::<Test>::SubnetNotExists
);
});
}
Expand Down Expand Up @@ -861,66 +874,3 @@ fn test_add_stake_burn_insufficient_balance_fails() {
);
});
}

#[test]
fn test_add_stake_burn_rate_limit_exceeded() {
new_test_ext(1).execute_with(|| {
let hotkey_account_id = U256::from(533453);
let coldkey_account_id = U256::from(55453);
let amount: u64 = 10_000_000_000; // 10 TAO

// Add network
let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id);

// Setup reserves with large liquidity
let tao_reserve = TaoBalance::from(1_000_000_000_000_u64);
let alpha_in = AlphaBalance::from(1_000_000_000_000_u64);
mock::setup_reserves(netuid, tao_reserve, alpha_in);

// Give coldkey sufficient balance for multiple "add stake and burn" operations.
add_balance_to_coldkey_account(&coldkey_account_id, (amount * 10).into());

assert_eq!(
SubtensorModule::get_rate_limited_last_block(&RateLimitKey::AddStakeBurn(netuid)),
0
);

// First "add stake and burn" should succeed
assert_ok!(SubtensorModule::add_stake_burn(
RuntimeOrigin::signed(coldkey_account_id),
hotkey_account_id,
netuid,
amount.into(),
None,
));

assert_eq!(
SubtensorModule::get_rate_limited_last_block(&RateLimitKey::AddStakeBurn(netuid)),
SubtensorModule::get_current_block_as_u64()
);

// Second "add stake and burn" immediately after should fail due to rate limit
assert_noop!(
SubtensorModule::add_stake_burn(
RuntimeOrigin::signed(coldkey_account_id),
hotkey_account_id,
netuid,
amount.into(),
None,
),
Error::<Test>::AddStakeBurnRateLimitExceeded
);

// After stepping past the rate limit, "add stake and burn" should succeed again
let rate_limit = TransactionType::AddStakeBurn.rate_limit_on_subnet::<Test>(netuid);
step_block(rate_limit as u16);

assert_ok!(SubtensorModule::add_stake_burn(
RuntimeOrigin::signed(coldkey_account_id),
hotkey_account_id,
netuid,
amount.into(),
None,
));
});
}
Loading
Loading