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

The call_builder macro to provide a more developer-friendly way to work with CallBuilder #1858

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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 crates/e2e/macro/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ fn build_contract(path_to_cargo_toml: &str) -> String {
output_type: OutputType::HumanReadable,
skip_wasm_validation: false,
target: Target::Wasm,
..ExecuteArgs::default()
};

match contract_build::execute(args) {
Expand Down
187 changes: 187 additions & 0 deletions crates/env/src/call/call_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::{
ExecutionInput,
},
types::Gas,
ContractEnv,
Environment,
Error,
};
Expand Down Expand Up @@ -785,3 +786,189 @@ where
self.params().try_invoke()
}
}

/// The analog of the [`build_call`] but from variable to get the [`ContractEnv::Env`].
#[doc(hidden)]
#[allow(clippy::type_complexity)]
pub fn build_call_from_variable<V>(
_: V,
) -> CallBuilder<
V::Env,
Unset<Call<V::Env>>,
Unset<ExecutionInput<EmptyArgumentList>>,
Unset<ReturnType<()>>,
>
where
V: ContractEnv,
{
build_call::<V::Env>()
}

/// The helper macro reverses the order of the token stream.
///
/// ```rust
/// use ink_env::reverse_tokens_order;
/// assert_eq!(
/// core::any::TypeId::of::<u64>(),
/// reverse_tokens_order!(()>u64<::of::TypeId::any::core)
/// )
/// ```
#[doc(hidden)]
#[macro_export]
macro_rules! reverse_tokens_order {
(@main_loop [ $( $heads:tt, )+ ],) => {
$( $heads )*
};
(@main_loop [ $( $heads:tt, )+ ], $head:tt $( $tail:tt )*) => {
$crate::reverse_tokens_order!(@main_loop [ $head, $( $heads, )+ ], $( $tail )*)
};
($head:tt $( $tail:tt )*) => {
$crate::reverse_tokens_order!(@main_loop [ $head, ], $( $tail )*)
};
}

/// The primary internal implementation of the [`call_builder`](crate::call_builder)
/// macro. It parses the token stream and generates [`CallBuilder`].
///
/// The macro works as a state machine with the next steps:
/// - Reverses the token stream.
/// - Tries to find the template `(args)method.` or `(args, receiver)method::`.
/// - Collects arguments, receiver, and message descriptor getter.
/// - Reverse the token stream back.
/// - Expands into [`CallBuilder`].
#[doc(hidden)]
#[macro_export]
macro_rules! call_builder_inner {
// Reverses the initial token stream to simplify handling in the `@reversed`.
(@reverse $head:tt $( $tail:tt )*) => {
$crate::call_builder_inner!(@reverse_main_loop [ $head, ], $( $tail )*)
};
(@reverse_main_loop [ $( $heads:tt, )+ ],) => {
$crate::call_builder_inner!(@reversed $( $heads )*)
};
(@reverse_main_loop [ $( $heads:tt, )+ ], $head:tt $( $tail:tt )*) => {
$crate::call_builder_inner!(@reverse_main_loop [ $head, $( $heads, )+ ], $( $tail )*)
};

// Entry point for the `... .method(inputs)`.
// The `$input_bindings` are not reversed because it is part
// of the `( $( $input_bindings:expr ),* )` that is single `tt`.
(@reversed ( $( $input_bindings:expr ),* ) $method:ident . $( $rest:tt )+ ) => {
$crate::call_builder_inner!(
@final
$crate::reverse_tokens_order!( $( $rest )+ ),
$crate::reverse_tokens_order!( ( $( $input_bindings ),* ) $method . $( $rest )+ ),
::ink::codegen::paste! {
$crate::reverse_tokens_order!( () [<__ink_ $method _description>] . $( $rest )+ )
},
$( $input_bindings )*
)
};

// Entry point for the `... ::method(self, inputs)`.
// The `$receiver` and `$input_bindings` are not reversed because it is part
// of the `( $receiver:expr $(, $input_bindings:expr )* )` that is single `tt`.
(@reversed ( $receiver:expr $(, $input_bindings:expr )* ) $method:ident :: $( $rest:tt )+ ) => {
$crate::call_builder_inner!(
@final
$receiver,
$crate::reverse_tokens_order!( ( $receiver $(, $input_bindings )* ) $method :: $( $rest )+ ),
::ink::codegen::paste! {
$crate::reverse_tokens_order!( ($receiver) [<__ink_ $method _description>] :: $( $rest )+ )
},
$( $input_bindings )*
)
};

// The final generated code by the macro.
(@final $caller:expr, $call:expr, $description:expr, $( $input_bindings:expr)* ) => {{
// Gets the message description with selector information.
#[allow(clippy::unnecessary_mut_passed)]
let message_description = $description;
let call_builder = match $caller {
ref caller => {
// Creates the call builder with the selector from
// the message descriptor with input arguments.
let call_builder = $crate::call::build_call_from_variable(caller)
.call(::ink::ToAccountId::to_account_id(caller))
.exec_input(
$crate::call::ExecutionInput::new(
message_description.selector()
)
$(
.push_arg($input_bindings)
)*
)
.returns::<_>();

call_builder
}
};

// Forces setting of the return type of the call builder.
if false {
let _ = if false {
$call
} else {
call_builder.invoke()
};
unreachable!();
};
call_builder
}};
}

/// Returns a [`CallBuilder`] based on the message call signature.
///
/// ```should_panic
/// use ink::contract_ref;
/// use ink_env::{
/// call_builder,
/// CallFlags,
/// DefaultEnvironment,
/// };
/// use ink_primitives::AccountId;
///
/// #[ink::trait_definition]
/// pub trait Erc20 {
/// /// Returns the total supply of the ERC-20 smart contract.
/// #[ink(message)]
/// fn total_supply(&self) -> u128;
///
/// /// Transfers balance from the caller to the given address.
/// #[ink(message)]
/// fn transfer(&mut self, amount: u128, to: AccountId) -> bool;
/// }
///
/// let mut callee: contract_ref!(Erc20, DefaultEnvironment) =
/// AccountId::from([0; 32]).into();
/// call_builder!(callee.total_supply())
/// .transferred_value(1000)
/// .invoke();
/// call_builder!(callee.transfer(20, AccountId::from([1; 32])))
/// .call_flags(CallFlags::default().set_allow_reentry(true))
/// .invoke();
/// let ink_err = call_builder!(callee.total_supply()).try_invoke();
/// let message_err = ink_err.unwrap();
/// let supply = message_err.unwrap();
///
/// // Other supported syntax
/// call_builder!(Erc20::total_supply(&callee)).invoke();
/// call_builder!(Erc20::transfer(&mut callee, 20, AccountId::from([2; 32]))).invoke();
/// call_builder!(<_ as Erc20>::total_supply(&callee)).invoke();
/// call_builder!(<_ as Erc20>::transfer(
/// &mut callee,
/// 20,
/// AccountId::from([3; 32])
/// ))
/// .invoke();
/// call_builder!(Erc20::total_supply(&callee)).invoke();
/// ```
#[macro_export]
macro_rules! call_builder {
( $( $tokens:tt )* ) => {{
// Forces the compiler to check first that the expression is valid.
let _ = || { $( $tokens )* };
$crate::call_builder_inner!(@reverse $( $tokens )* )
}};
}
1 change: 1 addition & 0 deletions crates/env/src/call/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub mod utils {
pub use self::{
call_builder::{
build_call,
build_call_from_variable,
Call,
CallBuilder,
CallParams,
Expand Down
8 changes: 8 additions & 0 deletions crates/env/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ pub trait ContractEnv {
type Env: crate::Environment;
}

impl<T: ContractEnv> ContractEnv for &T {
type Env = T::Env;
}

impl<T: ContractEnv> ContractEnv for &mut T {
type Env = T::Env;
}

/// Refers to the generated ink! smart contract reference type.
///
/// # Note
Expand Down
1 change: 1 addition & 0 deletions crates/ink/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ ink_metadata = { workspace = true, optional = true }
ink_prelude = { workspace = true }
ink_macro = { workspace = true }

paste = { workspace = true }
scale = { package = "parity-scale-codec", workspace = true }
derive_more = { workspace = true, features = ["from"] }

Expand Down
17 changes: 17 additions & 0 deletions crates/ink/codegen/src/generator/as_dependency/contract_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use ir::{
};
use proc_macro2::TokenStream as TokenStream2;
use quote::{
format_ident,
quote,
quote_spanned,
};
Expand Down Expand Up @@ -392,6 +393,13 @@ impl ContractRef<'_> {
let input_types = message.inputs().map(|input| &input.ty).collect::<Vec<_>>();
let output_type = message.output().map(|ty| quote! { -> #ty });
let wrapped_output_type = message.wrapped_output();

let cfg_attrs = message.get_cfg_attrs(span);
let description_ident = format_ident!("__ink_{}_description", message_ident);
let is_mutable = message.receiver().is_ref_mut();
let is_payable = message.is_payable();
let selector_bytes = message.composed_selector().hex_lits();

quote_spanned!(span=>
#( #attrs )*
#[inline]
Expand Down Expand Up @@ -424,6 +432,15 @@ impl ContractRef<'_> {
error,
))
}

#(#cfg_attrs)*
pub const fn #description_ident(&self) -> ::ink::reflect::MessageDescription {
::ink::reflect::MessageDescription::new(
#is_mutable,
#is_payable,
[ #( #selector_bytes ),* ]
)
}
)
}

Expand Down
31 changes: 25 additions & 6 deletions crates/ink/codegen/src/generator/trait_def/definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

use super::TraitDefinition;
use heck::ToLowerCamelCase as _;
use ir::Selector;
use proc_macro2::TokenStream as TokenStream2;
use quote::{
format_ident,
Expand All @@ -24,7 +25,10 @@ use quote::{
};

impl<'a> TraitDefinition<'a> {
fn generate_for_message(message: ir::InkTraitMessage<'a>) -> TokenStream2 {
fn generate_for_message(
message: ir::InkTraitMessage<'a>,
selector: Selector,
) -> TokenStream2 {
let span = message.span();
let attrs = message.attrs();
let sig = message.sig();
Expand All @@ -37,13 +41,29 @@ impl<'a> TraitDefinition<'a> {
};
let output_ident =
format_ident!("{}Output", ident.to_string().to_lower_camel_case());

let description_ident = format_ident!("__ink_{}_description", sig.ident);
let is_mutable = message.receiver().is_ref_mut();
let is_payable = message.ink_attrs().is_payable();
let selector_bytes = selector.hex_lits();

quote_spanned!(span =>
/// Output type of the respective trait message.
#(#cfg_attrs)*
type #output_ident: ::ink::codegen::ImpliesReturn<#output>;

#(#attrs)*
fn #ident(#inputs) -> Self::#output_ident;

#(#cfg_attrs)*
#[inline(always)]
fn #description_ident(&self) -> ::ink::reflect::MessageDescription {
::ink::reflect::MessageDescription::new(
#is_mutable,
#is_payable,
[ #( #selector_bytes ),* ]
)
}
)
}
}
Expand All @@ -54,11 +74,10 @@ impl TraitDefinition<'_> {
let span = item.span();
let attrs = item.attrs();
let ident = item.ident();
let messages = item
.iter_items()
.map(|(item, _)| item)
.flat_map(ir::InkTraitItem::filter_map_message)
.map(Self::generate_for_message);
let messages = item.iter_items().flat_map(|(item, selector)| {
ir::InkTraitItem::filter_map_message(item)
.map(|message| Self::generate_for_message(message, selector))
});
quote_spanned!(span =>
#(#attrs)*
pub trait #ident: ::ink::env::ContractEnv {
Expand Down
1 change: 1 addition & 0 deletions crates/ink/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ pub use self::{
TraitMessageSelector,
},
};
pub use paste::paste;
26 changes: 23 additions & 3 deletions crates/ink/src/contract_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,30 @@ macro_rules! contract_ref {
/// Implemented by contracts that are compiled as dependencies.
///
/// Allows them to return their underlying account identifier.
pub trait ToAccountId<T>
pub trait ToAccountId<E>
where
T: Environment,
E: Environment,
{
/// Returns the underlying account identifier of the instantiated contract.
fn to_account_id(&self) -> <T as Environment>::AccountId;
fn to_account_id(&self) -> <E as Environment>::AccountId;
}

impl<T, E> ToAccountId<E> for &T
where
T: ToAccountId<E>,
E: Environment,
{
fn to_account_id(&self) -> <E as Environment>::AccountId {
T::to_account_id(self)
}
}

impl<T, E> ToAccountId<E> for &mut T
where
T: ToAccountId<E>,
E: Environment,
{
fn to_account_id(&self) -> <E as Environment>::AccountId {
T::to_account_id(self)
}
}
Loading