Skip to content
This repository was archived by the owner on Jul 5, 2024. It is now read-only.
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
5 changes: 4 additions & 1 deletion bus-mapping/src/evm/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ mod swap;
mod error_invalid_jump;
mod error_invalid_opcode;
mod error_oog_call;
mod error_oog_exp;
mod error_oog_log;
mod error_oog_sload_sstore;
mod error_return_data_outofbound;
Expand All @@ -80,6 +81,7 @@ use dup::Dup;
use error_invalid_jump::InvalidJump;
use error_invalid_opcode::InvalidOpcode;
use error_oog_call::OOGCall;
use error_oog_exp::OOGExp;
use error_oog_log::ErrorOOGLog;
use error_oog_sload_sstore::OOGSloadSstore;
use error_return_data_outofbound::ErrorReturnDataOutOfBound;
Expand Down Expand Up @@ -271,8 +273,9 @@ fn fn_gen_error_state_associated_ops(error: &ExecError) -> Option<FnGenAssociate
ExecError::InvalidOpcode => Some(InvalidOpcode::gen_associated_ops),
ExecError::OutOfGas(OogError::Call) => Some(OOGCall::gen_associated_ops),
ExecError::OutOfGas(OogError::Constant) => Some(ErrorStackOogConstant::gen_associated_ops),
ExecError::OutOfGas(OogError::SloadSstore) => Some(OOGSloadSstore::gen_associated_ops),
ExecError::OutOfGas(OogError::Exp) => Some(OOGExp::gen_associated_ops),
ExecError::OutOfGas(OogError::Log) => Some(ErrorOOGLog::gen_associated_ops),
ExecError::OutOfGas(OogError::SloadSstore) => Some(OOGSloadSstore::gen_associated_ops),
ExecError::StackOverflow => Some(ErrorStackOogConstant::gen_associated_ops),
ExecError::StackUnderflow => Some(ErrorStackOogConstant::gen_associated_ops),
// call & callcode can encounter InsufficientBalance error, Use pop-7 generic CallOpcode
Expand Down
36 changes: 36 additions & 0 deletions bus-mapping/src/evm/opcodes/error_oog_exp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use super::{Opcode, OpcodeId};
use crate::circuit_input_builder::{CircuitInputStateRef, ExecStep};
use crate::error::{ExecError, OogError};
use crate::Error;
use eth_types::GethExecStep;

/// Placeholder structure used to implement [`Opcode`] trait over it
/// corresponding to the [`OogError::Exp`](crate::error::OogError::Exp).
#[derive(Clone, Copy, Debug)]
pub(crate) struct OOGExp;

impl Opcode for OOGExp {
fn gen_associated_ops(
state: &mut CircuitInputStateRef,
geth_steps: &[GethExecStep],
) -> Result<Vec<ExecStep>, Error> {
let geth_step = &geth_steps[0];
debug_assert_eq!(geth_step.op, OpcodeId::EXP);

let mut exec_step = state.new_step(geth_step)?;
exec_step.error = Some(ExecError::OutOfGas(OogError::Exp));

for i in 0..2 {
state.stack_read(
&mut exec_step,
geth_step.stack.nth_last_filled(i),
geth_step.stack.nth_last(i)?,
)?;
}

state.gen_restore_context_ops(&mut exec_step, geth_steps)?;
state.handle_return(geth_step)?;

Ok(vec![exec_step])
}
}
3 changes: 3 additions & 0 deletions eth-types/src/evm_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ impl GasCost {
pub const MEMORY_EXPANSION_LINEAR_COEFF: Self = Self(3);
/// Constant gas for LOG[0-4] op codes
pub const LOG: Self = Self(375);
/// Times ceil exponent byte size for the EXP instruction, EIP-158 changed
/// it from 10 to 50.
pub const EXP_BYTE_TIMES: Self = Self(50);
}

impl GasCost {
Expand Down
4 changes: 3 additions & 1 deletion zkevm-circuits/src/evm_circuit/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ mod error_invalid_jump;
mod error_invalid_opcode;
mod error_oog_call;
mod error_oog_constant;
mod error_oog_exp;
mod error_oog_log;
mod error_oog_sload_sstore;
mod error_oog_static_memory;
Expand Down Expand Up @@ -134,6 +135,7 @@ use error_invalid_jump::ErrorInvalidJumpGadget;
use error_invalid_opcode::ErrorInvalidOpcodeGadget;
use error_oog_call::ErrorOOGCallGadget;
use error_oog_constant::ErrorOOGConstantGadget;
use error_oog_exp::ErrorOOGExpGadget;
use error_oog_log::ErrorOOGLogGadget;
use error_oog_sload_sstore::ErrorOOGSloadSstoreGadget;
use error_return_data_oo_bound::ErrorReturnDataOutOfBoundGadget;
Expand Down Expand Up @@ -278,6 +280,7 @@ pub(crate) struct ExecutionConfig<F> {
// error gadgets
error_oog_call: ErrorOOGCallGadget<F>,
error_oog_constant: ErrorOOGConstantGadget<F>,
error_oog_exp: ErrorOOGExpGadget<F>,
error_oog_sload_sstore: ErrorOOGSloadSstoreGadget<F>,
error_oog_static_memory_gadget:
DummyGadget<F, 0, 0, { ExecutionState::ErrorOutOfGasStaticMemoryExpansion }>,
Expand All @@ -290,7 +293,6 @@ pub(crate) struct ExecutionConfig<F> {
error_oog_account_access: DummyGadget<F, 0, 0, { ExecutionState::ErrorOutOfGasAccountAccess }>,
error_oog_sha3: DummyGadget<F, 0, 0, { ExecutionState::ErrorOutOfGasSHA3 }>,
error_oog_ext_codecopy: DummyGadget<F, 0, 0, { ExecutionState::ErrorOutOfGasEXTCODECOPY }>,
error_oog_exp: DummyGadget<F, 0, 0, { ExecutionState::ErrorOutOfGasEXP }>,
error_oog_create2: DummyGadget<F, 0, 0, { ExecutionState::ErrorOutOfGasCREATE2 }>,
error_oog_self_destruct: DummyGadget<F, 0, 0, { ExecutionState::ErrorOutOfGasSELFDESTRUCT }>,
error_oog_code_store: DummyGadget<F, 0, 0, { ExecutionState::ErrorOutOfGasCodeStore }>,
Expand Down
279 changes: 279 additions & 0 deletions zkevm-circuits/src/evm_circuit/execution/error_oog_exp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
use crate::evm_circuit::execution::ExecutionGadget;
use crate::evm_circuit::param::N_BYTES_GAS;
use crate::evm_circuit::step::ExecutionState;
use crate::evm_circuit::util::common_gadget::RestoreContextGadget;
use crate::evm_circuit::util::constraint_builder::Transition::{Delta, Same};
use crate::evm_circuit::util::constraint_builder::{ConstraintBuilder, StepStateTransition};
use crate::evm_circuit::util::math_gadget::{ByteSizeGadget, LtGadget};
use crate::evm_circuit::util::{CachedRegion, Cell, Word};
use crate::evm_circuit::witness::{Block, Call, ExecStep, Transaction};
use crate::table::CallContextFieldTag;
use crate::util::Expr;
use eth_types::evm_types::{GasCost, OpcodeId};
use eth_types::{Field, ToLittleEndian};
use halo2_proofs::circuit::Value;
use halo2_proofs::plonk::Error;

/// Gadget to implement the corresponding out of gas errors for
/// [`OpcodeId::EXP`].
#[derive(Clone, Debug)]
pub(crate) struct ErrorOOGExpGadget<F> {
opcode: Cell<F>,
rw_counter_end_of_reversion: Cell<F>,
base: Word<F>,
exponent: Word<F>,
exponent_byte_size: ByteSizeGadget<F>,
insufficient_gas_cost: LtGadget<F, N_BYTES_GAS>,
restore_context: RestoreContextGadget<F>,
}

impl<F: Field> ExecutionGadget<F> for ErrorOOGExpGadget<F> {
const NAME: &'static str = "ErrorOutOfGasEXP";

const EXECUTION_STATE: ExecutionState = ExecutionState::ErrorOutOfGasEXP;

fn configure(cb: &mut ConstraintBuilder<F>) -> Self {
let opcode = cb.query_cell();
cb.opcode_lookup(opcode.expr(), 1.expr());

cb.require_equal(
"ErrorOutOfGasEXP opcode must be EXP",
opcode.expr(),
OpcodeId::EXP.expr(),
);

let base = cb.query_word_rlc();
let exponent = cb.query_word_rlc();
cb.stack_pop(base.expr());
cb.stack_pop(exponent.expr());

let exponent_byte_size = ByteSizeGadget::construct(cb, &exponent);

let insufficient_gas_cost = LtGadget::construct(
cb,
cb.curr.state.gas_left.expr(),
// static_gas = 10
// dynamic_gas = exponent_byte_size * 50
// gas_cost = dynamic_gas + static_gas
exponent_byte_size.byte_size() * GasCost::EXP_BYTE_TIMES.0.expr()
+ OpcodeId::EXP.constant_gas_cost().expr(),
);

cb.require_equal(
"Gas left is less than gas cost",
insufficient_gas_cost.expr(),
1.expr(),
);

// Current call must fail.
cb.call_context_lookup(false.expr(), None, CallContextFieldTag::IsSuccess, 0.expr());

let rw_counter_end_of_reversion = cb.query_cell();
cb.call_context_lookup(
false.expr(),
None,
CallContextFieldTag::RwCounterEndOfReversion,
rw_counter_end_of_reversion.expr(),
);

// Go to EndTx only when is_root.
let is_to_end_tx = cb.next.execution_state_selector([ExecutionState::EndTx]);
cb.require_equal(
"Go to EndTx only when is_root",
cb.curr.state.is_root.expr(),
is_to_end_tx,
);

// When it's a root call.
cb.condition(cb.curr.state.is_root.expr(), |cb| {
// Do step state transition.
cb.require_step_state_transition(StepStateTransition {
call_id: Same,
rw_counter: Delta(4.expr() + cb.curr.state.reversible_write_counter.expr()),
..StepStateTransition::any()
});
});

// When it's an internal call, need to restore caller's state as finishing this
// call. Restore caller state to next StepState.
let restore_context = cb.condition(1.expr() - cb.curr.state.is_root.expr(), |cb| {
RestoreContextGadget::construct(
cb,
0.expr(),
0.expr(),
0.expr(),
0.expr(),
0.expr(),
0.expr(),
)
});

// Constrain RwCounterEndOfReversion.
let rw_counter_end_of_step =
cb.curr.state.rw_counter.expr() + cb.rw_counter_offset() - 1.expr();
cb.require_equal(
"rw_counter_end_of_reversion = rw_counter_end_of_step + reversible_counter",
rw_counter_end_of_reversion.expr(),
rw_counter_end_of_step + cb.curr.state.reversible_write_counter.expr(),
);

Self {
opcode,
rw_counter_end_of_reversion,
base,
exponent,
exponent_byte_size,
insufficient_gas_cost,
restore_context,
}
}

fn assign_exec_step(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
block: &Block<F>,
_tx: &Transaction,
call: &Call,
step: &ExecStep,
) -> Result<(), Error> {
let opcode = step.opcode.unwrap();
let [base, exponent] = [0, 1].map(|idx| block.rws[step.rw_indices[idx]].stack_value());

log::debug!(
"ErrorOutOfGasEXP: gas_left = {}, gas_cost = {}",
step.gas_left,
step.gas_cost,
);

self.opcode
.assign(region, offset, Value::known(F::from(opcode.as_u64())))?;
self.rw_counter_end_of_reversion.assign(
region,
offset,
Value::known(F::from(call.rw_counter_end_of_reversion as u64)),
)?;
self.base.assign(region, offset, Some(base.to_le_bytes()))?;
self.exponent
.assign(region, offset, Some(exponent.to_le_bytes()))?;
self.exponent_byte_size.assign(region, offset, exponent)?;
self.insufficient_gas_cost.assign_value(
region,
offset,
Value::known(F::from(step.gas_left)),
Value::known(F::from(step.gas_cost)),
)?;
self.restore_context
.assign(region, offset, block, call, step, 4)
}
}

#[cfg(test)]
mod tests {
use crate::evm_circuit::test::{rand_bytes, rand_word};
use crate::test_util::CircuitTestBuilder;
use eth_types::evm_types::{GasCost, OpcodeId};
use eth_types::{bytecode, Bytecode, ToWord, U256};
use mock::test_ctx::helpers::account_0_code_account_1_no_code;
use mock::{eth, TestContext, MOCK_ACCOUNTS};

#[test]
fn test_oog_exp() {
[
U256::zero(),
U256::one(),
1023.into(),
U256::MAX,
rand_word(),
]
.into_iter()
.for_each(|exponent| {
let testing_data = TestingData::new(exponent);

test_root(&testing_data);
test_internal(&testing_data);
})
}

struct TestingData {
bytecode: Bytecode,
gas_cost: u64,
}

impl TestingData {
pub fn new(exponent: U256) -> Self {
let bytecode = bytecode! {
PUSH32(exponent)
PUSH32(rand_word())
EXP
};

let gas_cost = OpcodeId::PUSH32.constant_gas_cost().0 * 2
+ OpcodeId::EXP.constant_gas_cost().0
+ ((exponent.bits() as u64 + 7) / 8) * GasCost::EXP_BYTE_TIMES.0;

Self { bytecode, gas_cost }
}
}

fn test_root(testing_data: &TestingData) {
let ctx = TestContext::<2, 1>::new(
None,
account_0_code_account_1_no_code(testing_data.bytecode.clone()),
|mut txs, accs| {
// Decrease expected gas cost (by 1) to trigger out of gas error.
txs[0]
.from(accs[1].address)
.to(accs[0].address)
.gas((GasCost::TX.0 + testing_data.gas_cost - 1).into());
},
|block, _tx| block.number(0xcafe_u64),
)
.unwrap();

CircuitTestBuilder::new_from_test_ctx(ctx).run();
}

fn test_internal(testing_data: &TestingData) {
let (addr_a, addr_b) = (MOCK_ACCOUNTS[0], MOCK_ACCOUNTS[1]);

// code B gets called by code A, so the call is an internal call.
let code_b = testing_data.bytecode.clone();
let gas_cost_b = testing_data.gas_cost;

// Code A calls code B.
let code_a = bytecode! {
// populate memory in A's context.
PUSH8(U256::from_big_endian(&rand_bytes(8)))
PUSH1(0x00) // offset
MSTORE
// call ADDR_B.
PUSH1(0x00) // retLength
PUSH1(0x00) // retOffset
PUSH32(0x00) // argsLength
PUSH32(0x20) // argsOffset
PUSH1(0x00) // value
PUSH32(addr_b.to_word()) // addr
// Decrease expected gas cost (by 1) to trigger out of gas error.
PUSH32(gas_cost_b - 1) // gas
CALL
STOP
};

let ctx = TestContext::<3, 1>::new(
None,
|accs| {
accs[0].address(addr_b).code(code_b);
accs[1].address(addr_a).code(code_a);
accs[2].address(MOCK_ACCOUNTS[2]).balance(eth(10));
},
|mut txs, accs| {
txs[0].from(accs[2].address).to(accs[1].address);
},
|block, _tx| block,
)
.unwrap();

CircuitTestBuilder::new_from_test_ctx(ctx).run();
}
}
Loading