use crate::{
evm_circuit::{
execution::ExecutionGadget,
param::N_BYTES_PROGRAM_COUNTER,
step::ExecutionState,
util::{
common_gadget::RestoreContextGadget,
constraint_builder::{
ConstrainBuilderCommon, EVMConstraintBuilder, StepStateTransition,
Transition::{Delta, Same},
},
math_gadget::ComparisonGadget,
CachedRegion, Cell,
},
witness::{Block, Call, Chunk, ExecStep, Transaction},
},
table::CallContextFieldTag,
util::{
word::{WordExpr, WordLoHi},
Expr,
},
};
use bus_mapping::evm::OpcodeId;
use eth_types::{Field, OpsIdentity};
use halo2_proofs::{circuit::Value, plonk::Error};
#[derive(Clone, Debug)]
pub(crate) struct StopGadget<F> {
code_length: Cell<F>,
out_of_range: ComparisonGadget<F, N_BYTES_PROGRAM_COUNTER>,
opcode: Cell<F>,
restore_context: RestoreContextGadget<F>,
}
impl<F: Field> ExecutionGadget<F> for StopGadget<F> {
const NAME: &'static str = "STOP";
const EXECUTION_STATE: ExecutionState = ExecutionState::STOP;
fn configure(cb: &mut EVMConstraintBuilder<F>) -> Self {
let code_length = cb.query_cell();
cb.bytecode_length(cb.curr.state.code_hash.to_word(), code_length.expr());
let out_of_range = ComparisonGadget::construct(
cb,
code_length.expr(),
cb.curr.state.program_counter.expr(),
);
let (lt, eq) = out_of_range.expr();
let is_out_of_range = lt + eq;
let opcode = cb.query_cell();
cb.condition(1.expr() - is_out_of_range, |cb| {
cb.opcode_lookup(opcode.expr(), 1.expr());
});
cb.require_equal(
"Opcode should be STOP",
opcode.expr(),
OpcodeId::STOP.expr(),
);
cb.call_context_lookup_read(None, CallContextFieldTag::IsSuccess, WordLoHi::one());
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,
);
cb.condition(cb.curr.state.is_root.expr(), |cb| {
cb.require_step_state_transition(StepStateTransition {
call_id: Same,
rw_counter: Delta(1.expr()),
..StepStateTransition::any()
});
});
let restore_context = cb.condition(1.expr() - cb.curr.state.is_root.expr(), |cb| {
RestoreContextGadget::construct(
cb,
true.expr(),
0.expr(),
0.expr(),
0.expr(),
0.expr(),
0.expr(),
)
});
Self {
code_length,
out_of_range,
opcode,
restore_context,
}
}
fn assign_exec_step(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
block: &Block<F>,
_chunk: &Chunk<F>,
_: &Transaction,
call: &Call,
step: &ExecStep,
) -> Result<(), Error> {
let code = block
.bytecodes
.get_from_h256(&call.code_hash)
.expect("could not find current environment's bytecode");
let code_length = code.codesize() as u64;
self.code_length
.assign(region, offset, Value::known(F::from(code_length)))?;
self.out_of_range
.assign(region, offset, F::from(code_length), F::from(step.pc))?;
let opcode = step.opcode().unwrap();
self.opcode
.assign(region, offset, Value::known(F::from(opcode.as_u64())))?;
if !call.is_root {
self.restore_context
.assign(region, offset, block, call, step, 1)?;
}
Ok(())
}
}
#[cfg(test)]
mod test {
use crate::test_util::CircuitTestBuilder;
use eth_types::{address, bytecode, Bytecode, Word};
use itertools::Itertools;
use mock::TestContext;
fn test_ok(bytecode: Bytecode, is_root: bool) {
if is_root {
let ctx = TestContext::<2, 1>::new(
None,
|accs| {
accs[0]
.address(address!("0x0000000000000000000000000000000000000123"))
.balance(Word::from(1u64 << 30));
accs[1]
.address(address!("0x0000000000000000000000000000000000000010"))
.balance(Word::from(1u64 << 20))
.code(bytecode);
},
|mut txs, accs| {
txs[0]
.from(accs[0].address)
.to(accs[1].address)
.gas(Word::from(30000));
},
|block, _tx| block.number(0xcafeu64),
)
.unwrap();
CircuitTestBuilder::new_from_test_ctx(ctx).run();
} else {
let ctx = TestContext::<3, 1>::new(
None,
|accs| {
accs[0]
.address(address!("0x0000000000000000000000000000000000000123"))
.balance(Word::from(1u64 << 30));
accs[1]
.address(address!("0x0000000000000000000000000000000000000010"))
.balance(Word::from(1u64 << 20))
.code(bytecode! {
PUSH1(0)
PUSH1(0)
PUSH1(0)
PUSH1(0)
PUSH1(0)
PUSH1(0x20)
GAS
CALL
STOP
});
accs[2]
.address(address!("0x0000000000000000000000000000000000000020"))
.balance(Word::from(1u64 << 20))
.code(bytecode);
},
|mut txs, accs| {
txs[0]
.from(accs[0].address)
.to(accs[1].address)
.gas(Word::from(30000));
},
|block, _tx| block.number(0xcafeu64),
)
.unwrap();
CircuitTestBuilder::new_from_test_ctx(ctx).run();
};
}
#[test]
fn stop_gadget_simple() {
let bytecodes = vec![
bytecode! {
PUSH1(0)
STOP
},
bytecode! {
PUSH1(0)
},
];
let is_roots = vec![true, false];
for (bytecode, is_root) in bytecodes.into_iter().cartesian_product(is_roots) {
test_ok(bytecode, is_root);
}
}
}