Unverified Commit 0270add0 authored by Robin Freyler's avatar Robin Freyler Committed by GitHub
Browse files

Refactor and modernize dispatch codegen (#933)

* implement Display for ink_lang::Error

* re-export HexLiteral trait from ink_lang_ir crate

* use big-letters for hex-encoded message and constructor identifiers in codegen

* improve panic messages in dispatch codegen for call and deploy fns

* improve macro hygiene in dispatch codegen

* add ir::Selector::into_be_u32 method

* improve and modernize dispatch selector namespace codegen
parent b9eb9bec
Pipeline #158429 passed with stages
in 26 minutes and 4 seconds
...@@ -18,7 +18,10 @@ use crate::{ ...@@ -18,7 +18,10 @@ use crate::{
GenerateCodeUsing as _, GenerateCodeUsing as _,
}; };
use derive_more::From; use derive_more::From;
use ir::Callable as _; use ir::{
Callable as _,
HexLiteral as _,
};
use proc_macro2::{ use proc_macro2::{
Ident, Ident,
TokenStream as TokenStream2, TokenStream as TokenStream2,
...@@ -98,8 +101,9 @@ impl Dispatch<'_> { ...@@ -98,8 +101,9 @@ impl Dispatch<'_> {
fn deploy() { fn deploy() {
<#storage_ident as ::ink_lang::DispatchUsingMode>::dispatch_using_mode( <#storage_ident as ::ink_lang::DispatchUsingMode>::dispatch_using_mode(
::ink_lang::DispatchMode::Instantiate, ::ink_lang::DispatchMode::Instantiate,
) ).unwrap_or_else(|error| {
.expect("failed to dispatch the constructor") ::core::panic!("dispatching constructor failed: {}", error)
})
} }
#[cfg(not(test))] #[cfg(not(test))]
...@@ -111,8 +115,9 @@ impl Dispatch<'_> { ...@@ -111,8 +115,9 @@ impl Dispatch<'_> {
} }
<#storage_ident as ::ink_lang::DispatchUsingMode>::dispatch_using_mode( <#storage_ident as ::ink_lang::DispatchUsingMode>::dispatch_using_mode(
::ink_lang::DispatchMode::Call, ::ink_lang::DispatchMode::Call,
) ).unwrap_or_else(|error| {
.expect("failed to dispatch the call") ::core::panic!("dispatching message failed: {}", error)
})
} }
} }
} }
...@@ -125,7 +130,7 @@ impl Dispatch<'_> { ...@@ -125,7 +130,7 @@ impl Dispatch<'_> {
#[allow(unused_parens)] #[allow(unused_parens)]
fn dispatch_using_mode( fn dispatch_using_mode(
mode: ::ink_lang::DispatchMode mode: ::ink_lang::DispatchMode
) -> core::result::Result<(), ::ink_lang::DispatchError> { ) -> ::core::result::Result<(), ::ink_lang::DispatchError> {
match mode { match mode {
::ink_lang::DispatchMode::Instantiate => { ::ink_lang::DispatchMode::Instantiate => {
<<#storage_ident as ::ink_lang::ConstructorDispatcher>::Type as ::ink_lang::Execute>::execute( <<#storage_ident as ::ink_lang::ConstructorDispatcher>::Type as ::ink_lang::Execute>::execute(
...@@ -148,8 +153,8 @@ impl Dispatch<'_> { ...@@ -148,8 +153,8 @@ impl Dispatch<'_> {
/// Returns the generated ink! namespace identifier for the given callable kind. /// Returns the generated ink! namespace identifier for the given callable kind.
fn dispatch_trait_impl_namespace(kind: ir::CallableKind) -> Ident { fn dispatch_trait_impl_namespace(kind: ir::CallableKind) -> Ident {
match kind { match kind {
ir::CallableKind::Constructor => format_ident!("__ink_Constr"), ir::CallableKind::Constructor => format_ident!("__ink_ConstructorInfo"),
ir::CallableKind::Message => format_ident!("__ink_Msg"), ir::CallableKind::Message => format_ident!("__ink_MessageInfo"),
} }
} }
...@@ -162,31 +167,27 @@ impl Dispatch<'_> { ...@@ -162,31 +167,27 @@ impl Dispatch<'_> {
let constructor_namespace = let constructor_namespace =
Self::dispatch_trait_impl_namespace(ir::CallableKind::Constructor); Self::dispatch_trait_impl_namespace(ir::CallableKind::Constructor);
quote! { quote! {
// Namespace for messages. // Selector namespace for ink! messages of the root smart contract.
// //
// # Note // # Note
// //
// The `S` parameter is going to refer to array types `[(); N]` // - We have separate namespaces for ink! messages and constructors to
// where `N` is the unique identifier of the associated message // allow for overlapping selectors between them.
// selector. // - The `ID` const parameter uniquely identifies one of the ink! messages
// implemented by the root smart contract.
#[doc(hidden)] #[doc(hidden)]
pub struct #message_namespace<S> { pub struct #message_namespace<const ID: u32> {}
// We need to wrap inner because of Rust's orphan rules.
marker: core::marker::PhantomData<fn() -> S>,
}
// Namespace for constructors. // Selector namespace for ink! constructors of the root smart contract.
// //
// # Note // # Note
// //
// The `S` parameter is going to refer to array types `[(); N]` // - We have separate namespaces for ink! messages and constructors to
// where `N` is the unique identifier of the associated constructor // allow for overlapping selectors between them.
// selector. // - The `ID` const parameter uniquely identifies one of the ink! constructors
// implemented by the root smart contract.
#[doc(hidden)] #[doc(hidden)]
pub struct #constructor_namespace<S> { pub struct #constructor_namespace<const ID: u32> {}
// We need to wrap inner because of Rust's orphan rules.
marker: core::marker::PhantomData<fn() -> S>,
}
} }
} }
...@@ -201,7 +202,10 @@ impl Dispatch<'_> { ...@@ -201,7 +202,10 @@ impl Dispatch<'_> {
let callable = cws.callable(); let callable = cws.callable();
let callable_span = callable.span(); let callable_span = callable.span();
let selector = cws.composed_selector(); let selector = cws.composed_selector();
let (selector_bytes, selector_id) = (selector.as_bytes(), selector.unique_id()); let (selector_bytes, selector_id) = (
selector.hex_lits(),
selector.into_be_u32().hex_padded_suffixed(),
);
let input_types = callable let input_types = callable
.inputs() .inputs()
.map(|pat_type| &pat_type.ty) .map(|pat_type| &pat_type.ty)
...@@ -217,19 +221,19 @@ impl Dispatch<'_> { ...@@ -217,19 +221,19 @@ impl Dispatch<'_> {
quote! { #( #input_types )* } quote! { #( #input_types )* }
}; };
let fn_input_impl = quote_spanned!(callable.inputs_span() => let fn_input_impl = quote_spanned!(callable.inputs_span() =>
impl ::ink_lang::FnInput for #namespace<[(); #selector_id]> { impl ::ink_lang::FnInput for #namespace::<#selector_id> {
type Input = #input_types_tuple; type Input = #input_types_tuple;
} }
); );
let fn_selector_impl = quote_spanned!(callable_span => let fn_selector_impl = quote_spanned!(callable_span =>
impl ::ink_lang::FnSelector for #namespace<[(); #selector_id]> { impl ::ink_lang::FnSelector for #namespace::<#selector_id> {
const SELECTOR: ::ink_env::call::Selector = ::ink_env::call::Selector::new([ const SELECTOR: ::ink_env::call::Selector = ::ink_env::call::Selector::new([
#( #selector_bytes ),* #( #selector_bytes ),*
]); ]);
} }
); );
let fn_state_impl = quote_spanned!(callable_span => let fn_state_impl = quote_spanned!(callable_span =>
impl ::ink_lang::FnState for #namespace<[(); #selector_id]> { impl ::ink_lang::FnState for #namespace::<#selector_id> {
type State = #storage_ident; type State = #storage_ident;
} }
); );
...@@ -301,7 +305,7 @@ impl Dispatch<'_> { ...@@ -301,7 +305,7 @@ impl Dispatch<'_> {
let message = cws.callable(); let message = cws.callable();
let message_span = message.span(); let message_span = message.span();
let selector = cws.composed_selector(); let selector = cws.composed_selector();
let selector_id = selector.unique_id(); let selector_id = selector.into_be_u32().hex_padded_suffixed();
let output_tokens = message let output_tokens = message
.output() .output()
.map(quote::ToTokens::to_token_stream) .map(quote::ToTokens::to_token_stream)
...@@ -311,7 +315,7 @@ impl Dispatch<'_> { ...@@ -311,7 +315,7 @@ impl Dispatch<'_> {
let message_ident = message.ident(); let message_ident = message.ident();
let namespace = Self::dispatch_trait_impl_namespace(ir::CallableKind::Message); let namespace = Self::dispatch_trait_impl_namespace(ir::CallableKind::Message);
let fn_output_impl = quote_spanned!(message.output().span() => let fn_output_impl = quote_spanned!(message.output().span() =>
impl ::ink_lang::FnOutput for #namespace<[(); #selector_id]> { impl ::ink_lang::FnOutput for #namespace::<#selector_id> {
#[allow(unused_parens)] #[allow(unused_parens)]
type Output = #output_tokens; type Output = #output_tokens;
} }
...@@ -333,7 +337,7 @@ impl Dispatch<'_> { ...@@ -333,7 +337,7 @@ impl Dispatch<'_> {
) )
}); });
let message_impl = quote_spanned!(message_span => let message_impl = quote_spanned!(message_span =>
impl ::ink_lang::#message_trait_ident for #namespace<[(); #selector_id]> { impl ::ink_lang::#message_trait_ident for #namespace::<#selector_id> {
const CALLABLE: fn( const CALLABLE: fn(
&#mut_token <Self as ::ink_lang::FnState>::State, &#mut_token <Self as ::ink_lang::FnState>::State,
<Self as ::ink_lang::FnInput>::Input <Self as ::ink_lang::FnInput>::Input
...@@ -357,7 +361,7 @@ impl Dispatch<'_> { ...@@ -357,7 +361,7 @@ impl Dispatch<'_> {
let constructor = cws.callable(); let constructor = cws.callable();
let constructor_span = constructor.span(); let constructor_span = constructor.span();
let selector = cws.composed_selector(); let selector = cws.composed_selector();
let selector_id = selector.unique_id(); let selector_id = selector.into_be_u32().hex_padded_suffixed();
let storage_ident = self.contract.module().storage().ident(); let storage_ident = self.contract.module().storage().ident();
let constructor_ident = constructor.ident(); let constructor_ident = constructor.ident();
let namespace = let namespace =
...@@ -371,7 +375,7 @@ impl Dispatch<'_> { ...@@ -371,7 +375,7 @@ impl Dispatch<'_> {
) )
}); });
let constructor_impl = quote_spanned!(constructor_span => let constructor_impl = quote_spanned!(constructor_span =>
impl ::ink_lang::Constructor for #namespace<[(); #selector_id]> { impl ::ink_lang::Constructor for #namespace::<#selector_id> {
const CALLABLE: fn( const CALLABLE: fn(
<Self as ::ink_lang::FnInput>::Input <Self as ::ink_lang::FnInput>::Input
) -> <Self as ::ink_lang::FnState>::State = |#inputs_as_tuple_or_wildcard| { ) -> <Self as ::ink_lang::FnState>::State = |#inputs_as_tuple_or_wildcard| {
...@@ -424,7 +428,7 @@ impl Dispatch<'_> { ...@@ -424,7 +428,7 @@ impl Dispatch<'_> {
ir::CallableKind::Constructor => "Constructor", ir::CallableKind::Constructor => "Constructor",
}; };
quote::format_ident!( quote::format_ident!(
"__ink_{}_0x{:02x}{:02x}{:02x}{:02x}", "__ink_{}_0x{:02X}{:02X}{:02X}{:02X}",
prefix, prefix,
selector_bytes[0], selector_bytes[0],
selector_bytes[1], selector_bytes[1],
...@@ -446,12 +450,12 @@ impl Dispatch<'_> { ...@@ -446,12 +450,12 @@ impl Dispatch<'_> {
where where
C: ir::Callable, C: ir::Callable,
{ {
let selector_bytes = cws.composed_selector().as_bytes().to_owned(); let selector_bytes = cws.composed_selector().hex_lits();
let variant_ident = self.generate_dispatch_variant_ident(cws); let variant_ident = self.generate_dispatch_variant_ident(cws);
let variant_types = cws.callable().inputs().map(|arg| &arg.ty); let variant_types = cws.callable().inputs().map(|arg| &arg.ty);
quote! { quote! {
[ #( #selector_bytes ),* ] => { [ #( #selector_bytes ),* ] => {
Ok(Self::#variant_ident( ::core::result::Result::Ok(Self::#variant_ident(
#( #(
<#variant_types as ::scale::Decode>::decode(input)? <#variant_types as ::scale::Decode>::decode(input)?
),* ),*
...@@ -524,7 +528,7 @@ impl Dispatch<'_> { ...@@ -524,7 +528,7 @@ impl Dispatch<'_> {
(None, quote! { MessageRef }, quote! { execute_message }) (None, quote! { MessageRef }, quote! { execute_message })
} }
}; };
let selector_id = cws.composed_selector().unique_id(); let selector_id = cws.composed_selector().into_be_u32().hex_padded_suffixed();
let namespace = Self::dispatch_trait_impl_namespace(ir::CallableKind::Message); let namespace = Self::dispatch_trait_impl_namespace(ir::CallableKind::Message);
// If all ink! messages deny payment we can move the payment check to before // If all ink! messages deny payment we can move the payment check to before
// the message dispatch which is more efficient. // the message dispatch which is more efficient.
...@@ -535,11 +539,11 @@ impl Dispatch<'_> { ...@@ -535,11 +539,11 @@ impl Dispatch<'_> {
.is_dynamic_storage_allocator_enabled(); .is_dynamic_storage_allocator_enabled();
quote! { quote! {
Self::#ident(#(#arg_pats),*) => { Self::#ident(#(#arg_pats),*) => {
::ink_lang::#exec_fn::<<#storage_ident as ::ink_lang::ContractEnv>::Env, #namespace<[(); #selector_id]>, _>( ::ink_lang::#exec_fn::<<#storage_ident as ::ink_lang::ContractEnv>::Env, #namespace::<#selector_id>, _>(
::ink_lang::AcceptsPayments(#accepts_payments), ::ink_lang::AcceptsPayments(#accepts_payments),
::ink_lang::EnablesDynamicStorageAllocator(#is_dynamic_storage_allocation_enabled), ::ink_lang::EnablesDynamicStorageAllocator(#is_dynamic_storage_allocation_enabled),
move |state: &#mut_mod #storage_ident| { move |state: &#mut_mod #storage_ident| {
<#namespace<[(); #selector_id]> as ::ink_lang::#msg_trait>::CALLABLE( <#namespace::<#selector_id> as ::ink_lang::#msg_trait>::CALLABLE(
state, #arg_inputs state, #arg_inputs
) )
} }
...@@ -584,9 +588,13 @@ impl Dispatch<'_> { ...@@ -584,9 +588,13 @@ impl Dispatch<'_> {
impl ::scale::Decode for __ink_MessageDispatchEnum { impl ::scale::Decode for __ink_MessageDispatchEnum {
fn decode<I: ::scale::Input>(input: &mut I) -> ::core::result::Result<Self, ::scale::Error> { fn decode<I: ::scale::Input>(input: &mut I) -> ::core::result::Result<Self, ::scale::Error> {
match <[u8; 4] as ::scale::Decode>::decode(input)? { match <[::core::primitive::u8; 4usize] as ::scale::Decode>::decode(input)? {
#( #decode_message )* #( #decode_message )*
_invalid => Err(::scale::Error::from("encountered unknown ink! message selector")) _invalid => ::core::result::Result::Err(
<::scale::Error as ::core::convert::From<&'static ::core::primitive::str>>::from(
"encountered unknown ink! message selector"
)
)
} }
} }
} }
...@@ -620,7 +628,7 @@ impl Dispatch<'_> { ...@@ -620,7 +628,7 @@ impl Dispatch<'_> {
} else { } else {
quote! { ( #(#arg_pats),* ) } quote! { ( #(#arg_pats),* ) }
}; };
let selector_id = cws.composed_selector().unique_id(); let selector_id = cws.composed_selector().into_be_u32().hex_padded_suffixed();
let namespace = let namespace =
Self::dispatch_trait_impl_namespace(ir::CallableKind::Constructor); Self::dispatch_trait_impl_namespace(ir::CallableKind::Constructor);
let is_dynamic_storage_allocation_enabled = self let is_dynamic_storage_allocation_enabled = self
...@@ -629,10 +637,10 @@ impl Dispatch<'_> { ...@@ -629,10 +637,10 @@ impl Dispatch<'_> {
.is_dynamic_storage_allocator_enabled(); .is_dynamic_storage_allocator_enabled();
quote! { quote! {
Self::#ident(#(#arg_pats),*) => { Self::#ident(#(#arg_pats),*) => {
::ink_lang::execute_constructor::<#namespace<[(); #selector_id]>, _>( ::ink_lang::execute_constructor::<#namespace::<#selector_id>, _>(
::ink_lang::EnablesDynamicStorageAllocator(#is_dynamic_storage_allocation_enabled), ::ink_lang::EnablesDynamicStorageAllocator(#is_dynamic_storage_allocation_enabled),
move || { move || {
<#namespace<[(); #selector_id]> as ::ink_lang::Constructor>::CALLABLE( <#namespace::<#selector_id> as ::ink_lang::Constructor>::CALLABLE(
#arg_inputs #arg_inputs
) )
} }
...@@ -677,9 +685,13 @@ impl Dispatch<'_> { ...@@ -677,9 +685,13 @@ impl Dispatch<'_> {
impl ::scale::Decode for __ink_ConstructorDispatchEnum { impl ::scale::Decode for __ink_ConstructorDispatchEnum {
fn decode<I: ::scale::Input>(input: &mut I) -> ::core::result::Result<Self, ::scale::Error> { fn decode<I: ::scale::Input>(input: &mut I) -> ::core::result::Result<Self, ::scale::Error> {
match <[u8; 4] as ::scale::Decode>::decode(input)? { match <[::core::primitive::u8; 4usize] as ::scale::Decode>::decode(input)? {
#( #decode_message )* #( #decode_message )*
_invalid => Err(::scale::Error::from("encountered unknown ink! constructor selector")) _invalid => ::core::result::Result::Err(
<::scale::Error as ::core::convert::From<&'static ::core::primitive::str>>::from(
"encountered unknown ink! constructor selector"
)
)
} }
} }
} }
......
...@@ -45,7 +45,12 @@ impl Selector { ...@@ -45,7 +45,12 @@ impl Selector {
/// Returns a unique identifier as `usize`. /// Returns a unique identifier as `usize`.
pub fn unique_id(self) -> usize { pub fn unique_id(self) -> usize {
u32::from_le_bytes(self.bytes) as usize self.into_be_u32() as usize
}
/// Returns the big-endian `u32` representation of the selector bytes.
pub fn into_be_u32(self) -> u32 {
u32::from_be_bytes(self.bytes)
} }
/// Returns the 4 bytes that make up the selector as hex encoded bytes. /// Returns the 4 bytes that make up the selector as hex encoded bytes.
......
...@@ -34,7 +34,8 @@ mod ast; ...@@ -34,7 +34,8 @@ mod ast;
mod ir; mod ir;
mod literal; mod literal;
pub use self::ir::{ pub use self::{
ir::{
Callable, Callable,
CallableKind, CallableKind,
CallableWithSelector, CallableWithSelector,
...@@ -67,4 +68,6 @@ pub use self::ir::{ ...@@ -67,4 +68,6 @@ pub use self::ir::{
Selector, Selector,
Storage, Storage,
Visibility, Visibility,
},
literal::HexLiteral,
}; };
...@@ -12,22 +12,32 @@ ...@@ -12,22 +12,32 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use derive_more::Display;
/// A dispatch result. /// A dispatch result.
#[doc(hidden)] #[doc(hidden)]
pub type DispatchResult = core::result::Result<(), DispatchError>; pub type DispatchResult = core::result::Result<(), DispatchError>;
/// A dispatch error. /// A dispatch error.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone, Display)]
#[doc(hidden)] #[doc(hidden)]
pub enum DispatchError { pub enum DispatchError {
#[display(fmt = "unknown selector")]
UnknownSelector, UnknownSelector,
#[display(fmt = "unknown constructor selector")]
UnknownInstantiateSelector, UnknownInstantiateSelector,
#[display(fmt = "unknown message selector")]
UnknownCallSelector, UnknownCallSelector,
#[display(fmt = "unable to decoded input parameter bytes")]
InvalidParameters, InvalidParameters,
#[display(fmt = "unable to decoded input parameter bytes for constructor")]
InvalidInstantiateParameters, InvalidInstantiateParameters,
#[display(fmt = "unable to decoded input parameter bytes for message")]
InvalidCallParameters, InvalidCallParameters,
#[display(fmt = "could not read input parameters")]
CouldNotReadInput, CouldNotReadInput,
#[display(fmt = "paid an unpayable message")]
PaidUnpayableMessage, PaidUnpayableMessage,
} }
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment