use super::{
constraint_builder::ConstrainBuilderCommon,
from_bytes,
math_gadget::{IsEqualWordGadget, IsZeroGadget, IsZeroWordGadget, LtGadget},
memory_gadget::{CommonMemoryAddressGadget, MemoryExpansionGadget},
AccountAddress, CachedRegion, StepRws,
};
use crate::{
evm_circuit::{
param::{N_BYTES_GAS, N_BYTES_MEMORY_WORD_SIZE},
step::ExecutionState,
table::{FixedTableTag, Lookup},
util::{
and,
constraint_builder::{
EVMConstraintBuilder, ReversionInfo, StepStateTransition,
Transition::{Delta, Same, To},
},
math_gadget::{AddWordsGadget, RangeCheckGadget},
not, Cell,
},
},
table::{chunk_ctx_table::ChunkCtxFieldTag, AccountFieldTag, CallContextFieldTag},
util::{
word::{Word32, Word32Cell, WordExpr, WordLoHi, WordLoHiCell},
Expr,
},
witness::{Block, Call, Chunk, ExecStep},
};
use bus_mapping::state_db::CodeDB;
use eth_types::{
evm_types::GasCost, Field, OpsIdentity, ToAddress, ToLittleEndian, ToScalar, ToWord, U256,
};
use gadgets::util::{or, select, sum};
use halo2_proofs::{
circuit::Value,
plonk::{Error, Expression},
};
#[derive(Clone, Debug)]
pub(crate) struct SameContextGadget<F> {
opcode: Cell<F>,
sufficient_gas_left: RangeCheckGadget<F, N_BYTES_GAS>,
}
impl<F: Field> SameContextGadget<F> {
pub(crate) fn construct(
cb: &mut EVMConstraintBuilder<F>,
opcode: Cell<F>,
step_state_transition: StepStateTransition<F>,
) -> Self {
cb.opcode_lookup(opcode.expr(), 1.expr());
cb.add_lookup(
"Responsible opcode lookup",
Lookup::Fixed {
tag: FixedTableTag::ResponsibleOpcode.expr(),
values: [
cb.execution_state().as_u64().expr(),
opcode.expr(),
0.expr(),
],
},
);
let sufficient_gas_left = RangeCheckGadget::construct(cb, cb.next.state.gas_left.expr());
cb.require_step_state_transition(step_state_transition);
Self {
opcode,
sufficient_gas_left,
}
}
pub(crate) fn assign_exec_step(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
step: &ExecStep,
) -> Result<(), Error> {
let opcode = step.opcode().unwrap();
self.opcode
.assign(region, offset, Value::known(F::from(opcode.as_u64())))?;
self.sufficient_gas_left
.assign(region, offset, F::from(step.gas_left - step.gas_cost))?;
Ok(())
}
}
#[derive(Clone, Debug)]
pub(crate) struct RestoreContextGadget<F> {
caller_id: Cell<F>,
caller_is_root: Cell<F>,
caller_is_create: Cell<F>,
caller_code_hash: WordLoHiCell<F>,
caller_program_counter: Cell<F>,
caller_stack_pointer: Cell<F>,
caller_gas_left: Cell<F>,
caller_memory_word_size: Cell<F>,
caller_reversible_write_counter: Cell<F>,
}
impl<F: Field> RestoreContextGadget<F> {
pub(crate) fn construct(
cb: &mut EVMConstraintBuilder<F>,
is_success: Expression<F>,
subsequent_rw_lookups: Expression<F>,
return_data_offset: Expression<F>,
return_data_length: Expression<F>,
memory_expansion_cost: Expression<F>,
reversible_write_counter_increase: Expression<F>,
) -> Self {
Self::construct2(
cb,
is_success,
0.expr(),
subsequent_rw_lookups,
return_data_offset,
return_data_length,
memory_expansion_cost,
reversible_write_counter_increase,
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn construct2(
cb: &mut EVMConstraintBuilder<F>,
is_success: Expression<F>,
gas_cost: Expression<F>,
subsequent_rw_lookups: Expression<F>,
return_data_offset: Expression<F>,
return_data_length: Expression<F>,
memory_expansion_cost: Expression<F>,
reversible_write_counter_increase: Expression<F>,
) -> Self {
let caller_id = cb.call_context(None, CallContextFieldTag::CallerId);
let [caller_is_root, caller_is_create] =
[CallContextFieldTag::IsRoot, CallContextFieldTag::IsCreate]
.map(|field_tag| cb.call_context(Some(caller_id.expr()), field_tag));
let caller_code_hash =
cb.call_context_read_as_word(Some(caller_id.expr()), CallContextFieldTag::CodeHash);
let [caller_program_counter, caller_stack_pointer, caller_gas_left, caller_memory_word_size, caller_reversible_write_counter] =
[
CallContextFieldTag::ProgramCounter,
CallContextFieldTag::StackPointer,
CallContextFieldTag::GasLeft,
CallContextFieldTag::MemorySize,
CallContextFieldTag::ReversibleWriteCounter,
]
.map(|field_tag| cb.call_context(Some(caller_id.expr()), field_tag));
let is_call_create_and_success_expr = cb.curr.state.is_create.expr() * is_success.clone();
for (field_tag, value) in [
(
CallContextFieldTag::LastCalleeId,
cb.curr.state.call_id.expr(),
),
(
CallContextFieldTag::LastCalleeReturnDataOffset,
select::expr(
is_call_create_and_success_expr.clone(),
0.expr(),
return_data_offset,
),
),
(
CallContextFieldTag::LastCalleeReturnDataLength,
select::expr(
is_call_create_and_success_expr,
0.expr(),
return_data_length.clone(),
),
),
] {
cb.call_context_lookup_write(
Some(caller_id.expr()),
field_tag,
WordLoHi::from_lo_unchecked(value),
);
}
let code_deposit_cost = cb.curr.state.is_create.expr()
* is_success.clone()
* GasCost::CODE_DEPOSIT_BYTE_COST.expr()
* return_data_length;
let gas_refund = if cb.execution_state().halts_in_exception() {
0.expr() } else if cb.execution_state().is_precompiled() {
cb.curr.state.gas_left.expr() - gas_cost.expr()
} else {
cb.curr.state.gas_left.expr() - memory_expansion_cost - code_deposit_cost
};
let gas_left = caller_gas_left.expr() + gas_refund;
let reversible_write_counter = caller_reversible_write_counter.expr()
+ reversible_write_counter_increase
+ is_success.clone() * cb.curr.state.reversible_write_counter.expr();
let rw_counter_offset = cb.rw_counter_offset()
+ subsequent_rw_lookups
+ not::expr(is_success.expr()) * cb.curr.state.reversible_write_counter.expr();
cb.require_step_state_transition(StepStateTransition {
rw_counter: Delta(rw_counter_offset),
call_id: To(caller_id.expr()),
is_root: To(caller_is_root.expr()),
is_create: To(caller_is_create.expr()),
code_hash: To(caller_code_hash.to_word()),
program_counter: To(caller_program_counter.expr()),
stack_pointer: To(caller_stack_pointer.expr()),
gas_left: To(gas_left),
memory_word_size: To(caller_memory_word_size.expr()),
reversible_write_counter: To(reversible_write_counter),
..Default::default()
});
Self {
caller_id,
caller_is_root,
caller_is_create,
caller_code_hash,
caller_program_counter,
caller_stack_pointer,
caller_gas_left,
caller_memory_word_size,
caller_reversible_write_counter,
}
}
pub(crate) fn assign(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
block: &Block<F>,
call: &Call,
step: &ExecStep,
rw_offset: usize,
) -> Result<(), Error> {
let [caller_id, caller_is_root, caller_is_create, caller_code_hash, caller_program_counter, caller_stack_pointer, caller_gas_left, caller_memory_word_size, caller_reversible_write_counter] =
if call.is_root {
[U256::zero(); 9]
} else {
[0, 1, 2, 3, 4, 5, 6, 7, 8]
.map(|i| block.get_rws(step, i + rw_offset).call_context_value())
};
for (cell, value) in [
(&self.caller_id, caller_id),
(&self.caller_is_root, caller_is_root),
(&self.caller_is_create, caller_is_create),
(&self.caller_program_counter, caller_program_counter),
(&self.caller_stack_pointer, caller_stack_pointer),
(&self.caller_gas_left, caller_gas_left),
(&self.caller_memory_word_size, caller_memory_word_size),
(
&self.caller_reversible_write_counter,
caller_reversible_write_counter,
),
] {
cell.assign(
region,
offset,
Value::known(
value
.to_scalar()
.expect("unexpected U256 -> Scalar conversion failure"),
),
)?;
}
self.caller_code_hash
.assign_u256(region, offset, caller_code_hash)?;
Ok(())
}
}
#[derive(Clone, Debug)]
pub(crate) struct UpdateBalanceGadget<F, const N_ADDENDS: usize, const INCREASE: bool> {
add_words: AddWordsGadget<F, N_ADDENDS, true>,
}
impl<F: Field, const N_ADDENDS: usize, const INCREASE: bool>
UpdateBalanceGadget<F, N_ADDENDS, INCREASE>
{
pub(crate) fn construct(
cb: &mut EVMConstraintBuilder<F>,
address: WordLoHi<Expression<F>>,
updates: &[Word32Cell<F>],
reversion_info: Option<&mut ReversionInfo<F>>,
) -> Self {
debug_assert!(updates.len() == N_ADDENDS - 1);
let balance_addend = cb.query_word32();
let balance_sum = cb.query_word32();
let [value, value_prev]: [Word32<Expression<F>>; 2] = if INCREASE {
[balance_sum.to_word_n(), balance_addend.to_word_n()]
} else {
[balance_addend.to_word_n(), balance_sum.to_word_n()]
};
let add_words = AddWordsGadget::construct(
cb,
std::iter::once(balance_addend)
.chain(updates.to_vec())
.collect::<Vec<_>>()
.try_into()
.unwrap(),
balance_sum,
);
cb.account_write(
address,
AccountFieldTag::Balance,
value.to_word(),
value_prev.to_word(),
reversion_info,
);
Self { add_words }
}
pub(crate) fn assign(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
value_prev: U256,
updates: Vec<U256>,
value: U256,
) -> Result<(), Error> {
debug_assert!(updates.len() + 1 == N_ADDENDS);
let [value, value_prev] = if INCREASE {
[value, value_prev]
} else {
[value_prev, value]
};
let mut addends = vec![value_prev];
addends.extend(updates);
self.add_words
.assign(region, offset, addends.try_into().unwrap(), value)
}
}
#[derive(Clone, Debug)]
pub(crate) struct TransferToGadget<F> {
receiver_balance: UpdateBalanceGadget<F, 2, true>,
receiver_exists: Expression<F>,
is_create: Expression<F>,
value_is_zero: IsZeroWordGadget<F, Word32Cell<F>>,
}
impl<F: Field> TransferToGadget<F> {
#[allow(clippy::too_many_arguments)]
pub(crate) fn construct(
cb: &mut EVMConstraintBuilder<F>,
receiver_address: WordLoHi<Expression<F>>,
receiver_exists: Expression<F>,
is_create: Expression<F>,
value: Word32Cell<F>,
mut reversion_info: Option<&mut ReversionInfo<F>>,
) -> Self {
let value_is_zero = cb.is_zero_word(&value);
cb.condition(
and::expr([
not::expr(receiver_exists.expr()),
or::expr([not::expr(value_is_zero.expr()), is_create.expr()]),
]),
|cb| {
cb.account_write(
receiver_address.clone(),
AccountFieldTag::CodeHash,
cb.empty_code_hash(),
WordLoHi::zero(),
reversion_info.as_deref_mut(),
);
},
);
let receiver_balance = cb.condition(not::expr(value_is_zero.expr()), |cb| {
cb.increase_balance(receiver_address, value.clone(), reversion_info)
});
Self {
receiver_balance,
receiver_exists,
is_create,
value_is_zero,
}
}
pub(crate) fn assign(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
rws: &mut StepRws,
receiver_exists: bool,
value: U256,
is_create: bool,
) -> Result<(), Error> {
if !receiver_exists && (!value.is_zero() || is_create) {
rws.next().account_codehash_pair();
}
let (receiver_balance, receiver_balance_prev) = if !value.is_zero() {
rws.next().account_balance_pair()
} else {
(0.into(), 0.into())
};
self.receiver_balance.assign(
region,
offset,
receiver_balance_prev,
vec![value],
receiver_balance,
)?;
self.value_is_zero
.assign_value(region, offset, Value::known(WordLoHi::from(value)))?;
Ok(())
}
pub(crate) fn rw_delta(&self) -> Expression<F> {
and::expr([
not::expr(self.receiver_exists.expr()),
or::expr([not::expr(self.value_is_zero.expr()), self.is_create.expr()])
])+
not::expr(self.value_is_zero.expr())
}
}
#[derive(Clone, Debug)]
pub(crate) struct TransferGadget<F, const WITH_FEE: bool> {
sender_sub_fee: Option<UpdateBalanceGadget<F, 2, false>>,
sender_sub_value: UpdateBalanceGadget<F, 2, false>,
receiver: TransferToGadget<F>,
pub(crate) value_is_zero: IsZeroWordGadget<F, Word32Cell<F>>,
}
impl<F: Field, const WITH_FEE: bool> TransferGadget<F, WITH_FEE> {
#[allow(clippy::too_many_arguments)]
pub(crate) fn construct(
cb: &mut EVMConstraintBuilder<F>,
sender_address: WordLoHi<Expression<F>>,
receiver_address: WordLoHi<Expression<F>>,
receiver_exists: Expression<F>,
is_create: Expression<F>,
value: Word32Cell<F>,
reversion_info: &mut ReversionInfo<F>,
gas_fee: Option<Word32Cell<F>>,
) -> Self {
let sender_sub_fee = if WITH_FEE {
Some(cb.decrease_balance(sender_address.to_word(), gas_fee.expect("fee exists"), None))
} else {
None
};
let value_is_zero = cb.is_zero_word(&value);
let sender_sub_value = cb.condition(not::expr(value_is_zero.expr()), |cb| {
cb.decrease_balance(sender_address, value.clone(), Some(reversion_info))
});
let receiver = TransferToGadget::construct(
cb,
receiver_address,
receiver_exists,
is_create,
value,
Some(reversion_info),
);
Self {
sender_sub_fee,
sender_sub_value,
receiver,
value_is_zero,
}
}
pub(crate) fn rw_delta(&self) -> Expression<F> {
WITH_FEE.expr() +
not::expr(self.value_is_zero.expr()) +
self.receiver.rw_delta()
}
pub(crate) fn reversible_w_delta(&self) -> Expression<F> {
not::expr(self.value_is_zero.expr()) +
self.receiver.rw_delta()
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn assign(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
rws: &mut StepRws,
receiver_exists: bool,
value: U256,
is_create: bool,
gas_fee: Option<U256>,
) -> Result<(), Error> {
if WITH_FEE {
let sender_balance_sub_fee = rws.next().account_balance_pair();
self.sender_sub_fee.as_ref().expect("Exists").assign(
region,
offset,
sender_balance_sub_fee.1,
vec![gas_fee.expect("exists")],
sender_balance_sub_fee.0,
)?;
}
let sender_balance_sub_value = if !value.is_zero() {
rws.next().account_balance_pair()
} else {
(0.into(), 0.into())
};
self.sender_sub_value.assign(
region,
offset,
sender_balance_sub_value.1,
vec![value],
sender_balance_sub_value.0,
)?;
self.receiver
.assign(region, offset, rws, receiver_exists, value, is_create)?;
self.value_is_zero
.assign_value(region, offset, Value::known(WordLoHi::from(value)))?;
Ok(())
}
}
#[derive(Clone, Debug)]
pub(crate) struct CommonCallGadget<F, MemAddrGadget, const IS_SUCCESS_CALL: bool> {
pub is_success: Cell<F>,
pub gas: Word32Cell<F>,
pub gas_is_u64: IsZeroGadget<F>,
pub callee_address: AccountAddress<F>,
pub value: Word32Cell<F>,
pub cd_address: MemAddrGadget,
pub rd_address: MemAddrGadget,
pub memory_expansion: MemoryExpansionGadget<F, 2, N_BYTES_MEMORY_WORD_SIZE>,
value_is_zero: IsZeroWordGadget<F, Word32Cell<F>>,
pub has_value: Expression<F>,
pub callee_code_hash: WordLoHiCell<F>,
pub is_empty_code_hash: IsEqualWordGadget<F, WordLoHiCell<F>, WordLoHi<Expression<F>>>,
pub callee_not_exists: IsZeroWordGadget<F, WordLoHiCell<F>>,
is_call: Expression<F>,
is_callcode: Expression<F>,
_is_delegatecall: Expression<F>,
_is_staticcall: Expression<F>,
}
impl<F: Field, MemAddrGadget: CommonMemoryAddressGadget<F>, const IS_SUCCESS_CALL: bool>
CommonCallGadget<F, MemAddrGadget, IS_SUCCESS_CALL>
{
pub(crate) fn construct(
cb: &mut EVMConstraintBuilder<F>,
is_call: Expression<F>,
is_callcode: Expression<F>,
is_delegatecall: Expression<F>,
is_staticcall: Expression<F>,
) -> Self {
cb.require_equal(
"Opcode should be CALL, CALLCODE, DELEGATECALL or STATICCALL",
is_call.expr() + is_callcode.expr() + is_delegatecall.expr() + is_staticcall.expr(),
1.expr(),
);
let gas_word = cb.query_word32();
let callee_address = cb.query_account_address();
let value = cb.query_word32();
let is_success = cb.query_bool();
let cd_address = MemAddrGadget::construct_self(cb);
let rd_address = MemAddrGadget::construct_self(cb);
cb.stack_pop(gas_word.to_word());
cb.stack_pop(callee_address.to_word());
cb.condition(is_call.clone() + is_callcode.clone(), |cb| {
cb.stack_pop(value.to_word())
});
cb.stack_pop(cd_address.offset_word());
cb.stack_pop(cd_address.length_word());
cb.stack_pop(rd_address.offset_word());
cb.stack_pop(rd_address.length_word());
cb.stack_push(if IS_SUCCESS_CALL {
WordLoHi::from_lo_unchecked(is_success.expr()) } else {
WordLoHi::zero()
});
let gas_is_u64 = cb.is_zero(sum::expr(&gas_word.limbs[N_BYTES_GAS..]));
let memory_expansion =
MemoryExpansionGadget::construct(cb, [cd_address.address(), rd_address.address()]);
let value_is_zero = cb.is_zero_word(&value);
let has_value = select::expr(
is_delegatecall.expr() + is_staticcall.expr(),
0.expr(),
1.expr() - value_is_zero.expr(),
);
let callee_code_hash = cb.query_word_unchecked();
cb.account_read(
callee_address.to_word(),
AccountFieldTag::CodeHash,
callee_code_hash.to_word(),
);
let is_empty_code_hash = cb.is_eq_word(&callee_code_hash, &cb.empty_code_hash());
let callee_not_exists = cb.is_zero_word(&callee_code_hash);
Self {
is_success,
callee_address,
gas: gas_word,
gas_is_u64,
value,
cd_address,
rd_address,
memory_expansion,
value_is_zero,
has_value,
callee_code_hash,
is_empty_code_hash,
callee_not_exists,
is_call,
is_callcode,
_is_delegatecall: is_delegatecall,
_is_staticcall: is_staticcall,
}
}
pub fn callee_address(&self) -> WordLoHi<Expression<F>> {
self.callee_address.to_word()
}
pub fn gas_expr(&self) -> Expression<F> {
from_bytes::expr(&self.gas.limbs[..N_BYTES_GAS])
}
pub fn gas_cost_expr(
&self,
is_warm_prev: Expression<F>,
is_call: Expression<F>,
) -> Expression<F> {
select::expr(
is_warm_prev,
GasCost::WARM_ACCESS.expr(),
GasCost::COLD_ACCOUNT_ACCESS.expr(),
) + self.has_value.clone()
* (GasCost::CALL_WITH_VALUE.expr()
+ is_call * self.callee_not_exists.expr() * GasCost::NEW_ACCOUNT.expr())
+ self.memory_expansion.gas_cost()
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn assign(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
gas: U256,
callee_address: U256,
value: U256,
is_success: U256,
cd_offset: U256,
cd_length: U256,
rd_offset: U256,
rd_length: U256,
memory_word_size: u64,
callee_code_hash: U256,
) -> Result<u64, Error> {
self.gas.assign_u256(region, offset, gas)?;
self.callee_address
.assign_h160(region, offset, callee_address.to_address())?;
self.value.assign_u256(region, offset, value)?;
if IS_SUCCESS_CALL {
self.is_success
.assign(region, offset, Value::known(F::from(is_success.low_u64())))?;
}
self.gas_is_u64.assign(
region,
offset,
sum::value(&gas.to_le_bytes()[N_BYTES_GAS..]),
)?;
let cd_address = self
.cd_address
.assign(region, offset, cd_offset, cd_length)?;
let rd_address = self
.rd_address
.assign(region, offset, rd_offset, rd_length)?;
let (_, memory_expansion_gas_cost) = self.memory_expansion.assign(
region,
offset,
memory_word_size,
[cd_address, rd_address],
)?;
self.value_is_zero
.assign(region, offset, WordLoHi::from(value))?;
self.callee_code_hash
.assign_u256(region, offset, callee_code_hash)?;
self.is_empty_code_hash.assign_value(
region,
offset,
Value::known(WordLoHi::from(callee_code_hash)),
Value::known(WordLoHi::from(CodeDB::empty_code_hash().to_word())),
)?;
self.callee_not_exists.assign_value(
region,
offset,
Value::known(WordLoHi::from(callee_code_hash)),
)?;
Ok(memory_expansion_gas_cost)
}
pub(crate) fn cal_gas_cost_for_assignment(
&self,
memory_expansion_gas_cost: u64,
is_warm_prev: bool,
is_call: bool,
has_value: bool,
is_empty_account: bool,
) -> Result<u64, Error> {
let gas_cost = if is_warm_prev {
GasCost::WARM_ACCESS
} else {
GasCost::COLD_ACCOUNT_ACCESS
} + if has_value {
GasCost::CALL_WITH_VALUE
+ if is_call && is_empty_account {
GasCost::NEW_ACCOUNT
} else {
0
}
} else {
0
} + memory_expansion_gas_cost;
Ok(gas_cost)
}
pub(crate) fn rw_delta(&self) -> Expression<F> {
6.expr() + self.is_call.expr() + self.is_callcode.expr() + 1.expr() + 1.expr() }
}
#[derive(Clone, Debug)]
pub(crate) struct SloadGasGadget<F> {
gas_cost: Expression<F>,
}
impl<F: Field> SloadGasGadget<F> {
pub(crate) fn construct(_cb: &mut EVMConstraintBuilder<F>, is_warm: Expression<F>) -> Self {
let gas_cost = select::expr(
is_warm.expr(),
GasCost::WARM_ACCESS.expr(),
GasCost::COLD_SLOAD.expr(),
);
Self { gas_cost }
}
pub(crate) fn expr(&self) -> Expression<F> {
self.gas_cost.clone()
}
}
#[derive(Clone, Debug)]
pub(crate) struct SstoreGasGadget<F, T> {
is_warm: Cell<F>,
gas_cost: Expression<F>,
value_eq_prev: IsEqualWordGadget<F, T, T>,
original_eq_prev: IsEqualWordGadget<F, T, T>,
original_is_zero: IsZeroWordGadget<F, T>,
}
impl<F: Field, T: WordExpr<F> + Clone> SstoreGasGadget<F, T> {
pub(crate) fn construct(
cb: &mut EVMConstraintBuilder<F>,
is_warm: Cell<F>,
value: T,
value_prev: T,
original_value: T,
) -> Self {
let value_eq_prev = cb.is_eq_word(&value, &value_prev);
let original_eq_prev = cb.is_eq_word(&original_value, &value_prev);
let original_is_zero = cb.is_zero_word(&original_value);
let warm_case_gas = select::expr(
value_eq_prev.expr(),
GasCost::WARM_ACCESS.expr(),
select::expr(
original_eq_prev.expr(),
select::expr(
original_is_zero.expr(),
GasCost::SSTORE_SET.expr(),
GasCost::SSTORE_RESET.expr(),
),
GasCost::WARM_ACCESS.expr(),
),
);
let gas_cost = select::expr(
is_warm.expr(),
warm_case_gas.expr(),
warm_case_gas + GasCost::COLD_SLOAD.expr(),
);
Self {
is_warm,
gas_cost,
value_eq_prev,
original_eq_prev,
original_is_zero,
}
}
pub(crate) fn expr(&self) -> Expression<F> {
self.gas_cost.clone()
}
pub(crate) fn assign(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
value: eth_types::Word,
value_prev: eth_types::Word,
original_value: eth_types::Word,
is_warm: bool,
) -> Result<(), Error> {
self.is_warm
.assign(region, offset, Value::known(F::from(is_warm as u64)))?;
self.value_eq_prev.assign_value(
region,
offset,
Value::known(WordLoHi::from(value)),
Value::known(WordLoHi::from(value_prev)),
)?;
self.original_eq_prev.assign_value(
region,
offset,
Value::known(WordLoHi::from(original_value)),
Value::known(WordLoHi::from(value_prev)),
)?;
self.original_is_zero.assign_value(
region,
offset,
Value::known(WordLoHi::from(original_value)),
)?;
Ok(())
}
}
pub(crate) fn cal_sload_gas_cost_for_assignment(is_warm: bool) -> u64 {
if is_warm {
GasCost::WARM_ACCESS
} else {
GasCost::COLD_SLOAD
}
}
pub(crate) fn cal_sstore_gas_cost_for_assignment(
value: U256,
value_prev: U256,
original_value: U256,
is_warm: bool,
) -> u64 {
let warm_case_gas = if value_prev == value {
GasCost::WARM_ACCESS
} else if original_value == value_prev {
if original_value.is_zero() {
GasCost::SSTORE_SET
} else {
GasCost::SSTORE_RESET
}
} else {
GasCost::WARM_ACCESS
};
if is_warm {
warm_case_gas
} else {
warm_case_gas + GasCost::COLD_SLOAD
}
}
#[derive(Clone, Debug)]
pub(crate) struct CommonErrorGadget<F> {
rw_counter_end_of_reversion: WordLoHiCell<F>,
restore_context: RestoreContextGadget<F>,
}
impl<F: Field> CommonErrorGadget<F> {
pub(crate) fn construct(
cb: &mut EVMConstraintBuilder<F>,
opcode: Expression<F>,
rw_counter_delta: Expression<F>,
) -> Self {
Self::construct_with_return_data(cb, opcode, rw_counter_delta, 0.expr(), 0.expr())
}
pub(crate) fn construct_with_return_data(
cb: &mut EVMConstraintBuilder<F>,
opcode: Expression<F>,
rw_counter_delta: Expression<F>,
return_data_offset: Expression<F>,
return_data_length: Expression<F>,
) -> Self {
cb.opcode_lookup(opcode.expr(), 1.expr());
let rw_counter_end_of_reversion = cb.query_word_unchecked(); cb.call_context_lookup_read(None, CallContextFieldTag::IsSuccess, WordLoHi::zero());
cb.call_context_lookup_read(
None,
CallContextFieldTag::RwCounterEndOfReversion,
rw_counter_end_of_reversion.to_word(),
);
let updated_rw_counter_delta = rw_counter_delta + 2.expr();
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(
updated_rw_counter_delta + cb.curr.state.reversible_write_counter.expr(),
),
..StepStateTransition::any()
});
});
let restore_context = cb.condition(1.expr() - cb.curr.state.is_root.expr(), |cb| {
RestoreContextGadget::construct(
cb,
0.expr(),
0.expr(),
return_data_offset,
return_data_length,
0.expr(),
0.expr(),
)
});
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.lo().expr(),
rw_counter_end_of_step + cb.curr.state.reversible_write_counter.expr(),
);
Self {
rw_counter_end_of_reversion,
restore_context,
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn assign(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
block: &Block<F>,
call: &Call,
step: &ExecStep,
rw_offset: usize,
) -> Result<u64, Error> {
self.rw_counter_end_of_reversion.assign_u64(
region,
offset,
call.rw_counter_end_of_reversion as u64,
)?;
self.restore_context
.assign(region, offset, block, call, step, rw_offset)?;
Ok(1u64)
}
}
#[derive(Clone, Debug)]
pub(crate) struct WordByteCapGadget<F, const VALID_BYTES: usize> {
word: WordByteRangeGadget<F, VALID_BYTES>,
lt_cap: LtGadget<F, VALID_BYTES>,
}
impl<F: Field, const VALID_BYTES: usize> WordByteCapGadget<F, VALID_BYTES> {
pub(crate) fn construct(cb: &mut EVMConstraintBuilder<F>, cap: Expression<F>) -> Self {
let word = WordByteRangeGadget::construct(cb);
let value = select::expr(word.overflow(), cap.expr(), word.valid_value());
let lt_cap = cb.is_lt(value, cap);
Self { word, lt_cap }
}
pub(crate) fn assign(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
original: U256,
cap: F,
) -> Result<bool, Error> {
let not_overflow = self.word.assign(region, offset, original)?;
let value = if not_overflow {
let mut bytes = [0; 32];
bytes[0..VALID_BYTES].copy_from_slice(&original.to_le_bytes()[0..VALID_BYTES]);
F::from_repr(bytes).unwrap()
} else {
cap
};
self.lt_cap.assign(region, offset, value, cap)?;
Ok(not_overflow)
}
pub(crate) fn lt_cap(&self) -> Expression<F> {
self.lt_cap.expr()
}
pub(crate) fn original_word(&self) -> Word32Cell<F> {
self.word.original.clone()
}
pub(crate) fn overflow(&self) -> Expression<F> {
self.word.overflow()
}
pub(crate) fn valid_value(&self) -> Expression<F> {
self.word.valid_value()
}
pub(crate) fn not_overflow(&self) -> Expression<F> {
self.word.not_overflow()
}
}
#[derive(Clone, Debug)]
pub(crate) struct WordByteRangeGadget<F, const VALID_BYTES: usize> {
original: Word32Cell<F>,
not_overflow: IsZeroGadget<F>,
}
impl<F: Field, const VALID_BYTES: usize> WordByteRangeGadget<F, VALID_BYTES> {
pub(crate) fn construct(cb: &mut EVMConstraintBuilder<F>) -> Self {
debug_assert!(VALID_BYTES < 32);
let original = cb.query_word32();
let not_overflow = cb.is_zero(sum::expr(&original.limbs[VALID_BYTES..]));
Self {
original,
not_overflow,
}
}
pub(crate) fn assign(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
original: U256,
) -> Result<bool, Error> {
self.original.assign_u256(region, offset, original)?;
let overflow_hi = original.to_le_bytes()[VALID_BYTES..]
.iter()
.fold(0, |acc, val| acc + u64::from(*val));
self.not_overflow
.assign(region, offset, F::from(overflow_hi))?;
Ok(overflow_hi == 0)
}
pub(crate) fn overflow(&self) -> Expression<F> {
not::expr(self.not_overflow())
}
pub(crate) fn original(&self) -> WordLoHi<Expression<F>> {
self.original.to_word()
}
pub(crate) fn valid_value(&self) -> Expression<F> {
from_bytes::expr(&self.original.limbs[..VALID_BYTES])
}
pub(crate) fn not_overflow(&self) -> Expression<F> {
self.not_overflow.expr()
}
}
const MAX_RW_BYTES: usize = u32::BITS as usize / 8;
#[derive(Clone, Debug)]
pub(crate) struct RwTablePaddingGadget<F> {
is_empty_rwc: IsZeroGadget<F>,
max_rws: Cell<F>,
chunk_index: Cell<F>,
is_end_padding_exist: LtGadget<F, MAX_RW_BYTES>,
is_first_chunk: IsZeroGadget<F>,
}
impl<F: Field> RwTablePaddingGadget<F> {
pub(crate) fn construct(
cb: &mut EVMConstraintBuilder<F>,
inner_rws_before_padding: Expression<F>,
) -> Self {
let max_rws = cb.query_copy_cell();
let chunk_index = cb.query_cell();
let is_empty_rwc =
IsZeroGadget::construct(cb, cb.curr.state.rw_counter.clone().expr() - 1.expr());
cb.chunk_context_lookup(ChunkCtxFieldTag::CurrentChunkIndex, chunk_index.expr());
let is_first_chunk = IsZeroGadget::construct(cb, chunk_index.expr());
cb.condition(is_first_chunk.expr(), |cb| {
cb.rw_table_start_lookup(1.expr());
});
let is_end_padding_exist = LtGadget::<_, MAX_RW_BYTES>::construct(
cb,
1.expr(),
max_rws.expr() - inner_rws_before_padding.expr(),
);
cb.condition(is_end_padding_exist.expr(), |cb| {
cb.rw_table_padding_lookup(inner_rws_before_padding.expr() + 1.expr());
cb.rw_table_padding_lookup(max_rws.expr() - 1.expr());
});
Self {
is_empty_rwc,
max_rws,
chunk_index,
is_first_chunk,
is_end_padding_exist,
}
}
pub(crate) fn assign_exec_step(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
_block: &Block<F>,
chunk: &Chunk<F>,
inner_rws_before_padding: u64,
step: &ExecStep,
) -> Result<(), Error> {
let total_rwc = u64::from(step.rwc) - 1;
self.is_empty_rwc
.assign(region, offset, F::from(total_rwc))?;
let max_rws = F::from(chunk.fixed_param.max_rws as u64);
let max_rws_assigned = self.max_rws.assign(region, offset, Value::known(max_rws))?;
self.chunk_index.assign(
region,
offset,
Value::known(F::from(chunk.chunk_context.idx as u64)),
)?;
self.is_first_chunk
.assign(region, offset, F::from(chunk.chunk_context.idx as u64))?;
self.is_end_padding_exist.assign(
region,
offset,
F::ONE,
max_rws.sub(F::from(inner_rws_before_padding)),
)?;
if step.rw_indices_len() != 0 {
region.constrain_constant(max_rws_assigned, max_rws)?;
}
Ok(())
}
}