diff --git a/.gitignore b/.gitignore index 3a905f1e9dd..ef52b11d014 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,7 @@ # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock Cargo.lock +.idea + # Ignore history files. **/.history/** diff --git a/README.md b/README.md index 11ae02ab4c1..ae1c7412a67 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,7 @@ In a module annotated with `#[ink::contract]` these attributes are available: | `#[ink(anonymous)]` | Applicable to ink! events. | Tells the ink! codegen to treat the ink! event as anonymous which omits the event signature as topic upon emitting. Very similar to anonymous events in Solidity. | | `#[ink(topic)]` | Applicable on ink! event field. | Tells the ink! codegen to provide a topic hash for the given field. Every ink! event can only have a limited number of such topic fields. Similar semantics as to indexed event arguments in Solidity. | | `#[ink(payable)]` | Applicable to ink! messages. | Allows receiving value as part of the call of the ink! message. ink! constructors are implicitly payable. | +| `#[ink(allow_reentrancy)]` | Applicable to ink! messages. | Allows the ink! message to be called reentrantly. | | `#[ink(selector = S:u32)]` | Applicable to ink! messages and ink! constructors. | Specifies a concrete dispatch selector for the flagged entity. This allows a contract author to precisely control the selectors of their APIs making it possible to rename their API without breakage. | | `#[ink(selector = _)]` | Applicable to ink! messages. | Specifies a fallback message that is invoked if no other ink! message matches a selector. | | `#[ink(namespace = N:string)]` | 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! | diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index 3050c21df76..12c324bf9d3 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -799,3 +799,13 @@ where TypedEnvBackend::call_runtime::(instance, call) }) } + +/// Returns how many times caller exists on call stack. +pub fn reentrance_count() -> u32 +where + E: Environment, +{ + ::on_instance(|instance| { + TypedEnvBackend::reentrance_count::(instance) + }) +} diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 99455b5786e..b320eef3e3a 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -62,7 +62,7 @@ impl ReturnFlags { /// The flags used to change the behavior of a contract call. #[must_use] -#[derive(Copy, Clone, Debug, Default)] +#[derive(Copy, Clone, Debug)] pub struct CallFlags { forward_input: bool, clone_input: bool, @@ -70,6 +70,17 @@ pub struct CallFlags { allow_reentry: bool, } +impl Default for CallFlags { + fn default() -> Self { + Self { + forward_input: false, + clone_input: false, + tail_call: false, + allow_reentry: true, + } + } +} + impl CallFlags { /// Forwards the input for the current function to the callee. /// @@ -109,10 +120,10 @@ impl CallFlags { self } - /// Allow the callee to reenter into the current contract. + /// Disallow the callee to reenter into the current contract. /// /// Without this flag any reentrancy into the current contract that originates from - /// the callee (or any of its callees) is denied. This includes the first callee: + /// the callee (or any of its callees) is allowed. This includes the first callee: /// You cannot call into yourself with this flag set. pub const fn set_allow_reentry(mut self, allow_reentry: bool) -> Self { self.allow_reentry = allow_reentry; @@ -534,4 +545,8 @@ pub trait TypedEnvBackend: EnvBackend { where E: Environment, Call: scale::Encode; + + fn reentrance_count(&mut self) -> u32 + where + E: Environment; } diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 794126349aa..dfa2729450c 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -572,4 +572,11 @@ impl TypedEnvBackend for EnvInstance { { unimplemented!("off-chain environment does not support `call_runtime`") } + + fn reentrance_count(&mut self) -> u32 + where + E: Environment, + { + unimplemented!("off-chain environment does not support `reentrance_count`") + } } diff --git a/crates/env/src/engine/on_chain/ext/riscv32.rs b/crates/env/src/engine/on_chain/ext/riscv32.rs index 90809da9c1a..d9bb9beeba3 100644 --- a/crates/env/src/engine/on_chain/ext/riscv32.rs +++ b/crates/env/src/engine/on_chain/ext/riscv32.rs @@ -388,6 +388,11 @@ pub fn sr25519_verify( ret_code.into() } +pub fn reentrance_count() -> u32 { + let ret_val = sys::call0(FUNC_ID); + ret_val.into_u32() +} + pub fn is_contract(account_id: &[u8]) -> bool { let ret_val = sys::call(FUNC_ID, Ptr32::from_slice(account_id)); ret_val.into_bool() diff --git a/crates/env/src/engine/on_chain/ext/wasm32.rs b/crates/env/src/engine/on_chain/ext/wasm32.rs index 49f4dc37bc5..82e0413942b 100644 --- a/crates/env/src/engine/on_chain/ext/wasm32.rs +++ b/crates/env/src/engine/on_chain/ext/wasm32.rs @@ -163,6 +163,8 @@ mod sys { ) -> ReturnCode; pub fn call_runtime(call_ptr: Ptr32<[u8]>, call_len: u32) -> ReturnCode; + + pub fn reentrance_count() -> ReturnCode; } #[link(wasm_import_module = "seal1")] @@ -604,6 +606,11 @@ pub fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result ret_code.into() } +pub fn reentrance_count() -> u32 { + let ret_code = unsafe { sys::reentrance_count() }; + ret_code.into_u32() +} + pub fn sr25519_verify( signature: &[u8; 64], message: &[u8], diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index 9de712d4dd8..c307bcf6613 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -596,4 +596,11 @@ impl TypedEnvBackend for EnvInstance { let enc_call = scope.take_encoded(call); ext::call_runtime(enc_call).map_err(Into::into) } + + fn reentrance_count(&mut self) -> u32 + where + E: Environment, + { + ext::reentrance_count() + } } diff --git a/crates/env/src/tests.rs b/crates/env/src/tests.rs index 9bebca43b09..7ac9c3e95c6 100644 --- a/crates/env/src/tests.rs +++ b/crates/env/src/tests.rs @@ -70,34 +70,34 @@ fn test_call_flags() { // enable each flag one after the other let flags = flags.set_forward_input(true); assert!(flags.forward_input()); - assert_eq!(flags.into_u32(), 0b0000_0001); + assert_eq!(flags.into_u32(), 0b0000_1001); let flags = flags.set_clone_input(true); assert!(flags.clone_input()); - assert_eq!(flags.into_u32(), 0b0000_0011); + assert_eq!(flags.into_u32(), 0b0000_1011); let flags = flags.set_tail_call(true); assert!(flags.tail_call()); - assert_eq!(flags.into_u32(), 0b0000_0111); - - let flags = flags.set_allow_reentry(true); - assert!(flags.allow_reentry()); assert_eq!(flags.into_u32(), 0b0000_1111); - // disable each flag one after the other - let flags = flags.set_allow_reentry(false); - assert!(!flags.allow_reentry()); + let flags = flags.set_deny_reentry(true); + assert!(flags.deny_reentry()); assert_eq!(flags.into_u32(), 0b0000_0111); + // disable each flag one after the other + let flags = flags.set_deny_reentry(false); + assert!(!flags.deny_reentry()); + assert_eq!(flags.into_u32(), 0b0000_1111); + let flags = flags.set_tail_call(false); assert!(!flags.tail_call()); - assert_eq!(flags.into_u32(), 0b0000_0011); + assert_eq!(flags.into_u32(), 0b0000_1011); let flags = flags.set_clone_input(false); assert!(!flags.clone_input()); - assert_eq!(flags.into_u32(), 0b0000_0001); + assert_eq!(flags.into_u32(), 0b0000_1001); let flags = flags.set_forward_input(false); assert!(!flags.forward_input()); - assert_eq!(flags.into_u32(), 0b0000_0000); + assert_eq!(flags.into_u32(), 0b0000_1000); } diff --git a/crates/ink/codegen/src/generator/dispatch.rs b/crates/ink/codegen/src/generator/dispatch.rs index fe39a92d8d8..f62de29fb0c 100644 --- a/crates/ink/codegen/src/generator/dispatch.rs +++ b/crates/ink/codegen/src/generator/dispatch.rs @@ -189,6 +189,7 @@ impl Dispatch<'_> { let constructor_span = constructor.span(); let constructor_ident = constructor.ident(); let payable = constructor.is_payable(); + let allow_reentrancy = constructor.allow_reentrancy(); let selector_id = constructor.composed_selector().into_be_u32().hex_padded_suffixed(); let selector_bytes = constructor.composed_selector().hex_lits(); let cfg_attrs = constructor.get_cfg_attrs(constructor_span); @@ -214,6 +215,7 @@ impl Dispatch<'_> { #storage_ident::#constructor_ident(#( #input_bindings ),* ) }; const PAYABLE: ::core::primitive::bool = #payable; + const ALLOW_REENTRANCY: ::core::primitive::bool = #allow_reentrancy; const SELECTOR: [::core::primitive::u8; 4usize] = [ #( #selector_bytes ),* ]; const LABEL: &'static ::core::primitive::str = ::core::stringify!(#constructor_ident); } @@ -241,6 +243,7 @@ impl Dispatch<'_> { let message_span = message.span(); let message_ident = message.ident(); let payable = message.is_payable(); + let allow_reentrancy = message.allow_reentrancy(); let mutates = message.receiver().is_ref_mut(); let selector_id = message.composed_selector().into_be_u32().hex_padded_suffixed(); let selector_bytes = message.composed_selector().hex_lits(); @@ -265,6 +268,7 @@ impl Dispatch<'_> { }; const SELECTOR: [::core::primitive::u8; 4usize] = [ #( #selector_bytes ),* ]; const PAYABLE: ::core::primitive::bool = #payable; + const ALLOW_REENTRANCY: ::core::primitive::bool = #allow_reentrancy; const MUTATES: ::core::primitive::bool = #mutates; const LABEL: &'static ::core::primitive::str = ::core::stringify!(#message_ident); } @@ -295,6 +299,11 @@ impl Dispatch<'_> { as #trait_path>::__ink_TraitInfo as ::ink::reflect::TraitMessageInfo<#local_id>>::PAYABLE }}; + let allow_reentrancy = quote! {{ + <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env> + as #trait_path>::__ink_TraitInfo + as ::ink::reflect::TraitMessageInfo<#local_id>>::ALLOW_REENTRANCY + }}; let selector = quote! {{ <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env> as #trait_path>::__ink_TraitInfo @@ -325,6 +334,7 @@ impl Dispatch<'_> { }; const SELECTOR: [::core::primitive::u8; 4usize] = #selector; const PAYABLE: ::core::primitive::bool = #payable; + const ALLOW_REENTRANCY: ::core::primitive::bool = #allow_reentrancy; const MUTATES: ::core::primitive::bool = #mutates; const LABEL: &'static ::core::primitive::str = #label; } @@ -350,6 +360,10 @@ impl Dispatch<'_> { let any_constructor_accept_payment = self.any_constructor_accepts_payment(constructors); let any_message_accepts_payment = self.any_message_accepts_payment(messages); + let any_constructor_accept_reentrancy = + self.any_constructor_accepts_reentrancy(constructors); + let any_message_accepts_reentrancy = + self.any_message_accepts_reentrancy(messages); quote_spanned!(span=> #[allow(clippy::nonminimal_bool)] fn internal_deploy() { @@ -358,6 +372,11 @@ impl Dispatch<'_> { .unwrap_or_else(|error| ::core::panic!("{}", error)) } + if !#any_constructor_accept_reentrancy { + ::ink::codegen::deny_reentrancy::<<#storage_ident as ::ink::env::ContractEnv>::Env>() + .unwrap_or_else(|error| ::core::panic!("{}", error)) + } + let dispatchable = match ::ink::env::decode_input::< <#storage_ident as ::ink::reflect::ContractConstructorDecoder>::Type, >() { @@ -394,6 +413,11 @@ impl Dispatch<'_> { .unwrap_or_else(|error| ::core::panic!("{}", error)) } + if !#any_message_accepts_reentrancy { + ::ink::codegen::deny_reentrancy::<<#storage_ident as ::ink::env::ContractEnv>::Env>() + .unwrap_or_else(|error| ::core::panic!("{}", error)) + } + let dispatchable = match ::ink::env::decode_input::< <#storage_ident as ::ink::reflect::ContractMessageDecoder>::Type, >() { @@ -560,6 +584,9 @@ impl Dispatch<'_> { let deny_payment = quote_spanned!(constructor_span=> !<#storage_ident as ::ink::reflect::DispatchableConstructorInfo< #id >>::PAYABLE ); + let deny_reentrancy = quote_spanned!(constructor_span=> + !<#storage_ident as ::ink::reflect::DispatchableConstructorInfo< #id >>::ALLOW_REENTRANCY + ); let constructor_value = quote_spanned!(constructor_span=> <::ink::reflect::ConstructorOutputValue<#constructor_output> as ::ink::reflect::ConstructorOutput::<#storage_ident>> @@ -568,6 +595,9 @@ impl Dispatch<'_> { let constructor_accept_payment_assignment = self.any_constructor_accepts_payment(constructors); + let constructor_accept_reentrancy_assignment = + self.any_constructor_accepts_reentrancy(constructors); + quote_spanned!(constructor_span=> #( #cfg_attrs )* Self::#constructor_ident(input) => { @@ -577,6 +607,11 @@ impl Dispatch<'_> { <#storage_ident as ::ink::env::ContractEnv>::Env>()?; } + if #constructor_accept_reentrancy_assignment && #deny_reentrancy { + ::ink::codegen::deny_reentrancy::< + <#storage_ident as ::ink::env::ContractEnv>::Env>()?; + } + let result: #constructor_output = #constructor_callable(input); let output_value = ::ink::reflect::ConstructorOutputValue::new(result); let output_result = #constructor_value::as_result(&output_value); @@ -763,6 +798,9 @@ impl Dispatch<'_> { let deny_payment = quote_spanned!(message_span=> !<#storage_ident as ::ink::reflect::DispatchableMessageInfo< #id >>::PAYABLE ); + let deny_reentrancy = quote_spanned!(message_span=> + !<#storage_ident as ::ink::reflect::DispatchableMessageInfo< #id >>::ALLOW_REENTRANCY + ); let mutates_storage = quote_spanned!(message_span=> <#storage_ident as ::ink::reflect::DispatchableMessageInfo< #id >>::MUTATES ); @@ -770,6 +808,9 @@ impl Dispatch<'_> { let any_message_accepts_payment = self.any_message_accepts_payment(messages); + let any_message_accepts_reentrancy = + self.any_message_accepts_reentrancy(messages); + quote_spanned!(message_span=> #( #cfg_attrs )* Self::#message_ident(input) => { @@ -779,6 +820,11 @@ impl Dispatch<'_> { <#storage_ident as ::ink::env::ContractEnv>::Env>()?; } + if #any_message_accepts_reentrancy && #deny_reentrancy { + ::ink::codegen::deny_reentrancy::< + <#storage_ident as ::ink::env::ContractEnv>::Env>()?; + } + let result: #message_output = #message_callable(&mut contract, input); let is_reverted = ::ink::is_result_type!(#message_output) && ::ink::is_result_err!(result); @@ -913,6 +959,45 @@ impl Dispatch<'_> { ) } + /// Generates code to express if any dispatchable ink! message accepts reentrancy. + /// + /// Generates code in the form of variable assignments + /// which can be conditionally omitted + /// in which case the default assignment `let message_{id} = false` exists. + /// + /// This information can be used to speed-up dispatch since denying of payment + /// can be generalized to work before dispatch happens if none of the ink! messages + /// accept payment anyways. + fn any_message_accepts_reentrancy( + &self, + messages: &[MessageDispatchable], + ) -> TokenStream2 { + let span = self.contract.module().storage().span(); + let storage_ident = self.contract.module().storage().ident(); + let message_is_reentrant = messages + .iter() + .enumerate() + .map(|(index, item)| { + let message_span = item.message.span(); + let cfg_attrs = item.message.get_cfg_attrs(message_span); + let id = item.id.clone(); + let ident = quote::format_ident!("message_{}", index); + quote_spanned!(message_span=> + { + let #ident = false; + #( #cfg_attrs )* + let #ident = <#storage_ident as ::ink::reflect::DispatchableMessageInfo< #id >>::ALLOW_REENTRANCY; + #ident + } + ) + }); + quote_spanned!(span=> + { + false #( || #message_is_reentrant )* + } + ) + } + /// Generates code to express if any dispatchable ink! constructor accepts payment. /// /// Generates code in the form of variable assignments @@ -948,4 +1033,40 @@ impl Dispatch<'_> { } ) } + + /// Generates code to express if any dispatchable ink! constructor accepts reentrancy. + /// + /// Generates code in the form of variable assignments + /// which can be conditionally omitted + /// in which case the default assignment `let constructor_{id} = false` exists. + /// + /// This information can be used to speed-up dispatch since denying of payment + /// can be generalized to work before dispatch happens if none of the ink! + /// constructors accept payment anyways. + fn any_constructor_accepts_reentrancy( + &self, + constructors: &[ConstructorDispatchable], + ) -> TokenStream2 { + let span = self.contract.module().storage().span(); + let storage_ident = self.contract.module().storage().ident(); + let constructor_is_reentrant = constructors.iter().enumerate().map(|(index, item)| { + let constructor_span = item.constructor.span(); + let cfg_attrs = item.constructor.get_cfg_attrs(constructor_span); + let id = item.id.clone(); + let ident = quote::format_ident!("constructor_{}", index); + quote_spanned!(constructor_span=> + { + let #ident = false; + #( #cfg_attrs )* + let #ident = <#storage_ident as ::ink::reflect::DispatchableConstructorInfo< #id >>::ALLOW_REENTRANCY; + #ident + } + ) + }); + quote_spanned!(span=> + { + false #( || #constructor_is_reentrant )* + } + ) + } } diff --git a/crates/ink/codegen/src/generator/item_impls.rs b/crates/ink/codegen/src/generator/item_impls.rs index 0beb8ce386e..4812fe188fe 100644 --- a/crates/ink/codegen/src/generator/item_impls.rs +++ b/crates/ink/codegen/src/generator/item_impls.rs @@ -91,6 +91,15 @@ impl ItemImpls<'_> { }> = ::ink::codegen::TraitMessagePayable::; ) }); + let message_guard_reentrant = message.allow_reentrancy().then(|| { + quote_spanned!(message_span=> + const _: ::ink::codegen::TraitMessageReentrant<{ + <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env> + as #trait_path>::__ink_TraitInfo + as ::ink::reflect::TraitMessageInfo<#message_local_id>>::ALLOW_REENTRANCY + }> = ::ink::codegen::TraitMessageReentrant::; + ) + }); let message_guard_selector = message.user_provided_selector().map(|selector| { let given_selector = selector.into_be_u32().hex_padded_suffixed(); quote_spanned!(message_span=> @@ -105,6 +114,7 @@ impl ItemImpls<'_> { }); quote_spanned!(message_span=> #message_guard_payable + #message_guard_reentrant #message_guard_selector ) }); diff --git a/crates/ink/codegen/src/generator/metadata.rs b/crates/ink/codegen/src/generator/metadata.rs index 909a503448e..a40019575ae 100644 --- a/crates/ink/codegen/src/generator/metadata.rs +++ b/crates/ink/codegen/src/generator/metadata.rs @@ -144,6 +144,7 @@ impl Metadata<'_> { let selector_bytes = constructor.composed_selector().hex_lits(); let selector_id = constructor.composed_selector().into_be_u32(); let is_payable = constructor.is_payable(); + let allow_reentrancy = constructor.allow_reentrancy(); let is_default = constructor.is_default(); let constructor = constructor.callable(); let ident = constructor.ident(); @@ -161,6 +162,7 @@ impl Metadata<'_> { #( #args ),* ]) .payable(#is_payable) + .allow_reentrancy(#allow_reentrancy) .default(#is_default) .returns(#ret_ty) .docs([ @@ -241,6 +243,7 @@ impl Metadata<'_> { .filter_map(|attr| attr.extract_docs()); let selector_bytes = message.composed_selector().hex_lits(); let is_payable = message.is_payable(); + let allow_reentrancy = message.allow_reentrancy(); let is_default = message.is_default(); let message = message.callable(); let mutates = message.receiver().is_ref_mut(); @@ -260,6 +263,7 @@ impl Metadata<'_> { .returns(#ret_ty) .mutates(#mutates) .payable(#is_payable) + .allow_reentrancy(#allow_reentrancy) .default(#is_default) .docs([ #( #docs ),* @@ -305,6 +309,11 @@ impl Metadata<'_> { as #trait_path>::__ink_TraitInfo as ::ink::reflect::TraitMessageInfo<#local_id>>::PAYABLE }}; + let allow_reentrancy = quote! {{ + <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env> + as #trait_path>::__ink_TraitInfo + as ::ink::reflect::TraitMessageInfo<#local_id>>::ALLOW_REENTRANCY + }}; let selector = quote! {{ <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env> as #trait_path>::__ink_TraitInfo @@ -322,6 +331,7 @@ impl Metadata<'_> { .returns(#ret_ty) .mutates(#mutates) .payable(#is_payable) + .allow_reentrancy(#allow_reentrancy) .docs([ #( #message_docs ),* ]) 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 6d6471e53e3..05a61e222e1 100644 --- a/crates/ink/codegen/src/generator/trait_def/trait_registry.rs +++ b/crates/ink/codegen/src/generator/trait_def/trait_registry.rs @@ -325,10 +325,13 @@ impl TraitRegistry<'_> { let local_id = message.local_id(); let selector_bytes = selector.hex_lits(); let is_payable = message.ink_attrs().is_payable(); + let allow_reentrancy = message.ink_attrs().allow_reentrancy(); quote_spanned!(span=> impl ::ink::reflect::TraitMessageInfo<#local_id> for #trait_info_ident { const PAYABLE: ::core::primitive::bool = #is_payable; + const ALLOW_REENTRANCY: ::core::primitive::bool = #allow_reentrancy; + const SELECTOR: [::core::primitive::u8; 4usize] = [ #( #selector_bytes ),* ]; } ) diff --git a/crates/ink/ir/src/ir/attrs.rs b/crates/ink/ir/src/ir/attrs.rs index 4cd8320f434..475e29f46fa 100644 --- a/crates/ink/ir/src/ir/attrs.rs +++ b/crates/ink/ir/src/ir/attrs.rs @@ -288,6 +288,12 @@ impl InkAttribute { .any(|arg| matches!(arg.kind(), AttributeArg::Payable)) } + /// Returns `true` if the ink! attribute contains the `allow_reentrancy` argument. + pub fn allow_reentrancy(&self) -> bool { + self.args() + .any(|arg| matches!(arg.kind(), AttributeArg::AllowReentrancy)) + } + /// Returns `true` if the ink! attribute contains the `default` argument. pub fn is_default(&self) -> bool { self.args() @@ -356,6 +362,8 @@ pub enum AttributeArgKind { Constructor, /// `#[ink(payable)]` Payable, + /// `#[ink(allow_reentrancy)]` + AllowReentrancy, /// `#[ink(default)]` Default, /// `#[ink(selector = _)]` @@ -406,6 +414,11 @@ pub enum AttributeArg { /// Applied on ink! constructors or messages in order to specify that they /// can receive funds from callers. Payable, + /// `#[ink(allow_reentrancy)]` + /// + /// Applied on ink! constructors or messages in order to indicate + /// they are reentrant. + AllowReentrancy, /// Applied on ink! constructors or messages in order to indicate /// they are default. Default, @@ -457,6 +470,7 @@ impl core::fmt::Display for AttributeArgKind { Self::Message => write!(f, "message"), Self::Constructor => write!(f, "constructor"), Self::Payable => write!(f, "payable"), + Self::AllowReentrancy => write!(f, "allow_reentrancy"), Self::Selector => { write!(f, "selector = S:[u8; 4] || _") } @@ -483,6 +497,7 @@ impl AttributeArg { Self::Message => AttributeArgKind::Message, Self::Constructor => AttributeArgKind::Constructor, Self::Payable => AttributeArgKind::Payable, + Self::AllowReentrancy => AttributeArgKind::AllowReentrancy, Self::Selector(_) => AttributeArgKind::Selector, Self::Extension(_) => AttributeArgKind::Extension, Self::Namespace(_) => AttributeArgKind::Namespace, @@ -502,6 +517,7 @@ impl core::fmt::Display for AttributeArg { Self::Message => write!(f, "message"), Self::Constructor => write!(f, "constructor"), Self::Payable => write!(f, "payable"), + Self::AllowReentrancy => write!(f, "allow_reentrancy"), Self::Selector(selector) => core::fmt::Display::fmt(&selector, f), Self::Extension(extension) => { write!(f, "extension = {:?}", extension.into_u32()) @@ -972,6 +988,7 @@ impl Parse for AttributeFrag { "event" => Ok(AttributeArg::Event), "anonymous" => Ok(AttributeArg::Anonymous), "payable" => Ok(AttributeArg::Payable), + "allow_reentrancy" => Ok(AttributeArg::AllowReentrancy), "default" => Ok(AttributeArg::Default), "impl" => Ok(AttributeArg::Implementation), _ => match ident.to_string().as_str() { @@ -1411,6 +1428,7 @@ mod tests { constructor, event, payable, + allow_reentrancy, impl, )] }, @@ -1420,6 +1438,7 @@ mod tests { AttributeArg::Constructor, AttributeArg::Event, AttributeArg::Payable, + AttributeArg::AllowReentrancy, AttributeArg::Implementation, ])), ); diff --git a/crates/ink/ir/src/ir/item_impl/callable.rs b/crates/ink/ir/src/ir/item_impl/callable.rs index e1a1d22e1cb..fbfe12c80e2 100644 --- a/crates/ink/ir/src/ir/item_impl/callable.rs +++ b/crates/ink/ir/src/ir/item_impl/callable.rs @@ -116,6 +116,10 @@ where ::is_payable(self.callable) } + fn allow_reentrancy(&self) -> bool { + ::allow_reentrancy(self.callable) + } + fn is_default(&self) -> bool { ::is_default(self.callable) } @@ -174,6 +178,13 @@ pub trait Callable { /// Flagging as payable is done using the `#[ink(payable)]` attribute. fn is_payable(&self) -> bool; + /// Returns `true` if the ink! callable is flagged as reentrant. + /// + /// # Note + /// + /// Flagging as reentrant is done using the `#[ink(allow_reentrancy)]` attribute. + fn allow_reentrancy(&self) -> bool; + /// Returns `true` if the ink! callable is flagged as default. /// /// # Note diff --git a/crates/ink/ir/src/ir/item_impl/constructor.rs b/crates/ink/ir/src/ir/item_impl/constructor.rs index 6a1b5ff2bfd..84e0b08bc8b 100644 --- a/crates/ink/ir/src/ir/item_impl/constructor.rs +++ b/crates/ink/ir/src/ir/item_impl/constructor.rs @@ -70,6 +70,8 @@ pub struct Constructor { pub(super) item: syn::ImplItemFn, /// If the ink! constructor can receive funds. is_payable: bool, + /// If the ink! constructor can be called multiple times by reentering. + allow_reentrancy: bool, /// If the ink! constructor is default. is_default: bool, /// An optional user provided selector. @@ -141,6 +143,7 @@ impl Constructor { match arg.kind() { ir::AttributeArg::Constructor | ir::AttributeArg::Payable + | ir::AttributeArg::AllowReentrancy | ir::AttributeArg::Default | ir::AttributeArg::Selector(_) => Ok(()), _ => Err(None), @@ -159,11 +162,13 @@ impl TryFrom for Constructor { Self::ensure_no_self_receiver(&method_item)?; let (ink_attrs, other_attrs) = Self::sanitize_attributes(&method_item)?; let is_payable = ink_attrs.is_payable(); + let allow_reentrancy = ink_attrs.allow_reentrancy(); let is_default = ink_attrs.is_default(); let selector = ink_attrs.selector(); Ok(Constructor { selector, is_payable, + allow_reentrancy, is_default, item: syn::ImplItemFn { attrs: other_attrs, @@ -201,6 +206,10 @@ impl Callable for Constructor { self.is_payable } + fn allow_reentrancy(&self) -> bool { + self.allow_reentrancy + } + fn is_default(&self) -> bool { self.is_default } diff --git a/crates/ink/ir/src/ir/item_impl/message.rs b/crates/ink/ir/src/ir/item_impl/message.rs index a60585f702e..63d73323c49 100644 --- a/crates/ink/ir/src/ir/item_impl/message.rs +++ b/crates/ink/ir/src/ir/item_impl/message.rs @@ -100,6 +100,9 @@ pub struct Message { pub(super) item: syn::ImplItemFn, /// If the ink! message can receive funds. is_payable: bool, + /// If it is allowed to re-enter the corresponding ink! message. If reentrancy is disabled by default, + /// this flag can be used to enable it for a specific message. + allow_reentrancy: bool, /// If the ink! message is default. is_default: bool, /// An optional user provided selector. @@ -187,6 +190,7 @@ impl Message { match arg.kind() { ir::AttributeArg::Message | ir::AttributeArg::Payable + | ir::AttributeArg::AllowReentrancy | ir::AttributeArg::Default | ir::AttributeArg::Selector(_) => Ok(()), _ => Err(None), @@ -205,10 +209,12 @@ impl TryFrom for Message { Self::ensure_not_return_self(&method_item)?; let (ink_attrs, other_attrs) = Self::sanitize_attributes(&method_item)?; let is_payable = ink_attrs.is_payable(); + let allow_reentrancy = ink_attrs.allow_reentrancy(); let is_default = ink_attrs.is_default(); let selector = ink_attrs.selector(); Ok(Self { is_payable, + allow_reentrancy, is_default, selector, item: syn::ImplItemFn { @@ -247,6 +253,10 @@ impl Callable for Message { self.is_payable } + fn allow_reentrancy(&self) -> bool { + self.allow_reentrancy + } + fn is_default(&self) -> bool { self.is_default } diff --git a/crates/ink/ir/src/ir/trait_def/item/trait_item.rs b/crates/ink/ir/src/ir/trait_def/item/trait_item.rs index 20178613498..9e17bf3df75 100644 --- a/crates/ink/ir/src/ir/trait_def/item/trait_item.rs +++ b/crates/ink/ir/src/ir/trait_def/item/trait_item.rs @@ -92,6 +92,7 @@ impl<'a> InkTraitMessage<'a> { Err(Some(format_err!(arg.span(), "wildcard selectors are only supported for inherent ink! messages or constructors, not for traits."))), ir::AttributeArg::Message | ir::AttributeArg::Payable + | ir::AttributeArg::AllowReentrancy | ir::AttributeArg::Default | ir::AttributeArg::Selector(_) => Ok(()), _ => Err(None), diff --git a/crates/ink/src/codegen/dispatch/execution.rs b/crates/ink/src/codegen/dispatch/execution.rs index 1ed27639a1b..81079975be9 100644 --- a/crates/ink/src/codegen/dispatch/execution.rs +++ b/crates/ink/src/codegen/dispatch/execution.rs @@ -31,3 +31,17 @@ where } Ok(()) } + +#[inline] +pub fn deny_reentrancy() -> Result<(), DispatchError> +where + E: Environment, +{ + let reentrance_count = ink_env::reentrance_count::(); + + if reentrance_count != 0 { + return Err(DispatchError::ReentranceDenied) + } + + Ok(()) +} diff --git a/crates/ink/src/codegen/dispatch/mod.rs b/crates/ink/src/codegen/dispatch/mod.rs index 8faccea3d18..ee4ac1524fe 100644 --- a/crates/ink/src/codegen/dispatch/mod.rs +++ b/crates/ink/src/codegen/dispatch/mod.rs @@ -17,7 +17,10 @@ mod info; mod type_check; pub use self::{ - execution::deny_payment, + execution::{ + deny_payment, + deny_reentrancy, + }, info::ContractCallBuilder, type_check::{ DispatchInput, diff --git a/crates/ink/src/codegen/mod.rs b/crates/ink/src/codegen/mod.rs index fa297d67a6c..4c986df1987 100644 --- a/crates/ink/src/codegen/mod.rs +++ b/crates/ink/src/codegen/mod.rs @@ -23,6 +23,7 @@ pub mod utils; pub use self::{ dispatch::{ deny_payment, + deny_reentrancy, ContractCallBuilder, DispatchInput, DispatchOutput, @@ -37,6 +38,7 @@ pub use self::{ TraitCallForwarder, TraitCallForwarderFor, TraitMessagePayable, + TraitMessageReentrant, TraitMessageSelector, }, }; diff --git a/crates/ink/src/codegen/trait_def/mod.rs b/crates/ink/src/codegen/trait_def/mod.rs index 2a922ec338f..7dc62da33ac 100644 --- a/crates/ink/src/codegen/trait_def/mod.rs +++ b/crates/ink/src/codegen/trait_def/mod.rs @@ -23,6 +23,7 @@ pub use self::{ }, trait_message::{ TraitMessagePayable, + TraitMessageReentrant, TraitMessageSelector, }, }; diff --git a/crates/ink/src/codegen/trait_def/trait_message.rs b/crates/ink/src/codegen/trait_def/trait_message.rs index e853ad15059..5d282924759 100644 --- a/crates/ink/src/codegen/trait_def/trait_message.rs +++ b/crates/ink/src/codegen/trait_def/trait_message.rs @@ -22,6 +22,16 @@ /// the same ink! message as defined by the ink! trait message. pub struct TraitMessagePayable; +/// Used as `allow_reentrancy` property guard for ink! trait messages. +/// +/// # Note +/// +/// When an ink! trait message is annotated with `#[ink(allow_reentrancy)]` +/// a compile time check is generated by ink! to guard that the +/// reentrancy allowance of the ink! trait message matches the reentrancy +/// allowance of the same ink! message as defined by the ink! trait message. +pub struct TraitMessageReentrant; + /// Used as `selector` property guard for ink! trait messages. /// /// # Note diff --git a/crates/ink/src/reflect/dispatch.rs b/crates/ink/src/reflect/dispatch.rs index 2ff9822f13b..3e574c3f810 100644 --- a/crates/ink/src/reflect/dispatch.rs +++ b/crates/ink/src/reflect/dispatch.rs @@ -115,6 +115,8 @@ pub trait DispatchableMessageInfo { const MUTATES: bool; /// Yields `true` if the dispatchable ink! message is payable. const PAYABLE: bool; + /// Yields `true` if the dispatchable ink! message allows reentrancy. + const ALLOW_REENTRANCY: bool; /// The selectors of the dispatchable ink! message. const SELECTOR: [u8; 4]; /// The label of the dispatchable ink! message. @@ -208,6 +210,9 @@ pub trait DispatchableConstructorInfo { /// Yields `true` if the dispatchable ink! constructor is payable. const PAYABLE: bool; + /// Yields `true` if the dispatchable ink! constructor allows reentrancy. + const ALLOW_REENTRANCY: bool; + /// The selectors of the dispatchable ink! constructor. const SELECTOR: [u8; 4]; @@ -496,6 +501,8 @@ pub enum DispatchError { CouldNotReadInput, /// Invalidly paid an unpayable dispatchable. PaidUnpayableMessage, + /// Tried to recursively call the same ink! message, which is not allowed. + ReentranceDenied, } impl Display for DispatchError { @@ -514,6 +521,7 @@ impl DispatchError { Self::InvalidParameters => "unable to decode input", Self::CouldNotReadInput => "could not read input", Self::PaidUnpayableMessage => "paid an unpayable message", + Self::ReentranceDenied => "reentrance denied", } } } diff --git a/crates/ink/src/reflect/trait_def/info.rs b/crates/ink/src/reflect/trait_def/info.rs index 5b133888278..a96840693f9 100644 --- a/crates/ink/src/reflect/trait_def/info.rs +++ b/crates/ink/src/reflect/trait_def/info.rs @@ -124,6 +124,10 @@ pub trait TraitMessageInfo { /// Is `true` if the ink! trait message has been annotated with `#[ink(payable)]`. const PAYABLE: bool; + /// Is `true` if the ink! trait message has been annotated with + /// `#[ink(allow_reentrancy)]`. + const ALLOW_REENTRANCY: bool; + /// The unique selector of the ink! trait message. /// /// This might have been adjusted using `#[ink(selector = N:u32)]` at the diff --git a/crates/ink/tests/ui/contract/fail/constructor-input-non-codec.stderr b/crates/ink/tests/ui/contract/fail/constructor-input-non-codec.stderr index 2218f22a1c8..737f9bb89e1 100644 --- a/crates/ink/tests/ui/contract/fail/constructor-input-non-codec.stderr +++ b/crates/ink/tests/ui/contract/fail/constructor-input-non-codec.stderr @@ -2,7 +2,7 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeDecode` is not satisfied --> tests/ui/contract/fail/constructor-input-non-codec.rs:11:28 | 11 | pub fn constructor(_input: NonCodecType) -> Self { - | ^^^^^^ the trait `WrapperTypeDecode` is not implemented for `NonCodecType` + | ^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `NonCodecType` | = help: the following other types implement trait `WrapperTypeDecode`: Arc @@ -21,8 +21,10 @@ note: required by a bound in `DispatchInput` error[E0277]: the trait bound `NonCodecType: WrapperTypeDecode` is not satisfied --> tests/ui/contract/fail/constructor-input-non-codec.rs:11:9 | -11 | pub fn constructor(_input: NonCodecType) -> Self { - | ^^^ the trait `WrapperTypeDecode` is not implemented for `NonCodecType` +11 | / pub fn constructor(_input: NonCodecType) -> Self { +12 | | Self {} +13 | | } + | |_________^ the trait `WrapperTypeDecode` is not implemented for `NonCodecType` | = help: the following other types implement trait `WrapperTypeDecode`: Arc @@ -33,11 +35,13 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeDecode` is not satisfied error[E0277]: the trait bound `NonCodecType: WrapperTypeEncode` is not satisfied --> tests/ui/contract/fail/constructor-input-non-codec.rs:1:1 | -1 | #[ink::contract] - | ^^^^^^^^^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `NonCodecType` +1 | #[ink::contract] + | ^^^^^^^^^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `NonCodecType` ... -11 | pub fn constructor(_input: NonCodecType) -> Self { - | --- required by a bound introduced by this call +11 | / pub fn constructor(_input: NonCodecType) -> Self { +12 | | Self {} +13 | | } + | |_________- required by a bound introduced by this call | = help: the following other types implement trait `WrapperTypeEncode`: &T diff --git a/crates/ink/tests/ui/contract/fail/constructor-multiple-wildcard-selectors.stderr b/crates/ink/tests/ui/contract/fail/constructor-multiple-wildcard-selectors.stderr index e8c23317b1d..d414f29d565 100644 --- a/crates/ink/tests/ui/contract/fail/constructor-multiple-wildcard-selectors.stderr +++ b/crates/ink/tests/ui/contract/fail/constructor-multiple-wildcard-selectors.stderr @@ -1,11 +1,15 @@ error: encountered ink! constructor with overlapping wildcard selectors --> tests/ui/contract/fail/constructor-multiple-wildcard-selectors.rs:13:9 | -13 | pub fn constructor2() -> Self { - | ^^^ +13 | / pub fn constructor2() -> Self { +14 | | Self {} +15 | | } + | |_________^ error: first ink! constructor with overlapping wildcard selector here - --> tests/ui/contract/fail/constructor-multiple-wildcard-selectors.rs:8:9 - | -8 | pub fn constructor1() -> Self { - | ^^^ + --> tests/ui/contract/fail/constructor-multiple-wildcard-selectors.rs:8:9 + | +8 | / pub fn constructor1() -> Self { +9 | | Self {} +10 | | } + | |_________^ diff --git a/crates/ink/tests/ui/contract/fail/constructor-return-result-invalid.stderr b/crates/ink/tests/ui/contract/fail/constructor-return-result-invalid.stderr index 7101544b93e..7ebab3a7c7e 100644 --- a/crates/ink/tests/ui/contract/fail/constructor-return-result-invalid.stderr +++ b/crates/ink/tests/ui/contract/fail/constructor-return-result-invalid.stderr @@ -1,8 +1,10 @@ error[E0277]: the trait bound `ConstructorOutputValue>: ConstructorOutput` is not satisfied --> tests/ui/contract/fail/constructor-return-result-invalid.rs:14:9 | -14 | pub fn constructor() -> Result { - | ^^^ the trait `ConstructorOutput` is not implemented for `ConstructorOutputValue>` +14 | / pub fn constructor() -> Result { +15 | | Ok(5_u8) +16 | | } + | |_________^ the trait `ConstructorOutput` is not implemented for `ConstructorOutputValue>` | = help: the following other types implement trait `ConstructorOutput`: ConstructorOutputValue diff --git a/crates/ink/tests/ui/contract/fail/constructor-return-result-non-codec-error.stderr b/crates/ink/tests/ui/contract/fail/constructor-return-result-non-codec-error.stderr index 0907502d5ae..21b92bc51bc 100644 --- a/crates/ink/tests/ui/contract/fail/constructor-return-result-non-codec-error.stderr +++ b/crates/ink/tests/ui/contract/fail/constructor-return-result-non-codec-error.stderr @@ -1,8 +1,10 @@ error[E0277]: the trait bound `Result, LangError>: Encode` is not satisfied --> tests/ui/contract/fail/constructor-return-result-non-codec-error.rs:13:9 | -13 | pub fn constructor() -> Result { - | ^^^ the trait `Encode` is not implemented for `Result, LangError>` +13 | / pub fn constructor() -> Result { +14 | | Ok(Self {}) +15 | | } + | |_________^ the trait `Encode` is not implemented for `Result, LangError>` | = help: the trait `Encode` is implemented for `Result` note: required by a bound in `return_value` @@ -17,8 +19,10 @@ note: required by a bound in `return_value` error[E0277]: the trait bound `contract::Error: WrapperTypeDecode` is not satisfied --> tests/ui/contract/fail/constructor-return-result-non-codec-error.rs:13:9 | -13 | pub fn constructor() -> Result { - | ^^^ the trait `WrapperTypeDecode` is not implemented for `contract::Error` +13 | / pub fn constructor() -> Result { +14 | | Ok(Self {}) +15 | | } + | |_________^ the trait `WrapperTypeDecode` is not implemented for `contract::Error` | = help: the following other types implement trait `WrapperTypeDecode`: Arc diff --git a/crates/ink/tests/ui/contract/fail/constructor-selector-and-wildcard-selector.stderr b/crates/ink/tests/ui/contract/fail/constructor-selector-and-wildcard-selector.stderr index d5730eee213..67893789961 100644 --- a/crates/ink/tests/ui/contract/fail/constructor-selector-and-wildcard-selector.stderr +++ b/crates/ink/tests/ui/contract/fail/constructor-selector-and-wildcard-selector.stderr @@ -2,10 +2,10 @@ error: encountered ink! attribute arguments with equal kinds --> tests/ui/contract/fail/constructor-selector-and-wildcard-selector.rs:7:51 | 7 | #[ink(constructor, selector = 0xCAFEBABA, selector = _)] - | ^^^^^^^^ + | ^^^^^^^^^^^^ error: first equal ink! attribute argument with equal kind here --> tests/ui/contract/fail/constructor-selector-and-wildcard-selector.rs:7:28 | 7 | #[ink(constructor, selector = 0xCAFEBABA, selector = _)] - | ^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/ink/tests/ui/contract/fail/constructor-self-receiver-03.stderr b/crates/ink/tests/ui/contract/fail/constructor-self-receiver-03.stderr index 99087f2b51c..7582f2a15c2 100644 --- a/crates/ink/tests/ui/contract/fail/constructor-self-receiver-03.stderr +++ b/crates/ink/tests/ui/contract/fail/constructor-self-receiver-03.stderr @@ -8,7 +8,7 @@ error[E0411]: cannot find type `Self` in this scope --> tests/ui/contract/fail/constructor-self-receiver-03.rs:8:35 | 4 | pub struct Contract {} - | --- `Self` not allowed in a constant item + | ---------------------- `Self` not allowed in a constant item ... 8 | pub fn constructor(this: &Self) -> Self { | ^^^^ `Self` is only available in impls, traits, and type definitions @@ -23,13 +23,15 @@ error[E0411]: cannot find type `Self` in this scope | ^^^^ `Self` is only available in impls, traits, and type definitions error[E0277]: the trait bound `&Contract: WrapperTypeDecode` is not satisfied - --> tests/ui/contract/fail/constructor-self-receiver-03.rs:8:9 - | -8 | pub fn constructor(this: &Self) -> Self { - | ^^^ the trait `WrapperTypeDecode` is not implemented for `&Contract` - | - = help: the following other types implement trait `WrapperTypeDecode`: - Arc - Box - Rc - = note: required for `&Contract` to implement `parity_scale_codec::Decode` + --> tests/ui/contract/fail/constructor-self-receiver-03.rs:8:9 + | +8 | / pub fn constructor(this: &Self) -> Self { +9 | | Self {} +10 | | } + | |_________^ the trait `WrapperTypeDecode` is not implemented for `&Contract` + | + = help: the following other types implement trait `WrapperTypeDecode`: + Arc + Box + Rc + = note: required for `&Contract` to implement `parity_scale_codec::Decode` diff --git a/crates/ink/tests/ui/contract/fail/impl-block-for-non-storage-01.stderr b/crates/ink/tests/ui/contract/fail/impl-block-for-non-storage-01.stderr index c6418df8cf0..b5dba1afb28 100644 --- a/crates/ink/tests/ui/contract/fail/impl-block-for-non-storage-01.stderr +++ b/crates/ink/tests/ui/contract/fail/impl-block-for-non-storage-01.stderr @@ -11,7 +11,7 @@ error[E0599]: no function or associated item named `constructor_2` found for str --> tests/ui/contract/fail/impl-block-for-non-storage-01.rs:20:16 | 4 | pub struct Contract {} - | _____---________- + | _____------------------- | | | | | function or associated item `constructor_2` not found for this struct 5 | | @@ -30,7 +30,7 @@ error[E0599]: no function or associated item named `message_2` found for struct --> tests/ui/contract/fail/impl-block-for-non-storage-01.rs:25:16 | 4 | pub struct Contract {} - | _____---________- + | _____------------------- | | | | | function or associated item `message_2` not found for this struct 5 | | diff --git a/crates/ink/tests/ui/contract/fail/impl-block-using-static-env-no-marker.stderr b/crates/ink/tests/ui/contract/fail/impl-block-using-static-env-no-marker.stderr index 3ac2382e770..2be15ec8121 100644 --- a/crates/ink/tests/ui/contract/fail/impl-block-using-static-env-no-marker.stderr +++ b/crates/ink/tests/ui/contract/fail/impl-block-using-static-env-no-marker.stderr @@ -2,7 +2,7 @@ error[E0599]: no function or associated item named `env` found for struct `Contr --> tests/ui/contract/fail/impl-block-using-static-env-no-marker.rs:20:27 | 4 | pub struct Contract {} - | --- function or associated item `env` not found for this struct + | ------------------- function or associated item `env` not found for this struct ... 20 | let _ = Self::env().caller(); | ^^^ function or associated item not found in `Contract` diff --git a/crates/ink/tests/ui/contract/fail/message-hygiene-try.stderr b/crates/ink/tests/ui/contract/fail/message-hygiene-try.stderr index ad3c2b27ac8..95d45537b9e 100644 --- a/crates/ink/tests/ui/contract/fail/message-hygiene-try.stderr +++ b/crates/ink/tests/ui/contract/fail/message-hygiene-try.stderr @@ -5,4 +5,4 @@ error[E0592]: duplicate definitions with name `try_message` | ---------------- other definition for `try_message` ... 16 | pub fn try_message(&self) {} - | ^^^ duplicate definitions for `try_message` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ duplicate definitions for `try_message` diff --git a/crates/ink/tests/ui/contract/fail/message-input-non-codec.stderr b/crates/ink/tests/ui/contract/fail/message-input-non-codec.stderr index 3e407002f52..94caf39dccf 100644 --- a/crates/ink/tests/ui/contract/fail/message-input-non-codec.stderr +++ b/crates/ink/tests/ui/contract/fail/message-input-non-codec.stderr @@ -2,7 +2,7 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeDecode` is not satisfied --> tests/ui/contract/fail/message-input-non-codec.rs:16:31 | 16 | pub fn message(&self, _input: NonCodecType) {} - | ^^^^^^ the trait `WrapperTypeDecode` is not implemented for `NonCodecType` + | ^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `NonCodecType` | = help: the following other types implement trait `WrapperTypeDecode`: Arc @@ -22,7 +22,7 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeDecode` is not satisfied --> tests/ui/contract/fail/message-input-non-codec.rs:16:9 | 16 | pub fn message(&self, _input: NonCodecType) {} - | ^^^ the trait `WrapperTypeDecode` is not implemented for `NonCodecType` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `NonCodecType` | = help: the following other types implement trait `WrapperTypeDecode`: Arc @@ -37,7 +37,7 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeEncode` is not satisfied | ^^^^^^^^^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `NonCodecType` ... 16 | pub fn message(&self, _input: NonCodecType) {} - | --- required by a bound introduced by this call + | ---------------------------------------------- required by a bound introduced by this call | = help: the following other types implement trait `WrapperTypeEncode`: &T @@ -63,7 +63,7 @@ error[E0599]: the method `try_invoke` exists for struct `CallBuilder tests/ui/contract/fail/message-input-non-codec.rs:16:9 | 16 | pub fn message(&self, _input: NonCodecType) {} - | ^^^ method cannot be called due to unsatisfied trait bounds + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method cannot be called due to unsatisfied trait bounds | ::: $WORKSPACE/crates/env/src/call/execution_input.rs | diff --git a/crates/ink/tests/ui/contract/fail/message-multiple-wildcard-selectors.stderr b/crates/ink/tests/ui/contract/fail/message-multiple-wildcard-selectors.stderr index cdde97feb79..3168335a67c 100644 --- a/crates/ink/tests/ui/contract/fail/message-multiple-wildcard-selectors.stderr +++ b/crates/ink/tests/ui/contract/fail/message-multiple-wildcard-selectors.stderr @@ -2,10 +2,10 @@ error: encountered ink! messages with overlapping wildcard selectors --> tests/ui/contract/fail/message-multiple-wildcard-selectors.rs:16:9 | 16 | pub fn message2(&self) {} - | ^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: first ink! message with overlapping wildcard selector here --> tests/ui/contract/fail/message-multiple-wildcard-selectors.rs:13:9 | 13 | pub fn message1(&self) {} - | ^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr b/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr index 877500181fa..48a1e1bb326 100644 --- a/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr +++ b/crates/ink/tests/ui/contract/fail/message-returns-non-codec.stderr @@ -27,8 +27,10 @@ note: required by a bound in `DispatchOutput` error[E0277]: the trait bound `Result: Encode` is not satisfied --> tests/ui/contract/fail/message-returns-non-codec.rs:16:9 | -16 | pub fn message(&self) -> NonCodecType { - | ^^^ the trait `Encode` is not implemented for `Result` +16 | / pub fn message(&self) -> NonCodecType { +17 | | NonCodecType +18 | | } + | |_________^ the trait `Encode` is not implemented for `Result` | = help: the trait `Encode` is implemented for `Result` note: required by a bound in `return_value` @@ -43,11 +45,13 @@ note: required by a bound in `return_value` error[E0599]: the method `try_invoke` exists for struct `CallBuilder>, Set>, ...>`, but its trait bounds were not satisfied --> tests/ui/contract/fail/message-returns-non-codec.rs:16:9 | -4 | pub struct NonCodecType; - | ----------------------- doesn't satisfy `NonCodecType: parity_scale_codec::Decode` +4 | pub struct NonCodecType; + | ----------------------- doesn't satisfy `NonCodecType: parity_scale_codec::Decode` ... -16 | pub fn message(&self) -> NonCodecType { - | ^^^ method cannot be called due to unsatisfied trait bounds +16 | / pub fn message(&self) -> NonCodecType { +17 | | NonCodecType +18 | | } + | |_________^ method cannot be called due to unsatisfied trait bounds | = note: the following trait bounds were not satisfied: `NonCodecType: parity_scale_codec::Decode` diff --git a/crates/ink/tests/ui/contract/fail/message-selector-and-wildcard-selector.stderr b/crates/ink/tests/ui/contract/fail/message-selector-and-wildcard-selector.stderr index 3a0cef0f7b2..bc619b19899 100644 --- a/crates/ink/tests/ui/contract/fail/message-selector-and-wildcard-selector.stderr +++ b/crates/ink/tests/ui/contract/fail/message-selector-and-wildcard-selector.stderr @@ -2,10 +2,10 @@ error: encountered ink! attribute arguments with equal kinds --> tests/ui/contract/fail/message-selector-and-wildcard-selector.rs:12:47 | 12 | #[ink(message, selector = 0xCAFEBABA, selector = _)] - | ^^^^^^^^ + | ^^^^^^^^^^^^ error: first equal ink! attribute argument with equal kind here --> tests/ui/contract/fail/message-selector-and-wildcard-selector.rs:12:24 | 12 | #[ink(message, selector = 0xCAFEBABA, selector = _)] - | ^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/ink/tests/ui/contract/fail/message-self-receiver-invalid-01.stderr b/crates/ink/tests/ui/contract/fail/message-self-receiver-invalid-01.stderr index a33937b0861..b8069624fb1 100644 --- a/crates/ink/tests/ui/contract/fail/message-self-receiver-invalid-01.stderr +++ b/crates/ink/tests/ui/contract/fail/message-self-receiver-invalid-01.stderr @@ -2,4 +2,4 @@ error: ink! messages must have `&self` or `&mut self` receiver --> tests/ui/contract/fail/message-self-receiver-invalid-01.rs:13:24 | 13 | pub fn message(this: &Self) {} - | ^^^^ + | ^^^^^^^^^^^ diff --git a/crates/ink/tests/ui/contract/fail/message-self-receiver-invalid-02.stderr b/crates/ink/tests/ui/contract/fail/message-self-receiver-invalid-02.stderr index 74037923929..e64d38d8146 100644 --- a/crates/ink/tests/ui/contract/fail/message-self-receiver-invalid-02.stderr +++ b/crates/ink/tests/ui/contract/fail/message-self-receiver-invalid-02.stderr @@ -2,4 +2,4 @@ error: ink! messages must have `&self` or `&mut self` receiver --> tests/ui/contract/fail/message-self-receiver-invalid-02.rs:13:24 | 13 | pub fn message(this: &mut Self) {} - | ^^^^ + | ^^^^^^^^^^^^^^^ diff --git a/crates/ink/tests/ui/contract/fail/message-self-receiver-missing.stderr b/crates/ink/tests/ui/contract/fail/message-self-receiver-missing.stderr index 6676399dc6f..9b40617be97 100644 --- a/crates/ink/tests/ui/contract/fail/message-self-receiver-missing.stderr +++ b/crates/ink/tests/ui/contract/fail/message-self-receiver-missing.stderr @@ -1,5 +1,6 @@ error: ink! messages must have `&self` or `&mut self` receiver --> tests/ui/contract/fail/message-self-receiver-missing.rs:12:9 | -12 | #[ink(message)] - | ^ +12 | / #[ink(message)] +13 | | pub fn message() {} + | |___________________________^ diff --git a/crates/ink/tests/ui/contract/fail/module-missing-constructor.stderr b/crates/ink/tests/ui/contract/fail/module-missing-constructor.stderr index 0ba6d47f661..5af1026c08f 100644 --- a/crates/ink/tests/ui/contract/fail/module-missing-constructor.stderr +++ b/crates/ink/tests/ui/contract/fail/module-missing-constructor.stderr @@ -1,5 +1,11 @@ error: missing ink! constructor - --> tests/ui/contract/fail/module-missing-constructor.rs:2:1 - | -2 | mod contract { - | ^^^ + --> tests/ui/contract/fail/module-missing-constructor.rs:2:1 + | +2 | / mod contract { +3 | | #[ink(storage)] +4 | | pub struct Contract {} +5 | | +... | +9 | | } +10 | | } + | |_^ diff --git a/crates/ink/tests/ui/contract/fail/module-missing-message.stderr b/crates/ink/tests/ui/contract/fail/module-missing-message.stderr index 2d0cd3535df..84d45a91b0a 100644 --- a/crates/ink/tests/ui/contract/fail/module-missing-message.stderr +++ b/crates/ink/tests/ui/contract/fail/module-missing-message.stderr @@ -1,5 +1,11 @@ error: missing ink! message - --> tests/ui/contract/fail/module-missing-message.rs:2:1 - | -2 | mod contract { - | ^^^ + --> tests/ui/contract/fail/module-missing-message.rs:2:1 + | +2 | / mod contract { +3 | | #[ink(storage)] +4 | | pub struct Contract {} +5 | | +... | +11 | | } +12 | | } + | |_^ diff --git a/crates/ink/tests/ui/contract/fail/module-missing-storage.stderr b/crates/ink/tests/ui/contract/fail/module-missing-storage.stderr index 06fd1620b45..7484faf031e 100644 --- a/crates/ink/tests/ui/contract/fail/module-missing-storage.stderr +++ b/crates/ink/tests/ui/contract/fail/module-missing-storage.stderr @@ -1,5 +1,11 @@ error: missing ink! storage struct - --> tests/ui/contract/fail/module-missing-storage.rs:2:1 - | -2 | mod contract { - | ^^^ + --> tests/ui/contract/fail/module-missing-storage.rs:2:1 + | +2 | / mod contract { +3 | | // #[ink(storage)] +4 | | pub struct Contract {} +5 | | +... | +12 | | } +13 | | } + | |_^ diff --git a/crates/ink/tests/ui/contract/fail/module-multiple-storages.stderr b/crates/ink/tests/ui/contract/fail/module-multiple-storages.stderr index da92152db68..2ee9c921f63 100644 --- a/crates/ink/tests/ui/contract/fail/module-multiple-storages.stderr +++ b/crates/ink/tests/ui/contract/fail/module-multiple-storages.stderr @@ -1,17 +1,23 @@ error: encountered multiple ink! storage structs, expected exactly one - --> tests/ui/contract/fail/module-multiple-storages.rs:2:1 - | -2 | mod contract { - | ^^^ + --> tests/ui/contract/fail/module-multiple-storages.rs:2:1 + | +2 | / mod contract { +3 | | #[ink(storage)] +4 | | pub struct Contract {} +5 | | +... | +27 | | } +28 | | } + | |_^ error: ink! storage struct here --> tests/ui/contract/fail/module-multiple-storages.rs:4:5 | 4 | pub struct Contract {} - | ^^^ + | ^^^^^^^^^^^^^^^^^^^^^^ error: ink! storage struct here --> tests/ui/contract/fail/module-multiple-storages.rs:17:5 | 17 | pub struct Contract2 {} - | ^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/ink/tests/ui/contract/fail/trait-impl-namespace-invalid.stderr b/crates/ink/tests/ui/contract/fail/trait-impl-namespace-invalid.stderr index 86ca398df1e..3d6a3f352d7 100644 --- a/crates/ink/tests/ui/contract/fail/trait-impl-namespace-invalid.stderr +++ b/crates/ink/tests/ui/contract/fail/trait-impl-namespace-invalid.stderr @@ -1,5 +1,9 @@ error: namespace ink! property is not allowed on ink! trait implementation blocks --> tests/ui/contract/fail/trait-impl-namespace-invalid.rs:21:5 | -21 | #[ink(namespace = "namespace")] - | ^ +21 | / #[ink(namespace = "namespace")] +22 | | impl TraitDefinition for Contract { +23 | | #[ink(message)] +24 | | fn message(&self) {} +25 | | } + | |_____^ diff --git a/crates/ink/tests/ui/contract/fail/trait-message-allow-reentrancy-mismatch.rs b/crates/ink/tests/ui/contract/fail/trait-message-allow-reentrancy-mismatch.rs new file mode 100644 index 00000000000..e43771e5c30 --- /dev/null +++ b/crates/ink/tests/ui/contract/fail/trait-message-allow-reentrancy-mismatch.rs @@ -0,0 +1,27 @@ +#[ink::trait_definition] +pub trait TraitDefinition { + #[ink(message)] + fn message(&self); +} + +#[ink::contract] +mod contract { + use super::TraitDefinition; + + #[ink(storage)] + pub struct Contract {} + + impl Contract { + #[ink(constructor)] + pub fn constructor() -> Self { + Self {} + } + } + + impl TraitDefinition for Contract { + #[ink(message, allow_reentrancy)] + fn message(&self) {} + } +} + +fn main() {} diff --git a/crates/ink/tests/ui/contract/fail/trait-message-allow-reentrancy-mismatch.stderr b/crates/ink/tests/ui/contract/fail/trait-message-allow-reentrancy-mismatch.stderr new file mode 100644 index 00000000000..66873508010 --- /dev/null +++ b/crates/ink/tests/ui/contract/fail/trait-message-allow-reentrancy-mismatch.stderr @@ -0,0 +1,8 @@ +error[E0308]: mismatched types + --> tests/ui/contract/fail/trait-message-allow-reentrancy-mismatch.rs:23:9 + | +23 | fn message(&self) {} + | ^^^^^^^^^^^^^^^^^^^^ expected `false`, found `true` + | + = note: expected struct `TraitMessageReentrant` + found struct `TraitMessageReentrant` diff --git a/crates/ink/tests/ui/contract/fail/trait-message-payable-mismatch.stderr b/crates/ink/tests/ui/contract/fail/trait-message-payable-mismatch.stderr index 15202a2c81e..3dd203b4d4a 100644 --- a/crates/ink/tests/ui/contract/fail/trait-message-payable-mismatch.stderr +++ b/crates/ink/tests/ui/contract/fail/trait-message-payable-mismatch.stderr @@ -2,7 +2,7 @@ error[E0308]: mismatched types --> tests/ui/contract/fail/trait-message-payable-mismatch.rs:23:9 | 23 | fn message(&self) {} - | ^^ expected `false`, found `true` + | ^^^^^^^^^^^^^^^^^^^^ expected `false`, found `true` | = note: expected struct `TraitMessagePayable` found struct `TraitMessagePayable` diff --git a/crates/ink/tests/ui/contract/fail/trait-message-selector-mismatch.stderr b/crates/ink/tests/ui/contract/fail/trait-message-selector-mismatch.stderr index 745cb6e49f7..abb792b0038 100644 --- a/crates/ink/tests/ui/contract/fail/trait-message-selector-mismatch.stderr +++ b/crates/ink/tests/ui/contract/fail/trait-message-selector-mismatch.stderr @@ -2,7 +2,7 @@ error[E0308]: mismatched types --> tests/ui/contract/fail/trait-message-selector-mismatch.rs:23:9 | 23 | fn message(&self) {} - | ^^ expected `1`, found `2` + | ^^^^^^^^^^^^^^^^^^^^ expected `1`, found `2` | = note: expected struct `TraitMessageSelector<1>` found struct `TraitMessageSelector<2>` diff --git a/crates/ink/tests/ui/contract/fail/trait-message-selector-overlap-1.stderr b/crates/ink/tests/ui/contract/fail/trait-message-selector-overlap-1.stderr index 8326b74e9c3..229df39f15c 100644 --- a/crates/ink/tests/ui/contract/fail/trait-message-selector-overlap-1.stderr +++ b/crates/ink/tests/ui/contract/fail/trait-message-selector-overlap-1.stderr @@ -2,16 +2,22 @@ error[E0119]: conflicting implementations of trait `DispatchableMessageInfo<1083 --> tests/ui/contract/fail/trait-message-selector-overlap-1.rs:41:9 | 36 | fn message(&self) {} - | -- first implementation here + | -------------------- first implementation here ... 41 | fn message(&self) {} - | ^^ conflicting implementation for `Contract` + | ^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Contract` error[E0119]: conflicting implementations of trait `TraitCallForwarderFor<1083895717>` for type `contract::_::CallBuilder` --> tests/ui/contract/fail/trait-message-selector-overlap-1.rs:39:5 | -34 | impl TraitDefinition1 for Contract { - | ---- first implementation here -... -39 | impl TraitDefinition2 for Contract { - | ^^^^ conflicting implementation for `contract::_::CallBuilder` +34 | / impl TraitDefinition1 for Contract { +35 | | #[ink(message)] +36 | | fn message(&self) {} +37 | | } + | |_____- first implementation here +38 | +39 | / impl TraitDefinition2 for Contract { +40 | | #[ink(message)] +41 | | fn message(&self) {} +42 | | } + | |_____^ conflicting implementation for `contract::_::CallBuilder` diff --git a/crates/ink/tests/ui/contract/fail/trait-message-selector-overlap-2.stderr b/crates/ink/tests/ui/contract/fail/trait-message-selector-overlap-2.stderr index 4f2952c514e..3c923d7b8f5 100644 --- a/crates/ink/tests/ui/contract/fail/trait-message-selector-overlap-2.stderr +++ b/crates/ink/tests/ui/contract/fail/trait-message-selector-overlap-2.stderr @@ -2,16 +2,22 @@ error[E0119]: conflicting implementations of trait `DispatchableMessageInfo<1518 --> tests/ui/contract/fail/trait-message-selector-overlap-2.rs:41:9 | 36 | fn message(&self) {} - | -- first implementation here + | -------------------- first implementation here ... 41 | fn message(&self) {} - | ^^ conflicting implementation for `Contract` + | ^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Contract` error[E0119]: conflicting implementations of trait `TraitCallForwarderFor<1518209067>` for type `contract::_::CallBuilder` --> tests/ui/contract/fail/trait-message-selector-overlap-2.rs:39:5 | -34 | impl TraitDefinition1 for Contract { - | ---- first implementation here -... -39 | impl TraitDefinition2 for Contract { - | ^^^^ conflicting implementation for `contract::_::CallBuilder` +34 | / impl TraitDefinition1 for Contract { +35 | | #[ink(message)] +36 | | fn message(&self) {} +37 | | } + | |_____- first implementation here +38 | +39 | / impl TraitDefinition2 for Contract { +40 | | #[ink(message)] +41 | | fn message(&self) {} +42 | | } + | |_____^ conflicting implementation for `contract::_::CallBuilder` diff --git a/crates/ink/tests/ui/contract/fail/trait-message-selector-overlap-3.stderr b/crates/ink/tests/ui/contract/fail/trait-message-selector-overlap-3.stderr index a1f5a0524f8..284acc45005 100644 --- a/crates/ink/tests/ui/contract/fail/trait-message-selector-overlap-3.stderr +++ b/crates/ink/tests/ui/contract/fail/trait-message-selector-overlap-3.stderr @@ -2,16 +2,22 @@ error[E0119]: conflicting implementations of trait `DispatchableMessageInfo<42>` --> tests/ui/contract/fail/trait-message-selector-overlap-3.rs:41:9 | 36 | fn message1(&self) {} - | -- first implementation here + | --------------------- first implementation here ... 41 | fn message2(&self) {} - | ^^ conflicting implementation for `Contract` + | ^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Contract` error[E0119]: conflicting implementations of trait `TraitCallForwarderFor<42>` for type `contract::_::CallBuilder` --> tests/ui/contract/fail/trait-message-selector-overlap-3.rs:39:5 | -34 | impl TraitDefinition1 for Contract { - | ---- first implementation here -... -39 | impl TraitDefinition2 for Contract { - | ^^^^ conflicting implementation for `contract::_::CallBuilder` +34 | / impl TraitDefinition1 for Contract { +35 | | #[ink(message)] +36 | | fn message1(&self) {} +37 | | } + | |_____- first implementation here +38 | +39 | / impl TraitDefinition2 for Contract { +40 | | #[ink(message)] +41 | | fn message2(&self) {} +42 | | } + | |_____^ conflicting implementation for `contract::_::CallBuilder` diff --git a/crates/ink/tests/ui/contract/fail/trait-message-wildcard-selector.stderr b/crates/ink/tests/ui/contract/fail/trait-message-wildcard-selector.stderr index e954985afec..a4e6d9e5ea8 100644 --- a/crates/ink/tests/ui/contract/fail/trait-message-wildcard-selector.stderr +++ b/crates/ink/tests/ui/contract/fail/trait-message-wildcard-selector.stderr @@ -2,13 +2,13 @@ error: encountered conflicting ink! attribute argument --> tests/ui/contract/fail/trait-message-wildcard-selector.rs:4:24 | 4 | #[ink(message, selector = _)] - | ^^^^^^^^ + | ^^^^^^^^^^^^ error: wildcard selectors are only supported for inherent ink! messages or constructors, not for traits. --> tests/ui/contract/fail/trait-message-wildcard-selector.rs:4:24 | 4 | #[ink(message, selector = _)] - | ^^^^^^^^ + | ^^^^^^^^^^^^ error[E0432]: unresolved import `super::foo::TraitDefinition` --> tests/ui/contract/fail/trait-message-wildcard-selector.rs:11:9 diff --git a/crates/ink/tests/ui/contract/pass/constructor-allow-reentrancy.rs b/crates/ink/tests/ui/contract/pass/constructor-allow-reentrancy.rs new file mode 100644 index 00000000000..84e827de051 --- /dev/null +++ b/crates/ink/tests/ui/contract/pass/constructor-allow-reentrancy.rs @@ -0,0 +1,21 @@ +#[ink::contract] +mod contract { + #[ink(storage)] + pub struct Contract {} + + impl Contract { + #[ink(constructor, selector = 0, allow_reentrancy)] + pub fn constructor() -> Self { + Self {} + } + + #[ink(message)] + pub fn message(&self) {} + } +} + +use contract::Contract; + +fn main() { + assert!(>::ALLOW_REENTRANCY); +} diff --git a/crates/ink/tests/ui/contract/pass/constructor-reentrancy-not-allowed.rs b/crates/ink/tests/ui/contract/pass/constructor-reentrancy-not-allowed.rs new file mode 100644 index 00000000000..74e595c6de8 --- /dev/null +++ b/crates/ink/tests/ui/contract/pass/constructor-reentrancy-not-allowed.rs @@ -0,0 +1,21 @@ +#[ink::contract] +mod contract { + #[ink(storage)] + pub struct Contract {} + + impl Contract { + #[ink(constructor, selector = 0)] + pub fn constructor() -> Self { + Self {} + } + + #[ink(message)] + pub fn message(&self) {} + } +} + +use contract::Contract; + +fn main() { + assert!(!>::ALLOW_REENTRANCY); +} diff --git a/crates/ink/tests/ui/contract/pass/message-allow-reentrancy.rs b/crates/ink/tests/ui/contract/pass/message-allow-reentrancy.rs new file mode 100644 index 00000000000..118afb1eb17 --- /dev/null +++ b/crates/ink/tests/ui/contract/pass/message-allow-reentrancy.rs @@ -0,0 +1,25 @@ +#[ink::contract] +mod contract { + #[ink(storage)] + pub struct Contract {} + + impl Contract { + #[ink(constructor)] + pub fn constructor() -> Self { + Self {} + } + + #[ink(message, selector = 1, allow_reentrancy)] + pub fn message_1(&self) {} + + #[ink(message, selector = 2)] + pub fn message_2(&self) {} + } +} + +use contract::Contract; + +fn main() { + assert!(>::ALLOW_REENTRANCY); + assert!(!>::ALLOW_REENTRANCY); +} diff --git a/crates/ink/tests/ui/storage_item/fail/collections_only_packed_1.stderr b/crates/ink/tests/ui/storage_item/fail/collections_only_packed_1.stderr index 024f999a20c..9e2d7c917e6 100644 --- a/crates/ink/tests/ui/storage_item/fail/collections_only_packed_1.stderr +++ b/crates/ink/tests/ui/storage_item/fail/collections_only_packed_1.stderr @@ -2,7 +2,7 @@ error[E0277]: the trait bound `Vec: parity_scale_codec::Decode` is no --> tests/ui/storage_item/fail/collections_only_packed_1.rs:11:8 | 11 | a: Vec, - | ^^^ the trait `parity_scale_codec::Decode` is not implemented for `Vec` + | ^^^^^^^^^^^^^^ the trait `parity_scale_codec::Decode` is not implemented for `Vec` | = help: the trait `parity_scale_codec::Decode` is implemented for `Vec` = note: required for `Vec` to implement `Packed` @@ -13,7 +13,7 @@ error[E0277]: the trait bound `[NonPacked]: Encode` is not satisfied --> tests/ui/storage_item/fail/collections_only_packed_1.rs:11:8 | 11 | a: Vec, - | ^^^ the trait `Encode` is not implemented for `[NonPacked]` + | ^^^^^^^^^^^^^^ the trait `Encode` is not implemented for `[NonPacked]` | = help: the following other types implement trait `Encode`: [T; N] diff --git a/crates/ink/tests/ui/storage_item/fail/collections_only_packed_2.stderr b/crates/ink/tests/ui/storage_item/fail/collections_only_packed_2.stderr index b0a9b7a4b77..93d9f65ed8d 100644 --- a/crates/ink/tests/ui/storage_item/fail/collections_only_packed_2.stderr +++ b/crates/ink/tests/ui/storage_item/fail/collections_only_packed_2.stderr @@ -2,7 +2,7 @@ error[E0277]: the trait bound `BTreeMap: parity_scale_codec::De --> tests/ui/storage_item/fail/collections_only_packed_2.rs:11:8 | 11 | a: BTreeMap, - | ^^^^^^^^ the trait `parity_scale_codec::Decode` is not implemented for `BTreeMap` + | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `parity_scale_codec::Decode` is not implemented for `BTreeMap` | = help: the trait `parity_scale_codec::Decode` is implemented for `BTreeMap` = note: required for `BTreeMap` to implement `Packed` @@ -13,7 +13,7 @@ error[E0277]: the trait bound `BTreeMap: Encode` is not satisfi --> tests/ui/storage_item/fail/collections_only_packed_2.rs:11:8 | 11 | a: BTreeMap, - | ^^^^^^^^ the trait `Encode` is not implemented for `BTreeMap` + | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Encode` is not implemented for `BTreeMap` | = help: the trait `Encode` is implemented for `BTreeMap` = note: required for `BTreeMap` to implement `Packed` diff --git a/crates/ink/tests/ui/trait_def/fail/definition_constructor.stderr b/crates/ink/tests/ui/trait_def/fail/definition_constructor.stderr index 6ab123fba0e..b0d8499a4c3 100644 --- a/crates/ink/tests/ui/trait_def/fail/definition_constructor.stderr +++ b/crates/ink/tests/ui/trait_def/fail/definition_constructor.stderr @@ -1,5 +1,6 @@ error: ink! trait definitions must not have constructors --> tests/ui/trait_def/fail/definition_constructor.rs:3:5 | -3 | #[ink(constructor)] - | ^ +3 | / #[ink(constructor)] +4 | | fn constructor() -> Self; + | |_____________________________^ diff --git a/crates/ink/tests/ui/trait_def/fail/definition_empty.stderr b/crates/ink/tests/ui/trait_def/fail/definition_empty.stderr index fee507687a6..e7e00a93470 100644 --- a/crates/ink/tests/ui/trait_def/fail/definition_empty.stderr +++ b/crates/ink/tests/ui/trait_def/fail/definition_empty.stderr @@ -2,4 +2,4 @@ error: encountered invalid empty ink! trait definition --> tests/ui/trait_def/fail/definition_empty.rs:2:1 | 2 | pub trait TraitDefinition {} - | ^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/ink/tests/ui/trait_def/fail/message_input_non_codec.stderr b/crates/ink/tests/ui/trait_def/fail/message_input_non_codec.stderr index 5ec79abc41f..e059a2ca7c6 100644 --- a/crates/ink/tests/ui/trait_def/fail/message_input_non_codec.stderr +++ b/crates/ink/tests/ui/trait_def/fail/message_input_non_codec.stderr @@ -2,7 +2,7 @@ error[E0277]: the trait bound `NonCodec: WrapperTypeDecode` is not satisfied --> tests/ui/trait_def/fail/message_input_non_codec.rs:6:23 | 6 | fn message(&self, input: NonCodec); - | ^^^^^ the trait `WrapperTypeDecode` is not implemented for `NonCodec` + | ^^^^^^^^^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `NonCodec` | = help: the following other types implement trait `WrapperTypeDecode`: Arc @@ -21,11 +21,12 @@ note: required by a bound in `DispatchInput` error[E0277]: the trait bound `NonCodec: WrapperTypeEncode` is not satisfied --> tests/ui/trait_def/fail/message_input_non_codec.rs:3:1 | -3 | #[ink::trait_definition] - | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `NonCodec` -4 | pub trait TraitDefinition { -5 | #[ink(message)] - | - required by a bound introduced by this call +3 | #[ink::trait_definition] + | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `NonCodec` +4 | pub trait TraitDefinition { +5 | / #[ink(message)] +6 | | fn message(&self, input: NonCodec); + | |_______________________________________- required by a bound introduced by this call | = help: the following other types implement trait `WrapperTypeEncode`: &T @@ -55,8 +56,8 @@ error[E0599]: the method `try_invoke` exists for struct `CallBuilder { - | ----------------------------------- doesn't satisfy `_: Encode` + | pub struct ArgumentList { + | ----------------------------------- doesn't satisfy `_: Encode` | = note: the following trait bounds were not satisfied: `ArgumentList, ArgumentList>: Encode` diff --git a/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr b/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr index f2321903953..42b84ca9173 100644 --- a/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr +++ b/crates/ink/tests/ui/trait_def/fail/message_output_non_codec.stderr @@ -27,8 +27,8 @@ note: required by a bound in `DispatchOutput` error[E0599]: the method `try_invoke` exists for struct `CallBuilder>, Set>>, Set>>`, but its trait bounds were not satisfied --> tests/ui/trait_def/fail/message_output_non_codec.rs:5:5 | -1 | pub struct NonCodec; - | ------------------- doesn't satisfy `NonCodec: parity_scale_codec::Decode` +1 | pub struct NonCodec; + | ------------------- doesn't satisfy `NonCodec: parity_scale_codec::Decode` ... 5 | #[ink(message)] | ^ method cannot be called due to unsatisfied trait bounds diff --git a/crates/metadata/src/specs.rs b/crates/metadata/src/specs.rs index c43c8333a35..7e4dea386b7 100644 --- a/crates/metadata/src/specs.rs +++ b/crates/metadata/src/specs.rs @@ -379,6 +379,8 @@ pub struct ConstructorSpec { pub selector: Selector, /// If the constructor accepts any `value` from the caller. pub payable: bool, + /// If the constructor allows reentrancy. + pub allow_reentrancy: bool, /// The parameters of the deployment handler. pub args: Vec>, /// The return type of the constructor.. @@ -397,6 +399,7 @@ impl IntoPortable for ConstructorSpec { label: self.label.to_string(), selector: self.selector, payable: self.payable, + allow_reentrancy: self.allow_reentrancy, args: self .args .into_iter() @@ -431,6 +434,11 @@ where &self.payable } + /// Returns if the constructor allows reentrancy. + pub fn allow_reentrancy(&self) -> &bool { + &self.allow_reentrancy + } + /// Returns the parameters of the deployment handler. pub fn args(&self) -> &[MessageParamSpec] { &self.args @@ -460,9 +468,10 @@ where /// debug code-gen macros. #[allow(clippy::type_complexity)] #[must_use] -pub struct ConstructorSpecBuilder { +pub struct ConstructorSpecBuilder +{ spec: ConstructorSpec, - marker: PhantomData (Selector, IsPayable, Returns)>, + marker: PhantomData (Selector, IsPayable, AllowReentrancy, Returns)>, } impl ConstructorSpec @@ -476,6 +485,7 @@ where F, Missing, Missing, + Missing, Missing, > { ConstructorSpecBuilder { @@ -483,6 +493,7 @@ where label, selector: Selector::default(), payable: Default::default(), + allow_reentrancy: Default::default(), args: Vec::new(), return_type: ReturnTypeSpec::new(None), docs: Vec::new(), @@ -493,7 +504,7 @@ where } } -impl ConstructorSpecBuilder, P, R> +impl ConstructorSpecBuilder, P, A, R> where F: Form, { @@ -501,7 +512,7 @@ where pub fn selector( self, selector: [u8; 4], - ) -> ConstructorSpecBuilder { + ) -> ConstructorSpecBuilder { ConstructorSpecBuilder { spec: ConstructorSpec { selector: selector.into(), @@ -512,7 +523,7 @@ where } } -impl ConstructorSpecBuilder, R> +impl ConstructorSpecBuilder, A, R> where F: Form, { @@ -520,7 +531,7 @@ where pub fn payable( self, is_payable: bool, - ) -> ConstructorSpecBuilder { + ) -> ConstructorSpecBuilder { ConstructorSpecBuilder { spec: ConstructorSpec { payable: is_payable, @@ -531,7 +542,26 @@ where } } -impl ConstructorSpecBuilder> +impl ConstructorSpecBuilder, R> +where + F: Form, +{ + /// Sets if the constructor is reentrant. + pub fn allow_reentrancy( + self, + allow_reentrancy: bool, + ) -> ConstructorSpecBuilder { + ConstructorSpecBuilder { + spec: ConstructorSpec { + allow_reentrancy, + ..self.spec + }, + marker: PhantomData, + } + } +} + +impl ConstructorSpecBuilder> where F: Form, { @@ -539,7 +569,7 @@ where pub fn returns( self, return_type: ReturnTypeSpec, - ) -> ConstructorSpecBuilder { + ) -> ConstructorSpecBuilder { ConstructorSpecBuilder { spec: ConstructorSpec { return_type, @@ -550,14 +580,14 @@ where } } -impl ConstructorSpecBuilder +impl ConstructorSpecBuilder where F: Form, { /// Sets the input arguments of the constructor specification. - pub fn args(self, args: A) -> Self + pub fn args(self, args: T) -> Self where - A: IntoIterator>, + T: IntoIterator>, { let mut this = self; debug_assert!(this.spec.args.is_empty()); @@ -592,7 +622,14 @@ where } } -impl ConstructorSpecBuilder +impl + ConstructorSpecBuilder< + F, + state::Selector, + state::IsPayable, + state::AllowReentrancy, + state::Returns, + > where F: Form, { @@ -618,9 +655,11 @@ pub struct MessageSpec { /// The selector hash of the message. selector: Selector, /// If the message is allowed to mutate the contract state. - mutates: bool, + mutates: bool,allow_reentrancy /// If the message accepts any `value` from the caller. payable: bool, + /// If the message is allowed to re-enter the contract. + reentrancy_allowed: bool, /// The parameters of the message. args: Vec>, /// The return type of the message. @@ -645,6 +684,8 @@ mod state { pub struct Mutates; /// Type state for telling if the message is payable. pub struct IsPayable; + /// Type state for the telling if the message is allowed to be reentrant. + pub struct AllowReentrancy; /// Type state for the message return type. pub struct Returns; /// Type state for the `AccountId` type of the environment. @@ -677,6 +718,7 @@ where Missing, Missing, Missing, + Missing, Missing, > { MessageSpecBuilder { @@ -685,6 +727,7 @@ where selector: Selector::default(), mutates: false, payable: false, + reentrancy_allowed: false, args: Vec::new(), return_type: ReturnTypeSpec::new(None), docs: Vec::new(), @@ -751,15 +794,15 @@ where /// debug code-gen macros. #[allow(clippy::type_complexity)] #[must_use] -pub struct MessageSpecBuilder +pub struct MessageSpecBuilder where F: Form, { spec: MessageSpec, - marker: PhantomData (Selector, Mutates, IsPayable, Returns)>, + marker: PhantomData (Selector, Mutates, IsPayable, AllowReentrancy, Returns)>, } -impl MessageSpecBuilder, M, P, R> +impl MessageSpecBuilder, M, P, A, R> where F: Form, { @@ -767,7 +810,7 @@ where pub fn selector( self, selector: [u8; 4], - ) -> MessageSpecBuilder { + ) -> MessageSpecBuilder { MessageSpecBuilder { spec: MessageSpec { selector: selector.into(), @@ -778,7 +821,7 @@ where } } -impl MessageSpecBuilder, P, R> +impl MessageSpecBuilder, P, A, R> where F: Form, { @@ -787,7 +830,7 @@ where pub fn mutates( self, mutates: bool, - ) -> MessageSpecBuilder { + ) -> MessageSpecBuilder { MessageSpecBuilder { spec: MessageSpec { mutates, @@ -798,7 +841,7 @@ where } } -impl MessageSpecBuilder, R> +impl MessageSpecBuilder, A, R> where F: Form, { @@ -806,7 +849,7 @@ where pub fn payable( self, is_payable: bool, - ) -> MessageSpecBuilder { + ) -> MessageSpecBuilder { MessageSpecBuilder { spec: MessageSpec { payable: is_payable, @@ -817,7 +860,26 @@ where } } -impl MessageSpecBuilder> +impl MessageSpecBuilder, R> +where + F: Form, +{ + /// Sets if the message is reentrant. + pub fn allow_reentrancy( + self, + allow_reentrancy: bool, + ) -> MessageSpecBuilder { + MessageSpecBuilder { + spec: MessageSpec { + reentrancy_allowed: allow_reentrancy, + ..self.spec + }, + marker: PhantomData, + } + } +} + +impl MessageSpecBuilder> where F: Form, { @@ -825,7 +887,7 @@ where pub fn returns( self, return_type: ReturnTypeSpec, - ) -> MessageSpecBuilder { + ) -> MessageSpecBuilder { MessageSpecBuilder { spec: MessageSpec { return_type, @@ -836,14 +898,14 @@ where } } -impl MessageSpecBuilder +impl MessageSpecBuilder where F: Form, { /// Sets the input arguments of the message specification. - pub fn args(self, args: A) -> Self + pub fn args(self, args: T) -> Self where - A: IntoIterator>, + T: IntoIterator>, { let mut this = self; debug_assert!(this.spec.args.is_empty()); @@ -880,6 +942,7 @@ impl state::Selector, state::Mutates, state::IsPayable, + state::AllowReentrancy, state::Returns, > where @@ -900,6 +963,7 @@ impl IntoPortable for MessageSpec { selector: self.selector, mutates: self.mutates, payable: self.payable, + reentrancy_allowed: self.reentrancy_allowed, default: self.default, args: self .args diff --git a/crates/metadata/src/tests.rs b/crates/metadata/src/tests.rs index b9b4ae162ef..ad7c71b60ed 100644 --- a/crates/metadata/src/tests.rs +++ b/crates/metadata/src/tests.rs @@ -28,6 +28,7 @@ fn spec_constructor_selector_must_serialize_to_hex() { let cs = ConstructorSpec::from_label(label) .selector(123_456_789u32.to_be_bytes()) .payable(true) + .allow_reentrancy(true) .returns(ReturnTypeSpec::new(None)) .done(); let mut registry = Registry::new(); @@ -44,6 +45,7 @@ fn spec_constructor_selector_must_serialize_to_hex() { json!({ "label": "foo", "payable": true, + "allowReentrancy": true, "selector": "0x075bcd15", "returnType": null, "args": [], @@ -61,6 +63,7 @@ fn spec_contract_only_one_default_message_allowed() { .constructors(vec![ConstructorSpec::from_label("new") .selector([94u8, 189u8, 136u8, 214u8]) .payable(true) + .allow_reentrancy(true) .args(vec![MessageParamSpec::new("init_value") .of_type(TypeSpec::with_name_segs::( vec!["i32"].into_iter().map(AsRef::as_ref), @@ -74,6 +77,7 @@ fn spec_contract_only_one_default_message_allowed() { .selector([231u8, 208u8, 89u8, 15u8]) .mutates(true) .payable(true) + .allow_reentrancy(true) .args(vec![MessageParamSpec::new("by") .of_type(TypeSpec::with_name_segs::( vec!["i32"].into_iter().map(AsRef::as_ref), @@ -86,6 +90,7 @@ fn spec_contract_only_one_default_message_allowed() { .selector([37u8, 68u8, 74u8, 254u8]) .mutates(false) .payable(false) + .allow_reentrancy(false) .args(Vec::new()) .returns(ReturnTypeSpec::new(TypeSpec::with_name_segs::( vec!["i32"].into_iter().map(AsRef::as_ref), @@ -111,6 +116,7 @@ fn spec_contract_only_one_default_constructor_allowed() { ConstructorSpec::from_label("new") .selector([94u8, 189u8, 136u8, 214u8]) .payable(true) + .allow_reentrancy(true) .args(vec![MessageParamSpec::new("init_value") .of_type(TypeSpec::with_name_segs::( vec!["i32"].into_iter().map(AsRef::as_ref), @@ -123,6 +129,7 @@ fn spec_contract_only_one_default_constructor_allowed() { ConstructorSpec::from_label("default") .selector([2u8, 34u8, 255u8, 24u8]) .payable(Default::default()) + .allow_reentrancy(Default::default()) .args(Vec::new()) .returns(ReturnTypeSpec::new(None)) .docs(Vec::new()) @@ -133,6 +140,7 @@ fn spec_contract_only_one_default_constructor_allowed() { .selector([231u8, 208u8, 89u8, 15u8]) .mutates(true) .payable(true) + .allow_reentrancy(true) .args(vec![MessageParamSpec::new("by") .of_type(TypeSpec::with_name_segs::( vec!["i32"].into_iter().map(AsRef::as_ref), @@ -332,6 +340,7 @@ fn spec_contract_json() { ConstructorSpec::from_label("new") .selector([94u8, 189u8, 136u8, 214u8]) .payable(true) + .allow_reentrancy(true) .args(vec![MessageParamSpec::new("init_value") .of_type(TypeSpec::with_name_segs::( vec!["i32"].into_iter().map(AsRef::as_ref), @@ -343,6 +352,7 @@ fn spec_contract_json() { ConstructorSpec::from_label("default") .selector([2u8, 34u8, 255u8, 24u8]) .payable(Default::default()) + .allow_reentrancy(Default::default()) .args(Vec::new()) .returns(ReturnTypeSpec::new(None)) .docs(Vec::new()) @@ -351,6 +361,7 @@ fn spec_contract_json() { ConstructorSpec::from_label("result_new") .selector([6u8, 3u8, 55u8, 123u8]) .payable(Default::default()) + .allow_reentrancy(Default::default()) .args(Vec::new()) .returns(ReturnTypeSpec::new(Some(TypeSpec::with_name_str::< Result<(), ()>, @@ -365,6 +376,7 @@ fn spec_contract_json() { .selector([231u8, 208u8, 89u8, 15u8]) .mutates(true) .payable(true) + .allow_reentrancy(true) .args(vec![MessageParamSpec::new("by") .of_type(TypeSpec::with_name_segs::( vec!["i32"].into_iter().map(AsRef::as_ref), @@ -377,6 +389,7 @@ fn spec_contract_json() { .selector([37u8, 68u8, 74u8, 254u8]) .mutates(false) .payable(false) + .allow_reentrancy(false) .args(Vec::new()) .returns(ReturnTypeSpec::new(TypeSpec::with_name_segs::( vec!["i32"].into_iter().map(AsRef::as_ref), @@ -460,6 +473,7 @@ fn spec_contract_json() { "default": false, "label": "new", "payable": true, + "allowReentrancy": true, "returnType": null, "selector": "0x5ebd88d6" }, @@ -469,6 +483,7 @@ fn spec_contract_json() { "default": true, "label": "default", "payable": false, + "allowReentrancy": false, "returnType": null, "selector": "0x0222ff18" }, @@ -478,6 +493,7 @@ fn spec_contract_json() { "default": false, "label": "result_new", "payable": false, + "allowReentrancy": false, "returnType": { "displayName": [ "core", @@ -555,6 +571,7 @@ fn spec_contract_json() { "docs": [], "mutates": true, "payable": true, + "allowReentrancy": true, "label": "inc", "returnType": null, "selector": "0xe7d0590f" @@ -565,6 +582,7 @@ fn spec_contract_json() { "docs": [], "mutates": false, "payable": false, + "allowReentrancy": false, "label": "get", "returnType": { "displayName": [ @@ -588,6 +606,7 @@ fn trim_docs() { .selector(123_456_789u32.to_be_bytes()) .docs(vec![" foobar "]) .payable(Default::default()) + .allow_reentrancy(Default::default()) .returns(ReturnTypeSpec::new(None)) .done(); let mut registry = Registry::new(); @@ -604,6 +623,7 @@ fn trim_docs() { json!({ "label": "foo", "payable": false, + "allowReentrancy": false, "returnType": null, "selector": "0x075bcd15", "args": [], @@ -630,6 +650,7 @@ fn trim_docs_with_code() { " ```", ]) .payable(Default::default()) + .allow_reentrancy(Default::default()) .returns(ReturnTypeSpec::new(None)) .done(); let mut registry = Registry::new(); @@ -646,6 +667,7 @@ fn trim_docs_with_code() { json!({ "label": "foo", "payable": false, + "allowReentrancy": false, "returnType": null, "selector": "0x075bcd15", "args": [], @@ -736,6 +758,7 @@ fn runtime_constructor_spec() -> ConstructorSpec { ConstructorSpec::from_label("foo".to_string()) .selector(Default::default()) .payable(true) + .allow_reentrancy(true) .args(args) .docs(vec!["foo", "bar"]) .returns(ret_spec) @@ -753,6 +776,7 @@ fn runtime_message_spec() -> MessageSpec { .selector(Default::default()) .mutates(false) .payable(true) + .allow_reentrancy(true) .args(args) .returns(ret_spec) .docs(["foo".to_string(), "bar".to_string()]) @@ -794,6 +818,7 @@ fn construct_runtime_contract_spec() { "label": "foo", "selector": "0x00000000", "payable": true, + "allowReentrancy": true, "returnType": null, "args": [ { @@ -822,6 +847,7 @@ fn construct_runtime_contract_spec() { "selector": "0x00000000", "mutates": false, "payable": true, + "allowReentrancy": true, "args": [ { "label": "foo_arg",