diff --git a/Cargo.lock b/Cargo.lock index fc4b28688c..9a8eb3b8ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1951,6 +1951,17 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "regex-automata 0.4.11", + "serde", +] + [[package]] name = "build-helper" version = "0.1.1" @@ -3816,6 +3827,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.106", + "unicode-xid", ] [[package]] @@ -13184,18 +13196,23 @@ name = "precompile-utils" version = "0.1.0" source = "git+https://github.com/opentensor/frontier?rev=6dc7c0400cfee2a1acb62ae17149c4d3a983e58d#6dc7c0400cfee2a1acb62ae17149c4d3a983e58d" dependencies = [ + "derive_more 1.0.0", "environmental", "evm", "fp-evm", "frame-support", "frame-system", "hex", + "hex-literal", "impl-trait-for-tuples", "log", "num_enum", "pallet-evm", "parity-scale-codec", "precompile-utils-macro", + "scale-info", + "serde", + "similar-asserts", "sp-core", "sp-io", "sp-runtime", @@ -16547,6 +16564,26 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +dependencies = [ + "bstr", + "unicode-segmentation", +] + +[[package]] +name = "similar-asserts" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b441962c817e33508847a22bd82f03a30cff43642dc2fae8b050566121eb9a" +dependencies = [ + "console", + "similar", +] + [[package]] name = "simple-dns" version = "0.9.3" diff --git a/precompiles/src/balance_transfer.rs b/precompiles/src/balance_transfer.rs index f4d5fb3025..d8d10970a3 100644 --- a/precompiles/src/balance_transfer.rs +++ b/precompiles/src/balance_transfer.rs @@ -10,7 +10,7 @@ use sp_runtime::traits::{AsSystemOriginSigner, Dispatchable, StaticLookup, Uniqu use crate::{PrecompileExt, PrecompileHandleExt}; -pub(crate) struct BalanceTransferPrecompile(PhantomData); +pub struct BalanceTransferPrecompile(PhantomData); impl PrecompileExt for BalanceTransferPrecompile where diff --git a/precompiles/src/ed25519.rs b/precompiles/src/ed25519.rs index 1ea581bd35..dbfe032cdf 100644 --- a/precompiles/src/ed25519.rs +++ b/precompiles/src/ed25519.rs @@ -8,7 +8,7 @@ use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile, PrecompileFailure}; use crate::{PrecompileExt, parse_slice}; -pub(crate) struct Ed25519Verify(PhantomData); +pub struct Ed25519Verify(PhantomData); impl PrecompileExt for Ed25519Verify where diff --git a/precompiles/src/extensions.rs b/precompiles/src/extensions.rs index 6daa6b6aae..51287a1b88 100644 --- a/precompiles/src/extensions.rs +++ b/precompiles/src/extensions.rs @@ -186,7 +186,7 @@ fn extension_error(err: TransactionValidityError) -> PrecompileFailure { impl PrecompileHandleExt for T where T: PrecompileHandle {} -pub(crate) trait PrecompileExt>: Precompile { +pub trait PrecompileExt>: Precompile { const INDEX: u64; // ss58 public key i.e., the contract sends funds it received to the destination address from diff --git a/precompiles/src/lib.rs b/precompiles/src/lib.rs index ca86d3f9b3..a824ac39d4 100644 --- a/precompiles/src/lib.rs +++ b/precompiles/src/lib.rs @@ -10,6 +10,7 @@ use frame_support::{ dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo}, pallet_prelude::Decode, }; +use pallet_admin_utils::PrecompileEnum; use pallet_evm::{ AddressMapping, IsPrecompileResult, Precompile, PrecompileHandle, PrecompileResult, PrecompileSet, @@ -24,24 +25,24 @@ use sp_core::{H160, U256, crypto::ByteArray}; use sp_runtime::traits::{AsSystemOriginSigner, Dispatchable, StaticLookup}; use subtensor_runtime_common::ProxyType; -use pallet_admin_utils::PrecompileEnum; - -use crate::address_mapping::*; -use crate::alpha::*; -use crate::balance_transfer::*; -use crate::crowdloan::*; -use crate::ed25519::*; use crate::extensions::*; -use crate::leasing::*; -use crate::metagraph::*; -use crate::neuron::*; -use crate::proxy::*; -use crate::sr25519::*; -use crate::staking::*; -use crate::storage_query::*; -use crate::subnet::*; -use crate::uid_lookup::*; -use crate::voting_power::*; + +pub use address_mapping::AddressMappingPrecompile; +pub use alpha::AlphaPrecompile; +pub use balance_transfer::BalanceTransferPrecompile; +pub use crowdloan::CrowdloanPrecompile; +pub use ed25519::Ed25519Verify; +pub use extensions::PrecompileExt; +pub use leasing::LeasingPrecompile; +pub use metagraph::MetagraphPrecompile; +pub use neuron::NeuronPrecompile; +pub use proxy::ProxyPrecompile; +pub use sr25519::Sr25519Verify; +pub use staking::{StakingPrecompile, StakingPrecompileV2}; +pub use storage_query::StorageQueryPrecompile; +pub use subnet::SubnetPrecompile; +pub use uid_lookup::UidLookupPrecompile; +pub use voting_power::VotingPowerPrecompile; mod address_mapping; mod alpha; diff --git a/precompiles/src/sr25519.rs b/precompiles/src/sr25519.rs index 6c6245d8f0..054948d524 100644 --- a/precompiles/src/sr25519.rs +++ b/precompiles/src/sr25519.rs @@ -9,7 +9,7 @@ use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile, PrecompileFailure}; use crate::{PrecompileExt, parse_slice}; -pub(crate) struct Sr25519Verify(PhantomData); +pub struct Sr25519Verify(PhantomData); impl PrecompileExt for Sr25519Verify where diff --git a/precompiles/src/staking.rs b/precompiles/src/staking.rs index 2ed8891a5c..c276c32e60 100644 --- a/precompiles/src/staking.rs +++ b/precompiles/src/staking.rs @@ -48,7 +48,7 @@ use crate::{PrecompileExt, PrecompileHandleExt}; // to stop supporting both precompiles. // // All the future extensions should happen in StakingPrecompileV2. -pub(crate) struct StakingPrecompileV2(PhantomData); +pub struct StakingPrecompileV2(PhantomData); impl PrecompileExt for StakingPrecompileV2 where @@ -450,7 +450,7 @@ where } // Deprecated, exists for backward compatibility. -pub(crate) struct StakingPrecompile(PhantomData); +pub struct StakingPrecompile(PhantomData); impl PrecompileExt for StakingPrecompile where diff --git a/precompiles/src/storage_query.rs b/precompiles/src/storage_query.rs index b9455ae708..493e4949b0 100644 --- a/precompiles/src/storage_query.rs +++ b/precompiles/src/storage_query.rs @@ -7,7 +7,7 @@ use sp_std::vec::Vec; use crate::PrecompileExt; -pub(crate) struct StorageQueryPrecompile(PhantomData); +pub struct StorageQueryPrecompile(PhantomData); impl PrecompileExt for StorageQueryPrecompile where diff --git a/precompiles/src/uid_lookup.rs b/precompiles/src/uid_lookup.rs index be8c803b45..b791b96786 100644 --- a/precompiles/src/uid_lookup.rs +++ b/precompiles/src/uid_lookup.rs @@ -8,7 +8,7 @@ use sp_std::vec::Vec; use crate::PrecompileExt; -pub(crate) struct UidLookupPrecompile(PhantomData); +pub struct UidLookupPrecompile(PhantomData); impl PrecompileExt for UidLookupPrecompile where diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index d965e5b5c7..95564977cf 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -159,6 +159,7 @@ ethereum.workspace = true frame-metadata.workspace = true sp-io.workspace = true sp-tracing.workspace = true +precompile-utils = { workspace = true, features = ["testing"] } [build-dependencies] substrate-wasm-builder = { workspace = true, optional = true } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 89735b1011..664c1304f8 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -243,7 +243,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: 377, + spec_version: 378, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/runtime/tests/precompiles.rs b/runtime/tests/precompiles.rs new file mode 100644 index 0000000000..5565aa8f7f --- /dev/null +++ b/runtime/tests/precompiles.rs @@ -0,0 +1,219 @@ +#![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 sp_core::{H160, H256, U256}; +use sp_runtime::traits::Hash; +use subtensor_precompiles::{ + AddressMappingPrecompile, BalanceTransferPrecompile, PrecompileExt, Precompiles, +}; + +type AccountId = ::AccountId; + +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 +} + +fn execute_precompile( + precompiles: &Precompiles, + 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) +} + +fn evm_apparent_value_from_substrate(amount: u64) -> U256 { + ::BalanceConverter::into_evm_balance(amount.into()) + .expect("runtime balance conversion should work for test amount") + .into() +} + +fn addr_from_index(index: u64) -> H160 { + H160::from_low_u64_be(index) +} + +#[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()); + }); +} + +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()); + }); + } +} + +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); + assert_eq!( + destination_balance_after, + destination_balance_before + amount + ); + }); + } + + #[test] + fn balance_transfer_precompile_respects_subtensor_extension_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("transaction extension rejected"), + "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); + }); + } +}