Skip to content
Merged

Ckburn #1996

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
13 changes: 13 additions & 0 deletions evm-tests/src/contracts/alpha.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,5 +315,18 @@ export const IAlphaABI = [
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getCKBurn",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
]
19 changes: 19 additions & 0 deletions evm-tests/test/alpha.precompile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { PublicClient } from "viem";
import { PolkadotSigner, TypedApi } from "polkadot-api";
import { toViemAddress, convertPublicKeyToSs58 } from "../src/address-utils"
import { IAlphaABI, IALPHA_ADDRESS } from "../src/contracts/alpha"
import { u64 } from "@polkadot-api/substrate-bindings";

describe("Test Alpha Precompile", () => {
// init substrate part
Expand Down Expand Up @@ -209,6 +210,24 @@ describe("Test Alpha Precompile", () => {
assert.ok(typeof alphaIssuance === 'bigint', "Alpha issuance should be a bigint");
assert.ok(alphaIssuance >= BigInt(0), "Alpha issuance should be non-negative");
});

it("getCKBurn returns valid CK burn rate", async () => {
const ckBurn = await publicClient.readContract({
abi: IAlphaABI,
address: toViemAddress(IALPHA_ADDRESS),
functionName: "getCKBurn",
args: []
})

const ckBurnOnChain = await api.query.SubtensorModule.CKBurn.getValue()

assert.strictEqual(ckBurn, ckBurnOnChain, "CK burn should match on chain");
assert.ok(ckBurn !== undefined, "CK burn should be defined");
const ckBurnPercentage = BigInt(ckBurn) * BigInt(100) / BigInt(2 ** 64 - 1)
assert.ok(ckBurnPercentage >= BigInt(0), "CK burn percentage should be non-negative");
assert.ok(ckBurnPercentage <= BigInt(100), "CK burn percentage should be less than or equal to 100");
assert.ok(typeof ckBurn === 'bigint', "CK burn should be a bigint");
});
});

describe("Global Functions", () => {
Expand Down
14 changes: 14 additions & 0 deletions pallets/admin-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,20 @@ pub mod pallet {
pallet_subtensor::Pallet::<T>::set_owner_immune_neuron_limit(netuid, immune_neurons)?;
Ok(())
}

/// Sets the childkey burn for a subnet.
/// It is only callable by the root account.
/// The extrinsic will call the Subtensor pallet to set the childkey burn.
#[pallet::call_index(73)]
#[pallet::weight(Weight::from_parts(15_650_000, 0)
.saturating_add(<T as frame_system::Config>::DbWeight::get().reads(1_u64))
.saturating_add(<T as frame_system::Config>::DbWeight::get().writes(1_u64)))]
pub fn sudo_set_ck_burn(origin: OriginFor<T>, burn: u64) -> DispatchResult {
ensure_root(origin)?;
pallet_subtensor::Pallet::<T>::set_ck_burn(burn);
log::debug!("CKBurnSet( burn: {burn:?} ) ");
Ok(())
}
}
}

Expand Down
42 changes: 23 additions & 19 deletions pallets/subtensor/src/coinbase/run_coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -744,18 +744,19 @@ impl<T: Config> Pallet<T> {
// Calculate the hotkey's share of the validator emission based on its childkey take
let validating_emission: U96F32 = U96F32::saturating_from_num(dividends);
let mut remaining_emission: U96F32 = validating_emission;
let childkey_take_proportion: U96F32 =
let burn_take_proportion: U96F32 = Self::get_ck_burn();
let child_take_proportion: U96F32 =
U96F32::saturating_from_num(Self::get_childkey_take(hotkey, netuid))
.safe_div(U96F32::saturating_from_num(u16::MAX));
log::debug!("Childkey take proportion: {childkey_take_proportion:?} for hotkey {hotkey:?}");
log::debug!("Childkey take proportion: {child_take_proportion:?} for hotkey {hotkey:?}");
// NOTE: Only the validation emission should be split amongst parents.

// Grab the owner of the childkey.
let childkey_owner = Self::get_owning_coldkey_for_hotkey(hotkey);

// Initialize variables to track emission distribution
let mut to_parents: u64 = 0;
let mut total_child_emission_take: U96F32 = U96F32::saturating_from_num(0);
let mut total_child_take: U96F32 = U96F32::saturating_from_num(0);

// Initialize variables to calculate total stakes from parents
let mut total_contribution: U96F32 = U96F32::saturating_from_num(0);
Expand Down Expand Up @@ -819,23 +820,26 @@ impl<T: Config> Pallet<T> {
remaining_emission = remaining_emission.saturating_sub(parent_emission);

// Get the childkey take for this parent.
let child_emission_take: U96F32 = if parent_owner == childkey_owner {
// The parent is from the same coldkey, so we don't remove any childkey take.
U96F32::saturating_from_num(0)
} else {
childkey_take_proportion
.saturating_mul(U96F32::saturating_from_num(parent_emission))
let mut burn_take: U96F32 = U96F32::saturating_from_num(0);
let mut child_take: U96F32 = U96F32::saturating_from_num(0);
if parent_owner != childkey_owner {
// The parent is from a different coldkey, we burn some proportion
burn_take = burn_take_proportion.saturating_mul(parent_emission);
child_take = child_take_proportion.saturating_mul(parent_emission);
parent_emission = parent_emission.saturating_sub(burn_take);
parent_emission = parent_emission.saturating_sub(child_take);
total_child_take = total_child_take.saturating_add(child_take);

Self::burn_subnet_alpha(
netuid,
AlphaCurrency::from(burn_take.saturating_to_num::<u64>()),
);
};
log::debug!("burn_takee: {burn_take:?} for hotkey {hotkey:?}");
log::debug!("child_take: {child_take:?} for hotkey {hotkey:?}");
log::debug!("parent_emission: {parent_emission:?} for hotkey {hotkey:?}");
log::debug!("total_child_take: {total_child_take:?} for hotkey {hotkey:?}");

// Remove the childkey take from the parent's emission.
parent_emission = parent_emission.saturating_sub(child_emission_take);

// Add the childkey take to the total childkey take tracker.
total_child_emission_take =
total_child_emission_take.saturating_add(child_emission_take);

log::debug!("Child emission take: {child_emission_take:?} for hotkey {hotkey:?}");
log::debug!("Parent emission: {parent_emission:?} for hotkey {hotkey:?}");
log::debug!("remaining emission: {remaining_emission:?}");

// Add the parent's emission to the distribution list
Expand All @@ -853,7 +857,7 @@ impl<T: Config> Pallet<T> {
// Calculate the final emission for the hotkey itself.
// This includes the take left from the parents and the self contribution.
let child_emission = remaining_emission
.saturating_add(total_child_emission_take)
.saturating_add(total_child_take)
.saturating_to_num::<u64>()
.into();

Expand Down
9 changes: 9 additions & 0 deletions pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,12 @@ pub mod pallet {
50400
}

#[pallet::type_value]
/// Default value for ck burn, 18%.
pub fn DefaultCKBurn<T: Config>() -> u64 {
u64::MAX / 100 * 18
}

#[pallet::storage]
pub type MinActivityCutoff<T: Config> =
StorageValue<_, u16, ValueQuery, DefaultMinActivityCutoff<T>>;
Expand Down Expand Up @@ -920,6 +926,9 @@ pub mod pallet {
/// --- ITEM --> Global weight
pub type TaoWeight<T> = StorageValue<_, u64, ValueQuery, DefaultTaoWeight<T>>;
#[pallet::storage]
/// --- ITEM --> CK burn
pub type CKBurn<T> = StorageValue<_, u64, ValueQuery, DefaultCKBurn<T>>;
#[pallet::storage]
/// --- ITEM ( default_delegate_take )
pub type MaxDelegateTake<T> = StorageValue<_, u16, ValueQuery, DefaultDelegateTake<T>>;
#[pallet::storage]
Expand Down
6 changes: 6 additions & 0 deletions pallets/subtensor/src/staking/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,4 +321,10 @@ impl<T: Config> Pallet<T> {
pub fn is_user_liquidity_enabled(netuid: NetUid) -> bool {
T::SwapInterface::is_user_liquidity_enabled(netuid)
}

pub fn burn_subnet_alpha(netuid: NetUid, amount: AlphaCurrency) {
SubnetAlphaOut::<T>::mutate(netuid, |total| {
*total = total.saturating_sub(amount);
});
}
}
10 changes: 10 additions & 0 deletions pallets/subtensor/src/staking/stake_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ impl<T: Config> Pallet<T> {
// This ensures the result is always between 0 and 1
weight_fixed.safe_div(U96F32::saturating_from_num(u64::MAX))
}
pub fn get_ck_burn() -> U96F32 {
let stored_weight = CKBurn::<T>::get();
let weight_fixed = U96F32::saturating_from_num(stored_weight);
weight_fixed.safe_div(U96F32::saturating_from_num(u64::MAX))
}

/// Sets the global global weight in storage.
///
Expand All @@ -117,6 +122,11 @@ impl<T: Config> Pallet<T> {
// Update the TaoWeight storage with the new weight value
TaoWeight::<T>::set(weight);
}
// Set the amount burned on non owned CK
pub fn set_ck_burn(weight: u64) {
// Update the ck burn value.
CKBurn::<T>::set(weight);
}

/// Calculates the weighted combination of alpha and global tao for a single hotkey onet a subnet.
///
Expand Down
5 changes: 5 additions & 0 deletions pallets/subtensor/src/tests/children.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2861,6 +2861,7 @@ fn test_childkey_take_drain() {

// Add network, register hotkeys, and setup network parameters
add_network(netuid, subnet_tempo, 0);
SubtensorModule::set_ck_burn(0);
mock::setup_reserves(netuid, (stake * 10_000).into(), (stake * 10_000).into());
register_ok_neuron(netuid, child_hotkey, child_coldkey, 0);
register_ok_neuron(netuid, parent_hotkey, parent_coldkey, 1);
Expand Down Expand Up @@ -2980,6 +2981,7 @@ fn test_parent_child_chain_emission() {
let subnet_owner_coldkey = U256::from(1001);
let subnet_owner_hotkey = U256::from(1002);
let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey);
SubtensorModule::set_ck_burn(0);
Tempo::<Test>::insert(netuid, 1);

// Setup large LPs to prevent slippage
Expand Down Expand Up @@ -3192,6 +3194,7 @@ fn test_parent_child_chain_epoch() {
new_test_ext(1).execute_with(|| {
let netuid = NetUid::from(1);
add_network(netuid, 1, 0);
SubtensorModule::set_ck_burn(0);
// Set owner cut to 0
SubtensorModule::set_subnet_owner_cut(0_u16);

Expand Down Expand Up @@ -3336,6 +3339,7 @@ fn test_dividend_distribution_with_children() {
new_test_ext(1).execute_with(|| {
let netuid = NetUid::from(1);
add_network(netuid, 1, 0);
SubtensorModule::set_ck_burn(0);
mock::setup_reserves(
netuid,
1_000_000_000_000_000.into(),
Expand Down Expand Up @@ -3570,6 +3574,7 @@ fn test_dividend_distribution_with_children() {
fn test_dynamic_parent_child_relationships() {
new_test_ext(1).execute_with(|| {
let netuid = NetUid::from(1);
SubtensorModule::set_ck_burn(0);
add_network_disable_commit_reveal(netuid, 1, 0);

// Define hotkeys and coldkeys
Expand Down
73 changes: 73 additions & 0 deletions pallets/subtensor/src/tests/coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,7 @@ fn test_drain_alpha_childkey_parentkey() {
new_test_ext(1).execute_with(|| {
let netuid = NetUid::from(1);
add_network(netuid, 1, 0);
SubtensorModule::set_ck_burn(0);
let parent = U256::from(1);
let child = U256::from(2);
let coldkey = U256::from(3);
Expand Down Expand Up @@ -1238,6 +1239,7 @@ fn test_get_root_children_drain() {
let alpha = NetUid::from(1);
add_network(NetUid::ROOT, 1, 0);
add_network(alpha, 1, 0);
SubtensorModule::set_ck_burn(0);
// Set TAO weight to 1.
SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.
// Create keys.
Expand Down Expand Up @@ -1399,6 +1401,7 @@ fn test_get_root_children_drain_half_proportion() {
let alpha = NetUid::from(1);
add_network(NetUid::ROOT, 1, 0);
add_network(alpha, 1, 0);
SubtensorModule::set_ck_burn(0);
// Set TAO weight to 1.
SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.
// Create keys.
Expand Down Expand Up @@ -1576,6 +1579,7 @@ fn test_get_root_children_drain_with_half_take() {
add_network(alpha, 1, 0);
// Set TAO weight to 1.
SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.
SubtensorModule::set_ck_burn(0);
// Create keys.
let cold_alice = U256::from(0);
let cold_bob = U256::from(1);
Expand Down Expand Up @@ -2750,6 +2754,75 @@ fn test_coinbase_v3_liquidity_update() {
});
}

// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_drain_alpha_childkey_parentkey_with_burn --exact --show-output --nocapture
#[test]
fn test_drain_alpha_childkey_parentkey_with_burn() {
new_test_ext(1).execute_with(|| {
let netuid = NetUid::from(1);
add_network(netuid, 1, 0);
let parent = U256::from(1);
let child = U256::from(2);
let coldkey = U256::from(3);
let stake_before = AlphaCurrency::from(1_000_000_000);
register_ok_neuron(netuid, child, coldkey, 0);
SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet(
&parent,
&coldkey,
netuid,
stake_before,
);
mock_set_children_no_epochs(netuid, &parent, &[(u64::MAX, child)]);

// Childkey take is 10%
ChildkeyTake::<Test>::insert(child, netuid, u16::MAX / 10);

let burn_rate = SubtensorModule::get_ck_burn();
let parent_stake_before = SubtensorModule::get_stake_for_hotkey_on_subnet(&parent, netuid);
let child_stake_before = SubtensorModule::get_stake_for_hotkey_on_subnet(&child, netuid);

let pending_alpha = AlphaCurrency::from(1_000_000_000);
SubtensorModule::drain_pending_emission(
netuid,
pending_alpha,
TaoCurrency::ZERO,
AlphaCurrency::ZERO,
AlphaCurrency::ZERO,
);
let parent_stake_after = SubtensorModule::get_stake_for_hotkey_on_subnet(&parent, netuid);
let child_stake_after = SubtensorModule::get_stake_for_hotkey_on_subnet(&child, netuid);

let expected_ck_burn = I96F32::from_num(pending_alpha)
* I96F32::from_num(9.0 / 10.0)
* I96F32::from_num(burn_rate);

let expected_total = I96F32::from_num(pending_alpha) - expected_ck_burn;
let parent_ratio = (I96F32::from_num(pending_alpha) * I96F32::from_num(9.0 / 10.0)
- expected_ck_burn)
/ expected_total;
let child_ratio = (I96F32::from_num(pending_alpha) / I96F32::from_num(10)) / expected_total;

let expected =
I96F32::from_num(stake_before) + I96F32::from_num(pending_alpha) * parent_ratio;
log::info!(
"expected: {:?}, parent_stake_after: {:?}",
expected.to_num::<u64>(),
parent_stake_after
);

close(
expected.to_num::<u64>(),
parent_stake_after.into(),
3_000_000,
);
let expected = I96F32::from_num(u64::from(pending_alpha)) * child_ratio;
close(
expected.to_num::<u64>(),
child_stake_after.into(),
3_000_000,
);
});
}

#[test]
fn test_incentive_is_autostaked_to_owner_destination() {
new_test_ext(1).execute_with(|| {
Expand Down
7 changes: 7 additions & 0 deletions precompiles/src/alpha.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ where
Ok(U256::from(tao_weight))
}

#[precompile::public("getCKBurn()")]
#[precompile::view]
fn get_ck_burn(_handle: &mut impl PrecompileHandle) -> EvmResult<U256> {
let ck_burn = pallet_subtensor::CKBurn::<R>::get();
Ok(U256::from(ck_burn))
}

#[precompile::public("simSwapTaoForAlpha(uint16,uint64)")]
#[precompile::view]
fn sim_swap_tao_for_alpha(
Expand Down
13 changes: 13 additions & 0 deletions precompiles/src/solidity/alpha.abi
Original file line number Diff line number Diff line change
Expand Up @@ -313,5 +313,18 @@
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getCKBurn",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
]
4 changes: 4 additions & 0 deletions precompiles/src/solidity/alpha.sol
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,8 @@ interface IAlpha {
/// @dev Returns the sum of alpha prices for all subnets.
/// @return The sum of alpha prices.
function getSumAlphaPrice() external view returns (uint256);

/// @dev Returns the CK burn rate.
/// @return The CK burn rate.
function getCKBurn() external view returns (uint256);
}
Loading
Loading