-
Notifications
You must be signed in to change notification settings - Fork 477
Use decode_all for decoding cross contract call result
#1810
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 9 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
f42ea30
WIP
ascjones b700fe5
Add failing test for decoding return types
ascjones 65e5814
Use decode_all for decoding cross contract delegate call result
ascjones d269e55
Add comment
ascjones b6e7c18
Move cross contract return value tests to own example
ascjones 9911e94
Rename messages
ascjones 7cd6f3c
Fix up exmaple
ascjones cd9167e
Failing test for decoding forward call value
ascjones 3a229f2
Use decode_all for decoding contract forward result
ascjones 02286d7
Replace comment
ascjones fcd9749
Fmt
ascjones 1e84e35
Merge branch 'master' into aj/decode-all
ascjones 2e89d3e
Merge branch 'master' into aj/decode-all
ascjones 248d1a9
Merge branch 'master' into aj/decode-all
ascjones File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| [package] | ||
| name = "call_builder_return_value" | ||
| 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 = [] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,293 @@ | ||
| //! # 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::{ | ||
| 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 delegate_call(&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() | ||
| } | ||
|
|
||
| /// Delegate call to the given contract/selector and attempt to decode the return | ||
| /// value into an `i8`. | ||
| #[ink(message)] | ||
| pub fn delegate_call_short_return_type( | ||
| &mut self, | ||
| code_hash: Hash, | ||
| selector: [u8; 4], | ||
| ) -> Result<i8, String> { | ||
| use ink::env::call::build_call; | ||
|
|
||
| let result = build_call::<DefaultEnvironment>() | ||
| .delegate(code_hash) | ||
| .exec_input(ExecutionInput::new(Selector::new(selector))) | ||
| .returns::<i8>() | ||
| .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)), | ||
| } | ||
| } | ||
|
|
||
| /// 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::<DefaultEnvironment>() | ||
| .call(address) | ||
| .exec_input(ExecutionInput::new(Selector::new(selector))) | ||
| .returns::<i32>() | ||
| .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<i8, String> { | ||
| use ink::env::call::build_call; | ||
|
|
||
| let result = build_call::<DefaultEnvironment>() | ||
| .call(address) | ||
| .exec_input(ExecutionInput::new(Selector::new(selector))) | ||
| .returns::<i8>() | ||
| .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<T> = std::result::Result<T, Box<dyn std::error::Error>>; | ||
|
|
||
| #[ink_e2e::test] | ||
| async fn e2e_delegate_call_return_value_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 = 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::<CallBuilderReturnValue>(); | ||
|
|
||
| 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.delegate_call(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_delegate_call_return_value_errors_if_return_data_too_long( | ||
| 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 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::<CallBuilderReturnValue>(); | ||
|
|
||
| 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.delegate_call_short_return_type(code_hash, selector); | ||
| let call_result: Result<i8, String> = 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<C, E>, | ||
| ) -> 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::<CallBuilderReturnValue>(); | ||
|
|
||
| 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<C, E>, | ||
| ) -> 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::<CallBuilderReturnValue>(); | ||
|
|
||
| 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<i8, String> = 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(()) | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.