CVE-2023-34449: Incorrect Check of Function Return Value (CWE-253)
value: ink! is an embedded domain specific language to write smart contracts in Rust for blockchains built on the Substrate framework. Starting in version 4.0.0 and prior to version 4.2.1, the return value when using delegate call mechanics, either through `CallBuilder::delegate` or `ink_env::invoke_contract_delegate`, is decoded incorrectly. This bug was related to the mechanics around decoding a call's return buffer, which was changed as part of pull request 1450. Since this feature was only released in ink! 4.0.0, no previous versions are affected. Users who have an ink! 4.x series contract should upgrade to 4.2.1 to receive a patch.
CWE description: The product incorrectly checks a return value from a function, which prevents it from detecting errors or exceptional conditions.
CVSS v3 base score: 5.3
Published: 2023-06-14T21:15Z
Repository: https://github.com/paritytech/ink
Language: Rust
Files changed: crates/env/src/api.rs, crates/env/src/backend.rs, crates/env/src/call/call_builder.rs, crates/env/src/engine/off_chain/impls.rs, crates/env/src/engine/on_chain/impls.rs, crates/ink/src/env_access.rs, integration-tests/lang-err-integration-tests/call-builder-delegate/Cargo.toml, integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs
Commit date: 2023-06-14 19:44:19 +0200
Commit message: Merge pull request from GHSA-853p-5678-hv8f

* Handle `LangError` from `DelegateCall`

* Add basic delegate E2E test

* Get test working with integration flipper

* Use `incrementer` for test instead

* Add tests to make sure that `LangError`s can be handled

* Get rid of references to the interation Flipper
Diff: diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs
index f62d2955..e0c59586 100644
--- a/crates/env/src/api.rs
+++ b/crates/env/src/api.rs
@@ -298,11 +298,11 @@ where
 /// - If the specified code hash does not exist.
 /// - If arguments passed to the called code message are invalid.
 /// - If the called code execution has trapped.
 pub fn invoke_contract_delegate<E, Args, R>(
     params: &CallParams<E, DelegateCall<E>, Args, R>,
-) -> Result<R>
+) -> Result<ink_primitives::MessageResult<R>>
 where
     E: Environment,
     Args: scale::Encode,
     R: scale::Decode,
 {
diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs
index 05b8f890..c7ff7a0b 100644
--- a/crates/env/src/backend.rs
+++ b/crates/env/src/backend.rs
@@ -431,11 +431,11 @@ pub trait TypedEnvBackend: EnvBackend {
     /// For more details visit:
     /// [`invoke_contract_delegate`][`crate::invoke_contract_delegate`]
     fn invoke_contract_delegate<E, Args, R>(
         &mut self,
         call_data: &CallParams<E, DelegateCall<E>, Args, R>,
-    ) -> Result<R>
+    ) -> Result<ink_primitives::MessageResult<R>>
     where
         E: Environment,
         Args: scale::Encode,
         R: scale::Decode;
 
diff --git a/crates/env/src/call/call_builder.rs b/crates/env/src/call/call_builder.rs
index 50875acc..eedc2e15 100644
--- a/crates/env/src/call/call_builder.rs
+++ b/crates/env/src/call/call_builder.rs
@@ -149,29 +149,34 @@ where
     ///
     /// Returns the result of the contract execution.
     ///
     /// # Panics
     ///
-    /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`]. If you
-    /// want to handle those use the [`try_invoke`][`CallParams::try_invoke`] method
-    /// instead.
+    /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] or an
+    /// [`ink::primitives::LangError`][`ink_primitives::LangError`]. If you want to handle
+    /// those use the [`try_invoke`][`CallParams::try_invoke`] method instead.
     pub fn invoke(&self) -> R {
-        crate::invoke_contract_delegate(self).unwrap_or_else(|env_error| {
-            panic!("Cross-contract call failed with {env_error:?}")
-        })
+        crate::invoke_contract_delegate(self)
+            .unwrap_or_else(|env_error| {
+                panic!("Cross-contract call failed with {env_error:?}")
+            })
+            .unwrap_or_else(|lang_error| {
+                panic!("Cross-contract call failed with {lang_error:?}")
+            })
     }
 
     /// Invoke the contract using Delegate Call semantics with the given built-up call
     /// parameters.
     ///
     /// Returns the result of the contract execution.
     ///
     /// # Note
     ///
-    /// On failure this returns an [`ink::env::Error`][`crate::Error`] which can be
+    /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner
+    /// [`ink::primitives::LangError`][`ink_primitives::LangError`], both of which can be
     /// handled by the caller.
-    pub fn try_invoke(&self) -> Result<R, crate::Error> {
+    pub fn try_invoke(&self) -> Result<ink_primitives::MessageResult<R>, crate::Error> {
         crate::invoke_contract_delegate(self)
     }
 }
 
 /// Returns a new [`CallBuilder`] to build up the parameters to a cross-contract call.
@@ -712,11 +717,11 @@ where
     ///
     /// # Note
     ///
     /// On failure this an [`ink::env::Error`][`crate::Error`] which can be handled by the
     /// caller.
-    pub fn try_invoke(self) -> Result<(), Error> {
+    pub fn try_invoke(self) -> Result<ink_primitives::MessageResult<()>, Error> {
         self.params().try_invoke()
     }
 }
 
 impl<E, Args, R>
@@ -759,23 +764,24 @@ where
     /// Invokes the cross-chain function call using Delegate Call semantics and returns
     /// the result.
     ///
     /// # Panics
     ///
-    /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`]
-    /// If you want to handle those use the [`try_invoke`][`CallBuilder::try_invoke`]
-    /// method instead.
+    /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] or an
+    /// [`ink::primitives::LangError`][`ink_primitives::LangError`]. If you want to handle
+    /// those use the [`try_invoke`][`CallBuilder::try_invoke`] method instead.
     pub fn invoke(self) -> R {
         self.params().invoke()
     }
 
     /// Invokes the cross-chain function call using Delegate Call semantics and returns
     /// the result.
     ///
     /// # Note
     ///
-    /// On failure this an [`ink::env::Error`][`crate::Error`] which can be handled by the
-    /// caller.
-    pub fn try_invoke(self) -> Result<R, Error> {
+    /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner
+    /// [`ink::primitives::LangError`][`ink_primitives::LangError`], both of which can be
+    /// handled by the caller.
+    pub fn try_invoke(self) -> Result<ink_primitives::MessageResult<R>, Error> {
         self.params().try_invoke()
     }
 }
diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs
index 5475acd2..1970b280 100644
--- a/crates/env/src/engine/off_chain/impls.rs
+++ b/crates/env/src/engine/off_chain/impls.rs
@@ -451,11 +451,11 @@ impl TypedEnvBackend for EnvInstance {
     }
 
     fn invoke_contract_delegate<E, Args, R>(
         &mut self,
         params: &CallParams<E, DelegateCall<E>, Args, R>,
-    ) -> Result<R>
+    ) -> Result<ink_primitives::MessageResult<R>>
     where
         E: Environment,
         Args: scale::Encode,
         R: scale::Decode,
     {
diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs
index 429dba7f..bb0bc5ed 100644
--- a/crates/env/src/engine/on_chain/impls.rs
+++ b/crates/env/src/engine/on_chain/impls.rs
@@ -442,11 +442,11 @@ impl TypedEnvBackend for EnvInstance {
     }
 
     fn invoke_contract_delegate<E, Args, R>(
         &mut self,
         params: &CallParams<E, DelegateCall<E>, Args, R>,
-    ) -> Result<R>
+    ) -> Result<ink_primitives::MessageResult<R>>
     where
         E: Environment,
         Args: scale::Encode,
         R: scale::Decode,
     {
diff --git a/crates/ink/src/env_access.rs b/crates/ink/src/env_access.rs
index 6bab424c..02302066 100644
--- a/crates/ink/src/env_access.rs
+++ b/crates/ink/src/env_access.rs
@@ -629,11 +629,11 @@ where
     ///
     /// For more details visit: [`ink_env::invoke_contract_delegate`]
     pub fn invoke_contract_delegate<Args, R>(
         self,
         params: &CallParams<E, DelegateCall<E>, Args, R>,
-    ) -> Result<R>
+    ) -> Result<ink_primitives::MessageResult<R>>
     where
         Args: scale::Encode,
         R: scale::Decode,
     {
         ink_env::invoke_contract_delegate::<E, Args, R>(params)
diff --git a/integration-tests/lang-err-integration-tests/call-builder-delegate/Cargo.toml b/integration-tests/lang-err-integration-tests/call-builder-delegate/Cargo.toml
new file mode 100755
index 00000000..e4301348
--- /dev/null
+++ b/integration-tests/lang-err-integration-tests/call-builder-delegate/Cargo.toml
@@ -0,0 +1,32 @@
+[package]
+name = "call_builder_delegate"
+version = "4.2.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+ink = { path = "../../../crates/ink", default-features = false }
+
+scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
+scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true }
+
+incrementer = { path = "../../incrementer", default-features = false, features = ["ink-as-dependency"] }
+
+[dev-dependencies]
+ink_e2e = { path = "../../../crates/e2e" }
+
+[lib]
+path = "lib.rs"
+
+[features]
+default = ["std"]
+std = [
+    "ink/std",
+    "scale/std",
+    "scale-info/std",
+
+    "incrementer/std",
+]
+ink-as-dependency = []
+e2e-tests = []
diff --git a/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs b/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs
new file mode 100755
index 00000000..66ec07f6
--- /dev/null
+++ b/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs
@@ -0,0 +1,211 @@
+//! # Integration Tests for `LangError`
+//!
+//! This contract is used to ensure that the behavior around `LangError`s works as
+//! expected.
+//!
+//! In particular, it exercises the codepaths that stem from the usage of the
+//! [`CallBuilder`](`ink::env::call::CallBuilder`) and
+//! [`CreateBuilder`](`ink::env::call::CreateBuilder`) structs.
+//!
+//! This differs from the codepath used by external tooling, such as `cargo-contract` or
+//! the `Contracts-UI` which instead depend on methods from the Contracts pallet which are
+//! exposed via RPC.
+//!
+//! Note that during testing we make use of ink!'s end-to-end testing features, so ensure
+//! that you have a node which includes the Contracts pallet running alongside your tests.
+
+#![cfg_attr(not(feature = "std"), no_std, no_main)]
+
+#[ink::contract]
+mod call_builder {
+    use ink::env::{
+        call::{
+            build_call,
+            ExecutionInput,
+            Selector,
+        },
+        DefaultEnvironment,
+    };
+
+    #[ink(storage)]
+    #[derive(Default)]
+    pub struct CallBuilderDelegateTest {
+        /// Since we're going to `DelegateCall` into the `incrementer` contract, we need
+        /// to make sure our storage layout matches.
+        value: i32,
+    }
+
+    impl CallBuilderDelegateTest {
+        #[ink(constructor)]
+        pub fn new(value: i32) -> Self {
+            Self { value }
+        }
+
+        /// Call a contract using the `CallBuilder`.
+        ///
+        /// Since we can't use the `CallBuilder` in a test environment directly we need
+        /// this wrapper to test things like crafting calls with invalid
+        /// selectors.
+        ///
+        /// We also wrap the output in an `Option` since we can't return a `Result`
+        /// directly from a contract message without erroring out ourselves.
+        #[ink(message)]
+        pub fn delegate(
+            &mut self,
+            code_hash: Hash,
+            selector: [u8; 4],
+        ) -> Option<ink::LangError> {
+            let result = build_call::<DefaultEnvironment>()
+                .delegate(code_hash)
+                .exec_input(ExecutionInput::new(Selector::new(selector)))
+                .returns::<bool>()
+                .try_invoke()
+                .expect("Error from the Contracts pallet.");
+
+            match result {
+                Ok(_) => None,
+                Err(e @ ink::LangError::CouldNotReadInput) => Some(e),
+                Err(_) => {
+                    unimplemented!("No other `LangError` variants exist at the moment.")
+                }
+            }
+        }
+
+        /// Call a contract using the `CallBuilder`.
+        ///
+        /// Since we can't use the `CallBuilder` in a test environment directly we need
+        /// this wrapper to test things like crafting calls with invalid
+        /// selectors.
+        ///
+        /// This message does not allow the caller to handle any `LangErrors`, for that
+        /// use the `call` message instead.
+        #[ink(message)]
+        pub fn invoke(&mut self, code_hash: Hash, selector: [u8; 4]) -> i32 {
+            use ink::env::call::build_call;
+
+            build_call::<DefaultEnvironment>()
+                .delegate(code_hash)
+                .exec_input(ExecutionInput::new(Selector::new(selector)))
+                .returns::<i32>()
+                .invoke()
+        }
+    }
+
+    #[cfg(all(test, feature = "e2e-tests"))]
+    mod e2e_tests {
+        use super::*;
+
+        type E2EResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
+
+        #[ink_e2e::test]
+        async fn e2e_call_builder_delegate_returns_correct_value(
+            mut client: ink_e2e::Client<C, E>,
+        ) -> E2EResult<()> {
+            let origin = client
+                .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000)
+                .await;
+
+            let expected_value = 42;
+            let constructor = CallBuilderDelegateTestRef::new(expected_value);
+            let call_builder = client
+                .instantiate("call_builder_delegate", &origin, constructor, 0, None)
+                .await
+                .expect("instantiate failed");
+            let mut call_builder_call = call_builder.call::<CallBuilderDelegateTest>();
+
+            let code_hash = client
+                .upload("incrementer", &origin, None)
+                .await
+                .expect("upload `incrementer` failed")
+                .code_hash;
+
+            let selector = ink::selector_bytes!("get");
+            let call = call_builder_call.invoke(code_hash, selector);
+            let call_result = client
+                .call(&origin, &call, 0, None)
+                .await
+                .expect("Client failed to call `call_builder::invoke`.")
+                .return_value();
+
+            assert_eq!(
+                call_result, expected_value,
+                "Decoded an unexpected value from the call."
+            );
+
+            Ok(())
+        }
+
+        #[ink_e2e::test]
+        async fn e2e_invalid_message_selector_can_be_handled(
+            mut client: ink_e2e::Client<C, E>,
+        ) -> E2EResult<()> {
+            let origin = client
+                .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000)
+                .await;
+
+            let constructor = CallBuilderDelegateTestRef::new(Default::default());
+            let call_builder_contract = client
+                .instantiate("call_builder_delegate", &origin, constructor, 0, None)
+                .await
+                .expect("instantiate failed");
+            let mut call_builder_call =
+                call_builder_contract.call::<CallBuilderDelegateTest>();
+
+            let code_hash = client
+                .upload("incrementer", &origin, None)
+                .await
+                .expect("upload `incrementer` failed")
+                .code_hash;
+
+            let selector = ink::selector_bytes!("invalid_selector");
+            let call = call_builder_call.delegate(code_hash, selector);
+            let call_result = client
+                .call(&origin, &call, 0, None)
+                .await
+                .expect("Calling `call_builder::delegate` failed");
+
+            assert!(matches!(
+                call_result.return_value(),
+                Some(ink::LangError::CouldNotReadInput)
+            ));
+
+            Ok(())
+        }
+
+        #[ink_e2e::test]
+        async fn e2e_invalid_message_selector_panics_on_invoke(
+            mut client: ink_e2e::Client<C, E>,
+        ) -> E2EResult<()> {
+            let origin = client
+                .create_and_fund_account(&ink_e2e::charlie(), 10_000_000_000_000)
+                .await;
+
+            let constructor = CallBuilderDelegateTestRef::new(Default::default());
+            let call_builder_contract = client
+                .instantiate("call_builder_delegate", &origin, constructor, 0, None)
+                .await
+                .expect("instantiate failed");
+            let mut call_builder_call =
+                call_builder_contract.call::<CallBuilderDelegateTest>();
+
+            let code_hash = client
+                .upload("incrementer", &origin, None)
+                .await
+                .expect("upload `incrementer` failed")
+                .code_hash;
+
+            // Since `LangError`s can't be handled by the `CallBuilder::invoke()` method
+            // we expect this to panic.
+            let selector = ink::selector_bytes!("invalid_selector");
+            let call = call_builder_call.invoke(code_hash, selector);
+            let call_result = client.call_dry_run(&origin, &call, 0, None).await;
+
+            assert!(call_result.is_err());
+            assert!(call_result
+                .debug_message()
+                .contains("Cross-contract call failed with CouldNotReadInput"));
+
+            Ok(())
+        }
+    }
+}

Vulnerable code: ) -> Result<R>
    ) -> Result<R>
    /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`]. If you
    /// want to handle those use the [`try_invoke`][`CallParams::try_invoke`] method
    /// instead.
        crate::invoke_contract_delegate(self).unwrap_or_else(|env_error| {
            panic!("Cross-contract call failed with {env_error:?}")
        })
    /// On failure this returns an [`ink::env::Error`][`crate::Error`] which can be
    pub fn try_invoke(&self) -> Result<R, crate::Error> {
    pub fn try_invoke(self) -> Result<(), Error> {
    /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`]
    /// If you want to handle those use the [`try_invoke`][`CallBuilder::try_invoke`]
    /// method instead.
    /// On failure this an [`ink::env::Error`][`crate::Error`] which can be handled by the
    /// caller.
    pub fn try_invoke(self) -> Result<R, Error> {
    ) -> Result<R>
    ) -> Result<R>
    ) -> Result<R>
Fixed code: ) -> Result<ink_primitives::MessageResult<R>>
    ) -> Result<ink_primitives::MessageResult<R>>
    /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] or an
    /// [`ink::primitives::LangError`][`ink_primitives::LangError`]. If you want to handle
    /// those use the [`try_invoke`][`CallParams::try_invoke`] method instead.
        crate::invoke_contract_delegate(self)
            .unwrap_or_else(|env_error| {
                panic!("Cross-contract call failed with {env_error:?}")
            })
            .unwrap_or_else(|lang_error| {
                panic!("Cross-contract call failed with {lang_error:?}")
            })
    /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner
    /// [`ink::primitives::LangError`][`ink_primitives::LangError`], both of which can be
    pub fn try_invoke(&self) -> Result<ink_primitives::MessageResult<R>, crate::Error> {
    pub fn try_invoke(self) -> Result<ink_primitives::MessageResult<()>, Error> {
    /// This method panics if it encounters an [`ink::env::Error`][`crate::Error`] or an
    /// [`ink::primitives::LangError`][`ink_primitives::LangError`]. If you want to handle
    /// those use the [`try_invoke`][`CallBuilder::try_invoke`] method instead.
    /// On failure this returns an outer [`ink::env::Error`][`crate::Error`] or inner
    /// [`ink::primitives::LangError`][`ink_primitives::LangError`], both of which can be
    /// handled by the caller.
    pub fn try_invoke(self) -> Result<ink_primitives::MessageResult<R>, Error> {
    ) -> Result<ink_primitives::MessageResult<R>>
    ) -> Result<ink_primitives::MessageResult<R>>
    ) -> Result<ink_primitives::MessageResult<R>>
[package]
name = "call_builder_delegate"
version = "4.2.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
publish = false

[dependencies]
ink = { path = "../../../crates/ink", default-features = false }

scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true }

incrementer = { path = "../../incrementer", default-features = false, features = ["ink-as-dependency"] }

[dev-dependencies]
ink_e2e = { path = "../../../crates/e2e" }

[lib]
path = "lib.rs"

[features]
default = ["std"]
std = [
    "ink/std",
    "scale/std",
    "scale-info/std",

    "incrementer/std",
]
ink-as-dependency = []
e2e-tests = []
//! # Integration Tests for `LangError`
//!
//! This contract is used to ensure that the behavior around `LangError`s works as
//! expected.
//!
//! In particular, it exercises the codepaths that stem from the usage of the
//! [`CallBuilder`](`ink::env::call::CallBuilder`) and
//! [`CreateBuilder`](`ink::env::call::CreateBuilder`) structs.
//!
//! This differs from the codepath used by external tooling, such as `cargo-contract` or
//! the `Contracts-UI` which instead depend on methods from the Contracts pallet which are
//! exposed via RPC.
//!
//! Note that during testing we make use of ink!'s end-to-end testing features, so ensure
//! that you have a node which includes the Contracts pallet running alongside your tests.

#![cfg_attr(not(feature = "std"), no_std, no_main)]

#[ink::contract]
mod call_builder {
    use ink::env::{
        call::{
            build_call,
            ExecutionInput,
            Selector,
        },
        DefaultEnvironment,
    };

    #[ink(storage)]
    #[derive(Default)]
    pub struct CallBuilderDelegateTest {
        /// Since we're going to `DelegateCall` into the `incrementer` contract, we need
        /// to make sure our storage layout matches.
        value: i32,
    }

    impl CallBuilderDelegateTest {
        #[ink(constructor)]
        pub fn new(value: i32) -> Self {
            Self { value }
        }

        /// Call a contract using the `CallBuilder`.
        ///
        /// Since we can't use the `CallBuilder` in a test environment directly we need
        /// this wrapper to test things like crafting calls with invalid
        /// selectors.
        ///
        /// We also wrap the output in an `Option` since we can't return a `Result`
        /// directly from a contract message without erroring out ourselves.
        #[ink(message)]
        pub fn delegate(
            &mut self,
            code_hash: Hash,
            selector: [u8; 4],
        ) -> Option<ink::LangError> {
            let result = build_call::<DefaultEnvironment>()
                .delegate(code_hash)
                .exec_input(ExecutionInput::new(Selector::new(selector)))
                .returns::<bool>()
                .try_invoke()
                .expect("Error from the Contracts pallet.");

            match result {
                Ok(_) => None,
                Err(e @ ink::LangError::CouldNotReadInput) => Some(e),
                Err(_) => {
                    unimplemented!("No other `LangError` variants exist at the moment.")
                }
            }
        }

        /// Call a contract using the `CallBuilder`.
        ///
        /// Since we can't use the `CallBuilder` in a test environment directly we need
        /// this wrapper to test things like crafting calls with invalid
        /// selectors.
        ///
        /// This message does not allow the caller to handle any `LangErrors`, for that
        /// use the `call` message instead.
        #[ink(message)]
        pub fn invoke(&mut self, code_hash: Hash, selector: [u8; 4]) -> i32 {
            use ink::env::call::build_call;

            build_call::<DefaultEnvironment>()
                .delegate(code_hash)
                .exec_input(ExecutionInput::new(Selector::new(selector)))
                .returns::<i32>()
                .invoke()
        }
    }

    #[cfg(all(test, feature = "e2e-tests"))]
    mod e2e_tests {
        use super::*;

        type E2EResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;

        #[ink_e2e::test]
        async fn e2e_call_builder_delegate_returns_correct_value(
            mut client: ink_e2e::Client<C, E>,
        ) -> E2EResult<()> {
            let origin = client
                .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000)
                .await;

            let expected_value = 42;
            let constructor = CallBuilderDelegateTestRef::new(expected_value);
            let call_builder = client
                .instantiate("call_builder_delegate", &origin, constructor, 0, None)
                .await
                .expect("instantiate failed");
            let mut call_builder_call = call_builder.call::<CallBuilderDelegateTest>();

            let code_hash = client
                .upload("incrementer", &origin, None)
                .await
                .expect("upload `incrementer` failed")
                .code_hash;

            let selector = ink::selector_bytes!("get");
            let call = call_builder_call.invoke(code_hash, selector);
            let call_result = client
                .call(&origin, &call, 0, None)
                .await
                .expect("Client failed to call `call_builder::invoke`.")
                .return_value();

            assert_eq!(
                call_result, expected_value,
                "Decoded an unexpected value from the call."
            );

            Ok(())
        }

        #[ink_e2e::test]
        async fn e2e_invalid_message_selector_can_be_handled(
            mut client: ink_e2e::Client<C, E>,
        ) -> E2EResult<()> {
            let origin = client
                .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000)
                .await;

            let constructor = CallBuilderDelegateTestRef::new(Default::default());
            let call_builder_contract = client
                .instantiate("call_builder_delegate", &origin, constructor, 0, None)
                .await
                .expect("instantiate failed");
            let mut call_builder_call =
                call_builder_contract.call::<CallBuilderDelegateTest>();

            let code_hash = client
                .upload("incrementer", &origin, None)
                .await
                .expect("upload `incrementer` failed")
                .code_hash;

            let selector = ink::selector_bytes!("invalid_selector");
            let call = call_builder_call.delegate(code_hash, selector);
            let call_result = client
                .call(&origin, &call, 0, None)
                .await
                .expect("Calling `call_builder::delegate` failed");

            assert!(matches!(
                call_result.return_value(),
                Some(ink::LangError::CouldNotReadInput)
            ));

            Ok(())
        }

        #[ink_e2e::test]
        async fn e2e_invalid_message_selector_panics_on_invoke(
            mut client: ink_e2e::Client<C, E>,
        ) -> E2EResult<()> {
            let origin = client
                .create_and_fund_account(&ink_e2e::charlie(), 10_000_000_000_000)
                .await;

            let constructor = CallBuilderDelegateTestRef::new(Default::default());
            let call_builder_contract = client
                .instantiate("call_builder_delegate", &origin, constructor, 0, None)
                .await
                .expect("instantiate failed");
            let mut call_builder_call =
                call_builder_contract.call::<CallBuilderDelegateTest>();

            let code_hash = client
                .upload("incrementer", &origin, None)
                .await
                .expect("upload `incrementer` failed")
                .code_hash;

            // Since `LangError`s can't be handled by the `CallBuilder::invoke()` method
            // we expect this to panic.
            let selector = ink::selector_bytes!("invalid_selector");
            let call = call_builder_call.invoke(code_hash, selector);
            let call_result = client.call_dry_run(&origin, &call, 0, None).await;

            assert!(call_result.is_err());
            assert!(call_result
                .debug_message()
                .contains("Cross-contract call failed with CouldNotReadInput"));

            Ok(())
        }
    }
}