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

New metadata_name attribute #859

Closed
Closed
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,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(payable)]` | Applicable to ink! messages. | Allows receiving value as part of the call of the ink! message. ink! constructors are implicitly payable. |
| `#[ink(selector = "..")]` | 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. |
| `#[ink(metadata_name = "..")]` | Applicable to ink! messages, ink! constructors and ink! implementation blocks. | Specifies a concrete name of method/trait inside of metadata. This allows a contract author to precisely control the naming of methods inside of ABI making it possible to rename their methods without breakage. |
| `#[ink(namespace = "..")]` | Applicable to ink! trait implementation blocks. | Changes the resulting selectors of all the ink! messages and ink! constructors within the trait implementation. Allows to disambiguate between trait implementations with overlapping message or constructor names. Use only with great care and consideration! |
| `#[ink(impl)]` | Applicable to ink! implementation blocks. | Tells the ink! codegen that some implementation block shall be granted access to ink! internals even without it containing any ink! messages or ink! constructors. |

Expand Down
21 changes: 7 additions & 14 deletions crates/lang/codegen/src/generator/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,7 @@ impl Metadata<'_> {
let selector = constructor.composed_selector();
let selector_bytes = selector.as_bytes();
let constructor = constructor.callable();
let ident = constructor.ident();
let ident_lit = ident.to_string();
let ident_lit = constructor.metadata_name();
let args = constructor
.inputs()
.map(|arg| Self::generate_message_param(arg));
Expand Down Expand Up @@ -215,15 +214,11 @@ impl Metadata<'_> {
.module()
.impls()
.flat_map(|impl_block| {
let trait_ident = impl_block
.trait_path()
.map(|path| path.segments.last().map(|seg| &seg.ident))
.flatten();
impl_block
.iter_messages()
.map(move |message| (trait_ident, message))
.map(move |message| (impl_block.trait_metadata_name(), message))
})
.map(|(trait_ident, message)| {
.map(|(trait_name, message)| {
let span = message.span();
let attrs = message.attrs();
let docs = Self::extract_doc_comments(attrs);
Expand All @@ -232,16 +227,14 @@ impl Metadata<'_> {
let is_payable = message.is_payable();
let message = message.callable();
let mutates = message.receiver().is_ref_mut();
let ident = message.ident();
let ident_lit = ident.to_string();
let ident_lit = message.metadata_name();
let args = message
.inputs()
.map(|arg| Self::generate_message_param(arg));
let ret_ty = Self::generate_return_type(message.output());
let constr = match trait_ident {
Some(trait_ident) => {
let trait_ident_lit = trait_ident.to_string();
quote_spanned!(span => from_trait_and_name(#trait_ident_lit, #ident_lit))
let constr = match trait_name {
Some(trait_name_string) => {
quote_spanned!(span => from_trait_and_name(#trait_name_string, #ident_lit))
}
None => {
quote_spanned!(span => from_name(#ident_lit))
Expand Down
48 changes: 48 additions & 0 deletions crates/lang/ir/src/ir/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,16 @@ impl InkAttribute {
})
}

/// Returns the metadata name of the ink! attribute if any.
pub fn metadata_name(&self) -> Option<String> {
self.args().find_map(|arg| {
if let ir::AttributeArg::MetadataName(name) = arg.kind() {
return Some(name.clone())
}
None
})
}

/// Returns `true` if the ink! attribute contains the `payable` argument.
pub fn is_payable(&self) -> bool {
self.args()
Expand Down Expand Up @@ -316,6 +326,8 @@ pub enum AttributeArgKind {
Payable,
/// `#[ink(selector = "0xDEADBEEF")]`
Selector,
/// `#[ink(metadata_name = "transfer")]`
MetadataName,
/// `#[ink(extension = N: u32)]`
Extension,
/// `#[ink(namespace = "my_namespace")]`
Expand Down Expand Up @@ -372,6 +384,11 @@ pub enum AttributeArg {
/// Applied on ink! constructors or messages to manually control their
/// selectors.
Selector(Selector),
/// `#[ink(metadata_name = "transfer")]`
///
/// Applied on ink! constructors, messages or implementation blocks
/// with traits to manually control their naming inside of metadata.
MetadataName(String),
/// `#[ink(namespace = "my_namespace")]`
///
/// Applied on ink! trait implementation blocks to disambiguate other trait
Expand Down Expand Up @@ -423,6 +440,9 @@ impl core::fmt::Display for AttributeArgKind {
Self::Selector => {
write!(f, "selector = S:[u8; 4]")
}
Self::MetadataName => {
write!(f, "selector = N:string")
}
Self::Extension => {
write!(f, "extension = N:u32)")
}
Expand All @@ -448,6 +468,7 @@ impl AttributeArg {
Self::Constructor => AttributeArgKind::Constructor,
Self::Payable => AttributeArgKind::Payable,
Self::Selector(_) => AttributeArgKind::Selector,
Self::MetadataName(_) => AttributeArgKind::MetadataName,
Self::Extension(_) => AttributeArgKind::Extension,
Self::Namespace(_) => AttributeArgKind::Namespace,
Self::Implementation => AttributeArgKind::Implementation,
Expand All @@ -470,6 +491,9 @@ impl core::fmt::Display for AttributeArg {
Self::Selector(selector) => {
write!(f, "selector = {:?}", selector.as_bytes())
}
Self::MetadataName(name) => {
write!(f, "metadata_name = {}", name)
}
Self::Extension(extension) => {
write!(f, "extension = {:?}", extension.into_u32())
}
Expand Down Expand Up @@ -796,6 +820,18 @@ impl TryFrom<syn::NestedMeta> for AttributeFrag {
}
return Err(format_err!(name_value, "expecteded 4-digit hexcode for `selector` argument, e.g. #[ink(selector = 0xC0FEBABE]"))
}
if name_value.path.is_ident("metadata_name") {
if let syn::Lit::Str(lit_str) = &name_value.lit {
let name = lit_str.value();
return Ok(AttributeFrag {
ast: meta,
arg: AttributeArg::MetadataName(
name,
),
})
}
return Err(format_err!(name_value, "expecteded string type for `metadata_name` argument, e.g. #[ink(metadata_name = \"transfer\")]"))
}
if name_value.path.is_ident("namespace") {
if let syn::Lit::Str(lit_str) = &name_value.lit {
let bytes = lit_str.value().into_bytes();
Expand Down Expand Up @@ -1074,6 +1110,18 @@ mod tests {
);
}

#[test]
fn metadata_name_works() {
assert_attribute_try_from(
syn::parse_quote! {
#[ink(metadata_name = "transfer")]
},
Ok(test::Attribute::Ink(vec![AttributeArg::MetadataName(
String::from("transfer"),
)])),
);
}

#[test]
fn selector_non_hexcode() {
assert_attribute_try_from(
Expand Down
7 changes: 7 additions & 0 deletions crates/lang/ir/src/ir/item_impl/callable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ where
<C as Callable>::ident(self.callable)
}

fn metadata_name(&self) -> String {
<C as Callable>::metadata_name(self.callable)
}

fn user_provided_selector(&self) -> Option<&ir::Selector> {
<C as Callable>::user_provided_selector(self.callable)
}
Expand Down Expand Up @@ -152,6 +156,9 @@ pub trait Callable {
/// Returns the identifier of the ink! callable.
fn ident(&self) -> &Ident;

/// Returns the name of method for metadata.
fn metadata_name(&self) -> String;

/// Returns the selector of the ink! callable if any has been manually set.
fn user_provided_selector(&self) -> Option<&ir::Selector>;

Expand Down
24 changes: 20 additions & 4 deletions crates/lang/ir/src/ir/item_impl/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ pub struct Constructor {
/// This overrides the computed selector, even when using a manual namespace
/// for the parent implementation block.
selector: Option<ir::Selector>,
/// An optional user provided name of method for metadata.
///
/// # Note
///
/// This overrides the default name of method inside of metadata.
metadata_name: Option<String>,
}

impl quote::ToTokens for Constructor {
Expand Down Expand Up @@ -155,9 +161,9 @@ impl Constructor {
&ir::AttributeArgKind::Constructor,
|arg| {
match arg.kind() {
ir::AttributeArg::Constructor | ir::AttributeArg::Selector(_) => {
Ok(())
}
ir::AttributeArg::Constructor
| ir::AttributeArg::MetadataName(_)
| ir::AttributeArg::Selector(_) => Ok(()),
ir::AttributeArg::Payable => {
Err(Some(format_err!(
arg.span(),
Expand All @@ -180,8 +186,10 @@ impl TryFrom<syn::ImplItemMethod> for Constructor {
Self::ensure_no_self_receiver(&method_item)?;
let (ink_attrs, other_attrs) = Self::sanitize_attributes(&method_item)?;
let selector = ink_attrs.selector();
Ok(Constructor {
let metadata_name = ink_attrs.metadata_name();
Ok(Self {
selector,
metadata_name,
item: syn::ImplItemMethod {
attrs: other_attrs,
..method_item
Expand All @@ -199,6 +207,14 @@ impl Callable for Constructor {
&self.item.sig.ident
}

fn metadata_name(&self) -> String {
if self.metadata_name.is_some() {
self.metadata_name.clone().unwrap()
} else {
self.item.sig.ident.to_string()
}
}

fn user_provided_selector(&self) -> Option<&ir::Selector> {
self.selector.as_ref()
}
Expand Down
28 changes: 27 additions & 1 deletion crates/lang/ir/src/ir/item_impl/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ pub struct Message {
/// This overrides the computed selector, even when using a manual namespace
/// for the parent implementation block.
selector: Option<ir::Selector>,
/// An optional user provided name of method for metadata.
///
/// # Note
///
/// This overrides the default name of method inside of metadata.
metadata_name: Option<String>,
}

impl quote::ToTokens for Message {
Expand Down Expand Up @@ -184,7 +190,8 @@ impl Message {
match arg.kind() {
ir::AttributeArg::Message
| ir::AttributeArg::Payable
| ir::AttributeArg::Selector(_) => Ok(()),
| ir::AttributeArg::Selector(_)
| ir::AttributeArg::MetadataName(_) => Ok(()),
_ => Err(None),
}
},
Expand All @@ -202,9 +209,11 @@ impl TryFrom<syn::ImplItemMethod> for Message {
let (ink_attrs, other_attrs) = Self::sanitize_attributes(&method_item)?;
let is_payable = ink_attrs.is_payable();
let selector = ink_attrs.selector();
let metadata_name = ink_attrs.metadata_name();
Ok(Self {
is_payable,
selector,
metadata_name,
item: syn::ImplItemMethod {
attrs: other_attrs,
..method_item
Expand All @@ -222,6 +231,14 @@ impl Callable for Message {
&self.item.sig.ident
}

fn metadata_name(&self) -> String {
if self.metadata_name.is_some() {
self.metadata_name.clone().unwrap()
} else {
self.item.sig.ident.to_string()
}
}

fn user_provided_selector(&self) -> Option<&ir::Selector> {
self.selector.as_ref()
}
Expand Down Expand Up @@ -409,6 +426,15 @@ mod tests {
pub fn my_message(&self) {}
},
),
// Another ink! attribute, separate and normalized attribute.
(
true,
syn::parse_quote! {
#[ink(message)]
#[ink(selector = "0xDEADBEEF", payable, metadata_name = "your_message")]
pub fn my_message(&self) {}
},
),
];
for (expect_payable, item_method) in test_inputs {
let is_payable = <ir::Message as TryFrom<_>>::try_from(item_method)
Expand Down
36 changes: 33 additions & 3 deletions crates/lang/ir/src/ir/item_impl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ pub struct ItemImpl {
/// names. Generally can be used to change computation of message and
/// constructor selectors of the implementation block.
namespace: Option<ir::Namespace>,
/// A name of trait used in metadata.
///
/// # Note
///
/// User can provide name of trait which will be used inside of metadata.
/// But he is not obliged to implement this trait.
trait_metadata_name: Option<String>,
}

impl quote::ToTokens for ItemImpl {
Expand Down Expand Up @@ -293,20 +300,22 @@ impl TryFrom<syn::ItemImpl> for ItemImpl {
}
let (ink_attrs, other_attrs) = ir::partition_attributes(item_impl.attrs)?;
let mut namespace = None;
let mut trait_metadata_name = None;
if !ink_attrs.is_empty() {
let normalized =
ir::InkAttribute::from_expanded(ink_attrs).map_err(|err| {
err.into_combine(format_err!(impl_block_span, "at this invocation",))
})?;
normalized.ensure_no_conflicts(|arg| {
match arg.kind() {
ir::AttributeArg::Implementation | ir::AttributeArg::Namespace(_) => {
Ok(())
}
ir::AttributeArg::Implementation
| ir::AttributeArg::Namespace(_)
| ir::AttributeArg::MetadataName(_) => Ok(()),
_ => Err(None),
}
})?;
namespace = normalized.namespace();
trait_metadata_name = normalized.metadata_name();
}
Ok(Self {
attrs: other_attrs,
Expand All @@ -319,6 +328,7 @@ impl TryFrom<syn::ItemImpl> for ItemImpl {
brace_token: item_impl.brace_token,
items: impl_items,
namespace,
trait_metadata_name,
})
}
}
Expand Down Expand Up @@ -356,6 +366,26 @@ impl ItemImpl {
self.namespace.as_ref()
}

/// Returns the trait metadata name of the implementation block if any has been provided.
///
/// # Note
///
/// If metadata name is `""`, it means that user wants to remove trait naming in metadata
pub fn trait_metadata_name(&self) -> Option<String> {
if self.trait_metadata_name.is_some() {
let name = self.trait_metadata_name.clone();
if name.as_ref().unwrap().is_empty() {
None
} else {
name
}
} else {
self.trait_path()
.map(|path| path.segments.last().map(|seg| seg.ident.to_string()))
.flatten()
}
}

/// Returns an iterator yielding the ink! messages of the implementation block.
pub fn iter_messages(&self) -> IterMessages {
IterMessages::new(self)
Expand Down
Loading