diff --git a/crates/e2e/macro/Cargo.toml b/crates/e2e/macro/Cargo.toml index 35513abe9a1..4f25307ea48 100644 --- a/crates/e2e/macro/Cargo.toml +++ b/crates/e2e/macro/Cargo.toml @@ -21,7 +21,7 @@ proc-macro = true [dependencies] ink_ir = { version = "4.2.0", path = "../../ink/ir" } cargo_metadata = "0.15.3" -contract-build = "2.0.2" +contract-build = { git = "https://github.com/paritytech/cargo-contract", branch = "aj/shared-events-metadata", package = "contract-build" } derive_more = "0.99.17" env_logger = "0.10.0" log = "0.4.17" diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index c1a9a660ff5..03cbb2c5eb9 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -178,7 +178,7 @@ where pub fn emit_event(event: Event) where E: Environment, - Event: Topics + scale::Encode, + Event: Topics + scale::Encode, { ::on_instance(|instance| { TypedEnvBackend::emit_event::(instance, event) diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index fb6b00350c1..eabf5185549 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -408,7 +408,7 @@ pub trait TypedEnvBackend: EnvBackend { fn emit_event(&mut self, event: Event) where E: Environment, - Event: Topics + scale::Encode; + Event: Topics + scale::Encode; /// Invokes a contract message and returns its result. /// diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 5dbdf6a7733..3cf7ace40f8 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -425,10 +425,10 @@ impl TypedEnvBackend for EnvInstance { fn emit_event(&mut self, event: Event) where E: Environment, - Event: Topics + scale::Encode, + Event: Topics + scale::Encode, { let builder = TopicsBuilder::default(); - let enc_topics = event.topics::(builder.into()); + let enc_topics = event.topics(builder.into()); let enc_data = &scale::Encode::encode(&event)[..]; self.engine.deposit_event(&enc_topics[..], enc_data); } diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index 69747bba5cd..3f3500d2ba9 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -395,10 +395,11 @@ impl TypedEnvBackend for EnvInstance { fn emit_event(&mut self, event: Event) where E: Environment, - Event: Topics + scale::Encode, + Event: Topics + scale::Encode, { - let (mut scope, enc_topics) = - event.topics::(TopicsBuilder::from(self.scoped_buffer()).into()); + let (mut scope, enc_topics) = event.topics( + TopicsBuilder::<::Env>::from(self.scoped_buffer()).into(), + ); let enc_data = scope.take_encoded(&event); ext::deposit_event(enc_topics, enc_data); } diff --git a/crates/env/src/topics.rs b/crates/env/src/topics.rs index a55eac11b65..a9716f2debb 100644 --- a/crates/env/src/topics.rs +++ b/crates/env/src/topics.rs @@ -81,12 +81,12 @@ where /// Initializes the topics builder and informs it about how many topics it must expect /// to serialize. /// - /// The number of expected topics is given implicitly by the `E` type parameter. - pub fn build( + /// The number of expected topics is given by the `TopicsAmount` type parameter. + pub fn build( mut self, - ) -> TopicsBuilder<::RemainingTopics, E, B> { + ) -> TopicsBuilder { self.backend - .expect(<::RemainingTopics as EventTopicsAmount>::AMOUNT); + .expect(::AMOUNT); TopicsBuilder { backend: self.backend, state: Default::default(), @@ -191,18 +191,16 @@ impl EventTopicsAmount for state::NoRemainingTopics { /// /// Normally this trait should be implemented automatically via the ink! codegen. pub trait Topics { - /// Type state indicating how many event topics are to be expected by the topics - /// builder. - type RemainingTopics: EventTopicsAmount; + /// The environment type. + type Env: Environment; /// Guides event topic serialization using the given topics builder. - fn topics( + fn topics( &self, - builder: TopicsBuilder, - ) -> >::Output + builder: TopicsBuilder, + ) -> >::Output where - E: Environment, - B: TopicsBuilderBackend; + B: TopicsBuilderBackend; } /// For each topic a hash is generated. This hash must be unique @@ -237,3 +235,78 @@ where self.value.encode_to(dest); } } + +use core::marker::PhantomData; + +/// Guards that an ink! event definitions respects the topic limit. +/// +/// # Usage +/// +/// ``` +/// // #[ink(event)] +/// pub struct ExampleEvent {} +/// +/// /// The amount of the topics of the example event struct. +/// const LEN_TOPICS: usize = 3; +/// +/// /// The limit for the amount of topics per ink! event definition. +/// const TOPICS_LIMIT: usize = 4; +/// +/// impl ::ink::codegen::EventLenTopics for ExampleEvent { +/// type LenTopics = ::ink::codegen::EventTopics; +/// } +/// +/// // The below code only compiles successfully if the example ink! event +/// // definitions respects the topic limitation: it must have an amount of +/// // topics less than or equal to the topic limit. +/// const _: () = ::ink::codegen::utils::consume_type::< +/// ::ink::codegen::EventRespectsTopicLimit, +/// >(); +/// ``` +pub struct EventRespectsTopicLimit +where + Event: EventLenTopics, + ::LenTopics: RespectTopicLimit, +{ + marker: PhantomData Event>, +} + +/// Guards that an amount of event topics respects the event topic limit. +/// +/// # Note +/// +/// Implemented by `EventTopics` if M is less or equal to N. +/// Automatically implemented for up to 12 event topics. +pub trait RespectTopicLimit {} + +/// Represents an the amount of topics for an ink! event definition. +pub struct EventTopics; + +macro_rules! impl_is_smaller_or_equals { + ( $first:literal $( , $rest:literal )* $(,)? ) => { + impl RespectTopicLimit<$first> for EventTopics<$first> {} + $( + impl RespectTopicLimit<$rest> for EventTopics<$first> {} + )* + + impl_is_smaller_or_equals! { $( $rest ),* } + }; + ( ) => {}; +} +impl_is_smaller_or_equals! { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 +} + +/// Stores the number of event topics of the ink! event definition. +pub trait EventLenTopics { + /// Type denoting the number of event topics. + /// + /// # Note + /// + /// We use an associated type instead of an associated constant here + /// because Rust does not yet allow for generics in constant parameter + /// position which would be required in the `EventRespectsTopicLimit` + /// trait definition. + /// As soon as this is possible in Rust we might change this to a constant. + type LenTopics; +} diff --git a/crates/ink/codegen/src/generator/event_def.rs b/crates/ink/codegen/src/generator/event_def.rs new file mode 100644 index 00000000000..5e78318e73d --- /dev/null +++ b/crates/ink/codegen/src/generator/event_def.rs @@ -0,0 +1,223 @@ +// Copyright 2018-2022 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. + +use crate::GenerateCode; + +use derive_more::From; +use proc_macro2::TokenStream as TokenStream2; +use quote::{ + quote, + quote_spanned, +}; + +/// Generates code for an event definition. +#[derive(From)] +pub struct EventDefinition<'a> { + event_def: &'a ir::InkEventDefinition, +} + +impl GenerateCode for EventDefinition<'_> { + fn generate_code(&self) -> TokenStream2 { + let event_enum = self.generate_event_enum(); + let event_info_impls = self.generate_event_info_impl(); + let event_variant_info_impls = self.generate_event_variant_info_impls(); + let event_metadata_impl = self.generate_event_metadata_impl(); + let topics_impl = self.generate_topics_impl(); + let topics_guard = self.generate_topics_guard(); + quote! { + #event_enum + #event_info_impls + #event_variant_info_impls + #event_metadata_impl + #topics_impl + #topics_guard + } + } +} + +impl<'a> EventDefinition<'a> { + fn generate_event_enum(&'a self) -> TokenStream2 { + let span = self.event_def.span(); + let event_enum = &self.event_def.item; + quote_spanned!(span => + #[derive(::scale::Encode, ::scale::Decode)] + #event_enum + ) + } + + fn generate_event_info_impl(&self) -> TokenStream2 { + let span = self.event_def.span(); + let event_ident = self.event_def.ident(); + + quote_spanned!(span=> + impl ::ink::reflect::EventInfo for #event_ident { + const PATH: &'static str = ::core::concat!( + ::core::module_path!(), + "::", + ::core::stringify!(#event_ident) + ); + } + ) + } + + fn generate_event_variant_info_impls(&self) -> TokenStream2 { + let span = self.event_def.span(); + let event_ident = self.event_def.ident(); + + let impls = self.event_def.variants().map(|ev| { + let event_variant_ident = ev.ident(); + let signature_topic = ev.signature_topic_hex_lits(event_ident); + let index = ev.index(); + quote_spanned!(span=> + impl ::ink::reflect::EventVariantInfo<#index> for #event_ident { + const NAME: &'static str = ::core::stringify!(#event_variant_ident); + const SIGNATURE_TOPIC: [u8; 32] = [ #( #signature_topic ),* ]; + } + ) + }); + quote_spanned!(span=> + #( + #impls + )* + ) + } + + fn generate_event_metadata_impl(&self) -> TokenStream2 { + let event_metadata = super::metadata::EventMetadata::from(self.event_def); + event_metadata.generate_code() + } + + /// Generate checks to guard against too many topics in event definitions. + fn generate_topics_guard(&self) -> TokenStream2 { + let span = self.event_def.span(); + let event_ident = self.event_def.ident(); + // todo: [AJ] check if event signature topic should be included here (it is now, + // wasn't before) + let len_topics = self.event_def.max_len_topics(); + let max_len_topics = quote_spanned!(span=> + <<#event_ident as ::ink::env::Topics>::Env + as ::ink::env::Environment>::MAX_EVENT_TOPICS + ); + quote_spanned!(span=> + impl ::ink::codegen::EventLenTopics for #event_ident { + type LenTopics = ::ink::codegen::EventTopics<#len_topics>; + } + + const _: () = ::ink::codegen::utils::consume_type::< + ::ink::codegen::EventRespectsTopicLimit< + #event_ident, + { #max_len_topics }, + > + >(); + ) + } + + fn generate_topics_impl(&self) -> TokenStream2 { + let span = self.event_def.span(); + let event_ident = self.event_def.ident(); + + let variant_match_arms = self + .event_def + .variants() + .map(|variant| { + let span = variant.span(); + let variant_ident = variant.ident(); + let (field_bindings, field_topics): (Vec<_>, Vec<_>) = variant.fields() + .filter(|field| field.is_topic) + .map(|field| { + let field_type = field.ty(); + let field_ident = field.ident(); + let push_topic = + quote_spanned!(span => + .push_topic::<::ink::env::topics::PrefixedValue<#field_type>>( + &::ink::env::topics::PrefixedValue { + // todo: figure out whether we even need to include a prefix here? + // Previously the prefix would be the full field path e.g. + // erc20::Event::Transfer::from + value. + // However the value on its own might be sufficient, albeit + // requiring combination with the signature topic and some + // metadata to determine whether a topic value belongs to a + // specific field of a given Event variant. The upside is that + // indexers can use the unhashed value for meaningful topics + // e.g. addresses < 32 bytes. If the prefix is included we + // will always require to hash the value so need any indexer + // would not be able to go from hash > address. + prefix: &[], + value: #field_ident, + } + ) + ); + let binding = quote_spanned!(span=> ref #field_ident); + (binding, push_topic) + }) + .unzip(); + + let index = variant.index(); + let event_signature_topic = match variant.anonymous() { + true => None, + false => { + Some(quote_spanned!(span=> + .push_topic::<::ink::env::topics::PrefixedValue<()>>( + &::ink::env::topics::PrefixedValue { + prefix: &<#event_ident as ::ink::reflect::EventVariantInfo<#index>>::SIGNATURE_TOPIC, + value: &(), + } + ) + )) + } + }; + + let remaining_topics_ty = match variant.len_topics() { + 0 => quote_spanned!(span=> ::ink::env::topics::state::NoRemainingTopics), + n => { + quote_spanned!(span=> [::ink::env::topics::state::HasRemainingTopics; #n]) + } + }; + + quote_spanned!(span=> + Self::#variant_ident { #( #field_bindings, )* .. } => { + builder + .build::<#remaining_topics_ty>() + #event_signature_topic + #( + #field_topics + )* + .finish() + } + ) + }); + + quote_spanned!(span => + const _: () = { + impl ::ink::env::Topics for #event_ident { + type Env = ::ink::env::DefaultEnvironment; // todo: configure environment? + + fn topics( + &self, + builder: ::ink::env::topics::TopicsBuilder<::ink::env::topics::state::Uninit, Self::Env, B>, + ) -> >::Output + where + B: ::ink::env::topics::TopicsBuilderBackend, + { + match self { + #( + #variant_match_arms + )* + } + } + } + }; + ) + } +} diff --git a/crates/ink/codegen/src/generator/events.rs b/crates/ink/codegen/src/generator/events.rs index 48f16b14ebe..77dcf055757 100644 --- a/crates/ink/codegen/src/generator/events.rs +++ b/crates/ink/codegen/src/generator/events.rs @@ -12,17 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::GenerateCode; -use derive_more::From; -use proc_macro2::{ - Span, - TokenStream as TokenStream2, -}; -use quote::{ - quote, - quote_spanned, +use crate::{ + generator::EventDefinition, + GenerateCode, }; -use syn::spanned::Spanned as _; +use derive_more::From; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; /// Generates code for the ink! event structs of the contract. #[derive(From)] @@ -37,265 +33,19 @@ impl GenerateCode for Events<'_> { // Generate no code in case there are no event definitions. return TokenStream2::new() } - let emit_event_trait_impl = self.generate_emit_event_trait_impl(); - let event_base = self.generate_event_base(); - let topic_guards = self.generate_topic_guards(); - let topics_impls = self.generate_topics_impls(); - let event_structs = self.generate_event_structs(); + let event_defs = self.generate_event_definitions(); quote! { - #emit_event_trait_impl - #event_base - #( #topic_guards )* - #( #event_structs )* - #( #topics_impls )* + #( #event_defs )* } } } impl<'a> Events<'a> { - /// Used to allow emitting user defined events directly instead of converting - /// them first into the automatically generated base trait of the contract. - fn generate_emit_event_trait_impl(&self) -> TokenStream2 { - let storage_ident = &self.contract.module().storage().ident(); - quote! { - const _: () = { - impl<'a> ::ink::codegen::EmitEvent<#storage_ident> for ::ink::EnvAccess<'a, Environment> { - fn emit_event(self, event: E) - where - E: Into<<#storage_ident as ::ink::reflect::ContractEventBase>::Type>, - { - ::ink::env::emit_event::< - Environment, - <#storage_ident as ::ink::reflect::ContractEventBase>::Type - >(event.into()); - } - } - }; - } - } - - /// Generates the base event enum that comprises all user defined events. - /// All emitted events are converted into a variant of this enum before being - /// serialized and emitted to apply their unique event discriminant (ID). - /// - /// # Developer Note - /// - /// The `__ink_dylint_EventBase` config attribute is used here to convey the - /// information that the generated enum is an ink! event to `dylint`. - fn generate_event_base(&self) -> TokenStream2 { - let storage_ident = &self.contract.module().storage().ident(); - let event_idents = self - .contract - .module() - .events() - .map(|event| event.ident()) - .collect::>(); - let event_idents_cfgs = self - .contract - .module() - .events() - .map(|event| event.get_cfg_attrs(event.span())) - .collect::>(); - let base_event_ident = - proc_macro2::Ident::new("__ink_EventBase", Span::call_site()); - quote! { - #[allow(non_camel_case_types)] - #[derive(::scale::Encode, ::scale::Decode)] - #[cfg(not(feature = "__ink_dylint_EventBase"))] - pub enum #base_event_ident { - #( - #( #event_idents_cfgs )* - #event_idents(#event_idents), - )* - } - - const _: () = { - impl ::ink::reflect::ContractEventBase for #storage_ident { - type Type = #base_event_ident; - } - }; - - #( - #( #event_idents_cfgs )* - const _: () = { - impl From<#event_idents> for #base_event_ident { - fn from(event: #event_idents) -> Self { - Self::#event_idents(event) - } - } - }; - )* - - const _: () = { - pub enum __ink_UndefinedAmountOfTopics {} - impl ::ink::env::topics::EventTopicsAmount for __ink_UndefinedAmountOfTopics { - const AMOUNT: usize = 0; - } - - impl ::ink::env::Topics for #base_event_ident { - type RemainingTopics = __ink_UndefinedAmountOfTopics; - - fn topics( - &self, - builder: ::ink::env::topics::TopicsBuilder<::ink::env::topics::state::Uninit, E, B>, - ) -> >::Output - where - E: ::ink::env::Environment, - B: ::ink::env::topics::TopicsBuilderBackend, - { - match self { - #( - #( #event_idents_cfgs )* - Self::#event_idents(event) => { - <#event_idents as ::ink::env::Topics>::topics::(event, builder) - } - )*, - _ => panic!("Event does not exist!") - } - } - } - }; - } - } - - /// Generate checks to guard against too many topics in event definitions. - fn generate_topics_guard(&self, event: &ir::Event) -> TokenStream2 { - let span = event.span(); - let storage_ident = self.contract.module().storage().ident(); - let event_ident = event.ident(); - let cfg_attrs = event.get_cfg_attrs(span); - let len_topics = event.fields().filter(|event| event.is_topic).count(); - let max_len_topics = quote_spanned!(span=> - <<#storage_ident as ::ink::env::ContractEnv>::Env - as ::ink::env::Environment>::MAX_EVENT_TOPICS - ); - quote_spanned!(span=> - #( #cfg_attrs )* - impl ::ink::codegen::EventLenTopics for #event_ident { - type LenTopics = ::ink::codegen::EventTopics<#len_topics>; - } - - #( #cfg_attrs )* - const _: () = ::ink::codegen::utils::consume_type::< - ::ink::codegen::EventRespectsTopicLimit< - #event_ident, - { #max_len_topics }, - > - >(); - ) - } - - /// Generates the guard code that protects against having too many topics defined on - /// an ink! event. - fn generate_topic_guards(&'a self) -> impl Iterator + 'a { - self.contract.module().events().map(move |event| { - let span = event.span(); - let topics_guard = self.generate_topics_guard(event); - quote_spanned!(span => - #topics_guard - ) - }) - } - /// Generates the `Topics` trait implementations for the user defined events. - fn generate_topics_impls(&'a self) -> impl Iterator + 'a { - let contract_ident = self.contract.module().storage().ident(); - self.contract.module().events().map(move |event| { - let span = event.span(); - let event_ident = event.ident(); - let event_signature = syn::LitByteStr::new( - format!("{contract_ident}::{event_ident}" - ).as_bytes(), span); - let len_event_signature = event_signature.value().len(); - let len_topics = event.fields().filter(|field| field.is_topic).count(); - let topic_impls = event - .fields() - .enumerate() - .filter(|(_, field)| field.is_topic) - .map(|(n, topic_field)| { - let span = topic_field.span(); - let field_ident = topic_field - .ident() - .map(quote::ToTokens::into_token_stream) - .unwrap_or_else(|| quote_spanned!(span => #n)); - let field_type = topic_field.ty(); - let signature = syn::LitByteStr::new( - format!("{contract_ident}::{event_ident}::{field_ident}" - ).as_bytes(), span); - quote_spanned!(span => - .push_topic::<::ink::env::topics::PrefixedValue<#field_type>>( - &::ink::env::topics::PrefixedValue { value: &self.#field_ident, prefix: #signature } - ) - ) - }); - // Only include topic for event signature in case of non-anonymous event. - let event_signature_topic = match event.anonymous { - true => None, - false => Some(quote_spanned!(span=> - .push_topic::<::ink::env::topics::PrefixedValue<[u8; #len_event_signature]>>( - &::ink::env::topics::PrefixedValue { value: #event_signature, prefix: b"" } - ) - )) - }; - // Anonymous events require 1 fewer topics since they do not include their signature. - let anonymous_topics_offset = usize::from(!event.anonymous); - let remaining_topics_ty = match len_topics + anonymous_topics_offset { - 0 => quote_spanned!(span=> ::ink::env::topics::state::NoRemainingTopics), - n => quote_spanned!(span=> [::ink::env::topics::state::HasRemainingTopics; #n]), - }; - let cfg_attrs = event.get_cfg_attrs(span); - quote_spanned!(span => - #( #cfg_attrs )* - const _: () = { - impl ::ink::env::Topics for #event_ident { - type RemainingTopics = #remaining_topics_ty; - - fn topics( - &self, - builder: ::ink::env::topics::TopicsBuilder<::ink::env::topics::state::Uninit, E, B>, - ) -> >::Output - where - E: ::ink::env::Environment, - B: ::ink::env::topics::TopicsBuilderBackend, - { - builder - .build::() - #event_signature_topic - #( - #topic_impls - )* - .finish() - } - } - }; - ) - }) - } - - /// Generates all the user defined event struct definitions. - fn generate_event_structs(&'a self) -> impl Iterator + 'a { + fn generate_event_definitions(&'a self) -> impl Iterator + 'a { self.contract.module().events().map(move |event| { - let span = event.span(); - let ident = event.ident(); - let attrs = event.attrs(); - let fields = event.fields().map(|event_field| { - let span = event_field.span(); - let attrs = event_field.attrs(); - let vis = event_field.vis(); - let ident = event_field.ident(); - let ty = event_field.ty(); - quote_spanned!(span=> - #( #attrs )* - #vis #ident : #ty - ) - }); - quote_spanned!(span => - #( #attrs )* - #[derive(scale::Encode, scale::Decode)] - pub struct #ident { - #( #fields ),* - } - ) + let event_def_gen = EventDefinition::from(event); + event_def_gen.generate_code() }) } } diff --git a/crates/ink/codegen/src/generator/item_impls.rs b/crates/ink/codegen/src/generator/item_impls.rs index ca472091c88..ab4f2395032 100644 --- a/crates/ink/codegen/src/generator/item_impls.rs +++ b/crates/ink/codegen/src/generator/item_impls.rs @@ -46,16 +46,10 @@ impl GenerateCode for ItemImpls<'_> { .map(|item_impl| self.generate_item_impl(item_impl)); let inout_guards = self.generate_input_output_guards(); let trait_message_property_guards = self.generate_trait_message_property_guards(); - let use_emit_event = - self.contract.module().events().next().is_some().then(|| { - // Required to make `self.env().emit_event(...)` syntax available. - quote! { use ::ink::codegen::EmitEvent as _; } - }); quote! { const _: () = { // Required to make `self.env()` and `Self::env()` syntax available. use ::ink::codegen::{Env as _, StaticEnv as _}; - #use_emit_event #( #item_impls )* #inout_guards diff --git a/crates/ink/codegen/src/generator/metadata/event.rs b/crates/ink/codegen/src/generator/metadata/event.rs new file mode 100644 index 00000000000..487efdcea5c --- /dev/null +++ b/crates/ink/codegen/src/generator/metadata/event.rs @@ -0,0 +1,114 @@ +// Copyright 2018-2022 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. + +use crate::GenerateCode; + +use derive_more::From; +use ir::IsDocAttribute; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote_spanned; + +/// Generates code for an event definition. +#[derive(From)] +pub struct EventMetadata<'a> { + event_def: &'a ir::InkEventDefinition, +} + +impl GenerateCode for EventMetadata<'_> { + fn generate_code(&self) -> TokenStream2 { + let span = self.event_def.span(); + let event_ident = self.event_def.ident(); + let docs = self + .event_def + .attrs() + .iter() + .filter_map(|attr| attr.extract_docs()); + let variants = self.event_def.variants().map(|ev| { + let span = ev.span(); + let label = ev.ident(); + + let args = ev.fields().map(|event_field| { + let span = event_field.span(); + let ident = event_field.ident(); + let is_topic = event_field.is_topic; + let docs = event_field + .attrs() + .into_iter() + .filter_map(|attr| attr.extract_docs()); + let ty = super::generate_type_spec(event_field.ty()); + quote_spanned!(span => + ::ink::metadata::EventParamSpec::new(::core::stringify!(#ident)) + .of_type(#ty) + .indexed(#is_topic) + .docs([ + #( #docs ),* + ]) + .done() + ) + }); + + let docs = ev + .attrs() + .iter() + .filter_map(|attr| attr.extract_docs()) + .collect::>(); + + quote_spanned!(span=> + ::ink::metadata::EventVariantSpec::new(::core::stringify!(#label)) + .args([ + #( #args ),* + ]) + .docs([ + #( #docs ),* + ]) + .done() + ) + }); + + let unique_id = self.event_def.unique_id(); + let hex = impl_serde::serialize::to_hex(&unique_id, true); + let event_metadata_fn = quote::format_ident!("__ink_event_metadata_{}", hex); + + quote_spanned!(span=> + #[cfg(not(feature = "std"))] + #[link_section = stringify!(#event_metadata_fn)] + /// This adds a custom section to the unoptimized Wasm, the name of which + /// is used by `cargo-contract` to discover the extern function to get this + /// events metadata. + pub static __INK_EVENT_METADATA: u32 = 0; + + #[cfg(feature = "std")] + #[cfg(not(feature = "ink-as-dependency"))] + const _: () = { + #[no_mangle] + pub fn #event_metadata_fn () -> ::ink::metadata::EventSpec { + < #event_ident as ::ink::metadata::EventMetadata >::event_spec() + } + + impl ::ink::metadata::EventMetadata for #event_ident { + fn event_spec() -> ::ink::metadata::EventSpec { + ::ink::metadata::EventSpec::new(<#event_ident as ::ink::reflect::EventInfo>::PATH) + .variants([ + #( #variants ),* + ]) + .docs([ + #( #docs ),* + ]) + .done() + } + } + }; + ) + } +} diff --git a/crates/ink/codegen/src/generator/metadata.rs b/crates/ink/codegen/src/generator/metadata/mod.rs similarity index 85% rename from crates/ink/codegen/src/generator/metadata.rs rename to crates/ink/codegen/src/generator/metadata/mod.rs index 0dac7172326..d8beeebeaa2 100644 --- a/crates/ink/codegen/src/generator/metadata.rs +++ b/crates/ink/codegen/src/generator/metadata/mod.rs @@ -12,6 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod event; + +pub use event::EventMetadata; + use crate::GenerateCode; use ::core::iter; use derive_more::From; @@ -43,7 +47,18 @@ impl_as_ref_for_generator!(Metadata); impl GenerateCode for Metadata<'_> { fn generate_code(&self) -> TokenStream2 { - let contract = self.generate_contract(); + let constructors = self.generate_constructors(); + let messages = self.generate_messages(); + let docs = self + .contract + .module() + .attrs() + .iter() + .filter_map(|attr| attr.extract_docs()); + let error_ty = syn::parse_quote! { ::ink::LangError }; + let error = generate_type_spec(&error_ty); + let environment = self.generate_environment(); + let layout = self.generate_layout(); quote! { @@ -51,12 +66,35 @@ impl GenerateCode for Metadata<'_> { #[cfg(not(feature = "ink-as-dependency"))] const _: () = { #[no_mangle] - pub fn __ink_generate_metadata() -> ::ink::metadata::InkProject { + pub fn __ink_generate_metadata( + events: ::ink::prelude::vec::Vec<::ink::metadata::EventSpec> + ) -> ::ink::metadata::InkProject { let layout = #layout; ::ink::metadata::layout::ValidateLayout::validate(&layout).unwrap_or_else(|error| { ::core::panic!("metadata ink! generation failed: {}", error) }); - ::ink::metadata::InkProject::new(layout, #contract) + let contract = + ::ink::metadata::ContractSpec::new() + .constructors([ + #( #constructors ),* + ]) + .messages([ + #( #messages ),* + ]) + .events( + events + ) + .docs([ + #( #docs ),* + ]) + .lang_error( + #error + ) + .environment( + #environment + ) + .done(); + ::ink::metadata::InkProject::new(layout, contract) } }; } @@ -85,45 +123,6 @@ impl Metadata<'_> { ) } - fn generate_contract(&self) -> TokenStream2 { - let constructors = self.generate_constructors(); - let messages = self.generate_messages(); - let events = self.generate_events(); - let docs = self - .contract - .module() - .attrs() - .iter() - .filter_map(|attr| attr.extract_docs()); - let error_ty = syn::parse_quote! { - ::ink::LangError - }; - let error = Self::generate_type_spec(&error_ty); - let environment = self.generate_environment(); - quote! { - ::ink::metadata::ContractSpec::new() - .constructors([ - #( #constructors ),* - ]) - .messages([ - #( #messages ),* - ]) - .events([ - #( #events ),* - ]) - .docs([ - #( #docs ),* - ]) - .lang_error( - #error - ) - .environment( - #environment - ) - .done() - } - } - /// Generates ink! metadata for all ink! smart contract constructors. #[allow(clippy::redundant_closure)] // We are getting arcane lifetime errors otherwise. fn generate_constructors(&self) -> impl Iterator + '_ { @@ -179,7 +178,7 @@ impl Metadata<'_> { syn::Pat::Ident(ident) => &ident.ident, _ => unreachable!("encountered ink! dispatch input with missing identifier"), }; - let type_spec = Self::generate_type_spec(&pat_type.ty); + let type_spec = generate_type_spec(&pat_type.ty); quote! { ::ink::metadata::MessageParamSpec::new(::core::stringify!(#ident)) .of_type(#type_spec) @@ -219,6 +218,7 @@ impl Metadata<'_> { } } + /// Generates the ink! metadata for all ink! smart contract messages. fn generate_messages(&self) -> Vec { let mut messages = Vec::new(); @@ -343,7 +343,7 @@ impl Metadata<'_> { } } Some(ty) => { - let type_spec = Self::generate_type_spec(ty); + let type_spec = generate_type_spec(ty); quote! { ::ink::metadata::ReturnTypeSpec::new(#type_spec) } @@ -374,51 +374,6 @@ impl Metadata<'_> { ) } - /// Generates ink! metadata for all user provided ink! event definitions. - fn generate_events(&self) -> impl Iterator + '_ { - self.contract.module().events().map(|event| { - let span = event.span(); - let ident = event.ident(); - let docs = event.attrs().iter().filter_map(|attr| attr.extract_docs()); - let args = Self::generate_event_args(event); - let cfg_attrs = event.get_cfg_attrs(span); - quote_spanned!(span => - #( #cfg_attrs )* - ::ink::metadata::EventSpec::new(::core::stringify!(#ident)) - .args([ - #( #args ),* - ]) - .docs([ - #( #docs ),* - ]) - .done() - ) - }) - } - - /// Generate ink! metadata for a single argument of an ink! event definition. - fn generate_event_args(event: &ir::Event) -> impl Iterator + '_ { - event.fields().map(|event_field| { - let span = event_field.span(); - let ident = event_field.ident(); - let is_topic = event_field.is_topic; - let docs = event_field - .attrs() - .into_iter() - .filter_map(|attr| attr.extract_docs()); - let ty = Self::generate_type_spec(event_field.ty()); - quote_spanned!(span => - ::ink::metadata::EventParamSpec::new(::core::stringify!(#ident)) - .of_type(#ty) - .indexed(#is_topic) - .docs([ - #( #docs ),* - ]) - .done() - ) - }) - } - fn generate_environment(&self) -> TokenStream2 { let span = self.contract.module().span(); @@ -449,6 +404,38 @@ impl Metadata<'_> { } } +/// Generates the ink! metadata for the given type. +fn generate_type_spec(ty: &syn::Type) -> TokenStream2 { + fn without_display_name(ty: &syn::Type) -> TokenStream2 { + quote! { ::ink::metadata::TypeSpec::of_type::<#ty>() } + } + + if let syn::Type::Path(type_path) = ty { + if type_path.qself.is_some() { + return without_display_name(ty) + } + let path = &type_path.path; + if path.segments.is_empty() { + return without_display_name(ty) + } + let segs = path + .segments + .iter() + .map(|seg| &seg.ident) + .collect::>(); + quote! { + ::ink::metadata::TypeSpec::with_name_segs::<#ty, _>( + ::core::iter::Iterator::map( + ::core::iter::IntoIterator::into_iter([ #( ::core::stringify!(#segs) ),* ]), + ::core::convert::AsRef::as_ref + ) + ) + } + } else { + without_display_name(ty) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/ink/codegen/src/generator/mod.rs b/crates/ink/codegen/src/generator/mod.rs index 960a1b58dd7..86a1f049dbf 100644 --- a/crates/ink/codegen/src/generator/mod.rs +++ b/crates/ink/codegen/src/generator/mod.rs @@ -33,6 +33,7 @@ mod chain_extension; mod contract; mod dispatch; mod env; +mod event_def; mod events; mod ink_test; mod item_impls; @@ -58,6 +59,7 @@ pub use self::{ contract::Contract, dispatch::Dispatch, env::Env, + event_def::EventDefinition, events::Events, ink_test::InkTest, item_impls::ItemImpls, diff --git a/crates/ink/codegen/src/generator/storage.rs b/crates/ink/codegen/src/generator/storage.rs index a69c05648ab..cdc47776815 100644 --- a/crates/ink/codegen/src/generator/storage.rs +++ b/crates/ink/codegen/src/generator/storage.rs @@ -33,12 +33,6 @@ impl GenerateCode for Storage<'_> { let storage_span = self.contract.module().storage().span(); let access_env_impls = self.generate_access_env_trait_impls(); let storage_struct = self.generate_storage_struct(); - let use_emit_event = - self.contract.module().events().next().is_some().then(|| { - // Required to allow for `self.env().emit_event(...)` in messages and - // constructors. - quote! { use ::ink::codegen::EmitEvent as _; } - }); quote_spanned!(storage_span => #storage_struct #access_env_impls @@ -50,7 +44,6 @@ impl GenerateCode for Storage<'_> { Env as _, StaticEnv as _, }; - #use_emit_event }; ) } diff --git a/crates/ink/codegen/src/lib.rs b/crates/ink/codegen/src/lib.rs index b5d9631c077..d41a748880c 100644 --- a/crates/ink/codegen/src/lib.rs +++ b/crates/ink/codegen/src/lib.rs @@ -62,6 +62,10 @@ impl<'a> CodeGenerator for &'a ir::StorageItem { type Generator = generator::StorageItem<'a>; } +impl<'a> CodeGenerator for &'a ir::InkEventDefinition { + type Generator = generator::EventDefinition<'a>; +} + impl<'a> CodeGenerator for &'a ir::InkTraitDefinition { type Generator = generator::TraitDefinition<'a>; } diff --git a/crates/ink/ir/src/ir/event_def.rs b/crates/ink/ir/src/ir/event_def.rs new file mode 100644 index 00000000000..1ce0d085650 --- /dev/null +++ b/crates/ink/ir/src/ir/event_def.rs @@ -0,0 +1,562 @@ +// Copyright 2018-2022 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. + +use crate::{ + blake2b_256, + error::ExtError as _, + ir, + literal::HexLiteral, +}; +use proc_macro2::{ + Ident, + Span, + TokenStream as TokenStream2, +}; +use quote::ToTokens; +use syn::{ + spanned::Spanned as _, + Result, +}; + +/// An ink! event enum definition. +#[derive(Debug, PartialEq, Eq)] +pub struct InkEventDefinition { + pub item: syn::ItemEnum, + variants: Vec, +} + +impl TryFrom for InkEventDefinition { + type Error = syn::Error; + + fn try_from(mut item_enum: syn::ItemEnum) -> Result { + if !matches!(item_enum.vis, syn::Visibility::Public(_)) { + return Err(format_err_spanned!( + item_enum.vis, + "ink! event enum definitions must be declared as `pub`" + )) + } + if item_enum.variants.is_empty() { + return Err(format_err_spanned!( + item_enum, + "ink! event enum definitions must have at least one variant" + )) + } + + let mut variants = Vec::new(); + for (index, variant) in item_enum.variants.iter_mut().enumerate() { + let mut fields = Vec::new(); + let (ink_attrs, other_attrs) = ir::sanitize_optional_attributes( + variant.span(), + variant.attrs.clone(), + |arg| { + match arg.kind() { + ir::AttributeArg::Anonymous => Ok(()), + _ => Err(None), + } + }, + )?; + // strip out the `#[ink(anonymous)] attributes, since the item will be used to + // regenerate the event enum + variant.attrs = other_attrs; + let anonymous = ink_attrs.map_or(false, |attrs| attrs.is_anonymous()); + for (_index, field) in variant.fields.iter_mut().enumerate() { + let field_span = field.span(); + let (ink_attrs, other_attrs) = + ir::partition_attributes(field.attrs.clone())?; + + let is_topic = if ink_attrs.is_empty() { + false + } else { + let normalized = + ir::InkAttribute::from_expanded(ink_attrs).map_err(|err| { + err.into_combine(format_err!( + field_span, + "at this invocation", + )) + })?; + let mut topic_attr = false; + + for arg in normalized.args() { + match (topic_attr, arg.kind()) { + (false, ir::AttributeArg::Topic) => topic_attr = true, + (true, ir::AttributeArg::Topic) => return Err(format_err!( + arg.span(), + "encountered conflicting ink! attribute for event field", + )), + (_, _) => return Err(format_err!( + field_span, + "only the #[ink(topic)] attribute is supported for event fields", + )), + } + } + topic_attr + }; + + let ident = field.ident.clone().unwrap_or_else(|| { + panic!("FIELDS SHOULD HAVE A NAME {:?}", field.ident) + }); + // .unwrap_or(quote::format_ident!("{}", index)); // todo: should it also + // handle tuple variants? This breaks strip out the + // `#[ink(topic)] attributes, since the item will be used to + // regenerate the event enum + field.attrs = other_attrs; + fields.push(EventField { + is_topic, + field: field.clone(), + ident: ident.clone(), + }) + } + let named_fields = matches!(variant.fields, syn::Fields::Named(_)); + variants.push(EventVariant { + index, + item: variant.clone(), + named_fields, + fields, + anonymous, + }) + } + Ok(Self { + item: item_enum, + variants, + }) + } +} + +impl quote::ToTokens for InkEventDefinition { + /// We mainly implement this trait for this ink! type to have a derived + /// [`Spanned`](`syn::spanned::Spanned`) implementation for it. + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + self.item.to_tokens(tokens) + } +} + +impl InkEventDefinition { + /// Create an [`InkEventDefinition`] for a event defined externally to a contract. + /// + /// This will be an enum annotated with the `#[ink::event_definition]` attribute. + pub fn from_event_def_tokens( + config: TokenStream2, + input: TokenStream2, + ) -> Result { + let attr_span = config.span(); + // todo: remove this, or do we need config attrs? + // let _parsed_config = syn::parse2::(config)?; + let item = syn::parse2::(input).map_err(|err| { + err.into_combine(format_err!( + attr_span, + "ink! event definitions must be enums" + )) + })?; + Self::try_from(item) + } + + /// Returns the identifier of the event struct. + pub fn ident(&self) -> &Ident { + &self.item.ident + } + + pub fn span(&self) -> Span { + self.item.span() + } + + /// Returns all non-ink! attributes. + pub fn attrs(&self) -> &[syn::Attribute] { + &self.item.attrs + } + + /// Returns all event variants. + pub fn variants(&self) -> impl Iterator { + self.variants.iter() + } + + /// Returns the maximum number of topics of any event variant. + pub fn max_len_topics(&self) -> usize { + self.variants() + .map(|v| v.len_topics()) + .max() + .unwrap_or_default() + } + + /// Returns a unique identifier for this event definition. + /// + /// **Note:** this only needs to be unique within the context of a contract binary. + pub fn unique_id(&self) -> [u8; 32] { + let item_enum = &self.item; + let event_def_str = quote::quote!( #item_enum ).to_string(); + let mut output = [0u8; 32]; + blake2b_256(event_def_str.as_bytes(), &mut output); + output + } +} + +/// A variant of an event. +#[derive(Debug, PartialEq, Eq)] +pub struct EventVariant { + index: usize, + item: syn::Variant, + named_fields: bool, + fields: Vec, + anonymous: bool, +} + +impl EventVariant { + /// Returns the span of the event variant. + pub fn span(&self) -> Span { + self.item.span() + } + + /// Returns all non-ink! attributes of the event variant. + pub fn attrs(&self) -> Vec { + let (_, non_ink_attrs) = ir::partition_attributes(self.item.attrs.clone()) + .expect("encountered invalid event field attributes"); + non_ink_attrs + } + + /// The identifier of the event variant. + pub fn ident(&self) -> &Ident { + &self.item.ident + } + + /// The index of the the event variant in the enum definition. + pub fn index(&self) -> usize { + self.index + } + + /// Returns an iterator yielding all the fields of the event variant struct. + pub fn fields(&self) -> impl Iterator { + self.fields.iter() + } + + /// Returns true if the signature of the event variant should *not* be indexed by a + /// topic. + pub fn anonymous(&self) -> bool { + self.anonymous + } + + /// The number of topics of this event variant. + pub fn len_topics(&self) -> usize { + let topics_len = self.fields().filter(|event| event.is_topic).count(); + if self.anonymous { + topics_len + } else { + topics_len + 1usize + } + } + + /// The signature topic of an event variant. + /// + /// Calculated with `blake2b("EventEnum::EventVariant(field1_type,field2_type)")`. + pub fn signature_topic(&self, event_ident: &Ident) -> [u8; 32] { + let fields = self + .fields() + .map(|event_field| { + event_field + .field + .ty + .to_token_stream() + .to_string() + .replace(" ", "") + }) + .collect::>() + .join(","); + let topic_str = format!("{}::{}({fields})", event_ident, self.ident()); + let input = topic_str.as_bytes(); + let mut output = [0; 32]; + blake2b_256(&input, &mut output); + output + } + + /// The signature topic literal of an event variant. + /// + /// Calculated with `blake2b("EventEnum::EventVariant(field1_type,field2_type)")`. + pub fn signature_topic_hex_lits(&self, event_ident: &Ident) -> [syn::LitInt; 32] { + self.signature_topic(event_ident) + .map(::hex_padded_suffixed) + } +} + +/// An event field with a flag indicating if this field is an event topic. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EventField { + /// The associated `field` is an event topic if this is `true`. + pub is_topic: bool, + /// The event field. + field: syn::Field, + /// The event field ident. + ident: syn::Ident, +} + +impl EventField { + /// Returns the span of the event field. + pub fn span(&self) -> Span { + self.field.span() + } + + /// Returns all non-ink! attributes of the event field. + pub fn attrs(&self) -> Vec { + let (_, non_ink_attrs) = ir::partition_attributes(self.field.attrs.clone()) + .expect("encountered invalid event field attributes"); + non_ink_attrs + } + + /// Returns the visibility of the event field. + pub fn vis(&self) -> &syn::Visibility { + &self.field.vis + } + + /// Returns the identifier of the event field if any. + pub fn ident(&self) -> &Ident { + &self.ident + } + + /// Returns the type of the event field. + pub fn ty(&self) -> &syn::Type { + &self.field.ty + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn simple_try_from_works() { + let item_enum: syn::ItemEnum = syn::parse_quote! { + #[ink::event_definition] + pub enum MyEvent { + Event { + #[ink(topic)] + field_1: i32, + field_2: bool, + } + } + }; + assert!(InkEventDefinition::try_from(item_enum).is_ok()); + } + + fn assert_try_from_fails(item_enum: syn::ItemEnum, expected: &str) { + assert_eq!( + InkEventDefinition::try_from(item_enum).map_err(|err| err.to_string()), + Err(expected.to_string()) + ) + } + + #[test] + fn non_pub_event_struct() { + assert_try_from_fails( + syn::parse_quote! { + #[ink::event_definition] + enum PrivateEvent { + Event { + #[ink(topic)] + field_1: i32, + field_2: bool, + } + } + }, + "ink! event enum definitions must be declared as `pub`", + ) + } + + #[test] + fn duplicate_field_attributes_fails() { + assert_try_from_fails( + syn::parse_quote! { + #[ink::event_definition] + pub enum MyEvent { + Event { + #[ink(topic)] + #[ink(topic)] + field_1: i32, + field_2: bool, + } + } + }, + "encountered duplicate ink! attribute", + ) + } + + #[test] + fn invalid_field_attributes_fails() { + assert_try_from_fails( + syn::parse_quote! { + #[ink::event_definition] + pub enum MyEvent { + Event { + #[ink(message)] + field_1: i32, + field_2: bool, + } + } + }, + "only the #[ink(topic)] attribute is supported for event fields", + ) + } + + #[test] + fn conflicting_field_attributes_fails() { + assert_try_from_fails( + syn::parse_quote! { + #[ink::event_definition] + pub enum MyEvent { + Event { + #[ink(topic)] + #[ink(payable)] + field_1: i32, + field_2: bool, + } + } + }, + "only the #[ink(topic)] attribute is supported for event fields", + ) + } + + /// Used for the event fields iterator unit test because `syn::Field` does + /// not provide a `syn::parse::Parse` implementation. + #[derive(Debug, PartialEq, Eq)] + struct NamedField(syn::Field); + + impl syn::parse::Parse for NamedField { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + Ok(Self(syn::Field::parse_named(input)?)) + } + } + + impl NamedField { + /// Returns the identifier of the named field. + pub fn ident(&self) -> &Ident { + self.0.ident.as_ref().unwrap() + } + + /// Returns the type of the named field. + pub fn ty(&self) -> &syn::Type { + &self.0.ty + } + } + + #[test] + fn event_fields_iter_works() { + let expected_fields: Vec<(bool, NamedField)> = vec![ + ( + true, + syn::parse_quote! { + field_1: i32 + }, + ), + ( + false, + syn::parse_quote! { + field_2: u64 + }, + ), + ( + true, + syn::parse_quote! { + field_3: [u8; 32] + }, + ), + ]; + let event_def = + >::try_from(syn::parse_quote! { + #[ink::event_definition] + pub enum MyEvent { + Event { + #[ink(topic)] + field_1: i32, + field_2: u64, + #[ink(topic)] + field_3: [u8; 32], + } + } + }) + .unwrap(); + let event_variant = event_def.variants().next().expect("Event variant"); + let mut fields_iter = event_variant.fields(); + for (is_topic, expected_field) in expected_fields { + let field = fields_iter.next().unwrap(); + assert_eq!(field.is_topic, is_topic); + assert_eq!(field.ident(), expected_field.ident()); + assert_eq!(field.ty(), expected_field.ty()); + } + } + + #[test] + fn anonymous_event_works() { + fn assert_anonymous_event(event: syn::ItemEnum) { + match InkEventDefinition::try_from(event) { + Ok(event) => { + assert!(event.variants[0].anonymous); + } + Err(_) => panic!("encountered unexpected invalid anonymous event"), + } + } + assert_anonymous_event(syn::parse_quote! { + #[ink::event_definition] + pub enum MyEvent { + #[ink(anonymous)] + Event { + #[ink(topic)] + field_1: i32, + field_2: bool, + } + } + }); + } + + #[test] + fn event_variant_signature_topics() { + let event_def = + >::try_from(syn::parse_quote! { + #[ink::event_definition] + pub enum MyEvent { + EventA { + field_1: i32, + field_2: u64, + field_3: [u8; 32], + }, + EventB {}, + EventC { + field_1: (i32, u64), + } + } + }) + .unwrap(); + + let mut variants = event_def.variants(); + + fn signature_topic(input: &str) -> [u8; 32] { + let mut output = [0; 32]; + blake2b_256(input.as_bytes(), &mut output); + output + } + + let event_variant = variants.next().expect("EventA variant"); + assert_eq!( + event_variant.signature_topic(event_def.ident()), + signature_topic("MyEvent::EventA(i32,u64,[u8;32])") + ); + + let event_variant = variants.next().expect("EventB variant"); + assert_eq!( + event_variant.signature_topic(event_def.ident()), + signature_topic("MyEvent::EventB()") + ); + + let event_variant = variants.next().expect("EventC variant"); + assert_eq!( + event_variant.signature_topic(event_def.ident()), + signature_topic("MyEvent::EventC((i32,u64))") + ); + } +} diff --git a/crates/ink/ir/src/ir/item/event.rs b/crates/ink/ir/src/ir/item/event.rs deleted file mode 100644 index 476c6d40b70..00000000000 --- a/crates/ink/ir/src/ir/item/event.rs +++ /dev/null @@ -1,517 +0,0 @@ -// Copyright 2018-2022 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. - -use crate::{ - error::ExtError as _, - ir::{ - self, - utils, - utils::extract_cfg_attributes, - CFG_IDENT, - }, -}; -use proc_macro2::{ - Ident, - Span, - TokenStream, -}; -use syn::spanned::Spanned as _; - -/// An ink! event struct definition. -/// -/// # Example -/// -/// ``` -/// # let event = >::try_from(syn::parse_quote! { -/// #[ink(event)] -/// pub struct Transaction { -/// #[ink(topic)] -/// from: AccountId, -/// #[ink(topic)] -/// to: AccountId, -/// value: Balance, -/// } -/// # }).unwrap(); -/// ``` -#[derive(Debug, PartialEq, Eq)] -pub struct Event { - item: syn::ItemStruct, - pub anonymous: bool, -} - -impl quote::ToTokens for Event { - /// We mainly implement this trait for this ink! type to have a derived - /// [`Spanned`](`syn::spanned::Spanned`) implementation for it. - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - self.item.to_tokens(tokens) - } -} - -impl Event { - /// Returns `true` if the first ink! annotation on the given struct is - /// `#[ink(event)]`. - /// - /// # Errors - /// - /// If the first found ink! attribute is malformed. - pub(super) fn is_ink_event( - item_struct: &syn::ItemStruct, - ) -> Result { - if !ir::contains_ink_attributes(&item_struct.attrs) { - return Ok(false) - } - // At this point we know that there must be at least one ink! - // attribute. This can be either the ink! storage struct, - // an ink! event or an invalid ink! attribute. - let attr = ir::first_ink_attribute(&item_struct.attrs)? - .expect("missing expected ink! attribute for struct"); - Ok(matches!(attr.first().kind(), ir::AttributeArg::Event)) - } -} - -impl TryFrom for Event { - type Error = syn::Error; - - fn try_from(item_struct: syn::ItemStruct) -> Result { - let struct_span = item_struct.span(); - let (ink_attrs, other_attrs) = ir::sanitize_attributes( - struct_span, - item_struct.attrs, - &ir::AttributeArgKind::Event, - |arg| { - match arg.kind() { - ir::AttributeArg::Event | ir::AttributeArg::Anonymous => Ok(()), - _ => Err(None), - } - }, - )?; - if !item_struct.generics.params.is_empty() { - return Err(format_err_spanned!( - item_struct.generics.params, - "generic ink! event structs are not supported", - )) - } - utils::ensure_pub_visibility("event structs", struct_span, &item_struct.vis)?; - 'repeat: for field in item_struct.fields.iter() { - let field_span = field.span(); - let some_cfg_attrs = field - .attrs - .iter() - .find(|attr| attr.path().is_ident(CFG_IDENT)); - if some_cfg_attrs.is_some() { - return Err(format_err!( - field_span, - "conditional compilation is not allowed for event field" - )) - } - let (ink_attrs, _) = ir::partition_attributes(field.attrs.clone())?; - if ink_attrs.is_empty() { - continue 'repeat - } - let normalized = - ir::InkAttribute::from_expanded(ink_attrs).map_err(|err| { - err.into_combine(format_err!(field_span, "at this invocation",)) - })?; - if !matches!(normalized.first().kind(), ir::AttributeArg::Topic) { - return Err(format_err!( - field_span, - "first optional ink! attribute of an event field must be #[ink(topic)]", - )); - } - for arg in normalized.args() { - if !matches!(arg.kind(), ir::AttributeArg::Topic) { - return Err(format_err!( - arg.span(), - "encountered conflicting ink! attribute for event field", - )) - } - } - } - Ok(Self { - item: syn::ItemStruct { - attrs: other_attrs, - ..item_struct - }, - anonymous: ink_attrs.is_anonymous(), - }) - } -} - -impl Event { - /// Returns the identifier of the event struct. - pub fn ident(&self) -> &Ident { - &self.item.ident - } - - /// Returns an iterator yielding all the `#[ink(topic)]` annotated fields - /// of the event struct. - pub fn fields(&self) -> EventFieldsIter { - EventFieldsIter::new(self) - } - - /// Returns all non-ink! attributes. - pub fn attrs(&self) -> &[syn::Attribute] { - &self.item.attrs - } - - /// Returns a list of `cfg` attributes if any. - pub fn get_cfg_attrs(&self, span: Span) -> Vec { - extract_cfg_attributes(self.attrs(), span) - } -} - -/// An event field with a flag indicating if this field is an event topic. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct EventField<'a> { - /// The associated `field` is an event topic if this is `true`. - pub is_topic: bool, - /// The event field. - field: &'a syn::Field, -} - -impl<'a> EventField<'a> { - /// Returns the span of the event field. - pub fn span(self) -> Span { - self.field.span() - } - - /// Returns all non-ink! attributes of the event field. - pub fn attrs(self) -> Vec { - let (_, non_ink_attrs) = ir::partition_attributes(self.field.attrs.clone()) - .unwrap_or_else(|err| { - panic!("encountered invalid event field attributes: {err}") - }); - non_ink_attrs - } - - /// Returns the visibility of the event field. - pub fn vis(self) -> &'a syn::Visibility { - &self.field.vis - } - - /// Returns the identifier of the event field if any. - pub fn ident(self) -> Option<&'a Ident> { - self.field.ident.as_ref() - } - - /// Returns the type of the event field. - pub fn ty(self) -> &'a syn::Type { - &self.field.ty - } -} - -/// Iterator yielding all `#[ink(topic)]` annotated fields of an event struct. -pub struct EventFieldsIter<'a> { - iter: syn::punctuated::Iter<'a, syn::Field>, -} - -impl<'a> EventFieldsIter<'a> { - /// Creates a new topics fields iterator for the given ink! event struct. - fn new(event: &'a Event) -> Self { - Self { - iter: event.item.fields.iter(), - } - } -} - -impl<'a> Iterator for EventFieldsIter<'a> { - type Item = EventField<'a>; - - fn next(&mut self) -> Option { - match self.iter.next() { - None => None, - Some(field) => { - let is_topic = ir::first_ink_attribute(&field.attrs) - .unwrap_or_default() - .map(|attr| matches!(attr.first().kind(), ir::AttributeArg::Topic)) - .unwrap_or_default(); - Some(EventField { is_topic, field }) - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn simple_try_from_works() { - let item_struct: syn::ItemStruct = syn::parse_quote! { - #[ink(event)] - pub struct MyEvent { - #[ink(topic)] - field_1: i32, - field_2: bool, - } - }; - assert!(Event::try_from(item_struct).is_ok()); - } - - fn assert_try_from_fails(item_struct: syn::ItemStruct, expected: &str) { - assert_eq!( - Event::try_from(item_struct).map_err(|err| err.to_string()), - Err(expected.to_string()) - ) - } - - #[test] - fn conflicting_struct_attributes_fails() { - assert_try_from_fails( - syn::parse_quote! { - #[ink(event)] - #[ink(storage)] - pub struct MyEvent { - #[ink(topic)] - field_1: i32, - field_2: bool, - } - }, - "encountered conflicting ink! attribute argument", - ) - } - - #[test] - fn duplicate_struct_attributes_fails() { - assert_try_from_fails( - syn::parse_quote! { - #[ink(event)] - #[ink(event)] - pub struct MyEvent { - #[ink(topic)] - field_1: i32, - field_2: bool, - } - }, - "encountered duplicate ink! attribute", - ) - } - - #[test] - fn wrong_first_struct_attribute_fails() { - assert_try_from_fails( - syn::parse_quote! { - #[ink(storage)] - #[ink(event)] - pub struct MyEvent { - #[ink(topic)] - field_1: i32, - field_2: bool, - } - }, - "unexpected first ink! attribute argument", - ) - } - - #[test] - fn missing_storage_attribute_fails() { - assert_try_from_fails( - syn::parse_quote! { - pub struct MyEvent { - #[ink(topic)] - field_1: i32, - field_2: bool, - } - }, - "encountered unexpected empty expanded ink! attribute arguments", - ) - } - - #[test] - fn generic_event_fails() { - assert_try_from_fails( - syn::parse_quote! { - #[ink(event)] - pub struct GenericEvent { - #[ink(topic)] - field_1: T, - field_2: bool, - } - }, - "generic ink! event structs are not supported", - ) - } - - #[test] - fn non_pub_event_struct() { - assert_try_from_fails( - syn::parse_quote! { - #[ink(event)] - struct PrivateEvent { - #[ink(topic)] - field_1: i32, - field_2: bool, - } - }, - "non `pub` ink! event structs are not supported", - ) - } - - #[test] - fn duplicate_field_attributes_fails() { - assert_try_from_fails( - syn::parse_quote! { - #[ink(event)] - pub struct MyEvent { - #[ink(topic)] - #[ink(topic)] - field_1: i32, - field_2: bool, - } - }, - "encountered duplicate ink! attribute", - ) - } - - #[test] - fn invalid_field_attributes_fails() { - assert_try_from_fails( - syn::parse_quote! { - #[ink(event)] - pub struct MyEvent { - #[ink(message)] - field_1: i32, - field_2: bool, - } - }, - "first optional ink! attribute of an event field must be #[ink(topic)]", - ) - } - - #[test] - fn conflicting_field_attributes_fails() { - assert_try_from_fails( - syn::parse_quote! { - #[ink(event)] - pub struct MyEvent { - #[ink(topic)] - #[ink(payable)] - field_1: i32, - field_2: bool, - } - }, - "encountered conflicting ink! attribute for event field", - ) - } - - #[test] - fn cfg_marked_field_attribute_fails() { - assert_try_from_fails( - syn::parse_quote! { - #[ink(event)] - pub struct MyEvent { - #[ink(topic)] - field_1: i32, - #[cfg(unix)] - field_2: bool, - } - }, - "conditional compilation is not allowed for event field", - ) - } - - /// Used for the event fields iterator unit test because `syn::Field` does - /// not provide a `syn::parse::Parse` implementation. - #[derive(Debug, PartialEq, Eq)] - struct NamedField(syn::Field); - - impl syn::parse::Parse for NamedField { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - Ok(Self(syn::Field::parse_named(input)?)) - } - } - - impl NamedField { - /// Returns the identifier of the named field. - pub fn ident(&self) -> &Ident { - self.0.ident.as_ref().unwrap() - } - - /// Returns the type of the named field. - pub fn ty(&self) -> &syn::Type { - &self.0.ty - } - } - - #[test] - fn event_fields_iter_works() { - let expected_fields: Vec<(bool, NamedField)> = vec![ - ( - true, - syn::parse_quote! { - field_1: i32 - }, - ), - ( - false, - syn::parse_quote! { - field_2: u64 - }, - ), - ( - true, - syn::parse_quote! { - field_3: [u8; 32] - }, - ), - ]; - let input = >::try_from(syn::parse_quote! { - #[ink(event)] - pub struct MyEvent { - #[ink(topic)] - field_1: i32, - field_2: u64, - #[ink(topic)] - field_3: [u8; 32], - } - }) - .unwrap(); - let mut fields_iter = input.fields(); - for (is_topic, expected_field) in expected_fields { - let field = fields_iter.next().unwrap(); - assert_eq!(field.is_topic, is_topic); - assert_eq!(field.ident(), Some(expected_field.ident())); - assert_eq!(field.ty(), expected_field.ty()); - } - } - - #[test] - fn anonymous_event_works() { - fn assert_anonymous_event(event: syn::ItemStruct) { - match Event::try_from(event) { - Ok(event) => { - assert!(event.anonymous); - } - Err(_) => panic!("encountered unexpected invalid anonymous event"), - } - } - assert_anonymous_event(syn::parse_quote! { - #[ink(event)] - #[ink(anonymous)] - pub struct MyEvent { - #[ink(topic)] - field_1: i32, - field_2: bool, - } - }); - assert_anonymous_event(syn::parse_quote! { - #[ink(event, anonymous)] - pub struct MyEvent { - #[ink(topic)] - field_1: i32, - field_2: bool, - } - }); - } -} diff --git a/crates/ink/ir/src/ir/item/mod.rs b/crates/ink/ir/src/ir/item/mod.rs index 5bb3d6b83ed..5aaf3579c08 100644 --- a/crates/ink/ir/src/ir/item/mod.rs +++ b/crates/ink/ir/src/ir/item/mod.rs @@ -12,21 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod event; mod storage; #[cfg(test)] mod tests; -pub use self::{ - event::Event, - storage::Storage, -}; +pub use self::storage::Storage; use crate::{ error::ExtError as _, ir, - ir::attrs::Attrs as _, + ir::{ + attrs::Attrs as _, + event_def::InkEventDefinition, + }, }; use syn::spanned::Spanned as _; @@ -72,15 +71,31 @@ impl TryFrom for Item { .map(Into::into) .map(Self::Ink) } + _invalid => { + Err(format_err!( + attr.span(), + "encountered unsupported ink! attribute argument on struct", + )) + } + } + } + syn::Item::Enum(item_enum) => { + // todo: dedup this with similar struct code above + if !ir::contains_ink_attributes(&item_enum.attrs) { + return Ok(Self::Rust(item_enum.into())) + } + let attr = ir::first_ink_attribute(&item_enum.attrs)? + .expect("missing expected ink! attribute for struct"); + match attr.first().kind() { ir::AttributeArg::Event => { - >::try_from(item_struct) + >::try_from(item_enum) .map(Into::into) .map(Self::Ink) } _invalid => { Err(format_err!( attr.span(), - "encountered unsupported ink! attribute argument on struct", + "encountered unsupported ink! attribute argument an enum", )) } } @@ -152,9 +167,9 @@ impl Item { #[derive(Debug, PartialEq, Eq)] pub enum InkItem { /// The ink! storage struct definition. - Storage(ir::Storage), + Storage(Storage), /// An ink! event definition. - Event(ir::Event), + Event(InkEventDefinition), /// An ink! implementation block. ImplBlock(ir::ItemImpl), } @@ -181,7 +196,7 @@ impl InkItem { match item { syn::Item::Struct(item_struct) => { if ir::Storage::is_ink_storage(item_struct)? - || ir::Event::is_ink_event(item_struct)? + || Self::is_ink_event(&item_struct.attrs)? { return Ok(true) } @@ -193,6 +208,24 @@ impl InkItem { } Ok(false) } + + /// Returns `true` if the first ink! annotation on the given struct is + /// `#[ink(event)]`. + /// + /// # Errors + /// + /// If the first found ink! attribute is malformed. + fn is_ink_event(attrs: &[syn::Attribute]) -> Result { + if !ir::contains_ink_attributes(attrs) { + return Ok(false) + } + // At this point we know that there must be at least one ink! + // attribute. This can be either the ink! storage struct, + // an ink! event or an invalid ink! attribute. + let attr = ir::first_ink_attribute(attrs)? + .expect("missing expected ink! attribute for struct"); + Ok(matches!(attr.first().kind(), ir::AttributeArg::Event)) + } } impl From for InkItem { @@ -201,18 +234,18 @@ impl From for InkItem { } } -impl From for InkItem { - fn from(event: ir::Event) -> Self { - Self::Event(event) - } -} - impl From for InkItem { fn from(impl_block: ir::ItemImpl) -> Self { Self::ImplBlock(impl_block) } } +impl From for InkItem { + fn from(event_def: InkEventDefinition) -> Self { + Self::Event(event_def) + } +} + impl InkItem { /// Returns `Some` if `self` is the ink! storage struct definition. /// @@ -232,7 +265,7 @@ impl InkItem { /// Returns `Some` if `self` is an ink! event struct definition. /// /// Otherwise, returns `None`. - pub fn filter_map_event_item(&self) -> Option<&ir::Event> { + pub fn filter_map_event_item(&self) -> Option<&InkEventDefinition> { match self { InkItem::Event(event) => Some(event), _ => None, @@ -244,6 +277,24 @@ impl InkItem { self.filter_map_event_item().is_some() } + /// Returns `true` if the first ink! annotation on the given struct is + /// `#[ink(event)]`. + /// + /// # Errors + /// + /// If the first found ink! attribute is malformed. + // pub(super) fn is_ink_event(attrs: &[syn::Attribute]) -> Result { + // if !ir::contains_ink_attributes(attrs) { + // return Ok(false) + // } + // // At this point we know that there must be at least one ink! + // // attribute. This can be either the ink! storage struct, + // // an ink! event or an invalid ink! attribute. + // let attr = ir::first_ink_attribute(attrs)? + // .expect("missing expected ink! attribute for struct"); + // Ok(matches!(attr.first().kind(), ir::AttributeArg::Event)) + // } + /// Returns `Some` if `self` is an ink! implementation block. /// /// Otherwise, returns `None`. diff --git a/crates/ink/ir/src/ir/item/tests.rs b/crates/ink/ir/src/ir/item/tests.rs index 51bee26b475..11188e105cc 100644 --- a/crates/ink/ir/src/ir/item/tests.rs +++ b/crates/ink/ir/src/ir/item/tests.rs @@ -33,10 +33,12 @@ fn simple_storage_works() { fn simple_event_works() { let event_struct: syn::Item = syn::parse_quote! { #[ink(event)] - pub struct MyEvent { - #[ink(topic)] - param_1: bool, - param_2: i32, + pub enum MyEvent { + Event { + #[ink(topic)] + param_1: bool, + param_2: i32, + } } }; assert!(matches!( diff --git a/crates/ink/ir/src/ir/item_mod.rs b/crates/ink/ir/src/ir/item_mod.rs index 5d70a43bb28..37b205c416a 100644 --- a/crates/ink/ir/src/ir/item_mod.rs +++ b/crates/ink/ir/src/ir/item_mod.rs @@ -50,8 +50,10 @@ use syn::{ /// } /// /// #[ink(event)] -/// pub struct MyEvent { -/// // event fields +/// pub enum MyEvent { +/// Event { +/// // event fields +/// }, /// } /// /// impl MyStorage { @@ -589,7 +591,7 @@ impl<'a> IterEvents<'a> { } impl<'a> Iterator for IterEvents<'a> { - type Item = &'a ir::Event; + type Item = &'a ir::InkEventDefinition; fn next(&mut self) -> Option { 'repeat: loop { diff --git a/crates/ink/ir/src/ir/mod.rs b/crates/ink/ir/src/ir/mod.rs index a2e8ea09498..38847cb0c98 100644 --- a/crates/ink/ir/src/ir/mod.rs +++ b/crates/ink/ir/src/ir/mod.rs @@ -19,6 +19,7 @@ mod blake2; mod chain_extension; mod config; mod contract; +mod event_def; mod idents_lint; mod ink_test; mod item; @@ -69,9 +70,9 @@ pub use self::{ }, config::Config, contract::Contract, + event_def::InkEventDefinition, ink_test::InkTest, item::{ - Event, InkItem, Item, Storage, diff --git a/crates/ink/ir/src/lib.rs b/crates/ink/ir/src/lib.rs index fdb12f30f66..102253dea31 100644 --- a/crates/ink/ir/src/lib.rs +++ b/crates/ink/ir/src/lib.rs @@ -53,9 +53,9 @@ pub use self::{ Config, Constructor, Contract, - Event, ExtensionId, ImplItem, + InkEventDefinition, InkItem, InkItemTrait, InkTest, diff --git a/crates/ink/src/codegen/event/emit.rs b/crates/ink/macro/src/event_def.rs similarity index 53% rename from crates/ink/src/codegen/event/emit.rs rename to crates/ink/macro/src/event_def.rs index 702bfd687f5..045b50284b7 100644 --- a/crates/ink/src/codegen/event/emit.rs +++ b/crates/ink/macro/src/event_def.rs @@ -12,15 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::reflect::ContractEventBase; +use ink_codegen::generate_code; +use proc_macro2::TokenStream as TokenStream2; +use syn::Result; -/// Allows for `self.env().emit_event(...)` syntax in ink! implementation blocks. -pub trait EmitEvent -where - C: ContractEventBase, -{ - /// Emits an event that can be trivially converted into the base event. - fn emit_event(self, event: E) - where - E: Into<::Type>; +pub fn generate(attr: TokenStream2, item: TokenStream2) -> TokenStream2 { + match generate_or_err(attr, item) { + Ok(tokens) => tokens, + Err(err) => err.to_compile_error(), + } +} + +pub fn generate_or_err( + config: TokenStream2, + input: TokenStream2, +) -> Result { + let trait_definition = + ink_ir::InkEventDefinition::from_event_def_tokens(config, input)?; + Ok(generate_code(&trait_definition)) } diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index 4cd03837d65..08a62a766ad 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -22,6 +22,7 @@ extern crate proc_macro; mod blake2b; mod chain_extension; mod contract; +mod event_def; mod ink_test; mod selector; mod storage; @@ -646,6 +647,12 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { trait_def::analyze(attr.into(), item.into()).into() } +/// todo derive Event docs +#[proc_macro_attribute] +pub fn event_definition(attr: TokenStream, item: TokenStream) -> TokenStream { + event_def::generate(attr.into(), item.into()).into() +} + /// Prepares the type to be fully compatible and usable with the storage. /// It implements all necessary traits and calculates the storage key for types. /// `Packed` types don't have a storage key, but non-packed types (like `Mapping`, `Lazy` diff --git a/crates/ink/src/codegen/event/mod.rs b/crates/ink/src/codegen/event/mod.rs index 73002bdc4da..d2c2bac9080 100644 --- a/crates/ink/src/codegen/event/mod.rs +++ b/crates/ink/src/codegen/event/mod.rs @@ -12,15 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod emit; mod topics; -pub use self::{ - emit::EmitEvent, - topics::{ - EventLenTopics, - EventRespectsTopicLimit, - EventTopics, - RespectTopicLimit, - }, +pub use self::topics::{ + EventLenTopics, + EventRespectsTopicLimit, + EventTopics, + RespectTopicLimit, }; diff --git a/crates/ink/src/codegen/mod.rs b/crates/ink/src/codegen/mod.rs index d53fc3517b6..7fcbe98366b 100644 --- a/crates/ink/src/codegen/mod.rs +++ b/crates/ink/src/codegen/mod.rs @@ -33,7 +33,6 @@ pub use self::{ StaticEnv, }, event::{ - EmitEvent, EventLenTopics, EventRespectsTopicLimit, EventTopics, diff --git a/crates/ink/src/env_access.rs b/crates/ink/src/env_access.rs index 63c42d01f85..4bdbe3e3286 100644 --- a/crates/ink/src/env_access.rs +++ b/crates/ink/src/env_access.rs @@ -413,6 +413,15 @@ where ink_env::minimum_balance::() } + /// todo: [AJ] docs + pub fn emit_event(self, event: Event) + where + E: Environment, + Event: ink_env::Topics + scale::Encode, + { + ink_env::emit_event::(event) + } + /// Instantiates another contract. /// /// # Example diff --git a/crates/ink/src/lib.rs b/crates/ink/src/lib.rs index 700be363516..d8bfd091210 100644 --- a/crates/ink/src/lib.rs +++ b/crates/ink/src/lib.rs @@ -70,6 +70,7 @@ pub use ink_macro::{ blake2x256, chain_extension, contract, + event_definition, selector_bytes, selector_id, storage_item, diff --git a/crates/ink/src/reflect/event.rs b/crates/ink/src/reflect/event.rs index 69181659e3c..9fe643e9128 100644 --- a/crates/ink/src/reflect/event.rs +++ b/crates/ink/src/reflect/event.rs @@ -12,41 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -/// Defines a base event type for the contract. -/// -/// This is usually the event enum that comprises all defined event types. -/// -/// # Usage -/// -/// ``` -/// #[ink::contract] -/// pub mod contract { -/// #[ink(storage)] -/// pub struct Contract {} -/// -/// #[ink(event)] -/// pub struct Event1 {} -/// -/// #[ink(event)] -/// pub struct Event2 {} -/// -/// impl Contract { -/// #[ink(constructor)] -/// pub fn constructor() -> Self { -/// Self {} -/// } -/// -/// #[ink(message)] -/// pub fn message(&self) {} -/// } -/// } -/// -/// use contract::Contract; -/// # use ink::reflect::ContractEventBase; -/// -/// type BaseEvent = ::Type; -/// ``` -pub trait ContractEventBase { - /// The generated base event enum. - type Type; +/// todo: docs +pub trait EventInfo { + const PATH: &'static str; +} + +/// todo: docs +/// The ID is the index of the event variant in the enum +pub trait EventVariantInfo { + const NAME: &'static str; + /// todo: docs + /// Will be hashed unique path of Event -> Variant, used for topic of Event variant + /// Should be able to compute up front + const SIGNATURE_TOPIC: [u8; 32]; } diff --git a/crates/ink/src/reflect/mod.rs b/crates/ink/src/reflect/mod.rs index 349ca327e16..3e6760f8a71 100644 --- a/crates/ink/src/reflect/mod.rs +++ b/crates/ink/src/reflect/mod.rs @@ -41,7 +41,10 @@ pub use self::{ DispatchableMessageInfo, ExecuteDispatchable, }, - event::ContractEventBase, + event::{ + EventInfo, + EventVariantInfo, + }, trait_def::{ TraitDefinitionRegistry, TraitInfo, diff --git a/crates/ink/tests/return_type_metadata.rs b/crates/ink/tests/return_type_metadata.rs index d7f41a9bb93..a6510414da7 100644 --- a/crates/ink/tests/return_type_metadata.rs +++ b/crates/ink/tests/return_type_metadata.rs @@ -41,6 +41,10 @@ mod contract { #[cfg(test)] mod tests { + use ink::metadata::{ + EventSpec, + InkProject, + }; use scale_info::{ form::PortableForm, Type, @@ -49,19 +53,19 @@ mod tests { TypeDefTuple, }; - fn generate_metadata() -> ink_metadata::InkProject { + fn generate_metadata() -> InkProject { extern "Rust" { - fn __ink_generate_metadata() -> ink_metadata::InkProject; + fn __ink_generate_metadata(events: Vec) -> InkProject; } - unsafe { __ink_generate_metadata() } + unsafe { __ink_generate_metadata(vec![]) } } /// Extract the type defs of the `Ok` and `Error` variants of a `Result` type. /// /// Panics if the type def is not a valid result fn extract_result<'a>( - metadata: &'a ink_metadata::InkProject, + metadata: &'a InkProject, ty: &'a Type, ) -> (&'a Type, &'a Type) { assert_eq!( @@ -89,10 +93,7 @@ mod tests { } /// Resolve a type with the given id from the type registry - fn resolve_type( - metadata: &ink_metadata::InkProject, - type_id: u32, - ) -> &Type { + fn resolve_type(metadata: &InkProject, type_id: u32) -> &Type { metadata .registry() .resolve(type_id) diff --git a/crates/ink/tests/ui/contract/fail/event-conflicting-storage.rs b/crates/ink/tests/ui/contract/fail/event-conflicting-storage.rs index 647d8ec8ce0..078fc6dac78 100644 --- a/crates/ink/tests/ui/contract/fail/event-conflicting-storage.rs +++ b/crates/ink/tests/ui/contract/fail/event-conflicting-storage.rs @@ -3,9 +3,11 @@ mod contract { #[ink(storage)] pub struct Contract {} - #[ink(event)] + #[ink::event_definition] #[ink(storage)] - pub struct Event {} + pub enum Event { + Event {}, + } impl Contract { #[ink(constructor)] diff --git a/crates/ink/tests/ui/contract/fail/event-no-variants.rs b/crates/ink/tests/ui/contract/fail/event-no-variants.rs new file mode 100644 index 00000000000..14de0555d52 --- /dev/null +++ b/crates/ink/tests/ui/contract/fail/event-no-variants.rs @@ -0,0 +1,4 @@ +#[ink::event_definition] +pub enum Event {} + +fn main() {} diff --git a/crates/ink/tests/ui/contract/fail/event-no-variants.stderr b/crates/ink/tests/ui/contract/fail/event-no-variants.stderr new file mode 100644 index 00000000000..4b6dce8e465 --- /dev/null +++ b/crates/ink/tests/ui/contract/fail/event-no-variants.stderr @@ -0,0 +1,5 @@ +error: ink! event enum definitions must have at least one variant + --> tests/ui/contract/fail/event-no-variants.rs:2:1 + | +2 | pub enum Event {} + | ^^^^^^^^^^^^^^^^^ diff --git a/crates/ink/tests/ui/contract/fail/event-struct.rs b/crates/ink/tests/ui/contract/fail/event-struct.rs new file mode 100644 index 00000000000..3769396d9d0 --- /dev/null +++ b/crates/ink/tests/ui/contract/fail/event-struct.rs @@ -0,0 +1,4 @@ +#[ink::event_definition] +pub struct Event {} + +fn main() {} diff --git a/crates/ink/tests/ui/contract/fail/event-struct.stderr b/crates/ink/tests/ui/contract/fail/event-struct.stderr new file mode 100644 index 00000000000..c0c7289200e --- /dev/null +++ b/crates/ink/tests/ui/contract/fail/event-struct.stderr @@ -0,0 +1,13 @@ +error: expected `enum` + --> tests/ui/contract/fail/event-struct.rs:2:5 + | +2 | pub struct Event {} + | ^^^^^^ + +error: ink! event definitions must be enums + --> tests/ui/contract/fail/event-struct.rs:1:1 + | +1 | #[ink::event_definition] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `ink::event_definition` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/ink/tests/ui/contract/fail/event-too-many-topics-anonymous.rs b/crates/ink/tests/ui/contract/fail/event-too-many-topics-anonymous.rs index 440ba81c419..3eccf36565b 100644 --- a/crates/ink/tests/ui/contract/fail/event-too-many-topics-anonymous.rs +++ b/crates/ink/tests/ui/contract/fail/event-too-many-topics-anonymous.rs @@ -21,22 +21,25 @@ mod contract { #[ink(storage)] pub struct Contract {} - #[ink(event, anonymous)] - pub struct Event { - #[ink(topic)] - arg_1: i8, - #[ink(topic)] - arg_2: i16, - #[ink(topic)] - arg_3: i32, - #[ink(topic)] - arg_4: i32, + #[ink::event_definition] + pub enum Event { + #[ink(anonymous)] + Event { + #[ink(topic)] + arg_1: i8, + #[ink(topic)] + arg_2: i16, + #[ink(topic)] + arg_3: i32, + #[ink(topic)] + arg_4: i32, + }, } impl Contract { #[ink(constructor)] pub fn constructor() -> Self { - Self::env().emit_event(Event { + Self::env().emit_event(Event::Event { arg_1: 1, arg_2: 2, arg_3: 3, @@ -47,7 +50,7 @@ mod contract { #[ink(message)] pub fn message(&self) { - self.env().emit_event(Event { + self.env().emit_event(Event::Event { arg_1: 1, arg_2: 2, arg_3: 3, diff --git a/crates/ink/tests/ui/contract/fail/event-too-many-topics.rs b/crates/ink/tests/ui/contract/fail/event-too-many-topics.rs index 4b7be584017..8153477a152 100644 --- a/crates/ink/tests/ui/contract/fail/event-too-many-topics.rs +++ b/crates/ink/tests/ui/contract/fail/event-too-many-topics.rs @@ -16,25 +16,31 @@ impl ink_env::Environment for EnvironmentMoreTopics { type ChainExtension = (); } -#[ink::contract(env = super::EnvironmentMoreTopics)] -mod contract { - #[ink(storage)] - pub struct Contract {} - - #[ink(event)] - pub struct Event { +#[ink::event_definition] +// #[ink::event_definition(env = super::EnvironmentMoreTopics)] +pub enum Event { + Event { #[ink(topic)] arg_1: i8, #[ink(topic)] arg_2: i16, #[ink(topic)] arg_3: i32, - } + }, +} + +#[ink::contract] +// #[ink::contract(env = super::EnvironmentMoreTopics)] +mod contract { + use super::Event; + + #[ink(storage)] + pub struct Contract {} impl Contract { #[ink(constructor)] pub fn constructor() -> Self { - Self::env().emit_event(Event { + Self::env().emit_event(Event::Event { arg_1: 1, arg_2: 2, arg_3: 3, @@ -44,7 +50,7 @@ mod contract { #[ink(message)] pub fn message(&self) { - self.env().emit_event(Event { + self.env().emit_event(Event::Event { arg_1: 1, arg_2: 2, arg_3: 3, diff --git a/crates/ink/tests/ui/contract/pass/event-anonymous.rs b/crates/ink/tests/ui/contract/pass/event-anonymous.rs index 371d10e595e..1b9aa5a9294 100644 --- a/crates/ink/tests/ui/contract/pass/event-anonymous.rs +++ b/crates/ink/tests/ui/contract/pass/event-anonymous.rs @@ -3,77 +3,75 @@ mod contract { #[ink(storage)] pub struct Contract {} - #[ink(event, anonymous)] - pub struct Event0 {} - - #[ink(event, anonymous)] - pub struct Event1 { - #[ink(topic)] - arg_1: i8, - } - - #[ink(event, anonymous)] - pub struct Event2 { - #[ink(topic)] - arg_1: i8, - #[ink(topic)] - arg_2: i16, - } - - #[ink(event, anonymous)] - pub struct Event3 { - #[ink(topic)] - arg_1: i8, - #[ink(topic)] - arg_2: i16, - #[ink(topic)] - arg_3: i32, - } - - #[ink(event, anonymous)] - pub struct Event4 { - #[ink(topic)] - arg_1: i8, - #[ink(topic)] - arg_2: i16, - #[ink(topic)] - arg_3: i32, - #[ink(topic)] - arg_4: i64, - } - - #[ink(event, anonymous)] - pub struct Event5 { - #[ink(topic)] - arg_1: i8, - #[ink(topic)] - arg_2: i16, - #[ink(topic)] - arg_3: i32, - #[ink(topic)] - arg_4: i64, - // #[ink(topic)] <- Cannot have more than 4 topics by default. - arg_5: i128, + #[ink::event_definition] + pub enum Event { + #[ink(anonymous)] + Event0 {}, + #[ink(anonymous)] + Event1 { + #[ink(topic)] + arg_1: i8, + }, + #[ink(anonymous)] + Event2 { + #[ink(topic)] + arg_1: i8, + #[ink(topic)] + arg_2: i16, + }, + #[ink(anonymous)] + Event3 { + #[ink(topic)] + arg_1: i8, + #[ink(topic)] + arg_2: i16, + #[ink(topic)] + arg_3: i32, + }, + #[ink(anonymous)] + Event4 { + #[ink(topic)] + arg_1: i8, + #[ink(topic)] + arg_2: i16, + #[ink(topic)] + arg_3: i32, + #[ink(topic)] + arg_4: i64, + }, + #[ink(anonymous)] + Event5 { + #[ink(topic)] + arg_1: i8, + #[ink(topic)] + arg_2: i16, + #[ink(topic)] + arg_3: i32, + #[ink(topic)] + arg_4: i64, + // #[ink(topic)] <- Cannot have more than 4 topics by default. + arg_5: i128, + }, } impl Contract { #[ink(constructor)] pub fn constructor() -> Self { - Self::env().emit_event(Event0 {}); - Self::env().emit_event(Event1 { arg_1: 1 }); - Self::env().emit_event(Event2 { arg_1: 1, arg_2: 2 }); - Self::env().emit_event(Event3 { + Self::env().emit_event(Event::Event0 {}); + Self::env().emit_event(Event::Event1 { arg_1: 1 }); + Self::env().emit_event(Event::Event2 { arg_1: 1, arg_2: 2 }); + Self::env().emit_event(Event::Event3 { arg_1: 1, arg_2: 2, arg_3: 3, }); - Self::env().emit_event(Event4 { + Self::env().emit_event(Event::Event4 { arg_1: 1, arg_2: 2, arg_3: 3, arg_4: 4, }); - Self::env().emit_event(Event5 { + Self::env().emit_event(Event::Event5 { arg_1: 1, arg_2: 2, arg_3: 3, @@ -85,21 +83,21 @@ mod contract { #[ink(message)] pub fn message(&self) { - self.env().emit_event(Event0 {}); - self.env().emit_event(Event1 { arg_1: 1 }); - self.env().emit_event(Event2 { arg_1: 1, arg_2: 2 }); - self.env().emit_event(Event3 { + self.env().emit_event(Event::Event0 {}); + self.env().emit_event(Event::Event1 { arg_1: 1 }); + self.env().emit_event(Event::Event2 { arg_1: 1, arg_2: 2 }); + self.env().emit_event(Event::Event3 { arg_1: 1, arg_2: 2, arg_3: 3, }); - self.env().emit_event(Event4 { + self.env().emit_event(Event::Event4 { arg_1: 1, arg_2: 2, arg_3: 3, arg_4: 4, }); - self.env().emit_event(Event5 { + self.env().emit_event(Event::Event5 { arg_1: 1, arg_2: 2, arg_3: 3, diff --git a/crates/ink/tests/ui/contract/pass/event-config-more-topics.rs b/crates/ink/tests/ui/contract/pass/event-config-more-topics.rs index 5628749fb75..40aec920799 100644 --- a/crates/ink/tests/ui/contract/pass/event-config-more-topics.rs +++ b/crates/ink/tests/ui/contract/pass/event-config-more-topics.rs @@ -21,34 +21,37 @@ mod contract { #[ink(storage)] pub struct Contract {} - #[ink(event, anonymous)] - pub struct EventWithManyTopics { - #[ink(topic)] - arg_1: i8, - #[ink(topic)] - arg_2: i16, - #[ink(topic)] - arg_3: i32, - #[ink(topic)] - arg_4: i64, - #[ink(topic)] - arg_5: i128, - #[ink(topic)] - arg_6: u8, - #[ink(topic)] - arg_7: u16, - #[ink(topic)] - arg_8: u32, - #[ink(topic)] - arg_9: u64, - #[ink(topic)] - arg_10: u128, + #[ink::event_definition] + pub enum Event { + #[ink(anonymous)] + EventWithManyTopics { + #[ink(topic)] + arg_1: i8, + #[ink(topic)] + arg_2: i16, + #[ink(topic)] + arg_3: i32, + #[ink(topic)] + arg_4: i64, + #[ink(topic)] + arg_5: i128, + #[ink(topic)] + arg_6: u8, + #[ink(topic)] + arg_7: u16, + #[ink(topic)] + arg_8: u32, + #[ink(topic)] + arg_9: u64, + #[ink(topic)] + arg_10: u128, + }, } impl Contract { #[ink(constructor)] pub fn constructor() -> Self { - Self::env().emit_event(EventWithManyTopics { + Self::env().emit_event(Event::EventWithManyTopics { arg_1: 1, arg_2: 2, arg_3: 3, @@ -65,7 +68,7 @@ mod contract { #[ink(message)] pub fn message(&self) { - self.env().emit_event(EventWithManyTopics { + self.env().emit_event(Event::EventWithManyTopics { arg_1: 1, arg_2: 2, arg_3: 3, diff --git a/crates/ink/tests/ui/contract/pass/event-many-definitions.rs b/crates/ink/tests/ui/contract/pass/event-many-definitions.rs index 00f9400dbe9..82a11ac3d63 100644 --- a/crates/ink/tests/ui/contract/pass/event-many-definitions.rs +++ b/crates/ink/tests/ui/contract/pass/event-many-definitions.rs @@ -3,62 +3,54 @@ mod contract { #[ink(storage)] pub struct Contract {} - #[ink(event)] - pub struct Event0 {} - - #[ink(event)] - pub struct Event1 { - arg_1: i8, - } - - #[ink(event)] - pub struct Event2 { - arg_1: i8, - arg_2: i16, - } - - #[ink(event)] - pub struct Event3 { - arg_1: i8, - arg_2: i16, - arg_3: i32, - } - - #[ink(event)] - pub struct Event4 { - arg_1: i8, - arg_2: i16, - arg_3: i32, - arg_4: i64, - } - - #[ink(event)] - pub struct Event5 { - arg_1: i8, - arg_2: i16, - arg_3: i32, - arg_4: i64, - arg_5: i128, + #[ink::event_definition] + pub enum Event { + Event0 {}, + Event1 { + arg_1: i8, + }, + Event2 { + arg_1: i8, + arg_2: i16, + }, + Event3 { + arg_1: i8, + arg_2: i16, + arg_3: i32, + }, + Event4 { + arg_1: i8, + arg_2: i16, + arg_3: i32, + arg_4: i64, + }, + Event5 { + arg_1: i8, + arg_2: i16, + arg_3: i32, + arg_4: i64, + arg_5: i128, + }, } impl Contract { #[ink(constructor)] pub fn constructor() -> Self { - Self::env().emit_event(Event0 {}); - Self::env().emit_event(Event1 { arg_1: 1 }); - Self::env().emit_event(Event2 { arg_1: 1, arg_2: 2 }); - Self::env().emit_event(Event3 { + Self::env().emit_event(Event::Event0 {}); + Self::env().emit_event(Event::Event1 { arg_1: 1 }); + Self::env().emit_event(Event::Event2 { arg_1: 1, arg_2: 2 }); + Self::env().emit_event(Event::Event3 { arg_1: 1, arg_2: 2, arg_3: 3, }); - Self::env().emit_event(Event4 { + Self::env().emit_event(Event::Event4 { arg_1: 1, arg_2: 2, arg_3: 3, arg_4: 4, }); - Self::env().emit_event(Event5 { + Self::env().emit_event(Event::Event5 { arg_1: 1, arg_2: 2, arg_3: 3, @@ -70,21 +62,21 @@ mod contract { #[ink(message)] pub fn message(&self) { - self.env().emit_event(Event0 {}); - self.env().emit_event(Event1 { arg_1: 1 }); - self.env().emit_event(Event2 { arg_1: 1, arg_2: 2 }); - self.env().emit_event(Event3 { + self.env().emit_event(Event::Event0 {}); + self.env().emit_event(Event::Event1 { arg_1: 1 }); + self.env().emit_event(Event::Event2 { arg_1: 1, arg_2: 2 }); + self.env().emit_event(Event::Event3 { arg_1: 1, arg_2: 2, arg_3: 3, }); - self.env().emit_event(Event4 { + self.env().emit_event(Event::Event4 { arg_1: 1, arg_2: 2, arg_3: 3, arg_4: 4, }); - self.env().emit_event(Event5 { + self.env().emit_event(Event::Event5 { arg_1: 1, arg_2: 2, arg_3: 3, diff --git a/crates/ink/tests/ui/contract/pass/event-shared-external.rs b/crates/ink/tests/ui/contract/pass/event-shared-external.rs new file mode 100644 index 00000000000..8d06c61de55 --- /dev/null +++ b/crates/ink/tests/ui/contract/pass/event-shared-external.rs @@ -0,0 +1,29 @@ +#[ink::event_definition] +pub enum SharedEvent { + Event1 { + arg_1: u8, + #[ink(topic)] + arg_2: u16, + }, +} + +#[ink::contract] +mod contract { + #[ink(storage)] + pub struct Contract {} + + impl Contract { + #[ink(constructor)] + pub fn constructor() -> Self { + Self {} + } + + #[ink(message)] + pub fn message(&self) { + self.env() + .emit_event(super::SharedEvent::Event1 { arg_1: 1, arg_2: 2 }); + } + } +} + +fn main() {} diff --git a/crates/ink/tests/ui/contract/pass/event-single-definition.rs b/crates/ink/tests/ui/contract/pass/event-single-definition.rs index 6932d4dc3f6..731b29785b3 100644 --- a/crates/ink/tests/ui/contract/pass/event-single-definition.rs +++ b/crates/ink/tests/ui/contract/pass/event-single-definition.rs @@ -3,8 +3,10 @@ mod contract { #[ink(storage)] pub struct Contract {} - #[ink(event)] - pub struct Event0 {} + #[ink::event_definition] + pub enum Event { + Event0 {}, + } impl Contract { #[ink(constructor)] diff --git a/crates/ink/tests/ui/contract/pass/event-topics.rs b/crates/ink/tests/ui/contract/pass/event-topics.rs index 236e63a0c7f..6c3331cbdf6 100644 --- a/crates/ink/tests/ui/contract/pass/event-topics.rs +++ b/crates/ink/tests/ui/contract/pass/event-topics.rs @@ -3,77 +3,69 @@ mod contract { #[ink(storage)] pub struct Contract {} - #[ink(event)] - pub struct Event0 {} - - #[ink(event)] - pub struct Event1 { - #[ink(topic)] - arg_1: i8, - } - - #[ink(event)] - pub struct Event2 { - #[ink(topic)] - arg_1: i8, - #[ink(topic)] - arg_2: i16, - } - - #[ink(event)] - pub struct Event3 { - #[ink(topic)] - arg_1: i8, - #[ink(topic)] - arg_2: i16, - #[ink(topic)] - arg_3: i32, - } - - #[ink(event)] - pub struct Event4 { - #[ink(topic)] - arg_1: i8, - #[ink(topic)] - arg_2: i16, - #[ink(topic)] - arg_3: i32, - #[ink(topic)] - arg_4: i64, - } - - #[ink(event)] - pub struct Event5 { - #[ink(topic)] - arg_1: i8, - #[ink(topic)] - arg_2: i16, - #[ink(topic)] - arg_3: i32, - #[ink(topic)] - arg_4: i64, - // #[ink(topic)] <- Cannot have more than 4 topics by default. - arg_5: i128, + #[ink::event_definition] + pub enum Event { + Event0 {}, + Event1 { + #[ink(topic)] + arg_1: i8, + }, + Event2 { + #[ink(topic)] + arg_1: i8, + #[ink(topic)] + arg_2: i16, + }, + Event3 { + #[ink(topic)] + arg_1: i8, + #[ink(topic)] + arg_2: i16, + #[ink(topic)] + arg_3: i32, + }, + Event4 { + #[ink(topic)] + arg_1: i8, + #[ink(topic)] + arg_2: i16, + #[ink(topic)] + arg_3: i32, + #[ink(topic)] + arg_4: i64, + }, + Event5 { + #[ink(topic)] + arg_1: i8, + #[ink(topic)] + arg_2: i16, + #[ink(topic)] + arg_3: i32, + #[ink(topic)] + arg_4: i64, + // #[ink(topic)] <- Cannot have more than 4 topics by default. + arg_5: i128, + }, } impl Contract { #[ink(constructor)] pub fn constructor() -> Self { - Self::env().emit_event(Event0 {}); - Self::env().emit_event(Event1 { arg_1: 1 }); - Self::env().emit_event(Event2 { arg_1: 1, arg_2: 2 }); - Self::env().emit_event(Event3 { + Self::env().emit_event(Event::Event0 {}); + Self::env().emit_event(Event::Event1 { arg_1: 1 }); + Self::env().emit_event(Event::Event2 { arg_1: 1, arg_2: 2 }); + Self::env().emit_event(Event::Event3 { arg_1: 1, arg_2: 2, arg_3: 3, }); - Self::env().emit_event(Event4 { + Self::env().emit_event(Event::Event4 { arg_1: 1, arg_2: 2, arg_3: 3, arg_4: 4, }); - Self::env().emit_event(Event5 { + Self::env().emit_event(Event::Event5 { arg_1: 1, arg_2: 2, arg_3: 3, @@ -85,21 +77,21 @@ mod contract { #[ink(message)] pub fn message(&self) { - self.env().emit_event(Event0 {}); - self.env().emit_event(Event1 { arg_1: 1 }); - self.env().emit_event(Event2 { arg_1: 1, arg_2: 2 }); - self.env().emit_event(Event3 { + self.env().emit_event(Event::Event0 {}); + self.env().emit_event(Event::Event1 { arg_1: 1 }); + self.env().emit_event(Event::Event2 { arg_1: 1, arg_2: 2 }); + self.env().emit_event(Event::Event3 { arg_1: 1, arg_2: 2, arg_3: 3, }); - self.env().emit_event(Event4 { + self.env().emit_event(Event::Event4 { arg_1: 1, arg_2: 2, arg_3: 3, arg_4: 4, }); - self.env().emit_event(Event5 { + self.env().emit_event(Event::Event5 { arg_1: 1, arg_2: 2, arg_3: 3, diff --git a/crates/ink/tests/ui/contract/pass/example-erc20-works.rs b/crates/ink/tests/ui/contract/pass/example-erc20-works.rs index d5876c6ffc3..a342e3a536f 100644 --- a/crates/ink/tests/ui/contract/pass/example-erc20-works.rs +++ b/crates/ink/tests/ui/contract/pass/example-erc20-works.rs @@ -15,25 +15,25 @@ mod erc20 { allowances: Mapping<(AccountId, AccountId), Balance>, } - /// Event emitted when a token transfer occurs. - #[ink(event)] - pub struct Transfer { - #[ink(topic)] - from: Option, - #[ink(topic)] - to: Option, - value: Balance, - } - - /// Event emitted when an approval occurs that `spender` is allowed to withdraw - /// up to the amount of `value` tokens from `owner`. - #[ink(event)] - pub struct Approval { - #[ink(topic)] - owner: AccountId, - #[ink(topic)] - spender: AccountId, - value: Balance, + #[ink::event_definition] + pub enum Event { + /// Event emitted when a token transfer occurs. + Transfer { + #[ink(topic)] + from: Option, + #[ink(topic)] + to: Option, + value: Balance, + }, + /// Event emitted when an approval occurs that `spender` is allowed to withdraw + /// up to the amount of `value` tokens from `owner`. + Approval { + #[ink(topic)] + owner: AccountId, + #[ink(topic)] + spender: AccountId, + value: Balance, + }, } /// The ERC-20 error types. @@ -56,7 +56,7 @@ mod erc20 { let mut balances = Mapping::default(); let caller = Self::env().caller(); balances.insert(&caller, &total_supply); - Self::env().emit_event(Transfer { + Self::env().emit_event(Event::Transfer { from: None, to: Some(caller), value: total_supply, @@ -141,7 +141,7 @@ mod erc20 { pub fn approve(&mut self, spender: AccountId, value: Balance) -> Result<()> { let owner = self.env().caller(); self.allowances.insert((&owner, &spender), &value); - self.env().emit_event(Approval { + self.env().emit_event(Event::Approval { owner, spender, value, @@ -203,7 +203,7 @@ mod erc20 { self.balances.insert(from, &(from_balance - value)); let to_balance = self.balance_of_impl(to); self.balances.insert(to, &(to_balance + value)); - self.env().emit_event(Transfer { + self.env().emit_event(Event::Transfer { from: Some(*from), to: Some(*to), value, diff --git a/crates/ink/tests/ui/contract/pass/example-erc721-works.rs b/crates/ink/tests/ui/contract/pass/example-erc721-works.rs index c5dbf9ed49d..805a7163b6e 100644 --- a/crates/ink/tests/ui/contract/pass/example-erc721-works.rs +++ b/crates/ink/tests/ui/contract/pass/example-erc721-works.rs @@ -35,37 +35,35 @@ mod erc721 { NotAllowed, } - /// Event emitted when a token transfer occurs. - #[ink(event)] - pub struct Transfer { - #[ink(topic)] - from: Option, - #[ink(topic)] - to: Option, - #[ink(topic)] - id: TokenId, - } - - /// Event emitted when a token approve occurs. - #[ink(event)] - pub struct Approval { - #[ink(topic)] - from: AccountId, - #[ink(topic)] - to: AccountId, - #[ink(topic)] - id: TokenId, - } - - /// Event emitted when an operator is enabled or disabled for an owner. - /// The operator can manage all NFTs of the owner. - #[ink(event)] - pub struct ApprovalForAll { - #[ink(topic)] - owner: AccountId, - #[ink(topic)] - operator: AccountId, - approved: bool, + #[ink::event_definition] + pub enum Event { + /// Event emitted when a token transfer occurs. + Transfer { + #[ink(topic)] + from: Option, + #[ink(topic)] + to: Option, + #[ink(topic)] + id: TokenId, + }, + /// Event emitted when a token approve occurs. + Approval { + #[ink(topic)] + from: AccountId, + #[ink(topic)] + to: AccountId, + #[ink(topic)] + id: TokenId, + }, + /// Event emitted when an operator is enabled or disabled for an owner. + /// The operator can manage all NFTs of the owner. + ApprovalForAll { + #[ink(topic)] + owner: AccountId, + #[ink(topic)] + operator: AccountId, + approved: bool, + }, } impl Erc721 { @@ -148,7 +146,7 @@ mod erc721 { pub fn mint(&mut self, id: TokenId) -> Result<(), Error> { let caller = self.env().caller(); self.add_token_to(&caller, id)?; - self.env().emit_event(Transfer { + self.env().emit_event(Event::Transfer { from: Some(AccountId::from([0x0; 32])), to: Some(caller), id, @@ -178,7 +176,7 @@ mod erc721 { owned_tokens_count.insert(&caller, &count); token_owner.remove(&id); - self.env().emit_event(Transfer { + self.env().emit_event(Event::Transfer { from: Some(caller), to: Some(AccountId::from([0x0; 32])), id, @@ -204,7 +202,7 @@ mod erc721 { self.clear_approval(id); self.remove_token_from(from, id)?; self.add_token_to(to, id)?; - self.env().emit_event(Transfer { + self.env().emit_event(Event::Transfer { from: Some(*from), to: Some(*to), id, @@ -272,7 +270,7 @@ mod erc721 { if to == caller { return Err(Error::NotAllowed) } - self.env().emit_event(ApprovalForAll { + self.env().emit_event(Event::ApprovalForAll { owner: caller, operator: to, approved, @@ -308,7 +306,7 @@ mod erc721 { self.token_approvals.insert(&id, to); } - self.env().emit_event(Approval { + self.env().emit_event(Event::Approval { from: caller, to: *to, id, diff --git a/crates/ink/tests/unique_topics.rs b/crates/ink/tests/unique_topics.rs index 8234d8470fa..be9643a94b7 100644 --- a/crates/ink/tests/unique_topics.rs +++ b/crates/ink/tests/unique_topics.rs @@ -16,22 +16,22 @@ #[ink::contract] mod my_contract { - #[ink(storage)] - pub struct MyContract {} - /// Exemplary event - #[ink(event)] - pub struct MyEvent { - #[ink(topic)] - v0: Option, - #[ink(topic)] - v1: Balance, - #[ink(topic)] - v2: bool, - #[ink(topic)] - v3: bool, + #[ink::event_definition] + pub enum Event { + MyEvent { + #[ink(topic)] + v0: Option, + #[ink(topic)] + v1: Balance, + #[ink(topic)] + v2: bool, + }, } + #[ink(storage)] + pub struct MyContract {} + impl MyContract { /// Creates a new `MyContract` instance. #[ink(constructor)] @@ -42,11 +42,10 @@ mod my_contract { /// Emits a `MyEvent`. #[ink(message)] pub fn emit_my_event(&self) { - self.env().emit_event(MyEvent { + self.env().emit_event(Event::MyEvent { v0: None, v1: 0, v2: false, - v3: false, }); } } diff --git a/crates/metadata/src/lib.rs b/crates/metadata/src/lib.rs index 29673caeba3..5af067808a7 100644 --- a/crates/metadata/src/lib.rs +++ b/crates/metadata/src/lib.rs @@ -42,6 +42,8 @@ pub use self::specs::{ EventParamSpecBuilder, EventSpec, EventSpecBuilder, + EventVariantSpec, + EventVariantSpecBuilder, MessageParamSpec, MessageParamSpecBuilder, MessageSpec, @@ -153,3 +155,9 @@ impl InkProject { &self.spec } } + +/// todo EventMetadata docs +pub trait EventMetadata { + /// todo event_spec docs + fn event_spec() -> EventSpec; +} diff --git a/crates/metadata/src/specs.rs b/crates/metadata/src/specs.rs index e49f4655c7f..a26aa99c48c 100644 --- a/crates/metadata/src/specs.rs +++ b/crates/metadata/src/specs.rs @@ -843,15 +843,15 @@ impl IntoPortable for MessageSpec { deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned" ))] pub struct EventSpec { - /// The label of the event. - label: F::String, - /// The event arguments. - args: Vec>, + /// The fully qualified path of the event. + path: F::String, + /// The event variants. + variants: Vec>, /// The event documentation. docs: Vec, } -/// An event specification builder. +/// An event variant specification builder. #[must_use] pub struct EventSpecBuilder where @@ -864,18 +864,25 @@ impl EventSpecBuilder where F: Form, { - /// Sets the input arguments of the event specification. - pub fn args(self, args: A) -> Self + /// Sets the fully qualified path of the event. + pub fn path(self, path: F::String) -> Self { + let mut this = self; + this.spec.path = path; + this + } + + /// Sets the variants of the event specification. + pub fn variants(self, variants: A) -> Self where - A: IntoIterator>, + A: IntoIterator>, { let mut this = self; - debug_assert!(this.spec.args.is_empty()); - this.spec.args = args.into_iter().collect::>(); + debug_assert!(this.spec.variants.is_empty()); + this.spec.variants = variants.into_iter().collect::>(); this } - /// Sets the input arguments of the event specification. + /// Sets the documentation of the event specification. pub fn docs<'a, D>(self, docs: D) -> Self where D: IntoIterator, @@ -901,9 +908,9 @@ impl IntoPortable for EventSpec { fn into_portable(self, registry: &mut Registry) -> Self::Output { EventSpec { - label: self.label.to_string(), - args: self - .args + path: self.path.into_portable(registry), + variants: self + .variants .into_iter() .map(|arg| arg.into_portable(registry)) .collect::>(), @@ -917,11 +924,11 @@ where F: Form, { /// Creates a new event specification builder. - pub fn new(label: ::String) -> EventSpecBuilder { + pub fn new(path: ::String) -> EventSpecBuilder { EventSpecBuilder { spec: Self { - label, - args: Vec::new(), + path, + variants: Vec::new(), docs: Vec::new(), }, } @@ -932,17 +939,123 @@ impl EventSpec where F: Form, { - /// Returns the label of the event. + /// Returns the fully qualified path of the event. + pub fn path(&self) -> &F::String { + &self.path + } + + /// The event variants. + pub fn variants(&self) -> &[EventVariantSpec] { + &self.variants + } + + /// The event variant documentation. + pub fn docs(&self) -> &[F::String] { + &self.docs + } +} + +/// Describes an event variant. +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(bound( + serialize = "F::Type: Serialize, F::String: Serialize", + deserialize = "F::Type: DeserializeOwned, F::String: DeserializeOwned" +))] +pub struct EventVariantSpec { + /// The label of the event variant. + label: F::String, + /// todo: The unique event signature for event topics. + // signature: F::String, + /// The event variant arguments. + args: Vec>, + /// The event variant documentation. + docs: Vec, +} + +/// An event variant specification builder. +#[must_use] +pub struct EventVariantSpecBuilder { + spec: EventVariantSpec, +} + +impl EventVariantSpecBuilder +where + F: Form, +{ + /// Sets the input arguments of the event variant specification. + pub fn args(self, args: A) -> Self + where + A: IntoIterator>, + { + let mut this = self; + debug_assert!(this.spec.args.is_empty()); + this.spec.args = args.into_iter().collect::>(); + this + } + + /// Sets the documentation of the event variant specification. + pub fn docs<'a, D>(self, docs: D) -> Self + where + D: IntoIterator, + F::String: From<&'a str>, + { + let mut this = self; + debug_assert!(this.spec.docs.is_empty()); + this.spec.docs = docs + .into_iter() + .map(|s| trim_extra_whitespace(s).into()) + .collect::>(); + this + } + + /// Finalizes building the event specification. + pub fn done(self) -> EventVariantSpec { + self.spec + } +} + +impl IntoPortable for EventVariantSpec { + type Output = EventVariantSpec; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + EventVariantSpec { + label: self.label.into_portable(registry), + args: self + .args + .into_iter() + .map(|arg| arg.into_portable(registry)) + .collect::>(), + docs: registry.map_into_portable(self.docs), + } + } +} + +impl EventVariantSpec +where + F: Form, +{ + /// Creates a new event variant specification builder. + pub fn new(label: F::String) -> EventVariantSpecBuilder { + EventVariantSpecBuilder { + spec: Self { + label, + args: Vec::new(), + docs: Vec::new(), + }, + } + } + + /// Returns the label of the event variant. pub fn label(&self) -> &F::String { &self.label } - /// The event arguments. + /// The event variant arguments. pub fn args(&self) -> &[EventParamSpec] { &self.args } - /// The event documentation. + /// The event variant documentation. pub fn docs(&self) -> &[F::String] { &self.docs } diff --git a/crates/metadata/src/tests.rs b/crates/metadata/src/tests.rs index 9beee133c0b..1b2aa216ca6 100644 --- a/crates/metadata/src/tests.rs +++ b/crates/metadata/src/tests.rs @@ -587,9 +587,12 @@ fn runtime_event_spec() -> EventSpec { .indexed(true) .docs(vec![]) .done()]; - EventSpec::new("foobar".into()) + let variants = [EventVariantSpec::new("EventVariant".to_string()) .args(args) - .docs(["foobar event"]) + .done()]; + EventSpec::new("foobar".into()) + .variants(variants) + .docs(["foobar event".into()]) .done() } diff --git a/crates/primitives/src/event.rs b/crates/primitives/src/event.rs new file mode 100644 index 00000000000..011aaeb6327 --- /dev/null +++ b/crates/primitives/src/event.rs @@ -0,0 +1,40 @@ +// Copyright 2018-2022 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. + +// use xxhash_rust::const_xxh3::xxh3_128; +// +// /// Generate the topic for the event signature. +// /// +// /// xxh3_128(path) ++ xxh3_128(event_variant) todo: + fields? +// pub const fn event_signature_topic( +// path: &'static str, +// event_variant: &'static str, +// ) -> [u8; 32] { +// let p = xxh3_128(path.as_bytes()).to_be_bytes(); +// // todo: add fields to signature? +// let s = xxh3_128(event_variant.as_bytes()).to_be_bytes(); +// [ +// p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], +// p[12], p[13], p[14], p[15], s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], +// s[8], s[9], s[10], s[11], s[12], s[13], s[14], s[15], +// ] +// } + +// pub const fn event_field_topic_prefix( +// path: &'static str, +// event_variant: &'static str, +// ) -> [u8; 32] { +// let path = xxh3_128(path.as_bytes()); +// let signature = xxh3_128() +// } diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 1054bb000c2..05eb9a69abf 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -28,6 +28,7 @@ )] #![cfg_attr(not(feature = "std"), no_std)] +mod event; mod key; mod types; diff --git a/integration-tests/dns/lib.rs b/integration-tests/dns/lib.rs index a3fcc1a095d..9ba8be9d563 100644 --- a/integration-tests/dns/lib.rs +++ b/integration-tests/dns/lib.rs @@ -4,37 +4,35 @@ mod dns { use ink::storage::Mapping; - /// Emitted whenever a new name is being registered. - #[ink(event)] - pub struct Register { - #[ink(topic)] - name: Hash, - #[ink(topic)] - from: AccountId, - } - - /// Emitted whenever an address changes. - #[ink(event)] - pub struct SetAddress { - #[ink(topic)] - name: Hash, - from: AccountId, - #[ink(topic)] - old_address: Option, - #[ink(topic)] - new_address: AccountId, - } - - /// Emitted whenever a name is being transferred. - #[ink(event)] - pub struct Transfer { - #[ink(topic)] - name: Hash, - from: AccountId, - #[ink(topic)] - old_owner: Option, - #[ink(topic)] - new_owner: AccountId, + #[ink::event_definition] + pub enum Event { + /// Emitted whenever a new name is being registered. + Register { + #[ink(topic)] + name: Hash, + #[ink(topic)] + from: AccountId, + }, + /// Emitted whenever an address changes. + SetAddress { + #[ink(topic)] + name: Hash, + from: AccountId, + #[ink(topic)] + old_address: Option, + #[ink(topic)] + new_address: AccountId, + }, + /// Emitted whenever a name is being transferred. + Transfer { + #[ink(topic)] + name: Hash, + from: AccountId, + #[ink(topic)] + old_owner: Option, + #[ink(topic)] + new_owner: AccountId, + }, } /// Domain name service contract inspired by @@ -106,7 +104,8 @@ mod dns { } self.name_to_owner.insert(name, &caller); - self.env().emit_event(Register { name, from: caller }); + self.env() + .emit_event(Event::Register { name, from: caller }); Ok(()) } @@ -123,7 +122,7 @@ mod dns { let old_address = self.name_to_address.get(name); self.name_to_address.insert(name, &new_address); - self.env().emit_event(SetAddress { + self.env().emit_event(Event::SetAddress { name, from: caller, old_address, @@ -144,7 +143,7 @@ mod dns { let old_owner = self.name_to_owner.get(name); self.name_to_owner.insert(name, &to); - self.env().emit_event(Transfer { + self.env().emit_event(Event::Transfer { name, from: caller, old_owner, diff --git a/integration-tests/erc1155/lib.rs b/integration-tests/erc1155/lib.rs index ec0bf7ba698..1fe8d2d5acb 100644 --- a/integration-tests/erc1155/lib.rs +++ b/integration-tests/erc1155/lib.rs @@ -198,37 +198,35 @@ mod erc1155 { type Owner = AccountId; type Operator = AccountId; - /// Indicate that a token transfer has occured. - /// - /// This must be emitted even if a zero value transfer occurs. - #[ink(event)] - pub struct TransferSingle { - #[ink(topic)] - operator: Option, - #[ink(topic)] - from: Option, - #[ink(topic)] - to: Option, - token_id: TokenId, - value: Balance, - } - - /// Indicate that an approval event has happened. - #[ink(event)] - pub struct ApprovalForAll { - #[ink(topic)] - owner: AccountId, - #[ink(topic)] - operator: AccountId, - approved: bool, - } - - /// Indicate that a token's URI has been updated. - #[ink(event)] - pub struct Uri { - value: ink::prelude::string::String, - #[ink(topic)] - token_id: TokenId, + #[ink::event_definition] + pub enum Event { + /// Indicate that a token transfer has occured. + /// + /// This must be emitted even if a zero value transfer occurs. + TransferSingle { + #[ink(topic)] + operator: Option, + #[ink(topic)] + from: Option, + #[ink(topic)] + to: Option, + token_id: TokenId, + value: Balance, + }, + /// Indicate that an approval event has happened. + ApprovalForAll { + #[ink(topic)] + owner: AccountId, + #[ink(topic)] + operator: AccountId, + approved: bool, + }, + /// Indicate that a token's URI has been updated. + Uri { + value: ink::prelude::string::String, + #[ink(topic)] + token_id: TokenId, + }, } /// An ERC-1155 contract. @@ -271,7 +269,7 @@ mod erc1155 { self.balances.insert((caller, self.token_id_nonce), &value); // Emit transfer event but with mint semantics - self.env().emit_event(TransferSingle { + self.env().emit_event(Event::TransferSingle { operator: Some(caller), from: None, to: if value == 0 { None } else { Some(caller) }, @@ -298,7 +296,7 @@ mod erc1155 { self.balances.insert((caller, token_id), &value); // Emit transfer event but with mint semantics - self.env().emit_event(TransferSingle { + self.env().emit_event(Event::TransferSingle { operator: Some(caller), from: None, to: Some(caller), @@ -337,7 +335,7 @@ mod erc1155 { self.balances.insert((to, token_id), &recipient_balance); let caller = self.env().caller(); - self.env().emit_event(TransferSingle { + self.env().emit_event(Event::TransferSingle { operator: Some(caller), from: Some(from), to: Some(to), @@ -536,7 +534,7 @@ mod erc1155 { self.approvals.remove((&caller, &operator)); } - self.env().emit_event(ApprovalForAll { + self.env().emit_event(Event::ApprovalForAll { owner: caller, operator, approved, diff --git a/integration-tests/erc20/lib.rs b/integration-tests/erc20/lib.rs index 4a26e5ed30f..b5a90ad70ec 100644 --- a/integration-tests/erc20/lib.rs +++ b/integration-tests/erc20/lib.rs @@ -17,25 +17,25 @@ mod erc20 { allowances: Mapping<(AccountId, AccountId), Balance>, } - /// Event emitted when a token transfer occurs. - #[ink(event)] - pub struct Transfer { - #[ink(topic)] - from: Option, - #[ink(topic)] - to: Option, - value: Balance, - } - - /// Event emitted when an approval occurs that `spender` is allowed to withdraw - /// up to the amount of `value` tokens from `owner`. - #[ink(event)] - pub struct Approval { - #[ink(topic)] - owner: AccountId, - #[ink(topic)] - spender: AccountId, - value: Balance, + #[ink::event_definition] + pub enum Event { + /// Event emitted when a token transfer occurs. + Transfer { + #[ink(topic)] + from: Option, + #[ink(topic)] + to: Option, + value: Balance, + }, + /// Event emitted when an approval occurs that `spender` is allowed to withdraw + /// up to the amount of `value` tokens from `owner`. + Approval { + #[ink(topic)] + owner: AccountId, + #[ink(topic)] + spender: AccountId, + value: Balance, + }, } /// The ERC-20 error types. @@ -58,7 +58,7 @@ mod erc20 { let mut balances = Mapping::default(); let caller = Self::env().caller(); balances.insert(caller, &total_supply); - Self::env().emit_event(Transfer { + Self::env().emit_event(Event::Transfer { from: None, to: Some(caller), value: total_supply, @@ -143,7 +143,7 @@ mod erc20 { pub fn approve(&mut self, spender: AccountId, value: Balance) -> Result<()> { let owner = self.env().caller(); self.allowances.insert((&owner, &spender), &value); - self.env().emit_event(Approval { + self.env().emit_event(Event::Approval { owner, spender, value, @@ -205,7 +205,7 @@ mod erc20 { self.balances.insert(from, &(from_balance - value)); let to_balance = self.balance_of_impl(to); self.balances.insert(to, &(to_balance + value)); - self.env().emit_event(Transfer { + self.env().emit_event(Event::Transfer { from: Some(*from), to: Some(*to), value, @@ -223,8 +223,6 @@ mod erc20 { Hash, }; - type Event = ::Type; - fn assert_transfer_event( event: &ink::env::test::EmittedEvent, expected_from: Option, @@ -233,7 +231,7 @@ mod erc20 { ) { let decoded_event = ::decode(&mut &event.data[..]) .expect("encountered invalid contract event data buffer"); - if let Event::Transfer(Transfer { from, to, value }) = decoded_event { + if let Event::Transfer { from, to, value } = decoded_event { assert_eq!(from, expected_from, "encountered invalid Transfer.from"); assert_eq!(to, expected_to, "encountered invalid Transfer.to"); assert_eq!(value, expected_value, "encountered invalid Trasfer.value"); diff --git a/integration-tests/erc721/lib.rs b/integration-tests/erc721/lib.rs index 17b638fe584..272aebc4b1c 100644 --- a/integration-tests/erc721/lib.rs +++ b/integration-tests/erc721/lib.rs @@ -89,37 +89,35 @@ mod erc721 { NotAllowed, } - /// Event emitted when a token transfer occurs. - #[ink(event)] - pub struct Transfer { - #[ink(topic)] - from: Option, - #[ink(topic)] - to: Option, - #[ink(topic)] - id: TokenId, - } - - /// Event emitted when a token approve occurs. - #[ink(event)] - pub struct Approval { - #[ink(topic)] - from: AccountId, - #[ink(topic)] - to: AccountId, - #[ink(topic)] - id: TokenId, - } - - /// Event emitted when an operator is enabled or disabled for an owner. - /// The operator can manage all NFTs of the owner. - #[ink(event)] - pub struct ApprovalForAll { - #[ink(topic)] - owner: AccountId, - #[ink(topic)] - operator: AccountId, - approved: bool, + #[ink::event_definition] + pub enum Event { + /// Event emitted when a token transfer occurs. + Transfer { + #[ink(topic)] + from: Option, + #[ink(topic)] + to: Option, + #[ink(topic)] + id: TokenId, + }, + /// Event emitted when a token approve occurs. + Approval { + #[ink(topic)] + from: AccountId, + #[ink(topic)] + to: AccountId, + #[ink(topic)] + id: TokenId, + }, + /// Event emitted when an operator is enabled or disabled for an owner. + /// The operator can manage all NFTs of the owner. + ApprovalForAll { + #[ink(topic)] + owner: AccountId, + #[ink(topic)] + operator: AccountId, + approved: bool, + }, } impl Erc721 { @@ -202,7 +200,7 @@ mod erc721 { pub fn mint(&mut self, id: TokenId) -> Result<(), Error> { let caller = self.env().caller(); self.add_token_to(&caller, id)?; - self.env().emit_event(Transfer { + self.env().emit_event(Event::Transfer { from: Some(AccountId::from([0x0; 32])), to: Some(caller), id, @@ -232,7 +230,7 @@ mod erc721 { owned_tokens_count.insert(caller, &count); token_owner.remove(id); - self.env().emit_event(Transfer { + self.env().emit_event(Event::Transfer { from: Some(caller), to: Some(AccountId::from([0x0; 32])), id, @@ -258,7 +256,7 @@ mod erc721 { self.clear_approval(id); self.remove_token_from(from, id)?; self.add_token_to(to, id)?; - self.env().emit_event(Transfer { + self.env().emit_event(Event::Transfer { from: Some(*from), to: Some(*to), id, @@ -326,7 +324,7 @@ mod erc721 { if to == caller { return Err(Error::NotAllowed) } - self.env().emit_event(ApprovalForAll { + self.env().emit_event(Event::ApprovalForAll { owner: caller, operator: to, approved, @@ -362,7 +360,7 @@ mod erc721 { self.token_approvals.insert(id, to); } - self.env().emit_event(Approval { + self.env().emit_event(Event::Approval { from: caller, to: *to, id, diff --git a/integration-tests/mother/lib.rs b/integration-tests/mother/lib.rs index eacf144dae6..233e9167113 100755 --- a/integration-tests/mother/lib.rs +++ b/integration-tests/mother/lib.rs @@ -124,10 +124,10 @@ mod mother { Panic, } - /// Event emitted when an auction being echoed. - #[ink(event)] - pub struct AuctionEchoed { - auction: Auction, + #[ink::event_definition] + pub enum Event { + /// Event emitted when an auction being echoed. + AuctionEchoed { auction: Auction }, } /// Storage of the contract. @@ -165,7 +165,7 @@ mod mother { /// Takes an auction data struct as input and returns it back. #[ink(message)] pub fn echo_auction(&mut self, auction: Auction) -> Auction { - self.env().emit_event(AuctionEchoed { + self.env().emit_event(Event::AuctionEchoed { auction: auction.clone(), }); auction diff --git a/integration-tests/multisig/lib.rs b/integration-tests/multisig/lib.rs index 27303a6d0dc..9a4115846c7 100755 --- a/integration-tests/multisig/lib.rs +++ b/integration-tests/multisig/lib.rs @@ -170,82 +170,69 @@ mod multisig { next_id: TransactionId, } - /// Emitted when an owner confirms a transaction. - #[ink(event)] - pub struct Confirmation { - /// The transaction that was confirmed. - #[ink(topic)] - transaction: TransactionId, - /// The owner that sent the confirmation. - #[ink(topic)] - from: AccountId, - /// The confirmation status after this confirmation was applied. - #[ink(topic)] - status: ConfirmationStatus, - } - - /// Emitted when an owner revoked a confirmation. - #[ink(event)] - pub struct Revocation { - /// The transaction that was revoked. - #[ink(topic)] - transaction: TransactionId, - /// The owner that sent the revocation. - #[ink(topic)] - from: AccountId, - } - - /// Emitted when an owner submits a transaction. - #[ink(event)] - pub struct Submission { - /// The transaction that was submitted. - #[ink(topic)] - transaction: TransactionId, - } - - /// Emitted when a transaction was canceled. - #[ink(event)] - pub struct Cancellation { - /// The transaction that was canceled. - #[ink(topic)] - transaction: TransactionId, - } - - /// Emitted when a transaction was executed. - #[ink(event)] - pub struct Execution { - /// The transaction that was executed. - #[ink(topic)] - transaction: TransactionId, - /// Indicates whether the transaction executed successfully. If so the `Ok` value - /// holds the output in bytes. The Option is `None` when the transaction - /// was executed through `invoke_transaction` rather than - /// `evaluate_transaction`. - #[ink(topic)] - result: Result>, Error>, - } - - /// Emitted when an owner is added to the wallet. - #[ink(event)] - pub struct OwnerAddition { - /// The owner that was added. - #[ink(topic)] - owner: AccountId, - } - - /// Emitted when an owner is removed from the wallet. - #[ink(event)] - pub struct OwnerRemoval { - /// The owner that was removed. - #[ink(topic)] - owner: AccountId, - } - - /// Emitted when the requirement changed. - #[ink(event)] - pub struct RequirementChange { - /// The new requirement value. - new_requirement: u32, + #[ink::event_definition] + pub enum Event { + /// Emitted when an owner confirms a transaction. + Confirmation { + /// The transaction that was confirmed. + #[ink(topic)] + transaction: TransactionId, + /// The owner that sent the confirmation. + #[ink(topic)] + from: AccountId, + /// The confirmation status after this confirmation was applied. + #[ink(topic)] + status: ConfirmationStatus, + }, + /// Emitted when an owner revoked a confirmation. + Revocation { + /// The transaction that was revoked. + #[ink(topic)] + transaction: TransactionId, + /// The owner that sent the revocation. + #[ink(topic)] + from: AccountId, + }, + /// Emitted when an owner submits a transaction. + Submission { + /// The transaction that was submitted. + #[ink(topic)] + transaction: TransactionId, + }, + /// Emitted when a transaction was canceled. + Cancellation { + /// The transaction that was canceled. + #[ink(topic)] + transaction: TransactionId, + }, + /// Emitted when a transaction was executed. + Execution { + /// The transaction that was executed. + #[ink(topic)] + transaction: TransactionId, + /// Indicates whether the transaction executed successfully. If so the `Ok` value holds + /// the output in bytes. The Option is `None` when the transaction was executed through + /// `invoke_transaction` rather than `evaluate_transaction`. + #[ink(topic)] + result: Result>, Error>, + }, + /// Emitted when an owner is added to the wallet. + OwnerAddition { + /// The owner that was added. + #[ink(topic)] + owner: AccountId, + }, + /// Emitted when an owner is removed from the wallet. + OwnerRemoval { + /// The owner that was removed. + #[ink(topic)] + owner: AccountId, + }, + /// Emitted when the requirement changed. + RequirementChange { + /// The new requirement value. + new_requirement: u32, + }, } #[ink(storage)] @@ -382,7 +369,8 @@ mod multisig { ensure_requirement_is_valid(self.owners.len() as u32 + 1, self.requirement); self.is_owner.insert(new_owner, &()); self.owners.push(new_owner); - self.env().emit_event(OwnerAddition { owner: new_owner }); + self.env() + .emit_event(Event::OwnerAddition { owner: new_owner }); } /// Remove an owner from the contract. @@ -406,7 +394,7 @@ mod multisig { self.is_owner.remove(owner); self.requirement = requirement; self.clean_owner_confirmations(&owner); - self.env().emit_event(OwnerRemoval { owner }); + self.env().emit_event(Event::OwnerRemoval { owner }); } /// Replace an owner from the contract with a new one. @@ -426,8 +414,10 @@ mod multisig { self.is_owner.remove(old_owner); self.is_owner.insert(new_owner, &()); self.clean_owner_confirmations(&old_owner); - self.env().emit_event(OwnerRemoval { owner: old_owner }); - self.env().emit_event(OwnerAddition { owner: new_owner }); + self.env() + .emit_event(Event::OwnerRemoval { owner: old_owner }); + self.env() + .emit_event(Event::OwnerAddition { owner: new_owner }); } /// Change the requirement to a new value. @@ -442,7 +432,8 @@ mod multisig { self.ensure_from_wallet(); ensure_requirement_is_valid(self.owners.len() as u32, new_requirement); self.requirement = new_requirement; - self.env().emit_event(RequirementChange { new_requirement }); + self.env() + .emit_event(Event::RequirementChange { new_requirement }); } /// Add a new transaction candidate to the contract. @@ -460,7 +451,7 @@ mod multisig { trans_id.checked_add(1).expect("Transaction ids exhausted."); self.transactions.insert(trans_id, &transaction); self.transaction_list.transactions.push(trans_id); - self.env().emit_event(Submission { + self.env().emit_event(Event::Submission { transaction: trans_id, }); ( @@ -479,7 +470,7 @@ mod multisig { pub fn cancel_transaction(&mut self, trans_id: TransactionId) { self.ensure_from_wallet(); if self.take_transaction(trans_id).is_some() { - self.env().emit_event(Cancellation { + self.env().emit_event(Event::Cancellation { transaction: trans_id, }); } @@ -525,7 +516,7 @@ mod multisig { confirmation_count -= 1; self.confirmation_count .insert(trans_id, &confirmation_count); - self.env().emit_event(Revocation { + self.env().emit_event(Event::Revocation { transaction: trans_id, from: caller, }); @@ -564,7 +555,7 @@ mod multisig { _ => Err(Error::TransactionFailed), }; - self.env().emit_event(Execution { + self.env().emit_event(Event::Execution { transaction: trans_id, result: result.map(|_| None), }); @@ -599,7 +590,7 @@ mod multisig { _ => Err(Error::TransactionFailed), }; - self.env().emit_event(Execution { + self.env().emit_event(Event::Execution { transaction: trans_id, result: result.clone().map(Some), }); @@ -630,7 +621,7 @@ mod multisig { } }; if new_confirmation { - self.env().emit_event(Confirmation { + self.env().emit_event(Event::Confirmation { transaction, from: confirmer, status, diff --git a/integration-tests/payment-channel/lib.rs b/integration-tests/payment-channel/lib.rs index 6ee4016973e..40da2876f44 100755 --- a/integration-tests/payment-channel/lib.rs +++ b/integration-tests/payment-channel/lib.rs @@ -87,11 +87,13 @@ mod payment_channel { /// Type alias for the contract's `Result` type. pub type Result = core::result::Result; - /// Emitted when the sender starts closing the channel. - #[ink(event)] - pub struct SenderCloseStarted { - expiration: Timestamp, - close_duration: Timestamp, + #[ink::event_definition] + pub enum Event { + /// Emitted when the sender starts closing the channel. + SenderCloseStarted { + expiration: Timestamp, + close_duration: Timestamp, + }, } impl PaymentChannel { @@ -159,7 +161,7 @@ mod payment_channel { let now = self.env().block_timestamp(); let expiration = now + self.close_duration; - self.env().emit_event(SenderCloseStarted { + self.env().emit_event(Event::SenderCloseStarted { expiration, close_duration: self.close_duration, }); diff --git a/integration-tests/rand-extension/lib.rs b/integration-tests/rand-extension/lib.rs index 3b61b057fc5..69eea7ae89a 100755 --- a/integration-tests/rand-extension/lib.rs +++ b/integration-tests/rand-extension/lib.rs @@ -64,10 +64,12 @@ mod rand_extension { value: [u8; 32], } - #[ink(event)] - pub struct RandomUpdated { - #[ink(topic)] - new: [u8; 32], + #[ink::event_definition] + pub enum Event { + RandomUpdated { + #[ink(topic)] + new: [u8; 32], + }, } impl RandExtension { @@ -95,7 +97,8 @@ mod rand_extension { self.value = new_random; // Emit the `RandomUpdated` event when the random seed // is successfully fetched. - self.env().emit_event(RandomUpdated { new: new_random }); + self.env() + .emit_event(Event::RandomUpdated { new: new_random }); Ok(()) } diff --git a/integration-tests/trait-erc20/lib.rs b/integration-tests/trait-erc20/lib.rs index 30f5517b725..8745701d224 100644 --- a/integration-tests/trait-erc20/lib.rs +++ b/integration-tests/trait-erc20/lib.rs @@ -64,27 +64,27 @@ mod erc20 { allowances: Mapping<(AccountId, AccountId), Balance>, } - /// Event emitted when a token transfer occurs. - #[ink(event)] - pub struct Transfer { - #[ink(topic)] - from: Option, - #[ink(topic)] - to: Option, - #[ink(topic)] - value: Balance, - } - - /// Event emitted when an approval occurs that `spender` is allowed to withdraw - /// up to the amount of `value` tokens from `owner`. - #[ink(event)] - pub struct Approval { - #[ink(topic)] - owner: AccountId, - #[ink(topic)] - spender: AccountId, - #[ink(topic)] - value: Balance, + #[ink::event_definition] + pub enum Event { + /// Event emitted when a token transfer occurs. + Transfer { + #[ink(topic)] + from: Option, + #[ink(topic)] + to: Option, + #[ink(topic)] + value: Balance, + }, + /// Event emitted when an approval occurs that `spender` is allowed to withdraw + /// up to the amount of `value` tokens from `owner`. + Approval { + #[ink(topic)] + owner: AccountId, + #[ink(topic)] + spender: AccountId, + #[ink(topic)] + value: Balance, + }, } impl Erc20 { @@ -94,7 +94,7 @@ mod erc20 { let mut balances = Mapping::default(); let caller = Self::env().caller(); balances.insert(caller, &total_supply); - Self::env().emit_event(Transfer { + Self::env().emit_event(Event::Transfer { from: None, to: Some(caller), value: total_supply, @@ -155,7 +155,7 @@ mod erc20 { fn approve(&mut self, spender: AccountId, value: Balance) -> Result<()> { let owner = self.env().caller(); self.allowances.insert((&owner, &spender), &value); - self.env().emit_event(Approval { + self.env().emit_event(Event::Approval { owner, spender, value, @@ -246,7 +246,7 @@ mod erc20 { self.balances.insert(from, &(from_balance - value)); let to_balance = self.balance_of_impl(to); self.balances.insert(to, &(to_balance + value)); - self.env().emit_event(Transfer { + self.env().emit_event(Event::Transfer { from: Some(*from), to: Some(*to), value,