Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom signature topic #2031

Merged
merged 22 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
2 changes: 1 addition & 1 deletion crates/engine/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
6 changes: 4 additions & 2 deletions crates/env/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,11 @@ pub trait Event: scale::Encode {

/// 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: Option<[u8; 32]>;
/// 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<E, B>(
Expand Down
13 changes: 11 additions & 2 deletions crates/ink/codegen/src/generator/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,36 @@
use crate::GenerateCode;
use derive_more::From;
use proc_macro2::TokenStream as TokenStream2;
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.
item: &'a ir::Event,
}

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
.item
.anonymous()
.then(|| quote::quote! { #[ink(anonymous)] });
let signature_topic = self
.item
.signature_topic_hash()
.map(|hash| quote::quote! { #[ink(signature_topic = #hash)] });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hash again here

let cfg_attrs = self.item.get_cfg_attrs(item.span());

quote::quote! (
#( #cfg_attrs )*
#[cfg_attr(feature = "std", derive(::ink::EventMetadata))]
#[derive(::ink::Event)]
#[::ink::scale_derive(Encode, Decode)]
#anonymous
#signature_topic
#item
)
}
Expand Down
1 change: 1 addition & 0 deletions crates/ink/ir/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ proc-macro2 = { workspace = true }
itertools = { workspace = true }
either = { workspace = true }
blake2 = { workspace = true }
impl-serde = { workspace = true }
ink_prelude = { workspace = true }

[features]
Expand Down
69 changes: 55 additions & 14 deletions crates/ink/ir/src/ir/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ impl IsDocAttribute for syn::Attribute {

fn extract_docs(&self) -> Option<String> {
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());
}
}
}
Expand Down Expand Up @@ -172,7 +172,7 @@ impl InkAttribute {
return Err(format_err!(
self.span(),
"unexpected first ink! attribute argument",
))
));
}
Ok(())
}
Expand Down Expand Up @@ -200,7 +200,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!(
Expand All @@ -210,7 +210,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());
Expand Down Expand Up @@ -242,7 +242,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 })
Expand All @@ -268,7 +268,7 @@ impl InkAttribute {
pub fn namespace(&self) -> Option<ir::Namespace> {
self.args().find_map(|arg| {
if let ir::AttributeArg::Namespace(namespace) = arg.kind() {
return Some(namespace.clone())
return Some(namespace.clone());
}
None
})
Expand All @@ -278,7 +278,17 @@ impl InkAttribute {
pub fn selector(&self) -> Option<SelectorOrWildcard> {
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_hash(&self) -> Option<String> {
SkymanOne marked this conversation as resolved.
Show resolved Hide resolved
self.args().find_map(|arg| {
if let ir::AttributeArg::SignatureTopic(hash) = arg.kind() {
return Some(hash.clone());
}
None
})
Expand Down Expand Up @@ -363,6 +373,8 @@ pub enum AttributeArgKind {
/// `#[ink(selector = _)]`
/// `#[ink(selector = 0xDEADBEEF)]`
Selector,
/// `#[ink(signature_topic = "DEADBEEF")]`
ascjones marked this conversation as resolved.
Show resolved Hide resolved
SignatureTopicArg,
/// `#[ink(function = N: u16)]`
Function,
/// `#[ink(namespace = "my_namespace")]`
Expand Down Expand Up @@ -418,6 +430,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")]`
ascjones marked this conversation as resolved.
Show resolved Hide resolved
SignatureTopic(String),
/// `#[ink(namespace = "my_namespace")]`
///
/// Applied on ink! trait implementation blocks to disambiguate other trait
Expand Down Expand Up @@ -462,6 +476,9 @@ impl core::fmt::Display for AttributeArgKind {
Self::Selector => {
write!(f, "selector = S:[u8; 4] || _")
}
Self::SignatureTopicArg => {
write!(f, "signature_topic = S:[u8; 32]")
}
Self::Function => {
write!(f, "function = N:u16)")
}
Expand All @@ -486,6 +503,7 @@ impl AttributeArg {
Self::Constructor => AttributeArgKind::Constructor,
Self::Payable => AttributeArgKind::Payable,
Self::Selector(_) => AttributeArgKind::Selector,
Self::SignatureTopic(_) => AttributeArgKind::SignatureTopicArg,
Self::Function(_) => AttributeArgKind::Function,
Self::Namespace(_) => AttributeArgKind::Namespace,
Self::Implementation => AttributeArgKind::Implementation,
Expand All @@ -505,6 +523,9 @@ 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(hash) => {
write!(f, "signature_topic = {:?}", hash)
}
Self::Function(function) => {
write!(f, "function = {:?}", function.into_u16())
}
Expand Down Expand Up @@ -778,7 +799,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",))
Expand Down Expand Up @@ -807,7 +828,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);
}
Expand All @@ -820,7 +841,7 @@ impl TryFrom<syn::Attribute> for Attribute {

fn try_from(attr: syn::Attribute) -> Result<Self, Self::Error> {
if attr.path().is_ident("ink") {
return <InkAttribute as TryFrom<_>>::try_from(attr).map(Into::into)
return <InkAttribute as TryFrom<_>>::try_from(attr).map(Into::into);
}
Ok(Attribute::Other(attr))
}
Expand All @@ -837,7 +858,7 @@ impl TryFrom<syn::Attribute> for InkAttribute {

fn try_from(attr: syn::Attribute) -> Result<Self, Self::Error> {
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
Expand All @@ -850,7 +871,7 @@ impl TryFrom<syn::Attribute> for InkAttribute {
return Err(format_err_spanned!(
attr,
"encountered unsupported empty ink! attribute"
))
));
}
Ok(InkAttribute { args })
}
Expand Down Expand Up @@ -898,7 +919,7 @@ impl InkAttribute {
}
}
if let Some(err) = err {
return Err(err)
return Err(err);
}
Ok(())
}
Expand All @@ -925,6 +946,16 @@ impl Parse for AttributeFrag {
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::<u16>()
Expand Down Expand Up @@ -1508,4 +1539,14 @@ mod tests {
Err("encountered duplicate ink! attribute"),
)
}
#[test]
fn signature_topic_works() {
let s = "11".repeat(32);
assert_attribute_try_from(
syn::parse_quote! {
#[ink(signature_topic = #s)]
},
Ok(test::Attribute::Ink(vec![AttributeArg::SignatureTopic(s)])),
);
}
}
39 changes: 36 additions & 3 deletions crates/ink/ir/src/ir/event/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,21 @@ 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 hash.
signature_topic_hash: Option<String>,
}

impl TryFrom<ast::AttributeArgs> for EventConfig {
type Error = syn::Error;

fn try_from(args: ast::AttributeArgs) -> Result<Self, Self::Error> {
let mut anonymous: Option<syn::LitBool> = None;
let mut signature_topic: Option<syn::LitStr> = 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())
Expand All @@ -44,27 +48,56 @@ impl TryFrom<ast::AttributeArgs> 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",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is the error we want to see in those ui tests

));
}

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",
));
}
} else {
return Err(format_err_spanned!(
arg,
"encountered unknown or unsupported ink! storage item configuration argument",
));
}
}

Ok(EventConfig::new(
anonymous.map(|lit_bool| lit_bool.value).unwrap_or(false),
signature_topic.map(|lit_str| lit_str.value()),
))
}
}

impl EventConfig {
/// Construct a new [`EventConfig`].
pub fn new(anonymous: bool) -> Self {
Self { anonymous }
pub fn new(anonymous: bool, signature_topic_hash: Option<String>) -> Self {
Self {
anonymous,
signature_topic_hash,
}
}

/// Returns the anonymous configuration argument.
pub fn anonymous(&self) -> bool {
self.anonymous
}

/// Returns the manually specified signature topic.
pub fn signature_topic_hash(&self) -> Option<String> {
self.signature_topic_hash.clone()
ascjones marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading