Skip to content

Add missing code to tracer extension #377

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions runtime/extensions/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ evm-core = { version = "0.26.0", default-features = false, features = ["with-cod
sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "rococo-v1" }
sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "rococo-v1" }
sp-io = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "rococo-v1" }
frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "rococo-v1" }
sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "rococo-v1" }
moonbeam-rpc-primitives-debug = { path = "../../../primitives/rpc/debug", default-features = false }
ethereum-types = { version = "0.11.0", default-features = false }
Expand Down
30 changes: 30 additions & 0 deletions runtime/extensions/evm/src/executor/wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ use sp_std::{
cmp::min, collections::btree_map::BTreeMap, convert::Infallible, rc::Rc, vec, vec::Vec,
};

pub type PrecompileExecutable = fn(
H160,
&[u8],
Option<u64>,
&Context,
) -> Option<Result<(ExitSucceed, Vec<u8>, u64), ExitError>>;

pub struct TraceExecutorWrapper<'config, S> {
// Common parts.
pub inner: &'config mut StackExecutor<'config, S>,
Expand All @@ -46,6 +53,7 @@ pub struct TraceExecutorWrapper<'config, S> {
entries_next_index: u32,
call_type: Option<CallType>,
trace_address: Vec<u32>,
precompile: Option<PrecompileExecutable>,
}

enum ContextType {
Expand All @@ -58,6 +66,7 @@ impl<'config, S: StackStateT<'config>> TraceExecutorWrapper<'config, S> {
inner: &'config mut StackExecutor<'config, S>,
is_tracing: bool,
trace_type: TraceType,
precompile: Option<PrecompileExecutable>,
) -> TraceExecutorWrapper<'config, S> {
TraceExecutorWrapper {
inner,
Expand All @@ -68,6 +77,7 @@ impl<'config, S: StackStateT<'config>> TraceExecutorWrapper<'config, S> {
entries_next_index: 0,
call_type: None,
trace_address: vec![],
precompile,
}
}

Expand Down Expand Up @@ -532,6 +542,26 @@ impl<'config, S: StackStateT<'config>> TraceExecutorWrapper<'config, S> {
}
}
}
if let Some(precompile) = self.precompile {
if let Some(ret) = (precompile)(address, &data, Some(gas_limit), &context) {
return match ret {
Ok((s, out, cost)) => {
let _ = self
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@notlesh isn't this also what you recently fixed ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, however in our wrapper we need to do exactly what the evm is doing, even if it's wrong! Otherwise the trace won't be replicating the original transaction behaviour.

Of course once the PR is merged we can include that change in our wrapper. Once again, this can be avoided with the hook.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it looks like it should effectively do the same thing.

.inner
.state_mut()
.metadata_mut()
.gasometer_mut()
.record_cost(cost);
let _ = self.inner.exit_substate(StackExitKind::Succeeded);
Capture::Exit((ExitReason::Succeed(s), out))
}
Err(e) => {
let _ = self.inner.exit_substate(StackExitKind::Failed);
Capture::Exit((ExitReason::Error(e), Vec::new()))
}
};
}
}

let mut runtime = Runtime::new(
Rc::new(code),
Expand Down
177 changes: 111 additions & 66 deletions runtime/extensions/evm/src/runner/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,39 @@

use sp_std::{convert::Infallible, vec::Vec};

use crate::executor::wrapper::TraceExecutorWrapper;
use crate::executor::wrapper::{PrecompileExecutable, TraceExecutorWrapper};
use moonbeam_rpc_primitives_debug::single::{TraceType, TransactionTrace};

use ethereum_types::{H160, U256};
use evm::{
executor::{StackExecutor, StackState as StackStateT, StackSubstateMetadata},
Capture, Config as EvmConfig, Context, CreateScheme, Transfer,
gasometer, Capture, Config as EvmConfig, Context, CreateScheme, Transfer,
};
use frame_support::ensure;
use pallet_evm::{
runner::stack::{Runner, SubstrateStackState},
Config, ExitError, ExitReason, PrecompileSet, Vicinity,
Config, Error, ExitError, ExitReason, Module, OnChargeEVMTransaction, PrecompileSet, Vicinity,
};

pub enum TraceRunnerError<T: Config> {
EvmExitError(ExitError),
RuntimeExitError(Error<T>),
}

pub trait TraceRunner<T: Config> {
/// Handle an Executor wrapper `call`.
fn execute_call<'config, F>(
executor: &'config mut StackExecutor<'config, SubstrateStackState<'_, 'config, T>>,
trace_type: TraceType,
precompile: PrecompileExecutable,
f: F,
) -> Result<TransactionTrace, ExitError>
where
F: FnOnce(
&mut TraceExecutorWrapper<'config, SubstrateStackState<'_, 'config, T>>,
) -> Capture<(ExitReason, Vec<u8>), Infallible>;

/// Handle an Executor wrapper `create`. Used by `trace_create`.
/// Handle an Executor wrapper `create`.
fn execute_create<'config, F>(
executor: &'config mut StackExecutor<'config, SubstrateStackState<'_, 'config, T>>,
trace_type: TraceType,
Expand All @@ -51,39 +59,33 @@ pub trait TraceRunner<T: Config> {
&mut TraceExecutorWrapper<'config, SubstrateStackState<'_, 'config, T>>,
) -> Capture<(ExitReason, Option<H160>, Vec<u8>), Infallible>;

/// Context creation for `call`. Typically called by the Runtime Api.
fn trace_call(
/// Interfaces runtime api and executor wrapper.
fn trace(
source: H160,
target: H160,
target: Option<H160>,
input: Vec<u8>,
value: U256,
gas_limit: u64,
gas_price: U256,
nonce: U256,
config: &EvmConfig,
trace_type: TraceType,
) -> Result<TransactionTrace, ExitError>;

fn trace_create(
source: H160,
init: Vec<u8>,
value: U256,
gas_limit: u64,
config: &EvmConfig,
trace_type: TraceType,
) -> Result<TransactionTrace, ExitError>;
) -> Result<TransactionTrace, TraceRunnerError<T>>;
}

impl<T: Config> TraceRunner<T> for Runner<T> {
fn execute_call<'config, F>(
executor: &'config mut StackExecutor<'config, SubstrateStackState<'_, 'config, T>>,
trace_type: TraceType,
precompile: PrecompileExecutable,
f: F,
) -> Result<TransactionTrace, ExitError>
where
F: FnOnce(
&mut TraceExecutorWrapper<'config, SubstrateStackState<'_, 'config, T>>,
) -> Capture<(ExitReason, Vec<u8>), Infallible>,
{
let mut wrapper = TraceExecutorWrapper::new(executor, true, trace_type);
let mut wrapper = TraceExecutorWrapper::new(executor, true, trace_type, Some(precompile));

let execution_result = match f(&mut wrapper) {
Capture::Exit((_reason, result)) => result,
Expand Down Expand Up @@ -116,7 +118,7 @@ impl<T: Config> TraceRunner<T> for Runner<T> {
&mut TraceExecutorWrapper<'config, SubstrateStackState<'_, 'config, T>>,
) -> Capture<(ExitReason, Option<H160>, Vec<u8>), Infallible>,
{
let mut wrapper = TraceExecutorWrapper::new(executor, true, trace_type);
let mut wrapper = TraceExecutorWrapper::new(executor, true, trace_type, None);

let execution_result = match f(&mut wrapper) {
Capture::Exit((_reason, _address, result)) => result,
Expand All @@ -139,67 +141,110 @@ impl<T: Config> TraceRunner<T> for Runner<T> {
}
}

fn trace_call(
fn trace(
source: H160,
target: H160,
target: Option<H160>,
input: Vec<u8>,
value: U256,
gas_limit: u64,
gas_price: U256,
nonce: U256,
config: &EvmConfig,
trace_type: TraceType,
) -> Result<TransactionTrace, ExitError> {
) -> Result<TransactionTrace, TraceRunnerError<T>> {
let vicinity = Vicinity {
gas_price: U256::zero(),
gas_price,
origin: source,
};

let metadata = StackSubstateMetadata::new(gas_limit, &config);
let state = SubstrateStackState::new(&vicinity, metadata);

let mut executor =
StackExecutor::new_with_precompile(state, config, T::Precompiles::execute);
let context = Context {
caller: source,
address: target,
apparent_value: value,
};

Self::execute_call(&mut executor, trace_type, |executor| {
executor.trace_call(
target,
Some(Transfer {
source,
target,
value,
}),
input,
Some(gas_limit as u64),
false,
false,
false,
context,
)
})
}

fn trace_create(
source: H160,
init: Vec<u8>,
value: U256,
gas_limit: u64,
config: &EvmConfig,
trace_type: TraceType,
) -> Result<TransactionTrace, ExitError> {
let vicinity = Vicinity {
gas_price: U256::zero(),
origin: source,
let total_fee = gas_price
.checked_mul(U256::from(gas_limit))
.ok_or(TraceRunnerError::RuntimeExitError(Error::<T>::FeeOverflow))?;

let total_payment =
value
.checked_add(total_fee)
.ok_or(TraceRunnerError::RuntimeExitError(
Error::<T>::PaymentOverflow,
))?;
let source_account = Module::<T>::account_basic(&source);
ensure!(
source_account.balance >= total_payment,
TraceRunnerError::RuntimeExitError(Error::<T>::BalanceLow)
);

ensure!(
source_account.nonce == nonce,
TraceRunnerError::RuntimeExitError(Error::<T>::InvalidNonce)
);

// Deduct fee from the `source` account.
let fee = T::OnChargeTransaction::withdraw_fee(&source, total_fee)
.map_err(|e| TraceRunnerError::RuntimeExitError(e))?;

let transaction_cost = gasometer::create_transaction_cost(&input);
let _ = executor
.state_mut()
.metadata_mut()
.gasometer_mut()
.record_transaction(transaction_cost)
.map_err(|e| TraceRunnerError::EvmExitError(e))?;

let mut actual_fee: U256 = U256::default();

let res = {
if let Some(target) = target {
// Call context
let context = Context {
caller: source,
address: target,
apparent_value: value,
};
Self::execute_call(
&mut executor,
trace_type,
T::Precompiles::execute,
|executor| {
let res = executor.trace_call(
target,
Some(Transfer {
source,
target,
value,
}),
input,
Some(gas_limit as u64),
false,
false,
false,
context,
);
actual_fee = executor.inner.fee(gas_price);
res
},
)
.map_err(|e| TraceRunnerError::EvmExitError(e))?
} else {
// Create context
let scheme = CreateScheme::Legacy { caller: source };
Self::execute_create(&mut executor, trace_type, |executor| {
let res =
executor.trace_create(source, scheme, value, input, Some(gas_limit as u64));
actual_fee = executor.inner.fee(gas_price);
res
})
.map_err(|e| TraceRunnerError::EvmExitError(e))?
}
};

let metadata = StackSubstateMetadata::new(gas_limit, &config);
let state = SubstrateStackState::new(&vicinity, metadata);
let mut executor =
StackExecutor::new_with_precompile(state, config, T::Precompiles::execute);
let scheme = CreateScheme::Legacy { caller: source };
Self::execute_create(&mut executor, trace_type, |executor| {
executor.trace_create(source, scheme, value, init, Some(gas_limit as u64))
})
// Refund fees to the `source` account if deducted more before,
T::OnChargeTransaction::correct_and_deposit_fee(&source, actual_fee, fee)
.map_err(|e| TraceRunnerError::RuntimeExitError(e))?;
Ok(res)
}
}
Loading