diff --git a/ethcore/evm/src/instructions.rs b/ethcore/evm/src/instructions.rs index 266bf7ded..d222c4cf5 100644 --- a/ethcore/evm/src/instructions.rs +++ b/ethcore/evm/src/instructions.rs @@ -321,6 +321,13 @@ enum_with_from_u8! { #[doc = "Makes a log entry, 4 topics."] LOG4 = 0xa4, + #[doc = "Marks the entry point to a subroutine."] + BEGINSUB = 0x5c, + #[doc = "Returns from a subroutine."] + RETURNSUB = 0x5d, + #[doc = "Jumps to a defined BEGINSUB subroutine."] + JUMPSUB = 0x5e, + #[doc = "create a new account with associated code"] CREATE = 0xf0, #[doc = "message-call into an account"] @@ -591,6 +598,9 @@ lazy_static! { arr[LOG2 as usize] = Some(InstructionInfo::new("LOG2", 4, 0, GasPriceTier::Special)); arr[LOG3 as usize] = Some(InstructionInfo::new("LOG3", 5, 0, GasPriceTier::Special)); arr[LOG4 as usize] = Some(InstructionInfo::new("LOG4", 6, 0, GasPriceTier::Special)); + arr[BEGINSUB as usize] = Some(InstructionInfo::new("BEGINSUB", 0, 0, GasPriceTier::Base)); + arr[JUMPSUB as usize] = Some(InstructionInfo::new("JUMPSUB", 1, 0, GasPriceTier::High)); + arr[RETURNSUB as usize] = Some(InstructionInfo::new("RETURNSUB", 0, 0, GasPriceTier::Low)); arr[CREATE as usize] = Some(InstructionInfo::new("CREATE", 3, 1, GasPriceTier::Special)); arr[CALL as usize] = Some(InstructionInfo::new("CALL", 7, 1, GasPriceTier::Special)); arr[CALLCODE as usize] = Some(InstructionInfo::new("CALLCODE", 7, 1, GasPriceTier::Special)); diff --git a/ethcore/evm/src/interpreter/mod.rs b/ethcore/evm/src/interpreter/mod.rs index 082dcd33b..b3cf9ad80 100644 --- a/ethcore/evm/src/interpreter/mod.rs +++ b/ethcore/evm/src/interpreter/mod.rs @@ -61,6 +61,10 @@ const TWO_POW_96: U256 = U256([0, 0x100000000, 0, 0]); //0x1 00000000 00000000 0 const TWO_POW_224: U256 = U256([0, 0, 0, 0x100000000]); //0x1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 const TWO_POW_248: U256 = U256([0, 0, 0, 0x100000000000000]); //0x1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 000000 +/// Maximum subroutine stack size as specified in +/// https://eips.ethereum.org/EIPS/eip-2315. +pub const MAX_SUB_STACK_SIZE: usize = 1023; + fn to_biguint(x: U256) -> BigUint { let mut bytes = [0u8; 32]; x.to_little_endian(&mut bytes); @@ -101,6 +105,8 @@ enum InstructionResult { Ok, UnusedGas(Gas), JumpToPosition(U256), + JumpToSubroutine(U256), + ReturnFromSubroutine(usize), StopExecutionNeedsReturn { /// Gas left. gas: Gas, @@ -183,8 +189,10 @@ pub struct Interpreter { do_trace: bool, done: bool, valid_jump_destinations: Option>, + valid_subroutine_destinations: Option>, gasometer: Option>, stack: VecStack, + return_stack: Vec, resume_output_range: Option<(U256, U256)>, resume_result: Option>, last_stack_ret_len: usize, @@ -290,10 +298,12 @@ impl Interpreter { let params = InterpreterParams::from(params); let informant = informant::EvmInformant::new(depth); let valid_jump_destinations = None; + let valid_subroutine_destinations = None; let gasometer = Cost::from_u256(params.gas) .ok() .map(|gas| Gasometer::::new(gas)); let stack = VecStack::with_capacity(schedule.stack_limit, U256::zero()); + let return_stack = Vec::with_capacity(MAX_SUB_STACK_SIZE); Interpreter { cache, @@ -301,8 +311,10 @@ impl Interpreter { reader, informant, valid_jump_destinations, + valid_subroutine_destinations, gasometer, stack, + return_stack, done: false, // Overridden in `step_inner` based on // the result of `ext.trace_next_instruction`. @@ -478,7 +490,8 @@ impl Interpreter { if self.valid_jump_destinations.is_none() { self.valid_jump_destinations = Some( self.cache - .jump_destinations(&self.params.code_hash, &self.reader.code), + .jump_and_sub_destinations(&self.params.code_hash, &self.reader.code) + .0, ); } let jump_destinations = self @@ -491,6 +504,28 @@ impl Interpreter { }; self.reader.position = pos; } + InstructionResult::JumpToSubroutine(position) => { + if self.valid_subroutine_destinations.is_none() { + self.valid_subroutine_destinations = Some( + self.cache + .jump_and_sub_destinations(&self.params.code_hash, &self.reader.code) + .1, + ); + } + let subroutine_destinations = self + .valid_subroutine_destinations + .as_ref() + .expect("subroutine_destinations are initialized on first jump; qed"); + let pos = match self.verify_jump(position, subroutine_destinations) { + Ok(x) => x, + Err(e) => return InterpreterResult::Done(Err(e)), + }; + self.return_stack.push(self.reader.position); + self.reader.position = pos + 1; + } + InstructionResult::ReturnFromSubroutine(pos) => { + self.reader.position = pos; + } InstructionResult::StopExecutionNeedsReturn { gas, init_off, @@ -537,20 +572,20 @@ impl Interpreter { ) -> vm::Result<()> { let schedule = ext.schedule(); - if (instruction == instructions::DELEGATECALL && !schedule.have_delegate_call) - || (instruction == instructions::CREATE2 && !schedule.have_create2) - || (instruction == instructions::STATICCALL && !schedule.have_static_call) - || ((instruction == instructions::RETURNDATACOPY - || instruction == instructions::RETURNDATASIZE) + use instructions::*; + if (instruction == DELEGATECALL && !schedule.have_delegate_call) + || (instruction == CREATE2 && !schedule.have_create2) + || (instruction == STATICCALL && !schedule.have_static_call) + || ((instruction == RETURNDATACOPY || instruction == RETURNDATASIZE) && !schedule.have_return_data) - || (instruction == instructions::REVERT && !schedule.have_revert) - || ((instruction == instructions::SHL - || instruction == instructions::SHR - || instruction == instructions::SAR) + || (instruction == REVERT && !schedule.have_revert) + || ((instruction == SHL || instruction == SHR || instruction == SAR) && !schedule.have_bitwise_shifting) - || (instruction == instructions::EXTCODEHASH && !schedule.have_extcodehash) - || (instruction == instructions::CHAINID && !schedule.have_chain_id) - || (instruction == instructions::SELFBALANCE && !schedule.have_selfbalance) + || (instruction == EXTCODEHASH && !schedule.have_extcodehash) + || (instruction == CHAINID && !schedule.have_chain_id) + || (instruction == SELFBALANCE && !schedule.have_selfbalance) + || ((instruction == BEGINSUB || instruction == JUMPSUB || instruction == RETURNSUB) + && !schedule.have_subs) { return Err(vm::Error::BadInstruction { instruction: instruction as u8, @@ -623,6 +658,29 @@ impl Interpreter { instructions::JUMPDEST => { // ignore } + instructions::BEGINSUB => { + return Err(vm::Error::InvalidSubEntry); + } + instructions::JUMPSUB => { + if self.return_stack.len() >= MAX_SUB_STACK_SIZE { + return Err(vm::Error::OutOfSubStack { + wanted: 1, + limit: MAX_SUB_STACK_SIZE, + }); + } + let sub_destination = self.stack.pop_back(); + return Ok(InstructionResult::JumpToSubroutine(sub_destination)); + } + instructions::RETURNSUB => { + if let Some(pos) = self.return_stack.pop() { + return Ok(InstructionResult::ReturnFromSubroutine(pos)); + } else { + return Err(vm::Error::SubStackUnderflow { + wanted: 1, + on_stack: 0, + }); + } + } instructions::CREATE | instructions::CREATE2 => { let endowment = self.stack.pop_back(); let init_off = self.stack.pop_back(); @@ -1413,6 +1471,7 @@ impl Interpreter { if valid_jump_destinations.contains(jump) && U256::from(jump) == jump_u { Ok(jump) } else { + // Note: if jump > usize, BadJumpDestination value is trimmed Err(vm::Error::BadJumpDestination { destination: jump }) } } diff --git a/ethcore/evm/src/interpreter/shared_cache.rs b/ethcore/evm/src/interpreter/shared_cache.rs index c74383341..77fb11e32 100644 --- a/ethcore/evm/src/interpreter/shared_cache.rs +++ b/ethcore/evm/src/interpreter/shared_cache.rs @@ -26,6 +26,7 @@ use std::sync::Arc; const DEFAULT_CACHE_SIZE: usize = 4 * 1024 * 1024; // stub for a HeapSizeOf implementation. +#[derive(Clone)] struct Bits(Arc); impl HeapSizeOf for Bits { @@ -35,9 +36,21 @@ impl HeapSizeOf for Bits { } } +#[derive(Clone)] +struct CacheItem { + jump_destination: Bits, + sub_entrypoint: Bits, +} + +impl HeapSizeOf for CacheItem { + fn heap_size_of_children(&self) -> usize { + self.jump_destination.heap_size_of_children() + self.sub_entrypoint.heap_size_of_children() + } +} + /// Global cache for EVM interpreter pub struct SharedCache { - jump_destinations: Mutex>, + jump_destinations: Mutex>, } impl SharedCache { @@ -50,47 +63,62 @@ impl SharedCache { } /// Get jump destinations bitmap for a contract. - pub fn jump_destinations(&self, code_hash: &Option, code: &[u8]) -> Arc { + pub fn jump_and_sub_destinations( + &self, + code_hash: &Option, + code: &[u8], + ) -> (Arc, Arc) { if let Some(ref code_hash) = code_hash { if code_hash == &KECCAK_EMPTY { - return Self::find_jump_destinations(code); + let cache_item = Self::find_jump_and_sub_destinations(code); + return (cache_item.jump_destination.0, cache_item.sub_entrypoint.0); } if let Some(d) = self.jump_destinations.lock().get_mut(code_hash) { - return d.0.clone(); + return (d.jump_destination.0.clone(), d.sub_entrypoint.0.clone()); } } - let d = Self::find_jump_destinations(code); + let d = Self::find_jump_and_sub_destinations(code); if let Some(ref code_hash) = code_hash { - self.jump_destinations - .lock() - .insert(*code_hash, Bits(d.clone())); + self.jump_destinations.lock().insert(*code_hash, d.clone()); } - d + (d.jump_destination.0, d.sub_entrypoint.0) } - fn find_jump_destinations(code: &[u8]) -> Arc { + fn find_jump_and_sub_destinations(code: &[u8]) -> CacheItem { let mut jump_dests = BitSet::with_capacity(code.len()); + let mut sub_entrypoints = BitSet::with_capacity(code.len()); let mut position = 0; while position < code.len() { let instruction = Instruction::from_u8(code[position]); if let Some(instruction) = instruction { - if instruction == instructions::JUMPDEST { - jump_dests.insert(position); - } else if let Some(push_bytes) = instruction.push_bytes() { - position += push_bytes; + match instruction { + instructions::JUMPDEST => { + jump_dests.insert(position); + } + instructions::BEGINSUB => { + sub_entrypoints.insert(position); + } + _ => { + if let Some(push_bytes) = instruction.push_bytes() { + position += push_bytes; + } + } } } position += 1; } jump_dests.shrink_to_fit(); - Arc::new(jump_dests) + CacheItem { + jump_destination: Bits(Arc::new(jump_dests)), + sub_entrypoint: Bits(Arc::new(sub_entrypoints)), + } } } @@ -100,15 +128,92 @@ impl Default for SharedCache { } } -#[test] -fn test_find_jump_destinations() { - use rustc_hex::FromHex; - // given - let code = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b01600055".from_hex().unwrap(); +#[cfg(test)] +mod test { + use super::*; + use hex_literal::hex; - // when - let valid_jump_destinations = SharedCache::find_jump_destinations(&code); + #[test] + fn test_find_jump_destinations() { + // given - // then - assert!(valid_jump_destinations.contains(66)); + // 0000 7F PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + // 0021 7F PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + // 0042 5B JUMPDEST + // 0043 01 ADD + // 0044 60 PUSH1 0x00 + // 0046 55 SSTORE + let code = hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b01600055"); + + // when + let cache_item = SharedCache::find_jump_and_sub_destinations(&code); + + // then + assert!(cache_item + .jump_destination + .0 + .iter() + .eq(vec![66].into_iter())); + assert!(cache_item.sub_entrypoint.0.is_empty()); + } + + #[test] + fn test_find_jump_destinations_not_in_data_segments() { + // given + + // 0000 60 06 PUSH1 06 + // 0002 56 JUMP + // 0003 50 5B PUSH1 0x5B + // 0005 56 STOP + // 0006 5B JUMPDEST + // 0007 60 04 PUSH1 04 + // 0009 56 JUMP + let code = hex!("600656605B565B6004"); + + // when + let cache_item = SharedCache::find_jump_and_sub_destinations(&code); + + // then + assert!(cache_item.jump_destination.0.iter().eq(vec![6].into_iter())); + assert!(cache_item.sub_entrypoint.0.is_empty()); + } + + #[test] + fn test_find_sub_entrypoints() { + // given + + // see https://eips.ethereum.org/EIPS/eip-2315 for disassembly + let code = hex!("6800000000000000000c5e005c60115e5d5c5d"); + + // when + let cache_item = SharedCache::find_jump_and_sub_destinations(&code); + + // then + assert!(cache_item.jump_destination.0.is_empty()); + assert!(cache_item + .sub_entrypoint + .0 + .iter() + .eq(vec![12, 17].into_iter())); + } + + #[test] + fn test_find_jump_and_sub_allowing_unknown_opcodes() { + // precondition + assert!(Instruction::from_u8(0xcc) == None); + + // given + + // 0000 5B JUMPDEST + // 0001 CC ??? + // 0002 5C BEGINSUB + let code = hex!("5BCC5C"); + + // when + let cache_item = SharedCache::find_jump_and_sub_destinations(&code); + + // then + assert!(cache_item.jump_destination.0.iter().eq(vec![0].into_iter())); + assert!(cache_item.sub_entrypoint.0.iter().eq(vec![2].into_iter())); + } } diff --git a/ethcore/evm/src/tests.rs b/ethcore/evm/src/tests.rs index fb812b557..c8d80d022 100644 --- a/ethcore/evm/src/tests.rs +++ b/ethcore/evm/src/tests.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity Ethereum. If not, see . +use super::interpreter::MAX_SUB_STACK_SIZE; use ethereum_types::{Address, H256, U256}; use factory::Factory; use hex_literal::hex; @@ -1037,6 +1038,171 @@ fn test_jumps(factory: super::Factory) { assert_eq!(gas_left, U256::from(54_117)); } +evm_test! {test_subs_simple: test_subs_simple_int} +fn test_subs_simple(factory: super::Factory) { + // as defined in https://eips.ethereum.org/EIPS/eip-2315 + let code = hex!("60045e005c5d").to_vec(); + + let mut params = ActionParams::default(); + params.gas = U256::from(18); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new_berlin(); + + let gas_left = { + let vm = factory.create(params, ext.schedule(), ext.depth()); + test_finalize(vm.exec(&mut ext).ok().unwrap()).unwrap() + }; + + assert_eq!(gas_left, U256::from(0)); +} + +evm_test! {test_subs_two_levels: test_subs_two_levels_int} +fn test_subs_two_levels(factory: super::Factory) { + // as defined in https://eips.ethereum.org/EIPS/eip-2315 + let code = hex!("6800000000000000000c5e005c60115e5d5c5d").to_vec(); + + let mut params = ActionParams::default(); + params.gas = U256::from(36); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new_berlin(); + + let gas_left = { + let vm = factory.create(params, ext.schedule(), ext.depth()); + test_finalize(vm.exec(&mut ext).ok().unwrap()).unwrap() + }; + + assert_eq!(gas_left, U256::from(0)); +} + +evm_test! {test_subs_invalid_jump: test_subs_invalid_jump_int} +fn test_subs_invalid_jump(factory: super::Factory) { + // as defined in https://eips.ethereum.org/EIPS/eip-2315 + let code = hex!("6801000000000000000c5e005c60115e5d5c5d").to_vec(); + + let mut params = ActionParams::default(); + params.gas = U256::from(24); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new_berlin(); + + let current = { + let vm = factory.create(params, ext.schedule(), ext.depth()); + test_finalize(vm.exec(&mut ext).ok().unwrap()) + }; + + let expected = Result::Err(vm::Error::BadJumpDestination { destination: 0xc }); + assert_eq!(current, expected); +} + +evm_test! {test_subs_shallow_return_stack: test_subs_shallow_return_stack_int} +fn test_subs_shallow_return_stack(factory: super::Factory) { + // as defined in https://eips.ethereum.org/EIPS/eip-2315 + let code = hex!("5d5858").to_vec(); + + let mut params = ActionParams::default(); + params.gas = U256::from(24); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new_berlin(); + + let current = { + let vm = factory.create(params, ext.schedule(), ext.depth()); + test_finalize(vm.exec(&mut ext).ok().unwrap()) + }; + + let expected = Result::Err(vm::Error::SubStackUnderflow { + wanted: 1, + on_stack: 0, + }); + assert_eq!(current, expected); +} + +evm_test! {test_subs_substack_limit: test_subs_substack_limit_int} +fn test_subs_substack_limit(factory: super::Factory) { + // PUSH + // JUMP a + // s: BEGINSUB + // a: JUMPDEST + // DUP1 + // JUMPI c + // STOP + // c: JUMPDEST + // PUSH1 1 + // SWAP + // SUB + // JUMPSUB s + + let mut code = hex!("6104006007565c5b80600d57005b6001900360065e").to_vec(); + code[1..3].copy_from_slice(&(MAX_SUB_STACK_SIZE as u16).to_be_bytes()[..]); + + let mut params = ActionParams::default(); + params.gas = U256::from(1_000_000); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new_berlin(); + + let gas_left = { + let vm = factory.create(params, ext.schedule(), ext.depth()); + test_finalize(vm.exec(&mut ext).ok().unwrap()).unwrap() + }; + + assert_eq!(gas_left, U256::from(959_049)); +} + +evm_test! {test_subs_substack_out: test_subs_substack_out_int} +fn test_subs_substack_out(factory: super::Factory) { + let mut code = hex!("6104006007565c5b80600d57005b6001900360065e").to_vec(); + code[1..3].copy_from_slice(&((MAX_SUB_STACK_SIZE + 1) as u16).to_be_bytes()[..]); + + let mut params = ActionParams::default(); + params.gas = U256::from(1_000_000); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new_berlin(); + + let current = { + let vm = factory.create(params, ext.schedule(), ext.depth()); + test_finalize(vm.exec(&mut ext).ok().unwrap()) + }; + + let expected = Result::Err(vm::Error::OutOfSubStack { + wanted: 1, + limit: MAX_SUB_STACK_SIZE, + }); + assert_eq!(current, expected); +} + +evm_test! {test_subs_sub_at_end: test_subs_sub_at_end_int} +fn test_subs_sub_at_end(factory: super::Factory) { + let code = hex!("6005565c5d5b60035e").to_vec(); + + let mut params = ActionParams::default(); + params.gas = U256::from(30); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new_berlin(); + + let gas_left = { + let vm = factory.create(params, ext.schedule(), ext.depth()); + test_finalize(vm.exec(&mut ext).ok().unwrap()).unwrap() + }; + + assert_eq!(gas_left, U256::from(0)); +} + +evm_test! {test_subs_walk_into_subroutine: test_subs_walk_into_subroutine_int} +fn test_subs_walk_into_subroutine(factory: super::Factory) { + let code = hex!("5c5d00").to_vec(); + + let mut params = ActionParams::default(); + params.gas = U256::from(100); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new_berlin(); + + let current = { + let vm = factory.create(params, ext.schedule(), ext.depth()); + test_finalize(vm.exec(&mut ext).ok().unwrap()) + }; + + let expected = Result::Err(vm::Error::InvalidSubEntry); + assert_eq!(current, expected); +} + evm_test! {test_calls: test_calls_int} fn test_calls(factory: super::Factory) { let code = "600054602d57600160005560006000600060006050610998610100f160006000600060006050610998610100f25b".from_hex().unwrap(); diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 5164fa4d2..7a619bc27 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -435,6 +435,9 @@ impl<'a> CallCreateExecutive<'a> { | Err(vm::Error::MutableCallInStaticContext) | Err(vm::Error::OutOfBounds) | Err(vm::Error::Reverted) + | Err(vm::Error::SubStackUnderflow { .. }) + | Err(vm::Error::OutOfSubStack { .. }) + | Err(vm::Error::InvalidSubEntry) | Ok(FinalizationResult { apply_state: false, .. }) => { diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 4ff52a046..a262a2a3f 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -126,6 +126,8 @@ pub struct CommonParams { pub eip1884_transition: BlockNumber, /// Number of first block where EIP-2028 rules begin. pub eip2028_transition: BlockNumber, + /// Number of first block where EIP-2315 rules begin. + pub eip2315_transition: BlockNumber, /// Number of first block where dust cleanup rules (EIP-168 and EIP169) begin. pub dust_protection_transition: BlockNumber, /// Nonce cap increase per block. Nonce cap is only checked if dust protection is enabled. @@ -197,6 +199,7 @@ impl CommonParams { && !(block_number >= self.eip1283_disable_transition)) || block_number >= self.eip1283_reenable_transition; schedule.eip1706 = block_number >= self.eip1706_transition; + schedule.have_subs = block_number >= self.eip2315_transition; if block_number >= self.eip1884_transition { schedule.have_selfbalance = true; @@ -329,6 +332,9 @@ impl From for CommonParams { eip2028_transition: p .eip2028_transition .map_or_else(BlockNumber::max_value, Into::into), + eip2315_transition: p + .eip2315_transition + .map_or_else(BlockNumber::max_value, Into::into), dust_protection_transition: p .dust_protection_transition .map_or_else(BlockNumber::max_value, Into::into), diff --git a/ethcore/src/trace/types/error.rs b/ethcore/src/trace/types/error.rs index 9704b8082..367ceeccd 100644 --- a/ethcore/src/trace/types/error.rs +++ b/ethcore/src/trace/types/error.rs @@ -34,6 +34,12 @@ pub enum Error { StackUnderflow, /// When execution would exceed defined Stack Limit OutOfStack, + /// When there is not enough subroutine stack elements to return from + SubStackUnderflow, + /// When execution would exceed defined subroutine Stack Limit + OutOfSubStack, + /// When the code walks into a subroutine, that is not allowed + InvalidSubEntry, /// When builtin contract failed on input data BuiltIn, /// Returned on evm internal error. Should never be ignored during development. @@ -57,6 +63,9 @@ impl<'a> From<&'a VmError> for Error { VmError::BadInstruction { .. } => Error::BadInstruction, VmError::StackUnderflow { .. } => Error::StackUnderflow, VmError::OutOfStack { .. } => Error::OutOfStack, + VmError::SubStackUnderflow { .. } => Error::SubStackUnderflow, + VmError::OutOfSubStack { .. } => Error::OutOfSubStack, + VmError::InvalidSubEntry { .. } => Error::InvalidSubEntry, VmError::BuiltIn { .. } => Error::BuiltIn, VmError::Wasm { .. } => Error::Wasm, VmError::Internal(_) => Error::Internal, @@ -82,7 +91,10 @@ impl fmt::Display for Error { BadInstruction => "Bad instruction", StackUnderflow => "Stack underflow", OutOfStack => "Out of stack", + SubStackUnderflow => "Subroutine stack underflow", + OutOfSubStack => "Subroutine stack overflow", BuiltIn => "Built-in failed", + InvalidSubEntry => "Invalid subroutine entry", Wasm => "Wasm runtime error", Internal => "Internal error", MutableCallInStaticContext => "Mutable Call In Static Context", @@ -108,6 +120,9 @@ impl Encodable for Error { Wasm => 8, OutOfBounds => 9, Reverted => 10, + SubStackUnderflow => 11, + OutOfSubStack => 12, + InvalidSubEntry => 13, }; s.append_internal(&value); @@ -130,6 +145,9 @@ impl Decodable for Error { 8 => Ok(Wasm), 9 => Ok(OutOfBounds), 10 => Ok(Reverted), + 11 => Ok(SubStackUnderflow), + 12 => Ok(OutOfSubStack), + 13 => Ok(InvalidSubEntry), _ => Err(DecoderError::Custom("Invalid error type")), } } diff --git a/ethcore/vm/src/error.rs b/ethcore/vm/src/error.rs index 1d765cb62..8713c0963 100644 --- a/ethcore/vm/src/error.rs +++ b/ethcore/vm/src/error.rs @@ -72,6 +72,22 @@ pub enum Error { /// What was the stack limit limit: usize, }, + /// When there is not enough subroutine stack elements to return from + SubStackUnderflow { + /// How many stack elements was requested by instruction + wanted: usize, + /// How many elements were on stack + on_stack: usize, + }, + /// When execution would exceed defined subroutine Stack Limit + OutOfSubStack { + /// How many stack elements instruction wanted to pop + wanted: usize, + /// What was the stack limit + limit: usize, + }, + /// When the code walks into a subroutine, that is not allowed + InvalidSubEntry, /// Built-in contract failed on given input BuiltIn(&'static str), /// When execution tries to modify the state in static context @@ -103,9 +119,11 @@ impl fmt::Display for Error { use self::Error::*; match *self { OutOfGas => write!(f, "Out of gas"), - BadJumpDestination { destination } => { - write!(f, "Bad jump destination {:x}", destination) - } + BadJumpDestination { destination } => write!( + f, + "Bad jump destination {:x} (trimmed to usize)", + destination + ), BadInstruction { instruction } => write!(f, "Bad instruction {:x}", instruction), StackUnderflow { instruction, @@ -117,6 +135,13 @@ impl fmt::Display for Error { wanted, limit, } => write!(f, "Out of stack {} {}/{}", instruction, wanted, limit), + SubStackUnderflow { wanted, on_stack } => { + write!(f, "Subroutine stack underflow {}/{}", wanted, on_stack) + } + OutOfSubStack { wanted, limit } => { + write!(f, "Out of subroutine stack {}/{}", wanted, limit) + } + InvalidSubEntry => write!(f, "Invalid subroutine entry"), BuiltIn(name) => write!(f, "Built-in failed: {}", name), Internal(ref msg) => write!(f, "Internal error: {}", msg), MutableCallInStaticContext => write!(f, "Mutable call in static context"), diff --git a/ethcore/vm/src/schedule.rs b/ethcore/vm/src/schedule.rs index 3d5fdbc95..7e0358c47 100644 --- a/ethcore/vm/src/schedule.rs +++ b/ethcore/vm/src/schedule.rs @@ -120,6 +120,8 @@ pub struct Schedule { pub have_chain_id: bool, /// SELFBALANCE opcode enabled. pub have_selfbalance: bool, + /// BEGINSUB, JUMPSUB and RETURNSUB opcodes enabled. + pub have_subs: bool, /// Kill basic accounts below this balance if touched. pub kill_dust: CleanDustMode, /// Enable EIP-1283 rules @@ -224,6 +226,7 @@ impl Schedule { have_bitwise_shifting: false, have_chain_id: false, have_selfbalance: false, + have_subs: false, have_extcodehash: false, stack_limit: 1024, max_depth: 1024, @@ -303,6 +306,13 @@ impl Schedule { schedule } + /// Schedule for the Berlin fork of the Ethereum main net. + pub fn new_berlin() -> Schedule { + let mut schedule = Self::new_istanbul(); + schedule.have_subs = true; // EIP 2315 + schedule + } + fn new(efcd: bool, hdc: bool, tcg: usize) -> Schedule { Schedule { exceptional_failed_code_deposit: efcd, @@ -313,6 +323,7 @@ impl Schedule { have_bitwise_shifting: false, have_chain_id: false, have_selfbalance: false, + have_subs: false, have_extcodehash: false, stack_limit: 1024, max_depth: 1024, diff --git a/ethcore/vm/src/tests.rs b/ethcore/vm/src/tests.rs index 261473dac..4983c6efd 100644 --- a/ethcore/vm/src/tests.rs +++ b/ethcore/vm/src/tests.rs @@ -115,6 +115,13 @@ impl FakeExt { ext } + /// New fake externalities with Berlin schedule rules + pub fn new_berlin() -> Self { + let mut ext = FakeExt::default(); + ext.schedule = Schedule::new_berlin(); + ext + } + /// Alter fake externalities to allow wasm pub fn with_wasm(mut self) -> Self { self.schedule.wasm = Some(Default::default()); diff --git a/json/src/spec/params.rs b/json/src/spec/params.rs index c3dfef255..3473c270b 100644 --- a/json/src/spec/params.rs +++ b/json/src/spec/params.rs @@ -104,6 +104,8 @@ pub struct Params { /// See `CommonParams` docs. pub eip2028_transition: Option, /// See `CommonParams` docs. + pub eip2315_transition: Option, + /// See `CommonParams` docs. pub dust_protection_transition: Option, /// See `CommonParams` docs. pub nonce_cap_increment: Option,