Skip to content
This repository was archived by the owner on Apr 18, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all 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
15 changes: 15 additions & 0 deletions bus-mapping/src/circuit_input_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ pub enum ExecState {
EndTx,
/// Virtual step Copy To Memory
CopyToMemory,
/// Virtal step Copy Code To Memory
CopyCodeToMemory,
}

impl ExecState {
Expand Down Expand Up @@ -159,6 +161,19 @@ pub enum StepAuxiliaryData {
/// Indicate if copy from transaction call data
from_tx: bool,
},
/// Auxiliary data of Copy Code To Memory
CopyCodeToMemory {
/// Source start address
src_addr: u64,
/// Destination address
dst_addr: u64,
/// Bytes left
bytes_left: u64,
/// Source end address
src_addr_end: u64,
/// Bytecode to be copied
code: eth_types::Bytecode,
},
}

/// An execution step of the EVM.
Expand Down
4 changes: 3 additions & 1 deletion bus-mapping/src/evm/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ mod calldatacopy;
mod calldatasize;
mod caller;
mod callvalue;
mod codecopy;
mod dup;
mod mload;
mod mstore;
Expand All @@ -32,6 +33,7 @@ use calldatacopy::Calldatacopy;
use calldatasize::Calldatasize;
use caller::Caller;
use callvalue::Callvalue;
use codecopy::Codecopy;
use dup::Dup;
use mload::Mload;
use mstore::Mstore;
Expand Down Expand Up @@ -106,7 +108,7 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps {
OpcodeId::CALLDATALOAD => StackOnlyOpcode::<1, 1>::gen_associated_ops,
OpcodeId::CALLDATACOPY => Calldatacopy::gen_associated_ops,
// OpcodeId::CODESIZE => {},
// OpcodeId::CODECOPY => {},
OpcodeId::CODECOPY => Codecopy::gen_associated_ops,
// OpcodeId::GASPRICE => {},
// OpcodeId::EXTCODESIZE => {},
// OpcodeId::EXTCODECOPY => {},
Expand Down
210 changes: 210 additions & 0 deletions bus-mapping/src/evm/opcodes/codecopy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
use crate::{
circuit_input_builder::{CircuitInputStateRef, ExecState, ExecStep, StepAuxiliaryData},
operation::RW,
Error,
};
use eth_types::{Bytecode, GethExecStep};

use super::Opcode;

const MAX_COPY_BYTES: usize = 54;

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

impl Opcode for Codecopy {
fn gen_associated_ops(
state: &mut CircuitInputStateRef,
geth_steps: &[GethExecStep],
) -> Result<Vec<ExecStep>, Error> {
let geth_step = &geth_steps[0];
let mut exec_steps = vec![gen_codecopy_step(state, geth_step)?];
let memory_copy_steps = gen_memory_copy_steps(state, geth_steps)?;
exec_steps.extend(memory_copy_steps);
Ok(exec_steps)
}
}

fn gen_codecopy_step(
state: &mut CircuitInputStateRef,
geth_step: &GethExecStep,
) -> Result<ExecStep, Error> {
let mut exec_step = state.new_step(geth_step)?;

let memory_offset = geth_step.stack.nth_last(0)?;
let code_offset = geth_step.stack.nth_last(1)?;
let length = geth_step.stack.nth_last(2)?;

// stack reads
state.push_stack_op(
&mut exec_step,
RW::READ,
geth_step.stack.nth_last_filled(0),
memory_offset,
)?;
state.push_stack_op(
&mut exec_step,
RW::READ,
geth_step.stack.nth_last_filled(1),
code_offset,
)?;
state.push_stack_op(
&mut exec_step,
RW::READ,
geth_step.stack.nth_last_filled(2),
length,
)?;
Ok(exec_step)
}

fn gen_memory_copy_step(
state: &mut CircuitInputStateRef,
exec_step: &mut ExecStep,
src_addr: usize,
dst_addr: usize,
src_addr_end: usize,
bytes_left: usize,
code: eth_types::Bytecode,
) -> Result<(), Error> {
for idx in 0..std::cmp::min(bytes_left, MAX_COPY_BYTES) {
let addr = src_addr + idx;
let byte = if addr < src_addr_end {
code.code()[addr]
} else {
0
};
state.push_memory_op(exec_step, RW::WRITE, (dst_addr + idx).into(), byte)?;
}

exec_step.aux_data = Some(StepAuxiliaryData::CopyCodeToMemory {
src_addr: src_addr as u64,
dst_addr: dst_addr as u64,
bytes_left: bytes_left as u64,
src_addr_end: src_addr_end as u64,
code,
});

Ok(())
}

fn gen_memory_copy_steps(
state: &mut CircuitInputStateRef,
geth_steps: &[GethExecStep],
) -> Result<Vec<ExecStep>, Error> {
let memory_offset = geth_steps[0].stack.nth_last(0)?.as_usize();
let code_offset = geth_steps[0].stack.nth_last(1)?.as_usize();
let length = geth_steps[0].stack.nth_last(2)?.as_usize();

let code_hash = state.call()?.code_hash;
let code = state.code_db.0.get(&code_hash).unwrap();
let code = Bytecode::try_from(code.clone()).unwrap();
let src_addr_end = code.code().len();

let mut copied = 0;
let mut steps = vec![];
while copied < length {
let mut exec_step = state.new_step(&geth_steps[1])?;
exec_step.exec_state = ExecState::CopyCodeToMemory;
gen_memory_copy_step(
state,
&mut exec_step,
code_offset + copied,
memory_offset + copied,
src_addr_end,
length - copied,
code.clone(),
)?;
steps.push(exec_step);
copied += MAX_COPY_BYTES;
}

Ok(steps)
}

#[cfg(test)]
mod codecopy_tests {
use eth_types::{
bytecode,
evm_types::{MemoryAddress, OpcodeId, StackAddress},
Word,
};
use mock::new_single_tx_trace_code;

use crate::{
mock::BlockData,
operation::{MemoryOp, StackOp},
};

use super::*;

#[test]
fn codecopy_opcode_impl() {
test_ok(0x00, 0x00, 0x40);
test_ok(0x20, 0x40, 0xA0);
}

fn test_ok(memory_offset: usize, code_offset: usize, size: usize) {
let code = bytecode! {
PUSH32(size)
PUSH32(code_offset)
PUSH32(memory_offset)
CODECOPY
STOP
};

let block = BlockData::new_from_geth_data(new_single_tx_trace_code(&code).unwrap());

let mut builder = block.new_circuit_input_builder();
builder
.handle_block(&block.eth_block, &block.geth_traces)
.unwrap();

let step = builder.block.txs()[0]
.steps()
.iter()
.find(|step| step.exec_state == ExecState::Op(OpcodeId::CODECOPY))
.unwrap();

assert_eq!(
[0, 1, 2]
.map(|idx| &builder.block.container.stack[step.bus_mapping_instance[idx].as_usize()])
.map(|op| (op.rw(), op.op())),
[
(
RW::READ,
&StackOp::new(1, StackAddress::from(1021), Word::from(memory_offset)),
),
(
RW::READ,
&StackOp::new(1, StackAddress::from(1022), Word::from(code_offset)),
),
(
RW::READ,
&StackOp::new(1, StackAddress::from(1023), Word::from(size)),
),
]
);
assert_eq!(
(0..size)
.map(|idx| &builder.block.container.memory[idx])
.map(|op| (op.rw(), op.op().clone()))
.collect::<Vec<(RW, MemoryOp)>>(),
(0..size)
.map(|idx| {
(
RW::WRITE,
MemoryOp::new(
1,
MemoryAddress::from(memory_offset + idx),
if code_offset + idx < code.code().len() {
code.code()[code_offset + idx]
} else {
0
},
),
)
})
.collect::<Vec<(RW, MemoryOp)>>(),
);
}
}
65 changes: 64 additions & 1 deletion eth-types/src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@ use crate::Word;
use std::collections::HashMap;

/// EVM Bytecode
#[derive(Debug, Default, Clone)]
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Bytecode {
code: Vec<u8>,
num_opcodes: usize,
markers: HashMap<String, usize>,
}

/// Error while constructing `Bytecode` from raw bytes.
#[derive(Debug)]
pub enum BytecodeError {
/// Invalid byte that is not reserved for any known opcode.
InvalidByte(u8),
/// Insufficient number of bytes following a PUSH instruction.
InsufficientPush,
}

impl Bytecode {
/// Get a reference to the generated code
pub fn code(&self) -> &[u8] {
Expand Down Expand Up @@ -128,6 +137,37 @@ impl Bytecode {
}
}

impl TryFrom<Vec<u8>> for Bytecode {
type Error = BytecodeError;

fn try_from(input: Vec<u8>) -> Result<Self, Self::Error> {
let mut code = Bytecode::default();

let mut input_iter = input.iter();
while let Some(byte) = input_iter.next() {
if let Ok(op) = OpcodeId::try_from(*byte) {
if op.is_push() {
let n = (op.as_u8() - OpcodeId::PUSH1.as_u8() + 1) as usize;
let mut value = vec![0u8; n];
for value_byte in value.iter_mut() {
*value_byte = input_iter
.next()
.cloned()
.ok_or(BytecodeError::InsufficientPush)?;
}
code.push(n, Word::from(value.as_slice()));
} else {
code.write_op(op);
}
} else {
return Err(BytecodeError::InvalidByte(*byte));
}
}

Ok(code)
}
}

/// EVM code macro
#[macro_export]
macro_rules! bytecode {
Expand Down Expand Up @@ -169,3 +209,26 @@ macro_rules! bytecode_internal {
$crate::bytecode_internal!($code, $($rest)*);
}};
}

#[cfg(test)]
mod tests {
use crate::Bytecode;

#[test]
fn test_bytecode_roundtrip() {
let code = bytecode! {
PUSH8(0x123)
POP
PUSH24(0x321)
PUSH32(0x432)
MUL
CALLVALUE
CALLER
POP
POP
POP
STOP
};
assert_eq!(Bytecode::try_from(code.to_vec()).unwrap(), code);
}
}
Loading