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
219 changes: 215 additions & 4 deletions bus-mapping/src/evm/opcodes/balance.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -12,16 +13,39 @@ impl Opcode for Balance {
state: &mut CircuitInputStateRef,
geth_steps: &[GethExecStep],
) -> Result<Vec<ExecStep>, 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,
Expand All @@ -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),
Expand All @@ -43,3 +78,179 @@ 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, Word, U256};
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,
}
);
}
}
6 changes: 4 additions & 2 deletions zkevm-circuits/src/evm_circuit/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use strum::IntoEnumIterator;
mod add_sub;
mod addmod;
mod address;
mod balance;
mod begin_tx;
mod bitwise;
mod block_ctx;
Expand Down Expand Up @@ -79,6 +80,7 @@ use self::sha3::Sha3Gadget;
use add_sub::AddSubGadget;
use addmod::AddModGadget;
use address::AddressGadget;
use balance::BalanceGadget;
use begin_tx::BeginTxGadget;
use bitwise::BitwiseGadget;
use block_ctx::{BlockCtxU160Gadget, BlockCtxU256Gadget, BlockCtxU64Gadget};
Expand Down Expand Up @@ -164,6 +166,7 @@ pub(crate) struct ExecutionConfig<F> {
add_sub_gadget: AddSubGadget<F>,
addmod_gadget: AddModGadget<F>,
address_gadget: AddressGadget<F>,
balance_gadget: BalanceGadget<F>,
bitwise_gadget: BitwiseGadget<F>,
byte_gadget: ByteGadget<F>,
call_gadget: CallGadget<F>,
Expand Down Expand Up @@ -199,7 +202,6 @@ pub(crate) struct ExecutionConfig<F> {
selfbalance_gadget: SelfbalanceGadget<F>,
sha3_gadget: Sha3Gadget<F>,
shr_gadget: ShrGadget<F>,
balance_gadget: DummyGadget<F, 1, 1, { ExecutionState::BALANCE }>,
blockhash_gadget: DummyGadget<F, 1, 1, { ExecutionState::BLOCKHASH }>,
exp_gadget: DummyGadget<F, 2, 1, { ExecutionState::EXP }>,
shl_gadget: DummyGadget<F, 2, 1, { ExecutionState::SHL }>,
Expand Down Expand Up @@ -889,6 +891,7 @@ impl<F: Field> ExecutionConfig<F> {
ExecutionState::ADD_SUB => assign_exec_step!(self.add_sub_gadget),
ExecutionState::ADDMOD => assign_exec_step!(self.addmod_gadget),
ExecutionState::ADDRESS => assign_exec_step!(self.address_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),
Expand Down Expand Up @@ -927,7 +930,6 @@ impl<F: Field> ExecutionConfig<F> {
ExecutionState::BLOCKCTXU256 => assign_exec_step!(self.block_ctx_u256_gadget),
ExecutionState::SELFBALANCE => assign_exec_step!(self.selfbalance_gadget),
// dummy gadgets
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),
Expand Down
Loading