Unverified Commit 6f701e50 authored by Hero Bird's avatar Hero Bird Committed by GitHub
Browse files

Implement event topic code generation (#510)

* [env] remove unnecessary copy of no longer used mod.rs

* [env, lang] make crates compile again under newest Rust nightly

* [env] on-chain: enhance ScopedBuffer

Add append_encoded and take_appended methods.

* [env] remove old Topics trait

* [env] add new Topics infrastructure

* [env] implement new Topics infrastructure for on/off chain environments

* [env] remove dummy event

* [env] make env::topics::state mod externally accessible

It stays hidden in docs.

* [lang/codegen] generate proper Topics impls for ink! events

* [env] apply rustfmt

* [lang/codegen] fix codegen for events without topics

* [lang/macro] fix UI test

* [env] off-chain: clean-up slightly

* [examples] ERC-20: improve tests for event topics

* [env] remove commented-out line

* [lang/codegen] include event signature into topics

This is the same as it is done for non-anonymous Solidity events.
The signature of an ink! event is: ContractName::EventName.
E.g. for ERC-20's Transfer event it is: Erc20::Transfer

* [examples] ERC-20: adjust test for changes with event topics

* [lang/ir] add support for anonymous events

* [lang/codegen] add codegen for anonymous events

* [lang/ir] apply rustfmt

* [lang/macro] comment out failing unit test

Fails due to rustc version mismatch and some recent error display change.

* [examples] apply rustfmt
parent d7a47f4a
Pipeline #109933 failed with stages
in 4 minutes and 5 seconds
...@@ -33,9 +33,9 @@ use crate::{ ...@@ -33,9 +33,9 @@ use crate::{
CryptoHash, CryptoHash,
HashOutput, HashOutput,
}, },
topics::Topics,
EnvTypes, EnvTypes,
Result, Result,
Topics,
}; };
use ink_primitives::Key; use ink_primitives::Key;
...@@ -195,7 +195,7 @@ where ...@@ -195,7 +195,7 @@ where
pub fn emit_event<T, Event>(event: Event) pub fn emit_event<T, Event>(event: Event)
where where
T: EnvTypes, T: EnvTypes,
Event: Topics<T> + scale::Encode, Event: Topics + scale::Encode,
{ {
<EnvInstance as OnInstance>::on_instance(|instance| { <EnvInstance as OnInstance>::on_instance(|instance| {
TypedEnv::emit_event::<T, Event>(instance, event) TypedEnv::emit_event::<T, Event>(instance, event)
......
...@@ -22,9 +22,9 @@ use crate::{ ...@@ -22,9 +22,9 @@ use crate::{
CryptoHash, CryptoHash,
HashOutput, HashOutput,
}, },
topics::Topics,
EnvTypes, EnvTypes,
Result, Result,
Topics,
}; };
use ink_primitives::Key; use ink_primitives::Key;
...@@ -232,7 +232,7 @@ pub trait TypedEnv: Env { ...@@ -232,7 +232,7 @@ pub trait TypedEnv: Env {
fn emit_event<T, Event>(&mut self, event: Event) fn emit_event<T, Event>(&mut self, event: Event)
where where
T: EnvTypes, T: EnvTypes,
Event: Topics<T> + scale::Encode; Event: Topics + scale::Encode;
/// Sets the rent allowance of the executed contract to the new value. /// Sets the rent allowance of the executed contract to the new value.
/// ///
......
...@@ -393,7 +393,7 @@ where ...@@ -393,7 +393,7 @@ where
gas_limit: self.gas_limit.unwrap_or_else(|| 0), gas_limit: self.gas_limit.unwrap_or_else(|| 0),
transferred_value: self transferred_value: self
.transferred_value .transferred_value
.unwrap_or_else(|| E::Balance::from(0)), .unwrap_or_else(|| E::Balance::from(0u32)),
return_type: Default::default(), return_type: Default::default(),
exec_input: self.exec_input.value(), exec_input: self.exec_input.value(),
} }
......
...@@ -96,7 +96,7 @@ impl AccountsDb { ...@@ -96,7 +96,7 @@ impl AccountsDb {
self.get_account_mut::<T>(at) self.get_account_mut::<T>(at)
.expect("just checked that account exists") .expect("just checked that account exists")
} else { } else {
self.add_user_account::<T>(at.clone(), 0.into()); self.add_user_account::<T>(at.clone(), 0u32.into());
self.get_account_mut::<T>(at) self.get_account_mut::<T>(at)
.expect("just added the account so it must exist") .expect("just added the account so it must exist")
} }
......
...@@ -57,13 +57,13 @@ impl ChainSpec { ...@@ -57,13 +57,13 @@ impl ChainSpec {
<T as EnvTypes>::AccountId: From<[u8; 32]>, <T as EnvTypes>::AccountId: From<[u8; 32]>,
{ {
self.gas_price self.gas_price
.try_initialize::<T::Balance>(&T::Balance::from(100))?; .try_initialize::<T::Balance>(&T::Balance::from(100u32))?;
self.minimum_balance self.minimum_balance
.try_initialize::<T::Balance>(&T::Balance::from(42))?; .try_initialize::<T::Balance>(&T::Balance::from(42u32))?;
self.tombstone_deposit self.tombstone_deposit
.try_initialize::<T::Balance>(&T::Balance::from(16))?; .try_initialize::<T::Balance>(&T::Balance::from(16u32))?;
self.block_time self.block_time
.try_initialize::<T::Timestamp>(&T::Timestamp::from(5))?; .try_initialize::<T::Timestamp>(&T::Timestamp::from(5u32))?;
Ok(()) Ok(())
} }
......
...@@ -14,10 +14,57 @@ ...@@ -14,10 +14,57 @@
use super::super::OffHash; use super::super::OffHash;
use crate::{ use crate::{
hash::{
Blake2x256,
CryptoHash,
HashOutput,
},
topics::{
Topics,
TopicsBuilderBackend,
},
Clear,
EnvTypes, EnvTypes,
Topics,
}; };
#[derive(Default)]
pub struct TopicsBuilder {
topics: Vec<OffHash>,
}
impl<E> TopicsBuilderBackend<E> for TopicsBuilder
where
E: EnvTypes,
{
type Output = Vec<OffHash>;
fn expect(&mut self, _expected_topics: usize) {}
fn push_topic<T>(&mut self, topic_value: &T)
where
T: scale::Encode,
{
let encoded = topic_value.encode();
let len_encoded = encoded.len();
let mut result = <E as EnvTypes>::Hash::clear();
let len_result = result.as_ref().len();
if len_encoded <= len_result {
result.as_mut()[..len_encoded].copy_from_slice(&encoded[..]);
} else {
let mut hash_output = <Blake2x256 as HashOutput>::Type::default();
<Blake2x256 as CryptoHash>::hash(&encoded[..], &mut hash_output);
let copy_len = core::cmp::min(hash_output.len(), len_result);
result.as_mut()[0..copy_len].copy_from_slice(&hash_output[0..copy_len]);
}
let off_hash = OffHash::new(&result);
self.topics.push(off_hash);
}
fn output(self) -> Self::Output {
self.topics
}
}
/// Record for an emitted event. /// Record for an emitted event.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct EmittedEvent { pub struct EmittedEvent {
...@@ -32,14 +79,11 @@ impl EmittedEvent { ...@@ -32,14 +79,11 @@ impl EmittedEvent {
pub fn new<T, E>(emitted_event: E) -> Self pub fn new<T, E>(emitted_event: E) -> Self
where where
T: EnvTypes, T: EnvTypes,
E: Topics<T> + scale::Encode, E: Topics + scale::Encode,
{ {
let topics = emitted_event.topics::<T, _>(TopicsBuilder::default().into());
Self { Self {
topics: emitted_event topics,
.topics()
.iter()
.map(|hash| OffHash::new(hash))
.collect::<Vec<_>>(),
data: emitted_event.encode(), data: emitted_event.encode(),
} }
} }
...@@ -67,9 +111,10 @@ impl EmittedEventsRecorder { ...@@ -67,9 +111,10 @@ impl EmittedEventsRecorder {
pub fn record<T, E>(&mut self, new_event: E) pub fn record<T, E>(&mut self, new_event: E)
where where
T: EnvTypes, T: EnvTypes,
E: Topics<T> + scale::Encode, E: Topics + scale::Encode,
{ {
self.emitted_events.push(EmittedEvent::new(new_event)); self.emitted_events
.push(EmittedEvent::new::<T, E>(new_event));
} }
/// Returns an iterator over the emitted events in their emission order. /// Returns an iterator over the emitted events in their emission order.
......
...@@ -31,12 +31,12 @@ use crate::{ ...@@ -31,12 +31,12 @@ use crate::{
Keccak256, Keccak256,
Sha2x256, Sha2x256,
}, },
topics::Topics,
Env, Env,
EnvError, EnvError,
EnvTypes, EnvTypes,
Result, Result,
ReturnFlags, ReturnFlags,
Topics,
TypedEnv, TypedEnv,
}; };
use core::convert::TryInto; use core::convert::TryInto;
...@@ -325,7 +325,7 @@ impl TypedEnv for EnvInstance { ...@@ -325,7 +325,7 @@ impl TypedEnv for EnvInstance {
fn emit_event<T, Event>(&mut self, new_event: Event) fn emit_event<T, Event>(&mut self, new_event: Event)
where where
T: EnvTypes, T: EnvTypes,
Event: Topics<T> + scale::Encode, Event: Topics + scale::Encode,
{ {
self.emitted_events.record::<T, Event>(new_event) self.emitted_events.record::<T, Event>(new_event)
} }
......
...@@ -167,12 +167,12 @@ impl EnvInstance { ...@@ -167,12 +167,12 @@ impl EnvInstance {
// Alice has half of the maximum possible amount. // Alice has half of the maximum possible amount.
self.accounts.add_user_account::<T>( self.accounts.add_user_account::<T>(
default_accounts.alice.clone(), default_accounts.alice.clone(),
T::Balance::max_value().div(T::Balance::from(2)), T::Balance::max_value().div(T::Balance::from(2u32)),
); );
// Bob has half the balance that alice got. // Bob has half the balance that alice got.
self.accounts.add_user_account::<T>( self.accounts.add_user_account::<T>(
default_accounts.bob, default_accounts.bob,
T::Balance::max_value().div(T::Balance::from(4)), T::Balance::max_value().div(T::Balance::from(4u32)),
); );
// All other default accounts have zero balance. // All other default accounts have zero balance.
self.accounts self.accounts
...@@ -185,8 +185,8 @@ impl EnvInstance { ...@@ -185,8 +185,8 @@ impl EnvInstance {
.add_user_account::<T>(default_accounts.frank, T::Balance::zero()); .add_user_account::<T>(default_accounts.frank, T::Balance::zero());
// Initialize our first block. // Initialize our first block.
self.blocks.push(Block::new::<T>( self.blocks.push(Block::new::<T>(
T::BlockNumber::from(0), T::BlockNumber::from(0u32),
T::Timestamp::from(0), T::Timestamp::from(0u32),
)); ));
// Initialize chain specification. // Initialize chain specification.
self.chain_spec.initialize_as_default::<T>()?; self.chain_spec.initialize_as_default::<T>()?;
...@@ -194,8 +194,8 @@ impl EnvInstance { ...@@ -194,8 +194,8 @@ impl EnvInstance {
let contract_account_id = T::AccountId::from([0x07; 32]); let contract_account_id = T::AccountId::from([0x07; 32]);
self.accounts.add_contract_account::<T>( self.accounts.add_contract_account::<T>(
contract_account_id.clone(), contract_account_id.clone(),
T::Balance::from(0), T::Balance::from(0u32),
T::Balance::from(20), T::Balance::from(20u32),
); );
// Initialize the execution context for the first contract execution. // Initialize the execution context for the first contract execution.
use crate::call::Selector; use crate::call::Selector;
...@@ -206,8 +206,8 @@ impl EnvInstance { ...@@ -206,8 +206,8 @@ impl EnvInstance {
ExecContext::build::<T>() ExecContext::build::<T>()
.caller(default_accounts.alice) .caller(default_accounts.alice)
.callee(contract_account_id) .callee(contract_account_id)
.gas(T::Balance::from(500_000)) .gas(T::Balance::from(500_000u32))
.transferred_value(T::Balance::from(500)) .transferred_value(T::Balance::from(500u32))
.call_data(CallData::new(Selector::new(selector_bytes_for_call))) .call_data(CallData::new(Selector::new(selector_bytes_for_call)))
.finish(), .finish(),
); );
......
...@@ -44,6 +44,9 @@ impl core::ops::IndexMut<core::ops::RangeFull> for StaticBuffer { ...@@ -44,6 +44,9 @@ impl core::ops::IndexMut<core::ops::RangeFull> for StaticBuffer {
} }
} }
/// Utility to allow for non-heap allocating encoding into a static buffer.
///
/// Required by `ScopedBuffer` internals.
struct EncodeScope<'a> { struct EncodeScope<'a> {
buffer: &'a mut [u8], buffer: &'a mut [u8],
len: usize, len: usize,
...@@ -106,19 +109,31 @@ impl<'a> scale::Output for EncodeScope<'a> { ...@@ -106,19 +109,31 @@ impl<'a> scale::Output for EncodeScope<'a> {
/// into smaller sub buffers for processing different parts of computations. /// into smaller sub buffers for processing different parts of computations.
#[derive(Debug)] #[derive(Debug)]
pub struct ScopedBuffer<'a> { pub struct ScopedBuffer<'a> {
offset: usize,
buffer: &'a mut [u8], buffer: &'a mut [u8],
} }
impl<'a> From<&'a mut [u8]> for ScopedBuffer<'a> { impl<'a> From<&'a mut [u8]> for ScopedBuffer<'a> {
fn from(buffer: &'a mut [u8]) -> Self { fn from(buffer: &'a mut [u8]) -> Self {
Self { buffer } Self { offset: 0, buffer }
} }
} }
impl<'a> ScopedBuffer<'a> { impl<'a> ScopedBuffer<'a> {
/// Splits the scoped buffer into yet another piece to operate on it temporarily.
///
/// The splitted buffer will have an offset of 0 but be offset by `self`'s offset.
pub fn split(&mut self) -> ScopedBuffer {
ScopedBuffer {
offset: 0,
buffer: &mut self.buffer[self.offset..],
}
}
/// Returns the first `len` bytes of the buffer as mutable slice. /// Returns the first `len` bytes of the buffer as mutable slice.
pub fn take(&mut self, len: usize) -> &'a mut [u8] { pub fn take(&mut self, len: usize) -> &'a mut [u8] {
assert!(len <= self.buffer.len()); debug_assert_eq!(self.offset, 0);
debug_assert!(len <= self.buffer.len());
let len_before = self.buffer.len(); let len_before = self.buffer.len();
let buffer = core::mem::take(&mut self.buffer); let buffer = core::mem::take(&mut self.buffer);
let (lhs, rhs) = buffer.split_at_mut(len); let (lhs, rhs) = buffer.split_at_mut(len);
...@@ -131,6 +146,7 @@ impl<'a> ScopedBuffer<'a> { ...@@ -131,6 +146,7 @@ impl<'a> ScopedBuffer<'a> {
/// Returns a buffer scope filled with `bytes` with the proper length. /// Returns a buffer scope filled with `bytes` with the proper length.
pub fn take_bytes(&mut self, bytes: &[u8]) -> &'a mut [u8] { pub fn take_bytes(&mut self, bytes: &[u8]) -> &'a mut [u8] {
debug_assert_eq!(self.offset, 0);
let buffer = self.take(bytes.len()); let buffer = self.take(bytes.len());
buffer.copy_from_slice(bytes); buffer.copy_from_slice(bytes);
buffer buffer
...@@ -142,6 +158,7 @@ impl<'a> ScopedBuffer<'a> { ...@@ -142,6 +158,7 @@ impl<'a> ScopedBuffer<'a> {
where where
T: scale::Encode, T: scale::Encode,
{ {
debug_assert_eq!(self.offset, 0);
let buffer = core::mem::take(&mut self.buffer); let buffer = core::mem::take(&mut self.buffer);
let mut encode_scope = EncodeScope::from(buffer); let mut encode_scope = EncodeScope::from(buffer);
scale::Encode::encode_to(&value, &mut encode_scope); scale::Encode::encode_to(&value, &mut encode_scope);
...@@ -150,9 +167,37 @@ impl<'a> ScopedBuffer<'a> { ...@@ -150,9 +167,37 @@ impl<'a> ScopedBuffer<'a> {
self.take(encode_len) self.take(encode_len)
} }
/// Appends the encoding of `value` to the scoped buffer.
///
/// Does not return the buffer immediately so that other values can be appended
/// afterwards. The [`take_appended`] method shall be used to return the buffer
/// that includes all appended encodings as a single buffer.
pub fn append_encoded<T>(&mut self, value: &T)
where
T: scale::Encode,
{
let offset = self.offset;
let buffer = core::mem::take(&mut self.buffer);
let mut encode_scope = EncodeScope::from(&mut buffer[offset..]);
scale::Encode::encode_to(&value, &mut encode_scope);
let encode_len = encode_scope.len();
self.offset += encode_len;
let _ = core::mem::replace(&mut self.buffer, buffer);
}
/// Returns the buffer containing all encodings appended via [`append_encoded`]
/// in a single byte buffer.
pub fn take_appended(&mut self) -> &'a mut [u8] {
debug_assert_ne!(self.offset, 0);
let offset = self.offset;
self.offset = 0;
self.take(offset)
}
/// Returns all of the remaining bytes of the buffer as mutable slice. /// Returns all of the remaining bytes of the buffer as mutable slice.
pub fn take_rest(self) -> &'a mut [u8] { pub fn take_rest(self) -> &'a mut [u8] {
assert!(!self.buffer.is_empty()); debug_assert_eq!(self.offset, 0);
debug_assert!(!self.buffer.is_empty());
self.buffer self.buffer
} }
} }
...@@ -32,12 +32,16 @@ use crate::{ ...@@ -32,12 +32,16 @@ use crate::{
Keccak256, Keccak256,
Sha2x256, Sha2x256,
}, },
topics::{
Topics,
TopicsBuilderBackend,
},
Clear,
Env, Env,
EnvError, EnvError,
EnvTypes, EnvTypes,
Result, Result,
ReturnFlags, ReturnFlags,
Topics,
TypedEnv, TypedEnv,
}; };
use ink_primitives::Key; use ink_primitives::Key;
...@@ -106,6 +110,60 @@ impl From<ext::Error> for EnvError { ...@@ -106,6 +110,60 @@ impl From<ext::Error> for EnvError {
} }
} }
pub struct TopicsBuilder<'a, E> {
scoped_buffer: ScopedBuffer<'a>,
marker: core::marker::PhantomData<fn() -> E>,
}
impl<'a, E> From<ScopedBuffer<'a>> for TopicsBuilder<'a, E>
where
E: EnvTypes,
{
fn from(scoped_buffer: ScopedBuffer<'a>) -> Self {
Self {
scoped_buffer,
marker: Default::default(),
}
}
}
impl<'a, E> TopicsBuilderBackend<E> for TopicsBuilder<'a, E>
where
E: EnvTypes,
{
type Output = (ScopedBuffer<'a>, &'a mut [u8]);
fn expect(&mut self, expected_topics: usize) {
self.scoped_buffer
.append_encoded(&scale::Compact(expected_topics as u32));
}
fn push_topic<T>(&mut self, topic_value: &T)
where
T: scale::Encode,
{
let mut split = self.scoped_buffer.split();
let encoded = split.take_encoded(topic_value);
let len_encoded = encoded.len();
let mut result = <E as EnvTypes>::Hash::clear();
let len_result = result.as_ref().len();
if len_encoded <= len_result {
result.as_mut()[..len_encoded].copy_from_slice(encoded);
} else {
let mut hash_output = <Blake2x256 as HashOutput>::Type::default();
<Blake2x256 as CryptoHash>::hash(encoded, &mut hash_output);
let copy_len = core::cmp::min(hash_output.len(), len_result);
result.as_mut()[0..copy_len].copy_from_slice(&hash_output[0..copy_len]);
}
self.scoped_buffer.append_encoded(&result);
}
fn output(mut self) -> Self::Output {
let encoded_topics = self.scoped_buffer.take_appended();
(self.scoped_buffer, encoded_topics)
}
}
impl EnvInstance { impl EnvInstance {
/// Returns a new scoped buffer for the entire scope of the static 16kB buffer. /// Returns a new scoped buffer for the entire scope of the static 16kB buffer.
fn scoped_buffer(&mut self) -> ScopedBuffer { fn scoped_buffer(&mut self) -> ScopedBuffer {
...@@ -272,10 +330,10 @@ impl TypedEnv for EnvInstance { ...@@ -272,10 +330,10 @@ impl TypedEnv for EnvInstance {
fn emit_event<T, Event>(&mut self, event: Event) fn emit_event<T, Event>(&mut self, event: Event)
where where
T: EnvTypes, T: EnvTypes,
Event: Topics<T> + scale::Encode, Event: Topics + scale::Encode,
{ {
let mut scope = self.scoped_buffer(); let (mut scope, enc_topics) =
let enc_topics = scope.take_encoded(&event.topics()); event.topics::<T, _>(TopicsBuilder::from(self.scoped_buffer()).into());
let enc_data = scope.take_encoded(&event); let enc_data = scope.take_encoded(&event);
ext::deposit_event(enc_topics, enc_data); ext::deposit_event(enc_topics, enc_data);
} }
......
...@@ -66,6 +66,8 @@ pub mod call; ...@@ -66,6 +66,8 @@ pub mod call;
mod engine; mod engine;
mod error; mod error;
pub mod hash; pub mod hash;
#[doc(hidden)]
pub mod topics;
mod types; mod types;
#[cfg(test)] #[cfg(test)]
...@@ -86,12 +88,12 @@ pub use self::{ ...@@ -86,12 +88,12 @@ pub use self::{
EnvError, EnvError,
Result,