From db56464daa892662f90a1f2454a05351036239d8 Mon Sep 17 00:00:00 2001 From: Steven Gu Date: Wed, 10 Aug 2022 11:17:46 +0800 Subject: [PATCH 1/2] Add bus-mapping and circuit of opcode `BALANCE`. --- bus-mapping/src/evm/opcodes/balance.rs | 220 ++++++++++++++++- zkevm-circuits/src/evm_circuit/execution.rs | 6 +- .../src/evm_circuit/execution/balance.rs | 225 ++++++++++++++++++ zkevm-circuits/src/evm_circuit/witness.rs | 2 +- 4 files changed, 446 insertions(+), 7 deletions(-) create mode 100644 zkevm-circuits/src/evm_circuit/execution/balance.rs diff --git a/bus-mapping/src/evm/opcodes/balance.rs b/bus-mapping/src/evm/opcodes/balance.rs index f578e8f454..357cbd3de0 100644 --- a/bus-mapping/src/evm/opcodes/balance.rs +++ b/bus-mapping/src/evm/opcodes/balance.rs @@ -1,8 +1,9 @@ use crate::circuit_input_builder::{CircuitInputStateRef, ExecStep}; use crate::evm::Opcode; -use crate::operation::{TxAccessListAccountOp, RW}; +use crate::operation::{AccountField, CallContextField, TxAccessListAccountOp, RW}; +use crate::state_db::Account; use crate::Error; -use eth_types::{GethExecStep, ToAddress, ToWord}; +use eth_types::{GethExecStep, ToAddress, ToWord, U256}; #[derive(Debug, Copy, Clone)] pub(crate) struct Balance; @@ -12,16 +13,39 @@ impl Opcode for Balance { state: &mut CircuitInputStateRef, geth_steps: &[GethExecStep], ) -> Result, Error> { - // TODO: finish this, only access list part is done let geth_step = &geth_steps[0]; let mut exec_step = state.new_step(geth_step)?; - let address = geth_steps[0].stack.last()?.to_address(); + // Read account address from stack. + let address = geth_step.stack.last()?.to_address(); state.stack_read( &mut exec_step, geth_step.stack.last_filled(), address.to_word(), )?; + + // Read transaction ID, rw_counter_end_of_reversion, and is_persistent + // from call context. + state.call_context_read( + &mut exec_step, + state.call()?.call_id, + CallContextField::TxId, + U256::from(state.tx_ctx.id()), + ); + state.call_context_read( + &mut exec_step, + state.call()?.call_id, + CallContextField::RwCounterEndOfReversion, + U256::from(state.call()?.rw_counter_end_of_reversion as u64), + ); + state.call_context_read( + &mut exec_step, + state.call()?.call_id, + CallContextField::IsPersistent, + U256::from(state.call()?.is_persistent as u64), + ); + + // Update transaction access list for account address. let is_warm = state.sdb.check_account_in_access_list(&address); state.push_op_reversible( &mut exec_step, @@ -34,6 +58,17 @@ impl Opcode for Balance { }, )?; + // Read account balance. + let &Account { balance, .. } = state.sdb.get_account(&address).1; + state.account_read( + &mut exec_step, + address, + AccountField::Balance, + balance, + balance, + )?; + + // Write the BALANCE result to stack. state.stack_write( &mut exec_step, geth_steps[1].stack.nth_last_filled(0), @@ -43,3 +78,180 @@ impl Opcode for Balance { Ok(vec![exec_step]) } } + +#[cfg(test)] +mod balance_tests { + use super::*; + use crate::circuit_input_builder::ExecState; + use crate::mock::BlockData; + use crate::operation::{AccountOp, CallContextOp, StackOp}; + use eth_types::evm_types::{OpcodeId, StackAddress}; + use eth_types::geth_types::GethData; + use eth_types::{address, bytecode, Bytecode, Bytes, Word, U256}; + use ethers_core::utils::keccak256; + use mock::TestContext; + use pretty_assertions::assert_eq; + + #[test] + fn test_balance_of_non_existing_address() { + test_ok(false, false); + } + + #[test] + fn test_balance_of_cold_address() { + test_ok(true, false); + } + + #[test] + fn test_balance_of_warm_address() { + test_ok(true, true); + } + + fn test_ok(is_existing: bool, is_warm: bool) { + let address = address!("0xaabbccddee000000000000000000000000000000"); + + // Pop balance first for warm account. + let mut code = Bytecode::default(); + if is_warm { + code.append(&bytecode! { + PUSH20(address.to_word()) + BALANCE + POP + }); + } + code.append(&bytecode! { + PUSH20(address.to_word()) + BALANCE + STOP + }); + + let balance = if is_existing { + Word::from(800u64) + } else { + Word::zero() + }; + + // Get the execution steps from the external tracer. + let block: GethData = TestContext::<3, 1>::new( + None, + |accs| { + accs[0] + .address(address!("0x0000000000000000000000000000000000000010")) + .balance(Word::from(1u64 << 20)) + .code(code.clone()); + accs[1].address(address).balance(balance); + accs[2] + .address(address!("0x0000000000000000000000000000000000cafe01")) + .balance(Word::from(1u64 << 20)); + }, + |mut txs, accs| { + txs[0].to(accs[0].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(); + + // Check if account address is in access list as a result of bus mapping. + assert!(builder.sdb.add_account_to_access_list(address)); + + let tx_id = 1; + let transaction = &builder.block.txs()[tx_id - 1]; + let call_id = transaction.calls()[0].call_id; + + let indices = transaction + .steps() + .iter() + .filter(|step| step.exec_state == ExecState::Op(OpcodeId::BALANCE)) + .last() + .unwrap() + .bus_mapping_instance + .clone(); + + let container = builder.block.container; + + let operation = &container.stack[indices[0].as_usize()]; + assert_eq!(operation.rw(), RW::READ); + assert_eq!( + operation.op(), + &StackOp { + call_id, + address: StackAddress::from(1023u32), + value: address.to_word() + } + ); + + let operation = &container.call_context[indices[1].as_usize()]; + assert_eq!(operation.rw(), RW::READ); + assert_eq!( + operation.op(), + &CallContextOp { + call_id, + field: CallContextField::TxId, + value: tx_id.into() + } + ); + + let operation = &container.call_context[indices[2].as_usize()]; + assert_eq!(operation.rw(), RW::READ); + assert_eq!( + operation.op(), + &CallContextOp { + call_id, + field: CallContextField::RwCounterEndOfReversion, + value: U256::zero() + } + ); + + let operation = &container.call_context[indices[3].as_usize()]; + assert_eq!(operation.rw(), RW::READ); + assert_eq!( + operation.op(), + &CallContextOp { + call_id, + field: CallContextField::IsPersistent, + value: U256::one() + } + ); + + let operation = &container.tx_access_list_account[indices[4].as_usize()]; + assert_eq!(operation.rw(), RW::WRITE); + assert_eq!( + operation.op(), + &TxAccessListAccountOp { + tx_id, + address, + is_warm: true, + is_warm_prev: is_warm + } + ); + + let operation = &container.account[indices[5].as_usize()]; + assert_eq!(operation.rw(), RW::READ); + assert_eq!( + operation.op(), + &AccountOp { + address, + field: AccountField::Balance, + value: balance, + value_prev: balance, + } + ); + + let operation = &container.stack[indices[6].as_usize()]; + assert_eq!(operation.rw(), RW::WRITE); + assert_eq!( + operation.op(), + &StackOp { + call_id, + address: 1023u32.into(), + value: balance, + } + ); + } +} diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 4b2f178127..a28f768b2e 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -25,6 +25,7 @@ use strum::IntoEnumIterator; mod add_sub; mod addmod; +mod balance; mod begin_tx; mod bitwise; mod block_ctx; @@ -77,6 +78,7 @@ mod swap; use self::sha3::Sha3Gadget; use add_sub::AddSubGadget; use addmod::AddModGadget; +use balance::BalanceGadget; use begin_tx::BeginTxGadget; use bitwise::BitwiseGadget; use block_ctx::{BlockCtxU160Gadget, BlockCtxU256Gadget, BlockCtxU64Gadget}; @@ -161,6 +163,7 @@ pub(crate) struct ExecutionConfig { // opcode gadgets add_sub_gadget: AddSubGadget, addmod_gadget: AddModGadget, + balance_gadget: BalanceGadget, bitwise_gadget: BitwiseGadget, byte_gadget: ByteGadget, call_gadget: CallGadget, @@ -197,7 +200,6 @@ pub(crate) struct ExecutionConfig { sha3_gadget: Sha3Gadget, shr_gadget: ShrGadget, address_gadget: DummyGadget, - balance_gadget: DummyGadget, blockhash_gadget: DummyGadget, exp_gadget: DummyGadget, shl_gadget: DummyGadget, @@ -886,6 +888,7 @@ impl ExecutionConfig { // opcode ExecutionState::ADD_SUB => assign_exec_step!(self.add_sub_gadget), ExecutionState::ADDMOD => assign_exec_step!(self.addmod_gadget), + ExecutionState::BALANCE => assign_exec_step!(self.balance_gadget), ExecutionState::BITWISE => assign_exec_step!(self.bitwise_gadget), ExecutionState::BYTE => assign_exec_step!(self.byte_gadget), ExecutionState::CALL => assign_exec_step!(self.call_gadget), @@ -925,7 +928,6 @@ impl ExecutionConfig { ExecutionState::SELFBALANCE => assign_exec_step!(self.selfbalance_gadget), // dummy gadgets ExecutionState::ADDRESS => assign_exec_step!(self.address_gadget), - ExecutionState::BALANCE => assign_exec_step!(self.balance_gadget), ExecutionState::BLOCKHASH => assign_exec_step!(self.blockhash_gadget), ExecutionState::EXP => assign_exec_step!(self.exp_gadget), ExecutionState::SHL => assign_exec_step!(self.shl_gadget), diff --git a/zkevm-circuits/src/evm_circuit/execution/balance.rs b/zkevm-circuits/src/evm_circuit/execution/balance.rs new file mode 100644 index 0000000000..04e36ec75b --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/balance.rs @@ -0,0 +1,225 @@ +use crate::evm_circuit::execution::ExecutionGadget; +use crate::evm_circuit::param::N_BYTES_ACCOUNT_ADDRESS; +use crate::evm_circuit::step::ExecutionState; +use crate::evm_circuit::util::common_gadget::SameContextGadget; +use crate::evm_circuit::util::constraint_builder::Transition::Delta; +use crate::evm_circuit::util::constraint_builder::{ + ConstraintBuilder, ReversionInfo, StepStateTransition, +}; +use crate::evm_circuit::util::{from_bytes, CachedRegion, Cell, RandomLinearCombination}; +use crate::evm_circuit::witness::{Block, Call, ExecStep, Transaction}; +use crate::table::{AccountFieldTag, CallContextFieldTag}; +use crate::util::Expr; +use eth_types::evm_types::GasCost; +use eth_types::{Field, ToAddress, ToScalar, U256}; +use halo2_proofs::plonk::Error; + +#[derive(Clone, Debug)] +pub(crate) struct BalanceGadget { + same_context: SameContextGadget, + address: RandomLinearCombination, + tx_id: Cell, + reversion_info: ReversionInfo, + is_warm: Cell, + balance: Cell, +} + +impl ExecutionGadget for BalanceGadget { + const NAME: &'static str = "BALANCE"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::BALANCE; + + fn configure(cb: &mut ConstraintBuilder) -> Self { + let address = cb.query_rlc(); + cb.stack_pop(address.expr()); + + let tx_id = cb.call_context(None, CallContextFieldTag::TxId); + let mut reversion_info = cb.reversion_info(None); + let is_warm = cb.query_bool(); + + cb.account_access_list_write( + tx_id.expr(), + from_bytes::expr(&address.cells), + 1.expr(), + is_warm.expr(), + Some(&mut reversion_info), + ); + + let balance = cb.query_cell(); + cb.account_read( + from_bytes::expr(&address.cells), + AccountFieldTag::Balance, + balance.expr(), + ); + + cb.stack_push(balance.expr()); + + let gas_cost = is_warm.expr() * GasCost::WARM_ACCESS.expr() + + (1.expr() - is_warm.expr()) * GasCost::COLD_ACCOUNT_ACCESS.expr(); + + let step_state_transition = StepStateTransition { + rw_counter: Delta(cb.rw_counter_offset()), + program_counter: Delta(1.expr()), + stack_pointer: Delta(0.expr()), + gas_left: Delta(-gas_cost), + reversible_write_counter: Delta(1.expr()), + ..Default::default() + }; + + let opcode = cb.query_cell(); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); + + Self { + same_context, + address, + tx_id, + reversion_info, + is_warm, + balance, + } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + tx: &Transaction, + call: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + self.same_context.assign_exec_step(region, offset, step)?; + + let mut address_bytes = block.rws[step.rw_indices[0]].stack_value().to_address().0; + address_bytes.reverse(); + self.address.assign(region, offset, Some(address_bytes))?; + + self.tx_id + .assign(region, offset, U256::from(tx.id).to_scalar())?; + + self.reversion_info.assign( + region, + offset, + call.rw_counter_end_of_reversion, + call.is_persistent, + )?; + + let is_warm = match GasCost::from(step.gas_cost) { + GasCost::COLD_ACCOUNT_ACCESS => 0, + GasCost::WARM_ACCESS => 1, + _ => unreachable!(), + }; + self.is_warm + .assign(region, offset, Some(F::from(is_warm)))?; + + let balance = block.rws[step.rw_indices[5]] + .table_assignment(block.randomness) + .value; + self.balance.assign(region, offset, Some(balance))?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use crate::evm_circuit::witness::block_convert; + use crate::test_util::{test_circuits_using_witness_block, BytecodeTestConfig}; + use bus_mapping::mock::BlockData; + use eth_types::geth_types::{Account, GethData}; + use eth_types::{address, bytecode, Address, Bytecode, ToWord, Word, U256}; + use lazy_static::lazy_static; + use mock::TestContext; + + lazy_static! { + static ref ADDRESS: Address = address!("0xaabbccddee000000000000000000000000000000"); + } + + #[test] + fn balance_gadget_of_non_existing_address() { + test_ok(None, false); + } + + #[test] + fn balance_gadget_of_cold_address() { + test_ok( + Some(Account { + address: *ADDRESS, + balance: U256::from(900), + ..Default::default() + }), + false, + ); + } + + #[test] + fn balance_gadget_of_warm_address() { + test_ok( + Some(Account { + address: *ADDRESS, + balance: U256::from(900), + ..Default::default() + }), + true, + ); + } + + fn test_ok(external_account: Option, is_warm: bool) { + let address = external_account + .as_ref() + .map(|a| a.address) + .unwrap_or(*ADDRESS); + + // Make the external account warm, if needed, by first getting its external code + // hash. + let mut code = Bytecode::default(); + if is_warm { + code.append(&bytecode! { + PUSH20(address.to_word()) + BALANCE + POP + }); + } + code.append(&bytecode! { + PUSH20(address.to_word()) + #[start] + BALANCE + STOP + }); + + // Execute the bytecode and get trace + let block: GethData = TestContext::<3, 1>::new( + None, + |accs| { + accs[0] + .address(address!("0x000000000000000000000000000000000000cafe")) + .balance(Word::from(1u64 << 20)) + .code(code); + accs[1].address(address); + if let Some(external_account) = external_account { + accs[1].balance(external_account.balance); + } + accs[2] + .address(address!("0x0000000000000000000000000000000000000010")) + .balance(Word::from(1u64 << 20)); + }, + |mut txs, accs| { + txs[0].to(accs[0].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) + .expect("could not handle block tx"); + + test_circuits_using_witness_block( + block_convert(&builder.block, &builder.code_db), + BytecodeTestConfig::default(), + ) + .unwrap(); + } +} diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index 149a390ddc..fd238be491 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -1173,6 +1173,7 @@ impl From<&circuit_input_builder::ExecStep> for ExecutionState { match op { OpcodeId::ADD | OpcodeId::SUB => ExecutionState::ADD_SUB, OpcodeId::ADDMOD => ExecutionState::ADDMOD, + OpcodeId::BALANCE => ExecutionState::BALANCE, OpcodeId::MUL | OpcodeId::DIV | OpcodeId::MOD => ExecutionState::MUL_DIV_MOD, OpcodeId::MULMOD => ExecutionState::MULMOD, OpcodeId::SDIV | OpcodeId::SMOD => ExecutionState::SDIV_SMOD, @@ -1222,7 +1223,6 @@ impl From<&circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::RETURN | OpcodeId::REVERT => ExecutionState::RETURN, // dummy ops OpcodeId::ADDRESS => dummy!(ExecutionState::ADDRESS), - OpcodeId::BALANCE => dummy!(ExecutionState::BALANCE), OpcodeId::BLOCKHASH => dummy!(ExecutionState::BLOCKHASH), OpcodeId::EXP => dummy!(ExecutionState::EXP), OpcodeId::SHL => dummy!(ExecutionState::SHL), From 715936bdf9ef58032a2064b57d0e0e1820b59790 Mon Sep 17 00:00:00 2001 From: Steven Gu Date: Fri, 12 Aug 2022 09:18:06 +0800 Subject: [PATCH 2/2] Add test cases of internal call. --- bus-mapping/src/evm/opcodes/balance.rs | 3 +- .../src/evm_circuit/execution/balance.rs | 162 +++++++++++------- 2 files changed, 105 insertions(+), 60 deletions(-) diff --git a/bus-mapping/src/evm/opcodes/balance.rs b/bus-mapping/src/evm/opcodes/balance.rs index 357cbd3de0..73a36f6c9b 100644 --- a/bus-mapping/src/evm/opcodes/balance.rs +++ b/bus-mapping/src/evm/opcodes/balance.rs @@ -87,8 +87,7 @@ mod balance_tests { use crate::operation::{AccountOp, CallContextOp, StackOp}; use eth_types::evm_types::{OpcodeId, StackAddress}; use eth_types::geth_types::GethData; - use eth_types::{address, bytecode, Bytecode, Bytes, Word, U256}; - use ethers_core::utils::keccak256; + use eth_types::{address, bytecode, Bytecode, Word, U256}; use mock::TestContext; use pretty_assertions::assert_eq; diff --git a/zkevm-circuits/src/evm_circuit/execution/balance.rs b/zkevm-circuits/src/evm_circuit/execution/balance.rs index 04e36ec75b..8b4d352b77 100644 --- a/zkevm-circuits/src/evm_circuit/execution/balance.rs +++ b/zkevm-circuits/src/evm_circuit/execution/balance.rs @@ -18,8 +18,8 @@ use halo2_proofs::plonk::Error; pub(crate) struct BalanceGadget { same_context: SameContextGadget, address: RandomLinearCombination, - tx_id: Cell, reversion_info: ReversionInfo, + tx_id: Cell, is_warm: Cell, balance: Cell, } @@ -36,7 +36,6 @@ impl ExecutionGadget for BalanceGadget { let tx_id = cb.call_context(None, CallContextFieldTag::TxId); let mut reversion_info = cb.reversion_info(None); let is_warm = cb.query_bool(); - cb.account_access_list_write( tx_id.expr(), from_bytes::expr(&address.cells), @@ -51,7 +50,6 @@ impl ExecutionGadget for BalanceGadget { AccountFieldTag::Balance, balance.expr(), ); - cb.stack_push(balance.expr()); let gas_cost = is_warm.expr() * GasCost::WARM_ACCESS.expr() @@ -72,8 +70,8 @@ impl ExecutionGadget for BalanceGadget { Self { same_context, address, - tx_id, reversion_info, + tx_id, is_warm, balance, } @@ -104,11 +102,7 @@ impl ExecutionGadget for BalanceGadget { call.is_persistent, )?; - let is_warm = match GasCost::from(step.gas_cost) { - GasCost::COLD_ACCOUNT_ACCESS => 0, - GasCost::WARM_ACCESS => 1, - _ => unreachable!(), - }; + let (_, is_warm) = block.rws[step.rw_indices[4]].tx_access_list_value_pair(); self.is_warm .assign(region, offset, Some(F::from(is_warm)))?; @@ -123,55 +117,53 @@ impl ExecutionGadget for BalanceGadget { #[cfg(test)] mod test { - use crate::evm_circuit::witness::block_convert; - use crate::test_util::{test_circuits_using_witness_block, BytecodeTestConfig}; - use bus_mapping::mock::BlockData; - use eth_types::geth_types::{Account, GethData}; + use crate::evm_circuit::test::rand_bytes; + use crate::test_util::run_test_circuits; + use eth_types::geth_types::Account; use eth_types::{address, bytecode, Address, Bytecode, ToWord, Word, U256}; use lazy_static::lazy_static; use mock::TestContext; lazy_static! { - static ref ADDRESS: Address = address!("0xaabbccddee000000000000000000000000000000"); + static ref TEST_ADDRESS: Address = address!("0xaabbccddee000000000000000000000000000000"); } #[test] - fn balance_gadget_of_non_existing_address() { - test_ok(None, false); + fn balance_gadget_non_existing_account() { + test_root_ok(&None, false); + test_internal_ok(0x20, 0x00, &None, false); + test_internal_ok(0x1010, 0xff, &None, false); } #[test] - fn balance_gadget_of_cold_address() { - test_ok( - Some(Account { - address: *ADDRESS, - balance: U256::from(900), - ..Default::default() - }), - false, - ); + fn balance_gadget_cold_account() { + let account = Some(Account { + address: *TEST_ADDRESS, + balance: U256::from(900), + ..Default::default() + }); + + test_root_ok(&account, false); + test_internal_ok(0x20, 0x00, &account, false); + test_internal_ok(0x1010, 0xff, &account, false); } #[test] - fn balance_gadget_of_warm_address() { - test_ok( - Some(Account { - address: *ADDRESS, - balance: U256::from(900), - ..Default::default() - }), - true, - ); + fn balance_gadget_warm_account() { + let account = Some(Account { + address: *TEST_ADDRESS, + balance: U256::from(900), + ..Default::default() + }); + + test_root_ok(&account, true); + test_internal_ok(0x20, 0x00, &account, true); + test_internal_ok(0x1010, 0xff, &account, true); } - fn test_ok(external_account: Option, is_warm: bool) { - let address = external_account - .as_ref() - .map(|a| a.address) - .unwrap_or(*ADDRESS); + fn test_root_ok(account: &Option, is_warm: bool) { + let address = account.as_ref().map(|a| a.address).unwrap_or(*TEST_ADDRESS); - // Make the external account warm, if needed, by first getting its external code - // hash. let mut code = Bytecode::default(); if is_warm { code.append(&bytecode! { @@ -182,44 +174,98 @@ mod test { } code.append(&bytecode! { PUSH20(address.to_word()) - #[start] BALANCE STOP }); - // Execute the bytecode and get trace - let block: GethData = TestContext::<3, 1>::new( + let ctx = TestContext::<3, 1>::new( None, |accs| { accs[0] .address(address!("0x000000000000000000000000000000000000cafe")) - .balance(Word::from(1u64 << 20)) + .balance(Word::from(1_u64 << 20)) .code(code); accs[1].address(address); - if let Some(external_account) = external_account { - accs[1].balance(external_account.balance); + if let Some(account) = account { + accs[1].balance(account.balance); } accs[2] .address(address!("0x0000000000000000000000000000000000000010")) - .balance(Word::from(1u64 << 20)); + .balance(Word::from(1_u64 << 20)); }, |mut txs, accs| { txs[0].to(accs[0].address).from(accs[2].address); }, - |block, _tx| block.number(0xcafeu64), + |block, _tx| block, ) - .unwrap() - .into(); + .unwrap(); + + assert_eq!(run_test_circuits(ctx, None), Ok(())); + } - let mut builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); - builder - .handle_block(&block.eth_block, &block.geth_traces) - .expect("could not handle block tx"); + fn test_internal_ok( + call_data_offset: usize, + call_data_length: usize, + account: &Option, + is_warm: bool, + ) { + let address = account.as_ref().map(|a| a.address).unwrap_or(*TEST_ADDRESS); + let (addr_a, addr_b) = (mock::MOCK_ACCOUNTS[0], mock::MOCK_ACCOUNTS[1]); - test_circuits_using_witness_block( - block_convert(&builder.block, &builder.code_db), - BytecodeTestConfig::default(), + // code B gets called by code A, so the call is an internal call. + let mut code_b = Bytecode::default(); + if is_warm { + code_b.append(&bytecode! { + PUSH20(address.to_word()) + BALANCE + POP + }); + } + code_b.append(&bytecode! { + PUSH20(address.to_word()) + BALANCE + STOP + }); + + // code A calls code B. + let pushdata = rand_bytes(8); + 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 + PUSH32(call_data_length) // argsLength + PUSH32(call_data_offset) // argsOffset + PUSH1(0x00) // value + PUSH32(addr_b.to_word()) // addr + PUSH32(0x1_0000) // gas + CALL + STOP + }; + + let ctx = TestContext::<4, 1>::new( + None, + |accs| { + accs[0].address(addr_b).code(code_b); + accs[1].address(addr_a).code(code_a); + accs[2].address(address); + if let Some(account) = account { + accs[2].balance(account.balance); + } + accs[3] + .address(mock::MOCK_ACCOUNTS[2]) + .balance(Word::from(1_u64 << 20)); + }, + |mut txs, accs| { + txs[0].to(accs[1].address).from(accs[3].address); + }, + |block, _tx| block, ) .unwrap(); + + assert_eq!(run_test_circuits(ctx, None), Ok(())); } }