Skip to content
Closed
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
33 changes: 33 additions & 0 deletions .claude/skills/fix/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
name: fix
description: Commit current changes, run Rust autofix/lint/format, run pallet-subtensor tests, amend with any fixes.
---

# Fix Skill

Create or reuse one commit, run the Rust fix pipeline in order, run unit tests, and fold all resulting changes into that same commit.

## Steps

1. Run /format
2. In a subagent (subagent_type: `general-purpose`, model: `sonnet`) run:
- `cargo test -p pallet-subtensor --lib` and capture full output
- If any tests fail, analyze the failures
- Read the failing test code AND the source code it tests
- Determine the root cause
- Apply fixes using Edit tools
- Re-run the tests to confirm the fix works
- After fixing, if there are further failures, repeat (up to 3 fix-and-retest cycles)
- Summarize:
- Which tests failed, if any
- What was fixed and how
- Whether all tests pass now
3. Amend commit with test fixes, if any, then /format
4. Run `git show -s` for user to review

## Important

- Do NOT run `scripts/fix_rust.sh` — let /format take care of it
- Do NOT skip any steps
- The test subagent must fix source code to make tests pass, NOT modify tests to make them pass (unless the test itself is clearly wrong)
- If the test subagent cannot fix all failures after 3 cycles, it must return the remaining failures so the main agent can report them to the user
25 changes: 25 additions & 0 deletions .claude/skills/format/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
name: format
description: Commit current changes, run Rust autofix/lint/format, amend with any fixes.
---

# Format Skill

Create or reuse one commit, run the Rust fix pipeline in order and fold all resulting changes into that same commit.

## Steps

1. Stage all changes and create a commit with a descriptive message summarizing the changes (unless there are none)
2. Do this:
a. Run `cargo check --workspace`
b. Run `cargo clippy --fix --workspace --all-features --all-targets --allow-dirty`
c. Run `cargo fix --workspace --all-features --all-targets --allow-dirty`
d. Run `cargo fmt --all`
e. Amend the commit with any changes
3. Run `git show -s` for user to review

## Important

- If a fix tool fails in step 2, stop and report the error to the user rather than continuing
- Do NOT run `scripts/fix_rust.sh` itself — run the individual commands listed above instead
- Do NOT skip any steps
77 changes: 77 additions & 0 deletions .claude/skills/ship/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
name: ship
description: Ship current branch end-to-end: run /fix, push, open/update PR, triage CI failures, then deliver review findings for approval.
---

# Ship Skill

Ship the branch through CI and review without force-pushes, and never apply review fixes without explicit user approval.

Run the following skill in a subagent to prevent context pollution. Make the subagent return a short summary to the main agent.

1. Run `/fix`
2. Push the branch to origin
3. Create a PR with a comprehensive description if none exists yet
- Update the description if PR exists already
- Add label `skip-cargo-audit` to the PR
4. Poll CI status in a loop:
- Run: `gh pr checks --json name,state,conclusion,link --watch --fail-fast 2>/dev/null || gh pr checks`
- If `--watch` is not available, poll manually every 90 seconds using `gh pr checks --json name,state,conclusion,link` until all checks have completed (no checks with state "pending" or conclusion "").
- **Ignore these known-flaky/irrelevant checks** — treat them as passing even if they fail:
- `validate-benchmarks` (benchmark CI — not relevant)
- Any `Contract E2E Tests` check that failed only due to a timeout (look for timeout in the failure link/logs)
- `cargo-audit`
5. **If there are real CI failures** (failures NOT in the ignore list above):
- For EACH distinct failing check, launch a **separate Task subagent** (subagent_type: `general-purpose`, model: `sonnet`) in parallel. Each subagent must:
- Fetch the failed check's logs: use `gh run view <run-id> --log-failed` or the check link to get failure details.
- Investigate the root cause by reading relevant source files.
- Return a **fix plan**: a description of what needs to change and in which files, with specific code snippets showing the fix.
- **Wait for all subagents** to return their fix plans.
6. **Aggregate and apply fixes**:
- Review all returned fix plans for conflicts or overlaps.
- Apply the fixes using Edit/Write tools.
- Invoke the /fix skill
- `git push`
7. **Re-check CI**: Go back to step 4 and poll again. Repeat the fix cycle up to **3 times**. If CI still fails after 3 rounds, report the remaining failures to the user and stop.
8. **Once CI is green** (or only ignored checks are failing), perform a thorough code review.
- **Launch a single Opus subagent** (subagent_type: `general-purpose`, model: `opus`) for the review:
- It must get the full PR diff: `git diff main...HEAD`.
- It must read every changed file in full.
- It must produce a numbered list of **issues** found, where each issue has:
- A unique sequential ID (e.g., `R-1`, `R-2`, ...).
- **Severity**: critical / major / minor / nit.
- **File and line(s)** affected.
- **Description** of the problem.
- The review must check for: correctness, safety (no panics, no unchecked arithmetic, no indexing), edge cases, naming, documentation gaps, test coverage, and adherence to Substrate/Rust best practices.
- Return the full list of issues.
9. **For each issue**, run fix designer then fix reviewer in sequence; run all issues concurrently with each other:
- **Fix designer** (subagent_type: `general-purpose`, model: `sonnet`): Given the issue description and relevant code context, design a concrete proposed fix with exact code changes (old code -> new code). Return the fix as a structured plan.
- **Fix reviewer** (subagent_type: `general-purpose`, model: `opus`): Given the issue description, the relevant code context, and the proposed fix (once the fix designer returns — so the reviewer runs AFTER the designer, but reviewers for different issues run in parallel with each other). The reviewer must check:
- Does the fix actually solve the issue?
- Does it introduce new problems?
- Is it the simplest correct fix?
- Return: approved / rejected with reasoning.

Implementation note: For each issue, first launch the fix designer. Once the fix designer for that issue returns, launch the fix reviewer for that issue. But all issues should be processed in parallel — i.e., launch all fix designers at once, then as each designer returns, launch its corresponding reviewer. You may batch reviewers if designers finish close together.

10. **Report to user**: Present a formatted summary:
```
## Code Review Results

### R-1: <title> [severity]
**File**: path/to/file.rs:42
**Issue**: <description>
**Proposed fix**: <summary of fix>
**Review**: Approved / Rejected — <reasoning>

### R-2: ...
```
Ask the user which fixes to apply (all approved ones, specific ones by ID, or none).

## Important Rules

- Never force-push. Always use regular `git push`.
- All CI polling must have a maximum total wall-clock timeout of 45 minutes. If CI hasn't finished by then, report current status and stop waiting.
- When fetching CI logs, use a subagent to isolate the relevant part. If `gh run view` output is very long, focus on the failed step output only.
- Do NOT apply code review fixes automatically — always present them for user approval first.
- Use HEREDOC syntax for PR body and commit messages to preserve formatting.
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- never use slice indexing like `arr[n..]` or `arr[i]`; use `.get(n..)`, `.get(i)` etc. instead to avoid panics (clippy::indexing_slicing)
- never use `*`, `+`, `-`, `/` for arithmetic; use `.saturating_mul()`, `.saturating_add()`, `.saturating_sub()`, `.saturating_div()` or checked variants instead (clippy::arithmetic_side_effects)
- if you are creating a PR to `subtensor` add a `skip-cargo-audit` label
- no `Co-Authored-By` in commits or attribution in PRs
96 changes: 90 additions & 6 deletions pallets/subtensor/src/epoch/run_epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1274,9 +1274,21 @@ impl<T: Config> Pallet<T> {
.iter()
.any(|&c| c != I32F32::saturating_from_num(0))
{
// Liquid Alpha is enabled, compute the liquid alphas matrix.
let alphas: Vec<Vec<I32F32>> =
Self::compute_liquid_alpha_values(netuid, weights, bonds, consensus);
// Liquid Alpha is enabled, compute the appropriate consensus for liquid alpha based on mode
let consensus_for_liquid_alpha =
Self::compute_consensus_for_liquid_alpha(netuid, consensus);
log::trace!(
"consensus_for_liquid_alpha: {:?}",
&consensus_for_liquid_alpha
);

// Compute the liquid alphas matrix.
let alphas: Vec<Vec<I32F32>> = Self::compute_liquid_alpha_values(
netuid,
weights,
bonds,
&consensus_for_liquid_alpha,
);
log::trace!("alphas: {:?}", &alphas);

// Compute the Exponential Moving Average (EMA) of bonds using the provided clamped alpha values.
Expand Down Expand Up @@ -1316,9 +1328,21 @@ impl<T: Config> Pallet<T> {
.iter()
.any(|&c| c != I32F32::saturating_from_num(0))
{
// Liquid Alpha is enabled, compute the liquid alphas matrix.
let alphas: Vec<Vec<I32F32>> =
Self::compute_liquid_alpha_values_sparse(netuid, weights, bonds, consensus);
// Liquid Alpha is enabled, compute the appropriate consensus for liquid alpha based on mode
let consensus_for_liquid_alpha =
Self::compute_consensus_for_liquid_alpha(netuid, consensus);
log::trace!(
"consensus_for_liquid_alpha: {:?}",
&consensus_for_liquid_alpha
);

// Compute the liquid alphas matrix.
let alphas: Vec<Vec<I32F32>> = Self::compute_liquid_alpha_values_sparse(
netuid,
weights,
bonds,
&consensus_for_liquid_alpha,
);
log::trace!("alphas: {:?}", &alphas);

// Compute the Exponential Moving Average (EMA) of bonds using the provided clamped alpha values.
Expand All @@ -1332,6 +1356,53 @@ impl<T: Config> Pallet<T> {
}
}

/// Compute the consensus to use for liquid alpha calculation based on the configured mode
///
/// # Args:
/// * `netuid` - The network ID.
/// * `current_consensus` - The current in-memory consensus values.
///
/// # Returns:
/// A vector of consensus values to use for liquid alpha calculation
pub fn compute_consensus_for_liquid_alpha(
netuid: NetUid,
current_consensus: &[I32F32],
) -> Vec<I32F32> {
let mode = LiquidAlphaConsensusMode::<T>::get(netuid);

match mode {
ConsensusMode::Current => {
// Use the in-memory consensus (current behavior)
current_consensus.to_vec()
}
ConsensusMode::Previous => {
// Use consensus from storage
Self::get_previous_consensus_as_i32f32(netuid)
}
ConsensusMode::Auto => {
// Auto mode: Previous if bond_penalty == 1, otherwise Current
let bonds_penalty = Self::get_float_bonds_penalty(netuid);
if bonds_penalty == I32F32::from_num(1) {
Self::get_previous_consensus_as_i32f32(netuid)
} else {
current_consensus.to_vec()
}
}
}
}

/// Convert stored consensus (u16 values) to I32F32 format
/// Used by consensus modes that need to read from storage
fn get_previous_consensus_as_i32f32(netuid: NetUid) -> Vec<I32F32> {
let previous_consensus_u16 = Consensus::<T>::get(netuid);
previous_consensus_u16
.iter()
.map(|&c| {
I32F32::saturating_from_num(c).safe_div(I32F32::saturating_from_num(u16::MAX))
})
.collect()
}

/// Compute liquid alphas matrix
/// There is a separate alpha param for each validator-miner binding
///
Expand Down Expand Up @@ -1539,6 +1610,19 @@ impl<T: Config> Pallet<T> {
Ok(())
}

pub fn do_set_liquid_alpha_consensus_mode(
origin: OriginFor<T>,
netuid: NetUid,
mode: ConsensusMode,
) -> Result<(), DispatchError> {
Self::ensure_subnet_owner_or_root(origin, netuid)?;

LiquidAlphaConsensusMode::<T>::insert(netuid, mode.clone());

log::debug!("LiquidAlphaConsensusModeSet( netuid: {netuid:?}, mode: {mode:?} )",);
Ok(())
}

pub fn do_reset_bonds(
netuid_index: NetUidStorageIndex,
account_id: &T::AccountId,
Expand Down
26 changes: 25 additions & 1 deletion pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,19 @@ pub mod pallet {
},
}

/// Enum for consensus mode used in liquid alpha calculation
#[derive(
Encode, Decode, DecodeWithMemTracking, Default, TypeInfo, Clone, PartialEq, Eq, Debug,
)]
pub enum ConsensusMode {
/// Use current in-memory consensus (current behavior)
Current,
/// Use previous consensus from storage
Previous,
/// Auto mode: Previous if bond_penalty == 1, otherwise Current
#[default]
Auto,
}
/// Default minimum root claim amount.
/// This is the minimum amount of root claim that can be made.
/// Any amount less than this will not be claimed.
Expand Down Expand Up @@ -951,6 +964,12 @@ pub mod pallet {
(45875, 58982)
}

#[pallet::type_value]
/// Default consensus mode for liquid alpha calculation
pub fn DefaultConsensusMode<T: Config>() -> ConsensusMode {
ConsensusMode::default()
}

/// Default value for coldkey swap announcement delay.
#[pallet::type_value]
pub fn DefaultColdkeySwapAnnouncementDelay<T: Config>() -> BlockNumberFor<T> {
Expand Down Expand Up @@ -1918,8 +1937,13 @@ pub mod pallet {
pub type AlphaValues<T> =
StorageMap<_, Identity, NetUid, (u16, u16), ValueQuery, DefaultAlphaValues<T>>;

/// --- MAP ( netuid ) --> If subtoken trading enabled
#[pallet::storage]
/// MAP ( netuid ) --> consensus mode for liquid alpha calculation
pub type LiquidAlphaConsensusMode<T> =
StorageMap<_, Identity, NetUid, ConsensusMode, ValueQuery, DefaultConsensusMode<T>>;

#[pallet::storage]
/// --- MAP ( netuid ) --> If subtoken trading enabled
pub type SubtokenEnabled<T> =
StorageMap<_, Identity, NetUid, bool, ValueQuery, DefaultFalse<T>>;

Expand Down
31 changes: 31 additions & 0 deletions pallets/subtensor/src/macros/dispatches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2626,5 +2626,36 @@ mod dispatches {
Self::deposit_event(Event::ColdkeySwapCleared { who });
Ok(())
}

/// Sets the consensus mode for liquid alpha calculation on a subnet.
///
/// This function can only be called by the subnet owner or root.
/// The consensus mode determines which consensus values are used for liquid alpha calculation:
/// - `Current`: Use current in-memory consensus
/// - `Previous`: Use previous consensus from storage
/// - `Auto`: Use Previous if bond_penalty == 1, otherwise Current (default)
///
/// # Arguments:
/// * `origin` - The origin of the call, must be subnet owner or root.
/// * `netuid` - The subnet to set the mode for.
/// * `mode` - The consensus mode to use.
///
/// # Errors:
/// * `BadOrigin` - If the origin is not the subnet owner or root.
#[pallet::call_index(134)]
#[pallet::weight((
Weight::from_parts(10_000, 0)
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1)),
DispatchClass::Normal,
Pays::Yes
))]
pub fn set_liquid_alpha_consensus_mode(
origin: OriginFor<T>,
netuid: NetUid,
mode: ConsensusMode,
) -> DispatchResult {
Self::do_set_liquid_alpha_consensus_mode(origin, netuid, mode)
}
}
}
Loading