Skip to content

Commit

Permalink
[Soundness] Tx.to == precompile (scroll-tech#1068)
Browse files Browse the repository at this point in the history
* wip

* complete busmapping part

* fixes

* correct bus-mapping part

* begin_tx circuit

* fix input copy issue

* fix errors

* enable precompile gadget to return in root call

* update all precompile gadgets

* add more unittest

* fmt

* update sha256 gadget

* update baseprecompile gadget

* add more unittest
  • Loading branch information
noel2004 authored Jan 8, 2024
1 parent 8736818 commit e2cf6cd
Show file tree
Hide file tree
Showing 21 changed files with 612 additions and 226 deletions.
36 changes: 14 additions & 22 deletions bus-mapping/src/circuit_input_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use crate::{
error::Error,
evm::opcodes::{gen_associated_ops, gen_associated_steps},
operation::{self, CallContextField, Operation, RWCounter, StartOp, StorageOp, RW},
precompile::is_precompiled,
rpc::GethClient,
state_db::{self, CodeDB, StateDB},
util::{hash_code_keccak, KECCAK_CODE_HASH_EMPTY},
Expand Down Expand Up @@ -583,39 +582,32 @@ impl<'a> CircuitInputBuilder {
}

// Generate BeginTx step
let mut begin_tx_step = gen_associated_steps(
let begin_tx_steps = gen_associated_steps(
&mut self.state_ref(&mut tx, &mut tx_ctx),
ExecState::BeginTx,
)?;

// check gas cost
{
let steps_gas_cost: u64 = begin_tx_steps.iter().map(|st| st.gas_cost.0).sum();
let real_gas_cost = if geth_trace.struct_logs.is_empty() {
GasCost(geth_trace.gas.0)
} else {
GasCost(tx.gas - geth_trace.struct_logs[0].gas.0)
};
if real_gas_cost != begin_tx_step.gas_cost {
let is_precompile = tx.to.map(|ref addr| is_precompiled(addr)).unwrap_or(false);
if is_precompile {
// FIXME after we implement all precompiles
if begin_tx_step.gas_cost != real_gas_cost {
log::warn!(
"change begin tx precompile gas from {:?} to {real_gas_cost:?}, step {begin_tx_step:?}",
begin_tx_step.gas_cost
);
begin_tx_step.gas_cost = real_gas_cost;
}
} else {
// EIP2930 not implemented
if tx.access_list.is_none() {
debug_assert_eq!(begin_tx_step.gas_cost, real_gas_cost);
}
}
// EIP2930 not implemented
if tx.access_list.is_none() {
debug_assert_eq!(
steps_gas_cost,
real_gas_cost.as_u64(),
"begin step cost {:?}, precompile step cost {:?}",
begin_tx_steps[0].gas_cost,
begin_tx_steps.get(1).map(|st| st.gas_cost),
);
}
}

tx.steps_mut().push(begin_tx_step);
tx.steps_mut().extend(begin_tx_steps);

for (index, geth_step) in geth_trace.struct_logs.iter().enumerate() {
let tx_gas = tx.gas;
Expand Down Expand Up @@ -700,9 +692,9 @@ impl<'a> CircuitInputBuilder {

// Generate EndTx step
log::trace!("gen_end_tx_ops");
let end_tx_step =
let end_tx_steps =
gen_associated_steps(&mut self.state_ref(&mut tx, &mut tx_ctx), ExecState::EndTx)?;
tx.steps_mut().push(end_tx_step);
tx.steps_mut().extend(end_tx_steps);

self.sdb.commit_tx();
self.block.txs.push(tx);
Expand Down
24 changes: 24 additions & 0 deletions bus-mapping/src/circuit_input_builder/input_state_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,30 @@ impl<'a> CircuitInputStateRef<'a> {
}
}

/// Create a step right after the ref_step, it shared the same
/// exec_state and call context with ref_step
pub fn new_next_step(&self, ref_step: &ExecStep) -> Result<ExecStep, Error> {
let call_ctx = self.tx_ctx.call_ctx()?;
let gas_left = ref_step.gas_left.0 - ref_step.gas_cost.as_u64();

let step = ExecStep {
exec_state: ref_step.exec_state.clone(),
pc: ref_step.pc,
stack_size: ref_step.stack_size,

memory_size: call_ctx.memory.len(),
call_index: call_ctx.index,
reversible_write_counter: call_ctx.reversible_write_counter,
rwc: self.block_ctx.rwc,
log_id: self.tx_ctx.log_id,

gas_left: Gas(gas_left),
..Default::default()
};

Ok(step)
}

/// Create a new EndTx step
pub fn new_end_tx_step(&self) -> ExecStep {
let prev_step = self
Expand Down
14 changes: 10 additions & 4 deletions bus-mapping/src/evm/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ use self::{pushn::PushN, sha3::Sha3};
use address::Address;
use arithmetic::ArithmeticOpcode;
use balance::Balance;
use begin_end_tx::BeginEndTx;
use begin_end_tx::{gen_begin_tx_steps, gen_end_tx_steps};
use blockhash::Blockhash;
use calldatacopy::Calldatacopy;
use calldataload::Calldataload;
Expand Down Expand Up @@ -481,15 +481,21 @@ pub fn gen_associated_ops(
pub fn gen_associated_steps(
state: &mut CircuitInputStateRef,
execution_step: ExecState,
) -> Result<ExecStep, Error> {
) -> Result<Vec<ExecStep>, Error> {
fn gen_end_tx_steps_adapt(state: &mut CircuitInputStateRef) -> Result<Vec<ExecStep>, Error> {
let ret = gen_end_tx_steps(state)?;
Ok(vec![ret])
}

let fn_gen_associated_steps = match execution_step {
ExecState::BeginTx | ExecState::EndTx => BeginEndTx::gen_associated_steps,
ExecState::BeginTx => gen_begin_tx_steps,
ExecState::EndTx => gen_end_tx_steps_adapt,
_ => {
unreachable!()
}
};

fn_gen_associated_steps(state, execution_step)
fn_gen_associated_steps(state)
}

#[derive(Debug, Copy, Clone)]
Expand Down
186 changes: 156 additions & 30 deletions bus-mapping/src/evm/opcodes/begin_end_tx.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
use super::TxExecSteps;
use super::{
error_oog_precompile::ErrorOOGPrecompile,
precompiles::gen_ops as precompile_gen_ops_for_begin_tx,
};
use crate::{
circuit_input_builder::{
CircuitInputStateRef, CopyBytes, CopyDataType, CopyEvent, ExecState, ExecStep, NumberOrHash,
CircuitInputStateRef, CopyBytes, CopyDataType, CopyEvent, ExecStep, NumberOrHash,
},
l2_predeployed::l1_gas_price_oracle,
operation::{
AccountField, AccountOp, CallContextField, StorageOp, TxReceiptField, TxRefundOp, RW,
},
precompile::is_precompiled,
precompile::{execute_precompiled, is_precompiled, PrecompileCalls},
state_db::CodeDB,
Error,
};
use core::fmt::Debug;
use eth_types::{
evm_types::{
gas_utils::{tx_access_list_gas_cost, tx_data_gas_cost},
Expand All @@ -21,25 +23,25 @@ use eth_types::{
};
use ethers_core::utils::get_contract_address;

#[derive(Clone, Copy, Debug)]
pub(crate) struct BeginEndTx;

impl TxExecSteps for BeginEndTx {
fn gen_associated_steps(
state: &mut CircuitInputStateRef,
execution_step: ExecState,
) -> Result<ExecStep, Error> {
match execution_step {
ExecState::BeginTx => gen_begin_tx_steps(state),
ExecState::EndTx => gen_end_tx_steps(state),
_ => {
unreachable!()
}
}
}
}

pub fn gen_begin_tx_steps(state: &mut CircuitInputStateRef) -> Result<ExecStep, Error> {
// #[derive(Clone, Copy, Debug)]
// pub(crate) struct BeginEndTx;

// impl TxExecSteps for BeginEndTx {
// fn gen_associated_steps(
// state: &mut CircuitInputStateRef,
// execution_step: ExecState,
// ) -> Result<ExecStep, Error> {
// match execution_step {
// ExecState::BeginTx => gen_begin_tx_steps(state),
// ExecState::EndTx => gen_end_tx_steps(state),
// _ => {
// unreachable!()
// }
// }
// }
// }

pub fn gen_begin_tx_steps(state: &mut CircuitInputStateRef) -> Result<Vec<ExecStep>, Error> {
let mut exec_step = state.new_begin_tx_step();

// Add two copy-events for tx access-list addresses and storage keys if EIP-2930.
Expand Down Expand Up @@ -198,7 +200,7 @@ pub fn gen_begin_tx_steps(state: &mut CircuitInputStateRef) -> Result<ExecStep,
} + call_data_gas_cost
+ access_list_gas_cost
+ init_code_gas_cost;
log::trace!("intrinsic_gas_cost {intrinsic_gas_cost}, call_data_gas_cost {call_data_gas_cost}, access_list_gas_cost {access_list_gas_cost}, init_code_gas_cost {init_code_gas_cost}, exec_step.gas_cost {:?}", exec_step.gas_cost);
log::trace!("intrinsic_gas_cost {intrinsic_gas_cost}, call_data_gas_cost {call_data_gas_cost}, access_list_gas_cost {access_list_gas_cost}, init_code_gas_cost {init_code_gas_cost}, &mut exec_step.gas_cost {:?}", &mut exec_step.gas_cost);
exec_step.gas_cost = GasCost(intrinsic_gas_cost);

// Get code_hash of callee account
Expand Down Expand Up @@ -319,6 +321,8 @@ pub fn gen_begin_tx_steps(state: &mut CircuitInputStateRef) -> Result<ExecStep,
);
}

let mut precompile_step = None;

// There are 4 branches from here.
match (
call.is_create(),
Expand Down Expand Up @@ -364,7 +368,132 @@ pub fn gen_begin_tx_steps(state: &mut CircuitInputStateRef) -> Result<ExecStep,
}
}
// 2. Call to precompiled.
(_, true, _) => (),
(_, true, _) => {
// some *pre-handling* for precompile address, like what we have done in callop
// the generation of precompile step is in `handle_tx`, right after the generation
// of begin_tx step

// add dummy field which precompile call contexts needed
for (field, value) in [
(CallContextField::ReturnDataOffset, 0.into()),
(CallContextField::ReturnDataLength, 0.into()),
] {
state.call_context_write(&mut exec_step, call.call_id, field, value)?;
}

// init call context fields like calling normal contract
for (field, value) in [
(CallContextField::Depth, call.depth.into()),
(
CallContextField::CallerAddress,
call.caller_address.to_word(),
),
(CallContextField::CalleeAddress, call.address.to_word()),
(
CallContextField::CallDataOffset,
call.call_data_offset.into(),
),
(
CallContextField::CallDataLength,
call.call_data_length.into(),
),
(CallContextField::Value, call.value),
(CallContextField::IsStatic, (call.is_static as usize).into()),
(CallContextField::LastCalleeId, 0.into()),
(CallContextField::LastCalleeReturnDataOffset, 0.into()),
(CallContextField::LastCalleeReturnDataLength, 0.into()),
(CallContextField::IsRoot, 1.into()),
(CallContextField::IsCreate, call.is_create().to_word()),
(CallContextField::CodeHash, account_code_hash),
] {
state.call_context_write(&mut exec_step, call.call_id, field, value)?;
}

let precompile_call: PrecompileCalls = call.address.0[19].into();
let (result, precompile_call_gas_cost, has_oog_err) = execute_precompiled(
&precompile_call.into(),
&state.tx.input,
exec_step.gas_left.0 - exec_step.gas_cost.as_u64(),
);

// insert a copy event (input) generate word memory read for input.
// we do not handle output / return since it is not part of the mined tx
let n_input_bytes = if let Some(input_len) = precompile_call.input_len() {
std::cmp::min(input_len as u64, call.call_data_length)
} else {
call.call_data_length
};
// we copy the truncated part or whole call data
let src_addr = call.call_data_offset;
let src_addr_end = call.call_data_offset.checked_add(n_input_bytes).unwrap();

let copy_steps = state
.tx
.input
.iter()
.copied()
.take(n_input_bytes as usize)
.map(|b| (b, false, false))
.collect::<Vec<_>>();

let input_bytes = copy_steps
.iter()
.filter(|(_, _, is_mask)| !*is_mask)
.map(|t| t.0)
.collect::<Vec<u8>>();
let rw_counter_start = state.block_ctx.rwc;
state.push_copy(
&mut exec_step,
CopyEvent {
src_id: NumberOrHash::Number(state.tx_ctx.id()),
src_type: CopyDataType::TxCalldata,
src_addr,
src_addr_end,
dst_id: NumberOrHash::Number(call.call_id),
dst_type: CopyDataType::RlcAcc,
dst_addr: 0,
log_id: None,
rw_counter_start,
copy_bytes: CopyBytes::new(copy_steps, None, None),
access_list: vec![],
},
);

let call_success = call.is_success;
// modexp's oog error is handled in ModExpGadget
let mut next_step = if has_oog_err && precompile_call != PrecompileCalls::Modexp {
let next_step = state.new_next_step(&exec_step)?;
log::debug!(
"precompile call ({:?}) runs out of gas: callee_gas_left = {}",
precompile_call,
next_step.gas_left.0,
);

ErrorOOGPrecompile::gen_ops(state, next_step, call)
} else {
precompile_gen_ops_for_begin_tx(
state,
state.new_next_step(&exec_step)?,
call,
precompile_call,
&input_bytes,
&result,
&[], // notice we suppose return is omitted
)
}?;

// adjust gas cost
next_step.gas_cost = GasCost(precompile_call_gas_cost);

// notice we are handling a 'handle_return' process without associated geth step
// 1.handle reversion if needed
if !call_success {
state.handle_reversion(&mut [&mut exec_step, &mut next_step]);
}
// 2.pop call ctx
state.tx_ctx.pop_call_ctx();
precompile_step.replace(next_step);
}
(_, _, is_empty_code_hash) => {
// 3. Call to account with empty code (is_empty_code_hash == true).
// 4. Call to account with non-empty code (is_empty_code_hash == false).
Expand Down Expand Up @@ -398,12 +527,9 @@ pub fn gen_begin_tx_steps(state: &mut CircuitInputStateRef) -> Result<ExecStep,
}
}
}
log::trace!("begin_tx_step: {:?}", exec_step);
if is_precompile && !state.call().unwrap().is_success {
state.handle_reversion(&mut [&mut exec_step]);
}
log::trace!("begin_tx_step: {:?}, {:?}", exec_step, precompile_step);

Ok(exec_step)
Ok(std::iter::once(exec_step).chain(precompile_step).collect())
}

pub fn gen_end_tx_steps(state: &mut CircuitInputStateRef) -> Result<ExecStep, Error> {
Expand Down
1 change: 1 addition & 0 deletions bus-mapping/src/evm/opcodes/callop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {
callee_call.code_address().unwrap().to_word(),
),
(CallContextField::CallerId, callee_call.caller_id.into()),
(CallContextField::IsRoot, 0.into()),
(
CallContextField::CallDataOffset,
callee_call.call_data_offset.into(),
Expand Down
Loading

0 comments on commit e2cf6cd

Please sign in to comment.