From 8df58c33bd590732ffc8a43e7e95723987bef034 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Wed, 20 Dec 2023 19:05:40 +0000 Subject: [PATCH 01/20] basic setup --- Cargo.lock | 1 + Cargo.toml | 1 + crates/env/src/event.rs | 10 ++++ crates/ink/ir/Cargo.toml | 2 + crates/ink/ir/src/ir/attrs.rs | 14 +++++ crates/ink/ir/src/ir/event/config.rs | 52 +++++++++++++++++-- crates/ink/ir/src/ir/event/mod.rs | 3 ++ crates/ink/ir/src/ir/event/signature_topic.rs | 38 ++++++++++++++ crates/ink/ir/src/ir/mod.rs | 5 +- 9 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 crates/ink/ir/src/ir/event/signature_topic.rs diff --git a/Cargo.lock b/Cargo.lock index 8374fa4f51..e43d1868af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2771,6 +2771,7 @@ version = "5.0.0-rc" dependencies = [ "blake2", "either", + "hex", "ink_prelude 5.0.0-rc", "itertools 0.12.0", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 3bcd5ac43a..919ef1a144 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ wasm-instrument = { version = "0.4.0" } which = { version = "5.0.0" } xxhash-rust = { version = "0.8" } const_env = { version = "0.1"} +hex = { version = "0.4" } # Substrate dependencies pallet-contracts-primitives = { version = "26.0.0", default-features = false } diff --git a/crates/env/src/event.rs b/crates/env/src/event.rs index 859ef7515e..f949480589 100644 --- a/crates/env/src/event.rs +++ b/crates/env/src/event.rs @@ -206,6 +206,8 @@ pub trait Event: scale::Encode { /// default calculates this as `blake2b("Event(field1_type,field2_type)")` const SIGNATURE_TOPIC: Option<[u8; 32]>; + type SignatureTopic: GetSignatureTopic; + /// Guides event topic serialization using the given topics builder. fn topics( &self, @@ -215,3 +217,11 @@ pub trait Event: scale::Encode { E: Environment, B: TopicsBuilderBackend; } + +/// Getter that returns the signature topic for the specific event. +/// +/// It can be automatically calculated or manually specified. +pub trait GetSignatureTopic { + /// Retrieve the signature topic + fn signature_topic(&self) -> Option<[u8; 32]>; +} diff --git a/crates/ink/ir/Cargo.toml b/crates/ink/ir/Cargo.toml index 74fe25698b..24a74695cb 100644 --- a/crates/ink/ir/Cargo.toml +++ b/crates/ink/ir/Cargo.toml @@ -24,6 +24,8 @@ proc-macro2 = { workspace = true } itertools = { workspace = true } either = { workspace = true } blake2 = { workspace = true } +hex = { workspace = true } + ink_prelude = { workspace = true } [features] diff --git a/crates/ink/ir/src/ir/attrs.rs b/crates/ink/ir/src/ir/attrs.rs index 34d3f5feba..152929e7fe 100644 --- a/crates/ink/ir/src/ir/attrs.rs +++ b/crates/ink/ir/src/ir/attrs.rs @@ -43,6 +43,8 @@ use crate::{ }, }; +use super::SignatureTopic; + /// An extension trait for [`syn::Attribute`] in order to query for documentation. pub trait IsDocAttribute { /// Returns `true` if the attribute is a Rust documentation attribute. @@ -363,6 +365,8 @@ pub enum AttributeArgKind { /// `#[ink(selector = _)]` /// `#[ink(selector = 0xDEADBEEF)]` Selector, + /// `#[ink(signature_topic = "DEADBEEF")]` + SignatureTopic, /// `#[ink(function = N: u16)]` Function, /// `#[ink(namespace = "my_namespace")]` @@ -418,6 +422,8 @@ pub enum AttributeArg { /// - `#[ink(selector = _)]` Applied on ink! messages to define a fallback messages /// that is invoked if no other ink! message matches a given selector. Selector(SelectorOrWildcard), + /// `#[ink(signature_topic = "DEADBEEF")]` + SignatureTopic(SignatureTopic), /// `#[ink(namespace = "my_namespace")]` /// /// Applied on ink! trait implementation blocks to disambiguate other trait @@ -462,6 +468,9 @@ impl core::fmt::Display for AttributeArgKind { Self::Selector => { write!(f, "selector = S:[u8; 4] || _") } + Self::SignatureTopic => { + write!(f, "signature_topic = S:[u8; 32]") + } Self::Function => { write!(f, "function = N:u16)") } @@ -486,6 +495,7 @@ impl AttributeArg { Self::Constructor => AttributeArgKind::Constructor, Self::Payable => AttributeArgKind::Payable, Self::Selector(_) => AttributeArgKind::Selector, + Self::SignatureTopic(_) => AttributeArgKind::SignatureTopic, Self::Function(_) => AttributeArgKind::Function, Self::Namespace(_) => AttributeArgKind::Namespace, Self::Implementation => AttributeArgKind::Implementation, @@ -505,6 +515,10 @@ impl core::fmt::Display for AttributeArg { Self::Constructor => write!(f, "constructor"), Self::Payable => write!(f, "payable"), Self::Selector(selector) => core::fmt::Display::fmt(&selector, f), + Self::SignatureTopic(sig_topic) => { + // TODO + write!(f, "signature_topic") + } Self::Function(function) => { write!(f, "function = {:?}", function.into_u16()) } diff --git a/crates/ink/ir/src/ir/event/config.rs b/crates/ink/ir/src/ir/event/config.rs index 45280b7b8c..bfea87b92b 100644 --- a/crates/ink/ir/src/ir/event/config.rs +++ b/crates/ink/ir/src/ir/event/config.rs @@ -16,6 +16,9 @@ use crate::{ ast, utils::duplicate_config_err, }; +use hex::decode_to_slice; + +use super::signature_topic; /// The configuration arguments to the `#[ink::event(..)]` attribute macro. #[derive(Debug, PartialEq, Eq)] @@ -24,6 +27,9 @@ pub struct EventConfig { /// If set to `true`, **no** signature topic is generated or emitted for this event., /// This is the default value. anonymous: bool, + + /// Manually specified signature topic. + signature_topic: Option<[u8; 32]>, } impl TryFrom for EventConfig { @@ -31,10 +37,11 @@ impl TryFrom for EventConfig { fn try_from(args: ast::AttributeArgs) -> Result { let mut anonymous: Option = None; + let mut signature_topic: Option<[u8; 32]> = None; for arg in args.into_iter() { if arg.name.is_ident("anonymous") { if let Some(lit_bool) = anonymous { - return Err(duplicate_config_err(lit_bool, arg, "anonymous", "event")) + return Err(duplicate_config_err(lit_bool, arg, "anonymous", "event")); } if let ast::MetaValue::Lit(syn::Lit::Bool(lit_bool)) = &arg.value { anonymous = Some(lit_bool.clone()) @@ -44,6 +51,40 @@ impl TryFrom for EventConfig { "expected a bool literal for `anonymous` ink! event item configuration argument", )); } + } else if arg.name.is_ident("signature_topic") { + if anonymous.is_some() { + return Err(format_err_spanned!( + arg, + "cannot specify `signature_topic` with `anonymous` in ink! event item configuration argument", + )); + } + + if let Some(lit_str) = anonymous { + return Err(duplicate_config_err( + lit_str, + arg, + "signature_topic", + "event", + )); + } + if let ast::MetaValue::Lit(syn::Lit::Str(lit_str)) = &arg.value { + // signature_topic = Some(lit_str.clone()) + let mut bytes = [0u8; 32]; + + if decode_to_slice(lit_str.value(), &mut bytes).is_ok() { + signature_topic = Some(bytes); + } else { + return Err(format_err_spanned!( + arg, + "`signature_topic` has invalid hex string", + )); + } + } else { + return Err(format_err_spanned!( + arg, + "expected a string literal for `signature_topic` ink! event item configuration argument", + )); + } } else { return Err(format_err_spanned!( arg, @@ -51,16 +92,21 @@ impl TryFrom for EventConfig { )); } } + Ok(EventConfig::new( anonymous.map(|lit_bool| lit_bool.value).unwrap_or(false), + signature_topic, )) } } impl EventConfig { /// Construct a new [`EventConfig`]. - pub fn new(anonymous: bool) -> Self { - Self { anonymous } + pub fn new(anonymous: bool, signature_topic: Option<[u8; 32]>) -> Self { + Self { + anonymous, + signature_topic, + } } /// Returns the anonymous configuration argument. diff --git a/crates/ink/ir/src/ir/event/mod.rs b/crates/ink/ir/src/ir/event/mod.rs index 9a2e1b0b6e..671d1e0a2e 100644 --- a/crates/ink/ir/src/ir/event/mod.rs +++ b/crates/ink/ir/src/ir/event/mod.rs @@ -13,6 +13,7 @@ // limitations under the License. mod config; +mod signature_topic; use config::EventConfig; use proc_macro2::TokenStream as TokenStream2; @@ -24,6 +25,8 @@ use crate::{ ir, }; +pub use signature_topic::SignatureTopic; + /// A checked ink! event with its configuration. #[derive(Debug, PartialEq, Eq)] pub struct Event { diff --git a/crates/ink/ir/src/ir/event/signature_topic.rs b/crates/ink/ir/src/ir/event/signature_topic.rs new file mode 100644 index 0000000000..e02a38277b --- /dev/null +++ b/crates/ink/ir/src/ir/event/signature_topic.rs @@ -0,0 +1,38 @@ +// Copyright (C) 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::ir::blake2b_256; + +/// The signature topic of an event variant. +/// +/// Calculated with `blake2b("Event(field1_type,field2_type)")`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct SignatureTopic { + pub topic: Option<[u8; 32]>, +} + +impl SignatureTopic { + /// Computes the BLAKE-2 256-bit based signature topic from the given input bytes. + pub fn compute(input: &[u8]) -> Self { + assert!( + input.len() >= 32, + "Input array for signature topic is to short" + ); + let mut output = [0; 32]; + blake2b_256(input, &mut output); + Self { + topic: Some(output), + } + } +} diff --git a/crates/ink/ir/src/ir/mod.rs b/crates/ink/ir/src/ir/mod.rs index c7c28f681e..bb0a8564a2 100644 --- a/crates/ink/ir/src/ir/mod.rs +++ b/crates/ink/ir/src/ir/mod.rs @@ -70,7 +70,10 @@ pub use self::{ }, config::Config, contract::Contract, - event::Event, + event::{ + Event, + SignatureTopic, + }, ink_test::InkTest, item::{ InkItem, From ff82dcbd87e5bc6572b8491fcc669535ee7eeef4 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Wed, 20 Dec 2023 22:03:34 +0000 Subject: [PATCH 02/20] successful parsing of args --- Cargo.lock | 41 +++--- crates/ink/codegen/src/generator/event.rs | 2 + crates/ink/ir/src/ir/attrs.rs | 123 +++++++++--------- crates/ink/ir/src/ir/event/config.rs | 45 +++---- crates/ink/ir/src/ir/event/mod.rs | 60 +++++++-- crates/ink/ir/src/ir/event/signature_topic.rs | 54 ++++++-- 6 files changed, 197 insertions(+), 128 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e43d1868af..a88a87e82b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2197,6 +2197,16 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "getrandom_or_panic" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" +dependencies = [ + "rand 0.8.5", + "rand_core 0.6.4", +] + [[package]] name = "ghash" version = "0.5.0" @@ -2729,7 +2739,7 @@ dependencies = [ "scale-decode", "scale-encode", "scale-info", - "schnorrkel 0.11.3", + "schnorrkel 0.11.4", "secp256k1 0.28.0", "sha2 0.10.8", "sha3", @@ -2758,7 +2768,7 @@ dependencies = [ "scale-decode", "scale-encode", "scale-info", - "schnorrkel 0.11.3", + "schnorrkel 0.11.4", "secp256k1 0.28.0", "sha2 0.10.8", "sha3", @@ -3230,18 +3240,18 @@ dependencies = [ [[package]] name = "linkme" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ed2ee9464ff9707af8e9ad834cffa4802f072caad90639c583dd3c62e6e608" +checksum = "b1e6b0bb9ca88d3c5ae88240beb9683821f903b824ee8381ef9ab4e8522fbfa9" dependencies = [ "linkme-impl", ] [[package]] name = "linkme-impl" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba125974b109d512fccbc6c0244e7580143e460895dfd6ea7f8bbb692fd94396" +checksum = "b3b3f61e557a617ec6ba36c79431e1f3b5e100d67cfbdb61ed6ef384298af016" dependencies = [ "proc-macro2", "quote", @@ -4651,15 +4661,16 @@ dependencies = [ [[package]] name = "schnorrkel" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da18ffd9f2f5d01bc0b3050b37ce7728665f926b4dd1157fe3221b05737d924f" +checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" dependencies = [ + "aead", "arrayref", "arrayvec 0.7.4", "curve25519-dalek 4.1.1", + "getrandom_or_panic", "merlin 3.0.0", - "rand 0.8.5", "rand_core 0.6.4", "serde_bytes", "sha2 0.10.8", @@ -6140,9 +6151,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.34.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", @@ -7242,18 +7253,18 @@ checksum = "ff4524214bc4629eba08d78ceb1d6507070cc0bcbbed23af74e19e6e924a24cf" [[package]] name = "zerocopy" -version = "0.7.26" +version = "0.7.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0" +checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.26" +version = "0.7.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" +checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" dependencies = [ "proc-macro2", "quote", diff --git a/crates/ink/codegen/src/generator/event.rs b/crates/ink/codegen/src/generator/event.rs index 3bcbef53c6..5bd91e1219 100644 --- a/crates/ink/codegen/src/generator/event.rs +++ b/crates/ink/codegen/src/generator/event.rs @@ -31,6 +31,8 @@ impl GenerateCode for Event<'_> { .item .anonymous() .then(|| quote::quote! { #[ink(anonymous)] }); + + quote::quote! ( #[cfg_attr(feature = "std", derive(::ink::EventMetadata))] #[derive(::ink::Event)] diff --git a/crates/ink/ir/src/ir/attrs.rs b/crates/ink/ir/src/ir/attrs.rs index 152929e7fe..3542f70923 100644 --- a/crates/ink/ir/src/ir/attrs.rs +++ b/crates/ink/ir/src/ir/attrs.rs @@ -16,31 +16,20 @@ use core::result::Result; use std::collections::HashMap; use ink_prelude::IIP2_WILDCARD_COMPLEMENT_SELECTOR; -use proc_macro2::{ - Span, - TokenStream as TokenStream2, -}; +use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::ToTokens; use syn::{ - parse::{ - Parse, - ParseStream, - }, + parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, Token, }; use crate::{ - ast::{ - self, - }, + ast::{self}, error::ExtError as _, ir, - ir::{ - chain_extension::FunctionId, - Selector, - }, + ir::{chain_extension::FunctionId, Selector}, }; use super::SignatureTopic; @@ -61,13 +50,13 @@ impl IsDocAttribute for syn::Attribute { fn extract_docs(&self) -> Option { if !self.is_doc_attribute() { - return None + return None; } match &self.meta { syn::Meta::NameValue(nv) => { if let syn::Expr::Lit(l) = &nv.value { if let syn::Lit::Str(s) = &l.lit { - return Some(s.value()) + return Some(s.value()); } } } @@ -174,7 +163,7 @@ impl InkAttribute { return Err(format_err!( self.span(), "unexpected first ink! attribute argument", - )) + )); } Ok(()) } @@ -202,7 +191,7 @@ impl InkAttribute { .into_combine(format_err!( seen.span(), "first equal ink! attribute argument here" - ))) + ))); } if let Some(seen) = seen2.get(&arg.kind().kind()) { return Err(format_err!( @@ -212,7 +201,7 @@ impl InkAttribute { .into_combine(format_err!( *seen, "first equal ink! attribute argument with equal kind here" - ))) + ))); } seen.insert(arg); seen2.insert(arg.kind().kind(), arg.span()); @@ -244,7 +233,7 @@ impl InkAttribute { return Err(format_err!( Span::call_site(), "encountered unexpected empty expanded ink! attribute arguments", - )) + )); } Self::ensure_no_duplicate_args(&args)?; Ok(Self { args }) @@ -270,7 +259,7 @@ impl InkAttribute { pub fn namespace(&self) -> Option { self.args().find_map(|arg| { if let ir::AttributeArg::Namespace(namespace) = arg.kind() { - return Some(namespace.clone()) + return Some(namespace.clone()); } None }) @@ -280,7 +269,17 @@ impl InkAttribute { pub fn selector(&self) -> Option { self.args().find_map(|arg| { if let ir::AttributeArg::Selector(selector) = arg.kind() { - return Some(*selector) + return Some(*selector); + } + None + }) + } + + /// Returns the signature topic of the ink! attribute if any. + pub fn signature_topic(&self) -> Option { + self.args().find_map(|arg| { + if let ir::AttributeArg::SignatureTopic(topic) = arg.kind() { + return Some(*topic); } None }) @@ -516,8 +515,7 @@ impl core::fmt::Display for AttributeArg { Self::Payable => write!(f, "payable"), Self::Selector(selector) => core::fmt::Display::fmt(&selector, f), Self::SignatureTopic(sig_topic) => { - // TODO - write!(f, "signature_topic") + write!(f, "signature_topic = {:?}", sig_topic.signature_topic()) } Self::Function(function) => { write!(f, "function = {:?}", function.into_u16()) @@ -704,11 +702,9 @@ where .map(>::try_from) .collect::, syn::Error>>()? .into_iter() - .partition_map(|attr| { - match attr { - Attribute::Ink(ink_attr) => Either::Left(ink_attr), - Attribute::Other(other_attr) => Either::Right(other_attr), - } + .partition_map(|attr| match attr { + Attribute::Ink(ink_attr) => Either::Left(ink_attr), + Attribute::Other(other_attr) => Either::Right(other_attr), }); Attribute::ensure_no_duplicate_attrs(&ink_attrs)?; Ok((ink_attrs, others)) @@ -792,7 +788,7 @@ where { let (ink_attrs, rust_attrs) = ir::partition_attributes(attrs)?; if ink_attrs.is_empty() { - return Ok((None, rust_attrs)) + return Ok((None, rust_attrs)); } let normalized = ir::InkAttribute::from_expanded(ink_attrs).map_err(|err| { err.into_combine(format_err!(parent_span, "at this invocation",)) @@ -821,7 +817,7 @@ impl Attribute { attr.span(), "encountered duplicate ink! attribute" ) - .into_combine(format_err!(seen.span(), "first ink! attribute here"))) + .into_combine(format_err!(seen.span(), "first ink! attribute here"))); } seen.insert(attr); } @@ -834,7 +830,7 @@ impl TryFrom for Attribute { fn try_from(attr: syn::Attribute) -> Result { if attr.path().is_ident("ink") { - return >::try_from(attr).map(Into::into) + return >::try_from(attr).map(Into::into); } Ok(Attribute::Other(attr)) } @@ -851,7 +847,7 @@ impl TryFrom for InkAttribute { fn try_from(attr: syn::Attribute) -> Result { if !attr.path().is_ident("ink") { - return Err(format_err_spanned!(attr, "unexpected non-ink! attribute")) + return Err(format_err_spanned!(attr, "unexpected non-ink! attribute")); } let args: Vec<_> = attr @@ -864,7 +860,7 @@ impl TryFrom for InkAttribute { return Err(format_err_spanned!( attr, "encountered unsupported empty ink! attribute" - )) + )); } Ok(InkAttribute { args }) } @@ -912,7 +908,7 @@ impl InkAttribute { } } if let Some(err) = err { - return Err(err) + return Err(err); } Ok(()) } @@ -931,14 +927,12 @@ impl Parse for AttributeFrag { ) })?; match ident.to_string().as_str() { - "selector" => { - SelectorOrWildcard::try_from(&name_value.value) - .map(AttributeArg::Selector) - } - "namespace" => { - Namespace::try_from(&name_value.value) - .map(AttributeArg::Namespace) - } + "selector" => SelectorOrWildcard::try_from(&name_value.value) + .map(AttributeArg::Selector), + "signature_topic" => SignatureTopic::try_from(&name_value.value) + .map(AttributeArg::SignatureTopic), + "namespace" => Namespace::try_from(&name_value.value) + .map(AttributeArg::Namespace), "function" => { if let Some(lit_int) = name_value.value.as_lit_int() { let id = lit_int.base10_parse::() @@ -965,13 +959,11 @@ impl Parse for AttributeFrag { )) } } - _ => { - Err(format_err_spanned!( - ident, - "encountered unknown ink! attribute argument: {}", - ident - )) - } + _ => Err(format_err_spanned!( + ident, + "encountered unknown ink! attribute argument: {}", + ident + )), } } ast::Meta::Path(path) => { @@ -1093,15 +1085,13 @@ mod tests { impl From for Attribute { fn from(attr: ir::Attribute) -> Self { match attr { - ir::Attribute::Ink(ink_attr) => { - Self::Ink( - ink_attr - .args - .into_iter() - .map(|arg| arg.arg) - .collect::>(), - ) - } + ir::Attribute::Ink(ink_attr) => Self::Ink( + ink_attr + .args + .into_iter() + .map(|arg| arg.arg) + .collect::>(), + ), ir::Attribute::Other(other_attr) => Self::Other(other_attr), } } @@ -1522,4 +1512,17 @@ mod tests { Err("encountered duplicate ink! attribute"), ) } + #[test] + fn signature_topic_works() { + let bytes = [17u8; 32]; + let s = "11".repeat(32); + assert_attribute_try_from( + syn::parse_quote! { + #[ink(signature_topic = #s)] + }, + Ok(test::Attribute::Ink(vec![AttributeArg::SignatureTopic( + SignatureTopic::from(&bytes), + )])), + ); + } } diff --git a/crates/ink/ir/src/ir/event/config.rs b/crates/ink/ir/src/ir/event/config.rs index bfea87b92b..385545b7d0 100644 --- a/crates/ink/ir/src/ir/event/config.rs +++ b/crates/ink/ir/src/ir/event/config.rs @@ -12,13 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{ - ast, - utils::duplicate_config_err, -}; -use hex::decode_to_slice; +use crate::{ast, utils::duplicate_config_err}; -use super::signature_topic; +use super::SignatureTopic; /// The configuration arguments to the `#[ink::event(..)]` attribute macro. #[derive(Debug, PartialEq, Eq)] @@ -37,11 +33,16 @@ impl TryFrom for EventConfig { fn try_from(args: ast::AttributeArgs) -> Result { let mut anonymous: Option = None; - let mut signature_topic: Option<[u8; 32]> = None; + let mut signature_topic: Option = None; for arg in args.into_iter() { if arg.name.is_ident("anonymous") { if let Some(lit_bool) = anonymous { - return Err(duplicate_config_err(lit_bool, arg, "anonymous", "event")); + return Err(duplicate_config_err( + lit_bool, + arg, + "anonymous", + "event", + )); } if let ast::MetaValue::Lit(syn::Lit::Bool(lit_bool)) = &arg.value { anonymous = Some(lit_bool.clone()) @@ -67,24 +68,7 @@ impl TryFrom for EventConfig { "event", )); } - if let ast::MetaValue::Lit(syn::Lit::Str(lit_str)) = &arg.value { - // signature_topic = Some(lit_str.clone()) - let mut bytes = [0u8; 32]; - - if decode_to_slice(lit_str.value(), &mut bytes).is_ok() { - signature_topic = Some(bytes); - } else { - return Err(format_err_spanned!( - arg, - "`signature_topic` has invalid hex string", - )); - } - } else { - return Err(format_err_spanned!( - arg, - "expected a string literal for `signature_topic` ink! event item configuration argument", - )); - } + signature_topic = Some(SignatureTopic::try_from(&arg.value)?); } else { return Err(format_err_spanned!( arg, @@ -102,10 +86,10 @@ impl TryFrom for EventConfig { impl EventConfig { /// Construct a new [`EventConfig`]. - pub fn new(anonymous: bool, signature_topic: Option<[u8; 32]>) -> Self { + pub fn new(anonymous: bool, signature_topic: Option) -> Self { Self { anonymous, - signature_topic, + signature_topic: signature_topic.map(|s| s.signature_topic()), } } @@ -113,4 +97,9 @@ impl EventConfig { pub fn anonymous(&self) -> bool { self.anonymous } + + /// Returns the manually specified signature topic. + pub fn signature_topic(&self) -> Option<[u8; 32]> { + self.signature_topic + } } diff --git a/crates/ink/ir/src/ir/event/mod.rs b/crates/ink/ir/src/ir/event/mod.rs index 671d1e0a2e..bedea65f81 100644 --- a/crates/ink/ir/src/ir/event/mod.rs +++ b/crates/ink/ir/src/ir/event/mod.rs @@ -20,10 +20,7 @@ use proc_macro2::TokenStream as TokenStream2; use quote::ToTokens; use syn::spanned::Spanned as _; -use crate::{ - error::ExtError, - ir, -}; +use crate::{error::ExtError, ir}; pub use signature_topic::SignatureTopic; @@ -51,7 +48,7 @@ impl Event { return Err(format_err_spanned!( attr, "only one `ink::event` is allowed", - )) + )); } } @@ -78,7 +75,7 @@ impl Event { item_struct: &syn::ItemStruct, ) -> Result { if !ir::contains_ink_attributes(&item_struct.attrs) { - return Ok(false) + 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, @@ -93,6 +90,15 @@ impl Event { pub fn anonymous(&self) -> bool { self.config.anonymous() } + + /// Return manually specified signature topic. + /// + /// # Note + /// + /// Conflicts with `anonymous` + pub fn signature_topic(&self) -> Option<[u8; 32]> { + self.config.signature_topic() + } } impl ToTokens for Event { @@ -110,21 +116,30 @@ impl TryFrom for Event { let struct_span = item_struct.span(); let (ink_attrs, other_attrs) = ir::sanitize_attributes( struct_span, - item_struct.attrs, + item_struct.attrs.clone(), &ir::AttributeArgKind::Event, - |arg| { - match arg.kind() { - ir::AttributeArg::Event | ir::AttributeArg::Anonymous => Ok(()), - _ => Err(None), - } + |arg| match arg.kind() { + ir::AttributeArg::Event + | ir::AttributeArg::SignatureTopic(_) + | ir::AttributeArg::Anonymous => Ok(()), + _ => Err(None), }, )?; + if ink_attrs.is_anonymous() && ink_attrs.signature_topic().is_some() { + return Err(format_err_spanned!( + item_struct, + "cannot use use `anonymous` with `signature_topic`", + )); + } Ok(Self { item: syn::ItemStruct { attrs: other_attrs, ..item_struct }, - config: EventConfig::new(ink_attrs.is_anonymous()), + config: EventConfig::new( + ink_attrs.is_anonymous(), + ink_attrs.signature_topic(), + ), }) } } @@ -135,8 +150,10 @@ mod tests { #[test] fn simple_try_from_works() { + let s = "11".repeat(32); let item_struct: syn::ItemStruct = syn::parse_quote! { #[ink(event)] + #[ink(signature_topic = #s)] pub struct MyEvent { #[ink(topic)] field_1: i32, @@ -243,4 +260,21 @@ mod tests { } }); } + #[test] + fn signature_conflict_fails() { + let s = "11".repeat(32); + assert_try_from_fails( + syn::parse_quote! { + #[ink(event)] + #[ink(anonymous)] + #[ink(signature_topic = #s)] + pub struct MyEvent { + #[ink(topic)] + field_1: i32, + field_2: bool, + } + }, + "cannot use use `anonymous` with `signature_topic`", + ) + } } diff --git a/crates/ink/ir/src/ir/event/signature_topic.rs b/crates/ink/ir/src/ir/event/signature_topic.rs index e02a38277b..18acf9d331 100644 --- a/crates/ink/ir/src/ir/event/signature_topic.rs +++ b/crates/ink/ir/src/ir/event/signature_topic.rs @@ -12,27 +12,57 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::ir::blake2b_256; +use hex::decode_to_slice; + +use crate::ast; /// The signature topic of an event variant. /// /// Calculated with `blake2b("Event(field1_type,field2_type)")`. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SignatureTopic { - pub topic: Option<[u8; 32]>, + topic: [u8; 32], } impl SignatureTopic { - /// Computes the BLAKE-2 256-bit based signature topic from the given input bytes. - pub fn compute(input: &[u8]) -> Self { - assert!( - input.len() >= 32, - "Input array for signature topic is to short" - ); - let mut output = [0; 32]; - blake2b_256(input, &mut output); - Self { - topic: Some(output), + pub fn signature_topic(&self) -> [u8; 32] { + self.topic + } +} + +impl From<&[u8; 32]> for SignatureTopic { + fn from(value: &[u8; 32]) -> Self { + Self { topic: *value } + } +} + +impl TryFrom<&ast::MetaValue> for SignatureTopic { + type Error = syn::Error; + + fn try_from(value: &ast::MetaValue) -> Result { + if let ast::MetaValue::Lit(lit) = value { + if let syn::Lit::Str(s) = lit { + let mut bytes = [0u8; 32]; + + if decode_to_slice(s.value(), &mut bytes).is_ok() { + Ok(Self { topic: bytes }) + } else { + Err(format_err_spanned!( + value, + "`signature_topic` has invalid hex string", + )) + } + } else { + Err(format_err_spanned!( + value, + "Expected string literal argument for the `signature_topic`" + )) + } + } else { + Err(format_err_spanned!( + value, + "Expected string argument for the `signature_topic`" + )) } } } From 6706b968b32990f5ca703272304e725f39c2e5c0 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Thu, 21 Dec 2023 02:01:24 +0000 Subject: [PATCH 03/20] working specification --- crates/env/src/event.rs | 17 ++--- crates/env/src/lib.rs | 5 +- .../generator/as_dependency/contract_ref.rs | 4 +- crates/ink/codegen/src/generator/event.rs | 47 ++++++++++++ crates/ink/ir/src/ir/attrs.rs | 73 ++++++++++++------- crates/ink/ir/src/ir/event/config.rs | 12 ++- crates/ink/ir/src/ir/event/mod.rs | 17 +++-- crates/ink/macro/src/event/metadata.rs | 2 +- crates/ink/macro/src/event/mod.rs | 28 +------ crates/ink/macro/src/tests/event.rs | 29 ++++---- crates/ink/macro/src/tests/event_metadata.rs | 25 ++++++- 11 files changed, 163 insertions(+), 96 deletions(-) diff --git a/crates/env/src/event.rs b/crates/env/src/event.rs index f949480589..22ca8f8654 100644 --- a/crates/env/src/event.rs +++ b/crates/env/src/event.rs @@ -195,19 +195,11 @@ impl EventTopicsAmount for state::NoRemainingTopics { /// builder. /// /// Normally this trait should be implemented automatically via `#[derive(ink::Event)`. -pub trait Event: scale::Encode { +pub trait Event: scale::Encode + GetSignatureTopic { /// Type state indicating how many event topics are to be expected by the topics /// builder. type RemainingTopics: EventTopicsAmount; - /// The unique signature topic of the event. `None` for anonymous events. - /// - /// Usually this is calculated using the `#[derive(ink::Event)]` derive, which by - /// default calculates this as `blake2b("Event(field1_type,field2_type)")` - const SIGNATURE_TOPIC: Option<[u8; 32]>; - - type SignatureTopic: GetSignatureTopic; - /// Guides event topic serialization using the given topics builder. fn topics( &self, @@ -221,7 +213,12 @@ pub trait Event: scale::Encode { /// Getter that returns the signature topic for the specific event. /// /// It can be automatically calculated or manually specified. +/// +/// The unique signature topic of the event. `None` for anonymous events. +/// +/// Usually this is calculated using the `#[derive(ink::Event)]` derive, which by +/// default calculates this as `blake2b("Event(field1_type,field2_type)") pub trait GetSignatureTopic { /// Retrieve the signature topic - fn signature_topic(&self) -> Option<[u8; 32]>; + fn signature_topic() -> Option<[u8; 32]>; } diff --git a/crates/env/src/lib.rs b/crates/env/src/lib.rs index 2f0bbf5277..3cdfe9d908 100644 --- a/crates/env/src/lib.rs +++ b/crates/env/src/lib.rs @@ -120,7 +120,10 @@ pub use self::{ Error, Result, }, - event::Event, + event::{ + Event, + GetSignatureTopic, + }, types::{ AccountIdGuard, DefaultEnvironment, diff --git a/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs b/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs index a2c026b1f9..0801345fb6 100644 --- a/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs +++ b/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs @@ -81,8 +81,8 @@ impl ContractRef<'_> { .storage() .attrs() .iter() - .cloned() - .filter(syn::Attribute::is_doc_attribute); + .filter(|&x| syn::Attribute::is_doc_attribute(x)) + .cloned(); let storage_ident = self.contract.module().storage().ident(); let ref_ident = self.generate_contract_ref_ident(); quote_spanned!(span=> diff --git a/crates/ink/codegen/src/generator/event.rs b/crates/ink/codegen/src/generator/event.rs index 5bd91e1219..7ce9bbb6f9 100644 --- a/crates/ink/codegen/src/generator/event.rs +++ b/crates/ink/codegen/src/generator/event.rs @@ -14,7 +14,9 @@ use crate::GenerateCode; use derive_more::From; +use ir::HexLiteral; use proc_macro2::TokenStream as TokenStream2; +use quote::quote; /// Generates code for the storage item. #[derive(From, Copy, Clone)] @@ -32,8 +34,11 @@ impl GenerateCode for Event<'_> { .anonymous() .then(|| quote::quote! { #[ink(anonymous)] }); + let signature_topic = self.generate_signature_topic(); quote::quote! ( + #signature_topic + #[cfg_attr(feature = "std", derive(::ink::EventMetadata))] #[derive(::ink::Event)] #[::ink::scale_derive(Encode, Decode)] @@ -42,3 +47,45 @@ impl GenerateCode for Event<'_> { ) } } + +impl Event<'_> { + fn generate_signature_topic(&self) -> TokenStream2 { + let item_ident = &self.item.item().ident; + let signature_topic = if let Some(bytes) = self.item.signature_topic() { + let hash_bytes = bytes.map(|b| b.hex_padded_suffixed()); + quote! { + ::core::option::Option::Some([ #( #hash_bytes ),* ]) + } + } else if self.item.anonymous() { + quote! { ::core::option::Option::None } + } else { + let calculated_topic = signature_topic(&self.item.item().fields, item_ident); + quote! { ::core::option::Option::Some(#calculated_topic) } + }; + + quote! { + impl ::ink::env::GetSignatureTopic for #item_ident { + fn signature_topic() -> Option<[u8; 32]> { + #signature_topic + } + } + } + } +} + +/// The signature topic of an event variant. +/// +/// Calculated with `blake2b("Event(field1_type,field2_type)")`. +fn signature_topic(fields: &syn::Fields, event_ident: &syn::Ident) -> TokenStream2 { + let fields = fields + .iter() + .map(|field| { + quote::ToTokens::to_token_stream(&field.ty) + .to_string() + .replace(' ', "") + }) + .collect::>() + .join(","); + let topic_str = format!("{}({fields})", event_ident); + quote!(::ink::blake2x256!(#topic_str)) +} diff --git a/crates/ink/ir/src/ir/attrs.rs b/crates/ink/ir/src/ir/attrs.rs index 3542f70923..b912fa1cb8 100644 --- a/crates/ink/ir/src/ir/attrs.rs +++ b/crates/ink/ir/src/ir/attrs.rs @@ -16,20 +16,31 @@ use core::result::Result; use std::collections::HashMap; use ink_prelude::IIP2_WILDCARD_COMPLEMENT_SELECTOR; -use proc_macro2::{Span, TokenStream as TokenStream2}; +use proc_macro2::{ + Span, + TokenStream as TokenStream2, +}; use quote::ToTokens; use syn::{ - parse::{Parse, ParseStream}, + parse::{ + Parse, + ParseStream, + }, punctuated::Punctuated, spanned::Spanned, Token, }; use crate::{ - ast::{self}, + ast::{ + self, + }, error::ExtError as _, ir, - ir::{chain_extension::FunctionId, Selector}, + ir::{ + chain_extension::FunctionId, + Selector, + }, }; use super::SignatureTopic; @@ -702,9 +713,11 @@ where .map(>::try_from) .collect::, syn::Error>>()? .into_iter() - .partition_map(|attr| match attr { - Attribute::Ink(ink_attr) => Either::Left(ink_attr), - Attribute::Other(other_attr) => Either::Right(other_attr), + .partition_map(|attr| { + match attr { + Attribute::Ink(ink_attr) => Either::Left(ink_attr), + Attribute::Other(other_attr) => Either::Right(other_attr), + } }); Attribute::ensure_no_duplicate_attrs(&ink_attrs)?; Ok((ink_attrs, others)) @@ -927,12 +940,18 @@ impl Parse for AttributeFrag { ) })?; match ident.to_string().as_str() { - "selector" => SelectorOrWildcard::try_from(&name_value.value) - .map(AttributeArg::Selector), - "signature_topic" => SignatureTopic::try_from(&name_value.value) - .map(AttributeArg::SignatureTopic), - "namespace" => Namespace::try_from(&name_value.value) - .map(AttributeArg::Namespace), + "selector" => { + SelectorOrWildcard::try_from(&name_value.value) + .map(AttributeArg::Selector) + } + "signature_topic" => { + SignatureTopic::try_from(&name_value.value) + .map(AttributeArg::SignatureTopic) + } + "namespace" => { + Namespace::try_from(&name_value.value) + .map(AttributeArg::Namespace) + } "function" => { if let Some(lit_int) = name_value.value.as_lit_int() { let id = lit_int.base10_parse::() @@ -959,11 +978,13 @@ impl Parse for AttributeFrag { )) } } - _ => Err(format_err_spanned!( - ident, - "encountered unknown ink! attribute argument: {}", - ident - )), + _ => { + Err(format_err_spanned!( + ident, + "encountered unknown ink! attribute argument: {}", + ident + )) + } } } ast::Meta::Path(path) => { @@ -1085,13 +1106,15 @@ mod tests { impl From for Attribute { fn from(attr: ir::Attribute) -> Self { match attr { - ir::Attribute::Ink(ink_attr) => Self::Ink( - ink_attr - .args - .into_iter() - .map(|arg| arg.arg) - .collect::>(), - ), + ir::Attribute::Ink(ink_attr) => { + Self::Ink( + ink_attr + .args + .into_iter() + .map(|arg| arg.arg) + .collect::>(), + ) + } ir::Attribute::Other(other_attr) => Self::Other(other_attr), } } diff --git a/crates/ink/ir/src/ir/event/config.rs b/crates/ink/ir/src/ir/event/config.rs index 385545b7d0..2229252794 100644 --- a/crates/ink/ir/src/ir/event/config.rs +++ b/crates/ink/ir/src/ir/event/config.rs @@ -12,7 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{ast, utils::duplicate_config_err}; +use crate::{ + ast, + utils::duplicate_config_err, +}; use super::SignatureTopic; @@ -37,12 +40,7 @@ impl TryFrom for EventConfig { for arg in args.into_iter() { if arg.name.is_ident("anonymous") { if let Some(lit_bool) = anonymous { - return Err(duplicate_config_err( - lit_bool, - arg, - "anonymous", - "event", - )); + return Err(duplicate_config_err(lit_bool, arg, "anonymous", "event")); } if let ast::MetaValue::Lit(syn::Lit::Bool(lit_bool)) = &arg.value { anonymous = Some(lit_bool.clone()) diff --git a/crates/ink/ir/src/ir/event/mod.rs b/crates/ink/ir/src/ir/event/mod.rs index bedea65f81..e6828ebec8 100644 --- a/crates/ink/ir/src/ir/event/mod.rs +++ b/crates/ink/ir/src/ir/event/mod.rs @@ -20,7 +20,10 @@ use proc_macro2::TokenStream as TokenStream2; use quote::ToTokens; use syn::spanned::Spanned as _; -use crate::{error::ExtError, ir}; +use crate::{ + error::ExtError, + ir, +}; pub use signature_topic::SignatureTopic; @@ -118,11 +121,13 @@ impl TryFrom for Event { struct_span, item_struct.attrs.clone(), &ir::AttributeArgKind::Event, - |arg| match arg.kind() { - ir::AttributeArg::Event - | ir::AttributeArg::SignatureTopic(_) - | ir::AttributeArg::Anonymous => Ok(()), - _ => Err(None), + |arg| { + match arg.kind() { + ir::AttributeArg::Event + | ir::AttributeArg::SignatureTopic(_) + | ir::AttributeArg::Anonymous => Ok(()), + _ => Err(None), + } }, )?; if ink_attrs.is_anonymous() && ink_attrs.signature_topic().is_some() { diff --git a/crates/ink/macro/src/event/metadata.rs b/crates/ink/macro/src/event/metadata.rs index 5c9a70cd9a..0c44808e04 100644 --- a/crates/ink/macro/src/event/metadata.rs +++ b/crates/ink/macro/src/event/metadata.rs @@ -91,7 +91,7 @@ fn event_metadata_derive_struct(s: synstructure::Structure) -> syn::Result::SIGNATURE_TOPIC + ::signature_topic() ) .args([ #( #args ),* diff --git a/crates/ink/macro/src/event/mod.rs b/crates/ink/macro/src/event/mod.rs index 06bddf8b8d..1e95e14fd0 100644 --- a/crates/ink/macro/src/event/mod.rs +++ b/crates/ink/macro/src/event/mod.rs @@ -96,18 +96,11 @@ fn event_derive_struct(mut s: synstructure::Structure) -> syn::Result ::core::option::Option::Some(#signature_topic)) - } else { - quote_spanned!(span=> ::core::option::Option::None) - }; let event_signature_topic = if anonymous { None } else { Some(quote_spanned!(span=> - .push_topic(Self::SIGNATURE_TOPIC.as_ref()) + .push_topic(::signature_topic().as_ref()) )) }; @@ -133,8 +126,6 @@ fn event_derive_struct(mut s: synstructure::Structure) -> syn::Result = #signature_topic; - fn topics( &self, builder: ::ink::env::event::TopicsBuilder<::ink::env::event::state::Uninit, E, B>, @@ -150,23 +141,6 @@ fn event_derive_struct(mut s: synstructure::Structure) -> syn::Result TokenStream2 { - let fields = fields - .iter() - .map(|field| { - quote::ToTokens::to_token_stream(&field.ty) - .to_string() - .replace(' ', "") - }) - .collect::>() - .join(","); - let topic_str = format!("{}({fields})", event_ident); - quote!(::ink::blake2x256!(#topic_str)) -} - /// Checks if the given field's attributes contain an `#[ink(topic)]` attribute. /// /// Returns `Err` if: diff --git a/crates/ink/macro/src/tests/event.rs b/crates/ink/macro/src/tests/event.rs index 926f93ba84..ee07f6387a 100644 --- a/crates/ink/macro/src/tests/event.rs +++ b/crates/ink/macro/src/tests/event.rs @@ -28,15 +28,18 @@ fn unit_struct_works() { event_derive { #[derive(scale::Encode)] struct UnitStruct; + + impl ::ink::env::GetSignatureTopic for UnitStruct { + fn signature_topic() -> Option<[u8; 32]> { + Some([0; 32]) + } + } } expands to { const _: () = { impl ::ink::env::Event for UnitStruct { type RemainingTopics = [::ink::env::event::state::HasRemainingTopics; 1usize]; - const SIGNATURE_TOPIC: ::core::option::Option<[::core::primitive::u8; 32]> = - ::core::option::Option::Some( ::ink::blake2x256!("UnitStruct()") ); - fn topics( &self, builder: ::ink::env::event::TopicsBuilder<::ink::env::event::state::Uninit, E, B>, @@ -49,7 +52,7 @@ fn unit_struct_works() { UnitStruct => { builder .build::() - .push_topic(Self::SIGNATURE_TOPIC.as_ref()) + .push_topic(::signature_topic().as_ref()) .finish() } } @@ -73,9 +76,6 @@ fn unit_struct_anonymous_has_no_topics() { impl ::ink::env::Event for UnitStruct { type RemainingTopics = ::ink::env::event::state::NoRemainingTopics; - const SIGNATURE_TOPIC: ::core::option::Option<[::core::primitive::u8; 32]> = - ::core::option::Option::None; - fn topics( &self, builder: ::ink::env::event::TopicsBuilder<::ink::env::event::state::Uninit, E, B>, @@ -108,15 +108,18 @@ fn struct_with_fields_no_topics() { field_2: u64, field_3: u128, } + + impl ::ink::env::GetSignatureTopic for Event { + fn signature_topic() -> Option<[u8; 32]> { + Some([0; 32]) + } + } } expands to { const _: () = { impl ::ink::env::Event for Event { type RemainingTopics = [::ink::env::event::state::HasRemainingTopics; 1usize]; - const SIGNATURE_TOPIC: ::core::option::Option<[::core::primitive::u8; 32]> = - ::core::option::Option::Some( ::ink::blake2x256!("Event(u32,u64,u128)") ); - fn topics( &self, builder: ::ink::env::event::TopicsBuilder<::ink::env::event::state::Uninit, E, B>, @@ -129,7 +132,7 @@ fn struct_with_fields_no_topics() { Event { .. } => { builder .build::() - .push_topic(Self::SIGNATURE_TOPIC.as_ref()) + .push_topic(::signature_topic().as_ref()) .finish() } } @@ -158,8 +161,6 @@ fn struct_with_fields_and_some_topics() { impl ::ink::env::Event for Event { type RemainingTopics = [::ink::env::event::state::HasRemainingTopics; 3usize]; - const SIGNATURE_TOPIC: ::core::option::Option<[::core::primitive::u8; 32]> = - ::core::option::Option::Some( ::ink::blake2x256!("Event(u32,u64,u128)") ); fn topics( &self, @@ -173,7 +174,7 @@ fn struct_with_fields_and_some_topics() { Event { field_2 : __binding_1 , field_3 : __binding_2 , .. } => { builder .build::() - .push_topic(Self::SIGNATURE_TOPIC.as_ref()) + .push_topic(::signature_topic().as_ref()) .push_topic(::ink::as_option!(__binding_1)) .push_topic(::ink::as_option!(__binding_2)) .finish() diff --git a/crates/ink/macro/src/tests/event_metadata.rs b/crates/ink/macro/src/tests/event_metadata.rs index e2abc973c3..b4462326fc 100644 --- a/crates/ink/macro/src/tests/event_metadata.rs +++ b/crates/ink/macro/src/tests/event_metadata.rs @@ -20,6 +20,13 @@ fn unit_struct_works() { event_metadata_derive { #[derive(ink::Event, scale::Encode)] struct UnitStruct; + + impl ::ink::env::GetSignatureTopic for UnitStruct { + fn signature_topic() -> Option<[u8; 32]> { + Some([0; 32]) + } + } + } expands to { const _: () = { @@ -34,7 +41,7 @@ fn unit_struct_works() { ::ink::metadata::EventSpec::new(::core::stringify!(UnitStruct)) .module_path(::core::module_path!()) - .signature_topic(::SIGNATURE_TOPIC) + .signature_topic(::signature_topic()) .args([]) .docs([]) .done() @@ -55,6 +62,12 @@ fn struct_with_fields_no_topics() { field_2: u64, field_3: u128, } + + impl ::ink::env::GetSignatureTopic for Event { + fn signature_topic() -> Option<[u8; 32]> { + Some([0; 32]) + } + } } expands to { const _: () = { @@ -69,7 +82,7 @@ fn struct_with_fields_no_topics() { ::ink::metadata::EventSpec::new(::core::stringify!(Event)) .module_path(::core::module_path!()) - .signature_topic(::SIGNATURE_TOPIC) + .signature_topic(::signature_topic()) .args([ ::ink::metadata::EventParamSpec::new(::core::stringify!(field_1)) .of_type(::ink::metadata::TypeSpec::of_type::()) @@ -108,6 +121,12 @@ fn struct_with_fields_and_some_topics() { #[ink(topic)] field_3: u128, } + + impl ::ink::env::GetSignatureTopic for Event { + fn signature_topic() -> Option<[u8; 32]> { + Some([0; 32]) + } + } } expands to { const _: () = { @@ -122,7 +141,7 @@ fn struct_with_fields_and_some_topics() { ::ink::metadata::EventSpec::new(::core::stringify!(Event)) .module_path(::core::module_path!()) - .signature_topic(::SIGNATURE_TOPIC) + .signature_topic(::signature_topic()) .args([ ::ink::metadata::EventParamSpec::new(::core::stringify!(field_1)) .of_type(::ink::metadata::TypeSpec::of_type::()) From 14429acb395dfc06b47562f977228078bef8ff50 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Thu, 21 Dec 2023 18:06:02 +0000 Subject: [PATCH 04/20] fix tests --- crates/env/src/event.rs | 2 +- crates/ink/codegen/src/generator/event.rs | 8 ++++-- crates/ink/ir/src/ir/attrs.rs | 16 ++++++------ crates/ink/ir/src/ir/event/config.rs | 8 +++--- crates/ink/ir/src/ir/event/mod.rs | 13 ++++++++-- crates/ink/ir/src/ir/event/signature_topic.rs | 8 +++--- crates/ink/ir/src/ir/mod.rs | 2 +- crates/ink/macro/src/tests/event.rs | 18 +++---------- crates/ink/macro/src/tests/event_metadata.rs | 25 +++---------------- crates/ink/macro/src/tests/mod.rs | 1 + integration-tests/events/lib.rs | 10 ++++---- 11 files changed, 47 insertions(+), 64 deletions(-) diff --git a/crates/env/src/event.rs b/crates/env/src/event.rs index 22ca8f8654..72a560565b 100644 --- a/crates/env/src/event.rs +++ b/crates/env/src/event.rs @@ -217,7 +217,7 @@ pub trait Event: scale::Encode + GetSignatureTopic { /// The unique signature topic of the event. `None` for anonymous events. /// /// Usually this is calculated using the `#[derive(ink::Event)]` derive, which by -/// default calculates this as `blake2b("Event(field1_type,field2_type)") +/// default calculates this as `blake2b("Event(field1_type,field2_type)")` pub trait GetSignatureTopic { /// Retrieve the signature topic fn signature_topic() -> Option<[u8; 32]>; diff --git a/crates/ink/codegen/src/generator/event.rs b/crates/ink/codegen/src/generator/event.rs index 7ce9bbb6f9..4f8a627886 100644 --- a/crates/ink/codegen/src/generator/event.rs +++ b/crates/ink/codegen/src/generator/event.rs @@ -17,8 +17,9 @@ use derive_more::From; use ir::HexLiteral; use proc_macro2::TokenStream as TokenStream2; use quote::quote; +use syn::spanned::Spanned; -/// Generates code for the storage item. +/// Generates code for the event item. #[derive(From, Copy, Clone)] pub struct Event<'a> { /// The storage item to generate code for. @@ -26,7 +27,7 @@ pub struct Event<'a> { } impl GenerateCode for Event<'_> { - /// Generates ink! storage item code. + /// Generates ink! event item code. fn generate_code(&self) -> TokenStream2 { let item = self.item.item(); let anonymous = self @@ -35,10 +36,13 @@ impl GenerateCode for Event<'_> { .then(|| quote::quote! { #[ink(anonymous)] }); let signature_topic = self.generate_signature_topic(); + let cfg_attrs = self.item.get_cfg_attrs(item.span()); quote::quote! ( + #( #cfg_attrs )* #signature_topic + #( #cfg_attrs )* #[cfg_attr(feature = "std", derive(::ink::EventMetadata))] #[derive(::ink::Event)] #[::ink::scale_derive(Encode, Decode)] diff --git a/crates/ink/ir/src/ir/attrs.rs b/crates/ink/ir/src/ir/attrs.rs index b912fa1cb8..79933ebacb 100644 --- a/crates/ink/ir/src/ir/attrs.rs +++ b/crates/ink/ir/src/ir/attrs.rs @@ -43,7 +43,7 @@ use crate::{ }, }; -use super::SignatureTopic; +use super::SignatureTopicArg; /// An extension trait for [`syn::Attribute`] in order to query for documentation. pub trait IsDocAttribute { @@ -287,7 +287,7 @@ impl InkAttribute { } /// Returns the signature topic of the ink! attribute if any. - pub fn signature_topic(&self) -> Option { + pub fn signature_topic(&self) -> Option { self.args().find_map(|arg| { if let ir::AttributeArg::SignatureTopic(topic) = arg.kind() { return Some(*topic); @@ -376,7 +376,7 @@ pub enum AttributeArgKind { /// `#[ink(selector = 0xDEADBEEF)]` Selector, /// `#[ink(signature_topic = "DEADBEEF")]` - SignatureTopic, + SignatureTopicArg, /// `#[ink(function = N: u16)]` Function, /// `#[ink(namespace = "my_namespace")]` @@ -433,7 +433,7 @@ pub enum AttributeArg { /// that is invoked if no other ink! message matches a given selector. Selector(SelectorOrWildcard), /// `#[ink(signature_topic = "DEADBEEF")]` - SignatureTopic(SignatureTopic), + SignatureTopic(SignatureTopicArg), /// `#[ink(namespace = "my_namespace")]` /// /// Applied on ink! trait implementation blocks to disambiguate other trait @@ -478,7 +478,7 @@ impl core::fmt::Display for AttributeArgKind { Self::Selector => { write!(f, "selector = S:[u8; 4] || _") } - Self::SignatureTopic => { + Self::SignatureTopicArg => { write!(f, "signature_topic = S:[u8; 32]") } Self::Function => { @@ -505,7 +505,7 @@ impl AttributeArg { Self::Constructor => AttributeArgKind::Constructor, Self::Payable => AttributeArgKind::Payable, Self::Selector(_) => AttributeArgKind::Selector, - Self::SignatureTopic(_) => AttributeArgKind::SignatureTopic, + Self::SignatureTopic(_) => AttributeArgKind::SignatureTopicArg, Self::Function(_) => AttributeArgKind::Function, Self::Namespace(_) => AttributeArgKind::Namespace, Self::Implementation => AttributeArgKind::Implementation, @@ -945,7 +945,7 @@ impl Parse for AttributeFrag { .map(AttributeArg::Selector) } "signature_topic" => { - SignatureTopic::try_from(&name_value.value) + SignatureTopicArg::try_from(&name_value.value) .map(AttributeArg::SignatureTopic) } "namespace" => { @@ -1544,7 +1544,7 @@ mod tests { #[ink(signature_topic = #s)] }, Ok(test::Attribute::Ink(vec![AttributeArg::SignatureTopic( - SignatureTopic::from(&bytes), + SignatureTopicArg::from(&bytes), )])), ); } diff --git a/crates/ink/ir/src/ir/event/config.rs b/crates/ink/ir/src/ir/event/config.rs index 2229252794..a87cdd2c18 100644 --- a/crates/ink/ir/src/ir/event/config.rs +++ b/crates/ink/ir/src/ir/event/config.rs @@ -17,7 +17,7 @@ use crate::{ utils::duplicate_config_err, }; -use super::SignatureTopic; +use super::SignatureTopicArg; /// The configuration arguments to the `#[ink::event(..)]` attribute macro. #[derive(Debug, PartialEq, Eq)] @@ -36,7 +36,7 @@ impl TryFrom for EventConfig { fn try_from(args: ast::AttributeArgs) -> Result { let mut anonymous: Option = None; - let mut signature_topic: Option = None; + let mut signature_topic: Option = None; for arg in args.into_iter() { if arg.name.is_ident("anonymous") { if let Some(lit_bool) = anonymous { @@ -66,7 +66,7 @@ impl TryFrom for EventConfig { "event", )); } - signature_topic = Some(SignatureTopic::try_from(&arg.value)?); + signature_topic = Some(SignatureTopicArg::try_from(&arg.value)?); } else { return Err(format_err_spanned!( arg, @@ -84,7 +84,7 @@ impl TryFrom for EventConfig { impl EventConfig { /// Construct a new [`EventConfig`]. - pub fn new(anonymous: bool, signature_topic: Option) -> Self { + pub fn new(anonymous: bool, signature_topic: Option) -> Self { Self { anonymous, signature_topic: signature_topic.map(|s| s.signature_topic()), diff --git a/crates/ink/ir/src/ir/event/mod.rs b/crates/ink/ir/src/ir/event/mod.rs index e6828ebec8..bc0224c002 100644 --- a/crates/ink/ir/src/ir/event/mod.rs +++ b/crates/ink/ir/src/ir/event/mod.rs @@ -16,16 +16,20 @@ mod config; mod signature_topic; use config::EventConfig; -use proc_macro2::TokenStream as TokenStream2; +use proc_macro2::{ + Span, + TokenStream as TokenStream2, +}; use quote::ToTokens; use syn::spanned::Spanned as _; use crate::{ error::ExtError, ir, + utils::extract_cfg_attributes, }; -pub use signature_topic::SignatureTopic; +pub use signature_topic::SignatureTopicArg; /// A checked ink! event with its configuration. #[derive(Debug, PartialEq, Eq)] @@ -102,6 +106,11 @@ impl Event { pub fn signature_topic(&self) -> Option<[u8; 32]> { self.config.signature_topic() } + + /// Returns a list of `cfg` attributes if any. + pub fn get_cfg_attrs(&self, span: Span) -> Vec { + extract_cfg_attributes(&self.item.attrs, span) + } } impl ToTokens for Event { diff --git a/crates/ink/ir/src/ir/event/signature_topic.rs b/crates/ink/ir/src/ir/event/signature_topic.rs index 18acf9d331..df688b5d1d 100644 --- a/crates/ink/ir/src/ir/event/signature_topic.rs +++ b/crates/ink/ir/src/ir/event/signature_topic.rs @@ -20,23 +20,23 @@ use crate::ast; /// /// Calculated with `blake2b("Event(field1_type,field2_type)")`. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct SignatureTopic { +pub struct SignatureTopicArg { topic: [u8; 32], } -impl SignatureTopic { +impl SignatureTopicArg { pub fn signature_topic(&self) -> [u8; 32] { self.topic } } -impl From<&[u8; 32]> for SignatureTopic { +impl From<&[u8; 32]> for SignatureTopicArg { fn from(value: &[u8; 32]) -> Self { Self { topic: *value } } } -impl TryFrom<&ast::MetaValue> for SignatureTopic { +impl TryFrom<&ast::MetaValue> for SignatureTopicArg { type Error = syn::Error; fn try_from(value: &ast::MetaValue) -> Result { diff --git a/crates/ink/ir/src/ir/mod.rs b/crates/ink/ir/src/ir/mod.rs index bb0a8564a2..14e1f9b9b2 100644 --- a/crates/ink/ir/src/ir/mod.rs +++ b/crates/ink/ir/src/ir/mod.rs @@ -72,7 +72,7 @@ pub use self::{ contract::Contract, event::{ Event, - SignatureTopic, + SignatureTopicArg, }, ink_test::InkTest, item::{ diff --git a/crates/ink/macro/src/tests/event.rs b/crates/ink/macro/src/tests/event.rs index ee07f6387a..6390db5d31 100644 --- a/crates/ink/macro/src/tests/event.rs +++ b/crates/ink/macro/src/tests/event.rs @@ -28,12 +28,6 @@ fn unit_struct_works() { event_derive { #[derive(scale::Encode)] struct UnitStruct; - - impl ::ink::env::GetSignatureTopic for UnitStruct { - fn signature_topic() -> Option<[u8; 32]> { - Some([0; 32]) - } - } } expands to { const _: () = { @@ -59,7 +53,7 @@ fn unit_struct_works() { } } }; - } + } no_build } } @@ -108,12 +102,6 @@ fn struct_with_fields_no_topics() { field_2: u64, field_3: u128, } - - impl ::ink::env::GetSignatureTopic for Event { - fn signature_topic() -> Option<[u8; 32]> { - Some([0; 32]) - } - } } expands to { const _: () = { @@ -139,7 +127,7 @@ fn struct_with_fields_no_topics() { } } }; - } + } no_build } } @@ -174,7 +162,7 @@ fn struct_with_fields_and_some_topics() { Event { field_2 : __binding_1 , field_3 : __binding_2 , .. } => { builder .build::() - .push_topic(::signature_topic().as_ref()) + .push_topic(::signature_topic().as_ref()) .push_topic(::ink::as_option!(__binding_1)) .push_topic(::ink::as_option!(__binding_2)) .finish() diff --git a/crates/ink/macro/src/tests/event_metadata.rs b/crates/ink/macro/src/tests/event_metadata.rs index b4462326fc..03d11f7eef 100644 --- a/crates/ink/macro/src/tests/event_metadata.rs +++ b/crates/ink/macro/src/tests/event_metadata.rs @@ -20,13 +20,6 @@ fn unit_struct_works() { event_metadata_derive { #[derive(ink::Event, scale::Encode)] struct UnitStruct; - - impl ::ink::env::GetSignatureTopic for UnitStruct { - fn signature_topic() -> Option<[u8; 32]> { - Some([0; 32]) - } - } - } expands to { const _: () = { @@ -48,7 +41,7 @@ fn unit_struct_works() { } } }; - } + } no_build } } @@ -62,12 +55,6 @@ fn struct_with_fields_no_topics() { field_2: u64, field_3: u128, } - - impl ::ink::env::GetSignatureTopic for Event { - fn signature_topic() -> Option<[u8; 32]> { - Some([0; 32]) - } - } } expands to { const _: () = { @@ -105,7 +92,7 @@ fn struct_with_fields_no_topics() { } } }; - } + } no_build } } @@ -121,12 +108,6 @@ fn struct_with_fields_and_some_topics() { #[ink(topic)] field_3: u128, } - - impl ::ink::env::GetSignatureTopic for Event { - fn signature_topic() -> Option<[u8; 32]> { - Some([0; 32]) - } - } } expands to { const _: () = { @@ -164,6 +145,6 @@ fn struct_with_fields_and_some_topics() { } } }; - } + } no_build } } diff --git a/crates/ink/macro/src/tests/mod.rs b/crates/ink/macro/src/tests/mod.rs index 5d9df2e44d..1844132ad1 100644 --- a/crates/ink/macro/src/tests/mod.rs +++ b/crates/ink/macro/src/tests/mod.rs @@ -26,6 +26,7 @@ use crate::storage::{ storage_layout_derive, }; +#[cfg(test)] #[macro_export] macro_rules! test_derive { ($name:path { $($i:tt)* } expands to { $($o:tt)* }) => { diff --git a/integration-tests/events/lib.rs b/integration-tests/events/lib.rs index e1b56ae79a..5a8a66dd2c 100644 --- a/integration-tests/events/lib.rs +++ b/integration-tests/events/lib.rs @@ -147,7 +147,7 @@ pub mod events { assert_eq!(event.topics.len(), 3); let signature_topic = - ::SIGNATURE_TOPIC + ::signature_topic() .map(|topic| topic.to_vec()); assert_eq!(Some(&event.topics[0]), signature_topic.as_ref()); assert_eq!(event.topics[1], [0x42; 32]); @@ -167,7 +167,7 @@ pub mod events { let event = &emitted_events[0]; let signature_topic = - ::SIGNATURE_TOPIC + ::signature_topic() .map(|topic| topic.to_vec()) .unwrap(); @@ -241,7 +241,7 @@ pub mod events { assert_eq!(!init_value, flipped.value); let signature_topic = - ::SIGNATURE_TOPIC + ::signature_topic() .map(H256::from) .unwrap(); @@ -283,7 +283,7 @@ pub mod events { .expect("encountered invalid contract event data buffer"); assert_eq!(!init_value, flipped.value); - let signature_topic = ::SIGNATURE_TOPIC + let signature_topic = ::signature_topic() .map(H256::from) .unwrap(); @@ -326,7 +326,7 @@ pub mod events { assert!(event.maybe_hash.is_none()); let signature_topic = - ::SIGNATURE_TOPIC + ::signature_topic() .map(H256::from) .unwrap(); From 5d3333ba5b9bc46131b39498ebec2d2e751d45cd Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Thu, 21 Dec 2023 18:09:01 +0000 Subject: [PATCH 05/20] add changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5740c893b5..6ad986f1eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Custom signature topic in Events - #[2031](https://github.com/paritytech/ink/pull/2031) + ## Version 5.0.0-rc From 48cec1f4a4679e2f8e851c991dad744450b88071 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Thu, 21 Dec 2023 18:12:18 +0000 Subject: [PATCH 06/20] fmt --- integration-tests/events/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/integration-tests/events/lib.rs b/integration-tests/events/lib.rs index 5a8a66dd2c..eb2dede3fc 100644 --- a/integration-tests/events/lib.rs +++ b/integration-tests/events/lib.rs @@ -283,9 +283,10 @@ pub mod events { .expect("encountered invalid contract event data buffer"); assert_eq!(!init_value, flipped.value); - let signature_topic = ::signature_topic() - .map(H256::from) - .unwrap(); + let signature_topic = + ::signature_topic() + .map(H256::from) + .unwrap(); let expected_topics = vec![signature_topic]; assert_eq!(expected_topics, contract_event.topics); From b802df26026f56e5d1d41a7ac0e3b195db807567 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Thu, 21 Dec 2023 23:00:05 +0000 Subject: [PATCH 07/20] add signature_topic macro --- Cargo.lock | 1 + crates/ink/codegen/Cargo.toml | 1 + crates/ink/codegen/src/generator/event.rs | 40 ++------ crates/ink/codegen/src/generator/mod.rs | 2 + .../codegen/src/generator/signature_topic.rs | 80 ++++++++++++++++ crates/ink/codegen/src/lib.rs | 4 + crates/ink/ir/src/ir/event/config.rs | 12 +-- crates/ink/ir/src/ir/event/mod.rs | 5 +- crates/ink/ir/src/ir/event/signature_topic.rs | 95 ++++++++++++++++++- crates/ink/ir/src/ir/mod.rs | 1 + crates/ink/ir/src/lib.rs | 1 + crates/ink/macro/src/event/mod.rs | 32 ++++++- crates/ink/macro/src/event/signature_topic.rs | 27 ++++++ crates/ink/macro/src/lib.rs | 5 + crates/ink/macro/src/tests/event.rs | 12 ++- crates/ink/macro/src/tests/event_metadata.rs | 9 +- crates/ink/src/lib.rs | 1 + 17 files changed, 277 insertions(+), 51 deletions(-) create mode 100644 crates/ink/codegen/src/generator/signature_topic.rs create mode 100644 crates/ink/macro/src/event/signature_topic.rs diff --git a/Cargo.lock b/Cargo.lock index a88a87e82b..71ed9d4697 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2630,6 +2630,7 @@ dependencies = [ "derive_more", "either", "heck", + "hex", "impl-serde", "ink_ir", "ink_primitives 5.0.0-rc", diff --git a/crates/ink/codegen/Cargo.toml b/crates/ink/codegen/Cargo.toml index 531ce41d5f..8c8de0b430 100644 --- a/crates/ink/codegen/Cargo.toml +++ b/crates/ink/codegen/Cargo.toml @@ -30,6 +30,7 @@ blake2 = { workspace = true } heck = { workspace = true } scale = { workspace = true } impl-serde = { workspace = true, default-features = true } +hex = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/crates/ink/codegen/src/generator/event.rs b/crates/ink/codegen/src/generator/event.rs index 4f8a627886..88cc8c6c01 100644 --- a/crates/ink/codegen/src/generator/event.rs +++ b/crates/ink/codegen/src/generator/event.rs @@ -14,7 +14,6 @@ use crate::GenerateCode; use derive_more::From; -use ir::HexLiteral; use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::spanned::Spanned; @@ -39,13 +38,11 @@ impl GenerateCode for Event<'_> { let cfg_attrs = self.item.get_cfg_attrs(item.span()); quote::quote! ( - #( #cfg_attrs )* - #signature_topic - #( #cfg_attrs )* #[cfg_attr(feature = "std", derive(::ink::EventMetadata))] #[derive(::ink::Event)] #[::ink::scale_derive(Encode, Decode)] + #signature_topic #anonymous #item ) @@ -54,42 +51,17 @@ impl GenerateCode for Event<'_> { impl Event<'_> { fn generate_signature_topic(&self) -> TokenStream2 { - let item_ident = &self.item.item().ident; let signature_topic = if let Some(bytes) = self.item.signature_topic() { - let hash_bytes = bytes.map(|b| b.hex_padded_suffixed()); + let hash_string = hex::encode(bytes); quote! { - ::core::option::Option::Some([ #( #hash_bytes ),* ]) + #[::ink::signature_topic(hash = #hash_string)] } } else if self.item.anonymous() { - quote! { ::core::option::Option::None } + quote! {} } else { - let calculated_topic = signature_topic(&self.item.item().fields, item_ident); - quote! { ::core::option::Option::Some(#calculated_topic) } + quote! { #[::ink::signature_topic] } }; - quote! { - impl ::ink::env::GetSignatureTopic for #item_ident { - fn signature_topic() -> Option<[u8; 32]> { - #signature_topic - } - } - } + quote! { #signature_topic } } } - -/// The signature topic of an event variant. -/// -/// Calculated with `blake2b("Event(field1_type,field2_type)")`. -fn signature_topic(fields: &syn::Fields, event_ident: &syn::Ident) -> TokenStream2 { - let fields = fields - .iter() - .map(|field| { - quote::ToTokens::to_token_stream(&field.ty) - .to_string() - .replace(' ', "") - }) - .collect::>() - .join(","); - let topic_str = format!("{}({fields})", event_ident); - quote!(::ink::blake2x256!(#topic_str)) -} diff --git a/crates/ink/codegen/src/generator/mod.rs b/crates/ink/codegen/src/generator/mod.rs index b306a84c6f..3d18493bd4 100644 --- a/crates/ink/codegen/src/generator/mod.rs +++ b/crates/ink/codegen/src/generator/mod.rs @@ -38,6 +38,7 @@ mod ink_test; mod item_impls; mod metadata; mod selector; +mod signature_topic; mod storage; mod storage_item; mod trait_def; @@ -67,6 +68,7 @@ pub use self::{ SelectorBytes, SelectorId, }, + signature_topic::SignatureTopic, storage::Storage, storage_item::StorageItem, trait_def::TraitDefinition, diff --git a/crates/ink/codegen/src/generator/signature_topic.rs b/crates/ink/codegen/src/generator/signature_topic.rs new file mode 100644 index 0000000000..8568a0b06d --- /dev/null +++ b/crates/ink/codegen/src/generator/signature_topic.rs @@ -0,0 +1,80 @@ +// Copyright (C) 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::HexLiteral; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::spanned::Spanned; + +/// Generates code to generate signature topic. +#[derive(From, Copy, Clone)] +pub struct SignatureTopic<'a> { + /// The event item to generate code for. + item: &'a ir::SignatureTopic, +} + +impl GenerateCode for SignatureTopic<'_> { + /// Generates ink! signature topic item code. + fn generate_code(&self) -> TokenStream2 { + let item = self.item.item(); + let signature_topic = self.generate_signature_topic(); + let cfg_attrs = self.item.get_cfg_attrs(item.span()); + + quote::quote! ( + #( #cfg_attrs )* + #signature_topic + #item + ) + } +} + +impl SignatureTopic<'_> { + fn generate_signature_topic(&self) -> TokenStream2 { + let item_ident = &self.item.item().ident; + let signature_topic = if let Some(bytes) = self.item.signature_topic() { + let hash_bytes = bytes.map(|b| b.hex_padded_suffixed()); + quote! { ::core::option::Option::Some([ #( #hash_bytes ),* ]) } + } else { + let calculated_topic = signature_topic(&self.item.item().fields, item_ident); + quote! { ::core::option::Option::Some(#calculated_topic) } + }; + + quote! { + impl ::ink::env::GetSignatureTopic for #item_ident { + fn signature_topic() -> Option<[u8; 32]> { + #signature_topic + } + } + } + } +} + +/// The signature topic of an event variant. +/// +/// Calculated with `blake2b("Event(field1_type,field2_type)")`. +fn signature_topic(fields: &syn::Fields, event_ident: &syn::Ident) -> TokenStream2 { + let fields = fields + .iter() + .map(|field| { + quote::ToTokens::to_token_stream(&field.ty) + .to_string() + .replace(' ', "") + }) + .collect::>() + .join(","); + let topic_str = format!("{}({fields})", event_ident); + quote!(::ink::blake2x256!(#topic_str)) +} diff --git a/crates/ink/codegen/src/lib.rs b/crates/ink/codegen/src/lib.rs index b9c97ee994..ee59b7235a 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::Event { type Generator = generator::Event<'a>; } +impl<'a> CodeGenerator for &'a ir::SignatureTopic { + type Generator = generator::SignatureTopic<'a>; +} + impl<'a> CodeGenerator for &'a ir::StorageItem { type Generator = generator::StorageItem<'a>; } diff --git a/crates/ink/ir/src/ir/event/config.rs b/crates/ink/ir/src/ir/event/config.rs index a87cdd2c18..90ed36db0f 100644 --- a/crates/ink/ir/src/ir/event/config.rs +++ b/crates/ink/ir/src/ir/event/config.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use syn::spanned::Spanned; + use crate::{ ast, utils::duplicate_config_err, @@ -58,12 +60,10 @@ impl TryFrom for EventConfig { )); } - if let Some(lit_str) = anonymous { - return Err(duplicate_config_err( - lit_str, - arg, - "signature_topic", - "event", + if signature_topic.is_some() { + return Err(format_err!( + arg.span(), + "encountered duplicate ink! configuration argument" )); } signature_topic = Some(SignatureTopicArg::try_from(&arg.value)?); diff --git a/crates/ink/ir/src/ir/event/mod.rs b/crates/ink/ir/src/ir/event/mod.rs index bc0224c002..c2cefe2b42 100644 --- a/crates/ink/ir/src/ir/event/mod.rs +++ b/crates/ink/ir/src/ir/event/mod.rs @@ -29,7 +29,10 @@ use crate::{ utils::extract_cfg_attributes, }; -pub use signature_topic::SignatureTopicArg; +pub use signature_topic::{ + SignatureTopic, + SignatureTopicArg, +}; /// A checked ink! event with its configuration. #[derive(Debug, PartialEq, Eq)] diff --git a/crates/ink/ir/src/ir/event/signature_topic.rs b/crates/ink/ir/src/ir/event/signature_topic.rs index df688b5d1d..182084f188 100644 --- a/crates/ink/ir/src/ir/event/signature_topic.rs +++ b/crates/ink/ir/src/ir/event/signature_topic.rs @@ -13,12 +13,22 @@ // limitations under the License. use hex::decode_to_slice; +use proc_macro2::{ + Span, + TokenStream as TokenStream2, +}; +use quote::ToTokens; +use syn::spanned::Spanned; -use crate::ast; +use crate::{ + ast, + error::ExtError, + utils::extract_cfg_attributes, +}; -/// The signature topic of an event variant. +/// The signature topic argument of an event variant. /// -/// Calculated with `blake2b("Event(field1_type,field2_type)")`. +/// Used as part of `ink::event` macro. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SignatureTopicArg { topic: [u8; 32], @@ -66,3 +76,82 @@ impl TryFrom<&ast::MetaValue> for SignatureTopicArg { } } } + +impl TryFrom for Option { + type Error = syn::Error; + + fn try_from(args: ast::AttributeArgs) -> Result { + let mut signature_topic: Option = None; + for arg in args.into_iter() { + if arg.name.is_ident("hash") { + if signature_topic.is_some() { + return Err(format_err!( + arg.span(), + "encountered duplicate ink! event configuration argument" + )); + } + signature_topic = Some(SignatureTopicArg::try_from(&arg.value)?); + } else { + return Err(format_err_spanned!( + arg, + "encountered unknown or unsupported ink! storage item configuration argument", + )); + } + } + Ok(signature_topic) + } +} + +/// The signature topic argument of an event variant. +/// +/// Used as part of `ink::signature_topic` macro. +/// +/// Calculated with `blake2b("Event(field1_type,field2_type)")`. +#[derive(Debug, PartialEq, Eq)] +pub struct SignatureTopic { + item: syn::ItemStruct, + arg: Option, +} + +impl SignatureTopic { + pub fn new(config: TokenStream2, item: TokenStream2) -> Result { + let item = syn::parse2::(item.clone()).map_err(|err| { + err.into_combine(format_err_spanned!( + item, + "event definition must be a `struct`", + )) + })?; + let parsed_config = syn::parse2::(config)?; + let arg = Option::::try_from(parsed_config)?; + + for attr in &item.attrs { + if attr + .path() + .to_token_stream() + .to_string() + .contains("signature_topic") + { + return Err(format_err_spanned!( + attr, + "only one `ink::signature_topic` is allowed", + )); + } + } + Ok(Self { item, arg }) + } + + /// Returns the event definition . + pub fn item(&self) -> &syn::ItemStruct { + &self.item + } + + /// Return manually specified signature topic hash. + pub fn signature_topic(&self) -> Option<[u8; 32]> { + self.arg.map(|a| a.signature_topic()) + } + + /// Returns a list of `cfg` attributes if any. + pub fn get_cfg_attrs(&self, span: Span) -> Vec { + extract_cfg_attributes(&self.item.attrs, span) + } +} diff --git a/crates/ink/ir/src/ir/mod.rs b/crates/ink/ir/src/ir/mod.rs index 14e1f9b9b2..dc418f0008 100644 --- a/crates/ink/ir/src/ir/mod.rs +++ b/crates/ink/ir/src/ir/mod.rs @@ -72,6 +72,7 @@ pub use self::{ contract::Contract, event::{ Event, + SignatureTopic, SignatureTopicArg, }, ink_test::InkTest, diff --git a/crates/ink/ir/src/lib.rs b/crates/ink/ir/src/lib.rs index 53ca060e85..a2bd0e0be2 100644 --- a/crates/ink/ir/src/lib.rs +++ b/crates/ink/ir/src/lib.rs @@ -77,6 +77,7 @@ pub use self::{ Receiver, Selector, SelectorMacro, + SignatureTopic, Storage, StorageItem, Visibility, diff --git a/crates/ink/macro/src/event/mod.rs b/crates/ink/macro/src/event/mod.rs index 1e95e14fd0..26cf06d5a2 100644 --- a/crates/ink/macro/src/event/mod.rs +++ b/crates/ink/macro/src/event/mod.rs @@ -13,8 +13,10 @@ // limitations under the License. mod metadata; +mod signature_topic; pub use metadata::event_metadata_derive; +pub use signature_topic::generate_signature_topic; use ink_codegen::generate_code; use proc_macro2::TokenStream as TokenStream2; @@ -123,7 +125,7 @@ fn event_derive_struct(mut s: synstructure::Structure) -> syn::Result( @@ -138,7 +140,33 @@ fn event_derive_struct(mut s: synstructure::Structure) -> syn::Result TokenStream2 { + if anonymous { + quote! { + impl ::ink::env::GetSignatureTopic for #item_ident { + fn signature_topic() -> Option<[u8; 32]> { + None + } + } + } + } else { + quote! {} + } } /// Checks if the given field's attributes contain an `#[ink(topic)]` attribute. diff --git a/crates/ink/macro/src/event/signature_topic.rs b/crates/ink/macro/src/event/signature_topic.rs new file mode 100644 index 0000000000..d455e602fa --- /dev/null +++ b/crates/ink/macro/src/event/signature_topic.rs @@ -0,0 +1,27 @@ +// Copyright (C) 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 ink_codegen::generate_code; +use proc_macro2::TokenStream as TokenStream2; + +/// Generate code from the `#[ink::event]` attribute. This expands to the required +/// derive macros to satisfy an event implementation. +pub fn generate_signature_topic( + config: TokenStream2, + input: TokenStream2, +) -> TokenStream2 { + ink_ir::SignatureTopic::new(config, input) + .map(|event| generate_code(&event)) + .unwrap_or_else(|err| err.to_compile_error()) +} diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index d55fbc97c8..15ede0ff6f 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -683,6 +683,11 @@ pub fn event(attr: TokenStream, item: TokenStream) -> TokenStream { event::generate(attr.into(), item.into()).into() } +#[proc_macro_attribute] +pub fn signature_topic(attr: TokenStream, item: TokenStream) -> TokenStream { + event::generate_signature_topic(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/macro/src/tests/event.rs b/crates/ink/macro/src/tests/event.rs index 6390db5d31..05266aa403 100644 --- a/crates/ink/macro/src/tests/event.rs +++ b/crates/ink/macro/src/tests/event.rs @@ -27,6 +27,7 @@ fn unit_struct_works() { crate::test_derive! { event_derive { #[derive(scale::Encode)] + #[::ink::signature_topic] struct UnitStruct; } expands to { @@ -53,7 +54,7 @@ fn unit_struct_works() { } } }; - } no_build + } } } @@ -66,6 +67,11 @@ fn unit_struct_anonymous_has_no_topics() { struct UnitStruct; } expands to { + impl ::ink::env::GetSignatureTopic for UnitStruct { + fn signature_topic() -> Option<[u8; 32]> { + None + } + } const _: () = { impl ::ink::env::Event for UnitStruct { type RemainingTopics = ::ink::env::event::state::NoRemainingTopics; @@ -97,6 +103,7 @@ fn struct_with_fields_no_topics() { crate::test_derive! { event_derive { #[derive(scale::Encode)] + #[::ink::signature_topic] struct Event { field_1: u32, field_2: u64, @@ -127,7 +134,7 @@ fn struct_with_fields_no_topics() { } } }; - } no_build + } } } @@ -136,6 +143,7 @@ fn struct_with_fields_and_some_topics() { crate::test_derive! { event_derive { #[derive(scale::Encode)] + #[::ink::signature_topic] struct Event { field_1: u32, #[ink(topic)] diff --git a/crates/ink/macro/src/tests/event_metadata.rs b/crates/ink/macro/src/tests/event_metadata.rs index 03d11f7eef..dceeded448 100644 --- a/crates/ink/macro/src/tests/event_metadata.rs +++ b/crates/ink/macro/src/tests/event_metadata.rs @@ -19,6 +19,7 @@ fn unit_struct_works() { crate::test_derive! { event_metadata_derive { #[derive(ink::Event, scale::Encode)] + #[::ink::signature_topic] struct UnitStruct; } expands to { @@ -41,7 +42,7 @@ fn unit_struct_works() { } } }; - } no_build + } } } @@ -50,6 +51,7 @@ fn struct_with_fields_no_topics() { crate::test_derive! { event_metadata_derive { #[derive(ink::Event, scale::Encode)] + #[::ink::signature_topic] struct Event { field_1: u32, field_2: u64, @@ -92,7 +94,7 @@ fn struct_with_fields_no_topics() { } } }; - } no_build + } } } @@ -101,6 +103,7 @@ fn struct_with_fields_and_some_topics() { crate::test_derive! { event_metadata_derive { #[derive(ink::Event, scale::Encode)] + #[::ink::signature_topic] struct Event { field_1: u32, #[ink(topic)] @@ -145,6 +148,6 @@ fn struct_with_fields_and_some_topics() { } } }; - } no_build + } } } diff --git a/crates/ink/src/lib.rs b/crates/ink/src/lib.rs index 6cd7d2c500..00ffc0416d 100644 --- a/crates/ink/src/lib.rs +++ b/crates/ink/src/lib.rs @@ -80,6 +80,7 @@ pub use ink_macro::{ scale_derive, selector_bytes, selector_id, + signature_topic, storage_item, test, trait_definition, From ac0d28c3ae7bd0d908b456f9ef65a5c3288ea9b9 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Thu, 21 Dec 2023 23:18:39 +0000 Subject: [PATCH 08/20] simpligy argument parsing --- crates/ink/codegen/src/generator/event.rs | 5 ++-- crates/ink/ir/src/ir/attrs.rs | 33 +++++++++++----------- crates/ink/ir/src/ir/event/config.rs | 34 +++++++++++------------ crates/ink/ir/src/ir/event/mod.rs | 15 ++++------ crates/ink/ir/src/ir/mod.rs | 1 - 5 files changed, 42 insertions(+), 46 deletions(-) diff --git a/crates/ink/codegen/src/generator/event.rs b/crates/ink/codegen/src/generator/event.rs index 88cc8c6c01..e959ffe5cb 100644 --- a/crates/ink/codegen/src/generator/event.rs +++ b/crates/ink/codegen/src/generator/event.rs @@ -51,10 +51,9 @@ impl GenerateCode for Event<'_> { impl Event<'_> { fn generate_signature_topic(&self) -> TokenStream2 { - let signature_topic = if let Some(bytes) = self.item.signature_topic() { - let hash_string = hex::encode(bytes); + let signature_topic = if let Some(hash) = self.item.signature_topic_hash() { quote! { - #[::ink::signature_topic(hash = #hash_string)] + #[::ink::signature_topic(hash = #hash)] } } else if self.item.anonymous() { quote! {} diff --git a/crates/ink/ir/src/ir/attrs.rs b/crates/ink/ir/src/ir/attrs.rs index 79933ebacb..b89713514b 100644 --- a/crates/ink/ir/src/ir/attrs.rs +++ b/crates/ink/ir/src/ir/attrs.rs @@ -43,8 +43,6 @@ use crate::{ }, }; -use super::SignatureTopicArg; - /// An extension trait for [`syn::Attribute`] in order to query for documentation. pub trait IsDocAttribute { /// Returns `true` if the attribute is a Rust documentation attribute. @@ -287,10 +285,10 @@ impl InkAttribute { } /// Returns the signature topic of the ink! attribute if any. - pub fn signature_topic(&self) -> Option { + pub fn signature_topic_hash(&self) -> Option { self.args().find_map(|arg| { - if let ir::AttributeArg::SignatureTopic(topic) = arg.kind() { - return Some(*topic); + if let ir::AttributeArg::SignatureTopic(hash) = arg.kind() { + return Some(hash.clone()); } None }) @@ -433,7 +431,7 @@ pub enum AttributeArg { /// that is invoked if no other ink! message matches a given selector. Selector(SelectorOrWildcard), /// `#[ink(signature_topic = "DEADBEEF")]` - SignatureTopic(SignatureTopicArg), + SignatureTopic(String), /// `#[ink(namespace = "my_namespace")]` /// /// Applied on ink! trait implementation blocks to disambiguate other trait @@ -525,8 +523,8 @@ impl core::fmt::Display for AttributeArg { Self::Constructor => write!(f, "constructor"), Self::Payable => write!(f, "payable"), Self::Selector(selector) => core::fmt::Display::fmt(&selector, f), - Self::SignatureTopic(sig_topic) => { - write!(f, "signature_topic = {:?}", sig_topic.signature_topic()) + Self::SignatureTopic(hash) => { + write!(f, "signature_topic = {:?}", hash) } Self::Function(function) => { write!(f, "function = {:?}", function.into_u16()) @@ -944,14 +942,20 @@ impl Parse for AttributeFrag { SelectorOrWildcard::try_from(&name_value.value) .map(AttributeArg::Selector) } - "signature_topic" => { - SignatureTopicArg::try_from(&name_value.value) - .map(AttributeArg::SignatureTopic) - } "namespace" => { Namespace::try_from(&name_value.value) .map(AttributeArg::Namespace) } + "signature_topic" => { + if let Some(hash) = name_value.value.as_string() { + Ok(AttributeArg::SignatureTopic(hash)) + } else { + Err(format_err_spanned!( + name_value.value, + "expected String type for `S` in #[ink(signature_topic = S)]", + )) + } + } "function" => { if let Some(lit_int) = name_value.value.as_lit_int() { let id = lit_int.base10_parse::() @@ -1537,15 +1541,12 @@ mod tests { } #[test] fn signature_topic_works() { - let bytes = [17u8; 32]; let s = "11".repeat(32); assert_attribute_try_from( syn::parse_quote! { #[ink(signature_topic = #s)] }, - Ok(test::Attribute::Ink(vec![AttributeArg::SignatureTopic( - SignatureTopicArg::from(&bytes), - )])), + Ok(test::Attribute::Ink(vec![AttributeArg::SignatureTopic(s)])), ); } } diff --git a/crates/ink/ir/src/ir/event/config.rs b/crates/ink/ir/src/ir/event/config.rs index 90ed36db0f..c0f133675b 100644 --- a/crates/ink/ir/src/ir/event/config.rs +++ b/crates/ink/ir/src/ir/event/config.rs @@ -12,15 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use syn::spanned::Spanned; - use crate::{ ast, utils::duplicate_config_err, }; -use super::SignatureTopicArg; - /// The configuration arguments to the `#[ink::event(..)]` attribute macro. #[derive(Debug, PartialEq, Eq)] pub struct EventConfig { @@ -29,8 +25,8 @@ pub struct EventConfig { /// This is the default value. anonymous: bool, - /// Manually specified signature topic. - signature_topic: Option<[u8; 32]>, + /// Manually specified signature topic hash. + signature_topic_hash: Option, } impl TryFrom for EventConfig { @@ -38,7 +34,7 @@ impl TryFrom for EventConfig { fn try_from(args: ast::AttributeArgs) -> Result { let mut anonymous: Option = None; - let mut signature_topic: Option = None; + let mut signature_topic: Option = None; for arg in args.into_iter() { if arg.name.is_ident("anonymous") { if let Some(lit_bool) = anonymous { @@ -60,13 +56,17 @@ impl TryFrom for EventConfig { )); } - if signature_topic.is_some() { - return Err(format_err!( - arg.span(), - "encountered duplicate ink! configuration argument" + if let Some(lit_str) = signature_topic { + return Err(duplicate_config_err(lit_str, arg, "anonymous", "event")); + } + if let ast::MetaValue::Lit(syn::Lit::Str(lis_str)) = &arg.value { + signature_topic = Some(lis_str.clone()) + } else { + return Err(format_err_spanned!( + arg, + "expected a bool literal for `anonymous` ink! event item configuration argument", )); } - signature_topic = Some(SignatureTopicArg::try_from(&arg.value)?); } else { return Err(format_err_spanned!( arg, @@ -77,17 +77,17 @@ impl TryFrom for EventConfig { Ok(EventConfig::new( anonymous.map(|lit_bool| lit_bool.value).unwrap_or(false), - signature_topic, + signature_topic.map(|lit_str| lit_str.value()), )) } } impl EventConfig { /// Construct a new [`EventConfig`]. - pub fn new(anonymous: bool, signature_topic: Option) -> Self { + pub fn new(anonymous: bool, signature_topic_hash: Option) -> Self { Self { anonymous, - signature_topic: signature_topic.map(|s| s.signature_topic()), + signature_topic_hash, } } @@ -97,7 +97,7 @@ impl EventConfig { } /// Returns the manually specified signature topic. - pub fn signature_topic(&self) -> Option<[u8; 32]> { - self.signature_topic + pub fn signature_topic_hash(&self) -> Option { + self.signature_topic_hash.clone() } } diff --git a/crates/ink/ir/src/ir/event/mod.rs b/crates/ink/ir/src/ir/event/mod.rs index c2cefe2b42..efc4a1b8be 100644 --- a/crates/ink/ir/src/ir/event/mod.rs +++ b/crates/ink/ir/src/ir/event/mod.rs @@ -29,10 +29,7 @@ use crate::{ utils::extract_cfg_attributes, }; -pub use signature_topic::{ - SignatureTopic, - SignatureTopicArg, -}; +pub use signature_topic::SignatureTopic; /// A checked ink! event with its configuration. #[derive(Debug, PartialEq, Eq)] @@ -101,13 +98,13 @@ impl Event { self.config.anonymous() } - /// Return manually specified signature topic. + /// Return manually specified signature topic hash. /// /// # Note /// /// Conflicts with `anonymous` - pub fn signature_topic(&self) -> Option<[u8; 32]> { - self.config.signature_topic() + pub fn signature_topic_hash(&self) -> Option { + self.config.signature_topic_hash() } /// Returns a list of `cfg` attributes if any. @@ -142,7 +139,7 @@ impl TryFrom for Event { } }, )?; - if ink_attrs.is_anonymous() && ink_attrs.signature_topic().is_some() { + if ink_attrs.is_anonymous() && ink_attrs.signature_topic_hash().is_some() { return Err(format_err_spanned!( item_struct, "cannot use use `anonymous` with `signature_topic`", @@ -155,7 +152,7 @@ impl TryFrom for Event { }, config: EventConfig::new( ink_attrs.is_anonymous(), - ink_attrs.signature_topic(), + ink_attrs.signature_topic_hash(), ), }) } diff --git a/crates/ink/ir/src/ir/mod.rs b/crates/ink/ir/src/ir/mod.rs index dc418f0008..bb0a8564a2 100644 --- a/crates/ink/ir/src/ir/mod.rs +++ b/crates/ink/ir/src/ir/mod.rs @@ -73,7 +73,6 @@ pub use self::{ event::{ Event, SignatureTopic, - SignatureTopicArg, }, ink_test::InkTest, item::{ From 8feaf2d2b8909a621863736f09e864630e1822cb Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Fri, 22 Dec 2023 14:10:15 +0000 Subject: [PATCH 09/20] add tests --- crates/ink/codegen/src/generator/event.rs | 5 ++ .../codegen/src/generator/signature_topic.rs | 1 + crates/ink/ir/src/ir/event/mod.rs | 29 +++++++- crates/ink/ir/src/ir/event/signature_topic.rs | 2 +- crates/ink/macro/src/event/mod.rs | 4 + crates/ink/macro/src/lib.rs | 59 +++++++++++++-- .../ui/event/pass/custom_signature_works.rs | 8 ++ integration-tests/events/lib.rs | 74 ++++++++++++++++++- 8 files changed, 172 insertions(+), 10 deletions(-) create mode 100644 crates/ink/tests/ui/event/pass/custom_signature_works.rs diff --git a/crates/ink/codegen/src/generator/event.rs b/crates/ink/codegen/src/generator/event.rs index e959ffe5cb..f3edb37149 100644 --- a/crates/ink/codegen/src/generator/event.rs +++ b/crates/ink/codegen/src/generator/event.rs @@ -50,6 +50,11 @@ impl GenerateCode for Event<'_> { } impl Event<'_> { + /// Generates the `#[ink::signature_topic]` attribute for the given event. + /// + /// # Note + /// If `anonymous` is present, no attribute is generated + /// and the blank implementation is generated by `#derive(Event)`. fn generate_signature_topic(&self) -> TokenStream2 { let signature_topic = if let Some(hash) = self.item.signature_topic_hash() { quote! { diff --git a/crates/ink/codegen/src/generator/signature_topic.rs b/crates/ink/codegen/src/generator/signature_topic.rs index 8568a0b06d..2a541b1d48 100644 --- a/crates/ink/codegen/src/generator/signature_topic.rs +++ b/crates/ink/codegen/src/generator/signature_topic.rs @@ -42,6 +42,7 @@ impl GenerateCode for SignatureTopic<'_> { } impl SignatureTopic<'_> { + /// Generates the implementation of `GetSignatureTopic` trait. fn generate_signature_topic(&self) -> TokenStream2 { let item_ident = &self.item.item().ident; let signature_topic = if let Some(bytes) = self.item.signature_topic() { diff --git a/crates/ink/ir/src/ir/event/mod.rs b/crates/ink/ir/src/ir/event/mod.rs index efc4a1b8be..ee471aae46 100644 --- a/crates/ink/ir/src/ir/event/mod.rs +++ b/crates/ink/ir/src/ir/event/mod.rs @@ -213,7 +213,34 @@ mod tests { } }, "encountered duplicate ink! attribute", - ) + ); + assert_try_from_fails( + syn::parse_quote! { + #[ink(event)] + #[ink(anonymous)] + #[ink(anonymous)] + pub struct MyEvent { + #[ink(topic)] + field_1: i32, + field_2: bool, + } + }, + "encountered duplicate ink! attribute", + ); + let s = "11".repeat(32); + assert_try_from_fails( + syn::parse_quote! { + #[ink(event)] + #[ink(signature_topic = #s)] + #[ink(signature_topic = #s)] + pub struct MyEvent { + #[ink(topic)] + field_1: i32, + field_2: bool, + } + }, + "encountered duplicate ink! attribute", + ); } #[test] diff --git a/crates/ink/ir/src/ir/event/signature_topic.rs b/crates/ink/ir/src/ir/event/signature_topic.rs index 182084f188..65a2b6a267 100644 --- a/crates/ink/ir/src/ir/event/signature_topic.rs +++ b/crates/ink/ir/src/ir/event/signature_topic.rs @@ -145,7 +145,7 @@ impl SignatureTopic { &self.item } - /// Return manually specified signature topic hash. + /// Return a signature topic, if required. pub fn signature_topic(&self) -> Option<[u8; 32]> { self.arg.map(|a| a.signature_topic()) } diff --git a/crates/ink/macro/src/event/mod.rs b/crates/ink/macro/src/event/mod.rs index 26cf06d5a2..f01614b635 100644 --- a/crates/ink/macro/src/event/mod.rs +++ b/crates/ink/macro/src/event/mod.rs @@ -152,6 +152,10 @@ fn event_derive_struct(mut s: synstructure::Structure) -> syn::Result TokenStream { /// By default, a signature topic will be generated for the event. This allows consumers /// to filter and identify events of this type. Marking an event with `anonymous = true` /// means no signature topic will be generated or emitted. +/// Custom signature topic can be specified with `signature_topic = <32 byte hex string>`. +/// +/// `signature_topic` and `anonymous` are conflicting arguments. /// /// # Examples /// @@ -677,12 +680,48 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// #[ink(topic)] /// pub topic: [u8; 32], /// } +/// // Setting `signature_topic = ` specifies custom signature topic. +/// #[ink::event( +/// signature_topic = "1111111111111111111111111111111111111111111111111111111111111111" +/// )] +/// pub struct MyCustomSignatureEvent { +/// pub field: u32, +/// #[ink(topic)] +/// pub topic: [u8; 32], +/// } /// ``` #[proc_macro_attribute] pub fn event(attr: TokenStream, item: TokenStream) -> TokenStream { event::generate(attr.into(), item.into()).into() } +/// Implements the [`ink::env::GetSignatureTopic`] traits for a `struct` to generate +/// a signature topic +/// +/// By default, a signature topic will be generated for the event. +/// Custom signature topic can be specified with `hash` argument that takes 32 byte hex +/// string. +/// +/// # Examples +/// +/// ``` +/// // Generates the default signature topic +/// #[ink::signature_topic] +/// pub struct MyEvent { +/// pub field: u32, +/// pub topic: [u8; 32], +/// } +/// +/// // Generates custom signature topic +/// #[ink::signature_topic(hash = "1111111111111111111111111111111111111111111111111111111111111111")] +/// pub struct MyCustomSignatureEvent { +/// pub field: u32, +/// pub topic: [u8; 32], +/// } +/// +/// use ink::env::GetSignatureTopic; +/// assert_eq!(Some([17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17]), +/// ::signature_topic()) #[proc_macro_attribute] pub fn signature_topic(attr: TokenStream, item: TokenStream) -> TokenStream { event::generate_signature_topic(attr.into(), item.into()).into() @@ -1337,8 +1376,8 @@ synstructure::decl_derive!( [Event, attributes(ink)] => /// Derives an implementation of the [`ink::Event`] trait for the given `struct`. /// - /// **Note** [`ink::Event`] requires a [`scale::Encode`] implementation, it is up to - /// the user to provide that: usually via the derive. + /// **Note** [`ink::Event`] requires [`scale::Encode`] and [`ink::env::GetSignatureTopic`] implementation, + /// it is up to the user to provide that: usually via the derive and `#[ink::signature_topic]` macros. /// /// Usually this is used in conjunction with the [`EventMetadata`] derive. /// @@ -1350,11 +1389,12 @@ synstructure::decl_derive!( /// ``` /// use ink::{ /// Event, - /// env::DefaultEnvironment, + /// env::{ DefaultEnvironment, GetSignatureTopic }, /// }; /// use scale::Encode; /// /// #[derive(Event, Encode)] + /// #[ink::signature_topic] /// struct MyEvent { /// a: u32, /// #[ink(topic)] @@ -1391,6 +1431,7 @@ synstructure::decl_derive!( /// /// ``` /// #[derive(ink::Event, scale::Encode)] + /// #[ink::signature_topic] /// pub struct MyEvent { /// a: u32, /// } @@ -1399,18 +1440,21 @@ synstructure::decl_derive!( /// type MyU32 = u32; /// /// #[derive(ink::Event, scale::Encode)] + /// #[ink::signature_topic] /// pub struct MyEvent { /// a: MyU32, /// } /// } /// - /// use ink::env::Event; - /// assert_ne!(::SIGNATURE_TOPIC, ::SIGNATURE_TOPIC); + /// use ink::env::GetSignatureTopic; + /// assert_ne!(::signature_topic(), ::signature_topic()); /// ``` /// /// ## Anonymous Events /// /// If the event is annotated with `#[ink(anonymous)]` then no signature topic is generated. + /// The macro will generate a blank implementation of `GetSignatureTopic`, + /// and `#[ink::signature_topic]` should not be used. event::event_derive ); @@ -1419,8 +1463,8 @@ synstructure::decl_derive!( /// Derives the [`ink::EventMetadata`] trait for the given `struct`, which provides metadata /// about the event definition. /// - /// Requires that the `struct` also implements the [`ink::Event`] trait, so this derive is - /// usually used in combination with the [`Event`] derive. + /// Requires that the `struct` also implements the [`ink::Event`] and `[ink::env::GetSignatureTopic]` traits, + /// so this derive is usually used in combination with the [`Event`] derive and `#[ink::signature_topic]` macro. /// /// Metadata is not embedded into the contract binary, it is generated from a separate /// compilation of the contract with the `std` feature, therefore this derive must be @@ -1441,6 +1485,7 @@ synstructure::decl_derive!( /// /// #[cfg_attr(feature = "std", derive(::ink::EventMetadata))] /// #[derive(Event, Encode)] + /// #[ink::signature_topic] /// struct MyEvent { /// a: u32, /// #[ink(topic)] diff --git a/crates/ink/tests/ui/event/pass/custom_signature_works.rs b/crates/ink/tests/ui/event/pass/custom_signature_works.rs new file mode 100644 index 0000000000..d47711a6ec --- /dev/null +++ b/crates/ink/tests/ui/event/pass/custom_signature_works.rs @@ -0,0 +1,8 @@ +#[ink::event(signature_topic = "1111111111111111111111111111111111111111111111111111111111111111")] +pub struct Event { + #[ink(topic)] + pub topic: [u8; 32], + pub field_1: u32, +} + +fn main() {} diff --git a/integration-tests/events/lib.rs b/integration-tests/events/lib.rs index eb2dede3fc..e5a567c672 100644 --- a/integration-tests/events/lib.rs +++ b/integration-tests/events/lib.rs @@ -19,6 +19,14 @@ pub mod events { value: bool, } + #[ink( + event, + signature_topic = "1111111111111111111111111111111111111111111111111111111111111111" + )] + pub struct InlineCustomFlipped { + value: bool, + } + #[ink(event)] #[ink(anonymous)] pub struct InlineAnonymousEvent { @@ -49,6 +57,14 @@ pub mod events { self.env().emit_event(InlineFlipped { value: self.value }) } + /// Flips the current value of the boolean. + #[ink(message)] + pub fn flip_with_inline_custom_event(&mut self) { + self.value = !self.value; + self.env() + .emit_event(InlineCustomFlipped { value: self.value }) + } + /// Emit an event with a 32 byte topic. #[ink(message)] pub fn emit_32_byte_topic_event(&self, maybe_hash: Option<[u8; 32]>) { @@ -94,7 +110,7 @@ pub mod events { #[test] fn collects_specs_for_all_linked_and_used_events() { let event_specs = ink::metadata::collect_events(); - assert_eq!(7, event_specs.len()); + assert_eq!(8, event_specs.len()); assert!(event_specs .iter() @@ -102,6 +118,9 @@ pub mod events { assert!(event_specs .iter() .any(|evt| evt.label() == &"InlineFlipped")); + assert!(event_specs + .iter() + .any(|evt| evt.label() == &"InlineCustomFlipped")); assert!(event_specs .iter() .any(|evt| evt.label() == &"ThirtyTwoByteTopics")); @@ -179,6 +198,20 @@ pub mod events { assert_eq!(expected_topics, event.topics); } + #[ink::test] + fn custom_signature_topic() { + let mut events = Events::new(false); + events.flip_with_inline_custom_event(); + + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(1, emitted_events.len()); + + let signature_topic = + ::signature_topic(); + + assert_eq!(Some([17u8; 32]), signature_topic); + } + #[ink::test] fn anonymous_events_emit_no_signature_topics() { let events = Events::new(false); @@ -195,6 +228,10 @@ pub mod events { let event = &emitted_events[1]; assert_eq!(event.topics.len(), 1); assert_eq!(event.topics[0], topic); + + let signature_topic = + ::signature_topic(); + assert_eq!(None, signature_topic); } } @@ -340,5 +377,40 @@ pub mod events { Ok(()) } + + #[ink_e2e::test] + async fn emits_custom_signature_event( + mut client: Client, + ) -> E2EResult<()> { + // given + let init_value = false; + let mut constructor = EventsRef::new(init_value); + let contract = client + .instantiate("events", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + // when + let call = call.flip_with_inline_custom_event(); + let call_res = client + .call(&ink_e2e::bob(), &call) + .submit() + .await + .expect("flip_with_inline_custom_event failed"); + + let contract_events = call_res.contract_emitted_events()?; + + // then + assert_eq!(1, contract_events.len()); + + let signature_topic = + ::signature_topic(); + + assert_eq!(Some([17u8; 32]), signature_topic); + + Ok(()) + } } } From 21994a27cb29acafbe7f7630317e90c43fbfc802 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Fri, 22 Dec 2023 14:48:47 +0000 Subject: [PATCH 10/20] remove links --- crates/ink/macro/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index 0242d6c304..b837e5d2f6 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -695,7 +695,7 @@ pub fn event(attr: TokenStream, item: TokenStream) -> TokenStream { event::generate(attr.into(), item.into()).into() } -/// Implements the [`ink::env::GetSignatureTopic`] traits for a `struct` to generate +/// Implements the `GetSignatureTopic` traits for a `struct` to generate /// a signature topic /// /// By default, a signature topic will be generated for the event. @@ -1463,7 +1463,7 @@ synstructure::decl_derive!( /// Derives the [`ink::EventMetadata`] trait for the given `struct`, which provides metadata /// about the event definition. /// - /// Requires that the `struct` also implements the [`ink::Event`] and `[ink::env::GetSignatureTopic]` traits, + /// Requires that the `struct` also implements the [`ink::Event`] and `GetSignatureTopic` traits, /// so this derive is usually used in combination with the [`Event`] derive and `#[ink::signature_topic]` macro. /// /// Metadata is not embedded into the contract binary, it is generated from a separate From 683e180f25b537dc3babc687603d470f22825e6c Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Fri, 22 Dec 2023 16:03:04 +0000 Subject: [PATCH 11/20] use const array --- crates/env/src/event.rs | 2 +- .../ink/codegen/src/generator/signature_topic.rs | 4 +--- crates/ink/macro/src/event/metadata.rs | 2 +- crates/ink/macro/src/event/mod.rs | 6 ++---- crates/ink/macro/src/lib.rs | 4 ++-- crates/ink/macro/src/tests/event.rs | 10 ++++------ crates/ink/macro/src/tests/event_metadata.rs | 6 +++--- integration-tests/events/lib.rs | 16 ++++++++-------- 8 files changed, 22 insertions(+), 28 deletions(-) diff --git a/crates/env/src/event.rs b/crates/env/src/event.rs index 72a560565b..b813ab0afd 100644 --- a/crates/env/src/event.rs +++ b/crates/env/src/event.rs @@ -220,5 +220,5 @@ pub trait Event: scale::Encode + GetSignatureTopic { /// default calculates this as `blake2b("Event(field1_type,field2_type)")` pub trait GetSignatureTopic { /// Retrieve the signature topic - fn signature_topic() -> Option<[u8; 32]>; + const SIGNATURE_TOPIC: core::option::Option<[u8; 32]>; } diff --git a/crates/ink/codegen/src/generator/signature_topic.rs b/crates/ink/codegen/src/generator/signature_topic.rs index 2a541b1d48..f0c0769828 100644 --- a/crates/ink/codegen/src/generator/signature_topic.rs +++ b/crates/ink/codegen/src/generator/signature_topic.rs @@ -55,9 +55,7 @@ impl SignatureTopic<'_> { quote! { impl ::ink::env::GetSignatureTopic for #item_ident { - fn signature_topic() -> Option<[u8; 32]> { - #signature_topic - } + const SIGNATURE_TOPIC: ::core::option::Option<[u8; 32]> = #signature_topic; } } } diff --git a/crates/ink/macro/src/event/metadata.rs b/crates/ink/macro/src/event/metadata.rs index 0c44808e04..8d09d24213 100644 --- a/crates/ink/macro/src/event/metadata.rs +++ b/crates/ink/macro/src/event/metadata.rs @@ -91,7 +91,7 @@ fn event_metadata_derive_struct(s: synstructure::Structure) -> syn::Result::signature_topic() + ::SIGNATURE_TOPIC ) .args([ #( #args ),* diff --git a/crates/ink/macro/src/event/mod.rs b/crates/ink/macro/src/event/mod.rs index f01614b635..7045f6e734 100644 --- a/crates/ink/macro/src/event/mod.rs +++ b/crates/ink/macro/src/event/mod.rs @@ -102,7 +102,7 @@ fn event_derive_struct(mut s: synstructure::Structure) -> syn::Result - .push_topic(::signature_topic().as_ref()) + .push_topic(::SIGNATURE_TOPIC.as_ref()) )) }; @@ -163,9 +163,7 @@ fn generate_signature_topic_blank( if anonymous { quote! { impl ::ink::env::GetSignatureTopic for #item_ident { - fn signature_topic() -> Option<[u8; 32]> { - None - } + const SIGNATURE_TOPIC: ::core::option::Option<[u8; 32]> = None; } } } else { diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index b837e5d2f6..bad13eb774 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -721,7 +721,7 @@ pub fn event(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// use ink::env::GetSignatureTopic; /// assert_eq!(Some([17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17]), -/// ::signature_topic()) +/// ::SIGNATURE_TOPIC) #[proc_macro_attribute] pub fn signature_topic(attr: TokenStream, item: TokenStream) -> TokenStream { event::generate_signature_topic(attr.into(), item.into()).into() @@ -1447,7 +1447,7 @@ synstructure::decl_derive!( /// } /// /// use ink::env::GetSignatureTopic; - /// assert_ne!(::signature_topic(), ::signature_topic()); + /// assert_ne!(::SIGNATURE_TOPIC, ::SIGNATURE_TOPIC); /// ``` /// /// ## Anonymous Events diff --git a/crates/ink/macro/src/tests/event.rs b/crates/ink/macro/src/tests/event.rs index 05266aa403..de428daeae 100644 --- a/crates/ink/macro/src/tests/event.rs +++ b/crates/ink/macro/src/tests/event.rs @@ -47,7 +47,7 @@ fn unit_struct_works() { UnitStruct => { builder .build::() - .push_topic(::signature_topic().as_ref()) + .push_topic(::SIGNATURE_TOPIC.as_ref()) .finish() } } @@ -68,9 +68,7 @@ fn unit_struct_anonymous_has_no_topics() { } expands to { impl ::ink::env::GetSignatureTopic for UnitStruct { - fn signature_topic() -> Option<[u8; 32]> { - None - } + const SIGNATURE_TOPIC: ::core::option::Option<[u8; 32]> = None; } const _: () = { impl ::ink::env::Event for UnitStruct { @@ -127,7 +125,7 @@ fn struct_with_fields_no_topics() { Event { .. } => { builder .build::() - .push_topic(::signature_topic().as_ref()) + .push_topic(::SIGNATURE_TOPIC.as_ref()) .finish() } } @@ -170,7 +168,7 @@ fn struct_with_fields_and_some_topics() { Event { field_2 : __binding_1 , field_3 : __binding_2 , .. } => { builder .build::() - .push_topic(::signature_topic().as_ref()) + .push_topic(::SIGNATURE_TOPIC.as_ref()) .push_topic(::ink::as_option!(__binding_1)) .push_topic(::ink::as_option!(__binding_2)) .finish() diff --git a/crates/ink/macro/src/tests/event_metadata.rs b/crates/ink/macro/src/tests/event_metadata.rs index dceeded448..109ca3281b 100644 --- a/crates/ink/macro/src/tests/event_metadata.rs +++ b/crates/ink/macro/src/tests/event_metadata.rs @@ -35,7 +35,7 @@ fn unit_struct_works() { ::ink::metadata::EventSpec::new(::core::stringify!(UnitStruct)) .module_path(::core::module_path!()) - .signature_topic(::signature_topic()) + .signature_topic(::SIGNATURE_TOPIC) .args([]) .docs([]) .done() @@ -71,7 +71,7 @@ fn struct_with_fields_no_topics() { ::ink::metadata::EventSpec::new(::core::stringify!(Event)) .module_path(::core::module_path!()) - .signature_topic(::signature_topic()) + .signature_topic(::SIGNATURE_TOPIC) .args([ ::ink::metadata::EventParamSpec::new(::core::stringify!(field_1)) .of_type(::ink::metadata::TypeSpec::of_type::()) @@ -125,7 +125,7 @@ fn struct_with_fields_and_some_topics() { ::ink::metadata::EventSpec::new(::core::stringify!(Event)) .module_path(::core::module_path!()) - .signature_topic(::signature_topic()) + .signature_topic(::SIGNATURE_TOPIC) .args([ ::ink::metadata::EventParamSpec::new(::core::stringify!(field_1)) .of_type(::ink::metadata::TypeSpec::of_type::()) diff --git a/integration-tests/events/lib.rs b/integration-tests/events/lib.rs index e5a567c672..525c6e5b2d 100644 --- a/integration-tests/events/lib.rs +++ b/integration-tests/events/lib.rs @@ -166,7 +166,7 @@ pub mod events { assert_eq!(event.topics.len(), 3); let signature_topic = - ::signature_topic() + ::SIGNATURE_TOPIC .map(|topic| topic.to_vec()); assert_eq!(Some(&event.topics[0]), signature_topic.as_ref()); assert_eq!(event.topics[1], [0x42; 32]); @@ -186,7 +186,7 @@ pub mod events { let event = &emitted_events[0]; let signature_topic = - ::signature_topic() + ::SIGNATURE_TOPIC .map(|topic| topic.to_vec()) .unwrap(); @@ -207,7 +207,7 @@ pub mod events { assert_eq!(1, emitted_events.len()); let signature_topic = - ::signature_topic(); + ::SIGNATURE_TOPIC; assert_eq!(Some([17u8; 32]), signature_topic); } @@ -230,7 +230,7 @@ pub mod events { assert_eq!(event.topics[0], topic); let signature_topic = - ::signature_topic(); + ::SIGNATURE_TOPIC; assert_eq!(None, signature_topic); } } @@ -278,7 +278,7 @@ pub mod events { assert_eq!(!init_value, flipped.value); let signature_topic = - ::signature_topic() + ::SIGNATURE_TOPIC .map(H256::from) .unwrap(); @@ -321,7 +321,7 @@ pub mod events { assert_eq!(!init_value, flipped.value); let signature_topic = - ::signature_topic() + ::SIGNATURE_TOPIC .map(H256::from) .unwrap(); @@ -364,7 +364,7 @@ pub mod events { assert!(event.maybe_hash.is_none()); let signature_topic = - ::signature_topic() + ::SIGNATURE_TOPIC .map(H256::from) .unwrap(); @@ -406,7 +406,7 @@ pub mod events { assert_eq!(1, contract_events.len()); let signature_topic = - ::signature_topic(); + ::SIGNATURE_TOPIC; assert_eq!(Some([17u8; 32]), signature_topic); From 91b3f91493d95c1321a4d813e95639bc845dc800 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Sat, 6 Jan 2024 14:44:30 +0000 Subject: [PATCH 12/20] remove hex crate --- Cargo.lock | 3 +- Cargo.toml | 1 - crates/engine/src/tests.rs | 2 +- crates/ink/codegen/Cargo.toml | 1 - crates/ink/ir/Cargo.toml | 3 +- crates/ink/ir/src/ir/event/signature_topic.rs | 29 ++++++++++++------- 6 files changed, 21 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 71ed9d4697..c0d51160a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2630,7 +2630,6 @@ dependencies = [ "derive_more", "either", "heck", - "hex", "impl-serde", "ink_ir", "ink_primitives 5.0.0-rc", @@ -2782,7 +2781,7 @@ version = "5.0.0-rc" dependencies = [ "blake2", "either", - "hex", + "impl-serde", "ink_prelude 5.0.0-rc", "itertools 0.12.0", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 919ef1a144..3bcd5ac43a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,7 +80,6 @@ wasm-instrument = { version = "0.4.0" } which = { version = "5.0.0" } xxhash-rust = { version = "0.8" } const_env = { version = "0.1"} -hex = { version = "0.4" } # Substrate dependencies pallet-contracts-primitives = { version = "26.0.0", default-features = false } diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index b9359ab187..3fc156b19d 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -151,7 +151,7 @@ fn events() { let event = events.next().expect("event must exist"); assert_eq!(event.topics.len(), 2); assert_eq!( - event.topics.get(0).expect("first topic must exist"), + event.topics.first().expect("first topic must exist"), &topic1 ); assert_eq!( diff --git a/crates/ink/codegen/Cargo.toml b/crates/ink/codegen/Cargo.toml index 8c8de0b430..531ce41d5f 100644 --- a/crates/ink/codegen/Cargo.toml +++ b/crates/ink/codegen/Cargo.toml @@ -30,7 +30,6 @@ blake2 = { workspace = true } heck = { workspace = true } scale = { workspace = true } impl-serde = { workspace = true, default-features = true } -hex = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/crates/ink/ir/Cargo.toml b/crates/ink/ir/Cargo.toml index 24a74695cb..43624737ae 100644 --- a/crates/ink/ir/Cargo.toml +++ b/crates/ink/ir/Cargo.toml @@ -24,8 +24,7 @@ proc-macro2 = { workspace = true } itertools = { workspace = true } either = { workspace = true } blake2 = { workspace = true } -hex = { workspace = true } - +impl-serde = { workspace = true } ink_prelude = { workspace = true } [features] diff --git a/crates/ink/ir/src/ir/event/signature_topic.rs b/crates/ink/ir/src/ir/event/signature_topic.rs index 65a2b6a267..fd00d358b1 100644 --- a/crates/ink/ir/src/ir/event/signature_topic.rs +++ b/crates/ink/ir/src/ir/event/signature_topic.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use hex::decode_to_slice; +use impl_serde::serialize as serde_hex; use proc_macro2::{ Span, TokenStream as TokenStream2, @@ -52,16 +52,23 @@ impl TryFrom<&ast::MetaValue> for SignatureTopicArg { fn try_from(value: &ast::MetaValue) -> Result { if let ast::MetaValue::Lit(lit) = value { if let syn::Lit::Str(s) = lit { - let mut bytes = [0u8; 32]; - - if decode_to_slice(s.value(), &mut bytes).is_ok() { - Ok(Self { topic: bytes }) - } else { - Err(format_err_spanned!( - value, - "`signature_topic` has invalid hex string", - )) - } + let bytes: [u8; 32] = serde_hex::from_hex(&s.value()) + .map_err(|_| { + format_err_spanned!( + value, + "`signature_topic` has invalid hex string", + ) + })? + .try_into() + .map_err(|e: Vec| { + format_err_spanned!( + value, + "`signature_topic` is expected to be 32-byte hash. Found {} bytes", + e.len() + ) + })?; + + Ok(Self { topic: bytes }) } else { Err(format_err_spanned!( value, From 3c788c36f8cbe250c640fbfd9d84b9cc56d22f27 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Sat, 6 Jan 2024 19:26:54 +0000 Subject: [PATCH 13/20] Refactor. Remove signature topic macro in favor of local custom ink attr in the derive --- README.md | 1 + crates/env/src/event.rs | 23 ++- crates/env/src/lib.rs | 5 +- crates/ink/codegen/src/generator/event.rs | 30 +--- crates/ink/codegen/src/generator/mod.rs | 2 - .../codegen/src/generator/signature_topic.rs | 79 --------- crates/ink/codegen/src/lib.rs | 4 - crates/ink/ir/src/ir/event/mod.rs | 5 +- crates/ink/ir/src/ir/event/signature_topic.rs | 52 ++++-- crates/ink/ir/src/ir/mod.rs | 1 + crates/ink/ir/src/lib.rs | 1 + crates/ink/macro/src/event/metadata.rs | 2 +- crates/ink/macro/src/event/mod.rs | 153 +++++++++++------- crates/ink/macro/src/event/signature_topic.rs | 27 ---- crates/ink/macro/src/lib.rs | 69 +++----- crates/ink/macro/src/tests/event.rs | 62 +++++-- crates/ink/macro/src/tests/event_metadata.rs | 9 +- crates/ink/src/lib.rs | 1 - integration-tests/events/lib.rs | 21 ++- 19 files changed, 249 insertions(+), 298 deletions(-) delete mode 100644 crates/ink/codegen/src/generator/signature_topic.rs delete mode 100644 crates/ink/macro/src/event/signature_topic.rs diff --git a/README.md b/README.md index 32ce3d8e76..96354cf641 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,7 @@ In a module annotated with `#[ink::contract]` these attributes are available: | `#[ink(constructor)]` | Applicable to method. | Flags a method for the ink! storage struct as constructor making it available to the API for instantiating the contract. | | `#[ink(event)]` | On `struct` definitions. | Defines an ink! event. A contract can define multiple such ink! events. | | `#[ink(anonymous)]` | Applicable to ink! events. | Tells the ink! codegen to treat the ink! event as anonymous which omits the event signature as topic upon emitting. Very similar to anonymous events in Solidity. | +| `#[ink(signature_topic = _)]` | Applicable to ink! events. | Specifies custom signature topic of the event that allows to use manually specify shared event definition. | | `#[ink(topic)]` | Applicable on ink! event field. | Tells the ink! codegen to provide a topic hash for the given field. Every ink! event can only have a limited number of such topic fields. Similar semantics as to indexed event arguments in Solidity. | | `#[ink(payable)]` | Applicable to ink! messages. | Allows receiving value as part of the call of the ink! message. ink! constructors are implicitly payable. | | `#[ink(selector = S:u32)]` | Applicable to ink! messages and ink! constructors. | Specifies a concrete dispatch selector for the flagged entity. This allows a contract author to precisely control the selectors of their APIs making it possible to rename their API without breakage. | diff --git a/crates/env/src/event.rs b/crates/env/src/event.rs index b813ab0afd..0c4b2a3861 100644 --- a/crates/env/src/event.rs +++ b/crates/env/src/event.rs @@ -195,11 +195,19 @@ impl EventTopicsAmount for state::NoRemainingTopics { /// builder. /// /// Normally this trait should be implemented automatically via `#[derive(ink::Event)`. -pub trait Event: scale::Encode + GetSignatureTopic { +pub trait Event: scale::Encode { /// Type state indicating how many event topics are to be expected by the topics /// builder. type RemainingTopics: EventTopicsAmount; + /// The unique signature topic of the event. `None` for anonymous events. + /// + /// It can be automatically calculated or manually specified. + /// + /// Usually this is calculated using the `#[derive(ink::Event)]` derive, which by + /// default calculates this as `blake2b("Event(field1_type,field2_type)" + const SIGNATURE_TOPIC: core::option::Option<[u8; 32]>; + /// Guides event topic serialization using the given topics builder. fn topics( &self, @@ -209,16 +217,3 @@ pub trait Event: scale::Encode + GetSignatureTopic { E: Environment, B: TopicsBuilderBackend; } - -/// Getter that returns the signature topic for the specific event. -/// -/// It can be automatically calculated or manually specified. -/// -/// The unique signature topic of the event. `None` for anonymous events. -/// -/// Usually this is calculated using the `#[derive(ink::Event)]` derive, which by -/// default calculates this as `blake2b("Event(field1_type,field2_type)")` -pub trait GetSignatureTopic { - /// Retrieve the signature topic - const SIGNATURE_TOPIC: core::option::Option<[u8; 32]>; -} diff --git a/crates/env/src/lib.rs b/crates/env/src/lib.rs index 3cdfe9d908..2f0bbf5277 100644 --- a/crates/env/src/lib.rs +++ b/crates/env/src/lib.rs @@ -120,10 +120,7 @@ pub use self::{ Error, Result, }, - event::{ - Event, - GetSignatureTopic, - }, + event::Event, types::{ AccountIdGuard, DefaultEnvironment, diff --git a/crates/ink/codegen/src/generator/event.rs b/crates/ink/codegen/src/generator/event.rs index f3edb37149..8dbcd265c2 100644 --- a/crates/ink/codegen/src/generator/event.rs +++ b/crates/ink/codegen/src/generator/event.rs @@ -15,7 +15,6 @@ use crate::GenerateCode; use derive_more::From; use proc_macro2::TokenStream as TokenStream2; -use quote::quote; use syn::spanned::Spanned; /// Generates code for the event item. @@ -33,8 +32,10 @@ impl GenerateCode for Event<'_> { .item .anonymous() .then(|| quote::quote! { #[ink(anonymous)] }); - - let signature_topic = self.generate_signature_topic(); + let signature_topic = self + .item + .signature_topic_hash() + .map(|hash| quote::quote! { #[ink(signature_topic = #hash)] }); let cfg_attrs = self.item.get_cfg_attrs(item.span()); quote::quote! ( @@ -42,30 +43,9 @@ impl GenerateCode for Event<'_> { #[cfg_attr(feature = "std", derive(::ink::EventMetadata))] #[derive(::ink::Event)] #[::ink::scale_derive(Encode, Decode)] - #signature_topic #anonymous + #signature_topic #item ) } } - -impl Event<'_> { - /// Generates the `#[ink::signature_topic]` attribute for the given event. - /// - /// # Note - /// If `anonymous` is present, no attribute is generated - /// and the blank implementation is generated by `#derive(Event)`. - fn generate_signature_topic(&self) -> TokenStream2 { - let signature_topic = if let Some(hash) = self.item.signature_topic_hash() { - quote! { - #[::ink::signature_topic(hash = #hash)] - } - } else if self.item.anonymous() { - quote! {} - } else { - quote! { #[::ink::signature_topic] } - }; - - quote! { #signature_topic } - } -} diff --git a/crates/ink/codegen/src/generator/mod.rs b/crates/ink/codegen/src/generator/mod.rs index 3d18493bd4..b306a84c6f 100644 --- a/crates/ink/codegen/src/generator/mod.rs +++ b/crates/ink/codegen/src/generator/mod.rs @@ -38,7 +38,6 @@ mod ink_test; mod item_impls; mod metadata; mod selector; -mod signature_topic; mod storage; mod storage_item; mod trait_def; @@ -68,7 +67,6 @@ pub use self::{ SelectorBytes, SelectorId, }, - signature_topic::SignatureTopic, storage::Storage, storage_item::StorageItem, trait_def::TraitDefinition, diff --git a/crates/ink/codegen/src/generator/signature_topic.rs b/crates/ink/codegen/src/generator/signature_topic.rs deleted file mode 100644 index f0c0769828..0000000000 --- a/crates/ink/codegen/src/generator/signature_topic.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (C) 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::HexLiteral; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use syn::spanned::Spanned; - -/// Generates code to generate signature topic. -#[derive(From, Copy, Clone)] -pub struct SignatureTopic<'a> { - /// The event item to generate code for. - item: &'a ir::SignatureTopic, -} - -impl GenerateCode for SignatureTopic<'_> { - /// Generates ink! signature topic item code. - fn generate_code(&self) -> TokenStream2 { - let item = self.item.item(); - let signature_topic = self.generate_signature_topic(); - let cfg_attrs = self.item.get_cfg_attrs(item.span()); - - quote::quote! ( - #( #cfg_attrs )* - #signature_topic - #item - ) - } -} - -impl SignatureTopic<'_> { - /// Generates the implementation of `GetSignatureTopic` trait. - fn generate_signature_topic(&self) -> TokenStream2 { - let item_ident = &self.item.item().ident; - let signature_topic = if let Some(bytes) = self.item.signature_topic() { - let hash_bytes = bytes.map(|b| b.hex_padded_suffixed()); - quote! { ::core::option::Option::Some([ #( #hash_bytes ),* ]) } - } else { - let calculated_topic = signature_topic(&self.item.item().fields, item_ident); - quote! { ::core::option::Option::Some(#calculated_topic) } - }; - - quote! { - impl ::ink::env::GetSignatureTopic for #item_ident { - const SIGNATURE_TOPIC: ::core::option::Option<[u8; 32]> = #signature_topic; - } - } - } -} - -/// The signature topic of an event variant. -/// -/// Calculated with `blake2b("Event(field1_type,field2_type)")`. -fn signature_topic(fields: &syn::Fields, event_ident: &syn::Ident) -> TokenStream2 { - let fields = fields - .iter() - .map(|field| { - quote::ToTokens::to_token_stream(&field.ty) - .to_string() - .replace(' ', "") - }) - .collect::>() - .join(","); - let topic_str = format!("{}({fields})", event_ident); - quote!(::ink::blake2x256!(#topic_str)) -} diff --git a/crates/ink/codegen/src/lib.rs b/crates/ink/codegen/src/lib.rs index ee59b7235a..b9c97ee994 100644 --- a/crates/ink/codegen/src/lib.rs +++ b/crates/ink/codegen/src/lib.rs @@ -62,10 +62,6 @@ impl<'a> CodeGenerator for &'a ir::Event { type Generator = generator::Event<'a>; } -impl<'a> CodeGenerator for &'a ir::SignatureTopic { - type Generator = generator::SignatureTopic<'a>; -} - impl<'a> CodeGenerator for &'a ir::StorageItem { type Generator = generator::StorageItem<'a>; } diff --git a/crates/ink/ir/src/ir/event/mod.rs b/crates/ink/ir/src/ir/event/mod.rs index ee471aae46..d2c1b101d0 100644 --- a/crates/ink/ir/src/ir/event/mod.rs +++ b/crates/ink/ir/src/ir/event/mod.rs @@ -29,7 +29,10 @@ use crate::{ utils::extract_cfg_attributes, }; -pub use signature_topic::SignatureTopic; +pub use signature_topic::{ + SignatureTopic, + SignatureTopicArg, +}; /// A checked ink! event with its configuration. #[derive(Debug, PartialEq, Eq)] diff --git a/crates/ink/ir/src/ir/event/signature_topic.rs b/crates/ink/ir/src/ir/event/signature_topic.rs index fd00d358b1..4d549b5baf 100644 --- a/crates/ink/ir/src/ir/event/signature_topic.rs +++ b/crates/ink/ir/src/ir/event/signature_topic.rs @@ -46,35 +46,43 @@ impl From<&[u8; 32]> for SignatureTopicArg { } } -impl TryFrom<&ast::MetaValue> for SignatureTopicArg { +impl TryFrom<&syn::Lit> for SignatureTopicArg { type Error = syn::Error; - fn try_from(value: &ast::MetaValue) -> Result { - if let ast::MetaValue::Lit(lit) = value { - if let syn::Lit::Str(s) = lit { - let bytes: [u8; 32] = serde_hex::from_hex(&s.value()) + fn try_from(lit: &syn::Lit) -> Result { + if let syn::Lit::Str(s) = lit { + let bytes: [u8; 32] = serde_hex::from_hex(&s.value()) .map_err(|_| { format_err_spanned!( - value, + lit, "`signature_topic` has invalid hex string", ) })? .try_into() .map_err(|e: Vec| { format_err_spanned!( - value, + lit, "`signature_topic` is expected to be 32-byte hash. Found {} bytes", e.len() ) })?; - Ok(Self { topic: bytes }) - } else { - Err(format_err_spanned!( - value, - "Expected string literal argument for the `signature_topic`" - )) - } + Ok(Self { topic: bytes }) + } else { + Err(format_err_spanned!( + lit, + "Expected string literal argument for the `signature_topic`" + )) + } + } +} + +impl TryFrom<&ast::MetaValue> for SignatureTopicArg { + type Error = syn::Error; + + fn try_from(value: &ast::MetaValue) -> Result { + if let ast::MetaValue::Lit(lit) = value { + Self::try_from(lit) } else { Err(format_err_spanned!( value, @@ -109,6 +117,22 @@ impl TryFrom for Option { } } +impl TryFrom<&syn::MetaNameValue> for SignatureTopicArg { + type Error = syn::Error; + + fn try_from(nv: &syn::MetaNameValue) -> Result { + if nv.path.is_ident("signature_topic") { + if let syn::Expr::Lit(lit_expr) = &nv.value { + Self::try_from(&lit_expr.lit) + } else { + Err(format_err_spanned!(&nv.value, "Expected literal argument")) + } + } else { + Err(format_err_spanned!(nv, "Expected `signature_topic` ident")) + } + } +} + /// The signature topic argument of an event variant. /// /// Used as part of `ink::signature_topic` macro. diff --git a/crates/ink/ir/src/ir/mod.rs b/crates/ink/ir/src/ir/mod.rs index bb0a8564a2..dc418f0008 100644 --- a/crates/ink/ir/src/ir/mod.rs +++ b/crates/ink/ir/src/ir/mod.rs @@ -73,6 +73,7 @@ pub use self::{ event::{ Event, SignatureTopic, + SignatureTopicArg, }, ink_test::InkTest, item::{ diff --git a/crates/ink/ir/src/lib.rs b/crates/ink/ir/src/lib.rs index a2bd0e0be2..31f4bf32f0 100644 --- a/crates/ink/ir/src/lib.rs +++ b/crates/ink/ir/src/lib.rs @@ -78,6 +78,7 @@ pub use self::{ Selector, SelectorMacro, SignatureTopic, + SignatureTopicArg, Storage, StorageItem, Visibility, diff --git a/crates/ink/macro/src/event/metadata.rs b/crates/ink/macro/src/event/metadata.rs index 8d09d24213..5c9a70cd9a 100644 --- a/crates/ink/macro/src/event/metadata.rs +++ b/crates/ink/macro/src/event/metadata.rs @@ -91,7 +91,7 @@ fn event_metadata_derive_struct(s: synstructure::Structure) -> syn::Result::SIGNATURE_TOPIC + ::SIGNATURE_TOPIC ) .args([ #( #args ),* diff --git a/crates/ink/macro/src/event/mod.rs b/crates/ink/macro/src/event/mod.rs index 7045f6e734..fd9e8be2d0 100644 --- a/crates/ink/macro/src/event/mod.rs +++ b/crates/ink/macro/src/event/mod.rs @@ -13,10 +13,9 @@ // limitations under the License. mod metadata; -mod signature_topic; +use ink_ir::SignatureTopicArg; pub use metadata::event_metadata_derive; -pub use signature_topic::generate_signature_topic; use ink_codegen::generate_code; use proc_macro2::TokenStream as TokenStream2; @@ -24,7 +23,11 @@ use quote::{ quote, quote_spanned, }; -use syn::spanned::Spanned; +use syn::{ + punctuated::Punctuated, + spanned::Spanned, + Token, +}; /// Generate code from the `#[ink::event]` attribute. This expands to the required /// derive macros to satisfy an event implementation. @@ -61,7 +64,7 @@ fn event_derive_struct(mut s: synstructure::Structure) -> syn::Result syn::Result syn::Result - .push_topic(::SIGNATURE_TOPIC.as_ref()) + .push_topic(Self::SIGNATURE_TOPIC.as_ref()) )) }; + let signature_topic = if !anonymous { + let event_ident = variant.ast().ident; + if let Some(sig_arg) = parse_signature_arg(&s.ast().attrs)? { + let bytes = sig_arg.signature_topic(); + quote_spanned!(span=> ::core::option::Option::Some([ #(#bytes),* ])) + } else { + let calculated_signature_topic = + signature_topic(variant.ast().fields, event_ident); + quote_spanned!(span=> ::core::option::Option::Some(#calculated_signature_topic)) + } + } else { + quote_spanned!(span=> ::core::option::Option::None) + }; + let topics = variant.bindings().iter().fold(quote!(), |acc, field| { let field_ty = &field.ast().ty; let field_span = field_ty.span(); @@ -125,8 +142,9 @@ fn event_derive_struct(mut s: synstructure::Structure) -> syn::Result = #signature_topic; fn topics( &self, @@ -140,35 +158,7 @@ fn event_derive_struct(mut s: synstructure::Structure) -> syn::Result TokenStream2 { - if anonymous { - quote! { - impl ::ink::env::GetSignatureTopic for #item_ident { - const SIGNATURE_TOPIC: ::core::option::Option<[u8; 32]> = None; - } - } - } else { - quote! {} - } + })) } /// Checks if the given field's attributes contain an `#[ink(topic)]` attribute. @@ -193,41 +183,92 @@ fn has_ink_topic_attribute(field: &synstructure::BindingInfo) -> syn::Result syn::Result { + let parsed_args = parse_arg_attr(attrs)?; + let Some(meta) = parsed_args else { + return Ok(false); + }; + Ok(meta.path().is_ident(path)) +} + +/// Parses custom `ink` attribute with the arbitrary argument. /// /// # Errors -/// - If there are multiple `ink` attributes with the given path. -/// - If multiple arguments are given to the `ink` attribute. -/// - If any other `ink` attributes are present other than the one with the given path. -fn has_ink_attribute(attrs: &[syn::Attribute], path: &str) -> syn::Result { +/// - Multiple arguments are specified +/// - Multiple `ink` attribute present +fn parse_arg_attr(attrs: &[syn::Attribute]) -> syn::Result> { let ink_attrs = attrs .iter() .filter_map(|attr| { if attr.path().is_ident("ink") { - let parse_result = attr.parse_nested_meta(|meta| { - if meta.path.is_ident(path) { - if meta.input.is_empty() { - Ok(()) - } else { - Err(meta.error(format!( - "Invalid `#[ink({path})]` attribute: multiple arguments not allowed.", - ))) - } + let parse_result = || -> Result { + let nested = attr.parse_args_with( + Punctuated::::parse_separated_nonempty, + )?; + if nested.len() > 1 { + Err(syn::Error::new( + nested[1].span(), + "Only a single argument is allowed".to_string(), + )) } else { - Err(meta - .error(format!("Only `#[ink({path})]` attribute allowed."))) + Ok(nested + .first() + .ok_or(syn::Error::new( + nested[0].span(), + "Expected to have an argument".to_string(), + ))? + .clone()) } - }); - Some(parse_result.map(|_| attr)) + }; + Some(parse_result()) } else { None } }) .collect::>>()?; + if ink_attrs.len() > 1 { - return Err(syn::Error::new( + Err(syn::Error::new( ink_attrs[1].span(), - format!("Only a single `#[ink({})]` attribute allowed.", path), + "Only a single custom ink attribute is allowed.".to_string(), )) + } else { + Ok(ink_attrs.first().cloned()) } - Ok(!ink_attrs.is_empty()) +} + +/// Parses signature topic from the list of attributes. +/// +/// # Errors +/// - Name-value pair is not specified correctly. +/// - Provided value is of wrong format. +/// - Provided hash string is of wrong length. +fn parse_signature_arg( + attrs: &[syn::Attribute], +) -> syn::Result> { + let Some(meta) = parse_arg_attr(attrs)? else { + return Ok(None); + }; + if let syn::Meta::NameValue(nv) = &meta { + Ok(Some(SignatureTopicArg::try_from(nv)?)) + } else { + Ok(None) + } +} + +/// The signature topic of an event variant. +/// +/// Calculated with `blake2b("Event(field1_type,field2_type)")`. +fn signature_topic(fields: &syn::Fields, event_ident: &syn::Ident) -> TokenStream2 { + let fields = fields + .iter() + .map(|field| { + quote::ToTokens::to_token_stream(&field.ty) + .to_string() + .replace(' ', "") + }) + .collect::>() + .join(","); + let topic_str = format!("{}({fields})", event_ident); + quote!(::ink::blake2x256!(#topic_str)) } diff --git a/crates/ink/macro/src/event/signature_topic.rs b/crates/ink/macro/src/event/signature_topic.rs deleted file mode 100644 index d455e602fa..0000000000 --- a/crates/ink/macro/src/event/signature_topic.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (C) 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 ink_codegen::generate_code; -use proc_macro2::TokenStream as TokenStream2; - -/// Generate code from the `#[ink::event]` attribute. This expands to the required -/// derive macros to satisfy an event implementation. -pub fn generate_signature_topic( - config: TokenStream2, - input: TokenStream2, -) -> TokenStream2 { - ink_ir::SignatureTopic::new(config, input) - .map(|event| generate_code(&event)) - .unwrap_or_else(|err| err.to_compile_error()) -} diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index bad13eb774..f1be512526 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -695,38 +695,6 @@ pub fn event(attr: TokenStream, item: TokenStream) -> TokenStream { event::generate(attr.into(), item.into()).into() } -/// Implements the `GetSignatureTopic` traits for a `struct` to generate -/// a signature topic -/// -/// By default, a signature topic will be generated for the event. -/// Custom signature topic can be specified with `hash` argument that takes 32 byte hex -/// string. -/// -/// # Examples -/// -/// ``` -/// // Generates the default signature topic -/// #[ink::signature_topic] -/// pub struct MyEvent { -/// pub field: u32, -/// pub topic: [u8; 32], -/// } -/// -/// // Generates custom signature topic -/// #[ink::signature_topic(hash = "1111111111111111111111111111111111111111111111111111111111111111")] -/// pub struct MyCustomSignatureEvent { -/// pub field: u32, -/// pub topic: [u8; 32], -/// } -/// -/// use ink::env::GetSignatureTopic; -/// assert_eq!(Some([17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17]), -/// ::SIGNATURE_TOPIC) -#[proc_macro_attribute] -pub fn signature_topic(attr: TokenStream, item: TokenStream) -> TokenStream { - event::generate_signature_topic(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` @@ -1376,7 +1344,7 @@ synstructure::decl_derive!( [Event, attributes(ink)] => /// Derives an implementation of the [`ink::Event`] trait for the given `struct`. /// - /// **Note** [`ink::Event`] requires [`scale::Encode`] and [`ink::env::GetSignatureTopic`] implementation, + /// **Note** [`ink::Event`] requires [`scale::Encode`] implementation, /// it is up to the user to provide that: usually via the derive and `#[ink::signature_topic]` macros. /// /// Usually this is used in conjunction with the [`EventMetadata`] derive. @@ -1389,12 +1357,11 @@ synstructure::decl_derive!( /// ``` /// use ink::{ /// Event, - /// env::{ DefaultEnvironment, GetSignatureTopic }, + /// env::DefaultEnvironment, /// }; /// use scale::Encode; /// /// #[derive(Event, Encode)] - /// #[ink::signature_topic] /// struct MyEvent { /// a: u32, /// #[ink(topic)] @@ -1431,7 +1398,6 @@ synstructure::decl_derive!( /// /// ``` /// #[derive(ink::Event, scale::Encode)] - /// #[ink::signature_topic] /// pub struct MyEvent { /// a: u32, /// } @@ -1440,21 +1406,37 @@ synstructure::decl_derive!( /// type MyU32 = u32; /// /// #[derive(ink::Event, scale::Encode)] - /// #[ink::signature_topic] /// pub struct MyEvent { /// a: MyU32, /// } /// } /// - /// use ink::env::GetSignatureTopic; - /// assert_ne!(::SIGNATURE_TOPIC, ::SIGNATURE_TOPIC); + /// assert_ne!(::SIGNATURE_TOPIC, ::SIGNATURE_TOPIC); /// ``` /// + /// ## Custom Signature + /// + /// Sometimes it is useful to specify the custom signature topic. + /// For example, when the event definition from the other contract is not accessible. + /// + /// The macro provides `#[ink(signature_topic = _)]` nested macro that allows to provide + /// 32 byte hash string of the custom signature topic. + /// + /// Generates custom signature topic + /// #[derive(ink::Event, scale::Encode)] + /// #[ink(signature_topic = "1111111111111111111111111111111111111111111111111111111111111111")] + /// pub struct MyCustomSignatureEvent { + /// pub field: u32, + /// pub topic: [u8; 32], + /// } + /// + /// assert_eq!(Some([17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17]), + /// ::SIGNATURE_TOPIC) + /// /// ## Anonymous Events /// /// If the event is annotated with `#[ink(anonymous)]` then no signature topic is generated. - /// The macro will generate a blank implementation of `GetSignatureTopic`, - /// and `#[ink::signature_topic]` should not be used. + /// `#[ink(signature_topic = _)]` should not be used. event::event_derive ); @@ -1463,8 +1445,8 @@ synstructure::decl_derive!( /// Derives the [`ink::EventMetadata`] trait for the given `struct`, which provides metadata /// about the event definition. /// - /// Requires that the `struct` also implements the [`ink::Event`] and `GetSignatureTopic` traits, - /// so this derive is usually used in combination with the [`Event`] derive and `#[ink::signature_topic]` macro. + /// Requires that the `struct` also implements the [`ink::Event`] trait, + /// so this derive is usually used in combination with the [`Event`] derive. /// /// Metadata is not embedded into the contract binary, it is generated from a separate /// compilation of the contract with the `std` feature, therefore this derive must be @@ -1485,7 +1467,6 @@ synstructure::decl_derive!( /// /// #[cfg_attr(feature = "std", derive(::ink::EventMetadata))] /// #[derive(Event, Encode)] - /// #[ink::signature_topic] /// struct MyEvent { /// a: u32, /// #[ink(topic)] diff --git a/crates/ink/macro/src/tests/event.rs b/crates/ink/macro/src/tests/event.rs index de428daeae..056498c223 100644 --- a/crates/ink/macro/src/tests/event.rs +++ b/crates/ink/macro/src/tests/event.rs @@ -27,7 +27,6 @@ fn unit_struct_works() { crate::test_derive! { event_derive { #[derive(scale::Encode)] - #[::ink::signature_topic] struct UnitStruct; } expands to { @@ -35,6 +34,9 @@ fn unit_struct_works() { impl ::ink::env::Event for UnitStruct { type RemainingTopics = [::ink::env::event::state::HasRemainingTopics; 1usize]; + const SIGNATURE_TOPIC: ::core::option::Option<[::core::primitive::u8; 32]> = + ::core::option::Option::Some( ::ink::blake2x256!("UnitStruct()") ); + fn topics( &self, builder: ::ink::env::event::TopicsBuilder<::ink::env::event::state::Uninit, E, B>, @@ -47,7 +49,7 @@ fn unit_struct_works() { UnitStruct => { builder .build::() - .push_topic(::SIGNATURE_TOPIC.as_ref()) + .push_topic(Self::SIGNATURE_TOPIC.as_ref()) .finish() } } @@ -67,13 +69,13 @@ fn unit_struct_anonymous_has_no_topics() { struct UnitStruct; } expands to { - impl ::ink::env::GetSignatureTopic for UnitStruct { - const SIGNATURE_TOPIC: ::core::option::Option<[u8; 32]> = None; - } const _: () = { impl ::ink::env::Event for UnitStruct { type RemainingTopics = ::ink::env::event::state::NoRemainingTopics; + const SIGNATURE_TOPIC: ::core::option::Option<[::core::primitive::u8; 32]> = + ::core::option::Option::None; + fn topics( &self, builder: ::ink::env::event::TopicsBuilder<::ink::env::event::state::Uninit, E, B>, @@ -101,7 +103,6 @@ fn struct_with_fields_no_topics() { crate::test_derive! { event_derive { #[derive(scale::Encode)] - #[::ink::signature_topic] struct Event { field_1: u32, field_2: u64, @@ -113,6 +114,9 @@ fn struct_with_fields_no_topics() { impl ::ink::env::Event for Event { type RemainingTopics = [::ink::env::event::state::HasRemainingTopics; 1usize]; + const SIGNATURE_TOPIC: ::core::option::Option<[::core::primitive::u8; 32]> = + ::core::option::Option::Some( ::ink::blake2x256!("Event(u32,u64,u128)") ); + fn topics( &self, builder: ::ink::env::event::TopicsBuilder<::ink::env::event::state::Uninit, E, B>, @@ -125,7 +129,7 @@ fn struct_with_fields_no_topics() { Event { .. } => { builder .build::() - .push_topic(::SIGNATURE_TOPIC.as_ref()) + .push_topic(Self::SIGNATURE_TOPIC.as_ref()) .finish() } } @@ -141,7 +145,6 @@ fn struct_with_fields_and_some_topics() { crate::test_derive! { event_derive { #[derive(scale::Encode)] - #[::ink::signature_topic] struct Event { field_1: u32, #[ink(topic)] @@ -155,6 +158,8 @@ fn struct_with_fields_and_some_topics() { impl ::ink::env::Event for Event { type RemainingTopics = [::ink::env::event::state::HasRemainingTopics; 3usize]; + const SIGNATURE_TOPIC: ::core::option::Option<[::core::primitive::u8; 32]> = + ::core::option::Option::Some( ::ink::blake2x256!("Event(u32,u64,u128)") ); fn topics( &self, @@ -168,7 +173,7 @@ fn struct_with_fields_and_some_topics() { Event { field_2 : __binding_1 , field_3 : __binding_2 , .. } => { builder .build::() - .push_topic(::SIGNATURE_TOPIC.as_ref()) + .push_topic(Self::SIGNATURE_TOPIC.as_ref()) .push_topic(::ink::as_option!(__binding_1)) .push_topic(::ink::as_option!(__binding_2)) .finish() @@ -180,3 +185,42 @@ fn struct_with_fields_and_some_topics() { } no_build } } + +#[test] +fn custom_signature_topic() { + crate::test_derive! { + event_derive { + #[derive(scale::Encode)] + #[ink(signature_topic = "1111111111111111111111111111111111111111111111111111111111111111")] + struct UnitStruct; + } + expands to { + const _: () = { + impl ::ink::env::Event for UnitStruct { + type RemainingTopics = [::ink::env::event::state::HasRemainingTopics; 1usize]; + + const SIGNATURE_TOPIC: ::core::option::Option<[::core::primitive::u8; 32]> = + ::core::option::Option::Some( [17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8, 17u8] ); + + fn topics( + &self, + builder: ::ink::env::event::TopicsBuilder<::ink::env::event::state::Uninit, E, B>, + ) -> >::Output + where + E: ::ink::env::Environment, + B: ::ink::env::event::TopicsBuilderBackend, + { + match self { + UnitStruct => { + builder + .build::() + .push_topic(Self::SIGNATURE_TOPIC.as_ref()) + .finish() + } + } + } + } + }; + } no_build + } +} diff --git a/crates/ink/macro/src/tests/event_metadata.rs b/crates/ink/macro/src/tests/event_metadata.rs index 109ca3281b..e2abc973c3 100644 --- a/crates/ink/macro/src/tests/event_metadata.rs +++ b/crates/ink/macro/src/tests/event_metadata.rs @@ -19,7 +19,6 @@ fn unit_struct_works() { crate::test_derive! { event_metadata_derive { #[derive(ink::Event, scale::Encode)] - #[::ink::signature_topic] struct UnitStruct; } expands to { @@ -35,7 +34,7 @@ fn unit_struct_works() { ::ink::metadata::EventSpec::new(::core::stringify!(UnitStruct)) .module_path(::core::module_path!()) - .signature_topic(::SIGNATURE_TOPIC) + .signature_topic(::SIGNATURE_TOPIC) .args([]) .docs([]) .done() @@ -51,7 +50,6 @@ fn struct_with_fields_no_topics() { crate::test_derive! { event_metadata_derive { #[derive(ink::Event, scale::Encode)] - #[::ink::signature_topic] struct Event { field_1: u32, field_2: u64, @@ -71,7 +69,7 @@ fn struct_with_fields_no_topics() { ::ink::metadata::EventSpec::new(::core::stringify!(Event)) .module_path(::core::module_path!()) - .signature_topic(::SIGNATURE_TOPIC) + .signature_topic(::SIGNATURE_TOPIC) .args([ ::ink::metadata::EventParamSpec::new(::core::stringify!(field_1)) .of_type(::ink::metadata::TypeSpec::of_type::()) @@ -103,7 +101,6 @@ fn struct_with_fields_and_some_topics() { crate::test_derive! { event_metadata_derive { #[derive(ink::Event, scale::Encode)] - #[::ink::signature_topic] struct Event { field_1: u32, #[ink(topic)] @@ -125,7 +122,7 @@ fn struct_with_fields_and_some_topics() { ::ink::metadata::EventSpec::new(::core::stringify!(Event)) .module_path(::core::module_path!()) - .signature_topic(::SIGNATURE_TOPIC) + .signature_topic(::SIGNATURE_TOPIC) .args([ ::ink::metadata::EventParamSpec::new(::core::stringify!(field_1)) .of_type(::ink::metadata::TypeSpec::of_type::()) diff --git a/crates/ink/src/lib.rs b/crates/ink/src/lib.rs index 00ffc0416d..6cd7d2c500 100644 --- a/crates/ink/src/lib.rs +++ b/crates/ink/src/lib.rs @@ -80,7 +80,6 @@ pub use ink_macro::{ scale_derive, selector_bytes, selector_id, - signature_topic, storage_item, test, trait_definition, diff --git a/integration-tests/events/lib.rs b/integration-tests/events/lib.rs index 525c6e5b2d..2fa51eafdc 100644 --- a/integration-tests/events/lib.rs +++ b/integration-tests/events/lib.rs @@ -166,7 +166,7 @@ pub mod events { assert_eq!(event.topics.len(), 3); let signature_topic = - ::SIGNATURE_TOPIC + ::SIGNATURE_TOPIC .map(|topic| topic.to_vec()); assert_eq!(Some(&event.topics[0]), signature_topic.as_ref()); assert_eq!(event.topics[1], [0x42; 32]); @@ -186,7 +186,7 @@ pub mod events { let event = &emitted_events[0]; let signature_topic = - ::SIGNATURE_TOPIC + ::SIGNATURE_TOPIC .map(|topic| topic.to_vec()) .unwrap(); @@ -207,7 +207,7 @@ pub mod events { assert_eq!(1, emitted_events.len()); let signature_topic = - ::SIGNATURE_TOPIC; + ::SIGNATURE_TOPIC; assert_eq!(Some([17u8; 32]), signature_topic); } @@ -230,7 +230,7 @@ pub mod events { assert_eq!(event.topics[0], topic); let signature_topic = - ::SIGNATURE_TOPIC; + ::SIGNATURE_TOPIC; assert_eq!(None, signature_topic); } } @@ -278,7 +278,7 @@ pub mod events { assert_eq!(!init_value, flipped.value); let signature_topic = - ::SIGNATURE_TOPIC + ::SIGNATURE_TOPIC .map(H256::from) .unwrap(); @@ -320,10 +320,9 @@ pub mod events { .expect("encountered invalid contract event data buffer"); assert_eq!(!init_value, flipped.value); - let signature_topic = - ::SIGNATURE_TOPIC - .map(H256::from) - .unwrap(); + let signature_topic = ::SIGNATURE_TOPIC + .map(H256::from) + .unwrap(); let expected_topics = vec![signature_topic]; assert_eq!(expected_topics, contract_event.topics); @@ -364,7 +363,7 @@ pub mod events { assert!(event.maybe_hash.is_none()); let signature_topic = - ::SIGNATURE_TOPIC + ::SIGNATURE_TOPIC .map(H256::from) .unwrap(); @@ -406,7 +405,7 @@ pub mod events { assert_eq!(1, contract_events.len()); let signature_topic = - ::SIGNATURE_TOPIC; + ::SIGNATURE_TOPIC; assert_eq!(Some([17u8; 32]), signature_topic); From 1c92ba8d96695945755d07ddfbd109ec9cb2c10e Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Sat, 6 Jan 2024 19:38:30 +0000 Subject: [PATCH 14/20] minor fixes --- Cargo.lock | 12 ------------ crates/env/src/event.rs | 2 +- crates/ink/macro/src/lib.rs | 3 ++- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f1b025b696..99638c6e2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7126,30 +7126,18 @@ checksum = "ff4524214bc4629eba08d78ceb1d6507070cc0bcbbed23af74e19e6e924a24cf" [[package]] name = "zerocopy" -<<<<<<< HEAD -version = "0.7.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" -======= version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" ->>>>>>> master dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -<<<<<<< HEAD -version = "0.7.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" -======= version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" ->>>>>>> master dependencies = [ "proc-macro2", "quote", diff --git a/crates/env/src/event.rs b/crates/env/src/event.rs index 0c4b2a3861..9072cad604 100644 --- a/crates/env/src/event.rs +++ b/crates/env/src/event.rs @@ -205,7 +205,7 @@ pub trait Event: scale::Encode { /// It can be automatically calculated or manually specified. /// /// Usually this is calculated using the `#[derive(ink::Event)]` derive, which by - /// default calculates this as `blake2b("Event(field1_type,field2_type)" + /// default calculates this as `blake2b("Event(field1_type,field2_type)"` const SIGNATURE_TOPIC: core::option::Option<[u8; 32]>; /// Guides event topic serialization using the given topics builder. diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index f1be512526..55d2661dd1 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -1423,6 +1423,7 @@ synstructure::decl_derive!( /// 32 byte hash string of the custom signature topic. /// /// Generates custom signature topic + /// ``` /// #[derive(ink::Event, scale::Encode)] /// #[ink(signature_topic = "1111111111111111111111111111111111111111111111111111111111111111")] /// pub struct MyCustomSignatureEvent { @@ -1432,7 +1433,7 @@ synstructure::decl_derive!( /// /// assert_eq!(Some([17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17]), /// ::SIGNATURE_TOPIC) - /// + ///``` /// ## Anonymous Events /// /// If the event is annotated with `#[ink(anonymous)]` then no signature topic is generated. From 9e67b3e367fb14e1818681a9f36255173fa633f2 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Sat, 6 Jan 2024 19:50:09 +0000 Subject: [PATCH 15/20] remove trailing code --- crates/ink/ir/src/ir/event/mod.rs | 5 +- crates/ink/ir/src/ir/event/signature_topic.rs | 65 +------------------ crates/ink/ir/src/ir/mod.rs | 1 - crates/ink/ir/src/lib.rs | 1 - crates/ink/macro/src/lib.rs | 3 +- 5 files changed, 3 insertions(+), 72 deletions(-) diff --git a/crates/ink/ir/src/ir/event/mod.rs b/crates/ink/ir/src/ir/event/mod.rs index d2c1b101d0..a34bb897b6 100644 --- a/crates/ink/ir/src/ir/event/mod.rs +++ b/crates/ink/ir/src/ir/event/mod.rs @@ -29,10 +29,7 @@ use crate::{ utils::extract_cfg_attributes, }; -pub use signature_topic::{ - SignatureTopic, - SignatureTopicArg, -}; +pub use signature_topic::SignatureTopicArg; /// A checked ink! event with its configuration. #[derive(Debug, PartialEq, Eq)] diff --git a/crates/ink/ir/src/ir/event/signature_topic.rs b/crates/ink/ir/src/ir/event/signature_topic.rs index 4d549b5baf..aac02357c4 100644 --- a/crates/ink/ir/src/ir/event/signature_topic.rs +++ b/crates/ink/ir/src/ir/event/signature_topic.rs @@ -13,18 +13,9 @@ // limitations under the License. use impl_serde::serialize as serde_hex; -use proc_macro2::{ - Span, - TokenStream as TokenStream2, -}; -use quote::ToTokens; use syn::spanned::Spanned; -use crate::{ - ast, - error::ExtError, - utils::extract_cfg_attributes, -}; +use crate::ast; /// The signature topic argument of an event variant. /// @@ -132,57 +123,3 @@ impl TryFrom<&syn::MetaNameValue> for SignatureTopicArg { } } } - -/// The signature topic argument of an event variant. -/// -/// Used as part of `ink::signature_topic` macro. -/// -/// Calculated with `blake2b("Event(field1_type,field2_type)")`. -#[derive(Debug, PartialEq, Eq)] -pub struct SignatureTopic { - item: syn::ItemStruct, - arg: Option, -} - -impl SignatureTopic { - pub fn new(config: TokenStream2, item: TokenStream2) -> Result { - let item = syn::parse2::(item.clone()).map_err(|err| { - err.into_combine(format_err_spanned!( - item, - "event definition must be a `struct`", - )) - })?; - let parsed_config = syn::parse2::(config)?; - let arg = Option::::try_from(parsed_config)?; - - for attr in &item.attrs { - if attr - .path() - .to_token_stream() - .to_string() - .contains("signature_topic") - { - return Err(format_err_spanned!( - attr, - "only one `ink::signature_topic` is allowed", - )); - } - } - Ok(Self { item, arg }) - } - - /// Returns the event definition . - pub fn item(&self) -> &syn::ItemStruct { - &self.item - } - - /// Return a signature topic, if required. - pub fn signature_topic(&self) -> Option<[u8; 32]> { - self.arg.map(|a| a.signature_topic()) - } - - /// Returns a list of `cfg` attributes if any. - pub fn get_cfg_attrs(&self, span: Span) -> Vec { - extract_cfg_attributes(&self.item.attrs, span) - } -} diff --git a/crates/ink/ir/src/ir/mod.rs b/crates/ink/ir/src/ir/mod.rs index dc418f0008..14e1f9b9b2 100644 --- a/crates/ink/ir/src/ir/mod.rs +++ b/crates/ink/ir/src/ir/mod.rs @@ -72,7 +72,6 @@ pub use self::{ contract::Contract, event::{ Event, - SignatureTopic, SignatureTopicArg, }, ink_test::InkTest, diff --git a/crates/ink/ir/src/lib.rs b/crates/ink/ir/src/lib.rs index 31f4bf32f0..6aaeaff802 100644 --- a/crates/ink/ir/src/lib.rs +++ b/crates/ink/ir/src/lib.rs @@ -77,7 +77,6 @@ pub use self::{ Receiver, Selector, SelectorMacro, - SignatureTopic, SignatureTopicArg, Storage, StorageItem, diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index 55d2661dd1..503ad92510 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -1344,8 +1344,7 @@ synstructure::decl_derive!( [Event, attributes(ink)] => /// Derives an implementation of the [`ink::Event`] trait for the given `struct`. /// - /// **Note** [`ink::Event`] requires [`scale::Encode`] implementation, - /// it is up to the user to provide that: usually via the derive and `#[ink::signature_topic]` macros. + /// **Note** [`ink::Event`] requires [`scale::Encode`] implementation. /// /// Usually this is used in conjunction with the [`EventMetadata`] derive. /// From c1168e7dec2d695184bd74dddc8c6ae07ffc3091 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Sat, 6 Jan 2024 21:23:59 +0000 Subject: [PATCH 16/20] fix UI tests --- crates/ink/macro/src/event/mod.rs | 13 +++++--- crates/ink/tests/compile_tests.rs | 32 +++++++++---------- .../ui/event/fail/multiple_topic_args.stderr | 4 +-- .../fail/multiple_topic_args_inline.stderr | 6 ++-- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/crates/ink/macro/src/event/mod.rs b/crates/ink/macro/src/event/mod.rs index fd9e8be2d0..22db7dc565 100644 --- a/crates/ink/macro/src/event/mod.rs +++ b/crates/ink/macro/src/event/mod.rs @@ -184,7 +184,7 @@ fn has_ink_topic_attribute(field: &synstructure::BindingInfo) -> syn::Result syn::Result { - let parsed_args = parse_arg_attr(attrs)?; + let parsed_args = parse_arg_attr(attrs, path)?; let Some(meta) = parsed_args else { return Ok(false); }; @@ -196,7 +196,10 @@ fn has_ink_attribute(attrs: &[syn::Attribute], path: &str) -> syn::Result /// # Errors /// - Multiple arguments are specified /// - Multiple `ink` attribute present -fn parse_arg_attr(attrs: &[syn::Attribute]) -> syn::Result> { +fn parse_arg_attr( + attrs: &[syn::Attribute], + path: &str, +) -> syn::Result> { let ink_attrs = attrs .iter() .filter_map(|attr| { @@ -208,7 +211,7 @@ fn parse_arg_attr(attrs: &[syn::Attribute]) -> syn::Result> { if nested.len() > 1 { Err(syn::Error::new( nested[1].span(), - "Only a single argument is allowed".to_string(), + format!("Invalid `#[ink({})]` attribute: multiple arguments not allowed", path), )) } else { Ok(nested @@ -230,7 +233,7 @@ fn parse_arg_attr(attrs: &[syn::Attribute]) -> syn::Result> { if ink_attrs.len() > 1 { Err(syn::Error::new( ink_attrs[1].span(), - "Only a single custom ink attribute is allowed.".to_string(), + format!("Only a single `#[ink({})]` attribute allowed.", path), )) } else { Ok(ink_attrs.first().cloned()) @@ -246,7 +249,7 @@ fn parse_arg_attr(attrs: &[syn::Attribute]) -> syn::Result> { fn parse_signature_arg( attrs: &[syn::Attribute], ) -> syn::Result> { - let Some(meta) = parse_arg_attr(attrs)? else { + let Some(meta) = parse_arg_attr(attrs, "signature_topic")? else { return Ok(None); }; if let syn::Meta::NameValue(nv) = &meta { diff --git a/crates/ink/tests/compile_tests.rs b/crates/ink/tests/compile_tests.rs index 4370f36b07..e9cdff6bd9 100644 --- a/crates/ink/tests/compile_tests.rs +++ b/crates/ink/tests/compile_tests.rs @@ -16,31 +16,31 @@ fn ui_tests() { let t = trybuild::TestCases::new(); - t.pass("tests/ui/blake2b/pass/*.rs"); - t.compile_fail("tests/ui/blake2b/fail/*.rs"); + // t.pass("tests/ui/blake2b/pass/*.rs"); + // t.compile_fail("tests/ui/blake2b/fail/*.rs"); - t.pass("tests/ui/selector_id/pass/*.rs"); - t.compile_fail("tests/ui/selector_id/fail/*.rs"); + // t.pass("tests/ui/selector_id/pass/*.rs"); + // t.compile_fail("tests/ui/selector_id/fail/*.rs"); - t.pass("tests/ui/selector_bytes/pass/*.rs"); - t.compile_fail("tests/ui/selector_bytes/fail/*.rs"); + // t.pass("tests/ui/selector_bytes/pass/*.rs"); + // t.compile_fail("tests/ui/selector_bytes/fail/*.rs"); - t.pass("tests/ui/contract/pass/*.rs"); - t.compile_fail("tests/ui/contract/fail/*.rs"); + // t.pass("tests/ui/contract/pass/*.rs"); + // t.compile_fail("tests/ui/contract/fail/*.rs"); t.pass("tests/ui/event/pass/*.rs"); t.compile_fail("tests/ui/event/fail/*.rs"); - t.pass("tests/ui/storage_item/pass/*.rs"); - t.compile_fail("tests/ui/storage_item/fail/*.rs"); + // t.pass("tests/ui/storage_item/pass/*.rs"); + // t.compile_fail("tests/ui/storage_item/fail/*.rs"); - t.pass("tests/ui/trait_def/pass/*.rs"); - t.compile_fail("tests/ui/trait_def/fail/*.rs"); + // t.pass("tests/ui/trait_def/pass/*.rs"); + // t.compile_fail("tests/ui/trait_def/fail/*.rs"); - t.pass("tests/ui/chain_extension/E-01-simple.rs"); + // t.pass("tests/ui/chain_extension/E-01-simple.rs"); - t.pass("tests/ui/pay_with_call/pass/multiple_args.rs"); + // t.pass("tests/ui/pay_with_call/pass/multiple_args.rs"); - t.pass("tests/ui/scale_derive/pass/*.rs"); - t.compile_fail("tests/ui/scale_derive/fail/*.rs"); + // t.pass("tests/ui/scale_derive/pass/*.rs"); + // t.compile_fail("tests/ui/scale_derive/fail/*.rs"); } diff --git a/crates/ink/tests/ui/event/fail/multiple_topic_args.stderr b/crates/ink/tests/ui/event/fail/multiple_topic_args.stderr index da54f02997..5efa379e32 100644 --- a/crates/ink/tests/ui/event/fail/multiple_topic_args.stderr +++ b/crates/ink/tests/ui/event/fail/multiple_topic_args.stderr @@ -1,5 +1,5 @@ error: Only a single `#[ink(topic)]` attribute allowed. - --> tests/ui/event/fail/multiple_topic_args.rs:4:5 + --> tests/ui/event/fail/multiple_topic_args.rs:4:11 | 4 | #[ink(topic)] - | ^^^^^^^^^^^^^ + | ^^^^^ diff --git a/crates/ink/tests/ui/event/fail/multiple_topic_args_inline.stderr b/crates/ink/tests/ui/event/fail/multiple_topic_args_inline.stderr index 92e8159645..fd362ff6fd 100644 --- a/crates/ink/tests/ui/event/fail/multiple_topic_args_inline.stderr +++ b/crates/ink/tests/ui/event/fail/multiple_topic_args_inline.stderr @@ -1,5 +1,5 @@ -error: Invalid `#[ink(topic)]` attribute: multiple arguments not allowed. - --> tests/ui/event/fail/multiple_topic_args_inline.rs:3:11 +error: Invalid `#[ink(topic)]` attribute: multiple arguments not allowed + --> tests/ui/event/fail/multiple_topic_args_inline.rs:3:18 | 3 | #[ink(topic, topic)] - | ^^^^^ + | ^^^^^ From 764443c52e930058093b570f71f9541b06936897 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Sat, 6 Jan 2024 21:35:20 +0000 Subject: [PATCH 17/20] add UI tests --- crates/ink/tests/compile_tests.rs | 32 +++++++++---------- .../ui/event/fail/conficting_attributes.rs | 8 +++++ .../event/fail/conficting_attributes.stderr | 13 ++++++++ .../fail/conficting_attributes_inline.rs | 7 ++++ .../fail/conficting_attributes_inline.stderr | 13 ++++++++ .../ui/event/pass/custom_signature_works.rs | 3 +- .../pass/inline_custom_signature_works.rs | 8 +++++ 7 files changed, 67 insertions(+), 17 deletions(-) create mode 100644 crates/ink/tests/ui/event/fail/conficting_attributes.rs create mode 100644 crates/ink/tests/ui/event/fail/conficting_attributes.stderr create mode 100644 crates/ink/tests/ui/event/fail/conficting_attributes_inline.rs create mode 100644 crates/ink/tests/ui/event/fail/conficting_attributes_inline.stderr create mode 100644 crates/ink/tests/ui/event/pass/inline_custom_signature_works.rs diff --git a/crates/ink/tests/compile_tests.rs b/crates/ink/tests/compile_tests.rs index e9cdff6bd9..4370f36b07 100644 --- a/crates/ink/tests/compile_tests.rs +++ b/crates/ink/tests/compile_tests.rs @@ -16,31 +16,31 @@ fn ui_tests() { let t = trybuild::TestCases::new(); - // t.pass("tests/ui/blake2b/pass/*.rs"); - // t.compile_fail("tests/ui/blake2b/fail/*.rs"); + t.pass("tests/ui/blake2b/pass/*.rs"); + t.compile_fail("tests/ui/blake2b/fail/*.rs"); - // t.pass("tests/ui/selector_id/pass/*.rs"); - // t.compile_fail("tests/ui/selector_id/fail/*.rs"); + t.pass("tests/ui/selector_id/pass/*.rs"); + t.compile_fail("tests/ui/selector_id/fail/*.rs"); - // t.pass("tests/ui/selector_bytes/pass/*.rs"); - // t.compile_fail("tests/ui/selector_bytes/fail/*.rs"); + t.pass("tests/ui/selector_bytes/pass/*.rs"); + t.compile_fail("tests/ui/selector_bytes/fail/*.rs"); - // t.pass("tests/ui/contract/pass/*.rs"); - // t.compile_fail("tests/ui/contract/fail/*.rs"); + t.pass("tests/ui/contract/pass/*.rs"); + t.compile_fail("tests/ui/contract/fail/*.rs"); t.pass("tests/ui/event/pass/*.rs"); t.compile_fail("tests/ui/event/fail/*.rs"); - // t.pass("tests/ui/storage_item/pass/*.rs"); - // t.compile_fail("tests/ui/storage_item/fail/*.rs"); + t.pass("tests/ui/storage_item/pass/*.rs"); + t.compile_fail("tests/ui/storage_item/fail/*.rs"); - // t.pass("tests/ui/trait_def/pass/*.rs"); - // t.compile_fail("tests/ui/trait_def/fail/*.rs"); + t.pass("tests/ui/trait_def/pass/*.rs"); + t.compile_fail("tests/ui/trait_def/fail/*.rs"); - // t.pass("tests/ui/chain_extension/E-01-simple.rs"); + t.pass("tests/ui/chain_extension/E-01-simple.rs"); - // t.pass("tests/ui/pay_with_call/pass/multiple_args.rs"); + t.pass("tests/ui/pay_with_call/pass/multiple_args.rs"); - // t.pass("tests/ui/scale_derive/pass/*.rs"); - // t.compile_fail("tests/ui/scale_derive/fail/*.rs"); + t.pass("tests/ui/scale_derive/pass/*.rs"); + t.compile_fail("tests/ui/scale_derive/fail/*.rs"); } diff --git a/crates/ink/tests/ui/event/fail/conficting_attributes.rs b/crates/ink/tests/ui/event/fail/conficting_attributes.rs new file mode 100644 index 0000000000..7fcd26e160 --- /dev/null +++ b/crates/ink/tests/ui/event/fail/conficting_attributes.rs @@ -0,0 +1,8 @@ +#[ink::event] +#[ink(anonymous)] +#[ink(signature_topic = "1111111111111111111111111111111111111111111111111111111111111111")] +pub struct Event { + pub topic: [u8; 32], +} + +fn main() {} diff --git a/crates/ink/tests/ui/event/fail/conficting_attributes.stderr b/crates/ink/tests/ui/event/fail/conficting_attributes.stderr new file mode 100644 index 0000000000..d5cc4abf85 --- /dev/null +++ b/crates/ink/tests/ui/event/fail/conficting_attributes.stderr @@ -0,0 +1,13 @@ +error: Only a single `#[ink(anonymous)]` attribute allowed. + --> tests/ui/event/fail/conficting_attributes.rs:3:7 + | +3 | #[ink(signature_topic = "1111111111111111111111111111111111111111111111111111111111111111")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0277]: the trait bound `Event: ink::ink_env::Event` is not satisfied + --> tests/ui/event/fail/conficting_attributes.rs:1:1 + | +1 | #[ink::event] + | ^^^^^^^^^^^^^ the trait `ink::ink_env::Event` is not implemented for `Event` + | + = note: this error originates in the attribute macro `ink::event` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/ink/tests/ui/event/fail/conficting_attributes_inline.rs b/crates/ink/tests/ui/event/fail/conficting_attributes_inline.rs new file mode 100644 index 0000000000..d9079a752d --- /dev/null +++ b/crates/ink/tests/ui/event/fail/conficting_attributes_inline.rs @@ -0,0 +1,7 @@ +#[ink::event] +#[ink(anonymous, signature_topic = "1111111111111111111111111111111111111111111111111111111111111111")] +pub struct Event { + pub topic: [u8; 32], +} + +fn main() {} diff --git a/crates/ink/tests/ui/event/fail/conficting_attributes_inline.stderr b/crates/ink/tests/ui/event/fail/conficting_attributes_inline.stderr new file mode 100644 index 0000000000..6bd82dd609 --- /dev/null +++ b/crates/ink/tests/ui/event/fail/conficting_attributes_inline.stderr @@ -0,0 +1,13 @@ +error: Invalid `#[ink(anonymous)]` attribute: multiple arguments not allowed + --> tests/ui/event/fail/conficting_attributes_inline.rs:2:18 + | +2 | #[ink(anonymous, signature_topic = "1111111111111111111111111111111111111111111111111111111111111111")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0277]: the trait bound `Event: ink::ink_env::Event` is not satisfied + --> tests/ui/event/fail/conficting_attributes_inline.rs:1:1 + | +1 | #[ink::event] + | ^^^^^^^^^^^^^ the trait `ink::ink_env::Event` is not implemented for `Event` + | + = note: this error originates in the attribute macro `ink::event` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/ink/tests/ui/event/pass/custom_signature_works.rs b/crates/ink/tests/ui/event/pass/custom_signature_works.rs index d47711a6ec..ba9275c8d1 100644 --- a/crates/ink/tests/ui/event/pass/custom_signature_works.rs +++ b/crates/ink/tests/ui/event/pass/custom_signature_works.rs @@ -1,4 +1,5 @@ -#[ink::event(signature_topic = "1111111111111111111111111111111111111111111111111111111111111111")] +#[ink::event] +#[ink(signature_topic = "1111111111111111111111111111111111111111111111111111111111111111")] pub struct Event { #[ink(topic)] pub topic: [u8; 32], diff --git a/crates/ink/tests/ui/event/pass/inline_custom_signature_works.rs b/crates/ink/tests/ui/event/pass/inline_custom_signature_works.rs new file mode 100644 index 0000000000..d47711a6ec --- /dev/null +++ b/crates/ink/tests/ui/event/pass/inline_custom_signature_works.rs @@ -0,0 +1,8 @@ +#[ink::event(signature_topic = "1111111111111111111111111111111111111111111111111111111111111111")] +pub struct Event { + #[ink(topic)] + pub topic: [u8; 32], + pub field_1: u32, +} + +fn main() {} From 67ed0651bcec9ddf450abb102298e0d72535b9bd Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Tue, 9 Jan 2024 23:47:32 +0000 Subject: [PATCH 18/20] extra UI tests and improved error reporting --- crates/ink/codegen/src/generator/event.rs | 2 +- crates/ink/ir/src/ir/attrs.rs | 6 +- crates/ink/ir/src/ir/event/config.rs | 6 +- crates/ink/ir/src/ir/event/mod.rs | 2 +- crates/ink/ir/src/ir/event/signature_topic.rs | 4 +- crates/ink/macro/src/event/mod.rs | 191 ++++++++++++------ crates/ink/macro/src/lib.rs | 4 +- crates/ink/tests/compile_tests.rs | 32 +-- .../event/fail/conficting_attributes.stderr | 2 +- .../fail/conficting_attributes_inline.stderr | 2 +- .../ui/event/fail/multiple_topic_args.stderr | 2 +- .../fail/multiple_topic_args_inline.stderr | 2 +- .../ui/event/fail/signature_hex_short.rs | 7 + .../ui/event/fail/signature_hex_short.stderr | 13 ++ .../tests/ui/event/fail/unknown_attribute.rs | 8 + .../ui/event/fail/unknown_attribute.stderr | 13 ++ 16 files changed, 204 insertions(+), 92 deletions(-) create mode 100644 crates/ink/tests/ui/event/fail/signature_hex_short.rs create mode 100644 crates/ink/tests/ui/event/fail/signature_hex_short.stderr create mode 100644 crates/ink/tests/ui/event/fail/unknown_attribute.rs create mode 100644 crates/ink/tests/ui/event/fail/unknown_attribute.stderr diff --git a/crates/ink/codegen/src/generator/event.rs b/crates/ink/codegen/src/generator/event.rs index 8dbcd265c2..873f2c66ec 100644 --- a/crates/ink/codegen/src/generator/event.rs +++ b/crates/ink/codegen/src/generator/event.rs @@ -35,7 +35,7 @@ impl GenerateCode for Event<'_> { let signature_topic = self .item .signature_topic_hash() - .map(|hash| quote::quote! { #[ink(signature_topic = #hash)] }); + .map(|hex_s| quote::quote! { #[ink(signature_topic = #hex_s)] }); let cfg_attrs = self.item.get_cfg_attrs(item.span()); quote::quote! ( diff --git a/crates/ink/ir/src/ir/attrs.rs b/crates/ink/ir/src/ir/attrs.rs index b89713514b..7d55834bc6 100644 --- a/crates/ink/ir/src/ir/attrs.rs +++ b/crates/ink/ir/src/ir/attrs.rs @@ -373,7 +373,8 @@ pub enum AttributeArgKind { /// `#[ink(selector = _)]` /// `#[ink(selector = 0xDEADBEEF)]` Selector, - /// `#[ink(signature_topic = "DEADBEEF")]` + /// `#[ink(signature_topic = + /// "325c98ff66bd0d9d1c10789ae1f2a17bdfb2dcf6aa3d8092669afafdef1cb72d")]` SignatureTopicArg, /// `#[ink(function = N: u16)]` Function, @@ -430,7 +431,8 @@ pub enum AttributeArg { /// - `#[ink(selector = _)]` Applied on ink! messages to define a fallback messages /// that is invoked if no other ink! message matches a given selector. Selector(SelectorOrWildcard), - /// `#[ink(signature_topic = "DEADBEEF")]` + /// `#[ink(signature_topic = + /// "325c98ff66bd0d9d1c10789ae1f2a17bdfb2dcf6aa3d8092669afafdef1cb72d")]` SignatureTopic(String), /// `#[ink(namespace = "my_namespace")]` /// diff --git a/crates/ink/ir/src/ir/event/config.rs b/crates/ink/ir/src/ir/event/config.rs index c0f133675b..527f5ae9fc 100644 --- a/crates/ink/ir/src/ir/event/config.rs +++ b/crates/ink/ir/src/ir/event/config.rs @@ -70,7 +70,7 @@ impl TryFrom for EventConfig { } else { return Err(format_err_spanned!( arg, - "encountered unknown or unsupported ink! storage item configuration argument", + "encountered unknown or unsupported ink! event item configuration argument", )); } } @@ -97,7 +97,7 @@ impl EventConfig { } /// Returns the manually specified signature topic. - pub fn signature_topic_hash(&self) -> Option { - self.signature_topic_hash.clone() + pub fn signature_topic_hash(&self) -> Option<&str> { + self.signature_topic_hash.as_deref() } } diff --git a/crates/ink/ir/src/ir/event/mod.rs b/crates/ink/ir/src/ir/event/mod.rs index a34bb897b6..0cc465e955 100644 --- a/crates/ink/ir/src/ir/event/mod.rs +++ b/crates/ink/ir/src/ir/event/mod.rs @@ -103,7 +103,7 @@ impl Event { /// # Note /// /// Conflicts with `anonymous` - pub fn signature_topic_hash(&self) -> Option { + pub fn signature_topic_hash(&self) -> Option<&str> { self.config.signature_topic_hash() } diff --git a/crates/ink/ir/src/ir/event/signature_topic.rs b/crates/ink/ir/src/ir/event/signature_topic.rs index aac02357c4..4e4aa4d46f 100644 --- a/crates/ink/ir/src/ir/event/signature_topic.rs +++ b/crates/ink/ir/src/ir/event/signature_topic.rs @@ -53,7 +53,7 @@ impl TryFrom<&syn::Lit> for SignatureTopicArg { .map_err(|e: Vec| { format_err_spanned!( lit, - "`signature_topic` is expected to be 32-byte hash. Found {} bytes", + "`signature_topic` is expected to be 32-byte hex string. Found {} bytes", e.len() ) })?; @@ -100,7 +100,7 @@ impl TryFrom for Option { } else { return Err(format_err_spanned!( arg, - "encountered unknown or unsupported ink! storage item configuration argument", + "encountered unknown or unsupported ink! event item configuration argument", )); } } diff --git a/crates/ink/macro/src/event/mod.rs b/crates/ink/macro/src/event/mod.rs index 22db7dc565..5f438fb553 100644 --- a/crates/ink/macro/src/event/mod.rs +++ b/crates/ink/macro/src/event/mod.rs @@ -14,7 +14,11 @@ mod metadata; -use ink_ir::SignatureTopicArg; +use ink_ir::{ + format_err_spanned, + utils::duplicate_config_err, + SignatureTopicArg, +}; pub use metadata::event_metadata_derive; use ink_codegen::generate_code; @@ -29,6 +33,81 @@ use syn::{ Token, }; +/// Event item configurations specified by nested `ink` attributes. +struct EventConfig { + /// Event is anonymous. + pub anonymous: bool, + /// Event has a specified signature topic. + pub signature_topic: Option, +} + +impl EventConfig { + pub fn new(anonymous: bool, signature_topic: Option) -> Self { + EventConfig { + anonymous, + signature_topic, + } + } +} + +impl TryFrom<&[syn::Meta]> for EventConfig { + type Error = syn::Error; + + fn try_from(args: &[syn::Meta]) -> Result { + let mut anonymous: Option<&syn::Meta> = None; + let mut signature_topic: Option<&syn::Meta> = None; + for arg in args.iter() { + if arg.path().is_ident("anonymous") { + if let Some(a_meta) = anonymous { + return Err(duplicate_config_err(a_meta, arg, "anonymous", "event")); + } + match arg { + syn::Meta::Path(_) => anonymous = Some(arg), + _ => { + return Err(format_err_spanned!( + arg, + "`#[ink(anonymous)]` takes no arguments", + )); + } + } + } else if arg.path().is_ident("signature_topic") { + if anonymous.is_some() { + return Err(format_err_spanned!( + arg, + "cannot specify `signature_topic` with `anonymous` in ink! event item configuration argument", + )); + } + + if let Some(lit_str) = signature_topic { + return Err(duplicate_config_err(lit_str, arg, "anonymous", "event")); + } + match arg { + syn::Meta::NameValue(_) => signature_topic = Some(arg), + _ => { + return Err(format_err_spanned!( + arg, + "expected a name-value pair", + )); + } + } + } else { + return Err(format_err_spanned!( + arg, + "encountered unknown or unsupported ink! event item configuration argument", + )); + } + } + + let signature_topic = if let Some(meta) = signature_topic { + Some(parse_signature_arg(meta.clone())?) + } else { + None + }; + + Ok(EventConfig::new(anonymous.is_some(), signature_topic)) + } +} + /// Generate code from the `#[ink::event]` attribute. This expands to the required /// derive macros to satisfy an event implementation. pub fn generate(config: TokenStream2, input: TokenStream2) -> TokenStream2 { @@ -68,7 +147,9 @@ fn event_derive_struct(mut s: synstructure::Structure) -> syn::Result = None; @@ -111,7 +192,7 @@ fn event_derive_struct(mut s: synstructure::Structure) -> syn::Result ::core::option::Option::Some([ #(#bytes),* ])) } else { @@ -178,66 +259,56 @@ fn has_ink_topic_attribute(field: &synstructure::BindingInfo) -> syn::Result syn::Result { - let parsed_args = parse_arg_attr(attrs, path)?; - let Some(meta) = parsed_args else { - return Ok(false); - }; - Ok(meta.path().is_ident(path)) +fn has_ink_attribute(ink_attrs: &[syn::Meta], path: &str) -> syn::Result { + let mut present = false; + for a in ink_attrs { + if a.path().is_ident(path) && !present { + present = true; + } else if a.path().is_ident(path) { + return Err(syn::Error::new( + a.span(), + format!("Only a single `#[ink({})]` is allowed", path), + )); + } else { + return Err(syn::Error::new( + a.span(), + "Unknown ink! attribute at this position".to_string(), + )); + } + } + Ok(present) } -/// Parses custom `ink` attribute with the arbitrary argument. +/// Parses custom `ink` attributes with the arbitrary arguments. /// /// # Errors -/// - Multiple arguments are specified -/// - Multiple `ink` attribute present -fn parse_arg_attr( - attrs: &[syn::Attribute], - path: &str, -) -> syn::Result> { - let ink_attrs = attrs - .iter() - .filter_map(|attr| { - if attr.path().is_ident("ink") { - let parse_result = || -> Result { - let nested = attr.parse_args_with( - Punctuated::::parse_separated_nonempty, - )?; - if nested.len() > 1 { - Err(syn::Error::new( - nested[1].span(), - format!("Invalid `#[ink({})]` attribute: multiple arguments not allowed", path), - )) - } else { - Ok(nested - .first() - .ok_or(syn::Error::new( - nested[0].span(), - "Expected to have an argument".to_string(), - ))? - .clone()) - } - }; - Some(parse_result()) - } else { - None - } - }) - .collect::>>()?; +/// - Attribute has no argument (i.e. `#[ink()]`) +fn parse_arg_attrs(attrs: &[syn::Attribute]) -> syn::Result> { + let mut ink_attrs = Vec::new(); + for a in attrs { + if !a.path().is_ident("ink") { + continue; + } - if ink_attrs.len() > 1 { - Err(syn::Error::new( - ink_attrs[1].span(), - format!("Only a single `#[ink({})]` attribute allowed.", path), - )) - } else { - Ok(ink_attrs.first().cloned()) + let nested = a.parse_args_with( + Punctuated::::parse_separated_nonempty, + )?; + if nested.is_empty() { + return Err(syn::Error::new( + a.span(), + "Expected to have an argument".to_string(), + )); + } + ink_attrs.extend(nested.into_iter()) } + + Ok(ink_attrs) } /// Parses signature topic from the list of attributes. @@ -246,16 +317,14 @@ fn parse_arg_attr( /// - Name-value pair is not specified correctly. /// - Provided value is of wrong format. /// - Provided hash string is of wrong length. -fn parse_signature_arg( - attrs: &[syn::Attribute], -) -> syn::Result> { - let Some(meta) = parse_arg_attr(attrs, "signature_topic")? else { - return Ok(None); - }; +fn parse_signature_arg(meta: syn::Meta) -> syn::Result { if let syn::Meta::NameValue(nv) = &meta { - Ok(Some(SignatureTopicArg::try_from(nv)?)) + Ok(SignatureTopicArg::try_from(nv)?) } else { - Ok(None) + Err(syn::Error::new( + meta.span(), + "Expected to have an argument".to_string(), + )) } } diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index 503ad92510..e1738c6e83 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -680,7 +680,7 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// #[ink(topic)] /// pub topic: [u8; 32], /// } -/// // Setting `signature_topic = ` specifies custom signature topic. +/// // Setting `signature_topic = ` specifies custom signature topic. /// #[ink::event( /// signature_topic = "1111111111111111111111111111111111111111111111111111111111111111" /// )] @@ -1419,7 +1419,7 @@ synstructure::decl_derive!( /// For example, when the event definition from the other contract is not accessible. /// /// The macro provides `#[ink(signature_topic = _)]` nested macro that allows to provide - /// 32 byte hash string of the custom signature topic. + /// 32 byte hex string of the custom signature topic. /// /// Generates custom signature topic /// ``` diff --git a/crates/ink/tests/compile_tests.rs b/crates/ink/tests/compile_tests.rs index 4370f36b07..e9cdff6bd9 100644 --- a/crates/ink/tests/compile_tests.rs +++ b/crates/ink/tests/compile_tests.rs @@ -16,31 +16,31 @@ fn ui_tests() { let t = trybuild::TestCases::new(); - t.pass("tests/ui/blake2b/pass/*.rs"); - t.compile_fail("tests/ui/blake2b/fail/*.rs"); + // t.pass("tests/ui/blake2b/pass/*.rs"); + // t.compile_fail("tests/ui/blake2b/fail/*.rs"); - t.pass("tests/ui/selector_id/pass/*.rs"); - t.compile_fail("tests/ui/selector_id/fail/*.rs"); + // t.pass("tests/ui/selector_id/pass/*.rs"); + // t.compile_fail("tests/ui/selector_id/fail/*.rs"); - t.pass("tests/ui/selector_bytes/pass/*.rs"); - t.compile_fail("tests/ui/selector_bytes/fail/*.rs"); + // t.pass("tests/ui/selector_bytes/pass/*.rs"); + // t.compile_fail("tests/ui/selector_bytes/fail/*.rs"); - t.pass("tests/ui/contract/pass/*.rs"); - t.compile_fail("tests/ui/contract/fail/*.rs"); + // t.pass("tests/ui/contract/pass/*.rs"); + // t.compile_fail("tests/ui/contract/fail/*.rs"); t.pass("tests/ui/event/pass/*.rs"); t.compile_fail("tests/ui/event/fail/*.rs"); - t.pass("tests/ui/storage_item/pass/*.rs"); - t.compile_fail("tests/ui/storage_item/fail/*.rs"); + // t.pass("tests/ui/storage_item/pass/*.rs"); + // t.compile_fail("tests/ui/storage_item/fail/*.rs"); - t.pass("tests/ui/trait_def/pass/*.rs"); - t.compile_fail("tests/ui/trait_def/fail/*.rs"); + // t.pass("tests/ui/trait_def/pass/*.rs"); + // t.compile_fail("tests/ui/trait_def/fail/*.rs"); - t.pass("tests/ui/chain_extension/E-01-simple.rs"); + // t.pass("tests/ui/chain_extension/E-01-simple.rs"); - t.pass("tests/ui/pay_with_call/pass/multiple_args.rs"); + // t.pass("tests/ui/pay_with_call/pass/multiple_args.rs"); - t.pass("tests/ui/scale_derive/pass/*.rs"); - t.compile_fail("tests/ui/scale_derive/fail/*.rs"); + // t.pass("tests/ui/scale_derive/pass/*.rs"); + // t.compile_fail("tests/ui/scale_derive/fail/*.rs"); } diff --git a/crates/ink/tests/ui/event/fail/conficting_attributes.stderr b/crates/ink/tests/ui/event/fail/conficting_attributes.stderr index d5cc4abf85..bd36889165 100644 --- a/crates/ink/tests/ui/event/fail/conficting_attributes.stderr +++ b/crates/ink/tests/ui/event/fail/conficting_attributes.stderr @@ -1,4 +1,4 @@ -error: Only a single `#[ink(anonymous)]` attribute allowed. +error: cannot specify `signature_topic` with `anonymous` in ink! event item configuration argument --> tests/ui/event/fail/conficting_attributes.rs:3:7 | 3 | #[ink(signature_topic = "1111111111111111111111111111111111111111111111111111111111111111")] diff --git a/crates/ink/tests/ui/event/fail/conficting_attributes_inline.stderr b/crates/ink/tests/ui/event/fail/conficting_attributes_inline.stderr index 6bd82dd609..fb222f2865 100644 --- a/crates/ink/tests/ui/event/fail/conficting_attributes_inline.stderr +++ b/crates/ink/tests/ui/event/fail/conficting_attributes_inline.stderr @@ -1,4 +1,4 @@ -error: Invalid `#[ink(anonymous)]` attribute: multiple arguments not allowed +error: cannot specify `signature_topic` with `anonymous` in ink! event item configuration argument --> tests/ui/event/fail/conficting_attributes_inline.rs:2:18 | 2 | #[ink(anonymous, signature_topic = "1111111111111111111111111111111111111111111111111111111111111111")] diff --git a/crates/ink/tests/ui/event/fail/multiple_topic_args.stderr b/crates/ink/tests/ui/event/fail/multiple_topic_args.stderr index 5efa379e32..919dd2ed2b 100644 --- a/crates/ink/tests/ui/event/fail/multiple_topic_args.stderr +++ b/crates/ink/tests/ui/event/fail/multiple_topic_args.stderr @@ -1,4 +1,4 @@ -error: Only a single `#[ink(topic)]` attribute allowed. +error: Only a single `#[ink(topic)]` is allowed --> tests/ui/event/fail/multiple_topic_args.rs:4:11 | 4 | #[ink(topic)] diff --git a/crates/ink/tests/ui/event/fail/multiple_topic_args_inline.stderr b/crates/ink/tests/ui/event/fail/multiple_topic_args_inline.stderr index fd362ff6fd..bb0e6ee447 100644 --- a/crates/ink/tests/ui/event/fail/multiple_topic_args_inline.stderr +++ b/crates/ink/tests/ui/event/fail/multiple_topic_args_inline.stderr @@ -1,4 +1,4 @@ -error: Invalid `#[ink(topic)]` attribute: multiple arguments not allowed +error: Only a single `#[ink(topic)]` is allowed --> tests/ui/event/fail/multiple_topic_args_inline.rs:3:18 | 3 | #[ink(topic, topic)] diff --git a/crates/ink/tests/ui/event/fail/signature_hex_short.rs b/crates/ink/tests/ui/event/fail/signature_hex_short.rs new file mode 100644 index 0000000000..e1f47889bb --- /dev/null +++ b/crates/ink/tests/ui/event/fail/signature_hex_short.rs @@ -0,0 +1,7 @@ +#[ink::event] +#[ink(signature_topic = "1111111111111111111111111111")] +pub struct Event { + pub topic: [u8; 32], +} + +fn main() {} diff --git a/crates/ink/tests/ui/event/fail/signature_hex_short.stderr b/crates/ink/tests/ui/event/fail/signature_hex_short.stderr new file mode 100644 index 0000000000..d5e5e7e9b8 --- /dev/null +++ b/crates/ink/tests/ui/event/fail/signature_hex_short.stderr @@ -0,0 +1,13 @@ +error: `signature_topic` is expected to be 32-byte hex string. Found 14 bytes + --> tests/ui/event/fail/signature_hex_short.rs:2:25 + | +2 | #[ink(signature_topic = "1111111111111111111111111111")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0277]: the trait bound `Event: ink::ink_env::Event` is not satisfied + --> tests/ui/event/fail/signature_hex_short.rs:1:1 + | +1 | #[ink::event] + | ^^^^^^^^^^^^^ the trait `ink::ink_env::Event` is not implemented for `Event` + | + = note: this error originates in the attribute macro `ink::event` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/ink/tests/ui/event/fail/unknown_attribute.rs b/crates/ink/tests/ui/event/fail/unknown_attribute.rs new file mode 100644 index 0000000000..f4be10da4c --- /dev/null +++ b/crates/ink/tests/ui/event/fail/unknown_attribute.rs @@ -0,0 +1,8 @@ +#[ink::event] +#[ink(anonymous)] +#[ink(my_arg)] +pub struct Event { + pub topic: [u8; 32], +} + +fn main() {} diff --git a/crates/ink/tests/ui/event/fail/unknown_attribute.stderr b/crates/ink/tests/ui/event/fail/unknown_attribute.stderr new file mode 100644 index 0000000000..0cb23cee4e --- /dev/null +++ b/crates/ink/tests/ui/event/fail/unknown_attribute.stderr @@ -0,0 +1,13 @@ +error: encountered unknown or unsupported ink! event item configuration argument + --> tests/ui/event/fail/unknown_attribute.rs:3:7 + | +3 | #[ink(my_arg)] + | ^^^^^^ + +error[E0277]: the trait bound `Event: ink::ink_env::Event` is not satisfied + --> tests/ui/event/fail/unknown_attribute.rs:1:1 + | +1 | #[ink::event] + | ^^^^^^^^^^^^^ the trait `ink::ink_env::Event` is not implemented for `Event` + | + = note: this error originates in the attribute macro `ink::event` (in Nightly builds, run with -Z macro-backtrace for more info) From 07433cb451610b576dc8ec651ce64d03a58cc5ef Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Tue, 9 Jan 2024 23:47:56 +0000 Subject: [PATCH 19/20] uncomment tests --- crates/ink/tests/compile_tests.rs | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/ink/tests/compile_tests.rs b/crates/ink/tests/compile_tests.rs index e9cdff6bd9..4370f36b07 100644 --- a/crates/ink/tests/compile_tests.rs +++ b/crates/ink/tests/compile_tests.rs @@ -16,31 +16,31 @@ fn ui_tests() { let t = trybuild::TestCases::new(); - // t.pass("tests/ui/blake2b/pass/*.rs"); - // t.compile_fail("tests/ui/blake2b/fail/*.rs"); + t.pass("tests/ui/blake2b/pass/*.rs"); + t.compile_fail("tests/ui/blake2b/fail/*.rs"); - // t.pass("tests/ui/selector_id/pass/*.rs"); - // t.compile_fail("tests/ui/selector_id/fail/*.rs"); + t.pass("tests/ui/selector_id/pass/*.rs"); + t.compile_fail("tests/ui/selector_id/fail/*.rs"); - // t.pass("tests/ui/selector_bytes/pass/*.rs"); - // t.compile_fail("tests/ui/selector_bytes/fail/*.rs"); + t.pass("tests/ui/selector_bytes/pass/*.rs"); + t.compile_fail("tests/ui/selector_bytes/fail/*.rs"); - // t.pass("tests/ui/contract/pass/*.rs"); - // t.compile_fail("tests/ui/contract/fail/*.rs"); + t.pass("tests/ui/contract/pass/*.rs"); + t.compile_fail("tests/ui/contract/fail/*.rs"); t.pass("tests/ui/event/pass/*.rs"); t.compile_fail("tests/ui/event/fail/*.rs"); - // t.pass("tests/ui/storage_item/pass/*.rs"); - // t.compile_fail("tests/ui/storage_item/fail/*.rs"); + t.pass("tests/ui/storage_item/pass/*.rs"); + t.compile_fail("tests/ui/storage_item/fail/*.rs"); - // t.pass("tests/ui/trait_def/pass/*.rs"); - // t.compile_fail("tests/ui/trait_def/fail/*.rs"); + t.pass("tests/ui/trait_def/pass/*.rs"); + t.compile_fail("tests/ui/trait_def/fail/*.rs"); - // t.pass("tests/ui/chain_extension/E-01-simple.rs"); + t.pass("tests/ui/chain_extension/E-01-simple.rs"); - // t.pass("tests/ui/pay_with_call/pass/multiple_args.rs"); + t.pass("tests/ui/pay_with_call/pass/multiple_args.rs"); - // t.pass("tests/ui/scale_derive/pass/*.rs"); - // t.compile_fail("tests/ui/scale_derive/fail/*.rs"); + t.pass("tests/ui/scale_derive/pass/*.rs"); + t.compile_fail("tests/ui/scale_derive/fail/*.rs"); } From 7eba6a8efa055cd920010f8565e707dc3858ff99 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Thu, 11 Jan 2024 04:01:38 +0000 Subject: [PATCH 20/20] rename function --- crates/ink/codegen/src/generator/event.rs | 2 +- crates/ink/ir/src/ir/attrs.rs | 2 +- crates/ink/ir/src/ir/event/config.rs | 10 +++++----- crates/ink/ir/src/ir/event/mod.rs | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/ink/codegen/src/generator/event.rs b/crates/ink/codegen/src/generator/event.rs index 873f2c66ec..2289052957 100644 --- a/crates/ink/codegen/src/generator/event.rs +++ b/crates/ink/codegen/src/generator/event.rs @@ -34,7 +34,7 @@ impl GenerateCode for Event<'_> { .then(|| quote::quote! { #[ink(anonymous)] }); let signature_topic = self .item - .signature_topic_hash() + .signature_topic_hex() .map(|hex_s| quote::quote! { #[ink(signature_topic = #hex_s)] }); let cfg_attrs = self.item.get_cfg_attrs(item.span()); diff --git a/crates/ink/ir/src/ir/attrs.rs b/crates/ink/ir/src/ir/attrs.rs index 7d55834bc6..b21615cb2b 100644 --- a/crates/ink/ir/src/ir/attrs.rs +++ b/crates/ink/ir/src/ir/attrs.rs @@ -285,7 +285,7 @@ impl InkAttribute { } /// Returns the signature topic of the ink! attribute if any. - pub fn signature_topic_hash(&self) -> Option { + pub fn signature_topic_hex(&self) -> Option { self.args().find_map(|arg| { if let ir::AttributeArg::SignatureTopic(hash) = arg.kind() { return Some(hash.clone()); diff --git a/crates/ink/ir/src/ir/event/config.rs b/crates/ink/ir/src/ir/event/config.rs index 527f5ae9fc..71313c3653 100644 --- a/crates/ink/ir/src/ir/event/config.rs +++ b/crates/ink/ir/src/ir/event/config.rs @@ -26,7 +26,7 @@ pub struct EventConfig { anonymous: bool, /// Manually specified signature topic hash. - signature_topic_hash: Option, + signature_topic_hex: Option, } impl TryFrom for EventConfig { @@ -84,10 +84,10 @@ impl TryFrom for EventConfig { impl EventConfig { /// Construct a new [`EventConfig`]. - pub fn new(anonymous: bool, signature_topic_hash: Option) -> Self { + pub fn new(anonymous: bool, signature_topic_hex: Option) -> Self { Self { anonymous, - signature_topic_hash, + signature_topic_hex, } } @@ -97,7 +97,7 @@ impl EventConfig { } /// Returns the manually specified signature topic. - pub fn signature_topic_hash(&self) -> Option<&str> { - self.signature_topic_hash.as_deref() + pub fn signature_topic_hex(&self) -> Option<&str> { + self.signature_topic_hex.as_deref() } } diff --git a/crates/ink/ir/src/ir/event/mod.rs b/crates/ink/ir/src/ir/event/mod.rs index 0cc465e955..41c5d8988a 100644 --- a/crates/ink/ir/src/ir/event/mod.rs +++ b/crates/ink/ir/src/ir/event/mod.rs @@ -103,8 +103,8 @@ impl Event { /// # Note /// /// Conflicts with `anonymous` - pub fn signature_topic_hash(&self) -> Option<&str> { - self.config.signature_topic_hash() + pub fn signature_topic_hex(&self) -> Option<&str> { + self.config.signature_topic_hex() } /// Returns a list of `cfg` attributes if any. @@ -139,7 +139,7 @@ impl TryFrom for Event { } }, )?; - if ink_attrs.is_anonymous() && ink_attrs.signature_topic_hash().is_some() { + if ink_attrs.is_anonymous() && ink_attrs.signature_topic_hex().is_some() { return Err(format_err_spanned!( item_struct, "cannot use use `anonymous` with `signature_topic`", @@ -152,7 +152,7 @@ impl TryFrom for Event { }, config: EventConfig::new( ink_attrs.is_anonymous(), - ink_attrs.signature_topic_hash(), + ink_attrs.signature_topic_hex(), ), }) }