Skip to content
Open
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
1,404 changes: 1,404 additions & 0 deletions contract-tests/.papi/contracts/bittensor.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions contract-tests/.papi/descriptors/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*
!.gitignore
!package.json
24 changes: 24 additions & 0 deletions contract-tests/.papi/descriptors/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"version": "0.1.0-autogenerated.15753407080843946085",
"name": "@polkadot-api/descriptors",
"files": [
"dist"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"module": "./dist/index.mjs",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./package.json": "./package.json"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"browser": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"sideEffects": false,
"peerDependencies": {
"polkadot-api": ">=1.21.0"
}
}
Binary file added contract-tests/.papi/metadata/devnet.scale
Binary file not shown.
15 changes: 15 additions & 0 deletions contract-tests/.papi/polkadot-api.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"version": 0,
"descriptorPath": ".papi/descriptors",
"entries": {
"devnet": {
"wsUrl": "ws://localhost:9944",
"metadata": ".papi/metadata/devnet.scale",
"genesis": "0x56c04ae0094e08e671ff781eb7122ffca041a55d208befc4603bd441b44e8e3a",
"codeHash": "0xf3fc94ee2cf40e8a3e48a4d88d4880d606a706312512f6e0317eba65e032a0d8"
}
},
"ink": {
"bittensor": ".papi/contracts/bittensor.json"
}
}
13 changes: 12 additions & 1 deletion pallets/subtensor/src/coinbase/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,22 @@ impl<T: Config> Pallet<T> {
Error::<T>::SubnetNotExists
);

let reservoir_tao = T::SwapInterface::protocol_tao_reservoir(netuid);
let reservoir_alpha = T::SwapInterface::protocol_alpha_reservoir(netuid);
T::SwapInterface::clear_protocol_liquidity_reservoirs(netuid);
Self::increase_provided_tao_reserve(netuid, reservoir_tao);
Self::increase_provided_alpha_reserve(netuid, reservoir_alpha);
if !reservoir_tao.is_zero() {
TotalStake::<T>::mutate(|total| {
*total = total.saturating_add(reservoir_tao);
});
}

Self::finalize_all_subnet_root_dividends(netuid);

// --- Perform the cleanup before removing the network.
Self::destroy_alpha_in_out_stakes(netuid)?;
T::SwapInterface::clear_protocol_liquidity(netuid)?;
T::SwapInterface::clear_protocol_liquidity(netuid);
T::CommitmentsInterface::purge_netuid(netuid);

// --- Remove the network
Expand Down
67 changes: 40 additions & 27 deletions pallets/subtensor/src/coinbase/run_coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,6 @@ impl<T: Config> Pallet<T> {
let tao_to_swap_with: TaoBalance =
tou64!(excess_tao.get(netuid_i).unwrap_or(&asfloat!(0))).into();

// Inject tao and alpha into protocol liquidity. In theorry, it may not always
// be a success (returned values are 0s) in case of high liquidity disbalance
let (actual_injected_tao, actual_injected_alpha) =
T::SwapInterface::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i);

// Clear per-block pool-side emission counters up front so a subnet
// disabled this block does not display stale values from an earlier block.
SubnetExcessTao::<T>::insert(*netuid_i, TaoBalance::ZERO);
Expand Down Expand Up @@ -130,38 +125,56 @@ impl<T: Config> Pallet<T> {
}
}

// Inject Alpha in.
SubnetAlphaInEmission::<T>::insert(*netuid_i, actual_injected_alpha);

// Mint alpha and resolve to alpha reserve
Self::resolve_to_alpha_in(Self::mint_alpha(*netuid_i, actual_injected_alpha));

// Inject TAO in.
if !actual_injected_tao.is_zero() {
match Self::spend_tao(&subnet_account_id, remaining_credit, actual_injected_tao)
{
// Materialize this block's TAO before updating balancer reservoir
// state. If spending fails, do not let the swap pallet consume
// reservoir state as if this block's TAO arrived.
let materialized_tao_delta = if tao_in_i.is_zero() {
TaoBalance::ZERO
} else {
match Self::spend_tao(&subnet_account_id, remaining_credit, tao_in_i) {
Ok(remainder) => {
remaining_credit = remainder;

SubnetTaoInEmission::<T>::insert(*netuid_i, actual_injected_tao);
SubnetTAO::<T>::mutate(*netuid_i, |total| {
*total = total.saturating_add(actual_injected_tao);
});
TotalStake::<T>::mutate(|total| {
*total = total.saturating_add(actual_injected_tao);
});

// Record emission injection as protocol inflow.
Self::record_protocol_inflow(*netuid_i, actual_injected_tao);
tao_in_i
}
Err(remainder) => {
remaining_credit = remainder;
let remaining_balance = remaining_credit.peek();
log::error!(
"Failed to spend credit: injected_tao = {actual_injected_tao:?}, netuid_i = {netuid_i:?}, remaining_balance = {remaining_balance:?}"
"Failed to spend credit: tao_delta = {tao_in_i:?}, netuid_i = {netuid_i:?}, remaining_balance = {remaining_balance:?}"
);
TaoBalance::ZERO
}
}
};

// Decide which current/reservoir liquidity can become price-active
// without pushing balancer weights out of range. Only already
// materialized current TAO is offered to the swap pallet.
let (price_active_tao, price_active_alpha) =
T::SwapInterface::adjust_protocol_liquidity(
*netuid_i,
materialized_tao_delta,
alpha_in_i,
);

// Materialize this block's alpha emission, then add only the
// price-active portion to the pool reserve. The price-active
// portion may include alpha that was materialized in an earlier
// block and held in the reservoir.
let _ = Self::mint_alpha(*netuid_i, alpha_in_i);
SubnetAlphaInEmission::<T>::insert(*netuid_i, price_active_alpha);
Self::increase_provided_alpha_reserve(*netuid_i, price_active_alpha);

// Add only the price-active TAO to the pool reserve. This may
// include TAO materialized in an earlier block and held in the
// reservoir.
if !price_active_tao.is_zero() {
SubnetTaoInEmission::<T>::insert(*netuid_i, price_active_tao);
Self::increase_provided_tao_reserve(*netuid_i, price_active_tao);
TotalStake::<T>::mutate(|total| {
*total = total.saturating_add(price_active_tao);
});
Self::record_protocol_inflow(*netuid_i, price_active_tao);
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion pallets/subtensor/src/staking/stake_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ impl<T: Config> Pallet<T> {
/// # Returns
/// * `u64` - The total alpha issuance for the specified subnet.
pub fn get_alpha_issuance(netuid: NetUid) -> AlphaBalance {
SubnetAlphaIn::<T>::get(netuid).saturating_add(SubnetAlphaOut::<T>::get(netuid))
SubnetAlphaIn::<T>::get(netuid)
.saturating_add(SubnetAlphaOut::<T>::get(netuid))
.saturating_add(T::SwapInterface::protocol_alpha_reservoir(netuid))
}

pub fn get_moving_alpha_price(netuid: NetUid) -> U64F64 {
Expand Down
64 changes: 64 additions & 0 deletions pallets/subtensor/src/tests/coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3701,6 +3701,70 @@ fn test_coinbase_inject_and_maybe_swap_does_not_skew_reserves() {
});
}

#[test]
fn test_coinbase_failed_tao_materialization_does_not_activate_current_tao() {
new_test_ext(1).execute_with(|| {
let netuid = add_dynamic_network(&U256::from(1), &U256::from(2));
let initial_reserve = TaoBalance::from(1_000_000_u64);
let reservoir_tao = TaoBalance::from(100_u64);
let current_tao = TaoBalance::from(200_u64);
let current_alpha = AlphaBalance::from(100_u64);

mock::setup_reserves(netuid, initial_reserve, AlphaBalance::from(1_000_000_u64));
Swap::maybe_initialize_palswap(netuid, None);
pallet_subtensor_swap::BalancerTaoReservoir::<Test>::insert(netuid, reservoir_tao);

let tao_in = BTreeMap::from([(netuid, U96F32::saturating_from_num(current_tao))]);
let alpha_in = BTreeMap::from([(netuid, U96F32::saturating_from_num(current_alpha))]);
let excess_tao = BTreeMap::new();
let credit = SubtensorModule::mint_tao(TaoBalance::ZERO);

SubtensorModule::inject_and_maybe_swap(&[netuid], &tao_in, &alpha_in, &excess_tao, credit);

assert_eq!(
SubnetTAO::<Test>::get(netuid),
initial_reserve.saturating_add(reservoir_tao)
);
assert_eq!(SubnetTaoInEmission::<Test>::get(netuid), reservoir_tao);
assert_eq!(
SubnetProtocolFlow::<Test>::get(netuid),
reservoir_tao.to_u64() as i64
);
assert_eq!(
pallet_subtensor_swap::BalancerTaoReservoir::<Test>::get(netuid),
TaoBalance::ZERO
);
});
}

#[test]
fn test_alpha_reservoir_counts_toward_subnet_issuance_across_blocks() {
new_test_ext(1).execute_with(|| {
let netuid = add_dynamic_network(&U256::from(1), &U256::from(2));
let alpha_in = AlphaBalance::from(10_000_u64);
let alpha_out = AlphaBalance::from(20_000_u64);
let reservoir_alpha = AlphaBalance::from(30_000_u64);

SubnetAlphaIn::<Test>::insert(netuid, alpha_in);
SubnetAlphaOut::<Test>::insert(netuid, alpha_out);
pallet_subtensor_swap::BalancerAlphaReservoir::<Test>::insert(netuid, reservoir_alpha);

let expected = alpha_in
.saturating_add(alpha_out)
.saturating_add(reservoir_alpha);
assert_eq!(SubtensorModule::get_alpha_issuance(netuid), expected);

System::set_block_number(System::block_number().saturating_add(1));

assert_eq!(SubnetAlphaIn::<Test>::get(netuid), alpha_in);
assert_eq!(
pallet_subtensor_swap::BalancerAlphaReservoir::<Test>::get(netuid),
reservoir_alpha
);
assert_eq!(SubtensorModule::get_alpha_issuance(netuid), expected);
});
}

#[test]
fn test_coinbase_drain_pending_increments_blockssincelaststep() {
new_test_ext(1).execute_with(|| {
Expand Down
54 changes: 54 additions & 0 deletions pallets/subtensor/src/tests/networks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,8 @@ fn dissolve_clears_all_per_subnet_storages() {
assert!(!SubnetAlphaOutEmission::<Test>::contains_key(net));
assert!(!SubnetTaoInEmission::<Test>::contains_key(net));
assert!(!SubnetVolume::<Test>::contains_key(net));
assert!(!pallet_subtensor_swap::BalancerTaoReservoir::<Test>::contains_key(net));
assert!(!pallet_subtensor_swap::BalancerAlphaReservoir::<Test>::contains_key(net));

// TAO Flow
assert!(!SubnetTaoFlow::<Test>::contains_key(net));
Expand Down Expand Up @@ -643,6 +645,58 @@ fn dissolve_clears_all_per_subnet_storages() {
});
}

#[test]
fn dissolve_materializes_nonzero_protocol_reservoirs_before_cleanup() {
new_test_ext(0).execute_with(|| {
let owner_cold = U256::from(123);
let owner_hot = U256::from(456);
let net = add_dynamic_network(&owner_hot, &owner_cold);
remove_owner_registration_stake(net);

// Force the modern dissolve branch where pool alpha participates in
// the protocol denominator.
TaoInRefundDeploymentBlock::<Test>::put(0);
NetworkRegisteredAt::<Test>::insert(net, 1);

let reservoir_tao = TaoBalance::from(100_u64);
let reservoir_alpha = AlphaBalance::from(100_u64);
let staker_hot = U256::from(789);
let staker_cold = U256::from(987);

let subnet_account = SubtensorModule::get_subnet_account_id(net).unwrap();
add_balance_to_coldkey_account(&subnet_account, reservoir_tao);

SubnetTAO::<Test>::insert(net, TaoBalance::ZERO);
SubtensorModule::set_subnet_locked_balance(net, TaoBalance::ZERO);
SubnetAlphaIn::<Test>::insert(net, AlphaBalance::ZERO);
SubnetProtocolAlpha::<Test>::insert(net, AlphaBalance::ZERO);
AlphaV2::<Test>::insert((staker_hot, staker_cold, net), sf_from_u64(100u64));
TotalHotkeyAlpha::<Test>::insert(staker_hot, net, AlphaBalance::from(100u64));
pallet_subtensor_swap::BalancerTaoReservoir::<Test>::insert(net, reservoir_tao);
pallet_subtensor_swap::BalancerAlphaReservoir::<Test>::insert(net, reservoir_alpha);

let staker_before = SubtensorModule::get_coldkey_balance(&staker_cold);
let issuance_before = TotalIssuance::<Test>::get();

assert_ok!(SubtensorModule::do_dissolve_network(net));

// Reservoir alpha is treated like materialized protocol pool alpha.
// The staker owns half the denominator, so receives half the reservoir
// TAO pot; the protocol share is recycled.
assert_eq!(
SubtensorModule::get_coldkey_balance(&staker_cold),
staker_before + TaoBalance::from(50_u64)
);
assert!(TotalIssuance::<Test>::get() < issuance_before);
assert!(!NetworksAdded::<Test>::contains_key(net));
assert!(!SubnetOwner::<Test>::contains_key(net));
assert!(!SubnetAlphaIn::<Test>::contains_key(net));
assert!(!SubnetProtocolAlpha::<Test>::contains_key(net));
assert!(!pallet_subtensor_swap::BalancerTaoReservoir::<Test>::contains_key(net));
assert!(!pallet_subtensor_swap::BalancerAlphaReservoir::<Test>::contains_key(net));
});
}

#[test]
fn dissolve_alpha_out_but_zero_tao_no_rewards() {
new_test_ext(0).execute_with(|| {
Expand Down
8 changes: 2 additions & 6 deletions pallets/subtensor/src/tests/staking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4279,12 +4279,8 @@ fn test_move_stake_limit_partial() {

// Registration now goes through the burn/swap path, which initializes swap V3 state.
// Clear that state first so the manual reserve fixture below actually controls price.
assert_ok!(
<Test as pallet::Config>::SwapInterface::clear_protocol_liquidity(origin_netuid)
);
assert_ok!(
<Test as pallet::Config>::SwapInterface::clear_protocol_liquidity(destination_netuid)
);
<Test as pallet::Config>::SwapInterface::clear_protocol_liquidity(origin_netuid);
<Test as pallet::Config>::SwapInterface::clear_protocol_liquidity(destination_netuid);

// Force-set alpha in and tao reserve to make price equal 1.5 on both origin and destination,
// but there's much more liquidity on destination, so its price wouldn't go up when restaked.
Expand Down
Loading
Loading