Skip to content
Merged
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
2 changes: 1 addition & 1 deletion crates/interpreter/src/gas/calc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,4 +421,4 @@ pub fn validate_initial_tx_gas(
}

initial_gas
}
}
66 changes: 64 additions & 2 deletions crates/interpreter/src/instructions/bitwise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ pub fn gt<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
*op2 = U256::from(op1 > *op2);
}

/// Implements the CLZ instruction - count leading zeros.
pub fn clz<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
// check!(interpreter, OSAKA);
// gas!(interpreter, gas::LOW);
pop_top!(interpreter, op1);

let leading_zeros = op1.leading_zeros();
*op1 = U256::from(leading_zeros);
}

pub fn slt<H: Host + ?Sized>(interpreter: &mut Interpreter, _host: &mut H) {
gas!(interpreter, gas::VERYLOW);
pop_top!(interpreter, op1, op2);
Expand Down Expand Up @@ -123,9 +133,9 @@ pub fn sar<H: Host + ?Sized, SPEC: Spec>(interpreter: &mut Interpreter, _host: &

#[cfg(test)]
mod tests {
use crate::instructions::bitwise::{byte, sar, shl, shr};
use crate::instructions::bitwise::{byte, clz, sar, shl, shr};
use crate::{Contract, DummyHost, Interpreter};
use revm_primitives::{uint, Env, LatestSpec, U256};
use revm_primitives::{uint, Env, LatestSpec, SpecId, U256};

#[test]
fn test_shift_left() {
Expand Down Expand Up @@ -429,4 +439,56 @@ mod tests {
assert_eq!(res, test.expected, "Failed at index: {}", test.index);
}
}

#[test]
fn test_clz() {
let mut host = DummyHost::new(Env::default());
let mut interpreter = Interpreter::default();
struct TestCase {
value: U256,
expected: U256,
}

uint! {
let test_cases = [
TestCase { value: 0x0_U256, expected: 256_U256 },
TestCase { value: 0x1_U256, expected: 255_U256 },
TestCase { value: 0x2_U256, expected: 254_U256 },
TestCase { value: 0x3_U256, expected: 254_U256 },
TestCase { value: 0x4_U256, expected: 253_U256 },
TestCase { value: 0x7_U256, expected: 253_U256 },
TestCase { value: 0x8_U256, expected: 252_U256 },
TestCase { value: 0xff_U256, expected: 248_U256 },
TestCase { value: 0x100_U256, expected: 247_U256 },
TestCase { value: 0xffff_U256, expected: 240_U256 },
TestCase {
value: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256, // U256::MAX
expected: 0_U256,
},
TestCase {
value: 0x8000000000000000000000000000000000000000000000000000000000000000_U256, // 1 << 255
expected: 0_U256,
},
TestCase { // Smallest value with 1 leading zero
value: 0x4000000000000000000000000000000000000000000000000000000000000000_U256, // 1 << 254
expected: 1_U256,
},
TestCase { // Value just below 1 << 255
value: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff_U256,
expected: 1_U256,
},
];
}

for test in test_cases {
push!(interpreter, test.value);
clz(&mut interpreter, &mut host);
let res = interpreter.stack.pop().unwrap();
assert_eq!(
res, test.expected,
"CLZ for value {:#x} failed. Expected: {}, Got: {}",
test.value, test.expected, res
);
}
}
}
6 changes: 3 additions & 3 deletions crates/interpreter/src/opcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ opcodes! {
0x1B => SHL => bitwise::shl::<H, SPEC> => stack_io(2, 1);
0x1C => SHR => bitwise::shr::<H, SPEC> => stack_io(2, 1);
0x1D => SAR => bitwise::sar::<H, SPEC> => stack_io(2, 1);
// 0x1E
0x1E => CLZ => bitwise::clz => stack_io(1, 1);
// 0x1F
0x20 => KECCAK256 => system::keccak256 => stack_io(2, 1);
// 0x21
Expand Down Expand Up @@ -792,8 +792,8 @@ mod tests {
eof_opcode_num += 1;
}
}
assert_eq!(opcode_num, 168);
assert_eq!(eof_opcode_num, 152);
assert_eq!(opcode_num, 169);
assert_eq!(eof_opcode_num, 153);
}

#[test]
Expand Down
21 changes: 21 additions & 0 deletions crates/precompile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ impl Precompiles {
PrecompileSpecId::MORPH203 => Self::morph203(),
#[cfg(feature = "morph")]
PrecompileSpecId::VIRIDIAN => Self::viridian(),
#[cfg(feature = "morph")]
PrecompileSpecId::EMERALD => Self::emerald(),
PrecompileSpecId::CANCUN => Self::cancun(),
PrecompileSpecId::PRAGUE => Self::prague(),
PrecompileSpecId::LATEST => Self::latest(),
Expand Down Expand Up @@ -247,6 +249,21 @@ impl Precompiles {
})
}

/// Returns precompiles for Morph
#[cfg(feature = "morph")]
pub fn emerald() -> &'static Self {
static INSTANCE: OnceBox<Precompiles> = OnceBox::new();
INSTANCE.get_or_init(|| {
let precompiles = Self::viridian().clone();
precompiles.extend(bls12_381::precompiles()); // add BLS12-381 precompiles
precompiles.extend([
modexp::OSAKA, // 0x05
secp256r1::P256VERIFY_OSAKA,
]);
Box::new(precompiles)
})
}

/// Returns the precompiles for the latest spec.
pub fn latest() -> &'static Self {
Self::prague()
Expand Down Expand Up @@ -352,6 +369,8 @@ pub enum PrecompileSpecId {
MORPH203,
#[cfg(feature = "morph")]
VIRIDIAN,
#[cfg(feature = "morph")]
EMERALD,
CANCUN,
PRAGUE,
LATEST,
Expand Down Expand Up @@ -383,6 +402,8 @@ impl PrecompileSpecId {
MORPH203 => Self::MORPH203,
#[cfg(feature = "morph")]
VIRIDIAN => Self::VIRIDIAN,
#[cfg(feature = "morph")]
EMERALD => Self::EMERALD,
}
}
}
Expand Down
125 changes: 90 additions & 35 deletions crates/precompile/src/modexp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,20 @@ pub const BYZANTIUM: PrecompileWithAddress = PrecompileWithAddress(
pub const BERLIN: PrecompileWithAddress =
PrecompileWithAddress(crate::u64_to_address(5), Precompile::Standard(berlin_run));

/// `modexp` precompile with OSAKA gas rules.
pub const OSAKA: PrecompileWithAddress =
PrecompileWithAddress(crate::u64_to_address(5), Precompile::Standard(osaka_run));

#[cfg(feature = "morph")]
pub const BERNOULLI: PrecompileWithAddress = PrecompileWithAddress(
crate::u64_to_address(5),
Precompile::Standard(bernoilli_run),
);

#[cfg(feature = "morph")]
pub const OSAKA: PrecompileWithAddress =
PrecompileWithAddress(crate::u64_to_address(5), Precompile::Standard(osaka_run));

/// See: <https://eips.ethereum.org/EIPS/eip-198>
/// See: <https://etherscan.io/address/0000000000000000000000000000000000000005>
pub fn byzantium_run(input: &Bytes, gas_limit: u64) -> PrecompileResult {
Expand All @@ -38,6 +46,14 @@ pub fn berlin_run(input: &Bytes, gas_limit: u64) -> PrecompileResult {
})
}

/// See: <https://eips.ethereum.org/EIPS/eip-7823>
/// Gas cost of berlin is modified from byzantium.
pub fn osaka_run(input: &Bytes, gas_limit: u64) -> PrecompileResult {
run_inner(input, gas_limit, 500, |a, b, c, d| {
osaka_gas_calc(a, b, c, d)
})
}

#[cfg(feature = "morph")]
pub fn bernoilli_run(input: &Bytes, gas_limit: u64) -> PrecompileResult {
let base_len = U256::from_be_bytes(right_pad_with_offset::<32>(input, 0).into_owned());
Expand All @@ -60,15 +76,39 @@ pub fn bernoilli_run(input: &Bytes, gas_limit: u64) -> PrecompileResult {
})
}

pub fn calculate_iteration_count(exp_length: u64, exp_highp: &U256) -> u64 {
#[cfg(feature = "morph")]
/// See: <https://eips.ethereum.org/EIPS/eip-7823>
/// Gas cost of berlin is modified from byzantium.
pub fn osaka_run(input: &[u8], gas_limit: u64) -> PrecompileResult {
let base_len = U256::from_be_bytes(right_pad_with_offset::<32>(input, 0).into_owned());
let exp_len = U256::from_be_bytes(right_pad_with_offset::<32>(input, 32).into_owned());
let mod_len = U256::from_be_bytes(right_pad_with_offset::<32>(input, 64).into_owned());

// modexp temporarily only accepts inputs of 32 bytes (256 bits) or less
if base_len > MORPH_LEN_LIMIT {
return Err(Error::ModexpBaseOverflow.into());
}
if exp_len > MORPH_LEN_LIMIT {
return Err(Error::ModexpExpOverflow.into());
}
if mod_len > MORPH_LEN_LIMIT {
return Err(Error::ModexpModOverflow.into());
}
run_inner::<_, true>(input, gas_limit, 500, |a, b, c, d| {
osaka_gas_calc(a, b, c, d)
})
}

/// Calculate the iteration count for the modexp precompile.
pub fn calculate_iteration_count<const MULTIPLIER: u64>(exp_length: u64, exp_highp: &U256) -> u64 {
let mut iteration_count: u64 = 0;

if exp_length <= 32 && exp_highp.is_zero() {
iteration_count = 0;
} else if exp_length <= 32 {
iteration_count = exp_highp.bit_len() as u64 - 1;
} else if exp_length > 32 {
iteration_count = (8u64.saturating_mul(exp_length - 32))
iteration_count = (MULTIPLIER.saturating_mul(exp_length - 32))
.saturating_add(max(1, exp_highp.bit_len() as u64) - 1);
}

Expand Down Expand Up @@ -150,50 +190,65 @@ where
))
}

/// Calculate the gas cost for the modexp precompile with BYZANTIUM gas rules.
pub fn byzantium_gas_calc(base_len: u64, exp_len: u64, mod_len: u64, exp_highp: &U256) -> u64 {
// output of this function is bounded by 2^128
fn mul_complexity(x: u64) -> U256 {
if x <= 64 {
U256::from(x * x)
} else if x <= 1_024 {
U256::from(x * x / 4 + 96 * x - 3_072)
gas_calc::<0, 8, 20, _>(base_len, exp_len, mod_len, exp_highp, |max_len| -> U256 {
// Output of this function is bounded by 2^128
if max_len <= 64 {
U256::from(max_len * max_len)
} else if max_len <= 1_024 {
U256::from(max_len * max_len / 4 + 96 * max_len - 3_072)
} else {
// up-cast to avoid overflow
let x = U256::from(x);
// Up-cast to avoid overflow
let x = U256::from(max_len);
let x_sq = x * x; // x < 2^64 => x*x < 2^128 < 2^256 (no overflow)
x_sq / U256::from(16) + U256::from(480) * x - U256::from(199_680)
}
}

let mul = mul_complexity(core::cmp::max(mod_len, base_len));
let iter_count = U256::from(calculate_iteration_count(exp_len, exp_highp));
// mul * iter_count bounded by 2^195 < 2^256 (no overflow)
let gas = (mul * iter_count) / U256::from(20);
gas.saturating_to()
})
}

// Calculate gas cost according to EIP 2565:
// https://eips.ethereum.org/EIPS/eip-2565
pub fn berlin_gas_calc(
base_length: u64,
exp_length: u64,
mod_length: u64,
exp_highp: &U256,
) -> u64 {
fn calculate_multiplication_complexity(base_length: u64, mod_length: u64) -> U256 {
let max_length = max(base_length, mod_length);
let mut words = max_length / 8;
if max_length % 8 > 0 {
words += 1;
}
let words = U256::from(words);
pub fn berlin_gas_calc(base_len: u64, exp_len: u64, mod_len: u64, exp_highp: &U256) -> u64 {
gas_calc::<200, 8, 3, _>(base_len, exp_len, mod_len, exp_highp, |max_len| -> U256 {
let words = U256::from(max_len.div_ceil(8));
words * words
}
})
}

/// Calculate gas cost according to EIP-7883:
/// <https://eips.ethereum.org/EIPS/eip-7883>
///
/// There are three changes:
/// 1. Increase minimal price from 200 to 500
/// 2. Increase cost when exponent is larger than 32 bytes
/// 3. Increase cost when base or modulus is larger than 32 bytes
pub fn osaka_gas_calc(base_len: u64, exp_len: u64, mod_len: u64, exp_highp: &U256) -> u64 {
gas_calc::<500, 16, 1, _>(base_len, exp_len, mod_len, exp_highp, |max_len| -> U256 {
if max_len <= 32 {
return U256::from(16); // multiplication_complexity = 16
}

let words = U256::from(max_len.div_ceil(8));
words * words * U256::from(2) // multiplication_complexity = 2 * words**2
})
}

let multiplication_complexity = calculate_multiplication_complexity(base_length, mod_length);
let iteration_count = calculate_iteration_count(exp_length, exp_highp);
let gas = (multiplication_complexity * U256::from(iteration_count)) / U256::from(3);
max(200, gas.saturating_to())
/// Calculate gas cost.
pub fn gas_calc<const MIN_PRICE: u64, const MULTIPLIER: u64, const GAS_DIVISOR: u64, F>(
base_len: u64,
exp_len: u64,
mod_len: u64,
exp_highp: &U256,
calculate_multiplication_complexity: F,
) -> u64
where
F: Fn(u64) -> U256,
{
let multiplication_complexity = calculate_multiplication_complexity(max(base_len, mod_len));
let iteration_count = calculate_iteration_count::<MULTIPLIER>(exp_len, exp_highp);
let gas = (multiplication_complexity * U256::from(iteration_count)) / U256::from(GAS_DIVISOR);
max(MIN_PRICE, gas.saturating_to())
}

#[cfg(test)]
Expand Down
Loading
Loading