Skip to content
This repository was archived by the owner on Jul 5, 2024. It is now read-only.
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bus-mapping/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ serde = {version = "1.0.130", features = ["derive"] }
serde_json = "1.0.66"

[dev-dependencies]
hex = "0.4.3"
mock = { path = "../mock" }
pretty_assertions = "1.0.0"
tokio = { version = "1.13", features = ["macros"] }
Expand Down
257 changes: 230 additions & 27 deletions bus-mapping/src/evm/opcodes/calldatacopy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,36 @@ fn gen_calldatacopy_step(
geth_step.stack.nth_last_filled(2),
length,
)?;
state.push_op(
&mut exec_step,
RW::READ,
CallContextOp {
call_id: state.call()?.call_id,
field: CallContextField::TxId,
value: state.tx_ctx.id().into(),
},
);

if !state.call()?.is_root {
if state.call()?.is_root {
state.push_op(
&mut exec_step,
RW::READ,
CallContextOp {
call_id: state.call()?.call_id,
field: CallContextField::TxId,
value: state.tx_ctx.id().into(),
},
);
state.push_op(
&mut exec_step,
RW::READ,
CallContextOp {
call_id: state.call()?.call_id,
field: CallContextField::CallDataLength,
value: state.call()?.call_data_length.into(),
},
);
} else {
state.push_op(
&mut exec_step,
RW::READ,
CallContextOp {
call_id: state.call()?.call_id,
field: CallContextField::CallerId,
value: state.call()?.caller_id.into(),
},
);
state.push_op(
&mut exec_step,
RW::READ,
Expand All @@ -82,6 +101,7 @@ fn gen_calldatacopy_step(
},
);
};

Ok(exec_step)
}

Expand Down Expand Up @@ -165,20 +185,192 @@ mod calldatacopy_tests {
use crate::{
circuit_input_builder::ExecState,
mock::BlockData,
operation::{CallContextField, CallContextOp, StackOp, RW},
operation::{CallContextField, CallContextOp, MemoryOp, StackOp, RW},
};
use eth_types::{
bytecode,
address, bytecode,
evm_types::{OpcodeId, StackAddress},
geth_types::GethData,
Word,
Address, ToWord, Word,
};

use mock::test_ctx::{helpers::*, TestContext};
use pretty_assertions::assert_eq;

#[test]
fn calldatacopy_opcode_impl() {
fn calldatacopy_opcode_internal() {
let addr_a = address!("0x000000000000000000000000000000000cafe00a");
let addr_b = address!("0x000000000000000000000000000000000cafe00b");

// code B gets called by code A, so the call is an internal call.
let dst_offset = 0x00usize;
let offset = 0x00usize;
let copy_size = 0x10usize;
let code_b = bytecode! {
PUSH32(copy_size) // size
PUSH32(offset) // offset
PUSH32(dst_offset) // dst_offset
CALLDATACOPY
STOP
};

// code A calls code B.
let pushdata = hex::decode("1234567890abcdef").unwrap();
let memory_a = std::iter::repeat(0)
.take(24)
.chain(pushdata.clone())
.collect::<Vec<u8>>();
let call_data_length = 0x20usize;
let call_data_offset = 0x10usize;
let code_a = bytecode! {
// populate memory in A's context.
PUSH8(Word::from_big_endian(&pushdata))
PUSH1(0x00) // offset
MSTORE
// call addr_b.
PUSH1(0x00) // retLength
PUSH1(0x00) // retOffset
PUSH1(call_data_length) // argsLength
PUSH1(call_data_offset) // argsOffset
PUSH1(0x00) // value
PUSH32(addr_b.to_word()) // addr
PUSH32(0x1_0000) // gas
CALL
STOP
};

// Get the execution steps from the external tracer
let block: GethData = TestContext::<3, 1>::new(
None,
|accs| {
accs[0].address(addr_b).code(code_b);
accs[1].address(addr_a).code(code_a);
accs[2]
.address(Address::random())
.balance(Word::from(1u64 << 30));
},
|mut txs, accs| {
txs[0].to(accs[1].address).from(accs[2].address);
},
|block, _tx| block.number(0xcafeu64),
)
.unwrap()
.into();

let mut builder = BlockData::new_from_geth_data(block.clone()).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::CALLDATACOPY))
.unwrap();

let caller_id = builder.block.txs()[0].calls()[step.call_index].caller_id;
let expected_call_id = builder.block.txs()[0].calls()[step.call_index].call_id;

// 3 stack reads + 3 call context reads.
assert_eq!(step.bus_mapping_instance.len(), 6);

// 3 stack reads.
assert_eq!(
[0, 1, 2]
.map(|idx| &builder.block.container.stack[step.bus_mapping_instance[idx].as_usize()])
.map(|operation| (operation.rw(), operation.op())),
[
(
RW::READ,
&StackOp::new(expected_call_id, StackAddress::from(1021), Word::from(dst_offset))
),
(
RW::READ,
&StackOp::new(expected_call_id, StackAddress::from(1022), Word::from(offset))
),
(
RW::READ,
&StackOp::new(expected_call_id, StackAddress::from(1023), Word::from(copy_size))
),
]
);

// 3 call context reads.
assert_eq!(
[3, 4, 5]
.map(|idx| &builder.block.container.call_context
[step.bus_mapping_instance[idx].as_usize()])
.map(|operation| (operation.rw(), operation.op())),
[
(
RW::READ,
&CallContextOp {
call_id: expected_call_id,
field: CallContextField::CallerId,
value: Word::from(1),
}
),
(
RW::READ,
&CallContextOp {
call_id: expected_call_id,
field: CallContextField::CallDataLength,
value: Word::from(call_data_length),
},
),
(
RW::READ,
&CallContextOp {
call_id: expected_call_id,
field: CallContextField::CallDataOffset,
value: Word::from(call_data_offset),
},
),
]
);

// memory writes.
// 1. First `call_data_length` memory ops are RW::WRITE and come from the `CALL`
// opcode. (we skip checking those)
// 2. Following that, we should have tuples of (RW::READ and RW::WRITE) where
// the caller memory is read and the current call's memory is written
// to.
assert_eq!(
builder.block.container.memory.len(),
call_data_length + 2 * copy_size
);
assert_eq!(
(call_data_length..(call_data_length + (2 * copy_size)))
.map(|idx| &builder.block.container.memory[idx])
.map(|op| (op.rw(), op.op().clone()))
.collect::<Vec<(RW, MemoryOp)>>(),
{
let mut memory_ops = Vec::with_capacity(2 * copy_size);
(0..copy_size).for_each(|idx| {
memory_ops.push((
RW::READ,
MemoryOp::new(
caller_id,
(call_data_offset + offset + idx).into(),
memory_a[call_data_offset + idx],
),
));
memory_ops.push((
RW::WRITE,
MemoryOp::new(
expected_call_id,
(dst_offset + idx).into(),
memory_a[call_data_offset + idx],
),
));
});
memory_ops
},
);
}

#[test]
fn calldatacopy_opcode_root() {
let code = bytecode! {
PUSH32(0)
PUSH32(0)
Expand Down Expand Up @@ -208,6 +400,8 @@ mod calldatacopy_tests {
.find(|step| step.exec_state == ExecState::Op(OpcodeId::CALLDATACOPY))
.unwrap();

assert_eq!(step.bus_mapping_instance.len(), 5);

assert_eq!(
[0, 1, 2]
.map(|idx| &builder.block.container.stack[step.bus_mapping_instance[idx].as_usize()])
Expand All @@ -229,19 +423,28 @@ mod calldatacopy_tests {
);

assert_eq!(
{
let operation =
&builder.block.container.call_context[step.bus_mapping_instance[3].as_usize()];
(operation.rw(), operation.op())
},
(
RW::READ,
&CallContextOp {
call_id: builder.block.txs()[0].calls()[0].call_id,
field: CallContextField::TxId,
value: Word::from(1),
}
)
[3, 4]
.map(|idx| &builder.block.container.call_context
[step.bus_mapping_instance[idx].as_usize()])
.map(|operation| (operation.rw(), operation.op())),
[
(
RW::READ,
&CallContextOp {
call_id: builder.block.txs()[0].calls()[0].call_id,
field: CallContextField::TxId,
value: Word::from(1),
}
),
(
RW::READ,
&CallContextOp {
call_id: builder.block.txs()[0].calls()[0].call_id,
field: CallContextField::CallDataLength,
value: Word::zero(),
},
),
]
);
}
}
Loading