use crate::{
evm_circuit::{
execution::ExecutionGadget,
step::ExecutionState,
util::{
common_gadget::SameContextGadget,
constraint_builder::{
ConstrainBuilderCommon, EVMConstraintBuilder, StepStateTransition,
Transition::Delta,
},
CachedRegion, U64Cell,
},
witness::{Block, Call, Chunk, ExecStep, Transaction},
},
util::{word::WordExpr, Expr},
};
use eth_types::{evm_types::OpcodeId, Field};
use halo2_proofs::plonk::Error;
#[derive(Clone, Debug)]
pub(crate) struct GasGadget<F> {
same_context: SameContextGadget<F>,
gas_left: U64Cell<F>,
}
impl<F: Field> ExecutionGadget<F> for GasGadget<F> {
const NAME: &'static str = "GAS";
const EXECUTION_STATE: ExecutionState = ExecutionState::GAS;
fn configure(cb: &mut EVMConstraintBuilder<F>) -> Self {
let gas_left = cb.query_u64();
cb.require_equal(
"Constraint: gas left equal to stack value",
gas_left.expr(),
cb.curr.state.gas_left.expr() - OpcodeId::GAS.constant_gas_cost().expr(),
);
cb.stack_push(gas_left.to_word());
let step_state_transition = StepStateTransition {
rw_counter: Delta(1.expr()),
program_counter: Delta(1.expr()),
stack_pointer: Delta((-1).expr()),
gas_left: Delta(-OpcodeId::GAS.constant_gas_cost().expr()),
..Default::default()
};
let opcode = cb.query_cell();
let same_context = SameContextGadget::construct(cb, opcode, step_state_transition);
Self {
same_context,
gas_left,
}
}
fn assign_exec_step(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
_: &Block<F>,
_: &Chunk<F>,
_: &Transaction,
_call: &Call,
step: &ExecStep,
) -> Result<(), Error> {
self.same_context.assign_exec_step(region, offset, step)?;
self.gas_left.assign(
region,
offset,
Some(
step.gas_left
.saturating_sub(OpcodeId::GAS.constant_gas_cost())
.to_le_bytes(),
),
)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use crate::test_util::CircuitTestBuilder;
use eth_types::{address, bytecode, Word};
use mock::TestContext;
fn test_ok() {
let bytecode = bytecode! {
GAS
STOP
};
CircuitTestBuilder::new_from_test_ctx(
TestContext::<2, 1>::simple_ctx_with_bytecode(bytecode).unwrap(),
)
.run();
}
#[test]
fn gas_gadget_simple() {
test_ok();
}
#[test]
fn gas_gadget_incorrect_deduction() {
let bytecode = bytecode! {
GAS
STOP
};
let ctx = TestContext::<2, 1>::new(
None,
|accs| {
accs[0]
.address(address!("0x0000000000000000000000000000000000000010"))
.balance(Word::from(1u64 << 20))
.code(bytecode);
accs[1]
.address(address!("0x0000000000000000000000000000000000000000"))
.balance(Word::from(1u64 << 20));
},
|mut txs, accs| {
txs[0]
.to(accs[0].address)
.from(accs[1].address)
.gas(Word::from(1_000_000u64));
},
|block, _tx| block.number(0xcafeu64),
)
.unwrap();
CircuitTestBuilder::<2, 1>::new_from_test_ctx(ctx)
.block_modifier(Box::new(|block, _chunk| {
assert_eq!(block.txs.len(), 1);
assert_eq!(block.txs[0].steps().len(), 4);
block.txs[0].steps_mut()[2].gas_left -= 1;
}))
.run_with_result()
.unwrap_err()
.assert_evm_failure()
}
}