From 4c1b74a42e513c8d8003a8099f62ced51d6db729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 5 Jul 2016 09:15:44 -0400 Subject: [PATCH] Calculating gas using usize (if supplied gaslimit fits in usize) (#1518) * Spliting gasometer out of interpreter * Choosing right gas calculations implementation based on supplied gas * Moving verification out of gasometer * MemGasCost benchmark. Conflicts: ethcore/src/evm/benches/mod.rs * Some simple benchmarks * Benchmark for simple loop * Calculating gas_for_memory only when it's actually needed * Removing superfluous newline [ci skip] --- ethcore/Cargo.toml | 1 + ethcore/src/evm/benches/mod.rs | 126 ++++ ethcore/src/evm/evm.rs | 55 ++ ethcore/src/evm/factory.rs | 25 +- ethcore/src/evm/instructions.rs | 2 +- ethcore/src/evm/interpreter/gasometer.rs | 261 ++++++++ ethcore/src/evm/interpreter/memory.rs | 150 +++++ .../{interpreter.rs => interpreter/mod.rs} | 590 +++--------------- ethcore/src/evm/interpreter/stack.rs | 106 ++++ ethcore/src/evm/mod.rs | 4 +- ethcore/src/evm/tests.rs | 80 +-- ethcore/src/executive.rs | 4 +- ethcore/src/json_tests/executive.rs | 2 +- ethcore/src/lib.rs | 1 + 14 files changed, 839 insertions(+), 568 deletions(-) create mode 100644 ethcore/src/evm/benches/mod.rs create mode 100644 ethcore/src/evm/interpreter/gasometer.rs create mode 100644 ethcore/src/evm/interpreter/memory.rs rename ethcore/src/evm/{interpreter.rs => interpreter/mod.rs} (57%) create mode 100644 ethcore/src/evm/interpreter/stack.rs diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index 0ea0023d6..acdcd667a 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -43,3 +43,4 @@ json-tests = [] test-heavy = [] dev = ["clippy"] default = [] +benches = [] diff --git a/ethcore/src/evm/benches/mod.rs b/ethcore/src/evm/benches/mod.rs new file mode 100644 index 000000000..8ef730d88 --- /dev/null +++ b/ethcore/src/evm/benches/mod.rs @@ -0,0 +1,126 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity 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. + +// Parity 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 Parity. If not, see . + +//! benchmarking for EVM +//! should be started with: +//! ```bash +//! multirust run nightly cargo bench +//! ``` + +extern crate test; + +use self::test::{Bencher, black_box}; + +use common::*; +use evm::{self, Factory, VMType}; +use evm::tests::FakeExt; + +#[bench] +fn simple_loop_log0_usize(b: &mut Bencher) { + simple_loop_log0(U256::from(::std::usize::MAX), b) +} + +#[bench] +fn simple_loop_log0_u256(b: &mut Bencher) { + simple_loop_log0(!U256::zero(), b) +} + +fn simple_loop_log0(gas: U256, b: &mut Bencher) { + let mut vm = Factory::new(VMType::Interpreter).create(gas); + let mut ext = FakeExt::new(); + + let address = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap(); + let code = black_box( + "62ffffff5b600190036000600fa0600357".from_hex().unwrap() + ); + + b.iter(|| { + let mut params = ActionParams::default(); + params.address = address.clone(); + params.gas = gas; + params.code = Some(code.clone()); + + result(vm.exec(params, &mut ext)) + }); +} + +#[bench] +fn mem_gas_calculation_same_usize(b: &mut Bencher) { + mem_gas_calculation_same(U256::from(::std::usize::MAX), b) +} + +#[bench] +fn mem_gas_calculation_same_u256(b: &mut Bencher) { + mem_gas_calculation_same(!U256::zero(), b) +} + +fn mem_gas_calculation_same(gas: U256, b: &mut Bencher) { + let mut vm = Factory::new(VMType::Interpreter).create(gas); + let mut ext = FakeExt::new(); + + let address = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap(); + + b.iter(|| { + let code = black_box( + "6110006001556001546000555b610fff805560016000540380600055600c57".from_hex().unwrap() + ); + + let mut params = ActionParams::default(); + params.address = address.clone(); + params.gas = gas; + params.code = Some(code.clone()); + + result(vm.exec(params, &mut ext)) + }); +} + +#[bench] +fn mem_gas_calculation_increasing_usize(b: &mut Bencher) { + mem_gas_calculation_increasing(U256::from(::std::usize::MAX), b) +} + +#[bench] +fn mem_gas_calculation_increasing_u256(b: &mut Bencher) { + mem_gas_calculation_increasing(!U256::zero(), b) +} + +fn mem_gas_calculation_increasing(gas: U256, b: &mut Bencher) { + let mut vm = Factory::new(VMType::Interpreter).create(gas); + let mut ext = FakeExt::new(); + + let address = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap(); + + b.iter(|| { + let code = black_box( + "6110006001556001546000555b610fff60005401805560016000540380600055600c57".from_hex().unwrap() + ); + + let mut params = ActionParams::default(); + params.address = address.clone(); + params.gas = gas; + params.code = Some(code.clone()); + + result(vm.exec(params, &mut ext)) + }); +} + +fn result(r: evm::Result) -> U256 { + match r { + Ok(evm::GasLeft::Known(v)) => v, + Ok(evm::GasLeft::NeedsReturn(v, _)) => v, + _ => U256::zero(), + } +} diff --git a/ethcore/src/evm/evm.rs b/ethcore/src/evm/evm.rs index 740774f38..43919034e 100644 --- a/ethcore/src/evm/evm.rs +++ b/ethcore/src/evm/evm.rs @@ -95,6 +95,61 @@ impl<'a> Finalize for Result> { } } +pub trait CostType: ops::Mul + ops::Div + ops::Add + ops::Sub + ops::Shr + ops::Shl + cmp::Ord + Sized + From + Copy { + fn as_u256(&self) -> U256; + fn from_u256(val: U256) -> Result; + fn as_usize(&self) -> usize; + fn overflow_add(self, other: Self) -> (Self, bool); + fn overflow_mul(self, other: Self) -> (Self, bool); +} + +impl CostType for U256 { + fn as_u256(&self) -> U256 { + *self + } + + fn from_u256(val: U256) -> Result { + Ok(val) + } + + fn as_usize(&self) -> usize { + self.as_u64() as usize + } + + fn overflow_add(self, other: Self) -> (Self, bool) { + Uint::overflowing_add(self, other) + } + + fn overflow_mul(self, other: Self) -> (Self, bool) { + Uint::overflowing_mul(self, other) + } +} + +impl CostType for usize { + fn as_u256(&self) -> U256 { + U256::from(*self) + } + + fn from_u256(val: U256) -> Result { + if U256::from(val.low_u64()) != val { + return Err(Error::OutOfGas); + } + Ok(val.low_u64() as usize) + } + + fn as_usize(&self) -> usize { + *self + } + + fn overflow_add(self, other: Self) -> (Self, bool) { + self.overflowing_add(other) + } + + fn overflow_mul(self, other: Self) -> (Self, bool) { + self.overflowing_mul(other) + } +} + /// Evm interface pub trait Evm { /// This function should be used to execute transaction. diff --git a/ethcore/src/evm/factory.rs b/ethcore/src/evm/factory.rs index f55e064c7..082b9d050 100644 --- a/ethcore/src/evm/factory.rs +++ b/ethcore/src/evm/factory.rs @@ -19,6 +19,7 @@ //! TODO: consider spliting it into two separate files. use std::fmt; use evm::Evm; +use util::{U256, Uint}; #[derive(Debug, Clone)] /// Type of EVM to use. @@ -85,24 +86,30 @@ pub struct Factory { impl Factory { /// Create fresh instance of VM + /// Might choose implementation depending on supplied gas. #[cfg(feature = "jit")] - pub fn create(&self) -> Box { + pub fn create(&self, gas: U256) -> Box { match self.evm { VMType::Jit => { Box::new(super::jit::JitEvm::default()) }, - VMType::Interpreter => { - Box::new(super::interpreter::Interpreter::default()) + VMType::Interpreter => if Self::can_fit_in_usize(gas) { + Box::new(super::interpreter::Interpreter::::default()) + } else { + Box::new(super::interpreter::Interpreter::::default()) } } } /// Create fresh instance of VM + /// Might choose implementation depending on supplied gas. #[cfg(not(feature = "jit"))] - pub fn create(&self) -> Box { + pub fn create(&self, gas: U256) -> Box { match self.evm { - VMType::Interpreter => { - Box::new(super::interpreter::Interpreter::default()) + VMType::Interpreter => if Self::can_fit_in_usize(gas) { + Box::new(super::interpreter::Interpreter::::default()) + } else { + Box::new(super::interpreter::Interpreter::::default()) } } } @@ -113,6 +120,10 @@ impl Factory { evm: evm } } + + fn can_fit_in_usize(gas: U256) -> bool { + gas == U256::from(gas.low_u64() as usize) + } } impl Default for Factory { @@ -135,7 +146,7 @@ impl Default for Factory { #[test] fn test_create_vm() { - let _vm = Factory::default().create(); + let _vm = Factory::default().create(U256::zero()); } /// Create tests by injecting different VM factories diff --git a/ethcore/src/evm/instructions.rs b/ethcore/src/evm/instructions.rs index c313726f1..9a1ea97cc 100644 --- a/ethcore/src/evm/instructions.rs +++ b/ethcore/src/evm/instructions.rs @@ -79,7 +79,7 @@ fn test_get_log_topics() { assert_eq!(get_log_topics(LOG4), 4); } -#[derive(PartialEq)] +#[derive(PartialEq, Clone, Copy)] pub enum GasPriceTier { /// 0 Zero Zero, diff --git a/ethcore/src/evm/interpreter/gasometer.rs b/ethcore/src/evm/interpreter/gasometer.rs new file mode 100644 index 000000000..069d70e19 --- /dev/null +++ b/ethcore/src/evm/interpreter/gasometer.rs @@ -0,0 +1,261 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity 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. + +// Parity 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 Parity. If not, see . + +use common::*; +use super::u256_to_address; +use evm::{self, CostType}; +use evm::instructions::{self, Instruction, InstructionInfo}; +use evm::interpreter::stack::Stack; + +macro_rules! overflowing { + ($x: expr) => {{ + let (v, overflow) = $x; + if overflow { return Err(evm::Error::OutOfGas); } + v + }} +} + +#[cfg_attr(feature="dev", allow(enum_variant_names))] +enum InstructionCost { + Gas(Cost), + GasMem(Cost, Cost), + GasMemCopy(Cost, Cost, Cost) +} + +pub struct Gasometer { + pub current_gas: Gas, +} + +impl Gasometer { + + pub fn new(current_gas: Gas) -> Self { + Gasometer { + current_gas: current_gas, + } + } + + pub fn verify_gas(&self, gas_cost: &Gas) -> evm::Result<()> { + match &self.current_gas < gas_cost { + true => Err(evm::Error::OutOfGas), + false => Ok(()) + } + } + + #[cfg_attr(feature="dev", allow(cyclomatic_complexity))] + pub fn get_gas_cost_mem( + &mut self, + ext: &evm::Ext, + instruction: Instruction, + info: &InstructionInfo, + stack: &Stack, + current_mem_size: usize, + ) -> evm::Result<(Gas, usize)> { + let schedule = ext.schedule(); + let tier = instructions::get_tier_idx(info.tier); + let default_gas = Gas::from(schedule.tier_step_gas[tier]); + + let cost = match instruction { + instructions::SSTORE => { + let address = H256::from(stack.peek(0)); + let newval = stack.peek(1); + let val = U256::from(ext.storage_at(&address).as_slice()); + + let gas = if U256::zero() == val && &U256::zero() != newval { + schedule.sstore_set_gas + } else { + // Refund for below case is added when actually executing sstore + // !self.is_zero(&val) && self.is_zero(newval) + schedule.sstore_reset_gas + }; + InstructionCost::Gas(Gas::from(gas)) + }, + instructions::SLOAD => { + InstructionCost::Gas(Gas::from(schedule.sload_gas)) + }, + instructions::MSTORE | instructions::MLOAD => { + InstructionCost::GasMem(default_gas, try!(self.mem_needed_const(stack.peek(0), 32))) + }, + instructions::MSTORE8 => { + InstructionCost::GasMem(default_gas, try!(self.mem_needed_const(stack.peek(0), 1))) + }, + instructions::RETURN => { + InstructionCost::GasMem(default_gas, try!(self.mem_needed(stack.peek(0), stack.peek(1)))) + }, + instructions::SHA3 => { + let w = overflowing!(add_gas_usize(try!(Gas::from_u256(*stack.peek(1))), 31)); + let words = w >> 5; + let gas = Gas::from(schedule.sha3_gas) + (Gas::from(schedule.sha3_word_gas) * words); + InstructionCost::GasMem(gas, try!(self.mem_needed(stack.peek(0), stack.peek(1)))) + }, + instructions::CALLDATACOPY | instructions::CODECOPY => { + InstructionCost::GasMemCopy(default_gas, try!(self.mem_needed(stack.peek(0), stack.peek(2))), try!(Gas::from_u256(*stack.peek(2)))) + }, + instructions::EXTCODECOPY => { + InstructionCost::GasMemCopy(default_gas, try!(self.mem_needed(stack.peek(1), stack.peek(3))), try!(Gas::from_u256(*stack.peek(3)))) + }, + instructions::JUMPDEST => { + InstructionCost::Gas(Gas::from(1)) + }, + instructions::LOG0...instructions::LOG4 => { + let no_of_topics = instructions::get_log_topics(instruction); + let log_gas = schedule.log_gas + schedule.log_topic_gas * no_of_topics; + + let data_gas = overflowing!(try!(Gas::from_u256(*stack.peek(1))).overflow_mul(Gas::from(schedule.log_data_gas))); + let gas = overflowing!(data_gas.overflow_add(Gas::from(log_gas))); + InstructionCost::GasMem(gas, try!(self.mem_needed(stack.peek(0), stack.peek(1)))) + }, + instructions::CALL | instructions::CALLCODE => { + let mut gas = overflowing!(add_gas_usize(try!(Gas::from_u256(*stack.peek(0))), schedule.call_gas)); + let mem = cmp::max( + try!(self.mem_needed(stack.peek(5), stack.peek(6))), + try!(self.mem_needed(stack.peek(3), stack.peek(4))) + ); + + let address = u256_to_address(stack.peek(1)); + + if instruction == instructions::CALL && !ext.exists(&address) { + gas = overflowing!(gas.overflow_add(Gas::from(schedule.call_new_account_gas))); + }; + + if stack.peek(2) > &U256::zero() { + gas = overflowing!(gas.overflow_add(Gas::from(schedule.call_value_transfer_gas))); + }; + + InstructionCost::GasMem(gas,mem) + }, + instructions::DELEGATECALL => { + let gas = overflowing!(add_gas_usize(try!(Gas::from_u256(*stack.peek(0))), schedule.call_gas)); + let mem = cmp::max( + try!(self.mem_needed(stack.peek(4), stack.peek(5))), + try!(self.mem_needed(stack.peek(2), stack.peek(3))) + ); + InstructionCost::GasMem(gas, mem) + }, + instructions::CREATE => { + let gas = Gas::from(schedule.create_gas); + let mem = try!(self.mem_needed(stack.peek(1), stack.peek(2))); + InstructionCost::GasMem(gas, mem) + }, + instructions::EXP => { + let expon = stack.peek(1); + let bytes = ((expon.bits() + 7) / 8) as usize; + let gas = Gas::from(schedule.exp_gas + schedule.exp_byte_gas * bytes); + InstructionCost::Gas(gas) + }, + _ => InstructionCost::Gas(default_gas) + }; + + match cost { + InstructionCost::Gas(gas) => { + Ok((gas, 0)) + }, + InstructionCost::GasMem(gas, mem_size) => { + let (mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size)); + let gas = overflowing!(gas.overflow_add(mem_gas)); + Ok((gas, new_mem_size)) + }, + InstructionCost::GasMemCopy(gas, mem_size, copy) => { + let (mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size)); + let copy = overflowing!(add_gas_usize(copy, 31)); + let copy_gas = Gas::from(schedule.copy_gas) * (copy / Gas::from(32 as usize)); + let gas = overflowing!(gas.overflow_add(copy_gas)); + let gas = overflowing!(gas.overflow_add(mem_gas)); + Ok((gas, new_mem_size)) + } + } + } + + fn is_zero(&self, val: &Gas) -> bool { + &Gas::from(0) == val + } + + fn mem_needed_const(&self, mem: &U256, add: usize) -> evm::Result { + Gas::from_u256(overflowing!(mem.overflowing_add(U256::from(add)))) + } + + fn mem_needed(&self, offset: &U256, size: &U256) -> evm::Result { + if self.is_zero(&try!(Gas::from_u256(*size))) { + return Ok(Gas::from(0)); + } + + Gas::from_u256(overflowing!(offset.overflowing_add(*size))) + } + + fn mem_gas_cost(&self, schedule: &evm::Schedule, current_mem_size: usize, mem_size: &Gas) -> evm::Result<(Gas, usize)> { + let gas_for_mem = |mem_size: Gas| { + let s = mem_size >> 5; + // s * memory_gas + s * s / quad_coeff_div + let a = overflowing!(s.overflow_mul(Gas::from(schedule.memory_gas))); + // We need to go to U512 to calculate s*s/quad_coeff_div + let b = U512::from(s.as_u256()) * U512::from(s.as_u256()) / U512::from(schedule.quad_coeff_div); + if b > U512::from(!U256::zero()) { + Err(evm::Error::OutOfGas) + } else { + Ok(overflowing!(a.overflow_add(try!(Gas::from_u256(U256::from(b)))))) + } + }; + let current_mem_size = Gas::from(current_mem_size); + let req_mem_size_rounded = (overflowing!(mem_size.overflow_add(Gas::from(31 as usize))) >> 5) << 5; + + let mem_gas_cost = if req_mem_size_rounded > current_mem_size { + let new_mem_gas = try!(gas_for_mem(req_mem_size_rounded)); + let current_mem_gas = try!(gas_for_mem(current_mem_size)); + new_mem_gas - current_mem_gas + } else { + Gas::from(0) + }; + + Ok((mem_gas_cost, req_mem_size_rounded.as_usize())) + } +} + +#[inline] +fn add_gas_usize(value: Gas, num: usize) -> (Gas, bool) { + value.overflow_add(Gas::from(num)) +} + +#[test] +fn test_mem_gas_cost() { + // given + let gasometer = Gasometer::::new(U256::zero()); + let schedule = evm::Schedule::default(); + let current_mem_size = 5; + let mem_size = !U256::zero(); + + // when + let result = gasometer.mem_gas_cost(&schedule, current_mem_size, &mem_size); + + // then + if let Ok(_) = result { + assert!(false, "Should fail with OutOfGas"); + } +} + +#[test] +fn test_calculate_mem_cost() { + // given + let gasometer = Gasometer::::new(0); + let schedule = evm::Schedule::default(); + let current_mem_size = 0; + let mem_size = 5; + + // when + let (mem_cost, mem_size) = gasometer.mem_gas_cost(&schedule, current_mem_size, &mem_size).unwrap(); + + // then + assert_eq!(mem_cost, 3); + assert_eq!(mem_size, 32); +} diff --git a/ethcore/src/evm/interpreter/memory.rs b/ethcore/src/evm/interpreter/memory.rs new file mode 100644 index 000000000..a77a6d494 --- /dev/null +++ b/ethcore/src/evm/interpreter/memory.rs @@ -0,0 +1,150 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity 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. + +// Parity 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 Parity. If not, see . + +use util::{U256, Uint}; + +pub trait Memory { + /// Retrieve current size of the memory + fn size(&self) -> usize; + /// Resize (shrink or expand) the memory to specified size (fills 0) + fn resize(&mut self, new_size: usize); + /// Resize the memory only if its smaller + fn expand(&mut self, new_size: usize); + /// Write single byte to memory + fn write_byte(&mut self, offset: U256, value: U256); + /// Write a word to memory. Does not resize memory! + fn write(&mut self, offset: U256, value: U256); + /// Read a word from memory + fn read(&self, offset: U256) -> U256; + /// Write slice of bytes to memory. Does not resize memory! + fn write_slice(&mut self, offset: U256, &[u8]); + /// Retrieve part of the memory between offset and offset + size + fn read_slice(&self, offset: U256, size: U256) -> &[u8]; + /// Retrieve writeable part of memory + fn writeable_slice(&mut self, offset: U256, size: U256) -> &mut[u8]; + fn dump(&self); +} + +/// Checks whether offset and size is valid memory range +fn is_valid_range(off: usize, size: usize) -> bool { + // When size is zero we haven't actually expanded the memory + let overflow = off.overflowing_add(size).1; + size > 0 && !overflow +} + +impl Memory for Vec { + fn dump(&self) { + println!("MemoryDump:"); + for i in self.iter() { + println!("{:02x} ", i); + } + println!(""); + } + + fn size(&self) -> usize { + self.len() + } + + fn read_slice(&self, init_off_u: U256, init_size_u: U256) -> &[u8] { + let off = init_off_u.low_u64() as usize; + let size = init_size_u.low_u64() as usize; + if !is_valid_range(off, size) { + &self[0..0] + } else { + &self[off..off+size] + } + } + + fn read(&self, offset: U256) -> U256 { + let off = offset.low_u64() as usize; + U256::from(&self[off..off+32]) + } + + fn writeable_slice(&mut self, offset: U256, size: U256) -> &mut [u8] { + let off = offset.low_u64() as usize; + let s = size.low_u64() as usize; + if !is_valid_range(off, s) { + &mut self[0..0] + } else { + &mut self[off..off+s] + } + } + + fn write_slice(&mut self, offset: U256, slice: &[u8]) { + let off = offset.low_u64() as usize; + + // TODO [todr] Optimize? + for pos in off..off+slice.len() { + self[pos] = slice[pos - off]; + } + } + + fn write(&mut self, offset: U256, value: U256) { + let off = offset.low_u64() as usize; + let mut val = value; + + let end = off + 32; + for pos in 0..32 { + self[end - pos - 1] = val.low_u64() as u8; + val = val >> 8; + } + } + + fn write_byte(&mut self, offset: U256, value: U256) { + let off = offset.low_u64() as usize; + let val = value.low_u64() as u64; + self[off] = val as u8; + } + + fn resize(&mut self, new_size: usize) { + self.resize(new_size, 0); + } + + fn expand(&mut self, size: usize) { + if size > self.len() { + Memory::resize(self, size) + } + } +} + + +#[test] +fn test_memory_read_and_write() { + // given + let mem: &mut Memory = &mut vec![]; + mem.resize(0x80 + 32); + + // when + mem.write(U256::from(0x80), U256::from(0xabcdef)); + + // then + assert_eq!(mem.read(U256::from(0x80)), U256::from(0xabcdef)); +} + +#[test] +fn test_memory_read_and_write_byte() { + // given + let mem: &mut Memory = &mut vec![]; + mem.resize(32); + + // when + mem.write_byte(U256::from(0x1d), U256::from(0xab)); + mem.write_byte(U256::from(0x1e), U256::from(0xcd)); + mem.write_byte(U256::from(0x1f), U256::from(0xef)); + + // then + assert_eq!(mem.read(U256::from(0x00)), U256::from(0xabcdef)); +} diff --git a/ethcore/src/evm/interpreter.rs b/ethcore/src/evm/interpreter/mod.rs similarity index 57% rename from ethcore/src/evm/interpreter.rs rename to ethcore/src/evm/interpreter/mod.rs index 5b97b66af..2e03f44b8 100644 --- a/ethcore/src/evm/interpreter.rs +++ b/ethcore/src/evm/interpreter/mod.rs @@ -16,12 +16,6 @@ ///! Rust VM implementation -use common::*; -use super::instructions as instructions; -use super::instructions::{Instruction, get_info}; -use std::marker::Copy; -use evm::{self, MessageCallResult, ContractCreateResult, GasLeft}; - #[cfg(not(feature = "evm-debug"))] macro_rules! evm_debug { ($x: expr) => {} @@ -34,6 +28,19 @@ macro_rules! evm_debug { } } +mod gasometer; +mod stack; +mod memory; + +use self::gasometer::Gasometer; +use self::stack::{Stack, VecStack}; +use self::memory::Memory; + +use std::marker::PhantomData; +use common::*; +use super::instructions::{self, Instruction, InstructionInfo}; +use evm::{self, MessageCallResult, ContractCreateResult, GasLeft, CostType}; + #[cfg(feature = "evm-debug")] fn color(instruction: Instruction, name: &'static str) -> String { let c = instruction as usize % 6; @@ -41,209 +48,9 @@ fn color(instruction: Instruction, name: &'static str) -> String { format!("\x1B[1;{}m{}\x1B[0m", colors[c], name) } -macro_rules! overflowing { - ($x: expr) => {{ - let (v, overflow) = $x; - if overflow { return Err(evm::Error::OutOfGas); } - v - }} -} - type CodePosition = usize; -type Gas = U256; type ProgramCounter = usize; -/// Stack trait with VM-friendly API -trait Stack { - /// Returns `Stack[len(Stack) - no_from_top]` - fn peek(&self, no_from_top: usize) -> &T; - /// Swaps Stack[len(Stack)] and Stack[len(Stack) - no_from_top] - fn swap_with_top(&mut self, no_from_top: usize); - /// Returns true if Stack has at least `no_of_elems` elements - fn has(&self, no_of_elems: usize) -> bool; - /// Get element from top and remove it from Stack. Panics if stack is empty. - fn pop_back(&mut self) -> T; - /// Get (up to `instructions::MAX_NO_OF_TOPICS`) elements from top and remove them from Stack. Panics if stack is empty. - fn pop_n(&mut self, no_of_elems: usize) -> &[T]; - /// Add element on top of the Stack - fn push(&mut self, elem: T); - /// Get number of elements on Stack - fn size(&self) -> usize; - /// Returns all data on stack. - fn peek_top(&mut self, no_of_elems: usize) -> &[T]; -} - -struct VecStack { - stack: Vec, - logs: [S; instructions::MAX_NO_OF_TOPICS] -} - -impl VecStack { - fn with_capacity(capacity: usize, zero: S) -> Self { - VecStack { - stack: Vec::with_capacity(capacity), - logs: [zero; instructions::MAX_NO_OF_TOPICS] - } - } -} - -impl Stack for VecStack { - fn peek(&self, no_from_top: usize) -> &S { - &self.stack[self.stack.len() - no_from_top - 1] - } - - fn swap_with_top(&mut self, no_from_top: usize) { - let len = self.stack.len(); - self.stack.swap(len - no_from_top - 1, len - 1); - } - - fn has(&self, no_of_elems: usize) -> bool { - self.stack.len() >= no_of_elems - } - - fn pop_back(&mut self) -> S { - let val = self.stack.pop(); - match val { - Some(x) => { - evm_debug!({ - println!(" POP: {}", x) - }); - x - }, - None => panic!("Tried to pop from empty stack.") - } - } - - fn pop_n(&mut self, no_of_elems: usize) -> &[S] { - assert!(no_of_elems <= instructions::MAX_NO_OF_TOPICS); - - for i in 0..no_of_elems { - self.logs[i] = self.pop_back(); - } - &self.logs[0..no_of_elems] - } - - fn push(&mut self, elem: S) { - evm_debug!({ - println!(" PUSH: {}", elem) - }); - self.stack.push(elem); - } - - fn size(&self) -> usize { - self.stack.len() - } - - fn peek_top(&mut self, no_from_top: usize) -> &[S] { - assert!(self.stack.len() >= no_from_top, "peek_top asked for more items than exist."); - &self.stack[self.stack.len() - no_from_top .. self.stack.len()] - } -} - -trait Memory { - /// Retrieve current size of the memory - fn size(&self) -> usize; - /// Resize (shrink or expand) the memory to specified size (fills 0) - fn resize(&mut self, new_size: usize); - /// Resize the memory only if its smaller - fn expand(&mut self, new_size: usize); - /// Write single byte to memory - fn write_byte(&mut self, offset: U256, value: U256); - /// Write a word to memory. Does not resize memory! - fn write(&mut self, offset: U256, value: U256); - /// Read a word from memory - fn read(&self, offset: U256) -> U256; - /// Write slice of bytes to memory. Does not resize memory! - fn write_slice(&mut self, offset: U256, &[u8]); - /// Retrieve part of the memory between offset and offset + size - fn read_slice(&self, offset: U256, size: U256) -> &[u8]; - /// Retrieve writeable part of memory - fn writeable_slice(&mut self, offset: U256, size: U256) -> &mut[u8]; - fn dump(&self); -} - -/// Checks whether offset and size is valid memory range -fn is_valid_range(off: usize, size: usize) -> bool { - // When size is zero we haven't actually expanded the memory - let overflow = off.overflowing_add(size).1; - size > 0 && !overflow -} - -impl Memory for Vec { - fn dump(&self) { - println!("MemoryDump:"); - for i in self.iter() { - println!("{:02x} ", i); - } - println!(""); - } - - fn size(&self) -> usize { - self.len() - } - - fn read_slice(&self, init_off_u: U256, init_size_u: U256) -> &[u8] { - let off = init_off_u.low_u64() as usize; - let size = init_size_u.low_u64() as usize; - if !is_valid_range(off, size) { - &self[0..0] - } else { - &self[off..off+size] - } - } - - fn read(&self, offset: U256) -> U256 { - let off = offset.low_u64() as usize; - U256::from(&self[off..off+32]) - } - - fn writeable_slice(&mut self, offset: U256, size: U256) -> &mut [u8] { - let off = offset.low_u64() as usize; - let s = size.low_u64() as usize; - if !is_valid_range(off, s) { - &mut self[0..0] - } else { - &mut self[off..off+s] - } - } - - fn write_slice(&mut self, offset: U256, slice: &[u8]) { - let off = offset.low_u64() as usize; - - // TODO [todr] Optimize? - for pos in off..off+slice.len() { - self[pos] = slice[pos - off]; - } - } - - fn write(&mut self, offset: U256, value: U256) { - let off = offset.low_u64() as usize; - let mut val = value; - - let end = off + 32; - for pos in 0..32 { - self[end - pos - 1] = val.low_u64() as u8; - val = val >> 8; - } - } - - fn write_byte(&mut self, offset: U256, value: U256) { - let off = offset.low_u64() as usize; - let val = value.low_u64() as u64; - self[off] = val as u8; - } - - fn resize(&mut self, new_size: usize) { - self.resize(new_size, 0); - } - - fn expand(&mut self, size: usize) { - if size > self.len() { - Memory::resize(self, size) - } - } -} - /// Abstraction over raw vector of Bytes. Easier state management of PC. struct CodeReader<'a> { position: ProgramCounter, @@ -265,38 +72,33 @@ impl<'a> CodeReader<'a> { } } -#[cfg_attr(feature="dev", allow(enum_variant_names))] -enum InstructionCost { - Gas(U256), - GasMem(U256, U256), - GasMemCopy(U256, U256, U256) -} - -enum InstructionResult { +enum InstructionResult { Ok, UseAllGas, - GasLeft(U256), - UnusedGas(U256), + GasLeft(Gas), + UnusedGas(Gas), JumpToPosition(U256), // gas left, init_orf, init_size - StopExecutionNeedsReturn(U256, U256, U256), + StopExecutionNeedsReturn(Gas, U256, U256), StopExecution, } + /// Intepreter EVM implementation #[derive(Default)] -pub struct Interpreter { +pub struct Interpreter { mem: Vec, + _type: PhantomData, } -impl evm::Evm for Interpreter { +impl evm::Evm for Interpreter { fn exec(&mut self, params: ActionParams, ext: &mut evm::Ext) -> evm::Result { self.mem.clear(); let code = ¶ms.code.as_ref().unwrap(); let valid_jump_destinations = self.find_jump_destinations(&code); - let mut current_gas = params.gas; + let mut gasometer = Gasometer::::new(try!(Cost::from_u256(params.gas))); let mut stack = VecStack::with_capacity(ext.schedule().stack_limit, U256::zero()); let mut reader = CodeReader { position: 0, @@ -305,26 +107,27 @@ impl evm::Evm for Interpreter { while reader.position < code.len() { let instruction = code[reader.position]; - - // Calculate gas cost - let (gas_cost, mem_size) = try!(self.get_gas_cost_mem(ext, instruction, &stack)); - - // TODO: make compile-time removable if too much of a performance hit. - let trace_executed = ext.trace_prepare_execute(reader.position, instruction, &gas_cost); - reader.position += 1; - try!(self.verify_gas(¤t_gas, &gas_cost)); + let info = instructions::get_info(instruction); + try!(self.verify_instruction(ext, instruction, &info, &stack)); + + // Calculate gas cost + let (gas_cost, mem_size) = try!(gasometer.get_gas_cost_mem(ext, instruction, &info, &stack, self.mem.size())); + // TODO: make compile-time removable if too much of a performance hit. + let trace_executed = ext.trace_prepare_execute(reader.position - 1, instruction, &gas_cost.as_u256()); + + try!(gasometer.verify_gas(&gas_cost)); self.mem.expand(mem_size); - current_gas = current_gas - gas_cost; //TODO: use operator -= + gasometer.current_gas = gasometer.current_gas - gas_cost; evm_debug!({ println!("[0x{:x}][{}(0x{:x}) Gas: {:x}\n Gas Before: {:x}", reader.position, - color(instruction, instructions::get_info(instruction).name), + color(instruction, info.name), instruction, gas_cost, - current_gas + gas_cost + gasometer.current_gas + gas_cost ); }); @@ -335,50 +138,44 @@ impl evm::Evm for Interpreter { // Execute instruction let result = try!(self.exec_instruction( - current_gas, ¶ms, ext, instruction, &mut reader, &mut stack + gasometer.current_gas, ¶ms, ext, instruction, &mut reader, &mut stack )); if trace_executed { - ext.trace_executed(current_gas, stack.peek_top(get_info(instruction).ret), mem_written.map(|(o, s)| (o, &(self.mem[o..(o + s)]))), store_written); + ext.trace_executed(gasometer.current_gas.as_u256(), stack.peek_top(info.ret), mem_written.map(|(o, s)| (o, &(self.mem[o..(o + s)]))), store_written); } // Advance match result { InstructionResult::Ok => {}, InstructionResult::UnusedGas(gas) => { - current_gas = current_gas + gas; //TODO: use operator += + gasometer.current_gas = gasometer.current_gas + gas; }, InstructionResult::UseAllGas => { - current_gas = U256::zero(); + gasometer.current_gas = Cost::from(0); }, InstructionResult::GasLeft(gas_left) => { - current_gas = gas_left; + gasometer.current_gas = gas_left; }, InstructionResult::JumpToPosition(position) => { let pos = try!(self.verify_jump(position, &valid_jump_destinations)); reader.position = pos; }, InstructionResult::StopExecutionNeedsReturn(gas, off, size) => { - return Ok(GasLeft::NeedsReturn(gas, self.mem.read_slice(off, size))); + return Ok(GasLeft::NeedsReturn(gas.as_u256(), self.mem.read_slice(off, size))); }, InstructionResult::StopExecution => break, } } - Ok(GasLeft::Known(current_gas)) + Ok(GasLeft::Known(gasometer.current_gas.as_u256())) } } -impl Interpreter { - #[cfg_attr(feature="dev", allow(cyclomatic_complexity))] - fn get_gas_cost_mem( - &mut self, - ext: &evm::Ext, - instruction: Instruction, - stack: &Stack - ) -> evm::Result<(U256, usize)> { +impl Interpreter { + + fn verify_instruction(&self, ext: &evm::Ext, instruction: Instruction, info: &InstructionInfo, stack: &Stack) -> evm::Result<()> { let schedule = ext.schedule(); - let info = instructions::get_info(instruction); if !schedule.have_delegate_call && instruction == instructions::DELEGATECALL { return Err(evm::Error::BadInstruction { @@ -391,119 +188,20 @@ impl Interpreter { }); } - try!(self.verify_instructions_requirements(&info, schedule.stack_limit, stack)); - - let tier = instructions::get_tier_idx(info.tier); - let default_gas = U256::from(schedule.tier_step_gas[tier]); - - let cost = match instruction { - instructions::SSTORE => { - let address = H256::from(stack.peek(0)); - let newval = stack.peek(1); - let val = U256::from(ext.storage_at(&address).as_slice()); - - let gas = if self.is_zero(&val) && !self.is_zero(newval) { - schedule.sstore_set_gas - } else { - // Refund for below case is added when actually executing sstore - // !self.is_zero(&val) && self.is_zero(newval) - schedule.sstore_reset_gas - }; - InstructionCost::Gas(U256::from(gas)) - }, - instructions::SLOAD => { - InstructionCost::Gas(U256::from(schedule.sload_gas)) - }, - instructions::MSTORE | instructions::MLOAD => { - InstructionCost::GasMem(default_gas, try!(self.mem_needed_const(stack.peek(0), 32))) - }, - instructions::MSTORE8 => { - InstructionCost::GasMem(default_gas, try!(self.mem_needed_const(stack.peek(0), 1))) - }, - instructions::RETURN => { - InstructionCost::GasMem(default_gas, try!(self.mem_needed(stack.peek(0), stack.peek(1)))) - }, - instructions::SHA3 => { - let w = overflowing!(add_u256_usize(stack.peek(1), 31)); - let words = w >> 5; - let gas = U256::from(schedule.sha3_gas) + (U256::from(schedule.sha3_word_gas) * words); - InstructionCost::GasMem(gas, try!(self.mem_needed(stack.peek(0), stack.peek(1)))) - }, - instructions::CALLDATACOPY | instructions::CODECOPY => { - InstructionCost::GasMemCopy(default_gas, try!(self.mem_needed(stack.peek(0), stack.peek(2))), stack.peek(2).clone()) - }, - instructions::EXTCODECOPY => { - InstructionCost::GasMemCopy(default_gas, try!(self.mem_needed(stack.peek(1), stack.peek(3))), stack.peek(3).clone()) - }, - instructions::JUMPDEST => { - InstructionCost::Gas(U256::one()) - }, - instructions::LOG0...instructions::LOG4 => { - let no_of_topics = instructions::get_log_topics(instruction); - let log_gas = schedule.log_gas + schedule.log_topic_gas * no_of_topics; - - let data_gas = overflowing!(stack.peek(1).overflowing_mul(U256::from(schedule.log_data_gas))); - let gas = overflowing!(data_gas.overflowing_add(U256::from(log_gas))); - InstructionCost::GasMem(gas, try!(self.mem_needed(stack.peek(0), stack.peek(1)))) - }, - instructions::CALL | instructions::CALLCODE => { - let mut gas = overflowing!(add_u256_usize(stack.peek(0), schedule.call_gas)); - let mem = cmp::max( - try!(self.mem_needed(stack.peek(5), stack.peek(6))), - try!(self.mem_needed(stack.peek(3), stack.peek(4))) - ); - - let address = u256_to_address(stack.peek(1)); - - if instruction == instructions::CALL && !ext.exists(&address) { - gas = overflowing!(gas.overflowing_add(U256::from(schedule.call_new_account_gas))); - }; - - if stack.peek(2).clone() > U256::zero() { - gas = overflowing!(gas.overflowing_add(U256::from(schedule.call_value_transfer_gas))); - }; - - InstructionCost::GasMem(gas,mem) - }, - instructions::DELEGATECALL => { - let gas = overflowing!(add_u256_usize(stack.peek(0), schedule.call_gas)); - let mem = cmp::max( - try!(self.mem_needed(stack.peek(4), stack.peek(5))), - try!(self.mem_needed(stack.peek(2), stack.peek(3))) - ); - InstructionCost::GasMem(gas, mem) - }, - instructions::CREATE => { - let gas = U256::from(schedule.create_gas); - let mem = try!(self.mem_needed(stack.peek(1), stack.peek(2))); - InstructionCost::GasMem(gas, mem) - }, - instructions::EXP => { - let expon = stack.peek(1); - let bytes = ((expon.bits() + 7) / 8) as usize; - let gas = U256::from(schedule.exp_gas + schedule.exp_byte_gas * bytes); - InstructionCost::Gas(gas) - }, - _ => InstructionCost::Gas(default_gas) - }; - - match cost { - InstructionCost::Gas(gas) => { - Ok((gas, 0)) - }, - InstructionCost::GasMem(gas, mem_size) => { - let (mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, self.mem.size(), &mem_size)); - let gas = overflowing!(gas.overflowing_add(mem_gas)); - Ok((gas, new_mem_size)) - }, - InstructionCost::GasMemCopy(gas, mem_size, copy) => { - let (mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, self.mem.size(), &mem_size)); - let copy = overflowing!(add_u256_usize(©, 31)); - let copy_gas = U256::from(schedule.copy_gas) * (copy / U256::from(32)); - let gas = overflowing!(gas.overflowing_add(copy_gas)); - let gas = overflowing!(gas.overflowing_add(mem_gas)); - Ok((gas, new_mem_size)) - } + if !stack.has(info.args) { + Err(evm::Error::StackUnderflow { + instruction: info.name, + wanted: info.args, + on_stack: stack.size() + }) + } else if stack.size() - info.args + info.ret > schedule.stack_limit { + Err(evm::Error::OutOfStack { + instruction: info.name, + wanted: info.ret - info.args, + limit: schedule.stack_limit + }) + } else { + Ok(()) } } @@ -532,53 +230,16 @@ impl Interpreter { } } - fn mem_gas_cost(&self, schedule: &evm::Schedule, current_mem_size: usize, mem_size: &U256) -> evm::Result<(U256, usize)> { - let gas_for_mem = |mem_size: U256| { - let s = mem_size >> 5; - // s * memory_gas + s * s / quad_coeff_div - let a = overflowing!(s.overflowing_mul(U256::from(schedule.memory_gas))); - // We need to go to U512 to calculate s*s/quad_coeff_div - let b = U512::from(s) * U512::from(s) / U512::from(schedule.quad_coeff_div); - if b > U512::from(!U256::zero()) { - Err(evm::Error::OutOfGas) - } else { - Ok(overflowing!(a.overflowing_add(U256::from(b)))) - } - }; - let current_mem_size = U256::from(current_mem_size); - let req_mem_size_rounded = (overflowing!(mem_size.overflowing_add(U256::from(31))) >> 5) << 5; - let new_mem_gas = try!(gas_for_mem(U256::from(req_mem_size_rounded))); - let current_mem_gas = try!(gas_for_mem(current_mem_size)); - - Ok((if req_mem_size_rounded > current_mem_size { - new_mem_gas - current_mem_gas - } else { - U256::zero() - }, req_mem_size_rounded.low_u64() as usize)) - } - - fn mem_needed_const(&self, mem: &U256, add: usize) -> evm::Result { - Ok(overflowing!(mem.overflowing_add(U256::from(add)))) - } - - fn mem_needed(&self, offset: &U256, size: &U256) -> evm::Result { - if self.is_zero(size) { - return Ok(U256::zero()); - } - - Ok(overflowing!(offset.overflowing_add(size.clone()))) - } - #[cfg_attr(feature="dev", allow(too_many_arguments))] fn exec_instruction( &mut self, - gas: Gas, + gas: Cost, params: &ActionParams, ext: &mut evm::Ext, instruction: Instruction, code: &mut CodeReader, stack: &mut Stack - ) -> evm::Result { + ) -> evm::Result> { match instruction { instructions::JUMP => { let jump = stack.pop_back(); @@ -611,11 +272,11 @@ impl Interpreter { return Ok(InstructionResult::Ok); } - let create_result = ext.create(&gas, &endowment, &contract_code); + let create_result = ext.create(&gas.as_u256(), &endowment, &contract_code); return match create_result { ContractCreateResult::Created(address, gas_left) => { stack.push(address_to_u256(address)); - Ok(InstructionResult::GasLeft(gas_left)) + Ok(InstructionResult::GasLeft(Cost::from_u256(gas_left).expect("Gas left cannot be greater."))) }, ContractCreateResult::Failed => { stack.push(U256::zero()); @@ -626,7 +287,7 @@ impl Interpreter { }, instructions::CALL | instructions::CALLCODE | instructions::DELEGATECALL => { assert!(ext.schedule().call_value_transfer_gas > ext.schedule().call_stipend, "overflow possible"); - let call_gas = stack.pop_back(); + let call_gas = Cost::from_u256(stack.pop_back()).expect("Gas is already validated."); let code_address = stack.pop_back(); let code_address = u256_to_address(&code_address); @@ -642,9 +303,9 @@ impl Interpreter { let out_size = stack.pop_back(); // Add stipend (only CALL|CALLCODE when value > 0) - let call_gas = call_gas + value.map_or_else(U256::zero, |val| match val > U256::zero() { - true => U256::from(ext.schedule().call_stipend), - false => U256::zero() + let call_gas = call_gas + value.map_or_else(|| Cost::from(0), |val| match val > U256::zero() { + true => Cost::from(ext.schedule().call_stipend), + false => Cost::from(0) }); // Get sender & receive addresses, check if we have balance @@ -672,13 +333,13 @@ impl Interpreter { // and we don't want to copy let input = unsafe { ::std::mem::transmute(self.mem.read_slice(in_off, in_size)) }; let output = self.mem.writeable_slice(out_off, out_size); - ext.call(&call_gas, sender_address, receive_address, value, input, &code_address, output) + ext.call(&call_gas.as_u256(), sender_address, receive_address, value, input, &code_address, output) }; return match call_result { MessageCallResult::Success(gas_left) => { stack.push(U256::one()); - Ok(InstructionResult::UnusedGas(gas_left)) + Ok(InstructionResult::UnusedGas(Cost::from_u256(gas_left).expect("Gas left cannot be greater then current one"))) }, MessageCallResult::Failed => { stack.push(U256::zero()); @@ -759,7 +420,7 @@ impl Interpreter { stack.push(U256::from(code.position - 1)); }, instructions::GAS => { - stack.push(gas.clone()); + stack.push(gas.as_u256()); }, instructions::ADDRESS => { stack.push(address_to_u256(params.address.clone())); @@ -876,36 +537,6 @@ impl Interpreter { } } - fn verify_instructions_requirements( - &self, - info: &instructions::InstructionInfo, - stack_limit: usize, - stack: &Stack - ) -> evm::Result<()> { - if !stack.has(info.args) { - Err(evm::Error::StackUnderflow { - instruction: info.name, - wanted: info.args, - on_stack: stack.size() - }) - } else if stack.size() - info.args + info.ret > stack_limit { - Err(evm::Error::OutOfStack { - instruction: info.name, - wanted: info.ret - info.args, - limit: stack_limit - }) - } else { - Ok(()) - } - } - - fn verify_gas(&self, current_gas: &U256, gas_cost: &U256) -> evm::Result<()> { - match current_gas < gas_cost { - true => Err(evm::Error::OutOfGas), - false => Ok(()) - } - } - fn verify_jump(&self, jump_u: U256, valid_jump_destinations: &HashSet) -> evm::Result { let jump = jump_u.low_u64() as usize; @@ -1163,11 +794,6 @@ fn set_sign(value: U256, sign: bool) -> U256 { } } -#[inline] -fn add_u256_usize(value: &U256, num: usize) -> (U256, bool) { - value.clone().overflowing_add(U256::from(num)) -} - #[inline] fn u256_to_address(value: &U256) -> Address { Address::from(H256::from(value)) @@ -1179,82 +805,14 @@ fn address_to_u256(value: Address) -> U256 { } #[test] -fn test_mem_gas_cost() { +fn test_find_jump_destinations() { // given - let interpreter = Interpreter::default(); - let schedule = evm::Schedule::default(); - let current_mem_size = 5; - let mem_size = !U256::zero(); + let interpreter = Interpreter::::default(); + let code = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b01600055".from_hex().unwrap(); // when - let result = interpreter.mem_gas_cost(&schedule, current_mem_size, &mem_size); + let valid_jump_destinations = interpreter.find_jump_destinations(&code); // then - if let Ok(_) = result { - assert!(false, "Should fail with OutOfGas"); - } -} - -#[cfg(test)] -mod tests { - use common::*; - use super::*; - use evm; - - #[test] - fn test_find_jump_destinations() { - // given - let interpreter = Interpreter::default(); - let code = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b01600055".from_hex().unwrap(); - - // when - let valid_jump_destinations = interpreter.find_jump_destinations(&code); - - // then - assert!(valid_jump_destinations.contains(&66)); - } - - #[test] - fn test_calculate_mem_cost() { - // given - let interpreter = Interpreter::default(); - let schedule = evm::Schedule::default(); - let current_mem_size = 0; - let mem_size = U256::from(5); - - // when - let (mem_cost, mem_size) = interpreter.mem_gas_cost(&schedule, current_mem_size, &mem_size).unwrap(); - - // then - assert_eq!(mem_cost, U256::from(3)); - assert_eq!(mem_size, 32); - } - - #[test] - fn test_memory_read_and_write() { - // given - let mem: &mut super::Memory = &mut vec![]; - mem.resize(0x80 + 32); - - // when - mem.write(U256::from(0x80), U256::from(0xabcdef)); - - // then - assert_eq!(mem.read(U256::from(0x80)), U256::from(0xabcdef)); - } - - #[test] - fn test_memory_read_and_write_byte() { - // given - let mem: &mut super::Memory = &mut vec![]; - mem.resize(32); - - // when - mem.write_byte(U256::from(0x1d), U256::from(0xab)); - mem.write_byte(U256::from(0x1e), U256::from(0xcd)); - mem.write_byte(U256::from(0x1f), U256::from(0xef)); - - // then - assert_eq!(mem.read(U256::from(0x00)), U256::from(0xabcdef)); - } + assert!(valid_jump_destinations.contains(&66)); } diff --git a/ethcore/src/evm/interpreter/stack.rs b/ethcore/src/evm/interpreter/stack.rs new file mode 100644 index 000000000..98adf8539 --- /dev/null +++ b/ethcore/src/evm/interpreter/stack.rs @@ -0,0 +1,106 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity 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. + +// Parity 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 Parity. If not, see . + +use std::fmt; +use evm::instructions; + +/// Stack trait with VM-friendly API +pub trait Stack { + /// Returns `Stack[len(Stack) - no_from_top]` + fn peek(&self, no_from_top: usize) -> &T; + /// Swaps Stack[len(Stack)] and Stack[len(Stack) - no_from_top] + fn swap_with_top(&mut self, no_from_top: usize); + /// Returns true if Stack has at least `no_of_elems` elements + fn has(&self, no_of_elems: usize) -> bool; + /// Get element from top and remove it from Stack. Panics if stack is empty. + fn pop_back(&mut self) -> T; + /// Get (up to `instructions::MAX_NO_OF_TOPICS`) elements from top and remove them from Stack. Panics if stack is empty. + fn pop_n(&mut self, no_of_elems: usize) -> &[T]; + /// Add element on top of the Stack + fn push(&mut self, elem: T); + /// Get number of elements on Stack + fn size(&self) -> usize; + /// Returns all data on stack. + fn peek_top(&mut self, no_of_elems: usize) -> &[T]; +} + +pub struct VecStack { + stack: Vec, + logs: [S; instructions::MAX_NO_OF_TOPICS] +} + +impl VecStack { + pub fn with_capacity(capacity: usize, zero: S) -> Self { + VecStack { + stack: Vec::with_capacity(capacity), + logs: [zero; instructions::MAX_NO_OF_TOPICS] + } + } +} + +impl Stack for VecStack { + fn peek(&self, no_from_top: usize) -> &S { + &self.stack[self.stack.len() - no_from_top - 1] + } + + fn swap_with_top(&mut self, no_from_top: usize) { + let len = self.stack.len(); + self.stack.swap(len - no_from_top - 1, len - 1); + } + + fn has(&self, no_of_elems: usize) -> bool { + self.stack.len() >= no_of_elems + } + + fn pop_back(&mut self) -> S { + let val = self.stack.pop(); + match val { + Some(x) => { + evm_debug!({ + println!(" POP: {}", x) + }); + x + }, + None => panic!("Tried to pop from empty stack.") + } + } + + fn pop_n(&mut self, no_of_elems: usize) -> &[S] { + assert!(no_of_elems <= instructions::MAX_NO_OF_TOPICS); + + for i in 0..no_of_elems { + self.logs[i] = self.pop_back(); + } + &self.logs[0..no_of_elems] + } + + fn push(&mut self, elem: S) { + evm_debug!({ + println!(" PUSH: {}", elem) + }); + self.stack.push(elem); + } + + fn size(&self) -> usize { + self.stack.len() + } + + fn peek_top(&mut self, no_from_top: usize) -> &[S] { + assert!(self.stack.len() >= no_from_top, "peek_top asked for more items than exist."); + &self.stack[self.stack.len() - no_from_top .. self.stack.len()] + } +} + diff --git a/ethcore/src/evm/mod.rs b/ethcore/src/evm/mod.rs index 5e7b67cfb..de208c6a4 100644 --- a/ethcore/src/evm/mod.rs +++ b/ethcore/src/evm/mod.rs @@ -28,8 +28,10 @@ mod jit; #[cfg(test)] mod tests; +#[cfg(all(feature="benches", test))] +mod benches; -pub use self::evm::{Evm, Error, Finalize, GasLeft, Result}; +pub use self::evm::{Evm, Error, Finalize, GasLeft, Result, CostType}; pub use self::ext::{Ext, ContractCreateResult, MessageCallResult}; pub use self::factory::{Factory, VMType}; pub use self::schedule::Schedule; diff --git a/ethcore/src/evm/tests.rs b/ethcore/src/evm/tests.rs index ba156e6dd..573ffd3b4 100644 --- a/ethcore/src/evm/tests.rs +++ b/ethcore/src/evm/tests.rs @@ -18,18 +18,18 @@ use common::*; use evm::{self, Ext, Schedule, Factory, GasLeft, VMType, ContractCreateResult, MessageCallResult}; use std::fmt::Debug; -struct FakeLogEntry { +pub struct FakeLogEntry { topics: Vec, data: Bytes } #[derive(PartialEq, Eq, Hash, Debug)] -enum FakeCallType { +pub enum FakeCallType { Call, Create } #[derive(PartialEq, Eq, Hash, Debug)] -struct FakeCall { +pub struct FakeCall { call_type: FakeCallType, gas: U256, sender_address: Option
, @@ -43,7 +43,7 @@ struct FakeCall { /// /// Can't do recursive calls. #[derive(Default)] -struct FakeExt { +pub struct FakeExt { sstore_clears: usize, depth: usize, store: HashMap, @@ -67,7 +67,7 @@ fn test_finalize(res: Result) -> Result { } impl FakeExt { - fn new() -> Self { + pub fn new() -> Self { FakeExt::default() } } @@ -181,7 +181,7 @@ fn test_stack_underflow() { let mut ext = FakeExt::new(); let err = { - let mut vm : Box = Box::new(super::interpreter::Interpreter::default()); + let mut vm : Box = Box::new(super::interpreter::Interpreter::::default()); test_finalize(vm.exec(params, &mut ext)).unwrap_err() }; @@ -208,7 +208,7 @@ fn test_add(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -228,7 +228,7 @@ fn test_sha3(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -248,7 +248,7 @@ fn test_address(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -270,7 +270,7 @@ fn test_origin(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -292,7 +292,7 @@ fn test_sender(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -327,7 +327,7 @@ fn test_extcodecopy(factory: super::Factory) { ext.codes.insert(sender, sender_code); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -347,7 +347,7 @@ fn test_log_empty(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -379,7 +379,7 @@ fn test_log_sender(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -404,7 +404,7 @@ fn test_blockhash(factory: super::Factory) { ext.blockhashes.insert(U256::zero(), blockhash.clone()); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -426,7 +426,7 @@ fn test_calldataload(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -447,7 +447,7 @@ fn test_author(factory: super::Factory) { ext.info.author = author; let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -467,7 +467,7 @@ fn test_timestamp(factory: super::Factory) { ext.info.timestamp = timestamp; let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -487,7 +487,7 @@ fn test_number(factory: super::Factory) { ext.info.number = number; let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -507,7 +507,7 @@ fn test_difficulty(factory: super::Factory) { ext.info.difficulty = difficulty; let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -527,7 +527,7 @@ fn test_gas_limit(factory: super::Factory) { ext.info.gas_limit = gas_limit; let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -545,7 +545,7 @@ fn test_mul(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -563,7 +563,7 @@ fn test_sub(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -581,7 +581,7 @@ fn test_div(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -599,7 +599,7 @@ fn test_div_zero(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -617,7 +617,7 @@ fn test_mod(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -636,7 +636,7 @@ fn test_smod(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -655,7 +655,7 @@ fn test_sdiv(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -674,7 +674,7 @@ fn test_exp(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -694,7 +694,7 @@ fn test_comparison(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -715,7 +715,7 @@ fn test_signed_comparison(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -736,7 +736,7 @@ fn test_bitops(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -759,7 +759,7 @@ fn test_addmod_mulmod(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -780,7 +780,7 @@ fn test_byte(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -799,7 +799,7 @@ fn test_signextend(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -819,7 +819,7 @@ fn test_badinstruction_int() { let mut ext = FakeExt::new(); let err = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap_err() }; @@ -839,7 +839,7 @@ fn test_pop(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -859,7 +859,7 @@ fn test_extops(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -882,7 +882,7 @@ fn test_jumps(factory: super::Factory) { let mut ext = FakeExt::new(); let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; @@ -911,7 +911,7 @@ fn test_calls(factory: super::Factory) { }; let gas_left = { - let mut vm = factory.create(); + let mut vm = factory.create(params.gas); test_finalize(vm.exec(params, &mut ext)).unwrap() }; diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 4525d6a24..f5ff95d71 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -211,7 +211,7 @@ impl<'a> Executive<'a> { let vm_factory = self.vm_factory; let mut ext = self.as_externalities(OriginInfo::from(¶ms), unconfirmed_substate, output_policy, tracer, vm_tracer); trace!(target: "executive", "ext.schedule.have_delegate_call: {}", ext.schedule().have_delegate_call); - return vm_factory.create().exec(params, &mut ext).finalize(ext); + return vm_factory.create(params.gas).exec(params, &mut ext).finalize(ext); } // Start in new thread to reset stack @@ -222,7 +222,7 @@ impl<'a> Executive<'a> { let mut ext = self.as_externalities(OriginInfo::from(¶ms), unconfirmed_substate, output_policy, tracer, vm_tracer); scope.spawn(move || { - vm_factory.create().exec(params, &mut ext).finalize(ext) + vm_factory.create(params.gas).exec(params, &mut ext).finalize(ext) }) }).join() } diff --git a/ethcore/src/json_tests/executive.rs b/ethcore/src/json_tests/executive.rs index f4a34a33e..28dde2a38 100644 --- a/ethcore/src/json_tests/executive.rs +++ b/ethcore/src/json_tests/executive.rs @@ -208,7 +208,7 @@ fn do_json_test_for(vm_type: &VMType, json_data: &[u8]) -> Vec { &mut tracer, &mut vm_tracer, ); - let mut evm = vm_factory.create(); + let mut evm = vm_factory.create(params.gas); let res = evm.exec(params, &mut ex); // a return in finalize will not alter callcreates let callcreates = ex.callcreates.clone(); diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index 54a944331..183a3c251 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -31,6 +31,7 @@ #![cfg_attr(feature="dev", allow(needless_borrow))] #![cfg_attr(feature="dev", allow(assign_op_pattern))] +#![cfg_attr(feature="benches", feature(test))] //! Ethcore library //!