diff --git a/core/src/error.rs b/core/src/error.rs index ee459bb45..eaf4586f7 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -109,6 +109,8 @@ pub enum ExitError { StackOverflow, /// Jump destination is invalid. InvalidJump, + /// Subroutine is invalid. + InvalidSubroutine, /// An opcode accesses memory region, but the region is invalid. InvalidRange, /// Encountered the designated invalid opcode. @@ -133,6 +135,9 @@ pub enum ExitError { /// Attempt to create an empty account (runtime, unused). CreateEmpty, + /// Unknown opcode. + UnknownOpcode, + /// Other normal errors. Other(Cow<'static, str>), } diff --git a/core/src/eval/misc.rs b/core/src/eval/misc.rs index b30d1526d..19439827f 100644 --- a/core/src/eval/misc.rs +++ b/core/src/eval/misc.rs @@ -108,7 +108,7 @@ pub fn jump(state: &mut Machine) -> Control { pop_u256!(state, dest); let dest = as_usize_or_fail!(dest, ExitError::InvalidJump); - if state.valids.is_valid(dest) { + if state.valids.is_jumpdest(dest) { Control::Jump(dest) } else { Control::Exit(ExitError::InvalidJump.into()) @@ -122,7 +122,7 @@ pub fn jumpi(state: &mut Machine) -> Control { let dest = as_usize_or_fail!(dest, ExitError::InvalidJump); if value != H256::zero() { - if state.valids.is_valid(dest) { + if state.valids.is_jumpdest(dest) { Control::Jump(dest) } else { Control::Exit(ExitError::InvalidJump.into()) @@ -132,6 +132,54 @@ pub fn jumpi(state: &mut Machine) -> Control { } } +#[inline] +pub fn beginsub(state: &mut Machine) -> Control { + if state.returns.is_some() { + Control::Exit(ExitError::InvalidSubroutine.into()) + } else { + Control::Exit(ExitError::UnknownOpcode.into()) + } +} + +#[inline] +pub fn jumpsub(state: &mut Machine, position: usize) -> Control { + if let Some(ref mut returns) = state.returns { + if position == usize::max_value() { + return Control::Exit(ExitFatal::NotSupported.into()) + } + + pop_u256!(state, dest); + let dest = as_usize_or_fail!(dest, ExitError::InvalidSubroutine); + + if state.valids.is_beginsub(dest) { + if dest == usize::max_value() { + return Control::Exit(ExitSucceed::Stopped.into()) + } + + try_or_fail!(returns.push(position + 1)); + Control::Jump(dest + 1) + } else { + Control::Exit(ExitError::InvalidSubroutine.into()) + } + } else { + Control::Exit(ExitError::UnknownOpcode.into()) + } +} + +#[inline] +pub fn returnsub(state: &mut Machine) -> Control { + if let Some(ref mut returns) = state.returns { + let value = match returns.pop() { + Ok(value) => value, + Err(e) => return Control::Exit(e.into()), + }; + + Control::Jump(value) + } else { + Control::Exit(ExitError::UnknownOpcode.into()) + } +} + #[inline] pub fn pc(state: &mut Machine, position: usize) -> Control { push_u256!(state, U256::from(position)); diff --git a/core/src/eval/mod.rs b/core/src/eval/mod.rs index 721ca22f5..feada1e6d 100644 --- a/core/src/eval/mod.rs +++ b/core/src/eval/mod.rs @@ -164,6 +164,18 @@ fn eval_jumpi(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control self::misc::jumpi(state) } +fn eval_beginsub(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { + self::misc::beginsub(state) +} + +fn eval_jumpsub(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { + self::misc::jumpsub(state, position) +} + +fn eval_returnsub(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { + self::misc::returnsub(state) +} + fn eval_pc(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { self::misc::pc(state, position) } @@ -490,6 +502,9 @@ pub fn eval(state: &mut Machine, opcode: Opcode, position: usize) -> Control { table[Opcode::MSTORE8.as_usize()] = eval_mstore8 as _; table[Opcode::JUMP.as_usize()] = eval_jump as _; table[Opcode::JUMPI.as_usize()] = eval_jumpi as _; + table[Opcode::BEGINSUB.as_usize()] = eval_beginsub as _; + table[Opcode::JUMPSUB.as_usize()] = eval_jumpsub as _; + table[Opcode::RETURNSUB.as_usize()] = eval_returnsub as _; table[Opcode::PC.as_usize()] = eval_pc as _; table[Opcode::MSIZE.as_usize()] = eval_msize as _; table[Opcode::JUMPDEST.as_usize()] = eval_jumpdest as _; diff --git a/core/src/lib.rs b/core/src/lib.rs index d63459e16..fb4e1b610 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -17,8 +17,8 @@ mod eval; mod utils; pub use crate::memory::Memory; -pub use crate::stack::Stack; -pub use crate::valids::Valids; +pub use crate::stack::{Stack, Returns}; +pub use crate::valids::{Valid, Valids}; pub use crate::opcode::Opcode; pub use crate::error::{Trap, Capture, ExitReason, ExitSucceed, ExitError, ExitRevert, ExitFatal}; @@ -42,8 +42,10 @@ pub struct Machine { valids: Valids, /// Memory. memory: Memory, - /// Stack. + /// Data stack. stack: Stack, + /// Return stack. + returns: Option, } impl Machine { @@ -61,7 +63,8 @@ impl Machine { code: Rc>, data: Rc>, stack_limit: usize, - memory_limit: usize + memory_limit: usize, + returns_limit: Option, ) -> Self { let valids = Valids::new(&code[..]); @@ -71,6 +74,7 @@ impl Machine { position: Ok(0), return_range: U256::zero()..U256::zero(), valids, + returns: returns_limit.map(|l| Returns::new(l)), memory: Memory::new(memory_limit), stack: Stack::new(stack_limit), } diff --git a/core/src/opcode.rs b/core/src/opcode.rs index 9665bd782..d303594b9 100644 --- a/core/src/opcode.rs +++ b/core/src/opcode.rs @@ -89,6 +89,13 @@ impl Opcode { /// `JUMPDEST` pub const JUMPDEST: Opcode = Opcode(0x5b); + /// `BEGINSUB` + pub const BEGINSUB: Opcode = Opcode(0x5c); + /// `RETURNSUB` + pub const RETURNSUB: Opcode = Opcode(0x5d); + /// `JUMPSUB` + pub const JUMPSUB: Opcode = Opcode(0x5e); + /// `PUSHn` pub const PUSH1: Opcode = Opcode(0x60); pub const PUSH2: Opcode = Opcode(0x61); diff --git a/core/src/stack.rs b/core/src/stack.rs index c25eb055a..d6c5faabd 100644 --- a/core/src/stack.rs +++ b/core/src/stack.rs @@ -74,3 +74,50 @@ impl Stack { } } } + +/// EVM returns. +#[derive(Clone, Debug)] +pub struct Returns { + data: Vec, + limit: usize, +} + +impl Returns { + /// Create a new stack with given limit. + pub fn new(limit: usize) -> Self { + Self { + data: Vec::new(), + limit, + } + } + + #[inline] + /// Stack limit. + pub fn limit(&self) -> usize { + self.limit + } + + #[inline] + /// Stack length. + pub fn len(&self) -> usize { + self.data.len() + } + + #[inline] + /// Pop a value from the stack. If the stack is already empty, returns the + /// `StackUnderflow` error. + pub fn pop(&mut self) -> Result { + self.data.pop().ok_or(ExitError::StackUnderflow) + } + + #[inline] + /// Push a new value into the stack. If it will exceed the stack limit, + /// returns `StackOverflow` error and leaves the stack unchanged. + pub fn push(&mut self, value: usize) -> Result<(), ExitError> { + if self.data.len() + 1 > self.limit { + return Err(ExitError::StackOverflow) + } + self.data.push(value); + Ok(()) + } +} diff --git a/core/src/valids.rs b/core/src/valids.rs index 1717207d5..52a4a6819 100644 --- a/core/src/valids.rs +++ b/core/src/valids.rs @@ -1,21 +1,36 @@ use alloc::vec::Vec; use crate::Opcode; +/// A valid marker. +#[repr(u8)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum Valid { + /// Invalid position. + None, + /// Valid jump destination. + JumpDest, + /// Valid begin subroutine. + BeginSub, +} + /// Mapping of valid jump destination from code. #[derive(Clone, Debug, Eq, PartialEq)] -pub struct Valids(Vec); +pub struct Valids(Vec); impl Valids { /// Create a new valid mapping from given code bytes. pub fn new(code: &[u8]) -> Self { - let mut valids: Vec = Vec::with_capacity(code.len()); - valids.resize(code.len(), false); + let mut valids: Vec = Vec::with_capacity(code.len()); + valids.resize(code.len(), Valid::None); let mut i = 0; while i < code.len() { let opcode = Opcode(code[i]); if opcode == Opcode::JUMPDEST { - valids[i] = true; + valids[i] = Valid::JumpDest; + i += 1; + } else if opcode == Opcode::BEGINSUB { + valids[i] = Valid::BeginSub; i += 1; } else if let Some(v) = opcode.is_push() { i += v as usize + 1; @@ -42,15 +57,21 @@ impl Valids { /// Returns `true` if the position is a valid jump destination. If /// not, returns `false`. - pub fn is_valid(&self, position: usize) -> bool { + pub fn is_jumpdest(&self, position: usize) -> bool { if position >= self.0.len() { - return false; + return false } - if !self.0[position] { - return false; + self.0[position] == Valid::JumpDest + } + + /// Returns `true` if the position is a valid begin subroutine. If + /// not, returns `false`. + pub fn is_beginsub(&self, position: usize) -> bool { + if position >= self.0.len() { + return false } - true + self.0[position] == Valid::BeginSub } } diff --git a/core/tests/performance.rs b/core/tests/performance.rs index f6dda908d..0580dcb91 100644 --- a/core/tests/performance.rs +++ b/core/tests/performance.rs @@ -8,7 +8,7 @@ macro_rules! ret_test { let code = hex::decode($code).unwrap(); let data = hex::decode($data).unwrap(); - let mut vm = Machine::new(Rc::new(code), Rc::new(data), 1024, 10000); + let mut vm = Machine::new(Rc::new(code), Rc::new(data), 1024, 10000, None); assert_eq!(vm.run(), Capture::Exit(ExitSucceed::Returned.into())); assert_eq!(vm.return_value(), hex::decode($ret).unwrap()); } diff --git a/runtime/src/handler.rs b/runtime/src/handler.rs index d4c698644..eeca90c65 100644 --- a/runtime/src/handler.rs +++ b/runtime/src/handler.rs @@ -117,6 +117,6 @@ pub trait Handler { _opcode: Opcode, _stack: &mut Machine ) -> Result<(), ExitError> { - Err(ExitError::OutOfGas) + Err(ExitError::UnknownOpcode) } } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 40ec39a51..4e91958d5 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -93,7 +93,7 @@ impl<'config> Runtime<'config> { config: &'config Config, ) -> Self { Self { - machine: Machine::new(code, data, config.stack_limit, config.memory_limit), + machine: Machine::new(code, data, config.stack_limit, config.memory_limit, config.returns_limit), status: Ok(()), return_data_buffer: Vec::new(), context, @@ -176,6 +176,8 @@ pub struct Config { pub stack_limit: usize, /// Memory limit. pub memory_limit: usize, + /// Returns stake limit. + pub returns_limit: Option, /// Call limit. pub call_stack_limit: usize, /// Create contract limit. @@ -229,6 +231,7 @@ impl Config { call_l64_after_gas: false, stack_limit: 1024, memory_limit: usize::max_value(), + returns_limit: None, call_stack_limit: 1024, create_contract_limit: None, call_stipend: 2300, @@ -270,6 +273,49 @@ impl Config { call_l64_after_gas: true, stack_limit: 1024, memory_limit: usize::max_value(), + returns_limit: None, + call_stack_limit: 1024, + create_contract_limit: Some(0x6000), + call_stipend: 2300, + has_delegate_call: true, + has_create2: true, + has_revert: true, + has_return_data: true, + has_bitwise_shifting: true, + has_chain_id: true, + has_self_balance: true, + has_ext_code_hash: true, + estimate: false, + } + } + + /// Istanbul hard fork configuration. + pub const fn berlin() -> Config { + Config { + gas_ext_code: 700, + gas_ext_code_hash: 700, + gas_balance: 700, + gas_sload: 800, + gas_sstore_set: 20000, + gas_sstore_reset: 5000, + refund_sstore_clears: 15000, + gas_suicide: 5000, + gas_suicide_new_account: 25000, + gas_call: 700, + gas_expbyte: 50, + gas_transaction_create: 53000, + gas_transaction_call: 21000, + gas_transaction_zero_data: 4, + gas_transaction_non_zero_data: 16, + sstore_gas_metering: true, + sstore_revert_under_stipend: true, + err_on_call_with_more_gas: false, + empty_considered_exists: false, + create_increase_nonce: true, + call_l64_after_gas: true, + stack_limit: 1024, + memory_limit: usize::max_value(), + returns_limit: Some(1024), call_stack_limit: 1024, create_contract_limit: Some(0x6000), call_stipend: 2300,