From 3b27bebf31ab9c03b3fc8bd7b97b328622f31616 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Wed, 22 Nov 2023 19:56:56 +0000 Subject: [PATCH 1/6] adjust macro in parsing tests --- crates/ink/ir/src/ir/item_impl/message.rs | 30 ++++++++++++++++++----- integration-tests/flipper/lib.rs | 6 +++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/crates/ink/ir/src/ir/item_impl/message.rs b/crates/ink/ir/src/ir/item_impl/message.rs index a60585f702e..af3bcf61aca 100644 --- a/crates/ink/ir/src/ir/item_impl/message.rs +++ b/crates/ink/ir/src/ir/item_impl/message.rs @@ -382,13 +382,23 @@ mod tests { #[test] fn inputs_works() { + macro_rules! expected_input { + ( mut $name:ident: $ty:ty ) => {{ + syn::parse_quote! { + mut $name: $ty + } + }}; + ( $name:ident: $ty:ty ) => {{ + syn::parse_quote! { + $name: $ty + } + }}; + } macro_rules! expected_inputs { - ( $( $name:ident: $ty:ty ),* ) => {{ + ( $( $($ts:ident)+: $ty:ty ),* ) => {{ vec![ $( - syn::parse_quote! { - $name: $ty - } + expected_input!($($ts)+: $ty) ),* ] }}; @@ -410,12 +420,20 @@ mod tests { fn my_message(&self, a: i32) {} }, ), + ( + // Single mutable input: + expected_inputs!(mut a: i32), + syn::parse_quote! { + #[ink(message)] + fn my_message(&self, mut a: i32) {} + }, + ), ( // Some inputs: - expected_inputs!(a: i32, b: u64, c: [u8; 32]), + expected_inputs!(a: i32, b: u64, mut c: [u8; 32]), syn::parse_quote! { #[ink(message)] - fn my_message(&self, a: i32, b: u64, c: [u8; 32]) {} + fn my_message(&self, a: i32, b: u64, mut c: [u8; 32]) {} }, ), ]; diff --git a/integration-tests/flipper/lib.rs b/integration-tests/flipper/lib.rs index ccc6aa2cff7..c34fcd8dbbc 100644 --- a/integration-tests/flipper/lib.rs +++ b/integration-tests/flipper/lib.rs @@ -31,6 +31,12 @@ pub mod flipper { pub fn get(&self) -> bool { self.value } + + /// Returns the current value of the Flipper's boolean. + #[ink(message)] + pub fn set(&mut self, mut b: bool) { + self.value = b; + } } #[cfg(test)] From ffb5081954dc6385ffb2f69f77151d406b6802e5 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Thu, 23 Nov 2023 15:57:05 +0000 Subject: [PATCH 2/6] Add tests and filter out mut keyword --- crates/ink/codegen/src/generator/arg_list.rs | 19 ++- .../generator/as_dependency/contract_ref.rs | 16 +-- crates/ink/codegen/src/generator/mod.rs | 1 + integration-tests/flipper/lib.rs | 6 - integration-tests/incrementer-mut/.gitignore | 9 ++ integration-tests/incrementer-mut/Cargo.toml | 23 +++ integration-tests/incrementer-mut/lib.rs | 136 ++++++++++++++++++ 7 files changed, 195 insertions(+), 15 deletions(-) create mode 100644 integration-tests/incrementer-mut/.gitignore create mode 100644 integration-tests/incrementer-mut/Cargo.toml create mode 100644 integration-tests/incrementer-mut/lib.rs diff --git a/crates/ink/codegen/src/generator/arg_list.rs b/crates/ink/codegen/src/generator/arg_list.rs index 503bdee9fcd..e53d474db06 100644 --- a/crates/ink/codegen/src/generator/arg_list.rs +++ b/crates/ink/codegen/src/generator/arg_list.rs @@ -28,7 +28,8 @@ pub fn output_ident(message_name: &syn::Ident) -> syn::Ident { format_ident!("{}Output", message_name.to_string().to_lower_camel_case()) } -/// Returns the sequence of artificial input parameter bindings for the message. +/// Returns the sequence of artificial input parameter bindings +/// for the message or constructor. /// /// # Note /// @@ -46,6 +47,22 @@ pub fn input_types(inputs: ir::InputsIter) -> Vec<&syn::Type> { inputs.map(|pat_type| &*pat_type.ty).collect::>() } +/// Returns the sequence of input idents for the message. +pub fn input_message_idents(inputs: ir::InputsIter) -> Vec<&syn::Ident> { + inputs + .map(|input| { + match &*input.pat { + syn::Pat::Ident(ident) => &ident.ident, + _ => { + unreachable!( + "encountered ink! dispatch input with missing identifier" + ) + } + } + }) + .collect::>() +} + /// Returns a tuple type representing the types yielded by the input types. pub fn input_types_tuple(inputs: ir::InputsIter) -> TokenStream2 { let input_types = input_types(inputs); diff --git a/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs b/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs index bbedecc333e..a2c026b1f91 100644 --- a/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs +++ b/crates/ink/codegen/src/generator/as_dependency/contract_ref.rs @@ -289,7 +289,7 @@ impl ContractRef<'_> { ir::Receiver::RefMut => quote! { forward_mut }, }; let mut_token = message.receiver().is_ref_mut().then(|| quote! { mut }); - let input_bindings = message.inputs().map(|input| &input.pat).collect::>(); + let input_idents = generator::input_message_idents(message.inputs()); let input_types = message.inputs().map(|input| &input.ty).collect::>(); let cfg_attrs = message.get_cfg_attrs(span); quote_spanned!(span=> @@ -301,13 +301,13 @@ impl ContractRef<'_> { #( #cfg_attrs )* fn #message_ident( & #mut_token self - #( , #input_bindings : #input_types )* + #( , #input_idents : #input_types )* ) -> Self::#output_ident { <_ as #trait_path>::#message_ident( <_ as ::ink::codegen::TraitCallForwarderFor<{#trait_info_id}>>::#forward_operator( ::#call_operator(self), ) - #( , #input_bindings )* + #( , #input_idents )* ) } ) @@ -384,7 +384,7 @@ impl ContractRef<'_> { ir::Receiver::RefMut => quote! { call_mut }, }; let mut_token = message.receiver().is_ref_mut().then(|| quote! { mut }); - let input_bindings = message.inputs().map(|input| &input.pat).collect::>(); + let input_idents = generator::input_message_idents(message.inputs()); let input_types = message.inputs().map(|input| &input.ty).collect::>(); let output_type = message.output().map(|ty| quote! { -> #ty }); let wrapped_output_type = message.wrapped_output(); @@ -393,9 +393,9 @@ impl ContractRef<'_> { #[inline] pub fn #message_ident( & #mut_token self - #( , #input_bindings : #input_types )* + #( , #input_idents : #input_types )* ) #output_type { - self.#try_message_ident( #( #input_bindings, )* ) + self.#try_message_ident( #( #input_idents, )* ) .unwrap_or_else(|error| ::core::panic!( "encountered error while calling {}::{}: {:?}", ::core::stringify!(#storage_ident), @@ -408,10 +408,10 @@ impl ContractRef<'_> { #[inline] pub fn #try_message_ident( & #mut_token self - #( , #input_bindings : #input_types )* + #( , #input_idents : #input_types )* ) -> #wrapped_output_type { ::#call_operator(self) - .#message_ident( #( #input_bindings ),* ) + .#message_ident( #( #input_idents ),* ) .try_invoke() .unwrap_or_else(|error| ::core::panic!( "encountered error while calling {}::{}: {:?}", diff --git a/crates/ink/codegen/src/generator/mod.rs b/crates/ink/codegen/src/generator/mod.rs index 5021bc5ca4d..b306a84c6f5 100644 --- a/crates/ink/codegen/src/generator/mod.rs +++ b/crates/ink/codegen/src/generator/mod.rs @@ -48,6 +48,7 @@ pub use self::{ generate_reference_to_trait_info, input_bindings, input_bindings_tuple, + input_message_idents, input_types, input_types_tuple, output_ident, diff --git a/integration-tests/flipper/lib.rs b/integration-tests/flipper/lib.rs index c34fcd8dbbc..ccc6aa2cff7 100644 --- a/integration-tests/flipper/lib.rs +++ b/integration-tests/flipper/lib.rs @@ -31,12 +31,6 @@ pub mod flipper { pub fn get(&self) -> bool { self.value } - - /// Returns the current value of the Flipper's boolean. - #[ink(message)] - pub fn set(&mut self, mut b: bool) { - self.value = b; - } } #[cfg(test)] diff --git a/integration-tests/incrementer-mut/.gitignore b/integration-tests/incrementer-mut/.gitignore new file mode 100644 index 00000000000..bf910de10af --- /dev/null +++ b/integration-tests/incrementer-mut/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock \ No newline at end of file diff --git a/integration-tests/incrementer-mut/Cargo.toml b/integration-tests/incrementer-mut/Cargo.toml new file mode 100644 index 00000000000..7f293927c81 --- /dev/null +++ b/integration-tests/incrementer-mut/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "incrementer_mut" +version = "5.0.0-alpha" +authors = ["Parity Technologies "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../crates/e2e" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/incrementer-mut/lib.rs b/integration-tests/incrementer-mut/lib.rs new file mode 100644 index 00000000000..c0be1f4f3a2 --- /dev/null +++ b/integration-tests/incrementer-mut/lib.rs @@ -0,0 +1,136 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +//! A simple incrementer contract +//! demonstrating the internal mutability of message parameters. + +pub use self::incrementer_mut::{ + Incrementer, + IncrementerRef, +}; + +#[ink::contract] +mod incrementer_mut { + #[ink(storage)] + pub struct Incrementer { + value: i32, + } + + impl Incrementer { + /// Create a new contract with the specified counter value. + /// If it is below 0, it is set to 0 + #[ink(constructor)] + pub fn new(mut init_value: i32) -> Self { + if init_value < 0 { + init_value = 0; + } + Self { value: init_value } + } + + #[ink(constructor)] + pub fn new_default() -> Self { + Self::new(Default::default()) + } + + #[ink(message)] + pub fn inc(&mut self, by: i32) { + self.value = self.value.checked_add(by).unwrap(); + } + + /// Update the counter with the specified value. + /// If it is above 100, we set it to 0. + #[ink(message)] + pub fn update(&mut self, mut value: i32) { + if value > 100 { + value = 0; + } + self.value = value; + } + + #[ink(message)] + pub fn get(&self) -> i32 { + self.value + } + } + + #[cfg(test)] + mod tests { + use super::*; + + #[ink::test] + fn default_works() { + let contract = Incrementer::new_default(); + assert_eq!(contract.get(), 0); + } + + #[ink::test] + fn it_works() { + let mut contract = Incrementer::new(42); + assert_eq!(contract.get(), 42); + contract.inc(5); + assert_eq!(contract.get(), 47); + contract.inc(-50); + assert_eq!(contract.get(), -3); + } + #[ink::test] + fn mutability_works() { + let mut contract = Incrementer::new(-5); + assert_eq!(contract.get(), 0); + contract.update(80); + assert_eq!(contract.get(), 80); + contract.update(120); + assert_eq!(contract.get(), 0); + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn it_works(mut client: Client) -> E2EResult<()> { + // given + let mut constructor = IncrementerRef::new(-2); + let contract = client + .instantiate("incrementer_mut", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let mut call = contract.call::(); + + let get = call.get(); + let get_res = client.call(&ink_e2e::bob(), &get).dry_run().await?; + assert!(matches!(get_res.return_value(), 0)); + + // when + let flip = call.update(50); + let _flip_res = client + .call(&ink_e2e::bob(), &flip) + .submit() + .await + .expect("update failed"); + + // then + let get = call.get(); + let get_res = client.call(&ink_e2e::bob(), &get).dry_run().await?; + assert!(matches!(get_res.return_value(), 50)); + + // when + let flip = call.update(150); + let _flip_res = client + .call(&ink_e2e::bob(), &flip) + .submit() + .await + .expect("update failed"); + + // then + let get = call.get(); + let get_res = client.call(&ink_e2e::bob(), &get).dry_run().await?; + assert!(matches!(get_res.return_value(), 0)); + + Ok(()) + } + } +} From 6e75dcadb616af84a5f407cb26196e7a6b4e6efe Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Thu, 23 Nov 2023 16:04:55 +0000 Subject: [PATCH 3/6] add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17f08c0858d..683c0830d61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Allow mutable parameters in messages - [#2004](https://github.com/paritytech/ink/pull/2004) - [E2E] Allow testing with live-chain state - [#1949](https://github.com/paritytech/ink/pull/1949) - [E2E] Call builders and extra gas margin option - [#1917](https://github.com/paritytech/ink/pull/1917) - Linter: `storage_never_freed` lint - [#1932](https://github.com/paritytech/ink/pull/1932) From c5b606f86ac0c2db9c358a23b1c11f24d2d6e991 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Thu, 23 Nov 2023 16:08:02 +0000 Subject: [PATCH 4/6] add idents to dictionary --- .config/cargo_spellcheck.dic | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/cargo_spellcheck.dic b/.config/cargo_spellcheck.dic index ca8ac821f9b..093974a6f84 100644 --- a/.config/cargo_spellcheck.dic +++ b/.config/cargo_spellcheck.dic @@ -52,6 +52,7 @@ fuzzer getter growable ident +idents interoperate invariants kB From 75bd4e51dacd53eaec87144d2f0509fe3bd8e1d6 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Fri, 24 Nov 2023 20:18:46 +0000 Subject: [PATCH 5/6] remove integration test --- integration-tests/incrementer-mut/.gitignore | 9 -- integration-tests/incrementer-mut/Cargo.toml | 23 ---- integration-tests/incrementer-mut/lib.rs | 136 ------------------- integration-tests/mother/lib.rs | 15 ++ 4 files changed, 15 insertions(+), 168 deletions(-) delete mode 100644 integration-tests/incrementer-mut/.gitignore delete mode 100644 integration-tests/incrementer-mut/Cargo.toml delete mode 100644 integration-tests/incrementer-mut/lib.rs diff --git a/integration-tests/incrementer-mut/.gitignore b/integration-tests/incrementer-mut/.gitignore deleted file mode 100644 index bf910de10af..00000000000 --- a/integration-tests/incrementer-mut/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Ignore build artifacts from the local tests sub-crate. -/target/ - -# Ignore backup files creates by cargo fmt. -**/*.rs.bk - -# Remove Cargo.lock when creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock \ No newline at end of file diff --git a/integration-tests/incrementer-mut/Cargo.toml b/integration-tests/incrementer-mut/Cargo.toml deleted file mode 100644 index 7f293927c81..00000000000 --- a/integration-tests/incrementer-mut/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "incrementer_mut" -version = "5.0.0-alpha" -authors = ["Parity Technologies "] -edition = "2021" -publish = false - -[dependencies] -ink = { path = "../../crates/ink", default-features = false } - -[dev-dependencies] -ink_e2e = { path = "../../crates/e2e" } - -[lib] -path = "lib.rs" - -[features] -default = ["std"] -std = [ - "ink/std", -] -ink-as-dependency = [] -e2e-tests = [] diff --git a/integration-tests/incrementer-mut/lib.rs b/integration-tests/incrementer-mut/lib.rs deleted file mode 100644 index c0be1f4f3a2..00000000000 --- a/integration-tests/incrementer-mut/lib.rs +++ /dev/null @@ -1,136 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -//! A simple incrementer contract -//! demonstrating the internal mutability of message parameters. - -pub use self::incrementer_mut::{ - Incrementer, - IncrementerRef, -}; - -#[ink::contract] -mod incrementer_mut { - #[ink(storage)] - pub struct Incrementer { - value: i32, - } - - impl Incrementer { - /// Create a new contract with the specified counter value. - /// If it is below 0, it is set to 0 - #[ink(constructor)] - pub fn new(mut init_value: i32) -> Self { - if init_value < 0 { - init_value = 0; - } - Self { value: init_value } - } - - #[ink(constructor)] - pub fn new_default() -> Self { - Self::new(Default::default()) - } - - #[ink(message)] - pub fn inc(&mut self, by: i32) { - self.value = self.value.checked_add(by).unwrap(); - } - - /// Update the counter with the specified value. - /// If it is above 100, we set it to 0. - #[ink(message)] - pub fn update(&mut self, mut value: i32) { - if value > 100 { - value = 0; - } - self.value = value; - } - - #[ink(message)] - pub fn get(&self) -> i32 { - self.value - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[ink::test] - fn default_works() { - let contract = Incrementer::new_default(); - assert_eq!(contract.get(), 0); - } - - #[ink::test] - fn it_works() { - let mut contract = Incrementer::new(42); - assert_eq!(contract.get(), 42); - contract.inc(5); - assert_eq!(contract.get(), 47); - contract.inc(-50); - assert_eq!(contract.get(), -3); - } - #[ink::test] - fn mutability_works() { - let mut contract = Incrementer::new(-5); - assert_eq!(contract.get(), 0); - contract.update(80); - assert_eq!(contract.get(), 80); - contract.update(120); - assert_eq!(contract.get(), 0); - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::ContractsBackend; - - type E2EResult = std::result::Result>; - - #[ink_e2e::test] - async fn it_works(mut client: Client) -> E2EResult<()> { - // given - let mut constructor = IncrementerRef::new(-2); - let contract = client - .instantiate("incrementer_mut", &ink_e2e::alice(), &mut constructor) - .submit() - .await - .expect("instantiate failed"); - let mut call = contract.call::(); - - let get = call.get(); - let get_res = client.call(&ink_e2e::bob(), &get).dry_run().await?; - assert!(matches!(get_res.return_value(), 0)); - - // when - let flip = call.update(50); - let _flip_res = client - .call(&ink_e2e::bob(), &flip) - .submit() - .await - .expect("update failed"); - - // then - let get = call.get(); - let get_res = client.call(&ink_e2e::bob(), &get).dry_run().await?; - assert!(matches!(get_res.return_value(), 50)); - - // when - let flip = call.update(150); - let _flip_res = client - .call(&ink_e2e::bob(), &flip) - .submit() - .await - .expect("update failed"); - - // then - let get = call.get(); - let get_res = client.call(&ink_e2e::bob(), &get).dry_run().await?; - assert!(matches!(get_res.return_value(), 0)); - - Ok(()) - } - } -} diff --git a/integration-tests/mother/lib.rs b/integration-tests/mother/lib.rs index b7ec82fec7a..27f60a11780 100755 --- a/integration-tests/mother/lib.rs +++ b/integration-tests/mother/lib.rs @@ -19,6 +19,7 @@ #[ink::contract] mod mother { use ink::prelude::{ + format, string::{ String, ToString, @@ -181,6 +182,13 @@ mod mother { pub fn debug_log(&mut self, _message: String) { ink::env::debug_println!("debug_log: {}", _message); } + + /// Mutates the input string to return "Hello, {name}" + #[ink(message)] + pub fn mut_hello_world(&self, mut message: String) -> String { + message = format!("Hello, {}", message); + message + } } #[cfg(test)] @@ -227,5 +235,12 @@ mod mother { let mut contract = Mother::default(); let _ = contract.revert_or_trap(Some(Failure::Panic)); } + + #[ink::test] + fn mut_works() { + let contract = Mother::default(); + let res = contract.mut_hello_world("Alice".to_string()); + assert_eq!("Hello, Alice", res) + } } } From e2e23e10f8c4267e83c137463109fab8077108bb Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Tue, 28 Nov 2023 16:09:20 +0000 Subject: [PATCH 6/6] make clippy happy --- integration-tests/mother/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/mother/lib.rs b/integration-tests/mother/lib.rs index 27f60a11780..98e6acf2b82 100755 --- a/integration-tests/mother/lib.rs +++ b/integration-tests/mother/lib.rs @@ -183,7 +183,7 @@ mod mother { ink::env::debug_println!("debug_log: {}", _message); } - /// Mutates the input string to return "Hello, {name}" + /// Mutates the input string to return "Hello, { name }" #[ink(message)] pub fn mut_hello_world(&self, mut message: String) -> String { message = format!("Hello, {}", message);