From 8321e6fe43f9e895971271d5ac07e13b05a8d65c Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 24 Jan 2023 19:24:40 +0000 Subject: [PATCH] `[ink_e2e]` method to generate and fund unique accounts (#1615) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Handle `LangError` from instantiate (fails for success case) This commit lets us grab what's in the output buffer after our call to `instantiate`, however we are unable to succesfully decode the `AccountId` from the success case. * Change generic in `CreateBuilder` to be more consistent * Remove extra generic parameter I accidently introduced this not knowing that the generic `C` was for the return type * Remove generic return type parameter from `CreateBuidler` codegen * Hardcode assumption that `instantiate` returns a `ConstructorResult` * Update `CreateBuilder` codegen to just return `Self` * Remove generic usage to fix formatting * Unwrap `ConstructorResult` in `contract-ref` E2E test * Clean up some comments * Bring back the assumption that we expect an `AccountId` Is supposed to help give better error messages if somebody uses a wrong type with the builder * Remove unused method * Update doc tests for new builder pattern * Clean up some comments * Fix Clippy warning * Fix typo * Add `try_instantiate` method to `CreateBuilder` * Remove unneeded `unwrap` * Remove debug logging * Update doc test * Fix some typos Co-authored-by: Andrew Jones * Mention panicking behaviour of `instantiate` methods * Improve error messages from wrong `returns()` type * Actually check return values from `call_instantiate` * Add test showing a reverting constructor with `Ok` in error buffer * Check that we're only returning `LangError`s if the contract reverted * Clean up the manual encoding test a bit * Add test for constructors which return a contract level error * Add `CreateBuilder` message which calls a fallible constructor * Add test which calls falliable constructor for success case * Get verbose `instantiate_contract_with_result` decoding past typechecker * Add `try_instantiate_with_result` to `CreateBuilder` * Clean up decoding logic for output from `seal_instatiate` * Small cleanups in `call-builder` E2E tests * RustFmt `env_access` * Remove unused import * Flip decoding logic so that it's more strict initially Otherwise we may end up decoding a `Result` too eagerly and end up in a wrong branch. * Add test which revert a fallible constructor * Remove note about removing `assert` statement We still need this to prevent someone from manually encoding an `Ok` value into the return buffer. * Check return value from fallible constructor tests * Update E2E Builder typedef to match changes * Update E2E test for new call syntax * Use `selector_bytes!` macro in more places * Change order to accounts used in tests The tests started failing due to nonce issues, re-ordering the accounts seems to help with that. * Update function names to use `fallible` * Add note about docs * Update `ContractRef` codegen to use fallible constructor return types * Stop returning an `AccountId` directly from `CreateBuilder::try_instantiate_fallible` This matches the behaviour of the other intantiate methods * Add panicking version of `try_instantiate_fallible` * Add test for using fallible constructors through ContractRefs * Add test for instantiation failure too * Add `instantiate_fallible` to `CreateParams` * Add a couple of missing docs * Convert `call-builder` test return type to `AccountId` * Extract reverted fallible constructor fn for testing * Fmt * Add fallible_constructor_reverted_lang_error FAILs * Rename tests * Add test for a decode error * Rename some tests * Make `Result` types more explicit * Add another test * Clean up decoding match statement * Andrew was right * Small cleanups to naming and imports * Couple more import and comment fixes * Use decode trait method directly * Fix `call-builder` E2E tests This now accounts for the better error handling in the `CalleeReverted` case * Remove leading colons from non-codegen contexts * Add doc test for `instantiate_fallible_contract` * Add doc test to `build_create` function * Remove leftover trait bound We can't use this with fallible constructors * Remove a few more leading colons * Panic in case where we get `Ok` encoded into error buffer * Add some links to env docs * Add more docs to `call-builder` E2E tests * Use correct path in `call-builder` docs * Remove fallible create_builder.rs methods * WIP experiment * InstantiateResult blanket impl for T and Result * Introduce ContractRef type parameter * Fix up env access * WIP... * Make it compile * Add ContractStorage parameter * Remove commented out instantiate_fallible_contract * Convert to env Error in helper * Return Decode errors in case of invalid Result first byte * Fix impls::instantiate_contract * Remove ContractStorage generic parameter * Fix env access * Use generated constructor ref, introduces update_selector * Fix e2e * Use return_value() method in e2e test * Remove commented out code * Typos * Clippy * Rename some instantiate_fallible * Restore `returns` method * Remove ContractReference Result impl * WIP implementing ConstructorReturnType * Reorder ContractRef parameter, move ContractRef and ContractEnv trait to env crate * Fmt and fix * Remove E param from build_create * Fix up build_create * Fix up e2e creat builder * Implement ContstructorReturnType for the storage_ident * Fmt * Fix envaccess test * Fully qualify Result in macro * More fully qualify Result in macro * Fix up build_create examples * Add test for different combos of Self and struct name * Fix ui test * Fmt * Remove unused assoc type * Change error fn to return Option * Remove commented out code * Fmt * Fix `call-builder` E2E test compilation * Fix `contract-ref` E2E test compilation * ConstructorReturnType comments * Fix up return types after merge * Fmt * Clippy * Fix create_builder tests * Fix some of the comment links * Unwrap errors from default `instantiate_fallible` codepath * Fix `contract-ref` E2E test * Wrap long line * Remove TODO * Fix instatiation doc test * Fix cross-contract compile test * Clean up some comments * Fix `contract-ref` compilation * Remove outdated doc * Update comment * Another comment fix * Bump `contract-metadata` Fixes some inconsistent errors between Clippy and `rustc` * Remove fallible create_builder.rs methods * WIP experiment * InstantiateResult blanket impl for T and Result * Introduce ContractRef type parameter * Fix up env access * WIP... * Make it compile * Add ContractStorage parameter * Remove commented out instantiate_fallible_contract * Convert to env Error in helper * Return Decode errors in case of invalid Result first byte * Fix impls::instantiate_contract * Remove ContractStorage generic parameter * Fix env access * Use generated constructor ref, introduces update_selector * Fix e2e * Remove commented out code * Typos * Clippy * Rename some instantiate_fallible * Restore `returns` method * Remove ContractReference Result impl * WIP implementing ConstructorReturnType * Reorder ContractRef parameter, move ContractRef and ContractEnv trait to env crate * Fmt and fix * Remove E param from build_create * Fix up build_create * Fix up e2e creat builder * Implement ContstructorReturnType for the storage_ident * Fmt * Fix envaccess test * Fully qualify Result in macro * More fully qualify Result in macro * Fix up build_create examples * Add test for different combos of Self and struct name * Fix ui test * Fmt * Remove unused assoc type * Change error fn to return Option * Remove commented out code * Fmt * ConstructorReturnType comments * Fix `contract-ref` E2E test compilation * Fix up return types after merge * Fmt * Clippy * Fix create_builder tests * Fix cross-contract compile test * Clean up some comments * Remove outdated doc * Update comment * Another comment fix * Wrap long line * Remove TODO * Bump `contract-metadata` Fixes some inconsistent errors between Clippy and `rustc` * Fix `CreateBuilder` compilation * Fix one of the doc tests * Clean up doc tests a bit * WIP create accounts * Try transfer balance * WIP try creating and funding account for single test. * Update all tests to use create_and_fund_account * Fix error * Fmt * SP * Clippy * Remove commented out code * Update crates/e2e/src/xts.rs Co-authored-by: Michael Müller Co-authored-by: Hernando Castano Co-authored-by: Hernando Castano Co-authored-by: Michael Müller --- crates/e2e/src/client.rs | 61 ++++++++-- crates/e2e/src/xts.rs | 58 +++++++++- .../call-builder/lib.rs | 106 +++++++++++------- 3 files changed, 171 insertions(+), 54 deletions(-) diff --git a/crates/e2e/src/client.rs b/crates/e2e/src/client.rs index d38433e6e4c..5dd976b1917 100644 --- a/crates/e2e/src/client.rs +++ b/crates/e2e/src/client.rs @@ -21,10 +21,6 @@ use super::{ log_error, log_info, sr25519, - xts::{ - Call, - InstantiateWithCode, - }, CodeUploadResult, ContractExecResult, ContractInstantiateResult, @@ -35,6 +31,7 @@ use contract_metadata::ContractMetadata; use ink_env::Environment; use ink_primitives::MessageResult; +use sp_core::Pair; use sp_runtime::traits::{ IdentifyAccount, Verify, @@ -55,7 +52,10 @@ use subxt::{ ValueDef, }, }, - tx::ExtrinsicParams, + tx::{ + ExtrinsicParams, + PairSigner, + }, }; /// Result of a contract instantiation. @@ -300,11 +300,8 @@ where E: Environment, E::AccountId: Debug, - E::Balance: Debug + scale::Encode + serde::Serialize, + E::Balance: Debug + scale::HasCompact + serde::Serialize, E::Hash: Debug + scale::Encode, - - Call: scale::Encode, - InstantiateWithCode: scale::Encode, { /// Creates a new [`Client`] instance. pub async fn new(url: &str, contracts: impl IntoIterator) -> Self { @@ -342,6 +339,52 @@ where } } + /// Generate a new keypair and fund with the given amount from the origin account. + /// + /// Because many tests may execute this in parallel, transfers may fail due to a race condition + /// with account indices. Therefore this will reattempt transfers a number of times. + pub async fn create_and_fund_account( + &self, + origin: &Signer, + amount: E::Balance, + ) -> Signer + where + E::Balance: Clone, + C::AccountId: Clone + core::fmt::Display, + { + let (pair, _, _) = ::generate_with_phrase(None); + let account_id = + ::Signer::from(pair.public()).into_account(); + + for _ in 0..6 { + let transfer_result = self + .api + .try_transfer_balance(origin, account_id.clone(), amount) + .await; + match transfer_result { + Ok(_) => { + log_info(&format!( + "transfer from {} to {} succeeded", + origin.account_id(), + account_id, + )); + break + } + Err(err) => { + log_info(&format!( + "transfer from {} to {} failed with {:?}", + origin.account_id(), + account_id, + err + )); + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + } + } + + PairSigner::new(pair) + } + /// This function extracts the metadata of the contract at the file path /// `target/ink/$contract_name.contract`. /// diff --git a/crates/e2e/src/xts.rs b/crates/e2e/src/xts.rs index 9fea3e270f2..eab05f73ae0 100644 --- a/crates/e2e/src/xts.rs +++ b/crates/e2e/src/xts.rs @@ -68,6 +68,25 @@ pub struct Call { data: Vec, } +/// A raw call to `pallet-contracts`'s `call`. +#[derive(Debug, scale::Encode, scale::Decode)] +pub struct Call2 { + dest: sp_runtime::MultiAddress, + #[codec(compact)] + value: B, + gas_limit: Weight, + storage_deposit_limit: Option, + data: Vec, +} + +/// A raw call to `pallet-contracts`'s `call`. +#[derive(Debug, scale::Encode, scale::Decode)] +pub struct Transfer { + dest: C::Address, + #[codec(compact)] + value: E::Balance, +} + #[derive( Debug, Clone, Copy, scale::Encode, scale::Decode, PartialEq, Eq, serde::Serialize, )] @@ -167,10 +186,7 @@ where sr25519::Signature: Into, E: Environment, - E::Balance: scale::Encode + serde::Serialize, - - Call: scale::Encode, - InstantiateWithCode: scale::Encode, + E::Balance: scale::HasCompact + serde::Serialize, { /// Creates a new [`ContractsApi`] instance. pub async fn new(client: OnlineClient, url: &str) -> Self { @@ -189,6 +205,40 @@ where } } + /// Attempt to transfer the `value` from `origin` to `dest`. + /// + /// Returns `Ok` on success, and a [`subxt::Error`] if the extrinsic is + /// invalid (e.g. out of date nonce) + pub async fn try_transfer_balance( + &self, + origin: &Signer, + dest: C::AccountId, + value: E::Balance, + ) -> Result<(), subxt::Error> { + let call = subxt::tx::StaticTxPayload::new( + "Balances", + "transfer", + Transfer:: { + dest: dest.into(), + value, + }, + Default::default(), + ) + .unvalidated(); + + let tx_progress = self + .client + .tx() + .sign_and_submit_then_watch_default(&call, origin) + .await?; + + tx_progress.wait_for_in_block().await.unwrap_or_else(|err| { + panic!("error on call `wait_for_in_block`: {:?}", err); + }); + + Ok(()) + } + /// Dry runs the instantiation of the given `code`. pub async fn instantiate_with_code_dry_run( &self, diff --git a/examples/lang-err-integration-tests/call-builder/lib.rs b/examples/lang-err-integration-tests/call-builder/lib.rs index fd2b80d35e6..874138c29ed 100755 --- a/examples/lang-err-integration-tests/call-builder/lib.rs +++ b/examples/lang-err-integration-tests/call-builder/lib.rs @@ -173,22 +173,20 @@ mod call_builder { async fn e2e_invalid_message_selector_can_be_handled( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::alice(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let flipper_constructor = FlipperRef::new_default(); let flipper_acc_id = client - .instantiate( - "integration_flipper", - &ink_e2e::alice(), - flipper_constructor, - 0, - None, - ) + .instantiate("integration_flipper", &origin, flipper_constructor, 0, None) .await .expect("instantiate `flipper` failed") .account_id; @@ -196,7 +194,7 @@ mod call_builder { let flipper_get = build_message::(flipper_acc_id) .call(|contract| contract.get()); let get_call_result = client - .call(&ink_e2e::alice(), flipper_get, 0, None) + .call(&origin, flipper_get, 0, None) .await .expect("Calling `flipper::get` failed"); let initial_value = get_call_result.return_value(); @@ -205,7 +203,7 @@ mod call_builder { let call = build_message::(contract_acc_id) .call(|contract| contract.call(flipper_acc_id, selector)); let call_result = client - .call(&ink_e2e::alice(), call, 0, None) + .call(&origin, call, 0, None) .await .expect("Calling `call_builder::call` failed"); @@ -219,7 +217,7 @@ mod call_builder { let flipper_get = build_message::(flipper_acc_id) .call(|contract| contract.get()); let get_call_result = client - .call(&ink_e2e::alice(), flipper_get, 0, None) + .call(&origin, flipper_get, 0, None) .await .expect("Calling `flipper::get` failed"); let flipped_value = get_call_result.return_value(); @@ -232,22 +230,20 @@ mod call_builder { async fn e2e_invalid_message_selector_panics_on_invoke( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::ferdie(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let flipper_constructor = FlipperRef::new_default(); let flipper_acc_id = client - .instantiate( - "integration_flipper", - &ink_e2e::ferdie(), - flipper_constructor, - 0, - None, - ) + .instantiate("integration_flipper", &origin, flipper_constructor, 0, None) .await .expect("instantiate `flipper` failed") .account_id; @@ -257,7 +253,7 @@ mod call_builder { let invalid_selector = [0x00, 0x00, 0x00, 0x00]; let call = build_message::(contract_acc_id) .call(|contract| contract.invoke(flipper_acc_id, invalid_selector)); - let call_result = client.call(&ink_e2e::ferdie(), call, 0, None).await; + let call_result = client.call(&origin, call, 0, None).await; assert!(call_result.is_err()); let contains_err_msg = match call_result.unwrap_err() { @@ -276,15 +272,19 @@ mod call_builder { async fn e2e_create_builder_works_with_valid_selector( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::bob(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let code_hash = client - .upload("constructors_return_value", &ink_e2e::bob(), None) + .upload("constructors_return_value", &origin, None) .await .expect("upload `constructors_return_value` failed") .code_hash; @@ -296,7 +296,7 @@ mod call_builder { contract.call_instantiate(code_hash, selector, init_value) }); let call_result = client - .call(&ink_e2e::bob(), call, 0, None) + .call(&origin, call, 0, None) .await .expect("Client failed to call `call_builder::call_instantiate`.") .return_value(); @@ -313,15 +313,19 @@ mod call_builder { async fn e2e_create_builder_fails_with_invalid_selector( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::charlie(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let code_hash = client - .upload("constructors_return_value", &ink_e2e::charlie(), None) + .upload("constructors_return_value", &origin, None) .await .expect("upload `constructors_return_value` failed") .code_hash; @@ -333,7 +337,7 @@ mod call_builder { contract.call_instantiate(code_hash, selector, init_value) }); let call_result = client - .call(&ink_e2e::charlie(), call, 0, None) + .call(&origin, call, 0, None) .await .expect("Client failed to call `call_builder::call_instantiate`.") .return_value(); @@ -350,15 +354,19 @@ mod call_builder { async fn e2e_create_builder_with_infallible_revert_constructor_encodes_ok( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::dave(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let code_hash = client - .upload("constructors_return_value", &ink_e2e::dave(), None) + .upload("constructors_return_value", &origin, None) .await .expect("upload `constructors_return_value` failed") .code_hash; @@ -370,7 +378,7 @@ mod call_builder { contract.call_instantiate(code_hash, selector, init_value) }); - let call_result = client.call(&mut ink_e2e::dave(), call, 0, None).await; + let call_result = client.call(&origin, call, 0, None).await; assert!( call_result.is_err(), "Call execution should've failed, but didn't." @@ -394,15 +402,19 @@ mod call_builder { async fn e2e_create_builder_can_handle_fallible_constructor_success( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::eve(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let code_hash = client - .upload("constructors_return_value", &ink_e2e::eve(), None) + .upload("constructors_return_value", &origin, None) .await .expect("upload `constructors_return_value` failed") .code_hash; @@ -414,7 +426,7 @@ mod call_builder { contract.call_instantiate_fallible(code_hash, selector, init_value) }); let call_result = client - .call(&mut ink_e2e::eve(), call, 0, None) + .call(&origin, call, 0, None) .await .expect("Calling `call_builder::call_instantiate_fallible` failed") .return_value(); @@ -431,15 +443,19 @@ mod call_builder { async fn e2e_create_builder_can_handle_fallible_constructor_error( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::ferdie(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let code_hash = client - .upload("constructors_return_value", &ink_e2e::ferdie(), None) + .upload("constructors_return_value", &origin, None) .await .expect("upload `constructors_return_value` failed") .code_hash; @@ -451,7 +467,7 @@ mod call_builder { contract.call_instantiate_fallible(code_hash, selector, init_value) }); let call_result = client - .call(&mut ink_e2e::ferdie(), call, 0, None) + .call(&origin, call, 0, None) .await .expect("Calling `call_builder::call_instantiate_fallible` failed") .return_value(); @@ -475,15 +491,19 @@ mod call_builder { async fn e2e_create_builder_with_fallible_revert_constructor_encodes_ok( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::alice(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let code_hash = client - .upload("constructors_return_value", &ink_e2e::alice(), None) + .upload("constructors_return_value", &origin, None) .await .expect("upload `constructors_return_value` failed") .code_hash; @@ -494,7 +514,7 @@ mod call_builder { build_message::(contract_acc_id).call(|contract| { contract.call_instantiate_fallible(code_hash, selector, init_value) }); - let call_result = client.call(&mut ink_e2e::alice(), call, 0, None).await; + let call_result = client.call(&origin, call, 0, None).await; assert!( call_result.is_err(), @@ -520,15 +540,19 @@ mod call_builder { async fn e2e_create_builder_with_fallible_revert_constructor_encodes_err( mut client: ink_e2e::Client, ) -> E2EResult<()> { + let origin = client + .create_and_fund_account(&ink_e2e::bob(), 10_000_000_000_000) + .await; + let constructor = CallBuilderTestRef::new(); let contract_acc_id = client - .instantiate("call_builder", &ink_e2e::bob(), constructor, 0, None) + .instantiate("call_builder", &origin, constructor, 0, None) .await .expect("instantiate failed") .account_id; let code_hash = client - .upload("constructors_return_value", &ink_e2e::bob(), None) + .upload("constructors_return_value", &origin, None) .await .expect("upload `constructors_return_value` failed") .code_hash; @@ -540,7 +564,7 @@ mod call_builder { contract.call_instantiate_fallible(code_hash, selector, init_value) }); let call_result = client - .call(&mut ink_e2e::bob(), call, 0, None) + .call(&origin, call, 0, None) .await .expect( "Client failed to call `call_builder::call_instantiate_fallible`.",