diff --git a/features/evm64/Cargo.toml b/features/evm64/Cargo.toml index 55ae4a7ce..6ee45ef30 100644 --- a/features/evm64/Cargo.toml +++ b/features/evm64/Cargo.toml @@ -9,4 +9,13 @@ repository = { workspace = true } keywords = { workspace = true } [dependencies] -evm = { path = "../.." } +primitive-types = { version = "0.12", default-features = false, features = ["rlp"] } + +evm = { path = "../..", default-features = false } + +[features] +default = ["std"] +std = [ + "primitive-types/std", + "evm/std", +] diff --git a/features/evm64/src/eval.rs b/features/evm64/src/eval.rs index b797d0570..b6b11e92c 100644 --- a/features/evm64/src/eval.rs +++ b/features/evm64/src/eval.rs @@ -1,9 +1,457 @@ -use evm::interpreter::{etable::Control, machine::Machine}; +use core::ops::{BitAnd, BitOr, BitXor}; +use primitive_types::U256; + +use evm::interpreter::{error::ExitException, etable::Control, machine::Machine}; + +macro_rules! pop_u64 { + ( $machine:expr, $( $x:ident ),* ) => ( + $( + let $x = match $machine.stack.pop() { + Ok(value) => value.0[0], + Err(e) => return Control::Exit(e.into()), + }; + )* + ); +} + +macro_rules! push_u64 { + ( $machine:expr, $( $x:expr ),* ) => ( + $( + match $machine.stack.push(U256([$x, 0, 0, 0])) { + Ok(()) => (), + Err(e) => return Control::Exit(e.into()), + } + )* + ) +} + +macro_rules! op1_u64_fn { + ($machine:expr, $op:path) => {{ + pop_u64!($machine, op1); + let ret = $op(op1); + push_u64!($machine, ret); + + Control::Continue(1) + }}; +} + +macro_rules! op2_u64_bool_ref { + ($machine:expr, $op:ident) => {{ + pop_u64!($machine, op1, op2); + let ret = op1.$op(&op2); + push_u64!($machine, if ret { 1 } else { 0 }); + + Control::Continue(1) + }}; +} + +macro_rules! op2_u64 { + ($machine:expr, $op:ident) => {{ + pop_u64!($machine, op1, op2); + let ret = op1.$op(op2); + push_u64!($machine, ret); + + Control::Continue(1) + }}; +} + +macro_rules! op2_u64_tuple { + ($machine:expr, $op:ident) => {{ + pop_u64!($machine, op1, op2); + let (ret, ..) = op1.$op(op2); + push_u64!($machine, ret); + + Control::Continue(1) + }}; +} + +macro_rules! op2_u64_fn { + ($machine:expr, $op:path) => {{ + pop_u64!($machine, op1, op2); + let ret = $op(op1, op2); + push_u64!($machine, ret); + + Control::Continue(1) + }}; +} + +macro_rules! op3_u64_fn { + ($machine:expr, $op:path) => {{ + pop_u64!($machine, op1, op2, op3); + let ret = $op(op1, op2, op3); + push_u64!($machine, ret); + + Control::Continue(1) + }}; +} pub fn eval_add( - _machine: &mut Machine, + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64_tuple!(machine, overflowing_add) +} + +pub fn eval_mul( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64_tuple!(machine, overflowing_mul) +} + +pub fn eval_sub( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64_tuple!(machine, overflowing_sub) +} + +#[inline] +fn div(op1: u64, op2: u64) -> u64 { + if op2 == 0 { + 0 + } else { + op1 / op2 + } +} + +pub fn eval_div( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64_fn!(machine, div) +} + +#[inline] +pub fn sdiv(op1: u64, op2: u64) -> u64 { + let op1 = op1 as i64; + let op2 = op2 as i64; + let ret = op1 / op2; + ret as u64 +} + +pub fn eval_sdiv( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64_fn!(machine, sdiv) +} + +#[inline] +pub fn rem(op1: u64, op2: u64) -> u64 { + if op2 == 0 { + 0 + } else { + // For unsigned integers overflow never occurs. + op1.overflowing_rem(op2).0 + } +} + +pub fn eval_mod( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64_fn!(machine, rem) +} + +#[inline] +pub fn srem(op1: u64, op2: u64) -> u64 { + if op2 == 0 { + 0 + } else { + let op1 = op1 as i64; + let op2 = op2 as i64; + let ret = op1.overflowing_rem(op2).0; + ret as u64 + } +} + +pub fn eval_smod( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64_fn!(machine, srem) +} + +#[inline] +pub fn addmod(op1: u64, op2: u64, op3: u64) -> u64 { + let op1 = op1 as u128; + let op2 = op2 as u128; + let op3 = op3 as u128; + + if op3 == 0 { + 0 + } else { + let v = (op1 + op2) % op3; + v as u64 + } +} + +pub fn eval_addmod( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op3_u64_fn!(machine, addmod) +} + +#[inline] +pub fn mulmod(op1: u64, op2: u64, op3: u64) -> u64 { + let op1 = op1 as u128; + let op2 = op2 as u128; + let op3 = op3 as u128; + + if op3 == 0 { + 0 + } else { + let v = (op1 * op2) % op3; + v as u64 + } +} + +pub fn eval_mulmod( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op3_u64_fn!(machine, mulmod) +} + +#[inline] +pub fn exp(op1: u64, op2: u64) -> u64 { + let mut op1 = op1; + let mut op2 = op2; + let mut r = 1u64; + + while op2 != 0 { + if op2 & 1 != 0 { + r = r.overflowing_mul(op1).0; + } + op2 >>= 1; + op1 = op1.overflowing_mul(op1).0; + } + + r +} + +pub fn eval_exp( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64_fn!(machine, exp) +} + +pub fn eval_lt( + machine: &mut Machine, _handle: &mut H, _position: usize, ) -> Control { - todo!() + op2_u64_bool_ref!(machine, lt) +} + +pub fn eval_gt( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64_bool_ref!(machine, gt) +} + +#[inline] +pub fn slt(op1: u64, op2: u64) -> u64 { + let op1 = op1 as i64; + let op2 = op2 as i64; + + if op1.lt(&op2) { + 1 + } else { + 0 + } +} + +pub fn eval_slt( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64_fn!(machine, slt) +} + +#[inline] +pub fn sgt(op1: u64, op2: u64) -> u64 { + let op1 = op1 as i64; + let op2 = op2 as i64; + + if op1.gt(&op2) { + 1 + } else { + 0 + } +} + +pub fn eval_sgt( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64_fn!(machine, sgt) +} + +pub fn eval_eq( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64_bool_ref!(machine, eq) +} + +#[inline] +pub fn iszero(op1: u64) -> u64 { + if op1 == 0 { + 1 + } else { + 0 + } +} + +pub fn eval_iszero( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op1_u64_fn!(machine, iszero) +} + +pub fn eval_and( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64!(machine, bitand) +} + +pub fn eval_or( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64!(machine, bitor) +} + +pub fn eval_xor( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64!(machine, bitxor) +} + +#[inline] +pub fn not(op1: u64) -> u64 { + !op1 +} + +pub fn eval_not( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op1_u64_fn!(machine, not) +} + +#[inline] +pub fn shl(shift: u64, value: u64) -> u64 { + if value == 0 || shift >= 64 { + 0 + } else { + value << shift as usize + } +} + +pub fn eval_shl( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64_fn!(machine, shl) +} + +#[inline] +pub fn shr(shift: u64, value: u64) -> u64 { + if value == 0 || shift >= 64 { + 0 + } else { + value >> shift as usize + } +} + +pub fn eval_shr( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64_fn!(machine, shr) +} + +#[inline] +pub fn sar(shift: u64, value: u64) -> u64 { + let value = value as i64; + + let ret = if value == 0 || shift >= 64 { + if value >= 0 { + 0 + } else { + -1 + } + } else { + value >> shift as usize + }; + + ret as u64 +} + +pub fn eval_sar( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + op2_u64_fn!(machine, sar) +} + +pub fn eval_jump( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + pop_u64!(machine, dest); + + if let Ok(dest) = usize::try_from(dest) { + Control::Jump(dest) + } else { + Control::Exit(ExitException::InvalidJump.into()) + } +} + +pub fn eval_jumpi( + machine: &mut Machine, + _handle: &mut H, + _position: usize, +) -> Control { + pop_u64!(machine, dest, value); + + if value == 0 { + Control::Continue(1) + } else if let Ok(dest) = usize::try_from(dest) { + Control::Jump(dest) + } else { + Control::Exit(ExitException::InvalidJump.into()) + } } diff --git a/features/evm64/src/gasometer.rs b/features/evm64/src/gasometer.rs index f6c26426c..bf281dedc 100644 --- a/features/evm64/src/gasometer.rs +++ b/features/evm64/src/gasometer.rs @@ -1,15 +1,86 @@ use evm::{ - interpreter::{etable::Control, machine::Machine}, + interpreter::{error::ExitException, etable::Control, machine::Machine, opcode::Opcode}, standard::GasometerState, }; +macro_rules! try_or_fail { + ($e:expr) => { + match $e { + Ok(v) => v, + Err(e) => return Control::Exit(e.into()), + } + }; +} + pub fn eval<'config, S, H, Tr>( - _machine: &mut Machine, + machine: &mut Machine, _handler: &mut H, - _position: usize, + position: usize, ) -> Control where S: AsRef> + AsMut>, { - todo!() + const _G_BASE64: u64 = 1; + const G_VERYLOW64: u64 = 2; + const G_LOW64: u64 = 3; + const G_MID64: u64 = 5; + const G_HIGH64: u64 = 7; + const G_EXP64_STATIC: u64 = 5; + const G_EXP64_DYNAMIC: u64 = 25; + + const STATIC_COST_TABLE: [u64; 256] = { + let mut table = [0; 256]; + table[Opcode::ADD.as_usize()] = G_VERYLOW64; + table[Opcode::SUB.as_usize()] = G_VERYLOW64; + table[Opcode::MUL.as_usize()] = G_LOW64; + table[Opcode::DIV.as_usize()] = G_LOW64; + table[Opcode::SDIV.as_usize()] = G_LOW64; + table[Opcode::MOD.as_usize()] = G_LOW64; + table[Opcode::SMOD.as_usize()] = G_LOW64; + table[Opcode::ADDMOD.as_usize()] = G_MID64; + table[Opcode::MULMOD.as_usize()] = G_MID64; + table[Opcode::LT.as_usize()] = G_VERYLOW64; + table[Opcode::GT.as_usize()] = G_VERYLOW64; + table[Opcode::SLT.as_usize()] = G_VERYLOW64; + table[Opcode::SGT.as_usize()] = G_VERYLOW64; + table[Opcode::EQ.as_usize()] = G_VERYLOW64; + table[Opcode::AND.as_usize()] = G_VERYLOW64; + table[Opcode::OR.as_usize()] = G_VERYLOW64; + table[Opcode::XOR.as_usize()] = G_VERYLOW64; + table[Opcode::ISZERO.as_usize()] = G_VERYLOW64; + table[Opcode::NOT.as_usize()] = G_VERYLOW64; + table[Opcode::SHL.as_usize()] = G_VERYLOW64; + table[Opcode::SHR.as_usize()] = G_VERYLOW64; + table[Opcode::SAR.as_usize()] = G_VERYLOW64; + table[Opcode::JUMP.as_usize()] = G_MID64; + table[Opcode::JUMPI.as_usize()] = G_HIGH64; + + table + }; + + let opcode = Opcode(machine.code()[position]); + + try_or_fail!(machine + .state + .as_mut() + .record_gas64(STATIC_COST_TABLE[opcode.as_usize()])); + + #[allow(clippy::single_match)] + match opcode { + Opcode::EXP => { + let power = try_or_fail!(machine.stack.peek(1)).0[0]; + let cost = if power == 0 { + G_EXP64_STATIC + } else { + try_or_fail!(G_EXP64_DYNAMIC + .checked_mul(power.ilog2() as u64 / 8 + 1) + .and_then(|dynamic| G_EXP64_STATIC.checked_add(dynamic)) + .ok_or(ExitException::OutOfGas)) + }; + try_or_fail!(machine.state.as_mut().record_gas64(cost)); + } + _ => (), + } + + Control::NoAction } diff --git a/features/evm64/src/lib.rs b/features/evm64/src/lib.rs index aacb99fd4..04abab5ef 100644 --- a/features/evm64/src/lib.rs +++ b/features/evm64/src/lib.rs @@ -30,6 +30,30 @@ where let mut mode_etable = Etable::none(); mode_etable[Opcode::ADD.as_usize()] = eval::eval_add; + mode_etable[Opcode::MUL.as_usize()] = eval::eval_mul; + mode_etable[Opcode::SUB.as_usize()] = eval::eval_sub; + mode_etable[Opcode::DIV.as_usize()] = eval::eval_div; + mode_etable[Opcode::SDIV.as_usize()] = eval::eval_sdiv; + mode_etable[Opcode::MOD.as_usize()] = eval::eval_mod; + mode_etable[Opcode::SMOD.as_usize()] = eval::eval_smod; + mode_etable[Opcode::ADDMOD.as_usize()] = eval::eval_addmod; + mode_etable[Opcode::MULMOD.as_usize()] = eval::eval_mulmod; + mode_etable[Opcode::EXP.as_usize()] = eval::eval_exp; + mode_etable[Opcode::LT.as_usize()] = eval::eval_lt; + mode_etable[Opcode::GT.as_usize()] = eval::eval_gt; + mode_etable[Opcode::SLT.as_usize()] = eval::eval_slt; + mode_etable[Opcode::SGT.as_usize()] = eval::eval_sgt; + mode_etable[Opcode::EQ.as_usize()] = eval::eval_eq; + mode_etable[Opcode::ISZERO.as_usize()] = eval::eval_iszero; + mode_etable[Opcode::AND.as_usize()] = eval::eval_and; + mode_etable[Opcode::OR.as_usize()] = eval::eval_or; + mode_etable[Opcode::XOR.as_usize()] = eval::eval_xor; + mode_etable[Opcode::NOT.as_usize()] = eval::eval_not; + mode_etable[Opcode::SHL.as_usize()] = eval::eval_shl; + mode_etable[Opcode::SHR.as_usize()] = eval::eval_shr; + mode_etable[Opcode::SAR.as_usize()] = eval::eval_sar; + mode_etable[Opcode::JUMP.as_usize()] = eval::eval_jump; + mode_etable[Opcode::JUMPI.as_usize()] = eval::eval_jumpi; gasometer_etable[OPCODE_EVM64_MODE.as_usize()] = eval_gasometer; eval_etable[OPCODE_EVM64_MODE.as_usize()] = MultiEfn::Node(Box::new(mode_etable.into()));