From 0814a9564c0627688622da57f64ad9b312c87e0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Tue, 29 Aug 2023 15:28:04 +0200 Subject: [PATCH 1/9] Prepare test --- .../e2e-runtime-only-backend/lib.rs | 72 +++++++++++++++++-- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/integration-tests/e2e-runtime-only-backend/lib.rs b/integration-tests/e2e-runtime-only-backend/lib.rs index 6f9eb11b71..12cc49f0b2 100644 --- a/integration-tests/e2e-runtime-only-backend/lib.rs +++ b/integration-tests/e2e-runtime-only-backend/lib.rs @@ -31,20 +31,39 @@ pub mod flipper { pub fn get(&self) -> bool { self.value } + + /// Returns the current balance of the Flipper. + #[ink(message)] + pub fn get_contract_balance(&self) -> Balance { + self.env().balance() + } } #[cfg(all(test, feature = "e2e-tests"))] mod e2e_tests { use super::*; - use ink_e2e::ContractsBackend; + use ink::env::DefaultEnvironment; + use ink_e2e::{ + subxt::dynamic::Value, + ChainBackend, + ContractsBackend, + E2EBackend, + InstantiationResult, + }; type E2EResult = std::result::Result>; - #[ink_e2e::test(backend = "runtime-only")] - async fn it_works(mut client: Client) -> E2EResult<()> { - // given + async fn deploy( + client: &mut Client, + ) -> Result< + InstantiationResult< + DefaultEnvironment, + >::EventLog, + >, + >::Error, + > { let constructor = FlipperRef::new(false); - let contract = client + client .instantiate( "e2e-runtime-only-backend", &ink_e2e::alice(), @@ -53,11 +72,15 @@ pub mod flipper { None, ) .await - .expect("instantiate failed"); + } - let mut call = contract.call::(); + #[ink_e2e::test(backend = "runtime-only")] + async fn it_works(mut client: Client) -> E2EResult<()> { + // given + let contract = deploy(&mut client).await.expect("deploy failed"); // when + let mut call = contract.call::(); let _flip_res = client .call(&ink_e2e::bob(), &call.flip(), 0, None) .await @@ -73,5 +96,40 @@ pub mod flipper { Ok(()) } + + #[ink_e2e::test(backend = "runtime-only")] + async fn runtime_call_works() -> E2EResult<()> { + // given + let contract = deploy(&mut client).await.expect("deploy failed"); + let call = contract.call::(); + + let old_balance = client + .call(&ink_e2e::alice(), &call.get_contract_balance(), 0, None) + .await + .expect("get_contract_balance failed") + .return_value(); + + const ENDOWMENT: u128 = 10; + + // when + let call_data = vec![ + Value::unnamed_variant("Id", [Value::from_bytes(&contract.account_id)]), + Value::u128(ENDOWMENT), + ]; + client + .runtime_call(&ink_e2e::alice(), "Balances", "transfer", call_data) + .await + .expect("runtime call failed"); + + // then + let new_balance = client + .call(&ink_e2e::alice(), &call.get_contract_balance(), 0, None) + .await + .expect("get_contract_balance failed") + .return_value(); + + assert_eq!(old_balance + ENDOWMENT, new_balance); + Ok(()) + } } } From 8ded5c0755d39bda57d5e5e8e10846eaeb3ff33e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Tue, 29 Aug 2023 16:04:01 +0200 Subject: [PATCH 2/9] Disgusting, but works --- Cargo.toml | 3 +- crates/e2e/Cargo.toml | 1 + crates/e2e/src/drink_client.rs | 37 +++++++++++++++---- .../e2e-runtime-only-backend/lib.rs | 2 +- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ada0acdb96..13b9052dd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ cargo_metadata = { version = "0.17.0" } cfg-if = { version = "1.0" } contract-build = { version = "3.2.0" } derive_more = { version = "0.99.17", default-features = false } -drink = { version = "0.1.2" } +drink = { version = "0.1.3" } either = { version = "1.5", default-features = false } funty = { version = "2.0.0" } heck = { version = "0.4.0" } @@ -66,6 +66,7 @@ sha2 = { version = "0.10" } sha3 = { version = "0.10" } static_assertions = { version = "1.1" } subxt = { version = "0.31.0" } +subxt-metadata = { version = "0.31.0" } subxt-signer = { version = "0.31.0" } syn = { version = "2" } synstructure = { version = "0.13.0" } diff --git a/crates/e2e/Cargo.toml b/crates/e2e/Cargo.toml index 1a84ff241f..63a7f195bc 100644 --- a/crates/e2e/Cargo.toml +++ b/crates/e2e/Cargo.toml @@ -33,6 +33,7 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } scale = { package = "parity-scale-codec", workspace = true } subxt = { workspace = true } +subxt-metadata = { workspace = true } subxt-signer = { workspace = true, features = ["subxt", "sr25519"] } wasm-instrument = { workspace = true } diff --git a/crates/e2e/src/drink_client.rs b/crates/e2e/src/drink_client.rs index 6f8884e64e..894f328d1d 100644 --- a/crates/e2e/src/drink_client.rs +++ b/crates/e2e/src/drink_client.rs @@ -18,7 +18,10 @@ use crate::{ UploadResult, }; use drink::{ - chain_api::ChainApi, + chain_api::{ + ChainApi, + RuntimeCall, + }, contract_api::ContractApi, runtime::{ MinimalRuntime, @@ -48,7 +51,10 @@ use std::{ marker::PhantomData, path::PathBuf, }; -use subxt::dynamic::Value; +use subxt::{ + dynamic::Value, + tx::TxPayload, +}; use subxt_signer::sr25519::Keypair; pub struct Client { @@ -121,12 +127,29 @@ impl + Send, Hash> ChainBackend for Client( &mut self, - _origin: &Keypair, - _pallet_name: &'a str, - _call_name: &'a str, - _call_data: Vec, + origin: &Keypair, + pallet_name: &'a str, + call_name: &'a str, + call_data: Vec, ) -> Result { - todo!("https://github.com/Cardinal-Cryptography/drink/issues/36") + let raw_metadata: Vec = MinimalRuntime::metadata().into(); + let metadata = subxt_metadata::Metadata::decode(&mut raw_metadata.as_slice()) + .expect("Failed to decode metadata"); + + let call = subxt::dynamic::tx(pallet_name, call_name, call_data); + let encoded_call = call + .encode_call_data(&metadata.into()) + .expect("Failed to encode call data"); + + self.sandbox + .runtime_call( + RuntimeCall::::decode(&mut encoded_call.as_slice()) + .expect("Failed to decode runtime call"), + Some(keypair_to_account(origin)).into(), + ) + .map_err(|_| ())?; + + Ok(()) } } diff --git a/integration-tests/e2e-runtime-only-backend/lib.rs b/integration-tests/e2e-runtime-only-backend/lib.rs index 12cc49f0b2..b7e5fc21d9 100644 --- a/integration-tests/e2e-runtime-only-backend/lib.rs +++ b/integration-tests/e2e-runtime-only-backend/lib.rs @@ -113,7 +113,7 @@ pub mod flipper { // when let call_data = vec![ - Value::unnamed_variant("Id", [Value::from_bytes(&contract.account_id)]), + Value::from_bytes(&contract.account_id), Value::u128(ENDOWMENT), ]; client From 949ac8b1a3fecf3a8d141af67cbd5725d48e260a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Tue, 29 Aug 2023 16:24:18 +0200 Subject: [PATCH 3/9] Document test --- .../e2e-runtime-only-backend/lib.rs | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/integration-tests/e2e-runtime-only-backend/lib.rs b/integration-tests/e2e-runtime-only-backend/lib.rs index b7e5fc21d9..53ba83a47b 100644 --- a/integration-tests/e2e-runtime-only-backend/lib.rs +++ b/integration-tests/e2e-runtime-only-backend/lib.rs @@ -53,8 +53,13 @@ pub mod flipper { type E2EResult = std::result::Result>; + /// Deploys the flipper contract with `initial_value` and returns the contract + /// instantiation result. + /// + /// Uses `ink_e2e::alice()` as the caller. async fn deploy( client: &mut Client, + initial_value: bool, ) -> Result< InstantiationResult< DefaultEnvironment, @@ -62,7 +67,7 @@ pub mod flipper { >, >::Error, > { - let constructor = FlipperRef::new(false); + let constructor = FlipperRef::new(initial_value); client .instantiate( "e2e-runtime-only-backend", @@ -74,10 +79,18 @@ pub mod flipper { .await } + /// Tests standard flipper scenario: + /// - deploy the flipper contract with initial value `false` + /// - flip the flipper + /// - get the flipper's value + /// - assert that the value is `true` #[ink_e2e::test(backend = "runtime-only")] async fn it_works(mut client: Client) -> E2EResult<()> { // given - let contract = deploy(&mut client).await.expect("deploy failed"); + const INITIAL_VALUE: bool = false; + let contract = deploy(&mut client, INITIAL_VALUE) + .await + .expect("deploy failed"); // when let mut call = contract.call::(); @@ -91,16 +104,21 @@ pub mod flipper { .call(&ink_e2e::bob(), &call.get(), 0, None) .await .expect("get failed"); - - assert!(matches!(get_res.return_value(), true)); + assert_eq!(get_res.return_value(), !INITIAL_VALUE); Ok(()) } + /// Tests runtime call scenario: + /// - deploy the flipper contract + /// - get the contract's balance + /// - transfer some funds to the contract using runtime call + /// - get the contract's balance again + /// - assert that the contract's balance increased by the transferred amount #[ink_e2e::test(backend = "runtime-only")] async fn runtime_call_works() -> E2EResult<()> { // given - let contract = deploy(&mut client).await.expect("deploy failed"); + let contract = deploy(&mut client, false).await.expect("deploy failed"); let call = contract.call::(); let old_balance = client From 868fa5ada169ad752502586e8fabaa3a70570c4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Tue, 29 Aug 2023 16:31:30 +0200 Subject: [PATCH 4/9] Document implementation --- crates/e2e/src/backend.rs | 3 +++ crates/e2e/src/drink_client.rs | 26 ++++++++++++++++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/crates/e2e/src/backend.rs b/crates/e2e/src/backend.rs index c25db50c95..6abb0351c1 100644 --- a/crates/e2e/src/backend.rs +++ b/crates/e2e/src/backend.rs @@ -73,6 +73,9 @@ pub trait ChainBackend { /// /// Returns when the transaction is included in a block. The return value contains all /// events that are associated with this transaction. + /// + /// Since we might run node with an arbitrary runtime, this method inherently must + /// support dynamic calls. async fn runtime_call<'a>( &mut self, origin: &Keypair, diff --git a/crates/e2e/src/drink_client.rs b/crates/e2e/src/drink_client.rs index 894f328d1d..a1e6eaec71 100644 --- a/crates/e2e/src/drink_client.rs +++ b/crates/e2e/src/drink_client.rs @@ -132,21 +132,31 @@ impl + Send, Hash> ChainBackend for Client, ) -> Result { + // Since in general, `ChainBackend::runtime_call` must be dynamic, we have to + // perform some translation here in order to invoke strongly-typed drink! + // API. + + // Get metadata of the drink! runtime, so that we can encode the call object. + // Panic on error - metadata of the static im-memory runtime should always be + // available. let raw_metadata: Vec = MinimalRuntime::metadata().into(); let metadata = subxt_metadata::Metadata::decode(&mut raw_metadata.as_slice()) .expect("Failed to decode metadata"); + // Encode the call object. let call = subxt::dynamic::tx(pallet_name, call_name, call_data); - let encoded_call = call - .encode_call_data(&metadata.into()) - .expect("Failed to encode call data"); + let encoded_call = call.encode_call_data(&metadata.into()).map_err(|_| ())?; + + // Decode the call object. + // Panic on error - we just encoded a validated call object, so it should be + // decodable. + let decoded_call = + RuntimeCall::::decode(&mut encoded_call.as_slice()) + .expect("Failed to decode runtime call"); + // Execute the call. self.sandbox - .runtime_call( - RuntimeCall::::decode(&mut encoded_call.as_slice()) - .expect("Failed to decode runtime call"), - Some(keypair_to_account(origin)).into(), - ) + .runtime_call(decoded_call, Some(keypair_to_account(origin)).into()) .map_err(|_| ())?; Ok(()) From 26bc640182b8c74ee9f066210da0169590420b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Wed, 30 Aug 2023 08:15:51 +0200 Subject: [PATCH 5/9] UI tests / codec bump --- .../ink/tests/ui/contract/fail/message-returns-non-codec.stderr | 2 +- .../ink/tests/ui/trait_def/fail/message_output_non_codec.stderr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 877500181f..b1b22479ad 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 @@ -52,7 +52,7 @@ error[E0599]: the method `try_invoke` exists for struct `CallBuilder $CARGO/parity-scale-codec-3.6.4/src/codec.rs + --> $CARGO/parity-scale-codec-3.6.5/src/codec.rs | | pub trait Decode: Sized { | ^^^^^^^^^^^^^^^^^^^^^^^ 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 f232190395..27ce117e51 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 @@ -36,7 +36,7 @@ error[E0599]: the method `try_invoke` exists for struct `CallBuilder $CARGO/parity-scale-codec-3.6.4/src/codec.rs + --> $CARGO/parity-scale-codec-3.6.5/src/codec.rs | | pub trait Decode: Sized { | ^^^^^^^^^^^^^^^^^^^^^^^ From 4c4c419dd05ac16102216bbbe5704404a1e6fedf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 7 Sep 2023 16:53:52 +0200 Subject: [PATCH 6/9] Fix drink version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 13b9052dd8..c5969f2466 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ cargo_metadata = { version = "0.17.0" } cfg-if = { version = "1.0" } contract-build = { version = "3.2.0" } derive_more = { version = "0.99.17", default-features = false } -drink = { version = "0.1.3" } +drink = { version = "=0.1.3" } either = { version = "1.5", default-features = false } funty = { version = "2.0.0" } heck = { version = "0.4.0" } From d5acd7ecc548ba00d1a8a864dadba7eca121a276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 7 Sep 2023 16:57:43 +0200 Subject: [PATCH 7/9] Fix codec version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c5969f2466..3a6ad1fdde 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ quickcheck = { version = "1" } quickcheck_macros = { version = "1" } quote = { version = "1" } rlibc = { version = "1" } -scale = { package = "parity-scale-codec", version = "3.4", default-features = false, features = ["derive"] } +scale = { package = "parity-scale-codec", version = "=3.6.5", default-features = false, features = ["derive"] } scale-decode = { version = "0.9.0", default-features = false } scale-encode = { version = "0.5.0", default-features = false } scale-info = { version = "2.6", default-features = false } From b7d130786452e409dd091393923c0269df04c24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 7 Sep 2023 17:21:35 +0200 Subject: [PATCH 8/9] revert: Fix codec version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3a6ad1fdde..c5969f2466 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ quickcheck = { version = "1" } quickcheck_macros = { version = "1" } quote = { version = "1" } rlibc = { version = "1" } -scale = { package = "parity-scale-codec", version = "=3.6.5", default-features = false, features = ["derive"] } +scale = { package = "parity-scale-codec", version = "3.4", default-features = false, features = ["derive"] } scale-decode = { version = "0.9.0", default-features = false } scale-encode = { version = "0.5.0", default-features = false } scale-info = { version = "2.6", default-features = false } From d8b78526a9489ce1b385c25f73e2d858e2deee2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 7 Sep 2023 17:33:14 +0200 Subject: [PATCH 9/9] Optional subxt-metadata --- crates/e2e/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/e2e/Cargo.toml b/crates/e2e/Cargo.toml index 63a7f195bc..36d7ab1258 100644 --- a/crates/e2e/Cargo.toml +++ b/crates/e2e/Cargo.toml @@ -33,7 +33,7 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } scale = { package = "parity-scale-codec", workspace = true } subxt = { workspace = true } -subxt-metadata = { workspace = true } +subxt-metadata = { workspace = true, optional = true } subxt-signer = { workspace = true, features = ["subxt", "sr25519"] } wasm-instrument = { workspace = true } @@ -53,5 +53,6 @@ default = ["std"] std = [] drink = [ "dep:drink", + "subxt-metadata", "ink_e2e_macro/drink", ]