diff --git a/modules/evm/src/runner/mod.rs b/modules/evm/src/runner/mod.rs index 20a22082e6..aa37d2c2e1 100644 --- a/modules/evm/src/runner/mod.rs +++ b/modules/evm/src/runner/mod.rs @@ -19,6 +19,7 @@ pub mod stack; pub mod state; pub mod storage_meter; +pub mod tagged_runtime; use crate::{BalanceOf, CallInfo, Config, CreateInfo}; use module_evm_utility::evm; diff --git a/modules/evm/src/runner/state.rs b/modules/evm/src/runner/state.rs index 08c013e04f..d193cbfa48 100644 --- a/modules/evm/src/runner/state.rs +++ b/modules/evm/src/runner/state.rs @@ -18,12 +18,16 @@ // Synchronize with https://github.com/rust-blockchain/evm/blob/6534c1dd/src/executor/stack/executor.rs -use crate::{encode_revert_message, StorageMeter}; +use crate::{ + encode_revert_message, + runner::tagged_runtime::{RuntimeKind, TaggedRuntime}, + StorageMeter, +}; use core::{cmp::min, convert::Infallible}; use module_evm_utility::{ evm::{ - backend::Backend, Capture, Config, Context, CreateScheme, ExitError, ExitFatal, ExitReason, ExitRevert, - ExitSucceed, Opcode, Runtime, Stack, Transfer, + backend::Backend, maybe_borrowed::MaybeBorrowed, Capture, Config, Context, CreateScheme, ExitError, ExitFatal, + ExitReason, ExitRevert, ExitSucceed, Opcode, Resolve, Runtime, Stack, Transfer, }, evm_gasometer::{self as gasometer, Gasometer, StorageTarget}, evm_runtime::Handler, @@ -98,6 +102,8 @@ macro_rules! emit_exit { }}; } +const DEFAULT_CALL_STACK_CAPACITY: usize = 4; + pub enum StackExitKind { Succeeded, Reverted, @@ -531,10 +537,84 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> StackExecu } /// Execute the runtime until it returns. - pub fn execute(&mut self, runtime: &mut Runtime) -> ExitReason { - match runtime.run(self) { - Capture::Exit(s) => s, - Capture::Trap(_) => unreachable!("Trap is Infallible"), + pub fn execute(&mut self, runtime: &mut Runtime<'config>) -> ExitReason { + let mut call_stack = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY); + call_stack.push(TaggedRuntime { + kind: RuntimeKind::Execute, + inner: MaybeBorrowed::Borrowed(runtime), + }); + let (reason, _, _) = self.execute_with_call_stack(&mut call_stack); + reason + } + + /// Execute using Runtimes on the call_stack until it returns. + fn execute_with_call_stack<'borrow>( + &mut self, + call_stack: &mut Vec>, + ) -> (ExitReason, Option, Vec) { + // This `interrupt_runtime` is used to pass the runtime obtained from the + // `Capture::Trap` branch in the match below back to the top of the call stack. + // The reason we can't simply `push` the runtime directly onto the stack in the + // `Capture::Trap` branch is because the borrow-checker complains that the stack + // is already borrowed as long as we hold a pointer on the last element + // (i.e. the currently executing runtime). + let mut interrupt_runtime = None; + loop { + if let Some(rt) = interrupt_runtime.take() { + call_stack.push(rt); + } + let runtime = match call_stack.last_mut() { + Some(runtime) => runtime, + None => { + return (ExitReason::Fatal(ExitFatal::UnhandledInterrupt), None, Vec::new()); + } + }; + let reason = { + let inner_runtime = &mut runtime.inner; + match inner_runtime.run(self) { + Capture::Exit(reason) => reason, + Capture::Trap(Resolve::Call(rt, _)) => { + interrupt_runtime = Some(rt.0); + continue; + } + Capture::Trap(Resolve::Create(rt, _)) => { + interrupt_runtime = Some(rt.0); + continue; + } + } + }; + let runtime_kind = runtime.kind; + let (reason, maybe_address, return_data) = match runtime_kind { + RuntimeKind::Create(created_address) => { + let (reason, maybe_address, return_data) = + self.cleanup_for_create(created_address, reason, runtime.inner.machine().return_value()); + (reason, maybe_address, return_data) + } + RuntimeKind::Call(code_address) => { + let return_data = + self.cleanup_for_call(code_address, &reason, runtime.inner.machine().return_value()); + (reason, None, return_data) + } + RuntimeKind::Execute => (reason, None, runtime.inner.machine().return_value()), + }; + // We're done with that runtime now, so can pop it off the call stack + call_stack.pop(); + // Now pass the results from that runtime on to the next one in the stack + let runtime = match call_stack.last_mut() { + Some(r) => r, + None => return (reason, None, return_data), + }; + emit_exit!(&reason, &return_data); + let inner_runtime = &mut runtime.inner; + let maybe_error = match runtime_kind { + RuntimeKind::Create(_) => inner_runtime.finish_create(reason, maybe_address, return_data), + RuntimeKind::Call(_) => inner_runtime.finish_call(reason, return_data), + RuntimeKind::Execute => inner_runtime.finish_call(reason, return_data), + }; + // Early exit if passing on the result caused an error + if let Err(e) = maybe_error { + return (e, None, Vec::new()); + } } } @@ -584,7 +664,12 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> StackExecu false, ) { Capture::Exit((s, _, v)) => emit_exit!(s, v), - Capture::Trap(_) => unreachable!(), + Capture::Trap(rt) => { + let mut cs = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY); + cs.push(rt.0); + let (s, _, v) = self.execute_with_call_stack(&mut cs); + emit_exit!(s, v) + } } } @@ -630,7 +715,12 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> StackExecu false, ) { Capture::Exit((s, _, v)) => emit_exit!(s, v), - Capture::Trap(_) => unreachable!(), + Capture::Trap(rt) => { + let mut cs = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY); + cs.push(rt.0); + let (s, _, v) = self.execute_with_call_stack(&mut cs); + emit_exit!(s, v) + } } } @@ -666,7 +756,12 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> StackExecu false, ) { Capture::Exit((s, _, v)) => emit_exit!(s, v), - Capture::Trap(_) => unreachable!(), + Capture::Trap(rt) => { + let mut cs = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY); + cs.push(rt.0); + let (s, _, v) = self.execute_with_call_stack(&mut cs); + emit_exit!(s, v) + } } } @@ -733,7 +828,12 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> StackExecu context, ) { Capture::Exit((s, v)) => emit_exit!(s, v), - Capture::Trap(_) => unreachable!(), + Capture::Trap(rt) => { + let mut cs = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY); + cs.push(rt.0); + let (s, _, v) = self.execute_with_call_stack(&mut cs); + emit_exit!(s, v) + } } } @@ -812,7 +912,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> StackExecu init_code: Vec, target_gas: Option, take_l64: bool, - ) -> Capture<(ExitReason, Option, Vec), Infallible> { + ) -> Capture<(ExitReason, Option, Vec), StackExecutorCreateInterrupt<'config>> { macro_rules! try_or_fail { ( $e:expr ) => { match $e { @@ -822,13 +922,6 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> StackExecu }; } - fn check_first_byte(config: &Config, code: &[u8]) -> Result<(), ExitError> { - if config.disallow_executable_format && Some(&Opcode::EOFMAGIC.as_u8()) == code.first() { - return Err(ExitError::InvalidCode(Opcode::EOFMAGIC)); - } - Ok(()) - } - fn l64(gas: u64) -> u64 { gas - gas / 64 } @@ -925,58 +1018,12 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> StackExecu self.state.inc_nonce(address); } - let mut runtime = Runtime::new(Rc::new(init_code), Rc::new(Vec::new()), context, self.config); - - let reason = self.execute(&mut runtime); - log::debug!(target: "evm", "Create execution using address {}: {:?}", address, reason); - - match reason { - ExitReason::Succeed(s) => { - let out = runtime.machine().return_value(); + let runtime = Runtime::new(Rc::new(init_code), Rc::new(Vec::new()), context, self.config); - // As of EIP-3541 code starting with 0xef cannot be deployed - if let Err(e) = check_first_byte(self.config, &out) { - self.state.metadata_mut().gasometer.fail(); - let _ = self.exit_substate(StackExitKind::Failed); - return Capture::Exit((e.into(), None, Vec::new())); - } - - if let Some(limit) = self.config.create_contract_limit { - if out.len() > limit { - self.state.metadata_mut().gasometer.fail(); - let _ = self.exit_substate(StackExitKind::Failed); - return Capture::Exit((ExitError::CreateContractLimit.into(), None, Vec::new())); - } - } - - match self.state.metadata_mut().gasometer.record_deposit(out.len()) { - Ok(()) => { - self.state.set_code(address, out); - let e = self.exit_substate(StackExitKind::Succeeded); - try_or_fail!(e); - Capture::Exit((ExitReason::Succeed(s), Some(address), Vec::new())) - } - Err(e) => { - let _ = self.exit_substate(StackExitKind::Failed); - Capture::Exit((ExitReason::Error(e), None, Vec::new())) - } - } - } - ExitReason::Error(e) => { - self.state.metadata_mut().gasometer.fail(); - let _ = self.exit_substate(StackExitKind::Failed); - Capture::Exit((ExitReason::Error(e), None, Vec::new())) - } - ExitReason::Revert(e) => { - let _ = self.exit_substate(StackExitKind::Reverted); - Capture::Exit((ExitReason::Revert(e), None, runtime.machine().return_value())) - } - ExitReason::Fatal(e) => { - self.state.metadata_mut().gasometer.fail(); - let _ = self.exit_substate(StackExitKind::Failed); - Capture::Exit((ExitReason::Fatal(e), None, Vec::new())) - } - } + Capture::Trap(StackExecutorCreateInterrupt(TaggedRuntime { + kind: RuntimeKind::Create(address), + inner: MaybeBorrowed::Owned(runtime), + })) } #[allow(clippy::too_many_arguments)] @@ -990,7 +1037,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> StackExecu take_l64: bool, take_stipend: bool, context: Context, - ) -> Capture<(ExitReason, Vec), Infallible> { + ) -> Capture<(ExitReason, Vec), StackExecutorCallInterrupt<'config>> { macro_rules! try_or_fail { ( $e:expr ) => { match $e { @@ -1098,48 +1145,113 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> StackExecu }; } - let mut runtime = Runtime::new(Rc::new(code), Rc::new(input), context, self.config); + let runtime = Runtime::new(Rc::new(code), Rc::new(input), context, self.config); - #[cfg(not(feature = "tracing"))] - let reason = self.execute(&mut runtime); - #[cfg(feature = "tracing")] - //let reason = module_evm_utility::evm::tracing::using(&mut Tracer, || self.execute(&mut runtime)); - let reason = module_evm_utility::evm_runtime::tracing::using(&mut Tracer, || self.execute(&mut runtime)); - //let reason = module_evm_utility::evm_gasometer::tracing::using(&mut Tracer, || self.execute(&mut - // runtime)); + Capture::Trap(StackExecutorCallInterrupt(TaggedRuntime { + kind: RuntimeKind::Call(code_address), + inner: MaybeBorrowed::Owned(runtime), + })) + } - log::debug!(target: "evm", "Call execution using address {}: {:?}", code_address, reason); + fn cleanup_for_create( + &mut self, + created_address: H160, + reason: ExitReason, + return_data: Vec, + ) -> (ExitReason, Option, Vec) { + fn check_first_byte(config: &Config, code: &[u8]) -> Result<(), ExitError> { + if config.disallow_executable_format && Some(&Opcode::EOFMAGIC.as_u8()) == code.first() { + return Err(ExitError::InvalidCode(Opcode::EOFMAGIC)); + } + Ok(()) + } + + log::debug!(target: "evm", "Create execution using address {}: {:?}", created_address, reason); match reason { ExitReason::Succeed(s) => { - // let _ = self.exit_substate(StackExitKind::Succeeded); - let e = self.exit_substate(StackExitKind::Succeeded); - try_or_fail!(e); - Capture::Exit((ExitReason::Succeed(s), runtime.machine().return_value())) + let out = return_data; + let address = created_address; + // As of EIP-3541 code starting with 0xef cannot be deployed + if let Err(e) = check_first_byte(self.config, &out) { + self.state.metadata_mut().gasometer.fail(); + let _ = self.exit_substate(StackExitKind::Failed); + return (e.into(), None, Vec::new()); + } + + if let Some(limit) = self.config.create_contract_limit { + if out.len() > limit { + self.state.metadata_mut().gasometer.fail(); + let _ = self.exit_substate(StackExitKind::Failed); + return (ExitError::CreateContractLimit.into(), None, Vec::new()); + } + } + + match self.state.metadata_mut().gasometer.record_deposit(out.len()) { + Ok(()) => { + self.state.set_code(address, out); + let exit_result = self.exit_substate(StackExitKind::Succeeded); + if let Err(e) = exit_result { + return (e.into(), None, Vec::new()); + } + (ExitReason::Succeed(s), Some(address), Vec::new()) + } + Err(e) => { + let _ = self.exit_substate(StackExitKind::Failed); + (ExitReason::Error(e), None, Vec::new()) + } + } } ExitReason::Error(e) => { + self.state.metadata_mut().gasometer.fail(); let _ = self.exit_substate(StackExitKind::Failed); - Capture::Exit((ExitReason::Error(e), Vec::new())) + (ExitReason::Error(e), None, Vec::new()) } ExitReason::Revert(e) => { let _ = self.exit_substate(StackExitKind::Reverted); - Capture::Exit((ExitReason::Revert(e), runtime.machine().return_value())) + (ExitReason::Revert(e), None, return_data) } ExitReason::Fatal(e) => { self.state.metadata_mut().gasometer.fail(); let _ = self.exit_substate(StackExitKind::Failed); - Capture::Exit((ExitReason::Fatal(e), Vec::new())) + (ExitReason::Fatal(e), None, Vec::new()) + } + } + } + + fn cleanup_for_call(&mut self, code_address: H160, reason: &ExitReason, return_data: Vec) -> Vec { + log::debug!(target: "evm", "Call execution using address {}: {:?}", code_address, reason); + match reason { + ExitReason::Succeed(_) => { + let _ = self.exit_substate(StackExitKind::Succeeded); + return_data + } + ExitReason::Error(_) => { + let _ = self.exit_substate(StackExitKind::Failed); + Vec::new() + } + ExitReason::Revert(_) => { + let _ = self.exit_substate(StackExitKind::Reverted); + return_data + } + ExitReason::Fatal(_) => { + self.state.metadata_mut().gasometer.fail(); + let _ = self.exit_substate(StackExitKind::Failed); + Vec::new() } } } } +pub struct StackExecutorCallInterrupt<'config>(TaggedRuntime<'config, 'config>); +pub struct StackExecutorCreateInterrupt<'config>(TaggedRuntime<'config, 'config>); + impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler for StackExecutor<'config, 'precompiles, S, P> { - type CreateInterrupt = Infallible; + type CreateInterrupt = StackExecutorCreateInterrupt<'config>; type CreateFeedback = Infallible; - type CallInterrupt = Infallible; + type CallInterrupt = StackExecutorCallInterrupt<'config>; type CallFeedback = Infallible; fn balance(&self, address: H160) -> U256 { @@ -1437,7 +1549,17 @@ impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Pr context.clone(), ) { Capture::Exit((s, v)) => (s, v), - Capture::Trap(_) => unreachable!("Trap is infaillible since StackExecutor is sync"), + Capture::Trap(rt) => { + // Ideally this would pass the interrupt back to the executor so it could be + // handled like any other call, however the type signature of this function does + // not allow it. For now we'll make a recursive call instead of making a breaking + // change to the precompile API. But this means a custom precompile could still + // potentially cause a stack overflow if you're not careful. + let mut call_stack = Vec::with_capacity(DEFAULT_CALL_STACK_CAPACITY); + call_stack.push(rt.0); + let (reason, _, return_data) = self.executor.execute_with_call_stack(&mut call_stack); + emit_exit!(reason, return_data) + } } } diff --git a/modules/evm/src/runner/tagged_runtime.rs b/modules/evm/src/runner/tagged_runtime.rs new file mode 100644 index 0000000000..16da6ad9e4 --- /dev/null +++ b/modules/evm/src/runner/tagged_runtime.rs @@ -0,0 +1,33 @@ +// This file is part of Acala. + +// Copyright (C) 2020-2023 Acala Foundation. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use module_evm_utility::evm::{maybe_borrowed::MaybeBorrowed, Runtime}; +use sp_core::H160; + +pub struct TaggedRuntime<'config, 'borrow> { + pub kind: RuntimeKind, + pub inner: MaybeBorrowed<'borrow, Runtime<'config>>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RuntimeKind { + Create(H160), + Call(H160), + /// Special variant used only in `StackExecutor::execute` + Execute, +}