diff --git a/Cargo.lock b/Cargo.lock index e7cf36d22e..a9b72f109e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18234,16 +18234,21 @@ dependencies = [ "pallet-admin-utils", "pallet-balances", "pallet-crowdloan", + "pallet-drand", "pallet-evm", "pallet-evm-precompile-bn128", "pallet-evm-precompile-dispatch", "pallet-evm-precompile-modexp", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", + "pallet-preimage", + "pallet-scheduler", "pallet-shield", "pallet-subtensor", "pallet-subtensor-proxy", "pallet-subtensor-swap", + "pallet-timestamp", + "parity-scale-codec", "precompile-utils", "scale-info", "sp-core", diff --git a/contract-tests/test/addressMapping.precompile.test.ts b/contract-tests/test/addressMapping.precompile.test.ts deleted file mode 100644 index 4f316fc57a..0000000000 --- a/contract-tests/test/addressMapping.precompile.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import * as assert from "assert"; -import { ethers } from "ethers"; -import { generateRandomEthersWallet } from "../src/utils"; -import { IADDRESS_MAPPING_ADDRESS, IAddressMappingABI } from "../src/contracts/addressMapping"; -import { convertH160ToPublicKey } from "../src/address-utils"; -import { u8aToHex } from "@polkadot/util"; - -describe("Test address mapping precompile", () => { - const wallet1 = generateRandomEthersWallet(); - const wallet2 = generateRandomEthersWallet(); - - it("Address mapping converts H160 to AccountId32 correctly", async () => { - const contract = new ethers.Contract( - IADDRESS_MAPPING_ADDRESS, - IAddressMappingABI, - wallet1 - ); - - // Test with wallet1's address - const evmAddress = wallet1.address; - const accountId32 = await contract.addressMapping(evmAddress); - const expectedAcccountId32 = convertH160ToPublicKey(evmAddress); - - // Verify the result is a valid bytes32 (32 bytes) - assert.ok(accountId32.length === 66, "AccountId32 should be 32 bytes (66 hex chars with 0x)"); - assert.ok(accountId32.startsWith("0x"), "AccountId32 should start with 0x"); - - // Verify it's not all zeros - assert.notEqual( - accountId32, - "0x0000000000000000000000000000000000000000000000000000000000000000", - "AccountId32 should not be all zeros" - ); - - console.log("accountId32: {}", accountId32); - console.log("expectedAcccountId32: {}", expectedAcccountId32); - - assert.equal(accountId32, u8aToHex(expectedAcccountId32), "AccountId32 should be the same as the expected AccountId32"); - }); - - it("Address mapping works with different addresses", async () => { - const contract = new ethers.Contract( - IADDRESS_MAPPING_ADDRESS, - IAddressMappingABI, - wallet1 - ); - - // Test with wallet2's address - const evmAddress1 = wallet1.address; - const evmAddress2 = wallet2.address; - - const accountId1 = await contract.addressMapping(evmAddress1); - const accountId2 = await contract.addressMapping(evmAddress2); - - // Different addresses should map to different AccountIds - assert.notEqual( - accountId1, - accountId2, - "Different EVM addresses should map to different AccountIds" - ); - - // Both should be valid bytes32 - assert.ok(accountId1.length === 66, "AccountId1 should be 32 bytes"); - assert.ok(accountId2.length === 66, "AccountId2 should be 32 bytes"); - }); - - it("Address mapping is deterministic", async () => { - const contract = new ethers.Contract( - IADDRESS_MAPPING_ADDRESS, - IAddressMappingABI, - wallet1 - ); - - const evmAddress = wallet1.address; - - // Call multiple times with the same address - const accountId1 = await contract.addressMapping(evmAddress); - const accountId2 = await contract.addressMapping(evmAddress); - - // All calls should return the same result - assert.equal( - accountId1, - accountId2, - "First and second calls should return the same AccountId" - ); - }); -}); diff --git a/contract-tests/test/alpha.precompile.test.ts b/contract-tests/test/alpha.precompile.test.ts deleted file mode 100644 index 9c1a5daa8e..0000000000 --- a/contract-tests/test/alpha.precompile.test.ts +++ /dev/null @@ -1,443 +0,0 @@ -import * as assert from "assert"; - -import { getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" -import { getPublicClient } from "../src/utils"; -import { ETH_LOCAL_URL } from "../src/config"; -import { devnet } from "@polkadot-api/descriptors" -import { PublicClient } from "viem"; -import { TypedApi } from "polkadot-api"; -import { toViemAddress, convertPublicKeyToSs58 } from "../src/address-utils" -import { IAlphaABI, IALPHA_ADDRESS } from "../src/contracts/alpha" -import { forceSetBalanceToSs58Address, addNewSubnetwork, startCall } from "../src/subtensor"; -describe("Test Alpha Precompile", () => { - // init substrate part - const hotkey = getRandomSubstrateKeypair(); - const coldkey = getRandomSubstrateKeypair(); - let publicClient: PublicClient; - - let api: TypedApi; - - // init other variable - let subnetId = 0; - - before(async () => { - // init variables got from await and async - publicClient = await getPublicClient(ETH_LOCAL_URL) - api = await getDevnetApi() - - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)) - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) - - let netuid = await addNewSubnetwork(api, hotkey, coldkey) - await startCall(api, netuid, coldkey) - - }) - - describe("Alpha Price Functions", () => { - it("getAlphaPrice returns valid price for subnet", async () => { - const alphaPrice = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getAlphaPrice", - args: [subnetId] - }) - - assert.ok(alphaPrice !== undefined, "Alpha price should be defined"); - assert.ok(typeof alphaPrice === 'bigint', "Alpha price should be a bigint"); - assert.ok(alphaPrice >= BigInt(0), "Alpha price should be non-negative"); - }); - - it("getMovingAlphaPrice returns valid moving price for subnet", async () => { - const movingAlphaPrice = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getMovingAlphaPrice", - args: [subnetId] - }) - - assert.ok(movingAlphaPrice !== undefined, "Moving alpha price should be defined"); - assert.ok(typeof movingAlphaPrice === 'bigint', "Moving alpha price should be a bigint"); - assert.ok(movingAlphaPrice >= BigInt(0), "Moving alpha price should be non-negative"); - }); - - it("alpha prices are consistent for same subnet", async () => { - const alphaPrice = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getAlphaPrice", - args: [subnetId] - }) - - const movingAlphaPrice = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getMovingAlphaPrice", - args: [subnetId] - }) - - // Both should be defined and valid - assert.ok(alphaPrice !== undefined && movingAlphaPrice !== undefined); - }); - - it("Tao in / Alpha in / Alpha out are consistent for same subnet", async () => { - const taoInEmission = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getTaoInEmission", - args: [subnetId] - }) - - const alphaInEmission = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getAlphaInEmission", - args: [subnetId] - }) - - const alphaOutEmission = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getAlphaOutEmission", - args: [subnetId] - }) - - // all should be defined and valid - assert.ok(taoInEmission !== undefined && alphaInEmission !== undefined && alphaOutEmission !== undefined); - }); - - it("getSumAlphaPrice returns valid sum of alpha prices", async () => { - const sumAlphaPrice = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getSumAlphaPrice", - args: [] - }) - - assert.ok(sumAlphaPrice !== undefined, "Sum alpha price should be defined"); - }) - }); - - describe("Pool Data Functions", () => { - it("getTaoInPool returns valid TAO amount", async () => { - const taoInPool = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getTaoInPool", - args: [subnetId] - }) - - assert.ok(taoInPool !== undefined, "TAO in pool should be defined"); - assert.ok(typeof taoInPool === 'bigint', "TAO in pool should be a bigint"); - assert.ok(taoInPool >= BigInt(0), "TAO in pool should be non-negative"); - }); - - it("getAlphaInPool returns valid Alpha amount", async () => { - const alphaInPool = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getAlphaInPool", - args: [subnetId] - }) - - assert.ok(alphaInPool !== undefined, "Alpha in pool should be defined"); - assert.ok(typeof alphaInPool === 'bigint', "Alpha in pool should be a bigint"); - assert.ok(alphaInPool >= BigInt(0), "Alpha in pool should be non-negative"); - }); - - it("getAlphaOutPool returns valid Alpha out amount", async () => { - const alphaOutPool = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getAlphaOutPool", - args: [subnetId] - }) - - assert.ok(alphaOutPool !== undefined, "Alpha out pool should be defined"); - assert.ok(typeof alphaOutPool === 'bigint', "Alpha out pool should be a bigint"); - assert.ok(alphaOutPool >= BigInt(0), "Alpha out pool should be non-negative"); - }); - - it("getAlphaIssuance returns valid issuance amount", async () => { - const alphaIssuance = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getAlphaIssuance", - args: [subnetId] - }) - - assert.ok(alphaIssuance !== undefined, "Alpha issuance should be defined"); - assert.ok(typeof alphaIssuance === 'bigint', "Alpha issuance should be a bigint"); - assert.ok(alphaIssuance >= BigInt(0), "Alpha issuance should be non-negative"); - }); - - it("getCKBurn returns valid CK burn rate", async () => { - const ckBurn = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getCKBurn", - args: [] - }) - - const ckBurnOnChain = await api.query.SubtensorModule.CKBurn.getValue() - - assert.strictEqual(ckBurn, ckBurnOnChain, "CK burn should match on chain"); - assert.ok(ckBurn !== undefined, "CK burn should be defined"); - const ckBurnPercentage = BigInt(ckBurn) * BigInt(100) / BigInt(2 ** 64 - 1) - assert.ok(ckBurnPercentage >= BigInt(0), "CK burn percentage should be non-negative"); - assert.ok(ckBurnPercentage <= BigInt(100), "CK burn percentage should be less than or equal to 100"); - assert.ok(typeof ckBurn === 'bigint', "CK burn should be a bigint"); - }); - }); - - describe("Global Functions", () => { - it("getTaoWeight returns valid TAO weight", async () => { - const taoWeight = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getTaoWeight", - args: [] - }) - - assert.ok(taoWeight !== undefined, "TAO weight should be defined"); - assert.ok(typeof taoWeight === 'bigint', "TAO weight should be a bigint"); - assert.ok(taoWeight >= BigInt(0), "TAO weight should be non-negative"); - }); - - it("getRootNetuid returns correct root netuid", async () => { - const rootNetuid = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getRootNetuid", - args: [] - }) - - assert.ok(rootNetuid !== undefined, "Root netuid should be defined"); - assert.ok(typeof rootNetuid === 'number', "Root netuid should be a number"); - assert.strictEqual(rootNetuid, 0, "Root netuid should be 0"); - }); - }); - - describe("Swap Simulation Functions", () => { - it("simSwapTaoForAlpha returns valid simulation", async () => { - const taoAmount = BigInt(1000000000); // 1 TAO in RAO - const simulatedAlpha = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "simSwapTaoForAlpha", - args: [subnetId, taoAmount] - }) - - assert.ok(simulatedAlpha !== undefined, "Simulated alpha should be defined"); - assert.ok(typeof simulatedAlpha === 'bigint', "Simulated alpha should be a bigint"); - assert.ok(simulatedAlpha >= BigInt(0), "Simulated alpha should be non-negative"); - }); - - it("simSwapAlphaForTao returns valid simulation", async () => { - const alphaAmount = BigInt(1000000000); // 1 Alpha - const simulatedTao = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "simSwapAlphaForTao", - args: [subnetId, alphaAmount] - }) - - assert.ok(simulatedTao !== undefined, "Simulated tao should be defined"); - assert.ok(typeof simulatedTao === 'bigint', "Simulated tao should be a bigint"); - assert.ok(simulatedTao >= BigInt(0), "Simulated tao should be non-negative"); - }); - - it("swap simulations handle zero amounts", async () => { - const zeroTaoForAlpha = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "simSwapTaoForAlpha", - args: [subnetId, BigInt(0)] - }) - - const zeroAlphaForTao = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "simSwapAlphaForTao", - args: [subnetId, BigInt(0)] - }) - - assert.strictEqual(zeroTaoForAlpha, BigInt(0), "Zero TAO should result in zero Alpha"); - assert.strictEqual(zeroAlphaForTao, BigInt(0), "Zero Alpha should result in zero TAO"); - }); - - it("swap simulations are internally consistent", async () => { - const taoAmount = BigInt(1000000000); // 1 TAO - - // Simulate TAO -> Alpha - const simulatedAlpha = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "simSwapTaoForAlpha", - args: [subnetId, taoAmount] - }) - - // If we got alpha, simulate Alpha -> TAO - if ((simulatedAlpha as bigint) > BigInt(0)) { - const simulatedTao = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "simSwapAlphaForTao", - args: [subnetId, simulatedAlpha] - }) - - // Check if simulated values are reasonably close (allowing for rounding/fees) - if ((simulatedTao as bigint) > BigInt(0)) { - const ratio = Number(taoAmount) / Number(simulatedTao); - assert.ok(ratio >= 0.5 && ratio <= 2.0, "Swap simulation should be within reasonable bounds"); - } - } - }); - }); - - describe("Subnet Configuration Functions", () => { - it("getSubnetMechanism returns valid mechanism", async () => { - const mechanism = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getSubnetMechanism", - args: [subnetId] - }) - - assert.ok(mechanism !== undefined, "Subnet mechanism should be defined"); - assert.ok(typeof mechanism === 'number', "Subnet mechanism should be a number"); - assert.ok(mechanism >= 0, "Subnet mechanism should be non-negative"); - }); - - it("getEMAPriceHalvingBlocks returns valid halving period", async () => { - const halvingBlocks = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getEMAPriceHalvingBlocks", - args: [subnetId] - }) - - assert.ok(halvingBlocks !== undefined, "EMA price halving blocks should be defined"); - assert.ok(typeof halvingBlocks === 'bigint', "EMA halving blocks should be a bigint"); - assert.ok(halvingBlocks >= BigInt(0), "EMA halving blocks should be non-negative"); - }); - - it("getSubnetVolume returns valid volume data", async () => { - const subnetVolume = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getSubnetVolume", - args: [subnetId] - }) - - assert.ok(subnetVolume !== undefined, "Subnet volume should be defined"); - assert.ok(typeof subnetVolume === 'bigint', "Subnet volume should be a bigint"); - assert.ok(subnetVolume >= BigInt(0), "Subnet volume should be non-negative"); - }); - }); - - describe("Data Consistency with Pallet", () => { - it("precompile data matches pallet values", async () => { - // Get TAO in pool from precompile - const taoInPool = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getTaoInPool", - args: [subnetId] - }) - - // Get TAO in pool directly from the pallet - const taoInPoolFromPallet = await api.query.SubtensorModule.SubnetTAO.getValue(subnetId); - - // Compare values - assert.strictEqual(taoInPool as bigint, taoInPoolFromPallet, "TAO in pool values should match"); - - // Get Alpha in pool from precompile - const alphaInPool = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getAlphaInPool", - args: [subnetId] - }) - - // Get Alpha in pool directly from the pallet - const alphaInPoolFromPallet = await api.query.SubtensorModule.SubnetAlphaIn.getValue(subnetId); - - // Compare values - assert.strictEqual(alphaInPool as bigint, alphaInPoolFromPallet, "Alpha in pool values should match"); - - // Get Alpha out pool from precompile - const alphaOutPool = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getAlphaOutPool", - args: [subnetId] - }) - - // Get Alpha out pool directly from the pallet - const alphaOutPoolFromPallet = await api.query.SubtensorModule.SubnetAlphaOut.getValue(subnetId); - - // Compare values - assert.strictEqual(alphaOutPool as bigint, alphaOutPoolFromPallet, "Alpha out pool values should match"); - }); - - it("subnet volume data is consistent", async () => { - const subnetVolume = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getSubnetVolume", - args: [subnetId] - }) - - const subnetVolumeFromPallet = await api.query.SubtensorModule.SubnetVolume.getValue(subnetId); - - assert.strictEqual(subnetVolume as bigint, subnetVolumeFromPallet, "Subnet volume values should match"); - }); - }); - - describe("Edge Cases and Error Handling", () => { - it("handles non-existent subnet gracefully", async () => { - const nonExistentSubnet = 9999; - - // These should not throw but return default values - const alphaPrice = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getAlphaPrice", - args: [nonExistentSubnet] - }) - - const taoInPool = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "getTaoInPool", - args: [nonExistentSubnet] - }) - - // Should return default values, not throw - assert.ok(alphaPrice !== undefined, "Should handle non-existent subnet gracefully"); - assert.ok(taoInPool !== undefined, "Should handle non-existent subnet gracefully"); - }); - - it("simulation functions handle large amounts", async () => { - const largeAmount = BigInt("1000000000000000000"); // Very large amount - - const simulatedAlpha = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "simSwapTaoForAlpha", - args: [subnetId, largeAmount] - }) - - const simulatedTao = await publicClient.readContract({ - abi: IAlphaABI, - address: toViemAddress(IALPHA_ADDRESS), - functionName: "simSwapAlphaForTao", - args: [subnetId, largeAmount] - }) - - // Should handle large amounts without throwing - assert.ok(simulatedAlpha !== undefined, "Should handle large TAO amounts"); - assert.ok(simulatedTao !== undefined, "Should handle large Alpha amounts"); - }); - }); -}); diff --git a/contract-tests/test/ed25519.precompile.verify.test.ts b/contract-tests/test/ed25519.precompile.verify.test.ts deleted file mode 100644 index fcd79ec9d7..0000000000 --- a/contract-tests/test/ed25519.precompile.verify.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { IED25519VERIFY_ADDRESS, IEd25519VerifyABI, ETH_LOCAL_URL } from '../src/config' -import { getPublicClient } from "../src/utils"; -import { toHex, toBytes, keccak256, PublicClient } from 'viem' -import { Keyring } from "@polkadot/keyring"; -import * as assert from "assert"; - -describe("Verfication of ed25519 signature", () => { - // init eth part - let ethClient: PublicClient; - - before(async () => { - ethClient = await getPublicClient(ETH_LOCAL_URL); - }); - - it("Verification of ed25519 works", async () => { - const keyring = new Keyring({ type: "ed25519" }); - const alice = keyring.addFromUri("//Alice"); - - // Use this example: https://github.com/gztensor/evm-demo/blob/main/docs/ed25519verify-precompile.md - // const keyring = new Keyring({ type: "ed25519" }); - // const myAccount = keyring.addFromUri("//Alice"); - - ////////////////////////////////////////////////////////////////////// - // Generate a signature - - // Your message to sign - const message = "Sign this message"; - const messageU8a = new TextEncoder().encode(message); - const messageHex = toHex(messageU8a); // Convert message to hex string - const messageHash = keccak256(messageHex); // Hash the message to fit into bytes32 - console.log(`messageHash = ${messageHash}`); - const hashedMessageBytes = toBytes(messageHash); - console.log(`hashedMessageBytes = ${hashedMessageBytes}`); - - // Sign the message - const signature = await alice.sign(hashedMessageBytes); - console.log(`Signature: ${toHex(signature)}`); - - // Verify the signature locally - const isValid = alice.verify( - hashedMessageBytes, - signature, - alice.publicKey - ); - console.log(`Is the signature valid? ${isValid}`); - - ////////////////////////////////////////////////////////////////////// - // Verify the signature using the precompile contract - - const publicKeyBytes = toHex(alice.publicKey); - console.log(`publicKeyBytes = ${publicKeyBytes}`); - - // Split signture into Commitment (R) and response (s) - let r = signature.slice(0, 32); // Commitment, a.k.a. "r" - first 32 bytes - let s = signature.slice(32, 64); // Response, a.k.a. "s" - second 32 bytes - let rBytes = toHex(r); - let sBytes = toHex(s); - - const isPrecompileValid = await ethClient.readContract({ - address: IED25519VERIFY_ADDRESS, - abi: IEd25519VerifyABI, - functionName: "verify", - args: [messageHash, - publicKeyBytes, - rBytes, - sBytes] - - }); - - console.log( - `Is the signature valid according to the smart contract? ${isPrecompileValid}` - ); - assert.equal(isPrecompileValid, true) - - ////////////////////////////////////////////////////////////////////// - // Verify the signature for bad data using the precompile contract - - let brokenHashedMessageBytes = hashedMessageBytes; - brokenHashedMessageBytes[0] = (brokenHashedMessageBytes[0] + 1) % 0xff; - const brokenMessageHash = toHex(brokenHashedMessageBytes); - console.log(`brokenMessageHash = ${brokenMessageHash}`); - - const isPrecompileValidBadData = await ethClient.readContract({ - address: IED25519VERIFY_ADDRESS, - abi: IEd25519VerifyABI, - functionName: "verify", - args: [brokenMessageHash, - publicKeyBytes, - rBytes, - sBytes] - - }); - - console.log( - `Is the signature valid according to the smart contract for broken data? ${isPrecompileValidBadData}` - ); - assert.equal(isPrecompileValidBadData, false) - - ////////////////////////////////////////////////////////////////////// - // Verify the bad signature for good data using the precompile contract - - let brokenR = r; - brokenR[0] = (brokenR[0] + 1) % 0xff; - rBytes = toHex(r); - const isPrecompileValidBadSignature = await ethClient.readContract({ - address: IED25519VERIFY_ADDRESS, - abi: IEd25519VerifyABI, - functionName: "verify", - args: [messageHash, - publicKeyBytes, - rBytes, - sBytes] - - }); - - console.log( - `Is the signature valid according to the smart contract for broken signature? ${isPrecompileValidBadSignature}` - ); - assert.equal(isPrecompileValidBadSignature, false) - - }); -}); \ No newline at end of file diff --git a/contract-tests/test/evm-uid.precompile.lookup.test.ts b/contract-tests/test/evm-uid.precompile.lookup.test.ts deleted file mode 100644 index d8de138aaa..0000000000 --- a/contract-tests/test/evm-uid.precompile.lookup.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import * as assert from "assert"; - -import { getAliceSigner, getDevnetApi, waitForTransactionCompletion, getRandomSubstrateKeypair, getSignerFromKeypair } from "../src/substrate" -import { convertToFixedSizeBinary, generateRandomEthersWallet, getPublicClient } from "../src/utils"; -import { ETH_LOCAL_URL } from "../src/config"; -import { devnet } from "@polkadot-api/descriptors" -import { hexToU8a } from "@polkadot/util"; -import { u64 } from "scale-ts"; -import { PublicClient } from "viem"; -import { PolkadotSigner, TypedApi } from "polkadot-api"; -import { toViemAddress, convertPublicKeyToSs58 } from "../src/address-utils" -import { IUIDLookupABI, IUID_LOOKUP_ADDRESS } from "../src/contracts/uidLookup" -import { keccak256 } from 'ethers'; -import { addNewSubnetwork, forceSetBalanceToSs58Address, startCall } from "../src/subtensor"; - -describe("Test the UID Lookup precompile", () => { - const hotkey = getRandomSubstrateKeypair(); - const coldkey = getRandomSubstrateKeypair(); - const evmWallet = generateRandomEthersWallet(); - let publicClient: PublicClient; - - let api: TypedApi - - let alice: PolkadotSigner; - - let uid: number; - let blockNumber: number; - let netuid: number; - let blockNumberAssociated: bigint; - - before(async () => { - publicClient = await getPublicClient(ETH_LOCAL_URL) - api = await getDevnetApi() - alice = await getAliceSigner(); - - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(alice.publicKey)) - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)) - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) - - netuid = await addNewSubnetwork(api, hotkey, coldkey) - await startCall(api, netuid, coldkey) - - const maybeUid = await api.query.SubtensorModule.Uids.getValue(netuid, convertPublicKeyToSs58(hotkey.publicKey)) - - if (maybeUid === undefined) { - throw new Error("UID should be defined") - } - uid = maybeUid - - // Associate EVM key - blockNumber = await api.query.System.Number.getValue(); - const blockNumberBytes = u64.enc(BigInt(blockNumber)); - const blockNumberHash = hexToU8a(keccak256(blockNumberBytes)); - const concatenatedArray = new Uint8Array([...hotkey.publicKey, ...blockNumberHash]); - const signature = await evmWallet.signMessage(concatenatedArray); - const associateEvmKeyTx = api.tx.SubtensorModule.associate_evm_key({ - netuid: netuid, - evm_key: convertToFixedSizeBinary(evmWallet.address, 20), - block_number: BigInt(blockNumber), - signature: convertToFixedSizeBinary(signature, 65) - }); - const signer = getSignerFromKeypair(hotkey); - await waitForTransactionCompletion(api, associateEvmKeyTx, signer) - .then(() => { }) - .catch((error) => { console.log(`transaction error ${error}`) }); - - const storedEvmKey = await api.query.SubtensorModule.AssociatedEvmAddress.getValue(netuid, uid) - assert.notEqual(storedEvmKey, undefined, "storedEvmKey should be defined") - if (storedEvmKey !== undefined) { - assert.equal(storedEvmKey[0].asHex(), convertToFixedSizeBinary(evmWallet.address, 20).asHex()) - blockNumberAssociated = storedEvmKey[1] - } - }) - - it("UID lookup via precompile contract works correctly", async () => { - // Get UID for the EVM address - const uidArray = await publicClient.readContract({ - abi: IUIDLookupABI, - address: toViemAddress(IUID_LOOKUP_ADDRESS), - functionName: "uidLookup", - args: [netuid, evmWallet.address, 1024] - }) - - assert.notEqual(uidArray, undefined, "UID should be defined") - assert.ok(Array.isArray(uidArray), `UID should be an array, got ${typeof uidArray}`) - assert.ok(uidArray.length > 0, "UID array should not be empty") - assert.deepStrictEqual(uidArray[0], { uid: uid, block_associated: blockNumberAssociated }) - }) -}); diff --git a/contract-tests/test/metagraph.precompile.test.ts b/contract-tests/test/metagraph.precompile.test.ts deleted file mode 100644 index 18ab4bd421..0000000000 --- a/contract-tests/test/metagraph.precompile.test.ts +++ /dev/null @@ -1,146 +0,0 @@ -import * as assert from "assert"; - -import { getAliceSigner, getDevnetApi, convertPublicKeyToMultiAddress, getRandomSubstrateKeypair, getSignerFromKeypair, waitForTransactionWithRetry } from "../src/substrate" -import { getPublicClient, } from "../src/utils"; -import { ETH_LOCAL_URL } from "../src/config"; -import { devnet } from "@polkadot-api/descriptors" -import { PublicClient } from "viem"; -import { PolkadotSigner, TypedApi } from "polkadot-api"; -import { toViemAddress, convertPublicKeyToSs58 } from "../src/address-utils" -import { IMetagraphABI, IMETAGRAPH_ADDRESS } from "../src/contracts/metagraph" - -describe("Test the Metagraph precompile", () => { - // init substrate part - const hotkey = getRandomSubstrateKeypair(); - const coldkey = getRandomSubstrateKeypair(); - let publicClient: PublicClient; - - let api: TypedApi - - // sudo account alice as signer - let alice: PolkadotSigner; - - // init other variable - let subnetId = 0; - - before(async () => { - // init variables got from await and async - publicClient = await getPublicClient(ETH_LOCAL_URL) - api = await getDevnetApi() - alice = await getAliceSigner(); - - { - const multiAddress = convertPublicKeyToMultiAddress(hotkey.publicKey) - const internalCall = api.tx.Balances.force_set_balance({ who: multiAddress, new_free: BigInt(1e12) }) - const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) - - await waitForTransactionWithRetry(api, tx, alice) - .then(() => { }) - .catch((error) => { console.log(`transaction error ${error}`) }); - } - - { - const multiAddress = convertPublicKeyToMultiAddress(coldkey.publicKey) - const internalCall = api.tx.Balances.force_set_balance({ who: multiAddress, new_free: BigInt(1e12) }) - const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) - - await waitForTransactionWithRetry(api, tx, alice) - .then(() => { }) - .catch((error) => { console.log(`transaction error ${error}`) }); - } - - const signer = getSignerFromKeypair(coldkey) - const registerNetworkTx = api.tx.SubtensorModule.register_network({ hotkey: convertPublicKeyToSs58(hotkey.publicKey) }) - await waitForTransactionWithRetry(api, registerNetworkTx, signer) - .then(() => { }) - .catch((error) => { console.log(`transaction error ${error}`) }); - - let totalNetworks = await api.query.SubtensorModule.TotalNetworks.getValue() - assert.ok(totalNetworks > 1) - subnetId = totalNetworks - 1 - - let uid_count = - await api.query.SubtensorModule.SubnetworkN.getValue(subnetId) - if (uid_count === 0) { - const tx = api.tx.SubtensorModule.burned_register({ hotkey: convertPublicKeyToSs58(hotkey.publicKey), netuid: subnetId }) - await waitForTransactionWithRetry(api, tx, signer) - .then(() => { }) - .catch((error) => { console.log(`transaction error ${error}`) }); - } - }) - - it("Metagraph data access via precompile contract is ok", async () => { - const uid = 0 - const uid_count = await publicClient.readContract({ - abi: IMetagraphABI, - address: toViemAddress(IMETAGRAPH_ADDRESS), - functionName: "getUidCount", - args: [subnetId] - }) - // back to original value for other tests. and we can run it repeatedly - assert.ok(uid_count != undefined); - - // const axon = api.query.SubtensorModule.Axons.getValue() - - const axon = await publicClient.readContract({ - abi: IMetagraphABI, - address: toViemAddress(IMETAGRAPH_ADDRESS), - functionName: "getAxon", - args: [subnetId, uid] - }) - - assert.ok(axon != undefined); - if (axon instanceof Object) { - assert.ok(axon != undefined); - if ("block" in axon) { - assert.ok(axon.block != undefined); - } else { - throw new Error("block not included in axon") - } - - if ("version" in axon) { - assert.ok(axon.version != undefined); - } else { - throw new Error("version not included in axon") - } - - if ("ip" in axon) { - assert.ok(axon.ip != undefined); - } else { - throw new Error("ip not included in axon") - } - - if ("port" in axon) { - assert.ok(axon.port != undefined); - } else { - throw new Error("port not included in axon") - } - - if ("ip_type" in axon) { - assert.ok(axon.ip_type != undefined); - } else { - throw new Error("ip_type not included in axon") - } - - if ("protocol" in axon) { - assert.ok(axon.protocol != undefined); - } else { - throw new Error("protocol not included in axon") - } - } - - const methodList = ["getEmission", "getVtrust", "getValidatorStatus", "getLastUpdate", "getIsActive", - "getHotkey", "getColdkey" - ] - for (const method of methodList) { - const value = await publicClient.readContract({ - abi: IMetagraphABI, - address: toViemAddress(IMETAGRAPH_ADDRESS), - functionName: method, - args: [subnetId, uid] - }) - - assert.ok(value != undefined); - } - }); -}); \ No newline at end of file diff --git a/contract-tests/test/neuron.precompile.emission-check.test.ts b/contract-tests/test/neuron.precompile.emission-check.test.ts deleted file mode 100644 index 1a2b053ed0..0000000000 --- a/contract-tests/test/neuron.precompile.emission-check.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -import * as assert from "assert"; - -import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" -import { getPublicClient, } from "../src/utils"; -import { ETH_LOCAL_URL } from "../src/config"; -import { devnet } from "@polkadot-api/descriptors" -import { PublicClient } from "viem"; -import { PolkadotSigner, TypedApi } from "polkadot-api"; -import { convertPublicKeyToSs58, } from "../src/address-utils" -import { ethers } from "ethers" -import { INEURON_ADDRESS, INeuronABI } from "../src/contracts/neuron" -import { generateRandomEthersWallet } from "../src/utils" -import { forceSetBalanceToSs58Address, forceSetBalanceToEthAddress, addNewSubnetwork, startCall, setSubtokenEnable } from "../src/subtensor" - -describe("Test the Neuron precompile with emission", () => { - // init eth part - const wallet = generateRandomEthersWallet(); - - // init substrate part - const hotkey = getRandomSubstrateKeypair(); - const hotkey2 = getRandomSubstrateKeypair(); - const coldkey = getRandomSubstrateKeypair(); - let publicClient: PublicClient; - - let api: TypedApi - - // sudo account alice as signer - let alice: PolkadotSigner; - - before(async () => { - // init variables got from await and async - publicClient = await getPublicClient(ETH_LOCAL_URL) - api = await getDevnetApi() - alice = await getAliceSigner(); - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)) - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey2.publicKey)) - - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) - await forceSetBalanceToEthAddress(api, wallet.address) - - const netuid = await addNewSubnetwork(api, hotkey2, coldkey) - await startCall(api, netuid, coldkey) - console.log("test on subnet ", netuid) - }) - - 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); - - const tx = await contract.burnedRegister( - netuid, - hotkey.publicKey - ); - await tx.wait(); - - const uidAfterNew = await api.query.SubtensorModule.SubnetworkN.getValue(netuid) - assert.equal(uid + 1, uidAfterNew) - - const key = await api.query.SubtensorModule.Keys.getValue(netuid, uid) - assert.equal(key, convertPublicKeyToSs58(hotkey.publicKey)) - - let i = 0; - while (i < 10) { - const emission = await api.query.SubtensorModule.Emission.getValue(netuid) - - console.log("emission is ", emission); - await new Promise((resolve) => setTimeout(resolve, 2000)); - i += 1; - } - }) -}); \ No newline at end of file diff --git a/contract-tests/test/neuron.precompile.reveal-weights.test.ts b/contract-tests/test/neuron.precompile.reveal-weights.test.ts deleted file mode 100644 index 5d80183ec7..0000000000 --- a/contract-tests/test/neuron.precompile.reveal-weights.test.ts +++ /dev/null @@ -1,238 +0,0 @@ -import * as assert from "assert"; -import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair, waitForTransactionWithRetry } from "../src/substrate" -import { devnet } from "@polkadot-api/descriptors" -import { PolkadotSigner, TypedApi } from "polkadot-api"; -import { convertPublicKeyToSs58, convertH160ToSS58 } from "../src/address-utils" -import { Vec, Tuple, VecFixed, u16, u8, u64 } from "@polkadot/types-codec"; -import { TypeRegistry } from "@polkadot/types"; -import { ethers } from "ethers" -import { INEURON_ADDRESS, INeuronABI } from "../src/contracts/neuron" -import { generateRandomEthersWallet } from "../src/utils" -import { convertH160ToPublicKey } from "../src/address-utils" -import { blake2AsU8a } from "@polkadot/util-crypto" -import { - forceSetBalanceToEthAddress, forceSetBalanceToSs58Address, addNewSubnetwork, setWeightsSetRateLimit, burnedRegister, - setTempo, setCommitRevealWeightsInterval, - startCall, - disableAdminFreezeWindowAndOwnerHyperparamRateLimit, -} from "../src/subtensor" - -// hardcode some values for reveal hash -const uids = [1]; -const values = [5]; -const salt = [9]; -const version_key = 0; - -async function setStakeThreshold( - api: TypedApi, - alice: PolkadotSigner, - minStake: bigint, -) { - const internalCall = api.tx.AdminUtils.sudo_set_stake_threshold({ min_stake: minStake }) - const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) - await waitForTransactionWithRetry(api, tx, alice) -} - -function getCommitHash(netuid: number, address: string) { - const registry = new TypeRegistry(); - let publicKey = convertH160ToPublicKey(address); - - const tupleData = new Tuple( - registry, - [ - VecFixed.with(u8, 32), - u16, - Vec.with(u16), - Vec.with(u16), - Vec.with(u16), - u64, - ], - [publicKey, netuid, uids, values, salt, version_key] - ); - - const hash = blake2AsU8a(tupleData.toU8a()); - return hash; -} - -describe("Test neuron precompile reveal weights", () => { - // init eth part - const wallet = generateRandomEthersWallet(); - - // init substrate part - const hotkey = getRandomSubstrateKeypair(); - const coldkey = getRandomSubstrateKeypair(); - - let api: TypedApi - let commitEpoch: number | undefined; - - // sudo account alice as signer - let alice: PolkadotSigner; - before(async () => { - // init variables got from await and async - api = await getDevnetApi() - alice = await getAliceSigner(); - - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(alice.publicKey)) - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)) - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) - await forceSetBalanceToEthAddress(api, wallet.address) - let netuid = await addNewSubnetwork(api, hotkey, coldkey) - // await disableCommitRevealWeights(api, netuid) - await startCall(api, netuid, coldkey) - - console.log("test the case on subnet ", netuid) - await disableAdminFreezeWindowAndOwnerHyperparamRateLimit(api) - - await setWeightsSetRateLimit(api, netuid, BigInt(0)) - - const ss58Address = convertH160ToSS58(wallet.address) - await burnedRegister(api, netuid, ss58Address, coldkey) - - const uid = await api.query.SubtensorModule.Uids.getValue( - netuid, - ss58Address - ) - // eth wallet account should be the first neuron in the subnet - assert.equal(uid, uids[0]) - }) - - async function ensureCommitEpoch(netuid: number, contract: ethers.Contract) { - if (commitEpoch !== undefined) { - return - } - - const ss58Address = convertH160ToSS58(wallet.address) - const existingCommits = await api.query.SubtensorModule.WeightCommits.getValue( - netuid, - ss58Address - ) - if (Array.isArray(existingCommits) && existingCommits.length > 0) { - const entry = existingCommits[0] - const commitBlockRaw = - Array.isArray(entry) && entry.length > 1 ? entry[1] : undefined - const commitBlock = - typeof commitBlockRaw === "bigint" - ? Number(commitBlockRaw) - : Number(commitBlockRaw ?? NaN) - if (Number.isFinite(commitBlock)) { - commitEpoch = Math.trunc(commitBlock / (100 + 1)) - return - } - } - - await setStakeThreshold(api, alice, BigInt(0)) - const commitHash = getCommitHash(netuid, wallet.address) - const tx = await contract.commitWeights(netuid, commitHash) - await tx.wait() - - const commitBlock = await api.query.System.Number.getValue() - commitEpoch = Math.trunc(commitBlock / (100 + 1)) - } - - it("EVM neuron commit weights via call precompile", async () => { - let totalNetworks = await api.query.SubtensorModule.TotalNetworks.getValue() - const subnetId = totalNetworks - 1 - const commitHash = getCommitHash(subnetId, wallet.address) - const contract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet); - - await setStakeThreshold(api, alice, BigInt(1)) - await assert.rejects(async () => { - const tx = await contract.commitWeights(subnetId, commitHash) - await tx.wait() - }) - await setStakeThreshold(api, alice, BigInt(0)) - - try { - const tx = await contract.commitWeights(subnetId, commitHash) - await tx.wait() - } catch (e) { - console.log("commitWeights failed", e) - } - - const commitBlock = await api.query.System.Number.getValue() - commitEpoch = Math.trunc(commitBlock / (100 + 1)) - - const ss58Address = convertH160ToSS58(wallet.address) - - const weightsCommit = await api.query.SubtensorModule.WeightCommits.getValue(subnetId, ss58Address) - if (weightsCommit === undefined) { - throw new Error("submit weights failed") - } - else { assert.ok(weightsCommit.length > 0) } - }) - - // Temporarily disable it, there is a type error in CI. - it("EVM neuron reveal weights via call precompile", async () => { - let totalNetworks = await api.query.SubtensorModule.TotalNetworks.getValue() - const netuid = totalNetworks - 1 - const contract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet); - // set tempo or epoch large, then enough time to reveal weight - await setTempo(api, netuid, 100) - // set interval epoch as 1, it is the minimum value now - await setCommitRevealWeightsInterval(api, netuid, BigInt(1)) - - await ensureCommitEpoch(netuid, contract) - if (commitEpoch === undefined) { - throw new Error("commitEpoch should be set before revealing weights") - } - - while (true) { - const currentBlock = await api.query.System.Number.getValue() - const currentEpoch = Math.trunc(currentBlock / (100 + 1)) - // wait for one second for fast blocks - if (currentEpoch > commitEpoch) { - break - } - await new Promise(resolve => setTimeout(resolve, 1000)) - } - - await setStakeThreshold(api, alice, BigInt(1)) - await assert.rejects(async () => { - const tx = await contract.revealWeights( - netuid, - uids, - values, - salt, - version_key - ); - await tx.wait() - }) - await setStakeThreshold(api, alice, BigInt(0)) - - const tx = await contract.revealWeights( - netuid, - uids, - values, - salt, - version_key - ); - await tx.wait() - - const ss58Address = convertH160ToSS58(wallet.address) - - // check the weight commit is removed after reveal successfully - const weightsCommit = await api.query.SubtensorModule.WeightCommits.getValue(netuid, ss58Address) - assert.equal(weightsCommit, undefined) - - // check the weight is set after reveal with correct uid - const neuron_uid = await api.query.SubtensorModule.Uids.getValue( - netuid, - ss58Address - ) - - if (neuron_uid === undefined) { - throw new Error("neuron_uid not available onchain or invalid type") - } - - const weights = await api.query.SubtensorModule.Weights.getValue(netuid, neuron_uid) - - if (weights === undefined || !Array.isArray(weights)) { - throw new Error("weights not available onchain or invalid type") - } - - for (const weight of weights) { - assert.equal(weight[0], neuron_uid) - assert.ok(weight[1] !== undefined) - } - }) -}); diff --git a/contract-tests/test/neuron.precompile.serve.axon-prometheus.test.ts b/contract-tests/test/neuron.precompile.serve.axon-prometheus.test.ts deleted file mode 100644 index a80d79d486..0000000000 --- a/contract-tests/test/neuron.precompile.serve.axon-prometheus.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -import * as assert from "assert"; -import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" -import { devnet } from "@polkadot-api/descriptors" -import { PolkadotSigner, TypedApi } from "polkadot-api"; -import { convertPublicKeyToSs58, convertH160ToSS58 } from "../src/address-utils" -import { ethers } from "ethers" -import { INEURON_ADDRESS, INeuronABI } from "../src/contracts/neuron" -import { generateRandomEthersWallet } from "../src/utils" -import { forceSetBalanceToEthAddress, forceSetBalanceToSs58Address, addNewSubnetwork, burnedRegister, startCall } from "../src/subtensor" - -describe("Test neuron precompile Serve Axon Prometheus", () => { - // init eth part - const wallet1 = generateRandomEthersWallet(); - const wallet2 = generateRandomEthersWallet(); - const wallet3 = generateRandomEthersWallet(); - - // init substrate part - const hotkey = getRandomSubstrateKeypair(); - const coldkey = getRandomSubstrateKeypair(); - - let api: TypedApi - - // sudo account alice as signer - let alice: PolkadotSigner; - before(async () => { - // init variables got from await and async - api = await getDevnetApi() - alice = await getAliceSigner(); - - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(alice.publicKey)) - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)) - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) - await forceSetBalanceToEthAddress(api, wallet1.address) - await forceSetBalanceToEthAddress(api, wallet2.address) - await forceSetBalanceToEthAddress(api, wallet3.address) - let netuid = await addNewSubnetwork(api, hotkey, coldkey) - await startCall(api, netuid, coldkey) - - console.log("test the case on subnet ", netuid) - - await burnedRegister(api, netuid, convertH160ToSS58(wallet1.address), coldkey) - await burnedRegister(api, netuid, convertH160ToSS58(wallet2.address), coldkey) - await burnedRegister(api, netuid, convertH160ToSS58(wallet3.address), coldkey) - }) - - it("Serve Axon", async () => { - let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 - const version = 0; - const ip = 1; - const port = 2; - const ipType = 4; - const protocol = 0; - const placeholder1 = 8; - const placeholder2 = 9; - - const contract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet1); - - const tx = await contract.serveAxon( - netuid, - version, - ip, - port, - ipType, - protocol, - placeholder1, - placeholder2 - ); - await tx.wait(); - - const axon = await api.query.SubtensorModule.Axons.getValue( - netuid, - convertH160ToSS58(wallet1.address) - ) - assert.notEqual(axon?.block, undefined) - assert.equal(axon?.version, version) - assert.equal(axon?.ip, ip) - assert.equal(axon?.port, port) - assert.equal(axon?.ip_type, ipType) - assert.equal(axon?.protocol, protocol) - assert.equal(axon?.placeholder1, placeholder1) - assert.equal(axon?.placeholder2, placeholder2) - }); - - it("Serve Axon TLS", async () => { - let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 - const version = 0; - const ip = 1; - const port = 2; - const ipType = 4; - const protocol = 0; - const placeholder1 = 8; - const placeholder2 = 9; - // certificate length is 65 - const certificate = new Uint8Array([ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - 57, 58, 59, 60, 61, 62, 63, 64, 65, - ]); - - const contract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet2); - - const tx = await contract.serveAxonTls( - netuid, - version, - ip, - port, - ipType, - protocol, - placeholder1, - placeholder2, - certificate - ); - await tx.wait(); - - const axon = await api.query.SubtensorModule.Axons.getValue( - netuid, - convertH160ToSS58(wallet2.address)) - - assert.notEqual(axon?.block, undefined) - assert.equal(axon?.version, version) - assert.equal(axon?.ip, ip) - assert.equal(axon?.port, port) - assert.equal(axon?.ip_type, ipType) - assert.equal(axon?.protocol, protocol) - assert.equal(axon?.placeholder1, placeholder1) - assert.equal(axon?.placeholder2, placeholder2) - }); - - it("Serve Prometheus", async () => { - let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 - const version = 0; - const ip = 1; - const port = 2; - const ipType = 4; - - const contract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet3); - - const tx = await contract.servePrometheus( - netuid, - version, - ip, - port, - ipType - ); - await tx.wait(); - - const prometheus = ( - await api.query.SubtensorModule.Prometheus.getValue( - netuid, - convertH160ToSS58(wallet3.address) - ) - ) - - assert.notEqual(prometheus?.block, undefined) - assert.equal(prometheus?.version, version) - assert.equal(prometheus?.ip, ip) - assert.equal(prometheus?.port, port) - assert.equal(prometheus?.ip_type, ipType) - }); -}); \ No newline at end of file diff --git a/contract-tests/test/neuron.precompile.set-weights.test.ts b/contract-tests/test/neuron.precompile.set-weights.test.ts deleted file mode 100644 index 8ff9258664..0000000000 --- a/contract-tests/test/neuron.precompile.set-weights.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -import * as assert from "assert"; - -import { getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" -import { devnet } from "@polkadot-api/descriptors" -import { TypedApi } from "polkadot-api"; -import { convertH160ToSS58, convertPublicKeyToSs58, } from "../src/address-utils" -import { ethers } from "ethers" -import { INEURON_ADDRESS, INeuronABI } from "../src/contracts/neuron" -import { generateRandomEthersWallet } from "../src/utils" -import { - forceSetBalanceToSs58Address, forceSetBalanceToEthAddress, addNewSubnetwork, burnedRegister, setCommitRevealWeightsEnabled, - setWeightsSetRateLimit, - startCall, - disableAdminFreezeWindowAndOwnerHyperparamRateLimit -} from "../src/subtensor" - -describe("Test neuron precompile contract, set weights function", () => { - // init eth part - const wallet = generateRandomEthersWallet(); - - // init substrate part - const hotkey = getRandomSubstrateKeypair(); - const coldkey = getRandomSubstrateKeypair(); - - let api: TypedApi - - before(async () => { - api = await getDevnetApi() - - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)) - - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) - await forceSetBalanceToEthAddress(api, wallet.address) - - const netuid = await addNewSubnetwork(api, hotkey, coldkey) - await startCall(api, netuid, coldkey) - console.log("test on subnet ", netuid) - - await burnedRegister(api, netuid, convertH160ToSS58(wallet.address), coldkey) - const uid = await api.query.SubtensorModule.Uids.getValue(netuid, convertH160ToSS58(wallet.address)) - assert.notEqual(uid, undefined) - await disableAdminFreezeWindowAndOwnerHyperparamRateLimit(api) - // disable reveal and enable direct set weights - await setCommitRevealWeightsEnabled(api, netuid, false) - await setWeightsSetRateLimit(api, netuid, BigInt(0)) - }) - - it("Set weights is ok", async () => { - let netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 - const uid = await api.query.SubtensorModule.Uids.getValue(netuid, convertH160ToSS58(wallet.address)) - - const contract = new ethers.Contract(INEURON_ADDRESS, INeuronABI, wallet); - const dests = [1]; - const weights = [2]; - const version_key = 0; - - const tx = await contract.setWeights(netuid, dests, weights, version_key); - - await tx.wait(); - if (uid === undefined) { - throw new Error("uid not get on chain") - } else { - const weightsOnChain = await api.query.SubtensorModule.Weights.getValue(netuid, uid) - - weightsOnChain.forEach((weight, _) => { - const uidInWeight = weight[0]; - const value = weight[1]; - assert.equal(uidInWeight, uid) - assert.ok(value > 0) - }); - } - }) -}); diff --git a/contract-tests/test/staking.precompile.approval.test.ts b/contract-tests/test/staking.precompile.approval.test.ts index 372e1ac661..2717b90e5b 100644 --- a/contract-tests/test/staking.precompile.approval.test.ts +++ b/contract-tests/test/staking.precompile.approval.test.ts @@ -11,6 +11,7 @@ import { forceSetBalanceToEthAddress, forceSetBalanceToSs58Address, addNewSubnetwork, burnedRegister, sendProxyCall, startCall, + getStake, } from "../src/subtensor" import { ETH_LOCAL_URL } from "../src/config"; import { ISTAKING_ADDRESS, ISTAKING_V2_ADDRESS, IStakingABI, IStakingV2ABI } from "../src/contracts/staking" @@ -57,7 +58,7 @@ describe("Test approval in staking precompile", () => { stakeNetuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 // the unit in V2 is RAO, not ETH let stakeBalance = tao(20) - const stakeBefore = await api.query.SubtensorModule.Alpha.getValue(convertPublicKeyToSs58(hotkey.publicKey), convertH160ToSS58(wallet1.address), stakeNetuid) + const stakeBefore = await getStake(api, convertPublicKeyToSs58(hotkey.publicKey), convertH160ToSS58(wallet1.address), stakeNetuid) const contract = new ethers.Contract(ISTAKING_V2_ADDRESS, IStakingV2ABI, wallet1); const tx = await contract.addStake(hotkey.publicKey, stakeBalance.toString(), stakeNetuid) await tx.wait() @@ -67,7 +68,7 @@ describe("Test approval in staking precompile", () => { ); assert.ok(stakeFromContract > stakeBefore) - const stakeAfter = await api.query.SubtensorModule.Alpha.getValue(convertPublicKeyToSs58(hotkey.publicKey), convertH160ToSS58(wallet1.address), stakeNetuid) + const stakeAfter = await getStake(api, convertPublicKeyToSs58(hotkey.publicKey), convertH160ToSS58(wallet1.address), stakeNetuid) assert.ok(stakeAfter > stakeBefore) } }) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index a4f3c0df8c..ac157b2b30 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -534,6 +534,9 @@ impl Pallet { pub fn get_network_registered_block(netuid: NetUid) -> u64 { NetworkRegisteredAt::::get(netuid) } + pub fn get_registered_subnet_counter(netuid: NetUid) -> u64 { + RegisteredSubnetCounter::::get(netuid) + } pub fn get_network_immunity_period() -> u64 { NetworkImmunityPeriod::::get() } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 94a18d7d16..a1ae6cc3bb 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1693,6 +1693,18 @@ pub mod pallet { pub type NetworkRegisteredAt = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultNetworkRegisteredAt>; + /// --- MAP ( netuid ) --> registered_subnet_counter + /// + /// Monotonic counter incremented on every successful `do_register_network` + /// for a given netuid. Consumers that persist per-netuid state keyed by + /// `(user, netuid)` (e.g. the staking precompile `AllowancesStorage`) can + /// mix the current counter value into their storage key so that entries + /// written under a previous registration of the same netuid become + /// unreachable after the netuid is re-registered, without requiring + /// unbounded storage iteration on deregistration. + #[pallet::storage] + pub type RegisteredSubnetCounter = StorageMap<_, Identity, NetUid, u64, ValueQuery>; + /// --- MAP ( netuid ) --> pending_server_emission #[pallet::storage] pub type PendingServerEmission = diff --git a/pallets/subtensor/src/subnets/leasing.rs b/pallets/subtensor/src/subnets/leasing.rs index cc1094d227..4ff7888f8f 100644 --- a/pallets/subtensor/src/subnets/leasing.rs +++ b/pallets/subtensor/src/subnets/leasing.rs @@ -298,11 +298,19 @@ impl Pallet { let mut alpha_distributed = AlphaBalance::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 + // distributed so far to obtain how much alpha is left to distribute to the beneficiary. + // + // Use `floor` per contributor so that the sum of all per-contributor shares is + // guaranteed to be less than or equal to `total_contributors_cut_alpha`. Rounding + // up per contributor (as this code previously did) would accumulate over multiple + // contributors to more than the total, causing either an over-transfer from the + // lease coldkey stake or the beneficiary's leftover calculation on line ~327 to + // saturate to zero (so the beneficiary would receive nothing even when there was + // legitimately some leftover dust to send to them). 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() + .floor() .saturating_to_num::(); Self::transfer_stake_within_subnet( diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 2f2869d4ec..fd8b61b5dc 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -204,6 +204,7 @@ impl Pallet { // --- 15. Set the creation terms. NetworkRegisteredAt::::insert(netuid_to_register, current_block); + RegisteredSubnetCounter::::mutate(netuid_to_register, |c| *c = c.saturating_add(1)); // --- 16. Set the symbol. let symbol = Self::get_next_available_symbol(netuid_to_register); diff --git a/pallets/subtensor/src/tests/leasing.rs b/pallets/subtensor/src/tests/leasing.rs index 0c6ae629c3..c5336e96df 100644 --- a/pallets/subtensor/src/tests/leasing.rs +++ b/pallets/subtensor/src/tests/leasing.rs @@ -571,7 +571,7 @@ fn test_distribute_lease_network_dividends_multiple_contributors_works() { let expected_contributor1_alpha = SubnetLeaseShares::::get(lease_id, contributions[0].0) .saturating_mul(U64F64::from(distributed_alpha.to_u64())) - .ceil() + .floor() .to_num::(); assert_eq!(contributor1_alpha_delta, expected_contributor1_alpha.into()); assert_eq!( @@ -586,7 +586,7 @@ fn test_distribute_lease_network_dividends_multiple_contributors_works() { let expected_contributor2_alpha = SubnetLeaseShares::::get(lease_id, contributions[1].0) .saturating_mul(U64F64::from(distributed_alpha.to_u64())) - .ceil() + .floor() .to_num::(); assert_eq!(contributor2_alpha_delta, expected_contributor2_alpha.into()); assert_eq!( diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 0edb743e5a..c5107cffc5 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -2683,3 +2683,70 @@ fn register_network_non_associated_hotkey_does_not_withdraw_or_write_owner_alpha ); }); } + +#[test] +fn registered_subnet_counter_bumps_on_first_registration() { + new_test_ext(1).execute_with(|| { + let cold = U256::from(1); + let hot = U256::from(2); + + let netuid = add_dynamic_network(&hot, &cold); + + assert_eq!( + SubtensorModule::get_registered_subnet_counter(netuid), + 1, + "first registration of a netuid must leave counter == 1" + ); + }); +} + +#[test] +fn registered_subnet_counter_is_independent_per_netuid() { + new_test_ext(1).execute_with(|| { + let n1 = add_dynamic_network(&U256::from(10), &U256::from(11)); + let n2 = add_dynamic_network(&U256::from(20), &U256::from(21)); + + assert_ne!(n1, n2); + assert_eq!(SubtensorModule::get_registered_subnet_counter(n1), 1); + assert_eq!(SubtensorModule::get_registered_subnet_counter(n2), 1); + }); +} + +#[test] +fn registered_subnet_counter_survives_dissolve_and_bumps_on_reregistration() { + new_test_ext(1).execute_with(|| { + // Force reuse of the same netuid on re-registration by pinning the + // active subnet cap so the next registration must prune. + SubtensorModule::set_max_subnets(2); + + let owner_cold = U256::from(100); + let owner_hot = U256::from(101); + let netuid = add_dynamic_network(&owner_hot, &owner_cold); + assert_eq!(SubtensorModule::get_registered_subnet_counter(netuid), 1); + + // Dissolve: counter is intentionally *not* cleared — stale consumers + // can still detect the pre-dereg lifetime if they stored the counter + // value they observed at approval time. + assert_ok!(SubtensorModule::do_dissolve_network(netuid)); + assert!(!SubtensorModule::if_subnet_exist(netuid)); + assert_eq!( + SubtensorModule::get_registered_subnet_counter(netuid), + 1, + "dissolve must not clear or reset the counter" + ); + + // Re-register. With the cap pinned, the prune selector reuses the + // freed netuid; the counter bumps to 2 so that any state still keyed + // to the prior value becomes unreachable under the new registration. + let reg_netuid = add_dynamic_network(&owner_hot, &owner_cold); + assert_eq!( + reg_netuid, netuid, + "the pruned netuid should be reused under the subnet cap" + ); + assert_eq!( + SubtensorModule::get_registered_subnet_counter(netuid), + 2, + "re-registration must bump counter" + ); + }); +} diff --git a/precompiles/Cargo.toml b/precompiles/Cargo.toml index be1824cf91..68f617176d 100644 --- a/precompiles/Cargo.toml +++ b/precompiles/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/opentensor/subtensor/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -# codec.workspace = true +codec.workspace = true ed25519-dalek = { workspace = true, features = ["alloc"] } fp-evm.workspace = true frame-support.workspace = true @@ -46,7 +46,7 @@ workspace = true [features] default = ["std"] std = [ - # "codec/std", + "codec/std", "ed25519-dalek/std", "fp-evm/std", "frame-support/std", @@ -55,16 +55,20 @@ std = [ "pallet-admin-utils/std", "pallet-balances/std", "pallet-crowdloan/std", + "pallet-drand/std", "pallet-evm-precompile-bn128/std", "pallet-evm-precompile-dispatch/std", "pallet-evm-precompile-modexp/std", "pallet-evm-precompile-sha3fips/std", "pallet-evm-precompile-simple/std", "pallet-evm/std", + "pallet-preimage/std", + "pallet-scheduler/std", "pallet-subtensor-proxy/std", "pallet-subtensor-swap/std", "pallet-subtensor/std", "pallet-shield/std", + "pallet-timestamp/std", "precompile-utils/std", "scale-info/std", "sp-core/std", @@ -75,3 +79,28 @@ std = [ "subtensor-runtime-common/std", "subtensor-swap-interface/std", ] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-admin-utils/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-crowdloan/runtime-benchmarks", + "pallet-drand/runtime-benchmarks", + "pallet-evm/runtime-benchmarks", + "pallet-preimage/runtime-benchmarks", + "pallet-scheduler/runtime-benchmarks", + "pallet-shield/runtime-benchmarks", + "pallet-subtensor-swap/runtime-benchmarks", + "pallet-subtensor-proxy/runtime-benchmarks", + "pallet-subtensor/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "subtensor-runtime-common/runtime-benchmarks", +] + +[dev-dependencies] +pallet-drand = { workspace = true, features = ["std"] } +pallet-preimage = { workspace = true, features = ["std"] } +pallet-scheduler = { workspace = true, features = ["std"] } +pallet-timestamp = { workspace = true, features = ["std"] } +precompile-utils = { workspace = true, features = ["std", "testing"] } diff --git a/precompiles/src/address_mapping.rs b/precompiles/src/address_mapping.rs index fa34692657..c8f3815c49 100644 --- a/precompiles/src/address_mapping.rs +++ b/precompiles/src/address_mapping.rs @@ -75,3 +75,117 @@ where Ok(target_address.into()) } } + +#[cfg(test)] +mod tests { + #![allow(clippy::expect_used)] + + use super::*; + use crate::mock::{ + Runtime, addr_from_index, execute_precompile, new_test_ext, precompiles, selector_u32, + }; + use pallet_evm::AddressMapping; + use precompile_utils::solidity::{codec::Address, encode_with_selector}; + use precompile_utils::testing::PrecompileTesterExt; + use sp_core::U256; + + #[test] + fn address_mapping_precompile_returns_runtime_address_mapping() { + new_test_ext().execute_with(|| { + let precompiles = precompiles::>(); + let caller = addr_from_index(1); + let target_address = addr_from_index(0x1234); + let input = encode_with_selector( + selector_u32("addressMapping(address)"), + (Address(target_address),), + ); + let mapped_account = + ::AddressMapping::into_account_id(target_address); + let expected_output: [u8; 32] = mapped_account.into(); + + precompiles + .prepare_test( + caller, + addr_from_index(AddressMappingPrecompile::::INDEX), + input, + ) + .with_static_call(true) + .execute_returns_raw(expected_output.to_vec()); + }); + } + + #[test] + fn address_mapping_precompile_maps_distinct_addresses_to_distinct_accounts() { + new_test_ext().execute_with(|| { + let caller = addr_from_index(1); + let first_address = addr_from_index(0x1234); + let second_address = addr_from_index(0x5678); + let precompile_addr = addr_from_index(AddressMappingPrecompile::::INDEX); + + let first_output = execute_precompile( + &precompiles::>(), + precompile_addr, + caller, + encode_with_selector( + selector_u32("addressMapping(address)"), + (Address(first_address),), + ), + U256::zero(), + ) + .expect("expected precompile mapping call to be routed to a precompile") + .expect("address mapping call should succeed") + .output; + let second_output = execute_precompile( + &precompiles::>(), + precompile_addr, + caller, + encode_with_selector( + selector_u32("addressMapping(address)"), + (Address(second_address),), + ), + U256::zero(), + ) + .expect("expected precompile mapping call to be routed to a precompile") + .expect("address mapping call should succeed") + .output; + + assert_ne!(first_output, second_output); + }); + } + + #[test] + fn address_mapping_precompile_is_deterministic() { + new_test_ext().execute_with(|| { + let caller = addr_from_index(1); + let target_address = addr_from_index(0x1234); + let precompile_addr = addr_from_index(AddressMappingPrecompile::::INDEX); + let input = encode_with_selector( + selector_u32("addressMapping(address)"), + (Address(target_address),), + ); + + let first_output = execute_precompile( + &precompiles::>(), + precompile_addr, + caller, + input.clone(), + U256::zero(), + ) + .expect("expected precompile mapping call to be routed to a precompile") + .expect("address mapping call should succeed") + .output; + let second_output = execute_precompile( + &precompiles::>(), + precompile_addr, + caller, + input, + U256::zero(), + ) + .expect("expected precompile mapping call to be routed to a precompile") + .expect("address mapping call should succeed") + .output; + + assert_eq!(first_output, second_output); + }); + } +} diff --git a/precompiles/src/alpha.rs b/precompiles/src/alpha.rs index c90851c543..b183c5ec23 100644 --- a/precompiles/src/alpha.rs +++ b/precompiles/src/alpha.rs @@ -214,3 +214,365 @@ where Ok(price_eth) } } + +#[cfg(test)] +mod tests { + #![allow(clippy::expect_used)] + + use super::*; + use crate::PrecompileExt; + use crate::mock::{ + Runtime, addr_from_index, alpha_price_to_evm, assert_static_call, new_test_ext, + precompiles, selector_u32, + }; + use precompile_utils::solidity::encode_with_selector; + use substrate_fixed::types::I96F32; + use subtensor_runtime_common::{AlphaBalance, TaoBalance}; + + const DYNAMIC_NETUID_U16: u16 = 1; + const SUM_PRICE_NETUID_U16: u16 = 2; + const TAO_WEIGHT: u64 = 444; + const CK_BURN: u64 = 555; + const EMA_HALVING_BLOCKS: u64 = 777; + const SUBNET_VOLUME: u128 = 888; + const TAO_IN_EMISSION: u64 = 111; + const ALPHA_IN_EMISSION: u64 = 222; + const ALPHA_OUT_EMISSION: u64 = 333; + + fn seed_alpha_test_state() { + let dynamic_netuid = NetUid::from(DYNAMIC_NETUID_U16); + let sum_price_netuid = NetUid::from(SUM_PRICE_NETUID_U16); + + pallet_subtensor::TaoWeight::::put(TAO_WEIGHT); + pallet_subtensor::CKBurn::::put(CK_BURN); + + pallet_subtensor::NetworksAdded::::insert(dynamic_netuid, true); + pallet_subtensor::SubnetMechanism::::insert(dynamic_netuid, 1); + pallet_subtensor::SubnetTAO::::insert( + dynamic_netuid, + TaoBalance::from(20_000_000_000_u64), + ); + pallet_subtensor::SubnetAlphaIn::::insert( + dynamic_netuid, + AlphaBalance::from(10_000_000_000_u64), + ); + pallet_subtensor::SubnetAlphaOut::::insert( + dynamic_netuid, + AlphaBalance::from(3_000_000_000_u64), + ); + pallet_subtensor::SubnetTaoInEmission::::insert( + dynamic_netuid, + TaoBalance::from(TAO_IN_EMISSION), + ); + pallet_subtensor::SubnetAlphaInEmission::::insert( + dynamic_netuid, + AlphaBalance::from(ALPHA_IN_EMISSION), + ); + pallet_subtensor::SubnetAlphaOutEmission::::insert( + dynamic_netuid, + AlphaBalance::from(ALPHA_OUT_EMISSION), + ); + pallet_subtensor::SubnetVolume::::insert(dynamic_netuid, SUBNET_VOLUME); + pallet_subtensor::EMAPriceHalvingBlocks::::insert( + dynamic_netuid, + EMA_HALVING_BLOCKS, + ); + pallet_subtensor::SubnetMovingPrice::::insert( + dynamic_netuid, + I96F32::from_num(3.0 / 2.0), + ); + + pallet_subtensor::NetworksAdded::::insert(sum_price_netuid, true); + pallet_subtensor::SubnetMechanism::::insert(sum_price_netuid, 1); + pallet_subtensor::SubnetTAO::::insert( + sum_price_netuid, + TaoBalance::from(5_000_000_000_u64), + ); + pallet_subtensor::SubnetAlphaIn::::insert( + sum_price_netuid, + AlphaBalance::from(10_000_000_000_u64), + ); + } + + #[test] + fn alpha_precompile_matches_runtime_values_for_dynamic_subnet() { + new_test_ext().execute_with(|| { + seed_alpha_test_state(); + + let precompiles = precompiles::>(); + let caller = addr_from_index(1); + let precompile_addr = addr_from_index(AlphaPrecompile::::INDEX); + + let dynamic_netuid = NetUid::from(DYNAMIC_NETUID_U16); + let alpha_price = + as SwapHandler>::current_alpha_price( + dynamic_netuid, + ); + let moving_alpha_price = + pallet_subtensor::Pallet::::get_moving_alpha_price(dynamic_netuid); + + assert!(alpha_price > U96F32::from_num(1)); + assert!(moving_alpha_price > U96F32::from_num(1)); + + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector(selector_u32("getAlphaPrice(uint16)"), (DYNAMIC_NETUID_U16,)), + alpha_price_to_evm(alpha_price), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector( + selector_u32("getMovingAlphaPrice(uint16)"), + (DYNAMIC_NETUID_U16,), + ), + alpha_price_to_evm(moving_alpha_price), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector(selector_u32("getTaoInPool(uint16)"), (DYNAMIC_NETUID_U16,)), + pallet_subtensor::SubnetTAO::::get(dynamic_netuid) + .to_u64() + .into(), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector( + selector_u32("getAlphaInPool(uint16)"), + (DYNAMIC_NETUID_U16,), + ), + u64::from(pallet_subtensor::SubnetAlphaIn::::get( + dynamic_netuid, + )) + .into(), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector( + selector_u32("getAlphaOutPool(uint16)"), + (DYNAMIC_NETUID_U16,), + ), + u64::from(pallet_subtensor::SubnetAlphaOut::::get( + dynamic_netuid, + )) + .into(), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector( + selector_u32("getAlphaIssuance(uint16)"), + (DYNAMIC_NETUID_U16,), + ), + u64::from(pallet_subtensor::Pallet::::get_alpha_issuance( + dynamic_netuid, + )) + .into(), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector( + selector_u32("getSubnetMechanism(uint16)"), + (DYNAMIC_NETUID_U16,), + ), + pallet_subtensor::SubnetMechanism::::get(dynamic_netuid).into(), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector( + selector_u32("getEMAPriceHalvingBlocks(uint16)"), + (DYNAMIC_NETUID_U16,), + ), + pallet_subtensor::EMAPriceHalvingBlocks::::get(dynamic_netuid).into(), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector( + selector_u32("getSubnetVolume(uint16)"), + (DYNAMIC_NETUID_U16,), + ), + pallet_subtensor::SubnetVolume::::get(dynamic_netuid).into(), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector( + selector_u32("getTaoInEmission(uint16)"), + (DYNAMIC_NETUID_U16,), + ), + pallet_subtensor::SubnetTaoInEmission::::get(dynamic_netuid) + .to_u64() + .into(), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector( + selector_u32("getAlphaInEmission(uint16)"), + (DYNAMIC_NETUID_U16,), + ), + pallet_subtensor::SubnetAlphaInEmission::::get(dynamic_netuid) + .to_u64() + .into(), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector( + selector_u32("getAlphaOutEmission(uint16)"), + (DYNAMIC_NETUID_U16,), + ), + pallet_subtensor::SubnetAlphaOutEmission::::get(dynamic_netuid) + .to_u64() + .into(), + ); + }); + } + + #[test] + fn alpha_precompile_matches_runtime_global_values() { + new_test_ext().execute_with(|| { + seed_alpha_test_state(); + + let precompiles = precompiles::>(); + let caller = addr_from_index(1); + let precompile_addr = addr_from_index(AlphaPrecompile::::INDEX); + + let mut sum_alpha_price = U96F32::from_num(0); + for (netuid, _) in pallet_subtensor::NetworksAdded::::iter() { + if netuid.is_root() { + continue; + } + let price = + as SwapHandler>::current_alpha_price( + netuid, + ); + if price < U96F32::from_num(1) { + sum_alpha_price += price; + } + } + + assert!(sum_alpha_price > U96F32::from_num(0)); + + assert_static_call( + &precompiles, + caller, + precompile_addr, + selector_u32("getCKBurn()").to_be_bytes().to_vec(), + pallet_subtensor::CKBurn::::get().into(), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + selector_u32("getTaoWeight()").to_be_bytes().to_vec(), + pallet_subtensor::TaoWeight::::get().into(), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + selector_u32("getRootNetuid()").to_be_bytes().to_vec(), + u16::from(NetUid::ROOT).into(), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + selector_u32("getSumAlphaPrice()").to_be_bytes().to_vec(), + alpha_price_to_evm(sum_alpha_price), + ); + }); + } + + #[test] + fn alpha_precompile_matches_runtime_swap_simulations() { + new_test_ext().execute_with(|| { + seed_alpha_test_state(); + + let precompiles = precompiles::>(); + let caller = addr_from_index(1); + let precompile_addr = addr_from_index(AlphaPrecompile::::INDEX); + + let tao_amount = 1_000_000_000_u64; + let alpha_amount = 1_000_000_000_u64; + let expected_alpha = as SwapHandler>::sim_swap( + NetUid::from(DYNAMIC_NETUID_U16), + pallet_subtensor::GetAlphaForTao::::with_amount(tao_amount), + ) + .expect("tao-for-alpha simulation should succeed") + .amount_paid_out + .to_u64(); + let expected_tao = as SwapHandler>::sim_swap( + NetUid::from(DYNAMIC_NETUID_U16), + pallet_subtensor::GetTaoForAlpha::::with_amount(alpha_amount), + ) + .expect("alpha-for-tao simulation should succeed") + .amount_paid_out + .to_u64(); + + assert!(expected_alpha > 0); + assert!(expected_tao > 0); + + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector( + selector_u32("simSwapTaoForAlpha(uint16,uint64)"), + (DYNAMIC_NETUID_U16, tao_amount), + ), + expected_alpha.into(), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector( + selector_u32("simSwapAlphaForTao(uint16,uint64)"), + (DYNAMIC_NETUID_U16, alpha_amount), + ), + expected_tao.into(), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector( + selector_u32("simSwapTaoForAlpha(uint16,uint64)"), + (DYNAMIC_NETUID_U16, 0_u64), + ), + U256::zero(), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + encode_with_selector( + selector_u32("simSwapAlphaForTao(uint16,uint64)"), + (DYNAMIC_NETUID_U16, 0_u64), + ), + U256::zero(), + ); + }); + } +} diff --git a/precompiles/src/ed25519.rs b/precompiles/src/ed25519.rs index dbfe032cdf..38204c4304 100644 --- a/precompiles/src/ed25519.rs +++ b/precompiles/src/ed25519.rs @@ -56,3 +56,82 @@ where Ok((ExitSucceed::Returned, buf.to_vec())) } } + +#[cfg(test)] +mod tests { + #![allow(clippy::expect_used)] + + use super::*; + use crate::mock::{ + AccountId, abi_word, addr_from_index, new_test_ext, precompiles, selector_u32, + }; + use precompile_utils::solidity::encode_with_selector; + use precompile_utils::testing::PrecompileTesterExt; + use sp_core::{H256, Pair, U256, ed25519}; + + #[test] + fn ed25519_precompile_verifies_valid_and_invalid_signatures() { + new_test_ext().execute_with(|| { + let caller = addr_from_index(1); + let precompile_addr = addr_from_index(Ed25519Verify::::INDEX); + + let pair = ed25519::Pair::from_seed(&[1u8; 32]); + let message = [7u8; 32]; + let signature = pair.sign(&message); + let public_key = pair.public(); + let broken_message = [8u8; 32]; + let mut broken_signature = signature.0; + broken_signature[0] ^= 1; + let broken_signature = ed25519::Signature::from_raw(broken_signature); + + precompiles::>() + .prepare_test( + caller, + precompile_addr, + encode_with_selector( + selector_u32("verify(bytes32,bytes32,bytes32,bytes32)"), + ( + H256::from(message), + H256::from(public_key.0), + H256::from_slice(&signature.0[..32]), + H256::from_slice(&signature.0[32..]), + ), + ), + ) + .with_static_call(true) + .execute_returns_raw(abi_word(U256::one())); + precompiles::>() + .prepare_test( + caller, + precompile_addr, + encode_with_selector( + selector_u32("verify(bytes32,bytes32,bytes32,bytes32)"), + ( + H256::from(broken_message), + H256::from(public_key.0), + H256::from_slice(&signature.0[..32]), + H256::from_slice(&signature.0[32..]), + ), + ), + ) + .with_static_call(true) + .execute_returns_raw(abi_word(U256::zero())); + precompiles::>() + .prepare_test( + caller, + precompile_addr, + encode_with_selector( + selector_u32("verify(bytes32,bytes32,bytes32,bytes32)"), + ( + H256::from(message), + H256::from(public_key.0), + H256::from_slice(&broken_signature.0[..32]), + H256::from_slice(&broken_signature.0[32..]), + ), + ), + ) + .with_static_call(true) + .execute_returns_raw(abi_word(U256::zero())); + }); + } +} diff --git a/precompiles/src/lib.rs b/precompiles/src/lib.rs index a824ac39d4..39815a6946 100644 --- a/precompiles/src/lib.rs +++ b/precompiles/src/lib.rs @@ -61,6 +61,9 @@ mod subnet; mod uid_lookup; mod voting_power; +#[cfg(test)] +mod mock; + pub struct Precompiles(PhantomData); impl Default for Precompiles diff --git a/precompiles/src/metagraph.rs b/precompiles/src/metagraph.rs index c5b0b931f2..4cffb76a4f 100644 --- a/precompiles/src/metagraph.rs +++ b/precompiles/src/metagraph.rs @@ -187,3 +187,204 @@ impl From for AxonInfo { } } } + +#[cfg(test)] +mod tests { + #![allow(clippy::expect_used)] + + use super::*; + use crate::PrecompileExt; + use crate::mock::{ + Runtime, abi_word, addr_from_index, new_test_ext, precompiles, selector_u32, + }; + use precompile_utils::solidity::{encode_return_value, encode_with_selector}; + use precompile_utils::testing::PrecompileTesterExt; + use sp_core::H256; + use subtensor_runtime_common::{AlphaBalance, NetUid, NetUidStorageIndex}; + + const TEST_NETUID_U16: u16 = 1; + const UID: u16 = 0; + const EMISSION: u64 = 111; + const VTRUST: u16 = 222; + const LAST_UPDATE: u64 = 333; + const AXON_BLOCK: u64 = 444; + const AXON_VERSION: u32 = 555; + const AXON_IP: u128 = 666; + const AXON_PORT: u16 = 777; + const AXON_IP_TYPE: u8 = 4; + const AXON_PROTOCOL: u8 = 1; + + fn seed_metagraph_test_state() -> ( + NetUid, + ::AccountId, + ::AccountId, + pallet_subtensor::AxonInfo, + ) { + let netuid = NetUid::from(TEST_NETUID_U16); + let hotkey = ::AccountId::from([0x11; 32]); + let coldkey = ::AccountId::from([0x22; 32]); + + let axon = pallet_subtensor::AxonInfo { + block: AXON_BLOCK, + version: AXON_VERSION, + ip: AXON_IP, + port: AXON_PORT, + ip_type: AXON_IP_TYPE, + protocol: AXON_PROTOCOL, + placeholder1: 0, + placeholder2: 0, + }; + + pallet_subtensor::SubnetworkN::::insert(netuid, 1); + pallet_subtensor::Keys::::insert(netuid, UID, hotkey.clone()); + pallet_subtensor::Uids::::insert(netuid, &hotkey, UID); + pallet_subtensor::Owner::::insert(&hotkey, coldkey.clone()); + pallet_subtensor::Emission::::insert(netuid, vec![AlphaBalance::from(EMISSION)]); + pallet_subtensor::ValidatorTrust::::insert(netuid, vec![VTRUST]); + pallet_subtensor::ValidatorPermit::::insert(netuid, vec![true]); + pallet_subtensor::LastUpdate::::insert( + NetUidStorageIndex::from(netuid), + vec![LAST_UPDATE], + ); + pallet_subtensor::Active::::insert(netuid, vec![true]); + pallet_subtensor::Axons::::insert(netuid, &hotkey, axon.clone()); + + (netuid, hotkey, coldkey, axon) + } + + #[test] + fn metagraph_precompile_matches_runtime_values() { + new_test_ext().execute_with(|| { + let (netuid, hotkey, coldkey, axon) = seed_metagraph_test_state(); + let precompiles = precompiles::>(); + let caller = addr_from_index(1); + let precompile_addr = addr_from_index(MetagraphPrecompile::::INDEX); + + let uid_count = pallet_subtensor::SubnetworkN::::get(netuid); + let emission = + pallet_subtensor::Pallet::::get_emission_for_uid(netuid, UID).to_u64(); + let vtrust = + pallet_subtensor::Pallet::::get_validator_trust_for_uid(netuid, UID); + let validator_status = + pallet_subtensor::Pallet::::get_validator_permit_for_uid(netuid, UID); + let last_update = pallet_subtensor::Pallet::::get_last_update_for_uid( + NetUidStorageIndex::from(netuid), + UID, + ); + let is_active = pallet_subtensor::Pallet::::get_active_for_uid(netuid, UID); + let runtime_axon = pallet_subtensor::Pallet::::get_axon_info(netuid, &hotkey); + + assert_eq!(uid_count, 1); + assert_eq!(emission, EMISSION); + assert_eq!(vtrust, VTRUST); + assert!(validator_status); + assert_eq!(last_update, LAST_UPDATE); + assert!(is_active); + assert_eq!(runtime_axon, axon); + + precompiles + .prepare_test( + caller, + precompile_addr, + encode_with_selector(selector_u32("getUidCount(uint16)"), (TEST_NETUID_U16,)), + ) + .with_static_call(true) + .execute_returns_raw(abi_word(uid_count.into())); + precompiles + .prepare_test( + caller, + precompile_addr, + encode_with_selector( + selector_u32("getAxon(uint16,uint16)"), + (TEST_NETUID_U16, UID), + ), + ) + .with_static_call(true) + .execute_returns_raw(encode_return_value(( + runtime_axon.block, + runtime_axon.version, + runtime_axon.ip, + runtime_axon.port, + runtime_axon.ip_type, + runtime_axon.protocol, + ))); + precompiles + .prepare_test( + caller, + precompile_addr, + encode_with_selector( + selector_u32("getEmission(uint16,uint16)"), + (TEST_NETUID_U16, UID), + ), + ) + .with_static_call(true) + .execute_returns_raw(abi_word(emission.into())); + precompiles + .prepare_test( + caller, + precompile_addr, + encode_with_selector( + selector_u32("getVtrust(uint16,uint16)"), + (TEST_NETUID_U16, UID), + ), + ) + .with_static_call(true) + .execute_returns_raw(abi_word(vtrust.into())); + precompiles + .prepare_test( + caller, + precompile_addr, + encode_with_selector( + selector_u32("getValidatorStatus(uint16,uint16)"), + (TEST_NETUID_U16, UID), + ), + ) + .with_static_call(true) + .execute_returns_raw(abi_word((validator_status as u8).into())); + precompiles + .prepare_test( + caller, + precompile_addr, + encode_with_selector( + selector_u32("getLastUpdate(uint16,uint16)"), + (TEST_NETUID_U16, UID), + ), + ) + .with_static_call(true) + .execute_returns_raw(abi_word(last_update.into())); + precompiles + .prepare_test( + caller, + precompile_addr, + encode_with_selector( + selector_u32("getIsActive(uint16,uint16)"), + (TEST_NETUID_U16, UID), + ), + ) + .with_static_call(true) + .execute_returns_raw(abi_word((is_active as u8).into())); + precompiles + .prepare_test( + caller, + precompile_addr, + encode_with_selector( + selector_u32("getHotkey(uint16,uint16)"), + (TEST_NETUID_U16, UID), + ), + ) + .with_static_call(true) + .execute_returns_raw(H256::from_slice(hotkey.as_ref()).as_bytes().to_vec()); + precompiles + .prepare_test( + caller, + precompile_addr, + encode_with_selector( + selector_u32("getColdkey(uint16,uint16)"), + (TEST_NETUID_U16, UID), + ), + ) + .with_static_call(true) + .execute_returns_raw(H256::from_slice(coldkey.as_ref()).as_bytes().to_vec()); + }); + } +} diff --git a/precompiles/src/mock.rs b/precompiles/src/mock.rs new file mode 100644 index 0000000000..452d8cf6a7 --- /dev/null +++ b/precompiles/src/mock.rs @@ -0,0 +1,596 @@ +#![allow(clippy::unwrap_used)] +#![allow(clippy::expect_used)] +#![allow(clippy::arithmetic_side_effects)] + +use core::{marker::PhantomData, num::NonZeroU64}; + +use fp_evm::{Context, PrecompileResult}; +use frame_support::{ + PalletId, derive_impl, parameter_types, + traits::{Everything, InherentBuilder, PrivilegeCmp}, + weights::Weight, +}; +use frame_system::{EnsureRoot, limits, offchain::CreateTransactionBase}; +use pallet_evm::{ + BalanceConverter, EnsureAddressNever, EnsureAddressRoot, EvmBalance, PrecompileHandle, + PrecompileSet, SubstrateBalance, +}; +use precompile_utils::testing::MockHandle; +use sp_core::{ConstU64, H160, H256, U256, crypto::AccountId32}; +use sp_runtime::{ + BuildStorage, KeyTypeId, Perbill, Percent, + testing::TestXt, + traits::{BlakeTwo256, ConstU32, IdentityLookup}, +}; +use substrate_fixed::types::U96F32; +use subtensor_runtime_common::{AuthorshipInfo, NetUid, ProxyType, TaoBalance}; + +use crate::PrecompileExt; + +pub(crate) type AccountId = AccountId32; +pub(crate) type Block = frame_system::mocking::MockBlock; +pub(crate) type UncheckedExtrinsic = TestXt; + +frame_support::construct_runtime!( + pub enum Runtime { + System: frame_system = 1, + Balances: pallet_balances = 2, + Timestamp: pallet_timestamp = 3, + Shield: pallet_shield = 4, + SubtensorModule: pallet_subtensor::{Pallet, Call, Storage, Event} = 5, + Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event} = 6, + Preimage: pallet_preimage::{Pallet, Call, Storage, Event} = 7, + Drand: pallet_drand::{Pallet, Call, Storage, Event} = 8, + Swap: pallet_subtensor_swap::{Pallet, Call, Storage, Event} = 9, + Crowdloan: pallet_crowdloan::{Pallet, Call, Storage, Event} = 10, + Proxy: pallet_subtensor_proxy = 11, + Evm: pallet_evm = 12, + } +); + +const EVM_DECIMALS_FACTOR: u64 = 1_000_000_000; + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; + pub BlockWeights: limits::BlockWeights = limits::BlockWeights::with_sensible_defaults( + Weight::from_parts(2_000_000_000_000, u64::MAX), + Perbill::from_percent(75), + ); + pub const ExistentialDeposit: TaoBalance = TaoBalance::new(1); + pub const MinimumPeriod: u64 = 5; + pub const PreimageMaxSize: u32 = 4096 * 1024; + pub const PreimageBaseDeposit: TaoBalance = TaoBalance::new(1); + pub const PreimageByteDeposit: TaoBalance = TaoBalance::new(1); + pub const CrowdloanPalletId: PalletId = PalletId(*b"bt/cloan"); + pub const MinimumDeposit: TaoBalance = TaoBalance::new(50); + pub const AbsoluteMinimumContribution: TaoBalance = TaoBalance::new(10); + pub const MinimumBlockDuration: u64 = 20; + pub const MaximumBlockDuration: u64 = 100; + pub const RefundContributorsLimit: u32 = 5; + pub const MaxContributors: u32 = 10; + pub const SwapProtocolId: PalletId = PalletId(*b"ten/swap"); + pub const SwapMaxFeeRate: u16 = 10000; + pub const SwapMaxPositions: u32 = 100; + pub const SwapMinimumLiquidity: u64 = 1_000; + pub const SwapMinimumReserve: NonZeroU64 = NonZeroU64::new(1_000_000).unwrap(); + pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * + BlockWeights::get().max_block; + pub const MaxScheduledPerBlock: u32 = 50; + pub const MaxAuthorities: u32 = 32; + pub static BlockGasLimit: U256 = U256::max_value(); + pub WeightPerGas: Weight = Weight::from_parts(20_000, 0); + pub const ProxyDepositBase: TaoBalance = TaoBalance::new(1); + pub const ProxyDepositFactor: TaoBalance = TaoBalance::new(1); + pub const MaxProxies: u32 = 20; + pub const MaxPending: u32 = 15; + pub const AnnouncementDepositBase: TaoBalance = TaoBalance::new(1); + pub const AnnouncementDepositFactor: TaoBalance = TaoBalance::new(1); + pub const InitialMinAllowedWeights: u16 = 0; + pub const InitialEmissionValue: u16 = 0; + pub const InitialRho: u16 = 30; + pub const InitialAlphaSigmoidSteepness: i16 = 1000; + pub const InitialKappa: u16 = 32_767; + pub const InitialTempo: u16 = 360; + pub const InitialImmunityPeriod: u16 = 2; + pub const InitialMinAllowedUids: u16 = 2; + pub const InitialMaxAllowedUids: u16 = 256; + pub const InitialBondsMovingAverage: u64 = 900_000; + pub const InitialBondsPenalty: u16 = u16::MAX; + pub const InitialBondsResetOn: bool = false; + pub const InitialDefaultDelegateTake: u16 = 11_796; + pub const InitialMinDelegateTake: u16 = 5_898; + pub const InitialDefaultChildKeyTake: u16 = 0; + pub const InitialMinChildKeyTake: u16 = 0; + pub const InitialMaxChildKeyTake: u16 = 11_796; + pub const InitialWeightsVersionKey: u64 = 0; + pub const InitialServingRateLimit: u64 = 0; + pub const InitialTxRateLimit: u64 = 0; + pub const InitialTxDelegateTakeRateLimit: u64 = 0; + pub const InitialTxChildKeyTakeRateLimit: u64 = 0; + pub const InitialBurn: TaoBalance = TaoBalance::new(0); + pub const InitialMinBurn: TaoBalance = TaoBalance::new(500_000); + pub const InitialMaxBurn: TaoBalance = TaoBalance::new(1_000_000_000); + pub const MinBurnUpperBound: TaoBalance = TaoBalance::new(1_000_000_000); + pub const MaxBurnLowerBound: TaoBalance = TaoBalance::new(100_000_000); + pub const InitialValidatorPruneLen: u64 = 0; + pub const InitialScalingLawPower: u16 = 50; + pub const InitialMaxAllowedValidators: u16 = 100; + pub const InitialIssuance: TaoBalance = TaoBalance::new(0); + pub const InitialDifficulty: u64 = 10_000; + pub const InitialActivityCutoff: u16 = 5_000; + pub const InitialAdjustmentInterval: u16 = 100; + pub const InitialAdjustmentAlpha: u64 = 0; + pub const InitialMaxRegistrationsPerBlock: u16 = 3; + pub const InitialTargetRegistrationsPerInterval: u16 = 2; + pub const InitialPruningScore: u16 = u16::MAX; + pub const InitialMinDifficulty: u64 = 1; + pub const InitialMaxDifficulty: u64 = u64::MAX; + pub const InitialRAORecycledForRegistration: TaoBalance = TaoBalance::new(0); + pub const InitialNetworkImmunityPeriod: u64 = 1_296_000; + pub const InitialNetworkMinLockCost: TaoBalance = TaoBalance::new(100_000_000_000); + pub const InitialSubnetOwnerCut: u16 = 0; + pub const InitialNetworkLockReductionInterval: u64 = 2; + pub const InitialNetworkRateLimit: u64 = 0; + pub const InitialKeySwapCost: TaoBalance = TaoBalance::new(1_000_000_000); + pub const InitialAlphaHigh: u16 = 58_982; + pub const InitialAlphaLow: u16 = 45_875; + pub const InitialLiquidAlphaOn: bool = false; + pub const InitialYuma3On: bool = false; + pub const InitialColdkeySwapAnnouncementDelay: u64 = 50; + pub const InitialColdkeySwapReannouncementDelay: u64 = 10; + pub const InitialDissolveNetworkScheduleDuration: u64 = 36_000; + pub const InitialTaoWeight: u64 = u64::MAX / 10; + pub const InitialEmaPriceHalvingPeriod: u64 = 201_600; + pub const InitialStartCallDelay: u64 = 0; + pub const InitialKeySwapOnSubnetCost: TaoBalance = TaoBalance::new(10_000_000); + pub const HotkeySwapOnSubnetInterval: u64 = 50_400; + pub const LeaseDividendsDistributionInterval: u32 = 100; + pub const MaxImmuneUidsPercentage: Percent = Percent::from_percent(80); + pub const EvmKeyAssociateRateLimit: u64 = 0; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type BaseCallFilter = Everything; + type BlockWeights = BlockWeights; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type SS58Prefix = SS58Prefix; + type MaxConsumers = ConstU32<16>; + type Block = Block; + type Nonce = u64; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Runtime { + type Balance = TaoBalance; + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); +} + +#[derive_impl(pallet_timestamp::config_preludes::TestDefaultConfig)] +impl pallet_timestamp::Config for Runtime { + type MinimumPeriod = MinimumPeriod; +} + +impl pallet_shield::Config for Runtime { + type AuthorityId = sp_core::sr25519::Public; + type FindAuthors = (); + type RuntimeCall = RuntimeCall; + type ExtrinsicDecryptor = (); + type WeightInfo = (); +} + +impl pallet_preimage::Config for Runtime { + type WeightInfo = pallet_preimage::weights::SubstrateWeight; + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type ManagerOrigin = EnsureRoot; + type Consideration = (); +} + +pub struct FixedGasPrice; +impl pallet_evm::FeeCalculator for FixedGasPrice { + fn min_gas_price() -> (U256, Weight) { + (1_000_000_000u128.into(), Weight::from_parts(7, 0)) + } +} + +pub struct SubtensorEvmBalanceConverter; +impl BalanceConverter for SubtensorEvmBalanceConverter { + fn into_evm_balance(value: SubstrateBalance) -> Option { + value + .into_u256() + .checked_mul(U256::from(EVM_DECIMALS_FACTOR)) + .and_then(|evm_value| (evm_value <= U256::MAX).then(|| EvmBalance::new(evm_value))) + } + + fn into_substrate_balance(value: EvmBalance) -> Option { + value + .into_u256() + .checked_div(U256::from(EVM_DECIMALS_FACTOR)) + .and_then(|substrate_value| { + (substrate_value <= U256::from(u64::MAX)) + .then(|| SubstrateBalance::new(substrate_value)) + }) + } +} + +impl pallet_evm::Config for Runtime { + type BalanceConverter = SubtensorEvmBalanceConverter; + type AccountProvider = pallet_evm::FrameSystemAccountProvider; + type FeeCalculator = FixedGasPrice; + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; + type WeightPerGas = WeightPerGas; + type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; + type CallOrigin = EnsureAddressRoot; + type WithdrawOrigin = EnsureAddressNever; + type AddressMapping = pallet_evm::HashedAddressMapping; + type Currency = Balances; + type PrecompilesType = (); + type PrecompilesValue = (); + type ChainId = (); + type BlockGasLimit = BlockGasLimit; + type Runner = pallet_evm::runner::stack::Runner; + type OnChargeTransaction = (); + type OnCreate = (); + type FindAuthor = (); + type GasLimitPovSizeRatio = (); + type GasLimitStorageGrowthRatio = (); + type Timestamp = Timestamp; + type CreateInnerOriginFilter = (); + type CreateOriginFilter = (); + type WeightInfo = pallet_evm::weights::SubstrateWeight; +} + +impl pallet_crowdloan::Config for Runtime { + type PalletId = CrowdloanPalletId; + type Currency = Balances; + type RuntimeCall = RuntimeCall; + type WeightInfo = pallet_crowdloan::weights::SubstrateWeight; + type Preimages = Preimage; + type MinimumDeposit = MinimumDeposit; + type AbsoluteMinimumContribution = AbsoluteMinimumContribution; + type MinimumBlockDuration = MinimumBlockDuration; + type MaximumBlockDuration = MaximumBlockDuration; + type RefundContributorsLimit = RefundContributorsLimit; + type MaxContributors = MaxContributors; +} + +impl pallet_subtensor_swap::Config for Runtime { + type SubnetInfo = SubtensorModule; + type BalanceOps = SubtensorModule; + type ProtocolId = SwapProtocolId; + type TaoReserve = pallet_subtensor::TaoBalanceReserve; + type AlphaReserve = pallet_subtensor::AlphaBalanceReserve; + type MaxFeeRate = SwapMaxFeeRate; + type MaxPositions = SwapMaxPositions; + type MinimumLiquidity = SwapMinimumLiquidity; + type MinimumReserve = SwapMinimumReserve; + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +pub struct OriginPrivilegeCmp; +impl PrivilegeCmp for OriginPrivilegeCmp { + fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option { + None + } +} + +impl pallet_scheduler::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeEvent = RuntimeEvent; + type PalletsOrigin = OriginCaller; + type RuntimeCall = RuntimeCall; + type MaximumWeight = MaximumSchedulerWeight; + type ScheduleOrigin = EnsureRoot; + type MaxScheduledPerBlock = MaxScheduledPerBlock; + type WeightInfo = pallet_scheduler::weights::SubstrateWeight; + type OriginPrivilegeCmp = OriginPrivilegeCmp; + type Preimages = Preimage; + type BlockNumberProvider = System; +} + +pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"test"); + +mod test_crypto { + use super::{AccountId, KEY_TYPE}; + use sp_core::sr25519::{Public as Sr25519Public, Signature as Sr25519Signature}; + use sp_runtime::{ + app_crypto::{app_crypto, sr25519}, + traits::IdentifyAccount, + }; + + app_crypto!(sr25519, KEY_TYPE); + + pub struct TestAuthId; + + impl frame_system::offchain::AppCrypto for TestAuthId { + type RuntimeAppPublic = Public; + type GenericSignature = Sr25519Signature; + type GenericPublic = Sr25519Public; + } + + impl IdentifyAccount for Public { + type AccountId = AccountId; + + fn into_account(self) -> AccountId { + let mut bytes = [0u8; 32]; + bytes.copy_from_slice(self.as_ref()); + AccountId::new(bytes) + } + } +} + +impl pallet_drand::Config for Runtime { + type AuthorityId = test_crypto::TestAuthId; + type Verifier = pallet_drand::verifier::QuicknetVerifier; + type UnsignedPriority = ConstU64<{ 1 << 20 }>; + type HttpFetchTimeout = ConstU64<1_000>; + type WeightInfo = (); +} + +impl frame_system::offchain::SigningTypes for Runtime { + type Public = test_crypto::Public; + type Signature = test_crypto::Signature; +} + +impl CreateTransactionBase for Runtime +where + RuntimeCall: From, +{ + type Extrinsic = UncheckedExtrinsic; + type RuntimeCall = RuntimeCall; +} + +impl frame_system::offchain::CreateInherent for Runtime +where + RuntimeCall: From, +{ + fn create_bare(call: Self::RuntimeCall) -> Self::Extrinsic { + UncheckedExtrinsic::new_inherent(call) + } +} + +impl frame_system::offchain::CreateSignedTransaction for Runtime +where + RuntimeCall: From, +{ + fn create_signed_transaction< + C: frame_system::offchain::AppCrypto, + >( + call: >::RuntimeCall, + _public: Self::Public, + _account: Self::AccountId, + nonce: Self::Nonce, + ) -> Option { + Some(UncheckedExtrinsic::new_signed(call, nonce, (), ())) + } +} + +pub struct MockAuthorshipProvider; +impl AuthorshipInfo for MockAuthorshipProvider { + fn author() -> Option { + Some(AccountId::new([1; 32])) + } +} + +pub struct CommitmentsI; +impl pallet_subtensor::CommitmentsInterface for CommitmentsI { + fn purge_netuid(_netuid: NetUid) {} +} + +impl pallet_subtensor::Config for Runtime { + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type InitialIssuance = InitialIssuance; + type SudoRuntimeCall = frame_system::Call; + type Scheduler = Scheduler; + type InitialMinAllowedWeights = InitialMinAllowedWeights; + type InitialEmissionValue = InitialEmissionValue; + type InitialTempo = InitialTempo; + type InitialDifficulty = InitialDifficulty; + type InitialAdjustmentInterval = InitialAdjustmentInterval; + type InitialAdjustmentAlpha = InitialAdjustmentAlpha; + type InitialTargetRegistrationsPerInterval = InitialTargetRegistrationsPerInterval; + type InitialRho = InitialRho; + type InitialAlphaSigmoidSteepness = InitialAlphaSigmoidSteepness; + type InitialKappa = InitialKappa; + type InitialMinAllowedUids = InitialMinAllowedUids; + type InitialMaxAllowedUids = InitialMaxAllowedUids; + type InitialValidatorPruneLen = InitialValidatorPruneLen; + type InitialScalingLawPower = InitialScalingLawPower; + type InitialImmunityPeriod = InitialImmunityPeriod; + type InitialActivityCutoff = InitialActivityCutoff; + type InitialMaxRegistrationsPerBlock = InitialMaxRegistrationsPerBlock; + type InitialPruningScore = InitialPruningScore; + type InitialBondsMovingAverage = InitialBondsMovingAverage; + type InitialBondsPenalty = InitialBondsPenalty; + type InitialBondsResetOn = InitialBondsResetOn; + type InitialMaxAllowedValidators = InitialMaxAllowedValidators; + type InitialDefaultDelegateTake = InitialDefaultDelegateTake; + type InitialMinDelegateTake = InitialMinDelegateTake; + type InitialDefaultChildKeyTake = InitialDefaultChildKeyTake; + type InitialMinChildKeyTake = InitialMinChildKeyTake; + type InitialMaxChildKeyTake = InitialMaxChildKeyTake; + type InitialWeightsVersionKey = InitialWeightsVersionKey; + type InitialMaxDifficulty = InitialMaxDifficulty; + type InitialMinDifficulty = InitialMinDifficulty; + type InitialServingRateLimit = InitialServingRateLimit; + type InitialTxRateLimit = InitialTxRateLimit; + type InitialTxDelegateTakeRateLimit = InitialTxDelegateTakeRateLimit; + type InitialTxChildKeyTakeRateLimit = InitialTxChildKeyTakeRateLimit; + type InitialBurn = InitialBurn; + type InitialMaxBurn = InitialMaxBurn; + type InitialMinBurn = InitialMinBurn; + type MinBurnUpperBound = MinBurnUpperBound; + type MaxBurnLowerBound = MaxBurnLowerBound; + type InitialRAORecycledForRegistration = InitialRAORecycledForRegistration; + type InitialNetworkImmunityPeriod = InitialNetworkImmunityPeriod; + type InitialNetworkMinLockCost = InitialNetworkMinLockCost; + type InitialSubnetOwnerCut = InitialSubnetOwnerCut; + type InitialNetworkLockReductionInterval = InitialNetworkLockReductionInterval; + type InitialNetworkRateLimit = InitialNetworkRateLimit; + type KeySwapCost = InitialKeySwapCost; + type AlphaHigh = InitialAlphaHigh; + type AlphaLow = InitialAlphaLow; + type LiquidAlphaOn = InitialLiquidAlphaOn; + type Yuma3On = InitialYuma3On; + type Preimages = Preimage; + type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; + type InitialColdkeySwapReannouncementDelay = InitialColdkeySwapReannouncementDelay; + type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; + type InitialTaoWeight = InitialTaoWeight; + type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; + type InitialStartCallDelay = InitialStartCallDelay; + type SwapInterface = Swap; + type KeySwapOnSubnetCost = InitialKeySwapOnSubnetCost; + type HotkeySwapOnSubnetInterval = HotkeySwapOnSubnetInterval; + type ProxyInterface = (); + type LeaseDividendsDistributionInterval = LeaseDividendsDistributionInterval; + type GetCommitments = (); + type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage; + type CommitmentsInterface = CommitmentsI; + type EvmKeyAssociateRateLimit = EvmKeyAssociateRateLimit; + type AuthorshipProvider = MockAuthorshipProvider; + type WeightInfo = (); +} + +impl frame_support::traits::InstanceFilter for ProxyType { + fn filter(&self, _c: &RuntimeCall) -> bool { + true + } + + fn is_superset(&self, o: &Self) -> bool { + match (self, o) { + (x, y) if x == y => true, + (ProxyType::Any, _) => true, + _ => false, + } + } +} + +impl pallet_subtensor_proxy::Config for Runtime { + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = ProxyType; + type ProxyDepositBase = ProxyDepositBase; + type ProxyDepositFactor = ProxyDepositFactor; + type MaxProxies = MaxProxies; + type WeightInfo = pallet_subtensor_proxy::weights::SubstrateWeight; + type MaxPending = MaxPending; + type CallHasher = BlakeTwo256; + type AnnouncementDepositBase = AnnouncementDepositBase; + type AnnouncementDepositFactor = AnnouncementDepositFactor; + type BlockNumberProvider = System; +} + +pub(crate) struct SinglePrecompileSet

(PhantomData

); + +impl

Default for SinglePrecompileSet

{ + fn default() -> Self { + Self(PhantomData) + } +} + +impl

PrecompileSet for SinglePrecompileSet

+where + P: pallet_evm::Precompile + PrecompileExt, +{ + fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { + (handle.code_address() == H160::from_low_u64_be(P::INDEX)).then(|| P::execute(handle)) + } + + fn is_precompile(&self, address: H160, _gas: u64) -> pallet_evm::IsPrecompileResult { + pallet_evm::IsPrecompileResult::Answer { + is_precompile: address == H160::from_low_u64_be(P::INDEX), + extra_cost: 0, + } + } +} + +pub(crate) fn precompiles

() -> SinglePrecompileSet

+where + P: pallet_evm::Precompile + PrecompileExt, +{ + SinglePrecompileSet::default() +} + +pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig::default() + .build_storage() + .unwrap() + .into(); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub(crate) fn execute_precompile( + precompiles: &PSet, + precompile_address: H160, + caller: H160, + input: Vec, + apparent_value: U256, +) -> Option { + let mut handle = MockHandle::new( + precompile_address, + Context { + address: precompile_address, + caller, + apparent_value, + }, + ); + handle.input = input; + precompiles.execute(&mut handle) +} + +pub(crate) fn addr_from_index(index: u64) -> H160 { + H160::from_low_u64_be(index) +} + +pub(crate) fn abi_word(value: U256) -> Vec { + value.to_big_endian().to_vec() +} + +pub(crate) fn assert_static_call( + precompiles: &PSet, + caller: H160, + precompile_addr: H160, + input: Vec, + expected: U256, +) { + use precompile_utils::testing::PrecompileTesterExt; + + precompiles + .prepare_test(caller, precompile_addr, input) + .with_static_call(true) + .execute_returns_raw(abi_word(expected)); +} + +pub(crate) fn selector_u32(signature: &str) -> u32 { + let hash = sp_io::hashing::keccak_256(signature.as_bytes()); + u32::from_be_bytes([hash[0], hash[1], hash[2], hash[3]]) +} + +pub(crate) fn alpha_price_to_evm(price: U96F32) -> U256 { + let scaled_price = (price * U96F32::from_num(EVM_DECIMALS_FACTOR)).to_num::(); + ::BalanceConverter::into_evm_balance(scaled_price.into()) + .expect("runtime balance conversion should work for alpha price") + .into_u256() +} diff --git a/precompiles/src/neuron.rs b/precompiles/src/neuron.rs index 6c0b7f744f..1b66ea902c 100644 --- a/precompiles/src/neuron.rs +++ b/precompiles/src/neuron.rs @@ -252,3 +252,448 @@ where ) } } + +#[cfg(test)] +mod tests { + #![allow(clippy::expect_used, clippy::indexing_slicing)] + + use super::*; + use crate::PrecompileExt; + use crate::mock::{ + AccountId, Runtime, System, addr_from_index, execute_precompile, new_test_ext, precompiles, + selector_u32, + }; + use pallet_evm::AddressMapping; + use precompile_utils::solidity::encode_with_selector; + use precompile_utils::testing::PrecompileTesterExt; + use sp_core::{H160, H256, U256}; + use sp_runtime::traits::Hash; + use subtensor_runtime_common::{AlphaBalance, NetUid, NetUidStorageIndex, TaoBalance, Token}; + + const TEST_NETUID_U16: u16 = 1; + const REGISTRATION_BURN: u64 = 1_000; + const RESERVE: u64 = 1_000_000_000; + const COLDKEY_BALANCE: u64 = 50_000; + const TEMPO: u16 = 100; + const REVEAL_PERIOD: u64 = 1; + const VERSION_KEY: u64 = 0; + const REGISTERED_UID: u16 = 0; + const REVEAL_UIDS: [u16; 1] = [REGISTERED_UID]; + const REVEAL_VALUES: [u16; 1] = [5]; + const REVEAL_SALT: [u16; 1] = [9]; + const SERVE_VERSION: u32 = 0; + const SERVE_IP: u128 = 1; + const SERVE_PORT: u16 = 2; + const SERVE_IP_TYPE: u8 = 4; + const SERVE_PROTOCOL: u8 = 0; + const SERVE_PLACEHOLDER1: u8 = 8; + const SERVE_PLACEHOLDER2: u8 = 9; + + fn setup_registered_caller(caller: H160) -> (NetUid, AccountId) { + let netuid = NetUid::from(TEST_NETUID_U16); + let caller_account = + ::AddressMapping::into_account_id(caller); + let caller_hotkey = H256::from_slice(caller_account.as_ref()); + + pallet_subtensor::Pallet::::init_new_network(netuid, TEMPO); + pallet_subtensor::Pallet::::set_network_registration_allowed(netuid, true); + pallet_subtensor::Pallet::::set_burn(netuid, REGISTRATION_BURN.into()); + pallet_subtensor::Pallet::::set_max_allowed_uids(netuid, 4096); + pallet_subtensor::Pallet::::set_weights_set_rate_limit(netuid, 0); + pallet_subtensor::Pallet::::set_tempo(netuid, TEMPO); + pallet_subtensor::Pallet::::set_commit_reveal_weights_enabled(netuid, true); + pallet_subtensor::Pallet::::set_reveal_period(netuid, REVEAL_PERIOD) + .expect("reveal period setup should succeed"); + pallet_subtensor::SubnetTAO::::insert(netuid, TaoBalance::from(RESERVE)); + pallet_subtensor::SubnetAlphaIn::::insert(netuid, AlphaBalance::from(RESERVE)); + pallet_subtensor::Pallet::::add_balance_to_coldkey_account( + &caller_account, + COLDKEY_BALANCE.into(), + ); + + precompiles::>() + .prepare_test( + caller, + addr_from_index(NeuronPrecompile::::INDEX), + encode_with_selector( + selector_u32("burnedRegister(uint16,bytes32)"), + (TEST_NETUID_U16, caller_hotkey), + ), + ) + .execute_returns(()); + + let registered_uid = pallet_subtensor::Pallet::::get_uid_for_net_and_hotkey( + netuid, + &caller_account, + ) + .expect("caller should be registered on subnet"); + assert_eq!(registered_uid, REGISTERED_UID); + + (netuid, caller_account) + } + + fn reveal_commit_hash(caller_account: &AccountId, netuid: NetUid) -> H256 { + ::Hashing::hash_of(&( + caller_account.clone(), + NetUidStorageIndex::from(netuid), + REVEAL_UIDS.as_slice(), + REVEAL_VALUES.as_slice(), + REVEAL_SALT.as_slice(), + VERSION_KEY, + )) + } + + #[test] + fn neuron_precompile_burned_register_adds_a_new_uid_and_key() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(TEST_NETUID_U16); + let caller = addr_from_index(0x1234); + let caller_account = + ::AddressMapping::into_account_id(caller); + let hotkey_account = AccountId::from([0x42; 32]); + let hotkey = H256::from_slice(hotkey_account.as_ref()); + + pallet_subtensor::Pallet::::init_new_network(netuid, TEMPO); + pallet_subtensor::Pallet::::set_network_registration_allowed(netuid, true); + pallet_subtensor::Pallet::::set_burn(netuid, REGISTRATION_BURN.into()); + pallet_subtensor::Pallet::::set_max_allowed_uids(netuid, 4096); + pallet_subtensor::SubnetTAO::::insert(netuid, TaoBalance::from(RESERVE)); + pallet_subtensor::SubnetAlphaIn::::insert(netuid, AlphaBalance::from(RESERVE)); + pallet_subtensor::Pallet::::add_balance_to_coldkey_account( + &caller_account, + COLDKEY_BALANCE.into(), + ); + + let uid_before = pallet_subtensor::SubnetworkN::::get(netuid); + let balance_before = + pallet_subtensor::Pallet::::get_coldkey_balance(&caller_account).to_u64(); + + precompiles::>() + .prepare_test( + caller, + addr_from_index(NeuronPrecompile::::INDEX), + encode_with_selector( + selector_u32("burnedRegister(uint16,bytes32)"), + (TEST_NETUID_U16, hotkey), + ), + ) + .execute_returns(()); + + let uid_after = pallet_subtensor::SubnetworkN::::get(netuid); + let registered_hotkey = pallet_subtensor::Keys::::get(netuid, uid_before); + let owner = pallet_subtensor::Owner::::get(&hotkey_account); + let balance_after = + pallet_subtensor::Pallet::::get_coldkey_balance(&caller_account).to_u64(); + + assert_eq!(uid_after, uid_before + 1); + assert_eq!(registered_hotkey, hotkey_account); + assert_eq!(owner, caller_account); + assert!(balance_after < balance_before); + }); + } + + #[test] + fn neuron_precompile_commit_weights_respects_stake_threshold_and_stores_commit() { + new_test_ext().execute_with(|| { + let caller = addr_from_index(0x2234); + let (netuid, caller_account) = setup_registered_caller(caller); + let commit_hash = reveal_commit_hash(&caller_account, netuid); + let precompile_addr = addr_from_index(NeuronPrecompile::::INDEX); + + pallet_subtensor::Pallet::::set_stake_threshold(1); + let rejected = execute_precompile( + &precompiles::>(), + precompile_addr, + caller, + encode_with_selector( + selector_u32("commitWeights(uint16,bytes32)"), + (TEST_NETUID_U16, commit_hash), + ), + U256::zero(), + ) + .expect("commit weights should route to neuron precompile"); + assert!(rejected.is_err()); + + pallet_subtensor::Pallet::::set_stake_threshold(0); + precompiles::>() + .prepare_test( + caller, + precompile_addr, + encode_with_selector( + selector_u32("commitWeights(uint16,bytes32)"), + (TEST_NETUID_U16, commit_hash), + ), + ) + .execute_returns(()); + + let commits = pallet_subtensor::WeightCommits::::get( + NetUidStorageIndex::from(netuid), + &caller_account, + ) + .expect("weight commits should be stored after successful commit"); + assert_eq!(commits.len(), 1); + }); + } + + #[test] + fn neuron_precompile_reveal_weights_respects_stake_threshold_and_sets_weights() { + new_test_ext().execute_with(|| { + let caller = addr_from_index(0x3234); + let (netuid, caller_account) = setup_registered_caller(caller); + let commit_hash = reveal_commit_hash(&caller_account, netuid); + let precompile_addr = addr_from_index(NeuronPrecompile::::INDEX); + + precompiles::>() + .prepare_test( + caller, + precompile_addr, + encode_with_selector( + selector_u32("commitWeights(uint16,bytes32)"), + (TEST_NETUID_U16, commit_hash), + ), + ) + .execute_returns(()); + + let commits = pallet_subtensor::WeightCommits::::get( + NetUidStorageIndex::from(netuid), + &caller_account, + ) + .expect("weight commit should exist before reveal"); + let (_, _, first_reveal_block, _) = commits + .front() + .copied() + .expect("weight commit queue should contain the committed hash"); + + System::set_block_number(u64::from( + u32::try_from(first_reveal_block) + .expect("first reveal block should fit in runtime block number"), + )); + + pallet_subtensor::Pallet::::set_stake_threshold(1); + let rejected = execute_precompile( + &precompiles::>(), + precompile_addr, + caller, + encode_with_selector( + selector_u32("revealWeights(uint16,uint16[],uint16[],uint16[],uint64)"), + ( + TEST_NETUID_U16, + REVEAL_UIDS.to_vec(), + REVEAL_VALUES.to_vec(), + REVEAL_SALT.to_vec(), + VERSION_KEY, + ), + ), + U256::zero(), + ) + .expect("reveal weights should route to neuron precompile"); + assert!(rejected.is_err()); + + pallet_subtensor::Pallet::::set_stake_threshold(0); + precompiles::>() + .prepare_test( + caller, + precompile_addr, + encode_with_selector( + selector_u32("revealWeights(uint16,uint16[],uint16[],uint16[],uint64)"), + ( + TEST_NETUID_U16, + REVEAL_UIDS.to_vec(), + REVEAL_VALUES.to_vec(), + REVEAL_SALT.to_vec(), + VERSION_KEY, + ), + ), + ) + .execute_returns(()); + + assert!( + pallet_subtensor::WeightCommits::::get( + NetUidStorageIndex::from(netuid), + &caller_account + ) + .is_none() + ); + + let neuron_uid = pallet_subtensor::Pallet::::get_uid_for_net_and_hotkey( + netuid, + &caller_account, + ) + .expect("caller should remain registered after reveal"); + let weights = pallet_subtensor::Weights::::get( + NetUidStorageIndex::from(netuid), + neuron_uid, + ); + + assert_eq!(weights.len(), 1); + assert_eq!(weights[0].0, neuron_uid); + assert!(weights[0].1 > 0); + }); + } + + #[test] + fn neuron_precompile_set_weights_sets_weights_when_commit_reveal_is_disabled() { + new_test_ext().execute_with(|| { + let caller = addr_from_index(0x4234); + let (netuid, caller_account) = setup_registered_caller(caller); + let precompile_addr = addr_from_index(NeuronPrecompile::::INDEX); + + pallet_subtensor::Pallet::::set_commit_reveal_weights_enabled(netuid, false); + + precompiles::>() + .prepare_test( + caller, + precompile_addr, + encode_with_selector( + selector_u32("setWeights(uint16,uint16[],uint16[],uint64)"), + ( + TEST_NETUID_U16, + vec![REGISTERED_UID], + vec![2_u16], + VERSION_KEY, + ), + ), + ) + .execute_returns(()); + + let neuron_uid = pallet_subtensor::Pallet::::get_uid_for_net_and_hotkey( + netuid, + &caller_account, + ) + .expect("caller should remain registered after setting weights"); + let weights = pallet_subtensor::Weights::::get( + NetUidStorageIndex::from(netuid), + neuron_uid, + ); + + assert_eq!(weights.len(), 1); + assert_eq!(weights[0].0, neuron_uid); + assert!(weights[0].1 > 0); + }); + } + + #[test] + fn neuron_precompile_serve_axon_sets_axon_info() { + new_test_ext().execute_with(|| { + let caller = addr_from_index(0x5234); + let (netuid, caller_account) = setup_registered_caller(caller); + + precompiles::>() + .prepare_test( + caller, + addr_from_index(NeuronPrecompile::::INDEX), + encode_with_selector( + selector_u32( + "serveAxon(uint16,uint32,uint128,uint16,uint8,uint8,uint8,uint8)", + ), + ( + TEST_NETUID_U16, + SERVE_VERSION, + SERVE_IP, + SERVE_PORT, + SERVE_IP_TYPE, + SERVE_PROTOCOL, + SERVE_PLACEHOLDER1, + SERVE_PLACEHOLDER2, + ), + ), + ) + .execute_returns(()); + + let axon = pallet_subtensor::Axons::::get(netuid, &caller_account) + .expect("axon info should be stored"); + assert!(axon.block > 0); + assert_eq!(axon.version, SERVE_VERSION); + assert_eq!(axon.ip, SERVE_IP); + assert_eq!(axon.port, SERVE_PORT); + assert_eq!(axon.ip_type, SERVE_IP_TYPE); + assert_eq!(axon.protocol, SERVE_PROTOCOL); + assert_eq!(axon.placeholder1, SERVE_PLACEHOLDER1); + assert_eq!(axon.placeholder2, SERVE_PLACEHOLDER2); + }); + } + + #[test] + fn neuron_precompile_serve_axon_tls_sets_axon_info_and_certificate() { + new_test_ext().execute_with(|| { + let caller = addr_from_index(0x6234); + let (netuid, caller_account) = setup_registered_caller(caller); + let certificate: Vec = (1u8..=65).collect(); + + precompiles::>() + .prepare_test( + caller, + addr_from_index(NeuronPrecompile::::INDEX), + encode_with_selector( + selector_u32( + "serveAxonTls(uint16,uint32,uint128,uint16,uint8,uint8,uint8,uint8,bytes)", + ), + ( + TEST_NETUID_U16, + SERVE_VERSION, + SERVE_IP, + SERVE_PORT, + SERVE_IP_TYPE, + SERVE_PROTOCOL, + SERVE_PLACEHOLDER1, + SERVE_PLACEHOLDER2, + UnboundedBytes::from(certificate.clone()), + ), + ), + ) + .execute_returns(()); + + let axon = pallet_subtensor::Axons::::get(netuid, &caller_account) + .expect("axon info should be stored"); + assert!(axon.block > 0); + assert_eq!(axon.version, SERVE_VERSION); + assert_eq!(axon.ip, SERVE_IP); + assert_eq!(axon.port, SERVE_PORT); + assert_eq!(axon.ip_type, SERVE_IP_TYPE); + assert_eq!(axon.protocol, SERVE_PROTOCOL); + assert_eq!(axon.placeholder1, SERVE_PLACEHOLDER1); + assert_eq!(axon.placeholder2, SERVE_PLACEHOLDER2); + + let stored_certificate = + pallet_subtensor::NeuronCertificates::::get(netuid, caller_account) + .expect("certificate should be stored"); + assert_eq!( + stored_certificate.public_key.into_inner(), + certificate[1..].to_vec() + ); + }); + } + + #[test] + fn neuron_precompile_serve_prometheus_sets_prometheus_info() { + new_test_ext().execute_with(|| { + let caller = addr_from_index(0x7234); + let (netuid, caller_account) = setup_registered_caller(caller); + + precompiles::>() + .prepare_test( + caller, + addr_from_index(NeuronPrecompile::::INDEX), + encode_with_selector( + selector_u32("servePrometheus(uint16,uint32,uint128,uint16,uint8)"), + ( + TEST_NETUID_U16, + SERVE_VERSION, + SERVE_IP, + SERVE_PORT, + SERVE_IP_TYPE, + ), + ), + ) + .execute_returns(()); + + let prometheus = pallet_subtensor::Prometheus::::get(netuid, caller_account) + .expect("prometheus info should be stored"); + assert!(prometheus.block > 0); + assert_eq!(prometheus.version, SERVE_VERSION); + assert_eq!(prometheus.ip, SERVE_IP); + assert_eq!(prometheus.port, SERVE_PORT); + assert_eq!(prometheus.ip_type, SERVE_IP_TYPE); + }); + } +} diff --git a/precompiles/src/staking.rs b/precompiles/src/staking.rs index 30d28aaa13..3392de468e 100644 --- a/precompiles/src/staking.rs +++ b/precompiles/src/staking.rs @@ -66,9 +66,10 @@ pub type AllowancesStorage = StorageDoubleMap< // For each approver (EVM address as only EVM-natives need the precompile) Blake2_128Concat, H160, - // For each pair of (spender, netuid) (EVM address as only EVM-natives need the precompile) + // For each (spender, netuid, counter) triple — the counter tag invalidates + // entries written under a previous registration of the same netuid. Blake2_128Concat, - (H160, u16), + (H160, u16, u64), // Allowed amount U256, ValueQuery, @@ -480,6 +481,13 @@ where Ok(stake.to_u64().into()) } + /// Current registration counter for `netuid`, used as part of the + /// `AllowancesStorage` secondary key to invalidate approvals granted + /// for a previous registration of the same netuid. + fn current_subnet_counter(netuid: u16) -> u64 { + pallet_subtensor::Pallet::::get_registered_subnet_counter(netuid.into()) + } + #[precompile::public("approve(address,uint256,uint256)")] fn approve( handle: &mut impl PrecompileHandle, @@ -487,17 +495,19 @@ where origin_netuid: U256, amount_alpha: U256, ) -> EvmResult<()> { - // AllowancesStorage write + // AllowancesStorage write + RegisteredSubnetCounter read + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; handle.record_cost(RuntimeHelper::::db_write_gas_cost())?; let approver = handle.context().caller; let spender = spender_address.0; let netuid = try_u16_from_u256(origin_netuid)?; + let counter = Self::current_subnet_counter(netuid); if amount_alpha.is_zero() { - AllowancesStorage::remove(approver, (spender, netuid)); + AllowancesStorage::remove(approver, (spender, netuid, counter)); } else { - AllowancesStorage::insert(approver, (spender, netuid), amount_alpha); + AllowancesStorage::insert(approver, (spender, netuid, counter), amount_alpha); } Ok(()) @@ -511,13 +521,18 @@ where spender_address: Address, origin_netuid: U256, ) -> EvmResult { - // AllowancesStorage read + // AllowancesStorage read + RegisteredSubnetCounter read + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; let spender = spender_address.0; let netuid = try_u16_from_u256(origin_netuid)?; + let counter = Self::current_subnet_counter(netuid); - Ok(AllowancesStorage::get(source_address.0, (spender, netuid))) + Ok(AllowancesStorage::get( + source_address.0, + (spender, netuid, counter), + )) } #[precompile::public("increaseAllowance(address,uint256,uint256)")] @@ -531,15 +546,17 @@ where return Ok(()); } - // AllowancesStorage read + write + // AllowancesStorage read + write + RegisteredSubnetCounter read + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; handle.record_cost(RuntimeHelper::::db_write_gas_cost())?; let approver = handle.context().caller; let spender = spender_address.0; let netuid = try_u16_from_u256(origin_netuid)?; + let counter = Self::current_subnet_counter(netuid); - let approval_key = (spender, netuid); + let approval_key = (spender, netuid, counter); let current_amount = AllowancesStorage::get(approver, approval_key); let new_amount = current_amount.saturating_add(amount_alpha_increase); @@ -560,15 +577,17 @@ where return Ok(()); } - // AllowancesStorage read + write + // AllowancesStorage read + write + RegisteredSubnetCounter read + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; handle.record_cost(RuntimeHelper::::db_write_gas_cost())?; let approver = handle.context().caller; let spender = spender_address.0; let netuid = try_u16_from_u256(origin_netuid)?; + let counter = Self::current_subnet_counter(netuid); - let approval_key = (spender, netuid); + let approval_key = (spender, netuid, counter); let current_amount = AllowancesStorage::get(approver, approval_key); let new_amount = current_amount.saturating_sub(amount_alpha_decrease); @@ -593,11 +612,13 @@ where return Ok(()); } - // AllowancesStorage read + write + // AllowancesStorage read + write + RegisteredSubnetCounter read + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; handle.record_cost(RuntimeHelper::::db_write_gas_cost())?; - let approval_key = (spender, netuid); + let counter = Self::current_subnet_counter(netuid); + let approval_key = (spender, netuid, counter); let current_amount = AllowancesStorage::get(approver, approval_key); let Some(new_amount) = current_amount.checked_sub(amount) else { diff --git a/precompiles/src/uid_lookup.rs b/precompiles/src/uid_lookup.rs index b791b96786..5d87973368 100644 --- a/precompiles/src/uid_lookup.rs +++ b/precompiles/src/uid_lookup.rs @@ -51,3 +51,53 @@ where )) } } + +#[cfg(test)] +mod tests { + #![allow(clippy::expect_used)] + + use super::*; + use crate::mock::{Runtime, addr_from_index, new_test_ext, precompiles, selector_u32}; + use precompile_utils::solidity::{codec::Address, encode_return_value, encode_with_selector}; + use precompile_utils::testing::PrecompileTesterExt; + use subtensor_runtime_common::NetUid; + + const TEST_NETUID_U16: u16 = 1; + + #[test] + fn uid_lookup_precompile_returns_associated_uid_and_block() { + new_test_ext().execute_with(|| { + let precompiles = precompiles::>(); + let caller = addr_from_index(1); + let precompile_addr = addr_from_index(UidLookupPrecompile::::INDEX); + + let netuid = NetUid::from(TEST_NETUID_U16); + let uid = 0u16; + let evm_address = addr_from_index(0xdead_beef); + let block_associated = 42u64; + let limit = 1024u16; + + pallet_subtensor::AssociatedEvmAddress::::insert( + netuid, + uid, + (evm_address, block_associated), + ); + + let expected = + pallet_subtensor::Pallet::::uid_lookup(netuid, evm_address, limit); + assert_eq!(expected, vec![(uid, block_associated)]); + + precompiles + .prepare_test( + caller, + precompile_addr, + encode_with_selector( + selector_u32("uidLookup(uint16,address,uint16)"), + (TEST_NETUID_U16, Address(evm_address), limit), + ), + ) + .with_static_call(true) + .execute_returns_raw(encode_return_value(expected)); + }); + } +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 0c6a21acb6..e163661a8d 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -321,6 +321,7 @@ runtime-benchmarks = [ "pallet-drand/runtime-benchmarks", "pallet-transaction-payment/runtime-benchmarks", "pallet-subtensor-swap/runtime-benchmarks", + "subtensor-precompiles/runtime-benchmarks", # Smart Tx fees pallet "subtensor-transaction-fee/runtime-benchmarks", diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index ed6d4d6176..a2911cbe5e 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -272,7 +272,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: 397, + spec_version: 398, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/runtime/tests/precompiles.rs b/runtime/tests/precompiles.rs index 815d055ab7..519e434533 100644 --- a/runtime/tests/precompiles.rs +++ b/runtime/tests/precompiles.rs @@ -1,18 +1,15 @@ #![allow(clippy::unwrap_used)] #![allow(clippy::expect_used)] -use core::iter::IntoIterator; -use std::collections::BTreeSet; - use fp_evm::{Context, ExitError, PrecompileFailure, PrecompileResult}; use node_subtensor_runtime::{BuildStorage, Runtime, RuntimeGenesisConfig, System}; -use pallet_evm::{AddressMapping, BalanceConverter, PrecompileSet}; -use precompile_utils::testing::{MockHandle, PrecompileTesterExt}; +use pallet_evm::{BalanceConverter, PrecompileSet}; +use precompile_utils::solidity::encode_with_selector; +use precompile_utils::testing::MockHandle; use sp_core::{H160, H256, U256}; use sp_runtime::traits::Hash; -use subtensor_precompiles::{ - AddressMappingPrecompile, BalanceTransferPrecompile, PrecompileExt, Precompiles, -}; +use std::collections::BTreeSet; +use subtensor_precompiles::{BalanceTransferPrecompile, PrecompileExt, Precompiles}; type AccountId = ::AccountId; @@ -54,166 +51,117 @@ fn addr_from_index(index: u64) -> H160 { H160::from_low_u64_be(index) } +fn selector_u32(signature: &str) -> u32 { + let hash = sp_io::hashing::keccak_256(signature.as_bytes()); + u32::from_be_bytes([hash[0], hash[1], hash[2], hash[3]]) +} + #[test] fn precompile_registry_addresses_are_unique() { - new_test_ext().execute_with(|| { - let addresses = Precompiles::::used_addresses(); - let unique: BTreeSet<_> = IntoIterator::into_iter(addresses).collect(); - assert_eq!(unique.len(), addresses.len()); - }); + let addresses = Precompiles::::used_addresses(); + let unique: BTreeSet<_> = addresses.into_iter().collect(); + assert_eq!(unique.len(), addresses.len()); } -mod address_mapping { - use super::*; - - fn address_mapping_call_data(target: H160) -> Vec { - // Solidity selector for addressMapping(address). - let selector = sp_io::hashing::keccak_256(b"addressMapping(address)"); - let mut input = Vec::with_capacity(4 + 32); - // First 4 bytes of keccak256(function_signature): ABI function selector. - input.extend_from_slice(&selector[..4]); - // Left-pad the 20-byte address argument to a 32-byte ABI word. - input.extend_from_slice(&[0u8; 12]); - // The 20-byte address payload (right-aligned in the 32-byte ABI word). - input.extend_from_slice(target.as_bytes()); - input - } - - #[test] - fn address_mapping_precompile_returns_runtime_address_mapping() { - new_test_ext().execute_with(|| { - let precompiles = Precompiles::::new(); - - let caller = addr_from_index(1); - let target_address = addr_from_index(0x1234); - let input = address_mapping_call_data(target_address); - - let mapped_account = - ::AddressMapping::into_account_id(target_address); - let expected_output: [u8; 32] = mapped_account.into(); - - let precompile_addr = addr_from_index(AddressMappingPrecompile::::INDEX); - precompiles - .prepare_test(caller, precompile_addr, input) - .with_static_call(true) - .execute_returns_raw(expected_output.to_vec()); - }); - } +#[test] +fn balance_transfer_precompile_transfers_balance() { + new_test_ext().execute_with(|| { + let precompiles = Precompiles::::new(); + let precompile_addr = addr_from_index(BalanceTransferPrecompile::::INDEX); + let dispatch_account: AccountId = BalanceTransferPrecompile::::account_id(); + let destination_raw = H256::repeat_byte(7); + let destination_account: AccountId = destination_raw.0.into(); + + let amount = 123_456; + pallet_subtensor::Pallet::::add_balance_to_coldkey_account( + &dispatch_account, + (amount * 2).into(), + ); + + let source_balance_before = + pallet_balances::Pallet::::free_balance(&dispatch_account); + let destination_balance_before = + pallet_balances::Pallet::::free_balance(&destination_account); + + let result = execute_precompile( + &precompiles, + precompile_addr, + addr_from_index(1), + encode_with_selector(selector_u32("transfer(bytes32)"), (destination_raw,)), + evm_apparent_value_from_substrate(amount), + ); + let precompile_result = + result.expect("expected precompile transfer call to be routed to a precompile"); + precompile_result.expect("expected successful precompile transfer dispatch"); + + let source_balance_after = + pallet_balances::Pallet::::free_balance(&dispatch_account); + let destination_balance_after = + pallet_balances::Pallet::::free_balance(&destination_account); + + assert_eq!(source_balance_after, source_balance_before - amount.into()); + assert_eq!( + destination_balance_after, + destination_balance_before + amount.into() + ); + }); } -mod balance_transfer { - use super::*; - - fn balance_transfer_call_data(target: H256) -> Vec { - // Solidity selector for transfer(bytes32). - let selector = sp_io::hashing::keccak_256(b"transfer(bytes32)"); - let mut input = Vec::with_capacity(4 + 32); - input.extend_from_slice(&selector[..4]); - input.extend_from_slice(target.as_bytes()); - input - } - - #[test] - fn balance_transfer_precompile_transfers_balance() { - new_test_ext().execute_with(|| { - let precompiles = Precompiles::::new(); - let precompile_addr = addr_from_index(BalanceTransferPrecompile::::INDEX); - let dispatch_account: AccountId = BalanceTransferPrecompile::::account_id(); - let destination_raw = H256::repeat_byte(7); - let destination_account: AccountId = destination_raw.0.into(); - - let amount = 123_456; - pallet_subtensor::Pallet::::add_balance_to_coldkey_account( - &dispatch_account, - (amount * 2).into(), - ); - - let source_balance_before = - pallet_balances::Pallet::::free_balance(&dispatch_account); - let destination_balance_before = - pallet_balances::Pallet::::free_balance(&destination_account); - - let result = execute_precompile( - &precompiles, - precompile_addr, - addr_from_index(1), - balance_transfer_call_data(destination_raw), - evm_apparent_value_from_substrate(amount), - ); - let precompile_result = - result.expect("expected precompile transfer call to be routed to a precompile"); - precompile_result.expect("expected successful precompile transfer dispatch"); - - let source_balance_after = - pallet_balances::Pallet::::free_balance(&dispatch_account); - let destination_balance_after = - pallet_balances::Pallet::::free_balance(&destination_account); - - assert_eq!(source_balance_after, source_balance_before - amount.into()); - assert_eq!( - destination_balance_after, - destination_balance_before + amount.into() - ); - }); - } - - #[test] - fn balance_transfer_precompile_respects_dispatch_guard_policy() { - new_test_ext().execute_with(|| { - let precompiles = Precompiles::::new(); - let precompile_addr = addr_from_index(BalanceTransferPrecompile::::INDEX); - let dispatch_account: AccountId = BalanceTransferPrecompile::::account_id(); - let destination_raw = H256::repeat_byte(8); - let destination_account: AccountId = destination_raw.0.into(); - - let amount = 100; - pallet_subtensor::Pallet::::add_balance_to_coldkey_account( - &dispatch_account, - 1_000_000_u64.into(), - ); - - // Activate coldkey-swap guard for precompile dispatch account. - let replacement_coldkey = AccountId::from([9u8; 32]); - let replacement_hash = - ::Hashing::hash_of(&replacement_coldkey); - pallet_subtensor::ColdkeySwapAnnouncements::::insert( - &dispatch_account, - (System::block_number(), replacement_hash), - ); - - let source_balance_before = - pallet_balances::Pallet::::free_balance(&dispatch_account); - let destination_balance_before = - pallet_balances::Pallet::::free_balance(&destination_account); - - let result = execute_precompile( - &precompiles, - precompile_addr, - addr_from_index(1), - balance_transfer_call_data(destination_raw), - evm_apparent_value_from_substrate(amount), - ); - let precompile_result = - result.expect("expected precompile transfer call to be routed to a precompile"); - let failure = precompile_result - .expect_err("expected transaction extension rejection on precompile dispatch"); - let message = match failure { - PrecompileFailure::Error { - exit_status: ExitError::Other(message), - } => message, - other => panic!("unexpected precompile failure: {other:?}"), - }; - assert!( - message.contains("dispatch execution failed: ColdkeySwapAnnounced"), - "unexpected precompile failure: {message}" - ); - - let source_balance_after = - pallet_balances::Pallet::::free_balance(&dispatch_account); - let destination_balance_after = - pallet_balances::Pallet::::free_balance(&destination_account); - assert_eq!(source_balance_after, source_balance_before); - assert_eq!(destination_balance_after, destination_balance_before); - }); - } +#[test] +fn balance_transfer_precompile_respects_dispatch_guard_policy() { + new_test_ext().execute_with(|| { + let precompiles = Precompiles::::new(); + let precompile_addr = addr_from_index(BalanceTransferPrecompile::::INDEX); + let dispatch_account: AccountId = BalanceTransferPrecompile::::account_id(); + let destination_raw = H256::repeat_byte(8); + let destination_account: AccountId = destination_raw.0.into(); + + let amount = 100; + pallet_subtensor::Pallet::::add_balance_to_coldkey_account( + &dispatch_account, + 1_000_000_u64.into(), + ); + + let replacement_coldkey = AccountId::from([9u8; 32]); + let replacement_hash = + ::Hashing::hash_of(&replacement_coldkey); + pallet_subtensor::ColdkeySwapAnnouncements::::insert( + &dispatch_account, + (System::block_number(), replacement_hash), + ); + + let source_balance_before = + pallet_balances::Pallet::::free_balance(&dispatch_account); + let destination_balance_before = + pallet_balances::Pallet::::free_balance(&destination_account); + + let result = execute_precompile( + &precompiles, + precompile_addr, + addr_from_index(1), + encode_with_selector(selector_u32("transfer(bytes32)"), (destination_raw,)), + evm_apparent_value_from_substrate(amount), + ); + let precompile_result = + result.expect("expected precompile transfer call to be routed to a precompile"); + let failure = precompile_result + .expect_err("expected transaction extension rejection on precompile dispatch"); + let message = match failure { + PrecompileFailure::Error { + exit_status: ExitError::Other(message), + } => message, + other => panic!("unexpected precompile failure: {other:?}"), + }; + assert!( + message.contains("dispatch execution failed: ColdkeySwapAnnounced"), + "unexpected precompile failure: {message}" + ); + + let source_balance_after = + pallet_balances::Pallet::::free_balance(&dispatch_account); + let destination_balance_after = + pallet_balances::Pallet::::free_balance(&destination_account); + assert_eq!(source_balance_after, source_balance_before); + assert_eq!(destination_balance_after, destination_balance_before); + }); }