git.delta.rocks / unique-network / refs/commits / 0902e70cd1b8

difftreelog

source

pallets/evm-coder-substrate/src/lib.rs9.2 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819#[cfg(not(feature = "std"))]20extern crate alloc;21// #[cfg(feature = "runtime-benchmarks")]22// pub mod benchmarking;2324pub use pallet::*;2526#[frame_support::pallet]27pub mod pallet {28	#[cfg(not(feature = "std"))]29	use alloc::format;3031	use evm_coder::{32		ToLog,33		abi::{AbiReader, AbiWrite, AbiWriter},34		execution::{self, Result},35		types::{Msg, value},36	};37	use frame_support::{ensure, sp_runtime::ModuleError};38	use pallet_evm::{39		ExitError, ExitRevert, ExitSucceed, GasWeightMapping, PrecompileFailure, PrecompileOutput,40		PrecompileResult, runner::stack::MaybeMirroredLog,41	};42	use frame_system::ensure_signed;43	pub use frame_support::dispatch::DispatchResult;44	use pallet_ethereum::EthereumTransactionSender;45	use sp_std::cell::RefCell;46	use sp_std::vec::Vec;47	use sp_core::{H160, H256};48	use ethereum::TransactionV2;49	use frame_support::{pallet_prelude::*, traits::PalletInfo};50	use frame_system::pallet_prelude::*;5152	/// DispatchError is opaque, but we need to somehow extract correct error in case of OutOfGas failure53	/// So we have this pallet, which defines OutOfGas error, and knews its own id to check if DispatchError54	/// is thrown because of it55	///56	/// These errors shouldn't end in extrinsic results, as they only used in evm execution path57	#[pallet::error]58	pub enum Error<T> {59		OutOfGas,60		OutOfFund,61	}6263	#[pallet::config]64	pub trait Config: frame_system::Config {65		type EthereumTransactionSender: pallet_ethereum::EthereumTransactionSender;66		type GasWeightMapping: pallet_evm::GasWeightMapping;67	}6869	#[pallet::pallet]70	pub struct Pallet<T>(_);7172	#[pallet::call]73	impl<T: Config> Pallet<T> {74		#[pallet::weight(0)]75		pub fn empty_call(origin: OriginFor<T>) -> DispatchResult {76			let _sender = ensure_signed(origin)?;77			Ok(())78		}79	}8081	// From instabul hardfork configuration: https://github.com/rust-blockchain/evm/blob/fd4fd6acc0ca3208d6770fdb3ba407c94cdf97c6/runtime/src/lib.rs#L28482	pub const G_SLOAD_WORD: u64 = 800;83	pub const G_SSTORE_WORD: u64 = 20000;8485	pub fn generate_transaction() -> TransactionV2 {86		use ethereum::{TransactionV0, TransactionAction, TransactionSignature};87		TransactionV2::Legacy(TransactionV0 {88			nonce: 0.into(),89			gas_price: 0.into(),90			gas_limit: 0.into(),91			action: TransactionAction::Call(H160([0; 20])),92			value: 0.into(),93			// zero selector, this transaction always has same sender, so all data should be acquired from logs94			input: Vec::from([0, 0, 0, 0]),95			// if v is not 27 - then we need to pass some other validity checks96			signature: TransactionSignature::new(27, H256([0x88; 32]), H256([0x88; 32])).unwrap(),97		})98	}99100	#[derive(Default)]101	pub struct SubstrateRecorder<T: Config> {102		contract: H160,103		logs: RefCell<Vec<MaybeMirroredLog>>,104		initial_gas: u64,105		gas_limit: RefCell<u64>,106		_phantom: PhantomData<*const T>,107	}108109	impl<T: Config> SubstrateRecorder<T> {110		pub fn new(contract: H160, gas_limit: u64) -> Self {111			Self {112				contract,113				logs: RefCell::new(Vec::new()),114				initial_gas: gas_limit,115				gas_limit: RefCell::new(gas_limit),116				_phantom: PhantomData,117			}118		}119120		pub fn is_empty(&self) -> bool {121			self.logs.borrow().is_empty()122		}123		// Logs emitted with log_direct appear as substrate evm.Log event124		pub fn log_direct(&self, log: impl ToLog) {125			self.logs126				.borrow_mut()127				.push(MaybeMirroredLog::direct(log.to_log(self.contract)))128		}129		/// If log already has substrate equivalent - then we don't need to emit evm.Log130		pub fn log_mirrored(&self, log: impl ToLog) {131			self.logs132				.borrow_mut()133				.push(MaybeMirroredLog::mirrored(log.to_log(self.contract)))134		}135		pub fn retrieve_logs(self) -> Vec<MaybeMirroredLog> {136			self.logs.into_inner()137		}138139		pub fn gas_left(&self) -> u64 {140			*self.gas_limit.borrow()141		}142		pub fn consume_sload_sub(&self) -> DispatchResult {143			self.consume_gas_sub(G_SLOAD_WORD)144		}145		pub fn consume_sstores_sub(&self, amount: usize) -> DispatchResult {146			self.consume_gas_sub(G_SSTORE_WORD.saturating_mul(amount as u64))147		}148		pub fn consume_sstore_sub(&self) -> DispatchResult {149			self.consume_gas_sub(G_SSTORE_WORD)150		}151		pub fn consume_gas_sub(&self, gas: u64) -> DispatchResult {152			ensure!(gas != u64::MAX, Error::<T>::OutOfGas);153			let mut gas_limit = self.gas_limit.borrow_mut();154			ensure!(gas <= *gas_limit, Error::<T>::OutOfGas);155			*gas_limit -= gas;156			Ok(())157		}158159		pub fn consume_sload(&self) -> Result<()> {160			self.consume_gas(G_SLOAD_WORD)161		}162		pub fn consume_sstore(&self) -> Result<()> {163			self.consume_gas(G_SSTORE_WORD)164		}165		pub fn consume_gas(&self, gas: u64) -> Result<()> {166			if gas == u64::MAX {167				return Err(execution::Error::Error(ExitError::OutOfGas));168			}169			let mut gas_limit = self.gas_limit.borrow_mut();170			if gas > *gas_limit {171				return Err(execution::Error::Error(ExitError::OutOfGas));172			}173			*gas_limit -= gas;174			Ok(())175		}176		pub fn return_gas(&self, gas: u64) {177			let mut gas_limit = self.gas_limit.borrow_mut();178			*gas_limit += gas;179		}180181		pub fn evm_to_precompile_output(182			self,183			result: evm_coder::execution::Result<Option<AbiWriter>>,184		) -> Option<PrecompileResult> {185			use evm_coder::execution::Error;186			Some(match result {187				Ok(Some(v)) => Ok(PrecompileOutput {188					exit_status: ExitSucceed::Returned,189					cost: self.initial_gas - self.gas_left(),190					// TODO: preserve mirroring status191					logs: self.retrieve_logs().into_iter().map(|l| l.log).collect(),192					output: v.finish(),193				}),194				Ok(None) => return None,195				Err(Error::Revert(e)) => {196					let mut writer = AbiWriter::new_call(evm_coder::fn_selector!(Error(string)));197					(&e as &str).abi_write(&mut writer);198199					Err(PrecompileFailure::Revert {200						exit_status: ExitRevert::Reverted,201						cost: self.initial_gas - self.gas_left(),202						output: writer.finish(),203					})204				}205				Err(Error::Fatal(f)) => Err(f.into()),206				Err(Error::Error(e)) => Err(e.into()),207			})208		}209210		pub fn submit_logs(self) {211			let logs = self.retrieve_logs();212			if logs.is_empty() {213				return;214			}215			T::EthereumTransactionSender::submit_logs_transaction(216				Default::default(),217				generate_transaction(),218				logs,219			)220		}221	}222223	pub fn dispatch_to_evm<T: Config>(err: DispatchError) -> evm_coder::execution::Error {224		use evm_coder::execution::Error as ExError;225		match err {226			DispatchError::Module(ModuleError { index, error, .. })227				if index228					== T::PalletInfo::index::<Pallet<T>>()229						.expect("evm-coder-substrate is a pallet, which should be added to runtime")230						as u8 =>231			{232				match error {233					v if v == Error::<T>::OutOfGas.as_u8() => ExError::Error(ExitError::OutOfGas),234					v if v == Error::<T>::OutOfFund.as_u8() => ExError::Error(ExitError::OutOfFund),235					_ => unreachable!("this pallet only defines two possible errors"),236				}237			}238			DispatchError::Module(ModuleError {239				message: Some(msg), ..240			}) => ExError::Revert(msg.into()),241			DispatchError::Module(ModuleError { index, error, .. }) => {242				ExError::Revert(format!("error {} in pallet {}", error, index))243			}244			e => ExError::Revert(format!("substrate error: {:?}", e)),245		}246	}247248	pub trait WithRecorder<T: Config> {249		fn recorder(&self) -> &SubstrateRecorder<T>;250		fn into_recorder(self) -> SubstrateRecorder<T>;251	}252253	/// Helper to simplify implementing bridge between evm-coder definitions and pallet-evm254	pub fn call<255		T: Config,256		C: evm_coder::Call + evm_coder::Weighted,257		E: evm_coder::Callable<C> + WithRecorder<T>,258	>(259		caller: H160,260		mut e: E,261		value: value,262		input: &[u8],263	) -> Option<PrecompileResult> {264		let result = call_internal(caller, &mut e, value, input);265		e.into_recorder().evm_to_precompile_output(result)266	}267268	fn call_internal<269		T: Config,270		C: evm_coder::Call + evm_coder::Weighted,271		E: evm_coder::Callable<C> + WithRecorder<T>,272	>(273		caller: H160,274		e: &mut E,275		value: value,276		input: &[u8],277	) -> evm_coder::execution::Result<Option<AbiWriter>> {278		let (selector, mut reader) = AbiReader::new_call(input)?;279		let call = C::parse(selector, &mut reader)?;280		if call.is_none() {281			return Ok(None);282		}283		let call = call.unwrap();284285		let dispatch_info = call.weight();286		e.recorder()287			.consume_gas(T::GasWeightMapping::weight_to_gas(dispatch_info.weight))?;288289		match e.call(Msg {290			call,291			caller,292			value,293		}) {294			Ok(v) => {295				let unspent = v.post_info.calc_unspent(&dispatch_info);296				e.recorder()297					.return_gas(T::GasWeightMapping::weight_to_gas(unspent));298				Ok(Some(v.data))299			}300			Err(v) => {301				let unspent = v.post_info.calc_unspent(&dispatch_info);302				e.recorder()303					.return_gas(T::GasWeightMapping::weight_to_gas(unspent));304				Err(v.data)305			}306		}307	}308}