diff --git a/docs/evm-tutorials/alpha-precompile.md b/docs/evm-tutorials/alpha-precompile.md new file mode 100644 index 00000000..31ad9957 --- /dev/null +++ b/docs/evm-tutorials/alpha-precompile.md @@ -0,0 +1,112 @@ +--- +title: "Alpha Precompile" +--- + +# Alpha Precompile + +The Alpha precompile exposes the state of every subnet's AMM pool to EVM smart contracts. It is a read-only interface for token prices, pool reserves, swap simulation, and emission rates. Any contract that needs to react to subnet token economics uses this precompile as its data source. + +- **Address**: `0x0000000000000000000000000000000000000808` +- **Source code**: [AlphaPrecompile reference](https://github.com/opentensor/subtensor/blob/main/precompiles/src/alpha.rs) + +## Concepts + +### Spot price vs. moving price + +`getAlphaPrice` returns the current instantaneous price derived from the pool reserves (`TaoIn / AlphaIn`). This is the price you would get at this exact block. + +`getMovingAlphaPrice` returns an exponential moving average of the spot price, smoothed over `getEMAPriceHalvingBlocks` blocks. Use the moving price when you need a tamper-resistant value—it is significantly harder to manipulate through flash transactions than the spot price. + +### Pool reserves vs. outstanding supply + +The pool holds two reserves: + +- `TaoInPool` — TAO locked in the subnet's AMM pool. +- `AlphaInPool` — alpha tokens locked in the AMM pool. + +`AlphaOutPool` (also called `AlphaOut`) tracks the total alpha outstanding outside the pool — staked by validators, miners, and delegators. `AlphaIssuance` is the total ever minted. + +The ratio `AlphaInPool / (AlphaInPool + AlphaOutPool)` tells you what fraction of alpha supply is available for immediate trading. + +### Swap simulation + +`simSwapTaoForAlpha` and `simSwapAlphaForTao` compute expected output for a given input using the constant-product formula, including fees. Use these before a stake or unstake operation to estimate slippage and decide whether to use a limit-price variant. + +## Function reference + +### Prices and pool state + +| Function | Parameters | Returns | Description | +| ------------------------------------ | ---------- | ------------------------- | -------------------------------------- | +| `getAlphaPrice(uint16 netuid)` | netuid | `uint256` (RAO per alpha) | Spot price | +| `getMovingAlphaPrice(uint16 netuid)` | netuid | `uint256` (RAO per alpha) | EMA price | +| `getTaoInPool(uint16 netuid)` | netuid | `uint64` | TAO reserves in pool | +| `getAlphaInPool(uint16 netuid)` | netuid | `uint64` | Alpha reserves in pool | +| `getAlphaOutPool(uint16 netuid)` | netuid | `uint64` | Alpha outstanding outside pool | +| `getAlphaIssuance(uint16 netuid)` | netuid | `uint64` | Total alpha ever minted | +| `getTaoWeight()` | — | `uint256` | Global TAO weight scalar | +| `getSumAlphaPrice()` | — | `uint256` | Sum of alpha prices across all subnets | +| `getCKBurn()` | — | `uint256` | CK burn rate | + +### Swap simulation + +| Function | Parameters | Returns | +| ------------------------------------------------- | ------------------------- | ---------------------------------------- | +| `simSwapTaoForAlpha(uint16 netuid, uint64 tao)` | netuid, tao amount in RAO | Expected alpha received (`uint256`) | +| `simSwapAlphaForTao(uint16 netuid, uint64 alpha)` | netuid, alpha amount | Expected TAO received in RAO (`uint256`) | + +### Emission rates (per block) + +| Function | Parameters | Returns | Description | +| ------------------------------------ | ---------- | --------- | ----------------------------------------------------- | +| `getTaoInEmission(uint16 netuid)` | netuid | `uint256` | TAO flowing into the pool per block | +| `getAlphaInEmission(uint16 netuid)` | netuid | `uint256` | Alpha minted into the pool per block | +| `getAlphaOutEmission(uint16 netuid)` | netuid | `uint256` | Alpha emitted outside the pool (to stakers) per block | + +### Subnet metadata + +| Function | Parameters | Returns | Description | +| ----------------------------------------- | ---------- | ------------------------------ | ------------------------- | +| `getSubnetMechanism(uint16 netuid)` | netuid | `uint16` (0=Stable, 1=Dynamic) | AMM mechanism type | +| `getRootNetuid()` | — | `uint16` | Root subnet ID (always 0) | +| `getEMAPriceHalvingBlocks(uint16 netuid)` | netuid | `uint64` | Blocks for EMA half-life | +| `getSubnetVolume(uint16 netuid)` | netuid | `uint256` | Recent trading volume | + +## Usage examples + +### ABI + +The canonical ABI is exported from [`contract-tests/src/contracts/alpha.ts`](https://github.com/opentensor/subtensor/blob/main/contract-tests/src/contracts/alpha.ts). You can import the ABI and contract address from a local copy of the source file as shown: + +```javascript +import { IAlphaABI, IALPHA_ADDRESS } from "./contracts/alpha"; +``` + +### Reading pool state + +```javascript +import { ethers } from "ethers"; +import { IAlphaABI, IALPHA_ADDRESS } from "./contracts/alpha"; + +const { rpcUrl } = require("./config.js"); + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const alphaContract = new ethers.Contract(IALPHA_ADDRESS, IAlphaABI, provider); + +const netuid = 14; + +const spotPrice = await alphaContract.getAlphaPrice(netuid); +const movingPrice = await alphaContract.getMovingAlphaPrice(netuid); +const taoReserve = await alphaContract.getTaoInPool(netuid); +const alphaReserve = await alphaContract.getAlphaInPool(netuid); + +// Simulate a 1 TAO (1e9 RAO) stake +const ONE_TAO_RAO = 1_000_000_000n; +const expectedAlpha = await alphaContract.simSwapTaoForAlpha( + netuid, + ONE_TAO_RAO, +); + +console.log(`Spot price: ${spotPrice}`); +console.log(`Expected alpha for 1 TAO stake: ${expectedAlpha}`); +``` diff --git a/docs/evm-tutorials/crowdloan-precompile.md b/docs/evm-tutorials/crowdloan-precompile.md new file mode 100644 index 00000000..8359602f --- /dev/null +++ b/docs/evm-tutorials/crowdloan-precompile.md @@ -0,0 +1,167 @@ +--- +title: "Crowdloan Precompile" +--- + +# Crowdloan Precompile + +The Crowdloan precompile lets EVM contracts create and manage crowdloans entirely on-chain. A campaign creator sets a funding cap and deadline, contributors deposit TAO, and when the cap is reached, the crowdloan can be finalized to execute either the stored call or a transfer to a target address. + +- **Address**: `0x0000000000000000000000000000000000000809` +- **Source code**: [CrowdloanPrecompile reference](https://github.com/opentensor/subtensor/blob/main/precompiles/src/crowdloan.rs) + +See [Crowdloans](../subnets/crowdloans/index.md) for the full concept and Substrate-side workflow. + +## Functions + +| Function | Mutability | Description | +| ----------------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------- | +| `create(uint64 deposit, uint64 minContribution, uint64 cap, uint32 end, address targetAddress)` | payable | Create a new campaign. `end` is the block number at which contributions close. | +| `contribute(uint32 crowdloanId, uint64 amount)` | payable | Contribute to an active campaign. | +| `withdraw(uint32 crowdloanId)` | payable | Withdraw your contribution before finalization. | +| `finalize(uint32 crowdloanId)` | payable | Finalize a successful campaign and transfer funds to `targetAddress`. | +| `refund(uint32 crowdloanId)` | payable | Refund contributors of a failed campaign. Call repeatedly for large contributor sets. | +| `dissolve(uint32 crowdloanId)` | payable | Clean up storage after full refund. | +| `updateMinContribution(uint32 crowdloanId, uint64 newMinContribution)` | payable | Update minimum contribution (creator only). | +| `updateEnd(uint32 crowdloanId, uint32 newEnd)` | payable | Update end block (creator only). | +| `updateCap(uint32 crowdloanId, uint64 newCap)` | payable | Update funding cap (creator only). | +| `getCrowdloan(uint32 crowdloanId)` | view | Returns `CrowdloanInfo` struct. | +| `getContribution(uint32 crowdloanId, bytes32 coldkey)` | view | Returns contributor's total deposit in RAO. | + +## Usage examples + +### ABI + +The canonical ABI is exported from [`contract-tests/src/contracts/crowdloan.ts`](https://github.com/opentensor/subtensor/blob/main/contract-tests/src/contracts/crowdloan.ts). You can import the ABI and contract address if you have a local copy of the source files as shown: + +```javascript +import { ICrowdloanABI, ICROWDLOAN_ADDRESS } from "./contracts/crowdloan"; +``` + +### Creating a campaign + +Use `create` to launch a crowdloan campaign on-chain. The function takes the initial deposit amount, minimum contribution per contributor, funding cap, end block, and a target EVM address to receive the raised funds upon finalization. + +The target address is a H160 address type, not `bytes32`. You must pass it as a plain EVM address: + +```javascript +import { ethers } from "ethers"; +import { ICrowdloanABI, ICROWDLOAN_ADDRESS } from "./contracts/crowdloan"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +const { ethPrivateKey, rpcUrl } = require("./config.js"); + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(ethPrivateKey, provider); +const contract = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, signer); + +const DEPOSIT = 10_000_000_000n; // 10 TAO in RAO +const MIN_CONTRIBUTION = 1_000_000_000n; // 1 TAO in RAO +const CAP = 50_000_000_000n; // 50 TAO in RAO +const END_BLOCK = 8540500; + +const tx = await contract.create( + DEPOSIT, + MIN_CONTRIBUTION, + CAP, + END_BLOCK, + "0xYourTargetAddress", + { gasLimit: 100_000n }, +); +await tx.wait(); + +console.log(`Crowdloan created`); +``` + +### Checking campaign progress + +Use `getCrowdloan` to crowdloan by ID. + +```javascript +import { ethers } from "ethers"; +import { ICrowdloanABI, ICROWDLOAN_ADDRESS } from "./contracts/crowdloan"; +const { rpcUrl } = require("./config.js"); + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const contract = new ethers.Contract( + ICROWDLOAN_ADDRESS, + ICrowdloanABI, + provider, +); + +const info = await contract.getCrowdloan(0); +console.log(`Raised: ${info.raised} RAO of ${info.cap} RAO cap`); +console.log(`Finalized: ${info.finalized}`); +console.log(`Cap: ${info.cap}`); +console.log(`End: ${info.end}`); +``` + +### Contribute to a crowdloan campaign + +Use `contribute` to add TAO to an active crowdloan campaign. The function takes the `crowdloanId` and the `amount` in RAO. The `amount` must be at least the campaign's `min_contribution`. + +```javascript +import { ethers } from "ethers"; +import { ICrowdloanABI, ICROWDLOAN_ADDRESS } from "./contracts/crowdloan"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +const { ethPrivateKey, rpcUrl } = require("./config.js"); + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(ethPrivateKey, provider); +const contract = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, signer); + +const CROWDLOAN_ID = 0; +const AMOUNT = 1_000_000_000n; // 1 TAO in RAO + +const tx = await contract.contribute(CROWDLOAN_ID, AMOUNT, { + gasLimit: 2_000_000n, +}); + +console.log(`Contributed ${AMOUNT} RAO to crowdloan ${CROWDLOAN_ID}`); +``` + +### Finalize a crowdloan campaign + +Use `finalize` to close a successfully funded campaign and transfer the raised TAO to the target address set at creation. The campaign must have reached its cap before the `finalize` function can be called. + +```javascript +import { ethers } from "ethers"; +import { ICrowdloanABI, ICROWDLOAN_ADDRESS } from "./contracts/crowdloan"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +const { ethPrivateKey, rpcUrl } = require("./config.js"); + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(ethPrivateKey, provider); +const contract = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, signer); + +const CROWDLOAN_ID = 0; + +const tx = await contract.finalize(CROWDLOAN_ID, { gasLimit: 2_000_000n }); +const receipt = await tx.wait(); +console.log(`Crowdloan ${CROWDLOAN_ID} finalized`); +``` + +### Dissolve a crowdloan campaign + +Use `dissolve` to permanently remove a crowdloan from storage. + +```javascript +import { ethers } from "ethers"; +import { ICrowdloanABI, ICROWDLOAN_ADDRESS } from "./contracts/crowdloan"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +const { ethPrivateKey, rpcUrl } = require("./config.js"); + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(ethPrivateKey, provider); +const contract = new ethers.Contract(ICROWDLOAN_ADDRESS, ICrowdloanABI, signer); + +const CROWDLOAN_ID = 0; + +const tx = await contract.dissolve(CROWDLOAN_ID, { gasLimit: 2_000_000n }); +const receipt = await tx.wait(); +console.log(`Crowdloan ${CROWDLOAN_ID} dissolved`); +console.log(` Block: ${receipt.blockNumber}`); +console.log(` Tx hash: ${receipt.hash}`); +``` diff --git a/docs/evm-tutorials/ed25519-verify-precompile.md b/docs/evm-tutorials/ed25519-verify-precompile.md index b7799f80..782c92ab 100644 --- a/docs/evm-tutorials/ed25519-verify-precompile.md +++ b/docs/evm-tutorials/ed25519-verify-precompile.md @@ -9,6 +9,9 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; The Ed25519 Verify Precompile allows EVM smart contracts to verify Ed25519 signatures, which are commonly used in Substrate-based chains like Bittensor. This is essential for bridging identity and ownership between Substrate and EVM ecosystems. For example, you may want to verify coldkey ownership before transferring to someone. EVM functionality doesn't allow transferring directly to a `ss58` address—like the public key of a Bittensor coldkey—because EVM uses the H160 address schema. To bridge the gap, you can use this precompile to prove a claim of ownership. The owner of a coldkey can send an EVM transaction with a signed message, serving as proof of ownership of the coldkey's `ss58` address. +- **Address**: `0x0000000000000000000000000000000000000402` +- **Source code**: [Ed25519Verify reference](https://github.com/opentensor/subtensor/blob/main/precompiles/src/ed25519.rs) + ## Prerequisites - **Node.js** (v16 or later recommended) diff --git a/docs/evm-tutorials/evm-localnet-with-metamask-wallet.md b/docs/evm-tutorials/evm-localnet-with-metamask-wallet.md index 60f4d959..fce8f56b 100644 --- a/docs/evm-tutorials/evm-localnet-with-metamask-wallet.md +++ b/docs/evm-tutorials/evm-localnet-with-metamask-wallet.md @@ -13,24 +13,25 @@ Consider first trying [EVM with Bittensor testnet](./evm-testnet-with-metamask-w Key values: -- **EVM Subtensor Mainnet Chain ID:**: `964` (UTF-8 encoded TAO symbol) -- **EVM Subtensor Testnet Chain ID:**: `945` (UTF-8 encoded alpha character) +- **EVM Subtensor Mainnet Chain ID:** `964` (UTF-8 encoded TAO symbol) +- **EVM Subtensor Testnet Chain ID:** `945` (UTF-8 encoded alpha character) - **Opentensor EVM-Bittensor GitHub repo with code examples:** https://github.com/opentensor/evm-bittensor/tree/main ## Step 1. Run EVM-enabled localnet -```bash -git clone https://github.com/opentensor/subtensor -./scripts/localnet.sh -``` +Before setting up EVM on a local chain, ensure that you have a local Subtensor instance running. + +To do this, follow the steps to start a local Subtensor instance. You can run it [using Docker](../local-build/deploy?local-chain=docker) or a [local source build](../local-build/deploy?local-chain=local). ## Step 2. Set Chain ID -The bare local network doesn't have the Chain ID setup and it needs to be configured with an admin extrinsic. Use [sudo section of Polkadot AppsUI](https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/sudo) to call this extrinsic to set the ChainID to 945 (to simulate Testnet) or 964 (to simulate Mainnet): +A local Subtensor network does not have a Chain ID configured by default. You must set it using an admin extrinsic. Use the [sudo section of Polkadot Apps UI](https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944&utm_source=chatgpt.com#/sudo) to call the appropriate extrinsic and set the Chain ID: -``` -adminUtils >> sudoSetEvmChainId -``` +On the **call: Call(RuntimeCall)** input: + +- Select the `adminUtils` pallet +- Then select the `sudoSetEvmChainId` extrinsic from list of pallet extrinsics +- Finally, set the chain ID to either `964` for mainnet or `945` for testnet ## Step 3. Create a Metamask wallet diff --git a/docs/evm-tutorials/evm-testnet-with-metamask-wallet.md b/docs/evm-tutorials/evm-testnet-with-metamask-wallet.md index 5728d20e..54bff4c2 100644 --- a/docs/evm-tutorials/evm-testnet-with-metamask-wallet.md +++ b/docs/evm-tutorials/evm-testnet-with-metamask-wallet.md @@ -61,7 +61,7 @@ Next, request testnet TAO in the Bittensor community [Discord](https://discord.c In this step you will copy the private key from your Metamask wallet account and paste it into the configuration file in the repo. This step will ensure that you are not prompted with password each and every step as you run these tutorials. -1. Navigate to the `examples` directory of the EVM-Bittensor repo: +1. Navigate to the `examples` directory of the [EVM-Bittensor repo](https://github.com/opentensor/evm-bittensor): ```bash cd examples diff --git a/docs/evm-tutorials/examples.md b/docs/evm-tutorials/examples.md index 1a1eaa70..afff43d7 100644 --- a/docs/evm-tutorials/examples.md +++ b/docs/evm-tutorials/examples.md @@ -9,41 +9,56 @@ import { InstallPartial } from "./\_install.mdx"; ## Available Precompiles The following precompiled smart contracts are available on the Bittensor EVM. -The source code can be found [on GitHub](https://github.com/opentensor/subtensor/blob/main/precompiles). +The source code can be found [on GitHub](https://github.com/opentensor/subtensor/blob/main/precompiles/src). Code examples used throughout this section are provided by the _Opentensor Foundation_ (_OTF_), and come from [this repository](https://github.com/opentensor/evm-bittensor/tree/main/examples). ## Examples -- [Convert Ethereum (H160) Address to Substrate (SS58)](./convert-h160-to-ss58): Learn how to convert between H160 and SS58 address formats +The following tutorials cover common EVM workflows on Bittensor, including account management, token bridging, and interaction with precompiles: + +- [Convert Ethereum (H160) Address to Substrate (SS58)](./convert-h160-to-ss58): Learn how to convert between H160 and SS58 address formats. +- [Bridging and wrapping vTAO](./vtao-bridge-tutorial.md): Move native TAO from the Bittensor Substrate layer to the Bittensor EVM and wrap it into vTAO. +- [Transfer TAO from Metamask to SS58 Address](./transfer-from-metamask-to-ss58.md): Learn how to transfer TAO from your Metamask wallet to your Bittensor SS58 address for a coldkey (wallet) or a hotkey. +- [Transfer Between Two H160 Accounts](./transfer-between-two-h160-accounts.md): Learn how to transfer TAO between two H160 accounts. ## Standard Ethereum Precompiles -- `ECRecover` (0x1): Recover the address associated with the public key from elliptic curve signature -- `Sha256` (0x2): SHA-256 hash function -- `Ripemd160` (0x3): RIPEMD-160 hash function -- `Identity` (0x4): Identity function (returns input data) -- `Modexp` (0x5): Modular exponentiation -- `Sha3FIPS256` (0x400): SHA3-256 hash function (FIPS variant) -- `ECRecoverPublicKey` (0x401): Recover the public key from an elliptic curve signature +The following table consists of standard Ethereum precompiles with their addresses and description: + +| Precompile | Address | Description | +| -------------------- | ------- | ---------------------------------------------------------------------------------------- | +| `ECRecover` | `0x1` | Recover the address associated with the public key from elliptic curve signature (ECDSA) | +| `Sha256` | `0x2` | SHA-256 hash function | +| `Ripemd160` | `0x3` | RIPEMD-160 hash function | +| `Identity` | `0x4` | Identity function (returns input data) | +| `Modexp` | `0x5` | Modular exponentiation | +| `Dispatch` | `0x6` | Dispatch a runtime call from EVM | +| `Bn128Mul` | `0x7` | BN128 scalar multiplication | +| `Bn128Pairing` | `0x8` | BN128 pairing check | +| `Bn128Add` | `0x9` | BN128 point addition | +| `Sha3FIPS256` | `0x400` | SHA3-256 hash function (FIPS variant) | +| `ECRecoverPublicKey` | `0x401` | Recover public key from an elliptic curve signature (ECDSA) | ## Bittensor-Specific Precompiles -The following list consists of Bittensor-specific precompiles with links to their respective documentation: - -- [`Ed25519Verify`](./ed25519-verify-precompile.md): Verify Ed25519 signatures -- [`BalanceTransfer`](./transfer-between-two-h160-accounts.md): Transfer TAO between accounts -- [`StakingPrecompile`](./staking-precompile.md): Manage staking operations -- [`StakingPrecompileV2`](./staking-precompile.md) (0x805): Main staking operations including: - - `addStake`: Add stake to a hotkey - - `removeStake`: Remove stake from a hotkey - - `moveStake`: Move stake between hotkeys - - `transferStake`: Transfer stake between coldkeys - - `getTotalColdkeyStake`: Get total stake for a coldkey - - `getTotalHotkeyStake`: Get total stake for a hotkey - - `getStake`: Get stake between specific hotkey and coldkey - - `addProxy`: Add a proxy delegate - - `removeProxy`: Remove a proxy delegate -- [`SubnetPrecompile`](./subnet-precompile.md): Manage subnet operations -- [`MetagraphPrecompile`](./metagraph-precompile.md): Interact with the metagraph -- [`NeuronPrecompile`](./neuron-precompile.md): Manage neuron operations +The following table lists of Bittensor-specific precompiles with their addresses, descriptions and links to their respective documentation: + +| Precompile | Address | Description | +| --------------------------- | ------- | ------------------------------------------------------------------------------------------------------------- | +| `Ed25519Verify` | `0x402` | Verify Ed25519 signatures. See [Ed25519 Verify](./ed25519-verify-precompile.md) | +| `SR25519Verify` | `0x403` | Verify Substrate SR25519 signatures. See SR25519 Verify ./sr25519-verify-precompile.md | +| `BalanceTransferPrecompile` | `0x800` | Transfer TAO between H160 addresses. see [Transfer TAO](./transfer-between-two-h160-accounts.md) | +| `StakingPrecompile` | `0x801` | Staking via `msg.value` — **deprecated**, use V2. See [Staking Precompile](./staking-precompile.md) | +| `MetagraphPrecompile` | `0x802` | Interact with the metagraph (rank, trust, stake, axon). See [Metagraph Precompile](./metagraph-precompile.md) | +| `SubnetPrecompile` | `0x803` | Register subnets, get/set hyperparameters . See [Subnet Precompile](./subnet-precompile.md) | +| `NeuronPrecompile` | `0x804` | Register neurons, set weights, serve axon. See [Neuron Precompile](./neuron-precompile.md) | +| `StakingPrecompileV2` | `0x805` | Full staking with limits, allowances, moves . See | +| `UidLookupPrecompile` | `0x806` | Map EVM address → registered neuron UID. See | +| `StorageQueryPrecompile` | `0x807` | Manages EVM contracts read access to Substrate chain storage. | +| `AlphaPrecompile` | `0x808` | AMM pool state, prices, swap simulation. See | +| `CrowdloanPrecompile` | `0x809` | Create and manage crowdloan campaigns. See | +| `LeasingPrecompile` | `0x80A` | Create subnet lease crowdloans, terminate leases. See | +| `ProxyPrecompile` | `0x80B` | Add/remove/execute Substrate proxy relationships. See | +| `AddressMappingPrecompile` | `0x80C` | Convert H160 → Substrate AccountId32. See | +| `VotingPowerPrecompile` | `0x80D` | Query per-validator EMA voting power. See | diff --git a/docs/evm-tutorials/index.md b/docs/evm-tutorials/index.md index e1f882a2..0a064f72 100644 --- a/docs/evm-tutorials/index.md +++ b/docs/evm-tutorials/index.md @@ -55,10 +55,34 @@ Similarly, creating an Ethereum wallet gives you control of the h160 private key You can easily [convert an h160 address to an ss58 address](./convert-h160-to-ss58.md), or vice versa, but this does _not_ yield the corresponding private key. This means that if you create a wallet in Bittensor, you will not be able to sign Ethereum contracts with it, nor versa. ::: -Hence, in the context of Bittensor EVM we can distinguish between: +### The HashedAddressMapping -- 'Bittensor wallets': created using the Bittensor tool chain and therefore able to sign transactions using Bittensor transaction clients (BTCLI and the Bittensor SDK), but not EVM smart contracts, on the Bittensor blockchain. -- 'EVM wallets': created using an EVM client such as MetaMask and therefore able to sign EVM smart contracts, but not Subtensor extrinsics, on the Bittensor blockchain. +Every EVM call that touches Substrate state (staking, registering, setting weights) requires a Substrate Account—`AccountId32`. The runtime derives this from the H160 address using a one-way hash: + +``` +AccountId32 = Blake2b_256("evm:" ++ h160_bytes) +``` + +This is called **HashedAddressMapping**. It is deterministic — the same H160 always produces the same AccountId32 — but irreversible. You cannot reconstruct an H160 private key from the AccountId32. + +#### How it works + +When a smart contract calls a precompile function—for example, `addStake` on StakingV2, the contract's own H160 address is hashed to produce the coldkey for the stake position. The stake is held by the **contract** on-chain, not by the user who called the contract. + +Use the AddressMapping precompile (`0x80C`) to compute the Substrate coldkey for any EVM address. + +### Types of EVM wallets + +In the context of Bittensor EVM we can distinguish between the types of wallets available: + +| Type | Created with | Can sign | Used for | +| ---------------- | -------------------------------------- | ---------------------------------- | ----------------------------------------------------- | +| EVM wallet | MetaMask or any Ethereum key generator | EVM transactions, precompile calls | Interacting with smart contracts, calling precompiles | +| Bittensor wallet | `btcli wallet`, Bittensor SDK | Substrate extrinsics | Staking via btcli/SDK, key operations, governance | + +:::info Gas fees on Bittensor EVM +The Bittensor EVM uses the standard Ethereum gas model. Gas fees are paid in TAO (not a separate fee token). Always ensure that you have enough TAO in required the EVM wallet to cover the gas fees for each transaction. +::: ## Ethereum vs Bittensor EVM smart contract runtime @@ -68,26 +92,22 @@ On the Ethereum network, nodes such as full nodes, validator nodes and archive n Note that all operations performed by Bittensor EVM are executed solely on the Bittensor blockchain, not on the Ethereum blockchain. ::: - - - - for ProxyType`. + +| Value | Type | Notes | +| ----- | ------------------------ | ---------------------------- | +| 0 | `Any` | All operations | +| 1 | `Owner` | Subnet owner calls | +| 2 | `NonCritical` | | +| 3 | `NonTransfer` | All except balance transfers | +| 4 | `Senate` | Deprecated | +| 5 | `NonFungible` | Nothing involving moving TAO | +| 6 | `Triumvirate` | Deprecated | +| 7 | `Governance` | Deprecated | +| 8 | `Staking` | Staking operations | +| 9 | `Registration` | | +| 10 | `Transfer` | | +| 11 | `SmallTransfer` | Transfers up to limit | +| 12 | `RootWeights` | Deprecated | +| 13 | `ChildKeys` | | +| 14 | `SudoUncheckedSetCode` | | +| 15 | `SwapHotkey` | | +| 16 | `SubnetLeaseBeneficiary` | Operate a leased subnet | +| 17 | `RootClaim` | | + +Check [Proxy Types](../keys/proxies/index.md#proxytype) for the authoritative up-to-date list. + +## Usage examples + +### ABI + +The canonical ABI is exported from [`contract-tests/src/contracts/proxy.ts`](https://github.com/opentensor/subtensor/blob/main/contract-tests/src/contracts/proxy.ts). You can import the ABI and contract address from a local copy of the source file as shown: + +```javascript +import { IProxyABI, IPROXY_ADDRESS } from "./contracts/proxy"; +``` + +### Add a proxy relationship on a Bittensor EVM address + +Use `addProxy` to authorize an account to act on behalf of the caller on-chain. The function takes three arguments: `delegate` (the account being authorized), `proxy_type` (the scope of permitted operations), and `delay` (the number of blocks the proxy must wait before executing a call after announcing). + +```js +import { ethers } from "ethers"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +const { ethPrivateKey, rpcUrl } = require("./config.js"); +import { IProxyABI, IPROXY_ADDRESS } from "./contracts/proxy"; + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(ethPrivateKey, provider); +const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, signer); + +const DELEGATE_PUBLIC_KEY = "DELEGATE_COLDKEY_PUBLIC_KEY"; // raw 32-byte public key +const PROXY_TYPE = 0; // Any — see proxy type index table +const DELAY_BLOCKS = 0; + +const tx = await contract.addProxy( + DELEGATE_PUBLIC_KEY, + PROXY_TYPE, + DELAY_BLOCKS, + { gasLimit: 300_000n }, +); +const receipt = await tx.wait(); + +console.log(`Proxy added successfully`); +``` + +:::info + +The `delegate` parameter expects a 32-byte public key. For Substrate wallets use `decodeAddress(ss58)` imported from `@polkadot/util-crypto`; for EVM wallets you must derive the corresponding public key for the H160 account before passing it. +::: + +### Get all proxies for an EVM address + +Use `getProxies` to retrieve all active proxy relationships for a given account. It takes a single argument: the `bytes32` public key of the account whose proxies you want to look up. The function returns an array of `ProxyInfo` structs, each containing the delegate public key, proxy type, and delay. + +```javascript +import { ethers } from "ethers"; +import { blake2AsU8a } from "@polkadot/util-crypto"; +import { hexToU8a } from "@polkadot/util"; +import { IProxyABI, IPROXY_ADDRESS } from "./contracts/proxy"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +const { ethPrivateKey, rpcUrl } = require("./config.js"); + +function h160ToPublicKey(evmAddress) { + const combined = new Uint8Array(24); + new TextEncoder().encodeInto("evm:", combined); + combined.set(hexToU8a(evmAddress), 4); + return blake2AsU8a(combined); +} + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(ethPrivateKey, provider); +const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, signer); + +const proxies = await contract.getProxies(h160ToPublicKey(signer.address)); +for (const p of proxies) { + console.log( + `Delegate: ${p.delegate}, type: ${p.proxy_type}, delay: ${p.delay}`, + ); +} +``` + +:::info +Passing `signer.address` to the `h160ToPublicKey` function applies HashedAddressMapping on the EVM's H160 account to derive the corresponding Substrate public key. +::: + +### Execute a proxy with an EVM account + +Use the `proxyCall` function to execute a call on behalf of another account through an existing proxy relationship. + +The `force_proxy_type` optionally restricts which proxy type is used for the call — pass `[0]` for `Any` or the relevant proxy type index to match the relationship. The `call` parameter is the SCALE-encoded runtime call to execute. Both of these parameters accept `uint8[]` arrays — convert the encoded call hex before passing it as shown: + +```js +import { createRequire } from "module"; +const require = createRequire(import.meta.url); +const { rpcUrl, delegateEthPrivateKey } = require("./config.cjs"); +import { ethers } from "ethers"; +import { blake2AsU8a } from "@polkadot/util-crypto"; +import { hexToU8a } from "@polkadot/util"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +import { IProxyABI, IPROXY_ADDRESS } from "./contracts/proxy"; + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(delegateEthPrivateKey, provider); +const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, signer); + +const ENCODED_CALL = "0x050300fe65717dad0447d71...8a554d5860e0284d717"; + +const tx = await contract.proxyCall( + REAL_ADDRESS_PUBLIC_KEY, + [0], // force_proxy_type: Any + Array.from(ethers.getBytes(ENCODED_CALL)), + { gasLimit: 500_000n }, +); +const receipt = await tx.wait(); + +console.log(`Proxy call executed`); +``` + +:::info + +The `real` parameter expects a 32-byte public key. For Substrate wallets use `decodeAddress(ss58)` imported from `@polkadot/util-crypto`, for EVM wallets use `h160ToPublicKey(evmAddress)` to derive the corresponding public key before passing it. +::: + +### Create a pure proxy with an EVM account + +Use `createPureProxy` to create a keyless proxy account controlled entirely by the caller. + +```js +import { ethers } from "ethers"; +import { IProxyABI, IPROXY_ADDRESS } from "./contracts/proxy"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +const { ethPrivateKey, rpcUrl } = require("./config.js"); + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(ethPrivateKey, provider); +const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, signer); + +const PROXY_TYPE = 0; +const DELAY_BLOCKS = 0; +const DISAMBIGUATION_INDEX = 0; + +// Create pure proxy +const pureTx = await contract.createPureProxy( + PROXY_TYPE, + DELAY_BLOCKS, + DISAMBIGUATION_INDEX, + { + gasLimit: 300_000n, + }, +); +const pureReceipt = await pureTx.wait(); +console.log(`Pure proxy created in block ${pureReceipt.blockNumber}`); +``` + +### Remove a proxy relationship on an EVM account + +Use `removeProxy` to revoke a specific proxy relationship. The arguments must exactly match those used when the proxy was created — `delegate`, `proxy_type`, and `delay`. If any value differs, the call will fail. + +```js +import { ethers } from "ethers"; +import { IProxyABI, IPROXY_ADDRESS } from "./contracts/proxy"; + +// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE +const { ethPrivateKey, rpcUrl } = require("./config.js"); + +const provider = new ethers.JsonRpcProvider(rpcUrl); +const signer = new ethers.Wallet(ethPrivateKey, provider); +const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, signer); + +const DELEGATE_PUBLIC_KEY = "DELEGATE_COLDKEY_PUBLIC_KEY"; // raw 32-byte public key +const PROXY_TYPE = 0; +const DELAY_BLOCKS = 0; + +const tx = await contract.removeProxy( + DELEGATE_PUBLIC_KEY, + PROXY_TYPE, + DELAY_BLOCKS, + { gasLimit: 300_000n }, +); +await tx.wait(); +console.log(`Proxy removed`); +``` diff --git a/docs/evm-tutorials/staking-precompile.md b/docs/evm-tutorials/staking-precompile.md index fe0f10a7..24b52cd5 100644 --- a/docs/evm-tutorials/staking-precompile.md +++ b/docs/evm-tutorials/staking-precompile.md @@ -32,16 +32,11 @@ btcli subnet register --network ws://127.0.0.1:9944 3. Save the delegate hotkey address. You will use this in the staking pool use case below. -4. Disable staking rate limits by setting `targetStakesPerInterval` to 1000. Follow these below steps: - - Open the Polkadot JS app using [this link with encoded transaction](https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/extrinsics/decode/0x0c00132fe803000000000000). - - Click on **Submission** tab. - - From the **using the selected account** field, select **ALICE**. - - Click on **Submit Transaction** at the bottom right. This will open the **authorize transaction** window. - - On this **authorize transaction** window, make sure the **sign and submit** toggle is ON and click on the **Sign and Submit** on the bottom right. - ## Staking V1 and V2 -There are two versions of staking precompile implemenation, V1 and V2. The contract address for V1 is `0x0000000000000000000000000000000000000801`. The address for V2 is `0x0000000000000000000000000000000000000805`. V1 is deprecated, but is kept for backwards-compatibility. The major difference between V1 and V2 is that the staking amount is fetched from the `msg.value` in V1. Then precompile transfers the token back to the caller. It is misleading and confuses solidity developers. In the V2 implementation, all amount parameters are defined as parameter of transaction. +There are two versions of staking precompile implemenation, V1 and V2. The contract address for V1 is `0x0000000000000000000000000000000000000801`. The address for V2 is `0x0000000000000000000000000000000000000805`. V1 is deprecated, but is kept for backwards-compatibility. The major difference between V1 and V2 is that the staking amount is fetched from the `msg.value` in V1. Then precompile transfers the token back to the caller. It is misleading and confuses solidity developers. In the V2 implementation, all amount parameters are defined as parameter of transaction. + +- **Source code**: [StakingPrecompileV2 reference](https://github.com/opentensor/subtensor/blob/main/precompiles/src/staking.rs) ## Call the staking precompile from another smart contract (staking pool use case) @@ -74,7 +69,6 @@ In this tutorial, you will interact directly with the staking precompile by usin 3. Remix IDE will find the precompile at the precompile address on the subtensor EVM and show it in the list of deployed contracts. Expand the contract, then expand the `addStake` method, and paste the public key of your delegate hotkey into the `hotkey` field. Then click **transact** and wait for the transaction to be completed. 4. Follow these steps to see that the stake record is updated in [Polkadot JS app](https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/chainstate): - 1. Select **subtensorModule** + **stake** in the drop-down list. 2. Paste the delegate hotkey account ID in the first parameter. 3. Toggle **include option** OFF for the second parameter. @@ -82,9 +76,9 @@ In this tutorial, you will interact directly with the staking precompile by usin ## Notes: Calling the staking precompile from another smart contract - - The precompile takes the contract's address as the **coldkey**, since the precompile can't get the original caller. - - The **contract** (not the caller's coldkey) must have sufficient liquidity or the transaction will fail. - - The transaction must be *privileged* because the liquidity for `addStake` is subtracted from contract. - - - As the function parameter indicates, `amount` in `addStake` and `removeStake` are specified in TAO $\tau$. - - That when transferring liquidity to the contract, `msg.value` is in denominations of 1/1e18 TAO $\tau$ . The staking precompile, however, expects RAO, 1/1e9 TAO $\tau$. You must convert before calling it: **uint256 amount = msg.value / 1e9**. +- The precompile takes the contract's address as the **coldkey**, since the precompile can't get the original caller. +- The **contract** (not the caller's coldkey) must have sufficient liquidity or the transaction will fail. +- The transaction must be _privileged_ because the liquidity for `addStake` is subtracted from contract. + +- As the function parameter indicates, `amount` in `addStake` and `removeStake` are specified in TAO $\tau$. +- That when transferring liquidity to the contract, `msg.value` is in denominations of 1/1e18 TAO $\tau$ . The staking precompile, however, expects RAO, 1/1e9 TAO $\tau$. You must convert before calling it: **uint256 amount = msg.value / 1e9**. diff --git a/docs/evm-tutorials/subnet-precompile.md b/docs/evm-tutorials/subnet-precompile.md index a3344fdc..34a55e1a 100644 --- a/docs/evm-tutorials/subnet-precompile.md +++ b/docs/evm-tutorials/subnet-precompile.md @@ -9,14 +9,10 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; This precompile allows you to interact with Bittensor subnets through EVM smart contracts, affording functionality for registering networks, viewing and setting network parameters, and querying network state. -This page: +This page describes the precompile's [available functions](#available-functions) on the precompile and demonstrates the precompile's usage with [example scripts](#example-scripts). -- described the precompile's [available functions](#available-functions) on the precompile -- demonstrates the precompile's usage with [example scripts](#example-scripts). - -The subnet precompile is available at address `0x803` (2051 in decimal). - -View the [source on GitHub](https://github.com/opentensor/subtensor/blob/main/precompiles/src/subnet.rs) +- **Address**: The subnet precompile is available at address `0x803` (2051 in decimal). +- **Source code**: [SubnetPrecompile reference](https://github.com/opentensor/subtensor/blob/main/precompiles/src/subnet.rs) :::info permissions Subnet operations have distinct requirements! @@ -101,83 +97,6 @@ Sets the serving rate limit for a subnet. - None (payable function) -### Difficulty Management - -#### `getMinDifficulty` - -Gets the minimum difficulty for a subnet. - -**Parameters:** - -- `netuid` (uint16): The subnetwork ID - -**Returns:** - -- `uint64`: The minimum difficulty value - -#### `setMinDifficulty` - -Sets the minimum difficulty for a subnet. - -**Parameters:** - -- `netuid` (uint16): The subnetwork ID -- `minDifficulty` (uint64): The new minimum difficulty value - -**Returns:** - -- None (payable function) - -#### `getMaxDifficulty` - -Gets the maximum difficulty for a subnet. - -**Parameters:** - -- `netuid` (uint16): The subnetwork ID - -**Returns:** - -- `uint64`: The maximum difficulty value - -#### `setMaxDifficulty` - -Sets the maximum difficulty for a subnet. - -**Parameters:** - -- `netuid` (uint16): The subnetwork ID -- `maxDifficulty` (uint64): The new maximum difficulty value - -**Returns:** - -- None (payable function) - -#### `getDifficulty` - -Gets the current difficulty for a subnet. - -**Parameters:** - -- `netuid` (uint16): The subnetwork ID - -**Returns:** - -- `uint64`: The current difficulty value - -#### `setDifficulty` - -Sets the current difficulty for a subnet. - -**Parameters:** - -- `netuid` (uint16): The subnetwork ID -- `difficulty` (uint64): The new difficulty value - -**Returns:** - -- None (payable function) - ### Weight Management #### `getWeightsVersionKey` @@ -217,7 +136,7 @@ Gets the weights set rate limit for a subnet. - `uint64`: The weights set rate limit value -#### `setWeightsSetRateLimit` ⚠️ **DEPRECATED** +#### `setWeightsSetRateLimit` ⚠️ **ROOT-ONLY** Sets the weights set rate limit for a subnet. **This function is deprecated. Subnet owners cannot set weight setting rate limits.** @@ -285,31 +204,6 @@ Sets the minimum allowed weights for a subnet. ### Consensus Parameters -#### `getAdjustmentAlpha` - -Gets the adjustment alpha parameter for a subnet. - -**Parameters:** - -- `netuid` (uint16): The subnetwork ID - -**Returns:** - -- `uint64`: The adjustment alpha value - -#### `setAdjustmentAlpha` - -Sets the adjustment alpha parameter for a subnet. - -**Parameters:** - -- `netuid` (uint16): The subnetwork ID -- `adjustmentAlpha` (uint64): The new adjustment alpha value - -**Returns:** - -- None (payable function) - #### `getKappa` Gets the kappa parameter for a subnet. @@ -335,31 +229,6 @@ Sets the kappa parameter for a subnet. - None (payable function) -#### `getRho` - -Gets the rho parameter for a subnet. - -**Parameters:** - -- `netuid` (uint16): The subnetwork ID - -**Returns:** - -- `uint16`: The rho value - -#### `setRho` - -Sets the rho parameter for a subnet. - -**Parameters:** - -- `netuid` (uint16): The subnetwork ID -- `rho` (uint16): The new rho value - -**Returns:** - -- None (payable function) - #### `getAlphaSigmoidSteepness` Gets the alpha sigmoid steepness parameter for a subnet. @@ -530,9 +399,9 @@ Gets the minimum burn amount for a subnet. - `uint64`: The minimum burn amount -#### `setMinBurn` ⚠️ **DEPRECATED** +#### `setMinBurn` -Sets the minimum burn amount for a subnet. **This function is deprecated. Subnet owners cannot set the minimum burn anymore.** +Sets the minimum burn amount for a subnet. **Parameters:** @@ -558,9 +427,9 @@ Gets the maximum burn amount for a subnet. - `uint64`: The maximum burn amount -#### `setMaxBurn` ⚠️ **DEPRECATED** +#### `setMaxBurn` -Sets the maximum burn amount for a subnet. **This function is deprecated. Subnet owners cannot set the maximum burn anymore.** +Sets the maximum burn amount for a subnet. **Parameters:** @@ -924,19 +793,19 @@ async function createSubnetGetSetParameter() { // Get the substrate address public key const pubk = decodeAddress(destinationAddress); const hex = Array.from(pubk, (byte) => - byte.toString(16).padStart(2, "0") + byte.toString(16).padStart(2, "0"), ).join(""); const signer = new ethers.Wallet(ethPrivateKey, provider); const ss58mirror = convertH160ToSS58(signer.address); let txSudoSetBalance = api.tx.sudo.sudo( - api.tx.balances.forceSetBalance(ss58mirror, BigInt(1e18).toString()) + api.tx.balances.forceSetBalance(ss58mirror, BigInt(1e18).toString()), ); await sendTransaction(api, txSudoSetBalance, account); const txSudoSetWhitelist = api.tx.sudo.sudo( - api.tx.evm.setWhitelist([signer.address]) + api.tx.evm.setWhitelist([signer.address]), ); await sendTransaction(api, txSudoSetWhitelist, account); @@ -944,7 +813,7 @@ async function createSubnetGetSetParameter() { const contractFactory = new ethers.ContractFactory( subnet_contract_abi, subnet_contract_bytecode, - signer + signer, ); const subnet_contract = await contractFactory.deploy(signer.address); @@ -955,8 +824,8 @@ async function createSubnetGetSetParameter() { txSudoSetBalance = api.tx.sudo.sudo( api.tx.balances.forceSetBalance( convertH160ToSS58(subnet_contract.target), - BigInt(1e16).toString() - ) + BigInt(1e16).toString(), + ), ); await sendTransaction(api, txSudoSetBalance, account); @@ -976,7 +845,7 @@ async function createSubnetGetSetParameter() { let tx = await subnet_contract.registerNetwork( encoder.encode("name"), encoder.encode("repo"), - encoder.encode("contact") + encoder.encode("contact"), ); await tx.wait(); @@ -987,13 +856,13 @@ async function createSubnetGetSetParameter() { console.log("networkOwner is ", networkOwner); // Note: This example uses setHyperParameter which calls setServingRateLimit - // Some other functions like setMinBurn, setMaxBurn, setWeightsSetRateLimit are deprecated + // Some other functions like setWeightsSetRateLimit are root-only and cannot be set by the subnet owner tx = await subnet_contract.setHyperParameter(netuid, 255); await tx.wait(); // get parameter from chain let parameter = Number( - await api.query.subtensorModule.servingRateLimit(netuid) + await api.query.subtensorModule.servingRateLimit(netuid), ); assert(parameter == 255); @@ -1004,7 +873,7 @@ async function createSubnetGetSetParameter() { // check total networks after registration console.log( "total networks is ", - (await api.query.subtensorModule.totalNetworks()).toHuman() + (await api.query.subtensorModule.totalNetworks()).toHuman(), ); process.exit(0); diff --git a/docs/evm-tutorials/troubleshooting.md b/docs/evm-tutorials/troubleshooting.md index 67e4c1bb..891c30df 100644 --- a/docs/evm-tutorials/troubleshooting.md +++ b/docs/evm-tutorials/troubleshooting.md @@ -2,23 +2,25 @@ title: "Troubleshooting" --- -# Gas estimation failed +# Troubleshooting -Failure of `eth_estimateGas` may indicate a wide range of problems. To understand the root cause, it can help to understand the live cycle of EVM transaction first. When someone posts a transaction to the network, the first thing to happen is gas estimation. The signed transaction is sent to a chain node and the node checks the transaction for errors. In case of literally any error with the transaction (including inability to pay fees due to low sender balance or mis-formatted transaction, etc.) the gas estimation will fail. Failure may or may not indicate the issue with gas fees. If you get the gas estimation failure, please check the following (this list will be updated dynamically): +## Gas estimation failed + +Failure of `eth_estimateGas` may indicate a wide range of problems. To understand the root cause, it can help to understand the live cycle of EVM transaction first. + +When someone posts a transaction to the network, the first thing to happen is gas estimation. The signed transaction is sent to a chain node and the node checks the transaction for errors. In case of literally any error with the transaction (including inability to pay fees due to low sender balance or mis-formatted transaction, etc.) the gas estimation will fail. Failure may or may not indicate the issue with gas fees. If you get the gas estimation failure, please check the following (this list will be updated dynamically): 1. Sender address has sufficient balance to pay gas fees 2. If you're using local chain, it is properly setup: Chain ID is set, deployment white list is disabled - -# Gas estimation exceeds block limit (75000000) +## Gas estimation exceeds block limit (75000000) Select the correct Solidity/EVM version. It needs to be Cancun / 0.8.24 or below. -# InvalidCode(Opcode(94)) when calling a precompile from another contract +## InvalidCode(Opcode(94)) when calling a precompile from another contract Select the correct Solidity/EVM version. It needs to be Cancun / 0.8.24 or below. -# Transaction is pending for too long on localnet when posted through Metamask +## Transaction is pending for too long on localnet when posted through Metamask The issue may occur when Metamask is used to sign and post transactions onchain. The cause may be the nonce caching. Metamask caches address nonce, and doesn't revert it when the network is restarted. So, what may happen is Metamask will post the transaction with higher nonce, and it will get stuck in transaction pool, waiting for the address nonce to reach the expected value. To fix this, open Metamask setting, select "Advanced", and then click on "Clear activity tab data". - diff --git a/docs/evm-tutorials/voting-power-precompile.md b/docs/evm-tutorials/voting-power-precompile.md new file mode 100644 index 00000000..8c40c25b --- /dev/null +++ b/docs/evm-tutorials/voting-power-precompile.md @@ -0,0 +1,66 @@ +--- +title: "Voting Power Precompile" +--- + +# Voting Power Precompile + +The VotingPower precompile gives smart contracts read-only access to the per-validator EMA voting power scores tracked on each subnet. Use it to build on-chain governance mechanisms — slashing conditions, quorum checks, reward weighting — that respond to relative validator influence. + +- **Address**: `0x000000000000000000000000000000000000080d` +- **Source code**: [VotingPowerPrecompile reference](https://github.com/opentensor/subtensor/blob/main/precompiles/src/voting_power.rs) + +## Functions + +| Function | Parameters | Returns | Description | +| ----------------------------------------------- | -------------- | --------- | ----------------------------------------------------------------------------------------- | +| `getVotingPower(uint16 netuid, bytes32 hotkey)` | netuid, hotkey | `uint256` | EMA voting power for a validator. Returns `0` if tracking is disabled or no entry exists. | +| `getTotalVotingPower(uint16 netuid)` | netuid | `uint256` | Sum of voting power across all validators on the subnet. | +| `isVotingPowerTrackingEnabled(uint16 netuid)` | netuid | `bool` | Whether tracking is currently active. | +| `getVotingPowerDisableAtBlock(uint16 netuid)` | netuid | `uint64` | Block at which tracking will auto-disable, or `0` if none scheduled. | +| `getVotingPowerEmaAlpha(uint16 netuid)` | netuid | `uint64` | EMA smoothing factor (18 decimal precision, `1.0 = 10^18`). | + +## Usage examples + +### ABI + +The canonical ABI is exported from [`contract-tests/src/contracts/votingPower.ts`](https://github.com/opentensor/subtensor/blob/main/contract-tests/src/contracts/votingPower.ts). You can import the ABI and contract address if you have a local copy of the source files as shown: + +```javascript +import { + IVotingPowerABI, + IVOTING_POWER_ADDRESS, +} from "./contracts/votingPower"; +``` + +### Quorum checks + +```javascript +import { ethers } from "ethers"; +import { + IVotingPowerABI, + IVOTING_POWER_ADDRESS, +} from "./contracts/votingPower"; + +const provider = new ethers.JsonRpcProvider("rpcUrl"); +const vp = new ethers.Contract( + IVOTING_POWER_ADDRESS, + IVotingPowerABI, + provider, +); + +const netuid = 1; +const hotkey = "0xabcd..."; // 32-byte hotkey + +const enabled = await vp.isVotingPowerTrackingEnabled(netuid); +const validatorVP = await vp.getVotingPower(netuid, hotkey); +const totalVP = await vp.getTotalVotingPower(netuid); + +console.log(`Tracking enabled: ${enabled}`); +console.log(`Validator has ${validatorVP} voting power`); +console.log(`Total voting power: ${totalVP}`); +``` + +:::info + +The `hotkey` parameter on the `getVotingPower` function expects a 32-byte public key. For Substrate wallets use `decodeAddress(ss58)` imported from `@polkadot/util-crypto`; for EVM wallets you must derive the corresponding public key for the H160 account before passing it. +::: diff --git a/sidebars.js b/sidebars.js index 7446aa64..8fd8768f 100644 --- a/sidebars.js +++ b/sidebars.js @@ -54,7 +54,7 @@ const sidebars = { "learn/emissions", "learn/ema", "learn/yuma-consensus", - "learn/yc3-blog", + "learn/yc3-blog", "concepts/weight-copying-in-bittensor", "learn/yuma3-migration-guide", "learn/fees", @@ -83,7 +83,7 @@ const sidebars = { collapsed: true, items: [ "keys/wallets", - { + { type: "category", label: "Bittensor Wallet", collapsible: true, @@ -112,7 +112,6 @@ const sidebars = { }, "keys/multisig", "keys/coldkey-swap", - ], }, @@ -223,7 +222,7 @@ const sidebars = { collapsible: true, collapsed: true, items: [ - "sdk/index", + "sdk/index", "getting-started/installation", "sdk/env-vars", "sdk/bt-api-ref", @@ -321,6 +320,10 @@ const sidebars = { "evm-tutorials/subnet-precompile", "evm-tutorials/metagraph-precompile", "evm-tutorials/neuron-precompile", + "evm-tutorials/alpha-precompile", + "evm-tutorials/proxy-precompile", + "evm-tutorials/crowdloan-precompile", + "evm-tutorials/voting-power-precompile", ], }, // {