Skip to content

Commit

Permalink
Circuit for opcode EXTCODESIZE (privacy-scaling-explorations#1014)
Browse files Browse the repository at this point in the history
* Add bus-mapping and circuit of opcode `EXTCODESIZE`.

* Add test cases for `EXTCODESIZE` bus-mapping.

* Add circuit code.

* Update bus-mapping test cases.

* Update circuit test cases.

* Replace to convert all bytecodes of bus-mapping code DB to circuit (not only call context bytecodes).

* Merge test cases to one function `test_extcodesize_opcode` in bus-mapping.

* Update circuit test cases.

* Update bus-mapping/src/evm/opcodes/extcodesize.rs

Co-authored-by: Carlos Pérez <[email protected]>

* Update bus-mapping/src/evm/opcodes/extcodesize.rs

Co-authored-by: Carlos Pérez <[email protected]>

* Update bus-mapping/src/evm/opcodes/extcodesize.rs

Co-authored-by: Carlos Pérez <[email protected]>

* Update bus-mapping/src/evm/opcodes/extcodesize.rs

Co-authored-by: Carlos Pérez <[email protected]>

* Delete using `U256` to fix lint.

* Add `MOCK_1_ETH` and `MOCK_CODES` to `mock` crate, and update test cases in both bus-mapping and circuit.

* Delete test case `test_ok(None, false)` and update `test_ok` parameter from `&Option<Account>` to `&Account`.

Co-authored-by: Carlos Pérez <[email protected]>
  • Loading branch information
silathdiir and CPerezz authored Dec 29, 2022
1 parent a533b96 commit a606660
Show file tree
Hide file tree
Showing 7 changed files with 518 additions and 28 deletions.
246 changes: 238 additions & 8 deletions bus-mapping/src/evm/opcodes/extcodesize.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
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::Error;
use eth_types::{GethExecStep, ToAddress, ToWord};
use eth_types::{GethExecStep, ToAddress, ToWord, Word};

#[derive(Debug, Copy, Clone)]
pub(crate) struct Extcodesize;
Expand All @@ -12,34 +12,264 @@ impl Opcode for Extcodesize {
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 external_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(),
external_address.to_word(),
address.to_word(),
)?;
let is_warm = state.sdb.check_account_in_access_list(&external_address);

// Read transaction ID, rw_counter_end_of_reversion, and is_persistent from call
// context.
for (field, value) in [
(CallContextField::TxId, state.tx_ctx.id().to_word()),
(
CallContextField::RwCounterEndOfReversion,
state.call()?.rw_counter_end_of_reversion.to_word(),
),
(
CallContextField::IsPersistent,
state.call()?.is_persistent.to_word(),
),
] {
state.call_context_read(&mut exec_step, state.call()?.call_id, field, value);
}

// 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,
RW::WRITE,
TxAccessListAccountOp {
tx_id: state.tx_ctx.id(),
address: external_address,
address,
is_warm: true,
is_warm_prev: is_warm,
},
)?;

// Read account code hash and get code length.
let account = state.sdb.get_account(&address).1;
let code_size = if !account.is_empty() {
let code_hash = account.code_hash;
let code_hash_word = code_hash.to_word();
state.account_read(
&mut exec_step,
address,
AccountField::CodeHash,
code_hash_word,
code_hash_word,
)?;
state.code(code_hash)?.len()
} else {
state.account_read(
&mut exec_step,
address,
AccountField::NonExisting,
Word::zero(),
Word::zero(),
)?;
0
};

// Write the EXTCODESIZE result to stack.
debug_assert_eq!(code_size, geth_steps[1].stack.last()?.as_usize());
state.stack_write(
&mut exec_step,
geth_steps[1].stack.nth_last_filled(0),
geth_steps[1].stack.nth_last(0)?,
code_size.into(),
)?;

Ok(vec![exec_step])
}
}

#[cfg(test)]
mod extcodesize_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::{Account, GethData};
use eth_types::{bytecode, Bytecode, Word, U256};
use ethers_core::utils::keccak256;
use mock::{TestContext, MOCK_1_ETH, MOCK_ACCOUNTS, MOCK_CODES};
use pretty_assertions::assert_eq;

#[test]
fn test_extcodesize_opcode() {
let account = Account {
address: MOCK_ACCOUNTS[4],
code: MOCK_CODES[4].clone(),
..Default::default()
};

// Test for empty account.
test_ok(&Account::default(), false);
// Test for cold account.
test_ok(&account, false);
// Test for warm account.
test_ok(&account, true);
}

fn test_ok(account: &Account, is_warm: bool) {
let account_exists = !account.is_empty();

let mut bytecode = Bytecode::default();
if is_warm {
bytecode.append(&bytecode! {
PUSH20(account.address.to_word())
EXTCODESIZE
POP
});
}
bytecode.append(&bytecode! {
PUSH20(account.address.to_word())
EXTCODESIZE
STOP
});

// Get the execution steps from the external tracer.
let block: GethData = TestContext::<3, 1>::new(
None,
|accs| {
accs[0]
.address(MOCK_ACCOUNTS[0])
.balance(*MOCK_1_ETH)
.code(bytecode);
if account_exists {
accs[1].address(account.address).code(account.code.clone());
} else {
accs[1].address(MOCK_ACCOUNTS[1]).balance(*MOCK_1_ETH);
}
accs[2].address(MOCK_ACCOUNTS[2]).balance(*MOCK_1_ETH);
},
|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(account.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::EXTCODESIZE))
.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: account.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: account.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(),
&(if account_exists {
let code_hash = Word::from(keccak256(account.code.clone()));
AccountOp {
address: account.address,
field: AccountField::CodeHash,
value: code_hash,
value_prev: code_hash,
}
} else {
AccountOp {
address: account.address,
field: AccountField::NonExisting,
value: Word::zero(),
value_prev: Word::zero(),
}
})
);

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: (if account_exists {
account.code.len()
} else {
0
})
.into(),
}
);
}
}
10 changes: 10 additions & 0 deletions eth-types/src/geth_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ pub struct Account {
pub storage: HashMap<Word, Word>,
}

impl Account {
/// Return if account is empty or not.
pub fn is_empty(&self) -> bool {
self.nonce.is_zero()
&& self.balance.is_zero()
&& self.code.is_empty()
&& self.storage.is_empty()
}
}

fn serde_account_storage<S: Serializer>(
to_serialize: &HashMap<Word, Word>,
serializer: S,
Expand Down
12 changes: 11 additions & 1 deletion mock/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Mock types and functions to generate GethData used for tests
use eth_types::{address, Address, Word};
use eth_types::{address, Address, Bytes, Word};
use ethers_signers::LocalWallet;
use lazy_static::lazy_static;
use rand::SeedableRng;
Expand All @@ -16,6 +16,8 @@ pub use test_ctx::TestContext;
pub use transaction::{AddrOrWallet, MockTransaction, CORRECT_MOCK_TXS};

lazy_static! {
/// Mock 1 ETH
pub static ref MOCK_1_ETH: Word = eth(1);
/// Mock coinbase value
pub static ref MOCK_COINBASE: Address =
address!("0x00000000000000000000000000000000c014ba5e");
Expand All @@ -37,6 +39,14 @@ lazy_static! {
address!("0x000000000000000000000000000000000cafe444"),
address!("0x000000000000000000000000000000000cafe555"),
];
/// Mock EVM codes to use for test cases.
pub static ref MOCK_CODES: Vec<Bytes> = vec![
Bytes::from([0x60, 0x10, 0x00]), // PUSH1(0x10), STOP
Bytes::from([0x60, 0x01, 0x60, 0x02, 0x01, 0x00]), // PUSH1(1), PUSH1(2), ADD, STOP
Bytes::from([0x60, 0x01, 0x60, 0x02, 0x02, 0x00]), // PUSH1(1), PUSH1(2), MUL, STOP
Bytes::from([0x60, 0x02, 0x60, 0x01, 0x03, 0x00]), // PUSH1(2), PUSH1(1), SUB, STOP
Bytes::from([0x60, 0x09, 0x60, 0x03, 0x04, 0x00]), // PUSH1(9), PUSH1(3), DIV, STOP
];
/// Mock wallets used to generate correctly signed and hashed Transactions.
pub static ref MOCK_WALLETS: Vec<LocalWallet> = {
let mut rng = ChaCha20Rng::seed_from_u64(2u64);
Expand Down
8 changes: 5 additions & 3 deletions zkevm-circuits/src/evm_circuit/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ mod error_oog_static_memory;
mod error_stack;
mod exp;
mod extcodehash;
mod extcodesize;
mod gas;
mod gasprice;
mod is_zero;
Expand Down Expand Up @@ -118,6 +119,7 @@ use error_oog_constant::ErrorOOGConstantGadget;
use error_stack::ErrorStackGadget;
use exp::ExponentiationGadget;
use extcodehash::ExtcodehashGadget;
use extcodesize::ExtcodesizeGadget;
use gas::GasGadget;
use gasprice::GasPriceGadget;
use is_zero::IsZeroGadget;
Expand Down Expand Up @@ -210,6 +212,7 @@ pub(crate) struct ExecutionConfig<F> {
dup_gadget: DupGadget<F>,
exp_gadget: ExponentiationGadget<F>,
extcodehash_gadget: ExtcodehashGadget<F>,
extcodesize_gadget: ExtcodesizeGadget<F>,
gas_gadget: GasGadget<F>,
gasprice_gadget: GasPriceGadget<F>,
iszero_gadget: IsZeroGadget<F>,
Expand All @@ -232,7 +235,6 @@ pub(crate) struct ExecutionConfig<F> {
sha3_gadget: Sha3Gadget<F>,
shl_shr_gadget: ShlShrGadget<F>,
sar_gadget: DummyGadget<F, 2, 1, { ExecutionState::SAR }>,
extcodesize_gadget: DummyGadget<F, 1, 1, { ExecutionState::EXTCODESIZE }>,
extcodecopy_gadget: DummyGadget<F, 4, 0, { ExecutionState::EXTCODECOPY }>,
returndatasize_gadget: ReturnDataSizeGadget<F>,
returndatacopy_gadget: ReturnDataCopyGadget<F>,
Expand Down Expand Up @@ -444,6 +446,7 @@ impl<F: Field> ExecutionConfig<F> {
comparator_gadget: configure_gadget!(),
dup_gadget: configure_gadget!(),
extcodehash_gadget: configure_gadget!(),
extcodesize_gadget: configure_gadget!(),
gas_gadget: configure_gadget!(),
gasprice_gadget: configure_gadget!(),
iszero_gadget: configure_gadget!(),
Expand All @@ -469,7 +472,6 @@ impl<F: Field> ExecutionConfig<F> {
blockhash_gadget: configure_gadget!(),
exp_gadget: configure_gadget!(),
sar_gadget: configure_gadget!(),
extcodesize_gadget: configure_gadget!(),
extcodecopy_gadget: configure_gadget!(),
returndatasize_gadget: configure_gadget!(),
returndatacopy_gadget: configure_gadget!(),
Expand Down Expand Up @@ -990,6 +992,7 @@ impl<F: Field> ExecutionConfig<F> {
ExecutionState::DUP => assign_exec_step!(self.dup_gadget),
ExecutionState::EXP => assign_exec_step!(self.exp_gadget),
ExecutionState::EXTCODEHASH => assign_exec_step!(self.extcodehash_gadget),
ExecutionState::EXTCODESIZE => assign_exec_step!(self.extcodesize_gadget),
ExecutionState::GAS => assign_exec_step!(self.gas_gadget),
ExecutionState::GASPRICE => assign_exec_step!(self.gasprice_gadget),
ExecutionState::ISZERO => assign_exec_step!(self.iszero_gadget),
Expand Down Expand Up @@ -1018,7 +1021,6 @@ impl<F: Field> ExecutionConfig<F> {
ExecutionState::SELFBALANCE => assign_exec_step!(self.selfbalance_gadget),
// dummy gadgets
ExecutionState::SAR => assign_exec_step!(self.sar_gadget),
ExecutionState::EXTCODESIZE => assign_exec_step!(self.extcodesize_gadget),
ExecutionState::EXTCODECOPY => assign_exec_step!(self.extcodecopy_gadget),
ExecutionState::CREATE => assign_exec_step!(self.create_gadget),
ExecutionState::CREATE2 => assign_exec_step!(self.create2_gadget),
Expand Down
Loading

0 comments on commit a606660

Please sign in to comment.