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::{
CryptoHash,
HashOutput,
},
topics::Topics,
EnvTypes,
Result,
Topics,
};
use ink_primitives::Key;
......@@ -195,7 +195,7 @@ where
pub fn emit_event<T, Event>(event: Event)
where
T: EnvTypes,
Event: Topics<T> + scale::Encode,
Event: Topics + scale::Encode,
{
<EnvInstance as OnInstance>::on_instance(|instance| {
TypedEnv::emit_event::<T, Event>(instance, event)
......
......@@ -22,9 +22,9 @@ use crate::{
CryptoHash,
HashOutput,
},
topics::Topics,
EnvTypes,
Result,
Topics,
};
use ink_primitives::Key;
......@@ -232,7 +232,7 @@ pub trait TypedEnv: Env {
fn emit_event<T, Event>(&mut self, event: Event)
where
T: EnvTypes,
Event: Topics<T> + scale::Encode;
Event: Topics + scale::Encode;
/// Sets the rent allowance of the executed contract to the new value.
///
......
......@@ -393,7 +393,7 @@ where
gas_limit: self.gas_limit.unwrap_or_else(|| 0),
transferred_value: self
.transferred_value
.unwrap_or_else(|| E::Balance::from(0)),
.unwrap_or_else(|| E::Balance::from(0u32)),
return_type: Default::default(),
exec_input: self.exec_input.value(),
}
......
......@@ -96,7 +96,7 @@ impl AccountsDb {
self.get_account_mut::<T>(at)
.expect("just checked that account exists")
} 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)
.expect("just added the account so it must exist")
}
......
......@@ -57,13 +57,13 @@ impl ChainSpec {
<T as EnvTypes>::AccountId: From<[u8; 32]>,
{
self.gas_price
.try_initialize::<T::Balance>(&T::Balance::from(100))?;
.try_initialize::<T::Balance>(&T::Balance::from(100u32))?;
self.minimum_balance
.try_initialize::<T::Balance>(&T::Balance::from(42))?;
.try_initialize::<T::Balance>(&T::Balance::from(42u32))?;
self.tombstone_deposit
.try_initialize::<T::Balance>(&T::Balance::from(16))?;
.try_initialize::<T::Balance>(&T::Balance::from(16u32))?;
self.block_time
.try_initialize::<T::Timestamp>(&T::Timestamp::from(5))?;
.try_initialize::<T::Timestamp>(&T::Timestamp::from(5u32))?;
Ok(())
}
......
......@@ -14,10 +14,57 @@
use super::super::OffHash;
use crate::{
hash::{
Blake2x256,
CryptoHash,
HashOutput,
},
topics::{
Topics,
TopicsBuilderBackend,
},
Clear,
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.
#[derive(Debug, Clone)]
pub struct EmittedEvent {
......@@ -32,14 +79,11 @@ impl EmittedEvent {
pub fn new<T, E>(emitted_event: E) -> Self
where
T: EnvTypes,
E: Topics<T> + scale::Encode,
E: Topics + scale::Encode,
{
let topics = emitted_event.topics::<T, _>(TopicsBuilder::default().into());
Self {
topics: emitted_event
.topics()
.iter()
.map(|hash| OffHash::new(hash))
.collect::<Vec<_>>(),
topics,
data: emitted_event.encode(),
}
}
......@@ -67,9 +111,10 @@ impl EmittedEventsRecorder {
pub fn record<T, E>(&mut self, new_event: E)
where
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.
......
......@@ -31,12 +31,12 @@ use crate::{
Keccak256,
Sha2x256,
},
topics::Topics,
Env,
EnvError,
EnvTypes,
Result,
ReturnFlags,
Topics,
TypedEnv,
};
use core::convert::TryInto;
......@@ -325,7 +325,7 @@ impl TypedEnv for EnvInstance {
fn emit_event<T, Event>(&mut self, new_event: Event)
where
T: EnvTypes,
Event: Topics<T> + scale::Encode,
Event: Topics + scale::Encode,
{
self.emitted_events.record::<T, Event>(new_event)
}
......
......@@ -167,12 +167,12 @@ impl EnvInstance {
// Alice has half of the maximum possible amount.
self.accounts.add_user_account::<T>(
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.
self.accounts.add_user_account::<T>(
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.
self.accounts
......@@ -185,8 +185,8 @@ impl EnvInstance {
.add_user_account::<T>(default_accounts.frank, T::Balance::zero());
// Initialize our first block.
self.blocks.push(Block::new::<T>(
T::BlockNumber::from(0),
T::Timestamp::from(0),
T::BlockNumber::from(0u32),
T::Timestamp::from(0u32),
));
// Initialize chain specification.
self.chain_spec.initialize_as_default::<T>()?;
......@@ -194,8 +194,8 @@ impl EnvInstance {
let contract_account_id = T::AccountId::from([0x07; 32]);
self.accounts.add_contract_account::<T>(
contract_account_id.clone(),
T::Balance::from(0),
T::Balance::from(20),
T::Balance::from(0u32),
T::Balance::from(20u32),
);
// Initialize the execution context for the first contract execution.
use crate::call::Selector;
......@@ -206,8 +206,8 @@ impl EnvInstance {
ExecContext::build::<T>()
.caller(default_accounts.alice)
.callee(contract_account_id)
.gas(T::Balance::from(500_000))
.transferred_value(T::Balance::from(500))
.gas(T::Balance::from(500_000u32))
.transferred_value(T::Balance::from(500u32))
.call_data(CallData::new(Selector::new(selector_bytes_for_call)))
.finish(),
);
......
......@@ -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> {
buffer: &'a mut [u8],
len: usize,
......@@ -106,19 +109,31 @@ impl<'a> scale::Output for EncodeScope<'a> {
/// into smaller sub buffers for processing different parts of computations.
#[derive(Debug)]
pub struct ScopedBuffer<'a> {
offset: usize,
buffer: &'a mut [u8],
}
impl<'a> From<&'a mut [u8]> for ScopedBuffer<'a> {
fn from(buffer: &'a mut [u8]) -> Self {
Self { buffer }
Self { offset: 0, buffer }
}
}
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.
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 buffer = core::mem::take(&mut self.buffer);
let (lhs, rhs) = buffer.split_at_mut(len);
......@@ -131,6 +146,7 @@ impl<'a> ScopedBuffer<'a> {
/// Returns a buffer scope filled with `bytes` with the proper length.
pub fn take_bytes(&mut self, bytes: &[u8]) -> &'a mut [u8] {
debug_assert_eq!(self.offset, 0);
let buffer = self.take(bytes.len());
buffer.copy_from_slice(bytes);
buffer
......@@ -142,6 +158,7 @@ impl<'a> ScopedBuffer<'a> {
where
T: scale::Encode,
{
debug_assert_eq!(self.offset, 0);
let buffer = core::mem::take(&mut self.buffer);
let mut encode_scope = EncodeScope::from(buffer);
scale::Encode::encode_to(&value, &mut encode_scope);
......@@ -150,9 +167,37 @@ impl<'a> ScopedBuffer<'a> {
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.
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
}
}
......@@ -32,12 +32,16 @@ use crate::{
Keccak256,
Sha2x256,
},
topics::{
Topics,
TopicsBuilderBackend,
},
Clear,
Env,
EnvError,
EnvTypes,
Result,
ReturnFlags,
Topics,
TypedEnv,
};
use ink_primitives::Key;
......@@ -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 {
/// Returns a new scoped buffer for the entire scope of the static 16kB buffer.
fn scoped_buffer(&mut self) -> ScopedBuffer {
......@@ -272,10 +330,10 @@ impl TypedEnv for EnvInstance {
fn emit_event<T, Event>(&mut self, event: Event)
where
T: EnvTypes,
Event: Topics<T> + scale::Encode,
Event: Topics + scale::Encode,
{
let mut scope = self.scoped_buffer();
let enc_topics = scope.take_encoded(&event.topics());
let (mut scope, enc_topics) =
event.topics::<T, _>(TopicsBuilder::from(self.scoped_buffer()).into());
let enc_data = scope.take_encoded(&event);
ext::deposit_event(enc_topics, enc_data);
}
......
......@@ -66,6 +66,8 @@ pub mod call;
mod engine;
mod error;
pub mod hash;
#[doc(hidden)]
pub mod topics;
mod types;
#[cfg(test)]
......@@ -86,12 +88,12 @@ pub use self::{
EnvError,
Result,
},
topics::Topics,
types::{
AccountId,
Clear,
DefaultEnvTypes,
EnvTypes,
Hash,
Topics,
},
};
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Environmental interface. (version 3)
//!
//! This is the interface with which a smart contract is able to communicate
//! with the outside world through its sandbox boundaries.
mod api;
mod arithmetic;
mod backend;
pub mod call;
mod engine;
mod error;
pub mod hash;
mod types;
#[cfg(test)]
mod tests;
#[cfg(any(feature = "std", test, doc))]
#[doc(inline)]
pub use self::engine::off_chain::test_api as test;
use self::backend::{
Env,
TypedEnv,
};
pub use self::{
api::*,
backend::ReturnFlags,
error::{
EnvError,
Result,
},
hash::{
Blake2x128,
Blake2x256,
CryptoHash,
HashOutput,
Keccak256,
Sha2x256,
},
types::{
AccountId,
Clear,
DefaultEnvTypes,
EnvTypes,
Hash,
Topics,
},
};
// Copyright 2018-2020 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Docs
use crate::EnvTypes;
/// The concrete implementation that is guided by the topics builder.
///
/// To be implemented by the on-chain and off-chain environments respectively.
#[doc(hidden)]
pub trait TopicsBuilderBackend<E>
where
E: EnvTypes,
{
/// The type of the serialized event topics.
type Output;
/// Initialized the backend with the expected number of event topics.
fn expect(&mut self, expected_topics: usize);
/// Pushes another topic for serialization to the backend.
fn push_topic<T>(&mut self, topic_value: &T)
where
T: scale::Encode;
/// Extracts the serialized topics.
fn output(self) -> Self::Output;
}
/// Builder for event topic serialization.
///
/// Abstraction to build up event topic serialization with zero-overhead,
/// no heap-memory allocations and no dynamic dispatch.
#[doc(hidden)]
pub struct TopicsBuilder<S, E, B> {
backend: B,
state: core::marker::PhantomData<fn() -> (S, E)>,
}
impl<E, B> From<B> for TopicsBuilder<state::Uninit, E, B>
where
E: EnvTypes,
B: TopicsBuilderBackend<E>,
{
fn from(backend: B) -> Self {
Self {
backend,
state: Default::default(),
}
}
}
#[doc(hidden)]
pub mod state {
/// The topic builder is uninitialized and needs to be provided with the
/// expected number of topics that need to be constructed.
pub enum Uninit {}
/// There are some remaining topics that need to be provided with some values.
pub enum HasRemainingTopics {}
/// There are no more remaining topics and the topic builder shall be finalized.
pub enum NoRemainingTopics {}
}
impl<E, B> TopicsBuilder<state::Uninit, E, B>
where
E: EnvTypes,
B: TopicsBuilderBackend<E>,
{
/// Initializes the topics builder and informs it about how many topics it must expect to serialize.
///
/// The number of expected topics is given implicitely by the `E` type parameter.
pub fn build<Event: Topics>(