Skip to content
This repository has been archived by the owner on Jul 5, 2024. It is now read-only.

Identity (dataCopy) precompile #1396

Closed
Show file tree
Hide file tree
Changes from 16 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
4 changes: 3 additions & 1 deletion bus-mapping/src/circuit_input_builder/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use crate::{
circuit_input_builder::CallContext, error::ExecError, exec_trace::OperationRef,
operation::RWCounter,
operation::RWCounter, precompile::PrecompileCalls,
};
use eth_types::{
evm_types::{Gas, GasCost, OpcodeId, ProgramCounter},
Expand Down Expand Up @@ -132,6 +132,8 @@ impl ExecStep {
pub enum ExecState {
/// EVM Opcode ID
Op(OpcodeId),
/// Precompile call
Precompile(PrecompileCalls),
/// Virtual step Begin Tx
BeginTx,
/// Virtual step End Tx
Expand Down
2 changes: 2 additions & 0 deletions bus-mapping/src/evm/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ mod error_return_data_outofbound;
mod error_simple;
mod error_write_protection;

mod precompiles;

#[cfg(test)]
mod memory_expansion_test;

Expand Down
122 changes: 108 additions & 14 deletions bus-mapping/src/evm/opcodes/callop.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use super::Opcode;
use crate::{
circuit_input_builder::{CallKind, CircuitInputStateRef, CodeSource, ExecStep},
operation::{AccountField, CallContextField, TxAccessListAccountOp},
precompile::{execute_precompiled, is_precompiled},
circuit_input_builder::{
CallKind, CircuitInputStateRef, CodeSource, CopyDataType, CopyEvent, ExecStep, NumberOrHash,
},
evm::opcodes::precompiles::identity::gen_associated_ops as precompile_identity_ops,
operation::{AccountField, CallContextField, MemoryOp, TxAccessListAccountOp, RW},
precompile::{execute_precompiled, is_precompiled, PrecompileCalls},
state_db::CodeDB,
Error,
};
Expand Down Expand Up @@ -138,7 +141,7 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {
(call.is_persistent as u64).into(),
),
] {
state.call_context_write(&mut exec_step, call.clone().call_id, field, value);
state.call_context_write(&mut exec_step, call.call_id, field, value);
}

let (found, sender_account) = state.sdb.get_account(&call.caller_address);
Expand Down Expand Up @@ -225,29 +228,65 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {
// 1. Call to precompiled.
(false, true, _) => {
assert!(call.is_success, "call to precompile should not fail");
let caller_ctx = state.caller_ctx_mut()?;
let caller_ctx = state.caller_ctx()?;
let caller_memory = caller_ctx.memory.0.clone();
let code_address = code_address.unwrap();
let (result, contract_gas_cost) = execute_precompiled(
&code_address,
if args_length != 0 {
&caller_ctx.memory.0[args_offset..args_offset + args_length]
&caller_memory[args_offset..args_offset + args_length]
} else {
&[]
},
callee_gas_left,
);

log::trace!(
"precompile return data len {} gas {}",
result.len(),
contract_gas_cost
);
caller_ctx.return_data = result.clone();

let caller_ctx_mut = state.caller_ctx_mut()?;
caller_ctx_mut.return_data = result.clone();
let length = min(result.len(), ret_length);
if length != 0 {
caller_ctx.memory.extend_at_least(ret_offset + length);
caller_ctx_mut.memory.extend_at_least(ret_offset + length);
caller_ctx_mut.memory.0[ret_offset..ret_offset + length]
.copy_from_slice(&result[..length]);
}

for (field, value) in [
(
CallContextField::IsSuccess,
Word::from(call.is_success as u64),
),
(
CallContextField::CalleeAddress,
call.code_address().unwrap().to_word(),
),
(CallContextField::CallerId, call.caller_id.into()),
(
CallContextField::CallDataOffset,
call.call_data_offset.into(),
),
(
CallContextField::CallDataLength,
call.call_data_length.into(),
),
(
CallContextField::ReturnDataOffset,
call.return_data_offset.into(),
),
(
CallContextField::ReturnDataLength,
call.return_data_length.into(),
),
] {
state.call_context_write(&mut exec_step, call.call_id, field, value);
}
caller_ctx.memory.0[ret_offset..ret_offset + length]
.copy_from_slice(&result[..length]);

// return while restoring some of caller's context.
for (field, value) in [
(CallContextField::LastCalleeId, call.call_id.into()),
(CallContextField::LastCalleeReturnDataOffset, 0.into()),
Expand All @@ -259,7 +298,63 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {
state.call_context_write(&mut exec_step, current_call.call_id, field, value);
}

log::warn!("missing circuit part of precompile");
// insert a copy event for this step.
let rw_counter_start = state.block_ctx.rwc;
if call.is_success && call.call_data_length > 0 {
let bytes: Vec<(u8, bool)> = caller_memory
.iter()
.skip(call.call_data_offset as usize)
.take(call.call_data_length as usize)
.map(|b| (*b, false))
.collect();
for (i, &(byte, _is_code)) in bytes.iter().enumerate() {
// push caller memory read
state.push_op(
&mut exec_step,
RW::READ,
MemoryOp::new(
call.caller_id,
(call.call_data_offset + i as u64).into(),
byte,
),
);
// push callee memory write
state.push_op(
&mut exec_step,
RW::WRITE,
MemoryOp::new(call.call_id, i.into(), byte),
);
}
state.push_copy(
&mut exec_step,
CopyEvent {
src_addr: call.call_data_offset,
src_addr_end: call.call_data_offset + call.call_data_length,
src_type: CopyDataType::Memory,
src_id: NumberOrHash::Number(call.caller_id),
dst_addr: 0,
dst_type: CopyDataType::Memory,
dst_id: NumberOrHash::Number(call.call_id),
log_id: None,
rw_counter_start,
bytes,
},
);
}

let precompile_step = match code_address.0[19].into() {
PrecompileCalls::Identity => precompile_identity_ops(
state,
geth_steps[1].clone(),
call.clone(),
caller_memory.as_slice(),
)?,
_ => {
log::warn!("precompile not handled");
todo!();
}
};

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

let real_cost = geth_steps[0].gas.0 - geth_steps[1].gas.0;
Expand All @@ -272,7 +367,7 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {
);
}
exec_step.gas_cost = GasCost(real_cost);
Ok(vec![exec_step])
Ok(vec![exec_step, precompile_step])
}
// 2. Call to account with empty code.
(false, _, true) => {
Expand Down Expand Up @@ -350,7 +445,6 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {

Ok(vec![exec_step])
}

// 4. insufficient balance or error depth cases.
(true, _, _) => {
for (field, value) in [
Expand All @@ -362,7 +456,7 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {
}
state.handle_return(&mut exec_step, geth_steps, false)?;
Ok(vec![exec_step])
} //
}
}
}
}
Expand Down
70 changes: 70 additions & 0 deletions bus-mapping/src/evm/opcodes/precompiles/identity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use eth_types::GethExecStep;

use crate::{
circuit_input_builder::{
Call, CircuitInputStateRef, CopyDataType, CopyEvent, ExecState, ExecStep, NumberOrHash,
},
evm::opcodes::precompiles::common_call_ctx_reads,
operation::{MemoryOp, RW},
precompile::PrecompileCalls,
Error,
};

pub fn gen_associated_ops(
state: &mut CircuitInputStateRef,
geth_step: GethExecStep,
call: Call,
memory_bytes: &[u8],
) -> Result<ExecStep, Error> {
assert_eq!(call.code_address(), Some(PrecompileCalls::Identity.into()));
let mut exec_step = state.new_step(&geth_step)?;
exec_step.exec_state = ExecState::Precompile(PrecompileCalls::Identity);

common_call_ctx_reads(state, &mut exec_step, &call);

let rw_counter_start = state.block_ctx.rwc;
if call.is_success && call.call_data_length > 0 && call.return_data_length > 0 {
let length = std::cmp::min(call.call_data_length, call.return_data_length);
let bytes: Vec<(u8, bool)> = memory_bytes
.iter()
.skip(call.call_data_offset as usize)
.take(length as usize)
.map(|b| (*b, false))
.collect();
for (i, &(byte, _is_code)) in bytes.iter().enumerate() {
// push callee memory read
state.push_op(
&mut exec_step,
RW::READ,
MemoryOp::new(call.call_id, i.into(), byte),
);
// push caller memory write
state.push_op(
&mut exec_step,
RW::WRITE,
MemoryOp::new(
call.caller_id,
(call.return_data_offset + i as u64).into(),
byte,
),
);
}
state.push_copy(
&mut exec_step,
CopyEvent {
src_addr: 0,
src_addr_end: call.return_data_length,
src_type: CopyDataType::Memory,
src_id: NumberOrHash::Number(call.call_id),
dst_addr: call.return_data_offset,
dst_type: CopyDataType::Memory,
dst_id: NumberOrHash::Number(call.caller_id),
log_id: None,
rw_counter_start,
bytes,
},
);
}

Ok(exec_step)
}
44 changes: 44 additions & 0 deletions bus-mapping/src/evm/opcodes/precompiles/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use eth_types::{ToWord, Word};

use crate::{
circuit_input_builder::{Call, CircuitInputStateRef, ExecStep},
operation::CallContextField,
};

pub mod identity;

pub(crate) fn common_call_ctx_reads(
state: &mut CircuitInputStateRef,
exec_step: &mut ExecStep,
call: &Call,
) {
for (field, value) in [
(
CallContextField::IsSuccess,
Word::from(call.is_success as u64),
),
(
CallContextField::CalleeAddress,
call.code_address().unwrap().to_word(),
),
(CallContextField::CallerId, call.caller_id.into()),
(
CallContextField::CallDataOffset,
call.call_data_offset.into(),
),
(
CallContextField::CallDataLength,
call.call_data_length.into(),
),
(
CallContextField::ReturnDataOffset,
call.return_data_offset.into(),
),
(
CallContextField::ReturnDataLength,
call.return_data_length.into(),
),
] {
state.call_context_read(exec_step, call.call_id, field, value);
}
}
Loading