--- /dev/null +++ b/pallets/evm-coder-substrate/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "pallet-evm-coder-substrate" +version = "0.1.0" +edition = "2018" + +[dependencies] +sp-std = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.8' } +sp-core = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.8' } +ethereum = { default-features = false, version = "0.7.1" } +evm-coder = { default-features = false, path = "../../crates/evm-coder" } +pallet-ethereum = { default-features = false, version = "3.0.0-dev", git = "https://github.com/uniquenetwork/frontier.git", branch = "injected-transactions-parachain" } +pallet-evm = { default-features = false, version = "5.0.0-dev", git = "https://github.com/uniquenetwork/frontier.git", branch = "injected-transactions-parachain" } +frame-support = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.8' } +frame-system = { default-features = false, version = '3.0.0', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.8' } + +[dependencies.codec] +default-features = false +features = ['derive'] +package = 'parity-scale-codec' +version = '2.0.0' + +[features] +default = ["std"] +std = [ + "sp-std/std", + "sp-core/std", + "ethereum/std", + "evm-coder/std", + "pallet-ethereum/std", + "pallet-evm/std", + "frame-support/std", + "frame-system/std", +] --- /dev/null +++ b/pallets/evm-coder-substrate/src/lib.rs @@ -0,0 +1,227 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +extern crate alloc; + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + #[cfg(not(feature = "std"))] + use alloc::format; + + use evm_coder::{ + ToLog, + abi::{AbiReader, AbiWrite, AbiWriter}, + execution::{self, Result}, + types::{Msg, value}, + }; + use frame_support::ensure; + use pallet_evm::{ExitError, ExitReason, ExitRevert, ExitSucceed, PrecompileOutput}; + pub use frame_support::dispatch::DispatchResult; + use pallet_ethereum::EthereumTransactionSender; + use sp_std::cell::RefCell; + use sp_std::vec::Vec; + use sp_core::{H160, H256}; + use ethereum::Log; + use frame_support::{pallet_prelude::*, traits::PalletInfo}; + + /// DispatchError is opaque, but we need to somehow extract correct error in case of OutOfGas failure + /// So we have this pallet, which defines OutOfGas error, and knews its own id to check if DispatchError + /// is thrown because of it + /// + /// These errors shouldn't end in extrinsic results, as they only used in evm execution path + #[pallet::error] + pub enum Error { + OutOfGas, + OutOfFund, + } + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_evm::Config { + type EthereumTransactionSender: pallet_ethereum::EthereumTransactionSender; + } + + #[pallet::pallet] + pub struct Pallet(_); + + // FIXME: those items are defined as private in evm_gasometer::consts, and we can't directly use it + pub const G_LOG: u64 = 375; + pub const G_LOGDATA: u64 = 8; + pub const G_LOGTOPIC: u64 = 375; + + // From instabul hardfork configuration: https://github.com/rust-blockchain/evm/blob/fd4fd6acc0ca3208d6770fdb3ba407c94cdf97c6/runtime/src/lib.rs#L284 + pub const G_SLOAD_WORD: u64 = 800; + pub const G_SSTORE_WORD: u64 = 20000; + + fn log_price(data: usize, topics: usize) -> u64 { + G_LOG + .saturating_add((data as u64).saturating_mul(G_LOGDATA)) + .saturating_add((topics as u64).saturating_mul(G_LOGTOPIC)) + } + + pub fn generate_transaction() -> ethereum::Transaction { + use ethereum::{Transaction, TransactionAction, TransactionSignature}; + Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas_limit: 0.into(), + action: TransactionAction::Call(H160([0; 20])), + value: 0.into(), + // zero selector, this transaction always has same sender, so all data should be acquired from logs + input: Vec::from([0, 0, 0, 0]), + // if v is not 27 - then we need to pass some other validity checks + signature: TransactionSignature::new(27, H256([0x88; 32]), H256([0x88; 32])).unwrap(), + } + } + + #[derive(Default)] + pub struct SubstrateRecorder { + contract: H160, + logs: RefCell>, + initial_gas: u64, + gas_limit: RefCell, + _phantom: PhantomData<*const T>, + } + + impl SubstrateRecorder { + pub fn new(contract: H160, gas_limit: u64) -> Self { + Self { + contract, + logs: RefCell::new(Vec::new()), + initial_gas: gas_limit, + gas_limit: RefCell::new(gas_limit), + _phantom: PhantomData, + } + } + + pub fn is_empty(&self) -> bool { + self.logs.borrow().is_empty() + } + pub fn log_sub(&self, log: impl ToLog) -> DispatchResult { + self.log_raw_sub(log.to_log(self.contract)) + } + pub fn log_raw_sub(&self, log: Log) -> DispatchResult { + self.consume_gas_sub(log_price(log.data.len(), log.topics.len()))?; + self.logs.borrow_mut().push(log); + Ok(()) + } + pub fn retrieve_logs(self) -> Vec { + self.logs.into_inner() + } + + pub fn gas_left(&self) -> u64 { + *self.gas_limit.borrow() + } + pub fn consume_sload_sub(&self) -> DispatchResult { + self.consume_gas_sub(G_SLOAD_WORD) + } + pub fn consume_sstore_sub(&self) -> DispatchResult { + self.consume_gas_sub(G_SSTORE_WORD) + } + pub fn consume_gas_sub(&self, gas: u64) -> DispatchResult { + ensure!(gas != u64::MAX, Error::::OutOfGas); + let mut gas_limit = self.gas_limit.borrow_mut(); + ensure!(gas <= *gas_limit, Error::::OutOfGas); + *gas_limit -= gas; + Ok(()) + } + + pub fn consume_sload(&self) -> Result<()> { + self.consume_gas(G_SLOAD_WORD) + } + pub fn consume_sstore(&self) -> Result<()> { + self.consume_gas(G_SSTORE_WORD) + } + pub fn consume_gas(&self, gas: u64) -> Result<()> { + if gas == u64::MAX { + return Err(execution::Error::Error(ExitError::OutOfGas)); + } + let mut gas_limit = self.gas_limit.borrow_mut(); + if gas > *gas_limit { + return Err(execution::Error::Error(ExitError::OutOfGas)); + } + *gas_limit -= gas; + Ok(()) + } + + pub fn evm_to_precompile_output( + self, + result: evm_coder::execution::Result>, + ) -> Option { + use evm_coder::execution::Error; + let (writer, reason) = match result { + Ok(Some(v)) => (v, ExitReason::Succeed(ExitSucceed::Returned)), + Ok(None) => return None, + Err(Error::Revert(e)) => { + let mut writer = AbiWriter::new_call(evm_coder::fn_selector!(Error(string))); + (&e as &str).abi_write(&mut writer); + + (writer, ExitReason::Revert(ExitRevert::Reverted)) + } + Err(Error::Fatal(f)) => (AbiWriter::new(), ExitReason::Fatal(f)), + Err(Error::Error(e)) => (AbiWriter::new(), ExitReason::Error(e)), + }; + + Some(PrecompileOutput { + cost: self.initial_gas - self.gas_left(), + exit_status: reason, + logs: self.retrieve_logs(), + output: writer.finish(), + }) + } + + pub fn submit_logs(self) -> DispatchResult { + let logs = self.retrieve_logs(); + if logs.is_empty() { + return Ok(()); + } + T::EthereumTransactionSender::submit_logs_transaction(generate_transaction(), logs) + } + } + + pub fn dispatch_to_evm(err: DispatchError) -> evm_coder::execution::Error { + use evm_coder::execution::Error as ExError; + match err { + DispatchError::Module { index, error, .. } + if index + == T::PalletInfo::index::>() + .expect("evm-coder-substrate is a pallet, which should be added to runtime") + as u8 => + { + match error { + v if v == Error::::OutOfGas.as_u8() => ExError::Error(ExitError::OutOfGas), + v if v == Error::::OutOfFund.as_u8() => ExError::Error(ExitError::OutOfFund), + _ => unreachable!("this pallet only defines two possible errors"), + } + } + DispatchError::Module { + message: Some(msg), .. + } => ExError::Revert(msg.into()), + DispatchError::Module { index, error, .. } => { + ExError::Revert(format!("error {} in pallet {}", error, index)) + } + e => ExError::Revert(format!("substrate error: {:?}", e)), + } + } + + pub fn call_internal>( + caller: H160, + e: &mut E, + value: value, + input: &[u8], + ) -> evm_coder::execution::Result> { + let (selector, mut reader) = AbiReader::new_call(input)?; + let call = C::parse(selector, &mut reader)?; + if call.is_none() { + return Ok(None); + } + let call = call.unwrap(); + e.call(Msg { + call, + caller, + value, + }) + .map(Some) + } +}