From 31dbaebdc7d5da5373367b8350cc3407481360c3 Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Tue, 26 Apr 2022 14:03:58 +0000 Subject: [PATCH 01/17] trait and dummy example --- src/executor/stack/executor.rs | 97 ++++++++++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 9 deletions(-) diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index 8eea59d9..72ad15ef 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -243,11 +243,12 @@ pub type PrecompileResult = Result; /// A set of precompiles. /// Checks of the provided address being in the precompile set should be /// as cheap as possible since it may be called often. -pub trait PrecompileSet { +pub trait PrecompileSet { /// Tries to execute a precompile in the precompile set. /// If the provided address is not a precompile, returns None. fn execute( &self, + handle: &mut H, address: H160, input: &[u8], gas_limit: Option, @@ -261,9 +262,10 @@ pub trait PrecompileSet { fn is_precompile(&self, address: H160) -> bool; } -impl PrecompileSet for () { +impl PrecompileSet for () { fn execute( &self, + _: &mut H, _: H160, _: &[u8], _: Option, @@ -285,9 +287,10 @@ impl PrecompileSet for () { /// * Is static pub type PrecompileFn = fn(&[u8], Option, &Context, bool) -> PrecompileResult; -impl PrecompileSet for BTreeMap { +impl PrecompileSet for BTreeMap { fn execute( &self, + _handle: &mut H, address: H160, input: &[u8], gas_limit: Option, @@ -313,7 +316,7 @@ pub struct StackExecutor<'config, 'precompiles, S, P> { precompile_set: &'precompiles P, } -impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> +impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> StackExecutor<'config, 'precompiles, S, P> { /// Return a reference of the Config. @@ -855,10 +858,14 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> } } - if let Some(result) = - self.precompile_set - .execute(code_address, &input, Some(gas_limit), &context, is_static) - { + if let Some(result) = self.precompile_set.execute( + self, + code_address, + &input, + Some(gas_limit), + &context, + is_static, + ) { return match result { Ok(PrecompileOutput { exit_status, @@ -932,7 +939,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> } } -impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler +impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler for StackExecutor<'config, 'precompiles, S, P> { type CreateInterrupt = Infallible; @@ -1173,3 +1180,75 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler Ok(()) } } + +pub trait PrecompileHandle { + fn call( + &mut self, + code_address: H160, + transfer: Option, + input: Vec, + target_gas: Option, + is_static: bool, + context: Context, + ) -> (ExitReason, Vec); +} + +impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> PrecompileHandle + for StackExecutor<'config, 'precompiles, S, P> +{ + fn call( + &mut self, + code_address: H160, + transfer: Option, + input: Vec, + target_gas: Option, + is_static: bool, + context: Context, + ) -> (ExitReason, Vec) { + match self.call_inner( + code_address, + transfer, + input, + target_gas, + is_static, + true, + true, + context, + ) { + Capture::Exit((s, v)) => emit_exit!(s, v), + Capture::Trap(_) => unreachable!(), + } + } +} + +pub struct ExemplePrecompileSet(core::marker::PhantomData); + +impl PrecompileSet for ExemplePrecompileSet { + fn execute( + &self, + handle: &mut H, + address: H160, + input: &[u8], + gas_limit: Option, + context: &Context, + is_static: bool, + ) -> Option { + let (_s, _v) = handle.call( + address, + None, + input.to_vec(), + gas_limit, + is_static, + context.clone(), + ); + + todo!() + } + + /// Check if the given address is a precompile. Should only be called to + /// perform the check while not executing the precompile afterward, since + /// `execute` already performs a check internally. + fn is_precompile(&self, _address: H160) -> bool { + todo!() + } +} From 1f9348907f6fed853f0e763ce88b4869d9924fc2 Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Mon, 2 May 2022 14:35:35 +0000 Subject: [PATCH 02/17] improve PrecompileHandle trait --- src/executor/stack/executor.rs | 125 +++++++++++++++++++++++---------- 1 file changed, 87 insertions(+), 38 deletions(-) diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index 72ad15ef..182b30e6 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -1,8 +1,8 @@ use crate::backend::Backend; use crate::gasometer::{self, Gasometer, StorageTarget}; use crate::{ - Capture, Config, Context, CreateScheme, ExitError, ExitReason, ExitSucceed, Handler, Opcode, - Runtime, Stack, Transfer, + CallScheme, Capture, Config, Context, CreateScheme, ExitError, ExitReason, ExitSucceed, + Handler, Opcode, Runtime, Stack, Transfer, }; use alloc::{ collections::{BTreeMap, BTreeSet}, @@ -237,6 +237,31 @@ pub enum PrecompileFailure { Fatal { exit_status: ExitFatal }, } +/// Wraps an EVM context and prevents its construction from foreign code. +/// Prevents the precompile to modify its context when doing subcalls. +pub struct PrecompileContext(Context); + +impl core::ops::Deref for PrecompileContext { + type Target = Context; + + fn deref(&self) -> &Context { + &self.0 + } +} + +/// Handle provided to a precompile to interact with the EVM. +pub trait PrecompileHandle { + fn call( + &mut self, + to: H160, + scheme: CallScheme, + input: Vec, + value: U256, + target_gas: Option, + context: &PrecompileContext, + ) -> (ExitReason, Vec); +} + /// A precompile result. pub type PrecompileResult = Result; @@ -252,7 +277,7 @@ pub trait PrecompileSet { address: H160, input: &[u8], gas_limit: Option, - context: &Context, + context: &PrecompileContext, is_static: bool, ) -> Option; @@ -269,7 +294,7 @@ impl PrecompileSet for () { _: H160, _: &[u8], _: Option, - _: &Context, + _: &PrecompileContext, _: bool, ) -> Option { None @@ -294,7 +319,7 @@ impl PrecompileSet for BTreeMap { address: H160, input: &[u8], gas_limit: Option, - context: &Context, + context: &PrecompileContext, is_static: bool, ) -> Option { self.get(&address) @@ -863,7 +888,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> code_address, &input, Some(gas_limit), - &context, + &PrecompileContext(context.clone()), is_static, ) { return match result { @@ -1181,38 +1206,61 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Hand } } -pub trait PrecompileHandle { - fn call( - &mut self, - code_address: H160, - transfer: Option, - input: Vec, - target_gas: Option, - is_static: bool, - context: Context, - ) -> (ExitReason, Vec); -} - impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> PrecompileHandle for StackExecutor<'config, 'precompiles, S, P> { fn call( &mut self, - code_address: H160, - transfer: Option, + to: H160, + scheme: CallScheme, input: Vec, + value: U256, target_gas: Option, - is_static: bool, - context: Context, + context: &PrecompileContext, ) -> (ExitReason, Vec) { - match self.call_inner( - code_address, + let context = context.0.clone(); + + let context = match scheme { + CallScheme::Call | CallScheme::StaticCall => Context { + address: to.into(), + caller: context.address, + apparent_value: value, + }, + CallScheme::CallCode => Context { + address: context.address, + caller: context.address, + apparent_value: value, + }, + CallScheme::DelegateCall => Context { + address: context.address, + caller: context.caller, + apparent_value: context.apparent_value, + }, + }; + + let transfer = if scheme == CallScheme::Call { + Some(Transfer { + source: context.address, + target: to.into(), + value, + }) + } else if scheme == CallScheme::CallCode { + Some(Transfer { + source: context.address, + target: context.address, + value, + }) + } else { + None + }; + + match Handler::call( + self, + to.into(), transfer, input, target_gas, - is_static, - true, - true, + scheme == CallScheme::StaticCall, context, ) { Capture::Exit((s, v)) => emit_exit!(s, v), @@ -1227,22 +1275,23 @@ impl PrecompileSet for ExemplePrecompileSet { fn execute( &self, handle: &mut H, - address: H160, - input: &[u8], + _address: H160, + _input: &[u8], gas_limit: Option, - context: &Context, - is_static: bool, + context: &PrecompileContext, + _is_static: bool, ) -> Option { - let (_s, _v) = handle.call( - address, - None, - input.to_vec(), + // Subcall + let (_status, _data) = handle.call( + H160::repeat_byte(0x11), // exemple + CallScheme::Call, + b"Test".to_vec(), + U256::zero(), gas_limit, - is_static, - context.clone(), + context, ); - todo!() + todo!("do stuff with the result") } /// Check if the given address is a precompile. Should only be called to From 2a25d1a99dfa8edd954aae36363407e52f46d82a Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Wed, 4 May 2022 08:55:56 +0000 Subject: [PATCH 03/17] custom call context, handle is now used to record gas and logs --- src/executor/stack/executor.rs | 129 +++++++++------------------------ 1 file changed, 36 insertions(+), 93 deletions(-) diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index 182b30e6..89bec336 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -1,8 +1,8 @@ use crate::backend::Backend; use crate::gasometer::{self, Gasometer, StorageTarget}; use crate::{ - CallScheme, Capture, Config, Context, CreateScheme, ExitError, ExitReason, ExitSucceed, - Handler, Opcode, Runtime, Stack, Transfer, + Capture, Config, Context, CreateScheme, ExitError, ExitReason, ExitSucceed, Handler, Opcode, + Runtime, Stack, Transfer, }; use alloc::{ collections::{BTreeMap, BTreeSet}, @@ -10,7 +10,6 @@ use alloc::{ vec::Vec, }; use core::{cmp::min, convert::Infallible}; -use ethereum::Log; use evm_core::{ExitFatal, ExitRevert}; use primitive_types::{H160, H256, U256}; use sha3::{Digest, Keccak256}; @@ -216,9 +215,7 @@ pub trait StackState<'config>: Backend { #[derive(Debug, Eq, PartialEq, Clone)] pub struct PrecompileOutput { pub exit_status: ExitSucceed, - pub cost: u64, pub output: Vec, - pub logs: Vec, } /// Data returned by a precompile in case of failure. @@ -226,40 +223,33 @@ pub struct PrecompileOutput { pub enum PrecompileFailure { /// Reverts the state changes and consume all the gas. Error { exit_status: ExitError }, - /// Reverts the state changes and consume the provided `cost`. + /// Reverts the state changes. /// Returns the provided error message. Revert { exit_status: ExitRevert, output: Vec, - cost: u64, }, /// Mark this failure as fatal, and all EVM execution stacks must be exited. Fatal { exit_status: ExitFatal }, } -/// Wraps an EVM context and prevents its construction from foreign code. -/// Prevents the precompile to modify its context when doing subcalls. -pub struct PrecompileContext(Context); - -impl core::ops::Deref for PrecompileContext { - type Target = Context; - - fn deref(&self) -> &Context { - &self.0 - } -} - /// Handle provided to a precompile to interact with the EVM. pub trait PrecompileHandle { + /// Perform subcall in provided context. + /// Precompile specifies in which context the subcall is executed. fn call( &mut self, to: H160, - scheme: CallScheme, + transfer: Option, input: Vec, - value: U256, - target_gas: Option, - context: &PrecompileContext, + gas_limit: Option, + is_static: bool, + context: &Context, ) -> (ExitReason, Vec); + + fn record_cost(&mut self, cost: u64) -> Result<(), ExitError>; + + fn log(&mut self, address: H160, topics: Vec, data: Vec); } /// A precompile result. @@ -277,7 +267,7 @@ pub trait PrecompileSet { address: H160, input: &[u8], gas_limit: Option, - context: &PrecompileContext, + context: &Context, is_static: bool, ) -> Option; @@ -294,7 +284,7 @@ impl PrecompileSet for () { _: H160, _: &[u8], _: Option, - _: &PrecompileContext, + _: &Context, _: bool, ) -> Option { None @@ -319,7 +309,7 @@ impl PrecompileSet for BTreeMap { address: H160, input: &[u8], gas_limit: Option, - context: &PrecompileContext, + context: &Context, is_static: bool, ) -> Option { self.get(&address) @@ -888,31 +878,14 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> code_address, &input, Some(gas_limit), - &PrecompileContext(context.clone()), + &context, is_static, ) { return match result { Ok(PrecompileOutput { exit_status, output, - cost, - logs, }) => { - for Log { - address, - topics, - data, - } in logs - { - match self.log(address, topics, data) { - Ok(_) => continue, - Err(error) => { - return Capture::Exit((ExitReason::Error(error), output)); - } - } - } - - let _ = self.state.metadata_mut().gasometer.record_cost(cost); let _ = self.exit_substate(StackExitKind::Succeeded); Capture::Exit((ExitReason::Succeed(exit_status), output)) } @@ -923,9 +896,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Err(PrecompileFailure::Revert { exit_status, output, - cost, }) => { - let _ = self.state.metadata_mut().gasometer.record_cost(cost); let _ = self.exit_substate(StackExitKind::Reverted); Capture::Exit((ExitReason::Revert(exit_status), output)) } @@ -1212,61 +1183,33 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Prec fn call( &mut self, to: H160, - scheme: CallScheme, + transfer: Option, input: Vec, - value: U256, - target_gas: Option, - context: &PrecompileContext, + gas_limit: Option, + is_static: bool, + context: &Context, ) -> (ExitReason, Vec) { - let context = context.0.clone(); - - let context = match scheme { - CallScheme::Call | CallScheme::StaticCall => Context { - address: to.into(), - caller: context.address, - apparent_value: value, - }, - CallScheme::CallCode => Context { - address: context.address, - caller: context.address, - apparent_value: value, - }, - CallScheme::DelegateCall => Context { - address: context.address, - caller: context.caller, - apparent_value: context.apparent_value, - }, - }; - - let transfer = if scheme == CallScheme::Call { - Some(Transfer { - source: context.address, - target: to.into(), - value, - }) - } else if scheme == CallScheme::CallCode { - Some(Transfer { - source: context.address, - target: context.address, - value, - }) - } else { - None - }; - match Handler::call( self, to.into(), transfer, input, - target_gas, - scheme == CallScheme::StaticCall, - context, + gas_limit, + is_static, + context.clone(), ) { Capture::Exit((s, v)) => emit_exit!(s, v), Capture::Trap(_) => unreachable!(), } } + + fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> { + self.state.metadata_mut().gasometer.record_cost(cost) + } + + fn log(&mut self, address: H160, topics: Vec, data: Vec) { + let _ = Handler::log(self, address, topics, data); + } } pub struct ExemplePrecompileSet(core::marker::PhantomData); @@ -1278,16 +1221,16 @@ impl PrecompileSet for ExemplePrecompileSet { _address: H160, _input: &[u8], gas_limit: Option, - context: &PrecompileContext, + context: &Context, _is_static: bool, ) -> Option { // Subcall let (_status, _data) = handle.call( - H160::repeat_byte(0x11), // exemple - CallScheme::Call, + H160::repeat_byte(0x11), // exemple, + None, b"Test".to_vec(), - U256::zero(), gas_limit, + false, context, ); From 60f1566129ed54a6704ca88a665797f424d68dfd Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Wed, 4 May 2022 09:31:10 +0000 Subject: [PATCH 04/17] fixes --- src/executor/stack/executor.rs | 15 +++++++++++---- src/executor/stack/mod.rs | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index 89bec336..e08c9b75 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -233,6 +233,12 @@ pub enum PrecompileFailure { Fatal { exit_status: ExitFatal }, } +impl From for PrecompileFailure { + fn from(error: ExitError) -> PrecompileFailure { + PrecompileFailure::Error { exit_status: error } + } +} + /// Handle provided to a precompile to interact with the EVM. pub trait PrecompileHandle { /// Perform subcall in provided context. @@ -296,16 +302,17 @@ impl PrecompileSet for () { } /// Precompiles function signature. Expected input arguments are: +/// * Handle /// * Input /// * Gas limit /// * Context /// * Is static -pub type PrecompileFn = fn(&[u8], Option, &Context, bool) -> PrecompileResult; +pub type PrecompileFn = fn(&mut H, &[u8], Option, &Context, bool) -> PrecompileResult; -impl PrecompileSet for BTreeMap { +impl PrecompileSet for BTreeMap> { fn execute( &self, - _handle: &mut H, + handle: &mut H, address: H160, input: &[u8], gas_limit: Option, @@ -313,7 +320,7 @@ impl PrecompileSet for BTreeMap { is_static: bool, ) -> Option { self.get(&address) - .map(|precompile| (*precompile)(input, gas_limit, context, is_static)) + .map(|precompile| (*precompile)(handle, input, gas_limit, context, is_static)) } /// Check if the given address is a precompile. Should only be called to diff --git a/src/executor/stack/mod.rs b/src/executor/stack/mod.rs index f7b40efe..02e4351c 100644 --- a/src/executor/stack/mod.rs +++ b/src/executor/stack/mod.rs @@ -7,7 +7,7 @@ mod memory; pub use self::executor::{ Accessed, PrecompileFailure, PrecompileFn, PrecompileOutput, PrecompileSet, StackExecutor, - StackExitKind, StackState, StackSubstateMetadata, + StackExitKind, StackState, StackSubstateMetadata, PrecompileHandle, }; pub use self::memory::{MemoryStackAccount, MemoryStackState, MemoryStackSubstate}; From 767ac85aad2629d98e3c58ef721038a0a2640a1a Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Wed, 4 May 2022 12:34:20 +0000 Subject: [PATCH 05/17] move type param from trait to function --- src/executor/stack/executor.rs | 38 ++++++++++++++++++++-------------- src/executor/stack/mod.rs | 4 ++-- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index e08c9b75..2ac2673c 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -264,10 +264,10 @@ pub type PrecompileResult = Result; /// A set of precompiles. /// Checks of the provided address being in the precompile set should be /// as cheap as possible since it may be called often. -pub trait PrecompileSet { +pub trait PrecompileSet { /// Tries to execute a precompile in the precompile set. /// If the provided address is not a precompile, returns None. - fn execute( + fn execute( &self, handle: &mut H, address: H160, @@ -283,8 +283,8 @@ pub trait PrecompileSet { fn is_precompile(&self, address: H160) -> bool; } -impl PrecompileSet for () { - fn execute( +impl PrecompileSet for () { + fn execute( &self, _: &mut H, _: H160, @@ -307,10 +307,11 @@ impl PrecompileSet for () { /// * Gas limit /// * Context /// * Is static -pub type PrecompileFn = fn(&mut H, &[u8], Option, &Context, bool) -> PrecompileResult; +pub type PrecompileFn = + fn(&[u8], Option, &Context, bool) -> Result<(PrecompileOutput, u64), PrecompileFailure>; -impl PrecompileSet for BTreeMap> { - fn execute( +impl PrecompileSet for BTreeMap { + fn execute( &self, handle: &mut H, address: H160, @@ -319,8 +320,15 @@ impl PrecompileSet for BTreeMap> { context: &Context, is_static: bool, ) -> Option { - self.get(&address) - .map(|precompile| (*precompile)(handle, input, gas_limit, context, is_static)) + self.get(&address).map(|precompile| { + match (*precompile)(input, gas_limit, context, is_static) { + Ok((output, cost)) => { + handle.record_cost(cost)?; + Ok(output) + } + Err(err) => Err(err), + } + }) } /// Check if the given address is a precompile. Should only be called to @@ -338,7 +346,7 @@ pub struct StackExecutor<'config, 'precompiles, S, P> { precompile_set: &'precompiles P, } -impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> +impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> StackExecutor<'config, 'precompiles, S, P> { /// Return a reference of the Config. @@ -942,7 +950,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> } } -impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler +impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler for StackExecutor<'config, 'precompiles, S, P> { type CreateInterrupt = Infallible; @@ -1184,7 +1192,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Hand } } -impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> PrecompileHandle +impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> PrecompileHandle for StackExecutor<'config, 'precompiles, S, P> { fn call( @@ -1219,10 +1227,10 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Prec } } -pub struct ExemplePrecompileSet(core::marker::PhantomData); +pub struct ExemplePrecompileSet; -impl PrecompileSet for ExemplePrecompileSet { - fn execute( +impl PrecompileSet for ExemplePrecompileSet { + fn execute( &self, handle: &mut H, _address: H160, diff --git a/src/executor/stack/mod.rs b/src/executor/stack/mod.rs index 02e4351c..5bb448a7 100644 --- a/src/executor/stack/mod.rs +++ b/src/executor/stack/mod.rs @@ -6,8 +6,8 @@ mod executor; mod memory; pub use self::executor::{ - Accessed, PrecompileFailure, PrecompileFn, PrecompileOutput, PrecompileSet, StackExecutor, - StackExitKind, StackState, StackSubstateMetadata, PrecompileHandle, + Accessed, PrecompileFailure, PrecompileFn, PrecompileHandle, PrecompileOutput, PrecompileSet, + StackExecutor, StackExitKind, StackState, StackSubstateMetadata, }; pub use self::memory::{MemoryStackAccount, MemoryStackState, MemoryStackSubstate}; From 8abb44d2427a6751039f0321a094056a17ff570a Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Wed, 4 May 2022 13:08:44 +0000 Subject: [PATCH 06/17] handle.remaining_gas --- src/executor/stack/executor.rs | 35 ++++------------------------------ 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index 2ac2673c..40c02c83 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -255,6 +255,8 @@ pub trait PrecompileHandle { fn record_cost(&mut self, cost: u64) -> Result<(), ExitError>; + fn remaining_gas(&self) -> u64; + fn log(&mut self, address: H160, topics: Vec, data: Vec); } @@ -1225,37 +1227,8 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Precompile fn log(&mut self, address: H160, topics: Vec, data: Vec) { let _ = Handler::log(self, address, topics, data); } -} - -pub struct ExemplePrecompileSet; - -impl PrecompileSet for ExemplePrecompileSet { - fn execute( - &self, - handle: &mut H, - _address: H160, - _input: &[u8], - gas_limit: Option, - context: &Context, - _is_static: bool, - ) -> Option { - // Subcall - let (_status, _data) = handle.call( - H160::repeat_byte(0x11), // exemple, - None, - b"Test".to_vec(), - gas_limit, - false, - context, - ); - - todo!("do stuff with the result") - } - /// Check if the given address is a precompile. Should only be called to - /// perform the check while not executing the precompile afterward, since - /// `execute` already performs a check internally. - fn is_precompile(&self, _address: H160) -> bool { - todo!() + fn remaining_gas(&self) -> u64 { + self.state.metadata().gasometer.gas() } } From 53492572ffcab16fa2acf90f2f8fa3cfb617dd15 Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Wed, 4 May 2022 13:14:18 +0000 Subject: [PATCH 07/17] update doc --- src/executor/stack/executor.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index 40c02c83..a13f35a7 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -304,11 +304,12 @@ impl PrecompileSet for () { } /// Precompiles function signature. Expected input arguments are: -/// * Handle /// * Input /// * Gas limit /// * Context /// * Is static +/// +/// In case of success returns the output and the cost. pub type PrecompileFn = fn(&[u8], Option, &Context, bool) -> Result<(PrecompileOutput, u64), PrecompileFailure>; From baf3b9b42ad99ba980e08e7ecc6879d2bda4c03a Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Wed, 4 May 2022 13:58:27 +0000 Subject: [PATCH 08/17] private struct to prevent owners of StackExecutor to call functions outside of precompile context --- src/executor/stack/executor.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index a13f35a7..a398d3af 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -892,7 +892,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> } if let Some(result) = self.precompile_set.execute( - self, + &mut StackExecutorHandle(self), code_address, &input, Some(gas_limit), @@ -1195,8 +1195,12 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler } } -impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> PrecompileHandle - for StackExecutor<'config, 'precompiles, S, P> +struct StackExecutorHandle<'inner, 'config, 'precompiles, S, P>( + &'inner mut StackExecutor<'config, 'precompiles, S, P>, +); + +impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> PrecompileHandle + for StackExecutorHandle<'inner, 'config, 'precompiles, S, P> { fn call( &mut self, @@ -1208,7 +1212,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Precompile context: &Context, ) -> (ExitReason, Vec) { match Handler::call( - self, + self.0, to.into(), transfer, input, @@ -1222,14 +1226,14 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Precompile } fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> { - self.state.metadata_mut().gasometer.record_cost(cost) + self.0.state.metadata_mut().gasometer.record_cost(cost) } fn log(&mut self, address: H160, topics: Vec, data: Vec) { - let _ = Handler::log(self, address, topics, data); + let _ = Handler::log(self.0, address, topics, data); } - fn remaining_gas(&self) -> u64 { - self.state.metadata().gasometer.gas() + fn remaining_gas(&self) -> u64 { + self.0.state.metadata().gasometer.gas() } } From de95e2c1b07e804d016ccfad196dd939728867a6 Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Wed, 4 May 2022 16:37:12 +0000 Subject: [PATCH 09/17] impl Trait syntax --- src/executor/stack/executor.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index a398d3af..be2fb4be 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -269,9 +269,9 @@ pub type PrecompileResult = Result; pub trait PrecompileSet { /// Tries to execute a precompile in the precompile set. /// If the provided address is not a precompile, returns None. - fn execute( + fn execute( &self, - handle: &mut H, + handle: &mut impl PrecompileHandle, address: H160, input: &[u8], gas_limit: Option, @@ -286,9 +286,9 @@ pub trait PrecompileSet { } impl PrecompileSet for () { - fn execute( + fn execute( &self, - _: &mut H, + _: &mut impl PrecompileHandle, _: H160, _: &[u8], _: Option, @@ -314,9 +314,9 @@ pub type PrecompileFn = fn(&[u8], Option, &Context, bool) -> Result<(PrecompileOutput, u64), PrecompileFailure>; impl PrecompileSet for BTreeMap { - fn execute( + fn execute( &self, - handle: &mut H, + handle: &mut impl PrecompileHandle, address: H160, input: &[u8], gas_limit: Option, From 2b4e6f35afb7f6526bce2b89df8ce7c5b66a5a0d Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Tue, 10 May 2022 13:23:26 +0000 Subject: [PATCH 10/17] clippy --- src/executor/stack/executor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index be2fb4be..49f743be 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -1213,7 +1213,7 @@ impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Pr ) -> (ExitReason, Vec) { match Handler::call( self.0, - to.into(), + to, transfer, input, gas_limit, From 5096ac3798d63b64a3b2dedf4df391dce2104600 Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Thu, 12 May 2022 12:47:58 +0000 Subject: [PATCH 11/17] record precompile subcall intrinsic cost --- src/executor/stack/executor.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index 49f743be..fed65c98 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -1211,6 +1211,34 @@ impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Pr is_static: bool, context: &Context, ) -> (ExitReason, Vec) { + // For normal calls the cost is recorded at opcode level. + // Since we don't go throught opcodes we need manually record the call + // cost. Not doing so will make the code panic as recording the call stipend + // will do an underflow. + let gas_cost = crate::gasometer::GasCost::Call { + value: transfer.clone().map(|x| x.value).unwrap_or(U256::zero()), + gas: U256::from(gas_limit.unwrap_or(u64::MAX)), + target_is_cold: self.0.is_cold(to, None), + target_exists: self.0.exists(to), + }; + + // We're not reading from EVM memory, so we record the minimum MemoryCost. + let memory_cost = Some(crate::gasometer::MemoryCost { + offset: U256::zero(), + len: U256::zero(), + }); + + if let Err(error) = self + .0 + .state + .metadata_mut() + .gasometer + .record_dynamic_cost(gas_cost, memory_cost) + { + return (ExitReason::Error(error), vec![]); + } + + // Perform the subcall match Handler::call( self.0, to, From e1739440f15504d937e71555ddecbb09a310a6f9 Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Thu, 12 May 2022 13:09:11 +0000 Subject: [PATCH 12/17] clippy --- src/executor/stack/executor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index fed65c98..c9cbadd7 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -1216,7 +1216,7 @@ impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Pr // cost. Not doing so will make the code panic as recording the call stipend // will do an underflow. let gas_cost = crate::gasometer::GasCost::Call { - value: transfer.clone().map(|x| x.value).unwrap_or(U256::zero()), + value: transfer.clone().map(|x| x.value).unwrap_or_else(U256::zero), gas: U256::from(gas_limit.unwrap_or(u64::MAX)), target_is_cold: self.0.is_cold(to, None), target_exists: self.0.exists(to), From 69a4465e32ace876c2760c5c894828acf252d3c1 Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Thu, 12 May 2022 14:02:12 +0000 Subject: [PATCH 13/17] PrecompileSubcall event --- src/executor/stack/executor.rs | 9 +++++++++ src/tracing.rs | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index c9cbadd7..390625bd 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -1238,6 +1238,15 @@ impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Pr return (ExitReason::Error(error), vec![]); } + event!(PrecompileSubcall { + to: to.clone(), + transfer: &transfer, + input: &input, + target_gas: gas_limit, + is_static, + context + }); + // Perform the subcall match Handler::call( self.0, diff --git a/src/tracing.rs b/src/tracing.rs index 8c45e963..55c4a2da 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -59,6 +59,14 @@ pub enum Event<'a> { gas_limit: u64, address: H160, }, + PrecompileSubcall { + to: H160, + transfer: &'a Option, + input: &'a [u8], + target_gas: Option, + is_static: bool, + context: &'a Context, + }, } // Expose `listener::with` to the crate only. From 3de14be22b00af4a221523959156d17bbb2bcb72 Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Thu, 12 May 2022 14:08:48 +0000 Subject: [PATCH 14/17] naming + no_std fix --- src/executor/stack/executor.rs | 12 ++++++------ src/tracing.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index 390625bd..1bb8f700 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -1204,7 +1204,7 @@ impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Pr { fn call( &mut self, - to: H160, + code_address: H160, transfer: Option, input: Vec, gas_limit: Option, @@ -1218,8 +1218,8 @@ impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Pr let gas_cost = crate::gasometer::GasCost::Call { value: transfer.clone().map(|x| x.value).unwrap_or_else(U256::zero), gas: U256::from(gas_limit.unwrap_or(u64::MAX)), - target_is_cold: self.0.is_cold(to, None), - target_exists: self.0.exists(to), + target_is_cold: self.0.is_cold(code_address, None), + target_exists: self.0.exists(code_address), }; // We're not reading from EVM memory, so we record the minimum MemoryCost. @@ -1235,11 +1235,11 @@ impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Pr .gasometer .record_dynamic_cost(gas_cost, memory_cost) { - return (ExitReason::Error(error), vec![]); + return (ExitReason::Error(error), Vec::new()); } event!(PrecompileSubcall { - to: to.clone(), + code_address: code_address.clone(), transfer: &transfer, input: &input, target_gas: gas_limit, @@ -1250,7 +1250,7 @@ impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Pr // Perform the subcall match Handler::call( self.0, - to, + code_address, transfer, input, gas_limit, diff --git a/src/tracing.rs b/src/tracing.rs index 55c4a2da..686ef4dc 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -60,7 +60,7 @@ pub enum Event<'a> { address: H160, }, PrecompileSubcall { - to: H160, + code_address: H160, transfer: &'a Option, input: &'a [u8], target_gas: Option, From 7a617728dddb37910ee42b0d3c08d63f584af772 Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Thu, 12 May 2022 17:55:22 +0000 Subject: [PATCH 15/17] remove duplicated exit event --- src/executor/stack/executor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index 1bb8f700..39592a8d 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -1257,7 +1257,7 @@ impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Pr is_static, context.clone(), ) { - Capture::Exit((s, v)) => emit_exit!(s, v), + Capture::Exit((s, v)) => (s, v), Capture::Trap(_) => unreachable!(), } } From c303f20cf119a35d3a5b0cfb2bce9c2e73773b6e Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Mon, 16 May 2022 09:40:45 +0000 Subject: [PATCH 16/17] suggested changes --- src/executor/stack/executor.rs | 136 +++++++++++++++++++++------------ 1 file changed, 88 insertions(+), 48 deletions(-) diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index 39592a8d..bacd7c1b 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -253,11 +253,29 @@ pub trait PrecompileHandle { context: &Context, ) -> (ExitReason, Vec); + /// Record cost to the Runtime gasometer. fn record_cost(&mut self, cost: u64) -> Result<(), ExitError>; + /// Retreive the remaining gas. fn remaining_gas(&self) -> u64; - fn log(&mut self, address: H160, topics: Vec, data: Vec); + /// Record a log. + fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError>; + + /// Retreive the code address (what is the address of the precompile being called). + fn code_address(&self) -> H160; + + /// Retreive the input data the precompile is called with. + fn input(&self) -> &[u8]; + + /// Retreive the context in which the precompile is executed. + fn context(&self) -> &Context; + + /// Is the precompile call is done statically. + fn is_static(&self) -> bool; + + /// Retreive the gas limit of this call. + fn gas_limit(&self) -> Option; } /// A precompile result. @@ -269,15 +287,7 @@ pub type PrecompileResult = Result; pub trait PrecompileSet { /// Tries to execute a precompile in the precompile set. /// If the provided address is not a precompile, returns None. - fn execute( - &self, - handle: &mut impl PrecompileHandle, - address: H160, - input: &[u8], - gas_limit: Option, - context: &Context, - is_static: bool, - ) -> Option; + fn execute(&self, handle: &mut impl PrecompileHandle) -> Option; /// Check if the given address is a precompile. Should only be called to /// perform the check while not executing the precompile afterward, since @@ -286,15 +296,7 @@ pub trait PrecompileSet { } impl PrecompileSet for () { - fn execute( - &self, - _: &mut impl PrecompileHandle, - _: H160, - _: &[u8], - _: Option, - _: &Context, - _: bool, - ) -> Option { + fn execute(&self, _: &mut impl PrecompileHandle) -> Option { None } @@ -314,16 +316,15 @@ pub type PrecompileFn = fn(&[u8], Option, &Context, bool) -> Result<(PrecompileOutput, u64), PrecompileFailure>; impl PrecompileSet for BTreeMap { - fn execute( - &self, - handle: &mut impl PrecompileHandle, - address: H160, - input: &[u8], - gas_limit: Option, - context: &Context, - is_static: bool, - ) -> Option { + fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { + let address = handle.code_address(); + self.get(&address).map(|precompile| { + let input = handle.input(); + let gas_limit = handle.gas_limit(); + let context = handle.context(); + let is_static = handle.is_static(); + match (*precompile)(input, gas_limit, context, is_static) { Ok((output, cost)) => { handle.record_cost(cost)?; @@ -891,14 +892,14 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> } } - if let Some(result) = self.precompile_set.execute( - &mut StackExecutorHandle(self), + if let Some(result) = self.precompile_set.execute(&mut StackExecutorHandle { + executor: self, code_address, - &input, - Some(gas_limit), - &context, + input: &input, + gas_limit: Some(gas_limit), + context: &context, is_static, - ) { + }) { return match result { Ok(PrecompileOutput { exit_status, @@ -1195,13 +1196,20 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler } } -struct StackExecutorHandle<'inner, 'config, 'precompiles, S, P>( - &'inner mut StackExecutor<'config, 'precompiles, S, P>, -); +struct StackExecutorHandle<'inner, 'config, 'precompiles, S, P> { + executor: &'inner mut StackExecutor<'config, 'precompiles, S, P>, + code_address: H160, + input: &'inner [u8], + gas_limit: Option, + context: &'inner Context, + is_static: bool, +} impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> PrecompileHandle for StackExecutorHandle<'inner, 'config, 'precompiles, S, P> { + // Perform subcall in provided context. + /// Precompile specifies in which context the subcall is executed. fn call( &mut self, code_address: H160, @@ -1212,14 +1220,14 @@ impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Pr context: &Context, ) -> (ExitReason, Vec) { // For normal calls the cost is recorded at opcode level. - // Since we don't go throught opcodes we need manually record the call + // Since we don't go through opcodes we need manually record the call // cost. Not doing so will make the code panic as recording the call stipend // will do an underflow. let gas_cost = crate::gasometer::GasCost::Call { value: transfer.clone().map(|x| x.value).unwrap_or_else(U256::zero), gas: U256::from(gas_limit.unwrap_or(u64::MAX)), - target_is_cold: self.0.is_cold(code_address, None), - target_exists: self.0.exists(code_address), + target_is_cold: self.executor.is_cold(code_address, None), + target_exists: self.executor.exists(code_address), }; // We're not reading from EVM memory, so we record the minimum MemoryCost. @@ -1229,7 +1237,7 @@ impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Pr }); if let Err(error) = self - .0 + .executor .state .metadata_mut() .gasometer @@ -1249,7 +1257,7 @@ impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Pr // Perform the subcall match Handler::call( - self.0, + self.executor, code_address, transfer, input, @@ -1258,19 +1266,51 @@ impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Pr context.clone(), ) { Capture::Exit((s, v)) => (s, v), - Capture::Trap(_) => unreachable!(), + Capture::Trap(_) => unreachable!("Trap is infaillible since StackExecutor is sync"), } } + /// Record cost to the Runtime gasometer. fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> { - self.0.state.metadata_mut().gasometer.record_cost(cost) + self.executor + .state + .metadata_mut() + .gasometer + .record_cost(cost) } - fn log(&mut self, address: H160, topics: Vec, data: Vec) { - let _ = Handler::log(self.0, address, topics, data); + /// Retreive the remaining gas. + fn remaining_gas(&self) -> u64 { + self.executor.state.metadata().gasometer.gas() } - fn remaining_gas(&self) -> u64 { - self.0.state.metadata().gasometer.gas() + /// Record a log. + fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError> { + Handler::log(self.executor, address, topics, data) + } + + /// Retreive the code address (what is the address of the precompile being called). + fn code_address(&self) -> H160 { + self.code_address + } + + /// Retreive the input data the precompile is called with. + fn input(&self) -> &[u8] { + self.input + } + + /// Retreive the context in which the precompile is executed. + fn context(&self) -> &Context { + self.context + } + + /// Is the precompile call is done statically. + fn is_static(&self) -> bool { + self.is_static + } + + /// Retreive the gas limit of this call. + fn gas_limit(&self) -> Option { + self.gas_limit } } From 5f91f42250e6c433415867afc5649b59d985868b Mon Sep 17 00:00:00 2001 From: nanocryk <6422796+nanocryk@users.noreply.github.com> Date: Mon, 16 May 2022 15:35:28 +0000 Subject: [PATCH 17/17] record input length --- src/executor/stack/executor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index bacd7c1b..3d175003 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -1230,10 +1230,10 @@ impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Pr target_exists: self.executor.exists(code_address), }; - // We're not reading from EVM memory, so we record the minimum MemoryCost. + // We record the length of the input. let memory_cost = Some(crate::gasometer::MemoryCost { offset: U256::zero(), - len: U256::zero(), + len: input.len().into(), }); if let Err(error) = self