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> =
Etable::runtime();
pub struct UnimplementedHandler;
#[derive(Default)]
pub struct UnimplementedHandler {
logs: Vec<Log>,
}
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<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);
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)
}
+2 -1
View File
@@ -2,7 +2,8 @@
"Baseline": 3944,
"Computation": 7444,
"DivisionArithmetics": 42784,
"ERC20": 53524,
"ERC20": 56199,
"Events": 4784,
"FibonacciIterative": 6019,
"Flipper": 4392,
"SHA1": 36107
@@ -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 {
+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 {
/// 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)
+21 -2
View File
@@ -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::<Vec<_>>()
);
}
(state, output)
+40 -2
View File
@@ -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<u8>,
pub topics: Vec<U256>,
}
@@ -31,7 +32,7 @@ pub struct CallOutput {
/// The contract call output.
pub data: Vec<u8>,
/// The emitted events.
pub events: Event,
pub events: Vec<Event>,
}
/// The contract blob export to be called.
@@ -570,6 +571,43 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
)
.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
}
+6
View File
@@ -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);
}
@@ -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,
+64 -56
View File
@@ -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<inkwell::values::IntValue<'ctx>>,
context: &mut Context<'ctx, D>,
input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
topics: Vec<inkwell::values::IntValue<'ctx>>,
) -> 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(())
}