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/runtime/tests/precompiles.rs b/runtime/tests/precompiles.rs index 815d055ab7..8f906a09dc 100644 --- a/runtime/tests/precompiles.rs +++ b/runtime/tests/precompiles.rs @@ -1,5 +1,6 @@ #![allow(clippy::unwrap_used)] #![allow(clippy::expect_used)] +#![allow(clippy::arithmetic_side_effects)] use core::iter::IntoIterator; use std::collections::BTreeSet; @@ -10,9 +11,13 @@ use pallet_evm::{AddressMapping, BalanceConverter, PrecompileSet}; use precompile_utils::testing::{MockHandle, PrecompileTesterExt}; use sp_core::{H160, H256, U256}; use sp_runtime::traits::Hash; +use substrate_fixed::types::{I96F32, U96F32}; use subtensor_precompiles::{ - AddressMappingPrecompile, BalanceTransferPrecompile, PrecompileExt, Precompiles, + AddressMappingPrecompile, AlphaPrecompile, BalanceTransferPrecompile, PrecompileExt, + Precompiles, }; +use subtensor_runtime_common::{AlphaBalance, NetUid, TaoBalance, Token}; +use subtensor_swap_interface::{Order, SwapHandler}; type AccountId = ::AccountId; @@ -54,6 +59,52 @@ fn addr_from_index(index: u64) -> H160 { H160::from_low_u64_be(index) } +/// Appends one 32-byte ABI word to manually encoded precompile input. +fn push_abi_word(input: &mut Vec, value: U256) { + input.extend_from_slice(&value.to_big_endian()); +} + +/// Encodes one 32-byte ABI output word for exact raw return checks. +fn abi_word(value: U256) -> Vec { + value.to_big_endian().to_vec() +} + +/// Builds a 4-byte Solidity selector from a function signature. +fn selector(signature: &str) -> [u8; 4] { + let hash = sp_io::hashing::keccak_256(signature.as_bytes()); + [hash[0], hash[1], hash[2], hash[3]] +} + +/// Encodes a selector-only call with no arguments. +fn call_data_no_args(signature: &str) -> Vec { + selector(signature).to_vec() +} + +/// Encodes a selector plus one uint16 ABI argument. +fn call_data_u16(signature: &str, value: u16) -> Vec { + let mut input = Vec::with_capacity(4 + 32); + input.extend_from_slice(&selector(signature)); + push_abi_word(&mut input, U256::from(value)); + input +} + +/// Encodes a selector plus `(uint16,uint64)` ABI arguments. +fn call_data_u16_u64(signature: &str, first: u16, second: u64) -> Vec { + let mut input = Vec::with_capacity(4 + 64); + input.extend_from_slice(&selector(signature)); + push_abi_word(&mut input, U256::from(first)); + push_abi_word(&mut input, U256::from(second)); + input +} + +/// Matches the alpha precompile conversion from fixed-point price to EVM `uint256`. +fn alpha_price_to_evm(price: U96F32) -> U256 { + let scaled_price = (price * U96F32::from_num(1_000_000_000)).to_num::(); + ::BalanceConverter::into_evm_balance(scaled_price.into()) + .expect("runtime balance conversion should work for alpha price") + .into_u256() +} + #[test] fn precompile_registry_addresses_are_unique() { new_test_ext().execute_with(|| { @@ -67,11 +118,8 @@ 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]); + input.extend_from_slice(&selector("addressMapping(address)")); // 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). @@ -99,16 +147,421 @@ mod address_mapping { .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::::new(), + precompile_addr, + caller, + address_mapping_call_data(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::::new(), + precompile_addr, + caller, + address_mapping_call_data(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 = address_mapping_call_data(target_address); + + let first_output = execute_precompile( + &Precompiles::::new(), + 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::::new(), + 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); + }); + } +} + +mod alpha { + use super::*; + + 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 dynamic_netuid() -> NetUid { + NetUid::from(DYNAMIC_NETUID_U16) + } + + fn sum_price_netuid() -> NetUid { + NetUid::from(SUM_PRICE_NETUID_U16) + } + + fn seed_alpha_test_state() { + let dynamic_netuid = dynamic_netuid(); + let sum_price_netuid = sum_price_netuid(); + + 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), + ); + } + + fn assert_static_call( + precompiles: &Precompiles, + caller: H160, + precompile_addr: H160, + input: Vec, + expected: U256, + ) { + precompiles + .prepare_test(caller, precompile_addr, input) + .with_static_call(true) + .execute_returns_raw(abi_word(expected)); + } + + #[test] + fn alpha_precompile_matches_runtime_values_for_dynamic_subnet() { + new_test_ext().execute_with(|| { + seed_alpha_test_state(); + + let precompiles = Precompiles::::new(); + let caller = addr_from_index(1); + let precompile_addr = addr_from_index(AlphaPrecompile::::INDEX); + + let dynamic_netuid = dynamic_netuid(); + 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, + call_data_u16("getAlphaPrice(uint16)", DYNAMIC_NETUID_U16), + alpha_price_to_evm(alpha_price), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + call_data_u16("getMovingAlphaPrice(uint16)", DYNAMIC_NETUID_U16), + alpha_price_to_evm(moving_alpha_price), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + call_data_u16("getTaoInPool(uint16)", DYNAMIC_NETUID_U16), + U256::from(pallet_subtensor::SubnetTAO::::get(dynamic_netuid).to_u64()), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + call_data_u16("getAlphaInPool(uint16)", DYNAMIC_NETUID_U16), + U256::from(u64::from(pallet_subtensor::SubnetAlphaIn::::get( + dynamic_netuid, + ))), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + call_data_u16("getAlphaOutPool(uint16)", DYNAMIC_NETUID_U16), + U256::from(u64::from(pallet_subtensor::SubnetAlphaOut::::get( + dynamic_netuid, + ))), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + call_data_u16("getAlphaIssuance(uint16)", DYNAMIC_NETUID_U16), + U256::from(u64::from( + pallet_subtensor::Pallet::::get_alpha_issuance(dynamic_netuid), + )), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + call_data_u16("getSubnetMechanism(uint16)", DYNAMIC_NETUID_U16), + U256::from(pallet_subtensor::SubnetMechanism::::get( + dynamic_netuid, + )), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + call_data_u16("getEMAPriceHalvingBlocks(uint16)", DYNAMIC_NETUID_U16), + U256::from(pallet_subtensor::EMAPriceHalvingBlocks::::get( + dynamic_netuid, + )), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + call_data_u16("getSubnetVolume(uint16)", DYNAMIC_NETUID_U16), + U256::from(pallet_subtensor::SubnetVolume::::get( + dynamic_netuid, + )), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + call_data_u16("getTaoInEmission(uint16)", DYNAMIC_NETUID_U16), + U256::from( + pallet_subtensor::SubnetTaoInEmission::::get(dynamic_netuid).to_u64(), + ), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + call_data_u16("getAlphaInEmission(uint16)", DYNAMIC_NETUID_U16), + U256::from( + pallet_subtensor::SubnetAlphaInEmission::::get(dynamic_netuid) + .to_u64(), + ), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + call_data_u16("getAlphaOutEmission(uint16)", DYNAMIC_NETUID_U16), + U256::from( + pallet_subtensor::SubnetAlphaOutEmission::::get(dynamic_netuid) + .to_u64(), + ), + ); + }); + } + + #[test] + fn alpha_precompile_matches_runtime_global_values() { + new_test_ext().execute_with(|| { + seed_alpha_test_state(); + + let precompiles = Precompiles::::new(); + 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, + call_data_no_args("getCKBurn()"), + U256::from(pallet_subtensor::CKBurn::::get()), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + call_data_no_args("getTaoWeight()"), + U256::from(pallet_subtensor::TaoWeight::::get()), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + call_data_no_args("getRootNetuid()"), + U256::from(u16::from(NetUid::ROOT)), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + call_data_no_args("getSumAlphaPrice()"), + 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::::new(); + 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( + dynamic_netuid(), + 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( + dynamic_netuid(), + 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, + call_data_u16_u64( + "simSwapTaoForAlpha(uint16,uint64)", + DYNAMIC_NETUID_U16, + tao_amount, + ), + U256::from(expected_alpha), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + call_data_u16_u64( + "simSwapAlphaForTao(uint16,uint64)", + DYNAMIC_NETUID_U16, + alpha_amount, + ), + U256::from(expected_tao), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + call_data_u16_u64("simSwapTaoForAlpha(uint16,uint64)", DYNAMIC_NETUID_U16, 0), + U256::zero(), + ); + assert_static_call( + &precompiles, + caller, + precompile_addr, + call_data_u16_u64("simSwapAlphaForTao(uint16,uint64)", DYNAMIC_NETUID_U16, 0), + U256::zero(), + ); + }); + } } 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(&selector("transfer(bytes32)")); input.extend_from_slice(target.as_bytes()); input }