Signed-off-by: xermicus <cyrill@parity.io>
This commit is contained in:
xermicus
2024-05-14 09:46:18 +02:00
parent 2194aeebd0
commit 83bf9d6041
10 changed files with 194 additions and 72 deletions
+11 -10
View File
@@ -10,7 +10,10 @@ use primitive_types::{H160, H256, U256};
static RUNTIME_ETABLE: Etable<RuntimeState, UnimplementedHandler, CallCreateTrap> = static RUNTIME_ETABLE: Etable<RuntimeState, UnimplementedHandler, CallCreateTrap> =
Etable::runtime(); Etable::runtime();
pub struct UnimplementedHandler; #[derive(Default)]
pub struct UnimplementedHandler {
logs: Vec<Log>,
}
impl RuntimeEnvironment for UnimplementedHandler { impl RuntimeEnvironment for UnimplementedHandler {
fn block_hash(&self, _number: U256) -> H256 { 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> { fn set_storage(&mut self, _address: H160, _index: H256, _value: H256) -> Result<(), ExitError> {
unimplemented!() unimplemented!()
} }
fn log(&mut self, _log: Log) -> Result<(), ExitError> { fn log(&mut self, log: Log) -> Result<(), ExitError> {
unimplemented!() self.logs.push(log);
Ok(())
} }
fn mark_delete(&mut self, _address: H160) { fn mark_delete(&mut self, _address: H160) {
unimplemented!() unimplemented!()
@@ -147,12 +151,9 @@ pub fn prepare(code: Vec<u8>, data: Vec<u8>) -> PreparedEvm {
} }
} }
pub fn execute(pre: PreparedEvm) -> Vec<u8> { pub fn execute(pre: PreparedEvm) -> (Vec<u8>, Vec<Log>) {
let mut vm = EtableInterpreter::new_valid(pre.vm, &RUNTIME_ETABLE, pre.valids); let mut vm = EtableInterpreter::new_valid(pre.vm, &RUNTIME_ETABLE, pre.valids);
vm.run(&mut UnimplementedHandler {}) let mut handler = UnimplementedHandler::default();
.exit() vm.run(&mut handler).exit().unwrap().unwrap();
.unwrap() (vm.retval.clone(), handler.logs)
.unwrap();
vm.retval.clone()
} }
+2 -1
View File
@@ -2,7 +2,8 @@
"Baseline": 3944, "Baseline": 3944,
"Computation": 7444, "Computation": 7444,
"DivisionArithmetics": 42784, "DivisionArithmetics": 42784,
"ERC20": 53524, "ERC20": 56199,
"Events": 4784,
"FibonacciIterative": 6019, "FibonacciIterative": 6019,
"Flipper": 4392, "Flipper": 4392,
"SHA1": 36107 "SHA1": 36107
@@ -1,3 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
contract DivisionArithmetics { contract DivisionArithmetics {
function div(uint n, uint d) public pure returns (uint q) { function div(uint n, uint d) public pure returns (uint q) {
assembly { assembly {
+16
View File
@@ -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);
}
}
}
+22
View File
@@ -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 { impl Contract {
/// Execute the contract. /// Execute the contract.
/// ///
@@ -338,6 +347,18 @@ impl Contract {
calldata: MStore8::mStore8Call::new((value,)).abi_encode(), 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)] #[cfg(test)]
@@ -389,6 +410,7 @@ mod tests {
Contract::erc20 as fn() -> Contract, Contract::erc20 as fn() -> Contract,
(|| Contract::sha1(Vec::new())) as fn() -> Contract, (|| Contract::sha1(Vec::new())) as fn() -> Contract,
(|| Contract::division_arithmetics_div(U256::ZERO, U256::ZERO)) as fn() -> Contract, (|| Contract::division_arithmetics_div(U256::ZERO, U256::ZERO)) as fn() -> Contract,
(|| Contract::event(U256::ZERO)) as fn() -> Contract,
] ]
.into_par_iter() .into_par_iter()
.map(extract_code_size) .map(extract_code_size)
+21 -2
View File
@@ -1,7 +1,8 @@
use alloy_primitives::{Address, U256};
use cases::Contract; use cases::Contract;
use mock_runtime::{CallOutput, State}; use mock_runtime::{CallOutput, State};
use crate::mock_runtime::ReturnFlags; use crate::mock_runtime::{Event, ReturnFlags};
pub mod cases; pub mod cases;
pub mod mock_runtime; pub mod mock_runtime;
@@ -82,7 +83,25 @@ pub fn assert_success(contract: &Contract, differential: bool) -> (State, CallOu
if differential { if differential {
let evm = let evm =
revive_differential::prepare(contract.evm_runtime.clone(), contract.calldata.clone()); 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::<Vec<_>>()
);
} }
(state, output) (state, output)
+40 -2
View File
@@ -17,8 +17,9 @@ struct Account {
} }
/// Emitted event data. /// Emitted event data.
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone, PartialEq)]
pub struct Event { pub struct Event {
pub address: Address,
pub data: Vec<u8>, pub data: Vec<u8>,
pub topics: Vec<U256>, pub topics: Vec<U256>,
} }
@@ -31,7 +32,7 @@ pub struct CallOutput {
/// The contract call output. /// The contract call output.
pub data: Vec<u8>, pub data: Vec<u8>,
/// The emitted events. /// The emitted events.
pub events: Event, pub events: Vec<Event>,
} }
/// The contract blob export to be called. /// The contract blob export to be called.
@@ -570,6 +571,43 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
) )
.unwrap(); .unwrap();
linker
.func_wrap(
runtime_api::DEPOSIT_EVENT,
|caller: Caller<Transaction>,
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 linker
} }
+6
View File
@@ -415,3 +415,9 @@ fn signed_remainder() {
assert_eq!(received, expected); assert_eq!(received, expected);
} }
} }
#[test]
fn events() {
assert_success(&Contract::event(U256::ZERO), true);
assert_success(&Contract::event(U256::from(123)), true);
}
@@ -16,6 +16,8 @@ pub static BLOCK_NUMBER: &str = "block_number";
pub static CALLER: &str = "caller"; pub static CALLER: &str = "caller";
pub static DEPOSIT_EVENT: &str = "deposit_event";
pub static GET_STORAGE: &str = "get_storage"; pub static GET_STORAGE: &str = "get_storage";
pub static HASH_KECCAK_256: &str = "hash_keccak_256"; 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.. /// All imported runtime API symbols..
/// Useful for configuring common attributes and linkage. /// 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, GET_STORAGE,
HASH_KECCAK_256, HASH_KECCAK_256,
INPUT, INPUT,
NOW,
RETURN, RETURN,
SET_STORAGE, SET_STORAGE,
VALUE_TRANSFERRED, VALUE_TRANSFERRED,
+64 -56
View File
@@ -1,72 +1,80 @@
//! Translates a log or event call. //! Translates a log or event call.
use inkwell::values::BasicValue;
use crate::polkavm::context::Context; use crate::polkavm::context::Context;
use crate::polkavm::Dependency; use crate::polkavm::Dependency;
use crate::polkavm_const::runtime_api;
/// Translates a log or event call. /// 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 /// TODO: Splitting up into dedicated functions (log0..log4)
/// emits topics and values sequentially by one, but the LLVM intrinsic and bytecode instruction /// could potentially decrease code sizes (LLVM can still decide to inline).
/// accept two at once. /// However, passing many i256 parameters could also hurt a bit.
/// Should be reviewed after 64bit support.
pub fn log<'ctx, D>( pub fn log<'ctx, D>(
_context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
_input_offset: inkwell::values::IntValue<'ctx>, input_offset: inkwell::values::IntValue<'ctx>,
_input_length: inkwell::values::IntValue<'ctx>, input_length: inkwell::values::IntValue<'ctx>,
_topics: Vec<inkwell::values::IntValue<'ctx>>, topics: Vec<inkwell::values::IntValue<'ctx>>,
) -> anyhow::Result<()> ) -> anyhow::Result<()>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
/* let input_offset = context.safe_truncate_int_to_xlen(input_offset)?;
let failure_block = context.append_basic_block("event_failure_block"); let input_length = context.safe_truncate_int_to_xlen(input_length)?;
let join_block = context.append_basic_block("event_join_block"); let input_pointer = context.builder().build_ptr_to_int(
context.build_heap_gep(input_offset, input_length)?.value,
let gas = crate::polkavm::evm::ether_gas::gas(context)?.into_int_value(); context.xlen_type(),
let abi_data = crate::polkavm::utils::abi_data( "event_input_offset",
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,
)?; )?;
context.set_basic_block(failure_block); let arguments = if topics.is_empty() {
crate::polkavm::evm::r#return::revert(context, context.field_const(0), context.field_const(0))?; [
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(()) Ok(())
} }