extern crate alloc; use alloc::format; use frame_support::dispatch::{DispatchInfo, GetDispatchInfo, Pays, PostDispatchInfo}; use frame_system::RawOrigin; use pallet_admin_utils::{PrecompileEnable, PrecompileEnum}; use pallet_evm::{ AddressMapping, BalanceConverter, EvmBalance, ExitError, GasWeightMapping, Precompile, PrecompileFailure, PrecompileHandle, PrecompileResult, }; use precompile_utils::EvmResult; use precompile_utils::prelude::RuntimeHelper; use sp_core::{H160, U256, blake2_256}; use sp_runtime::traits::{Dispatchable, ExtensionPostDispatchWeightHandler}; use sp_std::vec::Vec; use subtensor_runtime_common::with_evm_context; pub(crate) trait PrecompileHandleExt: PrecompileHandle { fn caller_account_id(&self) -> R::AccountId where R: frame_system::Config + pallet_evm::Config, ::AddressMapping: AddressMapping, { ::AddressMapping::into_account_id(self.context().caller) } fn record_db_reads(&mut self, reads: u64) -> EvmResult<()> where R: frame_system::Config + pallet_evm::Config, { self.record_cost(RuntimeHelper::::db_read_gas_cost().saturating_mul(reads))?; Ok(()) } fn record_db_writes(&mut self, writes: u64) -> EvmResult<()> where R: frame_system::Config + pallet_evm::Config, { self.record_cost(RuntimeHelper::::db_write_gas_cost().saturating_mul(writes))?; Ok(()) } fn try_convert_apparent_value(&self) -> EvmResult where R: pallet_evm::Config, { let amount = EvmBalance::new(self.context().apparent_value); let result = ::BalanceConverter::into_substrate_balance(amount) .ok_or(PrecompileFailure::Error { exit_status: ExitError::Other( "error converting balance from ETH to subtensor".into(), ), })?; Ok(result.into()) } /// Dispatches a runtime call, but also checks and records the gas costs. fn try_dispatch_runtime_call( &mut self, call: Call, origin: RawOrigin, ) -> EvmResult<()> where R: frame_system::Config + pallet_evm::Config + pallet_subtensor::Config + pallet_shield::Config + pallet_subtensor_proxy::Config, ::RuntimeCall: From, ::RuntimeCall: GetDispatchInfo + Dispatchable, ::RuntimeOrigin: From> + Clone, { let call = ::RuntimeCall::from(call); let info = GetDispatchInfo::get_dispatch_info(&call); let target_gas = self.gas_limit(); if let Some(gas) = target_gas { let valid_weight = ::GasWeightMapping::gas_to_weight(gas, false).ref_time(); if info.total_weight().ref_time() > valid_weight { return Err(PrecompileFailure::Error { exit_status: ExitError::OutOfGas, }); } } self.record_external_cost( Some(info.total_weight().ref_time()), Some(info.total_weight().proof_size()), None, )?; let origin = ::RuntimeOrigin::from(origin); match with_evm_context(|| call.dispatch(origin)) { Ok(mut post_info) => { post_info.set_extension_weight(&info); log::debug!("Dispatch succeeded. Post info: {post_info:?}"); self.charge_and_refund_after_dispatch::(&info, &post_info)?; Ok(()) } Err(e) => { let err_str: &'static str = e.into(); let mut post_info = e.post_info; post_info.set_extension_weight(&info); log::info!("Precompile dispatch failed. message as: {e:?}"); self.charge_and_refund_after_dispatch::(&info, &post_info)?; Err(PrecompileFailure::Error { exit_status: ExitError::Other( format!("dispatch execution failed: {err_str}").into(), ), }) } } } fn charge_and_refund_after_dispatch( &mut self, info: &DispatchInfo, post_info: &PostDispatchInfo, ) -> EvmResult<()> where R: frame_system::Config + pallet_evm::Config, { if post_info.pays_fee(info) == Pays::Yes { let actual_weight = post_info.calc_actual_weight(info); let cost = ::GasWeightMapping::weight_to_gas(actual_weight); self.record_cost(cost)?; self.refund_external_cost( Some( info.total_weight() .ref_time() .saturating_sub(actual_weight.ref_time()), ), Some( info.total_weight() .proof_size() .saturating_sub(actual_weight.proof_size()), ), ); } Ok(()) } } impl PrecompileHandleExt for T where T: PrecompileHandle {} pub trait PrecompileExt>: Precompile { const INDEX: u64; // ss58 public key i.e., the contract sends funds it received to the destination address from // the method parameter. fn account_id() -> AccountId { let hash = H160::from_low_u64_be(Self::INDEX); let prefix = b"evm:"; // Concatenate prefix and ethereum address let mut combined = Vec::new(); combined.extend_from_slice(prefix); combined.extend_from_slice(hash.as_bytes()); let hash = blake2_256(&combined); hash.into() } fn try_execute( handle: &mut impl PrecompileHandle, precompile_enum: PrecompileEnum, ) -> Option where R: frame_system::Config + pallet_admin_utils::Config, { if PrecompileEnable::::get(&precompile_enum) { Some(Self::execute(handle)) } else { Some(Err(PrecompileFailure::Error { exit_status: ExitError::Other( format!("Precompile {precompile_enum:?} is disabled").into(), ), })) } } } // allowing unreachable for the whole module fixes clippy reports about precompile macro // implementation for `TestPrecompile`, that couldn't be fixed granularly #[allow(unreachable_code)] #[cfg(test)] mod test { use super::*; use sp_core::crypto::AccountId32; #[test] fn ss58_address_from_index_works() { assert_eq!( TestPrecompile::account_id(), AccountId32::from([ 0x3a, 0x86, 0x18, 0xfb, 0xbb, 0x1b, 0xbc, 0x47, 0x86, 0x64, 0xff, 0x53, 0x46, 0x18, 0x0c, 0x35, 0xd0, 0x9f, 0xac, 0x26, 0xf2, 0x02, 0x70, 0x85, 0xb3, 0x1c, 0x56, 0xc1, 0x06, 0x3c, 0x1c, 0xd3, ]) ); } struct TestPrecompile; impl PrecompileExt for TestPrecompile { const INDEX: u64 = 2051; } #[precompile_utils::precompile] impl TestPrecompile {} }