feat: net TAO flow for emission allocation#4
Open
igoraxz wants to merge 3 commits into
Open
Conversation
JohnReedV
reviewed
May 4, 2026
| // Record protocol costs for net-tao-flow EMA. | ||
| SubnetExcessTao::<T>::insert(*netuid_i, tao_to_swap_with); | ||
| Self::record_protocol_inflow(*netuid_i, injected_tao); | ||
| Self::record_protocol_inflow(*netuid_i, tao_to_swap_with); |
There was a problem hiding this comment.
Would this be more accurate as
Self::record_protocol_inflow(*netuid_i, buy_swap_result_ok.amount_paid_in); instead?
0e16e0e to
f8f89f6
Compare
Replace gross user flow (buys - sells) with net flow (user flow - protocol cost) for emission share computation. This ensures subnets only receive emissions when they generate positive net TAO inflow for the network, preventing subsidized extraction where a subnet receives more in protocol emissions than it attracts in external capital. New per-block storage (auditable base values): - SubnetExcessTao: excess TAO swapped (chain buys) per block - SubnetRootSellTao: TAO from root dividend sells per block New protocol cost tracking: - SubnetProtocolFlow: per-block accumulator (emit + chain_buys - root_sells) - SubnetEmaProtocolFlow: EMA of protocol cost, same smoothing as user flow EMA Emission share computation: - net_ema = ema(user_flow) - ema(protocol_cost) - Protocol EMA starts from zero and scales in over ~30 days - Sudo toggle NetTaoFlowEnabled (default: on) to switch between net and gross flow Sudo call: sudo_set_net_tao_flow_enabled (call_index 91) Cleanup: all new storage items removed on subnet deregistration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
c531b0a to
d67015b
Compare
gztensor
suggested changes
May 13, 2026
| #[allow(dead_code)] | ||
| fn get_shares_flow(subnets_to_emit_to: &[NetUid]) -> BTreeMap<NetUid, U64F64> { | ||
| let net_flow_enabled = NetTaoFlowEnabled::<T>::get(); | ||
| let protocol_cost_discount = I64F64::saturating_from_num(ProtocolCostDiscount::<T>::get()); |
7434139 to
260f015
Compare
Problem: net flow (user - protocol) creates a feedback loop when used
for emission allocation. Fewer qualifying subnets → each gets more
emissions → higher protocol cost → even fewer qualify. In simulation
this collapses from 70 to 10 subsidized subnets over 41 days.
Solution: scale protocol EMA by α before subtracting, where α normalizes
the total positive protocol EMA to match total positive user EMA:
α = min(1, Σ max(user_ema, 0) / Σ max(proto_ema, 0))
net_flow = user_ema − α × proto_ema
α is computed each block from current EMA state. No tunable parameters.
Guarantee: the aggregate net flow across all gross-positive subnets
is always ≥ 0. Proof: Σ net_i over {user > 0} = Σ user_i − α × Σ proto_i.
Since Σ proto_i (over user > 0) ≤ sum_pos_proto, and α × sum_pos_proto
≤ sum_pos_user (by construction), the sum is non-negative. QED.
This converts the feedback loop into a self-correcting mechanism:
more concentration → α drops → softer deduction → rebalances.
Simulation result: 70 → 46 subnets (vs 10 without normalization).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
260f015 to
175a463
Compare
Adds a second EMA layer on top of the existing user-flow EMA so emission credit from buy spikes accrues slowly, while sell pressure still debits the signal immediately: raw = EMA_30d(buys - sells) [existing] slow = EMA_30d(raw) [new, stored separately] matured = min(raw, slow) [read-time clamp, not stored] signal = max(matured - α × proto, 0) [used for emission shares] Buy spike: raw rises before slow → matured = slow (delayed credit). Sell spike: raw falls below slow → matured = raw (immediate debit). Steady flow: raw ≈ slow → matured ≈ raw (no distortion). Key implementation detail: SubnetEmaSlowTaoFlow stores EMA(raw), not the clamped min(raw, slow). The clamp is applied at read time only. This ensures the slow EMA tracks the true long-run raw signal without being polluted by the clamp. On first access for existing subnets, slow EMA initializes to the current raw EMA to avoid an emission cliff at deployment. Simulation: recycling attack (10k τ, 30d hold / 30d cool, 365d): Gross flow: +73,023 τ extra emissions captured Net normalized: +27,562 τ (2.6× lower than gross) Matured + net: +8,238 τ (8.9× lower than gross) Uses the existing FlowEmaSmoothingFactor for both EMAs — no new tunable parameters. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replace gross user flow
ema(buys - sells)with net flowema(buys - sells) - ema(emission + chain_buys - root_sells)for emission share computation.Subnets only receive emissions when they generate positive net TAO inflow for the network.
Simulation results (30-day backtest):
Rationale
Currently the network subsidizes subnets for gross TAO flow (buys − sells). This heavily misallocates incentives to subnets with negative net TAO flow for the network: e.g. a subnet gets 1 TAO in user buys → receives 4 TAO in protocol subsidy → net TAO flow ("subnet profit") is −3 TAO.
The fix is to change the emission allocator to maximize subnet TAO inflow net of protocol subsidy.
The net protocol cost to a subnet per block is:
A subnet that receives 10 TAO emission but returns 3 TAO via root sells has an actual protocol cost of 7 TAO. It should only need to attract 7 TAO in user buys to be net-positive, not 10.
This update also addresses the runaway effect of chain buys — runaway emission cannot be sustained unless a subnet continuously attracts MORE external TAO than the protocol gives it in emission + chain buys.
Flow separation (no cross-contamination)
User flows and protocol flows are tracked in completely independent accumulators:
stake_into_subnet→record_tao_inflowunstake_from_subnet→record_tao_outflowadjust_protocol_liquidity(direct)swap_tao_for_alpha(direct, not via stake)swap_alpha_for_tao(direct, not via unstake)Root sells are only recorded on the swap path (root validator claims TAO). If a root validator keeps alpha instead of swapping (
Keepmode), no TAO leaves the pool and nothing is recorded. The recorded amount isamount_paid_out— the actual TAO that left the pool via AMM, not a theoretical value.Block-level sequencing
Changes (7 files, +127 lines)
New per-block storage (auditable base values):
SubnetExcessTao— excess TAO swapped (chain buys) per blockSubnetRootSellTao— TAO from root dividend sells per blockProtocol cost EMA:
SubnetProtocolFlow— per-block accumulator (emit + chain_buys − root_sells)SubnetEmaProtocolFlow— EMA with same smoothing factor as user EMAShare computation:
net_ema = ema(user_flow) - ema(protocol_cost)when toggle is onSudo toggle:
NetTaoFlowEnabled(default: on) —sudo_set_net_tao_flow_enabled(call_index 91)falseto instantly revert to gross flow behaviorCleanup: all new storage items removed on subnet deregistration.
Test plan
get_ema_protocol_flowwith known inputsget_shares_flowproduces correct net shares when protocol EMA > 0SubnetEmaTaoFlowbehavior🤖 Generated with Claude Code