diff --git a/crates/differential/src/lib.rs b/crates/differential/src/lib.rs index f5a1b9c..fbe9190 100644 --- a/crates/differential/src/lib.rs +++ b/crates/differential/src/lib.rs @@ -10,7 +10,10 @@ use primitive_types::{H160, H256, U256}; static RUNTIME_ETABLE: Etable = Etable::runtime(); -pub struct UnimplementedHandler; +#[derive(Default)] +pub struct UnimplementedHandler { + logs: Vec, +} impl RuntimeEnvironment for UnimplementedHandler { fn block_hash(&self, _number: U256) -> H256 { @@ -87,8 +90,9 @@ impl RuntimeBackend for UnimplementedHandler { fn set_storage(&mut self, _address: H160, _index: H256, _value: H256) -> Result<(), ExitError> { unimplemented!() } - fn log(&mut self, _log: Log) -> Result<(), ExitError> { - unimplemented!() + fn log(&mut self, log: Log) -> Result<(), ExitError> { + self.logs.push(log); + Ok(()) } fn mark_delete(&mut self, _address: H160) { unimplemented!() @@ -147,12 +151,9 @@ pub fn prepare(code: Vec, data: Vec) -> PreparedEvm { } } -pub fn execute(pre: PreparedEvm) -> Vec { +pub fn execute(pre: PreparedEvm) -> (Vec, Vec) { let mut vm = EtableInterpreter::new_valid(pre.vm, &RUNTIME_ETABLE, pre.valids); - vm.run(&mut UnimplementedHandler {}) - .exit() - .unwrap() - .unwrap(); - - vm.retval.clone() + let mut handler = UnimplementedHandler::default(); + vm.run(&mut handler).exit().unwrap().unwrap(); + (vm.retval.clone(), handler.logs) } diff --git a/crates/integration/codesize.json b/crates/integration/codesize.json index 9b462c9..7303372 100644 --- a/crates/integration/codesize.json +++ b/crates/integration/codesize.json @@ -2,7 +2,8 @@ "Baseline": 3944, "Computation": 7444, "DivisionArithmetics": 42784, - "ERC20": 53524, + "ERC20": 56199, + "Events": 4784, "FibonacciIterative": 6019, "Flipper": 4392, "SHA1": 36107 diff --git a/crates/integration/contracts/DivisionArithmetics.sol b/crates/integration/contracts/DivisionArithmetics.sol index 972c6fd..2657abf 100644 --- a/crates/integration/contracts/DivisionArithmetics.sol +++ b/crates/integration/contracts/DivisionArithmetics.sol @@ -1,3 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8; + contract DivisionArithmetics { function div(uint n, uint d) public pure returns (uint q) { assembly { diff --git a/crates/integration/contracts/Events.sol b/crates/integration/contracts/Events.sol new file mode 100644 index 0000000..d52064b --- /dev/null +++ b/crates/integration/contracts/Events.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8; + +contract Events { + event A() anonymous; + event E(uint, uint indexed, uint indexed, uint indexed); + + function emitEvent(uint topics) public { + if (topics == 0) { + emit A(); + } else { + emit E(topics, 1, 2, 3); + } + } +} diff --git a/crates/integration/src/cases.rs b/crates/integration/src/cases.rs index 8c60095..80285d0 100644 --- a/crates/integration/src/cases.rs +++ b/crates/integration/src/cases.rs @@ -104,6 +104,15 @@ sol!( } ); +sol!( + contract Events { + event A(uint) anonymous; + event E(uint indexed, uint indexed, uint indexed); + + function emitEvent(uint topics) public; + } +); + impl Contract { /// Execute the contract. /// @@ -338,6 +347,18 @@ impl Contract { calldata: MStore8::mStore8Call::new((value,)).abi_encode(), } } + + pub fn event(topics: U256) -> Self { + let code = include_str!("../contracts/Events.sol"); + let name = "Events"; + + Self { + name, + evm_runtime: crate::compile_evm_bin_runtime(name, code), + pvm_runtime: crate::compile_blob(name, code), + calldata: Events::emitEventCall::new((topics,)).abi_encode(), + } + } } #[cfg(test)] @@ -389,6 +410,7 @@ mod tests { Contract::erc20 as fn() -> Contract, (|| Contract::sha1(Vec::new())) as fn() -> Contract, (|| Contract::division_arithmetics_div(U256::ZERO, U256::ZERO)) as fn() -> Contract, + (|| Contract::event(U256::ZERO)) as fn() -> Contract, ] .into_par_iter() .map(extract_code_size) diff --git a/crates/integration/src/lib.rs b/crates/integration/src/lib.rs index 8442612..8e58e61 100644 --- a/crates/integration/src/lib.rs +++ b/crates/integration/src/lib.rs @@ -1,7 +1,8 @@ +use alloy_primitives::{Address, U256}; use cases::Contract; use mock_runtime::{CallOutput, State}; -use crate::mock_runtime::ReturnFlags; +use crate::mock_runtime::{Event, ReturnFlags}; pub mod cases; pub mod mock_runtime; @@ -82,7 +83,25 @@ pub fn assert_success(contract: &Contract, differential: bool) -> (State, CallOu if differential { let evm = revive_differential::prepare(contract.evm_runtime.clone(), contract.calldata.clone()); - assert_eq!(output.data.clone(), revive_differential::execute(evm)); + let (evm_output, evm_log) = revive_differential::execute(evm); + + assert_eq!(output.data.clone(), evm_output); + assert_eq!(output.events.len(), evm_log.len()); + assert_eq!( + output.events, + evm_log + .iter() + .map(|log| Event { + address: Address::from_slice(log.address.as_bytes()), + data: log.data.clone(), + topics: log + .topics + .iter() + .map(|topic| U256::from_be_bytes(topic.0)) + .collect(), + }) + .collect::>() + ); } (state, output) diff --git a/crates/integration/src/mock_runtime.rs b/crates/integration/src/mock_runtime.rs index 379b94c..162cd7f 100644 --- a/crates/integration/src/mock_runtime.rs +++ b/crates/integration/src/mock_runtime.rs @@ -17,8 +17,9 @@ struct Account { } /// Emitted event data. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, PartialEq)] pub struct Event { + pub address: Address, pub data: Vec, pub topics: Vec, } @@ -31,7 +32,7 @@ pub struct CallOutput { /// The contract call output. pub data: Vec, /// The emitted events. - pub events: Event, + pub events: Vec, } /// The contract blob export to be called. @@ -570,6 +571,43 @@ fn link_host_functions(engine: &Engine) -> Linker { ) .unwrap(); + linker + .func_wrap( + runtime_api::DEPOSIT_EVENT, + |caller: Caller, + topics_ptr: u32, + topics_len: u32, + data_ptr: u32, + data_len: u32| { + let (caller, transaction) = caller.split(); + + let address = transaction.top_frame().callee; + let data = if data_len != 0 { + caller.read_memory_into_vec(data_ptr, data_len)? + } else { + Default::default() + }; + let topics = if topics_len != 0 { + caller + .read_memory_into_vec(topics_ptr, topics_len)? + .chunks(32) + .map(|chunk| U256::from_be_slice(chunk)) + .collect() + } else { + Default::default() + }; + + transaction.top_frame_mut().output.events.push(Event { + address, + data, + topics, + }); + + Ok(()) + }, + ) + .unwrap(); + linker } diff --git a/crates/integration/src/tests.rs b/crates/integration/src/tests.rs index a41fc5d..9c14fee 100644 --- a/crates/integration/src/tests.rs +++ b/crates/integration/src/tests.rs @@ -415,3 +415,9 @@ fn signed_remainder() { assert_eq!(received, expected); } } + +#[test] +fn events() { + assert_success(&Contract::event(U256::ZERO), true); + assert_success(&Contract::event(U256::from(123)), true); +} diff --git a/crates/llvm-context/src/polkavm/const/runtime_api.rs b/crates/llvm-context/src/polkavm/const/runtime_api.rs index 2e8dd40..9a81614 100644 --- a/crates/llvm-context/src/polkavm/const/runtime_api.rs +++ b/crates/llvm-context/src/polkavm/const/runtime_api.rs @@ -16,6 +16,8 @@ pub static BLOCK_NUMBER: &str = "block_number"; pub static CALLER: &str = "caller"; +pub static DEPOSIT_EVENT: &str = "deposit_event"; + pub static GET_STORAGE: &str = "get_storage"; pub static HASH_KECCAK_256: &str = "hash_keccak_256"; @@ -32,10 +34,15 @@ pub static VALUE_TRANSFERRED: &str = "value_transferred"; /// All imported runtime API symbols.. /// Useful for configuring common attributes and linkage. -pub static IMPORTS: [&str; 6] = [ +pub static IMPORTS: [&str; 11] = [ + ADDRESS, + BLOCK_NUMBER, + CALLER, + DEPOSIT_EVENT, GET_STORAGE, HASH_KECCAK_256, INPUT, + NOW, RETURN, SET_STORAGE, VALUE_TRANSFERRED, diff --git a/crates/llvm-context/src/polkavm/evm/event.rs b/crates/llvm-context/src/polkavm/evm/event.rs index 4f56a8d..e024b92 100644 --- a/crates/llvm-context/src/polkavm/evm/event.rs +++ b/crates/llvm-context/src/polkavm/evm/event.rs @@ -1,72 +1,80 @@ //! Translates a log or event call. +use inkwell::values::BasicValue; + use crate::polkavm::context::Context; use crate::polkavm::Dependency; +use crate::polkavm_const::runtime_api; /// Translates a log or event call. -/// The decoding logic is implemented in a system contract, which is called from here. -/// There are several cases of the translation for the sake of efficiency, since the front-end -/// emits topics and values sequentially by one, but the LLVM intrinsic and bytecode instruction -/// accept two at once. +/// +/// TODO: Splitting up into dedicated functions (log0..log4) +/// could potentially decrease code sizes (LLVM can still decide to inline). +/// However, passing many i256 parameters could also hurt a bit. +/// Should be reviewed after 64bit support. pub fn log<'ctx, D>( - _context: &mut Context<'ctx, D>, - _input_offset: inkwell::values::IntValue<'ctx>, - _input_length: inkwell::values::IntValue<'ctx>, - _topics: Vec>, + context: &mut Context<'ctx, D>, + input_offset: inkwell::values::IntValue<'ctx>, + input_length: inkwell::values::IntValue<'ctx>, + topics: Vec>, ) -> anyhow::Result<()> where D: Dependency + Clone, { - /* - let failure_block = context.append_basic_block("event_failure_block"); - let join_block = context.append_basic_block("event_join_block"); - - let gas = crate::polkavm::evm::ether_gas::gas(context)?.into_int_value(); - let abi_data = crate::polkavm::utils::abi_data( - context, - input_offset, - input_length, - Some(gas), - AddressSpace::Heap, - true, - )?; - let mut extra_abi_data = Vec::with_capacity(1 + topics.len()); - extra_abi_data.push(context.field_const(topics.len() as u64)); - extra_abi_data.extend(topics); - - let result = context - .build_call( - context.llvm_runtime().far_call, - crate::polkavm::utils::external_call_arguments( - context, - abi_data.as_basic_value_enum(), - context.field_const(zkevm_opcode_defs::ADDRESS_EVENT_WRITER as u64), - extra_abi_data, - None, - ) - .as_slice(), - "event_writer_call_external", - ) - .expect("Always returns a value"); - - let result_status_code_boolean = context - .builder() - .build_extract_value( - result.into_struct_value(), - 1, - "event_writer_external_result_status_code_boolean", - ) - .expect("Always exists"); - context.build_conditional_branch( - result_status_code_boolean.into_int_value(), - join_block, - failure_block, + let input_offset = context.safe_truncate_int_to_xlen(input_offset)?; + let input_length = context.safe_truncate_int_to_xlen(input_length)?; + let input_pointer = context.builder().build_ptr_to_int( + context.build_heap_gep(input_offset, input_length)?.value, + context.xlen_type(), + "event_input_offset", )?; - context.set_basic_block(failure_block); - crate::polkavm::evm::r#return::revert(context, context.field_const(0), context.field_const(0))?; + let arguments = if topics.is_empty() { + [ + context.xlen_type().const_zero().as_basic_value_enum(), + context.xlen_type().const_zero().as_basic_value_enum(), + input_pointer.as_basic_value_enum(), + input_length.as_basic_value_enum(), + ] + } else { + let topics_buffer_size = topics.len() * revive_common::BYTE_LENGTH_WORD; + let topics_buffer_pointer = context.build_alloca( + context.byte_type().array_type(topics_buffer_size as u32), + "topics_buffer", + ); + for (n, topic) in topics.iter().enumerate() { + let topic_buffer_offset = context + .xlen_type() + .const_int((n * revive_common::BYTE_LENGTH_WORD) as u64, false); + context.build_store( + context.build_gep( + topics_buffer_pointer, + &[context.xlen_type().const_zero(), topic_buffer_offset], + context.byte_type(), + "topic_buffer_gep", + ), + context.build_byte_swap(topic.as_basic_value_enum())?, + )?; + } + [ + context + .builder() + .build_ptr_to_int( + topics_buffer_pointer.value, + context.xlen_type(), + "event_topics_offset", + )? + .as_basic_value_enum(), + context + .xlen_type() + .const_int(topics_buffer_size as u64, false) + .as_basic_value_enum(), + input_pointer.as_basic_value_enum(), + input_length.as_basic_value_enum(), + ] + }; + + let _ = context.build_runtime_call(runtime_api::DEPOSIT_EVENT, &arguments); - context.set_basic_block(join_block); - */ Ok(()) }