diff --git a/bus-mapping/Cargo.toml b/bus-mapping/Cargo.toml index 3deaf80b041..04091862500 100644 --- a/bus-mapping/Cargo.toml +++ b/bus-mapping/Cargo.toml @@ -21,3 +21,4 @@ ethers-providers = "0.5.5" [dev-dependencies] url = "2.2.2" tokio = { version = "1.13", features = ["macros"] } +pretty_assertions = "1.0.0" diff --git a/bus-mapping/src/evm/memory.rs b/bus-mapping/src/evm/memory.rs index 76755fda024..0019c375718 100644 --- a/bus-mapping/src/evm/memory.rs +++ b/bus-mapping/src/evm/memory.rs @@ -58,6 +58,11 @@ impl MemoryAddress { .copy_from_slice(&bytes.as_ref()[..core::mem::size_of::()]); Ok(MemoryAddress::from(usize::from_be_bytes(array))) } + + /// Apply a function to the contained value. + pub fn map usize>(&self, f: F) -> Self { + Self(f(self.0)) + } } impl TryFrom for MemoryAddress { diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index d12f57195de..de49360da06 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -1,21 +1,31 @@ //! Definition of each opcode of the EVM. -mod add; +mod dup; pub mod ids; +mod jumpdest; mod mload; +mod mstore; +mod pc; mod push; mod sload; +mod stackonlyop; mod stop; +mod swap; use crate::circuit_input_builder::CircuitInputStateRef; use crate::eth_types::GethExecStep; use crate::Error; use core::fmt::Debug; use ids::OpcodeId; -use self::push::Push1; -use add::Add; +use self::push::Push; +use dup::Dup; +use jumpdest::Jumpdest; use mload::Mload; +use mstore::Mstore; +use pc::Pc; use sload::Sload; +use stackonlyop::StackOnlyOpcode; use stop::Stop; +use swap::Swap; /// Generic opcode trait which defines the logic of the /// [`Operation`](crate::operation::Operation) that should be generated for an @@ -40,11 +50,148 @@ type FnGenAssociatedOps = fn( impl OpcodeId { fn fn_gen_associated_ops(&self) -> FnGenAssociatedOps { match *self { + OpcodeId::STOP => Stop::gen_associated_ops, + OpcodeId::ADD => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::MUL => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::SUB => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::DIV => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::SDIV => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::MOD => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::SMOD => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::ADDMOD => StackOnlyOpcode::<3>::gen_associated_ops, + OpcodeId::MULMOD => StackOnlyOpcode::<3>::gen_associated_ops, + OpcodeId::EXP => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::SIGNEXTEND => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::LT => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::GT => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::SLT => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::SGT => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::EQ => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::ISZERO => StackOnlyOpcode::<1>::gen_associated_ops, + OpcodeId::AND => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::OR => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::XOR => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::NOT => StackOnlyOpcode::<1>::gen_associated_ops, + OpcodeId::BYTE => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::SHL => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::SHR => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::SAR => StackOnlyOpcode::<2>::gen_associated_ops, + // OpcodeId::SHA3 => {}, + // OpcodeId::ADDRESS => {}, + // OpcodeId::BALANCE => {}, + // OpcodeId::ORIGIN => {}, + // OpcodeId::CALLER => {}, + // OpcodeId::CALLVALUE => {}, + // OpcodeId::CALLDATALOAD => {}, + // OpcodeId::CALLDATASIZE => {}, + // OpcodeId::CALLDATACOPY => {}, + // OpcodeId::CODESIZE => {}, + // OpcodeId::CODECOPY => {}, + // OpcodeId::GASPRICE => {}, + // OpcodeId::EXTCODESIZE => {}, + // OpcodeId::EXTCODECOPY => {}, + // OpcodeId::RETURNDATASIZE => {}, + // OpcodeId::RETURNDATACOPY => {}, + // OpcodeId::EXTCODEHASH => {}, + // OpcodeId::BLOCKHASH => {}, + // OpcodeId::COINBASE => {}, + // OpcodeId::TIMESTAMP => {}, + // OpcodeId::NUMBER => {}, + // OpcodeId::DIFFICULTY => {}, + // OpcodeId::GASLIMIT => {}, + // OpcodeId::CHAINID => {}, + // OpcodeId::SELFBALANCE => {}, + // OpcodeId::BASEFEE => {}, + // OpcodeId::POP => {}, OpcodeId::MLOAD => Mload::gen_associated_ops, - OpcodeId::PUSH1 => Push1::gen_associated_ops, + OpcodeId::MSTORE => Mstore::gen_associated_ops, + // OpcodeId::MSTORE8 => {} OpcodeId::SLOAD => Sload::gen_associated_ops, - OpcodeId::STOP => Stop::gen_associated_ops, - OpcodeId::ADD => Add::gen_associated_ops, + // OpcodeId::SSTORE => {}, + // OpcodeId::JUMP => {}, + // OpcodeId::JUMPI => {}, + OpcodeId::PC => Pc::gen_associated_ops, + // OpcodeId::MSIZE => {}, + // OpcodeId::GAS => {}, + OpcodeId::JUMPDEST => Jumpdest::gen_associated_ops, + OpcodeId::PUSH1 => Push::<1>::gen_associated_ops, + OpcodeId::PUSH2 => Push::<2>::gen_associated_ops, + OpcodeId::PUSH3 => Push::<3>::gen_associated_ops, + OpcodeId::PUSH4 => Push::<4>::gen_associated_ops, + OpcodeId::PUSH5 => Push::<5>::gen_associated_ops, + OpcodeId::PUSH6 => Push::<6>::gen_associated_ops, + OpcodeId::PUSH7 => Push::<7>::gen_associated_ops, + OpcodeId::PUSH8 => Push::<8>::gen_associated_ops, + OpcodeId::PUSH9 => Push::<9>::gen_associated_ops, + OpcodeId::PUSH10 => Push::<10>::gen_associated_ops, + OpcodeId::PUSH11 => Push::<11>::gen_associated_ops, + OpcodeId::PUSH12 => Push::<12>::gen_associated_ops, + OpcodeId::PUSH13 => Push::<13>::gen_associated_ops, + OpcodeId::PUSH14 => Push::<14>::gen_associated_ops, + OpcodeId::PUSH15 => Push::<15>::gen_associated_ops, + OpcodeId::PUSH16 => Push::<16>::gen_associated_ops, + OpcodeId::PUSH17 => Push::<17>::gen_associated_ops, + OpcodeId::PUSH18 => Push::<18>::gen_associated_ops, + OpcodeId::PUSH19 => Push::<19>::gen_associated_ops, + OpcodeId::PUSH20 => Push::<20>::gen_associated_ops, + OpcodeId::PUSH21 => Push::<21>::gen_associated_ops, + OpcodeId::PUSH22 => Push::<22>::gen_associated_ops, + OpcodeId::PUSH23 => Push::<23>::gen_associated_ops, + OpcodeId::PUSH24 => Push::<24>::gen_associated_ops, + OpcodeId::PUSH25 => Push::<25>::gen_associated_ops, + OpcodeId::PUSH26 => Push::<26>::gen_associated_ops, + OpcodeId::PUSH27 => Push::<27>::gen_associated_ops, + OpcodeId::PUSH28 => Push::<28>::gen_associated_ops, + OpcodeId::PUSH29 => Push::<29>::gen_associated_ops, + OpcodeId::PUSH30 => Push::<30>::gen_associated_ops, + OpcodeId::PUSH31 => Push::<31>::gen_associated_ops, + OpcodeId::PUSH32 => Push::<32>::gen_associated_ops, + OpcodeId::DUP1 => Dup::<1>::gen_associated_ops, + OpcodeId::DUP2 => Dup::<2>::gen_associated_ops, + OpcodeId::DUP3 => Dup::<3>::gen_associated_ops, + OpcodeId::DUP4 => Dup::<4>::gen_associated_ops, + OpcodeId::DUP5 => Dup::<5>::gen_associated_ops, + OpcodeId::DUP6 => Dup::<6>::gen_associated_ops, + OpcodeId::DUP7 => Dup::<7>::gen_associated_ops, + OpcodeId::DUP8 => Dup::<8>::gen_associated_ops, + OpcodeId::DUP9 => Dup::<9>::gen_associated_ops, + OpcodeId::DUP10 => Dup::<10>::gen_associated_ops, + OpcodeId::DUP11 => Dup::<11>::gen_associated_ops, + OpcodeId::DUP12 => Dup::<12>::gen_associated_ops, + OpcodeId::DUP13 => Dup::<13>::gen_associated_ops, + OpcodeId::DUP14 => Dup::<14>::gen_associated_ops, + OpcodeId::DUP15 => Dup::<15>::gen_associated_ops, + OpcodeId::DUP16 => Dup::<16>::gen_associated_ops, + OpcodeId::SWAP1 => Swap::<1>::gen_associated_ops, + OpcodeId::SWAP2 => Swap::<2>::gen_associated_ops, + OpcodeId::SWAP3 => Swap::<3>::gen_associated_ops, + OpcodeId::SWAP4 => Swap::<4>::gen_associated_ops, + OpcodeId::SWAP5 => Swap::<5>::gen_associated_ops, + OpcodeId::SWAP6 => Swap::<6>::gen_associated_ops, + OpcodeId::SWAP7 => Swap::<7>::gen_associated_ops, + OpcodeId::SWAP8 => Swap::<8>::gen_associated_ops, + OpcodeId::SWAP9 => Swap::<9>::gen_associated_ops, + OpcodeId::SWAP10 => Swap::<10>::gen_associated_ops, + OpcodeId::SWAP11 => Swap::<11>::gen_associated_ops, + OpcodeId::SWAP12 => Swap::<12>::gen_associated_ops, + OpcodeId::SWAP13 => Swap::<13>::gen_associated_ops, + OpcodeId::SWAP14 => Swap::<14>::gen_associated_ops, + OpcodeId::SWAP15 => Swap::<15>::gen_associated_ops, + OpcodeId::SWAP16 => Swap::<16>::gen_associated_ops, + // OpcodeId::LOG0 => {}, + // OpcodeId::LOG1 => {}, + // OpcodeId::LOG2 => {}, + // OpcodeId::LOG3 => {}, + // OpcodeId::LOG4 => {}, + // OpcodeId::CREATE => {}, + // OpcodeId::CALL => {}, + // OpcodeId::CALLCODE => {}, + // OpcodeId::RETURN => {}, + // OpcodeId::DELEGATECALL => {}, + // OpcodeId::CREATE2 => {}, + // OpcodeId::STATICCALL => {}, + // OpcodeId::REVERT => {}, + // OpcodeId::SELFDESTRUCT => {}, _ => unimplemented!(), } } diff --git a/bus-mapping/src/evm/opcodes/dup.rs b/bus-mapping/src/evm/opcodes/dup.rs new file mode 100644 index 00000000000..55db7a294c3 --- /dev/null +++ b/bus-mapping/src/evm/opcodes/dup.rs @@ -0,0 +1,119 @@ +use super::Opcode; +use crate::circuit_input_builder::CircuitInputStateRef; +use crate::eth_types::GethExecStep; +use crate::{ + operation::{StackOp, RW}, + Error, +}; + +/// Placeholder structure used to implement [`Opcode`] trait over it corresponding to the +/// `OpcodeId::DUP*` `OpcodeId`. +#[derive(Debug, Copy, Clone)] +pub(crate) struct Dup; + +impl Opcode for Dup { + fn gen_associated_ops( + state: &mut CircuitInputStateRef, + steps: &[GethExecStep], + ) -> Result<(), Error> { + let step = &steps[0]; + + let stack_value_read = step.stack.nth_last(N - 1)?; + let stack_position = step.stack.nth_last_filled(N - 1); + state.push_op(StackOp::new(RW::READ, stack_position, stack_value_read)); + + state.push_op(StackOp::new( + RW::WRITE, + step.stack.last_filled().map(|a| a - 1), + stack_value_read, + )); + + Ok(()) + } +} + +#[cfg(test)] +mod dup_tests { + use super::*; + use crate::{ + bytecode, + circuit_input_builder::{ + CircuitInputBuilder, ExecStep, Transaction, TransactionContext, + }, + evm::StackAddress, + mock, word, + }; + use pretty_assertions::assert_eq; + + #[test] + fn dup_opcode_impl() -> Result<(), Error> { + let code = bytecode! { + PUSH1(0x1) + PUSH1(0x2) + PUSH1(0x3) + #[start] // [1,2,3] + DUP1 // [1,2,3,3] + DUP3 // [1,2,3,3,2] + DUP5 // [1,2,3,3,2,1] + STOP + }; + + // Get the execution steps from the external tracer + let block = + mock::BlockData::new_single_tx_trace_code_at_start(&code).unwrap(); + + let mut builder = CircuitInputBuilder::new( + block.eth_block.clone(), + block.block_ctants.clone(), + ); + builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); + + let mut test_builder = CircuitInputBuilder::new( + block.eth_block, + block.block_ctants.clone(), + ); + let mut tx = Transaction::new(&block.eth_tx); + let mut tx_ctx = TransactionContext::new(&block.eth_tx); + + // Generate steps corresponding to DUP1, DUP3, DUP5 + for (i, word) in [word!("0x3"), word!("0x2"), word!("0x1")] + .iter() + .enumerate() + { + let mut step = ExecStep::new( + &block.geth_trace.struct_logs[i], + test_builder.block_ctx.gc, + ); + let mut state_ref = + test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); + + state_ref.push_op(StackOp::new( + RW::READ, + StackAddress(1024 - 3 + i), + *word, + )); + + state_ref.push_op(StackOp::new( + RW::WRITE, + StackAddress(1024 - 4 - i), + *word, + )); + + tx.steps_mut().push(step); + } + + test_builder.block.txs_mut().push(tx); + + // Compare first 3 steps bus mapping instance + for i in 0..3 { + assert_eq!( + builder.block.txs()[0].steps()[i].bus_mapping_instance, + test_builder.block.txs()[0].steps()[i].bus_mapping_instance + ); + } + // Compare containers + assert_eq!(builder.block.container, test_builder.block.container); + + Ok(()) + } +} diff --git a/bus-mapping/src/evm/opcodes/jumpdest.rs b/bus-mapping/src/evm/opcodes/jumpdest.rs new file mode 100644 index 00000000000..470822b251a --- /dev/null +++ b/bus-mapping/src/evm/opcodes/jumpdest.rs @@ -0,0 +1,19 @@ +use super::Opcode; +use crate::circuit_input_builder::CircuitInputStateRef; +use crate::eth_types::GethExecStep; +use crate::Error; + +/// Placeholder structure used to implement [`Opcode`] trait over it corresponding to the +/// [`OpcodeId::JUMPDEST`](crate::evm::OpcodeId::JUMPDEST) `OpcodeId`. +#[derive(Debug, Copy, Clone)] +pub(crate) struct Jumpdest; + +impl Opcode for Jumpdest { + fn gen_associated_ops( + _state: &mut CircuitInputStateRef, + _steps: &[GethExecStep], + ) -> Result<(), Error> { + // Jumpdest does not generate any operations + Ok(()) + } +} diff --git a/bus-mapping/src/evm/opcodes/mload.rs b/bus-mapping/src/evm/opcodes/mload.rs index ad45f73d137..7d30494f18a 100644 --- a/bus-mapping/src/evm/opcodes/mload.rs +++ b/bus-mapping/src/evm/opcodes/mload.rs @@ -16,7 +16,6 @@ use core::convert::TryInto; pub(crate) struct Mload; impl Opcode for Mload { - #[allow(unused_variables)] fn gen_associated_ops( state: &mut CircuitInputStateRef, steps: &[GethExecStep], @@ -67,6 +66,7 @@ mod mload_tests { evm::StackAddress, mock, }; + use pretty_assertions::assert_eq; #[test] fn mload_opcode_impl() -> Result<(), Error> { diff --git a/bus-mapping/src/evm/opcodes/mstore.rs b/bus-mapping/src/evm/opcodes/mstore.rs new file mode 100644 index 00000000000..81aeb86530f --- /dev/null +++ b/bus-mapping/src/evm/opcodes/mstore.rs @@ -0,0 +1,132 @@ +use super::Opcode; +use crate::circuit_input_builder::CircuitInputStateRef; +use crate::eth_types::{GethExecStep, ToBigEndian}; +use crate::{ + evm::MemoryAddress, + operation::{MemoryOp, StackOp, RW}, + Error, +}; +use core::convert::TryInto; + +/// Placeholder structure used to implement [`Opcode`] trait over it corresponding to the +/// [`OpcodeId::MSTORE`](crate::evm::OpcodeId::MSTORE) `OpcodeId`. +#[derive(Debug, Copy, Clone)] +pub(crate) struct Mstore; + +impl Opcode for Mstore { + fn gen_associated_ops( + state: &mut CircuitInputStateRef, + steps: &[GethExecStep], + ) -> Result<(), Error> { + let step = &steps[0]; + // First stack read (offset) + let offset = step.stack.nth_last(0)?; + let offset_pos = step.stack.nth_last_filled(0); + state.push_op(StackOp::new(RW::READ, offset_pos, offset)); + + // Second stack read (value) + let value = step.stack.nth_last(1)?; + let value_pos = step.stack.nth_last_filled(1); + state.push_op(StackOp::new(RW::READ, value_pos, value)); + + // First mem write -> 32 MemoryOp generated. + let offset_addr: MemoryAddress = offset.try_into()?; + let bytes = value.to_be_bytes(); + for (i, byte) in bytes.iter().enumerate() { + state.push_op(MemoryOp::new( + RW::WRITE, + offset_addr.map(|a| a + i), + *byte, + )); + } + + Ok(()) + } +} + +#[cfg(test)] +mod mstore_tests { + use super::*; + use crate::{ + bytecode, + circuit_input_builder::{ + CircuitInputBuilder, ExecStep, Transaction, TransactionContext, + }, + eth_types::Word, + evm::{MemoryAddress, StackAddress}, + mock, + }; + use pretty_assertions::assert_eq; + + #[test] + fn mstore_opcode_impl() -> Result<(), Error> { + let code = bytecode! { + .setup_state() + PUSH2(0x1234) + PUSH2(0x100) + #[start] + MSTORE + STOP + }; + + // Get the execution steps from the external tracer + let block = + mock::BlockData::new_single_tx_trace_code_at_start(&code).unwrap(); + + let mut builder = CircuitInputBuilder::new( + block.eth_block.clone(), + block.block_ctants.clone(), + ); + builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); + + let mut test_builder = CircuitInputBuilder::new( + block.eth_block, + block.block_ctants.clone(), + ); + let mut tx = Transaction::new(&block.eth_tx); + let mut tx_ctx = TransactionContext::new(&block.eth_tx); + + // Generate step corresponding to MSTORE + let mut step = ExecStep::new( + &block.geth_trace.struct_logs[0], + test_builder.block_ctx.gc, + ); + let mut state_ref = + test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); + + // Add StackOps associated to the 0x100, 0x1234 reads starting from last stack position. + state_ref.push_op(StackOp::new( + RW::READ, + StackAddress::from(1022), + Word::from(0x100), + )); + state_ref.push_op(StackOp::new( + RW::READ, + StackAddress::from(1023), + Word::from(0x1234), + )); + + // Add the 32 MemoryOp generated from the Memory write at addr 0x100..0x120 for each byte. + for (i, byte) in Word::from(0x1234).to_be_bytes().iter().enumerate() { + state_ref.push_op(MemoryOp::new( + RW::WRITE, + MemoryAddress(0x100 + i), + *byte, + )); + } + + tx.steps_mut().push(step); + test_builder.block.txs_mut().push(tx); + + // Compare first step bus mapping instance + assert_eq!( + builder.block.txs()[0].steps()[0].bus_mapping_instance, + test_builder.block.txs()[0].steps()[0].bus_mapping_instance, + ); + + // Compare containers + assert_eq!(builder.block.container, test_builder.block.container); + + Ok(()) + } +} diff --git a/bus-mapping/src/evm/opcodes/add.rs b/bus-mapping/src/evm/opcodes/pc.rs similarity index 51% rename from bus-mapping/src/evm/opcodes/add.rs rename to bus-mapping/src/evm/opcodes/pc.rs index 23172b79dbd..2618095d84e 100644 --- a/bus-mapping/src/evm/opcodes/add.rs +++ b/bus-mapping/src/evm/opcodes/pc.rs @@ -6,51 +6,23 @@ use crate::{ Error, }; +/// Placeholder structure used to implement [`Opcode`] trait over it corresponding to the +/// [`OpcodeId::PC`](crate::evm::OpcodeId::PC) `OpcodeId`. #[derive(Debug, Copy, Clone)] -pub(crate) struct Add; +pub(crate) struct Pc; -impl Opcode for Add { +impl Opcode for Pc { fn gen_associated_ops( state: &mut CircuitInputStateRef, steps: &[GethExecStep], ) -> Result<(), Error> { let step = &steps[0]; - // - // First stack read - // - let stack_last_value_read = step.stack.last()?; - let stack_last_position = step.stack.last_filled(); - - // Manage first stack read at latest stack position - state.push_op(StackOp::new( - RW::READ, - stack_last_position, - stack_last_value_read, - )); - - // - // Second stack read - // - let stack_second_last_value_read = step.stack.nth_last(1)?; - let stack_second_last_position = step.stack.nth_last_filled(1); - - // Manage second stack read at second latest stack position - state.push_op(StackOp::new( - RW::READ, - stack_second_last_position, - stack_second_last_value_read, - )); - - // - // First plus second stack value write - // - let added_value = steps[1].stack.last()?; - - // Manage second stack read at second latest stack position + // Get value result from next step and do stack write + let value = steps[1].stack.last()?; state.push_op(StackOp::new( RW::WRITE, - stack_second_last_position, - added_value, + step.stack.last_filled().map(|a| a - 1), + value, )); Ok(()) @@ -58,7 +30,7 @@ impl Opcode for Add { } #[cfg(test)] -mod add_tests { +mod pc_tests { use super::*; use crate::{ bytecode, @@ -69,14 +41,15 @@ mod add_tests { evm::StackAddress, mock, }; + use pretty_assertions::assert_eq; #[test] - fn add_opcode_impl() -> Result<(), Error> { + fn pc_opcode_impl() -> Result<(), Error> { let code = bytecode! { - PUSH1(0x80u64) - PUSH1(0x80u64) + PUSH1(0x1) + PUSH1(0x2) #[start] - ADD + PC STOP }; @@ -97,7 +70,7 @@ mod add_tests { let mut tx = Transaction::new(&block.eth_tx); let mut tx_ctx = TransactionContext::new(&block.eth_tx); - // Generate step corresponding to ADD + // Generate step corresponding to MLOAD let mut step = ExecStep::new( &block.geth_trace.struct_logs[0], test_builder.block_ctx.gc, @@ -105,31 +78,11 @@ mod add_tests { let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - let last_stack_pointer = StackAddress(1022); - let second_last_stack_pointer = StackAddress(1023); - let stack_value_a = Word::from(0x80); - let stack_value_b = Word::from(0x80); - let sum = Word::from(0x100); - - // Manage first stack read at latest stack position - state_ref.push_op(StackOp::new( - RW::READ, - last_stack_pointer, - stack_value_a, - )); - - // Manage second stack read at second latest stack position - state_ref.push_op(StackOp::new( - RW::READ, - second_last_stack_pointer, - stack_value_b, - )); - - // Add StackOp associated to the 0x80 push at the latest Stack pos. + // Add the last Stack write state_ref.push_op(StackOp::new( RW::WRITE, - second_last_stack_pointer, - sum, + StackAddress::from(1024 - 3), + Word::from(0x4), )); tx.steps_mut().push(step); diff --git a/bus-mapping/src/evm/opcodes/push.rs b/bus-mapping/src/evm/opcodes/push.rs index bf309cfff93..5e246d9d525 100644 --- a/bus-mapping/src/evm/opcodes/push.rs +++ b/bus-mapping/src/evm/opcodes/push.rs @@ -1,28 +1,28 @@ +use super::Opcode; use crate::circuit_input_builder::CircuitInputStateRef; use crate::eth_types::GethExecStep; -// Port this to a macro if possible to avoid defining all the PushN -use super::Opcode; use crate::{ operation::{StackOp, RW}, Error, }; /// Placeholder structure used to implement [`Opcode`] trait over it corresponding to the -/// [`OpcodeId::PUSH1`](crate::evm::OpcodeId::PUSH1) `OpcodeId`. +/// `OpcodeId::PUSH*` `OpcodeId`. /// This is responsible of generating all of the associated [`StackOp`]s and place them /// inside the trace's [`OperationContainer`](crate::operation::OperationContainer). #[derive(Debug, Copy, Clone)] -pub(crate) struct Push1; +pub(crate) struct Push; -impl Opcode for Push1 { +impl Opcode for Push { fn gen_associated_ops( state: &mut CircuitInputStateRef, steps: &[GethExecStep], ) -> Result<(), Error> { + let step = &steps[0]; state.push_op(StackOp::new( RW::WRITE, // Get the value and addr from the next step. Being the last position filled with an element in the stack - steps[1].stack.last_filled(), + step.stack.last_filled().map(|a| a - 1), steps[1].stack.last()?, )); @@ -38,16 +38,18 @@ mod push_tests { circuit_input_builder::{ CircuitInputBuilder, ExecStep, Transaction, TransactionContext, }, - eth_types::Word, evm::StackAddress, - mock, + mock, word, }; + use pretty_assertions::assert_eq; #[test] - fn push1_opcode_impl() -> Result<(), Error> { + fn push_opcode_impl() -> Result<(), Error> { let code = bytecode! { #[start] - PUSH1(0x80u64) + PUSH1(0x80) + PUSH2(0x1234) + PUSH16(word!("0x00112233445566778899aabbccddeeff")) STOP }; @@ -68,28 +70,41 @@ mod push_tests { let mut tx = Transaction::new(&block.eth_tx); let mut tx_ctx = TransactionContext::new(&block.eth_tx); - // Generate step corresponding to PUSH1 80 - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - test_builder.block_ctx.gc, - ); - let mut state_ref = - test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); + // Generate steps corresponding to PUSH1 80, PUSH2 1234, + // PUSH16 0x00112233445566778899aabbccddeeff + for (i, word) in [ + word!("0x80"), + word!("0x1234"), + word!("0x00112233445566778899aabbccddeeff"), + ] + .iter() + .enumerate() + { + let mut step = ExecStep::new( + &block.geth_trace.struct_logs[i], + test_builder.block_ctx.gc, + ); + let mut state_ref = + test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); + + // Add StackOp associated to the push at the latest Stack pos. + state_ref.push_op(StackOp::new( + RW::WRITE, + StackAddress::from(1023 - i), + *word, + )); + tx.steps_mut().push(step); + } - // Add StackOp associated to the 0x80 push at the latest Stack pos. - state_ref.push_op(StackOp::new( - RW::WRITE, - StackAddress::from(1023), - Word::from(0x80), - )); - tx.steps_mut().push(step); test_builder.block.txs_mut().push(tx); - // Compare first step bus mapping instance - assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance - ); + // Compare first 3 steps bus mapping instance + for i in 0..3 { + assert_eq!( + builder.block.txs()[0].steps()[i].bus_mapping_instance, + test_builder.block.txs()[0].steps()[i].bus_mapping_instance + ); + } // Compare containers assert_eq!(builder.block.container, test_builder.block.container); diff --git a/bus-mapping/src/evm/opcodes/sload.rs b/bus-mapping/src/evm/opcodes/sload.rs index 98fbb8e95b7..bb392249737 100644 --- a/bus-mapping/src/evm/opcodes/sload.rs +++ b/bus-mapping/src/evm/opcodes/sload.rs @@ -13,13 +13,11 @@ use crate::{ pub(crate) struct Sload; impl Opcode for Sload { - #[allow(unused_variables)] fn gen_associated_ops( state: &mut CircuitInputStateRef, steps: &[GethExecStep], ) -> Result<(), Error> { let step = &steps[0]; - let gc_start = state.block_ctx.gc; // First stack read let stack_value_read = step.stack.last()?; @@ -61,6 +59,7 @@ mod sload_tests { evm::StackAddress, mock, }; + use pretty_assertions::assert_eq; #[test] fn sload_opcode_impl() -> Result<(), Error> { diff --git a/bus-mapping/src/evm/opcodes/stackonlyop.rs b/bus-mapping/src/evm/opcodes/stackonlyop.rs new file mode 100644 index 00000000000..8fbd07d7f1a --- /dev/null +++ b/bus-mapping/src/evm/opcodes/stackonlyop.rs @@ -0,0 +1,272 @@ +use super::Opcode; +use crate::circuit_input_builder::CircuitInputStateRef; +use crate::eth_types::GethExecStep; +use crate::{ + operation::{StackOp, RW}, + Error, +}; + +/// Placeholder structure used to implement [`Opcode`] trait over it corresponding to all the +/// Stack only operations: take N words and return one. The following cases exist in the EVM: +/// - N = 1: UnaryOpcode +/// - N = 2: BinaryOpcode +/// - N = 3: TernaryOpcode +#[derive(Debug, Copy, Clone)] +pub(crate) struct StackOnlyOpcode; + +impl Opcode for StackOnlyOpcode { + fn gen_associated_ops( + state: &mut CircuitInputStateRef, + steps: &[GethExecStep], + ) -> Result<(), Error> { + let step = &steps[0]; + // N stack reads + for i in 0..N { + state.push_op(StackOp::new( + RW::READ, + step.stack.nth_last_filled(i), + step.stack.nth_last(i)?, + )); + } + + // Get operator result from next step and do stack write + let result_value = steps[1].stack.last()?; + state.push_op(StackOp::new( + RW::WRITE, + step.stack.nth_last_filled(N - 1), + result_value, + )); + + Ok(()) + } +} + +#[cfg(test)] +mod stackonlyop_tests { + use super::*; + use crate::{ + bytecode, + circuit_input_builder::{ + CircuitInputBuilder, ExecStep, Transaction, TransactionContext, + }, + eth_types::Word, + evm::StackAddress, + mock, word, + }; + use pretty_assertions::assert_eq; + + #[test] + fn not_opcode_impl() -> Result<(), Error> { + let code = bytecode! { + PUSH32(word!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")) + #[start] + NOT + STOP + }; + + // Get the execution steps from the external tracer + let block = + mock::BlockData::new_single_tx_trace_code_at_start(&code).unwrap(); + + let mut builder = CircuitInputBuilder::new( + block.eth_block.clone(), + block.block_ctants.clone(), + ); + builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); + + let mut test_builder = CircuitInputBuilder::new( + block.eth_block, + block.block_ctants.clone(), + ); + let mut tx = Transaction::new(&block.eth_tx); + let mut tx_ctx = TransactionContext::new(&block.eth_tx); + + // Generate step corresponding to NOT + let mut step = ExecStep::new( + &block.geth_trace.struct_logs[0], + test_builder.block_ctx.gc, + ); + let mut state_ref = + test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); + + // Read a + state_ref.push_op(StackOp::new( + RW::READ, + StackAddress(1024 - 1), + word!("0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), + )); + + // Write ~a + state_ref.push_op(StackOp::new( + RW::WRITE, + StackAddress(1024 - 1), + word!("0xfffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0"), + )); + + tx.steps_mut().push(step); + test_builder.block.txs_mut().push(tx); + + // Compare first step bus mapping instance + assert_eq!( + builder.block.txs()[0].steps()[0].bus_mapping_instance, + test_builder.block.txs()[0].steps()[0].bus_mapping_instance, + ); + + // Compare containers + assert_eq!(builder.block.container, test_builder.block.container); + + Ok(()) + } + + #[test] + fn add_opcode_impl() -> Result<(), Error> { + let code = bytecode! { + PUSH1(0x80u64) + PUSH1(0x80u64) + #[start] + ADD + STOP + }; + + // Get the execution steps from the external tracer + let block = + mock::BlockData::new_single_tx_trace_code_at_start(&code).unwrap(); + + let mut builder = CircuitInputBuilder::new( + block.eth_block.clone(), + block.block_ctants.clone(), + ); + builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); + + let mut test_builder = CircuitInputBuilder::new( + block.eth_block, + block.block_ctants.clone(), + ); + let mut tx = Transaction::new(&block.eth_tx); + let mut tx_ctx = TransactionContext::new(&block.eth_tx); + + // Generate step corresponding to ADD + let mut step = ExecStep::new( + &block.geth_trace.struct_logs[0], + test_builder.block_ctx.gc, + ); + let mut state_ref = + test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); + + let last_stack_pointer = StackAddress(1022); + let second_last_stack_pointer = StackAddress(1023); + let stack_value_a = Word::from(0x80); + let stack_value_b = Word::from(0x80); + let sum = Word::from(0x100); + + // Manage first stack read at latest stack position + state_ref.push_op(StackOp::new( + RW::READ, + last_stack_pointer, + stack_value_a, + )); + + // Manage second stack read at second latest stack position + state_ref.push_op(StackOp::new( + RW::READ, + second_last_stack_pointer, + stack_value_b, + )); + + // Add StackOp associated to the 0x80 push at the latest Stack pos. + state_ref.push_op(StackOp::new( + RW::WRITE, + second_last_stack_pointer, + sum, + )); + + tx.steps_mut().push(step); + test_builder.block.txs_mut().push(tx); + + // Compare first step bus mapping instance + assert_eq!( + builder.block.txs()[0].steps()[0].bus_mapping_instance, + test_builder.block.txs()[0].steps()[0].bus_mapping_instance, + ); + + // Compare containers + assert_eq!(builder.block.container, test_builder.block.container); + + Ok(()) + } + + #[test] + fn addmod_opcode_impl() -> Result<(), Error> { + let code = bytecode! { + PUSH3(0xbcdef) + PUSH3(0x6789a) + PUSH3(0x12345) + #[start] + ADDMOD + STOP + }; + + // Get the execution steps from the external tracer + let block = + mock::BlockData::new_single_tx_trace_code_at_start(&code).unwrap(); + + let mut builder = CircuitInputBuilder::new( + block.eth_block.clone(), + block.block_ctants.clone(), + ); + builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); + + let mut test_builder = CircuitInputBuilder::new( + block.eth_block, + block.block_ctants.clone(), + ); + let mut tx = Transaction::new(&block.eth_tx); + let mut tx_ctx = TransactionContext::new(&block.eth_tx); + + // Generate step corresponding to ADDMOD + let mut step = ExecStep::new( + &block.geth_trace.struct_logs[0], + test_builder.block_ctx.gc, + ); + let mut state_ref = + test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); + + // Read a, b, n + state_ref.push_op(StackOp::new( + RW::READ, + StackAddress(1024 - 3), + Word::from(0x12345), + )); + state_ref.push_op(StackOp::new( + RW::READ, + StackAddress(1024 - 2), + Word::from(0x6789a), + )); + state_ref.push_op(StackOp::new( + RW::READ, + StackAddress(1024 - 1), + Word::from(0xbcdef), + )); + + // Write a + b % n + state_ref.push_op(StackOp::new( + RW::WRITE, + StackAddress(1024 - 1), + Word::from(0x79bdf), + )); + + tx.steps_mut().push(step); + test_builder.block.txs_mut().push(tx); + + // Compare first step bus mapping instance + assert_eq!( + builder.block.txs()[0].steps()[0].bus_mapping_instance, + test_builder.block.txs()[0].steps()[0].bus_mapping_instance, + ); + + // Compare containers + assert_eq!(builder.block.container, test_builder.block.container); + + Ok(()) + } +} diff --git a/bus-mapping/src/evm/opcodes/stop.rs b/bus-mapping/src/evm/opcodes/stop.rs index 45c68f4966f..5fcfa4f7d01 100644 --- a/bus-mapping/src/evm/opcodes/stop.rs +++ b/bus-mapping/src/evm/opcodes/stop.rs @@ -12,10 +12,9 @@ use crate::Error; pub(crate) struct Stop; impl Opcode for Stop { - #[allow(unused_variables)] fn gen_associated_ops( - state: &mut CircuitInputStateRef, - steps: &[GethExecStep], + _state: &mut CircuitInputStateRef, + _steps: &[GethExecStep], ) -> Result<(), Error> { // Stop does not generate any operations Ok(()) diff --git a/bus-mapping/src/evm/opcodes/swap.rs b/bus-mapping/src/evm/opcodes/swap.rs new file mode 100644 index 00000000000..5e9c9148cd5 --- /dev/null +++ b/bus-mapping/src/evm/opcodes/swap.rs @@ -0,0 +1,136 @@ +use super::Opcode; +use crate::circuit_input_builder::CircuitInputStateRef; +use crate::eth_types::GethExecStep; +use crate::{ + operation::{StackOp, RW}, + Error, +}; + +/// Placeholder structure used to implement [`Opcode`] trait over it corresponding to the +/// `OpcodeId::SWAP*` `OpcodeId`. +#[derive(Debug, Copy, Clone)] +pub(crate) struct Swap; + +impl Opcode for Swap { + fn gen_associated_ops( + state: &mut CircuitInputStateRef, + steps: &[GethExecStep], + ) -> Result<(), Error> { + let step = &steps[0]; + + // Peek b and a + let stack_b_value_read = step.stack.nth_last(N)?; + let stack_b_position = step.stack.nth_last_filled(N); + state.push_op(StackOp::new( + RW::READ, + stack_b_position, + stack_b_value_read, + )); + let stack_a_value_read = step.stack.last()?; + let stack_a_position = step.stack.last_filled(); + state.push_op(StackOp::new( + RW::READ, + stack_a_position, + stack_a_value_read, + )); + + // Write a into b_position, write b into a_position + state.push_op(StackOp::new( + RW::WRITE, + stack_b_position, + stack_a_value_read, + )); + state.push_op(StackOp::new( + RW::WRITE, + stack_a_position, + stack_b_value_read, + )); + + Ok(()) + } +} + +#[cfg(test)] +mod swap_tests { + use super::*; + use crate::{ + bytecode, + circuit_input_builder::{ + CircuitInputBuilder, ExecStep, Transaction, TransactionContext, + }, + eth_types::Word, + evm::StackAddress, + mock, + }; + use pretty_assertions::assert_eq; + + #[test] + fn swap_opcode_impl() -> Result<(), Error> { + let code = bytecode! { + PUSH1(0x1) + PUSH1(0x2) + PUSH1(0x3) + PUSH1(0x4) + PUSH1(0x5) + PUSH1(0x6) + #[start] // [1,2,3,4,5,6] + SWAP1 // [1,2,3,4,6,5] + SWAP3 // [1,2,5,4,6,3] + SWAP5 // [3,2,5,4,6,1] + STOP + }; + + // Get the execution steps from the external tracer + let block = + mock::BlockData::new_single_tx_trace_code_at_start(&code).unwrap(); + + let mut builder = CircuitInputBuilder::new( + block.eth_block.clone(), + block.block_ctants.clone(), + ); + builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); + + let mut test_builder = CircuitInputBuilder::new( + block.eth_block, + block.block_ctants.clone(), + ); + let mut tx = Transaction::new(&block.eth_tx); + let mut tx_ctx = TransactionContext::new(&block.eth_tx); + + // Generate steps corresponding to DUP1, DUP3, DUP5 + for (i, (a, b)) in [(6, 5), (5, 3), (3, 1)].iter().enumerate() { + let mut step = ExecStep::new( + &block.geth_trace.struct_logs[i], + test_builder.block_ctx.gc, + ); + let mut state_ref = + test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); + + let a_pos = StackAddress(1024 - 6); + let b_pos = StackAddress(1024 - 5 + i * 2); + let a_val = Word::from(*a); + let b_val = Word::from(*b); + + state_ref.push_op(StackOp::new(RW::READ, b_pos, b_val)); + state_ref.push_op(StackOp::new(RW::READ, a_pos, a_val)); + state_ref.push_op(StackOp::new(RW::WRITE, b_pos, a_val)); + state_ref.push_op(StackOp::new(RW::WRITE, a_pos, b_val)); + + tx.steps_mut().push(step); + } + + test_builder.block.txs_mut().push(tx); + + // Compare first 3 steps bus mapping instance + for i in 0..3 { + assert_eq!( + builder.block.txs()[0].steps()[i].bus_mapping_instance, + test_builder.block.txs()[0].steps()[i].bus_mapping_instance + ); + } + // Compare containers + assert_eq!(builder.block.container, test_builder.block.container); + + Ok(()) + } +} diff --git a/bus-mapping/src/evm/stack.rs b/bus-mapping/src/evm/stack.rs index ca464343b7a..bfdb9fe8f70 100644 --- a/bus-mapping/src/evm/stack.rs +++ b/bus-mapping/src/evm/stack.rs @@ -11,8 +11,13 @@ pub struct StackAddress(pub(crate) usize); impl StackAddress { /// Generates a new StackAddress given a `usize`. - pub const fn new(addr: usize) -> StackAddress { - StackAddress(addr) + pub const fn new(addr: usize) -> Self { + Self(addr) + } + + /// Apply a function to the contained value. + pub fn map usize>(&self, f: F) -> Self { + Self(f(self.0)) } }