Skip to content
This repository was archived by the owner on Jul 5, 2024. 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
7 changes: 7 additions & 0 deletions bus-mapping/src/circuit_input_builder/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ pub struct CallContext {
pub return_data: Vec<u8>,
}

impl CallContext {
/// Memory size in words, rounded up
pub fn memory_word_size(&self) -> u64 {
u64::try_from(self.memory.len()).expect("failed to convert usize to u64") / 32
}
}

/// A reversion group is the collection of calls and the operations which are
/// [`Operation::reversible`](crate::operation::Operation::reversible) that
/// happened in them, that will be reverted at once when the call that initiated
Expand Down
238 changes: 168 additions & 70 deletions bus-mapping/src/evm/opcodes/create.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use crate::circuit_input_builder::{CircuitInputStateRef, ExecStep};
use crate::circuit_input_builder::{
CircuitInputStateRef, CopyDataType, CopyEvent, ExecStep, NumberOrHash,
};
use crate::evm::Opcode;
use crate::operation::{AccountField, AccountOp, CallContextField, TxAccessListAccountOp, RW};
use crate::operation::{AccountField, AccountOp, CallContextField, MemoryOp, RW};
use crate::Error;
use eth_types::{evm_types::gas_utils::memory_expansion_gas_cost, GethExecStep, ToWord, Word};
use keccak256::EMPTY_HASH;
use eth_types::{Bytecode, ToBigEndian, H160, H256};
use ethers_core::utils::{get_create2_address, keccak256};

#[derive(Debug, Copy, Clone)]
pub struct DummyCreate<const IS_CREATE2: bool>;
Expand All @@ -13,23 +16,24 @@ impl<const IS_CREATE2: bool> Opcode for DummyCreate<IS_CREATE2> {
state: &mut CircuitInputStateRef,
geth_steps: &[GethExecStep],
) -> Result<Vec<ExecStep>, Error> {
// TODO: replace dummy create here
let geth_step = &geth_steps[0];

let offset = geth_step.stack.nth_last(1)?.as_usize();
let length = geth_step.stack.nth_last(2)?.as_usize();

let curr_memory_word_size = (state.call_ctx()?.memory.len() as u64) / 32;
let curr_memory_word_size = state.call_ctx()?.memory_word_size();
if length != 0 {
state
.call_ctx_mut()?
.memory
.extend_at_least(offset + length);
}
let next_memory_word_size = (state.call_ctx()?.memory.len() as u64) / 32;
let next_memory_word_size = state.call_ctx()?.memory_word_size();

let mut exec_step = state.new_step(geth_step)?;

let callee = state.parse_call(geth_step)?;

let n_pop = if IS_CREATE2 { 4 } else { 3 };
for i in 0..n_pop {
state.stack_read(
Expand All @@ -44,76 +48,88 @@ impl<const IS_CREATE2: bool> Opcode for DummyCreate<IS_CREATE2> {
} else {
state.create_address()?
};

// TODO: rename this to initialization call?
let call = state.parse_call(geth_step)?;
state.stack_write(
&mut exec_step,
geth_step.stack.nth_last_filled(n_pop - 1),
if call.is_success {
if callee.is_success {
address.to_word()
} else {
Word::zero()
},
)?;

let mut init_code = vec![];
if length > 0 {
init_code = handle_copy(state, &mut exec_step, state.call()?.call_id, offset, length)?;
}

let tx_id = state.tx_ctx.id();
let current_call = state.call()?.clone();

// Quote from [EIP-2929](https://eips.ethereum.org/EIPS/eip-2929)
// > When a CREATE or CREATE2 opcode is called,
// > immediately (i.e. before checks are done to determine
// > whether or not the address is unclaimed)
// > add the address being created to accessed_addresses,
// > but gas costs of CREATE and CREATE2 are unchanged
let caller = state.call()?.clone();

state.call_context_read(
&mut exec_step,
caller.call_id,
CallContextField::TxId,
tx_id.to_word(),
);
state.call_context_read(
&mut exec_step,
caller.call_id,
CallContextField::RwCounterEndOfReversion,
caller.rw_counter_end_of_reversion.to_word(),
);
state.call_context_read(
&mut exec_step,
caller.call_id,
CallContextField::IsPersistent,
caller.is_persistent.to_word(),
);

let is_warm = state.sdb.check_account_in_access_list(&address);
state.push_op_reversible(
state.tx_accesslist_account_write(&mut exec_step, tx_id, address, true, is_warm)?;

state.call_context_read(
&mut exec_step,
RW::WRITE,
TxAccessListAccountOp {
tx_id: state.tx_ctx.id(),
address,
is_warm: true,
is_warm_prev: is_warm,
},
)?;
caller.call_id,
CallContextField::CalleeAddress,
caller.address.to_word(),
);

// Increase caller's nonce
let nonce_prev = state.sdb.get_nonce(&call.caller_address);
let caller_nonce = state.sdb.get_nonce(&caller.address);
state.push_op_reversible(
&mut exec_step,
RW::WRITE,
AccountOp {
address: call.caller_address,
address: caller.address,
field: AccountField::Nonce,
value: (nonce_prev + 1).into(),
value_prev: nonce_prev.into(),
value: (caller_nonce + 1).into(),
value_prev: caller_nonce.into(),
},
)?;

// Add callee into access list
let is_warm = state.sdb.check_account_in_access_list(&call.address);
state.push_op_reversible(
&mut exec_step,
RW::WRITE,
TxAccessListAccountOp {
tx_id,
address: call.address,
is_warm: true,
is_warm_prev: is_warm,
},
)?;
// TODO: look into when this can be pushed. Could it be done in parse call?
state.push_call(callee.clone());

state.push_call(call.clone());
for (field, value) in [
(
CallContextField::RwCounterEndOfReversion,
callee.rw_counter_end_of_reversion.to_word(),
),
(
CallContextField::IsPersistent,
callee.is_persistent.to_word(),
),
] {
state.call_context_write(&mut exec_step, callee.call_id, field, value);
}

// Increase callee's nonce
let nonce_prev = state.sdb.get_nonce(&call.address);
debug_assert!(nonce_prev == 0);
debug_assert!(state.sdb.get_nonce(&callee.address) == 0);
state.push_op_reversible(
&mut exec_step,
RW::WRITE,
AccountOp {
address: call.address,
address: callee.address,
field: AccountField::Nonce,
value: 1.into(),
value_prev: 0.into(),
Expand All @@ -122,15 +138,16 @@ impl<const IS_CREATE2: bool> Opcode for DummyCreate<IS_CREATE2> {

state.transfer(
&mut exec_step,
call.caller_address,
call.address,
call.value,
callee.caller_address,
callee.address,
callee.value,
)?;

let memory_expansion_gas_cost =
memory_expansion_gas_cost(curr_memory_word_size, next_memory_word_size);

// EIP-150: all but one 64th of the caller's gas is sent to the callee.
// Per EIP-150, all but one 64th of the caller's gas is sent to the
// initialization call.
let caller_gas_left =
(geth_step.gas.0 - geth_step.gas_cost.0 - memory_expansion_gas_cost) / 64;

Expand All @@ -147,40 +164,121 @@ impl<const IS_CREATE2: bool> Opcode for DummyCreate<IS_CREATE2> {
(CallContextField::MemorySize, next_memory_word_size.into()),
(
CallContextField::ReversibleWriteCounter,
// +3 is because we do some transfers after pushing the call. can be just push the
// call later?
(exec_step.reversible_write_counter + 3).into(),
(exec_step.reversible_write_counter + 2).into(),
),
] {
state.call_context_write(&mut exec_step, current_call.call_id, field, value);
state.call_context_write(&mut exec_step, caller.call_id, field, value);
}

state.call_context_read(
&mut exec_step,
caller.call_id,
CallContextField::Depth,
caller.depth.to_word(),
);

let code_hash = keccak256(&init_code);
for (field, value) in [
(CallContextField::CallerId, current_call.call_id.into()),
(CallContextField::IsSuccess, call.is_success.to_word()),
(CallContextField::IsPersistent, call.is_persistent.to_word()),
(CallContextField::CallerId, caller.call_id.into()),
(CallContextField::IsSuccess, callee.is_success.to_word()),
(
CallContextField::IsPersistent,
callee.is_persistent.to_word(),
),
(CallContextField::TxId, state.tx_ctx.id().into()),
(
CallContextField::CallerAddress,
current_call.address.to_word(),
callee.caller_address.to_word(),
),
(CallContextField::CalleeAddress, call.address.to_word()),
(CallContextField::CalleeAddress, callee.address.to_word()),
(
CallContextField::RwCounterEndOfReversion,
call.rw_counter_end_of_reversion.to_word(),
callee.rw_counter_end_of_reversion.to_word(),
),
(CallContextField::IsPersistent, call.is_persistent.to_word()),
(CallContextField::Depth, callee.depth.to_word()),
(CallContextField::IsRoot, false.to_word()),
(CallContextField::IsStatic, false.to_word()),
(CallContextField::IsCreate, true.to_word()),
(CallContextField::CodeHash, Word::from(code_hash)),
] {
state.call_context_write(&mut exec_step, call.call_id, field, value);
state.call_context_write(&mut exec_step, callee.call_id, field, value);
}

if call.code_hash.to_fixed_bytes() == *EMPTY_HASH {
// 1. Create with empty initcode.
state.handle_return(geth_step)?;
Ok(vec![exec_step])
let keccak_input = if IS_CREATE2 {
let salt = geth_step.stack.nth_last(3)?;
assert_eq!(
address,
get_create2_address(
caller.address,
salt.to_be_bytes().to_vec(),
init_code.clone()
)
);
std::iter::once(0xffu8)
.chain(caller.address.to_fixed_bytes())
.chain(salt.to_be_bytes())
.chain(keccak256(&init_code))
.collect::<Vec<_>>()
} else {
// 2. Create with non-empty initcode.
Ok(vec![exec_step])
let mut stream = ethers_core::utils::rlp::RlpStream::new();
stream.begin_list(2);
stream.append(&caller.address);
stream.append(&Word::from(caller_nonce));
stream.out().to_vec()
};

assert_eq!(
address,
H160(keccak256(&keccak_input)[12..].try_into().unwrap())
);

state.block.sha3_inputs.push(keccak_input);

if length == 0 {
state.handle_return(geth_step)?;
}

Ok(vec![exec_step])
}
}

fn handle_copy(
state: &mut CircuitInputStateRef,
step: &mut ExecStep,
callee_id: usize,
offset: usize,
length: usize,
) -> Result<Vec<u8>, Error> {
let initialization_bytes = state.call_ctx()?.memory.0[offset..offset + length].to_vec();
let dst_id = NumberOrHash::Hash(H256(keccak256(&initialization_bytes)));
let bytes: Vec<_> = Bytecode::from(initialization_bytes.clone())
.code
.iter()
.map(|element| (element.value, element.is_code))
.collect();

let rw_counter_start = state.block_ctx.rwc;
for (i, (byte, _)) in bytes.iter().enumerate() {
// this could be a memory read, if this happens before we push the new call?
state.push_op(
step,
RW::READ,
MemoryOp::new(callee_id, (offset + i).into(), *byte),
);
}

state.push_copy(CopyEvent {
rw_counter_start,
src_type: CopyDataType::Memory,
src_id: NumberOrHash::Number(callee_id),
src_addr: offset.try_into().unwrap(),
src_addr_end: (offset + length).try_into().unwrap(),
dst_type: CopyDataType::Bytecode,
dst_id,
dst_addr: 0,
log_id: None,
bytes,
});

Ok(initialization_bytes)
}
Loading