Skip to content
This repository was archived by the owner on May 24, 2022. It is now read-only.

Commit f73d714

Browse files
adria0vorot93
authored andcommitted
Implement Simple subroutines (EIP-2315)
1 parent a348842 commit f73d714

File tree

9 files changed

+443
-40
lines changed

9 files changed

+443
-40
lines changed

ethcore/evm/src/instructions.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,13 @@ enum_with_from_u8! {
321321
#[doc = "Makes a log entry, 4 topics."]
322322
LOG4 = 0xa4,
323323

324+
#[doc = "Marks the entry point to a subroutine."]
325+
BEGINSUB = 0x5c,
326+
#[doc = "Returns from a subroutine."]
327+
RETURNSUB = 0x5d,
328+
#[doc = "Jumps to a defined BEGINSUB subroutine."]
329+
JUMPSUB = 0x5e,
330+
324331
#[doc = "create a new account with associated code"]
325332
CREATE = 0xf0,
326333
#[doc = "message-call into an account"]
@@ -591,6 +598,9 @@ lazy_static! {
591598
arr[LOG2 as usize] = Some(InstructionInfo::new("LOG2", 4, 0, GasPriceTier::Special));
592599
arr[LOG3 as usize] = Some(InstructionInfo::new("LOG3", 5, 0, GasPriceTier::Special));
593600
arr[LOG4 as usize] = Some(InstructionInfo::new("LOG4", 6, 0, GasPriceTier::Special));
601+
arr[BEGINSUB as usize] = Some(InstructionInfo::new("BEGINSUB", 0, 0, GasPriceTier::Base));
602+
arr[JUMPSUB as usize] = Some(InstructionInfo::new("JUMPSUB", 1, 0, GasPriceTier::High));
603+
arr[RETURNSUB as usize] = Some(InstructionInfo::new("RETURNSUB", 0, 0, GasPriceTier::Low));
594604
arr[CREATE as usize] = Some(InstructionInfo::new("CREATE", 3, 1, GasPriceTier::Special));
595605
arr[CALL as usize] = Some(InstructionInfo::new("CALL", 7, 1, GasPriceTier::Special));
596606
arr[CALLCODE as usize] = Some(InstructionInfo::new("CALLCODE", 7, 1, GasPriceTier::Special));

ethcore/evm/src/interpreter/mod.rs

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ const TWO_POW_96: U256 = U256([0, 0x100000000, 0, 0]); //0x1 00000000 00000000 0
6161
const TWO_POW_224: U256 = U256([0, 0, 0, 0x100000000]); //0x1 00000000 00000000 00000000 00000000 00000000 00000000 00000000
6262
const TWO_POW_248: U256 = U256([0, 0, 0, 0x100000000000000]); //0x1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 000000
6363

64+
/// Maximum subroutine stack size as specified in
65+
/// https://eips.ethereum.org/EIPS/eip-2315.
66+
pub const MAX_SUB_STACK_SIZE: usize = 1023;
67+
6468
fn to_biguint(x: U256) -> BigUint {
6569
let mut bytes = [0u8; 32];
6670
x.to_little_endian(&mut bytes);
@@ -101,6 +105,8 @@ enum InstructionResult<Gas> {
101105
Ok,
102106
UnusedGas(Gas),
103107
JumpToPosition(U256),
108+
JumpToSubroutine(U256),
109+
ReturnFromSubroutine(usize),
104110
StopExecutionNeedsReturn {
105111
/// Gas left.
106112
gas: Gas,
@@ -183,8 +189,10 @@ pub struct Interpreter<Cost: CostType> {
183189
do_trace: bool,
184190
done: bool,
185191
valid_jump_destinations: Option<Arc<BitSet>>,
192+
valid_subroutine_destinations: Option<Arc<BitSet>>,
186193
gasometer: Option<Gasometer<Cost>>,
187194
stack: VecStack<U256>,
195+
return_stack: Vec<usize>,
188196
resume_output_range: Option<(U256, U256)>,
189197
resume_result: Option<InstructionResult<Cost>>,
190198
last_stack_ret_len: usize,
@@ -290,19 +298,23 @@ impl<Cost: CostType> Interpreter<Cost> {
290298
let params = InterpreterParams::from(params);
291299
let informant = informant::EvmInformant::new(depth);
292300
let valid_jump_destinations = None;
301+
let valid_subroutine_destinations = None;
293302
let gasometer = Cost::from_u256(params.gas)
294303
.ok()
295304
.map(|gas| Gasometer::<Cost>::new(gas));
296305
let stack = VecStack::with_capacity(schedule.stack_limit, U256::zero());
306+
let return_stack = Vec::with_capacity(MAX_SUB_STACK_SIZE);
297307

298308
Interpreter {
299309
cache,
300310
params,
301311
reader,
302312
informant,
303313
valid_jump_destinations,
314+
valid_subroutine_destinations,
304315
gasometer,
305316
stack,
317+
return_stack,
306318
done: false,
307319
// Overridden in `step_inner` based on
308320
// the result of `ext.trace_next_instruction`.
@@ -478,7 +490,8 @@ impl<Cost: CostType> Interpreter<Cost> {
478490
if self.valid_jump_destinations.is_none() {
479491
self.valid_jump_destinations = Some(
480492
self.cache
481-
.jump_destinations(&self.params.code_hash, &self.reader.code),
493+
.jump_and_sub_destinations(&self.params.code_hash, &self.reader.code)
494+
.0,
482495
);
483496
}
484497
let jump_destinations = self
@@ -491,6 +504,28 @@ impl<Cost: CostType> Interpreter<Cost> {
491504
};
492505
self.reader.position = pos;
493506
}
507+
InstructionResult::JumpToSubroutine(position) => {
508+
if self.valid_subroutine_destinations.is_none() {
509+
self.valid_subroutine_destinations = Some(
510+
self.cache
511+
.jump_and_sub_destinations(&self.params.code_hash, &self.reader.code)
512+
.1,
513+
);
514+
}
515+
let subroutine_destinations = self
516+
.valid_subroutine_destinations
517+
.as_ref()
518+
.expect("subroutine_destinations are initialized on first jump; qed");
519+
let pos = match self.verify_jump(position, subroutine_destinations) {
520+
Ok(x) => x,
521+
Err(e) => return InterpreterResult::Done(Err(e)),
522+
};
523+
self.return_stack.push(self.reader.position);
524+
self.reader.position = pos + 1;
525+
}
526+
InstructionResult::ReturnFromSubroutine(pos) => {
527+
self.reader.position = pos;
528+
}
494529
InstructionResult::StopExecutionNeedsReturn {
495530
gas,
496531
init_off,
@@ -537,20 +572,20 @@ impl<Cost: CostType> Interpreter<Cost> {
537572
) -> vm::Result<()> {
538573
let schedule = ext.schedule();
539574

540-
if (instruction == instructions::DELEGATECALL && !schedule.have_delegate_call)
541-
|| (instruction == instructions::CREATE2 && !schedule.have_create2)
542-
|| (instruction == instructions::STATICCALL && !schedule.have_static_call)
543-
|| ((instruction == instructions::RETURNDATACOPY
544-
|| instruction == instructions::RETURNDATASIZE)
575+
use instructions::*;
576+
if (instruction == DELEGATECALL && !schedule.have_delegate_call)
577+
|| (instruction == CREATE2 && !schedule.have_create2)
578+
|| (instruction == STATICCALL && !schedule.have_static_call)
579+
|| ((instruction == RETURNDATACOPY || instruction == RETURNDATASIZE)
545580
&& !schedule.have_return_data)
546-
|| (instruction == instructions::REVERT && !schedule.have_revert)
547-
|| ((instruction == instructions::SHL
548-
|| instruction == instructions::SHR
549-
|| instruction == instructions::SAR)
581+
|| (instruction == REVERT && !schedule.have_revert)
582+
|| ((instruction == SHL || instruction == SHR || instruction == SAR)
550583
&& !schedule.have_bitwise_shifting)
551-
|| (instruction == instructions::EXTCODEHASH && !schedule.have_extcodehash)
552-
|| (instruction == instructions::CHAINID && !schedule.have_chain_id)
553-
|| (instruction == instructions::SELFBALANCE && !schedule.have_selfbalance)
584+
|| (instruction == EXTCODEHASH && !schedule.have_extcodehash)
585+
|| (instruction == CHAINID && !schedule.have_chain_id)
586+
|| (instruction == SELFBALANCE && !schedule.have_selfbalance)
587+
|| ((instruction == BEGINSUB || instruction == JUMPSUB || instruction == RETURNSUB)
588+
&& !schedule.have_subs)
554589
{
555590
return Err(vm::Error::BadInstruction {
556591
instruction: instruction as u8,
@@ -623,6 +658,29 @@ impl<Cost: CostType> Interpreter<Cost> {
623658
instructions::JUMPDEST => {
624659
// ignore
625660
}
661+
instructions::BEGINSUB => {
662+
return Err(vm::Error::InvalidSubEntry);
663+
}
664+
instructions::JUMPSUB => {
665+
if self.return_stack.len() >= MAX_SUB_STACK_SIZE {
666+
return Err(vm::Error::OutOfSubStack {
667+
wanted: 1,
668+
limit: MAX_SUB_STACK_SIZE,
669+
});
670+
}
671+
let sub_destination = self.stack.pop_back();
672+
return Ok(InstructionResult::JumpToSubroutine(sub_destination));
673+
}
674+
instructions::RETURNSUB => {
675+
if let Some(pos) = self.return_stack.pop() {
676+
return Ok(InstructionResult::ReturnFromSubroutine(pos));
677+
} else {
678+
return Err(vm::Error::SubStackUnderflow {
679+
wanted: 1,
680+
on_stack: 0,
681+
});
682+
}
683+
}
626684
instructions::CREATE | instructions::CREATE2 => {
627685
let endowment = self.stack.pop_back();
628686
let init_off = self.stack.pop_back();
@@ -1413,6 +1471,7 @@ impl<Cost: CostType> Interpreter<Cost> {
14131471
if valid_jump_destinations.contains(jump) && U256::from(jump) == jump_u {
14141472
Ok(jump)
14151473
} else {
1474+
// Note: if jump > usize, BadJumpDestination value is trimmed
14161475
Err(vm::Error::BadJumpDestination { destination: jump })
14171476
}
14181477
}

ethcore/evm/src/interpreter/shared_cache.rs

Lines changed: 128 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use std::sync::Arc;
2626
const DEFAULT_CACHE_SIZE: usize = 4 * 1024 * 1024;
2727

2828
// stub for a HeapSizeOf implementation.
29+
#[derive(Clone)]
2930
struct Bits(Arc<BitSet>);
3031

3132
impl HeapSizeOf for Bits {
@@ -35,9 +36,21 @@ impl HeapSizeOf for Bits {
3536
}
3637
}
3738

39+
#[derive(Clone)]
40+
struct CacheItem {
41+
jump_destination: Bits,
42+
sub_entrypoint: Bits,
43+
}
44+
45+
impl HeapSizeOf for CacheItem {
46+
fn heap_size_of_children(&self) -> usize {
47+
self.jump_destination.heap_size_of_children() + self.sub_entrypoint.heap_size_of_children()
48+
}
49+
}
50+
3851
/// Global cache for EVM interpreter
3952
pub struct SharedCache {
40-
jump_destinations: Mutex<MemoryLruCache<H256, Bits>>,
53+
jump_destinations: Mutex<MemoryLruCache<H256, CacheItem>>,
4154
}
4255

4356
impl SharedCache {
@@ -50,47 +63,62 @@ impl SharedCache {
5063
}
5164

5265
/// Get jump destinations bitmap for a contract.
53-
pub fn jump_destinations(&self, code_hash: &Option<H256>, code: &[u8]) -> Arc<BitSet> {
66+
pub fn jump_and_sub_destinations(
67+
&self,
68+
code_hash: &Option<H256>,
69+
code: &[u8],
70+
) -> (Arc<BitSet>, Arc<BitSet>) {
5471
if let Some(ref code_hash) = code_hash {
5572
if code_hash == &KECCAK_EMPTY {
56-
return Self::find_jump_destinations(code);
73+
let cache_item = Self::find_jump_and_sub_destinations(code);
74+
return (cache_item.jump_destination.0, cache_item.sub_entrypoint.0);
5775
}
5876

5977
if let Some(d) = self.jump_destinations.lock().get_mut(code_hash) {
60-
return d.0.clone();
78+
return (d.jump_destination.0.clone(), d.sub_entrypoint.0.clone());
6179
}
6280
}
6381

64-
let d = Self::find_jump_destinations(code);
82+
let d = Self::find_jump_and_sub_destinations(code);
6583

6684
if let Some(ref code_hash) = code_hash {
67-
self.jump_destinations
68-
.lock()
69-
.insert(*code_hash, Bits(d.clone()));
85+
self.jump_destinations.lock().insert(*code_hash, d.clone());
7086
}
7187

72-
d
88+
(d.jump_destination.0, d.sub_entrypoint.0)
7389
}
7490

75-
fn find_jump_destinations(code: &[u8]) -> Arc<BitSet> {
91+
fn find_jump_and_sub_destinations(code: &[u8]) -> CacheItem {
7692
let mut jump_dests = BitSet::with_capacity(code.len());
93+
let mut sub_entrypoints = BitSet::with_capacity(code.len());
7794
let mut position = 0;
7895

7996
while position < code.len() {
8097
let instruction = Instruction::from_u8(code[position]);
8198

8299
if let Some(instruction) = instruction {
83-
if instruction == instructions::JUMPDEST {
84-
jump_dests.insert(position);
85-
} else if let Some(push_bytes) = instruction.push_bytes() {
86-
position += push_bytes;
100+
match instruction {
101+
instructions::JUMPDEST => {
102+
jump_dests.insert(position);
103+
}
104+
instructions::BEGINSUB => {
105+
sub_entrypoints.insert(position);
106+
}
107+
_ => {
108+
if let Some(push_bytes) = instruction.push_bytes() {
109+
position += push_bytes;
110+
}
111+
}
87112
}
88113
}
89114
position += 1;
90115
}
91116

92117
jump_dests.shrink_to_fit();
93-
Arc::new(jump_dests)
118+
CacheItem {
119+
jump_destination: Bits(Arc::new(jump_dests)),
120+
sub_entrypoint: Bits(Arc::new(sub_entrypoints)),
121+
}
94122
}
95123
}
96124

@@ -100,15 +128,91 @@ impl Default for SharedCache {
100128
}
101129
}
102130

103-
#[test]
104-
fn test_find_jump_destinations() {
105-
use rustc_hex::FromHex;
106-
// given
107-
let code = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b01600055".from_hex().unwrap();
131+
#[cfg(test)]
132+
mod test {
133+
use super::*;
134+
135+
#[test]
136+
fn test_find_jump_destinations() {
137+
// given
138+
139+
// 0000 7F PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
140+
// 0021 7F PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
141+
// 0042 5B JUMPDEST
142+
// 0043 01 ADD
143+
// 0044 60 PUSH1 0x00
144+
// 0046 55 SSTORE
145+
let code = hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b01600055");
146+
147+
// when
148+
let cache_item = SharedCache::find_jump_and_sub_destinations(&code);
149+
150+
// then
151+
assert!(cache_item
152+
.jump_destination
153+
.0
154+
.iter()
155+
.eq(vec![66].into_iter()));
156+
assert!(cache_item.sub_entrypoint.0.is_empty());
157+
}
158+
159+
#[test]
160+
fn test_find_jump_destinations_not_in_data_segments() {
161+
// given
162+
163+
// 0000 60 06 PUSH1 06
164+
// 0002 56 JUMP
165+
// 0003 50 5B PUSH1 0x5B
166+
// 0005 56 STOP
167+
// 0006 5B JUMPDEST
168+
// 0007 60 04 PUSH1 04
169+
// 0009 56 JUMP
170+
let code = hex!("600656605B565B6004");
171+
172+
// when
173+
let cache_item = SharedCache::find_jump_and_sub_destinations(&code);
174+
175+
// then
176+
assert!(cache_item.jump_destination.0.iter().eq(vec![6].into_iter()));
177+
assert!(cache_item.sub_entrypoint.0.is_empty());
178+
}
108179

109-
// when
110-
let valid_jump_destinations = SharedCache::find_jump_destinations(&code);
180+
#[test]
181+
fn test_find_sub_entrypoints() {
182+
// given
111183

112-
// then
113-
assert!(valid_jump_destinations.contains(66));
184+
// see https://eips.ethereum.org/EIPS/eip-2315 for disassembly
185+
let code = hex!("6800000000000000000c5e005c60115e5d5c5d");
186+
187+
// when
188+
let cache_item = SharedCache::find_jump_and_sub_destinations(&code);
189+
190+
// then
191+
assert!(cache_item.jump_destination.0.is_empty());
192+
assert!(cache_item
193+
.sub_entrypoint
194+
.0
195+
.iter()
196+
.eq(vec![12, 17].into_iter()));
197+
}
198+
199+
#[test]
200+
fn test_find_jump_and_sub_allowing_unknown_opcodes() {
201+
// precondition
202+
assert!(Instruction::from_u8(0xcc) == None);
203+
204+
// given
205+
206+
// 0000 5B JUMPDEST
207+
// 0001 CC ???
208+
// 0002 5C BEGINSUB
209+
let code = hex!("5BCC5C");
210+
211+
// when
212+
let cache_item = SharedCache::find_jump_and_sub_destinations(&code);
213+
214+
// then
215+
assert!(cache_item.jump_destination.0.iter().eq(vec![0].into_iter()));
216+
assert!(cache_item.sub_entrypoint.0.iter().eq(vec![2].into_iter()));
217+
}
114218
}

0 commit comments

Comments
 (0)