git.delta.rocks / unique-network / refs/commits / b090e5828023

difftreelog

source

pallets/evm-coder-substrate/src/lib.rs7.1 KiBsourcehistory
1#![cfg_attr(not(feature = "std"), no_std)]23#[cfg(not(feature = "std"))]4extern crate alloc;56pub use pallet::*;78#[frame_support::pallet]9pub mod pallet {10	#[cfg(not(feature = "std"))]11	use alloc::format;1213	use evm_coder::{14		ToLog,15		abi::{AbiReader, AbiWrite, AbiWriter},16		execution::{self, Result},17		types::{Msg, value},18	};19	use frame_support::{ensure};20	use pallet_evm::{ExitError, ExitReason, ExitRevert, ExitSucceed, PrecompileOutput};21	pub use frame_support::dispatch::DispatchResult;22	use pallet_ethereum::EthereumTransactionSender;23	use sp_std::cell::RefCell;24	use sp_std::vec::Vec;25	use sp_core::{H160, H256};26	use ethereum::Log;27	use frame_support::{pallet_prelude::*, traits::PalletInfo};2829	/// DispatchError is opaque, but we need to somehow extract correct error in case of OutOfGas failure30	/// So we have this pallet, which defines OutOfGas error, and knews its own id to check if DispatchError31	/// is thrown because of it32	///33	/// These errors shouldn't end in extrinsic results, as they only used in evm execution path34	#[pallet::error]35	pub enum Error<T> {36		OutOfGas,37		OutOfFund,38	}3940	#[pallet::config]41	pub trait Config: frame_system::Config {42		type EthereumTransactionSender: pallet_ethereum::EthereumTransactionSender;43	}4445	#[pallet::pallet]46	pub struct Pallet<T>(_);4748	// FIXME: those items are defined as private in evm_gasometer::consts, and we can't directly use it49	pub const G_LOG: u64 = 375;50	pub const G_LOGDATA: u64 = 8;51	pub const G_LOGTOPIC: u64 = 375;5253	// From instabul hardfork configuration: https://github.com/rust-blockchain/evm/blob/fd4fd6acc0ca3208d6770fdb3ba407c94cdf97c6/runtime/src/lib.rs#L28454	pub const G_SLOAD_WORD: u64 = 800;55	pub const G_SSTORE_WORD: u64 = 20000;5657	fn log_price(data: usize, topics: usize) -> u64 {58		G_LOG59			.saturating_add((data as u64).saturating_mul(G_LOGDATA))60			.saturating_add((topics as u64).saturating_mul(G_LOGTOPIC))61	}6263	pub fn generate_transaction() -> ethereum::TransactionV0 {64		use ethereum::{TransactionV0, TransactionAction, TransactionSignature};65		TransactionV0 {66			nonce: 0.into(),67			gas_price: 0.into(),68			gas_limit: 0.into(),69			action: TransactionAction::Call(H160([0; 20])),70			value: 0.into(),71			// zero selector, this transaction always has same sender, so all data should be acquired from logs72			input: Vec::from([0, 0, 0, 0]),73			// if v is not 27 - then we need to pass some other validity checks74			signature: TransactionSignature::new(27, H256([0x88; 32]), H256([0x88; 32])).unwrap(),75		}76	}7778	#[derive(Default)]79	pub struct SubstrateRecorder<T: Config> {80		contract: H160,81		logs: RefCell<Vec<Log>>,82		initial_gas: u64,83		gas_limit: RefCell<u64>,84		_phantom: PhantomData<*const T>,85	}8687	impl<T: Config> SubstrateRecorder<T> {88		pub fn new(contract: H160, gas_limit: u64) -> Self {89			Self {90				contract,91				logs: RefCell::new(Vec::new()),92				initial_gas: gas_limit,93				gas_limit: RefCell::new(gas_limit),94				_phantom: PhantomData,95			}96		}9798		pub fn is_empty(&self) -> bool {99			self.logs.borrow().is_empty()100		}101		pub fn log_sub(&self, log: impl ToLog) -> DispatchResult {102			self.log_raw_sub(log.to_log(self.contract))103		}104		pub fn log_raw_sub(&self, log: Log) -> DispatchResult {105			self.consume_gas_sub(log_price(log.data.len(), log.topics.len()))?;106			self.logs.borrow_mut().push(log);107			Ok(())108		}109		/// Doesn't consumes any gas, should be used after consume_log_sub110		pub fn log_infallible(&self, log: impl ToLog) {111			self.logs.borrow_mut().push(log.to_log(self.contract));112		}113		pub fn retrieve_logs(self) -> Vec<Log> {114			self.logs.into_inner()115		}116117		pub fn gas_left(&self) -> u64 {118			*self.gas_limit.borrow()119		}120		pub fn consume_sload_sub(&self) -> DispatchResult {121			self.consume_gas_sub(G_SLOAD_WORD)122		}123		pub fn consume_sstores_sub(&self, amount: usize) -> DispatchResult {124			self.consume_gas_sub(G_SSTORE_WORD.saturating_mul(amount as u64))125		}126		pub fn consume_sstore_sub(&self) -> DispatchResult {127			self.consume_gas_sub(G_SSTORE_WORD)128		}129		pub fn consume_log_sub(&self, topics: usize, data: usize) -> DispatchResult {130			self.consume_gas_sub(log_price(data, topics))131		}132		pub fn consume_gas_sub(&self, gas: u64) -> DispatchResult {133			ensure!(gas != u64::MAX, Error::<T>::OutOfGas);134			let mut gas_limit = self.gas_limit.borrow_mut();135			ensure!(gas <= *gas_limit, Error::<T>::OutOfGas);136			*gas_limit -= gas;137			Ok(())138		}139140		pub fn consume_sload(&self) -> Result<()> {141			self.consume_gas(G_SLOAD_WORD)142		}143		pub fn consume_sstore(&self) -> Result<()> {144			self.consume_gas(G_SSTORE_WORD)145		}146		pub fn consume_gas(&self, gas: u64) -> Result<()> {147			if gas == u64::MAX {148				return Err(execution::Error::Error(ExitError::OutOfGas));149			}150			let mut gas_limit = self.gas_limit.borrow_mut();151			if gas > *gas_limit {152				return Err(execution::Error::Error(ExitError::OutOfGas));153			}154			*gas_limit -= gas;155			Ok(())156		}157158		pub fn evm_to_precompile_output(159			self,160			result: evm_coder::execution::Result<Option<AbiWriter>>,161		) -> Option<PrecompileOutput> {162			use evm_coder::execution::Error;163			let (writer, reason) = match result {164				Ok(Some(v)) => (v, ExitReason::Succeed(ExitSucceed::Returned)),165				Ok(None) => return None,166				Err(Error::Revert(e)) => {167					let mut writer = AbiWriter::new_call(evm_coder::fn_selector!(Error(string)));168					(&e as &str).abi_write(&mut writer);169170					(writer, ExitReason::Revert(ExitRevert::Reverted))171				}172				Err(Error::Fatal(f)) => (AbiWriter::new(), ExitReason::Fatal(f)),173				Err(Error::Error(e)) => (AbiWriter::new(), ExitReason::Error(e)),174			};175176			Some(PrecompileOutput {177				cost: self.initial_gas - self.gas_left(),178				exit_status: reason,179				logs: self.retrieve_logs(),180				output: writer.finish(),181			})182		}183184		pub fn submit_logs(self) -> DispatchResult {185			let logs = self.retrieve_logs();186			if logs.is_empty() {187				return Ok(());188			}189			T::EthereumTransactionSender::submit_logs_transaction(generate_transaction(), logs)190		}191	}192193	pub fn dispatch_to_evm<T: Config>(err: DispatchError) -> evm_coder::execution::Error {194		use evm_coder::execution::Error as ExError;195		match err {196			DispatchError::Module { index, error, .. }197				if index198					== T::PalletInfo::index::<Pallet<T>>()199						.expect("evm-coder-substrate is a pallet, which should be added to runtime")200						as u8 =>201			{202				match error {203					v if v == Error::<T>::OutOfGas.as_u8() => ExError::Error(ExitError::OutOfGas),204					v if v == Error::<T>::OutOfFund.as_u8() => ExError::Error(ExitError::OutOfFund),205					_ => unreachable!("this pallet only defines two possible errors"),206				}207			}208			DispatchError::Module {209				message: Some(msg), ..210			} => ExError::Revert(msg.into()),211			DispatchError::Module { index, error, .. } => {212				ExError::Revert(format!("error {} in pallet {}", error, index))213			}214			e => ExError::Revert(format!("substrate error: {:?}", e)),215		}216	}217218	pub fn call_internal<C: evm_coder::Call, E: evm_coder::Callable<C>>(219		caller: H160,220		e: &mut E,221		value: value,222		input: &[u8],223	) -> evm_coder::execution::Result<Option<AbiWriter>> {224		let (selector, mut reader) = AbiReader::new_call(input)?;225		let call = C::parse(selector, &mut reader)?;226		if call.is_none() {227			return Ok(None);228		}229		let call = call.unwrap();230		e.call(Msg {231			call,232			caller,233			value,234		})235		.map(Some)236	}237}