diff --git a/.agents/skills/evm-maintainer/SKILL.md b/.agents/skills/evm-maintainer/SKILL.md new file mode 100644 index 0000000000..555222f960 --- /dev/null +++ b/.agents/skills/evm-maintainer/SKILL.md @@ -0,0 +1,182 @@ +--- +name: evm-maintainer +description: Maintain backwards-compatible, versioned EVM precompiles that expose runtime extrinsics, state, constants, and APIs to Solidity. +--- + +# EVM Precompile Maintainer + +You are the maintainer of EVM precompiles. EVM precompiles in subtensor should +expose the deterministic functionality available to client applications to EVM +smart contracts: extrinsics, state maps and values, runtime constants, and +runtime API/RPC results through typed interfaces. Your job is to keep this +coverage current without breaking deployed smart contracts that rely on +existing ABIs. Read the notes below and then execute the workflow. + +## Reference routing + +- Before classifying or implementing any precompile change, including an + additive function, runtime adaptation, bug fix, deprecation, or disablement, + read [ABI versioning](references/abi-versioning.md). +- Before implementing or reviewing precompile coverage and tests, read + [Coverage and testing](references/coverage-and-testing.md). +- Before classifying pallet state or runtime constants, or adding, reviewing, + or omitting a typed view, read + [State exposure](references/state-exposure.md) and use its direct, wrapped, + and do-not-expose classifications. Do not override a classification without + an explicit human decision. +- Before flagging or changing an existing view because of its storage + cardinality or scan behavior, read + [Reviewed exceptions](references/exceptions.md). Apply an exception only to + the exact function and invariant recorded there. + +## Backwards compatibility + +Treat every released precompile as a permanent public API. Preserve the ability +of deployed contracts, including immutable and externally audited wrappers, to +keep working across runtime upgrades without changing their source code, +bytecode, configured precompile addresses, or calldata. + +Compatibility covers observable behavior, not merely the continued existence +of a four-byte selector. Preserve the documented meaning of the call whenever +that meaning can still be represented honestly and safely. + +For each affected released function or view: + +1. Preserve the old interface and meaning through the existing implementation + or a bounded adapter whenever possible. +2. Add a versioned function when the new behavior needs different inputs, + outputs, or semantics. Keep the old address and selector routed. +3. Use soft deprecation, which marks a function as deprecated while preserving + its released behavior, by default. Declare deprecation and replacement + metadata on the Rust precompile function with the lifecycle annotation + described in [ABI versioning](references/abi-versioning.md). Treat the + annotated Rust function as the source of truth and generate Solidity + lifecycle annotations and registry metadata from it; do not maintain + separate hand-written lifecycle data. Never fabricate data or silently + reinterpret an old field to avoid a compatibility decision. +4. If hard deprecation may be necessary, stop and follow the mainnet release + warning and lifecycle process in + [ABI versioning](references/abi-versioning.md). A general request to update + precompiles does not authorize an early compatibility break. +5. Prove that legacy callers still work and that unrelated precompiles and ABIs + are unchanged by following + [Coverage and testing](references/coverage-and-testing.md). + +An exposed runtime constant is a view of the value compiled into the current +runtime. Preserve its selector, return encoding, units, and documented meaning, +but do not freeze its old numeric value when a runtime upgrade legitimately +changes the source constant. Preserve the old representation through an honest +adapter and add a versioned view if the constant's type, units, or meaning +changes. + +## Notes on coding precompiles + +- Keep every precompile path O(1) in CPU and memory unless the exact path is a + human-reviewed exception in + [Reviewed exceptions](references/exceptions.md). +- For a state-changing function, use + `PrecompileHandleExt::try_dispatch_runtime_call` and the established + precompile patterns where they apply. Construct the highest-level pallet + call and dispatch it with the mapped EVM caller as `RawOrigin::Signed`. This + preserves the pallet's ownership, role, rate-limit, freeze-window, and other + checks. Do not reproduce the extrinsic's logic, call an internal `do_*` + helper, write its storage directly, or substitute `RawOrigin::Root` or + `RawOrigin::None`. +- Expose a state-changing extrinsic only when that highest-level pallet call + accepts a non-Root signed origin. +- An extrinsic that accepts either a signed authority, such as a subnet owner, + or Root may expose its signed path. Do not expose an extrinsic that is + Root-only or `None`-only unless a separately approved authorization design is + added to the runtime. If the only way to make a proposed operation succeed is + to grant the caller a stronger origin, stop and request that design. +- Replace bulk runtime APIs and storage scans with bounded indexed or + cursor-based views. Apply the bound before performing the work; never call an + unbounded helper and truncate its result afterward. Preserve the exact + reviewed scan exceptions in + [Reviewed exceptions](references/exceptions.md). +- Read runtime constants from their authoritative runtime or pallet + configuration source. Never duplicate the literal value in precompile code. + Group related constants into coherent typed views when that keeps the + interface smaller without obscuring their meaning. +- Follow [ABI versioning](references/abi-versioning.md) for every released + interface. +- Treat repository-owned Rust function lifecycle annotations as the source of + truth for registry deprecation metadata, replacement selectors, migration + messages, and generated Solidity interfaces and `@custom:deprecated` + NatSpec. Do not hand-edit generated Solidity lifecycle data. Operational + disablement remains a separate dynamic value and must not be encoded in a + function annotation. +- Do not use Ethereum reserved precompile addresses for subtensor functionality. +- Assign new Bittensor domain precompiles sequentially from the next unused + Bittensor address. The current proposal reserves `0x080f` through `0x0813` + for Scheduler, Drand, Timestamp, Runtime Configuration, and the Precompile + Registry, respectively. Add routing and tests that lock every implemented + address and selector before release. +- Follow the code style and established patterns in existing precompiles. +- Represent Substrate account IDs in EVM space as 32-byte public keys. +- Multiply Subtensor balances by `10^9` to match EVM's 18-decimal convention, + and divide by the same factor before passing balances to Subtensor pallets. + +## Maintenance workflow + +Perform this workflow on: + +- Every change to subtensor Rust codebase +- When explicitly prompted + +## Step 1 — Determine the diff + +Determine the diff between current branch and the most recent main branch (may need to pull it locally if it is outdated). See how this diff affects EVM precompiles: + +- Does it remove or change any functions that precompiles rely on? Does it change function signatures or underlying functionality? +- Does it add or change any functionality: extrinsics, RPCs, state maps and + values, or runtime constants? + +## Step 2 - Review the diff in the context of current precompiles vs. subtensor functionality + +- All extrinsics that accept a non-Root signed origin, as well as all state + variables, maps, and constants should be exposed directly or through + type-safe readers to precompile callers for the following pallets: + - subtensor + - admin-util + - balances + - proxy + - scheduler + - drand + - crowdloan + - timestamp + - swap +- Root-only, `None`-only, inherent, disabled, and compatibility no-op + extrinsics must be inventoried and explicitly classified as not callable + through typed EVM precompiles. +- All deterministic runtime API RPC results for the subtensor pallet should be + exposed through typed precompile views. Preserve a similar interface when it + is already bounded; redesign bulk results as bounded indexed or cursor-based + views when it is not. + +Use [Coverage and testing](references/coverage-and-testing.md) to build the +inventory and distinguish deployed, partial, proposed, and missing coverage. +Use [State exposure](references/state-exposure.md) to classify every state item +and runtime constant, and [Reviewed exceptions](references/exceptions.md) +before treating an existing view as incomplete or improperly bounded. + +## Step 3 - Handle changed functions, state variables and maps, and constants + +Apply the backwards-compatibility decision rule above and the detailed +[ABI versioning](references/abi-versioning.md) process. Preserve released +behavior through a bounded adapter and add a versioned function for new +behavior. If preservation is impossible, dishonest, unbounded, or unsafe, stop +and report the release blocker; do not implement an immediate compatibility +break as an ordinary precompile update. + +## Step 4 - Handle added functions, state variables and maps, and constants + +Determine the category under which the new functionality needs to be added and add to the corresponding existing precompile. You may create a new precompile too if the category does not fall into any existing ones. + +## Step 5 - Update precompile documentation + +Update the Solidity interface, generated ABI, NatSpec, registry metadata, SDK +copies, and public precompile documentation together. Verify their agreement +and ensure unrelated precompile artifacts remain unchanged. Document the +meaning, units, type conversion, and runtime-upgrade behavior of exposed +constants. diff --git a/.agents/skills/evm-maintainer/references/abi-versioning.md b/.agents/skills/evm-maintainer/references/abi-versioning.md new file mode 100644 index 0000000000..98a5febd89 --- /dev/null +++ b/.agents/skills/evm-maintainer/references/abi-versioning.md @@ -0,0 +1,323 @@ +# ABI versioning and lifecycle + +## Contents + +- [Establish the released baseline](#establish-the-released-baseline) +- [Preserve the external contract](#preserve-the-external-contract) +- [Reserve addresses and selectors](#reserve-addresses-and-selectors) +- [Version functions within a domain](#version-functions-within-a-domain) +- [Preserve old behavior through adapters](#preserve-old-behavior-through-adapters) +- [Classify changes](#classify-changes) +- [Apply the lifecycle model](#apply-the-lifecycle-model) +- [Stop an undeployed compatibility break](#stop-an-undeployed-compatibility-break) +- [Report lifecycle and availability](#report-lifecycle-and-availability) +- [Handle reversible disablement](#handle-reversible-disablement) + +## Establish the released baseline + +Before changing a precompile: + +1. Determine which addresses, selectors, Solidity interfaces, and ABI files + have been deployed or published for production use. Inspect release history + and the deployed runtime, not only the working tree. +2. Inspect `precompiles/src/lib.rs`, the Rust implementation, + `precompiles/src/solidity/*.sol`, generated `*.abi` files, tests, public + documentation, SDK copies, and known integration contracts. +3. Compare the branch with the relevant base and identify every runtime change + that affects inputs, outputs, state changes, errors, authorization, units, + value handling, runtime constants, or gas and weight requirements. +4. Treat uncertain production status as released until evidence establishes + otherwise. +5. Distinguish released interfaces from explicit proposals. Allow an + unassigned, unpublished proposal to change during design review; freeze its + address, selectors, and observable behavior once released. + +Do not infer compatibility from Rust names. Define the external contract as the +fixed EVM address plus accepted calldata, returned bytes, state effects, +authorization, charging, and success-or-revert behavior. + +## Preserve the external contract + +Preserve all observable properties of every released call: + +- address and selector handling; +- function name, input types, input order, and ABI encoding; +- return types, tuple and struct field order, and ABI encoding; +- documented meaning, units, precision, scaling, rounding, and defaults; +- whether a returned constant means the value compiled into the current + runtime or a value fixed by the released interface; +- view, state-changing, payable, and static-call behavior; +- treatment of attached EVM value; +- caller-to-Substrate account mapping and dispatched origin; +- authorization and proxy behavior; +- state transitions and atomicity; +- success-versus-revert behavior and documented error payloads; +- bounded-input and complexity guarantees; +- lifecycle-status selectors and their documented availability guarantees. + +Return types do not contribute to a Solidity selector, but changing them under +an existing selector still breaks old callers because they decode the returned +bytes with the old ABI. + +Allow internal Rust names, storage layouts, hashers, intermediate types, and +algorithms to change only when the implementation adapts them back to the +released behavior. + +Allow runtime weight corrections, but preserve the complexity class and input +bounds. Do not introduce an unannounced increase large enough to make a +previously practical call unusable. Never replace bounded work with an +unbounded scan. + +## Reserve addresses and selectors + +Keep every released precompile address recognized by the precompile set. +Preserve compatible handling at that address: existing calldata must still +reach behavior that honors its released contract. The internal Rust type or +dispatch structure may change; the observable routing contract may not. + +Keep every released selector reserved permanently, including after hard +deprecation. Route a hard-deprecated selector to its descriptive error. Never +allow a different function to claim it. + +Assign a genuinely new Bittensor domain the next unused sequential Bittensor +address. The current proposal reserves: + +| Address | Domain | +|---|---| +| `0x080f` | Scheduler | +| `0x0810` | Drand | +| `0x0811` | Timestamp | +| `0x0812` | Runtime Configuration | +| `0x0813` | Precompile Registry | + +A documented reservation prevents another domain from taking the address but +does not make the precompile callable. When implementing a reserved address, +add exact-value tests for its index and full address, routing tests through the +precompile set, and selector tests for every function at that address. + +Before adding a function, calculate its selector from the canonical Solidity +signature and compare it with the complete selector set at the address. Reject +collisions even when the Solidity names differ. + +Treat a new function as additive only when: + +- its selector does not collide; +- old input and output encodings remain identical; +- unknown-selector and fallback behavior remain unchanged; +- old results and side effects remain unchanged; and +- no unrelated Solidity interface or ABI changes. + +## Version functions within a domain + +Prefer one fixed address for each coherent domain. Add versions at that address: + +```text +functionName +functionNameV2 +functionNameV3 +``` + +Keep every earlier version routed. Use a new address only for a genuinely +different domain with an independent responsibility and lifecycle. + +Continue supporting legacy addresses created under earlier per-contract +versioning. Do not use them as a precedent for creating a new address whenever +one function changes. + +Do not attempt a return-type-only overload. Because return types do not +distinguish selectors, use a versioned name or a genuinely distinct input +signature. + +When an audited integration expects a missing chain value or operation, prefer +adding the typed function it expects to the appropriate existing precompile. +Do not require changes to an audited wrapper when the precompile can satisfy +the wrapper's existing interface safely. + +## Preserve old behavior through adapters + +Adapt released calls to new runtime representations whenever the old result can +still be produced honestly with bounded, proportionate work: + +- Reconstruct an old aggregate when one stored value becomes several. +- Return the original tuple when a struct gains fields; expose the extended + tuple through a new version. +- Update Rust storage access when names, keys, hashers, or map shapes change. +- Supply the exact old default when an extrinsic gains an option; expose the + option through a new version. +- Follow a renamed or relocated runtime constant to its authoritative source + while preserving the released view's meaning, type, and units. +- Derive the documented old result when the runtime replaces its computation. +- Preserve legacy units, precision, scaling, and rounding in the old function; + expose a corrected convention through a new version. + +Do not fabricate data to retain a byte shape. Do not reinterpret an old field +as a different concept. If an adapter cannot preserve the documented meaning, +make an explicit lifecycle decision. + +## Classify changes + +| Runtime change | Required treatment | +|---|---| +| Storage rename, hasher change, or map restructuring | Update the Rust implementation; preserve ABI and meaning. | +| Equivalent internal computation refactor | Keep the function and verify equivalent observable results. | +| Additional returned information | Keep the old subset; add a version for the richer result. | +| Input or return type/order change | Add a version with a new selector. | +| One concept splits into several | Reconstruct the old aggregate when honest; expose components through a version. | +| Extrinsic gains an option | Preserve the old default; expose the option through a version. | +| Runtime constant is added | Add a typed view in the appropriate domain. | +| Current-runtime constant value changes | Keep the existing selector returning the new authoritative value when that is its documented meaning. | +| Runtime constant type, units, or meaning changes | Preserve the old representation through an honest adapter or add a versioned view. | +| Entirely new operation or view | Add a selector to the appropriate domain. | +| Concept disappears without an honest representation | Reserve the selector and evaluate hard deprecation. | +| Bug fix changes observable semantics | Preserve the released behavior and add a corrected version unless retaining it is unsafe. | +| Urgent security or operational risk | Report the risk and consider whether reversible disablement should be recommended. | + +For a security-critical behavior that cannot remain callable, stop and report +the compatibility break. Do not silently change or delete the selector. + +## Apply the lifecycle model + +Keep function lifecycle separate from precompile availability: + +| Condition | Required call behavior | +|---|---| +| Active and enabled | Execute normally. | +| Soft-deprecated and enabled | Preserve the documented behavior and encoding. | +| Hard-deprecated and enabled | Keep routing the selector and return a descriptive precompile error. | +| Disabled | Return the precompile-disabled error regardless of function lifecycle. | + +Use soft deprecation by default. Preserve the call, annotate the Rust +precompile function with its lifecycle metadata, generate the Solidity +`@custom:deprecated` NatSpec from that annotation, and publish replacement +metadata without adding deprecation-only work to every invocation. + +Use hard deprecation only when old behavior cannot be represented honestly or +safely, for example because: + +- the underlying concept no longer exists and has no representation; +- the semantics changed beyond what the old return type can describe; or +- preservation requires fabricated data, dead state, unbounded work, or an + unacceptable security risk. + +Do not hard-deprecate because a replacement is newer, easier to maintain, or +more complete. First document why an adapter is impossible or disproportionate, +identify affected released functions and known callers, provide a replacement +when possible, and complete the agreed migration process. + +## Stop an undeployed compatibility break + +If the runtime change that makes old behavior impossible has not reached +mainnet, treat mainnet deployment as blocked by the compatibility break. Do not +interpret a request to update precompiles as authorization to deploy the break +or hard-deprecate affected functions immediately. + +Stop and give the developer this prominent warning: + +> **Mainnet compatibility warning:** This change would force hard deprecation +> of `` and break contracts that +> still call it. Do not deploy the incompatible runtime change to mainnet until +> `` is available, the old function has been soft-deprecated for +> the agreed migration window, and the phase-out criteria have been satisfied. + +State why an adapter cannot work, which released functions and known callers +are affected, what replacement is available or required, and which phase-out +steps remain. Continue only with non-breaking preparation such as adding the +replacement, tests, documentation, and lifecycle metadata. Preserve current +mainnet behavior throughout the migration window. Hard-deprecate only in the +later release that completes the planned phase-out. + +## Report lifecycle and availability + +Use this proposed registry shape as the compatibility target: + +```solidity +struct PrecompileStatus { + bool isDeprecated; + bool isDisabled; + address newPrecompile; + bytes4 newSelector; + string message; +} +``` + +### Function lifecycle annotations + +Declare deprecation metadata on the affected Rust precompile function with a +repository-owned annotation. The target syntax is: + +```rust +#[precompile_lifecycle::deprecated( + replace_with = "getStakeV2(uint16,uint16)", + message = "Use getStakeV2 for the current stake representation." +)] +#[precompile::public("getStake(uint16,uint16)")] +``` + +Do not use `#[precompile::deprecated]` unless the Frontier precompile macro +explicitly supports it: that namespace belongs to the Frontier macro. Do not +encode structured replacement metadata in Rust's built-in `#[deprecated]` +attribute, which does not provide `replace_with` and `message` fields. The +repository-owned annotation and its generator can be implemented in subtensor +without changing Frontier. + +The annotated Rust precompile function is the authoritative source for: + +- whether the function is deprecated; +- the canonical replacement signature used to derive `newSelector`; +- migration guidance returned in `message`; and +- the generated Solidity interface and its lifecycle NatSpec. + +The replacement precompile defaults to the containing precompile address. +Allow an explicit replacement address when migration genuinely crosses +domains. If no replacement exists, omit `replace_with`; the registry returns +zero replacement fields. A deprecated function without a useful human message +is incomplete metadata. + +Repository tooling should collect these Rust annotations and generate the +static registry metadata and Solidity interface lifecycle data. Generate +standards-compliant `@custom:deprecated` and, when applicable, +`@custom:replace-with` NatSpec; an additional `@notice` may make the warning +visible to clients that ignore custom tags. Do not duplicate the same +lifecycle data in a manually maintained Rust match, static table, Solidity +comment, or registry file, and do not hand-edit generated Solidity lifecycle +annotations. Build validation must fail when annotated Rust metadata, the +canonical replacement selector, generated Solidity and NatSpec, and public +documentation disagree. + +The annotation describes function lifecycle only. It does not perform +deprecation work on each invocation and does not control availability. +`isDisabled` remains a dynamic lookup of the containing precompile's +operational enablement state. + +Interpret `isDeprecated` as soft or hard function deprecation. Interpret +`isDisabled` as current unavailability through a reversible operational switch. +Use `newPrecompile` and `newSelector` for the recommended replacement; zero +replacement fields mean that none is available. Use `message` for +human-readable status or migration guidance. + +An active function has no deprecation annotation and therefore returns +`isDeprecated = false` with zero replacement fields and an empty message. Do +not infer deprecation from disablement. Do not clear deprecation when a +precompile is re-enabled. Do not describe the registry as callable until its +address and implementation are released. + +Keep generated registry metadata, Solidity NatSpec, public documentation, and +call behavior consistent. Prefer static registry queries over emitting a log +on every deprecated call. + +## Handle reversible disablement + +Treat disablement as an external, reversible operational action, not a normal +deprecation step. An agent may identify a risk, verify the mechanism, and +recommend that responsible decision-makers consider it. An agent cannot +perform or authorize the action. + +Require re-enablement to restore each function's previous active, +soft-deprecated, or hard-deprecated behavior. Never erase lifecycle metadata +when availability changes. + +Before recommending disablement, verify that the address routes through +`PrecompileExt::try_execute` and uses the intended `PrecompileEnum` entry. +Check whether multiple addresses share that entry and report the complete +effect of a toggle. Do not claim an address is toggleable merely because the +general mechanism exists. diff --git a/.agents/skills/evm-maintainer/references/coverage-and-testing.md b/.agents/skills/evm-maintainer/references/coverage-and-testing.md new file mode 100644 index 0000000000..83cab27509 --- /dev/null +++ b/.agents/skills/evm-maintainer/references/coverage-and-testing.md @@ -0,0 +1,302 @@ +# Precompile coverage and testing + +## Contents + +- [Define the coverage scope](#define-the-coverage-scope) +- [Build a coverage inventory](#build-a-coverage-inventory) +- [Cover extrinsics](#cover-extrinsics) +- [Cover state with typed views](#cover-state-with-typed-views) +- [Cover runtime constants](#cover-runtime-constants) +- [Cover runtime APIs and public RPCs](#cover-runtime-apis-and-public-rpcs) +- [Add regression tests first](#add-regression-tests-first) +- [Test observable behavior](#test-observable-behavior) +- [Validate ABIs and routing](#validate-abis-and-routing) +- [Validate cost and bounds](#validate-cost-and-bounds) +- [Run repository checks](#run-repository-checks) +- [Report the result](#report-the-result) + +## Define the coverage scope + +Take the authoritative pallet and API scope from `SKILL.md`. Do not silently +expand or narrow it based on an older document. + +For each in-scope pallet, inspect: + +- every dispatchable extrinsic; +- every public state map and value; +- every public runtime constant; +- every publicly facing runtime API and RPC; +- changes to types, guards, authorization, units, and error behavior used by + existing precompiles. + +Precompile coverage means that Solidity contracts receive a typed equivalent +of the authorized deterministic client-facing functionality. It does not mean +exposing raw pallet storage, SCALE bytes, or Rust types. + +Distinguish deployed coverage from proposed coverage. Do not describe a +documented proposal, unassigned address, or Rust stub as callable. + +## Build a coverage inventory + +Create or update a working matrix with one row per source item: + +| Source | Kind | Public functionality | Precompile domain | Function | Status | Evidence | +|---|---|---|---|---|---|---| +| Pallet and item | Extrinsic, state, constant, runtime API, or RPC | Meaning exposed to clients | Existing or proposed address/domain | Canonical signature | Covered, partial, missing, or excluded | Rust, Solidity, ABI, and test paths | + +For every partial, missing, or excluded row, state the exact reason. Do not +equate a similarly named function with coverage; compare parameters, returned +information, authorization, semantics, and failure behavior. + +Use the matrix to find both directions of drift: + +- runtime functionality with no typed EVM path; and +- precompile behavior whose runtime dependency changed or disappeared. + +Group additions by meaning under as few coherent contracts as reasonably +possible. Do not mirror pallet boundaries mechanically and do not create one +precompile per storage item. + +## Cover extrinsics + +Expose each extrinsic that accepts a non-Root signed origin through a typed +state-changing function unless an explicit scope decision excludes it. Calls +that accept either Root or a signed authority may expose only the signed path. +Classify Root-only and `None`-only calls as not EVM-callable; the existence of a +runtime extrinsic does not authorize a precompile to manufacture its origin. + +Preserve: + +- dispatched origin and caller mapping; +- authorization and proxy behavior; +- payable versus nonpayable behavior; +- attached-value conversion and handling; +- input validation and bounds; +- dispatch atomicity; +- runtime errors and EVM failure behavior; and +- gas and weight charging, including post-dispatch adjustment. + +Do not count a selector as coverage merely because it is routed. Test that a +mapped caller with the required signed authority can succeed and that a caller +without that authority fails without changing state. A selector that always +fails `BadOrigin` is not meaningful coverage. + +When an extrinsic changes, compare the old and new behavior rather than only +their Rust signatures. Follow [ABI versioning](abi-versioning.md) when an +existing function is affected. + +## Cover state with typed views + +Inventory every public state map and value in scope. Expose its meaningful +contents through typed view functions; never provide direct writable access to +storage. + +Apply the classifications in [State exposure](state-exposure.md). Before +changing an existing view because its shape appears incomplete or unbounded, +check [Reviewed exceptions](exceptions.md). Treat exceptions as exact, +human-reviewed cases rather than patterns to extend by analogy. + +Let a view read one or more storage items when that is required to return the +meaningful value. Keep the mapping from source storage to typed functions +explicit in the coverage inventory so no item disappears behind an abstract +claim of domain coverage. + +Group related reads into coherent domain precompiles. Do not expose pallet +prefixes, storage keys, hashers, or SCALE encodings as the contract interface. + +For every view, specify and test: + +- key and account conversions; +- missing-state behavior; +- result types and tuple order; +- units, precision, scaling, and rounding; +- overflow and narrowing conversions; +- maximum input and output size; and +- the exact database reads charged. + +When storage changes internally, update the Rust adapter and prove that released +calldata still returns the released meaning. + +## Cover runtime constants + +Inventory every public runtime constant in the in-scope pallet configuration +and expose its meaningful value through a typed view. Read the authoritative +`Get::get()`, associated constant, or equivalent runtime source; never repeat +its literal value in precompile code. + +Group related constants by contract use case when appropriate. Preserve each +constant's meaning, units, signedness, width, and overflow behavior. A constant +may change when a new runtime is compiled: when a released view promises the +current runtime value, test and document that behavior instead of treating the +old numeric value as ABI state. + +Do not expose generated weights, compiler/build constants, or private +implementation limits unless they are part of the pallet's deterministic +client-facing contract. + +## Cover runtime APIs and public RPCs + +Inventory the publicly facing runtime APIs and RPCs in scope, including the +Subtensor runtime API surface required by `SKILL.md`. + +Expose typed functions with equivalent inputs and meaningful outputs. A +precompile may call the same underlying helpers rather than reproduce an RPC +transport detail. Preserve pagination, bounds, defaults, and absence semantics +that affect callers. + +Do not copy a bulk runtime API into Solidity when its work or result can grow +with chain state. Prefer one of these bounded shapes: + +- an indexed item view plus a bounded count; +- a cursor and caller-supplied limit capped by a fixed runtime maximum; or +- a fixed-size key batch whose maximum is part of the interface contract. + +Return the next cursor or an explicit completion indicator when callers need to +walk the complete collection. Charge for the maximum work actually permitted. +Apply limits before reading or constructing the collection; calling an +unbounded runtime helper and slicing its returned vector is still unbounded. + +Do not expose node-only behavior that cannot execute deterministically in the +runtime. When a public RPC composes runtime state, implement the deterministic +runtime-side result and document any transport-only behavior that has no EVM +equivalent. + +## Add regression tests first + +For a bug fix, add a regression unit test that fails for the reported behavior +before implementing the fix. Confirm the failure is caused by the bug, then +apply the fix and confirm the same test passes. + +For an ABI-affecting runtime change, add a compatibility test that sends the +exact legacy calldata and decodes the result using the released ABI. A test +that only calls the new Rust helper or new selector does not prove backwards +compatibility. + +Keep precompile unit tests with the implementation's existing +`#[cfg(test)] mod tests` pattern and use `precompiles/src/mock.rs`. Reuse +`selector_u32`, `encode_with_selector`, `execute_returns`, +`execute_returns_raw`, and the established mock-state helpers where suitable. + +Name tests after observable behavior and the condition being protected. Avoid +tests that merely duplicate an implementation expression. + +## Test observable behavior + +Cover every affected path: + +- legacy success and return decoding; +- new selector success independently; +- invalid and boundary inputs; +- missing state; +- authorization and proxy origin; +- payable, nonpayable, attached-value, and static-call behavior; +- expected state transitions and rollback on failure; +- runtime dispatch errors and EVM errors; +- account and address conversion; +- TAO and Alpha unit conversion; +- precision, rounding, overflow, and narrowing; +- runtime-constant source values, units, and conversion boundaries; +- bounded collections and duplicate inputs; +- lifecycle status, hard-deprecation error, and disable/re-enable behavior when + applicable; and +- storage adapters against legacy and new state during migrations. + +Test both a representative normal case and the boundaries where conversion or +runtime semantics change. + +## Validate ABIs and routing + +Treat `precompiles/src/solidity/*.sol` and generated `*.abi` files as external +artifacts. Compare them with the relevant released or base-branch versions. + +Verify: + +1. Every old canonical signature and selector remains present. +2. Old input and output ABI encodings are unchanged. +3. Every new selector matches its canonical Solidity signature. +4. No selector collides with another selector at the address. +5. Only the intended Solidity interface and ABI gain the intended functions. +6. Unrelated precompile Solidity and ABI files are byte-for-byte unchanged. +7. The Rust macro signature, Solidity declaration, generated ABI, NatSpec, SDK + copies, registry metadata, and public documentation agree. + For deprecated functions, verify that registry metadata and Solidity + `@custom:deprecated` NatSpec are generated from the Rust function lifecycle + annotation rather than duplicated manually. +8. Every released address remains in `Precompiles::used_addresses()`. +9. `Precompiles::execute()` recognizes the address and routes it through the + intended availability control and compatible implementation. +10. Unknown-address and unknown-selector behavior remains unchanged. +11. Every new Bittensor domain uses the next reserved sequential address, and + address constants, `used_addresses()`, routing, documentation, Solidity + interfaces, and address-locking tests agree. + +Do not hand-wave generated-file churn. Inspect each changed ABI entry and +remove unrelated regeneration changes. + +## Validate cost and bounds + +Keep every precompile path bounded in CPU, memory, storage access, and output +size, except for the exact human-reviewed cases in +[Reviewed exceptions](exceptions.md). Record database reads and writes and +dispatch weight through the existing helpers. For an accepted scan exception, +test the protocol limit that makes the scan acceptable and charge for the +complete permitted scan. + +Test: + +- gas-limit rejection before overweight dispatch; +- post-dispatch charging and refund behavior when affected; +- the maximum accepted collection size; +- rejection just beyond the bound; +- proof-size-sensitive database access where relevant; +- failure paths that could otherwise perform unpaid work. + +Do not accept a bounded input if processing it can trigger an unbounded runtime +scan. Document any change large enough to make a previously practical call +unusable even if its asymptotic complexity is unchanged. + +## Run repository checks + +Run the narrowest relevant unit test while iterating, then run the complete +precompile package tests: + +```sh +cargo test -p subtensor-precompiles +``` + +Check formatting: + +```sh +cargo fmt --all --check +``` + +Run Clippy for the package when practical: + +```sh +SKIP_WASM_BUILD=1 cargo clippy \ + -p subtensor-precompiles \ + --all-targets \ + --all-features \ + -- -D warnings +``` + +Escalate to workspace checks or affected pallet tests when shared runtime +types, dispatchables, mocks, or routing changed. Report any check that could not +run and the specific reason; do not imply success from an unexecuted check. + +## Report the result + +Summarize: + +- source functionality added, changed, or still missing; +- runtime constants added, changed, or still missing; +- released addresses and selectors affected; +- adapters or new versions introduced; +- lifecycle or mainnet-release warnings; +- files and ABIs changed; +- evidence that unrelated precompiles and ABIs are unchanged; +- regression tests added and their before/after behavior; and +- commands run, results, and any remaining validation gaps. + +Do not claim completion while a required coverage row is unexplained or a +legacy caller test is missing. diff --git a/.agents/skills/evm-maintainer/references/exceptions.md b/.agents/skills/evm-maintainer/references/exceptions.md new file mode 100644 index 0000000000..5da61b2489 --- /dev/null +++ b/.agents/skills/evm-maintainer/references/exceptions.md @@ -0,0 +1,43 @@ +# Reviewed precompile exceptions + +This file records narrow, human-reviewed exceptions to the general state +coverage and bounded-work rules. Apply an exception only to the exact function +and invariant described here. Do not infer that a similar storage shape or +collection is also exempt. + +When reviewing one of these functions, verify that its supporting invariant +still holds. If the runtime changes that invariant, stop treating the function +as an exception and reassess its interface, compatibility, cost, and tests. + +## `getColdkeyLock(bytes32,uint256)` + +`getColdkeyLock` returns the one individual lock for a `(coldkey, netuid)`. +Although `Lock` includes the target hotkey in its storage key and the +implementation locates the row with `iter_prefix(...).next()`, multiple lock +rows are not valid state for that pair: + +- `do_lock_stake` creates the lock when none exists and rejects a different + target hotkey with `LockHotkeyMismatch` when one already exists; +- `move_lock` moves the existing lock to a new target instead of creating a + second lock; and +- the lock is subnet-wide for the coldkey, while the hotkey identifies its + current target. + +The precompile therefore reflects the runtime design accurately and does not +need a paginated or hotkey-keyed replacement. Keep tests proving that a second +target is rejected and that moving a lock leaves exactly one row. + +This exception becomes invalid if any lock creation, transfer, migration, or +repair path permits multiple `Lock` rows for the same `(coldkey, netuid)`. + +## `getSumAlphaPrice()` + +`getSumAlphaPrice` may scan every subnet. Subnets are a protocol-limited, +scarce resource, and the function's meaningful result is the aggregate over +the complete set. A cursor would change that meaning and move composition to +the caller. + +Keep the complete scan, charge for all permitted subnet reads, and test it at +the configured subnet limit. This exception does not apply to collections +whose size grows with accounts, neurons, stakes, commitments, or other +user-created records. diff --git a/.agents/skills/evm-maintainer/references/state-exposure.md b/.agents/skills/evm-maintainer/references/state-exposure.md new file mode 100644 index 0000000000..a6146e2efc --- /dev/null +++ b/.agents/skills/evm-maintainer/references/state-exposure.md @@ -0,0 +1,81 @@ +# Rules for exposing state and runtime constants + +This file lists concrete state variables, maps, and runtime constants and +classifies them as one of three classes: + +1. Safe to expose directly, as is, or +2. Need some type-safe wrapping, or +3. Internal, do not need to be exposed, or already known to be deprecated soon + +The class 1 items are not anticipated to change significantly. Even if they do, +their exposed values should remain honestly reproducible with no greater than +O(1) complexity. + +The class 2 items are temporary, use unstable internal representations, or +express complex formulas and need to be safely wrapped. + +## Runtime constants + +Inventory every public runtime constant declared by or supplied to the +configuration of an in-scope pallet. Expose it directly or through a coherent +typed grouped view, reading the authoritative runtime source rather than +copying its literal value into the precompile. + +Preserve semantic types and units when converting Rust values to Solidity. +Treat fixed-point values, balances, block numbers, bounded sizes, and other +representation-specific constants as type-safe wrapping cases when their Rust +representation is not a suitable permanent ABI. + +This requirement covers deterministic client-facing runtime configuration. It +does not cover generated weights, compiler/build constants, or private +implementation details that are not part of the pallet's public behavior. + +## Safe to expose directly + +### Pallet subtensor + +- Delegation and childkeys: Delegates, ChildkeyTake, PendingChildKeys, ChildKeys, ParentKeys, PendingChildKeyCooldown, minimum/maximum delegate and childkey takes, and MinChildkeyTakePerSubnet. + +- Ownership and account relationships: OwnedHotkeys, AutoStakeDestination, AutoStakeDestinationColdkeys, HotkeySuccessor, HotkeyRoot, ColdkeySuccessor, ColdkeyRoot, coldkey-swap announcements/disputes/delays, and LastHotkeySwapOnNetuid. Owner is only indirectly available when the caller already knows a subnet UID, so arbitrary hotkey ownership is only partially covered. + +- Subnet identity and configuration: TokenSymbol, SubnetOwner, SubnetOwnerHotkey, Tempo, RecycleOrBurn, BondsPenalty, MaxAllowedUids, MaxAllowedValidators, AdjustmentInterval, TargetRegistrationsPerInterval, OwnerCutEnabled, ImmuneOwnerUidsLimit, MechanismCountCurrent, MechanismEmissionSplit, BurnHalfLife, BurnIncreaseMult, TransferToggle, MinAllowedUids, MinNonImmuneUids, and numerous global network limits. + +- Emission and economic accounting: BlockEmission, Subtensor TotalIssuance, TotalStake, AlphaDividendsPerSubnet, RootAlphaDividendsPerSubnet, LastHotkeyEmissionOnNetuid, SubnetMovingAlpha, RootProp, SubnetEmissionEnabled, SubnetExcessTao, SubnetRootSellTao, SubnetProtocolAlpha, flow/EMA maps, emission gate configuration, pending emission/cut maps, MinerBurned, and RAORecycledForRegistration. + +- Neuron state: Uids, IsNetworkMember, Weights, Bonds, BlockAtRegistration, NeuronCertificates, Prometheus, IdentitiesV2, SubnetIdentitiesV3, LoadedEmission, transaction-rate timestamps, and all weight-commit maps and versions. + +- Collateral and leasing: MinerCollateral, ColdkeyMinerCollateral, ColdkeyCollateralHotkeys, CollateralLockShare, CollateralDrainRatio, NextSubnetLeaseId, and AccumulatedLeaseDividends. + +- EVM associations: Forward view for AssociatedEvmAddress(netuid, uid). + +### Pallet balances + +TotalIssuance + +### Pallet Proxy + +proxy deposit, Announcements, LastCallResult, RealPaysFee + +### Pallet Swap + +FeeRate, SwapBalancer, BalancerTaoReservoir, BalancerAlphaReservoir, HasMigrationRun + +## Need some type-safe wrapping + +### Pallet Swap + +PalSwapInitialized and its successors should be exposed as just generic "IsSwapInitialized", non-specific to palswap / balancer. + +## Do not expose + +### Pallet subtensor + +- Root claims: RootClaimableThreshold, RootClaimable, RootClaimed, RootClaimType. + +### Pallet balances + +InactiveIssuance, the reserved, frozen, and flags portions of Account: Locks, Reserves, Holds, Freezes + +### Pallet swap + +ScrapReservoirAlpha diff --git a/docs/guides/evm/index.mdx b/docs/guides/evm/index.mdx index b7b591cb62..8944daa5d4 100644 --- a/docs/guides/evm/index.mdx +++ b/docs/guides/evm/index.mdx @@ -39,11 +39,21 @@ deeper concepts (address mappings, decimals, precompiles). End-to-end tutorials that build on the commands below: + + + A deployed contract may be immutable. Treat every released precompile address, + function signature, and selector as a permanent public API. + + +## Design goals + +The precompile layer is designed around five goals: + +1. **Contracts at rest keep working.** Runtime upgrades must not silently break + deployed contracts. +2. **Interfaces evolve additively.** Existing selectors remain reserved, and + richer behavior is introduced through new function versions. +3. **Deprecation is normally soft.** An old function continues to preserve its + original behavior whenever that behavior can still be represented safely. +4. **Status is discoverable.** Solidity interfaces and a registry should tell + developers when a function is deprecated, replaced, or temporarily disabled. +5. **The signed, deterministic Substrate API has typed parity.** Storage and + runtime API results have bounded typed views, and extrinsics that accept a + non-Root signed origin have typed operations. + +## Fixed addresses and function selectors + +A precompile has a fixed EVM address for a domain such as staking, metagraph +data, or subnet operations. Solidity dispatches a call using the first four +bytes of the Keccak-256 hash of its canonical function signature. + +For example: + +```solidity +function getStake(uint16 netuid, uint16 uid) external view returns (uint64); +``` + +The selector belongs to that signature permanently once released. It must not +later be assigned different semantics, even if the original function is +hard-deprecated. Reusing a selector could make an old contract decode a +successful but unrelated result. + +The source-of-truth Solidity interfaces and generated ABIs live in +[`precompiles/src/solidity/`](https://github.com/RaoFoundation/subtensor/tree/main/precompiles/src/solidity). + +## Compatibility rules + +### Preserve released interfaces + +Do not remove or change a released function signature. A runtime implementation +may change internally to follow a new storage layout or computation, but the +observable result must retain the function's documented meaning. + +Changing any of these creates a different EVM interface: + +- function name or version suffix; +- parameter types or order; +- return types or order; +- mutability where it affects permitted calls; +- precompile address. + +### Version functions, not whole domains + +When a breaking return-type or parameter change is necessary, add a versioned +function at the same precompile address: + +```solidity +interface IMetagraph { + // Original selector remains supported. + function getStake( + uint16 netuid, + uint16 uid + ) external view returns (uint64); + + // New selector exposes the richer representation. + function getStakeV2( + uint16 netuid, + uint16 uid + ) external view returns (StakeInfo memory); +} +``` + +Use `functionName` for the initial version, followed by `functionNameV2`, +`functionNameV3`, and so on. Both selectors route independently, so adding a +version does not alter calls made by existing contracts. + +Creating a new domain address may still be appropriate when the functionality +is genuinely a different precompile, but it should not be the default +versioning mechanism. + +New Bittensor domain addresses are assigned sequentially from the next unused +Bittensor address. The currently proposed domains reserve: + +| Address | Domain | +|---|---| +| `0x000000000000000000000000000000000000080f` | Scheduler | +| `0x0000000000000000000000000000000000000810` | Drand | +| `0x0000000000000000000000000000000000000811` | Timestamp | +| `0x0000000000000000000000000000000000000812` | Runtime configuration | +| `0x0000000000000000000000000000000000000813` | Precompile registry | + +An address reservation does not make a proposed precompile callable. When an +implementation is added, routing and tests must lock the address and every +implemented selector before release. + +### Keep old semantics when possible + +Suppose `getStake` originally returned total stake, while a later runtime stores +self-stake and delegated stake separately. The original function can continue +returning their sum, while `getStakeV2` returns the breakdown. + +This is a soft deprecation: the old selector remains correct for callers that +depend on its original meaning. + +## Replace raw storage access with typed views + +Raw storage access couples a contract to pallet names, storage item names, +hashers, key shapes, and SCALE encodings. Any internal refactor can then make +the contract read an empty value or decode the wrong bytes without a useful +error. + +A typed view instead owns the encoding and decoding: + +```solidity +uint64 weight = IMetagraph(METAGRAPH_ADDRESS).getWeight(netuid, uid); +``` + +If the underlying storage map, key format, hasher, or value encoding changes, +the precompile implementation adapts while the Solidity interface remains +stable. A resulting Rust compilation failure provides a safety net that raw +storage queries do not. + +### Bound collection views + +Runtime APIs and storage collections that grow with chain state must not be +copied into a single Solidity function returning an unbounded array. Expose an +indexed item with a bounded count, or use a cursor and a caller-supplied limit +that is capped by a fixed runtime maximum. A fixed-size batch of explicit keys +is also suitable when callers already know which records they need. + +The bound must apply before storage is scanned or results are constructed. +Calling an unbounded runtime helper and truncating its result afterward does +not make the precompile bounded. Paginated views should return a next cursor or +completion indicator and define stable ordering, missing-item behavior, and +the maximum page size. + +### Preserve runtime authorization + +A state-changing precompile dispatches the highest-level pallet extrinsic with +the mapped EVM caller as a signed origin. The pallet then enforces the same +ownership, role, rate-limit, freeze-window, and validation checks that apply to +an ordinary signed Substrate transaction. + +An extrinsic that permits either Root or a non-Root signer, such as a subnet +owner, may expose its signed path. Root-only and `None`-only extrinsics are not +exposed through typed EVM functions. A precompile must never substitute Root, +invoke an internal state-changing helper, or reproduce the extrinsic logic to +bypass the top-level checks. + +### Read-only infrastructure views + +Read-only precompiles let contracts inspect deterministic consensus state +without receiving any authority to change it: + +- Scheduler views expose bounded task metadata so contracts can verify whether + and when runtime work is scheduled. +- Drand views expose beacon configuration, stored pulses, and round ranges for + contract logic that depends on the runtime's randomness state. +- Timestamp views replace raw reads of timestamp storage; `getTimestamp` + corresponds to the same underlying time represented by `block.timestamp`. +- Lifecycle views let contracts and tooling discover whether a selector is + deprecated, replaced, or currently unavailable. + +These views replace raw storage decoding or off-chain RPC composition. They do +not execute privileged extrinsics and do not provide a path to Root. + +### Phasing out raw storage reads + +`StorageQueryPrecompile` at `0x…0807` exposes raw Substrate storage and is +inherently brittle. The intended migration is: + +1. Add a bounded typed view for every storage item in the currently authorized pallets: + SubtensorModule, Balances, Proxy, Scheduler, Drand, Crowdloan, Sudo, + Multisig, Timestamp, and Swap. +2. Soft-deprecate raw storage access after that typed coverage exists. +3. Hard-deprecate it after a documented migration window. +4. Eventually disable it through an explicit root decision. + +Whether this 1:1 coverage should extend beyond the authorized pallets remains +an open design question. + +## Project-scoped event relays + +Substrate events are already recorded in chain data. Reproducing the complete +event stream through protocol-level EVM callbacks would add another on-chain +copy together with subscription storage, delivery queues, and callback +execution. It would also force the runtime to support broad event delivery even +when an application needs only a small, highly filtered set of signals. + +Bittensor therefore does not propose event-reporting precompiles. A project +that needs proactive notifications in its EVM contracts should run an +off-chain relay tailored to that project's use cases. The relay watches +finalized Substrate events, performs application-specific filtering, +aggregation, and enrichment off chain, and submits only the reports that the +project's contracts can act on. + +Typed precompile views remain the authoritative way for contracts to read +current runtime state. Relayed reports are notifications under the trust and +availability model chosen by the project. + +### Relay flow + +A typical relay operates as follows: + +1. Relay nodes read finalized blocks and events from Substrate RPC endpoints or + an indexer. +2. Each node applies the project's filters and derives a canonical typed + report. +3. A configured signer quorum attests to the report. +4. A relayer submits the report and its authorization proof in an ordinary EVM + transaction. +5. The reporting contract verifies the report, rejects duplicates, and either + emits a typed EVM log, invokes a bounded set of subscribed receivers, or + records data for receivers to pull. + +A report should identify at least the source chain, finalized block hash and +number, source event position or another unique event identifier, schema +version, payload, and relay sequence or nonce. The signed message must be +domain-separated by chain ID, reporting-contract address, and schema version so +that it cannot be replayed on another chain, contract, or report type. + +Filtering belongs primarily in the relay. A subnet application might publish +only completed tempo summaries, material configuration changes, or aggregate +emission results instead of reproducing every underlying pallet event. + +### Subscription-capable reporting contracts + +A project can deploy a reporting contract that lets users or other contracts +register subscriptions and lets authorized relayers submit observed reports. +A subscription can select typed report kinds, project-specific filters, a +receiver, and a callback gas limit. The contract should make its payment, +retry, ordering, and removal rules explicit. + +Neither report submission nor callback delivery should iterate an unbounded +subscriber set. Limit each transaction to a fixed-size batch, let relayers +target matching subscribers explicitly, or let subscribers pull verified +reports. Catch callback failures so one receiver cannot revert delivery to +others, and require receiver callbacks to be idempotent. + +Every successful relay submission has an EVM transaction and receipt. Projects +must decide whether relayers fund these transactions, subscribers prepay for +delivery, or another project account subsidizes them. + +### Relayer trust and security + +A single relay signer is the simplest design but makes that signer a trusted +oracle. Projects that need stronger guarantees can use an independently +operated committee with an explicit `M-of-N` multisignature, a threshold +signature scheme, or another auditable quorum mechanism. The reporting +contract must define signer enrollment, quorum, key rotation, emergency +revocation, and version upgrades. + +Relay implementations should also: + +- wait for the documented source-chain finality condition; +- use deterministic report encoding and reject duplicate event identifiers; +- expose sequences or source positions so receivers can detect gaps; +- tolerate delayed, reordered, and repeated submissions; +- bound report size, callback gas, batch size, and retained on-chain history; +- separate observation from submission so any permitted party can submit a + valid quorum-authorized report; and +- provide a reconciliation path through typed precompile views when a report is + missing or disputed. + +Contracts must not treat relayed events as consensus-authenticated merely +because they describe on-chain activity. Their integrity depends on the relay +committee and verification rules, while their availability depends on relay +operators continuing to observe and submit reports. + +## Function lifecycle + +Deprecation and disablement are different dimensions: + +- **Deprecation** communicates API evolution. It normally points callers toward + a replacement and is expected to remain part of the function's history. +- **Disablement** is an operational switch for an entire precompile. Root can + disable and later re-enable it through + `AdminUtils.sudo_toggle_evm_precompile`. + +| Lifecycle condition | Call behavior | +|---|---| +| Active and enabled | Executes normally | +| Soft-deprecated and enabled | Preserves its documented behavior | +| Hard-deprecated and enabled | Returns a descriptive precompile error | +| Disabled | Returns a precompile-disabled error regardless of function lifecycle | + +Soft deprecation is the default. Hard deprecation is reserved for cases where +the original behavior cannot be represented honestly or safely—for example, +when the underlying concept has been removed without a replacement. + +Disablement does not erase deprecation metadata. A soft-deprecated function can +also be disabled, and re-enabling its precompile restores its soft-deprecated +behavior. + +## Discovering status + +The proposed standalone registry precompile gives tooling and contracts one +place to inspect both API lifecycle and operational availability. + +Because the result covers both lifecycle and operational availability, it is +called `PrecompileStatus`: + +```solidity +interface IPrecompileRegistry { + struct PrecompileStatus { + bool isDeprecated; + bool isDisabled; + address newPrecompile; + bytes4 newSelector; + string message; + } + + function getPrecompileStatus( + address precompile, + bytes4 selector + ) external view returns (PrecompileStatus memory); +} +``` + +The fields have the following meaning: + +| Field | Meaning | +|---|---| +| `isDeprecated` | The function is soft- or hard-deprecated. | +| `isDisabled` | The containing precompile is currently disabled by Root; Root can re-enable it. | +| `newPrecompile` | Address of the recommended replacement, often the same address. | +| `newSelector` | Selector of the recommended replacement function. | +| `message` | Human-readable status or migration guidance. | + +Zero replacement fields mean that no replacement is available. Tooling should +not infer that `isDisabled` implies deprecation, or that re-enabling a +precompile clears `isDeprecated`. + +The registry avoids adding overhead to every deprecated call. Deployment tools, +frontends, and upgradeable contracts can query it when evaluating dependencies. + +Solidity interfaces should also carry NatSpec annotations: + +```solidity +interface IMetagraph { + /// @deprecated Use getStakeV2 instead. + function getStake( + uint16 netuid, + uint16 uid + ) external view returns (uint64); + + function getStakeV2( + uint16 netuid, + uint16 uid + ) external view returns (StakeInfo memory); +} +``` + +## Handling runtime changes + +### Additive representation changes + +Keep the original function returning the original subset, add a versioned +function for the extended result, and soft-deprecate the original if callers +should migrate. + +### Semantic refinements + +Adapt the original implementation to preserve its documented meaning. Add a new +version only when callers need a representation that the original return type +cannot express. + +### Storage and computation changes + +Change the precompile implementation without changing its interface. This +includes changing: + +- storage names, key shapes, or hashers; +- the number of storage items used; +- intermediate representations; +- the computation used to produce the exposed value. + +### Complete removal + +Keep the selector reserved and make the function return a descriptive error. +Mark it hard-deprecated and explain whether an alternative exists. Do not +delete the signature and do not reuse its selector. + +### Emergency disablement + +Root may disable a precompile with: + +```text +AdminUtils.sudo_toggle_evm_precompile(precompile_id, false) +``` + +and re-enable it with: + +```text +AdminUtils.sudo_toggle_evm_precompile(precompile_id, true) +``` + +This switch is reversible and applies to the precompile as a whole. It is not a +substitute for function-level lifecycle metadata or a normal deprecation +process. + +## Maintenance and testing requirements + +Every precompile change should verify: + +- all previously released selectors remain routed; +- existing function signatures and return encodings are unchanged; +- old semantics are preserved or explicitly hard-deprecated; +- new behavior uses a new versioned selector when necessary; +- Solidity interfaces, generated ABIs, SDK copies, and runtime implementations + agree; +- lifecycle registry metadata and NatSpec annotations agree; +- disable and re-enable behavior is covered for the affected precompile; +- state-changing functions dispatch the highest-level extrinsic as the mapped + signed caller and do not bypass its authorization checks; +- bulk views are bounded before they read or construct results; +- new domain addresses follow the documented sequential reservation and are + locked by routing tests; +- no selector is reused. + +Typed views provide a compile-time safety advantage: when runtime types or +storage APIs change, the Rust implementation is more likely to stop compiling, +forcing maintainers to make an explicit compatibility decision. Coverage checks +should ensure that every storage item in the authorized pallets has a +corresponding view. + +Macro or code-generation support may eventually reduce boilerplate and validate +selector coverage, ABI synchronization, and registry entries. The compatibility +rules should remain explicit even if their enforcement becomes automated. + +## Summary + +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; report both +through a common status model without treating them as the same condition. diff --git a/docs/guides/evm/precompiles/account-balance.mdx b/docs/guides/evm/precompiles/account-balance.mdx new file mode 100644 index 0000000000..ac400bc3c1 --- /dev/null +++ b/docs/guides/evm/precompiles/account-balance.mdx @@ -0,0 +1,33 @@ +--- +title: Account balance +description: Reference for the deployed BalancePrecompile. +--- + +| Property | Value | +|---|---| +| Rust implementation | `BalancePrecompile` | +| Solidity interface | `IBalance` | +| Address | `0x000000000000000000000000000000000000080e` | +| Status | Deployed | + +## Functions + +| Function | Mutability | +|---|---| +| `getFreeBalance(bytes32)` | `view` | + +## Proposed operations + +| Proposed function | Source extrinsic | +|---|---| +| `burnBalance` | `Balances.burn` | +| `upgradeAccounts` | `Balances.upgrade_accounts` | + +`upgradeAccounts` must have an explicit fixed input bound. The implementation +must dispatch the highest-level Balances call as the mapped signer and preserve +all runtime authorization and issuance invariants. Force operations require +Root and are not exposed. + +Proposed names and signatures do not reserve selectors. + +Source: [`balance.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/balance.sol) diff --git a/docs/guides/evm/precompiles/address-mapping.mdx b/docs/guides/evm/precompiles/address-mapping.mdx new file mode 100644 index 0000000000..1561208e92 --- /dev/null +++ b/docs/guides/evm/precompiles/address-mapping.mdx @@ -0,0 +1,20 @@ +--- +title: Address mapping +description: Reference for the deployed AddressMappingPrecompile. +--- + +| Property | Value | +|---|---| +| Rust implementation | `AddressMappingPrecompile` | +| Solidity interface | `IAddressMapping` | +| Address | `0x000000000000000000000000000000000000080c` | +| Status | Deployed | + +## Functions + +| Function | Mutability | +|---|---| +| `addressMapping(address)` | `view` | + +Source: [`addressMapping.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/addressMapping.sol) + diff --git a/docs/guides/evm/precompiles/alpha.mdx b/docs/guides/evm/precompiles/alpha.mdx new file mode 100644 index 0000000000..cb69de1c74 --- /dev/null +++ b/docs/guides/evm/precompiles/alpha.mdx @@ -0,0 +1,55 @@ +--- +title: Alpha +description: Reference for the deployed AlphaPrecompile. +--- + +| Property | Value | +|---|---| +| Rust implementation | `AlphaPrecompile` | +| Solidity interface | `IAlpha` | +| Address | `0x0000000000000000000000000000000000000808` | +| Status | Deployed | + +Provides typed views of subnet pools, prices, issuance, emissions, and simulated +swaps. All functions are `view`. + +## Functions + +```text +getAlphaPrice(uint16) +getMovingAlphaPrice(uint16) +getTaoInPool(uint16) +getAlphaInPool(uint16) +getAlphaOutPool(uint16) +getAlphaIssuance(uint16) +getTaoWeight() +simSwapTaoForAlpha(uint16,uint64) +simSwapAlphaForTao(uint16,uint64) +getSubnetMechanism(uint16) +getRootNetuid() +getEMAPriceHalvingBlocks(uint16) +getSubnetVolume(uint16) +getTaoInEmission(uint16) +getAlphaInEmission(uint16) +getAlphaOutEmission(uint16) +getSumAlphaPrice() +getCKBurn() +``` + +## Proposed operations + +| Proposed function | Source extrinsic | +|---|---| +| `setRecycleOrBurn` | `AdminUtils.sudo_set_recycle_or_burn` | +| `setBurnHalfLife` | `AdminUtils.sudo_set_burn_half_life` | +| `setBurnIncreaseMultiplier` | `AdminUtils.sudo_set_burn_increase_mult` | + +The five deprecated `Swap` liquidity extrinsics are intentionally not proposed; +they always return the pallet's `Deprecated` error. `Swap.set_fee_rate` and the +remaining Alpha-related AdminUtils calls require Root and are not exposed. +The three listed AdminUtils calls accept a signed subnet owner; only that +signed path is exposed and runtime authorization remains in force. + +Proposed names and signatures do not reserve selectors. + +Source: [`alpha.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/alpha.sol) diff --git a/docs/guides/evm/precompiles/balance-transfer.mdx b/docs/guides/evm/precompiles/balance-transfer.mdx new file mode 100644 index 0000000000..46d79d9f7b --- /dev/null +++ b/docs/guides/evm/precompiles/balance-transfer.mdx @@ -0,0 +1,38 @@ +--- +title: Balance transfer +description: Reference for the deployed BalanceTransferPrecompile. +--- + +| Property | Value | +|---|---| +| Rust implementation | `BalanceTransferPrecompile` | +| Solidity interface | `ISubtensorBalanceTransfer` | +| Address | `0x0000000000000000000000000000000000000800` | +| Status | Deployed | + +Transfers the EVM call value to the Substrate account supplied as a 32-byte +public key. + +## Functions + +| Function | Mutability | +|---|---| +| `transfer(bytes32)` | `payable` | + +## Proposed additions + +| Proposed function | Source extrinsic | +|---|---| +| `transferKeepAlive` | `Balances.transfer_keep_alive` | +| `transferAll` | `Balances.transfer_all` | + +The existing `transfer(bytes32)` semantically covers +`Balances.transfer_allow_death` by taking the amount from attached EVM value. +The proposed functions use explicit typed arguments where attached value does +not express the complete source operation. They dispatch the highest-level +Balances call as the mapped signer. `force_transfer` requires Root and the +feature-gated development faucet is not part of the production interface. + +Proposed names and signatures do not reserve selectors. + +Source: [`balanceTransfer.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/balanceTransfer.sol) diff --git a/docs/guides/evm/precompiles/crowdloan.mdx b/docs/guides/evm/precompiles/crowdloan.mdx new file mode 100644 index 0000000000..d563faf76e --- /dev/null +++ b/docs/guides/evm/precompiles/crowdloan.mdx @@ -0,0 +1,47 @@ +--- +title: Crowdloan +description: Reference for the deployed CrowdloanPrecompile. +--- + +| Property | Value | +|---|---| +| Rust implementation | `CrowdloanPrecompile` | +| Solidity interface | `ICrowdloan` | +| Address | `0x0000000000000000000000000000000000000809` | +| Status | Deployed | + +## Views + +```text +getCrowdloan(uint32) +getContribution(uint32,bytes32) +``` + +## Operations + +All operations are `payable`: + +```text +create(uint64,uint64,uint64,uint32,address) +contribute(uint32,uint64) +withdraw(uint32) +finalize(uint32) +refund(uint32) +dissolve(uint32) +updateMinContribution(uint32,uint64) +updateEnd(uint32,uint32) +updateCap(uint32,uint64) +``` + +## Proposed addition + +| Proposed function | Source extrinsic | +|---|---| +| `setMaxContribution` | `Crowdloan.set_max_contribution` | + +The typed interface must preserve the source call's optional value so the +creator can either set or clear the per-contributor maximum. + +The proposed name and signature do not reserve a selector. + +Source: [`crowdloan.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/crowdloan.sol) diff --git a/docs/guides/evm/precompiles/drand.mdx b/docs/guides/evm/precompiles/drand.mdx new file mode 100644 index 0000000000..970621c48b --- /dev/null +++ b/docs/guides/evm/precompiles/drand.mdx @@ -0,0 +1,36 @@ +--- +title: Drand +description: Proposed typed EVM interface for the Drand pallet. +--- + +| Property | Value | +|---|---| +| Proposed implementation | `DrandPrecompile` | +| Proposed Solidity interface | `IDrand` | +| Reserved address | `0x0000000000000000000000000000000000000810` | +| Status | Proposed; not yet callable | + +This precompile would expose typed beacon configuration and pulse data instead +of requiring callers to construct Drand storage keys and decode SCALE values. +Contracts can use the runtime's stored randomness state deterministically +without receiving permission to configure the beacon or submit pulses. + +## Planned views + +| Function | Replaces | +|---|---| +| `getBeaconConfig()` | `Drand.BeaconConfig` | +| `getPulse(uint64 round)` | `Drand.Pulses` | +| `getStoredRoundRange()` | `Drand.OldestStoredRound` and `Drand.LastStoredRound` | +| `getNextUnsignedAt()` | `Drand.NextUnsignedAt` | +| `hasMigrationRun(bytes key)` | `Drand.HasMigrationRun` | + +## State-changing operations + +`Drand.set_beacon_config` and `Drand.set_oldest_stored_round` require Root, and +`Drand.write_pulse` requires `None` origin as an unsigned offchain-worker +submission. None is exposed as a typed EVM operation because doing so would +bypass the pallet's top-level origin checks. + +The address is reserved for this domain. Names and signatures on this page are +provisional and do not reserve selectors. diff --git a/docs/guides/evm/precompiles/extrinsic-coverage.mdx b/docs/guides/evm/precompiles/extrinsic-coverage.mdx new file mode 100644 index 0000000000..1688bf4d57 --- /dev/null +++ b/docs/guides/evm/precompiles/extrinsic-coverage.mdx @@ -0,0 +1,87 @@ +--- +title: Extrinsic coverage +description: Audit of typed EVM coverage for every extrinsic in the authorized runtime pallets. +--- + +This audit covers the runtime's `SubtensorModule`, `AdminUtils`, `Balances`, +`Proxy`, `Scheduler`, `Drand`, `Crowdloan`, `Timestamp`, and `Swap` pallets. +Sudo and Multisig extrinsics are intentionally outside typed precompile +coverage. + +Generic SCALE dispatch through the Frontier `Dispatch` precompile does not +count as typed coverage. A covered operation must have a stable Solidity +interface or an explicit proposed typed replacement. + +## Coverage summary + +| Pallet | Runtime extrinsics | Typed today | Proposed signed additions | Not exposed | +|---|---:|---:|---:|---:| +| `SubtensorModule` | 82 | 24 | 44 | 14 | +| `AdminUtils` | 86 | 24 | 16 | 46 | +| `Balances` | 9 | 1 | 4 | 4 | +| `Proxy` | 12 | 7 | 5 | 0 | +| `Scheduler` | 10 | 0 | 0 | 10 | +| `Drand` | 3 | 0 | 0 | 3 | +| `Crowdloan` | 10 | 9 | 1 | 0 | +| `Timestamp` | 1 | 0 | 0 | 1 | +| `Swap` | 6 | 0 | 0 | 6 | +| **Total** | **219** | **65** | **70** | **84** | + +`Typed today` counts semantic coverage, not only direct dispatch to the same +Rust call. For example, `registerNetwork(bytes32)` covers basic subnet +registration by dispatching `register_network_with_identity` with empty +identity fields. + +`Proposed signed additions` includes extrinsics whose highest-level pallet call +accepts a non-Root signed origin. If a call also accepts Root, only its signed +path is exposed: the mapped EVM caller is dispatched as `Signed`, and the +pallet performs its normal authorization checks. + +## Classification of proposed signed additions + +Each missing operation is listed on the page of its target precompile: + +| Target precompile | Missing extrinsics assigned | +|---|---:| +| [Subnet](/docs/guides/evm/precompiles/subnet) | 13 | +| [Staking V2](/docs/guides/evm/precompiles/staking-v2) | 20 | +| [Neuron](/docs/guides/evm/precompiles/neuron) | 21 | +| [Alpha](/docs/guides/evm/precompiles/alpha) | 3 | +| [Account balance](/docs/guides/evm/precompiles/account-balance) | 2 | +| [Proxy](/docs/guides/evm/precompiles/proxy) | 5 | +| [Balance transfer](/docs/guides/evm/precompiles/balance-transfer) | 2 | +| [Voting power](/docs/guides/evm/precompiles/voting-power) | 2 | +| [Leasing](/docs/guides/evm/precompiles/leasing) | 1 | +| [Crowdloan](/docs/guides/evm/precompiles/crowdloan) | 1 | + +Proposed function names do not reserve selectors. Their final parameter types, +bounds, and return values must be specified before implementation. Each +implementation must dispatch the highest-level pallet extrinsic as the mapped +signed caller rather than reproducing its logic. + +## Extrinsics not exposed as EVM calls + +| Pallet extrinsic | Reason | +|---|---| +| Root-only `SubtensorModule` extrinsics | `dissolve_network`, `root_dissolve_network`, `swap_coldkey`, `sudo_set_tx_childkey_take_rate_limit`, `sudo_set_min_childkey_take`, `sudo_set_max_childkey_take`, `set_pending_childkey_cooldown`, `reset_coldkey_swap`, `sudo_set_num_root_claims`, and `sudo_set_voting_power_ema_alpha` require Root. | +| `SubtensorModule.schedule_swap_coldkey` | Deprecated compatibility call that always returns `Deprecated`. | +| `SubtensorModule.faucet` | Build-feature-only development call; it is not part of the production runtime interface. | +| `SubtensorModule.set_tempo` | Retained call-index compatibility entry point that succeeds without changing state. The real setting is `AdminUtils.sudo_set_tempo`, proposed as `SubnetPrecompile.setTempo`. | +| `SubtensorModule.set_activity_cutoff_factor` | Retained call-index compatibility entry point that succeeds without changing state. The active AdminUtils operation is already covered by `SubnetPrecompile.setActivityCutoffFactor`. | +| Root-only `AdminUtils` extrinsics | Root-only administration is not delegated to EVM callers. Calls that also accept a signed subnet owner remain in the proposed signed additions on the domain pages. | +| `AdminUtils.sudo_set_total_issuance` | Deprecated call that always returns `Deprecated`. | +| Root-only `Balances` extrinsics | `force_unreserve`, `force_transfer`, `force_set_balance`, and `force_adjust_total_issuance` require Root. | +| All `Scheduler` extrinsics | `Scheduler.ScheduleOrigin` is configured as Root in the runtime. | +| `Drand.write_pulse` | Unsigned offchain-worker submission requiring `None` origin. An EVM caller cannot satisfy that origin without changing its security model. | +| Drand configuration extrinsics | `set_beacon_config` and `set_oldest_stored_round` require Root. | +| `Timestamp.set` | Block-production inherent requiring `None` origin. Contracts already receive the same time through `block.timestamp`. | +| `Swap.set_fee_rate` | Requires Root. | +| `Swap.add_liquidity` | Permanently disabled pallet call that always returns `Deprecated`. | +| `Swap.remove_liquidity` | Permanently disabled pallet call that always returns `Deprecated`. | +| `Swap.modify_position` | Permanently disabled pallet call that always returns `Deprecated`. | +| `Swap.toggle_user_liquidity` | Permanently disabled pallet call that always returns `Deprecated`. | +| `Swap.disable_lp` | Permanently disabled pallet call that always returns `Deprecated`. | + +These exclusions preserve the existing runtime origin and lifecycle semantics. +A typed precompile must not manufacture Root or `None`, call an internal helper, +or write storage directly to make one of these operations callable. diff --git a/docs/guides/evm/precompiles/index.mdx b/docs/guides/evm/precompiles/index.mdx new file mode 100644 index 0000000000..902b925be8 --- /dev/null +++ b/docs/guides/evm/precompiles/index.mdx @@ -0,0 +1,66 @@ +--- +title: Precompiles +description: Addresses, implementations, and reference pages for Bittensor EVM precompiles. +--- + +Bittensor precompiles are fixed-address contracts implemented by the Subtensor +runtime. `Deployed` means that the address is registered in the current runtime; +it does not imply complete coverage of the underlying runtime domain. +`Proposed` precompiles are not callable. Their documented addresses are +reserved for those domains, while their function selectors remain provisional +until the interfaces are implemented and released. + +The [extrinsic coverage audit](/docs/guides/evm/precompiles/extrinsic-coverage) +tracks every runtime extrinsic in scope and identifies its deployed, proposed, +or intentionally non-callable EVM treatment. + +## Ethereum and Frontier precompiles + +| Precompile | Address | Status | +|---|---|---| +| `ECRecover` | | Deployed | +| `Sha256` | | Deployed | +| `Ripemd160` | | Deployed | +| `Identity` | | Deployed | +| `Modexp` | | Deployed | +| `Dispatch` | | Deployed | +| `Bn128Mul` | | Deployed | +| `Bn128Pairing` | | Deployed | +| `Bn128Add` | | Deployed | +| `Sha3FIPS256` | | Deployed | +| `ECRecoverPublicKey` | | Deployed | +| `Ed25519Verify` | | Deployed | +| `Sr25519Verify` | | Deployed | + +## Bittensor precompiles + +| Precompile | Solidity interface | Details | +|---|---|---| +| [`BalanceTransferPrecompile`](/docs/guides/evm/precompiles/balance-transfer) | `ISubtensorBalanceTransfer` |
Deployed | +| [`StakingPrecompile`](/docs/guides/evm/precompiles/staking-v1) | `IStaking` V1 |
Deployed | +| [`MetagraphPrecompile`](/docs/guides/evm/precompiles/metagraph) | `IMetagraph` |
Deployed | +| [`SubnetPrecompile`](/docs/guides/evm/precompiles/subnet) | `ISubnet` |
Deployed | +| [`NeuronPrecompile`](/docs/guides/evm/precompiles/neuron) | `INeuron` |
Deployed | +| [`StakingPrecompileV2`](/docs/guides/evm/precompiles/staking-v2) | `IStaking` V2 |
Deployed | +| [`UidLookupPrecompile`](/docs/guides/evm/precompiles/uid-lookup) | `IUidLookup` |
Deployed | +| [`StorageQueryPrecompile`](/docs/guides/evm/precompiles/storage-query) | Selectorless |
Deployed · deprecation planned | +| [`AlphaPrecompile`](/docs/guides/evm/precompiles/alpha) | `IAlpha` |
Deployed | +| [`CrowdloanPrecompile`](/docs/guides/evm/precompiles/crowdloan) | `ICrowdloan` |
Deployed | +| [`LeasingPrecompile`](/docs/guides/evm/precompiles/leasing) | `ILeasing` |
Deployed | +| [`ProxyPrecompile`](/docs/guides/evm/precompiles/proxy) | `IProxy` |
Deployed | +| [`AddressMappingPrecompile`](/docs/guides/evm/precompiles/address-mapping) | `IAddressMapping` |
Deployed | +| [`VotingPowerPrecompile`](/docs/guides/evm/precompiles/voting-power) | `IVotingPower` |
Deployed | +| [`BalancePrecompile`](/docs/guides/evm/precompiles/account-balance) | `IBalance` |
Deployed | +| [`SchedulerPrecompile`](/docs/guides/evm/precompiles/scheduler) | `IScheduler` |
Proposed · address reserved | +| [`DrandPrecompile`](/docs/guides/evm/precompiles/drand) | `IDrand` |
Proposed · address reserved | +| [`TimestampPrecompile`](/docs/guides/evm/precompiles/timestamp) | `ITimestamp` |
Proposed · address reserved | +| [`RuntimeConfigurationPrecompile`](/docs/guides/evm/precompiles/runtime-configuration) | `IRuntimeConfiguration` |
Proposed · address reserved | +| [`PrecompileRegistry`](/docs/guides/evm/precompiles/registry) | `IPrecompileRegistry` |
Proposed · address reserved | + +Projects that need proactive event delivery should use +[project-scoped event relays](/docs/guides/evm/precompile-design#project-scoped-event-relays) +instead of protocol-level event-reporting precompiles. + +Released addresses and selectors remain reserved permanently. The compatibility +and lifecycle rules are documented in +[Precompile design and lifecycle](/docs/guides/evm/precompile-design). diff --git a/docs/guides/evm/precompiles/leasing.mdx b/docs/guides/evm/precompiles/leasing.mdx new file mode 100644 index 0000000000..b211bc2a47 --- /dev/null +++ b/docs/guides/evm/precompiles/leasing.mdx @@ -0,0 +1,40 @@ +--- +title: Leasing +description: Reference for the deployed LeasingPrecompile. +--- + +| Property | Value | +|---|---| +| Rust implementation | `LeasingPrecompile` | +| Solidity interface | `ILeasing` | +| Address | `0x000000000000000000000000000000000000080a` | +| Status | Deployed | + +## Views + +```text +getLease(uint32) +getContributorShare(uint32,bytes32) +getLeaseIdForSubnet(uint16) +``` + +## Operations + +```text +createLeaseCrowdloan(uint64,uint64,uint64,uint32,uint8,bool,uint32) +terminateLease(uint32,bytes32) +``` + +Both operations are `payable`. + +## Proposed additions + +| Proposed function | Source extrinsic | +|---|---| +| `startCall` | `SubtensorModule.start_call` | + +`startCall` accepts a signed subnet owner. The Root-only start-call delay +configuration is not exposed. The proposed name and signature do not reserve a +selector. + +Source: [`leasing.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/leasing.sol) diff --git a/docs/guides/evm/precompiles/meta.json b/docs/guides/evm/precompiles/meta.json new file mode 100644 index 0000000000..423e4fb969 --- /dev/null +++ b/docs/guides/evm/precompiles/meta.json @@ -0,0 +1,28 @@ +{ + "title": "Precompiles", + "pages": [ + "index", + "extrinsic-coverage", + "balance-transfer", + "staking-v1", + "metagraph", + "subnet", + "neuron", + "staking-v2", + "uid-lookup", + "storage-query", + "alpha", + "crowdloan", + "leasing", + "proxy", + "address-mapping", + "voting-power", + "account-balance", + "---Proposed---", + "scheduler", + "drand", + "timestamp", + "runtime-configuration", + "registry" + ] +} diff --git a/docs/guides/evm/precompiles/metagraph.mdx b/docs/guides/evm/precompiles/metagraph.mdx new file mode 100644 index 0000000000..3b40492d35 --- /dev/null +++ b/docs/guides/evm/precompiles/metagraph.mdx @@ -0,0 +1,38 @@ +--- +title: Metagraph +description: Reference for the deployed MetagraphPrecompile. +--- + +| Property | Value | +|---|---| +| Rust implementation | `MetagraphPrecompile` | +| Solidity interface | `IMetagraph` | +| Address | `0x0000000000000000000000000000000000000802` | +| Status | Deployed | + +Provides typed views of per-neuron metagraph values. + +## Functions + +All functions are `view`: + +```text +getUidCount(uint16) +getStake(uint16,uint16) +getRank(uint16,uint16) +getTrust(uint16,uint16) +getConsensus(uint16,uint16) +getIncentive(uint16,uint16) +getDividends(uint16,uint16) +getEmission(uint16,uint16) +getVtrust(uint16,uint16) +getValidatorStatus(uint16,uint16) +getLastUpdate(uint16,uint16) +getIsActive(uint16,uint16) +getAxon(uint16,uint16) +getHotkey(uint16,uint16) +getColdkey(uint16,uint16) +``` + +Source: [`metagraph.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/metagraph.sol) + diff --git a/docs/guides/evm/precompiles/neuron.mdx b/docs/guides/evm/precompiles/neuron.mdx new file mode 100644 index 0000000000..47164e83aa --- /dev/null +++ b/docs/guides/evm/precompiles/neuron.mdx @@ -0,0 +1,69 @@ +--- +title: Neuron +description: Reference for the deployed NeuronPrecompile. +--- + +| Property | Value | +|---|---| +| Rust implementation | `NeuronPrecompile` | +| Solidity interface | `INeuron` | +| Address | `0x0000000000000000000000000000000000000804` | +| Status | Deployed | + +Registers neurons, publishes serving endpoints, and submits weights. Every +function is `payable`. + +## Functions + +```text +burnedRegister(uint16,bytes32) +registerLimit(uint16,bytes32,uint64) +serveAxon(uint16,uint32,uint128,uint16,uint8,uint8,uint8,uint8) +serveAxonTls(uint16,uint32,uint128,uint16,uint8,uint8,uint8,uint8,bytes) +servePrometheus(uint16,uint32,uint128,uint16,uint8) +setWeights(uint16,uint16[],uint16[],uint64) +commitWeights(uint16,bytes32) +revealWeights(uint16,uint16[],uint16[],uint16[],uint64) +``` + +## Proposed weight operations + +| Proposed function | Source extrinsic | +|---|---| +| `setMechanismWeights` | `SubtensorModule.set_mechanism_weights` | +| `batchSetWeights` | `SubtensorModule.batch_set_weights` | +| `commitMechanismWeights` | `SubtensorModule.commit_mechanism_weights` | +| `batchCommitWeights` | `SubtensorModule.batch_commit_weights` | +| `revealMechanismWeights` | `SubtensorModule.reveal_mechanism_weights` | +| `commitCrv3MechanismWeights` | `SubtensorModule.commit_crv3_mechanism_weights` | +| `batchRevealWeights` | `SubtensorModule.batch_reveal_weights` | +| `commitTimelockedWeights` | `SubtensorModule.commit_timelocked_weights` | +| `commitTimelockedMechanismWeights` | `SubtensorModule.commit_timelocked_mechanism_weights` | + +Every batch input must have an explicit fixed bound. Timelocked operations must +use typed Drand data rather than SCALE-encoded payloads. + +## Proposed registration and key operations + +| Proposed function | Source extrinsic | +|---|---| +| `register` | `SubtensorModule.register` | +| `rootRegister` | `SubtensorModule.root_register` | +| `swapHotkey` | `SubtensorModule.swap_hotkey` | +| `swapHotkeyV2` | `SubtensorModule.swap_hotkey_v2` | +| `setChildren` | `SubtensorModule.set_children` | +| `setIdentity` | `SubtensorModule.set_identity` | +| `tryAssociateHotkey` | `SubtensorModule.try_associate_hotkey` | +| `associateEvmKey` | `SubtensorModule.associate_evm_key` | +| `announceColdkeySwap` | `SubtensorModule.announce_coldkey_swap` | +| `executeAnnouncedColdkeySwap` | `SubtensorModule.swap_coldkey_announced` | +| `disputeColdkeySwap` | `SubtensorModule.dispute_coldkey_swap` | +| `clearColdkeySwapAnnouncement` | `SubtensorModule.clear_coldkey_swap_announcement` | + +Every proposed operation accepts a non-Root signed origin. The precompile must +dispatch the highest-level extrinsic as the mapped caller so runtime +authorization remains in force. Root-only and deprecated compatibility calls +are classified in the [coverage audit](./extrinsic-coverage). Proposed names +and signatures do not reserve selectors. + +Source: [`neuron.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/neuron.sol) diff --git a/docs/guides/evm/precompiles/proxy.mdx b/docs/guides/evm/precompiles/proxy.mdx new file mode 100644 index 0000000000..21a87446b2 --- /dev/null +++ b/docs/guides/evm/precompiles/proxy.mdx @@ -0,0 +1,42 @@ +--- +title: Proxy +description: Reference for the deployed ProxyPrecompile. +--- + +| Property | Value | +|---|---| +| Rust implementation | `ProxyPrecompile` | +| Solidity interface | `IProxy` | +| Address | `0x000000000000000000000000000000000000080b` | +| Status | Deployed | + +## Functions + +| Function | Mutability | +|---|---| +| `createPureProxy(uint8,uint32,uint16)` | nonpayable | +| `proxyCall(bytes32,uint8[],uint8[])` | nonpayable | +| `killPureProxy(bytes32,uint8,uint16,uint32,uint32)` | nonpayable | +| `addProxy(bytes32,uint8,uint32)` | nonpayable | +| `removeProxy(bytes32,uint8,uint32)` | nonpayable | +| `removeProxies()` | nonpayable | +| `pokeDeposit()` | nonpayable | +| `getProxies(bytes32)` | `view` | + +## Proposed additions + +| Proposed function | Source extrinsic | +|---|---| +| `announce` | `Proxy.announce` | +| `removeAnnouncement` | `Proxy.remove_announcement` | +| `rejectAnnouncement` | `Proxy.reject_announcement` | +| `proxyAnnounced` | `Proxy.proxy_announced` | +| `setRealPaysFee` | `Proxy.set_real_pays_fee` | + +`proxyAnnounced` must use the same versioned, stable EVM call description as +other typed proxy execution. A new interface must not introduce another +dependency on SCALE-encoded `RuntimeCall`. + +Proposed names and signatures do not reserve selectors. + +Source: [`proxy.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/proxy.sol) diff --git a/docs/guides/evm/precompiles/registry.mdx b/docs/guides/evm/precompiles/registry.mdx new file mode 100644 index 0000000000..df0a4782a2 --- /dev/null +++ b/docs/guides/evm/precompiles/registry.mdx @@ -0,0 +1,44 @@ +--- +title: Precompile registry +description: Proposed registry for precompile lifecycle and availability. +--- + +| Property | Value | +|---|---| +| Proposed implementation | `PrecompileRegistry` | +| Proposed Solidity interface | `IPrecompileRegistry` | +| Reserved address | `0x0000000000000000000000000000000000000813` | +| Status | Proposed; not yet callable | + +The registry provides function-level lifecycle metadata and the current +operational availability of the containing precompile. Contracts, deployment +tools, and frontends can inspect whether a selector is deprecated, has a +replacement, or is currently unavailable without attempting the affected call. + +## Proposed interface + +```solidity +interface IPrecompileRegistry { + struct PrecompileStatus { + bool isDeprecated; + bool isDisabled; + address newPrecompile; + bytes4 newSelector; + string message; + } + + function getPrecompileStatus( + address precompile, + bytes4 selector + ) external view returns (PrecompileStatus memory); +} +``` + +`AdminUtils.sudo_toggle_evm_precompile` is Root-only and is not exposed by this +precompile. The registry reports availability but does not grant callers +permission to change it. + +The lifecycle model is described in +[Precompile design and lifecycle](/docs/guides/evm/precompile-design#discovering-status). +The address is reserved for this domain. The proposed selector remains +provisional until the interface is implemented and released. diff --git a/docs/guides/evm/precompiles/runtime-configuration.mdx b/docs/guides/evm/precompiles/runtime-configuration.mdx new file mode 100644 index 0000000000..1c5f6d688e --- /dev/null +++ b/docs/guides/evm/precompiles/runtime-configuration.mdx @@ -0,0 +1,31 @@ +--- +title: Runtime configuration +description: Proposed typed EVM interface for global runtime configuration operations. +--- + +| Property | Value | +|---|---| +| Proposed implementation | `RuntimeConfigurationPrecompile` | +| Proposed Solidity interface | `IRuntimeConfiguration` | +| Reserved address | `0x0000000000000000000000000000000000000812` | +| Status | Proposed; not yet callable | + +This domain is reserved for bounded typed views of global runtime +configuration that do not belong to subnet, staking, Alpha, account-balance, +or precompile-lifecycle domains. + +## State-changing operations + +The currently identified global configuration extrinsics are Root-only: + +```text +AdminUtils.swap_authorities +AdminUtils.sudo_set_tx_rate_limit +AdminUtils.sudo_set_evm_chain_id +AdminUtils.schedule_grandpa_change +``` + +They are not proposed as typed EVM operations. A future view must return a +typed, bounded representation and must not expose SCALE-encoded runtime values. + +The address is reserved for this domain. No function selector is reserved. diff --git a/docs/guides/evm/precompiles/scheduler.mdx b/docs/guides/evm/precompiles/scheduler.mdx new file mode 100644 index 0000000000..3e61d9646f --- /dev/null +++ b/docs/guides/evm/precompiles/scheduler.mdx @@ -0,0 +1,38 @@ +--- +title: Scheduler +description: Proposed typed EVM interface for the Scheduler pallet. +--- + +| Property | Value | +|---|---| +| Proposed implementation | `SchedulerPrecompile` | +| Proposed Solidity interface | `IScheduler` | +| Reserved address | `0x000000000000000000000000000000000000080f` | +| Status | Proposed; not yet callable | + +This precompile would replace raw reads of Scheduler storage with a stable EVM +interface. It lets contracts inspect whether and when runtime work is +scheduled without decoding Scheduler storage or acquiring permission to modify +the schedule. + +## Planned views + +| Function | Replaces | +|---|---| +| `getIncompleteSince()` | `Scheduler.IncompleteSince` | +| `getScheduledCall(uint64 when,uint32 index)` | One entry of `Scheduler.Agenda` | +| `getScheduledCallCount(uint64 when)` | The bounded agenda length for a block | +| `getRetry(uint64 when,uint32 index)` | `Scheduler.Retries` | +| `getTaskAddress(bytes32 taskId)` | `Scheduler.Lookup` | + +Returning one agenda entry at a time keeps execution bounded and avoids an +unbounded array result. + +## State-changing operations + +The runtime configures `Scheduler.ScheduleOrigin` as Root. Scheduler extrinsics +therefore have no typed EVM operation: a precompile must not manufacture Root +or bypass the top-level Scheduler authorization check. + +The address is reserved for this domain. Names and signatures on this page are +provisional and do not reserve selectors. diff --git a/docs/guides/evm/precompiles/staking-v1.mdx b/docs/guides/evm/precompiles/staking-v1.mdx new file mode 100644 index 0000000000..b29cf3f203 --- /dev/null +++ b/docs/guides/evm/precompiles/staking-v1.mdx @@ -0,0 +1,29 @@ +--- +title: Staking V1 +description: Reference for the deployed legacy StakingPrecompile. +--- + +| Property | Value | +|---|---| +| Rust implementation | `StakingPrecompile` | +| Solidity interface | `IStaking` V1 | +| Address | `0x0000000000000000000000000000000000000801` | +| Status | Deployed | + +This legacy interface remains available for deployed callers. New staking +functionality belongs on [Staking V2](./staking-v2). + +## Functions + +| Function | Mutability | +|---|---| +| `addStake(bytes32,uint256)` | `payable` | +| `removeStake(bytes32,uint256,uint256)` | nonpayable | +| `getTotalColdkeyStake(bytes32)` | `view` | +| `getTotalHotkeyStake(bytes32)` | `view` | +| `addProxy(bytes32)` | nonpayable | +| `removeProxy(bytes32)` | nonpayable | +| `getStake(bytes32,bytes32,uint256)` | `view` | + +Source: [`staking.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/staking.sol) + diff --git a/docs/guides/evm/precompiles/staking-v2.mdx b/docs/guides/evm/precompiles/staking-v2.mdx new file mode 100644 index 0000000000..8db426ac92 --- /dev/null +++ b/docs/guides/evm/precompiles/staking-v2.mdx @@ -0,0 +1,118 @@ +--- +title: Staking V2 +description: Reference for the deployed StakingPrecompileV2. +--- + +| Property | Value | +|---|---| +| Rust implementation | `StakingPrecompileV2` | +| Solidity interface | `IStaking` V2 | +| Address | `0x0000000000000000000000000000000000000805` | +| Status | Deployed | + +This is the current staking interface. The V1 address remains available for +backward compatibility. + +## Stake operations + +```text +addStake(bytes32,uint256,uint256) +addStakeLimit(bytes32,uint256,uint256,bool,uint256) +removeStake(bytes32,uint256,uint256) +removeStakeLimit(bytes32,uint256,uint256,bool,uint256) +removeStakeFull(bytes32,uint256) +removeStakeFullLimit(bytes32,uint256,uint256) +moveStake(bytes32,bytes32,uint256,uint256,uint256) +transferStake(bytes32,bytes32,uint256,uint256,uint256) +burnAlpha(bytes32,uint256,uint256) +``` + +These functions are `payable`. + +## Stake views + +```text +getStake(bytes32,bytes32,uint256) +getStakeInfoForColdkeyAndNetuid(bytes32,uint256,bytes32[]) +getTotalColdkeyStake(bytes32) +getTotalColdkeyStakeOnSubnet(bytes32,uint256) +getTotalHotkeyStake(bytes32) +getAlphaStakedValidators(bytes32,uint256) +getTotalAlphaStaked(bytes32,uint256) +getNominatorMinRequiredStake() +getDefaultMinStake() +``` + +These functions are `view`. + +## Locks and account policy + +```text +lockStake(bytes32,uint256,uint256) +moveLock(bytes32,uint256) +setPerpetualLock(uint256,bool) +setRejectLockedAlpha(bool) +getColdkeyLock(bytes32,uint256) +getHotkeyLock(bytes32,uint256) +getHotkeyConvictions(uint256,bytes32[]) +getLockRates() +getRejectLockedAlpha(bytes32) +``` + +The `get` functions are `view`; the other functions are `payable`. + +## Proxies and stake allowances + +```text +addProxy(bytes32) +removeProxy(bytes32) +approve(address,uint256,uint256) +allowance(address,address,uint256) +increaseAllowance(address,uint256,uint256) +decreaseAllowance(address,uint256,uint256) +transferStakeFrom(address,address,bytes32,uint256,uint256,uint256) +``` + +`allowance` is `view`. Refer to the published ABI for the mutability and return +encoding of the allowance mutations. + +## Proposed Subtensor operations + +| Proposed function | Source extrinsic | +|---|---| +| `decreaseTake` | `SubtensorModule.decrease_take` | +| `increaseTake` | `SubtensorModule.increase_take` | +| `setChildkeyTake` | `SubtensorModule.set_childkey_take` | +| `unstakeAll` | `SubtensorModule.unstake_all` | +| `unstakeAllAlpha` | `SubtensorModule.unstake_all_alpha` | +| `swapStake` | `SubtensorModule.swap_stake` | +| `swapStakeLimit` | `SubtensorModule.swap_stake_limit` | +| `recycleAlpha` | `SubtensorModule.recycle_alpha` | +| `setColdkeyAutoStakeHotkey` | `SubtensorModule.set_coldkey_auto_stake_hotkey` | +| `claimRoot` | `SubtensorModule.claim_root` | +| `setRootClaimType` | `SubtensorModule.set_root_claim_type` | +| `setRootClaimThreshold` | `SubtensorModule.sudo_set_root_claim_threshold` | +| `addStakeBurn` | `SubtensorModule.add_stake_burn` | +| `setAutoParentDelegationEnabled` | `SubtensorModule.set_auto_parent_delegation_enabled` | +| `transferStakeAndHotkey` | `SubtensorModule.transfer_stake_and_hotkey` | +| `addCollateral` | `SubtensorModule.add_collateral` | +| `setMinCollateral` | `SubtensorModule.set_min_collateral` | + +`recycleAlpha` is distinct from deployed `burnAlpha`: recycling reduces +`SubnetAlphaOut` and Alpha issuance, while burning does not reduce +`SubnetAlphaOut`. + +## Proposed AdminUtils operations + +| Proposed function | Source extrinsic | +|---|---| +| `setMinChildkeyTakePerSubnet` | `AdminUtils.sudo_set_min_childkey_take_per_subnet` | +| `setCollateralLockShare` | `AdminUtils.sudo_set_collateral_lock_share` | +| `setCollateralDrainRatio` | `AdminUtils.sudo_set_collateral_drain_ratio` | + +The listed owner-or-Root calls expose only their signed subnet-owner path. +Every operation dispatches the highest-level extrinsic as the mapped caller so +runtime authorization remains in force. Proposed names and signatures do not +reserve selectors. + +Source: [`stakingV2.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/stakingV2.sol) diff --git a/docs/guides/evm/precompiles/storage-query.mdx b/docs/guides/evm/precompiles/storage-query.mdx new file mode 100644 index 0000000000..5dc7300588 --- /dev/null +++ b/docs/guides/evm/precompiles/storage-query.mdx @@ -0,0 +1,65 @@ +--- +title: Storage query +description: Reference for the deployed selectorless StorageQueryPrecompile. +--- + +| Property | Value | +|---|---| +| Rust implementation | `StorageQueryPrecompile` | +| Solidity interface | None | +| Address | `0x0000000000000000000000000000000000000807` | +| Status | Deployed · deprecation planned | + +This precompile has no named Solidity functions or four-byte function selector. +The complete call data is interpreted as a raw Substrate storage key. It returns +the stored SCALE-encoded bytes, or empty bytes when the key does not exist. + +Only keys whose first 16 bytes match an authorized pallet prefix are accepted: +SubtensorModule, Swap, Balances, Proxy, Scheduler, Drand, Crowdloan, Sudo, +Multisig, and Timestamp. + +Raw storage access is brittle because callers depend on runtime storage names, +hashers, key formats, and SCALE encodings. + +## Planned deprecation + + + Storage Query is still deployed and callable. Deprecation is planned, but it + does not begin until suitable typed replacement coverage is available. + + +The planned lifecycle is: + +1. Add typed views for all storage currently authorized through this + precompile. +2. Soft-deprecate Storage Query. Existing calls continue to execute identically + while the registry and documentation direct new callers to typed functions. +3. Allow a documented migration window for existing contracts and tooling. +4. Hard-deprecate Storage Query so calls return a descriptive precompile error. +5. Eventually disable the precompile through the existing Root-controlled + precompile switch. + +No migration-window length or activation block has been assigned. The general +lifecycle rules are described in +[Precompile design and lifecycle](/docs/guides/evm/precompile-design#phasing-out-raw-storage-reads). + +## Replacement destinations + +Typed coverage should be completed at existing domain addresses whenever a +compatible domain already exists. A new address is proposed only when no +existing precompile has a coherent responsibility for that state. + +| Authorized storage prefix | Typed replacement | +|---|---| +| `SubtensorModule` | Extend [Staking V2](./staking-v2), [Metagraph](./metagraph), [Subnet](./subnet), [Neuron](./neuron), [Alpha](./alpha), [Leasing](./leasing), [UID lookup](./uid-lookup), [Address mapping](./address-mapping), and [Voting power](./voting-power), according to the meaning of each value. | +| `Swap` | Extend [Alpha](./alpha) with typed liquidity, fee, balancer, reservoir, initialization, and migration-status views. | +| `Balances` | Extend [Account balance](./account-balance) with typed account, issuance, lock, reserve, hold, and freeze views. | +| `Proxy` | Extend [Proxy](./proxy) with typed announcement, last-call-result, and fee-payer views. | +| `Crowdloan` | Extend [Crowdloan](./crowdloan) with typed ID, contribution-limit, current-operation, and migration-status views. | +| `Scheduler` | Add the proposed [Scheduler](./scheduler) precompile. | +| `Drand` | Add the proposed [Drand](./drand) precompile. | +| `Sudo` | No dedicated EVM precompile is proposed; this access must be addressed explicitly before Storage Query is deprecated. | +| `Multisig` | No dedicated EVM precompile is proposed; this access must be addressed explicitly before Storage Query is deprecated. | +| `Timestamp` | Add the proposed [Timestamp](./timestamp) precompile for complete typed coverage; `getTimestamp()` is equivalent to the existing EVM `block.timestamp` value. | + +Source: [`storage_query.rs`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/storage_query.rs) diff --git a/docs/guides/evm/precompiles/subnet.mdx b/docs/guides/evm/precompiles/subnet.mdx new file mode 100644 index 0000000000..95b3be9881 --- /dev/null +++ b/docs/guides/evm/precompiles/subnet.mdx @@ -0,0 +1,133 @@ +--- +title: Subnet +description: Reference for the deployed SubnetPrecompile. +--- + +| Property | Value | +|---|---| +| Rust implementation | `SubnetPrecompile` | +| Solidity interface | `ISubnet` | +| Address | `0x0000000000000000000000000000000000000803` | +| Status | Deployed | + +Registers subnets and exposes selected subnet configuration. State-changing +functions are `payable`. + +## Registration + +`registerNetwork` has three overloads: + +```text +registerNetwork(bytes32) +registerNetwork(bytes32,string,string,string,string,string,string,string) +registerNetwork(bytes32,string,string,string,string,string,string,string,string) +``` + +## Views + +```text +getActivityCutoff(uint16) +getActivityCutoffFactor(uint16) +getAdjustmentAlpha(uint16) +getAlphaSigmoidSteepness(uint16) +getAlphaValues(uint16) +getBondsMovingAverage(uint16) +getBondsResetEnabled(uint16) +getCommitRevealWeightsEnabled(uint16) +getCommitRevealWeightsInterval(uint16) +getDifficulty(uint16) +getImmunityPeriod(uint16) +getKappa(uint16) +getLiquidAlphaEnabled(uint16) +getMaxBurn(uint16) +getMaxDifficulty(uint16) +getMaxWeightLimit(uint16) +getMinAllowedWeights(uint16) +getMinBurn(uint16) +getMinDifficulty(uint16) +getNetworkPowRegistrationAllowed(uint16) +getNetworkRegistrationAllowed(uint16) +getNetworkRegistrationBlock(uint16) +getOwnerCutAutoLockEnabled(uint16) +getRho(uint16) +getServingRateLimit(uint16) +getWeightsSetRateLimit(uint16) +getWeightsVersionKey(uint16) +getYuma3Enabled(uint16) +isSubnetDissolving(uint16) +``` + +## Configuration + +```text +setActivityCutoff(uint16,uint16) +setActivityCutoffFactor(uint16,uint32) +setAdjustmentAlpha(uint16,uint64) +setAlphaSigmoidSteepness(uint16,uint16) +setAlphaValues(uint16,uint16,uint16) +setBondsMovingAverage(uint16,uint64) +setBondsResetEnabled(uint16,bool) +setCommitRevealWeightsEnabled(uint16,bool) +setCommitRevealWeightsInterval(uint16,uint64) +setDifficulty(uint16,uint64) +setImmunityPeriod(uint16,uint16) +setKappa(uint16,uint16) +setLiquidAlphaEnabled(uint16,bool) +setMaxDifficulty(uint16,uint64) +setMinAllowedWeights(uint16,uint16) +setMinDifficulty(uint16,uint64) +setNetworkPowRegistrationAllowed(uint16,bool) +setNetworkRegistrationAllowed(uint16,bool) +setOwnerCutAutoLockEnabled(uint16,bool) +setRho(uint16,uint16) +setServingRateLimit(uint16,uint64) +setWeightsVersionKey(uint16,uint64) +setYuma3Enabled(uint16,bool) +toggleTransfers(uint16,bool) +``` + +## Legacy no-op functions + +These released selectors remain routed but intentionally do not change state: + +```text +setWeightsSetRateLimit(uint16,uint64) +setMinBurn(uint16,uint64) +setMaxBurn(uint16,uint64) +``` + +Changing their behavior in place would break their released semantics. The +real AdminUtils operations therefore require the proposed V2 selectors below. + +## Proposed subnet operations + +| Proposed function | Source extrinsic | +|---|---| +| `setSubnetIdentity` | `SubtensorModule.set_subnet_identity` | +| `updateSubnetSymbol` | `SubtensorModule.update_symbol` | +| `triggerEpoch` | `SubtensorModule.trigger_epoch` | + +## Proposed AdminUtils operations + +| Proposed function | Source extrinsic | +|---|---| +| `setBondsPenalty` | `AdminUtils.sudo_set_bonds_penalty` | +| `setMaxAllowedUids` | `AdminUtils.sudo_set_max_allowed_uids` | +| `setMaxBurnV2` | `AdminUtils.sudo_set_max_burn` | +| `setMechanismCount` | `AdminUtils.sudo_set_mechanism_count` | +| `setMechanismEmissionSplit` | `AdminUtils.sudo_set_mechanism_emission_split` | +| `setMinBurnV2` | `AdminUtils.sudo_set_min_burn` | +| `setOwnerCutEnabled` | `AdminUtils.sudo_set_owner_cut_enabled` | +| `setOwnerImmuneNeuronLimit` | `AdminUtils.sudo_set_owner_immune_neuron_limit` | +| `setTempo` | `AdminUtils.sudo_set_tempo` | +| `trimToMaxAllowedUids` | `AdminUtils.sudo_trim_to_max_allowed_uids` | + +Each listed AdminUtils call accepts a signed subnet owner as well as Root. The +precompile exposes only the signed path and dispatches the highest-level +extrinsic so owner limits, freeze windows, and other runtime checks remain in +force. Root-only calls are classified as not EVM-callable in the +[coverage audit](./extrinsic-coverage). + +Proposed names and signatures do not reserve selectors. + +Source: [`subnet.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/subnet.sol) diff --git a/docs/guides/evm/precompiles/timestamp.mdx b/docs/guides/evm/precompiles/timestamp.mdx new file mode 100644 index 0000000000..52cc225207 --- /dev/null +++ b/docs/guides/evm/precompiles/timestamp.mdx @@ -0,0 +1,34 @@ +--- +title: Timestamp +description: Proposed typed EVM interface for Timestamp pallet state. +--- + +| Property | Value | +|---|---| +| Proposed implementation | `TimestampPrecompile` | +| Proposed Solidity interface | `ITimestamp` | +| Reserved address | `0x0000000000000000000000000000000000000811` | +| Status | Proposed; not yet callable | + +## Planned views + +| Function | Replaces | +|---|---| +| `getTimestamp()` | `Timestamp.Now` | +| `wasUpdatedThisBlock()` | `Timestamp.DidUpdate` | + +`getTimestamp()` returns the same underlying time as the EVM +`block.timestamp` value. It exists here so every storage item authorized through +`StorageQueryPrecompile` has an explicit typed replacement. +`wasUpdatedThisBlock` provides the Timestamp pallet's update state without +requiring contracts to construct a storage key or decode SCALE. + +`Timestamp.set` is an inherent submitted by block production, not a public +user operation. The proposed precompile therefore exposes no state-changing +timestamp function. + +See the complete classification in +[Extrinsic coverage](/docs/guides/evm/precompiles/extrinsic-coverage). + +The address is reserved for this domain. Names and signatures on this page are +provisional and do not reserve selectors. diff --git a/docs/guides/evm/precompiles/uid-lookup.mdx b/docs/guides/evm/precompiles/uid-lookup.mdx new file mode 100644 index 0000000000..163b6c4e83 --- /dev/null +++ b/docs/guides/evm/precompiles/uid-lookup.mdx @@ -0,0 +1,20 @@ +--- +title: UID lookup +description: Reference for the deployed UidLookupPrecompile. +--- + +| Property | Value | +|---|---| +| Rust implementation | `UidLookupPrecompile` | +| Solidity interface | `IUidLookup` | +| Address | `0x0000000000000000000000000000000000000806` | +| Status | Deployed | + +## Functions + +| Function | Mutability | +|---|---| +| `uidLookup(uint16,address,uint16)` | `view` | + +Source: [`uidLookup.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/uidLookup.sol) + diff --git a/docs/guides/evm/precompiles/voting-power.mdx b/docs/guides/evm/precompiles/voting-power.mdx new file mode 100644 index 0000000000..1b2747f516 --- /dev/null +++ b/docs/guides/evm/precompiles/voting-power.mdx @@ -0,0 +1,37 @@ +--- +title: Voting power +description: Reference for the deployed VotingPowerPrecompile. +--- + +| Property | Value | +|---|---| +| Rust implementation | `VotingPowerPrecompile` | +| Solidity interface | `IVotingPower` | +| Address | `0x000000000000000000000000000000000000080d` | +| Status | Deployed | + +All functions are `view`. + +## Functions + +```text +getVotingPower(uint16,bytes32) +isVotingPowerTrackingEnabled(uint16) +getVotingPowerDisableAtBlock(uint16) +getVotingPowerEmaAlpha(uint16) +getTotalVotingPower(uint16) +``` + +## Proposed operations + +| Proposed function | Source extrinsic | +|---|---| +| `enableVotingPowerTracking` | `SubtensorModule.enable_voting_power_tracking` | +| `disableVotingPowerTracking` | `SubtensorModule.disable_voting_power_tracking` | + +Both calls accept a signed subnet owner as well as Root. The precompile exposes +only the signed path and preserves the pallet's owner checks. The Root-only EMA +configuration call is not exposed. Proposed names and signatures do not +reserve selectors. + +Source: [`votingPower.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/votingPower.sol) diff --git a/website/apps/bittensor-website/src/components/copy.tsx b/website/apps/bittensor-website/src/components/copy.tsx index 1eb4b5b831..d23fce704c 100644 --- a/website/apps/bittensor-website/src/components/copy.tsx +++ b/website/apps/bittensor-website/src/components/copy.tsx @@ -40,6 +40,28 @@ export function CopyCodeButton() { ); } +/** Compact EVM address that copies the complete 20-byte value. */ +export function EvmAddress({ address }: { address: string }) { + const { copied, flash } = useCopied(); + const shortAddress = `${address.slice(0, 3)}...${address.slice(-4)}`; + + return ( + + ); +} + /** "Copy Markdown" — fetches the page's raw markdown and copies it. */ export function CopyMarkdownButton({ markdownUrl, diff --git a/website/apps/bittensor-website/src/components/mdx.tsx b/website/apps/bittensor-website/src/components/mdx.tsx index f7fcf9b4dd..754c125924 100644 --- a/website/apps/bittensor-website/src/components/mdx.tsx +++ b/website/apps/bittensor-website/src/components/mdx.tsx @@ -1,7 +1,7 @@ import Link from 'next/link'; import type { MDXComponents } from 'mdx/types'; import type { ComponentProps, ReactNode } from 'react'; -import { CopyCodeButton } from './copy'; +import { CopyCodeButton, EvmAddress } from './copy'; import { EvmAddressDomains } from './docs/evm-address-domains'; import { EvmMoneyFlows } from './docs/evm-money-flows'; import { ConvictionLockChart } from './docs/conviction-lock-chart'; @@ -130,6 +130,7 @@ export function getMDXComponents(components?: MDXComponents) { Cards, Card, Callout, + EvmAddress, TaoHalvingChart, SubnetEmissionShareChart, YumaConsensusDemo,