Skip to content

Commit

Permalink
Implement Simple Subroutines for the EVM (EIP 2315) (openethereum#11629)
Browse files Browse the repository at this point in the history
* Implement Simple Subroutines for the EVM (EIP 2315)

* Finished base impl, added tests

* Add EIP2315 into berlin chainspec

* Update ethcore/evm/src/interpreter/shared_cache.rs

Co-Authored-By: Andronik Ordian <[email protected]>

* Update ethcore/trace/src/types/error.rs

Co-Authored-By: Andronik Ordian <[email protected]>

* Update ethcore/types/src/engines/params.rs

Co-Authored-By: Andronik Ordian <[email protected]>

* Update ethcore/trace/src/types/error.rs

Co-Authored-By: Andronik Ordian <[email protected]>

* fix gas tier & tests

* Update ethcore/evm/src/interpreter/mod.rs

Co-Authored-By: Andronik Ordian <[email protected]>

* Comment substack_limit test

* Revert using U256 for bad jump size reporting

Co-authored-by: adria0.eth <[email protected]>
Co-authored-by: adria0 <[email protected]>
Co-authored-by: Andronik Ordian <[email protected]>
  • Loading branch information
4 people authored May 15, 2020
1 parent 1ace3c7 commit 234a287
Show file tree
Hide file tree
Showing 12 changed files with 359 additions and 30 deletions.
11 changes: 10 additions & 1 deletion ethcore/evm/src/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ macro_rules! enum_with_from_u8 {
}
};
}

enum_with_from_u8! {
#[doc = "Virtual machine bytecode instruction."]
#[repr(u8)]
Expand Down Expand Up @@ -321,6 +320,13 @@ enum_with_from_u8! {
#[doc = "Makes a log entry, 4 topics."]
LOG4 = 0xa4,

#[doc = "Marks the entry point to a subroutine."]
BEGINSUB = 0xb2,
#[doc = "Jumps to a defined BEGINSUB subroutine."]
JUMPSUB = 0xb3,
#[doc = "Returns from a subroutine."]
RETURNSUB = 0xb7,

#[doc = "create a new account with associated code"]
CREATE = 0xf0,
#[doc = "message-call into an account"]
Expand Down Expand Up @@ -586,6 +592,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::Low));
arr[RETURNSUB as usize] = Some(InstructionInfo::new("RETURNSUB", 0, 0, GasPriceTier::VeryLow));
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));
Expand Down
78 changes: 66 additions & 12 deletions ethcore/evm/src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,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

/// Maximal subroutine stack size as specified in
/// https://eips.ethereum.org/EIPS/eip-2315.
pub const MAX_SUB_STACK_SIZE : usize = 1024;

/// Abstraction over raw vector of Bytes. Easier state management of PC.
struct CodeReader {
position: ProgramCounter,
Expand Down Expand Up @@ -94,6 +98,8 @@ enum InstructionResult<Gas> {
Ok,
UnusedGas(Gas),
JumpToPosition(U256),
JumpToSubroutine(U256),
ReturnFromSubroutine(usize),
StopExecutionNeedsReturn {
/// Gas left.
gas: Gas,
Expand Down Expand Up @@ -179,8 +185,10 @@ pub struct Interpreter<Cost: CostType> {
do_trace: bool,
done: bool,
valid_jump_destinations: Option<Arc<BitSet>>,
valid_subroutine_destinations: Option<Arc<BitSet>>,
gasometer: Option<Gasometer<Cost>>,
stack: VecStack<U256>,
return_stack: Vec<usize>,
resume_output_range: Option<(U256, U256)>,
resume_result: Option<InstructionResult<Cost>>,
last_stack_ret_len: usize,
Expand Down Expand Up @@ -271,12 +279,14 @@ impl<Cost: CostType> Interpreter<Cost> {
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::<Cost>::new(gas));
let stack = VecStack::with_capacity(schedule.stack_limit, U256::zero());

let return_stack = Vec::with_capacity(MAX_SUB_STACK_SIZE);
Interpreter {
cache, params, reader, informant,
valid_jump_destinations, gasometer, stack,
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`.
Expand Down Expand Up @@ -403,7 +413,7 @@ impl<Cost: CostType> Interpreter<Cost> {
match result {
InstructionResult::JumpToPosition(position) => {
if self.valid_jump_destinations.is_none() {
self.valid_jump_destinations = Some(self.cache.jump_destinations(&self.params.code_hash, &self.reader.code));
self.valid_jump_destinations = Some(self.cache.jump_and_sub_destinations(&self.params.code_hash, &self.reader.code).0);
}
let jump_destinations = self.valid_jump_destinations.as_ref().expect("jump_destinations are initialized on first jump; qed");
let pos = match self.verify_jump(position, jump_destinations) {
Expand All @@ -412,6 +422,21 @@ impl<Cost: CostType> Interpreter<Cost> {
};
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;
},
InstructionResult::ReturnFromSubroutine(pos) => {
self.reader.position = pos;
},
InstructionResult::StopExecutionNeedsReturn {gas, init_off, init_size, apply} => {
let mem = mem::replace(&mut self.mem, Vec::new());
return InterpreterResult::Done(Ok(GasLeft::NeedsReturn {
Expand All @@ -436,15 +461,17 @@ impl<Cost: CostType> Interpreter<Cost> {
fn verify_instruction(&self, ext: &dyn vm::Ext, instruction: Instruction, info: &InstructionInfo) -> 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) && !schedule.have_return_data) ||
(instruction == instructions::REVERT && !schedule.have_revert) ||
((instruction == instructions::SHL || instruction == instructions::SHR || instruction == instructions::SAR) && !schedule.have_bitwise_shifting) ||
(instruction == instructions::EXTCODEHASH && !schedule.have_extcodehash) ||
(instruction == instructions::CHAINID && !schedule.have_chain_id) ||
(instruction == instructions::SELFBALANCE && !schedule.have_selfbalance)
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 == REVERT && !schedule.have_revert) ||
((instruction == SHL || instruction == SHR || instruction == SAR) && !schedule.have_bitwise_shifting) ||
(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
Expand Down Expand Up @@ -525,6 +552,32 @@ impl<Cost: CostType> Interpreter<Cost> {
instructions::JUMPDEST => {
// ignore
},
instructions::BEGINSUB => {
// ignore
},
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();
Expand Down Expand Up @@ -1177,6 +1230,7 @@ impl<Cost: CostType> Interpreter<Cost> {
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
})
Expand Down
109 changes: 94 additions & 15 deletions ethcore/evm/src/interpreter/shared_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const DEFAULT_CACHE_SIZE: usize = 4 * 1024 * 1024;

/// Stub for a sharing `BitSet` data in cache (reference counted)
/// and implementing MallocSizeOf on it.
#[derive(Clone)]
struct Bits(Arc<BitSet>);

impl MallocSizeOf for Bits {
Expand All @@ -36,9 +37,14 @@ impl MallocSizeOf for Bits {
}
}

#[derive(MallocSizeOf, Clone)]
struct CacheItem {
jump_destination: Bits,
sub_entrypoint: Bits,
}
/// Global cache for EVM interpreter
pub struct SharedCache {
jump_destinations: Mutex<MemoryLruCache<H256, Bits>>,
jump_destinations: Mutex<MemoryLruCache<H256, CacheItem>>,
}

impl SharedCache {
Expand All @@ -51,45 +57,54 @@ impl SharedCache {
}

/// Get jump destinations bitmap for a contract.
pub fn jump_destinations(&self, code_hash: &Option<H256>, code: &[u8]) -> Arc<BitSet> {
pub fn jump_and_sub_destinations(&self, code_hash: &Option<H256>, code: &[u8]) -> (Arc<BitSet>, Arc<BitSet>) {
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<BitSet> {
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)),
}
}
}

Expand All @@ -102,11 +117,75 @@ impl Default for SharedCache {
#[test]
fn test_find_jump_destinations() {
// given

// 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 valid_jump_destinations = SharedCache::find_jump_destinations(&code);
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!("6800000000000000000cb300b26011b3b7b2b7");

// 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 B2 BEGINSUB
let code = hex!("5BCCB2");

// when
let cache_item = SharedCache::find_jump_and_sub_destinations(&code);

// then
assert!(valid_jump_destinations.contains(66));
assert!(cache_item.jump_destination.0.iter().eq(vec![0].into_iter()));
assert!(cache_item.sub_entrypoint.0.iter().eq(vec![2].into_iter()));
}
Loading

0 comments on commit 234a287

Please sign in to comment.