diff --git a/.github/workflows/check-bittensor-e2e-tests.yml.yml b/.github/workflows/check-bittensor-e2e-tests.yml.yml index 721eb96994..c133efab22 100644 --- a/.github/workflows/check-bittensor-e2e-tests.yml.yml +++ b/.github/workflows/check-bittensor-e2e-tests.yml.yml @@ -26,7 +26,7 @@ env: jobs: check-label: - runs-on: [self-hosted, type-ccx13] + runs-on: ubuntu-latest outputs: skip-bittensor-e2e-tests: ${{ steps.get-labels.outputs.skip-bittensor-e2e-tests }} steps: @@ -57,7 +57,7 @@ jobs: find-btcli-e2e-tests: needs: check-label if: needs.check-label.outputs.skip-bittensor-e2e-tests == 'false' - runs-on: [self-hosted, type-ccx13] + runs-on: ubuntu-latest outputs: test-files: ${{ steps.get-btcli-tests.outputs.test-files }} steps: @@ -84,7 +84,7 @@ jobs: find-sdk-e2e-tests: needs: check-label if: needs.check-label.outputs.skip-bittensor-e2e-tests == 'false' - runs-on: [self-hosted, type-ccx13] + runs-on: ubuntu-latest outputs: test-files: ${{ steps.get-sdk-tests.outputs.test-files }} steps: @@ -111,7 +111,7 @@ jobs: build-image-with-current-branch: needs: check-label if: needs.check-label.outputs.skip-bittensor-e2e-tests == 'false' - runs-on: [self-hosted, type-ccx33] + runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 @@ -130,17 +130,27 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Move Docker data-root to /mnt/data + run: | + sudo systemctl stop docker + sudo mkdir -p /mnt/data/docker + sudo chown -R runner:runner /mnt/data + sudo chmod -R 777 /mnt/data + echo '{"data-root": "/mnt/data/docker"}' | sudo tee /etc/docker/daemon.json + sudo systemctl start docker + docker info | grep "Docker Root Dir" + - name: Build Docker Image run: docker build -f Dockerfile-localnet -t localnet . - name: Save Docker Image as Tar - run: docker save -o subtensor-localnet.tar localnet + run: docker save -o /mnt/data/subtensor-localnet.tar localnet - name: Upload Docker Image as Artifact uses: actions/upload-artifact@v4 with: name: subtensor-localnet - path: subtensor-localnet.tar + path: /mnt/data/subtensor-localnet.tar # main btcli job run-btcli-e2e-tests: @@ -149,7 +159,7 @@ jobs: - find-btcli-e2e-tests - build-image-with-current-branch if: needs.check-label.outputs.skip-bittensor-e2e-tests == 'false' - runs-on: [self-hosted, type-ccx13] + runs-on: ubuntu-latest strategy: fail-fast: false max-parallel: 16 @@ -181,7 +191,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v5 with: - enable-cache: 'false' + enable-cache: "false" - name: Create Python virtual environment working-directory: ${{ github.workspace }} @@ -243,7 +253,7 @@ jobs: - find-sdk-e2e-tests - build-image-with-current-branch if: needs.check-label.outputs.skip-bittensor-e2e-tests == 'false' - runs-on: [self-hosted, type-ccx13] + runs-on: ubuntu-latest strategy: fail-fast: false max-parallel: 16 @@ -275,7 +285,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v5 with: - enable-cache: 'false' + enable-cache: "false" - name: Create Python virtual environment working-directory: ${{ github.workspace }} diff --git a/.github/workflows/evm-tests.yml b/.github/workflows/evm-tests.yml index b818695237..80debe2517 100644 --- a/.github/workflows/evm-tests.yml +++ b/.github/workflows/evm-tests.yml @@ -24,7 +24,7 @@ permissions: jobs: run: - runs-on: [self-hosted, type-ccx33] + runs-on: ubuntu-latest env: RUST_BACKTRACE: full steps: diff --git a/chain-extensions/src/mock.rs b/chain-extensions/src/mock.rs index 98ea096199..0efea56696 100644 --- a/chain-extensions/src/mock.rs +++ b/chain-extensions/src/mock.rs @@ -327,6 +327,7 @@ parameter_types! { pub const InitialYuma3On: bool = false; // Default value for Yuma3On // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days + pub const InitialDeregistrationPriorityScheduleDelay: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialColdkeySwapRescheduleDuration: u64 = 24 * 60 * 60 / 12; // Default as 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. @@ -398,6 +399,7 @@ impl pallet_subtensor::Config for Test { type Yuma3On = InitialYuma3On; type Preimages = Preimage; type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialDeregistrationPriorityScheduleDelay = InitialDeregistrationPriorityScheduleDelay; type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; diff --git a/common/src/lib.rs b/common/src/lib.rs index a98a957ad8..28a33c2ae6 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -162,6 +162,33 @@ pub enum ProxyType { RootClaim, } +impl TryFrom for ProxyType { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::Any), + 1 => Ok(Self::Owner), + 2 => Ok(Self::NonCritical), + 3 => Ok(Self::NonTransfer), + 4 => Ok(Self::Senate), + 5 => Ok(Self::NonFungible), + 6 => Ok(Self::Triumvirate), + 7 => Ok(Self::Governance), + 8 => Ok(Self::Staking), + 9 => Ok(Self::Registration), + 10 => Ok(Self::Transfer), + 11 => Ok(Self::SmallTransfer), + 12 => Ok(Self::RootWeights), + 13 => Ok(Self::ChildKeys), + 14 => Ok(Self::SudoUncheckedSetCode), + 15 => Ok(Self::SwapHotkey), + 16 => Ok(Self::SubnetLeaseBeneficiary), + _ => Err(()), + } + } +} + impl Default for ProxyType { // allow all Calls; required to be most permissive fn default() -> Self { diff --git a/evm-tests/src/address-utils.ts b/evm-tests/src/address-utils.ts index ed3abc5008..753eed2530 100644 --- a/evm-tests/src/address-utils.ts +++ b/evm-tests/src/address-utils.ts @@ -1,6 +1,6 @@ import { Address } from "viem" import { encodeAddress } from "@polkadot/util-crypto"; -import { ss58Address } from "@polkadot-labs/hdkd-helpers"; +import { ss58Address, ss58Decode } from "@polkadot-labs/hdkd-helpers"; import { hexToU8a } from "@polkadot/util"; import { blake2AsU8a, decodeAddress } from "@polkadot/util-crypto"; import { Binary } from "polkadot-api"; diff --git a/evm-tests/src/contracts/proxy.ts b/evm-tests/src/contracts/proxy.ts new file mode 100644 index 0000000000..48ffd0a50f --- /dev/null +++ b/evm-tests/src/contracts/proxy.ts @@ -0,0 +1,148 @@ +export const IPROXY_ADDRESS = "0x000000000000000000000000000000000000080b"; + +export const IProxyABI = [ + { + "inputs": [ + { + "internalType": "uint8", + "name": "proxy_type", + "type": "uint8" + }, + { + "internalType": "uint32", + "name": "delay", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "index", + "type": "uint16" + } + ], + "name": "createPureProxy", + "outputs": [ + { + "internalType": "bytes32", + "name": "proxy", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "spawner", + "type": "bytes32" + }, + { + "internalType": "uint8", + "name": "proxy_type", + "type": "uint8" + }, + { + "internalType": "uint16", + "name": "index", + "type": "uint16" + }, + { + "internalType": "uint32", + "name": "height", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "ext_index", + "type": "uint32" + } + ], + "name": "killPureProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "real", + "type": "bytes32" + }, + { + "internalType": "uint8[]", + "name": "force_proxy_type", // optional + "type": "uint8[]" + }, + { + "internalType": "uint8[]", + "name": "call", + "type": "uint8[]" + } + ], + "name": "proxyCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "removeProxies", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { + "inputs": [], + "name": "pokeDeposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "delegate", + "type": "bytes32" + }, + { + "internalType": "uint8", + "name": "proxy_type", + "type": "uint8" + }, + { + "internalType": "uint32", + "name": "delay", + "type": "uint32" + } + ], + "name": "removeProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "delegate", + "type": "bytes32" + }, + { + "internalType": "uint8", + "name": "proxy_type", + "type": "uint8" + }, + { + "internalType": "uint32", + "name": "delay", + "type": "uint32" + } + ], + "name": "addProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +]; diff --git a/evm-tests/test/neuron.precompile.emission-check.test.ts b/evm-tests/test/neuron.precompile.emission-check.test.ts index e54cb1ec88..1a2b053ed0 100644 --- a/evm-tests/test/neuron.precompile.emission-check.test.ts +++ b/evm-tests/test/neuron.precompile.emission-check.test.ts @@ -45,7 +45,7 @@ describe("Test the Neuron precompile with emission", () => { it("Burned register and check emission", async () => { let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 - + const uid = await api.query.SubtensorModule.SubnetworkN.getValue(netuid) const contract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet); @@ -63,7 +63,7 @@ describe("Test the Neuron precompile with emission", () => { let i = 0; while (i < 10) { - const emission = await api.query.SubtensorModule.PendingEmission.getValue(netuid) + const emission = await api.query.SubtensorModule.Emission.getValue(netuid) console.log("emission is ", emission); await new Promise((resolve) => setTimeout(resolve, 2000)); diff --git a/evm-tests/test/pure-proxy.precompile.test.ts b/evm-tests/test/pure-proxy.precompile.test.ts new file mode 100644 index 0000000000..1a34b02cf7 --- /dev/null +++ b/evm-tests/test/pure-proxy.precompile.test.ts @@ -0,0 +1,163 @@ +import * as assert from "assert"; + +import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" +import { generateRandomEthersWallet } from "../src/utils"; +import { devnet, MultiAddress } from "@polkadot-api/descriptors" +import { PolkadotSigner, TypedApi } from "polkadot-api"; +import { convertH160ToPublicKey, convertH160ToSS58, convertPublicKeyToSs58 } from "../src/address-utils" +import { IProxyABI, IPROXY_ADDRESS } from "../src/contracts/proxy" +import { ethers } from 'ethers'; +import { forceSetBalanceToEthAddress, forceSetBalanceToSs58Address } from "../src/subtensor"; +import { KeyPair } from "@polkadot-labs/hdkd-helpers"; + +import { decodeAddress } from "@polkadot/util-crypto"; + +async function getTransferCallCode(api: TypedApi, receiver: KeyPair, transferAmount: number) { + + const unsignedTx = api.tx.Balances.transfer_keep_alive({ + dest: MultiAddress.Id(convertPublicKeyToSs58(receiver.publicKey)), + value: BigInt(1000000000), + }); + const encodedCallDataBytes = await unsignedTx.getEncodedData(); + + // encoded call should be 0x050300d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d02286bee + // const transferCall = encodedCallDataBytes + + const data = encodedCallDataBytes.asBytes() + + return [...data] +} + +async function getProxies(api: TypedApi, address: string) { + const entries = await api.query.Proxy.Proxies.getEntries() + const result = [] + for (const entry of entries) { + const proxyAddress = entry.keyArgs[0] + const values = entry.value + const proxies = values[0] + for (const proxy of proxies) { + if (proxy.delegate === address) { + result.push(proxyAddress) + } + } + } + return result +} + +describe("Test pure proxy precompile", () => { + const evmWallet = generateRandomEthersWallet(); + const evmWallet2 = generateRandomEthersWallet(); + const evmWallet3 = generateRandomEthersWallet(); + const receiver = getRandomSubstrateKeypair(); + + let api: TypedApi + + let alice: PolkadotSigner; + + before(async () => { + api = await getDevnetApi() + alice = await getAliceSigner(); + + await forceSetBalanceToEthAddress(api, evmWallet.address) + await forceSetBalanceToEthAddress(api, evmWallet2.address) + await forceSetBalanceToEthAddress(api, evmWallet3.address) + }) + + it("Call createPureProxy, then use proxy to call transfer", async () => { + const proxies = await getProxies(api, convertH160ToSS58(evmWallet.address)) + const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet) + console.log("evmWallet", evmWallet.address) + + const type = 0; + const delay = 0; + const index = 0; + const tx = await contract.createPureProxy(type, delay, index) + const response = await tx.wait() + console.log("response", response.blockNumber) + + const proxiesAfterAdd = await getProxies(api, convertH160ToSS58(evmWallet.address)) + + const length = proxiesAfterAdd.length + assert.equal(length, proxies.length + 1, "proxy should be set") + const proxy = proxiesAfterAdd[proxiesAfterAdd.length - 1] + + await forceSetBalanceToSs58Address(api, proxy) + const balance = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free + + const amount = 1000000000; + + const callCode = await getTransferCallCode(api, receiver, amount) + const tx2 = await contract.proxyCall(decodeAddress(proxy), [type], callCode) + await tx2.wait() + + const balanceAfter = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free + assert.equal(balanceAfter, balance + BigInt(amount), "balance should be increased") + }) + + it("Call createPureProxy, add multiple proxies", async () => { + const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet) + const type = 0; + const delay = 0; + const index = 0; + const proxies = await getProxies(api, convertH160ToSS58(evmWallet.address)) + const length = proxies.length + for (let i = 0; i < 5; i++) { + const tx = await contract.createPureProxy(type, delay, index) + await tx.wait() + + await new Promise(resolve => setTimeout(resolve, 500)); + const currentProxies = await getProxies(api, convertH160ToSS58(evmWallet.address)) + assert.equal(currentProxies.length, length + i + 1, "proxy should be set") + } + }) + + it("Call createPureProxy, edge cases, call via wrong proxy", async () => { + const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet2) + const amount = 1000000000; + const callCode = await getTransferCallCode(api, receiver, amount) + const type = 0; + + // call with wrong proxy + try { + const tx = await contract.proxyCall(receiver, [type], callCode) + await tx.wait() + } catch (error) { + assert.notEqual(error, undefined, "should fail if proxy not set") + } + }) + + it("Call createProxy, then use proxy to call transfer", async () => { + const proxies = await api.query.Proxy.Proxies.getValue(convertH160ToSS58(evmWallet2.address)) + const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet2) + + const type = 0; + const delay = 0; + + const tx = await contract.addProxy(convertH160ToPublicKey(evmWallet3.address), type, delay) + await tx.wait() + + + const proxiesAfterAdd = await await api.query.Proxy.Proxies.getValue(convertH160ToSS58(evmWallet2.address)) + + const length = proxiesAfterAdd[0].length + assert.equal(length, proxies[0].length + 1, "proxy should be set") + const proxy = proxiesAfterAdd[0][proxiesAfterAdd[0].length - 1] + + assert.equal(proxy.delegate, convertH160ToSS58(evmWallet3.address), "proxy should be set") + + + const balance = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free + + const amount = 1000000000; + + const contract2 = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, evmWallet3) + + + const callCode = await getTransferCallCode(api, receiver, amount) + const tx2 = await contract2.proxyCall(convertH160ToPublicKey(evmWallet2.address), [type], callCode) + await tx2.wait() + + const balanceAfter = (await api.query.System.Account.getValue(convertPublicKeyToSs58(receiver.publicKey))).data.free + assert.equal(balanceAfter, balance + BigInt(amount), "balance should be increased") + }) +}); diff --git a/evm-tests/test/staking.precompile.reward.test.ts b/evm-tests/test/staking.precompile.reward.test.ts index 55a1312313..d04620c91b 100644 --- a/evm-tests/test/staking.precompile.reward.test.ts +++ b/evm-tests/test/staking.precompile.reward.test.ts @@ -74,14 +74,14 @@ describe("Test neuron precompile reward", () => { let index = 0; while (index < 60) { - const pending = await api.query.SubtensorModule.PendingEmission.getValue(netuid); + const pending = await api.query.SubtensorModule.PendingValidatorEmission.getValue(netuid); if (pending > 0) { console.log("pending amount is ", pending); break; } await new Promise((resolve) => setTimeout(resolve, 1000)); - console.log("wait for the pendingEmission update"); + console.log("wait for the ValidatorEmission update"); index += 1; } diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 08589e530b..a1b61fa5c8 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -485,6 +485,12 @@ mod benchmarks { _(RawOrigin::Root, 100u32.into()); } + #[benchmark] + fn sudo_set_deregistration_priority_schedule_delay() { + #[extrinsic_call] + _(RawOrigin::Root, 100u32.into()); + } + #[benchmark] fn sudo_set_dissolve_network_schedule_duration() { #[extrinsic_call] diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 01e9e7b33e..5f0d845ead 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -139,8 +139,8 @@ pub mod pallet { Alpha, /// Enum for crowdloan precompile Crowdloan, - /// Pure proxy precompile - PureProxy, + /// Proxy precompile + Proxy, /// Leasing precompile Leasing, } @@ -236,6 +236,7 @@ pub mod pallet { netuid, &[Hyperparameter::ServingRateLimit.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; pallet_subtensor::Pallet::::set_serving_rate_limit(netuid, serving_rate_limit); log::debug!("ServingRateLimitSet( serving_rate_limit: {serving_rate_limit:?} ) "); pallet_subtensor::Pallet::::record_owner_rl( @@ -288,6 +289,7 @@ pub mod pallet { netuid, &[Hyperparameter::MaxDifficulty.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -322,6 +324,7 @@ pub mod pallet { netuid, &[TransactionType::SetWeightsVersionKey], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -413,6 +416,7 @@ pub mod pallet { netuid, &[Hyperparameter::AdjustmentAlpha.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -445,6 +449,7 @@ pub mod pallet { netuid, &[Hyperparameter::ImmunityPeriod.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist @@ -479,6 +484,7 @@ pub mod pallet { netuid, &[Hyperparameter::MinAllowedWeights.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -513,6 +519,7 @@ pub mod pallet { netuid, &[Hyperparameter::MaxAllowedUids.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist @@ -572,6 +579,7 @@ pub mod pallet { netuid, &[Hyperparameter::Rho.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -604,6 +612,7 @@ pub mod pallet { netuid, &[Hyperparameter::ActivityCutoff.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -672,6 +681,7 @@ pub mod pallet { netuid, &[Hyperparameter::PowRegistrationAllowed.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; pallet_subtensor::Pallet::::set_network_pow_registration_allowed( netuid, @@ -733,6 +743,7 @@ pub mod pallet { netuid, &[Hyperparameter::MinBurn.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist @@ -773,6 +784,7 @@ pub mod pallet { netuid, &[Hyperparameter::MaxBurn.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist @@ -868,6 +880,7 @@ pub mod pallet { netuid, &[Hyperparameter::BondsMovingAverage.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; if maybe_owner.is_some() { ensure!( bonds_moving_average <= 975000, @@ -908,6 +921,7 @@ pub mod pallet { netuid, &[Hyperparameter::BondsPenalty.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -1245,6 +1259,7 @@ pub mod pallet { netuid, &[Hyperparameter::CommitRevealEnabled.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -1288,6 +1303,7 @@ pub mod pallet { netuid, &[Hyperparameter::LiquidAlphaEnabled.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; pallet_subtensor::Pallet::::set_liquid_alpha_enabled(netuid, enabled); log::debug!("LiquidAlphaEnableToggled( netuid: {netuid:?}, Enabled: {enabled:?} ) "); pallet_subtensor::Pallet::::record_owner_rl( @@ -1318,6 +1334,7 @@ pub mod pallet { netuid, &[Hyperparameter::AlphaValues.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; let res = pallet_subtensor::Pallet::::do_set_alpha_values( origin, netuid, alpha_low, alpha_high, ); @@ -1369,6 +1386,26 @@ pub mod pallet { Ok(()) } + /// Sets the delay for deregistration priority scheduling. + #[pallet::call_index(55)] + #[pallet::weight(( + Weight::from_parts(5_000_000, 0) + .saturating_add(T::DbWeight::get().reads(0_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)), + DispatchClass::Operational, + Pays::Yes + ))] + pub fn sudo_set_deregistration_priority_schedule_delay( + origin: OriginFor, + duration: BlockNumberFor, + ) -> DispatchResult { + ensure_root(origin)?; + pallet_subtensor::Pallet::::set_deregistration_priority_schedule_delay(duration); + log::trace!("DeregistrationPriorityScheduleDelaySet( duration: {duration:?} )"); + + Ok(()) + } + /// Sets the duration of the dissolve network schedule. /// /// This extrinsic allows the root account to set the duration for the dissolve network schedule. @@ -1383,7 +1420,7 @@ pub mod pallet { /// /// # Weight /// Weight is handled by the `#[pallet::weight]` attribute. - #[pallet::call_index(55)] + #[pallet::call_index(56)] #[pallet::weight(( Weight::from_parts(5_000_000, 0) .saturating_add(T::DbWeight::get().reads(0_u64)) @@ -1437,6 +1474,7 @@ pub mod pallet { netuid, &[Hyperparameter::WeightCommitInterval.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -1535,6 +1573,7 @@ pub mod pallet { netuid, &[Hyperparameter::TransferEnabled.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; let res = pallet_subtensor::Pallet::::toggle_transfer(netuid, toggle); if res.is_ok() { pallet_subtensor::Pallet::::record_owner_rl( @@ -1567,6 +1606,7 @@ pub mod pallet { netuid, &[Hyperparameter::RecycleOrBurn.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; pallet_subtensor::Pallet::::set_recycle_or_burn(netuid, recycle_or_burn); pallet_subtensor::Pallet::::record_owner_rl( @@ -1734,6 +1774,7 @@ pub mod pallet { netuid, &[Hyperparameter::AlphaSigmoidSteepness.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -1784,6 +1825,7 @@ pub mod pallet { netuid, &[Hyperparameter::Yuma3Enabled.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; pallet_subtensor::Pallet::::set_yuma3_enabled(netuid, enabled); Self::deposit_event(Event::Yuma3EnableToggled { netuid, enabled }); @@ -1823,6 +1865,7 @@ pub mod pallet { netuid, &[Hyperparameter::BondsResetEnabled.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; pallet_subtensor::Pallet::::set_bonds_reset(netuid, enabled); Self::deposit_event(Event::BondsResetToggled { netuid, enabled }); @@ -1948,6 +1991,7 @@ pub mod pallet { netuid, &[Hyperparameter::ImmuneNeuronLimit.into()], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; pallet_subtensor::Pallet::::set_owner_immune_neuron_limit(netuid, immune_neurons)?; pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, @@ -2021,6 +2065,7 @@ pub mod pallet { netuid, &[TransactionType::MechanismCountUpdate], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; pallet_subtensor::Pallet::::do_set_mechanism_count(netuid, mechanism_count)?; @@ -2047,6 +2092,7 @@ pub mod pallet { netuid, &[TransactionType::MechanismEmission], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; pallet_subtensor::Pallet::::do_set_emission_split(netuid, maybe_split)?; @@ -2077,6 +2123,7 @@ pub mod pallet { netuid, &[TransactionType::MaxUidsTrimming], )?; + pallet_subtensor::Pallet::::ensure_admin_window_open(netuid)?; pallet_subtensor::Pallet::::trim_to_max_allowed_uids(netuid, max_n)?; diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index 0140808baa..58e00e70ba 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -141,6 +141,7 @@ parameter_types! { // pub const InitialHotkeyEmissionTempo: u64 = 1; // (DEPRECATED) // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days + pub const InitialDeregistrationPriorityScheduleDelay: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialColdkeySwapRescheduleDuration: u64 = 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialTaoWeight: u64 = u64::MAX/10; // 10% global weight. @@ -211,6 +212,7 @@ impl pallet_subtensor::Config for Test { type Yuma3On = InitialYuma3On; type Preimages = (); type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialDeregistrationPriorityScheduleDelay = InitialDeregistrationPriorityScheduleDelay; type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index 1aaefc8f8d..be222888e8 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -1418,6 +1418,39 @@ fn test_sudo_set_coldkey_swap_schedule_duration() { }); } +#[test] +fn test_sudo_set_deregistration_priority_schedule_delay() { + new_test_ext().execute_with(|| { + let root = RuntimeOrigin::root(); + let non_root = RuntimeOrigin::signed(U256::from(1)); + let new_duration = 150u32.into(); + + assert_noop!( + AdminUtils::sudo_set_deregistration_priority_schedule_delay(non_root, new_duration), + DispatchError::BadOrigin + ); + + assert_ok!(AdminUtils::sudo_set_deregistration_priority_schedule_delay( + root.clone(), + new_duration + )); + + assert_eq!( + pallet_subtensor::DeregistrationPriorityScheduleDelay::::get(), + new_duration + ); + + System::assert_last_event( + Event::DeregistrationPriorityScheduleDelaySet(new_duration).into(), + ); + + assert_ok!(AdminUtils::sudo_set_deregistration_priority_schedule_delay( + root, + new_duration + )); + }); +} + #[test] fn test_sudo_set_dissolve_network_schedule_duration() { new_test_ext().execute_with(|| { diff --git a/pallets/proxy/src/lib.rs b/pallets/proxy/src/lib.rs index 93ac568668..70337fc7a8 100644 --- a/pallets/proxy/src/lib.rs +++ b/pallets/proxy/src/lib.rs @@ -39,6 +39,7 @@ use frame::{ prelude::*, traits::{Currency, InstanceFilter, ReservableCurrency}, }; +use frame_system::pallet_prelude::BlockNumberFor as SystemBlockNumberFor; pub use pallet::*; use subtensor_macros::freeze_struct; pub use weights::WeightInfo; @@ -752,6 +753,14 @@ pub mod pallet { InvalidDerivedAccountId, } + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_finalize(_n: SystemBlockNumberFor) { + // clear this map on end of each block + let _ = LastCallResult::::clear(u32::MAX, None); + } + } + /// The set of account proxies. Maps the account which has delegated to the accounts /// which are being delegated to, together with the amount held on deposit. #[pallet::storage] @@ -782,6 +791,11 @@ pub mod pallet { ValueQuery, >; + /// The result of the last call made by the proxy (key). + #[pallet::storage] + pub type LastCallResult = + StorageMap<_, Twox64Concat, T::AccountId, DispatchResult, OptionQuery>; + #[pallet::view_functions] impl Pallet { /// Check if a `RuntimeCall` is allowed for a given `ProxyType`. @@ -1028,7 +1042,7 @@ impl Pallet { ) { use frame::traits::{InstanceFilter as _, OriginTrait as _}; // This is a freshly authenticated new account, the origin restrictions doesn't apply. - let mut origin: T::RuntimeOrigin = frame_system::RawOrigin::Signed(real).into(); + let mut origin: T::RuntimeOrigin = frame_system::RawOrigin::Signed(real.clone()).into(); origin.add_filter(move |c: &::RuntimeCall| { let c = ::RuntimeCall::from_ref(c); // We make sure the proxy call does access this pallet to change modify proxies. @@ -1052,6 +1066,9 @@ impl Pallet { } }); let e = call.dispatch(origin); + + LastCallResult::::insert(real, e.map(|_| ()).map_err(|e| e.error)); + Self::deposit_event(Event::ProxyExecuted { result: e.map(|_| ()).map_err(|e| e.error), }); diff --git a/pallets/subtensor/Cargo.toml b/pallets/subtensor/Cargo.toml index fdd5e5f9ab..2e35a89d19 100644 --- a/pallets/subtensor/Cargo.toml +++ b/pallets/subtensor/Cargo.toml @@ -59,6 +59,8 @@ pallet-subtensor-proxy.workspace = true [dev-dependencies] pallet-balances = { workspace = true, features = ["std"] } pallet-scheduler.workspace = true +pallet-subtensor-proxy.workspace = true +subtensor-runtime-common.workspace = true pallet-subtensor-swap.workspace = true sp-version.workspace = true # Substrate diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 2a13a18aa0..223c086419 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -1615,10 +1615,11 @@ mod pallet_benchmarks { ); let pending_root_alpha = 10_000_000u64; - Subtensor::::drain_pending_emission( + Subtensor::::distribute_emission( netuid, AlphaCurrency::ZERO, pending_root_alpha.into(), + pending_root_alpha.into(), AlphaCurrency::ZERO, ); diff --git a/pallets/subtensor/src/coinbase/block_step.rs b/pallets/subtensor/src/coinbase/block_step.rs index 818235a955..21f7866459 100644 --- a/pallets/subtensor/src/coinbase/block_step.rs +++ b/pallets/subtensor/src/coinbase/block_step.rs @@ -18,13 +18,18 @@ impl Pallet { .to_u64(), ); log::debug!("Block emission: {block_emission:?}"); - // --- 3. Run emission through network. + + // --- 3. Reveal matured weights. + Self::reveal_crv3_commits(); + // --- 4. Run emission through network. Self::run_coinbase(block_emission); - // --- 4. Set pending children on the epoch; but only after the coinbase has been run. + // --- 5. Update moving prices AFTER using them for emissions. + Self::update_moving_prices(); + // --- 6. Set pending children on the epoch; but only after the coinbase has been run. Self::try_set_pending_children(block_number); - // --- 5. Run auto-claim root divs. + // --- 7. Run auto-claim root divs. Self::run_auto_claim_root_divs(last_block_hash); - // --- 6. Populate root coldkey maps. + // --- 8. Populate root coldkey maps. Self::populate_root_coldkey_staking_maps(); // Return ok. @@ -261,4 +266,24 @@ impl Pallet { return next_value.saturating_to_num::().into(); } } + + pub fn update_moving_prices() { + let subnets_to_emit_to: Vec = + Self::get_subnets_to_emit_to(&Self::get_all_subnet_netuids()); + // Only update price EMA for subnets that we emit to. + for netuid_i in subnets_to_emit_to.iter() { + // Update moving prices after using them above. + Self::update_moving_price(*netuid_i); + } + } + + pub fn reveal_crv3_commits() { + let netuids: Vec = Self::get_all_subnet_netuids(); + for netuid in netuids.into_iter().filter(|netuid| *netuid != NetUid::ROOT) { + // Reveal matured weights. + if let Err(e) = Self::reveal_crv3_commits_for_subnet(netuid) { + log::warn!("Failed to reveal commits for subnet {netuid} due to error: {e:?}"); + }; + } + } } diff --git a/pallets/subtensor/src/coinbase/reveal_commits.rs b/pallets/subtensor/src/coinbase/reveal_commits.rs index 889c41d96a..6fdafa76da 100644 --- a/pallets/subtensor/src/coinbase/reveal_commits.rs +++ b/pallets/subtensor/src/coinbase/reveal_commits.rs @@ -36,7 +36,7 @@ pub struct LegacyWeightsTlockPayload { impl Pallet { /// The `reveal_crv3_commits` function is run at the very beginning of epoch `n`, - pub fn reveal_crv3_commits(netuid: NetUid) -> dispatch::DispatchResult { + pub fn reveal_crv3_commits_for_subnet(netuid: NetUid) -> dispatch::DispatchResult { let reveal_period = Self::get_reveal_period(netuid); let cur_block = Self::get_current_block_as_u64(); let cur_epoch = Self::get_epoch_index(netuid, cur_block); diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 642a7f18ac..15275878a5 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -244,6 +244,7 @@ impl Pallet { // --- 5. Remove various network-related storages. NetworkRegisteredAt::::remove(netuid); + let _ = Self::remove_subnet_from_deregistration_priority_queue(netuid); // --- 6. Remove incentive mechanism memory. let _ = Uids::::clear_prefix(netuid, u32::MAX, None); @@ -314,7 +315,8 @@ impl Pallet { // --- 15. Mechanism step / emissions bookkeeping. FirstEmissionBlockNumber::::remove(netuid); - PendingEmission::::remove(netuid); + PendingValidatorEmission::::remove(netuid); + PendingServerEmission::::remove(netuid); PendingRootAlphaDivs::::remove(netuid); PendingOwnerCut::::remove(netuid); BlocksSinceLastStep::::remove(netuid); @@ -348,7 +350,6 @@ impl Pallet { RAORecycledForRegistration::::remove(netuid); MaxRegistrationsPerBlock::::remove(netuid); WeightsVersionKey::::remove(netuid); - PendingRootAlphaDivs::::remove(netuid); // --- 17. Subtoken / feature flags. LiquidAlphaOn::::remove(netuid); @@ -592,6 +593,10 @@ impl Pallet { pub fn get_network_to_prune() -> Option { let current_block: u64 = Self::get_current_block_as_u64(); + if let Some(priority_netuid) = Self::pop_ready_subnet_from_deregistration_priority_queue() { + return Some(priority_netuid); + } + let mut candidate_netuid: Option = None; let mut candidate_price: U96F32 = U96F32::saturating_from_num(u128::MAX); let mut candidate_timestamp: u64 = u64::MAX; @@ -610,8 +615,8 @@ impl Pallet { let price: U96F32 = Self::get_moving_alpha_price(netuid); - // If tie on price, earliest registration wins. - if price < candidate_price + if candidate_netuid.is_none() + || price < candidate_price || (price == candidate_price && registered_at < candidate_timestamp) { candidate_netuid = Some(netuid); diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 53ac3c6ac5..5cb2ea45f8 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -22,98 +22,66 @@ impl Pallet { pub fn run_coinbase(block_emission: U96F32) { // --- 0. Get current block. let current_block: u64 = Self::get_current_block_as_u64(); - log::debug!("Current block: {current_block:?}"); - - // --- 1. Get all netuids (filter out root) + log::debug!( + "Running coinbase for block {current_block:?} with block emission: {block_emission:?}" + ); + // --- 1. Get all subnets (excluding root). let subnets: Vec = Self::get_all_subnet_netuids() .into_iter() .filter(|netuid| *netuid != NetUid::ROOT) .collect(); - log::debug!("All subnet netuids: {subnets:?}"); + log::debug!("All subnets: {subnets:?}"); - // 2. Get subnets to emit to and emissions - let subnet_emissions = Self::get_subnet_block_emissions(&subnets, block_emission); - let subnets_to_emit_to: Vec = subnet_emissions.keys().copied().collect(); + // --- 2. Get subnets to emit to + let subnets_to_emit_to: Vec = Self::get_subnets_to_emit_to(&subnets); + log::debug!("Subnets to emit to: {subnets_to_emit_to:?}"); - // --- 3. Get subnet terms (tao_in, alpha_in, and alpha_out) - // Computation is described in detail in the dtao whitepaper. - let mut tao_in: BTreeMap = BTreeMap::new(); - let mut alpha_in: BTreeMap = BTreeMap::new(); - let mut alpha_out: BTreeMap = BTreeMap::new(); - let mut is_subsidized: BTreeMap = BTreeMap::new(); - // Only calculate for subnets that we are emitting to. + // --- 3. Get emissions for subnets to emit to + let subnet_emissions = + Self::get_subnet_block_emissions(&subnets_to_emit_to, block_emission); + log::debug!("Subnet emissions: {subnet_emissions:?}"); + let root_sell_flag = Self::get_network_root_sell_flag(&subnets_to_emit_to); + log::debug!("Root sell flag: {root_sell_flag:?}"); + + // --- 4. Emit to subnets for this block. + Self::emit_to_subnets(&subnets_to_emit_to, &subnet_emissions, root_sell_flag); + + // --- 5. Drain pending emissions. + let emissions_to_distribute = Self::drain_pending(&subnets, current_block); + + // --- 6. Distribute the emissions to the subnets. + Self::distribute_emissions_to_subnets(&emissions_to_distribute); + } + + pub fn inject_and_maybe_swap( + subnets_to_emit_to: &[NetUid], + tao_in: &BTreeMap, + alpha_in: &BTreeMap, + excess_tao: &BTreeMap, + ) { for netuid_i in subnets_to_emit_to.iter() { - // Get subnet price. - let price_i = T::SwapInterface::current_alpha_price((*netuid_i).into()); - log::debug!("price_i: {price_i:?}"); - // Emission is price over total. - let default_tao_in_i: U96F32 = subnet_emissions - .get(netuid_i) - .copied() - .unwrap_or(asfloat!(0)); - log::debug!("default_tao_in_i: {default_tao_in_i:?}"); - // Get alpha_emission total - let alpha_emission_i: U96F32 = asfloat!( - Self::get_block_emission_for_issuance(Self::get_alpha_issuance(*netuid_i).into()) - .unwrap_or(0) - ); - log::debug!("alpha_emission_i: {alpha_emission_i:?}"); + let tao_in_i: TaoCurrency = + tou64!(*tao_in.get(netuid_i).unwrap_or(&asfloat!(0))).into(); + let alpha_in_i: AlphaCurrency = + tou64!(*alpha_in.get(netuid_i).unwrap_or(&asfloat!(0))).into(); + let tao_to_swap_with: TaoCurrency = + tou64!(excess_tao.get(netuid_i).unwrap_or(&asfloat!(0))).into(); - // Get initial alpha_in - let mut alpha_in_i: U96F32; - let mut tao_in_i: U96F32; - let tao_in_ratio: U96F32 = default_tao_in_i.safe_div_or( - U96F32::saturating_from_num(block_emission), - U96F32::saturating_from_num(0.0), - ); - if price_i < tao_in_ratio { - tao_in_i = price_i.saturating_mul(U96F32::saturating_from_num(block_emission)); - alpha_in_i = block_emission; - let difference_tao: U96F32 = default_tao_in_i.saturating_sub(tao_in_i); - // Difference becomes buy. + T::SwapInterface::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i); + + if tao_to_swap_with > TaoCurrency::ZERO { let buy_swap_result = Self::swap_tao_for_alpha( *netuid_i, - tou64!(difference_tao).into(), + tao_to_swap_with, T::SwapInterface::max_price(), true, ); if let Ok(buy_swap_result_ok) = buy_swap_result { - let bought_alpha = AlphaCurrency::from(buy_swap_result_ok.amount_paid_out); - SubnetAlphaOut::::mutate(*netuid_i, |total| { - *total = total.saturating_sub(bought_alpha); - }); + let bought_alpha: AlphaCurrency = buy_swap_result_ok.amount_paid_out.into(); + Self::recycle_subnet_alpha(*netuid_i, bought_alpha); } - is_subsidized.insert(*netuid_i, true); - } else { - tao_in_i = default_tao_in_i; - alpha_in_i = tao_in_i.safe_div_or(price_i, alpha_emission_i); - is_subsidized.insert(*netuid_i, false); } - log::debug!("alpha_in_i: {alpha_in_i:?}"); - // Get alpha_out. - let mut alpha_out_i = alpha_emission_i; - // Only emit TAO if the subnetwork allows registration. - if !Self::get_network_registration_allowed(*netuid_i) - && !Self::get_network_pow_registration_allowed(*netuid_i) - { - tao_in_i = asfloat!(0.0); - alpha_in_i = asfloat!(0.0); - alpha_out_i = asfloat!(0.0); - } - // Insert values into maps - tao_in.insert(*netuid_i, tao_in_i); - alpha_in.insert(*netuid_i, alpha_in_i); - alpha_out.insert(*netuid_i, alpha_out_i); - } - log::debug!("tao_in: {tao_in:?}"); - log::debug!("alpha_in: {alpha_in:?}"); - log::debug!("alpha_out: {alpha_out:?}"); - - // --- 4. Injection. - // Actually perform the injection of alpha_in, alpha_out and tao_in into the subnet pool. - // This operation changes the pool liquidity each block. - for netuid_i in subnets_to_emit_to.iter() { // Inject Alpha in. let alpha_in_i = AlphaCurrency::from(tou64!(*alpha_in.get(netuid_i).unwrap_or(&asfloat!(0)))); @@ -121,51 +89,94 @@ impl Pallet { SubnetAlphaIn::::mutate(*netuid_i, |total| { *total = total.saturating_add(alpha_in_i); }); - // Injection Alpha out. - let alpha_out_i = - AlphaCurrency::from(tou64!(*alpha_out.get(netuid_i).unwrap_or(&asfloat!(0)))); - SubnetAlphaOutEmission::::insert(*netuid_i, alpha_out_i); - SubnetAlphaOut::::mutate(*netuid_i, |total| { - *total = total.saturating_add(alpha_out_i); - }); + // Inject TAO in. - let tao_in_i: TaoCurrency = + let injected_tao: TaoCurrency = tou64!(*tao_in.get(netuid_i).unwrap_or(&asfloat!(0))).into(); - SubnetTaoInEmission::::insert(*netuid_i, TaoCurrency::from(tao_in_i)); + SubnetTaoInEmission::::insert(*netuid_i, injected_tao); SubnetTAO::::mutate(*netuid_i, |total| { - *total = total.saturating_add(tao_in_i.into()); + *total = total.saturating_add(injected_tao); }); TotalStake::::mutate(|total| { - *total = total.saturating_add(tao_in_i.into()); + *total = total.saturating_add(injected_tao); }); + + // Update total TAO issuance. + let difference_tao = tou64!(*excess_tao.get(netuid_i).unwrap_or(&asfloat!(0))); TotalIssuance::::mutate(|total| { - *total = total.saturating_add(tao_in_i.into()); + *total = total + .saturating_add(injected_tao.into()) + .saturating_add(difference_tao.into()); }); - // Adjust protocol liquidity based on new reserves - T::SwapInterface::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i); } + } - // --- 5. Compute owner cuts and remove them from alpha_out remaining. - // Remove owner cuts here so that we can properly seperate root dividends in the next step. - // Owner cuts are accumulated and then fed to the drain at the end of this func. - let cut_percent: U96F32 = Self::get_float_subnet_owner_cut(); - let mut owner_cuts: BTreeMap = BTreeMap::new(); - for netuid_i in subnets_to_emit_to.iter() { - // Get alpha out. - let alpha_out_i: U96F32 = *alpha_out.get(netuid_i).unwrap_or(&asfloat!(0)); - log::debug!("alpha_out_i: {alpha_out_i:?}"); - // Calculate the owner cut. - let owner_cut_i: U96F32 = alpha_out_i.saturating_mul(cut_percent); - log::debug!("owner_cut_i: {owner_cut_i:?}"); - // Save owner cut. - *owner_cuts.entry(*netuid_i).or_insert(asfloat!(0)) = owner_cut_i; - // Save new alpha_out. - alpha_out.insert(*netuid_i, alpha_out_i.saturating_sub(owner_cut_i)); - // Accumulate the owner cut in pending. - PendingOwnerCut::::mutate(*netuid_i, |total| { - *total = total.saturating_add(tou64!(owner_cut_i).into()); - }); + pub fn get_subnet_terms( + subnet_emissions: &BTreeMap, + ) -> ( + BTreeMap, + BTreeMap, + BTreeMap, + BTreeMap, + ) { + // Computation is described in detail in the dtao whitepaper. + let mut tao_in: BTreeMap = BTreeMap::new(); + let mut alpha_in: BTreeMap = BTreeMap::new(); + let mut alpha_out: BTreeMap = BTreeMap::new(); + let mut excess_tao: BTreeMap = BTreeMap::new(); + + // Only calculate for subnets that we are emitting to. + for (&netuid_i, &tao_emission_i) in subnet_emissions.iter() { + // Get alpha_emission this block. + let alpha_emission_i: U96F32 = asfloat!( + Self::get_block_emission_for_issuance(Self::get_alpha_issuance(netuid_i).into()) + .unwrap_or(0) + ); + log::debug!("alpha_emission_i: {alpha_emission_i:?}"); + + // Get subnet price. + let price_i: U96F32 = T::SwapInterface::current_alpha_price(netuid_i.into()); + log::debug!("price_i: {price_i:?}"); + + let mut tao_in_i: U96F32 = tao_emission_i; + let alpha_out_i: U96F32 = alpha_emission_i; + let mut alpha_in_i: U96F32 = tao_emission_i.safe_div_or(price_i, U96F32::from_num(0.0)); + + if alpha_in_i > alpha_emission_i { + alpha_in_i = alpha_emission_i; + tao_in_i = alpha_in_i.saturating_mul(price_i); + } + + let excess_amount: U96F32 = tao_emission_i.saturating_sub(tao_in_i); + excess_tao.insert(netuid_i, excess_amount); + + // Insert values into maps + tao_in.insert(netuid_i, tao_in_i); + alpha_in.insert(netuid_i, alpha_in_i); + alpha_out.insert(netuid_i, alpha_out_i); } + (tao_in, alpha_in, alpha_out, excess_tao) + } + + pub fn emit_to_subnets( + subnets_to_emit_to: &[NetUid], + subnet_emissions: &BTreeMap, + root_sell_flag: bool, + ) { + // --- 1. Get subnet terms (tao_in, alpha_in, and alpha_out) + // and excess_tao amounts. + let (tao_in, alpha_in, alpha_out, excess_amount) = Self::get_subnet_terms(subnet_emissions); + + log::debug!("tao_in: {tao_in:?}"); + log::debug!("alpha_in: {alpha_in:?}"); + log::debug!("alpha_out: {alpha_out:?}"); + log::debug!("excess_amount: {excess_amount:?}"); + + // --- 2. Inject TAO and ALPHA to pool and swap with excess TAO. + Self::inject_and_maybe_swap(subnets_to_emit_to, &tao_in, &alpha_in, &excess_amount); + + // --- 3. Inject ALPHA for participants. + let cut_percent: U96F32 = Self::get_float_subnet_owner_cut(); // Get total TAO on root. let root_tao: U96F32 = asfloat!(SubnetTAO::::get(NetUid::ROOT)); @@ -174,58 +185,90 @@ impl Pallet { let tao_weight: U96F32 = root_tao.saturating_mul(Self::get_tao_weight()); log::debug!("tao_weight: {tao_weight:?}"); - // --- 6. Seperate out root dividends in alpha and keep them. - // Then accumulate those dividends for later. for netuid_i in subnets_to_emit_to.iter() { - // Get remaining alpha out. - let alpha_out_i: U96F32 = *alpha_out.get(netuid_i).unwrap_or(&asfloat!(0.0)); - log::debug!("alpha_out_i: {alpha_out_i:?}"); - // Get total ALPHA on subnet. + // Get alpha_out for this block. + let mut alpha_out_i: U96F32 = *alpha_out.get(netuid_i).unwrap_or(&asfloat!(0)); + + let alpha_created: AlphaCurrency = AlphaCurrency::from(tou64!(alpha_out_i)); + SubnetAlphaOutEmission::::insert(*netuid_i, alpha_created); + SubnetAlphaOut::::mutate(*netuid_i, |total| { + *total = total.saturating_add(alpha_created); + }); + + // Calculate the owner cut. + let owner_cut_i: U96F32 = alpha_out_i.saturating_mul(cut_percent); + log::debug!("owner_cut_i: {owner_cut_i:?}"); + // Deduct owner cut from alpha_out. + alpha_out_i = alpha_out_i.saturating_sub(owner_cut_i); + // Accumulate the owner cut in pending. + PendingOwnerCut::::mutate(*netuid_i, |total| { + *total = total.saturating_add(tou64!(owner_cut_i).into()); + }); + + // Get ALPHA issuance. let alpha_issuance: U96F32 = asfloat!(Self::get_alpha_issuance(*netuid_i)); log::debug!("alpha_issuance: {alpha_issuance:?}"); + // Get root proportional dividends. let root_proportion: U96F32 = tao_weight .checked_div(tao_weight.saturating_add(alpha_issuance)) .unwrap_or(asfloat!(0.0)); log::debug!("root_proportion: {root_proportion:?}"); - // Get root proportion of alpha_out dividends. + + // Get root alpha from root prop. let root_alpha: U96F32 = root_proportion .saturating_mul(alpha_out_i) // Total alpha emission per block remaining. .saturating_mul(asfloat!(0.5)); // 50% to validators. - // Remove root alpha from alpha_out. log::debug!("root_alpha: {root_alpha:?}"); - // Get pending alpha as original alpha_out - root_alpha. - let pending_alpha: U96F32 = alpha_out_i.saturating_sub(root_alpha); - log::debug!("pending_alpha: {pending_alpha:?}"); - let subsidized: bool = *is_subsidized.get(netuid_i).unwrap_or(&false); - if !subsidized { + // Get pending server alpha, which is the miner cut of the alpha out. + // Currently miner cut is 50% of the alpha out. + let pending_server_alpha = alpha_out_i.saturating_mul(asfloat!(0.5)); + log::debug!("pending_server_alpha: {pending_server_alpha:?}"); + // The total validator alpha is the remaining alpha out minus the server alpha. + let total_validator_alpha = alpha_out_i.saturating_sub(pending_server_alpha); + log::debug!("total_validator_alpha: {total_validator_alpha:?}"); + // The alpha validators don't get the root alpha. + let pending_validator_alpha = total_validator_alpha.saturating_sub(root_alpha); + log::debug!("pending_validator_alpha: {pending_validator_alpha:?}"); + + // Accumulate the server alpha emission. + PendingServerEmission::::mutate(*netuid_i, |total| { + *total = total.saturating_add(tou64!(pending_server_alpha).into()); + }); + // Accumulate the validator alpha emission. + PendingValidatorEmission::::mutate(*netuid_i, |total| { + *total = total.saturating_add(tou64!(pending_validator_alpha).into()); + }); + + if root_sell_flag { + // Only accumulate root alpha divs if root sell is allowed. PendingRootAlphaDivs::::mutate(*netuid_i, |total| { *total = total.saturating_add(tou64!(root_alpha).into()); }); + } else { + // If we are not selling the root alpha, we should recycle it. + Self::recycle_subnet_alpha(*netuid_i, AlphaCurrency::from(tou64!(root_alpha))); } - - // Accumulate alpha emission in pending. - PendingEmission::::mutate(*netuid_i, |total| { - *total = total.saturating_add(tou64!(pending_alpha).into()); - }); - } - - // --- 7. Update moving prices after using them in the emission calculation. - // Only update price EMA for subnets that we emit to. - for netuid_i in subnets_to_emit_to.iter() { - // Update moving prices after using them above. - Self::update_moving_price(*netuid_i); } + } - // --- 8. Drain pending emission through the subnet based on tempo. + pub fn drain_pending( + subnets: &[NetUid], + current_block: u64, + ) -> BTreeMap { + // Map of netuid to (pending_server_alpha, pending_validator_alpha, pending_root_alpha, pending_owner_cut). + let mut emissions_to_distribute: BTreeMap< + NetUid, + (AlphaCurrency, AlphaCurrency, AlphaCurrency, AlphaCurrency), + > = BTreeMap::new(); + // --- Drain pending emissions for all subnets hat are at their tempo. // Run the epoch for *all* subnets, even if we don't emit anything. for &netuid in subnets.iter() { - // Reveal matured weights. - if let Err(e) = Self::reveal_crv3_commits(netuid) { - log::warn!("Failed to reveal commits for subnet {netuid} due to error: {e:?}"); - }; - // Pass on subnets that have not reached their tempo. + // Increment blocks since last step. + BlocksSinceLastStep::::mutate(netuid, |total| *total = total.saturating_add(1)); + + // Run the epoch if applicable. if Self::should_run_epoch(netuid, current_block) && Self::is_epoch_input_state_consistent(netuid) { @@ -234,24 +277,66 @@ impl Pallet { LastMechansimStepBlock::::insert(netuid, current_block); // Get and drain the subnet pending emission. - let pending_alpha = PendingEmission::::get(netuid); - PendingEmission::::insert(netuid, AlphaCurrency::ZERO); + let pending_server_alpha = PendingServerEmission::::get(netuid); + PendingServerEmission::::insert(netuid, AlphaCurrency::ZERO); - // Get and drain the subnet pending root alpha divs. + let pending_validator_alpha = PendingValidatorEmission::::get(netuid); + PendingValidatorEmission::::insert(netuid, AlphaCurrency::ZERO); + + // Get and drain the pending Alpha for root divs. let pending_root_alpha = PendingRootAlphaDivs::::get(netuid); PendingRootAlphaDivs::::insert(netuid, AlphaCurrency::ZERO); - // Get owner cut and drain. + // Get and drain the pending owner cut. let owner_cut = PendingOwnerCut::::get(netuid); PendingOwnerCut::::insert(netuid, AlphaCurrency::ZERO); - // Drain pending root alpha divs, alpha emission, and owner cut. - Self::drain_pending_emission(netuid, pending_alpha, pending_root_alpha, owner_cut); - } else { - // Increment - BlocksSinceLastStep::::mutate(netuid, |total| *total = total.saturating_add(1)); + // Save the emissions to distribute. + emissions_to_distribute.insert( + netuid, + ( + pending_server_alpha, + pending_validator_alpha, + pending_root_alpha, + owner_cut, + ), + ); } } + emissions_to_distribute + } + + pub fn distribute_emissions_to_subnets( + emissions_to_distribute: &BTreeMap< + NetUid, + (AlphaCurrency, AlphaCurrency, AlphaCurrency, AlphaCurrency), + >, + ) { + for ( + &netuid, + &(pending_server_alpha, pending_validator_alpha, pending_root_alpha, pending_owner_cut), + ) in emissions_to_distribute.iter() + { + // Distribute the emission to the subnet. + Self::distribute_emission( + netuid, + pending_server_alpha, + pending_validator_alpha, + pending_root_alpha, + pending_owner_cut, + ); + } + } + + pub fn get_network_root_sell_flag(subnets_to_emit_to: &[NetUid]) -> bool { + let total_ema_price: U96F32 = subnets_to_emit_to + .iter() + .map(|netuid| Self::get_moving_alpha_price(*netuid)) + .sum(); + + // If the total EMA price is less than or equal to 1 + // then we WILL NOT root sell. + total_ema_price > U96F32::saturating_from_num(1) } pub fn calculate_dividends_and_incentives( @@ -482,6 +567,7 @@ impl Pallet { let destination = maybe_dest.clone().unwrap_or(hotkey.clone()); if let Some(dest) = maybe_dest { + log::debug!("incentives: auto staking {incentive:?} to {dest:?}"); Self::deposit_event(Event::::AutoStakeAdded { netuid, destination: dest, @@ -490,6 +576,7 @@ impl Pallet { incentive, }); } + Self::increase_stake_for_hotkey_and_coldkey_on_subnet( &destination, &owner, @@ -600,21 +687,25 @@ impl Pallet { (incentives, (alpha_dividends, root_alpha_dividends)) } - pub fn drain_pending_emission( + pub fn distribute_emission( netuid: NetUid, - pending_alpha: AlphaCurrency, + pending_server_alpha: AlphaCurrency, + pending_validator_alpha: AlphaCurrency, pending_root_alpha: AlphaCurrency, - owner_cut: AlphaCurrency, + pending_owner_cut: AlphaCurrency, ) { log::debug!( - "Draining pending alpha emission for netuid {netuid:?}, pending_alpha: {pending_alpha:?}, pending_root_alpha: {pending_root_alpha:?}, owner_cut: {owner_cut:?}" + "Draining pending alpha emission for netuid {netuid:?}, pending_server_alpha: {pending_server_alpha:?}, pending_validator_alpha: {pending_validator_alpha:?}, pending_root_alpha: {pending_root_alpha:?}, pending_owner_cut: {pending_owner_cut:?}" ); let tao_weight = Self::get_tao_weight(); + let total_alpha_minus_owner_cut = pending_server_alpha + .saturating_add(pending_validator_alpha) + .saturating_add(pending_root_alpha); - // Run the epoch. + // Run the epoch, using the alpha going to both the servers and the validators. let hotkey_emission: Vec<(T::AccountId, AlphaCurrency, AlphaCurrency)> = - Self::epoch_with_mechanisms(netuid, pending_alpha.saturating_add(pending_root_alpha)); + Self::epoch_with_mechanisms(netuid, total_alpha_minus_owner_cut); log::debug!("hotkey_emission: {hotkey_emission:?}"); // Compute the pending validator alpha. @@ -629,21 +720,20 @@ impl Pallet { }); log::debug!("incentive_sum: {incentive_sum:?}"); - let pending_validator_alpha = if !incentive_sum.is_zero() { - pending_alpha - .saturating_add(pending_root_alpha) - .saturating_div(2.into()) - .saturating_sub(pending_root_alpha) + let validator_alpha = if !incentive_sum.is_zero() { + pending_validator_alpha } else { - // If the incentive is 0, then Validators get 100% of the alpha. - pending_alpha + // If the incentive is 0, then Alpha Validators get both the server and validator alpha. + pending_validator_alpha.saturating_add(pending_server_alpha) }; + let root_alpha = pending_root_alpha; + let owner_cut = pending_owner_cut; let (incentives, (alpha_dividends, root_alpha_dividends)) = Self::calculate_dividend_and_incentive_distribution( netuid, - pending_root_alpha, - pending_validator_alpha, + root_alpha, + validator_alpha, hotkey_emission, tao_weight, ); diff --git a/pallets/subtensor/src/coinbase/subnet_emissions.rs b/pallets/subtensor/src/coinbase/subnet_emissions.rs index 3677b96f97..1a5a8ec402 100644 --- a/pallets/subtensor/src/coinbase/subnet_emissions.rs +++ b/pallets/subtensor/src/coinbase/subnet_emissions.rs @@ -1,26 +1,34 @@ use super::*; -use crate::alloc::borrow::ToOwned; use alloc::collections::BTreeMap; use safe_math::FixedExt; use substrate_fixed::transcendental::{exp, ln}; use substrate_fixed::types::{I32F32, I64F64, U64F64, U96F32}; +use subtensor_swap_interface::SwapHandler; impl Pallet { - pub fn get_subnet_block_emissions( - subnets: &[NetUid], - block_emission: U96F32, - ) -> BTreeMap { + pub fn get_subnets_to_emit_to(subnets: &[NetUid]) -> Vec { + // Filter out root subnet. // Filter out subnets with no first emission block number. - let subnets_to_emit_to: Vec = subnets - .to_owned() - .clone() - .into_iter() + subnets + .iter() + .filter(|netuid| !netuid.is_root()) .filter(|netuid| FirstEmissionBlockNumber::::get(*netuid).is_some()) - .collect(); - log::debug!("Subnets to emit to: {subnets_to_emit_to:?}"); + .filter(|netuid| SubtokenEnabled::::get(*netuid)) + .filter(|&netuid| { + // Only emit TAO if the subnetwork allows registration. + Self::get_network_registration_allowed(*netuid) + || Self::get_network_pow_registration_allowed(*netuid) + }) + .copied() + .collect() + } + pub fn get_subnet_block_emissions( + subnets_to_emit_to: &[NetUid], + block_emission: U96F32, + ) -> BTreeMap { // Get subnet TAO emissions. - let shares = Self::get_shares(&subnets_to_emit_to); + let shares = Self::get_shares(subnets_to_emit_to); log::debug!("Subnet emission shares = {shares:?}"); shares @@ -50,6 +58,7 @@ impl Pallet { // Update SubnetEmaTaoFlow if needed and return its value for // the current block + #[allow(dead_code)] fn get_ema_flow(netuid: NetUid) -> I64F64 { let current_block: u64 = Self::get_current_block_as_u64(); @@ -73,8 +82,12 @@ impl Pallet { last_block_ema } } else { - // Initialize EMA flow, set S(current_block) = 0 - let ema_flow = I64F64::saturating_from_num(0); + // Initialize EMA flow, set S(current_block) = min(price, ema_price) * init_factor + let init_factor = I64F64::saturating_from_num(1_000_000_000); + let moving_price = I64F64::saturating_from_num(Self::get_moving_alpha_price(netuid)); + let current_price = + I64F64::saturating_from_num(T::SwapInterface::current_alpha_price(netuid)); + let ema_flow = init_factor.saturating_mul(moving_price.min(current_price)); SubnetEmaTaoFlow::::insert(netuid, (current_block, ema_flow)); ema_flow } @@ -83,6 +96,7 @@ impl Pallet { // Either the minimal EMA flow L = min{Si}, or an artificial // cut off at some higher value A (TaoFlowCutoff) // L = max {A, min{min{S[i], 0}}} + #[allow(dead_code)] fn get_lower_limit(ema_flows: &BTreeMap) -> I64F64 { let zero = I64F64::saturating_from_num(0); let min_flow = ema_flows @@ -174,6 +188,7 @@ impl Pallet { } // Implementation of shares that uses TAO flow + #[allow(dead_code)] fn get_shares_flow(subnets_to_emit_to: &[NetUid]) -> BTreeMap { // Get raw flows let ema_flows = subnets_to_emit_to @@ -206,7 +221,14 @@ impl Pallet { offset_flows } + // Combines ema price method and tao flow method linearly over FlowHalfLife blocks + pub(crate) fn get_shares(subnets_to_emit_to: &[NetUid]) -> BTreeMap { + Self::get_shares_flow(subnets_to_emit_to) + // Self::get_shares_price_ema(subnets_to_emit_to) + } + // DEPRECATED: Implementation of shares that uses EMA prices will be gradually deprecated + #[allow(dead_code)] fn get_shares_price_ema(subnets_to_emit_to: &[NetUid]) -> BTreeMap { // Get sum of alpha moving prices let total_moving_prices = subnets_to_emit_to @@ -233,61 +255,4 @@ impl Pallet { }) .collect::>() } - - // Combines ema price method and tao flow method linearly over FlowHalfLife blocks - pub(crate) fn get_shares(subnets_to_emit_to: &[NetUid]) -> BTreeMap { - let current_block: u64 = Self::get_current_block_as_u64(); - - // Weight of tao flow method - let period = FlowHalfLife::::get(); - let one = U64F64::saturating_from_num(1); - let zero = U64F64::saturating_from_num(0); - let tao_flow_weight = if let Some(start_block) = FlowFirstBlock::::get() { - if (current_block > start_block) && (current_block < start_block.saturating_add(period)) - { - // Combination period in progress - let start_fixed = U64F64::saturating_from_num(start_block); - let current_fixed = U64F64::saturating_from_num(current_block); - let period_fixed = U64F64::saturating_from_num(period); - current_fixed - .saturating_sub(start_fixed) - .safe_div(period_fixed) - } else if current_block >= start_block.saturating_add(period) { - // Over combination period - one - } else { - // Not yet in combination period - zero - } - } else { - zero - }; - - // Get shares for each method as needed - let shares_flow = if tao_flow_weight > zero { - Self::get_shares_flow(subnets_to_emit_to) - } else { - BTreeMap::new() - }; - - let shares_prices = if tao_flow_weight < one { - Self::get_shares_price_ema(subnets_to_emit_to) - } else { - BTreeMap::new() - }; - - // Combine - let mut shares_combined = BTreeMap::new(); - for netuid in subnets_to_emit_to.iter() { - let share_flow = shares_flow.get(netuid).unwrap_or(&zero); - let share_prices = shares_prices.get(netuid).unwrap_or(&zero); - shares_combined.insert( - *netuid, - share_flow.saturating_mul(tao_flow_weight).saturating_add( - share_prices.saturating_mul(one.saturating_sub(tao_flow_weight)), - ), - ); - } - shares_combined - } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 93d082f4a6..eba1b7d48d 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -88,7 +88,6 @@ pub mod pallet { use frame_system::pallet_prelude::*; use pallet_drand::types::RoundNumber; use runtime_common::prod_or_fast; - use safe_math::FixedExt; use sp_core::{ConstU32, H160, H256}; use sp_runtime::traits::{Dispatchable, TrailingZeroInput}; use sp_std::collections::btree_map::BTreeMap; @@ -347,38 +346,39 @@ pub mod pallet { Manual, } - #[pallet::type_value] /// Default minimum root claim amount. /// This is the minimum amount of root claim that can be made. /// Any amount less than this will not be claimed. + #[pallet::type_value] pub fn DefaultMinRootClaimAmount() -> I96F32 { 500_000u64.into() } - #[pallet::type_value] /// Default root claim type. /// This is the type of root claim that will be made. /// This is set by the user. Either swap to TAO or keep as alpha. + #[pallet::type_value] pub fn DefaultRootClaimType() -> RootClaimTypeEnum { RootClaimTypeEnum::default() } - #[pallet::type_value] /// Default number of root claims per claim call. /// Ideally this is calculated using the number of staking coldkey /// and the block time. + #[pallet::type_value] pub fn DefaultNumRootClaim() -> u64 { // once per week (+ spare keys for skipped tries) 5 } - #[pallet::type_value] /// Default value for zero. + #[pallet::type_value] pub fn DefaultZeroU64() -> u64 { 0 } - #[pallet::type_value] + /// Default value for zero. + #[pallet::type_value] pub fn DefaultZeroI64() -> i64 { 0 } @@ -387,92 +387,105 @@ pub mod pallet { pub fn DefaultZeroAlpha() -> AlphaCurrency { AlphaCurrency::ZERO } + /// Default value for Tao currency. #[pallet::type_value] pub fn DefaultZeroTao() -> TaoCurrency { TaoCurrency::ZERO } - #[pallet::type_value] + /// Default value for zero. + #[pallet::type_value] pub fn DefaultZeroU128() -> u128 { 0 } - #[pallet::type_value] + /// Default value for zero. + #[pallet::type_value] pub fn DefaultZeroU16() -> u16 { 0 } - #[pallet::type_value] + /// Default value for false. + #[pallet::type_value] pub fn DefaultFalse() -> bool { false } - #[pallet::type_value] + /// Default value for false. + #[pallet::type_value] pub fn DefaultTrue() -> bool { true } - #[pallet::type_value] + /// Total Rao in circulation. + #[pallet::type_value] pub fn TotalSupply() -> u64 { 21_000_000_000_000_000 } - #[pallet::type_value] + /// Default Delegate Take. + #[pallet::type_value] pub fn DefaultDelegateTake() -> u16 { T::InitialDefaultDelegateTake::get() } - #[pallet::type_value] /// Default childkey take. + #[pallet::type_value] pub fn DefaultChildKeyTake() -> u16 { T::InitialDefaultChildKeyTake::get() } - #[pallet::type_value] + /// Default minimum delegate take. + #[pallet::type_value] pub fn DefaultMinDelegateTake() -> u16 { T::InitialMinDelegateTake::get() } - #[pallet::type_value] /// Default minimum childkey take. + #[pallet::type_value] pub fn DefaultMinChildKeyTake() -> u16 { T::InitialMinChildKeyTake::get() } - #[pallet::type_value] /// Default maximum childkey take. + #[pallet::type_value] pub fn DefaultMaxChildKeyTake() -> u16 { T::InitialMaxChildKeyTake::get() } - #[pallet::type_value] /// Default account take. + #[pallet::type_value] pub fn DefaultAccountTake() -> u64 { 0 } - #[pallet::type_value] + /// Default value for global weight. + #[pallet::type_value] pub fn DefaultTaoWeight() -> u64 { T::InitialTaoWeight::get() } - #[pallet::type_value] + /// Default emission per block. + #[pallet::type_value] pub fn DefaultBlockEmission() -> u64 { 1_000_000_000 } - #[pallet::type_value] + /// Default allowed delegation. + #[pallet::type_value] pub fn DefaultAllowsDelegation() -> bool { false } - #[pallet::type_value] + /// Default total issuance. + #[pallet::type_value] pub fn DefaultTotalIssuance() -> TaoCurrency { T::InitialIssuance::get().into() } - #[pallet::type_value] + /// Default account, derived from zero trailing bytes. + #[pallet::type_value] pub fn DefaultAccount() -> T::AccountId { #[allow(clippy::expect_used)] T::AccountId::decode(&mut TrailingZeroInput::zeroes()) @@ -481,329 +494,392 @@ pub mod pallet { // pub fn DefaultStakeInterval() -> u64 { // 360 // } (DEPRECATED) - #[pallet::type_value] + /// Default account linkage + #[pallet::type_value] pub fn DefaultAccountLinkage() -> Vec<(u64, T::AccountId)> { vec![] } - #[pallet::type_value] + /// Default pending childkeys + #[pallet::type_value] pub fn DefaultPendingChildkeys() -> (Vec<(u64, T::AccountId)>, u64) { (vec![], 0) } - #[pallet::type_value] + /// Default account linkage + #[pallet::type_value] pub fn DefaultProportion() -> u64 { 0 } - #[pallet::type_value] + /// Default accumulated emission for a hotkey + #[pallet::type_value] pub fn DefaultAccumulatedEmission() -> u64 { 0 } - #[pallet::type_value] + /// Default last adjustment block. + #[pallet::type_value] pub fn DefaultLastAdjustmentBlock() -> u64 { 0 } - #[pallet::type_value] + /// Default last adjustment block. + #[pallet::type_value] pub fn DefaultRegistrationsThisBlock() -> u16 { 0 } - #[pallet::type_value] + /// Default EMA price halving blocks + #[pallet::type_value] pub fn DefaultEMAPriceMovingBlocks() -> u64 { T::InitialEmaPriceHalvingPeriod::get() } - #[pallet::type_value] + /// Default registrations this block. + #[pallet::type_value] pub fn DefaultBurn() -> TaoCurrency { T::InitialBurn::get().into() } - #[pallet::type_value] + /// Default burn token. + #[pallet::type_value] pub fn DefaultMinBurn() -> TaoCurrency { T::InitialMinBurn::get().into() } - #[pallet::type_value] + /// Default min burn token. + #[pallet::type_value] pub fn DefaultMaxBurn() -> TaoCurrency { T::InitialMaxBurn::get().into() } - #[pallet::type_value] + /// Default max burn token. + #[pallet::type_value] pub fn DefaultDifficulty() -> u64 { T::InitialDifficulty::get() } - #[pallet::type_value] + /// Default difficulty value. + #[pallet::type_value] pub fn DefaultMinDifficulty() -> u64 { T::InitialMinDifficulty::get() } - #[pallet::type_value] + /// Default min difficulty value. + #[pallet::type_value] pub fn DefaultMaxDifficulty() -> u64 { T::InitialMaxDifficulty::get() } - #[pallet::type_value] + /// Default max difficulty value. + #[pallet::type_value] pub fn DefaultMaxRegistrationsPerBlock() -> u16 { T::InitialMaxRegistrationsPerBlock::get() } - #[pallet::type_value] + /// Default max registrations per block. + #[pallet::type_value] pub fn DefaultRAORecycledForRegistration() -> TaoCurrency { T::InitialRAORecycledForRegistration::get().into() } - #[pallet::type_value] + /// Default number of networks. + #[pallet::type_value] pub fn DefaultN() -> u16 { 0 } - #[pallet::type_value] + /// Default value for hotkeys. + #[pallet::type_value] pub fn DefaultHotkeys() -> Vec { vec![] } - #[pallet::type_value] + /// Default value if network is added. + #[pallet::type_value] pub fn DefaultNeworksAdded() -> bool { false } - #[pallet::type_value] + /// Default value for network member. + #[pallet::type_value] pub fn DefaultIsNetworkMember() -> bool { false } - #[pallet::type_value] + /// Default value for registration allowed. + #[pallet::type_value] pub fn DefaultRegistrationAllowed() -> bool { true } - #[pallet::type_value] + /// Default value for network registered at. + #[pallet::type_value] pub fn DefaultNetworkRegisteredAt() -> u64 { 0 } - #[pallet::type_value] + /// Default value for network immunity period. + #[pallet::type_value] pub fn DefaultNetworkImmunityPeriod() -> u64 { T::InitialNetworkImmunityPeriod::get() } - #[pallet::type_value] + /// Default value for network min lock cost. + #[pallet::type_value] pub fn DefaultNetworkMinLockCost() -> TaoCurrency { T::InitialNetworkMinLockCost::get().into() } - #[pallet::type_value] + /// Default value for network lock reduction interval. + #[pallet::type_value] pub fn DefaultNetworkLockReductionInterval() -> u64 { T::InitialNetworkLockReductionInterval::get() } - #[pallet::type_value] + /// Default value for subnet owner cut. + #[pallet::type_value] pub fn DefaultSubnetOwnerCut() -> u16 { T::InitialSubnetOwnerCut::get() } - #[pallet::type_value] + /// Default value for recycle or burn. + #[pallet::type_value] pub fn DefaultRecycleOrBurn() -> RecycleOrBurnEnum { RecycleOrBurnEnum::Burn // default to burn } - #[pallet::type_value] + /// Default value for network rate limit. + #[pallet::type_value] pub fn DefaultNetworkRateLimit() -> u64 { if cfg!(feature = "pow-faucet") { return 0; } T::InitialNetworkRateLimit::get() } - #[pallet::type_value] + /// Default value for network rate limit. + #[pallet::type_value] pub fn DefaultNetworkRegistrationStartBlock() -> u64 { 0 } - #[pallet::type_value] + /// Default value for weights version key rate limit. /// In units of tempos. + #[pallet::type_value] pub fn DefaultWeightsVersionKeyRateLimit() -> u64 { 5 // 5 tempos } - #[pallet::type_value] + /// Default value for pending emission. + #[pallet::type_value] pub fn DefaultPendingEmission() -> AlphaCurrency { 0.into() } - #[pallet::type_value] + /// Default value for blocks since last step. + #[pallet::type_value] pub fn DefaultBlocksSinceLastStep() -> u64 { 0 } - #[pallet::type_value] + /// Default value for last mechanism step block. + #[pallet::type_value] pub fn DefaultLastMechanismStepBlock() -> u64 { 0 } - #[pallet::type_value] + /// Default value for subnet owner. + #[pallet::type_value] pub fn DefaultSubnetOwner() -> T::AccountId { #[allow(clippy::expect_used)] T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) .expect("trailing zeroes always produce a valid account ID; qed") } - #[pallet::type_value] + /// Default value for subnet locked. + #[pallet::type_value] pub fn DefaultSubnetLocked() -> u64 { 0 } - #[pallet::type_value] + /// Default value for network tempo + #[pallet::type_value] pub fn DefaultTempo() -> u16 { T::InitialTempo::get() } - #[pallet::type_value] + /// Default value for weights set rate limit. + #[pallet::type_value] pub fn DefaultWeightsSetRateLimit() -> u64 { 100 } - #[pallet::type_value] + /// Default block number at registration. + #[pallet::type_value] pub fn DefaultBlockAtRegistration() -> u64 { 0 } - #[pallet::type_value] + /// Default value for rho parameter. + #[pallet::type_value] pub fn DefaultRho() -> u16 { T::InitialRho::get() } - #[pallet::type_value] + /// Default value for alpha sigmoid steepness. + #[pallet::type_value] pub fn DefaultAlphaSigmoidSteepness() -> i16 { T::InitialAlphaSigmoidSteepness::get() } - #[pallet::type_value] + /// Default value for kappa parameter. + #[pallet::type_value] pub fn DefaultKappa() -> u16 { T::InitialKappa::get() } - #[pallet::type_value] + /// Default value for network min allowed UIDs. + #[pallet::type_value] pub fn DefaultMinAllowedUids() -> u16 { T::InitialMinAllowedUids::get() } - #[pallet::type_value] + /// Default maximum allowed UIDs. + #[pallet::type_value] pub fn DefaultMaxAllowedUids() -> u16 { T::InitialMaxAllowedUids::get() } - #[pallet::type_value] + /// -- Rate limit for set max allowed UIDs + #[pallet::type_value] pub fn MaxUidsTrimmingRateLimit() -> u64 { prod_or_fast!(30 * 7200, 1) } - #[pallet::type_value] + /// Default immunity period. + #[pallet::type_value] pub fn DefaultImmunityPeriod() -> u16 { T::InitialImmunityPeriod::get() } - #[pallet::type_value] + /// Default activity cutoff. + #[pallet::type_value] pub fn DefaultActivityCutoff() -> u16 { T::InitialActivityCutoff::get() } - #[pallet::type_value] + /// Default weights version key. + #[pallet::type_value] pub fn DefaultWeightsVersionKey() -> u64 { T::InitialWeightsVersionKey::get() } - #[pallet::type_value] + /// Default minimum allowed weights. + #[pallet::type_value] pub fn DefaultMinAllowedWeights() -> u16 { T::InitialMinAllowedWeights::get() } - #[pallet::type_value] /// Default maximum allowed validators. + #[pallet::type_value] pub fn DefaultMaxAllowedValidators() -> u16 { T::InitialMaxAllowedValidators::get() } - #[pallet::type_value] + /// Default adjustment interval. + #[pallet::type_value] pub fn DefaultAdjustmentInterval() -> u16 { T::InitialAdjustmentInterval::get() } - #[pallet::type_value] + /// Default bonds moving average. + #[pallet::type_value] pub fn DefaultBondsMovingAverage() -> u64 { T::InitialBondsMovingAverage::get() } + /// Default bonds penalty. #[pallet::type_value] pub fn DefaultBondsPenalty() -> u16 { T::InitialBondsPenalty::get() } + /// Default value for bonds reset - will not reset bonds #[pallet::type_value] pub fn DefaultBondsResetOn() -> bool { T::InitialBondsResetOn::get() } + /// Default validator prune length. #[pallet::type_value] pub fn DefaultValidatorPruneLen() -> u64 { T::InitialValidatorPruneLen::get() } - #[pallet::type_value] + /// Default scaling law power. + #[pallet::type_value] pub fn DefaultScalingLawPower() -> u16 { T::InitialScalingLawPower::get() } - #[pallet::type_value] + /// Default target registrations per interval. + #[pallet::type_value] pub fn DefaultTargetRegistrationsPerInterval() -> u16 { T::InitialTargetRegistrationsPerInterval::get() } - #[pallet::type_value] + /// Default adjustment alpha. + #[pallet::type_value] pub fn DefaultAdjustmentAlpha() -> u64 { T::InitialAdjustmentAlpha::get() } - #[pallet::type_value] + /// Default minimum stake for weights. + #[pallet::type_value] pub fn DefaultStakeThreshold() -> u64 { 0 } - #[pallet::type_value] + /// Default Reveal Period Epochs + #[pallet::type_value] pub fn DefaultRevealPeriodEpochs() -> u64 { 1 } - #[pallet::type_value] + /// Value definition for vector of u16. + #[pallet::type_value] pub fn EmptyU16Vec() -> Vec { vec![] } - #[pallet::type_value] + /// Value definition for vector of u64. + #[pallet::type_value] pub fn EmptyU64Vec() -> Vec { vec![] } - #[pallet::type_value] + /// Value definition for vector of bool. + #[pallet::type_value] pub fn EmptyBoolVec() -> Vec { vec![] } - #[pallet::type_value] + /// Value definition for bonds with type vector of (u16, u16). + #[pallet::type_value] pub fn DefaultBonds() -> Vec<(u16, u16)> { vec![] } - #[pallet::type_value] + /// Value definition for weights with vector of (u16, u16). + #[pallet::type_value] pub fn DefaultWeights() -> Vec<(u16, u16)> { vec![] } - #[pallet::type_value] + /// Default value for key with type T::AccountId derived from trailing zeroes. + #[pallet::type_value] pub fn DefaultKey() -> T::AccountId { #[allow(clippy::expect_used)] T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) @@ -812,124 +888,143 @@ pub mod pallet { // pub fn DefaultHotkeyEmissionTempo() -> u64 { // T::InitialHotkeyEmissionTempo::get() // } (DEPRECATED) - #[pallet::type_value] + /// Default value for rate limiting + #[pallet::type_value] pub fn DefaultTxRateLimit() -> u64 { T::InitialTxRateLimit::get() } - #[pallet::type_value] + /// Default value for delegate take rate limiting + #[pallet::type_value] pub fn DefaultTxDelegateTakeRateLimit() -> u64 { T::InitialTxDelegateTakeRateLimit::get() } - #[pallet::type_value] + /// Default value for chidlkey take rate limiting + #[pallet::type_value] pub fn DefaultTxChildKeyTakeRateLimit() -> u64 { T::InitialTxChildKeyTakeRateLimit::get() } - #[pallet::type_value] + /// Default value for last extrinsic block. + #[pallet::type_value] pub fn DefaultLastTxBlock() -> u64 { 0 } - #[pallet::type_value] + /// Default value for serving rate limit. + #[pallet::type_value] pub fn DefaultServingRateLimit() -> u64 { T::InitialServingRateLimit::get() } - #[pallet::type_value] + /// Default value for weight commit/reveal enabled. + #[pallet::type_value] pub fn DefaultCommitRevealWeightsEnabled() -> bool { true } - #[pallet::type_value] + /// Default value for weight commit/reveal version. + #[pallet::type_value] pub fn DefaultCommitRevealWeightsVersion() -> u16 { 4 } - #[pallet::type_value] + /// -- ITEM (switches liquid alpha on) + #[pallet::type_value] pub fn DefaultLiquidAlpha() -> bool { false } - #[pallet::type_value] + /// -- ITEM (switches liquid alpha on) + #[pallet::type_value] pub fn DefaultYuma3() -> bool { false } - #[pallet::type_value] + /// (alpha_low: 0.7, alpha_high: 0.9) + #[pallet::type_value] pub fn DefaultAlphaValues() -> (u16, u16) { (45875, 58982) } - #[pallet::type_value] + /// Default value for coldkey swap schedule duration + #[pallet::type_value] pub fn DefaultColdkeySwapScheduleDuration() -> BlockNumberFor { T::InitialColdkeySwapScheduleDuration::get() } + /// Default value for deregistration priority schedule delay #[pallet::type_value] + pub fn DefaultDeregistrationPriorityScheduleDelay() -> BlockNumberFor { + T::InitialDeregistrationPriorityScheduleDelay::get() + } + /// Default value for coldkey swap reschedule duration + #[pallet::type_value] pub fn DefaultColdkeySwapRescheduleDuration() -> BlockNumberFor { T::InitialColdkeySwapRescheduleDuration::get() } - #[pallet::type_value] /// Default value for applying pending items (e.g. childkeys). + #[pallet::type_value] pub fn DefaultPendingCooldown() -> u64 { prod_or_fast!(7_200, 15) } - #[pallet::type_value] /// Default minimum stake. + #[pallet::type_value] pub fn DefaultMinStake() -> TaoCurrency { 2_000_000.into() } - #[pallet::type_value] /// Default unicode vector for tau symbol. + #[pallet::type_value] pub fn DefaultUnicodeVecU8() -> Vec { b"\xF0\x9D\x9C\x8F".to_vec() // Unicode for tau (𝜏) } - #[pallet::type_value] /// Default value for dissolve network schedule duration + #[pallet::type_value] pub fn DefaultDissolveNetworkScheduleDuration() -> BlockNumberFor { T::InitialDissolveNetworkScheduleDuration::get() } - #[pallet::type_value] /// Default moving alpha for the moving price. + #[pallet::type_value] pub fn DefaultMovingAlpha() -> I96F32 { // Moving average take 30 days to reach 50% of the price // and 3.5 months to reach 90%. I96F32::saturating_from_num(0.000003) } - #[pallet::type_value] + /// Default subnet moving price. + #[pallet::type_value] pub fn DefaultMovingPrice() -> I96F32 { I96F32::saturating_from_num(0.0) } - #[pallet::type_value] /// Default subnet root claimable + #[pallet::type_value] pub fn DefaultRootClaimable() -> BTreeMap { Default::default() } - #[pallet::type_value] + /// Default value for Share Pool variables + #[pallet::type_value] pub fn DefaultSharePoolZero() -> U64F64 { U64F64::saturating_from_num(0) } - #[pallet::type_value] /// Default value for minimum activity cutoff + #[pallet::type_value] pub fn DefaultMinActivityCutoff() -> u16 { 360 } - #[pallet::type_value] /// Default value for coldkey swap scheduled + #[pallet::type_value] pub fn DefaultColdkeySwapScheduled() -> (BlockNumberFor, T::AccountId) { #[allow(clippy::expect_used)] let default_account = T::AccountId::decode(&mut TrailingZeroInput::zeroes()) @@ -937,8 +1032,8 @@ pub mod pallet { (BlockNumberFor::::from(0_u32), default_account) } - #[pallet::type_value] /// Default value for setting subnet owner hotkey rate limit + #[pallet::type_value] pub fn DefaultSetSNOwnerHotkeyRateLimit() -> u64 { 50400 } @@ -949,58 +1044,62 @@ pub mod pallet { None } - #[pallet::type_value] /// Default number of terminal blocks in a tempo during which admin operations are prohibited + #[pallet::type_value] pub fn DefaultAdminFreezeWindow() -> u16 { 10 } - #[pallet::type_value] /// Default number of tempos for owner hyperparameter update rate limit + #[pallet::type_value] pub fn DefaultOwnerHyperparamRateLimit() -> u16 { 2 } - #[pallet::type_value] /// Default value for ck burn, 18%. + #[pallet::type_value] pub fn DefaultCKBurn() -> u64 { 0 } - #[pallet::type_value] /// Default value for subnet limit. + #[pallet::type_value] pub fn DefaultSubnetLimit() -> u16 { 128 } + /// Global minimum activity cutoff value #[pallet::storage] pub type MinActivityCutoff = StorageValue<_, u16, ValueQuery, DefaultMinActivityCutoff>; - #[pallet::storage] /// Global window (in blocks) at the end of each tempo where admin ops are disallowed + #[pallet::storage] pub type AdminFreezeWindow = StorageValue<_, u16, ValueQuery, DefaultAdminFreezeWindow>; - #[pallet::storage] /// Global number of epochs used to rate limit subnet owner hyperparameter updates + #[pallet::storage] pub type OwnerHyperparamRateLimit = StorageValue<_, u16, ValueQuery, DefaultOwnerHyperparamRateLimit>; + /// Duration of coldkey swap schedule before execution #[pallet::storage] pub type ColdkeySwapScheduleDuration = StorageValue<_, BlockNumberFor, ValueQuery, DefaultColdkeySwapScheduleDuration>; + /// Duration of coldkey swap reschedule before execution #[pallet::storage] pub type ColdkeySwapRescheduleDuration = StorageValue<_, BlockNumberFor, ValueQuery, DefaultColdkeySwapRescheduleDuration>; + /// Duration of dissolve network schedule before execution #[pallet::storage] pub type DissolveNetworkScheduleDuration = StorageValue<_, BlockNumberFor, ValueQuery, DefaultDissolveNetworkScheduleDuration>; - #[pallet::storage] /// --- DMap ( netuid, coldkey ) --> blocknumber | last hotkey swap on network. + #[pallet::storage] pub type LastHotkeySwapOnNetuid = StorageDoubleMap< _, Identity, @@ -1012,8 +1111,8 @@ pub mod pallet { DefaultZeroU64, >; - #[pallet::storage] /// Ensures unique IDs for StakeJobs storage map + #[pallet::storage] pub type NextStakeJobId = StorageValue<_, u64, ValueQuery, DefaultZeroU64>; /// ============================ @@ -1028,34 +1127,42 @@ pub mod pallet { /// /// Eventually, Bittensor should migrate to using Holds afterwhich time we will not require this /// separate accounting. - #[pallet::storage] /// --- ITEM --> Global weight - pub type TaoWeight = StorageValue<_, u64, ValueQuery, DefaultTaoWeight>; #[pallet::storage] + pub type TaoWeight = StorageValue<_, u64, ValueQuery, DefaultTaoWeight>; + /// --- ITEM --> CK burn - pub type CKBurn = StorageValue<_, u64, ValueQuery, DefaultCKBurn>; #[pallet::storage] + pub type CKBurn = StorageValue<_, u64, ValueQuery, DefaultCKBurn>; + /// --- ITEM ( default_delegate_take ) - pub type MaxDelegateTake = StorageValue<_, u16, ValueQuery, DefaultDelegateTake>; #[pallet::storage] + pub type MaxDelegateTake = StorageValue<_, u16, ValueQuery, DefaultDelegateTake>; + /// --- ITEM ( min_delegate_take ) - pub type MinDelegateTake = StorageValue<_, u16, ValueQuery, DefaultMinDelegateTake>; #[pallet::storage] + pub type MinDelegateTake = StorageValue<_, u16, ValueQuery, DefaultMinDelegateTake>; + /// --- ITEM ( default_childkey_take ) - pub type MaxChildkeyTake = StorageValue<_, u16, ValueQuery, DefaultMaxChildKeyTake>; #[pallet::storage] + pub type MaxChildkeyTake = StorageValue<_, u16, ValueQuery, DefaultMaxChildKeyTake>; + /// --- ITEM ( min_childkey_take ) + #[pallet::storage] pub type MinChildkeyTake = StorageValue<_, u16, ValueQuery, DefaultMinChildKeyTake>; + + /// MAP ( hot ) --> cold | Returns the controlling coldkey for a hotkey #[pallet::storage] - /// MAP ( hot ) --> cold | Returns the controlling coldkey for a hotkey. pub type Owner = StorageMap<_, Blake2_128Concat, T::AccountId, T::AccountId, ValueQuery, DefaultAccount>; + + /// MAP ( hot ) --> take | Returns the hotkey delegation take. And signals that this key is open for delegation #[pallet::storage] - /// MAP ( hot ) --> take | Returns the hotkey delegation take. And signals that this key is open for delegation. pub type Delegates = StorageMap<_, Blake2_128Concat, T::AccountId, u16, ValueQuery, DefaultDelegateTake>; - #[pallet::storage] + /// DMAP ( hot, netuid ) --> take | Returns the hotkey childkey take for a specific subnet + #[pallet::storage] pub type ChildkeyTake = StorageDoubleMap< _, Blake2_128Concat, @@ -1065,8 +1172,9 @@ pub mod pallet { u16, // Value: take ValueQuery, >; - #[pallet::storage] + /// DMAP ( netuid, parent ) --> (Vec<(proportion,child)>, cool_down_block) + #[pallet::storage] pub type PendingChildKeys = StorageDoubleMap< _, Identity, @@ -1077,8 +1185,9 @@ pub mod pallet { ValueQuery, DefaultPendingChildkeys, >; - #[pallet::storage] + /// DMAP ( parent, netuid ) --> Vec<(proportion,child)> + #[pallet::storage] pub type ChildKeys = StorageDoubleMap< _, Blake2_128Concat, @@ -1089,8 +1198,9 @@ pub mod pallet { ValueQuery, DefaultAccountLinkage, >; - #[pallet::storage] + /// DMAP ( child, netuid ) --> Vec<(proportion,parent)> + #[pallet::storage] pub type ParentKeys = StorageDoubleMap< _, Blake2_128Concat, @@ -1101,7 +1211,9 @@ pub mod pallet { ValueQuery, DefaultAccountLinkage, >; - #[pallet::storage] // --- DMAP ( netuid, hotkey ) --> u64 | Last total dividend this hotkey got on tempo. + + /// --- DMAP ( netuid, hotkey ) --> u64 | Last total dividend this hotkey got on tempo. + #[pallet::storage] pub type AlphaDividendsPerSubnet = StorageDoubleMap< _, Identity, @@ -1116,11 +1228,12 @@ pub mod pallet { /// ================== /// ==== Coinbase ==== /// ================== + /// --- ITEM ( global_block_emission ) #[pallet::storage] - /// --- ITEM ( global_block_emission ) pub type BlockEmission = StorageValue<_, u64, ValueQuery, DefaultBlockEmission>; - #[pallet::storage] + /// --- DMap ( hot, netuid ) --> emission | last hotkey emission on network. + #[pallet::storage] pub type LastHotkeyEmissionOnNetuid = StorageDoubleMap< _, Blake2_128Concat, @@ -1131,7 +1244,6 @@ pub mod pallet { ValueQuery, DefaultZeroAlpha, >; - /// ========================== /// ==== Staking Counters ==== /// ========================== @@ -1144,53 +1256,84 @@ pub mod pallet { /// /// Eventually, Bittensor should migrate to using Holds afterwhich time we will not require this /// separate accounting. - - #[pallet::storage] // --- ITEM ( maximum_number_of_networks ) + /// --- ITEM ( maximum_number_of_networks ) + #[pallet::storage] pub type SubnetLimit = StorageValue<_, u16, ValueQuery, DefaultSubnetLimit>; - #[pallet::storage] // --- ITEM ( total_issuance ) + + /// --- ITEM ( total_issuance ) + #[pallet::storage] pub type TotalIssuance = StorageValue<_, TaoCurrency, ValueQuery, DefaultTotalIssuance>; - #[pallet::storage] // --- ITEM ( total_stake ) - pub type TotalStake = StorageValue<_, TaoCurrency, ValueQuery>; - #[pallet::storage] // --- ITEM ( moving_alpha ) -- subnet moving alpha. + + /// --- ITEM ( total_stake ) + #[pallet::storage] + pub type TotalStake = StorageValue<_, TaoCurrency, ValueQuery, DefaultZeroTao>; + + /// --- ITEM ( moving_alpha ) -- subnet moving alpha. + #[pallet::storage] pub type SubnetMovingAlpha = StorageValue<_, I96F32, ValueQuery, DefaultMovingAlpha>; - #[pallet::storage] // --- MAP ( netuid ) --> moving_price | The subnet moving price. + + /// --- MAP ( netuid ) --> moving_price | The subnet moving price. + #[pallet::storage] pub type SubnetMovingPrice = StorageMap<_, Identity, NetUid, I96F32, ValueQuery, DefaultMovingPrice>; - #[pallet::storage] // --- MAP ( netuid ) --> total_volume | The total amount of TAO bought and sold since the start of the network. + + /// --- MAP ( netuid ) --> total_volume | The total amount of TAO bought and sold since the start of the network. + #[pallet::storage] pub type SubnetVolume = StorageMap<_, Identity, NetUid, u128, ValueQuery, DefaultZeroU128>; - #[pallet::storage] // --- MAP ( netuid ) --> tao_in_subnet | Returns the amount of TAO in the subnet. + + /// --- MAP ( netuid ) --> tao_in_subnet | Returns the amount of TAO in the subnet. + #[pallet::storage] pub type SubnetTAO = StorageMap<_, Identity, NetUid, TaoCurrency, ValueQuery, DefaultZeroTao>; - #[pallet::storage] // --- MAP ( netuid ) --> tao_in_user_subnet | Returns the amount of TAO in the subnet reserve provided by users as liquidity. + + /// --- MAP ( netuid ) --> tao_in_user_subnet | Returns the amount of TAO in the subnet reserve provided by users as liquidity. + #[pallet::storage] pub type SubnetTaoProvided = StorageMap<_, Identity, NetUid, TaoCurrency, ValueQuery, DefaultZeroTao>; - #[pallet::storage] // --- MAP ( netuid ) --> alpha_in_emission | Returns the amount of alph in emission into the pool per block. + + /// --- MAP ( netuid ) --> alpha_in_emission | Returns the amount of alph in emission into the pool per block. + #[pallet::storage] pub type SubnetAlphaInEmission = StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; - #[pallet::storage] // --- MAP ( netuid ) --> alpha_out_emission | Returns the amount of alpha out emission into the network per block. + + /// --- MAP ( netuid ) --> alpha_out_emission | Returns the amount of alpha out emission into the network per block. + #[pallet::storage] pub type SubnetAlphaOutEmission = StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; - #[pallet::storage] // --- MAP ( netuid ) --> tao_in_emission | Returns the amount of tao emitted into this subent on the last block. + + /// --- MAP ( netuid ) --> tao_in_emission | Returns the amount of tao emitted into this subent on the last block. + #[pallet::storage] pub type SubnetTaoInEmission = StorageMap<_, Identity, NetUid, TaoCurrency, ValueQuery, DefaultZeroTao>; - #[pallet::storage] // --- MAP ( netuid ) --> alpha_supply_in_pool | Returns the amount of alpha in the pool. + + /// --- MAP ( netuid ) --> alpha_supply_in_pool | Returns the amount of alpha in the pool. + #[pallet::storage] pub type SubnetAlphaIn = StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; - #[pallet::storage] // --- MAP ( netuid ) --> alpha_supply_user_in_pool | Returns the amount of alpha in the pool provided by users as liquidity. + + /// --- MAP ( netuid ) --> alpha_supply_user_in_pool | Returns the amount of alpha in the pool provided by users as liquidity. + #[pallet::storage] pub type SubnetAlphaInProvided = StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; - #[pallet::storage] + /// --- MAP ( netuid ) --> alpha_supply_in_subnet | Returns the amount of alpha in the subnet. + #[pallet::storage] pub type SubnetAlphaOut = StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; - #[pallet::storage] // --- MAP ( cold ) --> Vec | Maps coldkey to hotkeys that stake to it + + /// --- MAP ( cold ) --> Vec | Maps coldkey to hotkeys that stake to it + #[pallet::storage] pub type StakingHotkeys = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; - #[pallet::storage] // --- MAP ( cold ) --> Vec | Returns the vector of hotkeys controlled by this coldkey. + + /// --- MAP ( cold ) --> Vec | Returns the vector of hotkeys controlled by this coldkey. + #[pallet::storage] pub type OwnedHotkeys = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; - #[pallet::storage] // --- DMAP ( cold, netuid )--> hot | Returns the hotkey a coldkey will autostake to with mining rewards. + + /// --- DMAP ( cold, netuid )--> hot | Returns the hotkey a coldkey will autostake to with mining rewards. + #[pallet::storage] pub type AutoStakeDestination = StorageDoubleMap< _, Blake2_128Concat, @@ -1200,7 +1343,9 @@ pub mod pallet { T::AccountId, OptionQuery, >; - #[pallet::storage] // --- DMAP ( hot, netuid )--> Vec | Returns a list of coldkeys that are autostaking to a hotkey. + + /// --- DMAP ( hot, netuid )--> Vec | Returns a list of coldkeys that are autostaking to a hotkey + #[pallet::storage] pub type AutoStakeDestinationColdkeys = StorageDoubleMap< _, Blake2_128Concat, @@ -1211,7 +1356,8 @@ pub mod pallet { ValueQuery, >; - #[pallet::storage] // --- DMAP ( cold ) --> (block_expected, new_coldkey) | Maps coldkey to the block to swap at and new coldkey. + /// --- DMAP ( cold ) --> (block_expected, new_coldkey), Maps coldkey to the block to swap at and new coldkey. + #[pallet::storage] pub type ColdkeySwapScheduled = StorageMap< _, Blake2_128Concat, @@ -1221,7 +1367,8 @@ pub mod pallet { DefaultColdkeySwapScheduled, >; - #[pallet::storage] // --- DMAP ( hot, netuid ) --> alpha | Returns the total amount of alpha a hotkey owns. + /// --- DMAP ( hot, netuid ) --> alpha | Returns the total amount of alpha a hotkey owns. + #[pallet::storage] pub type TotalHotkeyAlpha = StorageDoubleMap< _, Blake2_128Concat, @@ -1232,7 +1379,9 @@ pub mod pallet { ValueQuery, DefaultZeroAlpha, >; - #[pallet::storage] // --- DMAP ( hot, netuid ) --> alpha | Returns the total amount of alpha a hotkey owned in the last epoch. + + /// --- DMAP ( hot, netuid ) --> alpha | Returns the total amount of alpha a hotkey owned in the last epoch. + #[pallet::storage] pub type TotalHotkeyAlphaLastEpoch = StorageDoubleMap< _, Blake2_128Concat, @@ -1243,8 +1392,9 @@ pub mod pallet { ValueQuery, DefaultZeroAlpha, >; - #[pallet::storage] + /// DMAP ( hot, netuid ) --> total_alpha_shares | Returns the number of alpha shares for a hotkey on a subnet. + #[pallet::storage] pub type TotalHotkeyShares = StorageDoubleMap< _, Blake2_128Concat, @@ -1255,7 +1405,9 @@ pub mod pallet { ValueQuery, DefaultSharePoolZero, >; - #[pallet::storage] // --- NMAP ( hot, cold, netuid ) --> alpha | Returns the alpha shares for a hotkey, coldkey, netuid triplet. + + /// --- NMAP ( hot, cold, netuid ) --> alpha | Returns the alpha shares for a hotkey, coldkey, netuid triplet. + #[pallet::storage] pub type Alpha = StorageNMap< _, ( @@ -1267,22 +1419,28 @@ pub mod pallet { ValueQuery, >; - #[pallet::storage] // Contains last Alpha storage map key to iterate (check first) + /// Contains last Alpha storage map key to iterate (check first) + #[pallet::storage] pub type AlphaMapLastKey = StorageValue<_, Option>, ValueQuery, DefaultAlphaIterationLastKey>; - #[pallet::storage] // --- MAP ( netuid ) --> token_symbol | Returns the token symbol for a subnet. + /// --- MAP ( netuid ) --> token_symbol | Returns the token symbol for a subnet. + #[pallet::storage] pub type TokenSymbol = StorageMap<_, Identity, NetUid, Vec, ValueQuery, DefaultUnicodeVecU8>; - #[pallet::storage] // --- MAP ( netuid ) --> subnet_tao_flow | Returns the TAO inflow-outflow balance. + /// --- MAP ( netuid ) --> subnet_tao_flow | Returns the TAO inflow-outflow balance. + #[pallet::storage] pub type SubnetTaoFlow = StorageMap<_, Identity, NetUid, i64, ValueQuery, DefaultZeroI64>; - #[pallet::storage] // --- MAP ( netuid ) --> subnet_ema_tao_flow | Returns the EMA of TAO inflow-outflow balance. + + /// --- MAP ( netuid ) --> subnet_ema_tao_flow | Returns the EMA of TAO inflow-outflow balance. + #[pallet::storage] pub type SubnetEmaTaoFlow = StorageMap<_, Identity, NetUid, (u64, I64F64), OptionQuery>; - #[pallet::type_value] + /// Default value for flow cutoff. + #[pallet::type_value] pub fn DefaultFlowCutoff() -> I64F64 { I64F64::saturating_from_num(0) } @@ -1292,7 +1450,7 @@ pub mod pallet { #[pallet::type_value] /// Default value for flow normalization exponent. pub fn DefaultFlowNormExponent() -> U64F64 { - U64F64::saturating_from_num(15).safe_div(U64F64::saturating_from_num(10)) + U64F64::saturating_from_num(1) } #[pallet::storage] /// --- ITEM --> Flow Normalization Exponent (p) @@ -1302,7 +1460,7 @@ pub mod pallet { /// Default value for flow EMA smoothing. pub fn DefaultFlowEmaSmoothingFactor() -> u64 { // Example values: - // half-life factor value i64 normalized + // half-life factor value i64 normalized (x 2^63) // 216000 (1 month) --> 0.000003209009576 ( 29_597_889_189_277) // 50400 (1 week) --> 0.000013752825678 (126_847_427_788_335) 29_597_889_189_277 @@ -1316,79 +1474,91 @@ pub mod pallet { /// --- ITEM --> Flow EMA smoothing factor (flow alpha), u64 normalized pub type FlowEmaSmoothingFactor = StorageValue<_, u64, ValueQuery, DefaultFlowEmaSmoothingFactor>; - #[pallet::storage] - /// --- ITEM --> Block when TAO flow calculation starts(ed) - pub type FlowFirstBlock = StorageValue<_, u64, OptionQuery>; /// ============================ /// ==== Global Parameters ===== /// ============================ - #[pallet::storage] /// --- StorageItem Global Used Work. - pub type UsedWork = StorageMap<_, Identity, Vec, u64, ValueQuery>; #[pallet::storage] + pub type UsedWork = StorageMap<_, Identity, Vec, u64, ValueQuery>; + /// --- ITEM( global_max_registrations_per_block ) + #[pallet::storage] pub type MaxRegistrationsPerBlock = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultMaxRegistrationsPerBlock>; - #[pallet::storage] + /// --- ITEM( total_number_of_existing_networks ) - pub type TotalNetworks = StorageValue<_, u16, ValueQuery>; #[pallet::storage] + pub type TotalNetworks = StorageValue<_, u16, ValueQuery>; + /// ITEM( network_immunity_period ) + #[pallet::storage] pub type NetworkImmunityPeriod = StorageValue<_, u64, ValueQuery, DefaultNetworkImmunityPeriod>; - #[pallet::storage] + /// ITEM( min_network_lock_cost ) + #[pallet::storage] pub type NetworkMinLockCost = StorageValue<_, TaoCurrency, ValueQuery, DefaultNetworkMinLockCost>; - #[pallet::storage] + /// ITEM( last_network_lock_cost ) + #[pallet::storage] pub type NetworkLastLockCost = StorageValue<_, TaoCurrency, ValueQuery, DefaultNetworkMinLockCost>; - #[pallet::storage] + /// ITEM( network_lock_reduction_interval ) + #[pallet::storage] pub type NetworkLockReductionInterval = StorageValue<_, u64, ValueQuery, DefaultNetworkLockReductionInterval>; - #[pallet::storage] + /// ITEM( subnet_owner_cut ) - pub type SubnetOwnerCut = StorageValue<_, u16, ValueQuery, DefaultSubnetOwnerCut>; #[pallet::storage] + pub type SubnetOwnerCut = StorageValue<_, u16, ValueQuery, DefaultSubnetOwnerCut>; + /// ITEM( network_rate_limit ) - pub type NetworkRateLimit = StorageValue<_, u64, ValueQuery, DefaultNetworkRateLimit>; #[pallet::storage] + pub type NetworkRateLimit = StorageValue<_, u64, ValueQuery, DefaultNetworkRateLimit>; + /// --- ITEM( nominator_min_required_stake ) --- Factor of DefaultMinStake in per-mill format. - pub type NominatorMinRequiredStake = StorageValue<_, u64, ValueQuery, DefaultZeroU64>; #[pallet::storage] + pub type NominatorMinRequiredStake = StorageValue<_, u64, ValueQuery, DefaultZeroU64>; + /// ITEM( weights_version_key_rate_limit ) --- Rate limit in tempos. + #[pallet::storage] pub type WeightsVersionKeyRateLimit = StorageValue<_, u64, ValueQuery, DefaultWeightsVersionKeyRateLimit>; /// ============================ /// ==== Rate Limiting ===== /// ============================ - - #[pallet::storage] /// --- MAP ( RateLimitKey ) --> Block number in which the last rate limited operation occured + #[pallet::storage] pub type LastRateLimitedBlock = StorageMap<_, Identity, RateLimitKey, u64, ValueQuery, DefaultZeroU64>; /// ============================ /// ==== Subnet Locks ===== /// ============================ - #[pallet::storage] // --- MAP ( netuid ) --> transfer_toggle + /// --- MAP ( netuid ) --> transfer_toggle + #[pallet::storage] pub type TransferToggle = StorageMap<_, Identity, NetUid, bool, ValueQuery, DefaultTrue>; - #[pallet::storage] // --- MAP ( netuid ) --> total_subnet_locked + + /// --- MAP ( netuid ) --> total_subnet_locked + #[pallet::storage] pub type SubnetLocked = StorageMap<_, Identity, NetUid, TaoCurrency, ValueQuery, DefaultZeroTao>; - #[pallet::storage] // --- MAP ( netuid ) --> largest_locked + + /// --- MAP ( netuid ) --> largest_locked + #[pallet::storage] pub type LargestLocked = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultZeroU64>; /// ================= /// ==== Tempos ===== /// ================= - #[pallet::storage] // --- MAP ( netuid ) --> tempo + /// --- MAP ( netuid ) --> tempo + #[pallet::storage] pub type Tempo = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultTempo>; /// ============================ @@ -1398,19 +1568,23 @@ pub mod pallet { #[pallet::storage] pub type FirstEmissionBlockNumber = StorageMap<_, Identity, NetUid, u64, OptionQuery>; + /// --- MAP ( netuid ) --> subnet mechanism #[pallet::storage] pub type SubnetMechanism = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultZeroU16>; - #[pallet::storage] + /// --- MAP ( netuid ) --> subnetwork_n (Number of UIDs in the network). - pub type SubnetworkN = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultN>; #[pallet::storage] + pub type SubnetworkN = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultN>; + /// --- MAP ( netuid ) --> network_is_added + #[pallet::storage] pub type NetworksAdded = StorageMap<_, Identity, NetUid, bool, ValueQuery, DefaultNeworksAdded>; - #[pallet::storage] + /// --- DMAP ( hotkey, netuid ) --> bool + #[pallet::storage] pub type IsNetworkMember = StorageDoubleMap< _, Blake2_128Concat, @@ -1421,90 +1595,136 @@ pub mod pallet { ValueQuery, DefaultIsNetworkMember, >; - #[pallet::storage] + /// --- MAP ( netuid ) --> network_registration_allowed + #[pallet::storage] pub type NetworkRegistrationAllowed = StorageMap<_, Identity, NetUid, bool, ValueQuery, DefaultRegistrationAllowed>; - #[pallet::storage] + /// --- MAP ( netuid ) --> network_pow_allowed + #[pallet::storage] pub type NetworkPowRegistrationAllowed = StorageMap<_, Identity, NetUid, bool, ValueQuery, DefaultRegistrationAllowed>; - #[pallet::storage] + /// --- MAP ( netuid ) --> block_created + #[pallet::storage] pub type NetworkRegisteredAt = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultNetworkRegisteredAt>; + + /// --- FIFO queue of netuids pending deregistration + #[pallet::storage] + pub type SubnetDeregistrationPriorityQueue = + StorageValue<_, sp_std::vec::Vec, ValueQuery>; + + /// --- MAP ( netuid ) --> scheduled block for enqueuing deregistration priority + #[pallet::storage] + pub type SubnetDeregistrationPrioritySchedule = + StorageMap<_, Identity, NetUid, BlockNumberFor, OptionQuery>; + + /// --- Global delay for scheduled deregistration priority activations + #[pallet::storage] + pub type DeregistrationPriorityScheduleDelay = StorageValue< + _, + BlockNumberFor, + ValueQuery, + DefaultDeregistrationPriorityScheduleDelay, + >; + + /// --- MAP ( netuid ) --> pending_server_emission + #[pallet::storage] + pub type PendingServerEmission = + StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; + + /// --- MAP ( netuid ) --> pending_validator_emission #[pallet::storage] - /// --- MAP ( netuid ) --> pending_emission - pub type PendingEmission = - StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultPendingEmission>; + pub type PendingValidatorEmission = + StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; + /// --- MAP ( netuid ) --> pending_root_alpha_emission #[pallet::storage] pub type PendingRootAlphaDivs = StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; - #[pallet::storage] + /// --- MAP ( netuid ) --> pending_owner_cut + #[pallet::storage] pub type PendingOwnerCut = StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; - #[pallet::storage] + /// --- MAP ( netuid ) --> blocks_since_last_step + #[pallet::storage] pub type BlocksSinceLastStep = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultBlocksSinceLastStep>; - #[pallet::storage] + /// --- MAP ( netuid ) --> last_mechanism_step_block + #[pallet::storage] pub type LastMechansimStepBlock = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultLastMechanismStepBlock>; - #[pallet::storage] + /// --- MAP ( netuid ) --> subnet_owner + #[pallet::storage] pub type SubnetOwner = StorageMap<_, Identity, NetUid, T::AccountId, ValueQuery, DefaultSubnetOwner>; - #[pallet::storage] + /// --- MAP ( netuid ) --> subnet_owner_hotkey + #[pallet::storage] pub type SubnetOwnerHotkey = StorageMap<_, Identity, NetUid, T::AccountId, ValueQuery, DefaultSubnetOwner>; - #[pallet::storage] + /// --- MAP ( netuid ) --> recycle_or_burn + #[pallet::storage] pub type RecycleOrBurn = StorageMap<_, Identity, NetUid, RecycleOrBurnEnum, ValueQuery, DefaultRecycleOrBurn>; - #[pallet::storage] + /// --- MAP ( netuid ) --> serving_rate_limit + #[pallet::storage] pub type ServingRateLimit = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultServingRateLimit>; - #[pallet::storage] + /// --- MAP ( netuid ) --> Rho - pub type Rho = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultRho>; #[pallet::storage] + pub type Rho = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultRho>; + /// --- MAP ( netuid ) --> AlphaSigmoidSteepness + #[pallet::storage] pub type AlphaSigmoidSteepness = StorageMap<_, Identity, NetUid, i16, ValueQuery, DefaultAlphaSigmoidSteepness>; - #[pallet::storage] + /// --- MAP ( netuid ) --> Kappa - pub type Kappa = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultKappa>; #[pallet::storage] + pub type Kappa = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultKappa>; + /// --- MAP ( netuid ) --> registrations_this_interval + #[pallet::storage] pub type RegistrationsThisInterval = StorageMap<_, Identity, NetUid, u16, ValueQuery>; - #[pallet::storage] + /// --- MAP ( netuid ) --> pow_registrations_this_interval + #[pallet::storage] pub type POWRegistrationsThisInterval = StorageMap<_, Identity, NetUid, u16, ValueQuery>; - #[pallet::storage] + /// --- MAP ( netuid ) --> burn_registrations_this_interval + #[pallet::storage] pub type BurnRegistrationsThisInterval = StorageMap<_, Identity, NetUid, u16, ValueQuery>; - #[pallet::storage] + /// --- MAP ( netuid ) --> min_allowed_uids + #[pallet::storage] pub type MinAllowedUids = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultMinAllowedUids>; - #[pallet::storage] + /// --- MAP ( netuid ) --> max_allowed_uids + #[pallet::storage] pub type MaxAllowedUids = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultMaxAllowedUids>; - #[pallet::storage] + /// --- MAP ( netuid ) --> immunity_period + #[pallet::storage] pub type ImmunityPeriod = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultImmunityPeriod>; - #[pallet::storage] + /// --- MAP ( netuid ) --> activity_cutoff + #[pallet::storage] pub type ActivityCutoff = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultActivityCutoff>; #[pallet::type_value] @@ -1512,98 +1732,122 @@ pub mod pallet { pub fn DefaultMaxWeightsLimit() -> u16 { u16::MAX } - #[pallet::storage] + /// --- MAP ( netuid ) --> max_weight_limit + #[pallet::storage] pub type MaxWeightsLimit = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultMaxWeightsLimit>; - #[pallet::storage] + /// --- MAP ( netuid ) --> weights_version_key + #[pallet::storage] pub type WeightsVersionKey = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultWeightsVersionKey>; - #[pallet::storage] + /// --- MAP ( netuid ) --> min_allowed_weights + #[pallet::storage] pub type MinAllowedWeights = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultMinAllowedWeights>; - #[pallet::storage] + /// --- MAP ( netuid ) --> max_allowed_validators + #[pallet::storage] pub type MaxAllowedValidators = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultMaxAllowedValidators>; - #[pallet::storage] + /// --- MAP ( netuid ) --> adjustment_interval + #[pallet::storage] pub type AdjustmentInterval = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultAdjustmentInterval>; - #[pallet::storage] + /// --- MAP ( netuid ) --> bonds_moving_average + #[pallet::storage] pub type BondsMovingAverage = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultBondsMovingAverage>; - #[pallet::storage] + /// --- MAP ( netuid ) --> bonds_penalty + #[pallet::storage] pub type BondsPenalty = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultBondsPenalty>; - #[pallet::storage] + /// --- MAP ( netuid ) --> bonds_reset + #[pallet::storage] pub type BondsResetOn = StorageMap<_, Identity, NetUid, bool, ValueQuery, DefaultBondsResetOn>; + /// --- MAP ( netuid ) --> weights_set_rate_limit #[pallet::storage] pub type WeightsSetRateLimit = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultWeightsSetRateLimit>; - #[pallet::storage] + /// --- MAP ( netuid ) --> validator_prune_len + #[pallet::storage] pub type ValidatorPruneLen = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultValidatorPruneLen>; - #[pallet::storage] + /// --- MAP ( netuid ) --> scaling_law_power + #[pallet::storage] pub type ScalingLawPower = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultScalingLawPower>; - #[pallet::storage] + /// --- MAP ( netuid ) --> target_registrations_this_interval + #[pallet::storage] pub type TargetRegistrationsPerInterval = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultTargetRegistrationsPerInterval>; - #[pallet::storage] + /// --- MAP ( netuid ) --> adjustment_alpha + #[pallet::storage] pub type AdjustmentAlpha = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultAdjustmentAlpha>; - #[pallet::storage] + /// --- MAP ( netuid ) --> commit reveal v2 weights are enabled + #[pallet::storage] pub type CommitRevealWeightsEnabled = StorageMap<_, Identity, NetUid, bool, ValueQuery, DefaultCommitRevealWeightsEnabled>; - #[pallet::storage] + /// --- MAP ( netuid ) --> Burn - pub type Burn = StorageMap<_, Identity, NetUid, TaoCurrency, ValueQuery, DefaultBurn>; #[pallet::storage] + pub type Burn = StorageMap<_, Identity, NetUid, TaoCurrency, ValueQuery, DefaultBurn>; + /// --- MAP ( netuid ) --> Difficulty - pub type Difficulty = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultDifficulty>; #[pallet::storage] + pub type Difficulty = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultDifficulty>; + /// --- MAP ( netuid ) --> MinBurn + #[pallet::storage] pub type MinBurn = StorageMap<_, Identity, NetUid, TaoCurrency, ValueQuery, DefaultMinBurn>; - #[pallet::storage] + /// --- MAP ( netuid ) --> MaxBurn + #[pallet::storage] pub type MaxBurn = StorageMap<_, Identity, NetUid, TaoCurrency, ValueQuery, DefaultMaxBurn>; - #[pallet::storage] + /// --- MAP ( netuid ) --> MinDifficulty + #[pallet::storage] pub type MinDifficulty = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultMinDifficulty>; - #[pallet::storage] + /// --- MAP ( netuid ) --> MaxDifficulty + #[pallet::storage] pub type MaxDifficulty = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultMaxDifficulty>; - #[pallet::storage] + /// --- MAP ( netuid ) --> Block at last adjustment. + #[pallet::storage] pub type LastAdjustmentBlock = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultLastAdjustmentBlock>; - #[pallet::storage] + /// --- MAP ( netuid ) --> Registrations of this Block. + #[pallet::storage] pub type RegistrationsThisBlock = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultRegistrationsThisBlock>; - #[pallet::storage] + /// --- MAP ( netuid ) --> Halving time of average moving price. + #[pallet::storage] pub type EMAPriceHalvingBlocks = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultEMAPriceMovingBlocks>; - #[pallet::storage] + /// --- MAP ( netuid ) --> global_RAO_recycled_for_registration + #[pallet::storage] pub type RAORecycledForRegistration = StorageMap< _, Identity, @@ -1612,66 +1856,79 @@ pub mod pallet { ValueQuery, DefaultRAORecycledForRegistration, >; - #[pallet::storage] + /// --- ITEM ( tx_rate_limit ) - pub type TxRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxRateLimit>; #[pallet::storage] + pub type TxRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxRateLimit>; + /// --- ITEM ( tx_delegate_take_rate_limit ) + #[pallet::storage] pub type TxDelegateTakeRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxDelegateTakeRateLimit>; - #[pallet::storage] + /// --- ITEM ( tx_childkey_take_rate_limit ) + #[pallet::storage] pub type TxChildkeyTakeRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxChildKeyTakeRateLimit>; - #[pallet::storage] + /// --- MAP ( netuid ) --> Whether or not Liquid Alpha is enabled + #[pallet::storage] pub type LiquidAlphaOn = StorageMap<_, Blake2_128Concat, NetUid, bool, ValueQuery, DefaultLiquidAlpha>; - #[pallet::storage] + /// --- MAP ( netuid ) --> Whether or not Yuma3 is enabled + #[pallet::storage] pub type Yuma3On = StorageMap<_, Blake2_128Concat, NetUid, bool, ValueQuery, DefaultYuma3>; - #[pallet::storage] + /// MAP ( netuid ) --> (alpha_low, alpha_high) + #[pallet::storage] pub type AlphaValues = StorageMap<_, Identity, NetUid, (u16, u16), ValueQuery, DefaultAlphaValues>; - #[pallet::storage] + /// --- MAP ( netuid ) --> If subtoken trading enabled + #[pallet::storage] pub type SubtokenEnabled = StorageMap<_, Identity, NetUid, bool, ValueQuery, DefaultFalse>; - #[pallet::type_value] /// Default value for burn keys limit + #[pallet::type_value] pub fn DefaultImmuneOwnerUidsLimit() -> u16 { 1 } - #[pallet::type_value] + /// Maximum value for burn keys limit + #[pallet::type_value] pub fn MaxImmuneOwnerUidsLimit() -> u16 { 10 } - #[pallet::type_value] + /// Minimum value for burn keys limit + #[pallet::type_value] pub fn MinImmuneOwnerUidsLimit() -> u16 { 1 } - #[pallet::storage] + /// --- MAP ( netuid ) --> Burn key limit + #[pallet::storage] pub type ImmuneOwnerUidsLimit = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultImmuneOwnerUidsLimit>; /// ======================================= /// ==== Subnetwork Consensus Storage ==== /// ======================================= - #[pallet::storage] // --- DMAP ( netuid ) --> stake_weight | weight for stake used in YC. + /// --- DMAP ( netuid ) --> stake_weight | weight for stake used in YC. + #[pallet::storage] pub type StakeWeight = StorageMap<_, Identity, NetUid, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] + /// --- DMAP ( netuid, hotkey ) --> uid + #[pallet::storage] pub type Uids = StorageDoubleMap<_, Identity, NetUid, Blake2_128Concat, T::AccountId, u16, OptionQuery>; - #[pallet::storage] + /// --- DMAP ( netuid, uid ) --> hotkey + #[pallet::storage] pub type Keys = StorageDoubleMap< _, Identity, @@ -1682,55 +1939,68 @@ pub mod pallet { ValueQuery, DefaultKey, >; - #[pallet::storage] + /// --- MAP ( netuid ) --> (hotkey, se, ve) + #[pallet::storage] pub type LoadedEmission = StorageMap<_, Identity, NetUid, Vec<(T::AccountId, u64, u64)>, OptionQuery>; - #[pallet::storage] + /// --- MAP ( netuid ) --> active + #[pallet::storage] pub type Active = StorageMap<_, Identity, NetUid, Vec, ValueQuery, EmptyBoolVec>; - #[pallet::storage] + /// --- MAP ( netuid ) --> rank + #[pallet::storage] pub type Rank = StorageMap<_, Identity, NetUid, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] + /// --- MAP ( netuid ) --> trust + #[pallet::storage] pub type Trust = StorageMap<_, Identity, NetUid, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] + /// --- MAP ( netuid ) --> consensus + #[pallet::storage] pub type Consensus = StorageMap<_, Identity, NetUid, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] + /// --- MAP ( netuid ) --> incentive + #[pallet::storage] pub type Incentive = StorageMap<_, Identity, NetUidStorageIndex, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] + /// --- MAP ( netuid ) --> dividends + #[pallet::storage] pub type Dividends = StorageMap<_, Identity, NetUid, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] + /// --- MAP ( netuid ) --> emission - pub type Emission = StorageMap<_, Identity, NetUid, Vec, ValueQuery>; #[pallet::storage] + pub type Emission = StorageMap<_, Identity, NetUid, Vec, ValueQuery>; + /// --- MAP ( netuid ) --> last_update + #[pallet::storage] pub type LastUpdate = StorageMap<_, Identity, NetUidStorageIndex, Vec, ValueQuery, EmptyU64Vec>; - #[pallet::storage] + /// --- MAP ( netuid ) --> validator_trust + #[pallet::storage] pub type ValidatorTrust = StorageMap<_, Identity, NetUid, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] + /// --- MAP ( netuid ) --> pruning_scores + #[pallet::storage] pub type PruningScores = StorageMap<_, Identity, NetUid, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] + /// --- MAP ( netuid ) --> validator_permit + #[pallet::storage] pub type ValidatorPermit = StorageMap<_, Identity, NetUid, Vec, ValueQuery, EmptyBoolVec>; - #[pallet::storage] + /// --- DMAP ( netuid, uid ) --> weights + #[pallet::storage] pub type Weights = StorageDoubleMap< _, Identity, @@ -1741,8 +2011,9 @@ pub mod pallet { ValueQuery, DefaultWeights, >; - #[pallet::storage] + /// --- DMAP ( netuid, uid ) --> bonds + #[pallet::storage] pub type Bonds = StorageDoubleMap< _, Identity, @@ -1753,8 +2024,9 @@ pub mod pallet { ValueQuery, DefaultBonds, >; - #[pallet::storage] + /// --- DMAP ( netuid, uid ) --> block_at_registration + #[pallet::storage] pub type BlockAtRegistration = StorageDoubleMap< _, Identity, @@ -1765,8 +2037,9 @@ pub mod pallet { ValueQuery, DefaultBlockAtRegistration, >; - #[pallet::storage] + /// --- MAP ( netuid, hotkey ) --> axon_info + #[pallet::storage] pub type Axons = StorageDoubleMap< _, Identity, @@ -1776,6 +2049,7 @@ pub mod pallet { AxonInfoOf, OptionQuery, >; + /// --- MAP ( netuid, hotkey ) --> certificate #[pallet::storage] pub type NeuronCertificates = StorageDoubleMap< @@ -1787,8 +2061,9 @@ pub mod pallet { NeuronCertificateOf, OptionQuery, >; - #[pallet::storage] + /// --- MAP ( netuid, hotkey ) --> prometheus_info + #[pallet::storage] pub type Prometheus = StorageDoubleMap< _, Identity, @@ -1798,30 +2073,37 @@ pub mod pallet { PrometheusInfoOf, OptionQuery, >; - #[pallet::storage] // --- MAP ( coldkey ) --> identity. (DEPRECATED for V2) + + /// --- MAP ( coldkey ) --> identity. (DEPRECATED for V2) + #[pallet::storage] pub type Identities = StorageMap<_, Blake2_128Concat, T::AccountId, ChainIdentityOf, OptionQuery>; - #[pallet::storage] // --- MAP ( coldkey ) --> identity + /// --- MAP ( coldkey ) --> identity + #[pallet::storage] pub type IdentitiesV2 = StorageMap<_, Blake2_128Concat, T::AccountId, ChainIdentityOfV2, OptionQuery>; - #[pallet::storage] // --- MAP ( netuid ) --> identity. (DEPRECATED for V2) + /// --- MAP ( netuid ) --> identity. (DEPRECATED for V2) + #[pallet::storage] pub type SubnetIdentities = StorageMap<_, Blake2_128Concat, NetUid, SubnetIdentityOf, OptionQuery>; - #[pallet::storage] // --- MAP ( netuid ) --> identityV2 (DEPRECATED for V3) + /// --- MAP ( netuid ) --> identityV2 (DEPRECATED for V3) + #[pallet::storage] pub type SubnetIdentitiesV2 = StorageMap<_, Blake2_128Concat, NetUid, SubnetIdentityOfV2, OptionQuery>; - #[pallet::storage] // --- MAP ( netuid ) --> SubnetIdentityOfV3 + /// --- MAP ( netuid ) --> SubnetIdentityOfV3 + #[pallet::storage] pub type SubnetIdentitiesV3 = StorageMap<_, Blake2_128Concat, NetUid, SubnetIdentityOfV3, OptionQuery>; /// ================================= /// ==== Axon / Promo Endpoints ===== /// ================================= - #[pallet::storage] // --- NMAP ( hot, netuid, name ) --> last_block | Returns the last block of a transaction for a given key, netuid, and name. + /// --- NMAP ( hot, netuid, name ) --> last_block | Returns the last block of a transaction for a given key, netuid, and name. + #[pallet::storage] pub type TransactionKeyLastBlock = StorageNMap< _, ( @@ -1832,27 +2114,32 @@ pub mod pallet { u64, ValueQuery, >; + + /// --- MAP ( key ) --> last_block #[deprecated] #[pallet::storage] - /// --- MAP ( key ) --> last_block pub type LastTxBlock = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; + + /// --- MAP ( key ) --> last_tx_block_childkey_take #[deprecated] #[pallet::storage] - /// --- MAP ( key ) --> last_tx_block_childkey_take pub type LastTxBlockChildKeyTake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; + + /// --- MAP ( key ) --> last_tx_block_delegate_take #[deprecated] #[pallet::storage] - /// --- MAP ( key ) --> last_tx_block_delegate_take pub type LastTxBlockDelegateTake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; + + /// ITEM( weights_min_stake ) // FIXME: this storage is used interchangably for alpha/tao #[pallet::storage] - /// ITEM( weights_min_stake ) pub type StakeThreshold = StorageValue<_, u64, ValueQuery, DefaultStakeThreshold>; - #[pallet::storage] + /// --- MAP (netuid, who) --> VecDeque<(hash, commit_block, first_reveal_block, last_reveal_block)> | Stores a queue of commits for an account on a given netuid. + #[pallet::storage] pub type WeightCommits = StorageDoubleMap< _, Twox64Concat, @@ -1862,9 +2149,10 @@ pub mod pallet { VecDeque<(H256, u64, u64, u64)>, OptionQuery, >; - #[pallet::storage] + /// MAP (netuid, epoch) → VecDeque<(who, commit_block, ciphertext, reveal_round)> /// Stores a queue of weight commits for an account on a given subnet. + #[pallet::storage] pub type TimelockedWeightCommits = StorageDoubleMap< _, Twox64Concat, @@ -1879,9 +2167,10 @@ pub mod pallet { )>, ValueQuery, >; - #[pallet::storage] + /// MAP (netuid, epoch) → VecDeque<(who, ciphertext, reveal_round)> /// DEPRECATED for CRV3WeightCommitsV2 + #[pallet::storage] pub type CRV3WeightCommits = StorageDoubleMap< _, Twox64Concat, @@ -1895,9 +2184,10 @@ pub mod pallet { )>, ValueQuery, >; - #[pallet::storage] + /// MAP (netuid, epoch) → VecDeque<(who, commit_block, ciphertext, reveal_round)> /// DEPRECATED for TimelockedWeightCommits + #[pallet::storage] pub type CRV3WeightCommitsV2 = StorageDoubleMap< _, Twox64Concat, @@ -1912,13 +2202,14 @@ pub mod pallet { )>, ValueQuery, >; - #[pallet::storage] + /// --- Map (netuid) --> Number of epochs allowed for commit reveal periods + #[pallet::storage] pub type RevealPeriodEpochs = StorageMap<_, Twox64Concat, NetUid, u64, ValueQuery, DefaultRevealPeriodEpochs>; - #[pallet::storage] /// --- Map (coldkey, hotkey) --> u64 the last block at which stake was added/removed. + #[pallet::storage] pub type LastColdkeyHotkeyStakeBlock = StorageDoubleMap< _, Twox64Concat, @@ -1929,9 +2220,9 @@ pub mod pallet { OptionQuery, >; - #[pallet::storage] /// DMAP ( hot, cold, netuid ) --> rate limits for staking operations /// Value contains just a marker: we use this map as a set. + #[pallet::storage] pub type StakingOperationRateLimiter = StorageNMap< _, ( @@ -1962,9 +2253,9 @@ pub mod pallet { pub type RootClaimed = StorageNMap< _, ( + NMapKey, // subnet NMapKey, // hot NMapKey, // cold - NMapKey, // subnet ), u128, ValueQuery, @@ -1993,94 +2284,100 @@ pub mod pallet { /// ============================= /// ==== EVM related storage ==== /// ============================= - #[pallet::storage] /// --- DMAP (netuid, uid) --> (H160, last_block_where_ownership_was_proven) + #[pallet::storage] pub type AssociatedEvmAddress = StorageDoubleMap<_, Twox64Concat, NetUid, Twox64Concat, u16, (H160, u64), OptionQuery>; /// ======================== /// ==== Subnet Leasing ==== /// ======================== - #[pallet::storage] /// --- MAP ( lease_id ) --> subnet lease | The subnet lease for a given lease id. + #[pallet::storage] pub type SubnetLeases = StorageMap<_, Twox64Concat, LeaseId, SubnetLeaseOf, OptionQuery>; - #[pallet::storage] /// --- DMAP ( lease_id, contributor ) --> shares | The shares of a contributor for a given lease. + #[pallet::storage] pub type SubnetLeaseShares = StorageDoubleMap<_, Twox64Concat, LeaseId, Identity, T::AccountId, U64F64, ValueQuery>; + /// --- MAP ( netuid ) --> lease_id | The lease id for a given netuid. #[pallet::storage] - // --- MAP ( netuid ) --> lease_id | The lease id for a given netuid. pub type SubnetUidToLeaseId = StorageMap<_, Twox64Concat, NetUid, LeaseId, OptionQuery>; - #[pallet::storage] /// --- ITEM ( next_lease_id ) | The next lease id. + #[pallet::storage] pub type NextSubnetLeaseId = StorageValue<_, LeaseId, ValueQuery, ConstU32<0>>; - #[pallet::storage] /// --- MAP ( lease_id ) --> accumulated_dividends | The accumulated dividends for a given lease that needs to be distributed. + #[pallet::storage] pub type AccumulatedLeaseDividends = StorageMap<_, Twox64Concat, LeaseId, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; - #[pallet::storage] /// --- ITEM ( CommitRevealWeightsVersion ) + #[pallet::storage] pub type CommitRevealWeightsVersion = StorageValue<_, u16, ValueQuery, DefaultCommitRevealWeightsVersion>; - #[pallet::storage] /// ITEM( NetworkRegistrationStartBlock ) + #[pallet::storage] pub type NetworkRegistrationStartBlock = StorageValue<_, u64, ValueQuery, DefaultNetworkRegistrationStartBlock>; /// ============================ /// ==== Subnet Mechanisms ===== /// ============================ - #[pallet::type_value] /// -- ITEM (Default number of sub-subnets) + #[pallet::type_value] pub fn DefaultMechanismCount() -> MechId { MechId::from(1) } - #[pallet::type_value] + /// -- ITEM (Maximum number of sub-subnets) + #[pallet::type_value] pub fn MaxMechanismCount() -> MechId { MechId::from(2) } - #[pallet::type_value] + /// -- ITEM (Rate limit for mechanism count updates) + #[pallet::type_value] pub fn MechanismCountSetRateLimit() -> u64 { prod_or_fast!(7_200, 1) } - #[pallet::type_value] + /// -- ITEM (Rate limit for mechanism emission distribution updates) + #[pallet::type_value] pub fn MechanismEmissionRateLimit() -> u64 { prod_or_fast!(7_200, 1) } - #[pallet::storage] + /// --- MAP ( netuid ) --> Current number of subnet mechanisms + #[pallet::storage] pub type MechanismCountCurrent = StorageMap<_, Twox64Concat, NetUid, MechId, ValueQuery, DefaultMechanismCount>; - #[pallet::storage] + /// --- MAP ( netuid ) --> Normalized vector of emission split proportion between subnet mechanisms + #[pallet::storage] pub type MechanismEmissionSplit = StorageMap<_, Twox64Concat, NetUid, Vec, OptionQuery>; /// ================== /// ==== Genesis ===== /// ================== - #[pallet::storage] // --- Storage for migration run status + /// --- Storage for migration run status + #[pallet::storage] pub type HasMigrationRun = StorageMap<_, Identity, Vec, bool, ValueQuery>; - #[pallet::type_value] /// Default value for pending childkey cooldown (settable by root, default 0) + #[pallet::type_value] pub fn DefaultPendingChildKeyCooldown() -> u64 { 0 } - #[pallet::storage] /// Storage value for pending childkey cooldown, settable by root. + #[pallet::storage] pub type PendingChildKeyCooldown = StorageValue<_, u64, ValueQuery, DefaultPendingChildKeyCooldown>; diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index a735bde1e1..54d94086bc 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -221,6 +221,9 @@ mod config { /// Coldkey swap schedule duartion. #[pallet::constant] type InitialColdkeySwapScheduleDuration: Get>; + /// Deregistration priority schedule delay. + #[pallet::constant] + type InitialDeregistrationPriorityScheduleDelay: Get>; /// Coldkey swap reschedule duration. #[pallet::constant] type InitialColdkeySwapRescheduleDuration: Get>; diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 3f34d75fa7..3bfbad7fab 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -712,8 +712,8 @@ mod dispatches { /// #[pallet::call_index(2)] #[pallet::weight((Weight::from_parts(340_800_000, 0) - .saturating_add(T::DbWeight::get().reads(24_u64)) - .saturating_add(T::DbWeight::get().writes(15)), DispatchClass::Normal, Pays::Yes))] + .saturating_add(T::DbWeight::get().reads(25_u64)) + .saturating_add(T::DbWeight::get().writes(16_u64)), DispatchClass::Normal, Pays::Yes))] pub fn add_stake( origin: OriginFor, hotkey: T::AccountId, @@ -1411,6 +1411,7 @@ mod dispatches { .map_err(|_| Error::::FailedToSchedule)?; ColdkeySwapScheduled::::insert(&who, (when, new_coldkey.clone())); + Self::cancel_deregistration_priority_schedule_for_owner(&who); // Emit the SwapScheduled event Self::deposit_event(Event::ColdkeySwapScheduled { old_coldkey: who.clone(), @@ -1587,8 +1588,8 @@ mod dispatches { /// - Thrown if key has hit transaction rate limit #[pallet::call_index(84)] #[pallet::weight((Weight::from_parts(358_500_000, 0) - .saturating_add(T::DbWeight::get().reads(39_u64)) - .saturating_add(T::DbWeight::get().writes(24_u64)), DispatchClass::Normal, Pays::Yes))] + .saturating_add(T::DbWeight::get().reads(41_u64)) + .saturating_add(T::DbWeight::get().writes(26_u64)), DispatchClass::Normal, Pays::Yes))] pub fn unstake_all_alpha(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_unstake_all_alpha(origin, hotkey) } @@ -1701,8 +1702,8 @@ mod dispatches { #[pallet::call_index(87)] #[pallet::weight(( Weight::from_parts(351_300_000, 0) - .saturating_add(T::DbWeight::get().reads(35_u64)) - .saturating_add(T::DbWeight::get().writes(22_u64)), + .saturating_add(T::DbWeight::get().reads(37_u64)) + .saturating_add(T::DbWeight::get().writes(24_u64)), DispatchClass::Normal, Pays::Yes ))] @@ -1766,8 +1767,8 @@ mod dispatches { /// #[pallet::call_index(88)] #[pallet::weight((Weight::from_parts(402_900_000, 0) - .saturating_add(T::DbWeight::get().reads(24_u64)) - .saturating_add(T::DbWeight::get().writes(15)), DispatchClass::Normal, Pays::Yes))] + .saturating_add(T::DbWeight::get().reads(25_u64)) + .saturating_add(T::DbWeight::get().writes(16_u64)), DispatchClass::Normal, Pays::Yes))] pub fn add_stake_limit( origin: OriginFor, hotkey: T::AccountId, @@ -1830,8 +1831,8 @@ mod dispatches { /// #[pallet::call_index(89)] #[pallet::weight((Weight::from_parts(377_400_000, 0) - .saturating_add(T::DbWeight::get().reads(28_u64)) - .saturating_add(T::DbWeight::get().writes(14)), DispatchClass::Normal, Pays::Yes))] + .saturating_add(T::DbWeight::get().reads(29_u64)) + .saturating_add(T::DbWeight::get().writes(15_u64)), DispatchClass::Normal, Pays::Yes))] pub fn remove_stake_limit( origin: OriginFor, hotkey: T::AccountId, @@ -1874,8 +1875,8 @@ mod dispatches { #[pallet::call_index(90)] #[pallet::weight(( Weight::from_parts(411_500_000, 0) - .saturating_add(T::DbWeight::get().reads(35_u64)) - .saturating_add(T::DbWeight::get().writes(22_u64)), + .saturating_add(T::DbWeight::get().reads(37_u64)) + .saturating_add(T::DbWeight::get().writes(24_u64)), DispatchClass::Normal, Pays::Yes ))] @@ -2052,8 +2053,8 @@ mod dispatches { /// Without limit_price it remove all the stake similar to `remove_stake` extrinsic #[pallet::call_index(103)] #[pallet::weight((Weight::from_parts(395_300_000, 10142) - .saturating_add(T::DbWeight::get().reads(28_u64)) - .saturating_add(T::DbWeight::get().writes(14_u64)), DispatchClass::Normal, Pays::Yes))] + .saturating_add(T::DbWeight::get().reads(29_u64)) + .saturating_add(T::DbWeight::get().writes(15_u64)), DispatchClass::Normal, Pays::Yes))] pub fn remove_stake_full_limit( origin: T::RuntimeOrigin, hotkey: T::AccountId, @@ -2152,6 +2153,139 @@ mod dispatches { Ok(()) } + /// Schedules the subnet to be appended to the deregistration priority queue. + /// + /// Accessible by the subnet owner or root origin. + /// + /// # Errors + /// * [`Error::SubnetNotExists`] if the subnet does not exist. + /// * [`Error::SubnetDeregistrationPriorityAlreadyScheduled`] if a set operation is already + /// scheduled. + #[pallet::call_index(125)] + #[pallet::weight(( + Weight::from_parts(40_000_000, 0) + .saturating_add(T::DbWeight::get().reads_writes(4, 2)), + DispatchClass::Normal, + Pays::Yes + ))] + pub fn schedule_deregistration_priority( + origin: OriginFor, + netuid: NetUid, + ) -> DispatchResult { + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + Self::ensure_subnet_owner_or_root(origin, netuid)?; + + ensure!( + SubnetDeregistrationPrioritySchedule::::get(netuid).is_none(), + Error::::SubnetDeregistrationPriorityAlreadyScheduled + ); + + let current_block: BlockNumberFor = >::block_number(); + let delay: BlockNumberFor = DeregistrationPriorityScheduleDelay::::get(); + let when: BlockNumberFor = current_block.saturating_add(delay); + + let call = Call::::enqueue_subnet_deregistration { netuid }; + let bound_call = ::Preimages::bound(LocalCallOf::::from(call)) + .map_err(|_| Error::::FailedToSchedule)?; + + T::Scheduler::schedule( + DispatchTime::At(when), + None, + 63, + frame_system::RawOrigin::Root.into(), + bound_call, + ) + .map_err(|_| Error::::FailedToSchedule)?; + + SubnetDeregistrationPrioritySchedule::::insert(netuid, when); + Self::deposit_event(Event::SubnetDeregistrationPriorityScheduleAdded( + netuid, when, + )); + + Ok(()) + } + + /// Enqueues the subnet for deregistration immediately. + /// + /// This call is intended to be used by the scheduler and requires root origin. + #[pallet::call_index(126)] + #[pallet::weight(( + Weight::from_parts(15_000_000, 0) + .saturating_add(T::DbWeight::get().reads_writes(3, 2)), + DispatchClass::Operational, + Pays::Yes + ))] + pub fn enqueue_subnet_deregistration( + origin: OriginFor, + netuid: NetUid, + ) -> DispatchResult { + ensure_root(origin)?; + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + + if SubnetDeregistrationPrioritySchedule::::take(netuid).is_some() { + Self::deposit_event(Event::SubnetDeregistrationPriorityScheduleRemoved(netuid)); + } + + Self::enqueue_subnet_to_deregistration_priority_queue(netuid); + Self::deposit_event(Event::SubnetDeregistrationPriorityQueueAdded(netuid)); + + Ok(()) + } + + /// Cancels a pending deregistration priority schedule. + /// + /// Accessible by the subnet owner or root origin. + #[pallet::call_index(127)] + #[pallet::weight(( + Weight::from_parts(20_000_000, 0) + .saturating_add(T::DbWeight::get().reads_writes(2, 1)), + DispatchClass::Normal, + Pays::Yes + ))] + pub fn cancel_deregistration_priority_schedules( + origin: OriginFor, + netuid: NetUid, + ) -> DispatchResult { + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + Self::ensure_subnet_owner_or_root(origin, netuid)?; + + if SubnetDeregistrationPrioritySchedule::::take(netuid).is_some() { + Self::deposit_event(Event::SubnetDeregistrationPriorityScheduleRemoved(netuid)); + } + + Ok(()) + } + + /// Clears any deregistration priority queue entry and pending schedule for a subnet. + /// + /// Only callable by root origin. + #[pallet::call_index(128)] + #[pallet::weight(( + Weight::from_parts(20_000_000, 0) + .saturating_add(T::DbWeight::get().reads_writes(2, 2)), + DispatchClass::Normal, + Pays::Yes + ))] + pub fn clear_deregistration_priority( + origin: OriginFor, + netuid: NetUid, + ) -> DispatchResult { + ensure_root(origin)?; + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + + let was_queued = Self::remove_subnet_from_deregistration_priority_queue(netuid); + let had_schedule = SubnetDeregistrationPrioritySchedule::::take(netuid).is_some(); + + if was_queued { + Self::deposit_event(Event::SubnetDeregistrationPriorityQueueRemoved(netuid)); + } + if had_schedule { + Self::deposit_event(Event::SubnetDeregistrationPriorityScheduleRemoved(netuid)); + } + + Ok(()) + } + /// ---- Used to commit timelock encrypted commit-reveal weight values to later be revealed. /// /// # Args: diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 5a15330075..d87d0f5938 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -152,6 +152,8 @@ mod errors { TxRateLimitExceeded, /// Swap already scheduled. SwapAlreadyScheduled, + /// Deregistration priority already scheduled. + SubnetDeregistrationPriorityAlreadyScheduled, /// failed to swap coldkey FailedToSchedule, /// New coldkey is hotkey diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index c2931024ee..61bc7d9e6c 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -13,6 +13,16 @@ mod events { NetworkAdded(NetUid, u16), /// a network is removed. NetworkRemoved(NetUid), + /// a subnet's deregistration queue entry was scheduled. + SubnetDeregistrationPriorityScheduleAdded(NetUid, BlockNumberFor), + /// a subnet was enqueued for deregistration. + SubnetDeregistrationPriorityQueueAdded(NetUid), + /// network deregistration queue entry cleared or canceled. + SubnetDeregistrationPriorityQueueRemoved(NetUid), + /// pending deregistration schedule cleared or canceled. + SubnetDeregistrationPriorityScheduleRemoved(NetUid), + /// deregistration priority schedule delay updated. + DeregistrationPriorityScheduleDelaySet(BlockNumberFor), /// stake has been transferred from the a coldkey account onto the hotkey staking account. StakeAdded( T::AccountId, @@ -467,5 +477,15 @@ mod events { /// Claim type root_claim_type: RootClaimTypeEnum, }, + + /// Subnet lease dividends have been distributed. + SubnetLeaseDividendsDistributed { + /// The lease ID + lease_id: LeaseId, + /// The contributor + contributor: T::AccountId, + /// The amount of alpha distributed + alpha: AlphaCurrency, + }, } } diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index a4cdbfbe94..1b7d5fd77e 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -72,9 +72,6 @@ mod hooks { .saturating_add(migrations::migrate_delete_subnet_21::migrate_delete_subnet_21::()) // Storage version v4 -> v5 .saturating_add(migrations::migrate_delete_subnet_3::migrate_delete_subnet_3::()) - // Doesn't check storage version. TODO: Remove after upgrade - // Storage version v5 -> v6 - .saturating_add(migrations::migrate_total_issuance::migrate_total_issuance::(false)) // Populate OwnedHotkeys map for coldkey swap. Doesn't update storage vesion. // Storage version v6 -> v7 .saturating_add(migrations::migrate_populate_owned_hotkeys::migrate_populate_owned::()) @@ -158,10 +155,14 @@ mod hooks { .saturating_add(migrations::migrate_auto_stake_destination::migrate_auto_stake_destination::()) // Migrate Kappa to default (0.5) .saturating_add(migrations::migrate_kappa_map_to_default::migrate_kappa_map_to_default::()) - // Set the first block of tao flow - .saturating_add(migrations::migrate_set_first_tao_flow_block::migrate_set_first_tao_flow_block::()) // Remove obsolete map entries - .saturating_add(migrations::migrate_remove_tao_dividends::migrate_remove_tao_dividends::()); + .saturating_add(migrations::migrate_remove_tao_dividends::migrate_remove_tao_dividends::()) + // Re-init tao flows + .saturating_add(migrations::migrate_init_tao_flow::migrate_init_tao_flow::()) + // Migrate pending emissions + .saturating_add(migrations::migrate_pending_emissions::migrate_pending_emissions::()) + // Reset unactive subnets + .saturating_add(migrations::migrate_reset_unactive_sn::migrate_reset_unactive_sn::()); weight } diff --git a/pallets/subtensor/src/migrations/migrate_init_tao_flow.rs b/pallets/subtensor/src/migrations/migrate_init_tao_flow.rs new file mode 100644 index 0000000000..477410600f --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_init_tao_flow.rs @@ -0,0 +1,40 @@ +use alloc::string::String; + +use frame_support::{traits::Get, weights::Weight}; + +use super::*; + +pub fn migrate_init_tao_flow() -> Weight { + let migration_name = b"migrate_init_tao_flow".to_vec(); + + // Initialize the weight with one read operation. + let mut weight = T::DbWeight::get().reads(1); + + // Check if the migration has already run + if HasMigrationRun::::get(&migration_name) { + log::info!( + "Migration '{:?}' has already run. Skipping.", + String::from_utf8_lossy(&migration_name) + ); + return weight; + } + log::info!( + "Running migration '{}'", + String::from_utf8_lossy(&migration_name) + ); + + let _ = SubnetEmaTaoFlow::::clear(u32::MAX, None); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + // Mark the migration as completed + HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + "Migration '{:?}' completed.", + String::from_utf8_lossy(&migration_name) + ); + + // Return the migration weight. + weight +} diff --git a/pallets/subtensor/src/migrations/migrate_pending_emissions.rs b/pallets/subtensor/src/migrations/migrate_pending_emissions.rs new file mode 100644 index 0000000000..416080f5b2 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_pending_emissions.rs @@ -0,0 +1,73 @@ +use super::*; +use frame_support::{storage_alias, traits::Get, weights::Weight}; +use substrate_fixed::types::U96F32; + +pub mod deprecated_pending_emission_format { + use super::*; + + #[storage_alias] + pub(super) type PendingEmission = + StorageMap, Identity, NetUid, AlphaCurrency, ValueQuery>; +} + +pub fn migrate_pending_emissions() -> Weight { + let migration_name = b"migrate_pending_emissions".to_vec(); + let mut weight: Weight = T::DbWeight::get().reads(1); + + // Skip if already executed + if HasMigrationRun::::get(&migration_name) { + log::info!( + target: "runtime", + "Migration '{}' already run - skipping.", + String::from_utf8_lossy(&migration_name) + ); + return weight; + } + log::info!( + "Running migration '{}'", + String::from_utf8_lossy(&migration_name) + ); + + // Pull from PendingEmission and distribute to PendingValidatorEmission and PendingServerEmission + for (netuid, pending_emission) in + deprecated_pending_emission_format::PendingEmission::::iter() + { + // Split up the pending emission into server and validator emission + // Server emission is pending+root_alpha times the 50% miner cut. + let root_alpha: U96F32 = + U96F32::saturating_from_num(PendingRootAlphaDivs::::get(netuid).to_u64()); + let server_emission_float: U96F32 = U96F32::saturating_from_num(pending_emission.to_u64()) + .saturating_add(root_alpha) + .saturating_div(U96F32::saturating_from_num(2)); + let server_emission: AlphaCurrency = + server_emission_float.saturating_to_num::().into(); + let validator_emission = pending_emission.saturating_sub(server_emission); + + PendingValidatorEmission::::mutate(netuid, |total| { + *total = total.saturating_add(validator_emission) + }); + PendingServerEmission::::mutate(netuid, |total| { + *total = total.saturating_add(server_emission) + }); + + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 2)); + } + + // Kill the map + let removal_result = + deprecated_pending_emission_format::PendingEmission::::clear(u32::MAX, None); + weight = weight.saturating_add( + T::DbWeight::get().reads_writes(removal_result.loops as u64, removal_result.backend as u64), + ); + + // Mark Migration as Completed + HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + "Migration '{:?}' completed successfully.", + String::from_utf8_lossy(&migration_name) + ); + + weight +} diff --git a/pallets/subtensor/src/migrations/migrate_reset_unactive_sn.rs b/pallets/subtensor/src/migrations/migrate_reset_unactive_sn.rs new file mode 100644 index 0000000000..e2e8ddc296 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_reset_unactive_sn.rs @@ -0,0 +1,73 @@ +use super::*; + +pub fn get_unactive_sn_netuids( + pool_initial_alpha: AlphaCurrency, +) -> (Vec, Weight) { + // Loop over all subnets, if the AlphaIssuance is > pool_initial_alpha + // but FirstEmissionBlockNumber is None + // then this subnet should be reset + let mut weight = T::DbWeight::get().reads(1); + let unactive_netuids = Pallet::::get_all_subnet_netuids() + .iter() + .filter(|&netuid| !netuid.is_root()) + .filter(|&netuid| { + let alpha_issuance = Pallet::::get_alpha_issuance(*netuid); + let first_emission_block_number = FirstEmissionBlockNumber::::get(*netuid); + alpha_issuance != pool_initial_alpha && first_emission_block_number.is_none() + }) + .copied() + .collect::>(); + weight = weight + .saturating_add(T::DbWeight::get().reads(unactive_netuids.len().saturating_mul(3) as u64)); + + (unactive_netuids, weight) +} + +pub fn migrate_reset_unactive_sn() -> Weight { + let migration_name = b"migrate_reset_unactive_sn".to_vec(); + let mut weight: Weight = T::DbWeight::get().reads(1); + + // Skip if already executed + if HasMigrationRun::::get(&migration_name) { + log::info!( + target: "runtime", + "Migration '{}' already run - skipping.", + String::from_utf8_lossy(&migration_name) + ); + return weight; + } + log::info!( + "Running migration '{}'", + String::from_utf8_lossy(&migration_name) + ); + + // From init_new_network + let pool_initial_tao: TaoCurrency = Pallet::::get_network_min_lock(); + let pool_initial_alpha: AlphaCurrency = pool_initial_tao.to_u64().into(); + + let (unactive_netuids, w) = get_unactive_sn_netuids::(pool_initial_alpha); + weight = weight.saturating_add(w); + + for netuid in unactive_netuids.iter() { + // Reset the subnet as it shouldn't have any emissions + PendingServerEmission::::remove(*netuid); + PendingValidatorEmission::::remove(*netuid); + PendingRootAlphaDivs::::remove(*netuid); + PendingOwnerCut::::remove(*netuid); + SubnetTaoInEmission::::remove(*netuid); + SubnetAlphaInEmission::::remove(*netuid); + SubnetAlphaOutEmission::::remove(*netuid); + weight = weight.saturating_add(T::DbWeight::get().writes(7)); + } + + // Mark Migration as Completed + HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + "Migration '{:?}' completed successfully.", + String::from_utf8_lossy(&migration_name) + ); + + weight +} diff --git a/pallets/subtensor/src/migrations/migrate_set_first_tao_flow_block.rs b/pallets/subtensor/src/migrations/migrate_set_first_tao_flow_block.rs deleted file mode 100644 index 99c10b99ba..0000000000 --- a/pallets/subtensor/src/migrations/migrate_set_first_tao_flow_block.rs +++ /dev/null @@ -1,38 +0,0 @@ -use super::*; -use crate::HasMigrationRun; -use frame_support::{traits::Get, weights::Weight}; -use scale_info::prelude::string::String; - -pub fn migrate_set_first_tao_flow_block() -> Weight { - let migration_name = b"migrate_set_first_tao_flow_block".to_vec(); - - let mut weight = T::DbWeight::get().reads(1); - if HasMigrationRun::::get(&migration_name) { - log::info!( - "Migration '{:?}' has already run. Skipping.", - String::from_utf8_lossy(&migration_name) - ); - return weight; - } - - log::info!( - "Running migration '{:?}'", - String::from_utf8_lossy(&migration_name) - ); - - // Actual migration - let current_block = Pallet::::get_current_block_as_u64(); - FlowFirstBlock::::set(Some(current_block)); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); - - // Mark Migration as Completed - HasMigrationRun::::insert(&migration_name, true); - weight = weight.saturating_add(T::DbWeight::get().reads(2)); - - log::info!( - "Migration '{:?}' completed successfully.", - String::from_utf8_lossy(&migration_name) - ); - - weight -} diff --git a/pallets/subtensor/src/migrations/migrate_total_issuance.rs b/pallets/subtensor/src/migrations/migrate_total_issuance.rs index bd69c60436..e87337b74e 100644 --- a/pallets/subtensor/src/migrations/migrate_total_issuance.rs +++ b/pallets/subtensor/src/migrations/migrate_total_issuance.rs @@ -51,14 +51,6 @@ pub fn migrate_total_issuance(test: bool) -> Weight { T::DbWeight::get().reads((Owner::::iter().count() as u64).saturating_mul(2)), ); - // Calculate the sum of all locked subnet values - let locked_sum = SubnetLocked::::iter().fold(TaoCurrency::ZERO, |acc, (_, locked)| { - acc.saturating_add(locked) - }); - // Add weight for reading all subnet locked entries - weight = weight - .saturating_add(T::DbWeight::get().reads(SubnetLocked::::iter().count() as u64)); - // Retrieve the total balance sum let total_balance = ::Currency::total_issuance(); // Add weight for reading total issuance @@ -69,8 +61,7 @@ pub fn migrate_total_issuance(test: bool) -> Weight { Ok(total_balance_sum) => { // Compute the total issuance value let total_issuance_value = stake_sum - .saturating_add(total_balance_sum.into()) - .saturating_add(locked_sum.into()); + .saturating_add(total_balance_sum.into()); // Update the total issuance in storage TotalIssuance::::put(total_issuance_value); diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index 2313870244..41c1333a89 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -20,12 +20,14 @@ pub mod migrate_fix_is_network_member; pub mod migrate_fix_root_subnet_tao; pub mod migrate_fix_root_tao_and_alpha_in; pub mod migrate_identities_v2; +pub mod migrate_init_tao_flow; pub mod migrate_init_total_issuance; pub mod migrate_kappa_map_to_default; pub mod migrate_network_immunity_period; pub mod migrate_network_lock_cost_2500; pub mod migrate_network_lock_reduction_interval; pub mod migrate_orphaned_storage_items; +pub mod migrate_pending_emissions; pub mod migrate_populate_owned_hotkeys; pub mod migrate_rao; pub mod migrate_rate_limit_keys; @@ -39,8 +41,8 @@ pub mod migrate_remove_unused_maps_and_values; pub mod migrate_remove_zero_total_hotkey_alpha; pub mod migrate_reset_bonds_moving_average; pub mod migrate_reset_max_burn; +pub mod migrate_reset_unactive_sn; pub mod migrate_set_first_emission_block_number; -pub mod migrate_set_first_tao_flow_block; pub mod migrate_set_min_burn; pub mod migrate_set_min_difficulty; pub mod migrate_set_nominator_min_stake; @@ -54,7 +56,6 @@ pub mod migrate_subnet_symbols; pub mod migrate_subnet_volume; pub mod migrate_to_v1_separate_emission; pub mod migrate_to_v2_fixed_total_stake; -pub mod migrate_total_issuance; pub mod migrate_transfer_ownership_to_foundation; pub mod migrate_upgrade_revealed_commitments; diff --git a/pallets/subtensor/src/rpc_info/dynamic_info.rs b/pallets/subtensor/src/rpc_info/dynamic_info.rs index 3bfbda8676..d4f99176ac 100644 --- a/pallets/subtensor/src/rpc_info/dynamic_info.rs +++ b/pallets/subtensor/src/rpc_info/dynamic_info.rs @@ -62,7 +62,9 @@ impl Pallet { alpha_out_emission: SubnetAlphaOutEmission::::get(netuid).into(), alpha_in_emission: SubnetAlphaInEmission::::get(netuid).into(), tao_in_emission: SubnetTaoInEmission::::get(netuid).into(), - pending_alpha_emission: PendingEmission::::get(netuid).into(), + pending_alpha_emission: PendingValidatorEmission::::get(netuid) + .saturating_add(PendingServerEmission::::get(netuid)) + .into(), pending_root_emission: TaoCurrency::from(0u64).into(), subnet_volume: SubnetVolume::::get(netuid).into(), network_registered_at: NetworkRegisteredAt::::get(netuid).into(), diff --git a/pallets/subtensor/src/rpc_info/metagraph.rs b/pallets/subtensor/src/rpc_info/metagraph.rs index df0c8023b0..ea24657aeb 100644 --- a/pallets/subtensor/src/rpc_info/metagraph.rs +++ b/pallets/subtensor/src/rpc_info/metagraph.rs @@ -694,7 +694,9 @@ impl Pallet { alpha_out_emission: SubnetAlphaOutEmission::::get(netuid).into(), // amount injected in alpha reserves per block alpha_in_emission: SubnetAlphaInEmission::::get(netuid).into(), // amount injected outstanding per block tao_in_emission: SubnetTaoInEmission::::get(netuid).into(), // amount of tao injected per block - pending_alpha_emission: PendingEmission::::get(netuid).into(), // pending alpha to be distributed + pending_alpha_emission: PendingValidatorEmission::::get(netuid) + .saturating_add(PendingServerEmission::::get(netuid)) + .into(), // pending alpha to be distributed pending_root_emission: TaoCurrency::from(0u64).into(), // panding tao for root divs to be distributed subnet_volume: subnet_volume.into(), moving_price: SubnetMovingPrice::::get(netuid), @@ -1000,7 +1002,11 @@ impl Pallet { }, Some(SelectiveMetagraphIndex::PendingAlphaEmission) => SelectiveMetagraph { netuid: netuid.into(), - pending_alpha_emission: Some(PendingEmission::::get(netuid).into()), + pending_alpha_emission: Some( + PendingValidatorEmission::::get(netuid) + .saturating_add(PendingServerEmission::::get(netuid)) + .into(), + ), ..Default::default() }, Some(SelectiveMetagraphIndex::PendingRootEmission) => SelectiveMetagraph { diff --git a/pallets/subtensor/src/staking/claim_root.rs b/pallets/subtensor/src/staking/claim_root.rs index 0ffbeb4b54..7071a1ad55 100644 --- a/pallets/subtensor/src/staking/claim_root.rs +++ b/pallets/subtensor/src/staking/claim_root.rs @@ -2,7 +2,7 @@ use super::*; use frame_support::weights::Weight; use sp_core::Get; use sp_std::collections::btree_set::BTreeSet; -use substrate_fixed::types::{I96F32, U64F64}; +use substrate_fixed::types::I96F32; use subtensor_swap_interface::SwapHandler; impl Pallet { @@ -99,7 +99,7 @@ impl Pallet { // Attain the root claimed to avoid overclaiming. let root_claimed: I96F32 = - I96F32::saturating_from_num(RootClaimed::::get((hotkey, coldkey, netuid))); + I96F32::saturating_from_num(RootClaimed::::get((netuid, hotkey, coldkey))); // Subtract the already claimed alpha. let owed: I96F32 = claimable.saturating_sub(root_claimed); @@ -165,7 +165,7 @@ impl Pallet { netuid, owed_u64.into(), T::SwapInterface::min_price::(), - false, + true, ) { Ok(owed_tao) => owed_tao, Err(err) => { @@ -200,7 +200,7 @@ impl Pallet { }; // Increase root claimed by owed amount. - RootClaimed::::mutate((hotkey, coldkey, netuid), |root_claimed| { + RootClaimed::::mutate((netuid, hotkey, coldkey), |root_claimed| { *root_claimed = root_claimed.saturating_add(owed_u64.into()); }); } @@ -250,7 +250,7 @@ impl Pallet { let root_claimable = RootClaimable::::get(hotkey); for (netuid, claimable_rate) in root_claimable.iter() { // Get current staker root claimed value. - let root_claimed: u128 = RootClaimed::::get((hotkey, coldkey, netuid)); + let root_claimed: u128 = RootClaimed::::get((netuid, hotkey, coldkey)); // Increase root claimed based on the claimable rate. let new_root_claimed = root_claimed.saturating_add( @@ -260,7 +260,7 @@ impl Pallet { ); // Set the new root claimed value. - RootClaimed::::insert((hotkey, coldkey, netuid), new_root_claimed); + RootClaimed::::insert((netuid, hotkey, coldkey), new_root_claimed); } } @@ -277,7 +277,7 @@ impl Pallet { } // Get current staker root claimed value. - let root_claimed: u128 = RootClaimed::::get((hotkey, coldkey, netuid)); + let root_claimed: u128 = RootClaimed::::get((netuid, hotkey, coldkey)); // Decrease root claimed based on the claimable rate. let new_root_claimed = root_claimed.saturating_sub( @@ -287,7 +287,7 @@ impl Pallet { ); // Set the new root_claimed value. - RootClaimed::::insert((hotkey, coldkey, netuid), new_root_claimed); + RootClaimed::::insert((netuid, hotkey, coldkey), new_root_claimed); } } @@ -359,10 +359,10 @@ impl Pallet { old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, ) { - let old_root_claimed = RootClaimed::::get((old_hotkey, old_coldkey, netuid)); - RootClaimed::::remove((old_hotkey, old_coldkey, netuid)); + let old_root_claimed = RootClaimed::::get((netuid, old_hotkey, old_coldkey)); + RootClaimed::::remove((netuid, old_hotkey, old_coldkey)); - RootClaimed::::mutate((new_hotkey, new_coldkey, netuid), |new_root_claimed| { + RootClaimed::::mutate((netuid, new_hotkey, new_coldkey), |new_root_claimed| { *new_root_claimed = old_root_claimed.saturating_add(*new_root_claimed); }); } @@ -386,35 +386,14 @@ impl Pallet { /// Claim all root dividends for subnet and remove all associated data. pub fn finalize_all_subnet_root_dividends(netuid: NetUid) { - let mut hotkeys_to_clear = BTreeSet::new(); - for (hotkey, root_claimable) in RootClaimable::::iter() { - if root_claimable.contains_key(&netuid) { - let alpha_values: Vec<((T::AccountId, NetUid), U64F64)> = - Alpha::::iter_prefix((&hotkey,)).collect(); - - for ((coldkey, alpha_netuid), _) in alpha_values.into_iter() { - if alpha_netuid == NetUid::ROOT { - Self::root_claim_on_subnet( - &hotkey, - &coldkey, - netuid, - RootClaimTypeEnum::Swap, - true, - ); - - RootClaimed::::remove((hotkey.clone(), coldkey, netuid)); - if !hotkeys_to_clear.contains(&hotkey) { - hotkeys_to_clear.insert(hotkey.clone()); - } - } - } - } - } + let hotkeys = RootClaimable::::iter_keys().collect::>(); - for hotkey in hotkeys_to_clear.into_iter() { - RootClaimable::::mutate(&hotkey, |claimable| { + for hotkey in hotkeys.iter() { + RootClaimable::::mutate(hotkey, |claimable| { claimable.remove(&netuid); }); } + + let _ = RootClaimed::::clear_prefix((netuid,), u32::MAX, None); } } diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index 5771029f74..7fdd335556 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -447,19 +447,13 @@ impl Pallet { let should_refund_owner: bool = reg_at < start_block; // 3) Compute owner's received emission in TAO at current price (ONLY if we may refund). - // Emission:: is Vec. We: - // - sum emitted α, + // We: + // - get the current alpha issuance, // - apply owner fraction to get owner α, // - price that α using a *simulated* AMM swap. let mut owner_emission_tao = TaoCurrency::ZERO; if should_refund_owner && !lock_cost.is_zero() { - let total_emitted_alpha_u128: u128 = - Emission::::get(netuid) - .into_iter() - .fold(0u128, |acc, e_alpha| { - let e_u64: u64 = Into::::into(e_alpha); - acc.saturating_add(e_u64 as u128) - }); + let total_emitted_alpha_u128: u128 = Self::get_alpha_issuance(netuid).to_u64() as u128; if total_emitted_alpha_u128 > 0 { let owner_fraction: U96F32 = Self::get_float_subnet_owner_cut(); @@ -469,22 +463,12 @@ impl Pallet { .saturating_to_num::(); owner_emission_tao = if owner_alpha_u64 > 0 { - let order = GetTaoForAlpha::with_amount(owner_alpha_u64); - match T::SwapInterface::sim_swap(netuid.into(), order) { - Ok(sim) => TaoCurrency::from(sim.amount_paid_out), - Err(e) => { - log::debug!( - "destroy_alpha_in_out_stakes: sim_swap owner α→τ failed (netuid={netuid:?}, alpha={owner_alpha_u64}, err={e:?}); falling back to price multiply.", - ); - let cur_price: U96F32 = - T::SwapInterface::current_alpha_price(netuid.into()); - let val_u64 = U96F32::from_num(owner_alpha_u64) - .saturating_mul(cur_price) - .floor() - .saturating_to_num::(); - TaoCurrency::from(val_u64) - } - } + let cur_price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into()); + let val_u64 = U96F32::from_num(owner_alpha_u64) + .saturating_mul(cur_price) + .floor() + .saturating_to_num::(); + TaoCurrency::from(val_u64) } else { TaoCurrency::ZERO }; diff --git a/pallets/subtensor/src/subnets/leasing.rs b/pallets/subtensor/src/subnets/leasing.rs index cf263a1335..a202a22a45 100644 --- a/pallets/subtensor/src/subnets/leasing.rs +++ b/pallets/subtensor/src/subnets/leasing.rs @@ -24,8 +24,7 @@ use frame_system::pallet_prelude::*; use sp_core::blake2_256; use sp_runtime::{Percent, traits::TrailingZeroInput}; use substrate_fixed::types::U64F64; -use subtensor_runtime_common::{AlphaCurrency, NetUid, TaoCurrency}; -use subtensor_swap_interface::SwapHandler; +use subtensor_runtime_common::{AlphaCurrency, NetUid}; pub type LeaseId = u32; @@ -130,6 +129,9 @@ impl Pallet { ); SubnetUidToLeaseId::::insert(netuid, lease_id); + // The lease take should be 0% to allow all contributors to receive dividends entirely. + Self::delegate_hotkey(&lease_hotkey, 0); + // Get all the contributions to the crowdloan except for the beneficiary // because its share will be computed as the dividends are distributed let contributions = pallet_crowdloan::Contributions::::iter_prefix(crowdloan_id) @@ -249,9 +251,8 @@ impl Pallet { /// Hook used when the subnet owner's cut is distributed to split the amount into dividends /// for the contributors and the beneficiary in shares relative to their initial contributions. - /// - /// It will ensure the subnet has enough alpha in its liquidity pool before swapping it to tao to be distributed, - /// and if not enough liquidity is available, it will accumulate the dividends for later distribution. + /// It accumulates dividends to be distributed later when the interval for distribution is reached. + /// Distribution is made in alpha and stake to the contributor coldkey and lease hotkey. pub fn distribute_leased_network_dividends(lease_id: LeaseId, owner_cut_alpha: AlphaCurrency) { // Ensure the lease exists let Some(lease) = SubnetLeases::::get(lease_id) else { @@ -290,55 +291,60 @@ impl Pallet { return; } - // Ensure there is enough liquidity to unstake the contributors cut - if let Err(err) = Self::validate_remove_stake( - &lease.coldkey, - &lease.hotkey, - lease.netuid, - total_contributors_cut_alpha, - total_contributors_cut_alpha, - false, - ) { - log::debug!("Couldn't distributing dividends for lease {lease_id}: {err:?}"); - AccumulatedLeaseDividends::::set(lease_id, total_contributors_cut_alpha); - return; - } - - // Unstake the contributors cut from the subnet as tao to the lease coldkey - let tao_unstaked = match Self::unstake_from_subnet( - &lease.hotkey, - &lease.coldkey, - lease.netuid, - total_contributors_cut_alpha, - T::SwapInterface::min_price(), - false, - ) { - Ok(tao_unstaked) => tao_unstaked, - Err(err) => { - log::debug!("Couldn't distributing dividends for lease {lease_id}: {err:?}"); - AccumulatedLeaseDividends::::set(lease_id, total_contributors_cut_alpha); - return; + // We use a storage layer to ensure the distribution is atomic. + if let Err(err) = frame_support::storage::with_storage_layer(|| { + let mut alpha_distributed = AlphaCurrency::ZERO; + + // Distribute the contributors cut to the contributors and accumulate the alpha + // distributed so far to obtain how much alpha is left to distribute to the beneficiary + for (contributor, share) in SubnetLeaseShares::::iter_prefix(lease_id) { + let alpha_for_contributor = share + .saturating_mul(U64F64::from(total_contributors_cut_alpha.to_u64())) + .ceil() + .saturating_to_num::(); + + Self::transfer_stake_within_subnet( + &lease.coldkey, + &lease.hotkey, + &contributor, + &lease.hotkey, + lease.netuid, + alpha_for_contributor.into(), + )?; + alpha_distributed = alpha_distributed.saturating_add(alpha_for_contributor.into()); + + Self::deposit_event(Event::SubnetLeaseDividendsDistributed { + lease_id, + contributor, + alpha: alpha_for_contributor.into(), + }); } - }; - // Distribute the contributors cut to the contributors and accumulate the tao - // distributed so far to obtain how much tao is left to distribute to the beneficiary - let mut tao_distributed = TaoCurrency::ZERO; - for (contributor, share) in SubnetLeaseShares::::iter_prefix(lease_id) { - let tao_for_contributor = share - .saturating_mul(U64F64::from(tao_unstaked.to_u64())) - .floor() - .saturating_to_num::(); - Self::add_balance_to_coldkey_account(&contributor, tao_for_contributor); - tao_distributed = tao_distributed.saturating_add(tao_for_contributor.into()); - } + // Distribute the leftover alpha to the beneficiary + let beneficiary_cut_alpha = + total_contributors_cut_alpha.saturating_sub(alpha_distributed); + Self::transfer_stake_within_subnet( + &lease.coldkey, + &lease.hotkey, + &lease.beneficiary, + &lease.hotkey, + lease.netuid, + beneficiary_cut_alpha.into(), + )?; + Self::deposit_event(Event::SubnetLeaseDividendsDistributed { + lease_id, + contributor: lease.beneficiary.clone(), + alpha: beneficiary_cut_alpha.into(), + }); - // Distribute the leftover tao to the beneficiary - let beneficiary_cut_tao = tao_unstaked.saturating_sub(tao_distributed); - Self::add_balance_to_coldkey_account(&lease.beneficiary, beneficiary_cut_tao.into()); + // Reset the accumulated dividends + AccumulatedLeaseDividends::::insert(lease_id, AlphaCurrency::ZERO); - // Reset the accumulated dividends - AccumulatedLeaseDividends::::insert(lease_id, AlphaCurrency::ZERO); + Ok::<(), DispatchError>(()) + }) { + log::debug!("Couldn't distributing dividends for lease {lease_id}: {err:?}"); + AccumulatedLeaseDividends::::set(lease_id, total_contributors_cut_alpha); + }; } fn lease_coldkey(lease_id: LeaseId) -> Result { diff --git a/pallets/subtensor/src/tests/children.rs b/pallets/subtensor/src/tests/children.rs index e9b8c2aa6b..e8be57f021 100644 --- a/pallets/subtensor/src/tests/children.rs +++ b/pallets/subtensor/src/tests/children.rs @@ -2827,7 +2827,7 @@ fn test_set_weights_no_parent() { }); } -/// Test that drain_pending_emission sends childkey take fully to the nominators if childkey +/// Test that distribute_emission sends childkey take fully to the nominators if childkey /// doesn't have its own stake, independently of parent hotkey take. /// cargo test --package pallet-subtensor --lib -- tests::children::test_childkey_take_drain --exact --show-output #[allow(clippy::assertions_on_constants)] @@ -3095,7 +3095,8 @@ fn test_parent_child_chain_emission() { ); // Set pending emission to 0 - PendingEmission::::insert(netuid, AlphaCurrency::ZERO); + PendingValidatorEmission::::insert(netuid, AlphaCurrency::ZERO); + PendingServerEmission::::insert(netuid, AlphaCurrency::ZERO); // Run epoch with emission value SubtensorModule::run_coinbase(emission); diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index e8a295ae0d..c11d88033f 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -6,8 +6,8 @@ use crate::tests::mock::{ use crate::{ DefaultMinRootClaimAmount, Error, MAX_NUM_ROOT_CLAIMS, MAX_ROOT_CLAIM_THRESHOLD, NetworksAdded, NumRootClaim, NumStakingColdkeys, PendingRootAlphaDivs, RootClaimable, RootClaimableThreshold, - StakingColdkeys, StakingColdkeysByIndex, SubnetAlphaIn, SubnetMechanism, SubnetTAO, - SubtokenEnabled, Tempo, pallet, + StakingColdkeys, StakingColdkeysByIndex, SubnetAlphaIn, SubnetMechanism, SubnetMovingPrice, + SubnetTAO, SubtokenEnabled, Tempo, pallet, }; use crate::{RootClaimType, RootClaimTypeEnum, RootClaimed}; use approx::assert_abs_diff_eq; @@ -18,7 +18,7 @@ use frame_support::{assert_err, assert_noop, assert_ok}; use sp_core::{H256, U256}; use sp_runtime::DispatchError; use std::collections::BTreeSet; -use substrate_fixed::types::{I96F32, U96F32}; +use substrate_fixed::types::{I96F32, U64F64, U96F32}; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; use subtensor_swap_interface::SwapHandler; @@ -72,9 +72,10 @@ fn test_claim_root_with_drain_emissions() { // Distribute pending root alpha let pending_root_alpha = 1_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, + AlphaCurrency::ZERO, pending_root_alpha.into(), AlphaCurrency::ZERO, ); @@ -135,14 +136,15 @@ fn test_claim_root_with_drain_emissions() { // Check root claimed value saved - let claimed = RootClaimed::::get((&hotkey, &coldkey, netuid)); + let claimed = RootClaimed::::get((netuid, &hotkey, &coldkey)); assert_eq!(u128::from(new_stake), claimed); // Distribute pending root alpha (round 2) - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, + AlphaCurrency::ZERO, pending_root_alpha.into(), AlphaCurrency::ZERO, ); @@ -180,7 +182,7 @@ fn test_claim_root_with_drain_emissions() { // Check root claimed value saved (round 2) - let claimed = RootClaimed::::get((&hotkey, &coldkey, netuid)); + let claimed = RootClaimed::::get((netuid, &hotkey, &coldkey)); assert_eq!(u128::from(u64::from(new_stake2)), claimed); }); } @@ -241,9 +243,10 @@ fn test_claim_root_adding_stake_proportionally_for_two_stakers() { // Distribute pending root alpha let pending_root_alpha = 10_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, + AlphaCurrency::ZERO, pending_root_alpha.into(), AlphaCurrency::ZERO, ); @@ -342,9 +345,10 @@ fn test_claim_root_adding_stake_disproportionally_for_two_stakers() { // Distribute pending root alpha let pending_root_alpha = 10_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, + AlphaCurrency::ZERO, pending_root_alpha.into(), AlphaCurrency::ZERO, ); @@ -433,9 +437,10 @@ fn test_claim_root_with_changed_stake() { // Distribute pending root alpha let pending_root_alpha = 10_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, + AlphaCurrency::ZERO, pending_root_alpha.into(), AlphaCurrency::ZERO, ); @@ -485,9 +490,10 @@ fn test_claim_root_with_changed_stake() { // Distribute pending root alpha let pending_root_alpha = 10_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, + AlphaCurrency::ZERO, pending_root_alpha.into(), AlphaCurrency::ZERO, ); @@ -538,9 +544,10 @@ fn test_claim_root_with_changed_stake() { // Distribute pending root alpha let pending_root_alpha = 10_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, + AlphaCurrency::ZERO, pending_root_alpha.into(), AlphaCurrency::ZERO, ); @@ -630,9 +637,10 @@ fn test_claim_root_with_drain_emissions_and_swap_claim_type() { // Distribute pending root alpha let pending_root_alpha = 10_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, + AlphaCurrency::ZERO, pending_root_alpha.into(), AlphaCurrency::ZERO, ); @@ -674,9 +682,10 @@ fn test_claim_root_with_drain_emissions_and_swap_claim_type() { // Distribute and claim pending root alpha (round 2) - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, + AlphaCurrency::ZERO, pending_root_alpha.into(), AlphaCurrency::ZERO, ); @@ -710,9 +719,10 @@ fn test_claim_root_with_drain_emissions_and_swap_claim_type() { ); // Distribute and claim pending root alpha (round 3) - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, + AlphaCurrency::ZERO, pending_root_alpha.into(), AlphaCurrency::ZERO, ); @@ -779,6 +789,18 @@ fn test_claim_root_with_run_coinbase() { initial_total_hotkey_alpha.into(), ); + // Set moving price > 1.0 and price > 1.0 + // So we turn ON root sell + SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); + pallet_subtensor_swap::AlphaSqrtPrice::::insert( + netuid, + U64F64::saturating_from_num(10.0), + ); + + // Make sure we are root selling, so we have root alpha divs. + let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + assert!(root_sell_flag, "Root sell flag should be true"); + // Distribute pending root alpha let initial_stake: u64 = @@ -878,6 +900,18 @@ fn test_claim_root_with_block_emissions() { ); SubtensorModule::maybe_add_coldkey_index(&coldkey); + // Set moving price > 1.0 and price > 1.0 + // So we turn ON root sell + SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); + pallet_subtensor_swap::AlphaSqrtPrice::::insert( + netuid, + U64F64::saturating_from_num(10.0), + ); + + // Make sure we are root selling, so we have root alpha divs. + let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + assert!(root_sell_flag, "Root sell flag should be true"); + let initial_total_hotkey_alpha = 10_000_000u64; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, @@ -991,12 +1025,27 @@ fn test_claim_root_coinbase_distribution() { let initial_alpha_issuance = SubtensorModule::get_alpha_issuance(netuid); let alpha_emissions: AlphaCurrency = 1_000_000_000u64.into(); - // Check total issuance (saved to pending alpha divs) + // Set moving price > 1.0 and price > 1.0 + // So we turn ON root sell + SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); + pallet_subtensor_swap::AlphaSqrtPrice::::insert( + netuid, + U64F64::saturating_from_num(10.0), + ); + // Make sure we are root selling, so we have root alpha divs. + let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + assert!(root_sell_flag, "Root sell flag should be true"); + + // Check total issuance (saved to pending alpha divs) run_to_block(2); let alpha_issuance = SubtensorModule::get_alpha_issuance(netuid); - assert_eq!(initial_alpha_issuance + alpha_emissions, alpha_issuance); + // We went two blocks so we should have 2x the alpha emissions + assert_eq!( + initial_alpha_issuance + alpha_emissions.saturating_mul(2.into()), + alpha_issuance + ); let root_prop = initial_tao as f64 / (u64::from(alpha_issuance) + initial_tao) as f64; let root_validators_share = 0.5f64; @@ -1096,9 +1145,10 @@ fn test_claim_root_with_swap_coldkey() { // Distribute pending root alpha let pending_root_alpha = 1_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, + AlphaCurrency::ZERO, pending_root_alpha.into(), AlphaCurrency::ZERO, ); @@ -1125,11 +1175,11 @@ fn test_claim_root_with_swap_coldkey() { assert_eq!( u128::from(new_stake), - RootClaimed::::get((&hotkey, &coldkey, netuid)) + RootClaimed::::get((netuid, &hotkey, &coldkey)) ); assert_eq!( 0u128, - RootClaimed::::get((&hotkey, &new_coldkey, netuid)) + RootClaimed::::get((netuid, &hotkey, &new_coldkey)) ); // Swap coldkey @@ -1143,10 +1193,10 @@ fn test_claim_root_with_swap_coldkey() { // Check swapped keys claimed values - assert_eq!(0u128, RootClaimed::::get((&hotkey, &coldkey, netuid))); + assert_eq!(0u128, RootClaimed::::get((netuid, &hotkey, &coldkey))); assert_eq!( u128::from(new_stake), - RootClaimed::::get((&hotkey, &new_coldkey, netuid)) + RootClaimed::::get((netuid, &hotkey, &new_coldkey,)) ); }); } @@ -1186,9 +1236,10 @@ fn test_claim_root_with_swap_hotkey() { // Distribute pending root alpha let pending_root_alpha = 1_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, + AlphaCurrency::ZERO, pending_root_alpha.into(), AlphaCurrency::ZERO, ); @@ -1215,11 +1266,11 @@ fn test_claim_root_with_swap_hotkey() { assert_eq!( u128::from(new_stake), - RootClaimed::::get((&hotkey, &coldkey, netuid)) + RootClaimed::::get((netuid, &hotkey, &coldkey,)) ); assert_eq!( 0u128, - RootClaimed::::get((&new_hotkey, &coldkey, netuid)) + RootClaimed::::get((netuid, &new_hotkey, &coldkey,)) ); let _old_claimable = *RootClaimable::::get(hotkey) @@ -1239,10 +1290,13 @@ fn test_claim_root_with_swap_hotkey() { // Check swapped keys claimed values - assert_eq!(0u128, RootClaimed::::get((&hotkey, &coldkey, netuid))); + assert_eq!( + 0u128, + RootClaimed::::get((netuid, &hotkey, &coldkey,)) + ); assert_eq!( u128::from(new_stake), - RootClaimed::::get((&new_hotkey, &coldkey, netuid)) + RootClaimed::::get((netuid, &new_hotkey, &coldkey,)) ); assert!(!RootClaimable::::get(hotkey).contains_key(&netuid)); @@ -1281,7 +1335,6 @@ fn test_claim_root_on_network_deregistration() { NetUid::ROOT, root_stake.into(), ); - let root_stake_rate = 0.1f64; SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &other_coldkey, @@ -1300,40 +1353,31 @@ fn test_claim_root_on_network_deregistration() { // Distribute pending root alpha let pending_root_alpha = 10_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, + AlphaCurrency::ZERO, pending_root_alpha.into(), AlphaCurrency::ZERO, ); - // Claim root via network deregistration - - assert_ok!(SubtensorModule::do_dissolve_network(netuid)); + assert_ok!(SubtensorModule::claim_root( + RuntimeOrigin::signed(coldkey), + BTreeSet::from([netuid]) + )); - // Check new stake - let validator_take_percent = 0.18f64; + assert!(RootClaimable::::get(hotkey).contains_key(&netuid)); - let new_stake: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, - &coldkey, - NetUid::ROOT, - ) - .into(); + assert!(RootClaimed::::contains_key(( + netuid, &hotkey, &coldkey, + ))); - let estimated_stake_increment = (pending_root_alpha as f64) - * (1f64 - validator_take_percent) - * current_price - * root_stake_rate; + // Claim root via network deregistration - assert_abs_diff_eq!( - new_stake, - root_stake + estimated_stake_increment as u64, - epsilon = 10000u64, - ); + assert_ok!(SubtensorModule::do_dissolve_network(netuid)); assert!(!RootClaimed::::contains_key(( - &hotkey, &coldkey, netuid + netuid, &hotkey, &coldkey, ))); assert!(!RootClaimable::::get(hotkey).contains_key(&netuid)); }); @@ -1450,9 +1494,10 @@ fn test_claim_root_with_unrelated_subnets() { // Distribute pending root alpha let pending_root_alpha = 1_000_000u64; - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, + AlphaCurrency::ZERO, pending_root_alpha.into(), AlphaCurrency::ZERO, ); @@ -1505,7 +1550,7 @@ fn test_claim_root_with_unrelated_subnets() { // Check root claimed value saved - let claimed = RootClaimed::::get((&hotkey, &coldkey, netuid)); + let claimed = RootClaimed::::get((netuid, &hotkey, &coldkey)); assert_eq!(u128::from(new_stake), claimed); }); } diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 75d4cc3ce9..2ba9f93556 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -1,4 +1,10 @@ -#![allow(unused, clippy::indexing_slicing, clippy::panic, clippy::unwrap_used)] +#![allow( + unused, + clippy::indexing_slicing, + clippy::panic, + clippy::unwrap_used, + clippy::expect_used +)] use super::mock::*; use crate::tests::mock; @@ -8,7 +14,10 @@ use approx::assert_abs_diff_eq; use frame_support::assert_ok; use pallet_subtensor_swap::position::PositionId; use sp_core::U256; -use substrate_fixed::types::{I64F64, I96F32, U64F64, U96F32}; +use substrate_fixed::{ + transcendental::sqrt, + types::{I64F64, I96F32, U64F64, U96F32}, +}; use subtensor_runtime_common::{AlphaCurrency, NetUidStorageIndex}; use subtensor_swap_interface::{SwapEngine, SwapHandler}; @@ -89,12 +98,16 @@ fn test_coinbase_tao_issuance_base() { let subnet_owner_ck = U256::from(1001); let subnet_owner_hk = U256::from(1002); let netuid = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); + let total_issuance_before = TotalIssuance::::get(); SubnetMovingPrice::::insert(netuid, I96F32::from(3141) / I96F32::from(1000)); let tao_in_before = SubnetTAO::::get(netuid); let total_stake_before = TotalStake::::get(); SubtensorModule::run_coinbase(U96F32::from_num(emission)); assert_eq!(SubnetTAO::::get(netuid), tao_in_before + emission); - assert_eq!(TotalIssuance::::get(), emission); + assert_eq!( + TotalIssuance::::get(), + total_issuance_before + emission + ); assert_eq!(TotalStake::::get(), total_stake_before + emission); }); } @@ -115,29 +128,28 @@ fn test_coinbase_tao_issuance_base_low() { } // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_coinbase_tao_issuance_base_low_flow --exact --show-output --nocapture -#[test] -fn test_coinbase_tao_issuance_base_low_flow() { - new_test_ext(1).execute_with(|| { - let emission = TaoCurrency::from(1_234_567); - let subnet_owner_ck = U256::from(1001); - let subnet_owner_hk = U256::from(1002); - let netuid = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); - let emission = TaoCurrency::from(1); - - // 100% tao flow method - let block_num = FlowHalfLife::::get(); - SubnetEmaTaoFlow::::insert(netuid, (block_num, I64F64::from_num(1_000_000_000))); - FlowFirstBlock::::set(Some(0_u64)); - System::set_block_number(block_num); - - let tao_in_before = SubnetTAO::::get(netuid); - let total_stake_before = TotalStake::::get(); - SubtensorModule::run_coinbase(U96F32::from_num(emission)); - assert_eq!(SubnetTAO::::get(netuid), tao_in_before + emission); - assert_eq!(TotalIssuance::::get(), emission); - assert_eq!(TotalStake::::get(), total_stake_before + emission); - }); -} +// #[test] +// fn test_coinbase_tao_issuance_base_low_flow() { +// new_test_ext(1).execute_with(|| { +// let emission = TaoCurrency::from(1_234_567); +// let subnet_owner_ck = U256::from(1001); +// let subnet_owner_hk = U256::from(1002); +// let netuid = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); +// let emission = TaoCurrency::from(1); + +// // 100% tao flow method +// let block_num = FlowHalfLife::::get(); +// SubnetEmaTaoFlow::::insert(netuid, (block_num, I64F64::from_num(1_000_000_000))); +// System::set_block_number(block_num); + +// let tao_in_before = SubnetTAO::::get(netuid); +// let total_stake_before = TotalStake::::get(); +// SubtensorModule::run_coinbase(U96F32::from_num(emission)); +// assert_eq!(SubnetTAO::::get(netuid), tao_in_before + emission); +// assert_eq!(TotalIssuance::::get(), emission); +// assert_eq!(TotalStake::::get(), total_stake_before + emission); +// }); +// } // Test emission distribution across multiple subnets. // This test verifies that: @@ -237,16 +249,16 @@ fn test_coinbase_tao_issuance_different_prices() { assert_abs_diff_eq!( SubnetTAO::::get(netuid1), TaoCurrency::from(initial_tao + emission / 3), - epsilon = 1.into(), + epsilon = 10.into(), ); assert_abs_diff_eq!( SubnetTAO::::get(netuid2), TaoCurrency::from(initial_tao + 2 * emission / 3), - epsilon = 1.into(), + epsilon = 10.into(), ); // Prices are low => we limit tao issued (buy alpha with it) - let tao_issued = TaoCurrency::from(((0.1 + 0.2) * emission as f64) as u64); + let tao_issued = TaoCurrency::from(((1.0) * emission as f64) as u64); assert_abs_diff_eq!( TotalIssuance::::get(), tao_issued, @@ -261,87 +273,85 @@ fn test_coinbase_tao_issuance_different_prices() { } // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_coinbase_tao_issuance_different_flows --exact --show-output --nocapture -#[test] -fn test_coinbase_tao_issuance_different_flows() { - new_test_ext(1).execute_with(|| { - let subnet_owner_ck = U256::from(1001); - let subnet_owner_hk = U256::from(1002); - let netuid1 = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); - let netuid2 = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); - let emission = 100_000_000; - - // Setup prices 0.1 and 0.2 - let initial_tao: u64 = 100_000_u64; - let initial_alpha1: u64 = initial_tao * 10; - let initial_alpha2: u64 = initial_tao * 5; - mock::setup_reserves(netuid1, initial_tao.into(), initial_alpha1.into()); - mock::setup_reserves(netuid2, initial_tao.into(), initial_alpha2.into()); - - // Force the swap to initialize - SubtensorModule::swap_tao_for_alpha( - netuid1, - TaoCurrency::ZERO, - 1_000_000_000_000.into(), - false, - ) - .unwrap(); - SubtensorModule::swap_tao_for_alpha( - netuid2, - TaoCurrency::ZERO, - 1_000_000_000_000.into(), - false, - ) - .unwrap(); - - // Set subnet prices to reversed proportion to ensure they don't affect emissions. - SubnetMovingPrice::::insert(netuid1, I96F32::from_num(2)); - SubnetMovingPrice::::insert(netuid2, I96F32::from_num(1)); - - // Set subnet tao flow ema. - // 100% tao flow method - let block_num = FlowHalfLife::::get(); - SubnetEmaTaoFlow::::insert(netuid1, (block_num, I64F64::from_num(1))); - SubnetEmaTaoFlow::::insert(netuid2, (block_num, I64F64::from_num(2))); - FlowFirstBlock::::set(Some(0_u64)); - System::set_block_number(block_num); - - // Set normalization exponent to 1 for simplicity - FlowNormExponent::::set(U64F64::from(1_u64)); - - // Assert initial TAO reserves. - assert_eq!(SubnetTAO::::get(netuid1), initial_tao.into()); - assert_eq!(SubnetTAO::::get(netuid2), initial_tao.into()); - let total_stake_before = TotalStake::::get(); - - // Run the coinbase with the emission amount. - SubtensorModule::run_coinbase(U96F32::from_num(emission)); - - // Assert tao emission is split evenly. - assert_abs_diff_eq!( - SubnetTAO::::get(netuid1), - TaoCurrency::from(initial_tao + emission / 3), - epsilon = 10.into(), - ); - assert_abs_diff_eq!( - SubnetTAO::::get(netuid2), - TaoCurrency::from(initial_tao + 2 * emission / 3), - epsilon = 10.into(), - ); +// #[test] +// fn test_coinbase_tao_issuance_different_flows() { +// new_test_ext(1).execute_with(|| { +// let subnet_owner_ck = U256::from(1001); +// let subnet_owner_hk = U256::from(1002); +// let netuid1 = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); +// let netuid2 = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); +// let emission = 100_000_000; + +// // Setup prices 0.1 and 0.2 +// let initial_tao: u64 = 100_000_u64; +// let initial_alpha1: u64 = initial_tao * 10; +// let initial_alpha2: u64 = initial_tao * 5; +// mock::setup_reserves(netuid1, initial_tao.into(), initial_alpha1.into()); +// mock::setup_reserves(netuid2, initial_tao.into(), initial_alpha2.into()); + +// // Force the swap to initialize +// SubtensorModule::swap_tao_for_alpha( +// netuid1, +// TaoCurrency::ZERO, +// 1_000_000_000_000.into(), +// false, +// ) +// .unwrap(); +// SubtensorModule::swap_tao_for_alpha( +// netuid2, +// TaoCurrency::ZERO, +// 1_000_000_000_000.into(), +// false, +// ) +// .unwrap(); + +// // Set subnet prices to reversed proportion to ensure they don't affect emissions. +// SubnetMovingPrice::::insert(netuid1, I96F32::from_num(2)); +// SubnetMovingPrice::::insert(netuid2, I96F32::from_num(1)); + +// // Set subnet tao flow ema. +// let block_num = FlowHalfLife::::get(); +// SubnetEmaTaoFlow::::insert(netuid1, (block_num, I64F64::from_num(1))); +// SubnetEmaTaoFlow::::insert(netuid2, (block_num, I64F64::from_num(2))); +// System::set_block_number(block_num); + +// // Set normalization exponent to 1 for simplicity +// FlowNormExponent::::set(U64F64::from(1_u64)); + +// // Assert initial TAO reserves. +// assert_eq!(SubnetTAO::::get(netuid1), initial_tao.into()); +// assert_eq!(SubnetTAO::::get(netuid2), initial_tao.into()); +// let total_stake_before = TotalStake::::get(); + +// // Run the coinbase with the emission amount. +// SubtensorModule::run_coinbase(U96F32::from_num(emission)); + +// // Assert tao emission is split evenly. +// assert_abs_diff_eq!( +// SubnetTAO::::get(netuid1), +// TaoCurrency::from(initial_tao + emission / 3), +// epsilon = 10.into(), +// ); +// assert_abs_diff_eq!( +// SubnetTAO::::get(netuid2), +// TaoCurrency::from(initial_tao + 2 * emission / 3), +// epsilon = 10.into(), +// ); - // Prices are low => we limit tao issued (buy alpha with it) - let tao_issued = TaoCurrency::from(((0.1 + 0.2) * emission as f64) as u64); - assert_abs_diff_eq!( - TotalIssuance::::get(), - tao_issued, - epsilon = 10.into() - ); - assert_abs_diff_eq!( - TotalStake::::get(), - total_stake_before + emission.into(), - epsilon = 10.into() - ); - }); -} +// // Prices are low => we limit tao issued (buy alpha with it) +// let tao_issued = TaoCurrency::from(((0.1 + 0.2) * emission as f64) as u64); +// assert_abs_diff_eq!( +// TotalIssuance::::get(), +// tao_issued, +// epsilon = 10.into() +// ); +// assert_abs_diff_eq!( +// TotalStake::::get(), +// total_stake_before + emission.into(), +// epsilon = 10.into() +// ); +// }); +// } // Test moving price updates with different alpha values. // This test verifies that: @@ -490,9 +500,9 @@ fn test_coinbase_alpha_issuance_base() { }); } -// Test alpha issuance with different subnet prices. +// Test alpha issuance with different subnet flows. // This test verifies that: -// - Alpha issuance is proportional to subnet prices +// - Alpha issuance is proportional to subnet flows // - Higher priced subnets receive more TAO emission // - Alpha issuance is correctly calculated based on price ratios // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_coinbase_alpha_issuance_different --exact --show-output --nocapture @@ -507,18 +517,16 @@ fn test_coinbase_alpha_issuance_different() { // Make subnets dynamic. SubnetMechanism::::insert(netuid1, 1); SubnetMechanism::::insert(netuid2, 1); - // Setup prices 1 and 1 + // Setup prices 1 and 2 let initial: u64 = 1_000_000; SubnetTAO::::insert(netuid1, TaoCurrency::from(initial)); SubnetAlphaIn::::insert(netuid1, AlphaCurrency::from(initial)); - SubnetTAO::::insert(netuid2, TaoCurrency::from(initial)); + SubnetTAO::::insert(netuid2, TaoCurrency::from(2 * initial)); SubnetAlphaIn::::insert(netuid2, AlphaCurrency::from(initial)); - // Set subnet prices. + // Set subnet ema prices to 1 and 2 SubnetMovingPrice::::insert(netuid1, I96F32::from_num(1)); SubnetMovingPrice::::insert(netuid2, I96F32::from_num(2)); - // Set tao flow - SubnetEmaTaoFlow::::insert(netuid1, (1u64, I64F64::from_num(1))); - SubnetEmaTaoFlow::::insert(netuid2, (1u64, I64F64::from_num(2))); + // Do NOT Set tao flow, let it initialize // Run coinbase SubtensorModule::run_coinbase(U96F32::from_num(emission)); // tao_in = 333_333 @@ -528,10 +536,10 @@ fn test_coinbase_alpha_issuance_different() { (initial + emission / 3).into() ); // tao_in = 666_666 - // alpha_in = 666_666/price = 666_666 + initial + // alpha_in = 666_666/price = 333_333 + initial assert_eq!( SubnetAlphaIn::::get(netuid2), - (initial + emission / 3 + emission / 3).into() + (initial + (emission * 2 / 3) / 2).into() ); }); } @@ -666,23 +674,47 @@ fn test_owner_cut_base() { #[test] fn test_pending_emission() { new_test_ext(1).execute_with(|| { - let netuid = NetUid::from(1); let emission: u64 = 1_000_000; - add_network(netuid, 1, 0); + let hotkey = U256::from(1); + let coldkey = U256::from(2); + let netuid = add_dynamic_network(&hotkey, &coldkey); + Tempo::::insert(netuid, 1); + FirstEmissionBlockNumber::::insert(netuid, 0); + mock::setup_reserves(netuid, 1_000_000.into(), 1.into()); SubtensorModule::run_coinbase(U96F32::from_num(0)); SubnetTAO::::insert(NetUid::ROOT, TaoCurrency::from(1_000_000_000)); // Add root weight. SubtensorModule::run_coinbase(U96F32::from_num(0)); SubtensorModule::set_tempo(netuid, 10000); // Large number (dont drain) SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + + // Set moving price > 1.0 + SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); + + // Make sure we are root selling, so we have root alpha divs. + let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + assert!(root_sell_flag, "Root sell flag should be true"); + SubtensorModule::run_coinbase(U96F32::from_num(0)); // 1 TAO / ( 1 + 3 ) = 0.25 * 1 / 2 = 125000000 assert_abs_diff_eq!( - u64::from(PendingEmission::::get(netuid)), - 1_000_000_000 - 125000000, + u64::from(PendingServerEmission::::get(netuid)), + 500_000_000, + epsilon = 1 + ); // 1 / 2. + + assert_abs_diff_eq!( + u64::from(PendingValidatorEmission::::get(netuid)), + 500_000_000 - 125000000, + epsilon = 1 + ); // 1 / 2 - swapped. + + assert_abs_diff_eq!( + u64::from(PendingRootAlphaDivs::::get(netuid)), + 125000000, epsilon = 1 - ); // 1 - swapped. + ); // 1 / 2 * 0.25 --> (from root_prop) }); } @@ -690,11 +722,12 @@ fn test_pending_emission() { #[test] fn test_drain_base() { new_test_ext(1).execute_with(|| { - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( 0.into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, + AlphaCurrency::ZERO, ) }); } @@ -705,11 +738,12 @@ fn test_drain_base_with_subnet() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(1); add_network(netuid, 1, 0); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, + AlphaCurrency::ZERO, ) }); } @@ -730,9 +764,10 @@ fn test_drain_base_with_subnet_with_single_staker_not_registered() { stake_before, ); let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, - pending_alpha.into(), + pending_alpha.saturating_div(2.into()).into(), + pending_alpha.saturating_div(2.into()).into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -759,9 +794,10 @@ fn test_drain_base_with_subnet_with_single_staker_registered() { stake_before, ); let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, - pending_alpha, + pending_alpha.saturating_div(2.into()).into(), + pending_alpha.saturating_div(2.into()).into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -803,9 +839,10 @@ fn test_drain_base_with_subnet_with_single_staker_registered_root_weight() { let pending_alpha = AlphaCurrency::from(1_000_000_000); let pending_root_alpha = AlphaCurrency::from(1_000_000_000); assert_eq!(SubnetTAO::::get(NetUid::ROOT), TaoCurrency::ZERO); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, - pending_alpha, + pending_alpha.saturating_div(2.into()).into(), + pending_alpha.saturating_div(2.into()).into(), pending_root_alpha, AlphaCurrency::ZERO, ); @@ -850,9 +887,10 @@ fn test_drain_base_with_subnet_with_two_stakers_registered() { stake_before, ); let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, - pending_alpha, + pending_alpha.saturating_div(2.into()).into(), + pending_alpha.saturating_div(2.into()).into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -915,9 +953,10 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root() { let pending_tao = TaoCurrency::from(1_000_000_000); let pending_alpha = AlphaCurrency::from(1_000_000_000); assert_eq!(SubnetTAO::::get(NetUid::ROOT), TaoCurrency::ZERO); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, - pending_alpha, + pending_alpha.saturating_div(2.into()).into(), + pending_alpha.saturating_div(2.into()).into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -990,11 +1029,12 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root_different_am let pending_tao = TaoCurrency::from(1_000_000_000); let pending_alpha = AlphaCurrency::from(1_000_000_000); assert_eq!(SubnetTAO::::get(NetUid::ROOT), TaoCurrency::ZERO); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, - pending_alpha, + pending_alpha.saturating_div(2.into()).into(), + pending_alpha.saturating_div(2.into()).into(), + AlphaCurrency::ZERO, AlphaCurrency::ZERO, - 0.into(), ); let stake_after1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey1, &coldkey, netuid); @@ -1070,9 +1110,10 @@ fn test_drain_base_with_subnet_with_two_stakers_registered_and_root_different_am let pending_tao = TaoCurrency::from(1_000_000_000); let pending_alpha = AlphaCurrency::from(1_000_000_000); assert_eq!(SubnetTAO::::get(NetUid::ROOT), TaoCurrency::ZERO); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, - pending_alpha, + pending_alpha.saturating_div(2.into()).into(), + pending_alpha.saturating_div(2.into()).into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1131,9 +1172,10 @@ fn test_drain_alpha_childkey_parentkey() { ChildkeyTake::::insert(child, netuid, u16::MAX / 10); let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, - pending_alpha, + pending_alpha.saturating_div(2.into()).into(), + pending_alpha.saturating_div(2.into()).into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1356,9 +1398,10 @@ fn test_get_root_children_drain() { // Lets drain let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( alpha, - pending_alpha, + pending_alpha.saturating_div(2.into()).into(), + pending_alpha.saturating_div(2.into()).into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1379,10 +1422,10 @@ fn test_get_root_children_drain() { // Lets drain let pending_alpha = AlphaCurrency::from(1_000_000_000); let pending_root1 = TaoCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( alpha, - pending_alpha, - // pending_root1, + pending_alpha.saturating_div(2.into()).into(), + pending_alpha.saturating_div(2.into()).into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1403,9 +1446,10 @@ fn test_get_root_children_drain() { // Lets drain let pending_alpha = AlphaCurrency::from(1_000_000_000); let pending_root2 = TaoCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( alpha, - pending_alpha, + pending_alpha.saturating_div(2.into()).into(), + pending_alpha.saturating_div(2.into()).into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1491,9 +1535,10 @@ fn test_get_root_children_drain_half_proportion() { // Lets drain! let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( alpha, - pending_alpha, + pending_alpha.saturating_div(2.into()).into(), + pending_alpha.saturating_div(2.into()).into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1577,9 +1622,10 @@ fn test_get_root_children_drain_with_take() { // Lets drain! let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( alpha, - pending_alpha, + pending_alpha.saturating_div(2.into()).into(), + pending_alpha.saturating_div(2.into()).into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1664,9 +1710,10 @@ fn test_get_root_children_drain_with_half_take() { // Lets drain! let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( alpha, - pending_alpha, + pending_alpha.saturating_div(2.into()).into(), + pending_alpha.saturating_div(2.into()).into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -1766,7 +1813,7 @@ fn test_get_root_children_drain_with_half_take() { // // Lets drain! // let pending_alpha = AlphaCurrency::from(1_000_000_000); -// SubtensorModule::drain_pending_emission(alpha, pending_alpha, 0, 0.into(), 0.into()); +// SubtensorModule::distribute_emission(alpha, pending_alpha, 0, 0.into(), 0.into()); // // Alice and Bob make the same amount. // close( @@ -2354,7 +2401,7 @@ fn test_calculate_dividends_and_incentives_only_miners() { } #[test] -fn test_drain_pending_emission_no_miners_all_drained() { +fn test_distribute_emission_no_miners_all_drained() { new_test_ext(1).execute_with(|| { let netuid = add_dynamic_network(&U256::from(1), &U256::from(2)); let hotkey = U256::from(3); @@ -2379,9 +2426,10 @@ fn test_drain_pending_emission_no_miners_all_drained() { // Set the emission to be 1 million. let emission = AlphaCurrency::from(1_000_000); // Run drain pending without any miners. - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, - emission, + emission.saturating_div(2.into()).into(), + emission.saturating_div(2.into()).into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -2398,9 +2446,9 @@ fn test_drain_pending_emission_no_miners_all_drained() { }); } -// cargo test --package pallet-subtensor --lib -- tests::coinbase::test_drain_pending_emission_zero_emission --exact --show-output +// cargo test --package pallet-subtensor --lib -- tests::coinbase::test_distribute_emission_zero_emission --exact --show-output #[test] -fn test_drain_pending_emission_zero_emission() { +fn test_distribute_emission_zero_emission() { new_test_ext(1).execute_with(|| { let netuid = add_dynamic_network_disable_commit_reveal(&U256::from(1), &U256::from(2)); let hotkey = U256::from(3); @@ -2451,9 +2499,10 @@ fn test_drain_pending_emission_zero_emission() { Dividends::::remove(netuid); // Set the emission to be ZERO. - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, - 0.into(), + AlphaCurrency::ZERO, + AlphaCurrency::ZERO, AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -2744,9 +2793,10 @@ fn test_drain_alpha_childkey_parentkey_with_burn() { let child_stake_before = SubtensorModule::get_stake_for_hotkey_on_subnet(&child, netuid); let pending_alpha = AlphaCurrency::from(1_000_000_000); - SubtensorModule::drain_pending_emission( + SubtensorModule::distribute_emission( netuid, - pending_alpha, + pending_alpha.saturating_div(2.into()).into(), + pending_alpha.saturating_div(2.into()).into(), AlphaCurrency::ZERO, AlphaCurrency::ZERO, ); @@ -2914,3 +2964,947 @@ fn test_zero_shares_zero_emission() { assert_eq!(SubnetAlphaIn::::get(netuid2), initial.into()); }); } + +#[test] +fn test_mining_emission_distribution_with_no_root_sell() { + new_test_ext(1).execute_with(|| { + let validator_coldkey = U256::from(1); + let validator_hotkey = U256::from(2); + let validator_miner_coldkey = U256::from(3); + let validator_miner_hotkey = U256::from(4); + let miner_coldkey = U256::from(5); + let miner_hotkey = U256::from(6); + let netuid = NetUid::from(1); + let subnet_tempo = 10; + let stake: u64 = 100_000_000_000; + let root_stake: u64 = 200_000_000_000; // 200 TAO + + // Create root network + SubtensorModule::set_tao_weight(0); // Start tao weight at 0 + SubtokenEnabled::::insert(NetUid::ROOT, true); + NetworksAdded::::insert(NetUid::ROOT, true); + + // Add network, register hotkeys, and setup network parameters + add_network(netuid, subnet_tempo, 0); + SubnetMechanism::::insert(netuid, 1); // Set mechanism to 1 + + // Setup large LPs to prevent slippage + SubnetTAO::::insert(netuid, TaoCurrency::from(1_000_000_000_000_000)); + SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(1_000_000_000_000_000)); + + register_ok_neuron(netuid, validator_hotkey, validator_coldkey, 0); + register_ok_neuron(netuid, validator_miner_hotkey, validator_miner_coldkey, 1); + register_ok_neuron(netuid, miner_hotkey, miner_coldkey, 2); + SubtensorModule::add_balance_to_coldkey_account( + &validator_coldkey, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::add_balance_to_coldkey_account( + &validator_miner_coldkey, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::add_balance_to_coldkey_account( + &miner_coldkey, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + step_block(subnet_tempo); + SubnetOwnerCut::::set(u16::MAX / 10); + // There are two validators and three neurons + MaxAllowedUids::::set(netuid, 3); + SubtensorModule::set_max_allowed_validators(netuid, 2); + + // Setup stakes: + // Stake from validator + // Stake from valiminer + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(validator_coldkey), + validator_hotkey, + netuid, + stake.into() + )); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(validator_miner_coldkey), + validator_miner_hotkey, + netuid, + stake.into() + )); + + // Setup YUMA so that it creates emissions + Weights::::insert(NetUidStorageIndex::from(netuid), 0, vec![(1, 0xFFFF)]); + Weights::::insert(NetUidStorageIndex::from(netuid), 1, vec![(2, 0xFFFF)]); + BlockAtRegistration::::set(netuid, 0, 1); + BlockAtRegistration::::set(netuid, 1, 1); + BlockAtRegistration::::set(netuid, 2, 1); + LastUpdate::::set(NetUidStorageIndex::from(netuid), vec![2, 2, 2]); + Kappa::::set(netuid, u16::MAX / 5); + ActivityCutoff::::set(netuid, u16::MAX); // makes all stake active + ValidatorPermit::::insert(netuid, vec![true, true, false]); + + // Run run_coinbase until emissions are drained + step_block(subnet_tempo); + + // Add stake to validator so it has root stake + SubtensorModule::add_balance_to_coldkey_account(&validator_coldkey, root_stake.into()); + // init root + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(validator_coldkey), + validator_hotkey, + NetUid::ROOT, + root_stake.into() + )); + // Set tao weight non zero + SubtensorModule::set_tao_weight(u64::MAX / 10); + + // Make root sell NOT happen + // set price very low, e.g. a lot of alpha in + //SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(1_000_000_000_000_000)); + pallet_subtensor_swap::AlphaSqrtPrice::::insert( + netuid, + U64F64::saturating_from_num(0.01), + ); + + // Make sure we ARE NOT root selling, so we do not have root alpha divs. + let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + assert!(!root_sell_flag, "Root sell flag should be false"); + + // Run run_coinbase until emissions are drained + step_block(subnet_tempo); + + let old_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); + let per_block_emission = SubtensorModule::get_block_emission_for_issuance( + SubtensorModule::get_alpha_issuance(netuid).into(), + ) + .unwrap_or(0); + + // step by one block + step_block(1); + // Verify that root alpha divs + let new_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); + // Check that we are indeed NOT root selling, i.e. that root alpha divs are NOT increasing + assert_eq!( + new_root_alpha_divs, old_root_alpha_divs, + "Root alpha divs should not increase" + ); + // Check root divs are zero + assert_eq!( + new_root_alpha_divs, + AlphaCurrency::ZERO, + "Root alpha divs should be zero" + ); + let miner_stake_before_epoch = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &miner_hotkey, + &miner_coldkey, + netuid, + ); + // Run again but with some root stake + step_block(subnet_tempo - 2); + assert_abs_diff_eq!( + PendingServerEmission::::get(netuid).to_u64(), + U96F32::saturating_from_num(per_block_emission) + .saturating_mul(U96F32::saturating_from_num(subnet_tempo as u64)) + .saturating_mul(U96F32::saturating_from_num(0.5)) // miner cut + .saturating_mul(U96F32::saturating_from_num(0.90)) + .saturating_to_num::(), + epsilon = 100_000_u64.into() + ); + step_block(1); + assert!( + BlocksSinceLastStep::::get(netuid) == 0, + "Blocks since last step should be 0" + ); + + let miner_uid = Uids::::get(netuid, miner_hotkey).unwrap_or(0); + log::info!("Miner uid: {miner_uid:?}"); + let miner_incentive: AlphaCurrency = { + let miner_incentive = Incentive::::get(NetUidStorageIndex::from(netuid)) + .get(miner_uid as usize) + .copied(); + + assert!(miner_incentive.is_some()); + + (miner_incentive.unwrap_or_default() as u64).into() + }; + log::info!("Miner incentive: {miner_incentive:?}"); + + // Miner emissions + let miner_emission_1: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &miner_hotkey, + &miner_coldkey, + netuid, + ) + .to_u64() + - miner_stake_before_epoch.to_u64(); + + assert_abs_diff_eq!( + Incentive::::get(NetUidStorageIndex::from(netuid)) + .iter() + .sum::(), + u16::MAX, + epsilon = 10 + ); + + assert_abs_diff_eq!( + miner_emission_1, + U96F32::saturating_from_num(miner_incentive) + .saturating_div(u16::MAX.into()) + .saturating_mul(U96F32::saturating_from_num(per_block_emission)) + .saturating_mul(U96F32::saturating_from_num(subnet_tempo + 1)) + .saturating_mul(U96F32::saturating_from_num(0.45)) // miner cut + .saturating_to_num::(), + epsilon = 1_000_000_u64 + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_mining_emission_distribution_with_root_sell --exact --show-output --nocapture +#[test] +fn test_mining_emission_distribution_with_root_sell() { + new_test_ext(1).execute_with(|| { + let validator_coldkey = U256::from(1); + let validator_hotkey = U256::from(2); + let validator_miner_coldkey = U256::from(3); + let validator_miner_hotkey = U256::from(4); + let miner_coldkey = U256::from(5); + let miner_hotkey = U256::from(6); + let subnet_tempo = 10; + let stake: u64 = 100_000_000_000; + let root_stake: u64 = 200_000_000_000; // 200 TAO + + // Create root network + SubtensorModule::set_tao_weight(0); // Start tao weight at 0 + SubtokenEnabled::::insert(NetUid::ROOT, true); + NetworksAdded::::insert(NetUid::ROOT, true); + + // Add network, register hotkeys, and setup network parameters + let owner_hotkey = U256::from(10); + let owner_coldkey = U256::from(11); + let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); + Tempo::::insert(netuid, 1); + FirstEmissionBlockNumber::::insert(netuid, 0); + + // Setup large LPs to prevent slippage + SubnetTAO::::insert(netuid, TaoCurrency::from(1_000_000_000_000_000)); + SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(1_000_000_000_000_000)); + + register_ok_neuron(netuid, validator_hotkey, validator_coldkey, 0); + register_ok_neuron(netuid, validator_miner_hotkey, validator_miner_coldkey, 1); + register_ok_neuron(netuid, miner_hotkey, miner_coldkey, 2); + SubtensorModule::add_balance_to_coldkey_account( + &validator_coldkey, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::add_balance_to_coldkey_account( + &validator_miner_coldkey, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::add_balance_to_coldkey_account( + &miner_coldkey, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + step_block(subnet_tempo); + SubnetOwnerCut::::set(u16::MAX / 10); + // There are two validators and three neurons + MaxAllowedUids::::set(netuid, 3); + SubtensorModule::set_max_allowed_validators(netuid, 2); + + // Setup stakes: + // Stake from validator + // Stake from valiminer + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(validator_coldkey), + validator_hotkey, + netuid, + stake.into() + )); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(validator_miner_coldkey), + validator_miner_hotkey, + netuid, + stake.into() + )); + + // Setup YUMA so that it creates emissions + Weights::::insert(NetUidStorageIndex::from(netuid), 0, vec![(1, 0xFFFF)]); + Weights::::insert(NetUidStorageIndex::from(netuid), 1, vec![(2, 0xFFFF)]); + BlockAtRegistration::::set(netuid, 0, 1); + BlockAtRegistration::::set(netuid, 1, 1); + BlockAtRegistration::::set(netuid, 2, 1); + LastUpdate::::set(NetUidStorageIndex::from(netuid), vec![2, 2, 2]); + Kappa::::set(netuid, u16::MAX / 5); + ActivityCutoff::::set(netuid, u16::MAX); // makes all stake active + ValidatorPermit::::insert(netuid, vec![true, true, false]); + + // Run run_coinbase until emissions are drained + step_block(subnet_tempo); + + // Add stake to validator so it has root stake + SubtensorModule::add_balance_to_coldkey_account(&validator_coldkey, root_stake.into()); + // init root + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(validator_coldkey), + validator_hotkey, + NetUid::ROOT, + root_stake.into() + )); + // Set tao weight non zero + SubtensorModule::set_tao_weight(u64::MAX / 10); + + // Make root sell happen + // Set moving price > 1.0 + // Set price > 1.0 + pallet_subtensor_swap::AlphaSqrtPrice::::insert( + netuid, + U64F64::saturating_from_num(10.0), + ); + + SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); + + // Make sure we are root selling, so we have root alpha divs. + let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + assert!(root_sell_flag, "Root sell flag should be true"); + + // Run run_coinbase until emissions are drained + step_block(subnet_tempo); + + let old_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); + let miner_stake_before_epoch = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &miner_hotkey, + &miner_coldkey, + netuid, + ); + + // step by one block + step_block(1); + // Verify root alpha divs + let new_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); + // Check that we ARE root selling, i.e. that root alpha divs are changing + assert_ne!( + new_root_alpha_divs, old_root_alpha_divs, + "Root alpha divs should be changing" + ); + assert!( + new_root_alpha_divs > AlphaCurrency::ZERO, + "Root alpha divs should be greater than 0" + ); + + // Run again but with some root stake + step_block(subnet_tempo - 1); + + let miner_uid = Uids::::get(netuid, miner_hotkey).unwrap_or(0); + let miner_incentive: AlphaCurrency = { + let miner_incentive = Incentive::::get(NetUidStorageIndex::from(netuid)) + .get(miner_uid as usize) + .copied(); + + assert!(miner_incentive.is_some()); + + (miner_incentive.unwrap_or_default() as u64).into() + }; + log::info!("Miner incentive: {miner_incentive:?}"); + + let per_block_emission = SubtensorModule::get_block_emission_for_issuance( + SubtensorModule::get_alpha_issuance(netuid).into(), + ) + .unwrap_or(0); + + // Miner emissions + let miner_emission_1: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &miner_hotkey, + &miner_coldkey, + netuid, + ) + .to_u64() + - miner_stake_before_epoch.to_u64(); + + assert_abs_diff_eq!( + miner_emission_1, + U96F32::saturating_from_num(miner_incentive) + .saturating_div(u16::MAX.into()) + .saturating_mul(U96F32::saturating_from_num(per_block_emission)) + .saturating_mul(U96F32::saturating_from_num(subnet_tempo + 1)) + .saturating_mul(U96F32::saturating_from_num(0.45)) // miner cut + .saturating_to_num::(), + epsilon = 1_000_000_u64 + ); + }); +} + +#[test] +fn test_coinbase_subnets_with_no_reg_get_no_emission() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + let netuid1 = add_dynamic_network(&U256::from(3), &U256::from(4)); + + // Setup initial state + SubtokenEnabled::::insert(netuid0, true); + SubtokenEnabled::::insert(netuid1, true); + FirstEmissionBlockNumber::::insert(netuid0, 0); + FirstEmissionBlockNumber::::insert(netuid1, 0); + // Explicitly allow registration for both subnets + NetworkRegistrationAllowed::::insert(netuid0, true); + NetworkRegistrationAllowed::::insert(netuid1, true); + NetworkPowRegistrationAllowed::::insert(netuid0, false); + NetworkPowRegistrationAllowed::::insert(netuid1, true); + + // Note that netuid0 has only one method allowed + // And, netuid1 has *both* methods allowed + // Both should be in the list. + let subnets_to_emit_to_0 = SubtensorModule::get_subnets_to_emit_to(&[netuid0, netuid1]); + // Check that both subnets are in the list + assert_eq!(subnets_to_emit_to_0.len(), 2); + assert!(subnets_to_emit_to_0.contains(&netuid0)); + assert!(subnets_to_emit_to_0.contains(&netuid1)); + + // Disabled registration of both methods on ONLY netuid0 + NetworkRegistrationAllowed::::insert(netuid0, false); + NetworkPowRegistrationAllowed::::insert(netuid0, false); + + // Check that netuid0 is not in the list + let subnets_to_emit_to_1 = SubtensorModule::get_subnets_to_emit_to(&[netuid0, netuid1]); + assert_eq!(subnets_to_emit_to_1.len(), 1); + assert!(!subnets_to_emit_to_1.contains(&netuid0)); + // Netuid1 still in the list + assert!(subnets_to_emit_to_1.contains(&netuid1)); + }); +} + +// Tests for the excess TAO condition +#[test] +fn test_coinbase_subnet_terms_with_alpha_in_gt_alpha_emission() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + mock::setup_reserves( + netuid0, + TaoCurrency::from(1_000_000_000_000_000), + AlphaCurrency::from(1_000_000_000_000_000), + ); + // Initialize swap v3 + Swap::maybe_initialize_v3(netuid0); + + // Set netuid0 to have price tao_emission / price > alpha_emission + let alpha_emission = U96F32::saturating_from_num( + SubtensorModule::get_block_emission_for_issuance( + SubtensorModule::get_alpha_issuance(netuid0).into(), + ) + .unwrap_or(0), + ); + let price_to_set: U64F64 = U64F64::saturating_from_num(0.01); + let price_to_set_fixed: U96F32 = U96F32::saturating_from_num(price_to_set); + let sqrt_price_to_set: U64F64 = sqrt(price_to_set).unwrap(); + + let tao_emission: U96F32 = U96F32::saturating_from_num(alpha_emission) + .saturating_mul(price_to_set_fixed) + .saturating_add(U96F32::saturating_from_num(0.01)); + + // Set the price + pallet_subtensor_swap::AlphaSqrtPrice::::insert(netuid0, sqrt_price_to_set); + // Check the price is set + assert_abs_diff_eq!( + pallet_subtensor_swap::Pallet::::current_alpha_price(netuid0).to_num::(), + price_to_set.to_num::(), + epsilon = 0.001 + ); + + let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); + + let (tao_in, alpha_in, alpha_out, excess_tao) = + SubtensorModule::get_subnet_terms(&subnet_emissions); + + // Check our condition is met + assert!(tao_emission / price_to_set_fixed > alpha_emission); + + // alpha_out should be the alpha_emission, always + assert_abs_diff_eq!( + alpha_out[&netuid0].to_num::(), + alpha_emission.to_num::(), + epsilon = 0.01 + ); + + // alpha_in should equal the alpha_emission + assert_abs_diff_eq!( + alpha_in[&netuid0].to_num::(), + alpha_emission.to_num::(), + epsilon = 0.01 + ); + // tao_in should be the alpha_in at the ratio of the price + assert_abs_diff_eq!( + tao_in[&netuid0].to_num::(), + alpha_in[&netuid0] + .saturating_mul(price_to_set_fixed) + .to_num::(), + epsilon = 0.01 + ); + + // excess_tao should be the difference between the tao_emission and the tao_in + assert_abs_diff_eq!( + excess_tao[&netuid0].to_num::(), + tao_emission.to_num::() - tao_in[&netuid0].to_num::(), + epsilon = 0.01 + ); + }); +} + +#[test] +fn test_coinbase_subnet_terms_with_alpha_in_lte_alpha_emission() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + mock::setup_reserves( + netuid0, + TaoCurrency::from(1_000_000_000_000_000), + AlphaCurrency::from(1_000_000_000_000_000), + ); + // Initialize swap v3 + Swap::maybe_initialize_v3(netuid0); + + let alpha_emission = U96F32::saturating_from_num( + SubtensorModule::get_block_emission_for_issuance( + SubtensorModule::get_alpha_issuance(netuid0).into(), + ) + .unwrap_or(0), + ); + let tao_emission = U96F32::saturating_from_num(34566756_u64); + + let price: U96F32 = Swap::current_alpha_price(netuid0); + + let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); + + let (tao_in, alpha_in, alpha_out, excess_tao) = + SubtensorModule::get_subnet_terms(&subnet_emissions); + + // Check our condition is met + assert!(tao_emission / price <= alpha_emission); + + // alpha_out should be the alpha_emission, always + assert_abs_diff_eq!( + alpha_out[&netuid0].to_num::(), + alpha_emission.to_num::(), + epsilon = 0.1 + ); + + // assuming alpha_in < alpha_emission + // Then alpha_in should be tao_emission / price + assert_abs_diff_eq!( + alpha_in[&netuid0].to_num::(), + tao_emission.to_num::() / price.to_num::(), + epsilon = 0.01 + ); + + // tao_in should be the tao_emission + assert_abs_diff_eq!( + tao_in[&netuid0].to_num::(), + tao_emission.to_num::(), + epsilon = 0.01 + ); + + // excess_tao should be 0 + assert_abs_diff_eq!( + excess_tao[&netuid0].to_num::(), + tao_emission.to_num::() - tao_in[&netuid0].to_num::(), + epsilon = 0.01 + ); + }); +} + +// Tests for the inject and swap are in the right order. +#[test] +fn test_coinbase_inject_and_maybe_swap_does_not_skew_reserves() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + mock::setup_reserves( + netuid0, + TaoCurrency::from(1_000_000_000_000_000), + AlphaCurrency::from(1_000_000_000_000_000), + ); + // Initialize swap v3 + Swap::maybe_initialize_v3(netuid0); + + let tao_in = BTreeMap::from([(netuid0, U96F32::saturating_from_num(123))]); + let alpha_in = BTreeMap::from([(netuid0, U96F32::saturating_from_num(456))]); + // We have excess TAO, so we will be swapping with it. + let excess_tao = BTreeMap::from([(netuid0, U96F32::saturating_from_num(789100))]); + + // Run the inject and maybe swap + SubtensorModule::inject_and_maybe_swap(&[netuid0], &tao_in, &alpha_in, &excess_tao); + + let tao_in_after = SubnetTAO::::get(netuid0); + let alpha_in_after = SubnetAlphaIn::::get(netuid0); + + // Make sure that when we inject and swap, we do it in the right order. + // Thereby not skewing the ratio away from the price. + let ratio_after: U96F32 = U96F32::saturating_from_num(alpha_in_after.to_u64()) + .saturating_div(U96F32::saturating_from_num(tao_in_after.to_u64())); + let price_after: U96F32 = U96F32::saturating_from_num( + pallet_subtensor_swap::Pallet::::current_alpha_price(netuid0).to_num::(), + ); + assert_abs_diff_eq!( + ratio_after.to_num::(), + price_after.to_num::(), + epsilon = 1.0 + ); + }); +} + +#[test] +fn test_coinbase_drain_pending_increments_blockssincelaststep() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + + let blocks_since_last_step_before = BlocksSinceLastStep::::get(netuid0); + + // Check that blockssincelaststep is incremented + SubtensorModule::drain_pending(&[netuid0], 1); + + let blocks_since_last_step_after = BlocksSinceLastStep::::get(netuid0); + assert!(blocks_since_last_step_after > blocks_since_last_step_before); + assert_eq!( + blocks_since_last_step_after, + blocks_since_last_step_before + 1 + ); + }); +} + +#[test] +fn test_coinbase_drain_pending_resets_blockssincelaststep() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + Tempo::::insert(netuid0, 100); + // Ensure the block number we use is the tempo block + let block_number = 98; + assert!(SubtensorModule::should_run_epoch(netuid0, block_number)); + + let blocks_since_last_step_before = 12345678; + BlocksSinceLastStep::::insert(netuid0, blocks_since_last_step_before); + LastMechansimStepBlock::::insert(netuid0, 12345); // garbage value + + // Check that blockssincelaststep is reset to 0 on tempo + SubtensorModule::drain_pending(&[netuid0], block_number); + + let blocks_since_last_step_after = BlocksSinceLastStep::::get(netuid0); + assert_eq!(blocks_since_last_step_after, 0); + // Also check LastMechansimStepBlock is set to the block number we ran on + assert_eq!(LastMechansimStepBlock::::get(netuid0), block_number); + }); +} + +#[test] +fn test_coinbase_drain_pending_gets_counters_and_resets_them() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + Tempo::::insert(netuid0, 100); + // Ensure the block number we use is the tempo block + let block_number = 98; + assert!(SubtensorModule::should_run_epoch(netuid0, block_number)); + + let pending_server_em = AlphaCurrency::from(123434534); + let pending_validator_em = AlphaCurrency::from(111111); + let pending_root = AlphaCurrency::from(12222222); + let pending_owner_cut = AlphaCurrency::from(12345678); + + PendingServerEmission::::insert(netuid0, pending_server_em); + PendingValidatorEmission::::insert(netuid0, pending_validator_em); + PendingRootAlphaDivs::::insert(netuid0, pending_root); + PendingOwnerCut::::insert(netuid0, pending_owner_cut); + + let emissions_to_distribute = SubtensorModule::drain_pending(&[netuid0], block_number); + assert_eq!(emissions_to_distribute.len(), 1); + assert_eq!( + emissions_to_distribute[&netuid0], + ( + pending_server_em, + pending_validator_em, + pending_root, + pending_owner_cut + ) + ); + + // Check that the pending emissions are reset + assert_eq!( + PendingServerEmission::::get(netuid0), + AlphaCurrency::ZERO + ); + assert_eq!( + PendingValidatorEmission::::get(netuid0), + AlphaCurrency::ZERO + ); + assert_eq!( + PendingRootAlphaDivs::::get(netuid0), + AlphaCurrency::ZERO + ); + assert_eq!(PendingOwnerCut::::get(netuid0), AlphaCurrency::ZERO); + }); +} + +#[test] +fn test_coinbase_emit_to_subnets_with_no_root_sell() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + // Set owner cut to ~10% + SubnetOwnerCut::::set(u16::MAX / 10); + mock::setup_reserves( + netuid0, + TaoCurrency::from(1_000_000_000_000_000), + AlphaCurrency::from(1_000_000_000_000_000), + ); + // Initialize swap v3 + Swap::maybe_initialize_v3(netuid0); + + let tao_emission = U96F32::saturating_from_num(12345678); + let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); + + // NO root sell + let root_sell_flag = false; + + let alpha_emission = U96F32::saturating_from_num( + SubtensorModule::get_block_emission_for_issuance( + SubtensorModule::get_alpha_issuance(netuid0).into(), + ) + .unwrap_or(0), + ); + let price: U96F32 = Swap::current_alpha_price(netuid0); + let (tao_in, alpha_in, alpha_out, excess_tao) = + SubtensorModule::get_subnet_terms(&subnet_emissions); + // Based on the price, we should have NO excess TAO + assert!(tao_emission / price <= alpha_emission); + + // ==== Run the emit to subnets ===== + SubtensorModule::emit_to_subnets(&[netuid0], &subnet_emissions, root_sell_flag); + + // Find the owner cut expected + let owner_cut: U96F32 = SubtensorModule::get_float_subnet_owner_cut(); + let owner_cut_expected: U96F32 = owner_cut.saturating_mul(alpha_emission); + log::info!("owner_cut_expected: {owner_cut_expected:?}"); + log::info!("alpha_emission: {alpha_emission:?}"); + log::info!("owner_cut: {owner_cut:?}"); + + let alpha_issuance: U96F32 = + U96F32::saturating_from_num(SubtensorModule::get_alpha_issuance(netuid0)); + let root_tao: U96F32 = U96F32::saturating_from_num(SubnetTAO::::get(NetUid::ROOT)); + let tao_weight: U96F32 = root_tao.saturating_mul(SubtensorModule::get_tao_weight()); + let root_prop: U96F32 = tao_weight + .checked_div(tao_weight.saturating_add(alpha_issuance)) + .unwrap_or(U96F32::min_value()); + // Expect root alpha divs to be root prop * alpha emission + let expected_root_alpha_divs: AlphaCurrency = AlphaCurrency::from( + root_prop + .saturating_mul(alpha_emission) + .saturating_to_num::(), + ); + + // ===== Check that the pending emissions are set correctly ===== + // Owner cut is as expected + assert_abs_diff_eq!( + PendingOwnerCut::::get(netuid0).to_u64(), + owner_cut_expected.saturating_to_num::(), + epsilon = 200_u64 + ); + // NO root sell, so no root alpha divs + assert_eq!( + PendingRootAlphaDivs::::get(netuid0), + AlphaCurrency::ZERO + ); + // Should be alpha_emission minus the owner cut, + assert_abs_diff_eq!( + PendingServerEmission::::get(netuid0).to_u64(), + alpha_emission + .saturating_sub(owner_cut_expected) + .saturating_div(U96F32::saturating_from_num(2)) + .saturating_to_num::(), + epsilon = 200_u64 + ); + // We ALWAYS deduct the root alpha divs + assert_abs_diff_eq!( + PendingValidatorEmission::::get(netuid0).to_u64(), + alpha_emission + .saturating_sub(owner_cut_expected) + .saturating_div(U96F32::saturating_from_num(2)) + .saturating_sub(expected_root_alpha_divs.to_u64().into()) + .saturating_to_num::(), + epsilon = 200_u64 + ); + }); +} + +#[test] +fn test_coinbase_emit_to_subnets_with_root_sell() { + new_test_ext(1).execute_with(|| { + let zero = U96F32::saturating_from_num(0); + let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + // Set owner cut to ~10% + SubnetOwnerCut::::set(u16::MAX / 10); + mock::setup_reserves( + netuid0, + TaoCurrency::from(1_000_000_000_000_000), + AlphaCurrency::from(1_000_000_000_000_000), + ); + // Initialize swap v3 + Swap::maybe_initialize_v3(netuid0); + + let tao_emission = U96F32::saturating_from_num(12345678); + let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); + + // NO root sell + let root_sell_flag = true; + + let alpha_emission: U96F32 = U96F32::saturating_from_num( + SubtensorModule::get_block_emission_for_issuance( + SubtensorModule::get_alpha_issuance(netuid0).into(), + ) + .unwrap_or(0), + ); + let price: U96F32 = Swap::current_alpha_price(netuid0); + let (tao_in, alpha_in, alpha_out, excess_tao) = + SubtensorModule::get_subnet_terms(&subnet_emissions); + // Based on the price, we should have NO excess TAO + assert!(tao_emission / price <= alpha_emission); + + // ==== Run the emit to subnets ===== + SubtensorModule::emit_to_subnets(&[netuid0], &subnet_emissions, root_sell_flag); + + // Find the owner cut expected + let owner_cut: U96F32 = SubtensorModule::get_float_subnet_owner_cut(); + let owner_cut_expected: U96F32 = owner_cut.saturating_mul(alpha_emission); + log::info!("owner_cut_expected: {owner_cut_expected:?}"); + log::info!("alpha_emission: {alpha_emission:?}"); + log::info!("owner_cut: {owner_cut:?}"); + + let alpha_issuance: U96F32 = + U96F32::saturating_from_num(SubtensorModule::get_alpha_issuance(netuid0)); + let root_tao: U96F32 = U96F32::saturating_from_num(SubnetTAO::::get(NetUid::ROOT)); + let tao_weight: U96F32 = root_tao.saturating_mul(SubtensorModule::get_tao_weight()); + let root_prop: U96F32 = tao_weight + .checked_div(tao_weight.saturating_add(alpha_issuance)) + .unwrap_or(U96F32::min_value()); + // Expect root alpha divs to be root prop * alpha emission + let expected_root_alpha_divs: AlphaCurrency = AlphaCurrency::from( + root_prop + .saturating_mul(alpha_emission) + .saturating_to_num::(), + ); + + // ===== Check that the pending emissions are set correctly ===== + // Owner cut is as expected + assert_abs_diff_eq!( + PendingOwnerCut::::get(netuid0).to_u64(), + owner_cut_expected.saturating_to_num::(), + epsilon = 200_u64 + ); + // YES root sell, so we have root alpha divs + assert_abs_diff_eq!( + PendingRootAlphaDivs::::get(netuid0).to_u64(), + expected_root_alpha_divs.to_u64(), + epsilon = 200_u64 + ); + // Should be alpha_emission minus the owner cut + assert_abs_diff_eq!( + PendingServerEmission::::get(netuid0).to_u64(), + alpha_emission + .saturating_sub(owner_cut_expected) + .saturating_div(U96F32::saturating_from_num(2)) + .saturating_to_num::(), + epsilon = 200_u64 + ); + // Validator emission is also minus root alpha divs + assert_abs_diff_eq!( + PendingValidatorEmission::::get(netuid0).to_u64(), + alpha_emission + .saturating_sub(owner_cut_expected) + .saturating_div(U96F32::saturating_from_num(2)) + .saturating_sub(expected_root_alpha_divs.to_u64().into()) + .saturating_to_num::(), + epsilon = 200_u64 + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_pending_emission_start_call_not_done --exact --show-output --nocapture +#[test] +fn test_pending_emission_start_call_not_done() { + new_test_ext(1).execute_with(|| { + let validator_coldkey = U256::from(1); + let validator_hotkey = U256::from(2); + let subnet_tempo = 10; + let stake: u64 = 100_000_000_000; + let root_stake: u64 = 200_000_000_000; // 200 TAO + + // Create root network + NetworksAdded::::insert(NetUid::ROOT, true); + // enabled root + SubtokenEnabled::::insert(NetUid::ROOT, true); + + // Add network, register hotkeys, and setup network parameters + let owner_hotkey = U256::from(10); + let owner_coldkey = U256::from(11); + let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); + // Remove FirstEmissionBlockNumber + FirstEmissionBlockNumber::::remove(netuid); + Tempo::::insert(netuid, subnet_tempo); + + register_ok_neuron(netuid, validator_hotkey, validator_coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account( + &validator_coldkey, + stake + ExistentialDeposit::get(), + ); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + step_block(subnet_tempo); + SubnetOwnerCut::::set(u16::MAX / 10); + // There are two validators and three neurons + MaxAllowedUids::::set(netuid, 3); + SubtensorModule::set_max_allowed_validators(netuid, 2); + + // Add stake to validator so it has root stake + SubtensorModule::add_balance_to_coldkey_account(&validator_coldkey, root_stake.into()); + // init root + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(validator_coldkey), + validator_hotkey, + NetUid::ROOT, + root_stake.into() + )); + // Set tao weight non zero + SubtensorModule::set_tao_weight(u64::MAX / 10); + + // Make root sell happen + // Set moving price > 1.0 + // Set price > 1.0 + pallet_subtensor_swap::AlphaSqrtPrice::::insert( + netuid, + U64F64::saturating_from_num(10.0), + ); + + SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); + + // Make sure we are root selling, so we have root alpha divs. + let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + assert!(root_sell_flag, "Root sell flag should be true"); + + // !!! Check that the subnet FirstEmissionBlockNumber is None -- no entry + assert!(FirstEmissionBlockNumber::::get(netuid).is_none()); + + // Run run_coinbase until emissions are accumulated + step_block(subnet_tempo - 2); + + // Verify that all pending emissions are zero + assert_eq!( + PendingServerEmission::::get(netuid), + AlphaCurrency::ZERO + ); + assert_eq!( + PendingValidatorEmission::::get(netuid), + AlphaCurrency::ZERO + ); + assert_eq!( + PendingRootAlphaDivs::::get(netuid), + AlphaCurrency::ZERO + ); + }); +} diff --git a/pallets/subtensor/src/tests/ensure.rs b/pallets/subtensor/src/tests/ensure.rs index a59bfd7484..3b4db87f4e 100644 --- a/pallets/subtensor/src/tests/ensure.rs +++ b/pallets/subtensor/src/tests/ensure.rs @@ -111,6 +111,7 @@ fn ensure_owner_or_root_with_limits_checks_rl_and_freeze() { ) .expect("should pass"); assert_eq!(res, Some(owner)); + assert_ok!(crate::Pallet::::ensure_admin_window_open(netuid)); // Simulate previous update at current block -> next call should fail due to rate limit let now = crate::Pallet::::get_current_block_as_u64(); @@ -127,11 +128,14 @@ fn ensure_owner_or_root_with_limits_checks_rl_and_freeze() { // Advance beyond RL and ensure passes again run_to_block(now + 3); + TransactionType::from(Hyperparameter::Kappa) + .set_last_block_on_subnet::(&owner, netuid, 0); assert_ok!(crate::Pallet::::ensure_sn_owner_or_root_with_limits( <::RuntimeOrigin>::signed(owner), netuid, &[Hyperparameter::Kappa.into()] )); + assert_ok!(crate::Pallet::::ensure_admin_window_open(netuid)); // Now advance into the freeze window; ensure blocks // (using loop for clarity, because epoch calculation function uses netuid) @@ -148,12 +152,13 @@ fn ensure_owner_or_root_with_limits_checks_rl_and_freeze() { } run_to_block(cur + 1); } + assert_ok!(crate::Pallet::::ensure_sn_owner_or_root_with_limits( + <::RuntimeOrigin>::signed(owner), + netuid, + &[Hyperparameter::Kappa.into()] + )); assert_noop!( - crate::Pallet::::ensure_sn_owner_or_root_with_limits( - <::RuntimeOrigin>::signed(owner), - netuid, - &[Hyperparameter::Kappa.into()], - ), + crate::Pallet::::ensure_admin_window_open(netuid), crate::Error::::AdminActionProhibitedDuringWeightsWindow ); }); diff --git a/pallets/subtensor/src/tests/leasing.rs b/pallets/subtensor/src/tests/leasing.rs index 4ffc18230a..9f8bf4d6bf 100644 --- a/pallets/subtensor/src/tests/leasing.rs +++ b/pallets/subtensor/src/tests/leasing.rs @@ -65,6 +65,9 @@ fn test_register_leased_network_works() { contributor2_share ); + // Ensure the lease hotkey has 0 take from staking + assert_eq!(SubtensorModule::get_hotkey_take(&lease.hotkey), 0); + // Ensure each contributor and beneficiary has been refunded their share of the leftover cap let leftover_cap = cap.saturating_sub(lease.cost); @@ -502,54 +505,111 @@ fn test_distribute_lease_network_dividends_multiple_contributors_works() { // Setup the correct block to distribute dividends run_to_block(::LeaseDividendsDistributionInterval::get() as u64); - // Get the initial subnet tao after stake and ensure all contributor - // balances are in initial state - let subnet_tao_before = SubnetTAO::::get(lease.netuid); - let contributor1_balance_before = SubtensorModule::get_coldkey_balance(&contributions[0].0); - let contributor2_balance_before = SubtensorModule::get_coldkey_balance(&contributions[1].0); - let beneficiary_balance_before = SubtensorModule::get_coldkey_balance(&beneficiary); + // Get the initial alpha for the contributors and beneficiary and ensure they are zero + let contributor1_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[0].0, + lease.netuid, + ); + assert_eq!(contributor1_alpha_before, AlphaCurrency::ZERO); + let contributor2_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[1].0, + lease.netuid, + ); + assert_eq!(contributor2_alpha_before, AlphaCurrency::ZERO); + let beneficiary_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &beneficiary, + lease.netuid, + ); + assert_eq!(beneficiary_alpha_before, AlphaCurrency::ZERO); // Setup some previously accumulated dividends - let accumulated_dividends = AlphaCurrency::from(5_000_000); + let accumulated_dividends = AlphaCurrency::from(10_000_000_000); AccumulatedLeaseDividends::::insert(lease_id, accumulated_dividends); // Distribute the dividends - let owner_cut_alpha = AlphaCurrency::from(5_000_000); + let owner_cut_alpha = AlphaCurrency::from(5_000_000_000); SubtensorModule::distribute_leased_network_dividends(lease_id, owner_cut_alpha); // Ensure the dividends were distributed correctly relative to their shares - let distributed_tao = subnet_tao_before - SubnetTAO::::get(lease.netuid); - let contributor1_balance_delta = SubtensorModule::get_coldkey_balance(&contributions[0].0) - .saturating_sub(contributor1_balance_before); - let contributor2_balance_delta = SubtensorModule::get_coldkey_balance(&contributions[1].0) - .saturating_sub(contributor2_balance_before); - let beneficiary_balance_delta = SubtensorModule::get_coldkey_balance(&beneficiary) - .saturating_sub(beneficiary_balance_before); - + let distributed_alpha = + accumulated_dividends + emissions_share.mul_ceil(owner_cut_alpha.to_u64()).into(); + assert_ne!(distributed_alpha, AlphaCurrency::ZERO); + + let contributor1_alpha_delta = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[0].0, + lease.netuid, + ) + .saturating_sub(contributor1_alpha_before); + assert_ne!(contributor1_alpha_delta, AlphaCurrency::ZERO); + + let contributor2_alpha_delta = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[1].0, + lease.netuid, + ) + .saturating_sub(contributor2_alpha_before); + assert_ne!(contributor2_alpha_delta, AlphaCurrency::ZERO); + + let beneficiary_alpha_delta = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &beneficiary, + lease.netuid, + ) + .saturating_sub(beneficiary_alpha_before); + assert_ne!(beneficiary_alpha_delta, AlphaCurrency::ZERO); + + // What has been distributed should be equal to the sum of all contributors received alpha assert_eq!( - distributed_tao, - (beneficiary_balance_delta + contributor1_balance_delta + contributor2_balance_delta) - .into() + distributed_alpha, + (beneficiary_alpha_delta + contributor1_alpha_delta + contributor2_alpha_delta).into() ); - let expected_contributor1_balance = + let expected_contributor1_alpha = SubnetLeaseShares::::get(lease_id, contributions[0].0) - .saturating_mul(U64F64::from(distributed_tao.to_u64())) - .floor() + .saturating_mul(U64F64::from(distributed_alpha.to_u64())) + .ceil() .to_num::(); - assert_eq!(contributor1_balance_delta, expected_contributor1_balance); + assert_eq!(contributor1_alpha_delta, expected_contributor1_alpha.into()); + assert_eq!( + System::events()[2].event, + RuntimeEvent::SubtensorModule(Event::SubnetLeaseDividendsDistributed { + lease_id, + contributor: contributions[0].0.into(), + alpha: expected_contributor1_alpha.into(), + },) + ); - let expected_contributor2_balance = + let expected_contributor2_alpha = SubnetLeaseShares::::get(lease_id, contributions[1].0) - .saturating_mul(U64F64::from(distributed_tao.to_u64())) - .floor() + .saturating_mul(U64F64::from(distributed_alpha.to_u64())) + .ceil() .to_num::(); - assert_eq!(contributor2_balance_delta, expected_contributor2_balance); + assert_eq!(contributor2_alpha_delta, expected_contributor2_alpha.into()); + assert_eq!( + System::events()[5].event, + RuntimeEvent::SubtensorModule(Event::SubnetLeaseDividendsDistributed { + lease_id, + contributor: contributions[1].0.into(), + alpha: expected_contributor2_alpha.into(), + },) + ); // The beneficiary should have received the remaining dividends - let expected_beneficiary_balance = distributed_tao.to_u64() - - (expected_contributor1_balance + expected_contributor2_balance); - assert_eq!(beneficiary_balance_delta, expected_beneficiary_balance); + let expected_beneficiary_alpha = distributed_alpha.to_u64() + - (expected_contributor1_alpha + expected_contributor2_alpha); + assert_eq!(beneficiary_alpha_delta, expected_beneficiary_alpha.into()); + assert_eq!( + System::events()[8].event, + RuntimeEvent::SubtensorModule(Event::SubnetLeaseDividendsDistributed { + lease_id, + contributor: beneficiary.into(), + alpha: expected_beneficiary_alpha.into(), + },) + ); // Ensure nothing was accumulated for later distribution assert_eq!( @@ -584,23 +644,40 @@ fn test_distribute_lease_network_dividends_only_beneficiary_works() { // Setup the correct block to distribute dividends run_to_block(::LeaseDividendsDistributionInterval::get() as u64); - // Get the initial subnet tao after stake and beneficiary balance - let subnet_tao_before = SubnetTAO::::get(lease.netuid); - let beneficiary_balance_before = SubtensorModule::get_coldkey_balance(&beneficiary); + // Get the initial alpha for the beneficiary and ensure it is zero + let beneficiary_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &beneficiary, + lease.netuid, + ); + assert_eq!(beneficiary_alpha_before, AlphaCurrency::ZERO); // Setup some previously accumulated dividends - let accumulated_dividends = AlphaCurrency::from(5_000_000); + let accumulated_dividends = AlphaCurrency::from(10_000_000_000); AccumulatedLeaseDividends::::insert(lease_id, accumulated_dividends); // Distribute the dividends - let owner_cut_alpha = AlphaCurrency::from(5_000_000); + let owner_cut_alpha = AlphaCurrency::from(5_000_000_000); SubtensorModule::distribute_leased_network_dividends(lease_id, owner_cut_alpha); // Ensure the dividends were distributed correctly relative to their shares - let distributed_tao = subnet_tao_before - SubnetTAO::::get(lease.netuid); - let beneficiary_balance_delta = SubtensorModule::get_coldkey_balance(&beneficiary) - .saturating_sub(beneficiary_balance_before); - assert_eq!(distributed_tao, beneficiary_balance_delta.into()); + let distributed_alpha = + accumulated_dividends + emissions_share.mul_ceil(owner_cut_alpha.to_u64()).into(); + assert_ne!(distributed_alpha, AlphaCurrency::ZERO); + let beneficiary_alpha_delta = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &beneficiary, + lease.netuid, + ) + .saturating_sub(beneficiary_alpha_before); + assert_eq!(beneficiary_alpha_delta, distributed_alpha.into()); + assert_last_event::(RuntimeEvent::SubtensorModule( + Event::SubnetLeaseDividendsDistributed { + lease_id, + contributor: beneficiary.into(), + alpha: distributed_alpha, + }, + )); // Ensure nothing was accumulated for later distribution assert_eq!( @@ -628,7 +705,7 @@ fn test_distribute_lease_network_dividends_accumulates_if_not_the_correct_block( let end_block = 500; let emissions_share = Percent::from_percent(30); let tao_to_stake = 100_000_000_000; // 100 TAO - let (lease_id, _) = setup_leased_network( + let (lease_id, lease) = setup_leased_network( beneficiary, emissions_share, Some(end_block), @@ -638,31 +715,58 @@ fn test_distribute_lease_network_dividends_accumulates_if_not_the_correct_block( // Setup incorrect block to distribute dividends run_to_block(::LeaseDividendsDistributionInterval::get() as u64 + 1); - // Get the initial subnet tao after stake and ensure all contributor - let contributor1_balance_before = SubtensorModule::get_coldkey_balance(&contributions[0].0); - let contributor2_balance_before = SubtensorModule::get_coldkey_balance(&contributions[1].0); - let beneficiary_balance_before = SubtensorModule::get_coldkey_balance(&beneficiary); + // Get the initial alpha for the contributors and beneficiary and ensure they are zero + let contributor1_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[0].0, + lease.netuid, + ); + assert_eq!(contributor1_alpha_before, AlphaCurrency::ZERO); + let contributor2_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[1].0, + lease.netuid, + ); + assert_eq!(contributor2_alpha_before, AlphaCurrency::ZERO); + let beneficiary_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &beneficiary, + lease.netuid, + ); + assert_eq!(beneficiary_alpha_before, AlphaCurrency::ZERO); // Setup some previously accumulated dividends - let accumulated_dividends = AlphaCurrency::from(5_000_000); + let accumulated_dividends = AlphaCurrency::from(10_000_000_000); AccumulatedLeaseDividends::::insert(lease_id, accumulated_dividends); // Distribute the dividends - let owner_cut_alpha = AlphaCurrency::from(5_000_000); + let owner_cut_alpha = AlphaCurrency::from(5_000_000_000); SubtensorModule::distribute_leased_network_dividends(lease_id, owner_cut_alpha); // Ensure the dividends were not distributed assert_eq!( - SubtensorModule::get_coldkey_balance(&contributions[0].0), - contributor1_balance_before + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[0].0, + lease.netuid + ), + contributor1_alpha_before ); assert_eq!( - SubtensorModule::get_coldkey_balance(&contributions[1].0), - contributor2_balance_before + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[1].0, + lease.netuid + ), + contributor2_alpha_before ); assert_eq!( - SubtensorModule::get_coldkey_balance(&beneficiary), - beneficiary_balance_before + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &beneficiary, + lease.netuid + ), + beneficiary_alpha_before ); // Ensure we correctly accumulated the dividends @@ -711,29 +815,58 @@ fn test_distribute_lease_network_dividends_does_nothing_if_lease_has_ended() { // Run to the end of the lease run_to_block(end_block); - let subnet_tao_before = SubnetTAO::::get(lease.netuid); - let contributor1_balance_before = SubtensorModule::get_coldkey_balance(&contributions[0].0); - let contributor2_balance_before = SubtensorModule::get_coldkey_balance(&contributions[1].0); - let beneficiary_balance_before = SubtensorModule::get_coldkey_balance(&beneficiary); + // Get the initial alpha for the contributors and beneficiary and ensure they are zero + let contributor1_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[0].0, + lease.netuid, + ); + assert_eq!(contributor1_alpha_before, AlphaCurrency::ZERO); + let contributor2_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[1].0, + lease.netuid, + ); + assert_eq!(contributor2_alpha_before, AlphaCurrency::ZERO); + let beneficiary_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &beneficiary, + lease.netuid, + ); + assert_eq!(beneficiary_alpha_before, AlphaCurrency::ZERO); + + // No dividends are present, lease is new let accumulated_dividends_before = AccumulatedLeaseDividends::::get(lease_id); + assert_eq!(accumulated_dividends_before, AlphaCurrency::ZERO); // Try to distribute the dividends - let owner_cut_alpha = AlphaCurrency::from(5_000_000); + let owner_cut_alpha = AlphaCurrency::from(5_000_000_000); SubtensorModule::distribute_leased_network_dividends(lease_id, owner_cut_alpha); // Ensure the dividends were not distributed - assert_eq!(SubnetTAO::::get(lease.netuid), subnet_tao_before); assert_eq!( - SubtensorModule::get_coldkey_balance(&contributions[0].0), - contributor1_balance_before + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[0].0, + lease.netuid + ), + contributor1_alpha_before ); assert_eq!( - SubtensorModule::get_coldkey_balance(&contributions[1].0), - contributor2_balance_before + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[1].0, + lease.netuid + ), + contributor2_alpha_before ); assert_eq!( - SubtensorModule::get_coldkey_balance(&beneficiary), - beneficiary_balance_before + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &beneficiary, + lease.netuid + ), + beneficiary_alpha_before ); // Ensure nothing was accumulated for later distribution assert_eq!( @@ -768,28 +901,54 @@ fn test_distribute_lease_network_dividends_accumulates_if_amount_is_too_low() { None, // We don't add any liquidity ); - let subnet_tao_before = SubnetTAO::::get(lease.netuid); - let contributor1_balance_before = SubtensorModule::get_coldkey_balance(&contributions[0].0); - let contributor2_balance_before = SubtensorModule::get_coldkey_balance(&contributions[1].0); - let beneficiary_balance_before = SubtensorModule::get_coldkey_balance(&beneficiary); + // Get the initial alpha for the contributors and beneficiary and ensure they are zero + let contributor1_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[0].0, + lease.netuid, + ); + assert_eq!(contributor1_alpha_before, AlphaCurrency::ZERO); + let contributor2_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[1].0, + lease.netuid, + ); + assert_eq!(contributor2_alpha_before, AlphaCurrency::ZERO); + let beneficiary_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &beneficiary, + lease.netuid, + ); + assert_eq!(beneficiary_alpha_before, AlphaCurrency::ZERO); // Try to distribute the dividends let owner_cut_alpha = AlphaCurrency::from(5_000); SubtensorModule::distribute_leased_network_dividends(lease_id, owner_cut_alpha); // Ensure the dividends were not distributed - assert_eq!(SubnetTAO::::get(lease.netuid), subnet_tao_before); assert_eq!( - SubtensorModule::get_coldkey_balance(&contributions[0].0), - contributor1_balance_before + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[0].0, + lease.netuid + ), + contributor1_alpha_before ); assert_eq!( - SubtensorModule::get_coldkey_balance(&contributions[1].0), - contributor2_balance_before + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[1].0, + lease.netuid + ), + contributor2_alpha_before ); assert_eq!( - SubtensorModule::get_coldkey_balance(&beneficiary), - beneficiary_balance_before + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &beneficiary, + lease.netuid + ), + beneficiary_alpha_before ); // Ensure the correct amount of alpha was accumulated for later dividends distribution assert_eq!( @@ -824,28 +983,53 @@ fn test_distribute_lease_network_dividends_accumulates_if_insufficient_liquidity None, // We don't add any liquidity ); - let subnet_tao_before = SubnetTAO::::get(lease.netuid); - let contributor1_balance_before = SubtensorModule::get_coldkey_balance(&contributions[0].0); - let contributor2_balance_before = SubtensorModule::get_coldkey_balance(&contributions[1].0); - let beneficiary_balance_before = SubtensorModule::get_coldkey_balance(&beneficiary); + let contributor1_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[0].0, + lease.netuid, + ); + assert_eq!(contributor1_alpha_before, AlphaCurrency::ZERO); + let contributor2_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[1].0, + lease.netuid, + ); + assert_eq!(contributor2_alpha_before, AlphaCurrency::ZERO); + let beneficiary_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &beneficiary, + lease.netuid, + ); + assert_eq!(beneficiary_alpha_before, AlphaCurrency::ZERO); // Try to distribute the dividends let owner_cut_alpha = AlphaCurrency::from(5_000_000); SubtensorModule::distribute_leased_network_dividends(lease_id, owner_cut_alpha); // Ensure the dividends were not distributed - assert_eq!(SubnetTAO::::get(lease.netuid), subnet_tao_before); assert_eq!( - SubtensorModule::get_coldkey_balance(&contributions[0].0), - contributor1_balance_before + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[0].0, + lease.netuid + ), + contributor1_alpha_before ); assert_eq!( - SubtensorModule::get_coldkey_balance(&contributions[1].0), - contributor2_balance_before + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &contributions[1].0, + lease.netuid + ), + contributor2_alpha_before ); assert_eq!( - SubtensorModule::get_coldkey_balance(&beneficiary), - beneficiary_balance_before + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &lease.hotkey, + &beneficiary, + lease.netuid + ), + beneficiary_alpha_before ); // Ensure the correct amount of alpha was accumulated for later dividends distribution assert_eq!( diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index 28159d41bc..b694459eaa 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -26,8 +26,8 @@ use scale_info::prelude::collections::VecDeque; use sp_core::{H256, U256, crypto::Ss58Codec}; use sp_io::hashing::twox_128; use sp_runtime::traits::Zero; -use substrate_fixed::types::I96F32; use substrate_fixed::types::extra::U2; +use substrate_fixed::types::{I96F32, U64F64}; use subtensor_runtime_common::{NetUidStorageIndex, TaoCurrency}; #[allow(clippy::arithmetic_side_effects)] @@ -2016,7 +2016,7 @@ fn test_migrate_network_lock_reduction_interval_and_decay() { // last_lock_block should be set one week in the future let last_lock_block = Pallet::::get_network_last_lock_block(); - let expected_block = current_block_before.saturating_add(ONE_WEEK_BLOCKS); + let expected_block = current_block_before + ONE_WEEK_BLOCKS; assert_eq!( last_lock_block, expected_block, @@ -2243,10 +2243,8 @@ fn test_migrate_network_lock_cost_2500_sets_price_and_decay() { let min_lock_rao: u64 = Pallet::::get_network_min_lock().to_u64(); step_block(1); - let expected_after_1: u64 = core::cmp::max( - min_lock_rao, - TARGET_COST_RAO.saturating_sub(per_block_decrement), - ); + let expected_after_1: u64 = + core::cmp::max(min_lock_rao, TARGET_COST_RAO - per_block_decrement); let lock_cost_after_1 = Pallet::::get_network_lock_cost(); assert_eq!( lock_cost_after_1, @@ -2293,9 +2291,9 @@ fn test_migrate_kappa_map_to_default() { let default: u16 = DefaultKappa::::get(); let not_default: u16 = if default == u16::MAX { - default.saturating_sub(1) + default - 1 } else { - default.saturating_add(1) + default + 1 }; // ------------------------------ @@ -2397,3 +2395,332 @@ fn test_migrate_remove_tao_dividends() { 200_000, ); } + +fn do_setup_unactive_sn() -> (Vec, Vec) { + // Register some subnets + let netuid0 = add_dynamic_network_without_emission_block(&U256::from(0), &U256::from(0)); + let netuid1 = add_dynamic_network_without_emission_block(&U256::from(1), &U256::from(1)); + let netuid2 = add_dynamic_network_without_emission_block(&U256::from(2), &U256::from(2)); + let inactive_netuids = vec![netuid0, netuid1, netuid2]; + // Add active subnets + let netuid3 = add_dynamic_network_without_emission_block(&U256::from(3), &U256::from(3)); + let netuid4 = add_dynamic_network_without_emission_block(&U256::from(4), &U256::from(4)); + let netuid5 = add_dynamic_network_without_emission_block(&U256::from(5), &U256::from(5)); + let active_netuids = vec![netuid3, netuid4, netuid5]; + let netuids: Vec = inactive_netuids + .iter() + .chain(active_netuids.iter()) + .copied() + .collect(); + + let initial_tao = Pallet::::get_network_min_lock(); + let initial_alpha: AlphaCurrency = initial_tao.to_u64().into(); + + const EXTRA_POOL_TAO: u64 = 123_123_u64; + const EXTRA_POOL_ALPHA: u64 = 123_123_u64; + + // Add stake to the subnet pools + for netuid in &netuids { + let extra_for_pool = TaoCurrency::from(EXTRA_POOL_TAO); + let stake_in_pool = TaoCurrency::from( + u64::from(initial_tao) + .checked_add(EXTRA_POOL_TAO) + .expect("initial_tao + extra_for_pool overflow"), + ); + SubnetTAO::::insert(netuid, stake_in_pool); + TotalStake::::mutate(|total_stake| { + let updated_total = u64::from(*total_stake) + .checked_add(EXTRA_POOL_TAO) + .expect("total stake overflow"); + *total_stake = updated_total.into(); + }); + TotalIssuance::::mutate(|total_issuance| { + let updated_total = u64::from(*total_issuance) + .checked_add(EXTRA_POOL_TAO) + .expect("total issuance overflow"); + *total_issuance = updated_total.into(); + }); + + let subnet_alpha_in = AlphaCurrency::from( + u64::from(initial_alpha) + .checked_add(EXTRA_POOL_ALPHA) + .expect("initial alpha + extra alpha overflow"), + ); + SubnetAlphaIn::::insert(netuid, subnet_alpha_in); + SubnetAlphaOut::::insert(netuid, AlphaCurrency::from(EXTRA_POOL_ALPHA)); + SubnetVolume::::insert(netuid, 123123_u128); + + // Try registering on the subnet to simulate a real network + // give balance to the coldkey + let coldkey_account_id = U256::from(1111); + let hotkey_account_id = U256::from(1111); + let burn_cost = SubtensorModule::get_burn(*netuid); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, burn_cost.into()); + TotalIssuance::::mutate(|total_issuance| { + let updated_total = u64::from(*total_issuance) + .checked_add(u64::from(burn_cost)) + .expect("total issuance overflow (burn)"); + *total_issuance = updated_total.into(); + }); + + // register the neuron + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(coldkey_account_id), + *netuid, + hotkey_account_id + )); + } + + for netuid in &active_netuids { + // Set the FirstEmissionBlockNumber for the active subnet + FirstEmissionBlockNumber::::insert(netuid, 100); + // Also set SubtokenEnabled to true + SubtokenEnabled::::insert(netuid, true); + } + + let alpha_amt = AlphaCurrency::from(123123_u64); + // Create some Stake entries + for netuid in &netuids { + for hotkey in 0..10 { + let hk = U256::from(hotkey); + TotalHotkeyAlpha::::insert(hk, netuid, alpha_amt); + TotalHotkeyShares::::insert(hk, netuid, U64F64::from(123123_u64)); + TotalHotkeyAlphaLastEpoch::::insert(hk, netuid, alpha_amt); + + RootClaimable::::mutate(hk, |claimable| { + claimable.insert(*netuid, I96F32::from(alpha_amt.to_u64())); + }); + for coldkey in 0..10 { + let ck = U256::from(coldkey); + Alpha::::insert((hk, ck, netuid), U64F64::from(123_u64)); + RootClaimed::::insert((netuid, hk, ck), 222_u128); + } + } + } + // Add some pending emissions + let alpha_em_amt = AlphaCurrency::from(355555_u64); + for netuid in &netuids { + PendingServerEmission::::insert(netuid, alpha_em_amt); + PendingValidatorEmission::::insert(netuid, alpha_em_amt); + PendingRootAlphaDivs::::insert(netuid, alpha_em_amt); + PendingOwnerCut::::insert(netuid, alpha_em_amt); + + SubnetTaoInEmission::::insert(netuid, TaoCurrency::from(12345678_u64)); + SubnetAlphaInEmission::::insert(netuid, AlphaCurrency::from(12345678_u64)); + SubnetAlphaOutEmission::::insert(netuid, AlphaCurrency::from(12345678_u64)); + } + + (active_netuids, inactive_netuids) +} + +#[test] +fn test_migrate_reset_unactive_sn_get_unactive_netuids() { + new_test_ext(1).execute_with(|| { + let (active_netuids, inactive_netuids) = do_setup_unactive_sn(); + + let initial_tao = Pallet::::get_network_min_lock(); + let initial_alpha: AlphaCurrency = initial_tao.to_u64().into(); + + let (unactive_netuids, w) = + crate::migrations::migrate_reset_unactive_sn::get_unactive_sn_netuids::( + initial_alpha, + ); + // Make sure ALL the inactive subnets are in the unactive netuids + assert!( + inactive_netuids + .iter() + .all(|netuid| unactive_netuids.contains(netuid)) + ); + // Make sure the active subnets are not in the unactive netuids + assert!( + active_netuids + .iter() + .all(|netuid| !unactive_netuids.contains(netuid)) + ); + }); +} + +#[test] +fn test_migrate_reset_unactive_sn() { + new_test_ext(1).execute_with(|| { + let (active_netuids, inactive_netuids) = do_setup_unactive_sn(); + + let initial_tao = Pallet::::get_network_min_lock(); + let initial_alpha: AlphaCurrency = initial_tao.to_u64().into(); + + // Run the migration + let w = crate::migrations::migrate_reset_unactive_sn::migrate_reset_unactive_sn::(); + assert!(!w.is_zero(), "weight must be non-zero"); + + // Verify the results + for netuid in &inactive_netuids { + let actual_tao_lock_amount = SubnetLocked::::get(*netuid); + let actual_tao_lock_amount_less_pool_tao = if (actual_tao_lock_amount < initial_tao) { + TaoCurrency::ZERO + } else { + actual_tao_lock_amount - initial_tao + }; + assert_eq!( + PendingServerEmission::::get(netuid), + AlphaCurrency::ZERO + ); + assert_eq!( + PendingValidatorEmission::::get(netuid), + AlphaCurrency::ZERO + ); + assert_eq!( + PendingRootAlphaDivs::::get(netuid), + AlphaCurrency::ZERO + ); + assert_eq!( + // not modified + RAORecycledForRegistration::::get(netuid), + actual_tao_lock_amount_less_pool_tao + ); + assert!(pallet_subtensor_swap::AlphaSqrtPrice::::contains_key( + *netuid + )); + assert_eq!(PendingOwnerCut::::get(netuid), AlphaCurrency::ZERO); + assert_ne!(SubnetTAO::::get(netuid), initial_tao); + assert_ne!(SubnetAlphaIn::::get(netuid), initial_alpha); + assert_ne!(SubnetAlphaOut::::get(netuid), AlphaCurrency::ZERO); + assert_eq!(SubnetTaoInEmission::::get(netuid), TaoCurrency::ZERO); + assert_eq!( + SubnetAlphaInEmission::::get(netuid), + AlphaCurrency::ZERO + ); + assert_eq!( + SubnetAlphaOutEmission::::get(netuid), + AlphaCurrency::ZERO + ); + assert_ne!(SubnetVolume::::get(netuid), 0u128); + for hotkey in 0..10 { + let hk = U256::from(hotkey); + assert_ne!( + TotalHotkeyAlpha::::get(hk, netuid), + AlphaCurrency::ZERO + ); + assert_ne!( + TotalHotkeyShares::::get(hk, netuid), + U64F64::from_num(0.0) + ); + assert_ne!( + TotalHotkeyAlphaLastEpoch::::get(hk, netuid), + AlphaCurrency::ZERO + ); + assert_ne!(RootClaimable::::get(hk).get(netuid), None); + for coldkey in 0..10 { + let ck = U256::from(coldkey); + assert_ne!(Alpha::::get((hk, ck, netuid)), U64F64::from_num(0.0)); + assert_ne!(RootClaimed::::get((netuid, hk, ck)), 0u128); + } + } + + // Don't touch SubnetLocked + assert_ne!(SubnetLocked::::get(netuid), TaoCurrency::ZERO); + } + + // !!! Make sure the active subnets were not reset + for netuid in &active_netuids { + let actual_tao_lock_amount = SubnetLocked::::get(*netuid); + let actual_tao_lock_amount_less_pool_tao = actual_tao_lock_amount - initial_tao; + assert_ne!( + PendingServerEmission::::get(netuid), + AlphaCurrency::ZERO + ); + assert_ne!( + PendingValidatorEmission::::get(netuid), + AlphaCurrency::ZERO + ); + assert_ne!( + PendingRootAlphaDivs::::get(netuid), + AlphaCurrency::ZERO + ); + assert_eq!( + // not modified + RAORecycledForRegistration::::get(netuid), + actual_tao_lock_amount_less_pool_tao + ); + assert_ne!(SubnetTaoInEmission::::get(netuid), TaoCurrency::ZERO); + assert_ne!( + SubnetAlphaInEmission::::get(netuid), + AlphaCurrency::ZERO + ); + assert_ne!( + SubnetAlphaOutEmission::::get(netuid), + AlphaCurrency::ZERO + ); + assert!(pallet_subtensor_swap::AlphaSqrtPrice::::contains_key( + *netuid + )); + assert_ne!(PendingOwnerCut::::get(netuid), AlphaCurrency::ZERO); + assert_ne!(SubnetTAO::::get(netuid), initial_tao); + assert_ne!(SubnetAlphaIn::::get(netuid), initial_alpha); + assert_ne!(SubnetAlphaOut::::get(netuid), AlphaCurrency::ZERO); + assert_ne!(SubnetVolume::::get(netuid), 0u128); + for hotkey in 0..10 { + let hk = U256::from(hotkey); + assert_ne!( + TotalHotkeyAlpha::::get(hk, netuid), + AlphaCurrency::ZERO + ); + assert_ne!( + TotalHotkeyShares::::get(hk, netuid), + U64F64::from_num(0.0) + ); + assert_ne!( + TotalHotkeyAlphaLastEpoch::::get(hk, netuid), + AlphaCurrency::ZERO + ); + assert!(RootClaimable::::get(hk).contains_key(netuid)); + for coldkey in 0..10 { + let ck = U256::from(coldkey); + assert_ne!(Alpha::::get((hk, ck, netuid)), U64F64::from_num(0.0)); + assert_ne!(RootClaimed::::get((netuid, hk, ck)), 0u128); + } + } + // Don't touch SubnetLocked + assert_ne!(SubnetLocked::::get(netuid), TaoCurrency::ZERO); + } + }); +} + +#[test] +fn test_migrate_reset_unactive_sn_idempotence() { + new_test_ext(1).execute_with(|| { + let (active_netuids, inactive_netuids) = do_setup_unactive_sn(); + let netuids = inactive_netuids + .iter() + .chain(active_netuids.iter()) + .copied() + .collect::>(); + + // Run total issuance migration *before* running the migration. + crate::migrations::migrate_init_total_issuance::migrate_init_total_issuance::(); + + // Run the migration + let w = crate::migrations::migrate_reset_unactive_sn::migrate_reset_unactive_sn::(); + assert!(!w.is_zero(), "weight must be non-zero"); + + // Store the values after running the migration + let mut subnet_tao_before = BTreeMap::new(); + for netuid in &netuids { + subnet_tao_before.insert(netuid, SubnetTAO::::get(netuid)); + } + let total_stake_before = TotalStake::::get(); + let total_issuance_before = TotalIssuance::::get(); + + // Run total issuance migration again, to make sure no changes happen from it. + crate::migrations::migrate_init_total_issuance::migrate_init_total_issuance::(); + + // Verify that none of the values are different + for netuid in &netuids { + assert_eq!( + SubnetTAO::::get(netuid), + *subnet_tao_before.get(netuid).unwrap_or(&TaoCurrency::ZERO) + ); + } + assert_eq!(TotalStake::::get(), total_stake_before); + assert_eq!(TotalIssuance::::get(), total_issuance_before); + }); +} diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 9d028d76ab..2ea61ef7f5 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -8,7 +8,7 @@ use core::num::NonZeroU64; use crate::utils::rate_limiting::TransactionType; use crate::*; -use frame_support::traits::{Contains, Everything, InherentBuilder, InsideBoth}; +use frame_support::traits::{Contains, Everything, InherentBuilder, InsideBoth, InstanceFilter}; use frame_support::weights::Weight; use frame_support::weights::constants::RocksDbWeight; use frame_support::{PalletId, derive_impl}; @@ -18,6 +18,7 @@ use frame_support::{ }; use frame_system as system; use frame_system::{EnsureRoot, RawOrigin, limits, offchain::CreateTransactionBase}; +use pallet_subtensor_proxy as pallet_proxy; use pallet_subtensor_utility as pallet_utility; use sp_core::{ConstU64, Get, H256, U256, offchain::KeyTypeId}; use sp_runtime::Perbill; @@ -30,7 +31,6 @@ use sp_tracing::tracing_subscriber; use subtensor_runtime_common::{NetUid, TaoCurrency}; use subtensor_swap_interface::{Order, SwapHandler}; use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt}; - type Block = frame_system::mocking::MockBlock; // Configure a mock runtime to test the pallet. @@ -46,6 +46,7 @@ frame_support::construct_runtime!( Drand: pallet_drand::{Pallet, Call, Storage, Event} = 11, Swap: pallet_subtensor_swap::{Pallet, Call, Storage, Event} = 12, Crowdloan: pallet_crowdloan::{Pallet, Call, Storage, Event} = 13, + Proxy: pallet_subtensor_proxy = 14, } ); @@ -213,6 +214,7 @@ parameter_types! { pub const InitialYuma3On: bool = false; // Default value for Yuma3On // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days + pub const InitialDeregistrationPriorityScheduleDelay: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialColdkeySwapRescheduleDuration: u64 = 24 * 60 * 60 / 12; // Default as 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. @@ -284,6 +286,7 @@ impl crate::Config for Test { type Yuma3On = InitialYuma3On; type Preimages = Preimage; type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialDeregistrationPriorityScheduleDelay = InitialDeregistrationPriorityScheduleDelay; type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; @@ -425,6 +428,51 @@ impl pallet_crowdloan::Config for Test { type MaxContributors = MaxContributors; } +// Proxy Pallet config +parameter_types! { + // Set as 1 for testing purposes + pub const ProxyDepositBase: Balance = 1; + // Set as 1 for testing purposes + pub const ProxyDepositFactor: Balance = 1; + // Set as 20 for testing purposes + pub const MaxProxies: u32 = 20; // max num proxies per acct + // Set as 15 for testing purposes + pub const MaxPending: u32 = 15; // max blocks pending ~15min + // Set as 1 for testing purposes + pub const AnnouncementDepositBase: Balance = 1; + // Set as 1 for testing purposes + pub const AnnouncementDepositFactor: Balance = 1; +} + +impl pallet_proxy::Config for Test { + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = subtensor_runtime_common::ProxyType; + type ProxyDepositBase = ProxyDepositBase; + type ProxyDepositFactor = ProxyDepositFactor; + type MaxProxies = MaxProxies; + type WeightInfo = pallet_proxy::weights::SubstrateWeight; + type MaxPending = MaxPending; + type CallHasher = BlakeTwo256; + type AnnouncementDepositBase = AnnouncementDepositBase; + type AnnouncementDepositFactor = AnnouncementDepositFactor; + type BlockNumberProvider = System; +} + +impl InstanceFilter for subtensor_runtime_common::ProxyType { + fn filter(&self, _c: &RuntimeCall) -> bool { + // In tests, allow all proxy types to pass through + true + } + fn is_superset(&self, o: &Self) -> bool { + match (self, o) { + (x, y) if x == y => true, + (subtensor_runtime_common::ProxyType::Any, _) => true, + _ => false, + } + } +} + mod test_crypto { use super::KEY_TYPE; use sp_core::{ @@ -570,6 +618,7 @@ pub fn test_ext_with_balances(balances: Vec<(U256, u128)>) -> sp_io::TestExterna pub(crate) fn step_block(n: u16) { for _ in 0..n { Scheduler::on_finalize(System::block_number()); + Proxy::on_finalize(System::block_number()); SubtensorModule::on_finalize(System::block_number()); System::on_finalize(System::block_number()); System::set_block_number(System::block_number() + 1); @@ -718,6 +767,9 @@ pub fn add_dynamic_network(hotkey: &U256, coldkey: &U256) -> NetUid { let netuid = SubtensorModule::get_next_netuid(); let lock_cost = SubtensorModule::get_network_lock_cost(); SubtensorModule::add_balance_to_coldkey_account(coldkey, lock_cost.into()); + TotalIssuance::::mutate(|total_issuance| { + *total_issuance = total_issuance.saturating_add(lock_cost); + }); assert_ok!(SubtensorModule::register_network( RawOrigin::Signed(*coldkey).into(), @@ -735,6 +787,9 @@ pub fn add_dynamic_network_without_emission_block(hotkey: &U256, coldkey: &U256) let netuid = SubtensorModule::get_next_netuid(); let lock_cost = SubtensorModule::get_network_lock_cost(); SubtensorModule::add_balance_to_coldkey_account(coldkey, lock_cost.into()); + TotalIssuance::::mutate(|total_issuance| { + *total_issuance = total_issuance.saturating_add(lock_cost); + }); assert_ok!(SubtensorModule::register_network( RawOrigin::Signed(*coldkey).into(), diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 0449c67f86..80700ad6a1 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -6,6 +6,7 @@ use crate::*; use frame_support::{assert_err, assert_ok}; use frame_system::Config; use sp_core::U256; +use sp_runtime::DispatchError; use sp_std::collections::{btree_map::BTreeMap, vec_deque::VecDeque}; use substrate_fixed::types::{I96F32, U64F64, U96F32}; use subtensor_runtime_common::{MechId, NetUidStorageIndex, TaoCurrency}; @@ -55,6 +56,8 @@ fn dissolve_no_stakers_no_alpha_no_emission() { SubtensorModule::set_subnet_locked_balance(net, TaoCurrency::from(0)); SubnetTAO::::insert(net, TaoCurrency::from(0)); Emission::::insert(net, Vec::::new()); + SubnetDeregistrationPriorityQueue::::mutate(|queue| queue.push(net)); + assert!(SubnetDeregistrationPriorityQueue::::get().contains(&net)); let before = SubtensorModule::get_coldkey_balance(&cold); assert_ok!(SubtensorModule::do_dissolve_network(net)); @@ -63,6 +66,142 @@ fn dissolve_no_stakers_no_alpha_no_emission() { // Balance should be unchanged (whatever the network-lock bookkeeping left there) assert_eq!(after, before); assert!(!SubtensorModule::if_subnet_exist(net)); + assert!(!SubnetDeregistrationPriorityQueue::::get().contains(&net)); + }); +} + +#[test] +fn schedule_priority_and_force_set() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(11); + let owner_hot = U256::from(22); + let net = add_dynamic_network(&owner_hot, &owner_cold); + + assert_ok!(SubtensorModule::schedule_deregistration_priority( + RuntimeOrigin::signed(owner_cold), + net + )); + + assert!(!SubnetDeregistrationPriorityQueue::::get().contains(&net)); + let when = + SubnetDeregistrationPrioritySchedule::::get(net).expect("Expected to not panic"); + assert!(when > 0); + + assert_ok!(SubtensorModule::enqueue_subnet_deregistration( + RuntimeOrigin::root(), + net + )); + + assert!(SubnetDeregistrationPriorityQueue::::get().contains(&net)); + assert!(!SubnetDeregistrationPrioritySchedule::::contains_key( + net + )); + }); +} + +#[test] +fn cancel_priority_schedule_only() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(13); + let owner_hot = U256::from(26); + let net = add_dynamic_network(&owner_hot, &owner_cold); + + SubnetDeregistrationPriorityQueue::::mutate(|queue| queue.push(net)); + SubnetDeregistrationPrioritySchedule::::insert(net, 42); + + assert_ok!(SubtensorModule::cancel_deregistration_priority_schedules( + RuntimeOrigin::signed(owner_cold), + net + )); + + assert!(SubnetDeregistrationPriorityQueue::::get().contains(&net)); + assert!(!SubnetDeregistrationPrioritySchedule::::contains_key( + net + )); + }); +} + +#[test] +fn clear_priority_requires_root() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(19); + let owner_hot = U256::from(38); + let net = add_dynamic_network(&owner_hot, &owner_cold); + + SubnetDeregistrationPriorityQueue::::mutate(|queue| queue.push(net)); + SubnetDeregistrationPrioritySchedule::::insert(net, 55); + + assert_ok!(SubtensorModule::clear_deregistration_priority( + RuntimeOrigin::root(), + net + )); + + assert!(!SubnetDeregistrationPriorityQueue::::get().contains(&net)); + assert!(!SubnetDeregistrationPrioritySchedule::::contains_key( + net + )); + }); +} + +#[test] +fn schedule_priority_requires_owner_or_root() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(15); + let owner_hot = U256::from(30); + let net = add_dynamic_network(&owner_hot, &owner_cold); + let intruder = U256::from(999); + + assert_err!( + SubtensorModule::schedule_deregistration_priority(RuntimeOrigin::signed(intruder), net), + DispatchError::BadOrigin + ); + + assert_ok!(SubtensorModule::schedule_deregistration_priority( + RuntimeOrigin::root(), + net + )); + }); +} + +#[test] +fn enqueue_subnet_deregistration_is_noop_without_schedule() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(17); + let owner_hot = U256::from(34); + let net = add_dynamic_network(&owner_hot, &owner_cold); + + assert_ok!(SubtensorModule::enqueue_subnet_deregistration( + RuntimeOrigin::root(), + net + )); + + assert!(!SubnetDeregistrationPriorityQueue::::get().contains(&net)); + }); +} + +#[test] +fn schedule_swap_coldkey_cancels_priority_schedule() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(21); + let owner_hot = U256::from(42); + let new_cold = U256::from(84); + let net = add_dynamic_network(&owner_hot, &owner_cold); + + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&owner_cold, swap_cost.to_u64() + 1_000); + + SubnetDeregistrationPriorityQueue::::mutate(|queue| queue.push(net)); + SubnetDeregistrationPrioritySchedule::::insert(net, 5); + + assert_ok!(SubtensorModule::schedule_swap_coldkey( + RuntimeOrigin::signed(owner_cold), + new_cold + )); + + assert!(SubnetDeregistrationPriorityQueue::::get().contains(&net)); + assert!(!SubnetDeregistrationPrioritySchedule::::contains_key( + net + )); }); } @@ -216,43 +355,49 @@ fn dissolve_owner_cut_refund_logic() { // One staker and a TAO pot (not relevant to refund amount). let sh = U256::from(77); let sc = U256::from(88); - Alpha::::insert((sh, sc, net), U64F64::from_num(100u128)); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &sh, + &sc, + net, + AlphaCurrency::from(800u64), + ); SubnetTAO::::insert(net, TaoCurrency::from(1_000)); // Lock & emissions: total emitted α = 800. let lock: TaoCurrency = TaoCurrency::from(2_000); SubtensorModule::set_subnet_locked_balance(net, lock); - Emission::::insert( - net, - vec![AlphaCurrency::from(200), AlphaCurrency::from(600)], - ); + // ensure there was some Alpha issued + assert!(SubtensorModule::get_alpha_issuance(net).to_u64() > 0); // Owner cut = 11796 / 65535 (about 18%). SubnetOwnerCut::::put(11_796u16); // Compute expected refund with the SAME math as the pallet. let frac: U96F32 = SubtensorModule::get_float_subnet_owner_cut(); - let total_emitted_alpha: u64 = 800; + let total_emitted_alpha: u64 = SubtensorModule::get_alpha_issuance(net).to_u64(); let owner_alpha_u64: u64 = U96F32::from_num(total_emitted_alpha) .saturating_mul(frac) .floor() .saturating_to_num::(); - // Current α→τ price for this subnet. - let price: U96F32 = - ::SwapInterface::current_alpha_price(net.into()); - let owner_emission_tao_u64: u64 = U96F32::from_num(owner_alpha_u64) - .saturating_mul(price) - .floor() - .saturating_to_num::(); + // Use the current alpha price to estimate the TAO equivalent. + let owner_emission_tao = { + let price: U96F32 = + ::SwapInterface::current_alpha_price(net.into()); + U96F32::from_num(owner_alpha_u64) + .saturating_mul(price) + .floor() + .saturating_to_num::() + .into() + }; - let expected_refund: TaoCurrency = - lock.saturating_sub(TaoCurrency::from(owner_emission_tao_u64)); + let expected_refund: TaoCurrency = lock.saturating_sub(owner_emission_tao); let before = SubtensorModule::get_coldkey_balance(&oc); assert_ok!(SubtensorModule::do_dissolve_network(net)); let after = SubtensorModule::get_coldkey_balance(&oc); + assert!(after > before); // some refund is expected assert_eq!( TaoCurrency::from(after), TaoCurrency::from(before) + expected_refund @@ -368,7 +513,8 @@ fn dissolve_clears_all_per_subnet_storages() { SubnetMechanism::::insert(net, 1u16); NetworkRegistrationAllowed::::insert(net, true); NetworkPowRegistrationAllowed::::insert(net, true); - PendingEmission::::insert(net, AlphaCurrency::from(1)); + PendingServerEmission::::insert(net, AlphaCurrency::from(1)); + PendingValidatorEmission::::insert(net, AlphaCurrency::from(1)); PendingRootAlphaDivs::::insert(net, AlphaCurrency::from(1)); PendingOwnerCut::::insert(net, AlphaCurrency::from(1)); BlocksSinceLastStep::::insert(net, 1u64); @@ -523,7 +669,8 @@ fn dissolve_clears_all_per_subnet_storages() { assert!(!SubnetMechanism::::contains_key(net)); assert!(!NetworkRegistrationAllowed::::contains_key(net)); assert!(!NetworkPowRegistrationAllowed::::contains_key(net)); - assert!(!PendingEmission::::contains_key(net)); + assert!(!PendingServerEmission::::contains_key(net)); + assert!(!PendingValidatorEmission::::contains_key(net)); assert!(!PendingRootAlphaDivs::::contains_key(net)); assert!(!PendingOwnerCut::::contains_key(net)); assert!(!BlocksSinceLastStep::::contains_key(net)); @@ -841,15 +988,10 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { SubnetTAO::::insert(netuid, TaoCurrency::from(tao_pot)); SubtensorModule::set_subnet_locked_balance(netuid, TaoCurrency::from(lock)); + // ensure there was some Alpha issued + assert!(SubtensorModule::get_alpha_issuance(netuid).to_u64() > 0); + // Owner already earned some emission; owner-cut = 50 % - Emission::::insert( - netuid, - vec![ - AlphaCurrency::from(1_000), - AlphaCurrency::from(2_000), - AlphaCurrency::from(1_500), - ], - ); SubnetOwnerCut::::put(32_768u16); // ~ 0.5 in fixed-point // ── 4) balances before ────────────────────────────────────────────── @@ -879,28 +1021,23 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { // ── 5b) expected owner refund with price-aware emission deduction ─── let frac: U96F32 = SubtensorModule::get_float_subnet_owner_cut(); - let total_emitted_alpha: u64 = 1_000 + 2_000 + 1_500; // 4500 α + let total_emitted_alpha: u64 = SubtensorModule::get_alpha_issuance(netuid).to_u64(); let owner_alpha_u64: u64 = U96F32::from_num(total_emitted_alpha) .saturating_mul(frac) .floor() .saturating_to_num::(); - let order = GetTaoForAlpha::::with_amount(owner_alpha_u64); - let owner_emission_tao = - ::SwapInterface::sim_swap(netuid.into(), order) - .map(|res| res.amount_paid_out) - .unwrap_or_else(|_| { - // Fallback matches the pallet's fallback - let price: U96F32 = - ::SwapInterface::current_alpha_price(netuid.into()); - U96F32::from_num(owner_alpha_u64) - .saturating_mul(price) - .floor() - .saturating_to_num::() - .into() - }); - - let expected_refund = lock.saturating_sub(owner_emission_tao.to_u64()); + let owner_emission_tao: u64 = { + // Fallback matches the pallet's fallback + let price: U96F32 = + ::SwapInterface::current_alpha_price(netuid.into()); + U96F32::from_num(owner_alpha_u64) + .saturating_mul(price) + .floor() + .saturating_to_num::() + }; + + let expected_refund = lock.saturating_sub(owner_emission_tao); // ── 6) run distribution (credits τ to coldkeys, wipes α state) ───── assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); @@ -947,34 +1084,38 @@ fn destroy_alpha_out_refund_gating_by_registration_block() { // Lock and (nonzero) emissions let lock_u64: u64 = 50_000; SubtensorModule::set_subnet_locked_balance(netuid, TaoCurrency::from(lock_u64)); - Emission::::insert( - netuid, - vec![AlphaCurrency::from(1_500u64), AlphaCurrency::from(3_000u64)], // total 4_500 α - ); // Owner cut ≈ 50% SubnetOwnerCut::::put(32_768u16); + // give some stake to other key + let other_cold = U256::from(1_234); + let other_hot = U256::from(2_345); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &other_hot, + &other_cold, + netuid, + AlphaCurrency::from(30u64), // not nearly enough to cover the lock + ); + + // ensure there was some Alpha issued + assert!(SubtensorModule::get_alpha_issuance(netuid).to_u64() > 0); + // Compute expected refund using the same math as the pallet let frac: U96F32 = SubtensorModule::get_float_subnet_owner_cut(); - let total_emitted_alpha: u64 = 1_500 + 3_000; // 4_500 α + let total_emitted_alpha: u64 = SubtensorModule::get_alpha_issuance(netuid).to_u64(); let owner_alpha_u64: u64 = U96F32::from_num(total_emitted_alpha) .saturating_mul(frac) .floor() .saturating_to_num::(); - // Prefer sim_swap; fall back to current price if unavailable. - let order = GetTaoForAlpha::::with_amount(owner_alpha_u64); - let owner_emission_tao_u64 = - ::SwapInterface::sim_swap(netuid.into(), order) - .map(|res| res.amount_paid_out.to_u64()) - .unwrap_or_else(|_| { - let price: U96F32 = - ::SwapInterface::current_alpha_price(netuid.into()); - U96F32::from_num(owner_alpha_u64) - .saturating_mul(price) - .floor() - .saturating_to_num::() - }); + let owner_emission_tao_u64 = { + let price: U96F32 = + ::SwapInterface::current_alpha_price(netuid.into()); + U96F32::from_num(owner_alpha_u64) + .saturating_mul(price) + .floor() + .saturating_to_num::() + }; let expected_refund: u64 = lock_u64.saturating_sub(owner_emission_tao_u64); @@ -1011,7 +1152,17 @@ fn destroy_alpha_out_refund_gating_by_registration_block() { // Lock and emissions present (should be ignored for refund) let lock_u64: u64 = 42_000; SubtensorModule::set_subnet_locked_balance(netuid, TaoCurrency::from(lock_u64)); - Emission::::insert(netuid, vec![AlphaCurrency::from(5_000u64)]); + // give some stake to other key + let other_cold = U256::from(1_234); + let other_hot = U256::from(2_345); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &other_hot, + &other_cold, + netuid, + AlphaCurrency::from(300u64), // not nearly enough to cover the lock + ); + // ensure there was some Alpha issued + assert!(SubtensorModule::get_alpha_issuance(netuid).to_u64() > 0); SubnetOwnerCut::::put(32_768u16); // ~50% // Balances before @@ -1046,7 +1197,9 @@ fn destroy_alpha_out_refund_gating_by_registration_block() { // lock = 0; emissions present (must not matter) SubtensorModule::set_subnet_locked_balance(netuid, TaoCurrency::from(0u64)); - Emission::::insert(netuid, vec![AlphaCurrency::from(10_000u64)]); + SubnetAlphaOut::::insert(netuid, AlphaCurrency::from(10_000)); + // ensure there was some Alpha issued + assert!(SubtensorModule::get_alpha_issuance(netuid).to_u64() > 0); SubnetOwnerCut::::put(32_768u16); // ~50% let owner_before = SubtensorModule::get_coldkey_balance(&owner_cold); @@ -1144,6 +1297,27 @@ fn prune_tie_on_price_earlier_registration_wins() { }); } +#[test] +fn prune_prefers_higher_priority_over_price() { + new_test_ext(0).execute_with(|| { + let n1 = add_dynamic_network(&U256::from(210), &U256::from(201)); + let n2 = add_dynamic_network(&U256::from(420), &U256::from(401)); + + let imm = SubtensorModule::get_network_immunity_period(); + System::set_block_number(imm + 5); + + SubnetMovingPrice::::insert(n1, I96F32::from_num(100)); + SubnetMovingPrice::::insert(n2, I96F32::from_num(1)); + + SubnetDeregistrationPriorityQueue::::mutate(|queue| { + queue.push(n1); + queue.push(n2); + }); + + assert_eq!(SubtensorModule::get_network_to_prune(), Some(n1)); + }); +} + #[test] fn prune_selection_complex_state_exhaustive() { new_test_ext(0).execute_with(|| { diff --git a/pallets/subtensor/src/tests/subnet_emissions.rs b/pallets/subtensor/src/tests/subnet_emissions.rs index aeece09c2e..311a930647 100644 --- a/pallets/subtensor/src/tests/subnet_emissions.rs +++ b/pallets/subtensor/src/tests/subnet_emissions.rs @@ -151,143 +151,137 @@ fn inplace_pow_normalize_fractional_exponent() { }) } -/// Normal (moderate, non-zero) EMA flows across 3 subnets. -/// Expect: shares sum to ~1 and are monotonic with flows. -#[test] -fn get_shares_normal_flows_three_subnets() { - new_test_ext(1).execute_with(|| { - let owner_hotkey = U256::from(10); - let owner_coldkey = U256::from(20); - - let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - let n3 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - - // 100% tao flow method - let block_num = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); - System::set_block_number(block_num); - - // Set (block_number, flow) with reasonable positive flows - SubnetEmaTaoFlow::::insert(n1, (block_num, i64f64(1_000.0))); - SubnetEmaTaoFlow::::insert(n2, (block_num, i64f64(3_000.0))); - SubnetEmaTaoFlow::::insert(n3, (block_num, i64f64(6_000.0))); - - let subnets = vec![n1, n2, n3]; - let shares = SubtensorModule::get_shares(&subnets); - - // Sum ≈ 1 - let sum: f64 = shares.values().map(|v| v.to_num::()).sum(); - assert_abs_diff_eq!(sum, 1.0_f64, epsilon = 1e-9); - - // Each share in [0,1] and finite - for (k, v) in &shares { - let f = v.to_num::(); - assert!(f.is_finite(), "share for {k:?} not finite"); - assert!( - (0.0..=1.0).contains(&f), - "share for {k:?} out of [0,1]: {f}" - ); - } - - // Monotonicity with the flows: share(n3) > share(n2) > share(n1) - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - let s3 = shares.get(&n3).unwrap().to_num::(); - assert!( - s3 > s2 && s2 > s1, - "expected s3 > s2 > s1; got {s1}, {s2}, {s3}" - ); - }); -} - -/// Very low (but non-zero) EMA flows across 2 subnets. -/// Expect: shares sum to ~1 and higher-flow subnet gets higher share. -#[test] -fn get_shares_low_flows_sum_one_and_ordering() { - new_test_ext(1).execute_with(|| { - let owner_hotkey = U256::from(11); - let owner_coldkey = U256::from(21); - - let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - - // 100% tao flow method - let block_num = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); - System::set_block_number(block_num); - - // Tiny flows to exercise precision/scaling path - SubnetEmaTaoFlow::::insert(n1, (block_num, i64f64(1e-9))); - SubnetEmaTaoFlow::::insert(n2, (block_num, i64f64(2e-9))); - - let subnets = vec![n1, n2]; - let shares = SubtensorModule::get_shares(&subnets); - - let sum: f64 = shares.values().map(|v| v.to_num::()).sum(); - assert_abs_diff_eq!(sum, 1.0_f64, epsilon = 1e-8); - - for (k, v) in &shares { - let f = v.to_num::(); - assert!(f.is_finite(), "share for {k:?} not finite"); - assert!( - (0.0..=1.0).contains(&f), - "share for {k:?} out of [0,1]: {f}" - ); - } - - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - assert!( - s2 > s1, - "expected s2 > s1 with higher flow; got s1={s1}, s2={s2}" - ); - }); -} - -/// High EMA flows across 2 subnets. -/// Expect: no overflow, shares sum to ~1, and ordering follows flows. -#[test] -fn get_shares_high_flows_sum_one_and_ordering() { - new_test_ext(1).execute_with(|| { - let owner_hotkey = U256::from(12); - let owner_coldkey = U256::from(22); - - let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - - // 100% tao flow method - let block_num = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); - System::set_block_number(block_num); - - // Large but safe flows for I64F64 - SubnetEmaTaoFlow::::insert(n1, (block_num, i64f64(9.0e11))); - SubnetEmaTaoFlow::::insert(n2, (block_num, i64f64(1.8e12))); - - let subnets = vec![n1, n2]; - let shares = SubtensorModule::get_shares(&subnets); - - let sum: f64 = shares.values().map(|v| v.to_num::()).sum(); - assert_abs_diff_eq!(sum, 1.0_f64, epsilon = 1e-9); - - for (k, v) in &shares { - let f = v.to_num::(); - assert!(f.is_finite(), "share for {k:?} not finite"); - assert!( - (0.0..=1.0).contains(&f), - "share for {k:?} out of [0,1]: {f}" - ); - } - - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - assert!( - s2 > s1, - "expected s2 > s1 with higher flow; got s1={s1}, s2={s2}" - ); - }); -} +// /// Normal (moderate, non-zero) EMA flows across 3 subnets. +// /// Expect: shares sum to ~1 and are monotonic with flows. +// #[test] +// fn get_shares_normal_flows_three_subnets() { +// new_test_ext(1).execute_with(|| { +// let owner_hotkey = U256::from(10); +// let owner_coldkey = U256::from(20); + +// let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); +// let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); +// let n3 = add_dynamic_network(&owner_hotkey, &owner_coldkey); + +// let block_num = FlowHalfLife::::get(); +// System::set_block_number(block_num); + +// // Set (block_number, flow) with reasonable positive flows +// SubnetEmaTaoFlow::::insert(n1, (block_num, i64f64(1_000.0))); +// SubnetEmaTaoFlow::::insert(n2, (block_num, i64f64(3_000.0))); +// SubnetEmaTaoFlow::::insert(n3, (block_num, i64f64(6_000.0))); + +// let subnets = vec![n1, n2, n3]; +// let shares = SubtensorModule::get_shares(&subnets); + +// // Sum ≈ 1 +// let sum: f64 = shares.values().map(|v| v.to_num::()).sum(); +// assert_abs_diff_eq!(sum, 1.0_f64, epsilon = 1e-9); + +// // Each share in [0,1] and finite +// for (k, v) in &shares { +// let f = v.to_num::(); +// assert!(f.is_finite(), "share for {k:?} not finite"); +// assert!( +// (0.0..=1.0).contains(&f), +// "share for {k:?} out of [0,1]: {f}" +// ); +// } + +// // Monotonicity with the flows: share(n3) > share(n2) > share(n1) +// let s1 = shares.get(&n1).unwrap().to_num::(); +// let s2 = shares.get(&n2).unwrap().to_num::(); +// let s3 = shares.get(&n3).unwrap().to_num::(); +// assert!( +// s3 > s2 && s2 > s1, +// "expected s3 > s2 > s1; got {s1}, {s2}, {s3}" +// ); +// }); +// } + +// /// Very low (but non-zero) EMA flows across 2 subnets. +// /// Expect: shares sum to ~1 and higher-flow subnet gets higher share. +// #[test] +// fn get_shares_low_flows_sum_one_and_ordering() { +// new_test_ext(1).execute_with(|| { +// let owner_hotkey = U256::from(11); +// let owner_coldkey = U256::from(21); + +// let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); +// let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); + +// let block_num = FlowHalfLife::::get(); +// System::set_block_number(block_num); + +// // Tiny flows to exercise precision/scaling path +// SubnetEmaTaoFlow::::insert(n1, (block_num, i64f64(1e-9))); +// SubnetEmaTaoFlow::::insert(n2, (block_num, i64f64(2e-9))); + +// let subnets = vec![n1, n2]; +// let shares = SubtensorModule::get_shares(&subnets); + +// let sum: f64 = shares.values().map(|v| v.to_num::()).sum(); +// assert_abs_diff_eq!(sum, 1.0_f64, epsilon = 1e-8); + +// for (k, v) in &shares { +// let f = v.to_num::(); +// assert!(f.is_finite(), "share for {k:?} not finite"); +// assert!( +// (0.0..=1.0).contains(&f), +// "share for {k:?} out of [0,1]: {f}" +// ); +// } + +// let s1 = shares.get(&n1).unwrap().to_num::(); +// let s2 = shares.get(&n2).unwrap().to_num::(); +// assert!( +// s2 > s1, +// "expected s2 > s1 with higher flow; got s1={s1}, s2={s2}" +// ); +// }); +// } + +// /// High EMA flows across 2 subnets. +// /// Expect: no overflow, shares sum to ~1, and ordering follows flows. +// #[test] +// fn get_shares_high_flows_sum_one_and_ordering() { +// new_test_ext(1).execute_with(|| { +// let owner_hotkey = U256::from(12); +// let owner_coldkey = U256::from(22); + +// let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); +// let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); + +// let block_num = FlowHalfLife::::get(); +// System::set_block_number(block_num); + +// // Large but safe flows for I64F64 +// SubnetEmaTaoFlow::::insert(n1, (block_num, i64f64(9.0e11))); +// SubnetEmaTaoFlow::::insert(n2, (block_num, i64f64(1.8e12))); + +// let subnets = vec![n1, n2]; +// let shares = SubtensorModule::get_shares(&subnets); + +// let sum: f64 = shares.values().map(|v| v.to_num::()).sum(); +// assert_abs_diff_eq!(sum, 1.0_f64, epsilon = 1e-9); + +// for (k, v) in &shares { +// let f = v.to_num::(); +// assert!(f.is_finite(), "share for {k:?} not finite"); +// assert!( +// (0.0..=1.0).contains(&f), +// "share for {k:?} out of [0,1]: {f}" +// ); +// } + +// let s1 = shares.get(&n1).unwrap().to_num::(); +// let s2 = shares.get(&n2).unwrap().to_num::(); +// assert!( +// s2 > s1, +// "expected s2 > s1 with higher flow; got s1={s1}, s2={s2}" +// ); +// }); +// } /// Helper to (re)seed EMA price & flow at the *current* block. fn seed_price_and_flow(n1: NetUid, n2: NetUid, price1: f64, price2: f64, flow1: f64, flow2: f64) { @@ -298,293 +292,199 @@ fn seed_price_and_flow(n1: NetUid, n2: NetUid, price1: f64, price2: f64, flow1: SubnetEmaTaoFlow::::insert(n2, (now, i64f64(flow2))); } -#[test] -fn get_shares_price_flow_blend_1v3_price_and_3v1_flow() { - new_test_ext(1).execute_with(|| { - // two subnets - let owner_hotkey = U256::from(42); - let owner_coldkey = U256::from(43); - let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - - // define "window" length half_life blocks and set first block to 0 - let half_life: u64 = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); - FlowNormExponent::::set(u64f64(1.0)); - - // t = 0: expect (0.25, 0.75) - frame_system::Pallet::::set_block_number(0); - seed_price_and_flow(n1, n2, /*price*/ 1.0, 3.0, /*flow*/ 3.0, 1.0); - let shares = SubtensorModule::get_shares(&[n1, n2]); - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - assert_abs_diff_eq!(s1 + s2, 1.0_f64, epsilon = 1e-9); - assert_abs_diff_eq!(s1, 0.25_f64, epsilon = 1e-6); - assert_abs_diff_eq!(s2, 0.75_f64, epsilon = 1e-6); - - // t = half_life/2: expect (0.5, 0.5) - frame_system::Pallet::::set_block_number(half_life / 2); - seed_price_and_flow(n1, n2, 1.0, 3.0, 3.0, 1.0); - let shares = SubtensorModule::get_shares(&[n1, n2]); - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - assert_abs_diff_eq!(s1 + s2, 1.0_f64, epsilon = 1e-9); - assert_abs_diff_eq!(s1, 0.5_f64, epsilon = 1e-6); - assert_abs_diff_eq!(s2, 0.5_f64, epsilon = 1e-6); - - // t = half_life: expect (0.75, 0.25) - frame_system::Pallet::::set_block_number(half_life); - seed_price_and_flow(n1, n2, 1.0, 3.0, 3.0, 1.0); - let shares = SubtensorModule::get_shares(&[n1, n2]); - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - assert_abs_diff_eq!(s1 + s2, 1.0_f64, epsilon = 1e-9); - assert_abs_diff_eq!(s1, 0.75_f64, epsilon = 1e-6); - assert_abs_diff_eq!(s2, 0.25_f64, epsilon = 1e-6); - }); -} - -#[test] -fn get_shares_price_flow_blend_3v1_price_and_1v3_flow() { - new_test_ext(1).execute_with(|| { - let owner_hotkey = U256::from(50); - let owner_coldkey = U256::from(51); - let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - - // window half_life and anchor at 0 - let half_life: u64 = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); - FlowNormExponent::::set(u64f64(1.0)); - - // t = 0: prices dominate → (0.75, 0.25) - frame_system::Pallet::::set_block_number(0); - seed_price_and_flow(n1, n2, /*price*/ 3.0, 1.0, /*flow*/ 1.0, 3.0); - let shares = SubtensorModule::get_shares(&[n1, n2]); - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - assert_abs_diff_eq!(s1 + s2, 1.0_f64, epsilon = 1e-9); - assert_abs_diff_eq!(s1, 0.75_f64, epsilon = 1e-6); - assert_abs_diff_eq!(s2, 0.25_f64, epsilon = 1e-6); - - // t = half_life/2: equal → (0.5, 0.5) - frame_system::Pallet::::set_block_number(half_life / 2); - seed_price_and_flow(n1, n2, 3.0, 1.0, 1.0, 3.0); - let shares = SubtensorModule::get_shares(&[n1, n2]); - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - assert_abs_diff_eq!(s1, 0.5_f64, epsilon = 1e-6); - assert_abs_diff_eq!(s2, 0.5_f64, epsilon = 1e-6); - - // t = half_life: flows dominate → (0.25, 0.75) - frame_system::Pallet::::set_block_number(half_life); - seed_price_and_flow(n1, n2, 3.0, 1.0, 1.0, 3.0); - let shares = SubtensorModule::get_shares(&[n1, n2]); - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - assert_abs_diff_eq!(s1, 0.25_f64, epsilon = 1e-6); - assert_abs_diff_eq!(s2, 0.75_f64, epsilon = 1e-6); - }); -} - -/// If one subnet has a negative EMA flow and the other positive, -/// the negative one should contribute no weight (treated as zero), -/// so the positive-flow subnet gets the full share. -#[test] -fn get_shares_negative_vs_positive_flow() { - new_test_ext(1).execute_with(|| { - // 2 subnets - let owner_hotkey = U256::from(60); - let owner_coldkey = U256::from(61); - let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - - // Configure blending window and current block - let half_life: u64 = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); - FlowNormExponent::::set(u64f64(1.0)); - frame_system::Pallet::::set_block_number(half_life); - TaoFlowCutoff::::set(I64F64::from_num(0)); - - // Equal EMA prices so price side doesn't bias - SubnetMovingPrice::::insert(n1, i96f32(1.0)); - SubnetMovingPrice::::insert(n2, i96f32(1.0)); - - // Set flows: n1 negative, n2 positive - let now = frame_system::Pallet::::block_number(); - SubnetEmaTaoFlow::::insert(n1, (now, i64f64(-100.0))); - SubnetEmaTaoFlow::::insert(n2, (now, i64f64(500.0))); - - let shares = SubtensorModule::get_shares(&[n1, n2]); - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - - // Sum ~ 1 - assert_abs_diff_eq!(s1 + s2, 1.0_f64, epsilon = 1e-9); - // Negative flow subnet should not get weight from flow; with equal prices mid-window, - // positive-flow subnet should dominate and get all the allocation. - assert!( - s2 > 0.999_999 && s1 < 1e-6, - "expected s2≈1, s1≈0; got s1={s1}, s2={s2}" - ); - }); -} - -/// If both subnets have negative EMA flows, flows should contribute zero weight -#[test] -fn get_shares_both_negative_flows_zero_emission() { - new_test_ext(1).execute_with(|| { - // 2 subnets - let owner_hotkey = U256::from(60); - let owner_coldkey = U256::from(61); - let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - - // Configure blending window and current block - let half_life: u64 = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); - FlowNormExponent::::set(u64f64(1.0)); - frame_system::Pallet::::set_block_number(half_life); - TaoFlowCutoff::::set(I64F64::from_num(0)); - - // Equal EMA prices so price side doesn't bias - SubnetMovingPrice::::insert(n1, i96f32(1.0)); - SubnetMovingPrice::::insert(n2, i96f32(1.0)); - - // Set flows - let now = frame_system::Pallet::::block_number(); - SubnetEmaTaoFlow::::insert(n1, (now, i64f64(-100.0))); - SubnetEmaTaoFlow::::insert(n2, (now, i64f64(-200.0))); - - let shares = SubtensorModule::get_shares(&[n1, n2]); - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - - assert!( - s1 < 1e-20 && s2 < 1e-20, - "expected s2≈0, s1≈0; got s1={s1}, s2={s2}" - ); - }); -} - -/// If both subnets have positive EMA flows lower than or equal to cutoff, flows should contribute zero weight -#[test] -fn get_shares_both_below_cutoff_zero_emission() { - new_test_ext(1).execute_with(|| { - // 2 subnets - let owner_hotkey = U256::from(60); - let owner_coldkey = U256::from(61); - let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - - // Configure blending window and current block - let half_life: u64 = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); - FlowNormExponent::::set(u64f64(1.0)); - frame_system::Pallet::::set_block_number(half_life); - TaoFlowCutoff::::set(I64F64::from_num(2_000)); - - // Equal EMA prices so price side doesn't bias - SubnetMovingPrice::::insert(n1, i96f32(1.0)); - SubnetMovingPrice::::insert(n2, i96f32(1.0)); - - // Set flows - let now = frame_system::Pallet::::block_number(); - SubnetEmaTaoFlow::::insert(n1, (now, i64f64(1000.0))); - SubnetEmaTaoFlow::::insert(n2, (now, i64f64(2000.0))); - - let shares = SubtensorModule::get_shares(&[n1, n2]); - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - - assert!( - s1 < 1e-20 && s2 < 1e-20, - "expected s2≈0, s1≈0; got s1={s1}, s2={s2}" - ); - }); -} - -/// If one subnet has positive EMA flow lower than cutoff, the other gets full emission -#[test] -fn get_shares_one_below_cutoff_other_full_emission() { - new_test_ext(1).execute_with(|| { - [(1000.0, 2000.00001), (1000.0, 2000.001), (1000.0, 5000.0)] - .into_iter() - .for_each(|(flow1, flow2)| { - // 2 subnets - let owner_hotkey = U256::from(60); - let owner_coldkey = U256::from(61); - let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - - // Configure blending window and current block - let half_life: u64 = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); - FlowNormExponent::::set(u64f64(1.0)); - frame_system::Pallet::::set_block_number(half_life); - TaoFlowCutoff::::set(I64F64::from_num(2_000)); - - // Equal EMA prices (price side doesn't bias) - SubnetMovingPrice::::insert(n1, i96f32(1.0)); - SubnetMovingPrice::::insert(n2, i96f32(1.0)); - - // Set flows - let now = frame_system::Pallet::::block_number(); - SubnetEmaTaoFlow::::insert(n1, (now, i64f64(flow1))); - SubnetEmaTaoFlow::::insert(n2, (now, i64f64(flow2))); - - let shares = SubtensorModule::get_shares(&[n1, n2]); - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - - // Sum ~ 1 - assert_abs_diff_eq!(s1 + s2, 1.0_f64, epsilon = 1e-9); - assert!( - s2 > 0.999_999 && s1 < 1e-6, - "expected s2≈1, s1≈0; got s1={s1}, s2={s2}" - ); - }); - }); -} - -/// If subnets have negative EMA flows, but they are above the cut-off, emissions are proportional -/// for all except the bottom one, which gets nothing -#[test] -fn get_shares_both_negative_above_cutoff() { - new_test_ext(1).execute_with(|| { - // 2 subnets - let owner_hotkey = U256::from(60); - let owner_coldkey = U256::from(61); - let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - let n3 = add_dynamic_network(&owner_hotkey, &owner_coldkey); - - // Configure blending window and current block - let half_life: u64 = FlowHalfLife::::get(); - FlowFirstBlock::::set(Some(0_u64)); - FlowNormExponent::::set(u64f64(1.0)); - frame_system::Pallet::::set_block_number(half_life); - TaoFlowCutoff::::set(I64F64::from_num(-1000.0)); - - // Equal EMA prices so price side doesn't bias - SubnetMovingPrice::::insert(n1, i96f32(1.0)); - SubnetMovingPrice::::insert(n2, i96f32(1.0)); - SubnetMovingPrice::::insert(n3, i96f32(1.0)); - - // Set flows - let now = frame_system::Pallet::::block_number(); - SubnetEmaTaoFlow::::insert(n1, (now, i64f64(-100.0))); - SubnetEmaTaoFlow::::insert(n2, (now, i64f64(-300.0))); - SubnetEmaTaoFlow::::insert(n3, (now, i64f64(-400.0))); - - let shares = SubtensorModule::get_shares(&[n1, n2, n3]); - let s1 = shares.get(&n1).unwrap().to_num::(); - let s2 = shares.get(&n2).unwrap().to_num::(); - let s3 = shares.get(&n3).unwrap().to_num::(); - - assert_abs_diff_eq!(s1, 0.75, epsilon = s1 / 100.0); - assert_abs_diff_eq!(s2, 0.25, epsilon = s2 / 100.0); - assert_abs_diff_eq!(s3, 0.0, epsilon = 1e-9); - assert_abs_diff_eq!(s1 + s2 + s3, 1.0, epsilon = 1e-9); - }); -} +// /// If one subnet has a negative EMA flow and the other positive, +// /// the negative one should contribute no weight (treated as zero), +// /// so the positive-flow subnet gets the full share. +// #[test] +// fn get_shares_negative_vs_positive_flow() { +// new_test_ext(1).execute_with(|| { +// // 2 subnets +// let owner_hotkey = U256::from(60); +// let owner_coldkey = U256::from(61); +// let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); +// let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); + +// // Configure blending window and current block +// let half_life: u64 = FlowHalfLife::::get(); +// FlowNormExponent::::set(u64f64(1.0)); +// frame_system::Pallet::::set_block_number(half_life); +// TaoFlowCutoff::::set(I64F64::from_num(0)); + +// // Equal EMA prices so price side doesn't bias +// SubnetMovingPrice::::insert(n1, i96f32(1.0)); +// SubnetMovingPrice::::insert(n2, i96f32(1.0)); + +// // Set flows: n1 negative, n2 positive +// let now = frame_system::Pallet::::block_number(); +// SubnetEmaTaoFlow::::insert(n1, (now, i64f64(-100.0))); +// SubnetEmaTaoFlow::::insert(n2, (now, i64f64(500.0))); + +// let shares = SubtensorModule::get_shares(&[n1, n2]); +// let s1 = shares.get(&n1).unwrap().to_num::(); +// let s2 = shares.get(&n2).unwrap().to_num::(); + +// // Sum ~ 1 +// assert_abs_diff_eq!(s1 + s2, 1.0_f64, epsilon = 1e-9); +// // Negative flow subnet should not get weight from flow; with equal prices mid-window, +// // positive-flow subnet should dominate and get all the allocation. +// assert!( +// s2 > 0.999_999 && s1 < 1e-6, +// "expected s2≈1, s1≈0; got s1={s1}, s2={s2}" +// ); +// }); +// } + +// /// If both subnets have negative EMA flows, flows should contribute zero weight +// #[test] +// fn get_shares_both_negative_flows_zero_emission() { +// new_test_ext(1).execute_with(|| { +// // 2 subnets +// let owner_hotkey = U256::from(60); +// let owner_coldkey = U256::from(61); +// let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); +// let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); + +// // Configure blending window and current block +// let half_life: u64 = FlowHalfLife::::get(); +// FlowNormExponent::::set(u64f64(1.0)); +// frame_system::Pallet::::set_block_number(half_life); +// TaoFlowCutoff::::set(I64F64::from_num(0)); + +// // Equal EMA prices so price side doesn't bias +// SubnetMovingPrice::::insert(n1, i96f32(1.0)); +// SubnetMovingPrice::::insert(n2, i96f32(1.0)); + +// // Set flows +// let now = frame_system::Pallet::::block_number(); +// SubnetEmaTaoFlow::::insert(n1, (now, i64f64(-100.0))); +// SubnetEmaTaoFlow::::insert(n2, (now, i64f64(-200.0))); + +// let shares = SubtensorModule::get_shares(&[n1, n2]); +// let s1 = shares.get(&n1).unwrap().to_num::(); +// let s2 = shares.get(&n2).unwrap().to_num::(); + +// assert!( +// s1 < 1e-20 && s2 < 1e-20, +// "expected s2≈0, s1≈0; got s1={s1}, s2={s2}" +// ); +// }); +// } + +// /// If both subnets have positive EMA flows lower than or equal to cutoff, flows should contribute zero weight +// #[test] +// fn get_shares_both_below_cutoff_zero_emission() { +// new_test_ext(1).execute_with(|| { +// // 2 subnets +// let owner_hotkey = U256::from(60); +// let owner_coldkey = U256::from(61); +// let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); +// let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); + +// // Configure blending window and current block +// let half_life: u64 = FlowHalfLife::::get(); +// FlowNormExponent::::set(u64f64(1.0)); +// frame_system::Pallet::::set_block_number(half_life); +// TaoFlowCutoff::::set(I64F64::from_num(2_000)); + +// // Equal EMA prices so price side doesn't bias +// SubnetMovingPrice::::insert(n1, i96f32(1.0)); +// SubnetMovingPrice::::insert(n2, i96f32(1.0)); + +// // Set flows +// let now = frame_system::Pallet::::block_number(); +// SubnetEmaTaoFlow::::insert(n1, (now, i64f64(1000.0))); +// SubnetEmaTaoFlow::::insert(n2, (now, i64f64(2000.0))); + +// let shares = SubtensorModule::get_shares(&[n1, n2]); +// let s1 = shares.get(&n1).unwrap().to_num::(); +// let s2 = shares.get(&n2).unwrap().to_num::(); + +// assert!( +// s1 < 1e-20 && s2 < 1e-20, +// "expected s2≈0, s1≈0; got s1={s1}, s2={s2}" +// ); +// }); +// } + +// /// If one subnet has positive EMA flow lower than cutoff, the other gets full emission +// #[test] +// fn get_shares_one_below_cutoff_other_full_emission() { +// new_test_ext(1).execute_with(|| { +// [(1000.0, 2000.00001), (1000.0, 2000.001), (1000.0, 5000.0)] +// .into_iter() +// .for_each(|(flow1, flow2)| { +// // 2 subnets +// let owner_hotkey = U256::from(60); +// let owner_coldkey = U256::from(61); +// let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); +// let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); + +// // Configure blending window and current block +// let half_life: u64 = FlowHalfLife::::get(); +// FlowNormExponent::::set(u64f64(1.0)); +// frame_system::Pallet::::set_block_number(half_life); +// TaoFlowCutoff::::set(I64F64::from_num(2_000)); + +// // Equal EMA prices (price side doesn't bias) +// SubnetMovingPrice::::insert(n1, i96f32(1.0)); +// SubnetMovingPrice::::insert(n2, i96f32(1.0)); + +// // Set flows +// let now = frame_system::Pallet::::block_number(); +// SubnetEmaTaoFlow::::insert(n1, (now, i64f64(flow1))); +// SubnetEmaTaoFlow::::insert(n2, (now, i64f64(flow2))); + +// let shares = SubtensorModule::get_shares(&[n1, n2]); +// let s1 = shares.get(&n1).unwrap().to_num::(); +// let s2 = shares.get(&n2).unwrap().to_num::(); + +// // Sum ~ 1 +// assert_abs_diff_eq!(s1 + s2, 1.0_f64, epsilon = 1e-9); +// assert!( +// s2 > 0.999_999 && s1 < 1e-6, +// "expected s2≈1, s1≈0; got s1={s1}, s2={s2}" +// ); +// }); +// }); +// } + +// /// If subnets have negative EMA flows, but they are above the cut-off, emissions are proportional +// /// for all except the bottom one, which gets nothing +// #[test] +// fn get_shares_both_negative_above_cutoff() { +// new_test_ext(1).execute_with(|| { +// // 2 subnets +// let owner_hotkey = U256::from(60); +// let owner_coldkey = U256::from(61); +// let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey); +// let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey); +// let n3 = add_dynamic_network(&owner_hotkey, &owner_coldkey); + +// // Configure blending window and current block +// let half_life: u64 = FlowHalfLife::::get(); +// FlowNormExponent::::set(u64f64(1.0)); +// frame_system::Pallet::::set_block_number(half_life); +// TaoFlowCutoff::::set(I64F64::from_num(-1000.0)); + +// // Equal EMA prices so price side doesn't bias +// SubnetMovingPrice::::insert(n1, i96f32(1.0)); +// SubnetMovingPrice::::insert(n2, i96f32(1.0)); +// SubnetMovingPrice::::insert(n3, i96f32(1.0)); + +// // Set flows +// let now = frame_system::Pallet::::block_number(); +// SubnetEmaTaoFlow::::insert(n1, (now, i64f64(-100.0))); +// SubnetEmaTaoFlow::::insert(n2, (now, i64f64(-300.0))); +// SubnetEmaTaoFlow::::insert(n3, (now, i64f64(-400.0))); + +// let shares = SubtensorModule::get_shares(&[n1, n2, n3]); +// let s1 = shares.get(&n1).unwrap().to_num::(); +// let s2 = shares.get(&n2).unwrap().to_num::(); +// let s3 = shares.get(&n3).unwrap().to_num::(); + +// assert_abs_diff_eq!(s1, 0.75, epsilon = s1 / 100.0); +// assert_abs_diff_eq!(s2, 0.25, epsilon = s2 / 100.0); +// assert_abs_diff_eq!(s3, 0.0, epsilon = 1e-9); +// assert_abs_diff_eq!(s1 + s2 + s3, 1.0, epsilon = 1e-9); +// }); +// } diff --git a/pallets/subtensor/src/tests/weights.rs b/pallets/subtensor/src/tests/weights.rs index a71a225dc7..21d37984e4 100644 --- a/pallets/subtensor/src/tests/weights.rs +++ b/pallets/subtensor/src/tests/weights.rs @@ -5016,7 +5016,7 @@ fn test_reveal_crv3_commits_cannot_reveal_after_reveal_epoch() { step_epochs(1, netuid); // Attempt to reveal commits after the reveal epoch has passed - assert_ok!(SubtensorModule::reveal_crv3_commits(netuid)); + assert_ok!(SubtensorModule::reveal_crv3_commits_for_subnet(netuid)); // Verify that the weights for the neuron have not been set let weights_sparse = SubtensorModule::get_weights_sparse(netuid.into()); @@ -5353,7 +5353,7 @@ fn test_reveal_crv3_commits_decryption_failure() { }, ); - assert_ok!(SubtensorModule::reveal_crv3_commits(netuid)); + assert_ok!(SubtensorModule::reveal_crv3_commits_for_subnet(netuid)); let neuron_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey) .expect("Failed to get neuron UID for hotkey") as usize; @@ -5973,7 +5973,7 @@ fn test_reveal_crv3_commits_removes_past_epoch_commits() { // --------------------------------------------------------------------- // Run the reveal pass WITHOUT a pulse – only expiry housekeeping runs. // --------------------------------------------------------------------- - assert_ok!(SubtensorModule::reveal_crv3_commits(netuid)); + assert_ok!(SubtensorModule::reveal_crv3_commits_for_subnet(netuid)); // past_epoch (< reveal_epoch) must be gone assert!( diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 0ba3df1103..53cab078c4 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -42,23 +42,16 @@ impl Pallet { netuid: NetUid, ) -> Result<(), DispatchError> { ensure_root(o)?; - let now = Self::get_current_block_as_u64(); - Self::ensure_not_in_admin_freeze_window(netuid, now)?; - Ok(()) + Self::ensure_admin_window_open(netuid) } /// Ensure owner-or-root with a set of TransactionType rate checks (owner only). - /// - Root: only freeze window is enforced; no TransactionType checks. - /// - Owner (Signed): freeze window plus all rate checks in `limits` using signer extracted from - /// origin. pub fn ensure_sn_owner_or_root_with_limits( o: T::RuntimeOrigin, netuid: NetUid, limits: &[crate::utils::rate_limiting::TransactionType], ) -> Result, DispatchError> { let maybe_who = Self::ensure_subnet_owner_or_root(o, netuid)?; - let now = Self::get_current_block_as_u64(); - Self::ensure_not_in_admin_freeze_window(netuid, now)?; if let Some(who) = maybe_who.as_ref() { for tx in limits.iter() { ensure!( @@ -70,26 +63,6 @@ impl Pallet { Ok(maybe_who) } - /// Ensure the caller is the subnet owner and passes all provided rate limits. - /// This does NOT allow root; it is strictly owner-only. - /// Returns the signer (owner) on success so callers may record last-blocks. - pub fn ensure_sn_owner_with_limits( - o: T::RuntimeOrigin, - netuid: NetUid, - limits: &[crate::utils::rate_limiting::TransactionType], - ) -> Result { - let who = Self::ensure_subnet_owner(o, netuid)?; - let now = Self::get_current_block_as_u64(); - Self::ensure_not_in_admin_freeze_window(netuid, now)?; - for tx in limits.iter() { - ensure!( - tx.passes_rate_limit_on_subnet::(&who, netuid), - Error::::TxRateLimitExceeded - ); - } - Ok(who) - } - /// Returns true if the current block is within the terminal freeze window of the tempo for the /// given subnet. During this window, admin ops are prohibited to avoid interference with /// validator weight submissions. @@ -103,7 +76,9 @@ impl Pallet { remaining < window } - fn ensure_not_in_admin_freeze_window(netuid: NetUid, now: u64) -> Result<(), DispatchError> { + /// Ensures the admin freeze window is not currently active for the given subnet. + pub fn ensure_admin_window_open(netuid: NetUid) -> Result<(), DispatchError> { + let now = Self::get_current_block_as_u64(); ensure!( !Self::is_in_admin_freeze_window(netuid, now), Error::::AdminActionProhibitedDuringWeightsWindow @@ -121,6 +96,52 @@ impl Pallet { Self::deposit_event(Event::OwnerHyperparamRateLimitSet(epochs)); } + pub fn cancel_deregistration_priority_schedule_for_owner(owner: &T::AccountId) { + let nets: sp_std::vec::Vec = SubnetOwner::::iter() + .filter_map(|(netuid, acct)| if acct == *owner { Some(netuid) } else { None }) + .collect(); + + for netuid in nets { + if SubnetDeregistrationPrioritySchedule::::take(netuid).is_some() { + Self::deposit_event(Event::SubnetDeregistrationPriorityScheduleRemoved(netuid)); + } + } + } + + pub fn enqueue_subnet_to_deregistration_priority_queue(netuid: NetUid) { + SubnetDeregistrationPriorityQueue::::mutate(|queue| { + if !queue.contains(&netuid) { + queue.push(netuid); + } + }) + } + + pub fn remove_subnet_from_deregistration_priority_queue(netuid: NetUid) -> bool { + SubnetDeregistrationPriorityQueue::::mutate(|queue| { + let original_len = queue.len(); + queue.retain(|&existing| existing != netuid); + original_len != queue.len() + }) + } + + pub fn pop_ready_subnet_from_deregistration_priority_queue() -> Option { + SubnetDeregistrationPriorityQueue::::mutate(|queue| { + let first_valid = queue + .iter() + .copied() + .enumerate() + .find(|&(_, netuid)| Self::if_subnet_exist(netuid) && netuid != NetUid::ROOT); + + if let Some((pos, netuid)) = first_valid { + queue.drain(..=pos); + Some(netuid) + } else { + queue.clear(); + None + } + }) + } + /// If owner is `Some`, record last-blocks for the provided `TransactionType`s. pub fn record_owner_rl( maybe_owner: Option<::AccountId>, @@ -334,9 +355,6 @@ impl Pallet { pub fn get_tempo(netuid: NetUid) -> u16 { Tempo::::get(netuid) } - pub fn get_pending_emission(netuid: NetUid) -> AlphaCurrency { - PendingEmission::::get(netuid) - } pub fn get_last_adjustment_block(netuid: NetUid) -> u64 { LastAdjustmentBlock::::get(netuid) } @@ -873,6 +891,12 @@ impl Pallet { Self::deposit_event(Event::ColdkeySwapScheduleDurationSet(duration)); } + /// Set the delay applied when scheduling deregistration priority. + pub fn set_deregistration_priority_schedule_delay(duration: BlockNumberFor) { + DeregistrationPriorityScheduleDelay::::set(duration); + Self::deposit_event(Event::DeregistrationPriorityScheduleDelaySet(duration)); + } + /// Set the duration for dissolve network /// /// # Arguments diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index 34b5e624e6..de08022060 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -68,7 +68,7 @@ impl Pallet { } // initializes V3 swap for a subnet if needed - pub(super) fn maybe_initialize_v3(netuid: NetUid) -> Result<(), Error> { + pub fn maybe_initialize_v3(netuid: NetUid) -> Result<(), Error> { if SwapV3Initialized::::get(netuid) { return Ok(()); } diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index ee5b1693ba..2cfc87ca85 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -206,6 +206,7 @@ parameter_types! { // pub const InitialHotkeyEmissionTempo: u64 = 1; // (DEPRECATED) // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days + pub const InitialDeregistrationPriorityScheduleDelay: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialColdkeySwapRescheduleDuration: u64 = 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialTaoWeight: u64 = u64::MAX/10; // 10% global weight. @@ -276,6 +277,7 @@ impl pallet_subtensor::Config for Test { type Yuma3On = InitialYuma3On; type Preimages = (); type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialDeregistrationPriorityScheduleDelay = InitialDeregistrationPriorityScheduleDelay; type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; diff --git a/precompiles/src/lib.rs b/precompiles/src/lib.rs index 6b1a004ef2..8069a1eb92 100644 --- a/precompiles/src/lib.rs +++ b/precompiles/src/lib.rs @@ -34,6 +34,7 @@ use crate::extensions::*; use crate::leasing::*; use crate::metagraph::*; use crate::neuron::*; +use crate::proxy::*; use crate::sr25519::*; use crate::staking::*; use crate::storage_query::*; @@ -48,6 +49,7 @@ mod extensions; mod leasing; mod metagraph; mod neuron; +mod proxy; mod sr25519; mod staking; mod storage_query; @@ -108,7 +110,7 @@ where Self(Default::default()) } - pub fn used_addresses() -> [H160; 24] { + pub fn used_addresses() -> [H160; 25] { [ hash(1), hash(2), @@ -134,6 +136,7 @@ where hash(AlphaPrecompile::::INDEX), hash(CrowdloanPrecompile::::INDEX), hash(LeasingPrecompile::::INDEX), + hash(ProxyPrecompile::::INDEX), ] } } @@ -220,6 +223,9 @@ where a if a == hash(LeasingPrecompile::::INDEX) => { LeasingPrecompile::::try_execute::(handle, PrecompileEnum::Leasing) } + a if a == hash(ProxyPrecompile::::INDEX) => { + ProxyPrecompile::::try_execute::(handle, PrecompileEnum::Proxy) + } _ => None, } } diff --git a/precompiles/src/proxy.rs b/precompiles/src/proxy.rs new file mode 100644 index 0000000000..1399177766 --- /dev/null +++ b/precompiles/src/proxy.rs @@ -0,0 +1,242 @@ +use core::marker::PhantomData; + +use crate::{PrecompileExt, PrecompileHandleExt}; + +use alloc::format; +use fp_evm::{ExitError, PrecompileFailure}; +use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; +use frame_system::RawOrigin; +use pallet_evm::{AddressMapping, PrecompileHandle}; +use pallet_subtensor_proxy as pallet_proxy; +use precompile_utils::EvmResult; +use sp_core::H256; +use sp_runtime::{ + codec::DecodeLimit, + traits::{Dispatchable, StaticLookup}, +}; +use sp_std::boxed::Box; +use sp_std::vec::Vec; +use subtensor_runtime_common::ProxyType; +pub struct ProxyPrecompile(PhantomData); +const MAX_DECODE_DEPTH: u32 = 8; + +impl PrecompileExt for ProxyPrecompile +where + R: frame_system::Config + + pallet_evm::Config + + pallet_subtensor::Config + + pallet_proxy::Config, + R::AccountId: From<[u8; 32]> + Into<[u8; 32]>, + ::AddressMapping: AddressMapping, + ::RuntimeCall: From> + + From> + + GetDispatchInfo + + Dispatchable, + ::AddressMapping: AddressMapping, + <::Lookup as StaticLookup>::Source: From, +{ + const INDEX: u64 = 2059; +} + +#[precompile_utils::precompile] +impl ProxyPrecompile +where + R: frame_system::Config + + pallet_evm::Config + + pallet_subtensor::Config + + pallet_proxy::Config, + R::AccountId: From<[u8; 32]> + Into<[u8; 32]>, + ::AddressMapping: AddressMapping, + ::RuntimeCall: From> + + From> + + GetDispatchInfo + + Dispatchable, + <::Lookup as StaticLookup>::Source: From, +{ + #[precompile::public("createPureProxy(uint8,uint32,uint16)")] + pub fn create_pure_proxy( + handle: &mut impl PrecompileHandle, + proxy_type_: u8, + delay: u32, + index: u16, + ) -> EvmResult { + let account_id = handle.caller_account_id::(); + let proxy_type = + ProxyType::try_from(proxy_type_).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid proxy type".into()), + })?; + + let call = pallet_proxy::Call::::create_pure { + proxy_type, + delay: delay.into(), + index, + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id.clone()))?; + + // Success! + // Try to get proxy address + let proxy_address: [u8; 32] = + pallet_proxy::pallet::Pallet::::pure_account(&account_id, &proxy_type, index, None) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Proxy not found".into()), + })? + .into(); + + // Check if in the proxies map + let proxy_entry = pallet_proxy::pallet::Pallet::::proxies(proxy_address.into()); + if proxy_entry + .0 + .iter() + .any(|p| account_id == p.delegate && proxy_type == p.proxy_type) + { + return Ok(proxy_address.into()); + } + + Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Proxy not found".into()), + }) + } + + #[precompile::public("killPureProxy(bytes32,uint8,uint16,uint32,uint32)")] + pub fn kill_pure_proxy( + handle: &mut impl PrecompileHandle, + spawner: H256, + proxy_type: u8, + index: u16, + height: u32, + ext_index: u32, + ) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let proxy_type = ProxyType::try_from(proxy_type).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid proxy type".into()), + })?; + + let call = pallet_proxy::Call::::kill_pure { + spawner: <::Lookup as StaticLookup>::Source::from( + spawner.0.into(), + ), + proxy_type, + index, + height: height.into(), + ext_index: ext_index.into(), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + #[precompile::public("proxyCall(bytes32,uint8[],uint8[])")] + pub fn proxy_call( + handle: &mut impl PrecompileHandle, + real: H256, + force_proxy_type: Vec, + call: Vec, + ) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + + let call = ::RuntimeCall::decode_with_depth_limit( + MAX_DECODE_DEPTH, + &mut &call[..], + ) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("The raw call data not correctly encoded".into()), + })?; + + let mut proxy_type: Option = None; + if let Some(p) = force_proxy_type.first() { + let proxy_type_ = ProxyType::try_from(*p).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid proxy type".into()), + })?; + proxy_type = Some(proxy_type_); + }; + + let call = pallet_proxy::Call::::proxy { + real: <::Lookup as StaticLookup>::Source::from( + real.0.into(), + ), + force_proxy_type: proxy_type, + call: Box::new(call), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id))?; + + let real_account_id = R::AccountId::from(real.0.into()); + + let last_call_result = pallet_proxy::LastCallResult::::get(real_account_id); + match last_call_result { + Some(last_call_result) => match last_call_result { + Ok(()) => Ok(()), + Err(e) => Err(PrecompileFailure::Error { + exit_status: ExitError::Other(format!("{e:?}").into()), + }), + }, + None => Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Proxy execution failed".into()), + }), + } + } + + #[precompile::public("addProxy(bytes32,uint8,uint32)")] + pub fn add_proxy( + handle: &mut impl PrecompileHandle, + delegate: H256, + proxy_type: u8, + delay: u32, + ) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let proxy_type = ProxyType::try_from(proxy_type).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid proxy type".into()), + })?; + + let call = pallet_proxy::Call::::add_proxy { + delegate: <::Lookup as StaticLookup>::Source::from( + delegate.0.into(), + ), + proxy_type, + delay: delay.into(), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + #[precompile::public("removeProxy(bytes32,uint8,uint32)")] + pub fn remove_proxy( + handle: &mut impl PrecompileHandle, + delegate: H256, + proxy_type: u8, + delay: u32, + ) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let proxy_type = ProxyType::try_from(proxy_type).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid proxy type".into()), + })?; + + let call = pallet_proxy::Call::::remove_proxy { + delegate: <::Lookup as StaticLookup>::Source::from( + delegate.0.into(), + ), + proxy_type, + delay: delay.into(), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + #[precompile::public("removeProxies()")] + pub fn remove_proxies(handle: &mut impl PrecompileHandle) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + + let call = pallet_proxy::Call::::remove_proxies {}; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + #[precompile::public("pokeDeposit()")] + pub fn poke_deposit(handle: &mut impl PrecompileHandle) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + + let call = pallet_proxy::Call::::poke_deposit {}; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } +} diff --git a/precompiles/src/solidity/proxy.sol b/precompiles/src/solidity/proxy.sol new file mode 100644 index 0000000000..b0e03031bf --- /dev/null +++ b/precompiles/src/solidity/proxy.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +address constant IPROXY_ADDRESS = 0x000000000000000000000000000000000000080b; + +interface IProxy { + function createPureProxy( + uint8 proxy_type, + uint32 delay, + uint16 index + ) external; + + function proxyCall(bytes32 real, uint8[] memory force_proxy_type, bytes memory call) external; + + function killPureProxy( + bytes32 spawner, + uint8 proxy_type, + uint16 index, + uint16 height, + uint32 ext_index + ) external; + + function addProxy(bytes32 delegate, uint8 proxy_type, uint32 delay) external; + + function removeProxy(bytes32 delegate, uint8 proxy_type, uint32 delay) external; + + function removeProxies() external; + + function pokeDeposit() external; +} diff --git a/precompiles/src/solidity/subnet.abi b/precompiles/src/solidity/subnet.abi index 4531f59246..692f39bf32 100644 --- a/precompiles/src/solidity/subnet.abi +++ b/precompiles/src/solidity/subnet.abi @@ -1028,8 +1028,5 @@ "outputs": [], "stateMutability": "payable", "type": "function" - }, - { - "inputs" } ] diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a4ee4a3b93..9dd95b941b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -220,7 +220,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 333, + spec_version: 346, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -1029,6 +1029,7 @@ parameter_types! { pub const InitialYuma3On: bool = false; // Default value for Yuma3On // pub const SubtensorInitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days + pub const InitialDeregistrationPriorityScheduleDelay: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialColdkeySwapRescheduleDuration: BlockNumber = 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days pub const SubtensorInitialTaoWeight: u64 = 971_718_665_099_567_868; // 0.05267697438728329% tao weight. @@ -1101,6 +1102,7 @@ impl pallet_subtensor::Config for Runtime { type InitialTaoWeight = SubtensorInitialTaoWeight; type Preimages = Preimage; type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialDeregistrationPriorityScheduleDelay = InitialDeregistrationPriorityScheduleDelay; type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod;