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 30 31 32 33 34 #[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 49 pub const G_LOG: u64 = 375;50 pub const G_LOGDATA: u64 = 8;51 pub const G_LOGTOPIC: u64 = 375;5253 54 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 72 input: Vec::from([0, 0, 0, 0]),73 74 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 110 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_sstore_sub(&self) -> DispatchResult {124 self.consume_gas_sub(G_SSTORE_WORD)125 }126 pub fn consume_log_sub(&self, topics: usize, data: usize) -> DispatchResult {127 self.consume_gas_sub(log_price(data, topics))128 }129 pub fn consume_gas_sub(&self, gas: u64) -> DispatchResult {130 ensure!(gas != u64::MAX, Error::<T>::OutOfGas);131 let mut gas_limit = self.gas_limit.borrow_mut();132 ensure!(gas <= *gas_limit, Error::<T>::OutOfGas);133 *gas_limit -= gas;134 Ok(())135 }136137 pub fn consume_sload(&self) -> Result<()> {138 self.consume_gas(G_SLOAD_WORD)139 }140 pub fn consume_sstore(&self) -> Result<()> {141 self.consume_gas(G_SSTORE_WORD)142 }143 pub fn consume_gas(&self, gas: u64) -> Result<()> {144 if gas == u64::MAX {145 return Err(execution::Error::Error(ExitError::OutOfGas));146 }147 let mut gas_limit = self.gas_limit.borrow_mut();148 if gas > *gas_limit {149 return Err(execution::Error::Error(ExitError::OutOfGas));150 }151 *gas_limit -= gas;152 Ok(())153 }154155 pub fn evm_to_precompile_output(156 self,157 result: evm_coder::execution::Result<Option<AbiWriter>>,158 ) -> Option<PrecompileOutput> {159 use evm_coder::execution::Error;160 let (writer, reason) = match result {161 Ok(Some(v)) => (v, ExitReason::Succeed(ExitSucceed::Returned)),162 Ok(None) => return None,163 Err(Error::Revert(e)) => {164 let mut writer = AbiWriter::new_call(evm_coder::fn_selector!(Error(string)));165 (&e as &str).abi_write(&mut writer);166167 (writer, ExitReason::Revert(ExitRevert::Reverted))168 }169 Err(Error::Fatal(f)) => (AbiWriter::new(), ExitReason::Fatal(f)),170 Err(Error::Error(e)) => (AbiWriter::new(), ExitReason::Error(e)),171 };172173 Some(PrecompileOutput {174 cost: self.initial_gas - self.gas_left(),175 exit_status: reason,176 logs: self.retrieve_logs(),177 output: writer.finish(),178 })179 }180181 pub fn submit_logs(self) -> DispatchResult {182 let logs = self.retrieve_logs();183 if logs.is_empty() {184 return Ok(());185 }186 T::EthereumTransactionSender::submit_logs_transaction(generate_transaction(), logs)187 }188 }189190 pub fn dispatch_to_evm<T: Config>(err: DispatchError) -> evm_coder::execution::Error {191 use evm_coder::execution::Error as ExError;192 match err {193 DispatchError::Module { index, error, .. }194 if index195 == T::PalletInfo::index::<Pallet<T>>()196 .expect("evm-coder-substrate is a pallet, which should be added to runtime")197 as u8 =>198 {199 match error {200 v if v == Error::<T>::OutOfGas.as_u8() => ExError::Error(ExitError::OutOfGas),201 v if v == Error::<T>::OutOfFund.as_u8() => ExError::Error(ExitError::OutOfFund),202 _ => unreachable!("this pallet only defines two possible errors"),203 }204 }205 DispatchError::Module {206 message: Some(msg), ..207 } => ExError::Revert(msg.into()),208 DispatchError::Module { index, error, .. } => {209 ExError::Revert(format!("error {} in pallet {}", error, index))210 }211 e => ExError::Revert(format!("substrate error: {:?}", e)),212 }213 }214215 pub fn call_internal<C: evm_coder::Call, E: evm_coder::Callable<C>>(216 caller: H160,217 e: &mut E,218 value: value,219 input: &[u8],220 ) -> evm_coder::execution::Result<Option<AbiWriter>> {221 let (selector, mut reader) = AbiReader::new_call(input)?;222 let call = C::parse(selector, &mut reader)?;223 if call.is_none() {224 return Ok(None);225 }226 let call = call.unwrap();227 e.call(Msg {228 call,229 caller,230 value,231 })232 .map(Some)233 }234}