From f42ea30442c9ab7fac23a78372d8f9b075b57d8f Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 15 Jun 2023 12:29:42 +0100 Subject: [PATCH 01/11] WIP --- .../call-builder-delegate/lib.rs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) 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 index 66ec07f6cc..aa0000ad31 100755 --- a/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs +++ b/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs @@ -89,6 +89,17 @@ mod call_builder { .returns::() .invoke() } + + #[ink(message)] + pub fn try_invoke_short_return_type(&mut self, code_hash: Hash, selector: [u8; 4]) -> i8 { + use ink::env::call::build_call; + + build_call::() + .delegate(code_hash) + .exec_input(ExecutionInput::new(Selector::new(selector))) + .returns::() + .try_invoke() + } } #[cfg(all(test, feature = "e2e-tests"))] @@ -135,6 +146,42 @@ mod call_builder { Ok(()) } + #[ink_e2e::test] + async fn e2e_call_builder_delegate_errors_if_return_data_too_long( + mut client: ink_e2e::Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) + .await; + + let constructor = CallBuilderDelegateTestRef::new(42); + let call_builder = client + .instantiate("call_builder_delegate", &origin, constructor, 0, None) + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + 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.try_invoke_short_return_type(code_hash, selector); + let call_result = client + .call(&origin, &call, 0, None) + .await + .expect("Client failed to call `call_builder::invoke`."); + + assert!( + call_result.is_err(), + "Should fail to decode short type if bytes remain from return data." + ); + + Ok(()) + } + #[ink_e2e::test] async fn e2e_invalid_message_selector_can_be_handled( mut client: ink_e2e::Client, From b700fe5d373e971ade72d89a7c62c3deda2891f6 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 15 Jun 2023 16:43:07 +0100 Subject: [PATCH 02/11] Add failing test for decoding return types --- .../call-builder-delegate/lib.rs | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) 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 index aa0000ad31..a0c736296a 100755 --- a/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs +++ b/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs @@ -18,13 +18,22 @@ #[ink::contract] mod call_builder { - use ink::env::{ - call::{ - build_call, - ExecutionInput, - Selector, + use ink::{ + env::{ + call::{ + build_call, + ExecutionInput, + Selector, + }, + DefaultEnvironment, + }, + prelude::{ + format, + string::{ + String, + ToString, + }, }, - DefaultEnvironment, }; #[ink(storage)] @@ -91,14 +100,25 @@ mod call_builder { } #[ink(message)] - pub fn try_invoke_short_return_type(&mut self, code_hash: Hash, selector: [u8; 4]) -> i8 { + pub fn invoke_short_return_type( + &mut self, + code_hash: Hash, + selector: [u8; 4], + ) -> Result { use ink::env::call::build_call; - build_call::() + let result = build_call::() .delegate(code_hash) .exec_input(ExecutionInput::new(Selector::new(selector))) .returns::() - .try_invoke() + .try_invoke(); + + match result { + Ok(Ok(value)) => Ok(value), + Ok(Err(err)) => Err(format!("LangError: {:?}", err)), + Err(ink::env::Error::Decode(_)) => Err("Decode Error".to_string()), + Err(err) => Err(format!("Env Error: {:?}", err)), + } } } @@ -168,14 +188,20 @@ mod call_builder { .code_hash; let selector = ink::selector_bytes!("get"); - let call = call_builder_call.try_invoke_short_return_type(code_hash, selector); - let call_result = client + let call = call_builder_call.invoke_short_return_type(code_hash, selector); + let call_result: Result = client .call(&origin, &call, 0, None) .await - .expect("Client failed to call `call_builder::invoke`."); + .expect("Client failed to call `call_builder::invoke`.") + .return_value(); assert!( call_result.is_err(), + "Should fail of decoding an `i32` into an `i8`" + ); + assert_eq!( + "Decode Error".to_string(), + call_result.unwrap_err(), "Should fail to decode short type if bytes remain from return data." ); From 65e5814e1816c2491257d5678f01cbb9c0a44601 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 15 Jun 2023 16:48:17 +0100 Subject: [PATCH 03/11] Use decode_all for decoding cross contract delegate call result --- crates/env/src/engine/on_chain/impls.rs | 2 +- .../lang-err-integration-tests/call-builder-delegate/lib.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index bb0bc5ed78..915dfca09b 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -463,7 +463,7 @@ impl TypedEnvBackend for EnvInstance { let call_result = ext::delegate_call(flags, enc_code_hash, enc_input, output); match call_result { Ok(()) | Err(ext::Error::CalleeReverted) => { - let decoded = scale::Decode::decode(&mut &output[..])?; + let decoded = scale::DecodeAll::decode_all(&mut &output[..])?; Ok(decoded) } Err(actual_error) => Err(actual_error.into()), 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 index a0c736296a..2a3a6fa8ce 100755 --- a/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs +++ b/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs @@ -190,9 +190,8 @@ mod call_builder { let selector = ink::selector_bytes!("get"); let call = call_builder_call.invoke_short_return_type(code_hash, selector); let call_result: Result = client - .call(&origin, &call, 0, None) + .call_dry_run(&origin, &call, 0, None) .await - .expect("Client failed to call `call_builder::invoke`.") .return_value(); assert!( From d269e55ebdd101fcbb60192c7c13990ebbb14535 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 15 Jun 2023 16:56:26 +0100 Subject: [PATCH 04/11] Add comment --- .../lang-err-integration-tests/call-builder-delegate/lib.rs | 2 ++ 1 file changed, 2 insertions(+) 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 index 2a3a6fa8ce..81b01feb61 100755 --- a/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs +++ b/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs @@ -99,6 +99,8 @@ mod call_builder { .invoke() } + /// Delegate call to the given contract/selector and attempt to decode the return value + /// into an `i8`. #[ink(message)] pub fn invoke_short_return_type( &mut self, From b6e7c18481bd89a99811ab203dfc6fe399424bf0 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 15 Jun 2023 17:18:15 +0100 Subject: [PATCH 05/11] Move cross contract return value tests to own example --- .../call-builder-return-value/Cargo.toml | 32 ++++ .../call-builder-return-value/lib.rs | 175 ++++++++++++++++++ .../call-builder-delegate/lib.rs | 124 +------------ .../call-builder/lib.rs | 31 ++++ 4 files changed, 244 insertions(+), 118 deletions(-) create mode 100755 integration-tests/call-builder-return-value/Cargo.toml create mode 100755 integration-tests/call-builder-return-value/lib.rs diff --git a/integration-tests/call-builder-return-value/Cargo.toml b/integration-tests/call-builder-return-value/Cargo.toml new file mode 100755 index 0000000000..25e75b8a2a --- /dev/null +++ b/integration-tests/call-builder-return-value/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "call_builder_return_value" +version = "4.2.0" +authors = ["Parity Technologies "] +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/call-builder-return-value/lib.rs b/integration-tests/call-builder-return-value/lib.rs new file mode 100755 index 0000000000..9f4cad28d8 --- /dev/null +++ b/integration-tests/call-builder-return-value/lib.rs @@ -0,0 +1,175 @@ +//! # 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, + }, + prelude::{ + format, + string::{ + String, + ToString, + }, + }, + }; + + #[ink(storage)] + #[derive(Default)] + pub struct CallBuilderReturnValue { + /// Since we're going to `DelegateCall` into the `incrementer` contract, we need + /// to make sure our storage layout matches. + value: i32, + } + + impl CallBuilderReturnValue { + #[ink(constructor)] + pub fn new(value: i32) -> Self { + Self { value } + } + + /// Delegate a call to the given contract/selector and return the result. + #[ink(message)] + pub fn invoke(&mut self, code_hash: Hash, selector: [u8; 4]) -> i32 { + use ink::env::call::build_call; + + build_call::() + .delegate(code_hash) + .exec_input(ExecutionInput::new(Selector::new(selector))) + .returns::() + .invoke() + } + + /// Delegate call to the given contract/selector and attempt to decode the return + /// value into an `i8`. + #[ink(message)] + pub fn invoke_short_return_type( + &mut self, + code_hash: Hash, + selector: [u8; 4], + ) -> Result { + use ink::env::call::build_call; + + let result = build_call::() + .delegate(code_hash) + .exec_input(ExecutionInput::new(Selector::new(selector))) + .returns::() + .try_invoke(); + + match result { + Ok(Ok(value)) => Ok(value), + Ok(Err(err)) => Err(format!("LangError: {:?}", err)), + Err(ink::env::Error::Decode(_)) => Err("Decode Error".to_string()), + Err(err) => Err(format!("Env Error: {:?}", err)), + } + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn e2e_call_builder_return_value_returns_correct_value( + mut client: ink_e2e::Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) + .await; + + let expected_value = 42; + let constructor = CallBuilderReturnValueRef::new(expected_value); + let call_builder = client + .instantiate("call_builder_return_value", &origin, constructor, 0, None) + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + 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_call_builder_return_value_errors_if_return_data_too_long( + mut client: ink_e2e::Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) + .await; + + let constructor = CallBuilderReturnValueRef::new(42); + let call_builder = client + .instantiate("call_builder_return_value", &origin, constructor, 0, None) + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + 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_short_return_type(code_hash, selector); + let call_result: Result = client + .call_dry_run(&origin, &call, 0, None) + .await + .return_value(); + + assert!( + call_result.is_err(), + "Should fail of decoding an `i32` into an `i8`" + ); + assert_eq!( + "Decode Error".to_string(), + call_result.unwrap_err(), + "Should fail to decode short type if bytes remain from return data." + ); + + Ok(()) + } + } +} 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 index 81b01feb61..f3b9997cc9 100755 --- a/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs +++ b/integration-tests/lang-err-integration-tests/call-builder-delegate/lib.rs @@ -18,22 +18,13 @@ #[ink::contract] mod call_builder { - use ink::{ - env::{ - call::{ - build_call, - ExecutionInput, - Selector, - }, - DefaultEnvironment, - }, - prelude::{ - format, - string::{ - String, - ToString, - }, + use ink::env::{ + call::{ + build_call, + ExecutionInput, + Selector, }, + DefaultEnvironment, }; #[ink(storage)] @@ -98,30 +89,6 @@ mod call_builder { .returns::() .invoke() } - - /// Delegate call to the given contract/selector and attempt to decode the return value - /// into an `i8`. - #[ink(message)] - pub fn invoke_short_return_type( - &mut self, - code_hash: Hash, - selector: [u8; 4], - ) -> Result { - use ink::env::call::build_call; - - let result = build_call::() - .delegate(code_hash) - .exec_input(ExecutionInput::new(Selector::new(selector))) - .returns::() - .try_invoke(); - - match result { - Ok(Ok(value)) => Ok(value), - Ok(Err(err)) => Err(format!("LangError: {:?}", err)), - Err(ink::env::Error::Decode(_)) => Err("Decode Error".to_string()), - Err(err) => Err(format!("Env Error: {:?}", err)), - } - } } #[cfg(all(test, feature = "e2e-tests"))] @@ -130,85 +97,6 @@ mod call_builder { type E2EResult = std::result::Result>; - #[ink_e2e::test] - async fn e2e_call_builder_delegate_returns_correct_value( - mut client: ink_e2e::Client, - ) -> 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::(); - - 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_call_builder_delegate_errors_if_return_data_too_long( - mut client: ink_e2e::Client, - ) -> E2EResult<()> { - let origin = client - .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) - .await; - - let constructor = CallBuilderDelegateTestRef::new(42); - let call_builder = client - .instantiate("call_builder_delegate", &origin, constructor, 0, None) - .await - .expect("instantiate failed"); - let mut call_builder_call = call_builder.call::(); - - 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_short_return_type(code_hash, selector); - let call_result: Result = client - .call_dry_run(&origin, &call, 0, None) - .await - .return_value(); - - assert!( - call_result.is_err(), - "Should fail of decoding an `i32` into an `i8`" - ); - assert_eq!( - "Decode Error".to_string(), - call_result.unwrap_err(), - "Should fail to decode short type if bytes remain from return data." - ); - - Ok(()) - } - #[ink_e2e::test] async fn e2e_invalid_message_selector_can_be_handled( mut client: ink_e2e::Client, diff --git a/integration-tests/lang-err-integration-tests/call-builder/lib.rs b/integration-tests/lang-err-integration-tests/call-builder/lib.rs index 1d0d52cc79..ba0a138714 100755 --- a/integration-tests/lang-err-integration-tests/call-builder/lib.rs +++ b/integration-tests/lang-err-integration-tests/call-builder/lib.rs @@ -26,6 +26,13 @@ mod call_builder { Selector, }, DefaultEnvironment, + prelude::{ + format, + string::{ + String, + ToString, + }, + }, }; #[ink(storage)] @@ -87,6 +94,30 @@ mod call_builder { .invoke() } + /// Forward call to the given contract/selector and attempt to decode the return value + /// into an `i8`. + #[ink(message)] + pub fn invoke_short_return_type( + &mut self, + code_hash: Hash, + selector: [u8; 4], + ) -> Result { + use ink::env::call::build_call; + + let result = build_call::() + .call(code_hash) + .exec_input(ExecutionInput::new(Selector::new(selector))) + .returns::() + .try_invoke(); + + match result { + Ok(Ok(value)) => Ok(value), + Ok(Err(err)) => Err(format!("LangError: {:?}", err)), + Err(ink::env::Error::Decode(_)) => Err("Decode Error".to_string()), + Err(err) => Err(format!("Env Error: {:?}", err)), + } + } + /// Instantiate a contract using the `CreateBuilder`. /// /// Since we can't use the `CreateBuilder` in a test environment directly we need From 9911e94ca0ec96548363953c785e6128c678ffa7 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 15 Jun 2023 17:20:42 +0100 Subject: [PATCH 06/11] Rename messages --- integration-tests/call-builder-return-value/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/integration-tests/call-builder-return-value/lib.rs b/integration-tests/call-builder-return-value/lib.rs index 9f4cad28d8..1cf2a8be40 100755 --- a/integration-tests/call-builder-return-value/lib.rs +++ b/integration-tests/call-builder-return-value/lib.rs @@ -52,7 +52,7 @@ mod call_builder { /// Delegate a call to the given contract/selector and return the result. #[ink(message)] - pub fn invoke(&mut self, code_hash: Hash, selector: [u8; 4]) -> i32 { + pub fn delegate_call(&mut self, code_hash: Hash, selector: [u8; 4]) -> i32 { use ink::env::call::build_call; build_call::() @@ -65,7 +65,7 @@ mod call_builder { /// Delegate call to the given contract/selector and attempt to decode the return /// value into an `i8`. #[ink(message)] - pub fn invoke_short_return_type( + pub fn delegate_call_short_return_type( &mut self, code_hash: Hash, selector: [u8; 4], @@ -116,7 +116,7 @@ mod call_builder { .code_hash; let selector = ink::selector_bytes!("get"); - let call = call_builder_call.invoke(code_hash, selector); + let call = call_builder_call.delegate_call(code_hash, selector); let call_result = client .call(&origin, &call, 0, None) .await @@ -153,7 +153,7 @@ mod call_builder { .code_hash; let selector = ink::selector_bytes!("get"); - let call = call_builder_call.invoke_short_return_type(code_hash, selector); + let call = call_builder_call.delegate_call_short_return_type(code_hash, selector); let call_result: Result = client .call_dry_run(&origin, &call, 0, None) .await From 7cd6f3cf09b34071e80482b20ddc55b323b98210 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 15 Jun 2023 17:42:12 +0100 Subject: [PATCH 07/11] Fix up exmaple --- .../call-builder/lib.rs | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/integration-tests/lang-err-integration-tests/call-builder/lib.rs b/integration-tests/lang-err-integration-tests/call-builder/lib.rs index ba0a138714..1d0d52cc79 100755 --- a/integration-tests/lang-err-integration-tests/call-builder/lib.rs +++ b/integration-tests/lang-err-integration-tests/call-builder/lib.rs @@ -26,13 +26,6 @@ mod call_builder { Selector, }, DefaultEnvironment, - prelude::{ - format, - string::{ - String, - ToString, - }, - }, }; #[ink(storage)] @@ -94,30 +87,6 @@ mod call_builder { .invoke() } - /// Forward call to the given contract/selector and attempt to decode the return value - /// into an `i8`. - #[ink(message)] - pub fn invoke_short_return_type( - &mut self, - code_hash: Hash, - selector: [u8; 4], - ) -> Result { - use ink::env::call::build_call; - - let result = build_call::() - .call(code_hash) - .exec_input(ExecutionInput::new(Selector::new(selector))) - .returns::() - .try_invoke(); - - match result { - Ok(Ok(value)) => Ok(value), - Ok(Err(err)) => Err(format!("LangError: {:?}", err)), - Err(ink::env::Error::Decode(_)) => Err("Decode Error".to_string()), - Err(err) => Err(format!("Env Error: {:?}", err)), - } - } - /// Instantiate a contract using the `CreateBuilder`. /// /// Since we can't use the `CreateBuilder` in a test environment directly we need From cd9167e24ae87091b74e111d26e746f9b21fd0f5 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 15 Jun 2023 17:50:53 +0100 Subject: [PATCH 08/11] Failing test for decoding forward call value --- .../call-builder-return-value/lib.rs | 126 +++++++++++++++++- integration-tests/incrementer/lib.rs | 5 + 2 files changed, 127 insertions(+), 4 deletions(-) diff --git a/integration-tests/call-builder-return-value/lib.rs b/integration-tests/call-builder-return-value/lib.rs index 1cf2a8be40..2a344dbf48 100755 --- a/integration-tests/call-builder-return-value/lib.rs +++ b/integration-tests/call-builder-return-value/lib.rs @@ -21,7 +21,6 @@ mod call_builder { use ink::{ env::{ call::{ - build_call, ExecutionInput, Selector, }, @@ -85,16 +84,53 @@ mod call_builder { Err(err) => Err(format!("Env Error: {:?}", err)), } } + + /// Forward a call to the given contract/selector and return the result. + #[ink(message)] + pub fn forward_call(&mut self, address: AccountId, selector: [u8; 4]) -> i32 { + use ink::env::call::build_call; + + build_call::() + .call(address) + .exec_input(ExecutionInput::new(Selector::new(selector))) + .returns::() + .invoke() + } + + /// Forward call to the given contract/selector and attempt to decode the return + /// value into an `i8`. + #[ink(message)] + pub fn forward_call_short_return_type( + &mut self, + address: AccountId, + selector: [u8; 4], + ) -> Result { + use ink::env::call::build_call; + + let result = build_call::() + .call(address) + .exec_input(ExecutionInput::new(Selector::new(selector))) + .returns::() + .try_invoke(); + + match result { + Ok(Ok(value)) => Ok(value), + Ok(Err(err)) => Err(format!("LangError: {:?}", err)), + Err(ink::env::Error::Decode(_)) => Err("Decode Error".to_string()), + Err(err) => Err(format!("Env Error: {:?}", err)), + } + } } #[cfg(all(test, feature = "e2e-tests"))] mod e2e_tests { use super::*; + use incrementer::IncrementerRef; type E2EResult = std::result::Result>; #[ink_e2e::test] - async fn e2e_call_builder_return_value_returns_correct_value( + async fn e2e_delegate_call_return_value_returns_correct_value( mut client: ink_e2e::Client, ) -> E2EResult<()> { let origin = client @@ -132,7 +168,7 @@ mod call_builder { } #[ink_e2e::test] - async fn e2e_call_builder_return_value_errors_if_return_data_too_long( + async fn e2e_delegate_call_return_value_errors_if_return_data_too_long( mut client: ink_e2e::Client, ) -> E2EResult<()> { let origin = client @@ -153,7 +189,89 @@ mod call_builder { .code_hash; let selector = ink::selector_bytes!("get"); - let call = call_builder_call.delegate_call_short_return_type(code_hash, selector); + let call = + call_builder_call.delegate_call_short_return_type(code_hash, selector); + let call_result: Result = client + .call_dry_run(&origin, &call, 0, None) + .await + .return_value(); + + assert!( + call_result.is_err(), + "Should fail of decoding an `i32` into an `i8`" + ); + assert_eq!( + "Decode Error".to_string(), + call_result.unwrap_err(), + "Should fail to decode short type if bytes remain from return data." + ); + + Ok(()) + } + + #[ink_e2e::test] + async fn e2e_forward_call_return_value_returns_correct_value( + mut client: ink_e2e::Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) + .await; + + let constructor = CallBuilderReturnValueRef::new(0); + let call_builder = client + .instantiate("call_builder_return_value", &origin, constructor, 0, None) + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + let expected_value = 42; + let incrementer_constructor = IncrementerRef::new(expected_value); + let incrementer = client + .instantiate("incrementer", &origin, incrementer_constructor, 0, None) + .await + .expect("instantiate failed"); + + let selector = ink::selector_bytes!("get"); + let call = call_builder_call.forward_call(incrementer.account_id, 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_forward_call_return_value_errors_if_return_data_too_long( + mut client: ink_e2e::Client, + ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) + .await; + + let constructor = CallBuilderReturnValueRef::new(0); + let call_builder = client + .instantiate("call_builder_return_value", &origin, constructor, 0, None) + .await + .expect("instantiate failed"); + let mut call_builder_call = call_builder.call::(); + + let expected_value = 42; + let incrementer_constructor = IncrementerRef::new(expected_value); + let incrementer = client + .instantiate("incrementer", &origin, incrementer_constructor, 0, None) + .await + .expect("instantiate failed"); + + let selector = ink::selector_bytes!("get"); + let call = call_builder_call + .forward_call_short_return_type(incrementer.account_id, selector); let call_result: Result = client .call_dry_run(&origin, &call, 0, None) .await diff --git a/integration-tests/incrementer/lib.rs b/integration-tests/incrementer/lib.rs index 86cb6fc147..cd0cd42fe7 100644 --- a/integration-tests/incrementer/lib.rs +++ b/integration-tests/incrementer/lib.rs @@ -1,5 +1,10 @@ #![cfg_attr(not(feature = "std"), no_std, no_main)] +pub use self::incrementer::{ + Incrementer, + IncrementerRef, +}; + #[ink::contract] mod incrementer { #[ink(storage)] From 3a229f2c39478a5a070f4c7fa4f7b82a4c174747 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 15 Jun 2023 17:53:20 +0100 Subject: [PATCH 09/11] Use decode_all for decoding contract forward result --- crates/env/src/engine/on_chain/impls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index 915dfca09b..b21ccea386 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -434,7 +434,7 @@ impl TypedEnvBackend for EnvInstance { ); match call_result { Ok(()) | Err(ext::Error::CalleeReverted) => { - let decoded = scale::Decode::decode(&mut &output[..])?; + let decoded = scale::DecodeAll::decode_all(&mut &output[..])?; Ok(decoded) } Err(actual_error) => Err(actual_error.into()), From 02286d73de8a2233536ad760f02f84849acaa85d Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 16 Jun 2023 09:28:30 +0100 Subject: [PATCH 10/11] Replace comment --- .../call-builder-return-value/lib.rs | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/integration-tests/call-builder-return-value/lib.rs b/integration-tests/call-builder-return-value/lib.rs index 2a344dbf48..f3b475d937 100755 --- a/integration-tests/call-builder-return-value/lib.rs +++ b/integration-tests/call-builder-return-value/lib.rs @@ -1,18 +1,5 @@ -//! # 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. +//! This contract is used to ensure that the values returned by cross contract calls using the +//! [`CallBuilder`](`ink::env::call::CallBuilder`) are properly decoded. #![cfg_attr(not(feature = "std"), no_std, no_main)] From fcd97497c924d6428513244c5bb6e2332cb94d84 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 16 Jun 2023 09:30:38 +0100 Subject: [PATCH 11/11] Fmt --- integration-tests/call-builder-return-value/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/call-builder-return-value/lib.rs b/integration-tests/call-builder-return-value/lib.rs index f3b475d937..b68210f04f 100755 --- a/integration-tests/call-builder-return-value/lib.rs +++ b/integration-tests/call-builder-return-value/lib.rs @@ -1,5 +1,5 @@ -//! This contract is used to ensure that the values returned by cross contract calls using the -//! [`CallBuilder`](`ink::env::call::CallBuilder`) are properly decoded. +//! This contract is used to ensure that the values returned by cross contract calls using +//! the [`CallBuilder`](`ink::env::call::CallBuilder`) are properly decoded. #![cfg_attr(not(feature = "std"), no_std, no_main)]