Skip to content

Commit

Permalink
Merge branch 'main' into levm/increase-call-cost
Browse files Browse the repository at this point in the history
  • Loading branch information
tomip01 authored Jan 9, 2025
2 parents df93555 + ae00775 commit f48364f
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 67 deletions.
182 changes: 139 additions & 43 deletions crates/vm/levm/src/gas_cost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ pub const EXTCODECOPY_WARM_DYNAMIC: u64 = DEFAULT_WARM_DYNAMIC;
pub const CALL_STATIC: u64 = DEFAULT_STATIC;
pub const CALL_COLD_DYNAMIC: u64 = DEFAULT_COLD_DYNAMIC;
pub const CALL_WARM_DYNAMIC: u64 = DEFAULT_WARM_DYNAMIC;
pub const CALL_PRE_BERLIN: u64 = 700;
pub const CALL_POSITIVE_VALUE: u64 = 9000;
pub const CALL_POSITIVE_VALUE_STIPEND: u64 = 2300;
pub const CALL_TO_EMPTY_ACCOUNT: u64 = 25000;
Expand Down Expand Up @@ -158,6 +159,7 @@ pub const CREATE_BASE_COST: u64 = 32000;
// Calldata costs
pub const CALLDATA_COST_ZERO_BYTE: u64 = 4;
pub const CALLDATA_COST_NON_ZERO_BYTE: u64 = 16;
pub const CALLDATA_COST_NON_ZERO_BYTE_PRE_ISTANBUL: u64 = 68;
pub const STANDARD_TOKEN_COST: u64 = 4;

// Blob gas costs
Expand Down Expand Up @@ -186,6 +188,8 @@ pub const MODEXP_STATIC_COST: u64 = 200;
pub const MODEXP_DYNAMIC_BASE: u64 = 200;
pub const MODEXP_DYNAMIC_QUOTIENT: u64 = 3;

pub const MODEXP_DYNAMIC_QUOTIENT_PRE_BERLIN: u64 = 20;

pub const ECADD_COST: u64 = 150;
pub const ECMUL_COST: u64 = 6000;

Expand Down Expand Up @@ -528,19 +532,26 @@ pub fn selfdestruct(
Ok(gas_cost)
}

pub fn tx_calldata(calldata: &Bytes) -> Result<u64, OutOfGasError> {
pub fn tx_calldata(calldata: &Bytes, spec_id: SpecId) -> Result<u64, OutOfGasError> {
// This cost applies both for call and create
// 4 gas for each zero byte in the transaction data 16 gas for each non-zero byte in the transaction.
let mut calldata_cost: u64 = 0;
for byte in calldata {
if *byte != 0 {
calldata_cost = calldata_cost
.checked_add(CALLDATA_COST_NON_ZERO_BYTE)
.ok_or(OutOfGasError::GasUsedOverflow)?;
calldata_cost = if *byte != 0 {
if spec_id >= SpecId::ISTANBUL {
calldata_cost
.checked_add(CALLDATA_COST_NON_ZERO_BYTE)
.ok_or(OutOfGasError::GasUsedOverflow)?
} else {
// EIP-2028
calldata_cost
.checked_add(CALLDATA_COST_NON_ZERO_BYTE_PRE_ISTANBUL)
.ok_or(OutOfGasError::GasUsedOverflow)?
}
} else {
calldata_cost = calldata_cost
calldata_cost
.checked_add(CALLDATA_COST_ZERO_BYTE)
.ok_or(OutOfGasError::GasUsedOverflow)?;
.ok_or(OutOfGasError::GasUsedOverflow)?
}
}
Ok(calldata_cost)
Expand Down Expand Up @@ -633,6 +644,7 @@ pub fn extcodehash(address_was_cold: bool) -> Result<u64, VMError> {
)
}

#[allow(clippy::too_many_arguments)]
pub fn call(
new_memory_size: usize,
current_memory_size: usize,
Expand All @@ -641,14 +653,20 @@ pub fn call(
value_to_transfer: U256,
gas_from_stack: U256,
gas_left: u64,
spec_id: SpecId,
) -> Result<(u64, u64), VMError> {
let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?;

let address_access_cost = address_access_cost(
address_was_cold,
CALL_STATIC,
CALL_COLD_DYNAMIC,
CALL_WARM_DYNAMIC,
if spec_id >= SpecId::BERLIN {
CALL_WARM_DYNAMIC
} else {
//https://eips.ethereum.org/EIPS/eip-2929
CALL_PRE_BERLIN
},
)?;
let positive_value_cost = if !value_to_transfer.is_zero() {
CALL_POSITIVE_VALUE
Expand Down Expand Up @@ -807,11 +825,120 @@ pub fn identity(data_size: usize) -> Result<u64, VMError> {
precompile(data_size, IDENTITY_STATIC_COST, IDENTITY_DYNAMIC_BASE)
}

//https://eips.ethereum.org/EIPS/eip-2565
pub fn modexp_eip2565(
max_length: u64,
exponent_first_32_bytes: &BigUint,
exponent_size: u64,
) -> Result<u64, VMError> {
let words = (max_length
.checked_add(7)
.ok_or(OutOfGasError::GasCostOverflow)?)
.checked_div(8)
.ok_or(InternalError::DivisionError)?;
let multiplication_complexity = words.checked_pow(2).ok_or(OutOfGasError::GasCostOverflow)?;

let calculate_iteration_count =
if exponent_size <= 32 && *exponent_first_32_bytes != BigUint::ZERO {
exponent_first_32_bytes
.bits()
.checked_sub(1)
.ok_or(InternalError::ArithmeticOperationUnderflow)?
} else if exponent_size > 32 {
let extra_size = (exponent_size
.checked_sub(32)
.ok_or(InternalError::ArithmeticOperationUnderflow)?)
.checked_mul(8)
.ok_or(OutOfGasError::GasCostOverflow)?;
extra_size
.checked_add(exponent_first_32_bytes.bits().max(1))
.ok_or(OutOfGasError::GasCostOverflow)?
.checked_sub(1)
.ok_or(InternalError::ArithmeticOperationUnderflow)?
} else {
0
}
.max(1);

let cost = MODEXP_STATIC_COST.max(
multiplication_complexity
.checked_mul(calculate_iteration_count)
.ok_or(OutOfGasError::GasCostOverflow)?
/ MODEXP_DYNAMIC_QUOTIENT,
);
Ok(cost)
}

//https://eips.ethereum.org/EIPS/eip-198
pub fn modexp_eip198(
max_length: u64,
exponent_first_32_bytes: &BigUint,
exponent_size: u64,
) -> Result<u64, VMError> {
let multiplication_complexity = if max_length <= 64 {
max_length
.checked_pow(2)
.ok_or(OutOfGasError::GasCostOverflow)?
} else if max_length <= 1024 {
max_length
.checked_pow(2)
.ok_or(OutOfGasError::GasCostOverflow)?
.checked_div(4)
.ok_or(OutOfGasError::GasCostOverflow)?
.checked_add(
max_length
.checked_mul(96)
.ok_or(OutOfGasError::GasCostOverflow)?,
)
.ok_or(OutOfGasError::GasCostOverflow)?
.checked_sub(3072)
.ok_or(OutOfGasError::GasCostOverflow)?
} else {
max_length
.checked_pow(2)
.ok_or(OutOfGasError::GasCostOverflow)?
.checked_div(16)
.ok_or(OutOfGasError::GasCostOverflow)?
.checked_add(
max_length
.checked_mul(480)
.ok_or(OutOfGasError::GasCostOverflow)?,
)
.ok_or(OutOfGasError::GasCostOverflow)?
.checked_sub(199680)
.ok_or(OutOfGasError::GasCostOverflow)?
};

let calculate_iteration_count = if exponent_size < 32 {
exponent_first_32_bytes.bits().saturating_sub(1)
} else {
let extra_size = (exponent_size
.checked_sub(32)
.ok_or(InternalError::ArithmeticOperationUnderflow)?)
.checked_mul(8)
.ok_or(OutOfGasError::GasCostOverflow)?;

let bits_part = exponent_first_32_bytes.bits().saturating_sub(1);

extra_size
.checked_add(bits_part)
.ok_or(OutOfGasError::GasCostOverflow)?
}
.max(1);

let cost = multiplication_complexity
.checked_mul(calculate_iteration_count)
.ok_or(OutOfGasError::GasCostOverflow)?
/ MODEXP_DYNAMIC_QUOTIENT_PRE_BERLIN;
Ok(cost)
}

pub fn modexp(
exponent_first_32_bytes: &BigUint,
base_size: usize,
exponent_size: usize,
modulus_size: usize,
spec_id: SpecId,
) -> Result<u64, VMError> {
let base_size: u64 = base_size
.try_into()
Expand All @@ -824,43 +951,12 @@ pub fn modexp(
.map_err(|_| PrecompileError::ParsingInputError)?;

let max_length = base_size.max(modulus_size);
let words = (max_length
.checked_add(7)
.ok_or(OutOfGasError::GasCostOverflow)?)
.checked_div(8)
.ok_or(InternalError::DivisionError)?;

let multiplication_complexity = words.checked_pow(2).ok_or(OutOfGasError::GasCostOverflow)?;

let iteration_count = if exponent_size <= 32 && *exponent_first_32_bytes != BigUint::ZERO {
exponent_first_32_bytes
.bits()
.checked_sub(1)
.ok_or(InternalError::ArithmeticOperationUnderflow)?
} else if exponent_size > 32 {
let extra_size = (exponent_size
.checked_sub(32)
.ok_or(InternalError::ArithmeticOperationUnderflow)?)
.checked_mul(8)
.ok_or(OutOfGasError::GasCostOverflow)?;
extra_size
.checked_add(exponent_first_32_bytes.bits().max(1))
.ok_or(OutOfGasError::GasCostOverflow)?
.checked_sub(1)
.ok_or(InternalError::ArithmeticOperationUnderflow)?
if spec_id >= SpecId::BERLIN {
modexp_eip2565(max_length, exponent_first_32_bytes, exponent_size)
} else {
0
};
let calculate_iteration_count = iteration_count.max(1);

let cost = MODEXP_STATIC_COST.max(
multiplication_complexity
.checked_mul(calculate_iteration_count)
.ok_or(OutOfGasError::GasCostOverflow)?
/ MODEXP_DYNAMIC_QUOTIENT,
);

Ok(cost)
modexp_eip198(max_length, exponent_first_32_bytes, exponent_size)
}
}

fn precompile(data_size: usize, static_cost: u64, dynamic_base: u64) -> Result<u64, VMError> {
Expand Down
1 change: 1 addition & 0 deletions crates/vm/levm/src/opcode_handlers/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ impl VM {
value_to_transfer,
gas,
gas_left,
self.env.spec_id,
)?;
self.increase_consumed_gas(current_call_frame, cost)?;

Expand Down
53 changes: 34 additions & 19 deletions crates/vm/levm/src/precompiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,10 @@ pub fn is_precompile(callee_address: &Address, spec_id: SpecId) -> bool {
PRECOMPILES.contains(callee_address)
}

pub fn execute_precompile(current_call_frame: &mut CallFrame) -> Result<Bytes, VMError> {
pub fn execute_precompile(
current_call_frame: &mut CallFrame,
spec_id: SpecId,
) -> Result<Bytes, VMError> {
let callee_address = current_call_frame.code_address;
let calldata = current_call_frame.calldata.clone();
let gas_for_call = current_call_frame
Expand All @@ -123,7 +126,9 @@ pub fn execute_precompile(current_call_frame: &mut CallFrame) -> Result<Bytes, V
address if address == RIPEMD_160_ADDRESS => {
ripemd_160(&calldata, gas_for_call, consumed_gas)?
}
address if address == MODEXP_ADDRESS => modexp(&calldata, gas_for_call, consumed_gas)?,
address if address == MODEXP_ADDRESS => {
modexp(&calldata, gas_for_call, consumed_gas, spec_id)?
}
address if address == ECADD_ADDRESS => ecadd(&calldata, gas_for_call, consumed_gas)?,
address if address == ECMUL_ADDRESS => ecmul(&calldata, gas_for_call, consumed_gas)?,
address if address == ECPAIRING_ADDRESS => {
Expand Down Expand Up @@ -277,73 +282,83 @@ pub fn modexp(
calldata: &Bytes,
gas_for_call: u64,
consumed_gas: &mut u64,
spec_id: SpecId,
) -> Result<Bytes, VMError> {
// If calldata does not reach the required length, we should fill the rest with zeros
let calldata = fill_with_zeros(calldata, 96)?;

let b_size = U256::from_big_endian(
let base_size = U256::from_big_endian(
calldata
.get(0..32)
.ok_or(PrecompileError::ParsingInputError)?,
);

let e_size = U256::from_big_endian(
let exponent_size = U256::from_big_endian(
calldata
.get(32..64)
.ok_or(PrecompileError::ParsingInputError)?,
);

let m_size = U256::from_big_endian(
let modulus_size = U256::from_big_endian(
calldata
.get(64..96)
.ok_or(PrecompileError::ParsingInputError)?,
);

if b_size == U256::zero() && m_size == U256::zero() {
if base_size == U256::zero() && modulus_size == U256::zero() {
increase_precompile_consumed_gas(gas_for_call, MODEXP_STATIC_COST, consumed_gas)?;
return Ok(Bytes::new());
}

// Because on some cases conversions to usize exploded before the check of the zero value could be done
let b_size = usize::try_from(b_size).map_err(|_| PrecompileError::ParsingInputError)?;
let e_size = usize::try_from(e_size).map_err(|_| PrecompileError::ParsingInputError)?;
let m_size = usize::try_from(m_size).map_err(|_| PrecompileError::ParsingInputError)?;
let base_size = usize::try_from(base_size).map_err(|_| PrecompileError::ParsingInputError)?;
let exponent_size =
usize::try_from(exponent_size).map_err(|_| PrecompileError::ParsingInputError)?;
let modulus_size =
usize::try_from(modulus_size).map_err(|_| PrecompileError::ParsingInputError)?;

let base_limit = b_size
let base_limit = base_size
.checked_add(96)
.ok_or(InternalError::ArithmeticOperationOverflow)?;

let exponent_limit = e_size
let exponent_limit = exponent_size
.checked_add(base_limit)
.ok_or(InternalError::ArithmeticOperationOverflow)?;

let modulus_limit = m_size
let modulus_limit = modulus_size
.checked_add(exponent_limit)
.ok_or(InternalError::ArithmeticOperationOverflow)?;

let b = get_slice_or_default(&calldata, 96, base_limit, b_size)?;
let b = get_slice_or_default(&calldata, 96, base_limit, base_size)?;
let base = BigUint::from_bytes_be(&b);

let e = get_slice_or_default(&calldata, base_limit, exponent_limit, e_size)?;
let e = get_slice_or_default(&calldata, base_limit, exponent_limit, exponent_size)?;
let exponent = BigUint::from_bytes_be(&e);

let m = get_slice_or_default(&calldata, exponent_limit, modulus_limit, m_size)?;
let m = get_slice_or_default(&calldata, exponent_limit, modulus_limit, modulus_size)?;
let modulus = BigUint::from_bytes_be(&m);

// First 32 bytes of exponent or exponent if e_size < 32
let bytes_to_take = 32.min(e_size);
let bytes_to_take = 32.min(exponent_size);
// Use of unwrap_or_default because if e == 0 get_slice_or_default returns an empty vec
let exp_first_32 = BigUint::from_bytes_be(e.get(0..bytes_to_take).unwrap_or_default());

let gas_cost = gas_cost::modexp(&exp_first_32, b_size, e_size, m_size)?;
let gas_cost = gas_cost::modexp(
&exp_first_32,
base_size,
exponent_size,
modulus_size,
spec_id,
)?;

increase_precompile_consumed_gas(gas_for_call, gas_cost, consumed_gas)?;

let result = mod_exp(base, exponent, modulus);

let res_bytes = result.to_bytes_be();
let res_bytes = increase_left_pad(&Bytes::from(res_bytes), m_size)?;
let res_bytes = increase_left_pad(&Bytes::from(res_bytes), modulus_size)?;

Ok(res_bytes.slice(..m_size))
Ok(res_bytes.slice(..modulus_size))
}

/// This function returns the slice between the lower and upper limit of the calldata (as a vector),
Expand Down
Loading

0 comments on commit f48364f

Please sign in to comment.