Skip to content
Draft
Show file tree
Hide file tree
Changes from 11 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
36 changes: 23 additions & 13 deletions docs/concepts/emissions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -71,27 +71,37 @@ protocol-owned alpha.

## Subnet emission shares

Each block's TAO emission is divided in two clear steps. First, the chain turns
each eligible subnet's **EMA price** into a share of total demand:
Each block's TAO emission is divided in three steps. First, the chain turns each
eligible subnet's **EMA price** into a share of total demand:

```
demand_share_i = price_ema_i / Σ price_ema
```

`MinerBurned` does not change this cross-subnet share. It still records what
happened to miner incentive inside the subnet, but one subnet's local burn or
recycle choice does not change another subnet's emission.
Next, the chain scales that price share by `1 − MinerBurned` and renormalizes:

Second, the **emission gate** reduces weak demand before the final shares are
normalized. The gate has a midpoint called `theta`. By default, `theta` is the
32nd-highest positive demand share. It is normally recalculated every 360
blocks and stays fixed between updates. A subnet at the midpoint passes half
of its demand weight. Subnets well above it pass almost all; subnets well below
it pass much less:
```
burn_adjusted_share_i = demand_share_i × (1 − miner_burned_i)
/ Σ(demand_share × (1 − miner_burned))
```

`MinerBurned` is the proportion of the last tempo's miner incentive withheld
because it was directed to subnet-owner hotkeys. Withheld incentive counts
whether it is recycled or burned, so changing `RecycleOrBurn` cannot bypass the
scaling. If every adjusted weight is zero, the runtime restores the unadjusted
price shares so emission is not stranded.

Finally, the **emission gate** reduces weak burn-adjusted shares before the final
shares are normalized. The gate has a midpoint called `theta`. By default,
`theta` is the 32nd-highest positive adjusted share. It is normally recalculated
every 360 blocks and stays fixed between updates. A subnet at the midpoint
passes half of its adjusted weight. Subnets well above it pass almost all;
subnets well below it pass much less:

```
gate_i = 1 / (1 + (theta / demand_share_i)^h)
final_share_i = demand_share_i × gate_i / Σ(demand_share × gate)
gate_i = 1 / (1 + (theta / burn_adjusted_share_i)^h)
final_share_i = burn_adjusted_share_i × gate_i
/ Σ(burn_adjusted_share × gate)
```

The default exponent `h` is 3. This makes the gate gradual rather than a hard
Expand Down
6 changes: 3 additions & 3 deletions docs/guides/evm/precompile-design.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ interface IPrecompileRegistry {
}
```

In v444, the fields have the following behavior:
In v445, the fields have the following behavior:

| Field | Meaning |
|---|---|
Expand All @@ -346,7 +346,7 @@ In v444, the fields have the following behavior:
| `newSelector` | Reserved; always `0x00000000`. |
| `message` | Reserved; always empty. |

The `selector` parameter is also reserved and is not interpreted in v444. A
The `selector` parameter is also reserved and is not interpreted in v445. A
response therefore does not prove that a selector exists or describe its
lifecycle. Tooling must use the canonical Solidity interfaces and JSON ABIs to
discover supported selectors.
Expand Down Expand Up @@ -455,6 +455,6 @@ Precompiles are a long-lived contract between Subtensor and deployed EVM code.
Keep addresses and released selectors stable, version functions additively,
preserve old semantics whenever possible, and replace raw storage access with
typed views that insulate callers from storage layouts. Use deprecation to guide
migration and reversible disablement to handle operational risk. The v444
migration and reversible disablement to handle operational risk. The v445
registry reports whole-precompile disablement; function-level lifecycle status
remains a future extension.
4 changes: 2 additions & 2 deletions docs/guides/evm/precompiles/registry.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ description: Registry for precompile availability.
| Address | `0x0000000000000000000000000000000000000813` |
| Status | Deployed |

In v444, the registry reports the current operational availability of a whole
In v445, the registry reports the current operational availability of a whole
precompile through `isDisabled`. It does not report whether an individual
selector exists, is deprecated, or has a replacement.

Expand All @@ -35,7 +35,7 @@ interface IPrecompileRegistry {

The `selector` parameter and the `isDeprecated`, `newPrecompile`, `newSelector`,
and `message` result fields are reserved for future selector-lifecycle support.
In v444 those fields are not populated, and the selector is not interpreted.
In v445 those fields are not populated, and the selector is not interpreted.
Do not use this call to test whether a selector is supported; use the canonical
Solidity interfaces and JSON ABIs for selector discovery.

Expand Down
13 changes: 6 additions & 7 deletions docs/migration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -513,20 +513,19 @@ if not result.success:
The v11 package ships against a runtime that also changed behavior. These are
not rename mappings — scripts that still "work" can fail or mis-account after
the upgrade. See the release notes for
[V431](/releases/v431-upgrade) and [V444](/releases/v444-upgrade).
[V431](/releases/v431-upgrade) and [V445](/releases/v445-upgrade).

### Ownership and emissions

- **Subnet ownership.** For subnets at least one year old, ownership transfers
to the highest-conviction hotkey when total conviction reaches 10% of
`SubnetAlphaOut`. Existing conviction counts toward this threshold. See
[Conviction](/docs/guides/conviction).
- **Cross-subnet emissions.** V444 allocation uses each subnet's EMA price,
followed by the emission gate. `MinerBurned` no longer changes a subnet's
cross-network share. [`root_proportion`](/code/pallets/subtensor/src/coinbase/block_step.rs#L74-L84)
is also not part of the inter-subnet split; it still applies inside each
subnet for injection caps and root dividends. Update emission calculations
and forecasts. See [Emissions](/docs/concepts/emissions).
- **Cross-subnet emissions.** Allocation continues to use each subnet's EMA
price scaled by `1 − MinerBurned`, followed by the emission gate.
[`root_proportion`](/code/pallets/subtensor/src/coinbase/block_step.rs#L74-L84)
is not part of the inter-subnet split; it still applies inside each subnet
for injection caps and root dividends. See [Emissions](/docs/concepts/emissions).

### Proxies and coldkeys

Expand Down
9 changes: 8 additions & 1 deletion pallets/admin-utils/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -622,14 +622,21 @@ mod benchmarks {
#[benchmark]
fn sudo_set_sn_owner_hotkey() {
let netuid = NetUid::from(1);
let old_hotkey: T::AccountId = account("OldOwner", 0, 1);
let hotkey: T::AccountId = account("Alice", 0, 1);

pallet_subtensor::Pallet::<T>::init_new_network(
netuid, 1u16, // tempo
);
pallet_subtensor::SubnetOwnerHotkey::<T>::insert(netuid, &old_hotkey);

#[extrinsic_call]
_(RawOrigin::Root, netuid, hotkey);
_(RawOrigin::Root, netuid, hotkey.clone());

assert_eq!(
pallet_subtensor::SubnetOwnerHotkey::<T>::get(netuid),
hotkey
);
}

#[benchmark]
Expand Down
12 changes: 11 additions & 1 deletion pallets/admin-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1774,7 +1774,17 @@ pub mod pallet {
/// # Rate Limiting
/// This function is rate-limited to one call per subnet per interval (e.g., one week).
#[pallet::call_index(67)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::sudo_set_sn_owner_hotkey())]
#[pallet::weight({
let member_count =
pallet_subtensor::Pallet::<T>::owner_transition_member_count(*netuid, hotkey);
<T as pallet::Config>::WeightInfo::sudo_set_sn_owner_hotkey().saturating_add(
<<T as pallet_subtensor::Config>::WeightInfo as pallet_subtensor::weights::WeightInfo>::transition_subnet_owner_locks(
member_count,
).saturating_add(
pallet_subtensor::Pallet::<T>::owner_transition_member_count_weight(member_count),
),
)
})]
pub fn sudo_set_sn_owner_hotkey(
origin: OriginFor<T>,
netuid: NetUid,
Expand Down
69 changes: 67 additions & 2 deletions pallets/admin-utils/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ use frame_support::{
use frame_system::Config;
use pallet_subtensor::{
Error as SubtensorError, Event, MaxRegistrationsPerBlock, SubnetOwner,
TargetRegistrationsPerInterval, Tempo, WeightsVersionKeyRateLimit,
TargetRegistrationsPerInterval, Tempo, WeightsVersionKeyRateLimit, staking::lock::LockState,
subnets::mechanism::MAX_MECHANISM_COUNT_PER_SUBNET, utils::rate_limiting::TransactionType, *,
};
use sp_consensus_grandpa::AuthorityId as GrandpaId;
use sp_core::{Get, Pair, U256, ed25519};
use sp_runtime::PerU16;
use substrate_fixed::types::I96F32;
use substrate_fixed::types::{I96F32, U64F64};
use subtensor_runtime_common::{MechId, NetUid, TaoBalance, Token};
pub mod mock;
use mock::*;
Expand Down Expand Up @@ -2103,11 +2103,58 @@ fn test_set_sn_owner_hotkey_owner() {
fn test_set_sn_owner_hotkey_root() {
new_test_ext().execute_with(|| {
let netuid = NetUid::from(1);
let old_hotkey = U256::from(2);
let hotkey: U256 = U256::from(3);
add_network(netuid, 10);

let owner = U256::from(10);
let old_perpetual_coldkey = U256::from(11);
let old_decaying_coldkey = U256::from(12);
let new_perpetual_coldkey = U256::from(13);
let new_decaying_coldkey = U256::from(14);
pallet_subtensor::SubnetOwner::<Test>::insert(netuid, owner);
pallet_subtensor::SubnetOwnerHotkey::<Test>::insert(netuid, old_hotkey);
let now = SubtensorModule::get_current_block_as_u64();
let old_owner_lock = LockState {
locked_mass: 1_000u64.into(),
conviction: U64F64::from_num(1_000),
last_update: now,
};
let new_owner_lock = LockState {
locked_mass: 2_000u64.into(),
conviction: U64F64::from_num(2_000),
last_update: now,
};
pallet_subtensor::DecayingLock::<Test>::insert(old_perpetual_coldkey, netuid, false);
pallet_subtensor::DecayingLock::<Test>::insert(new_perpetual_coldkey, netuid, false);
SubtensorModule::insert_lock_state(
&old_perpetual_coldkey,
netuid,
&old_hotkey,
old_owner_lock.clone(),
);
SubtensorModule::insert_lock_state(
&old_decaying_coldkey,
netuid,
&old_hotkey,
old_owner_lock.clone(),
);
SubtensorModule::insert_lock_state(
&new_perpetual_coldkey,
netuid,
&hotkey,
new_owner_lock.clone(),
);
SubtensorModule::insert_lock_state(
&new_decaying_coldkey,
netuid,
&hotkey,
new_owner_lock.clone(),
);
SubtensorModule::insert_owner_lock_state(netuid, old_owner_lock.clone());
SubtensorModule::insert_decaying_owner_lock_state(netuid, old_owner_lock);
SubtensorModule::insert_hotkey_lock_state(netuid, &hotkey, new_owner_lock.clone());
SubtensorModule::insert_decaying_hotkey_lock_state(netuid, &hotkey, new_owner_lock);

// Root can set the hotkey
assert_ok!(AdminUtils::sudo_set_sn_owner_hotkey(
Expand All @@ -2119,6 +2166,24 @@ fn test_set_sn_owner_hotkey_root() {
// Check the value
let actual_hotkey = pallet_subtensor::SubnetOwnerHotkey::<Test>::get(netuid);
assert_eq!(actual_hotkey, hotkey);
assert_eq!(
pallet_subtensor::HotkeyLock::<Test>::get(netuid, old_hotkey)
.map(|lock| lock.locked_mass),
Some(1_000u64.into())
);
assert_eq!(
pallet_subtensor::DecayingHotkeyLock::<Test>::get(netuid, old_hotkey)
.map(|lock| lock.locked_mass),
Some(1_000u64.into())
);
assert_eq!(
pallet_subtensor::OwnerLock::<Test>::get(netuid).map(|lock| lock.locked_mass),
Some(2_000u64.into())
);
assert_eq!(
pallet_subtensor::DecayingOwnerLock::<Test>::get(netuid).map(|lock| lock.locked_mass),
Some(2_000u64.into())
);
});
}

Expand Down
Loading
Loading