EIP-2315: Simple Subroutines for the EVM (#11629)
This commit is contained in:
parent
751210c963
commit
1460f6cc27
@ -321,6 +321,13 @@ enum_with_from_u8! {
|
||||
#[doc = "Makes a log entry, 4 topics."]
|
||||
LOG4 = 0xa4,
|
||||
|
||||
#[doc = "Marks the entry point to a subroutine."]
|
||||
BEGINSUB = 0x5c,
|
||||
#[doc = "Returns from a subroutine."]
|
||||
RETURNSUB = 0x5d,
|
||||
#[doc = "Jumps to a defined BEGINSUB subroutine."]
|
||||
JUMPSUB = 0x5e,
|
||||
|
||||
#[doc = "create a new account with associated code"]
|
||||
CREATE = 0xf0,
|
||||
#[doc = "message-call into an account"]
|
||||
@ -591,6 +598,9 @@ lazy_static! {
|
||||
arr[LOG2 as usize] = Some(InstructionInfo::new("LOG2", 4, 0, GasPriceTier::Special));
|
||||
arr[LOG3 as usize] = Some(InstructionInfo::new("LOG3", 5, 0, GasPriceTier::Special));
|
||||
arr[LOG4 as usize] = Some(InstructionInfo::new("LOG4", 6, 0, GasPriceTier::Special));
|
||||
arr[BEGINSUB as usize] = Some(InstructionInfo::new("BEGINSUB", 0, 0, GasPriceTier::Base));
|
||||
arr[JUMPSUB as usize] = Some(InstructionInfo::new("JUMPSUB", 1, 0, GasPriceTier::High));
|
||||
arr[RETURNSUB as usize] = Some(InstructionInfo::new("RETURNSUB", 0, 0, GasPriceTier::Low));
|
||||
arr[CREATE as usize] = Some(InstructionInfo::new("CREATE", 3, 1, GasPriceTier::Special));
|
||||
arr[CALL as usize] = Some(InstructionInfo::new("CALL", 7, 1, GasPriceTier::Special));
|
||||
arr[CALLCODE as usize] = Some(InstructionInfo::new("CALLCODE", 7, 1, GasPriceTier::Special));
|
||||
|
@ -61,6 +61,10 @@ const TWO_POW_96: U256 = U256([0, 0x100000000, 0, 0]); //0x1 00000000 00000000 0
|
||||
const TWO_POW_224: U256 = U256([0, 0, 0, 0x100000000]); //0x1 00000000 00000000 00000000 00000000 00000000 00000000 00000000
|
||||
const TWO_POW_248: U256 = U256([0, 0, 0, 0x100000000000000]); //0x1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 000000
|
||||
|
||||
/// Maximum subroutine stack size as specified in
|
||||
/// https://eips.ethereum.org/EIPS/eip-2315.
|
||||
pub const MAX_SUB_STACK_SIZE: usize = 1023;
|
||||
|
||||
fn to_biguint(x: U256) -> BigUint {
|
||||
let mut bytes = [0u8; 32];
|
||||
x.to_little_endian(&mut bytes);
|
||||
@ -101,6 +105,8 @@ enum InstructionResult<Gas> {
|
||||
Ok,
|
||||
UnusedGas(Gas),
|
||||
JumpToPosition(U256),
|
||||
JumpToSubroutine(U256),
|
||||
ReturnFromSubroutine(usize),
|
||||
StopExecutionNeedsReturn {
|
||||
/// Gas left.
|
||||
gas: Gas,
|
||||
@ -183,8 +189,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,
|
||||
@ -290,10 +298,12 @@ 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,
|
||||
@ -301,8 +311,10 @@ impl<Cost: CostType> Interpreter<Cost> {
|
||||
reader,
|
||||
informant,
|
||||
valid_jump_destinations,
|
||||
valid_subroutine_destinations,
|
||||
gasometer,
|
||||
stack,
|
||||
return_stack,
|
||||
done: false,
|
||||
// Overridden in `step_inner` based on
|
||||
// the result of `ext.trace_next_instruction`.
|
||||
@ -478,7 +490,8 @@ impl<Cost: CostType> Interpreter<Cost> {
|
||||
if self.valid_jump_destinations.is_none() {
|
||||
self.valid_jump_destinations = Some(
|
||||
self.cache
|
||||
.jump_destinations(&self.params.code_hash, &self.reader.code),
|
||||
.jump_and_sub_destinations(&self.params.code_hash, &self.reader.code)
|
||||
.0,
|
||||
);
|
||||
}
|
||||
let jump_destinations = self
|
||||
@ -491,6 +504,28 @@ impl<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 + 1;
|
||||
}
|
||||
InstructionResult::ReturnFromSubroutine(pos) => {
|
||||
self.reader.position = pos;
|
||||
}
|
||||
InstructionResult::StopExecutionNeedsReturn {
|
||||
gas,
|
||||
init_off,
|
||||
@ -537,20 +572,20 @@ impl<Cost: CostType> Interpreter<Cost> {
|
||||
) -> vm::Result<()> {
|
||||
let schedule = ext.schedule();
|
||||
|
||||
if (instruction == instructions::DELEGATECALL && !schedule.have_delegate_call)
|
||||
|| (instruction == instructions::CREATE2 && !schedule.have_create2)
|
||||
|| (instruction == instructions::STATICCALL && !schedule.have_static_call)
|
||||
|| ((instruction == instructions::RETURNDATACOPY
|
||||
|| instruction == instructions::RETURNDATASIZE)
|
||||
use instructions::*;
|
||||
if (instruction == DELEGATECALL && !schedule.have_delegate_call)
|
||||
|| (instruction == CREATE2 && !schedule.have_create2)
|
||||
|| (instruction == STATICCALL && !schedule.have_static_call)
|
||||
|| ((instruction == RETURNDATACOPY || instruction == RETURNDATASIZE)
|
||||
&& !schedule.have_return_data)
|
||||
|| (instruction == instructions::REVERT && !schedule.have_revert)
|
||||
|| ((instruction == instructions::SHL
|
||||
|| instruction == instructions::SHR
|
||||
|| instruction == instructions::SAR)
|
||||
|| (instruction == REVERT && !schedule.have_revert)
|
||||
|| ((instruction == SHL || instruction == SHR || instruction == SAR)
|
||||
&& !schedule.have_bitwise_shifting)
|
||||
|| (instruction == instructions::EXTCODEHASH && !schedule.have_extcodehash)
|
||||
|| (instruction == instructions::CHAINID && !schedule.have_chain_id)
|
||||
|| (instruction == instructions::SELFBALANCE && !schedule.have_selfbalance)
|
||||
|| (instruction == EXTCODEHASH && !schedule.have_extcodehash)
|
||||
|| (instruction == CHAINID && !schedule.have_chain_id)
|
||||
|| (instruction == SELFBALANCE && !schedule.have_selfbalance)
|
||||
|| ((instruction == BEGINSUB || instruction == JUMPSUB || instruction == RETURNSUB)
|
||||
&& !schedule.have_subs)
|
||||
{
|
||||
return Err(vm::Error::BadInstruction {
|
||||
instruction: instruction as u8,
|
||||
@ -623,6 +658,29 @@ impl<Cost: CostType> Interpreter<Cost> {
|
||||
instructions::JUMPDEST => {
|
||||
// ignore
|
||||
}
|
||||
instructions::BEGINSUB => {
|
||||
return Err(vm::Error::InvalidSubEntry);
|
||||
}
|
||||
instructions::JUMPSUB => {
|
||||
if self.return_stack.len() >= MAX_SUB_STACK_SIZE {
|
||||
return Err(vm::Error::OutOfSubStack {
|
||||
wanted: 1,
|
||||
limit: MAX_SUB_STACK_SIZE,
|
||||
});
|
||||
}
|
||||
let sub_destination = self.stack.pop_back();
|
||||
return Ok(InstructionResult::JumpToSubroutine(sub_destination));
|
||||
}
|
||||
instructions::RETURNSUB => {
|
||||
if let Some(pos) = self.return_stack.pop() {
|
||||
return Ok(InstructionResult::ReturnFromSubroutine(pos));
|
||||
} else {
|
||||
return Err(vm::Error::SubStackUnderflow {
|
||||
wanted: 1,
|
||||
on_stack: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
instructions::CREATE | instructions::CREATE2 => {
|
||||
let endowment = self.stack.pop_back();
|
||||
let init_off = self.stack.pop_back();
|
||||
@ -1413,6 +1471,7 @@ impl<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 })
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ use std::sync::Arc;
|
||||
const DEFAULT_CACHE_SIZE: usize = 4 * 1024 * 1024;
|
||||
|
||||
// stub for a HeapSizeOf implementation.
|
||||
#[derive(Clone)]
|
||||
struct Bits(Arc<BitSet>);
|
||||
|
||||
impl HeapSizeOf for Bits {
|
||||
@ -35,9 +36,21 @@ impl HeapSizeOf for Bits {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CacheItem {
|
||||
jump_destination: Bits,
|
||||
sub_entrypoint: Bits,
|
||||
}
|
||||
|
||||
impl HeapSizeOf for CacheItem {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.jump_destination.heap_size_of_children() + self.sub_entrypoint.heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
/// Global cache for EVM interpreter
|
||||
pub struct SharedCache {
|
||||
jump_destinations: Mutex<MemoryLruCache<H256, Bits>>,
|
||||
jump_destinations: Mutex<MemoryLruCache<H256, CacheItem>>,
|
||||
}
|
||||
|
||||
impl SharedCache {
|
||||
@ -50,47 +63,62 @@ 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 {
|
||||
match instruction {
|
||||
instructions::JUMPDEST => {
|
||||
jump_dests.insert(position);
|
||||
} else if let Some(push_bytes) = instruction.push_bytes() {
|
||||
}
|
||||
instructions::BEGINSUB => {
|
||||
sub_entrypoints.insert(position);
|
||||
}
|
||||
_ => {
|
||||
if let Some(push_bytes) = instruction.push_bytes() {
|
||||
position += push_bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
position += 1;
|
||||
}
|
||||
|
||||
jump_dests.shrink_to_fit();
|
||||
Arc::new(jump_dests)
|
||||
CacheItem {
|
||||
jump_destination: Bits(Arc::new(jump_dests)),
|
||||
sub_entrypoint: Bits(Arc::new(sub_entrypoints)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,15 +128,92 @@ impl Default for SharedCache {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use hex_literal::hex;
|
||||
|
||||
#[test]
|
||||
fn test_find_jump_destinations() {
|
||||
use rustc_hex::FromHex;
|
||||
// given
|
||||
let code = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b01600055".from_hex().unwrap();
|
||||
|
||||
// 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!(valid_jump_destinations.contains(66));
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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 factory::Factory;
|
||||
use hex_literal::hex;
|
||||
@ -1037,6 +1038,171 @@ fn test_jumps(factory: super::Factory) {
|
||||
assert_eq!(gas_left, U256::from(54_117));
|
||||
}
|
||||
|
||||
evm_test! {test_subs_simple: test_subs_simple_int}
|
||||
fn test_subs_simple(factory: super::Factory) {
|
||||
// as defined in https://eips.ethereum.org/EIPS/eip-2315
|
||||
let code = hex!("60045e005c5d").to_vec();
|
||||
|
||||
let mut params = ActionParams::default();
|
||||
params.gas = U256::from(18);
|
||||
params.code = Some(Arc::new(code));
|
||||
let mut ext = FakeExt::new_berlin();
|
||||
|
||||
let gas_left = {
|
||||
let vm = factory.create(params, ext.schedule(), ext.depth());
|
||||
test_finalize(vm.exec(&mut ext).ok().unwrap()).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(gas_left, U256::from(0));
|
||||
}
|
||||
|
||||
evm_test! {test_subs_two_levels: test_subs_two_levels_int}
|
||||
fn test_subs_two_levels(factory: super::Factory) {
|
||||
// as defined in https://eips.ethereum.org/EIPS/eip-2315
|
||||
let code = hex!("6800000000000000000c5e005c60115e5d5c5d").to_vec();
|
||||
|
||||
let mut params = ActionParams::default();
|
||||
params.gas = U256::from(36);
|
||||
params.code = Some(Arc::new(code));
|
||||
let mut ext = FakeExt::new_berlin();
|
||||
|
||||
let gas_left = {
|
||||
let vm = factory.create(params, ext.schedule(), ext.depth());
|
||||
test_finalize(vm.exec(&mut ext).ok().unwrap()).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(gas_left, U256::from(0));
|
||||
}
|
||||
|
||||
evm_test! {test_subs_invalid_jump: test_subs_invalid_jump_int}
|
||||
fn test_subs_invalid_jump(factory: super::Factory) {
|
||||
// as defined in https://eips.ethereum.org/EIPS/eip-2315
|
||||
let code = hex!("6801000000000000000c5e005c60115e5d5c5d").to_vec();
|
||||
|
||||
let mut params = ActionParams::default();
|
||||
params.gas = U256::from(24);
|
||||
params.code = Some(Arc::new(code));
|
||||
let mut ext = FakeExt::new_berlin();
|
||||
|
||||
let current = {
|
||||
let vm = factory.create(params, ext.schedule(), ext.depth());
|
||||
test_finalize(vm.exec(&mut ext).ok().unwrap())
|
||||
};
|
||||
|
||||
let expected = Result::Err(vm::Error::BadJumpDestination { destination: 0xc });
|
||||
assert_eq!(current, expected);
|
||||
}
|
||||
|
||||
evm_test! {test_subs_shallow_return_stack: test_subs_shallow_return_stack_int}
|
||||
fn test_subs_shallow_return_stack(factory: super::Factory) {
|
||||
// as defined in https://eips.ethereum.org/EIPS/eip-2315
|
||||
let code = hex!("5d5858").to_vec();
|
||||
|
||||
let mut params = ActionParams::default();
|
||||
params.gas = U256::from(24);
|
||||
params.code = Some(Arc::new(code));
|
||||
let mut ext = FakeExt::new_berlin();
|
||||
|
||||
let current = {
|
||||
let vm = factory.create(params, ext.schedule(), ext.depth());
|
||||
test_finalize(vm.exec(&mut ext).ok().unwrap())
|
||||
};
|
||||
|
||||
let expected = Result::Err(vm::Error::SubStackUnderflow {
|
||||
wanted: 1,
|
||||
on_stack: 0,
|
||||
});
|
||||
assert_eq!(current, expected);
|
||||
}
|
||||
|
||||
evm_test! {test_subs_substack_limit: test_subs_substack_limit_int}
|
||||
fn test_subs_substack_limit(factory: super::Factory) {
|
||||
// PUSH <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}
|
||||
fn test_calls(factory: super::Factory) {
|
||||
let code = "600054602d57600160005560006000600060006050610998610100f160006000600060006050610998610100f25b".from_hex().unwrap();
|
||||
|
@ -435,6 +435,9 @@ impl<'a> CallCreateExecutive<'a> {
|
||||
| Err(vm::Error::MutableCallInStaticContext)
|
||||
| Err(vm::Error::OutOfBounds)
|
||||
| Err(vm::Error::Reverted)
|
||||
| Err(vm::Error::SubStackUnderflow { .. })
|
||||
| Err(vm::Error::OutOfSubStack { .. })
|
||||
| Err(vm::Error::InvalidSubEntry)
|
||||
| Ok(FinalizationResult {
|
||||
apply_state: false, ..
|
||||
}) => {
|
||||
|
@ -126,6 +126,8 @@ pub struct CommonParams {
|
||||
pub eip1884_transition: BlockNumber,
|
||||
/// Number of first block where EIP-2028 rules begin.
|
||||
pub eip2028_transition: BlockNumber,
|
||||
/// Number of first block where EIP-2315 rules begin.
|
||||
pub eip2315_transition: BlockNumber,
|
||||
/// Number of first block where dust cleanup rules (EIP-168 and EIP169) begin.
|
||||
pub dust_protection_transition: BlockNumber,
|
||||
/// Nonce cap increase per block. Nonce cap is only checked if dust protection is enabled.
|
||||
@ -197,6 +199,7 @@ impl CommonParams {
|
||||
&& !(block_number >= self.eip1283_disable_transition))
|
||||
|| block_number >= self.eip1283_reenable_transition;
|
||||
schedule.eip1706 = block_number >= self.eip1706_transition;
|
||||
schedule.have_subs = block_number >= self.eip2315_transition;
|
||||
|
||||
if block_number >= self.eip1884_transition {
|
||||
schedule.have_selfbalance = true;
|
||||
@ -329,6 +332,9 @@ impl From<ethjson::spec::Params> for CommonParams {
|
||||
eip2028_transition: p
|
||||
.eip2028_transition
|
||||
.map_or_else(BlockNumber::max_value, Into::into),
|
||||
eip2315_transition: p
|
||||
.eip2315_transition
|
||||
.map_or_else(BlockNumber::max_value, Into::into),
|
||||
dust_protection_transition: p
|
||||
.dust_protection_transition
|
||||
.map_or_else(BlockNumber::max_value, Into::into),
|
||||
|
@ -34,6 +34,12 @@ pub enum Error {
|
||||
StackUnderflow,
|
||||
/// When execution would exceed defined Stack Limit
|
||||
OutOfStack,
|
||||
/// When there is not enough subroutine stack elements to return from
|
||||
SubStackUnderflow,
|
||||
/// When execution would exceed defined subroutine Stack Limit
|
||||
OutOfSubStack,
|
||||
/// When the code walks into a subroutine, that is not allowed
|
||||
InvalidSubEntry,
|
||||
/// When builtin contract failed on input data
|
||||
BuiltIn,
|
||||
/// Returned on evm internal error. Should never be ignored during development.
|
||||
@ -57,6 +63,9 @@ impl<'a> From<&'a VmError> for Error {
|
||||
VmError::BadInstruction { .. } => Error::BadInstruction,
|
||||
VmError::StackUnderflow { .. } => Error::StackUnderflow,
|
||||
VmError::OutOfStack { .. } => Error::OutOfStack,
|
||||
VmError::SubStackUnderflow { .. } => Error::SubStackUnderflow,
|
||||
VmError::OutOfSubStack { .. } => Error::OutOfSubStack,
|
||||
VmError::InvalidSubEntry { .. } => Error::InvalidSubEntry,
|
||||
VmError::BuiltIn { .. } => Error::BuiltIn,
|
||||
VmError::Wasm { .. } => Error::Wasm,
|
||||
VmError::Internal(_) => Error::Internal,
|
||||
@ -82,7 +91,10 @@ impl fmt::Display for Error {
|
||||
BadInstruction => "Bad instruction",
|
||||
StackUnderflow => "Stack underflow",
|
||||
OutOfStack => "Out of stack",
|
||||
SubStackUnderflow => "Subroutine stack underflow",
|
||||
OutOfSubStack => "Subroutine stack overflow",
|
||||
BuiltIn => "Built-in failed",
|
||||
InvalidSubEntry => "Invalid subroutine entry",
|
||||
Wasm => "Wasm runtime error",
|
||||
Internal => "Internal error",
|
||||
MutableCallInStaticContext => "Mutable Call In Static Context",
|
||||
@ -108,6 +120,9 @@ impl Encodable for Error {
|
||||
Wasm => 8,
|
||||
OutOfBounds => 9,
|
||||
Reverted => 10,
|
||||
SubStackUnderflow => 11,
|
||||
OutOfSubStack => 12,
|
||||
InvalidSubEntry => 13,
|
||||
};
|
||||
|
||||
s.append_internal(&value);
|
||||
@ -130,6 +145,9 @@ impl Decodable for Error {
|
||||
8 => Ok(Wasm),
|
||||
9 => Ok(OutOfBounds),
|
||||
10 => Ok(Reverted),
|
||||
11 => Ok(SubStackUnderflow),
|
||||
12 => Ok(OutOfSubStack),
|
||||
13 => Ok(InvalidSubEntry),
|
||||
_ => Err(DecoderError::Custom("Invalid error type")),
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,22 @@ pub enum Error {
|
||||
/// What was the stack limit
|
||||
limit: usize,
|
||||
},
|
||||
/// When there is not enough subroutine stack elements to return from
|
||||
SubStackUnderflow {
|
||||
/// How many stack elements was requested by instruction
|
||||
wanted: usize,
|
||||
/// How many elements were on stack
|
||||
on_stack: usize,
|
||||
},
|
||||
/// When execution would exceed defined subroutine Stack Limit
|
||||
OutOfSubStack {
|
||||
/// How many stack elements instruction wanted to pop
|
||||
wanted: usize,
|
||||
/// What was the stack limit
|
||||
limit: usize,
|
||||
},
|
||||
/// When the code walks into a subroutine, that is not allowed
|
||||
InvalidSubEntry,
|
||||
/// Built-in contract failed on given input
|
||||
BuiltIn(&'static str),
|
||||
/// When execution tries to modify the state in static context
|
||||
@ -103,9 +119,11 @@ impl fmt::Display for Error {
|
||||
use self::Error::*;
|
||||
match *self {
|
||||
OutOfGas => write!(f, "Out of gas"),
|
||||
BadJumpDestination { destination } => {
|
||||
write!(f, "Bad jump destination {:x}", destination)
|
||||
}
|
||||
BadJumpDestination { destination } => write!(
|
||||
f,
|
||||
"Bad jump destination {:x} (trimmed to usize)",
|
||||
destination
|
||||
),
|
||||
BadInstruction { instruction } => write!(f, "Bad instruction {:x}", instruction),
|
||||
StackUnderflow {
|
||||
instruction,
|
||||
@ -117,6 +135,13 @@ impl fmt::Display for Error {
|
||||
wanted,
|
||||
limit,
|
||||
} => write!(f, "Out of stack {} {}/{}", instruction, wanted, limit),
|
||||
SubStackUnderflow { wanted, on_stack } => {
|
||||
write!(f, "Subroutine stack underflow {}/{}", wanted, on_stack)
|
||||
}
|
||||
OutOfSubStack { wanted, limit } => {
|
||||
write!(f, "Out of subroutine stack {}/{}", wanted, limit)
|
||||
}
|
||||
InvalidSubEntry => write!(f, "Invalid subroutine entry"),
|
||||
BuiltIn(name) => write!(f, "Built-in failed: {}", name),
|
||||
Internal(ref msg) => write!(f, "Internal error: {}", msg),
|
||||
MutableCallInStaticContext => write!(f, "Mutable call in static context"),
|
||||
|
@ -120,6 +120,8 @@ pub struct Schedule {
|
||||
pub have_chain_id: bool,
|
||||
/// SELFBALANCE opcode enabled.
|
||||
pub have_selfbalance: bool,
|
||||
/// BEGINSUB, JUMPSUB and RETURNSUB opcodes enabled.
|
||||
pub have_subs: bool,
|
||||
/// Kill basic accounts below this balance if touched.
|
||||
pub kill_dust: CleanDustMode,
|
||||
/// Enable EIP-1283 rules
|
||||
@ -224,6 +226,7 @@ impl Schedule {
|
||||
have_bitwise_shifting: false,
|
||||
have_chain_id: false,
|
||||
have_selfbalance: false,
|
||||
have_subs: false,
|
||||
have_extcodehash: false,
|
||||
stack_limit: 1024,
|
||||
max_depth: 1024,
|
||||
@ -303,6 +306,13 @@ impl Schedule {
|
||||
schedule
|
||||
}
|
||||
|
||||
/// Schedule for the Berlin fork of the Ethereum main net.
|
||||
pub fn new_berlin() -> Schedule {
|
||||
let mut schedule = Self::new_istanbul();
|
||||
schedule.have_subs = true; // EIP 2315
|
||||
schedule
|
||||
}
|
||||
|
||||
fn new(efcd: bool, hdc: bool, tcg: usize) -> Schedule {
|
||||
Schedule {
|
||||
exceptional_failed_code_deposit: efcd,
|
||||
@ -313,6 +323,7 @@ impl Schedule {
|
||||
have_bitwise_shifting: false,
|
||||
have_chain_id: false,
|
||||
have_selfbalance: false,
|
||||
have_subs: false,
|
||||
have_extcodehash: false,
|
||||
stack_limit: 1024,
|
||||
max_depth: 1024,
|
||||
|
@ -115,6 +115,13 @@ impl FakeExt {
|
||||
ext
|
||||
}
|
||||
|
||||
/// New fake externalities with Berlin schedule rules
|
||||
pub fn new_berlin() -> Self {
|
||||
let mut ext = FakeExt::default();
|
||||
ext.schedule = Schedule::new_berlin();
|
||||
ext
|
||||
}
|
||||
|
||||
/// Alter fake externalities to allow wasm
|
||||
pub fn with_wasm(mut self) -> Self {
|
||||
self.schedule.wasm = Some(Default::default());
|
||||
|
@ -104,6 +104,8 @@ pub struct Params {
|
||||
/// See `CommonParams` docs.
|
||||
pub eip2028_transition: Option<Uint>,
|
||||
/// See `CommonParams` docs.
|
||||
pub eip2315_transition: Option<Uint>,
|
||||
/// See `CommonParams` docs.
|
||||
pub dust_protection_transition: Option<Uint>,
|
||||
/// See `CommonParams` docs.
|
||||
pub nonce_cap_increment: Option<Uint>,
|
||||
|
Loading…
Reference in New Issue
Block a user