EIP-2315: Simple Subroutines for the EVM (#11629)

This commit is contained in:
adria0.eth 2020-08-07 20:41:07 +03:00 committed by Artem Vorotnikov
parent 751210c963
commit 1460f6cc27
No known key found for this signature in database
GPG Key ID: E0148C3F2FBB7A20
11 changed files with 452 additions and 40 deletions

View File

@ -321,6 +321,13 @@ enum_with_from_u8! {
#[doc = "Makes a log entry, 4 topics."] #[doc = "Makes a log entry, 4 topics."]
LOG4 = 0xa4, 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"] #[doc = "create a new account with associated code"]
CREATE = 0xf0, CREATE = 0xf0,
#[doc = "message-call into an account"] #[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[LOG2 as usize] = Some(InstructionInfo::new("LOG2", 4, 0, GasPriceTier::Special));
arr[LOG3 as usize] = Some(InstructionInfo::new("LOG3", 5, 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[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[CREATE as usize] = Some(InstructionInfo::new("CREATE", 3, 1, GasPriceTier::Special));
arr[CALL as usize] = Some(InstructionInfo::new("CALL", 7, 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)); arr[CALLCODE as usize] = Some(InstructionInfo::new("CALLCODE", 7, 1, GasPriceTier::Special));

View File

@ -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_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 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 { fn to_biguint(x: U256) -> BigUint {
let mut bytes = [0u8; 32]; let mut bytes = [0u8; 32];
x.to_little_endian(&mut bytes); x.to_little_endian(&mut bytes);
@ -101,6 +105,8 @@ enum InstructionResult<Gas> {
Ok, Ok,
UnusedGas(Gas), UnusedGas(Gas),
JumpToPosition(U256), JumpToPosition(U256),
JumpToSubroutine(U256),
ReturnFromSubroutine(usize),
StopExecutionNeedsReturn { StopExecutionNeedsReturn {
/// Gas left. /// Gas left.
gas: Gas, gas: Gas,
@ -183,8 +189,10 @@ pub struct Interpreter<Cost: CostType> {
do_trace: bool, do_trace: bool,
done: bool, done: bool,
valid_jump_destinations: Option<Arc<BitSet>>, valid_jump_destinations: Option<Arc<BitSet>>,
valid_subroutine_destinations: Option<Arc<BitSet>>,
gasometer: Option<Gasometer<Cost>>, gasometer: Option<Gasometer<Cost>>,
stack: VecStack<U256>, stack: VecStack<U256>,
return_stack: Vec<usize>,
resume_output_range: Option<(U256, U256)>, resume_output_range: Option<(U256, U256)>,
resume_result: Option<InstructionResult<Cost>>, resume_result: Option<InstructionResult<Cost>>,
last_stack_ret_len: usize, last_stack_ret_len: usize,
@ -290,10 +298,12 @@ impl<Cost: CostType> Interpreter<Cost> {
let params = InterpreterParams::from(params); let params = InterpreterParams::from(params);
let informant = informant::EvmInformant::new(depth); let informant = informant::EvmInformant::new(depth);
let valid_jump_destinations = None; let valid_jump_destinations = None;
let valid_subroutine_destinations = None;
let gasometer = Cost::from_u256(params.gas) let gasometer = Cost::from_u256(params.gas)
.ok() .ok()
.map(|gas| Gasometer::<Cost>::new(gas)); .map(|gas| Gasometer::<Cost>::new(gas));
let stack = VecStack::with_capacity(schedule.stack_limit, U256::zero()); let stack = VecStack::with_capacity(schedule.stack_limit, U256::zero());
let return_stack = Vec::with_capacity(MAX_SUB_STACK_SIZE);
Interpreter { Interpreter {
cache, cache,
@ -301,8 +311,10 @@ impl<Cost: CostType> Interpreter<Cost> {
reader, reader,
informant, informant,
valid_jump_destinations, valid_jump_destinations,
valid_subroutine_destinations,
gasometer, gasometer,
stack, stack,
return_stack,
done: false, done: false,
// Overridden in `step_inner` based on // Overridden in `step_inner` based on
// the result of `ext.trace_next_instruction`. // the result of `ext.trace_next_instruction`.
@ -478,7 +490,8 @@ impl<Cost: CostType> Interpreter<Cost> {
if self.valid_jump_destinations.is_none() { if self.valid_jump_destinations.is_none() {
self.valid_jump_destinations = Some( self.valid_jump_destinations = Some(
self.cache 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 let jump_destinations = self
@ -491,6 +504,28 @@ impl<Cost: CostType> Interpreter<Cost> {
}; };
self.reader.position = pos; 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 { InstructionResult::StopExecutionNeedsReturn {
gas, gas,
init_off, init_off,
@ -537,20 +572,20 @@ impl<Cost: CostType> Interpreter<Cost> {
) -> vm::Result<()> { ) -> vm::Result<()> {
let schedule = ext.schedule(); let schedule = ext.schedule();
if (instruction == instructions::DELEGATECALL && !schedule.have_delegate_call) use instructions::*;
|| (instruction == instructions::CREATE2 && !schedule.have_create2) if (instruction == DELEGATECALL && !schedule.have_delegate_call)
|| (instruction == instructions::STATICCALL && !schedule.have_static_call) || (instruction == CREATE2 && !schedule.have_create2)
|| ((instruction == instructions::RETURNDATACOPY || (instruction == STATICCALL && !schedule.have_static_call)
|| instruction == instructions::RETURNDATASIZE) || ((instruction == RETURNDATACOPY || instruction == RETURNDATASIZE)
&& !schedule.have_return_data) && !schedule.have_return_data)
|| (instruction == instructions::REVERT && !schedule.have_revert) || (instruction == REVERT && !schedule.have_revert)
|| ((instruction == instructions::SHL || ((instruction == SHL || instruction == SHR || instruction == SAR)
|| instruction == instructions::SHR
|| instruction == instructions::SAR)
&& !schedule.have_bitwise_shifting) && !schedule.have_bitwise_shifting)
|| (instruction == instructions::EXTCODEHASH && !schedule.have_extcodehash) || (instruction == EXTCODEHASH && !schedule.have_extcodehash)
|| (instruction == instructions::CHAINID && !schedule.have_chain_id) || (instruction == CHAINID && !schedule.have_chain_id)
|| (instruction == instructions::SELFBALANCE && !schedule.have_selfbalance) || (instruction == SELFBALANCE && !schedule.have_selfbalance)
|| ((instruction == BEGINSUB || instruction == JUMPSUB || instruction == RETURNSUB)
&& !schedule.have_subs)
{ {
return Err(vm::Error::BadInstruction { return Err(vm::Error::BadInstruction {
instruction: instruction as u8, instruction: instruction as u8,
@ -623,6 +658,29 @@ impl<Cost: CostType> Interpreter<Cost> {
instructions::JUMPDEST => { instructions::JUMPDEST => {
// ignore // 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 => { instructions::CREATE | instructions::CREATE2 => {
let endowment = self.stack.pop_back(); let endowment = self.stack.pop_back();
let init_off = self.stack.pop_back(); let init_off = self.stack.pop_back();
@ -1413,6 +1471,7 @@ impl<Cost: CostType> Interpreter<Cost> {
if valid_jump_destinations.contains(jump) && U256::from(jump) == jump_u { if valid_jump_destinations.contains(jump) && U256::from(jump) == jump_u {
Ok(jump) Ok(jump)
} else { } else {
// Note: if jump > usize, BadJumpDestination value is trimmed
Err(vm::Error::BadJumpDestination { destination: jump }) Err(vm::Error::BadJumpDestination { destination: jump })
} }
} }

View File

@ -26,6 +26,7 @@ use std::sync::Arc;
const DEFAULT_CACHE_SIZE: usize = 4 * 1024 * 1024; const DEFAULT_CACHE_SIZE: usize = 4 * 1024 * 1024;
// stub for a HeapSizeOf implementation. // stub for a HeapSizeOf implementation.
#[derive(Clone)]
struct Bits(Arc<BitSet>); struct Bits(Arc<BitSet>);
impl HeapSizeOf for Bits { 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 /// Global cache for EVM interpreter
pub struct SharedCache { pub struct SharedCache {
jump_destinations: Mutex<MemoryLruCache<H256, Bits>>, jump_destinations: Mutex<MemoryLruCache<H256, CacheItem>>,
} }
impl SharedCache { impl SharedCache {
@ -50,47 +63,62 @@ impl SharedCache {
} }
/// Get jump destinations bitmap for a contract. /// 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 let Some(ref code_hash) = code_hash {
if code_hash == &KECCAK_EMPTY { 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) { 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 { if let Some(ref code_hash) = code_hash {
self.jump_destinations self.jump_destinations.lock().insert(*code_hash, d.clone());
.lock()
.insert(*code_hash, Bits(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 jump_dests = BitSet::with_capacity(code.len());
let mut sub_entrypoints = BitSet::with_capacity(code.len());
let mut position = 0; let mut position = 0;
while position < code.len() { while position < code.len() {
let instruction = Instruction::from_u8(code[position]); let instruction = Instruction::from_u8(code[position]);
if let Some(instruction) = instruction { if let Some(instruction) = instruction {
if instruction == instructions::JUMPDEST { match instruction {
jump_dests.insert(position); instructions::JUMPDEST => {
} else if let Some(push_bytes) = instruction.push_bytes() { jump_dests.insert(position);
position += push_bytes; }
instructions::BEGINSUB => {
sub_entrypoints.insert(position);
}
_ => {
if let Some(push_bytes) = instruction.push_bytes() {
position += push_bytes;
}
}
} }
} }
position += 1; position += 1;
} }
jump_dests.shrink_to_fit(); 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] #[cfg(test)]
fn test_find_jump_destinations() { mod test {
use rustc_hex::FromHex; use super::*;
// given use hex_literal::hex;
let code = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b01600055".from_hex().unwrap();
// when #[test]
let valid_jump_destinations = SharedCache::find_jump_destinations(&code); fn test_find_jump_destinations() {
// given
// then // 0000 7F PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
assert!(valid_jump_destinations.contains(66)); // 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()));
}
} }

View File

@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>. // along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
use super::interpreter::MAX_SUB_STACK_SIZE;
use ethereum_types::{Address, H256, U256}; use ethereum_types::{Address, H256, U256};
use factory::Factory; use factory::Factory;
use hex_literal::hex; use hex_literal::hex;
@ -1037,6 +1038,171 @@ fn test_jumps(factory: super::Factory) {
assert_eq!(gas_left, U256::from(54_117)); 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 <recursion_limit>
// 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} evm_test! {test_calls: test_calls_int}
fn test_calls(factory: super::Factory) { fn test_calls(factory: super::Factory) {
let code = "600054602d57600160005560006000600060006050610998610100f160006000600060006050610998610100f25b".from_hex().unwrap(); let code = "600054602d57600160005560006000600060006050610998610100f160006000600060006050610998610100f25b".from_hex().unwrap();

View File

@ -435,6 +435,9 @@ impl<'a> CallCreateExecutive<'a> {
| Err(vm::Error::MutableCallInStaticContext) | Err(vm::Error::MutableCallInStaticContext)
| Err(vm::Error::OutOfBounds) | Err(vm::Error::OutOfBounds)
| Err(vm::Error::Reverted) | Err(vm::Error::Reverted)
| Err(vm::Error::SubStackUnderflow { .. })
| Err(vm::Error::OutOfSubStack { .. })
| Err(vm::Error::InvalidSubEntry)
| Ok(FinalizationResult { | Ok(FinalizationResult {
apply_state: false, .. apply_state: false, ..
}) => { }) => {

View File

@ -126,6 +126,8 @@ pub struct CommonParams {
pub eip1884_transition: BlockNumber, pub eip1884_transition: BlockNumber,
/// Number of first block where EIP-2028 rules begin. /// Number of first block where EIP-2028 rules begin.
pub eip2028_transition: BlockNumber, 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. /// Number of first block where dust cleanup rules (EIP-168 and EIP169) begin.
pub dust_protection_transition: BlockNumber, pub dust_protection_transition: BlockNumber,
/// Nonce cap increase per block. Nonce cap is only checked if dust protection is enabled. /// 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_disable_transition))
|| block_number >= self.eip1283_reenable_transition; || block_number >= self.eip1283_reenable_transition;
schedule.eip1706 = block_number >= self.eip1706_transition; schedule.eip1706 = block_number >= self.eip1706_transition;
schedule.have_subs = block_number >= self.eip2315_transition;
if block_number >= self.eip1884_transition { if block_number >= self.eip1884_transition {
schedule.have_selfbalance = true; schedule.have_selfbalance = true;
@ -329,6 +332,9 @@ impl From<ethjson::spec::Params> for CommonParams {
eip2028_transition: p eip2028_transition: p
.eip2028_transition .eip2028_transition
.map_or_else(BlockNumber::max_value, Into::into), .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: p
.dust_protection_transition .dust_protection_transition
.map_or_else(BlockNumber::max_value, Into::into), .map_or_else(BlockNumber::max_value, Into::into),

View File

@ -34,6 +34,12 @@ pub enum Error {
StackUnderflow, StackUnderflow,
/// When execution would exceed defined Stack Limit /// When execution would exceed defined Stack Limit
OutOfStack, 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 /// When builtin contract failed on input data
BuiltIn, BuiltIn,
/// Returned on evm internal error. Should never be ignored during development. /// 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::BadInstruction { .. } => Error::BadInstruction,
VmError::StackUnderflow { .. } => Error::StackUnderflow, VmError::StackUnderflow { .. } => Error::StackUnderflow,
VmError::OutOfStack { .. } => Error::OutOfStack, VmError::OutOfStack { .. } => Error::OutOfStack,
VmError::SubStackUnderflow { .. } => Error::SubStackUnderflow,
VmError::OutOfSubStack { .. } => Error::OutOfSubStack,
VmError::InvalidSubEntry { .. } => Error::InvalidSubEntry,
VmError::BuiltIn { .. } => Error::BuiltIn, VmError::BuiltIn { .. } => Error::BuiltIn,
VmError::Wasm { .. } => Error::Wasm, VmError::Wasm { .. } => Error::Wasm,
VmError::Internal(_) => Error::Internal, VmError::Internal(_) => Error::Internal,
@ -82,7 +91,10 @@ impl fmt::Display for Error {
BadInstruction => "Bad instruction", BadInstruction => "Bad instruction",
StackUnderflow => "Stack underflow", StackUnderflow => "Stack underflow",
OutOfStack => "Out of stack", OutOfStack => "Out of stack",
SubStackUnderflow => "Subroutine stack underflow",
OutOfSubStack => "Subroutine stack overflow",
BuiltIn => "Built-in failed", BuiltIn => "Built-in failed",
InvalidSubEntry => "Invalid subroutine entry",
Wasm => "Wasm runtime error", Wasm => "Wasm runtime error",
Internal => "Internal error", Internal => "Internal error",
MutableCallInStaticContext => "Mutable Call In Static Context", MutableCallInStaticContext => "Mutable Call In Static Context",
@ -108,6 +120,9 @@ impl Encodable for Error {
Wasm => 8, Wasm => 8,
OutOfBounds => 9, OutOfBounds => 9,
Reverted => 10, Reverted => 10,
SubStackUnderflow => 11,
OutOfSubStack => 12,
InvalidSubEntry => 13,
}; };
s.append_internal(&value); s.append_internal(&value);
@ -130,6 +145,9 @@ impl Decodable for Error {
8 => Ok(Wasm), 8 => Ok(Wasm),
9 => Ok(OutOfBounds), 9 => Ok(OutOfBounds),
10 => Ok(Reverted), 10 => Ok(Reverted),
11 => Ok(SubStackUnderflow),
12 => Ok(OutOfSubStack),
13 => Ok(InvalidSubEntry),
_ => Err(DecoderError::Custom("Invalid error type")), _ => Err(DecoderError::Custom("Invalid error type")),
} }
} }

View File

@ -72,6 +72,22 @@ pub enum Error {
/// What was the stack limit /// What was the stack limit
limit: usize, 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 /// Built-in contract failed on given input
BuiltIn(&'static str), BuiltIn(&'static str),
/// When execution tries to modify the state in static context /// When execution tries to modify the state in static context
@ -103,9 +119,11 @@ impl fmt::Display for Error {
use self::Error::*; use self::Error::*;
match *self { match *self {
OutOfGas => write!(f, "Out of gas"), OutOfGas => write!(f, "Out of gas"),
BadJumpDestination { destination } => { BadJumpDestination { destination } => write!(
write!(f, "Bad jump destination {:x}", destination) f,
} "Bad jump destination {:x} (trimmed to usize)",
destination
),
BadInstruction { instruction } => write!(f, "Bad instruction {:x}", instruction), BadInstruction { instruction } => write!(f, "Bad instruction {:x}", instruction),
StackUnderflow { StackUnderflow {
instruction, instruction,
@ -117,6 +135,13 @@ impl fmt::Display for Error {
wanted, wanted,
limit, limit,
} => write!(f, "Out of stack {} {}/{}", instruction, 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), BuiltIn(name) => write!(f, "Built-in failed: {}", name),
Internal(ref msg) => write!(f, "Internal error: {}", msg), Internal(ref msg) => write!(f, "Internal error: {}", msg),
MutableCallInStaticContext => write!(f, "Mutable call in static context"), MutableCallInStaticContext => write!(f, "Mutable call in static context"),

View File

@ -120,6 +120,8 @@ pub struct Schedule {
pub have_chain_id: bool, pub have_chain_id: bool,
/// SELFBALANCE opcode enabled. /// SELFBALANCE opcode enabled.
pub have_selfbalance: bool, pub have_selfbalance: bool,
/// BEGINSUB, JUMPSUB and RETURNSUB opcodes enabled.
pub have_subs: bool,
/// Kill basic accounts below this balance if touched. /// Kill basic accounts below this balance if touched.
pub kill_dust: CleanDustMode, pub kill_dust: CleanDustMode,
/// Enable EIP-1283 rules /// Enable EIP-1283 rules
@ -224,6 +226,7 @@ impl Schedule {
have_bitwise_shifting: false, have_bitwise_shifting: false,
have_chain_id: false, have_chain_id: false,
have_selfbalance: false, have_selfbalance: false,
have_subs: false,
have_extcodehash: false, have_extcodehash: false,
stack_limit: 1024, stack_limit: 1024,
max_depth: 1024, max_depth: 1024,
@ -303,6 +306,13 @@ impl Schedule {
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 { fn new(efcd: bool, hdc: bool, tcg: usize) -> Schedule {
Schedule { Schedule {
exceptional_failed_code_deposit: efcd, exceptional_failed_code_deposit: efcd,
@ -313,6 +323,7 @@ impl Schedule {
have_bitwise_shifting: false, have_bitwise_shifting: false,
have_chain_id: false, have_chain_id: false,
have_selfbalance: false, have_selfbalance: false,
have_subs: false,
have_extcodehash: false, have_extcodehash: false,
stack_limit: 1024, stack_limit: 1024,
max_depth: 1024, max_depth: 1024,

View File

@ -115,6 +115,13 @@ impl FakeExt {
ext 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 /// Alter fake externalities to allow wasm
pub fn with_wasm(mut self) -> Self { pub fn with_wasm(mut self) -> Self {
self.schedule.wasm = Some(Default::default()); self.schedule.wasm = Some(Default::default());

View File

@ -104,6 +104,8 @@ pub struct Params {
/// See `CommonParams` docs. /// See `CommonParams` docs.
pub eip2028_transition: Option<Uint>, pub eip2028_transition: Option<Uint>,
/// See `CommonParams` docs. /// See `CommonParams` docs.
pub eip2315_transition: Option<Uint>,
/// See `CommonParams` docs.
pub dust_protection_transition: Option<Uint>, pub dust_protection_transition: Option<Uint>,
/// See `CommonParams` docs. /// See `CommonParams` docs.
pub nonce_cap_increment: Option<Uint>, pub nonce_cap_increment: Option<Uint>,