From 204df0c16fefcba2308b77edd946ba5fe6cc825c Mon Sep 17 00:00:00 2001 From: davidsemakula Date: Thu, 18 Sep 2025 15:37:22 +0300 Subject: [PATCH 01/11] codegen: Allow ABI override for generated trait definition abstractions --- .../src/generator/trait_def/call_builder.rs | 35 ++++++++++---- .../src/generator/trait_def/call_forwarder.rs | 47 ++++++++++++------- .../generator/trait_def/message_builder.rs | 17 +++++-- .../codegen/src/generator/trait_def/mod.rs | 23 +++++++-- .../src/generator/trait_def/trait_registry.rs | 31 ++++++------ crates/ink/codegen/src/lib.rs | 9 +++- 6 files changed, 110 insertions(+), 52 deletions(-) diff --git a/crates/ink/codegen/src/generator/trait_def/call_builder.rs b/crates/ink/codegen/src/generator/trait_def/call_builder.rs index 27dedcbac0..a345a71835 100644 --- a/crates/ink/codegen/src/generator/trait_def/call_builder.rs +++ b/crates/ink/codegen/src/generator/trait_def/call_builder.rs @@ -12,12 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::TraitDefinition; -use crate::{ - generator, - traits::GenerateCode, -}; use derive_more::From; +use ink_primitives::abi::Abi; use proc_macro2::{ Span, TokenStream as TokenStream2, @@ -27,6 +23,12 @@ use quote::{ quote_spanned, }; +use super::TraitDefinition; +use crate::{ + generator, + traits::GenerateCode, +}; + impl TraitDefinition<'_> { /// Generates code for the global trait call builder for an ink! trait. /// @@ -37,8 +39,8 @@ impl TraitDefinition<'_> { /// gas limit, endowment etc. /// - The call builder is used directly by the generated call forwarder. There exists /// one global call forwarder and call builder pair for every ink! trait definition. - pub fn generate_call_builder(&self) -> TokenStream2 { - CallBuilder::from(*self).generate_code() + pub fn generate_call_builder(&self, abi: Option) -> TokenStream2 { + CallBuilder::from((*self, abi)).generate_code() } /// The identifier of the ink! trait call builder. @@ -51,6 +53,7 @@ impl TraitDefinition<'_> { #[derive(From)] struct CallBuilder<'a> { trait_def: TraitDefinition<'a>, + abi: Option, } impl GenerateCode for CallBuilder<'_> { @@ -168,7 +171,9 @@ impl CallBuilder<'_> { fn generate_auxiliary_trait_impls(&self) -> TokenStream2 { let span = self.span(); let call_builder_ident = self.ident(); - let sol_codec = if cfg!(any(ink_abi = "sol", ink_abi = "all")) { + let sol_codec = if matches!(self.abi, Some(Abi::Sol)) + || cfg!(any(ink_abi = "sol", ink_abi = "all")) + { // These manual implementations are a bit more efficient than the derived // equivalents. quote_spanned!(span=> @@ -356,7 +361,7 @@ impl CallBuilder<'_> { let trait_ident = self.trait_def.trait_def.item().ident(); let trait_info_ident = self.trait_def.trait_info_ident(); let builder_ident = self.ident(); - generate_abi_impls!(@tokens |abi: TokenStream2| { + let generator = |abi: TokenStream2| { let message_impls = self.generate_ink_trait_impl_messages(abi.clone()); quote_spanned!(span=> impl #trait_ident for #builder_ident @@ -369,7 +374,17 @@ impl CallBuilder<'_> { #message_impls } ) - }) + }; + match self.abi { + None => generate_abi_impls!(@tokens generator), + Some(abi) => { + let abi_ty = match abi { + Abi::Ink => quote!(::ink::abi::Ink), + Abi::Sol => quote!(::ink::abi::Sol), + }; + generator(abi_ty) + } + } } /// Generate the code for all ink! trait messages implemented by the trait call diff --git a/crates/ink/codegen/src/generator/trait_def/call_forwarder.rs b/crates/ink/codegen/src/generator/trait_def/call_forwarder.rs index 10ba4781d8..479cc5f49d 100644 --- a/crates/ink/codegen/src/generator/trait_def/call_forwarder.rs +++ b/crates/ink/codegen/src/generator/trait_def/call_forwarder.rs @@ -12,12 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::TraitDefinition; -use crate::{ - generator, - traits::GenerateCode, -}; use derive_more::From; +use ink_primitives::abi::Abi; use proc_macro2::{ Span, TokenStream as TokenStream2, @@ -27,6 +23,12 @@ use quote::{ quote_spanned, }; +use super::TraitDefinition; +use crate::{ + generator, + traits::GenerateCode, +}; + impl TraitDefinition<'_> { /// Generates code for the global trait call forwarder for an ink! trait. /// @@ -38,8 +40,8 @@ impl TraitDefinition<'_> { /// - The call forwarder is associated to the call builder for the same ink! trait /// definition and handles all ink! trait calls into another contract instance /// on-chain. For constructing custom calls it forwards to the call builder. - pub fn generate_call_forwarder(&self) -> TokenStream2 { - CallForwarder::from(*self).generate_code() + pub fn generate_call_forwarder(&self, abi: Option) -> TokenStream2 { + CallForwarder::from((*self, abi)).generate_code() } /// The identifier of the ink! trait call forwarder. @@ -52,6 +54,7 @@ impl TraitDefinition<'_> { #[derive(From)] struct CallForwarder<'a> { trait_def: TraitDefinition<'a>, + abi: Option, } impl GenerateCode for CallForwarder<'_> { @@ -366,17 +369,29 @@ impl CallForwarder<'_> { let trait_info_ident = self.trait_def.trait_info_ident(); let forwarder_ident = self.ident(); let message_impls = self.generate_ink_trait_impl_messages(); - generate_abi_impls!(@tokens |abi| quote_spanned!(span=> - impl #trait_ident for #forwarder_ident - where - E: ::ink::env::Environment, - { - #[allow(non_camel_case_types)] - type __ink_TraitInfo = #trait_info_ident; + let generator = |abi| { + quote_spanned!(span=> + impl #trait_ident for #forwarder_ident + where + E: ::ink::env::Environment, + { + #[allow(non_camel_case_types)] + type __ink_TraitInfo = #trait_info_ident; - #message_impls + #message_impls + } + ) + }; + match self.abi { + None => generate_abi_impls!(@tokens generator), + Some(abi) => { + let abi_ty = match abi { + Abi::Ink => quote!(::ink::abi::Ink), + Abi::Sol => quote!(::ink::abi::Sol), + }; + generator(abi_ty) } - )) + } } /// Generate the code for all ink! trait messages implemented by the trait call diff --git a/crates/ink/codegen/src/generator/trait_def/message_builder.rs b/crates/ink/codegen/src/generator/trait_def/message_builder.rs index c7b60050b5..b93878f6e6 100644 --- a/crates/ink/codegen/src/generator/trait_def/message_builder.rs +++ b/crates/ink/codegen/src/generator/trait_def/message_builder.rs @@ -40,8 +40,8 @@ impl TraitDefinition<'_> { /// gas limit, endowment etc. /// - The call builder is used directly by the generated call forwarder. There exists /// one global call forwarder and call builder pair for every ink! trait definition. - pub fn generate_message_builder(&self) -> TokenStream2 { - MessageBuilder::from(*self).generate_code() + pub fn generate_message_builder(&self, abi: Option) -> TokenStream2 { + MessageBuilder::from((*self, abi)).generate_code() } /// The identifier of the ink! trait message builder. @@ -54,6 +54,7 @@ impl TraitDefinition<'_> { #[derive(From)] struct MessageBuilder<'a> { trait_def: TraitDefinition<'a>, + abi: Option, } impl GenerateCode for MessageBuilder<'_> { @@ -109,7 +110,9 @@ impl MessageBuilder<'_> { fn generate_auxiliary_trait_impls(&self) -> TokenStream2 { let span = self.span(); let message_builder_ident = self.trait_def.message_builder_ident(); - let sol_codec = if cfg!(any(ink_abi = "sol", ink_abi = "all")) { + let sol_codec = if matches!(self.abi, Some(Abi::Sol)) + || cfg!(any(ink_abi = "sol", ink_abi = "all")) + { // These manual implementations are a bit more efficient than the derived // equivalents. quote_spanned!(span=> @@ -173,7 +176,7 @@ impl MessageBuilder<'_> { let trait_ident = self.trait_def.trait_def.item().ident(); let trait_info_ident = self.trait_def.trait_info_ident(); let message_builder_ident = self.trait_def.message_builder_ident(); - generate_abi_impls!(@type |abi| { + let generator = |abi| { let abi_ty = match abi { Abi::Ink => quote!(::ink::abi::Ink), Abi::Sol => quote!(::ink::abi::Sol), @@ -190,7 +193,11 @@ impl MessageBuilder<'_> { #message_impls } ) - }) + }; + match self.abi { + None => generate_abi_impls!(@type generator), + Some(abi) => generator(abi), + } } /// Generate the code for all ink! trait messages implemented by the trait call diff --git a/crates/ink/codegen/src/generator/trait_def/mod.rs b/crates/ink/codegen/src/generator/trait_def/mod.rs index 449b6e5416..cc7bec46d9 100644 --- a/crates/ink/codegen/src/generator/trait_def/mod.rs +++ b/crates/ink/codegen/src/generator/trait_def/mod.rs @@ -18,8 +18,8 @@ mod definition; mod message_builder; mod trait_registry; -use crate::GenerateCode; use derive_more::From; +use ink_primitives::abi::Abi; use proc_macro2::{ Span, TokenStream as TokenStream2, @@ -29,10 +29,23 @@ use quote::{ quote_spanned, }; +use crate::GenerateCode; + /// Generator to create the ink! storage struct and important trait implementations. #[derive(From, Copy, Clone)] pub struct TraitDefinition<'a> { trait_def: &'a ir::InkTraitDefinition, + abi: Option, +} + +impl<'a> From<&'a ir::InkTraitDefinition> for TraitDefinition<'a> { + #[inline] + fn from(value: &'a ir::InkTraitDefinition) -> Self { + TraitDefinition { + trait_def: value, + abi: None, + } + } } impl TraitDefinition<'_> { @@ -55,10 +68,10 @@ impl GenerateCode for TraitDefinition<'_> { fn generate_code(&self) -> TokenStream2 { let span = self.trait_def.item().span(); let trait_definition = self.generate_trait_definition(); - let trait_registry = self.generate_trait_registry_impl(); - let trait_message_builder = self.generate_message_builder(); - let trait_call_builder = self.generate_call_builder(); - let trait_call_forwarder = self.generate_call_forwarder(); + let trait_registry = self.generate_trait_registry_impl(self.abi); + let trait_message_builder = self.generate_message_builder(self.abi); + let trait_call_builder = self.generate_call_builder(self.abi); + let trait_call_forwarder = self.generate_call_forwarder(self.abi); quote_spanned!(span => #trait_definition const _: () = { diff --git a/crates/ink/codegen/src/generator/trait_def/trait_registry.rs b/crates/ink/codegen/src/generator/trait_def/trait_registry.rs index 199e11ca8a..1c98394247 100644 --- a/crates/ink/codegen/src/generator/trait_def/trait_registry.rs +++ b/crates/ink/codegen/src/generator/trait_def/trait_registry.rs @@ -49,8 +49,8 @@ impl TraitDefinition<'_> { /// This also generates the code for the global trait info object which /// implements some `ink` traits to provide common information about /// the ink! trait definition such as its unique identifier. - pub fn generate_trait_registry_impl(&self) -> TokenStream2 { - TraitRegistry::from(*self).generate_code() + pub fn generate_trait_registry_impl(&self, abi: Option) -> TokenStream2 { + TraitRegistry::from((*self, abi)).generate_code() } /// Returns the identifier for the ink! trait definition info object. @@ -63,6 +63,7 @@ impl TraitDefinition<'_> { #[derive(From)] struct TraitRegistry<'a> { trait_def: TraitDefinition<'a>, + abi: Option, } impl GenerateCode for TraitRegistry<'_> { @@ -193,9 +194,11 @@ impl TraitRegistry<'_> { selector, message.mutates(), ); - let inout_guards = generate_abi_impls!(@type |abi| { - Self::generate_inout_guards_for_message(message, abi) - }); + let generator = |abi| Self::generate_inout_guards_for_message(message, abi); + let inout_guards = match self.abi { + None => generate_abi_impls!(@type generator), + Some(abi) => generator(abi), + }; let impl_body = match option_env!("INK_COVERAGE_REPORTING") { Some("true") => { quote! { @@ -343,15 +346,12 @@ impl TraitRegistry<'_> { let span = message.span(); let trait_info_ident = self.trait_def.trait_info_ident(); let is_payable = message.ink_attrs().is_payable(); - generate_abi_impls!(@type |abi| { + let generator = |abi| { let (local_id, selector_bytes) = match abi { Abi::Ink => { let local_id = message.local_id(); let selector_bytes = selector.hex_lits(); - ( - quote!(#local_id), - quote!([ #( #selector_bytes ),* ]) - ) + (quote!(#local_id), quote!([ #( #selector_bytes ),* ])) } Abi::Sol => { let name = message.normalized_name(); @@ -364,10 +364,7 @@ impl TraitRegistry<'_> { ::core::primitive::u32::from_be_bytes(#selector_bytes) } ); - ( - selector_id, - selector_bytes - ) + (selector_id, selector_bytes) } }; quote_spanned!(span=> @@ -376,6 +373,10 @@ impl TraitRegistry<'_> { const SELECTOR: [::core::primitive::u8; 4usize] = #selector_bytes; } ) - }) + }; + match self.abi { + None => generate_abi_impls!(@type generator), + Some(abi) => generator(abi), + } } } diff --git a/crates/ink/codegen/src/lib.rs b/crates/ink/codegen/src/lib.rs index dc73ab2c4f..c3ebf04cf5 100644 --- a/crates/ink/codegen/src/lib.rs +++ b/crates/ink/codegen/src/lib.rs @@ -35,7 +35,10 @@ html_favicon_url = "https://use.ink/crate-docs/favicon.png" )] -pub use ink_primitives::reflect; +pub use ink_primitives::{ + abi::Abi, + reflect, +}; mod enforced_error; mod generator; mod traits; @@ -73,6 +76,10 @@ impl<'a> CodeGenerator for &'a ir::InkTraitDefinition { type Generator = generator::TraitDefinition<'a>; } +impl<'a> CodeGenerator for (&'a ir::InkTraitDefinition, Option) { + type Generator = generator::TraitDefinition<'a>; +} + impl<'a> CodeGenerator for &'a ir::InkTest { type Generator = generator::InkTest<'a>; } From 2468a87a5564ed6a04dc080e7aba0e4a1f4bb56f Mon Sep 17 00:00:00 2001 From: davidsemakula Date: Thu, 18 Sep 2025 12:26:01 +0300 Subject: [PATCH 02/11] macro: `interface` attribute macro --- crates/ink/macro/src/interface.rs | 265 ++++++++++++++++++++++++++++++ crates/ink/macro/src/lib.rs | 164 ++++++++++++++++++ crates/ink/src/lib.rs | 1 + 3 files changed, 430 insertions(+) create mode 100644 crates/ink/macro/src/interface.rs diff --git a/crates/ink/macro/src/interface.rs b/crates/ink/macro/src/interface.rs new file mode 100644 index 0000000000..56265a40aa --- /dev/null +++ b/crates/ink/macro/src/interface.rs @@ -0,0 +1,265 @@ +// Copyright (C) Use Ink (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 ink_ir::{ + ast, + format_err_spanned, + utils::duplicate_config_err, +}; +use ink_primitives::abi::Abi; +use proc_macro2::TokenStream as TokenStream2; +use quote::{ + format_ident, + quote, + quote_spanned, +}; + +pub fn analyze(config: TokenStream2, input: TokenStream2) -> TokenStream2 { + match analyze_or_err(config, input) { + Ok(tokens) => tokens, + Err(err) => err.to_compile_error(), + } +} + +pub fn analyze_or_err( + config: TokenStream2, + input: TokenStream2, +) -> syn::Result { + // Parses interface/contract ref config (if any). + let config = Config::parse(config)?; + // Re-uses trait definition IR and codegen. + let trait_def = ink_ir::InkTraitDefinition::new(TokenStream2::new(), input)?; + let trait_def_impl = generate_code((&trait_def, config.abi)); + + let span = trait_def.item().span(); + let trait_name = trait_def.item().ident(); + let contract_ref_name = format_ident!("{trait_name}Ref"); + let abi_ty = match config.abi.unwrap_or({ + #[cfg(not(ink_abi = "sol"))] + { + Abi::Ink + } + + #[cfg(ink_abi = "sol")] + { + Abi::Sol + } + }) { + Abi::Ink => quote!(::ink::abi::Ink), + Abi::Sol => quote!(::ink::abi::Sol), + }; + let env = config + .env + .unwrap_or_else(|| syn::parse_quote! { ::ink::env::DefaultEnvironment }); + Ok(quote_spanned!(span => + // Trait def implementation. + #trait_def_impl + + // Type alias for contract ref. + type #contract_ref_name = + <<::ink::reflect::TraitDefinitionRegistry<#env> as #trait_name> + ::__ink_TraitInfo as ::ink::codegen::TraitCallForwarder>::Forwarder<#abi_ty>; + )) +} + +/// The interface/contract ref configuration. +#[derive(Debug, PartialEq, Eq, Default)] +struct Config { + /// The callee contract's ABI. + abi: Option, + /// The environmental types definition. + /// + /// This must be a type that implements `ink_env::Environment` and can + /// be used to change the underlying environmental types of an ink! smart + /// contract. + env: Option, +} + +impl Config { + /// Parses contract ref config from token stream. + fn parse(config: TokenStream2) -> syn::Result { + let args = syn::parse2::(config)?; + let mut abi_info: Option<(Abi, ast::MetaNameValue)> = None; + let mut env_info: Option<(syn::Path, ast::MetaNameValue)> = None; + for arg in args.into_iter() { + if arg.name().is_ident("abi") { + if let Some((_, ast)) = abi_info { + return Err(duplicate_config_err(ast, arg, "abi", "contract")); + } + let arg_info = arg + .name_value() + .zip(arg.value().and_then(ast::MetaValue::to_string)); + if let Some((name_value, abi_str)) = arg_info { + let abi = match abi_str.as_str() { + "ink" => Abi::Ink, + "sol" => Abi::Sol, + _ => { + return Err(format_err_spanned!( + arg, + "expected one of `ink` or `sol` for `abi` ink! configuration argument", + )) + } + }; + abi_info = Some((abi, name_value.clone())); + } else { + return Err(format_err_spanned!( + arg, + "expected a string literal value for `abi` ink! configuration argument", + )); + } + } else if arg.name().is_ident("env") { + if let Some((_, ast)) = env_info { + return Err(duplicate_config_err(ast, arg, "env", "contract")); + } + let arg_info = arg + .name_value() + .zip(arg.value().and_then(ast::MetaValue::as_path)); + if let Some((name_value, path)) = arg_info { + env_info = Some((path.clone(), name_value.clone())) + } else { + return Err(format_err_spanned!( + arg, + "expected a path value for `env` ink! configuration argument", + )); + } + } else { + return Err(format_err_spanned!( + arg, + "encountered unknown or unsupported ink! configuration argument", + )); + } + } + Ok(Config { + abi: abi_info.map(|(abi, _)| abi), + env: env_info.map(|(path, _)| path), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Asserts that the given input configuration attribute argument are converted + /// into the expected ink! configuration or yields the expected error message. + fn assert_try_from(input: TokenStream2, expected: Result) { + assert_eq!( + Config::parse(input).map_err(|err| err.to_string()), + expected.map_err(ToString::to_string), + ); + } + + #[test] + fn empty_config_works() { + assert_try_from(quote! {}, Ok(Config::default())) + } + + #[test] + fn abi_works() { + assert_try_from( + quote! { + abi = "ink" + }, + Ok(Config { + abi: Some(Abi::Ink), + env: None, + }), + ); + assert_try_from( + quote! { + abi = "sol" + }, + Ok(Config { + abi: Some(Abi::Sol), + env: None, + }), + ); + } + + #[test] + fn abi_invalid_value_fails() { + assert_try_from( + quote! { abi = "move" }, + Err("expected one of `ink` or `sol` for `abi` ink! configuration argument"), + ); + assert_try_from( + quote! { abi = 1u8 }, + Err("expected a string literal value for `abi` ink! configuration argument"), + ); + } + + #[test] + fn abi_missing_value_fails() { + assert_try_from( + syn::parse_quote! { abi }, + Err("expected a string literal value for `abi` ink! configuration argument"), + ); + } + + #[test] + fn env_works() { + assert_try_from( + quote! { + env = ::my::env::Types + }, + Ok(Config { + abi: None, + env: Some(syn::parse_quote! { ::my::env::Types }), + }), + ) + } + + #[test] + fn env_invalid_value_fails() { + assert_try_from( + quote! { env = "invalid" }, + Err("expected a path value for `env` ink! configuration argument"), + ); + } + + #[test] + fn env_missing_value_fails() { + assert_try_from( + quote! { env }, + Err("expected a path value for `env` ink! configuration argument"), + ); + } + + #[test] + fn unknown_arg_fails() { + assert_try_from( + quote! { unknown = argument }, + Err("encountered unknown or unsupported ink! configuration argument"), + ); + } + + #[test] + fn duplicate_args_fails() { + assert_try_from( + quote! { + abi = "ink", + abi = "sol", + }, + Err("encountered duplicate ink! contract `abi` configuration argument"), + ); + assert_try_from( + quote! { + env = ::my::env::Types, + env = ::my::other::env::Types, + }, + Err("encountered duplicate ink! contract `env` configuration argument"), + ); + } +} diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index 55ad1cecb2..56771f7e41 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -26,6 +26,7 @@ mod contract; mod error; mod event; mod ink_test; +mod interface; mod scale; mod selector; mod sol; @@ -658,6 +659,169 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { trait_def::analyze(attr.into(), item.into()).into() } +/// Defines the interface of a "callee" contract and generates a wrapper type which can be +/// used for interacting with the contract. +/// +/// The interface is defined using a trait, and the macro generates a native Rust type +/// (a contract reference) that implements this trait, so it can be used in any Rust +/// context that expects types. +/// +/// # Example +/// +/// # Definition: +/// +/// ``` +/// #[ink::interface(abi = "sol")] +/// pub trait Erc20 { +/// /// Returns the total supply of the ERC-20 smart contract. +/// #[ink(message)] +/// fn total_supply(&self) -> ink::U256; +/// +/// /// Transfers balance from the caller to the given address. +/// #[ink(message)] +/// fn transfer(&mut self, amount: ink::U256, to: ink::Address) -> bool; +/// +/// // etc. +/// } +/// ``` +/// +/// # Usage +/// +/// Given the above interface, you can use the generated contract reference in a +/// "caller" contract as shown below: +/// +/// ``` +/// #[ink::contract] +/// mod erc20_caller { +/// use ink::U256; +/// # // We somehow cannot put the trait in the doc-test crate root due to bugs. +/// # #[ink::interface(abi = "sol")] +/// # pub trait Erc20 { +/// # /// Returns the total supply of the ERC-20 smart contract. +/// # #[ink(message)] +/// # fn total_supply(&self) -> U256; +/// # +/// # /// Transfers balance from the caller to the given address. +/// # #[ink(message)] +/// # fn transfer(&mut self, amount: U256, to: Address) -> bool; +/// # } +/// # +/// #[ink(storage)] +/// pub struct Erc20Caller { +/// callee: ink::Address, +/// } +/// +/// impl Erc20Caller { +/// #[ink(constructor)] +/// pub fn new(addr: ink::Address) -> Self { +/// Self { callee: addr } +/// } +/// +/// #[ink(message)] +/// pub fn call_erc20(&self) { +/// // Calls the ERC20 contract using the contract ref generated above. +/// let total = Erc20Ref::from(self.callee).total_supply(); +/// +/// // Do some fun stuff! +/// } +/// } +/// } +/// ``` +/// +/// ## Header Arguments +/// +/// The `#[ink::interface]` macro can be provided with some additional +/// comma-separated header arguments: +/// +/// - `abi: String` +/// +/// Specifies the ABI (Application Binary Interface) of the "callee" contract. +/// +/// +/// **Usage Example:** +/// ``` +/// #[ink::interface(abi = "sol")] +/// pub trait Callee { +/// #[ink(message)] +/// fn message1(&self); +/// +/// #[ink(message, selector = 42)] +/// fn message2(&self); +/// } +/// ``` +/// +/// **Default value:** Empty. +/// +/// **Allowed values:** `"ink"`, `"sol"` +/// +/// **NOTE**: When no value is provided, the generated contract reference will use the +/// ABI of the root contract (i.e "ink" in "ink" and "all" ABI mode and "sol" in "sol" +/// ABI mode). +/// +/// - `env: impl Environment` +/// +/// Specifies the environment to use for the generated contract reference. +/// +/// This should be the same environment used by the root contract (if any). +/// +/// The environment must implement the `Environment` (defined in `ink_env`) +/// trait and provides all the necessary fundamental type definitions for `Balance`, +/// `AccountId` etc. +/// +/// **Usage Example:** +/// +/// Given a custom `Environment` implementation: +/// ``` +/// #[derive(Clone)] +/// pub struct MyEnvironment; +/// +/// impl ink_env::Environment for MyEnvironment { +/// const NATIVE_TO_ETH_RATIO: u32 = 100_000_000; +/// type AccountId = [u8; 16]; +/// type Balance = u128; +/// type Hash = [u8; 32]; +/// type Timestamp = u64; +/// type BlockNumber = u32; +/// type EventRecord = (); +/// } +/// ``` +/// A user might define an interface (and generate a contract reference) that uses the +/// above custom `Environment` implementation as demonstrated below: +/// ``` +/// #[ink::interface(env = MyEnvironment)] +/// pub trait Callee { +/// #[ink(message)] +/// fn message(&self); +/// +/// // ... +/// } +/// +/// # #[derive(Clone)] +/// # pub struct MyEnvironment; +/// # +/// # impl ink_env::Environment for MyEnvironment { +/// # const NATIVE_TO_ETH_RATIO: u32 = 100_000_000; +/// # type AccountId = [u8; 16]; +/// # type Balance = u128; +/// # type Hash = [u8; 32]; +/// # type Timestamp = u64; +/// # type BlockNumber = u32; +/// # type EventRecord = (); +/// # } +/// ``` +/// +/// **Default value:** `DefaultEnvironment` defined in `ink_env` crate. +// +// # Design Notes +// +// We would have preferred to name this attribute `#[ink::contract_ref]`, however, the +// `ink::contract_ref` name is already taken by the `ink::contract_ref!` declarative +// macro. +#[proc_macro_attribute] +pub fn interface(attr: TokenStream, item: TokenStream) -> TokenStream { + interface::analyze(attr.into(), item.into()).into() +} + /// Implements the necessary traits for a `struct` to be emitted as an event from a /// contract. /// diff --git a/crates/ink/src/lib.rs b/crates/ink/src/lib.rs index 6479d4fab0..acf84fa901 100644 --- a/crates/ink/src/lib.rs +++ b/crates/ink/src/lib.rs @@ -86,6 +86,7 @@ pub use ink_macro::{ contract, error, event, + interface, scale_derive, selector_bytes, selector_id, From 4362fea09222010b0693c0e69fc954de699eca64 Mon Sep 17 00:00:00 2001 From: davidsemakula Date: Wed, 17 Sep 2025 20:51:37 +0300 Subject: [PATCH 03/11] tests: Add precompile integration test --- .../public/precompile/Cargo.toml | 34 +++++++ integration-tests/public/precompile/lib.rs | 97 +++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100755 integration-tests/public/precompile/Cargo.toml create mode 100755 integration-tests/public/precompile/lib.rs diff --git a/integration-tests/public/precompile/Cargo.toml b/integration-tests/public/precompile/Cargo.toml new file mode 100755 index 0000000000..1504ae62d3 --- /dev/null +++ b/integration-tests/public/precompile/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "precompile" +version = "6.0.0-alpha.3" +authors = ["Use Ink "] +edition = "2024" +publish = false + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } +hex-literal = "1" + +[dev-dependencies] +ink_e2e = { path = "../../../crates/e2e" } +blake2 = "0.10" + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] + +[package.metadata.ink-lang] +abi = "ink" + +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = [ + 'cfg(ink_abi, values("ink", "sol", "all"))' +] diff --git a/integration-tests/public/precompile/lib.rs b/integration-tests/public/precompile/lib.rs new file mode 100755 index 0000000000..1e3ad02ae7 --- /dev/null +++ b/integration-tests/public/precompile/lib.rs @@ -0,0 +1,97 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +/// Defines (partial) interface of System precompile. +/// +/// See +#[ink::interface(abi = "sol")] +pub trait System { + /// Computes Blake2b 256-bit hash of given input. + /// + /// # Note + /// + /// This signature is the ink! equivalent of the following Solidity signature + /// ```solidity + /// function hashBlake256(bytes memory input) external pure returns (bytes32 digest); + /// ``` + #[ink(message)] + #[allow(non_snake_case)] + fn hashBlake256(&self, data: ink::sol::DynBytes) -> ink::sol::FixedBytes<32>; +} + +#[ink::contract] +mod precompile { + use super::System; + use ink::prelude::vec::Vec; + + #[ink(storage)] + pub struct Precompile; + + impl Precompile { + /// Initializes contract. + #[ink(constructor)] + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self {} + } + + /// Calls the `hashBlake256` function from the `System` precompile and returns the + /// result. + #[ink(message)] + pub fn blake2b_256(&self, data: Vec) -> [u8; 32] { + const SYS_ADDR: [u8; 20] = + hex_literal::hex!("0000000000000000000000000000000000000900"); + let system_ref: super::SystemRef = ink::Address::from(SYS_ADDR).into(); + let in_bytes = ink::sol::DynBytes(data); + let out_bytes = system_ref.hashBlake256(in_bytes); + out_bytes.0 + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn blake2b_256_works(mut client: ink_e2e::Client) -> E2EResult<()> { + // Given + let mut constructor = PrecompileRef::new(); + let contract = client + .instantiate("precompile", &ink_e2e::bob(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call_builder = contract.call_builder::(); + + // Then + let data = vec![0x1, 0x2, 0x3, 0x4]; + let expected = blake2b_256_ref(&data); + let blake2b_256 = call_builder.blake2b_256(data); + let res = client + .call(&ink_e2e::bob(), &blake2b_256) + .submit() + .await + .expect("blake2x256 failed"); + assert_eq!(res.return_value(), expected); + + Ok(()) + } + + /// Returns the Blake2b 256-bit hash for the given input. + fn blake2b_256_ref(input: &[u8]) -> [u8; 32] { + use blake2::digest::{ + Digest as _, + consts::U32, + }; + + let mut output = [0u8; 32]; + let mut blake2 = blake2::Blake2b::::new(); + blake2.update(input); + let result = blake2.finalize(); + output.copy_from_slice(&result); + output + } + } +} From 4d063a12bbaaa121e116987dc009a0847d7bf9b9 Mon Sep 17 00:00:00 2001 From: davidsemakula Date: Thu, 18 Sep 2025 22:58:01 +0300 Subject: [PATCH 04/11] chore: Remove unnecessary dependency --- integration-tests/public/fallible-setter/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/integration-tests/public/fallible-setter/Cargo.toml b/integration-tests/public/fallible-setter/Cargo.toml index 5847b4ff02..201e19a314 100644 --- a/integration-tests/public/fallible-setter/Cargo.toml +++ b/integration-tests/public/fallible-setter/Cargo.toml @@ -10,7 +10,6 @@ ink = { path = "../../../crates/ink", default-features = false } [dev-dependencies] ink_e2e = { path = "../../../crates/e2e" } -hex = { version = "0.4.3" } [lib] path = "lib.rs" From b4881eb16e6ded8c8fd5c69bdb96789f195e1f9a Mon Sep 17 00:00:00 2001 From: davidsemakula Date: Fri, 19 Sep 2025 00:03:35 +0300 Subject: [PATCH 05/11] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f90530b205..f8447ea9b4 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 +- Add `#[ink::interface]` attribute - [#2648](https://github.com/use-ink/ink/pull/2648) + ### Fixed - `name` override fixes for message id computation and trait definitions - [#2649](https://github.com/use-ink/ink/pull/2649) From 12676129bc63601ed1ff6f155b71ec13539a2b95 Mon Sep 17 00:00:00 2001 From: David Semakula Date: Mon, 22 Sep 2025 16:39:04 +0300 Subject: [PATCH 06/11] formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michael Müller --- crates/ink/macro/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index 56771f7e41..76997086c5 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -668,7 +668,7 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// # Example /// -/// # Definition: +/// # Definition /// /// ``` /// #[ink::interface(abi = "sol")] From 17384848db6e8173fe183d019702726e7dee68b3 Mon Sep 17 00:00:00 2001 From: David Semakula Date: Mon, 22 Sep 2025 16:39:27 +0300 Subject: [PATCH 07/11] formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michael Müller --- crates/ink/macro/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index 76997086c5..3f2523b5fd 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -737,7 +737,6 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// Specifies the ABI (Application Binary Interface) of the "callee" contract. /// -/// /// **Usage Example:** /// ``` /// #[ink::interface(abi = "sol")] From 4efc34397b20d966386d4dfb02ab13219f6d52e0 Mon Sep 17 00:00:00 2001 From: David Semakula Date: Mon, 22 Sep 2025 16:42:07 +0300 Subject: [PATCH 08/11] formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michael Müller --- crates/ink/macro/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index 3f2523b5fd..22519e3def 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -754,7 +754,7 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// **Allowed values:** `"ink"`, `"sol"` /// /// **NOTE**: When no value is provided, the generated contract reference will use the -/// ABI of the root contract (i.e "ink" in "ink" and "all" ABI mode and "sol" in "sol" +/// ABI of the root contract (i.e "ink" in "ink" and "all" ABI mode and "sol" in "sol" /// ABI mode). /// /// - `env: impl Environment` From c0e2a0a3d2e677259ade68dd83fba47c8ddea34d Mon Sep 17 00:00:00 2001 From: davidsemakula Date: Mon, 22 Sep 2025 17:30:04 +0300 Subject: [PATCH 09/11] macro: Rename `ink::contract_ref!` to `ink::contract_ref_from_path!` --- crates/ink/macro/src/lib.rs | 2 +- crates/ink/src/contract_ref.rs | 53 ++++++++++--------- crates/ink/src/message_builder.rs | 8 +-- .../trait-dyn-cross-contract-calls/lib.rs | 6 +-- .../trait-dyn-cross-contract-calls/lib.rs | 6 +-- 5 files changed, 39 insertions(+), 36 deletions(-) diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index 22519e3def..385b23fd39 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -814,7 +814,7 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { // # Design Notes // // We would have preferred to name this attribute `#[ink::contract_ref]`, however, the -// `ink::contract_ref` name is already taken by the `ink::contract_ref!` declarative +// `ink::contract_ref` name is already taken by the `ink::contract_ref_from_path!` declarative // macro. #[proc_macro_attribute] pub fn interface(attr: TokenStream, item: TokenStream) -> TokenStream { diff --git a/crates/ink/src/contract_ref.rs b/crates/ink/src/contract_ref.rs index ca9de5055c..3b16b174e4 100644 --- a/crates/ink/src/contract_ref.rs +++ b/crates/ink/src/contract_ref.rs @@ -32,7 +32,7 @@ use ink_primitives::Address; /// mod trait_caller { /// use ink::{ /// U256, -/// contract_ref, +/// contract_ref_from_path, /// }; /// /// #[ink::trait_definition] @@ -48,34 +48,34 @@ use ink_primitives::Address; /// /// #[ink(storage)] /// pub struct Caller { -/// /// The example of `contract_ref!` as a struct type. -/// erc20: contract_ref!(Erc20), +/// /// The example of `contract_ref_from_path!` as a struct type. +/// erc20: contract_ref_from_path!(Erc20), /// } /// /// impl Caller { -/// /// Example of `contract_ref!` as an argument type. +/// /// Example of `contract_ref_from_path!` as an argument type. /// #[ink(constructor)] -/// pub fn new(erc20: contract_ref!(Erc20)) -> Self { +/// pub fn new(erc20: contract_ref_from_path!(Erc20)) -> Self { /// Self { erc20 } /// } /// -/// /// Example of converting `AccountId` into `contract_ref!` implicitly. +/// /// Example of converting `AccountId` into `contract_ref_from_path!` implicitly. /// #[ink(message)] /// pub fn change_account_id_1(&mut self, new_erc20: Address) { /// self.erc20 = new_erc20.into(); /// } /// -/// /// Example of converting `AccountId` into `contract_ref!` explicitly. +/// /// Example of converting `AccountId` into `contract_ref_from_path!` explicitly. /// #[ink(message)] /// pub fn change_account_id_2(&mut self, new_erc20: Address) { -/// let erc20: contract_ref!(Erc20) = new_erc20.into(); +/// let erc20: contract_ref_from_path!(Erc20) = new_erc20.into(); /// self.erc20 = erc20; /// } /// -/// /// Example of converting `AccountId` into an alias from `contract_ref!`. +/// /// Example of converting `AccountId` into an alias from `contract_ref_from_path!`. /// #[ink(message)] /// pub fn change_account_id_3(&mut self, new_erc20: Address) { -/// type Erc20Wrapper = contract_ref!(Erc20); +/// type Erc20Wrapper = contract_ref_from_path!(Erc20); /// let erc20: Erc20Wrapper = new_erc20.into(); /// self.erc20 = erc20; /// } @@ -92,7 +92,7 @@ use ink_primitives::Address; /// self.erc20.total_supply() /// } /// -/// /// Example of how to use the call builder with `contract_ref!`. +/// /// Example of how to use the call builder with `contract_ref_from_path!`. /// #[ink(message)] /// pub fn total_supply_3(&self) -> U256 { /// use ink::codegen::TraitCallBuilder; @@ -105,7 +105,7 @@ use ink_primitives::Address; /// } /// /// /// Example of how to do common calls and convert -/// /// the `contract_ref!` into `AccountId`. +/// /// the `contract_ref_from_path!` into `AccountId`. /// #[ink(message)] /// pub fn transfer_to_erc20(&mut self, amount: U256) -> bool { /// let erc20_as_account_id = self.erc20.as_ref().clone(); @@ -133,7 +133,7 @@ use ink_primitives::Address; /// in the ink! project's manifest file (i.e. `Cargo.toml`). /// /// ```rust -/// use ink::contract_ref; +/// use ink::contract_ref_from_path; /// use ink_env::DefaultEnvironment; /// use ink_primitives::Address; /// @@ -161,12 +161,13 @@ use ink_primitives::Address; /// type EventRecord = (); /// } /// -/// type AliasWithDefaultEnv = contract_ref!(Erc20, DefaultEnvironment); -/// type AliasWithCustomEnv = contract_ref!(Erc20, CustomEnv); -/// type AliasWithGenericEnv = contract_ref!(Erc20, E); -/// type AliasWithCustomAbi = contract_ref!(Erc20, DefaultEnvironment, ink::abi::Ink); +/// type AliasWithDefaultEnv = contract_ref_from_path!(Erc20, DefaultEnvironment); +/// type AliasWithCustomEnv = contract_ref_from_path!(Erc20, CustomEnv); +/// type AliasWithGenericEnv = contract_ref_from_path!(Erc20, E); +/// type AliasWithCustomAbi = +/// contract_ref_from_path!(Erc20, DefaultEnvironment, ink::abi::Ink); /// -/// fn default(mut contract: contract_ref!(Erc20, DefaultEnvironment)) { +/// fn default(mut contract: contract_ref_from_path!(Erc20, DefaultEnvironment)) { /// let total_supply = contract.total_supply(); /// let to: Address = contract.as_ref().clone(); /// contract.transfer(total_supply, to); @@ -176,7 +177,7 @@ use ink_primitives::Address; /// default(contract) /// } /// -/// fn custom(mut contract: contract_ref!(Erc20, CustomEnv)) { +/// fn custom(mut contract: contract_ref_from_path!(Erc20, CustomEnv)) { /// let total_supply = contract.total_supply(); /// contract.transfer(total_supply, contract.as_ref().clone()); /// } @@ -185,7 +186,7 @@ use ink_primitives::Address; /// custom(contract) /// } /// -/// fn generic(mut contract: contract_ref!(Erc20, E)) +/// fn generic(mut contract: contract_ref_from_path!(Erc20, E)) /// where /// E: ink_env::Environment, /// A: Into
+ Clone, @@ -203,7 +204,9 @@ use ink_primitives::Address; /// generic(contract) /// } /// -/// fn custom_abi(mut contract: contract_ref!(Erc20, DefaultEnvironment, ink::abi::Ink)) { +/// fn custom_abi( +/// mut contract: contract_ref_from_path!(Erc20, DefaultEnvironment, ink::abi::Ink), +/// ) { /// let total_supply = contract.total_supply(); /// contract.transfer(total_supply, contract.as_ref().clone()); /// } @@ -214,21 +217,21 @@ use ink_primitives::Address; /// /// type Environment = DefaultEnvironment; /// -/// fn contract_ref_default_behaviour(mut contract: contract_ref!(Erc20)) { +/// fn contract_ref_default_behaviour(mut contract: contract_ref_from_path!(Erc20)) { /// let total_supply = contract.total_supply(); /// let to: Address = contract.as_ref().clone(); /// contract.transfer(total_supply, to); /// } /// ``` #[macro_export] -macro_rules! contract_ref { +macro_rules! contract_ref_from_path { // The case of the default `Environment` and ABI ( $trait_path:path ) => { - $crate::contract_ref!($trait_path, Environment) + $crate::contract_ref_from_path!($trait_path, Environment) }; // The case of the custom `Environment` and default ABI ( $trait_path:path, $env:ty ) => { - $crate::contract_ref!($trait_path, $env, $crate::env::DefaultAbi) + $crate::contract_ref_from_path!($trait_path, $env, $crate::env::DefaultAbi) }; // The case of the custom `Environment` and ABI ( $trait_path:path, $env:ty, $abi:ty ) => { diff --git a/crates/ink/src/message_builder.rs b/crates/ink/src/message_builder.rs index 557c482253..5b425f2bb7 100644 --- a/crates/ink/src/message_builder.rs +++ b/crates/ink/src/message_builder.rs @@ -20,10 +20,10 @@ /// The macro returns an instance of the generated message builder type which implements /// the trait, allowing the user to create and invoke messages on the trait. /// -/// This is similar to the call builder syntax accessible via the [`crate::contract_ref!`] -/// macro, except that it is decoupled from the callee account id, as well as the -/// underlying execution environment. This allows it to be used in execution contexts -/// other than cross-contract calls. +/// This is similar to the call builder syntax accessible via the +/// [`crate::contract_ref_from_path!`] macro, except that it is decoupled from the callee +/// account id, as well as the underlying execution environment. This allows it to be used +/// in execution contexts other than cross-contract calls. /// /// # Usage /// diff --git a/integration-tests/public/trait-dyn-cross-contract-calls/lib.rs b/integration-tests/public/trait-dyn-cross-contract-calls/lib.rs index 37bc6843a4..f777728a27 100644 --- a/integration-tests/public/trait-dyn-cross-contract-calls/lib.rs +++ b/integration-tests/public/trait-dyn-cross-contract-calls/lib.rs @@ -3,7 +3,7 @@ //! //! The `Caller` doesn't use the `trait_incrementer::IncrementerRef`. Instead, //! all interactions with the `Incrementer` is done through the wrapper from -//! `ink::contract_ref!` and the trait `dyn_traits::Increment`. +//! `ink::contract_ref_from_path!` and the trait `dyn_traits::Increment`. #![cfg_attr(not(feature = "std"), no_std, no_main)] #![allow(clippy::new_without_default)] @@ -15,7 +15,7 @@ pub mod caller { #[ink(storage)] pub struct Caller { /// Here we accept a type which implements the `Incrementer` ink! trait. - incrementer: ink::contract_ref!(Increment), + incrementer: ink::contract_ref_from_path!(Increment), } impl Caller { @@ -120,7 +120,7 @@ mod e2e_tests { .expect("calling `inc` failed"); // Ask the `trait-increment` about a value. It should be updated by the caller. - // Also use `contract_ref!(Increment)` instead of `IncrementerRef` + // Also use `contract_ref_from_path!(Increment)` instead of `IncrementerRef` // to check that it also works with e2e testing. let get = incrementer_call.get(); let value = client diff --git a/integration-tests/solidity-abi/trait-dyn-cross-contract-calls/lib.rs b/integration-tests/solidity-abi/trait-dyn-cross-contract-calls/lib.rs index 37bc6843a4..f777728a27 100644 --- a/integration-tests/solidity-abi/trait-dyn-cross-contract-calls/lib.rs +++ b/integration-tests/solidity-abi/trait-dyn-cross-contract-calls/lib.rs @@ -3,7 +3,7 @@ //! //! The `Caller` doesn't use the `trait_incrementer::IncrementerRef`. Instead, //! all interactions with the `Incrementer` is done through the wrapper from -//! `ink::contract_ref!` and the trait `dyn_traits::Increment`. +//! `ink::contract_ref_from_path!` and the trait `dyn_traits::Increment`. #![cfg_attr(not(feature = "std"), no_std, no_main)] #![allow(clippy::new_without_default)] @@ -15,7 +15,7 @@ pub mod caller { #[ink(storage)] pub struct Caller { /// Here we accept a type which implements the `Incrementer` ink! trait. - incrementer: ink::contract_ref!(Increment), + incrementer: ink::contract_ref_from_path!(Increment), } impl Caller { @@ -120,7 +120,7 @@ mod e2e_tests { .expect("calling `inc` failed"); // Ask the `trait-increment` about a value. It should be updated by the caller. - // Also use `contract_ref!(Increment)` instead of `IncrementerRef` + // Also use `contract_ref_from_path!(Increment)` instead of `IncrementerRef` // to check that it also works with e2e testing. let get = incrementer_call.get(); let value = client From 5b154a34d113f2272f7c03dedd6a3ead0548b6c1 Mon Sep 17 00:00:00 2001 From: davidsemakula Date: Mon, 22 Sep 2025 17:43:55 +0300 Subject: [PATCH 10/11] macro: Rename `#[ink::interface]` to `#[ink::contract_ref]` --- .../src/{interface.rs => contract_ref.rs} | 0 crates/ink/macro/src/lib.rs | 22 +++++++------------ crates/ink/src/lib.rs | 2 +- integration-tests/public/precompile/lib.rs | 2 +- 4 files changed, 10 insertions(+), 16 deletions(-) rename crates/ink/macro/src/{interface.rs => contract_ref.rs} (100%) diff --git a/crates/ink/macro/src/interface.rs b/crates/ink/macro/src/contract_ref.rs similarity index 100% rename from crates/ink/macro/src/interface.rs rename to crates/ink/macro/src/contract_ref.rs diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index 385b23fd39..d29b254547 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -23,10 +23,10 @@ extern crate ink_codegen; mod blake2b; mod contract; +mod contract_ref; mod error; mod event; mod ink_test; -mod interface; mod scale; mod selector; mod sol; @@ -671,7 +671,7 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// # Definition /// /// ``` -/// #[ink::interface(abi = "sol")] +/// #[ink::contract_ref(abi = "sol")] /// pub trait Erc20 { /// /// Returns the total supply of the ERC-20 smart contract. /// #[ink(message)] @@ -695,7 +695,7 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// mod erc20_caller { /// use ink::U256; /// # // We somehow cannot put the trait in the doc-test crate root due to bugs. -/// # #[ink::interface(abi = "sol")] +/// # #[ink::contract_ref(abi = "sol")] /// # pub trait Erc20 { /// # /// Returns the total supply of the ERC-20 smart contract. /// # #[ink(message)] @@ -730,7 +730,7 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// ## Header Arguments /// -/// The `#[ink::interface]` macro can be provided with some additional +/// The `#[ink::contract_ref]` macro can be provided with some additional /// comma-separated header arguments: /// /// - `abi: String` @@ -739,7 +739,7 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// **Usage Example:** /// ``` -/// #[ink::interface(abi = "sol")] +/// #[ink::contract_ref(abi = "sol")] /// pub trait Callee { /// #[ink(message)] /// fn message1(&self); @@ -787,7 +787,7 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// A user might define an interface (and generate a contract reference) that uses the /// above custom `Environment` implementation as demonstrated below: /// ``` -/// #[ink::interface(env = MyEnvironment)] +/// #[ink::contract_ref(env = MyEnvironment)] /// pub trait Callee { /// #[ink(message)] /// fn message(&self); @@ -810,15 +810,9 @@ pub fn trait_definition(attr: TokenStream, item: TokenStream) -> TokenStream { /// ``` /// /// **Default value:** `DefaultEnvironment` defined in `ink_env` crate. -// -// # Design Notes -// -// We would have preferred to name this attribute `#[ink::contract_ref]`, however, the -// `ink::contract_ref` name is already taken by the `ink::contract_ref_from_path!` declarative -// macro. #[proc_macro_attribute] -pub fn interface(attr: TokenStream, item: TokenStream) -> TokenStream { - interface::analyze(attr.into(), item.into()).into() +pub fn contract_ref(attr: TokenStream, item: TokenStream) -> TokenStream { + contract_ref::analyze(attr.into(), item.into()).into() } /// Implements the necessary traits for a `struct` to be emitted as an event from a diff --git a/crates/ink/src/lib.rs b/crates/ink/src/lib.rs index acf84fa901..d5b20b81e9 100644 --- a/crates/ink/src/lib.rs +++ b/crates/ink/src/lib.rs @@ -84,9 +84,9 @@ pub use ink_macro::{ SolErrorMetadata, blake2x256, contract, + contract_ref, error, event, - interface, scale_derive, selector_bytes, selector_id, diff --git a/integration-tests/public/precompile/lib.rs b/integration-tests/public/precompile/lib.rs index 1e3ad02ae7..5f6d6e6197 100755 --- a/integration-tests/public/precompile/lib.rs +++ b/integration-tests/public/precompile/lib.rs @@ -3,7 +3,7 @@ /// Defines (partial) interface of System precompile. /// /// See -#[ink::interface(abi = "sol")] +#[ink::contract_ref(abi = "sol")] pub trait System { /// Computes Blake2b 256-bit hash of given input. /// From d6d4c7f1cd4be100f28ca204554fdfe46413c474 Mon Sep 17 00:00:00 2001 From: davidsemakula Date: Mon, 22 Sep 2025 17:45:04 +0300 Subject: [PATCH 11/11] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8447ea9b4..81a911d353 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [Unreleased] ### Added -- Add `#[ink::interface]` attribute - [#2648](https://github.com/use-ink/ink/pull/2648) +- Add `#[ink::contract_ref]` attribute - [#2648](https://github.com/use-ink/ink/pull/2648) ### Fixed - `name` override fixes for message id computation and trait definitions - [#2649](https://github.com/use-ink/ink/pull/2649)