Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EIP-2315 simple subroutines support #14

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ pub enum ExitError {
StackOverflow,
/// Jump destination is invalid.
InvalidJump,
/// Subroutine is invalid.
InvalidSubroutine,
/// An opcode accesses memory region, but the region is invalid.
InvalidRange,
/// Encountered the designated invalid opcode.
Expand All @@ -133,6 +135,9 @@ pub enum ExitError {
/// Attempt to create an empty account (runtime, unused).
CreateEmpty,

/// Unknown opcode.
UnknownOpcode,

/// Other normal errors.
Other(Cow<'static, str>),
}
Expand Down
52 changes: 50 additions & 2 deletions core/src/eval/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pub fn jump(state: &mut Machine) -> Control {
pop_u256!(state, dest);
let dest = as_usize_or_fail!(dest, ExitError::InvalidJump);

if state.valids.is_valid(dest) {
if state.valids.is_jumpdest(dest) {
Control::Jump(dest)
} else {
Control::Exit(ExitError::InvalidJump.into())
Expand All @@ -122,7 +122,7 @@ pub fn jumpi(state: &mut Machine) -> Control {
let dest = as_usize_or_fail!(dest, ExitError::InvalidJump);

if value != H256::zero() {
if state.valids.is_valid(dest) {
if state.valids.is_jumpdest(dest) {
Control::Jump(dest)
} else {
Control::Exit(ExitError::InvalidJump.into())
Expand All @@ -132,6 +132,54 @@ pub fn jumpi(state: &mut Machine) -> Control {
}
}

#[inline]
pub fn beginsub(state: &mut Machine) -> Control {
if state.returns.is_some() {
Control::Exit(ExitError::InvalidSubroutine.into())
} else {
Control::Exit(ExitError::UnknownOpcode.into())
}
}

#[inline]
pub fn jumpsub(state: &mut Machine, position: usize) -> Control {
if let Some(ref mut returns) = state.returns {
if position == usize::max_value() {
return Control::Exit(ExitFatal::NotSupported.into())
}

pop_u256!(state, dest);
let dest = as_usize_or_fail!(dest, ExitError::InvalidSubroutine);

if state.valids.is_beginsub(dest) {
if dest == usize::max_value() {
return Control::Exit(ExitSucceed::Stopped.into())
}

try_or_fail!(returns.push(position + 1));
Control::Jump(dest + 1)
} else {
Control::Exit(ExitError::InvalidSubroutine.into())
}
} else {
Control::Exit(ExitError::UnknownOpcode.into())
}
}

#[inline]
pub fn returnsub(state: &mut Machine) -> Control {
if let Some(ref mut returns) = state.returns {
let value = match returns.pop() {
Ok(value) => value,
Err(e) => return Control::Exit(e.into()),
};

Control::Jump(value)
} else {
Control::Exit(ExitError::UnknownOpcode.into())
}
}

#[inline]
pub fn pc(state: &mut Machine, position: usize) -> Control {
push_u256!(state, U256::from(position));
Expand Down
15 changes: 15 additions & 0 deletions core/src/eval/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,18 @@ fn eval_jumpi(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control
self::misc::jumpi(state)
}

fn eval_beginsub(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control {
self::misc::beginsub(state)
}

fn eval_jumpsub(state: &mut Machine, _opcode: Opcode, position: usize) -> Control {
self::misc::jumpsub(state, position)
}

fn eval_returnsub(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control {
self::misc::returnsub(state)
}

fn eval_pc(state: &mut Machine, _opcode: Opcode, position: usize) -> Control {
self::misc::pc(state, position)
}
Expand Down Expand Up @@ -490,6 +502,9 @@ pub fn eval(state: &mut Machine, opcode: Opcode, position: usize) -> Control {
table[Opcode::MSTORE8.as_usize()] = eval_mstore8 as _;
table[Opcode::JUMP.as_usize()] = eval_jump as _;
table[Opcode::JUMPI.as_usize()] = eval_jumpi as _;
table[Opcode::BEGINSUB.as_usize()] = eval_beginsub as _;
table[Opcode::JUMPSUB.as_usize()] = eval_jumpsub as _;
table[Opcode::RETURNSUB.as_usize()] = eval_returnsub as _;
table[Opcode::PC.as_usize()] = eval_pc as _;
table[Opcode::MSIZE.as_usize()] = eval_msize as _;
table[Opcode::JUMPDEST.as_usize()] = eval_jumpdest as _;
Expand Down
12 changes: 8 additions & 4 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ mod eval;
mod utils;

pub use crate::memory::Memory;
pub use crate::stack::Stack;
pub use crate::valids::Valids;
pub use crate::stack::{Stack, Returns};
pub use crate::valids::{Valid, Valids};
pub use crate::opcode::Opcode;
pub use crate::error::{Trap, Capture, ExitReason, ExitSucceed, ExitError, ExitRevert, ExitFatal};

Expand All @@ -42,8 +42,10 @@ pub struct Machine {
valids: Valids,
/// Memory.
memory: Memory,
/// Stack.
/// Data stack.
stack: Stack,
/// Return stack.
returns: Option<Returns>,
}

impl Machine {
Expand All @@ -61,7 +63,8 @@ impl Machine {
code: Rc<Vec<u8>>,
data: Rc<Vec<u8>>,
stack_limit: usize,
memory_limit: usize
memory_limit: usize,
returns_limit: Option<usize>,
) -> Self {
let valids = Valids::new(&code[..]);

Expand All @@ -71,6 +74,7 @@ impl Machine {
position: Ok(0),
return_range: U256::zero()..U256::zero(),
valids,
returns: returns_limit.map(|l| Returns::new(l)),
memory: Memory::new(memory_limit),
stack: Stack::new(stack_limit),
}
Expand Down
7 changes: 7 additions & 0 deletions core/src/opcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ impl Opcode {
/// `JUMPDEST`
pub const JUMPDEST: Opcode = Opcode(0x5b);

/// `BEGINSUB`
pub const BEGINSUB: Opcode = Opcode(0x5c);
/// `RETURNSUB`
pub const RETURNSUB: Opcode = Opcode(0x5d);
/// `JUMPSUB`
pub const JUMPSUB: Opcode = Opcode(0x5e);

/// `PUSHn`
pub const PUSH1: Opcode = Opcode(0x60);
pub const PUSH2: Opcode = Opcode(0x61);
Expand Down
47 changes: 47 additions & 0 deletions core/src/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,50 @@ impl Stack {
}
}
}

/// EVM returns.
#[derive(Clone, Debug)]
pub struct Returns {
data: Vec<usize>,
limit: usize,
}

impl Returns {
/// Create a new stack with given limit.
pub fn new(limit: usize) -> Self {
Self {
data: Vec::new(),
limit,
}
}

#[inline]
/// Stack limit.
pub fn limit(&self) -> usize {
self.limit
}

#[inline]
/// Stack length.
pub fn len(&self) -> usize {
self.data.len()
}

#[inline]
/// Pop a value from the stack. If the stack is already empty, returns the
/// `StackUnderflow` error.
pub fn pop(&mut self) -> Result<usize, ExitError> {
self.data.pop().ok_or(ExitError::StackUnderflow)
}

#[inline]
/// Push a new value into the stack. If it will exceed the stack limit,
/// returns `StackOverflow` error and leaves the stack unchanged.
pub fn push(&mut self, value: usize) -> Result<(), ExitError> {
if self.data.len() + 1 > self.limit {
return Err(ExitError::StackOverflow)
}
self.data.push(value);
Ok(())
}
}
39 changes: 30 additions & 9 deletions core/src/valids.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
use alloc::vec::Vec;
use crate::Opcode;

/// A valid marker.
#[repr(u8)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Valid {
/// Invalid position.
None,
/// Valid jump destination.
JumpDest,
/// Valid begin subroutine.
BeginSub,
}

/// Mapping of valid jump destination from code.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Valids(Vec<bool>);
pub struct Valids(Vec<Valid>);

impl Valids {
/// Create a new valid mapping from given code bytes.
pub fn new(code: &[u8]) -> Self {
let mut valids: Vec<bool> = Vec::with_capacity(code.len());
valids.resize(code.len(), false);
let mut valids: Vec<Valid> = Vec::with_capacity(code.len());
valids.resize(code.len(), Valid::None);

let mut i = 0;
while i < code.len() {
let opcode = Opcode(code[i]);
if opcode == Opcode::JUMPDEST {
valids[i] = true;
valids[i] = Valid::JumpDest;
i += 1;
} else if opcode == Opcode::BEGINSUB {
valids[i] = Valid::BeginSub;
i += 1;
} else if let Some(v) = opcode.is_push() {
i += v as usize + 1;
Expand All @@ -42,15 +57,21 @@ impl Valids {

/// Returns `true` if the position is a valid jump destination. If
/// not, returns `false`.
pub fn is_valid(&self, position: usize) -> bool {
pub fn is_jumpdest(&self, position: usize) -> bool {
if position >= self.0.len() {
return false;
return false
}

if !self.0[position] {
return false;
self.0[position] == Valid::JumpDest
}

/// Returns `true` if the position is a valid begin subroutine. If
/// not, returns `false`.
pub fn is_beginsub(&self, position: usize) -> bool {
if position >= self.0.len() {
return false
}

true
self.0[position] == Valid::BeginSub
}
}
2 changes: 1 addition & 1 deletion core/tests/performance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ macro_rules! ret_test {
let code = hex::decode($code).unwrap();
let data = hex::decode($data).unwrap();

let mut vm = Machine::new(Rc::new(code), Rc::new(data), 1024, 10000);
let mut vm = Machine::new(Rc::new(code), Rc::new(data), 1024, 10000, None);
assert_eq!(vm.run(), Capture::Exit(ExitSucceed::Returned.into()));
assert_eq!(vm.return_value(), hex::decode($ret).unwrap());
}
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,6 @@ pub trait Handler {
_opcode: Opcode,
_stack: &mut Machine
) -> Result<(), ExitError> {
Err(ExitError::OutOfGas)
Err(ExitError::UnknownOpcode)
}
}
48 changes: 47 additions & 1 deletion runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ impl<'config> Runtime<'config> {
config: &'config Config,
) -> Self {
Self {
machine: Machine::new(code, data, config.stack_limit, config.memory_limit),
machine: Machine::new(code, data, config.stack_limit, config.memory_limit, config.returns_limit),
status: Ok(()),
return_data_buffer: Vec::new(),
context,
Expand Down Expand Up @@ -176,6 +176,8 @@ pub struct Config {
pub stack_limit: usize,
/// Memory limit.
pub memory_limit: usize,
/// Returns stake limit.
pub returns_limit: Option<usize>,
/// Call limit.
pub call_stack_limit: usize,
/// Create contract limit.
Expand Down Expand Up @@ -229,6 +231,7 @@ impl Config {
call_l64_after_gas: false,
stack_limit: 1024,
memory_limit: usize::max_value(),
returns_limit: None,
call_stack_limit: 1024,
create_contract_limit: None,
call_stipend: 2300,
Expand Down Expand Up @@ -270,6 +273,49 @@ impl Config {
call_l64_after_gas: true,
stack_limit: 1024,
memory_limit: usize::max_value(),
returns_limit: None,
call_stack_limit: 1024,
create_contract_limit: Some(0x6000),
call_stipend: 2300,
has_delegate_call: true,
has_create2: true,
has_revert: true,
has_return_data: true,
has_bitwise_shifting: true,
has_chain_id: true,
has_self_balance: true,
has_ext_code_hash: true,
estimate: false,
}
}

/// Istanbul hard fork configuration.
pub const fn berlin() -> Config {
Config {
gas_ext_code: 700,
gas_ext_code_hash: 700,
gas_balance: 700,
gas_sload: 800,
gas_sstore_set: 20000,
gas_sstore_reset: 5000,
refund_sstore_clears: 15000,
gas_suicide: 5000,
gas_suicide_new_account: 25000,
gas_call: 700,
gas_expbyte: 50,
gas_transaction_create: 53000,
gas_transaction_call: 21000,
gas_transaction_zero_data: 4,
gas_transaction_non_zero_data: 16,
sstore_gas_metering: true,
sstore_revert_under_stipend: true,
err_on_call_with_more_gas: false,
empty_considered_exists: false,
create_increase_nonce: true,
call_l64_after_gas: true,
stack_limit: 1024,
memory_limit: usize::max_value(),
returns_limit: Some(1024),
call_stack_limit: 1024,
create_contract_limit: Some(0x6000),
call_stipend: 2300,
Expand Down