From 3d9084f599c24b5b7203eaab10ff250a42bc76cc Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Wed, 14 Sep 2022 12:58:15 +0200 Subject: [PATCH 01/16] Add missing words to spellcheck dictionary --- .config/cargo_spellcheck.dic | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.config/cargo_spellcheck.dic b/.config/cargo_spellcheck.dic index 777960d2632..aaf325fb8a3 100644 --- a/.config/cargo_spellcheck.dic +++ b/.config/cargo_spellcheck.dic @@ -40,6 +40,7 @@ dereferencing deserialize/S deserialization dispatchable/S +E2E encodable evaluable fuzzer @@ -97,6 +98,7 @@ layout/JG namespace/S parameterize/SD runtime/S +storable struct/S vec/S vector/S @@ -107,4 +109,4 @@ natively payability unpayable initializer -storable +WebSocket/S From ee872be522bb61ba7c637c39c5d10e45658d83ad Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Wed, 14 Sep 2022 12:58:51 +0200 Subject: [PATCH 02/16] Add `contracts-node.scale` metadata Has been exported via cargo install subxt-cli subxt metadata > contracts-node.scale For `substrate-contracts-node` v0.20.0. --- crates/env/metadata/contracts-node.scale | Bin 0 -> 48563 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 crates/env/metadata/contracts-node.scale diff --git a/crates/env/metadata/contracts-node.scale b/crates/env/metadata/contracts-node.scale new file mode 100644 index 0000000000000000000000000000000000000000..45ea661dfdba1784b7fd4fad91381c0237cdfb18 GIT binary patch literal 48563 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 literal 0 HcmV?d00001 From 4ba3a3f0b9fb069f3f0f52f19dc2769d8ab8d853 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Wed, 14 Sep 2022 13:00:59 +0200 Subject: [PATCH 03/16] Run `substrate-contracts-node` in CI --- .gitlab-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b82df5b7b98..8779321560b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -91,6 +91,9 @@ workflow: tags: - kubernetes-parity-build +.start-substrate-contracts-node: &start-substrate-contracts-node + - substrate-contracts-node -linfo,runtime::contracts=debug 2>&1 | tee /tmp/contracts-node.log & + #### stage: lint # # Note: For all of these lints we `allow_failure` so that the rest of the build can @@ -349,6 +352,7 @@ examples-test: - job: clippy-std artifacts: false script: + - *start-substrate-contracts-node - for example in examples/*/; do if [ "$example" = "examples/upgradeable-contracts/" ]; then continue; fi; cargo test --verbose --manifest-path ${example}/Cargo.toml; From 6b7698f857384ee271445776d1e7bc943e2be4c1 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Wed, 14 Sep 2022 13:01:30 +0200 Subject: [PATCH 04/16] Invoke `cargo doc` separately for each crate --- .gitlab-ci.yml | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8779321560b..ce19c76ead9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -288,11 +288,25 @@ docs: paths: - ./crate-docs/ script: - - cargo doc --no-deps --all-features - -p scale-info -p ink_metadata -p ink_env - -p ink_storage -p ink_storage_traits -p ink_storage_codegen -p ink_storage_derive - -p ink_primitives -p ink_prelude - -p ink_lang -p ink_lang_macro -p ink_lang_ir -p ink_lang_codegen + # All crate docs currently need to be built separately. The reason + # is that `smart-bench-macro` is a dependency now in a number of places. + # This crate uses e.g. `ink_metadata`, but in its published form. So if + # e.g. the `-p ink_metadata` is added to the `ink_lang` command this + # results in the cargo failure "multiple packages with same spec, ambiguous". + # TODO(#XXX) We might be able to remove this again once we've forked + # `smart-bench-macro` into the ink! repository. + - cargo doc --no-deps --all-features -p ink_env + - cargo doc --no-deps --all-features -p ink_storage + - cargo doc --no-deps --all-features -p ink_storage_codegen + - cargo doc --no-deps --all-features -p ink_storage_traits + - cargo doc --no-deps --all-features -p ink_storage_derive + - cargo doc --no-deps --all-features -p ink_primitives + - cargo doc --no-deps --all-features -p ink_prelude + - cargo doc --no-deps --all-features -p ink_lang + - cargo doc --no-deps --all-features -p ink_lang_macro + - cargo doc --no-deps --all-features -p ink_lang_ir + - cargo doc --no-deps --all-features -p ink_lang_codegen + - cargo doc --no-deps --all-features -p ink_metadata - mv ${CARGO_TARGET_DIR}/doc ./crate-docs # FIXME: remove me after CI image gets nonroot - chown -R nonroot:nonroot ./crate-docs From 0d45a1fac168668b796f7186fc3c793cea7cf318 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Wed, 14 Sep 2022 13:00:31 +0200 Subject: [PATCH 05/16] Add MVP for E2E testing framework --- crates/env/Cargo.toml | 21 + crates/env/src/engine/e2e/client.rs | 532 ++++++++++++++++++ crates/env/src/engine/e2e/default_accounts.rs | 53 ++ crates/env/src/engine/e2e/mod.rs | 117 ++++ crates/env/src/engine/e2e/xts.rs | 360 ++++++++++++ crates/env/src/engine/mod.rs | 1 + crates/env/src/lib.rs | 4 + crates/lang/codegen/Cargo.toml | 6 + .../codegen/src/generator/ink_e2e_test.rs | 184 ++++++ crates/lang/codegen/src/generator/mod.rs | 2 + crates/lang/codegen/src/lib.rs | 4 + crates/lang/ir/src/ir/e2e_config.rs | 214 +++++++ crates/lang/ir/src/ir/ink_e2e_test.rs | 58 ++ crates/lang/ir/src/ir/mod.rs | 4 + crates/lang/ir/src/lib.rs | 1 + crates/lang/macro/src/ink_e2e_test.rs | 29 + crates/lang/macro/src/lib.rs | 136 +++++ crates/lang/src/lib.rs | 1 + .../fail/constructor-input-non-codec.stderr | 4 +- .../fail/message-input-non-codec.stderr | 4 +- .../fail/message-returns-non-codec.stderr | 4 +- ...packed_is_not_derived_automatically.stderr | 3 +- .../fail/message_input_non_codec.stderr | 3 +- .../fail/message_output_non_codec.stderr | 2 +- 24 files changed, 1740 insertions(+), 7 deletions(-) create mode 100644 crates/env/src/engine/e2e/client.rs create mode 100644 crates/env/src/engine/e2e/default_accounts.rs create mode 100644 crates/env/src/engine/e2e/mod.rs create mode 100644 crates/env/src/engine/e2e/xts.rs create mode 100644 crates/lang/codegen/src/generator/ink_e2e_test.rs create mode 100644 crates/lang/ir/src/ir/e2e_config.rs create mode 100644 crates/lang/ir/src/ir/ink_e2e_test.rs create mode 100644 crates/lang/macro/src/ink_e2e_test.rs diff --git a/crates/env/Cargo.toml b/crates/env/Cargo.toml index 6c49cff2a1a..eaefd979ed3 100644 --- a/crates/env/Cargo.toml +++ b/crates/env/Cargo.toml @@ -50,6 +50,27 @@ secp256k1 = { version = "0.24", features = ["recovery", "global-context"], optio rand = { version = "0.8", default-features = false, features = ["alloc"], optional = true } scale-info = { version = "2", default-features = false, features = ["derive"], optional = true } +contract-metadata = "2.0.0-alpha.1" +impl-serde = { version = "0.3.1", default-features = false } +jsonrpsee = { version = "0.14.0", features = ["ws-client"] } +pallet-contracts-primitives = "6.0.0" +serde = { version = "1.0.137", default-features = false, features = ["derive"] } +serde_json = "1.0.81" +tokio = { version = "1.18.2", features = ["rt-multi-thread"] } +log = "0.4" +env_logger = "0.8" +# TODO we need to use `subxt`'s `master` until the next release is published. +subxt = { git = "https://github.com/paritytech/subxt" } + +# Substrate +sp-rpc = "6.0.0" +sp-core = "6.0.0" +sp-keyring = "6.0.0" +sp-runtime = "6.0.0" + +# TODO(#xxx) `smart-bench_macro` needs to be forked. +smart-bench-macro = { git = "https://github.com/paritytech/smart-bench", branch = "cmichi-ink-e2e-test-mvp", package = "smart-bench-macro"} + [features] default = ["std"] std = [ diff --git a/crates/env/src/engine/e2e/client.rs b/crates/env/src/engine/e2e/client.rs new file mode 100644 index 00000000000..65960a99cb6 --- /dev/null +++ b/crates/env/src/engine/e2e/client.rs @@ -0,0 +1,532 @@ +// Copyright 2018-2022 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::{ + client::api::runtime_types::{ + frame_system::AccountInfo, + pallet_balances::AccountData, + }, + log_error, + log_info, + sr25519, + xts::{ + self, + api, + Call, + ContractDryCallResult, + ContractDryInstantiateResult, + InstantiateWithCode, + }, + ContractsApi, + InkConstructor, + InkMessage, + Signer, +}; +use crate::Environment; +use std::path::PathBuf; + +use sp_runtime::traits::{ + IdentifyAccount, + Verify, +}; +use subxt::{ + ext::bitvec::macros::internal::funty::Fundamental, + metadata::DecodeStaticType, + rpc::NumberOrHex, + storage::address::{ + StorageHasher, + StorageMapKey, + Yes, + }, + tx::{ + ExtrinsicParams, + TxEvents, + }, +}; + +/// An encoded `#[ink(message)]`. +#[derive(Clone)] +pub struct EncodedMessage(Vec); + +impl EncodedMessage { + fn new(call: &M) -> Self { + let mut call_data = M::SELECTOR.to_vec(); + ::encode_to(call, &mut call_data); + Self(call_data) + } +} + +impl From for EncodedMessage +where + M: InkMessage, +{ + fn from(msg: M) -> Self { + EncodedMessage::new(&msg) + } +} + +/// Result of a contract instantiation. +pub struct InstantiationResult { + /// The account id at which the contract was instantiated. + pub account_id: C::AccountId, + /// The result of the dry run, contains debug messages + /// if there were any. + pub dry_run: ContractDryInstantiateResult, + /// Events that happened with the contract instantiation. + pub events: TxEvents, +} + +/// We implement a custom `Debug` here, as to avoid requiring the trait +/// bound `Debug` for `E`. +// TODO(#xxx) Improve the `Debug` implementation. +impl core::fmt::Debug for InstantiationResult +where + C: subxt::Config, + E: Environment, + ::Balance: core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("CallResult") + .field("account_id", &self.account_id) + .field("dry_run", &self.dry_run) + .field("events", &self.events) + .finish() + } +} + +/// Result of a contract call. +pub struct CallResult { + /// The result of the dry run, contains debug messages + /// if there were any. + pub dry_run: ContractDryCallResult, + /// Events that happened with the contract instantiation. + pub events: TxEvents, +} + +/// We implement a custom `Debug` here, as to avoid requiring the trait +/// bound `Debug` for `E`. +// TODO(#xxx) Improve the `Debug` implementation. +impl core::fmt::Debug for CallResult +where + C: subxt::Config, + E: Environment, + ::Balance: core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("CallResult") + .field("dry_run", &self.dry_run) + .field("events", &self.events) + .finish() + } +} + +/// An error occurred while interacting with the Substrate node. +/// +/// We only convey errors here that are caused by the contract's +/// testing logic. For anything concerning the node (like inability +/// to communicate with it, fetch the nonce, account info, etc.) we +/// panic. +pub enum Error +where + C: subxt::Config, + E: Environment, + ::Balance: core::fmt::Debug, +{ + /// The `instantiate_with_code` dry run failed. + InstantiateDryRun(ContractDryInstantiateResult), + /// The `instantiate_with_code` extrinsic failed. + InstantiateExtrinsic(subxt::error::DispatchError), + /// The `call` dry run failed. + CallDryRun(ContractDryCallResult), + /// The `call` extrinsic failed. + CallExtrinsic(subxt::error::DispatchError), +} + +// We implement a custom `Debug` here, as to avoid requiring the trait +// bound `Debug` for `C`. +impl core::fmt::Debug for Error +where + C: subxt::Config, + E: Environment, + ::Balance: core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match &self { + Error::InstantiateDryRun(_) => f.write_str("InstantiateDryRun"), + Error::InstantiateExtrinsic(_) => f.write_str("InstantiateExtrinsic"), + Error::CallDryRun(_) => f.write_str("CallDryRun"), + Error::CallExtrinsic(_) => f.write_str("CallExtrinsic"), + } + } +} + +/// A contract was successfully instantiated. +#[derive(Debug, scale::Decode, scale::Encode)] +struct ContractInstantiatedEvent { + /// Account id of the deployer. + pub deployer: C::AccountId, + /// Account id where the contract was instantiated to. + pub contract: C::AccountId, +} + +impl subxt::events::StaticEvent for ContractInstantiatedEvent +where + C: subxt::Config, +{ + const PALLET: &'static str = "Contracts"; + const EVENT: &'static str = "Instantiated"; +} + +/// The `Client` takes care of communicating with the node. +/// +/// This node's RPC interface will be used for instantiating the contract +/// and interacting with it . +pub struct Client +where + C: subxt::Config, + E: Environment, +{ + api: ContractsApi, + node_log: String, + contract_path: PathBuf, +} + +impl Client +where + C: subxt::Config, + C::AccountId: Into + serde::de::DeserializeOwned, + C::Address: From, + C::Signature: From, + ::Signer: From, + >::OtherParams: Default, + ::Signer: + From + IdentifyAccount, + sr25519::Signature: Into, + + E: Environment, + E::Balance: core::fmt::Debug + + scale::Encode + + TryFrom + + TryFrom, + NumberOrHex: From<::Balance>, + + Call: scale::Encode, + InstantiateWithCode: scale::Encode, +{ + /// Creates a new [`Client`] instance. + pub async fn new(contract_path: &str, url: &str, node_log: &str) -> Self { + let client = subxt::OnlineClient::from_url(url) + .await + .unwrap_or_else(|err| { + log_error( + "Unable to create client! Please check that your node is running.", + ); + panic!("Unable to create client: {:?}", err); + }); + + Self { + api: ContractsApi::new(client, url).await, + contract_path: PathBuf::from(contract_path), + node_log: node_log.to_string(), + } + } + + /// This function extracts the metadata of the contract at the file path + /// `target/ink/$contract_name.contract`. + /// + /// The function subsequently uploads and instantiates an instance of the contract. + /// + /// Calling this function multiple times is idempotent, the contract is + /// newly instantiated each time using a unique salt. No existing contract + /// instance is reused! + pub async fn instantiate( + &mut self, + signer: &mut Signer, + // TODO(#xxx) It has to be possible to supply a contact bundle path directly here. + // Otherwise cross-contract testing is not possible. Currently we instantiate just + // by default the contract for which the test is executed. + // contract_path: Option, + constructor: CO, + value: E::Balance, + storage_deposit_limit: Option, + ) -> Result, Error> + where + CO: InkConstructor, + { + let reader = std::fs::File::open(&self.contract_path).unwrap_or_else(|err| { + panic!("contract path cannot be opened: {:?}", err); + }); + let contract: contract_metadata::ContractMetadata = + serde_json::from_reader(reader).map_err(|err| { + panic!("error reading metadata: {:?}", err); + })?; + let code = contract + .source + .wasm + .expect("contract bundle is missing `source.wasm`"); + + log_info(&format!( + "{:?} has {} KiB", + self.contract_path, + code.0.len() / 1024 + )); + + let nonce = self + .api + .client + .rpc() + .system_account_next_index(signer.account_id()) + .await + .unwrap_or_else(|err| { + panic!( + "error getting next index for {:?}: {:?}", + signer.account_id(), + err + ); + }); + log_info(&format!("nonce: {:?}", nonce)); + signer.set_nonce(nonce); + + let ret = self + .exec_instantiate(signer, value, storage_deposit_limit, code.0, &constructor) + .await?; + log_info(&format!("instantiated contract at {:?}", ret.account_id)); + + Ok(ret) + } + + /// Executes an `instantiate_with_code` call and captures the resulting events. + async fn exec_instantiate( + &mut self, + signer: &mut Signer, + value: E::Balance, + storage_deposit_limit: Option, + code: Vec, + constructor: &CO, + ) -> Result, Error> { + let mut data = CO::SELECTOR.to_vec(); + log_info(&format!("instantiating with selector: {:?}", CO::SELECTOR)); + ::encode_to(constructor, &mut data); + + let salt = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("unable to get unix time") + .as_millis() + .as_u128() + .to_le_bytes() + .to_vec(); + + // dry run the instantiate to calculate the gas limit + let dry_run = self + .api + .instantiate_with_code_dry_run( + value, + storage_deposit_limit, + code.clone(), + data.clone(), + salt.clone(), + signer, + ) + .await; + log_info(&format!( + "instantiate dry run debug message: {:?}", + String::from_utf8_lossy(&dry_run.debug_message) + )); + log_info(&format!("instantiate dry run result: {:?}", dry_run.result)); + if dry_run.result.is_err() { + return Err(Error::InstantiateDryRun(dry_run)) + } + + let tx_events = self + .api + .instantiate_with_code( + value, + dry_run.gas_required, + storage_deposit_limit, + code, + data.clone(), + salt, + signer, + ) + .await; + signer.increment_nonce(); + + let mut account_id = None; + for evt in tx_events.iter() { + let evt = evt.unwrap_or_else(|err| { + panic!("unable to unwrap event: {:?}", err); + }); + + if let Some(instantiated) = evt + .as_event::>() + .unwrap_or_else(|err| { + panic!("event conversion to `Instantiated` failed: {:?}", err); + }) + { + log_info(&format!( + "contract was instantiated at {:?}", + instantiated.contract + )); + account_id = Some(instantiated.contract); + break + } else if evt + .as_event::() + .unwrap_or_else(|err| { + panic!("event conversion to `ExtrinsicFailed` failed: {:?}", err) + }) + .is_some() + { + let metadata = self.api.client.metadata(); + let dispatch_error = subxt::error::DispatchError::decode_from( + evt.field_bytes(), + &metadata, + ); + log_error(&format!( + "extrinsic for instantiate failed: {:?}", + dispatch_error + )); + return Err(Error::InstantiateExtrinsic(dispatch_error)) + } + } + + Ok(InstantiationResult { + dry_run, + // The `account_id` must exist at this point. If the instantiation fails + // the dry-run must already return that. + account_id: account_id.expect("cannot extract account_id from events"), + events: tx_events, + }) + } + + /// Executes a `call` for the contract at `account_id`. + /// + /// Returns when the transaction is included in a block. The return value + /// contains all events that are associated with this transaction. + pub async fn call( + &self, + signer: &mut Signer, + account_id: C::AccountId, + contract_call: EncodedMessage, + value: E::Balance, + storage_deposit_limit: Option, + ) -> Result, Error> { + let dry_run = self + .api + .call_dry_run(account_id.clone(), value, None, contract_call.0.clone()) + .await; + log_info(&format!("call dry run: {:?}", &dry_run.result)); + log_info(&format!( + "call dry run debug message: {}", + String::from_utf8_lossy(&dry_run.debug_message) + )); + + if dry_run.result.is_err() { + return Err(Error::CallDryRun(dry_run)) + } + + let tx_events = self + .api + .call( + sp_runtime::MultiAddress::Id(account_id), + value, + dry_run.gas_required, + storage_deposit_limit, + contract_call.0.clone(), + signer, + ) + .await; + signer.increment_nonce(); + + for evt in tx_events.iter() { + let evt = evt.unwrap_or_else(|err| { + panic!("unable to unwrap event: {:?}", err); + }); + + if evt + .as_event::() + .unwrap_or_else(|err| { + panic!("event conversion to `ExtrinsicFailed` failed: {:?}", err) + }) + .is_some() + { + let metadata = self.api.client.metadata(); + let dispatch_error = subxt::error::DispatchError::decode_from( + evt.field_bytes(), + &metadata, + ); + log_error(&format!("extrinsic for call failed: {:?}", dispatch_error)); + return Err(Error::InstantiateExtrinsic(dispatch_error)) + } + } + + Ok(CallResult { + dry_run, + events: tx_events, + }) + } + + /// Returns the balance of `account_id`. + pub async fn balance( + &self, + account_id: C::AccountId, + ) -> Result> { + let account_addr = subxt::storage::StaticStorageAddress::< + DecodeStaticType>>, + Yes, + Yes, + (), + >::new( + "System", + "Account", + vec![StorageMapKey::new( + account_id.clone(), + StorageHasher::Blake2_128Concat, + )], + Default::default(), + ) + .unvalidated(); + + let alice_pre: AccountInfo> = self + .api + .client + .storage() + .fetch_or_default(&account_addr, None) + .await + .unwrap_or_else(|err| { + panic!("unable to fetch balance: {:?}", err); + }); + log_info(&format!( + "balance of contract {:?} is {:?}", + account_id, alice_pre + )); + Ok(alice_pre.data.free) + } + + /// Returns true if the `substrate-contracts-node` log under + /// `/tmp/contracts-node.log` contains `msg`. + pub fn node_log_contains(&self, msg: &str) -> bool { + let output = std::process::Command::new("grep") + .arg("-q") + .arg(msg) + .arg(&self.node_log) + .spawn() + .map_err(|err| { + format!("ERROR while executing `grep` with {:?}: {:?}", msg, err) + }) + .expect("failed to execute process") + .wait_with_output() + .expect("failed to receive output"); + output.status.success() + } +} diff --git a/crates/env/src/engine/e2e/default_accounts.rs b/crates/env/src/engine/e2e/default_accounts.rs new file mode 100644 index 00000000000..1e0b542411a --- /dev/null +++ b/crates/env/src/engine/e2e/default_accounts.rs @@ -0,0 +1,53 @@ +// Copyright 2018-2022 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Default accounts provided by [`sp_keyring::sr25519::Keyring`]. + +use super::{ + AccountKeyring, + IdentifyAccount, + PairSigner, + Verify, +}; +use sp_core::sr25519; + +#[rustfmt::skip] +macro_rules! default_account { + ($fn_name:ident $keyring_fn_name:ident) => { + #[doc = concat!( + "Returns the default [`sp_keyring::sr25519::Keyring`] for `//", + stringify!($keyring_fn_name), + "`." + )] + pub fn $fn_name() -> PairSigner + where + C: subxt::Config, + ::Signer: From, + C::Signature: From, + ::Signer: IdentifyAccount, + { + PairSigner::new(AccountKeyring::$keyring_fn_name.pair()) + } + }; +} + +// The following accounts are pre-defined in [`sp-keyring::sr25519`]. +default_account!(alice Alice); +default_account!(bob Bob); +default_account!(charlie Charlie); +default_account!(dave Dave); +default_account!(eve Eve); +default_account!(ferdie Ferdie); +default_account!(one One); +default_account!(two Two); diff --git a/crates/env/src/engine/e2e/mod.rs b/crates/env/src/engine/e2e/mod.rs new file mode 100644 index 00000000000..e93144f46b1 --- /dev/null +++ b/crates/env/src/engine/e2e/mod.rs @@ -0,0 +1,117 @@ +// Copyright 2018-2022 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Module for the logic behind ink!'s End-to-End testing framework. + +mod client; +mod default_accounts; +mod xts; + +pub use client::{ + Client, + Error, +}; +pub use default_accounts::*; +// TODO(#xxx) `smart-bench_macro` needs to be forked. +pub use smart_bench_macro; +use xts::ContractsApi; + +pub use env_logger; +pub use sp_keyring::AccountKeyring; +pub use subxt::tx::PairSigner; +pub use tokio; + +use log; +use sp_core::sr25519; +use sp_runtime::traits::{ + IdentifyAccount, + Verify, +}; +use std::{ + cell::RefCell, + sync::Once, +}; + +/// Default set of commonly used types by Substrate runtimes. +#[cfg(feature = "std")] +pub enum SubstrateConfig {} + +#[cfg(feature = "std")] +impl subxt::Config for SubstrateConfig { + type Index = u32; + type BlockNumber = u32; + type Hash = sp_core::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = sp_runtime::AccountId32; + type Address = sp_runtime::MultiAddress; + type Header = + sp_runtime::generic::Header; + type Signature = sp_runtime::MultiSignature; + type Extrinsic = sp_runtime::OpaqueExtrinsic; + type ExtrinsicParams = subxt::tx::SubstrateExtrinsicParams; +} + +/// Default set of commonly used types by Polkadot nodes. +#[cfg(feature = "std")] +pub type PolkadotConfig = subxt::config::WithExtrinsicParams< + SubstrateConfig, + subxt::tx::PolkadotExtrinsicParams, +>; + +/// Signer that is used throughout the E2E testing. +/// +/// The E2E testing can only be used with nodes that support `sr25519` +/// cryptography. +pub type Signer = PairSigner; + +/// Trait for contract constructors. +// TODO(#xxx) Merge this with `InkMessage` to be just `InkSelector`. Requires forking `smart-bench-macro`. +pub trait InkConstructor: scale::Encode { + /// An ink! selector consists of four bytes. + const SELECTOR: [u8; 4]; +} + +/// Trait for contract messages. +pub trait InkMessage: scale::Encode { + /// An ink! selector consists of four bytes. + const SELECTOR: [u8; 4]; +} + +/// We use this to only initialize `env_logger` once. +pub static INIT: Once = Once::new(); + +// We save the name of the currently executing test here as a mean +// of prefixing log entries to make it easier pinning them to tests. +thread_local! { + /// This prefix will be used for log output. It is set by each + /// `#[ink::e2e_test]` with the function name as String. + /// This way it is possible to distinguish the lines in stdout + /// and stderr, to still know which line belongs to which test. + pub static LOG_PREFIX: RefCell = RefCell::new(String::from("no prefix set")); +} + +/// Returns the name of the test which is currently executed. +pub fn log_prefix() -> String { + LOG_PREFIX.with(|log_prefix| log_prefix.borrow().clone()) +} + +/// Writes `msg` to stdout. +pub fn log_info(msg: &str) { + log::info!("[{}] {}", log_prefix(), msg); +} + +/// Writes `msg` to stderr. +pub fn log_error(msg: &str) { + log::error!("[{}] {}", log_prefix(), msg); +} diff --git a/crates/env/src/engine/e2e/xts.rs b/crates/env/src/engine/e2e/xts.rs new file mode 100644 index 00000000000..c06aeeaa98a --- /dev/null +++ b/crates/env/src/engine/e2e/xts.rs @@ -0,0 +1,360 @@ +// Copyright 2018-2022 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use crate::{ + e2e::Signer, + Environment, +}; + +use core::marker::PhantomData; +use jsonrpsee::{ + core::client::ClientT, + rpc_params, + ws_client::{ + WsClient, + WsClientBuilder, + }, +}; +use pallet_contracts_primitives::{ + ContractResult, + ExecReturnValue, + InstantiateReturnValue, +}; +use sp_core::{ + Bytes, + H256, +}; +use subxt::{ + rpc::NumberOrHex, + tx::{ + ExtrinsicParams, + TxEvents, + }, + OnlineClient, +}; + +/// The gas limit for contract instantiate and call dry runs. +const DRY_RUN_GAS_LIMIT: u64 = 500_000_000_000; + +// TODO(#xxx) Should be fetched automatically. +#[subxt::subxt(runtime_metadata_path = "metadata/contracts-node.scale")] +pub(super) mod api {} + +/// A raw call to `pallet-contracts`'s `instantiate_with_code`. +#[derive(Debug, scale::Encode, scale::Decode)] +pub struct InstantiateWithCode { + #[codec(compact)] + value: B, + #[codec(compact)] + gas_limit: crate::types::Gas, + storage_deposit_limit: Option, + code: Vec, + data: Vec, + salt: Vec, +} + +/// A raw call to `pallet-contracts`'s `call`. +#[derive(Debug, scale::Encode, scale::Decode)] +pub struct Call { + dest: ::subxt::ext::sp_runtime::MultiAddress, + #[codec(compact)] + value: B, + #[codec(compact)] + gas_limit: crate::types::Gas, + storage_deposit_limit: Option, + data: Vec, +} + +/// Result of a contract call dry run. +pub(super) type ContractDryCallResult = ContractResult< + Result, + ::Balance, +>; + +/// Result of a contract instantiation dry run. +pub(super) type ContractDryInstantiateResult = ContractResult< + Result::AccountId>, serde_json::Value>, + ::Balance, +>; + +/// A struct that encodes RPC parameters required to instantiate a new smart contract. +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct InstantiateRequest { + origin: C::AccountId, + value: NumberOrHex, + gas_limit: NumberOrHex, + storage_deposit_limit: Option, + code: Code, + data: Bytes, + salt: Bytes, +} + +/// Reference to an existing code hash or a new Wasm module. +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +enum Code { + /// A Wasm module as raw bytes. + Upload(Bytes), + #[allow(unused)] + /// The code hash of an on-chain Wasm blob. + Existing(H256), +} + +/// A struct that encodes RPC parameters required for a call to a smart contract. +/// +/// Copied from [`pallet-contracts-rpc`]. +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct RpcCallRequest { + origin: C::AccountId, + dest: C::AccountId, + value: NumberOrHex, + gas_limit: NumberOrHex, + storage_deposit_limit: Option, + input_data: Bytes, +} + +/// Provides functions for interacting with the `pallet-contracts` API. +pub struct ContractsApi { + pub client: OnlineClient, + ws_client: WsClient, + _phantom: PhantomData (C, E)>, +} + +impl ContractsApi +where + C: subxt::Config, + C::AccountId: Into + serde::de::DeserializeOwned, + >::OtherParams: Default, + + C::Signature: From, + ::Signer: From, + ::Signer: + From + IdentifyAccount, + sr25519::Signature: Into, + + E: Environment, + E::Balance: + scale::Encode + TryFrom + TryFrom, + NumberOrHex: From<::Balance>, + + Call: scale::Encode, + InstantiateWithCode: scale::Encode, +{ + /// Creates a new [`ContractsApi`] instance. + pub async fn new(client: OnlineClient, url: &str) -> Self { + let ws_client = + WsClientBuilder::default() + .build(&url) + .await + .unwrap_or_else(|err| { + panic!("error on ws request: {:?}", err); + }); + + Self { + client, + ws_client, + _phantom: Default::default(), + } + } + + /// Dry runs the instantiation of the given `code`. + pub async fn instantiate_with_code_dry_run( + &self, + value: E::Balance, + storage_deposit_limit: Option, + code: Vec, + data: Vec, + salt: Vec, + signer: &Signer, + ) -> ContractDryInstantiateResult { + let code = Code::Upload(code.into()); + let value = value.try_into().ok().unwrap(); + let call_request = InstantiateRequest:: { + origin: signer.account_id().clone(), + value, + gas_limit: NumberOrHex::Number(DRY_RUN_GAS_LIMIT), + storage_deposit_limit: storage_deposit_limit.map(|l| { + l.try_into() + .expect("unable to convert `storage_deposit_limit`") + }), + code, + data: data.into(), + salt: salt.into(), + }; + + let params = rpc_params![call_request]; + self.ws_client + .request("contracts_instantiate", params) + .await + .unwrap_or_else(|err| { + panic!("error on ws request `contracts_instantiate`: {:?}", err); + }) + } + + /// Submits an extrinsic to instantiate a contract with the given code. + /// + /// Returns when the transaction is included in a block. The return value + /// contains all events that are associated with this transaction. + #[allow(clippy::too_many_arguments)] + pub async fn instantiate_with_code( + &self, + value: E::Balance, + gas_limit: crate::types::Gas, + storage_deposit_limit: Option, + code: Vec, + data: Vec, + salt: Vec, + signer: &Signer, + ) -> TxEvents { + let call = subxt::tx::StaticTxPayload::new( + "Contracts", + "instantiate_with_code", + InstantiateWithCode:: { + value, + gas_limit, + storage_deposit_limit, + code, + data, + salt, + }, + Default::default(), + ) + .unvalidated(); + + self + .client + .tx() + .sign_and_submit_then_watch_default(&call, signer) + .await + .map(|tx_progress| { + log_info(&format!("signed and submitted tx with hash {:?}", tx_progress.extrinsic_hash())); + tx_progress + }) + .unwrap_or_else(|err| { + panic!( + "error on call `sign_and_submit_then_watch_default`: {:?}", + err + ); + }) + // TODO(#xxx) It should be configurable to use `.wait_for_finalized_success` instead. + .wait_for_in_block() + .await + .unwrap_or_else(|err| { + panic!( + "error on call `wait_for_in_block`: {:?}", + err + ); + }) + .fetch_events() + .await + .unwrap_or_else(|err| { + panic!( + "error on call `fetch_events`: {:?}", + err + ); + }) + } + + /// Dry runs a call of the contract at `contract` with the given parameters. + pub async fn call_dry_run( + &self, + contract: C::AccountId, + value: E::Balance, + storage_deposit_limit: Option, + data: Vec, + ) -> ContractDryCallResult { + let call_request = RpcCallRequest:: { + origin: contract.clone(), + dest: contract, + value: value.try_into().expect("unable to convert `value`"), + gas_limit: DRY_RUN_GAS_LIMIT.into(), + storage_deposit_limit: storage_deposit_limit.map(|l| { + l.try_into() + .expect("unable to convert `storage_deposit_limit`") + }), + input_data: Bytes(data), + }; + let params = rpc_params![call_request]; + self.ws_client + .request("contracts_call", params) + .await + .unwrap_or_else(|err| { + panic!("error on ws request `contracts_call`: {:?}", err); + }) + } + + /// Submits an extrinsic to call a contract with the given parameters. + /// + /// Returns when the transaction is included in a block. The return value + /// contains all events that are associated with this transaction. + pub async fn call( + &self, + contract: sp_runtime::MultiAddress, + value: E::Balance, + gas_limit: crate::types::Gas, + storage_deposit_limit: Option, + data: Vec, + signer: &Signer, + ) -> TxEvents { + let call = subxt::tx::StaticTxPayload::new( + "Contracts", + "call", + Call:: { + dest: contract, + value, + gas_limit, + storage_deposit_limit, + data, + }, + Default::default(), + ) + .unvalidated(); + + self + .client + .tx() + .sign_and_submit_then_watch_default(&call, signer) + .await + .map(|tx_progress| { + log_info(&format!("signed and submitted call with extrinsic hash {:?}", tx_progress.extrinsic_hash())); + tx_progress + }) + .unwrap_or_else(|err| { + panic!( + "error on call `sign_and_submit_then_watch_default`: {:?}", + err + ); + }) + // TODO(#xxx) It should be configurable to use `.wait_for_finalized_success` instead. + .wait_for_in_block() + .await + .unwrap_or_else(|err| { + panic!( + "error on call `wait_for_in_block`: {:?}", + err + ); + }) + .fetch_events() + .await + .unwrap_or_else(|err| { + panic!( + "error on call `fetch_events`: {:?}", + err + ); + }) + } +} diff --git a/crates/env/src/engine/mod.rs b/crates/env/src/engine/mod.rs index 2e74d582e12..811776663c5 100644 --- a/crates/env/src/engine/mod.rs +++ b/crates/env/src/engine/mod.rs @@ -29,6 +29,7 @@ cfg_if! { mod on_chain; pub use self::on_chain::EnvInstance; } else if #[cfg(feature = "std")] { + pub mod e2e; pub mod off_chain; pub use self::off_chain::EnvInstance; } else { diff --git a/crates/env/src/lib.rs b/crates/env/src/lib.rs index f02ca57ca78..f53f6fd7137 100644 --- a/crates/env/src/lib.rs +++ b/crates/env/src/lib.rs @@ -84,6 +84,10 @@ mod tests; #[doc(inline)] pub use self::engine::off_chain::test_api as test; +#[cfg(any(feature = "std", test, doc))] +#[doc(inline)] +pub use self::engine::e2e; + use self::backend::{ EnvBackend, TypedEnvBackend, diff --git a/crates/lang/codegen/Cargo.toml b/crates/lang/codegen/Cargo.toml index f19e76575c6..da9a11412ae 100644 --- a/crates/lang/codegen/Cargo.toml +++ b/crates/lang/codegen/Cargo.toml @@ -31,6 +31,12 @@ heck = "0.4.0" scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive", "full"] } impl-serde = "0.4.0" +serde = { version = "1.0.137", default-features = false, features = ["derive"] } +serde_json = "1.0.81" + +log = "0.4" +env_logger = "0.8" + [features] default = ["std"] std = [ diff --git a/crates/lang/codegen/src/generator/ink_e2e_test.rs b/crates/lang/codegen/src/generator/ink_e2e_test.rs new file mode 100644 index 00000000000..5cfe84f5b57 --- /dev/null +++ b/crates/lang/codegen/src/generator/ink_e2e_test.rs @@ -0,0 +1,184 @@ +// Copyright 2018-2022 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::GenerateCode; +use core::cell::RefCell; +use derive_more::From; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use std::{ + path::PathBuf, + sync::Once, +}; + +/// We use this to only build the contract once for all tests. +static BUILD_ONCE: Once = Once::new(); + +// We save the name of the currently executing test here. +thread_local! { + pub static CONTRACT_PATH: RefCell> = RefCell::new(None); +} + +/// Returns the path to the contract bundle of the contract for which a test +/// is currently executed. +pub fn contract_path() -> Option { + CONTRACT_PATH.with(|metadata_path| metadata_path.borrow().clone()) +} + +/// Generates code for the `[ink::e2e_test]` macro. +#[derive(From)] +pub struct InkE2ETest<'a> { + /// The test function to generate code for. + test: &'a ir::InkE2ETest, +} + +impl GenerateCode for InkE2ETest<'_> { + /// Generates the code for `#[ink:e2e_test]`. + fn generate_code(&self) -> TokenStream2 { + #[cfg(clippy)] + if true { + return quote! {} + } + + let item_fn = &self.test.item_fn.item_fn; + let fn_name = &item_fn.sig.ident; + let block = &item_fn.block; + let fn_return_type = &item_fn.sig.output; + let vis = &item_fn.vis; + let attrs = &item_fn.attrs; + let ret = match fn_return_type { + syn::ReturnType::Default => quote! {}, + syn::ReturnType::Type(rarrow, ret_type) => quote! { #rarrow #ret_type }, + }; + + let ws_url = &self.test.config.ws_url(); + let node_log = &self.test.config.node_log(); + let skip_build = &self.test.config.skip_build(); + + // This path will only be used in case `skip_build` is activated + // and no path was specified for it. + // TODO(#xxx) we should require specifying a path for `skip_build`. + let mut path = PathBuf::from("./target/ink/metadata.json".to_string()); + + // If a prior test did already build the contract and set the path + // to the metadata file. + if let Some(metadata_path) = contract_path() { + path = metadata_path; + } + + if !skip_build.value && contract_path().is_none() { + BUILD_ONCE.call_once(|| { + env_logger::init(); + use std::process::{ + Command, + Stdio, + }; + let output = Command::new("cargo") + // TODO(#xxx) Add possibility of configuring `skip_linting` in attributes. + .args(["+stable", "contract", "build", "--skip-linting", "--output-json"]) + .env("RUST_LOG", "") + .stderr(Stdio::inherit()) + .output() + .expect("failed to execute `cargo-contract` build process"); + + log::info!("`cargo-contract` returned status: {}", output.status); + eprintln!("`cargo-contract` returned status: {}", output.status); + log::info!( + "`cargo-contract` stdout: {}", + String::from_utf8_lossy(&output.stdout) + ); + eprintln!( + "`cargo-contract` stdout: {}", + String::from_utf8_lossy(&output.stdout) + ); + if !output.status.success() { + log::info!( + "`cargo-contract` stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); + eprintln!( + "`cargo-contract` stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + + assert!(output.status.success()); + + let json = String::from_utf8_lossy(&output.stdout); + let metadata: serde_json::Value = + serde_json::from_str(&json).expect("cannot convert json to utf8"); + let mut dest_metadata = + metadata["metadata_result"]["dest_bundle"].to_string(); + dest_metadata = dest_metadata.trim_matches('"').to_string(); + path = PathBuf::from(dest_metadata); + log::info!("extracted metadata path: {}", path.display()); + + CONTRACT_PATH.with(|metadata_path| { + *metadata_path.borrow_mut() = Some(path.clone()); + }); + }); + } + + log::info!("using metadata path: {:?}", path); + + path.try_exists().unwrap_or_else(|err| { + panic!("path {:?} does not exist: {:?}", path, err); + }); + let os_path = path + .as_os_str() + .to_str() + .expect("converting path to str failed"); + let path = syn::LitStr::new(os_path, proc_macro2::Span::call_site()); + + quote! { + #( #attrs )* + #[ink_env::e2e::tokio::test] + async #vis fn #fn_name () #ret { + use ink_env::e2e::log_info; + ink_env::e2e::LOG_PREFIX.with(|log_prefix| { + let str = format!("test: {}", stringify!(#fn_name)); + *log_prefix.borrow_mut() = String::from(str); + }); + log_info("setting up e2e test"); + + ink_env::e2e::INIT.call_once(|| { + ink_env::e2e::env_logger::init(); + }); + + log_info("extracting metadata"); + // TODO(#xxx) `smart-bench_macro` needs to be forked. + ink_env::e2e::smart_bench_macro::contract!(#path); + + log_info("creating new client"); + + // TODO(#xxx) Make those two generic environments customizable. + let mut client = ink_env::e2e::Client::< + ink_env::e2e::PolkadotConfig, + ink_env::DefaultEnvironment + >::new(&#path, &#ws_url, &#node_log).await; + + let __ret = { + #block + }; + __ret + } + } + } +} + +impl GenerateCode for ir::InkE2ETest { + fn generate_code(&self) -> TokenStream2 { + InkE2ETest::from(self).generate_code() + } +} diff --git a/crates/lang/codegen/src/generator/mod.rs b/crates/lang/codegen/src/generator/mod.rs index 960a1b58dd7..eec4fab2044 100644 --- a/crates/lang/codegen/src/generator/mod.rs +++ b/crates/lang/codegen/src/generator/mod.rs @@ -34,6 +34,7 @@ mod contract; mod dispatch; mod env; mod events; +mod ink_e2e_test; mod ink_test; mod item_impls; mod metadata; @@ -59,6 +60,7 @@ pub use self::{ dispatch::Dispatch, env::Env, events::Events, + ink_e2e_test::InkE2ETest, ink_test::InkTest, item_impls::ItemImpls, metadata::Metadata, diff --git a/crates/lang/codegen/src/lib.rs b/crates/lang/codegen/src/lib.rs index df7d2148ff0..a61efef20ec 100644 --- a/crates/lang/codegen/src/lib.rs +++ b/crates/lang/codegen/src/lib.rs @@ -60,6 +60,10 @@ impl<'a> CodeGenerator for &'a ir::InkTraitDefinition { type Generator = generator::TraitDefinition<'a>; } +impl<'a> CodeGenerator for &'a ir::InkE2ETest { + type Generator = generator::InkE2ETest<'a>; +} + impl<'a> CodeGenerator for &'a ir::InkTest { type Generator = generator::InkTest<'a>; } diff --git a/crates/lang/ir/src/ir/e2e_config.rs b/crates/lang/ir/src/ir/e2e_config.rs new file mode 100644 index 00000000000..ff00a8bff0c --- /dev/null +++ b/crates/lang/ir/src/ir/e2e_config.rs @@ -0,0 +1,214 @@ +// Copyright 2018-2022 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + ast, + utils::{ + duplicate_config_err, + WhitelistedAttributes, + }, +}; + +/// The End-to-End test configuration. +#[derive(Debug, Default, PartialEq, Eq)] +pub struct E2EConfig { + /// The path where the node writes its log. + node_log: Option, + /// The WebSocket URL where to connect with the node. + ws_url: Option, + /// Denotes if the contract should be build before executing the test. + skip_build: Option, + /// The set of attributes that can be passed to call builder in the codegen. + whitelisted_attributes: WhitelistedAttributes, +} + +impl TryFrom for E2EConfig { + type Error = syn::Error; + + fn try_from(args: ast::AttributeArgs) -> Result { + let mut node_log: Option<(syn::LitStr, ast::MetaNameValue)> = None; + let mut ws_url: Option<(syn::LitStr, ast::MetaNameValue)> = None; + let mut skip_build: Option<(syn::LitBool, ast::MetaNameValue)> = None; + let mut whitelisted_attributes = WhitelistedAttributes::default(); + + for arg in args.into_iter() { + if arg.name.is_ident("node_log") { + if let Some((_, ast)) = node_log { + return Err(duplicate_config_err(ast, arg, "node_log", "e2e test")) + } + if let ast::PathOrLit::Lit(syn::Lit::Str(lit_str)) = &arg.value { + node_log = Some((lit_str.clone(), arg)) + } else { + return Err(format_err_spanned!( + arg, + "expected a path for `node_log` ink! e2e test configuration argument", + )) + } + } else if arg.name.is_ident("ws_url") { + if let Some((_, ast)) = ws_url { + return Err(duplicate_config_err(ast, arg, "ws_url", "e2e test")) + } + if let ast::PathOrLit::Lit(syn::Lit::Str(lit_str)) = &arg.value { + ws_url = Some((lit_str.clone(), arg)) + } else { + return Err(format_err_spanned!( + arg, + "expected a string literal for `ws_url` ink! e2e test configuration argument", + )) + } + } else if arg.name.is_ident("skip_build") { + if let Some((_, ast)) = skip_build { + return Err(duplicate_config_err(ast, arg, "skip_build", "e2e test")) + } + if let ast::PathOrLit::Lit(syn::Lit::Bool(lit_bool)) = &arg.value { + skip_build = Some((lit_bool.clone(), arg)) + } else { + return Err(format_err_spanned!( + arg, + "expected a bool literal for `skip_build` ink! e2e test configuration argument", + )) + } + } else if arg.name.is_ident("keep_attr") { + whitelisted_attributes.parse_arg_value(&arg)?; + } else { + return Err(format_err_spanned!( + arg, + "encountered unknown or unsupported ink! configuration argument", + )) + } + } + Ok(E2EConfig { + node_log: node_log.map(|(value, _)| value), + ws_url: ws_url.map(|(value, _)| value), + skip_build: skip_build.map(|(value, _)| value), + whitelisted_attributes, + }) + } +} + +impl E2EConfig { + /// Returns the path to the node log if specified. + /// Otherwise returns the default path `/tmp/contracts-node.log`. + pub fn node_log(&self) -> syn::LitStr { + let default_node_log = + syn::LitStr::new("/tmp/contracts-node.log", proc_macro2::Span::call_site()); + self.node_log.clone().unwrap_or(default_node_log) + } + + /// Returns the WebSocket URL where to connect to the RPC endpoint + /// of the node, if specified. Otherwise returns the default URL + /// `ws://localhost:9944`. + pub fn ws_url(&self) -> syn::LitStr { + let default_ws_url = + syn::LitStr::new("ws://0.0.0.0:9944", proc_macro2::Span::call_site()); + self.ws_url.clone().unwrap_or(default_ws_url) + } + + /// Returns `true` if `skip_build = true` was configured. + /// Otherwise returns `false`. + pub fn skip_build(&self) -> syn::LitBool { + let default_skip_build = syn::LitBool::new(false, proc_macro2::Span::call_site()); + self.skip_build.clone().unwrap_or(default_skip_build) + } + + /// Return set of attributes that can be passed to call builder in the codegen. + pub fn whitelisted_attributes(&self) -> &WhitelistedAttributes { + &self.whitelisted_attributes + } +} + +/// The environmental types definition. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Environment { + /// The underlying Rust type. + pub path: syn::Path, +} + +impl Default for Environment { + fn default() -> Self { + Self { + path: syn::parse_quote! { ::ink_env::DefaultEnvironment }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Asserts that the given input configuration attribute argument are converted + /// into the expected ink! configuration or yields the expected error message. + fn assert_try_from( + input: ast::AttributeArgs, + expected: Result, + ) { + assert_eq!( + >::try_from(input) + .map_err(|err| err.to_string()), + expected.map_err(ToString::to_string), + ); + } + + #[test] + fn empty_config_works() { + assert_try_from(syn::parse_quote! {}, Ok(E2EConfig::default())) + } + + #[test] + fn unknown_arg_fails() { + assert_try_from( + syn::parse_quote! { unknown = argument }, + Err("encountered unknown or unsupported ink! configuration argument"), + ); + } + + #[test] + fn duplicate_args_fails() { + assert_try_from( + syn::parse_quote! { + skip_build = true, + skip_build = true, + }, + Err( + "encountered duplicate ink! e2e test `skip_build` configuration argument", + ), + ); + } + + #[test] + fn keep_attr_works() { + let mut attrs = WhitelistedAttributes::default(); + attrs.0.insert("foo".to_string(), ()); + attrs.0.insert("bar".to_string(), ()); + assert_try_from( + syn::parse_quote! { + keep_attr = "foo, bar" + }, + Ok(E2EConfig { + node_log: None, + ws_url: None, + whitelisted_attributes: attrs, + skip_build: None, + }), + ) + } + + #[test] + fn keep_attr_invalid_value_fails() { + assert_try_from( + syn::parse_quote! { keep_attr = 1u16 }, + Err("expected a string with attributes separated by `,`"), + ); + } +} diff --git a/crates/lang/ir/src/ir/ink_e2e_test.rs b/crates/lang/ir/src/ir/ink_e2e_test.rs new file mode 100644 index 00000000000..ab1b512c5de --- /dev/null +++ b/crates/lang/ir/src/ir/ink_e2e_test.rs @@ -0,0 +1,58 @@ +// Copyright 2018-2022 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + ast, + ir, + ir::idents_lint, +}; +use proc_macro2::TokenStream as TokenStream2; + +/// The End-to-End test with all required information. +pub struct InkE2ETest { + /// The function which was annotated. + pub item_fn: E2EFn, + /// The specified configuration. + pub config: ir::E2EConfig, +} + +/// The End-to-End test with all required information. +pub struct E2EFn { + /// The function which was annotated. + pub item_fn: syn::ItemFn, +} + +impl TryFrom for E2EFn { + type Error = syn::Error; + + fn try_from(item_fn: syn::ItemFn) -> Result { + idents_lint::ensure_no_ink_identifiers(&item_fn)?; + Ok(E2EFn { item_fn }) + } +} + +impl InkE2ETest { + /// Returns `Ok` if the test matches all requirements for an + /// ink! E2E test definition. + pub fn new(attrs: TokenStream2, input: TokenStream2) -> Result { + let config = syn::parse2::(attrs)?; + let e2e_config = ir::E2EConfig::try_from(config)?; + let item_fn = syn::parse2::(input)?; + let e2e_fn = E2EFn::try_from(item_fn)?; + Ok(Self { + item_fn: e2e_fn, + config: e2e_config, + }) + } +} diff --git a/crates/lang/ir/src/ir/mod.rs b/crates/lang/ir/src/ir/mod.rs index 1f09ca5f7af..bf255dea4ff 100644 --- a/crates/lang/ir/src/ir/mod.rs +++ b/crates/lang/ir/src/ir/mod.rs @@ -19,7 +19,9 @@ mod blake2; mod chain_extension; mod config; mod contract; +mod e2e_config; mod idents_lint; +mod ink_e2e_test; mod ink_test; mod item; mod item_impl; @@ -67,6 +69,8 @@ pub use self::{ }, config::Config, contract::Contract, + e2e_config::E2EConfig, + ink_e2e_test::InkE2ETest, ink_test::InkTest, item::{ Event, diff --git a/crates/lang/ir/src/lib.rs b/crates/lang/ir/src/lib.rs index 229f7c0b39c..cd43d5d4511 100644 --- a/crates/lang/ir/src/lib.rs +++ b/crates/lang/ir/src/lib.rs @@ -51,6 +51,7 @@ pub use self::{ Event, ExtensionId, ImplItem, + InkE2ETest, InkItem, InkItemTrait, InkTest, diff --git a/crates/lang/macro/src/ink_e2e_test.rs b/crates/lang/macro/src/ink_e2e_test.rs new file mode 100644 index 00000000000..6b459c1a006 --- /dev/null +++ b/crates/lang/macro/src/ink_e2e_test.rs @@ -0,0 +1,29 @@ +// Copyright 2018-2022 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use ink_lang_codegen::generate_code; +use proc_macro2::TokenStream as TokenStream2; +use syn::Result; + +pub fn generate(attr: TokenStream2, input: TokenStream2) -> TokenStream2 { + match generate_or_err(attr, input) { + Ok(tokens) => tokens, + Err(err) => err.to_compile_error(), + } +} + +pub fn generate_or_err(attr: TokenStream2, input: TokenStream2) -> Result { + let test_definition = ink_lang_ir::InkE2ETest::new(attr, input)?; + Ok(generate_code(&test_definition)) +} diff --git a/crates/lang/macro/src/lib.rs b/crates/lang/macro/src/lib.rs index bf37a8438fc..94fe4258a2f 100644 --- a/crates/lang/macro/src/lib.rs +++ b/crates/lang/macro/src/lib.rs @@ -17,6 +17,7 @@ extern crate proc_macro; mod blake2b; mod chain_extension; mod contract; +mod ink_e2e_test; mod ink_test; mod selector; mod storage_item; @@ -857,6 +858,141 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { ink_test::generate(attr.into(), item.into()).into() } +/// Defines an End-to-End test. +/// +/// The system requirements are: +/// +/// - A Substrate node with `pallet-contracts` running in the background. +/// You can e.g. use [`substrate-contracts-node`](https://github.com/paritytech/substrate-contracts-node) +/// and launch it with +/// `substrate-contracts-node -lerror,runtime::contracts=debug > /tmp/contracts-node.log 2>&1`. +/// - A `cargo-contract` installation that can build the contract. +/// +/// Before the test function is invoked the contract will have been build. Any errors +/// that occur during the contract build will prevent the test function from being +/// invoked. +/// +/// ## Header Arguments +/// +/// The `#[ink::e2e_test]` macro can be provided with some additional comma-separated +/// header arguments: +/// +/// - `ws_url: String` +/// +/// The `ws_url` denotes the WebSocket URL where to connect to the RPC +/// endpoint of the node. +/// +/// **Usage Example:** +/// ```no_compile +/// # // TODO(#xxx) Remove the `no_compile`. +/// # use ink_lang as ink; +/// type E2EResult = std::result::Result>; +/// #[ink::e2e_test(ws_url = "ws://localhost:9944")] +/// async fn e2e_contract_must_transfer_value_to_sender( +/// mut client: ink_env::e2e::Client, +/// ) -> E2EResult<()> { +/// Ok(()) +/// } +/// ``` +/// +/// **Default value:** `"ws://localhost:9944"`. +/// +/// - `node_log: String` +/// +/// The `node_log` denotes the path under which to find the node's log. +/// +/// **Usage Example:** +/// ```no_compile +/// # // TODO(#xxx) Remove the `no_compile`. +/// # use ink_lang as ink; +/// type E2EResult = std::result::Result>; +/// #[ink::e2e_test(ws_url = "ws://localhost:9944")] +/// async fn e2e_contract_must_transfer_value_to_sender( +/// mut client: ink_env::e2e::Client, +/// ) -> E2EResult<()> { +/// assert!(client.node_log_contains("requested value: 100000000000000\n")); +/// Ok(()) +/// } +/// ``` +/// +/// **Default value:** `"/tmp/contracts-node.log"`. +/// +/// - `skip_build: true` +/// +/// Skips building the contract as part of the test. This is handy for debugging +/// test logic, when one wants to avoid the overhead of building the contract. +/// +/// **Usage Example:** +/// ```no_compile +/// # // TODO(#xxx) Remove the `no_compile`. +/// # use ink_lang as ink; +/// type E2EResult = std::result::Result>; +/// #[ink::e2e_test(skip_build = true)] +/// async fn e2e_contract_must_transfer_value_to_sender( +/// mut client: ink_env::e2e::Client, +/// ) -> E2EResult<()> { +/// Ok(()) +/// } +/// ``` +/// +/// **Default value:** `false`. +/// +/// # Example +/// +/// ```no_compile +/// # // TODO(#xxx) Remove the `no_compile`. +/// use ink_lang as ink; +/// +/// #[cfg(test)] +/// mod tests { +/// use ink_env::e2e::*; +/// type E2EResult = std::result::Result>; +/// +/// #[ink::e2e_test(skip_build = true)] +/// async fn e2e_test_2(mut client: ink_env::e2e::Client) -> E2EResult<()> { +/// // given +/// let constructor = contract_transfer::constructors::new(); +/// let contract_acc_id = client.instantiate( +/// &mut ink_env::e2e::alice(), +/// constructor, +/// 1337, +/// None, +/// ) +/// .await +/// .expect("instantiating contract failed") +/// .account_id; +/// +/// // when +/// let transfer = contract_transfer::messages::give_me(120); +/// let call_res = client.call( +/// &mut ink_env::e2e::bob(), +/// contract_acc_id.clone(), +/// transfer.into(), +/// 10, +/// None, +/// ) +/// .await; +/// +/// // then +/// assert!(call_res.is_ok()); +/// Ok(()) +/// } +/// } +/// ``` +/// +/// You can also use build the `Signer` type yourself, without going through +/// the pre-defined functions: +/// +/// ```no_compile +/// let mut bob = ink_env::e2e::PairSigner::new( +/// ink_env::e2e::AccountKeyring::Bob.pair() +/// ); +/// ``` +#[proc_macro_attribute] +pub fn e2e_test(attr: TokenStream, item: TokenStream) -> TokenStream { + ink_e2e_test::generate(attr.into(), item.into()).into() +} + /// Defines the interface for a chain extension. /// /// # Structure diff --git a/crates/lang/src/lib.rs b/crates/lang/src/lib.rs index e0e3cd85c74..c84526ed2a4 100644 --- a/crates/lang/src/lib.rs +++ b/crates/lang/src/lib.rs @@ -39,6 +39,7 @@ pub use ink_lang_macro::{ blake2x256, chain_extension, contract, + e2e_test, selector_bytes, selector_id, storage_item, diff --git a/crates/lang/tests/ui/contract/fail/constructor-input-non-codec.stderr b/crates/lang/tests/ui/contract/fail/constructor-input-non-codec.stderr index dd654902eac..1f6322507ac 100644 --- a/crates/lang/tests/ui/contract/fail/constructor-input-non-codec.stderr +++ b/crates/lang/tests/ui/contract/fail/constructor-input-non-codec.stderr @@ -8,6 +8,7 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeDecode` is not satisfied Arc Box Rc + sp_core::Bytes = note: required because of the requirements on the impl of `parity_scale_codec::Decode` for `NonCodecType` note: required by a bound in `DispatchInput` --> src/codegen/dispatch/type_check.rs @@ -25,6 +26,7 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeDecode` is not satisfied Arc Box Rc + sp_core::Bytes = note: required because of the requirements on the impl of `parity_scale_codec::Decode` for `NonCodecType` error[E0277]: the trait bound `NonCodecType: WrapperTypeEncode` is not satisfied @@ -42,7 +44,7 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeEncode` is not satisfied Rc String Vec - parity_scale_codec::Ref<'a, T, U> + and 2 others = note: required because of the requirements on the impl of `Encode` for `NonCodecType` note: required by a bound in `ExecutionInput::>::push_arg` --> $WORKSPACE/crates/env/src/call/execution_input.rs diff --git a/crates/lang/tests/ui/contract/fail/message-input-non-codec.stderr b/crates/lang/tests/ui/contract/fail/message-input-non-codec.stderr index 966210367c2..7376d4dfe44 100644 --- a/crates/lang/tests/ui/contract/fail/message-input-non-codec.stderr +++ b/crates/lang/tests/ui/contract/fail/message-input-non-codec.stderr @@ -8,6 +8,7 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeDecode` is not satisfied Arc Box Rc + sp_core::Bytes = note: required because of the requirements on the impl of `parity_scale_codec::Decode` for `NonCodecType` note: required by a bound in `DispatchInput` --> src/codegen/dispatch/type_check.rs @@ -25,6 +26,7 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeDecode` is not satisfied Arc Box Rc + sp_core::Bytes = note: required because of the requirements on the impl of `parity_scale_codec::Decode` for `NonCodecType` error[E0277]: the trait bound `NonCodecType: WrapperTypeEncode` is not satisfied @@ -42,7 +44,7 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeEncode` is not satisfied Rc String Vec - parity_scale_codec::Ref<'a, T, U> + and 2 others = note: required because of the requirements on the impl of `Encode` for `NonCodecType` note: required by a bound in `ExecutionInput::>::push_arg` --> $WORKSPACE/crates/env/src/call/execution_input.rs diff --git a/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr b/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr index 5ff23670709..bd224e74e21 100644 --- a/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr +++ b/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr @@ -13,7 +13,7 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeEncode` is not satisfied Rc String Vec - parity_scale_codec::Ref<'a, T, U> + and 2 others = note: required because of the requirements on the impl of `Encode` for `NonCodecType` note: required by a bound in `DispatchOutput` --> src/codegen/dispatch/type_check.rs @@ -36,7 +36,7 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeEncode` is not satisfied Rc String Vec - parity_scale_codec::Ref<'a, T, U> + and 2 others = note: required because of the requirements on the impl of `Encode` for `NonCodecType` note: required by a bound in `return_value` --> $WORKSPACE/crates/env/src/api.rs diff --git a/crates/lang/tests/ui/storage_item/fail/packed_is_not_derived_automatically.stderr b/crates/lang/tests/ui/storage_item/fail/packed_is_not_derived_automatically.stderr index e91f54e5cef..eb03e03add0 100644 --- a/crates/lang/tests/ui/storage_item/fail/packed_is_not_derived_automatically.stderr +++ b/crates/lang/tests/ui/storage_item/fail/packed_is_not_derived_automatically.stderr @@ -8,6 +8,7 @@ error[E0277]: the trait bound `NonPacked: WrapperTypeDecode` is not satisfied Arc Box Rc + sp_core::Bytes = note: required because of the requirements on the impl of `Decode` for `NonPacked` = note: required because of the requirements on the impl of `Packed` for `NonPacked` note: required by a bound in `consume_packed` @@ -31,7 +32,7 @@ error[E0277]: the trait bound `NonPacked: WrapperTypeEncode` is not satisfied Rc String Vec - parity_scale_codec::Ref<'a, T, U> + and 2 others = note: required because of the requirements on the impl of `Encode` for `NonPacked` = note: required because of the requirements on the impl of `Packed` for `NonPacked` note: required by a bound in `consume_packed` diff --git a/crates/lang/tests/ui/trait_def/fail/message_input_non_codec.stderr b/crates/lang/tests/ui/trait_def/fail/message_input_non_codec.stderr index 90e3ff499e6..422531c9f92 100644 --- a/crates/lang/tests/ui/trait_def/fail/message_input_non_codec.stderr +++ b/crates/lang/tests/ui/trait_def/fail/message_input_non_codec.stderr @@ -8,6 +8,7 @@ error[E0277]: the trait bound `NonCodec: WrapperTypeDecode` is not satisfied Arc Box Rc + sp_core::Bytes = note: required because of the requirements on the impl of `parity_scale_codec::Decode` for `NonCodec` note: required by a bound in `DispatchInput` --> src/codegen/dispatch/type_check.rs @@ -30,7 +31,7 @@ error[E0277]: the trait bound `NonCodec: WrapperTypeEncode` is not satisfied Rc String Vec - parity_scale_codec::Ref<'a, T, U> + and 2 others = note: required because of the requirements on the impl of `Encode` for `NonCodec` note: required by a bound in `ExecutionInput::>::push_arg` --> $WORKSPACE/crates/env/src/call/execution_input.rs diff --git a/crates/lang/tests/ui/trait_def/fail/message_output_non_codec.stderr b/crates/lang/tests/ui/trait_def/fail/message_output_non_codec.stderr index c53b55066a7..6a6a1ec5dc7 100644 --- a/crates/lang/tests/ui/trait_def/fail/message_output_non_codec.stderr +++ b/crates/lang/tests/ui/trait_def/fail/message_output_non_codec.stderr @@ -13,7 +13,7 @@ error[E0277]: the trait bound `NonCodec: WrapperTypeEncode` is not satisfied Rc String Vec - parity_scale_codec::Ref<'a, T, U> + and 2 others = note: required because of the requirements on the impl of `Encode` for `NonCodec` note: required by a bound in `DispatchOutput` --> src/codegen/dispatch/type_check.rs From a786af38fd61e725b672b65c0f3488352a792167 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Wed, 14 Sep 2022 13:02:00 +0200 Subject: [PATCH 06/16] Add E2E tests for `contract-transfer` example --- examples/contract-transfer/lib.rs | 86 +++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/examples/contract-transfer/lib.rs b/examples/contract-transfer/lib.rs index 27e419dadd9..9490668bed6 100644 --- a/examples/contract-transfer/lib.rs +++ b/examples/contract-transfer/lib.rs @@ -180,4 +180,90 @@ pub mod give_me { .expect("Cannot get account balance") } } + + #[cfg(test)] + mod e2e_tests { + use super::*; + use ink_env::e2e::*; + use ink_lang as ink; + + type E2EResult = std::result::Result>; + + #[ink::e2e_test] + async fn e2e_sending_value_to_give_me_must_fail( + mut client: ink_env::e2e::Client, + ) -> E2EResult<()> { + // given + let constructor = contract_transfer::constructors::new(); + let contract_acc_id = client + .instantiate(&mut ink_env::e2e::alice(), constructor, 1000, None) + .await + .expect("instantiate failed") + .account_id; + + // when + let transfer = contract_transfer::messages::give_me(120); + let call_res = client + .call( + &mut ink_env::e2e::bob(), + contract_acc_id.clone(), + transfer.into(), + 10, + None, + ) + .await; + + // then + assert!(call_res.is_err()); + let contains_err_msg = match call_res.unwrap_err() { + ink_env::e2e::Error::CallDryRun(dry_run) => { + String::from_utf8_lossy(&dry_run.debug_message) + .contains("paid an unpayable message") + } + _ => false, + }; + assert!(contains_err_msg); + Ok(()) + } + + #[ink::e2e_test] + async fn e2e_contract_must_transfer_value_to_sender( + mut client: ink_env::e2e::Client, + ) -> E2EResult<()> { + // given + let constructor = contract_transfer::constructors::new(); + let contract_acc_id = client + .instantiate(&mut ink_env::e2e::bob(), constructor, 1337, None) + .await + .expect("instantiate failed") + .account_id; + let balance_before: Balance = client + .balance(contract_acc_id.clone()) + .await + .expect("getting balance failed"); + + // when + let transfer = contract_transfer::messages::give_me(120); + let _ = client + .call( + &mut ink_env::e2e::eve(), + contract_acc_id.clone(), + transfer.into(), + 0, + None, + ) + .await + .expect("call failed"); + + // then + let balance_after: Balance = client + .balance(contract_acc_id) + .await + .expect("getting balance failed"); + assert_eq!(balance_before - balance_after, 120); + assert!(client.node_log_contains("requested value: 100000000000000\n")); + + Ok(()) + } + } } From 3119f64a3b0692bc6ce1be8b5f4e5fea82f7e14d Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Mon, 19 Sep 2022 06:35:15 +0200 Subject: [PATCH 07/16] Add ToDo comment for migration to `state_call` RPC --- crates/env/src/engine/e2e/xts.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/env/src/engine/e2e/xts.rs b/crates/env/src/engine/e2e/xts.rs index c06aeeaa98a..2973d4f37f7 100644 --- a/crates/env/src/engine/e2e/xts.rs +++ b/crates/env/src/engine/e2e/xts.rs @@ -195,7 +195,7 @@ where data: data.into(), salt: salt.into(), }; - + // TODO(#xxx) Dry-run has to use the `state_call` RPC. let params = rpc_params![call_request]; self.ws_client .request("contracts_instantiate", params) @@ -288,6 +288,7 @@ where }), input_data: Bytes(data), }; + // TODO(#xxx) Dry-run has to use the `state_call` RPC. let params = rpc_params![call_request]; self.ws_client .request("contracts_call", params) From fe9ce068533e7eccaa842578c1ba2a2b0eda70ed Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Mon, 19 Sep 2022 08:27:49 +0200 Subject: [PATCH 08/16] Update to new `ink` entrance crate --- crates/env/Cargo.toml | 2 +- crates/ink/macro/src/ink_e2e_test.rs | 4 ++-- crates/ink/macro/src/lib.rs | 5 ----- examples/contract-transfer/lib.rs | 3 +-- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/crates/env/Cargo.toml b/crates/env/Cargo.toml index eaefd979ed3..f5f5ae5e05d 100644 --- a/crates/env/Cargo.toml +++ b/crates/env/Cargo.toml @@ -50,7 +50,7 @@ secp256k1 = { version = "0.24", features = ["recovery", "global-context"], optio rand = { version = "0.8", default-features = false, features = ["alloc"], optional = true } scale-info = { version = "2", default-features = false, features = ["derive"], optional = true } -contract-metadata = "2.0.0-alpha.1" +contract-metadata = "2.0.0-alpha.2" impl-serde = { version = "0.3.1", default-features = false } jsonrpsee = { version = "0.14.0", features = ["ws-client"] } pallet-contracts-primitives = "6.0.0" diff --git a/crates/ink/macro/src/ink_e2e_test.rs b/crates/ink/macro/src/ink_e2e_test.rs index 6b459c1a006..7cb58319bd3 100644 --- a/crates/ink/macro/src/ink_e2e_test.rs +++ b/crates/ink/macro/src/ink_e2e_test.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use ink_lang_codegen::generate_code; +use ink_codegen::generate_code; use proc_macro2::TokenStream as TokenStream2; use syn::Result; @@ -24,6 +24,6 @@ pub fn generate(attr: TokenStream2, input: TokenStream2) -> TokenStream2 { } pub fn generate_or_err(attr: TokenStream2, input: TokenStream2) -> Result { - let test_definition = ink_lang_ir::InkE2ETest::new(attr, input)?; + let test_definition = ink_ir::InkE2ETest::new(attr, input)?; Ok(generate_code(&test_definition)) } diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index 63e5cb4a390..e1b6792fd8d 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -862,7 +862,6 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// **Usage Example:** /// ```no_compile /// # // TODO(#xxx) Remove the `no_compile`. -/// # use ink_lang as ink; /// type E2EResult = std::result::Result>; /// #[ink::e2e_test(ws_url = "ws://localhost:9944")] /// async fn e2e_contract_must_transfer_value_to_sender( @@ -881,7 +880,6 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// **Usage Example:** /// ```no_compile /// # // TODO(#xxx) Remove the `no_compile`. -/// # use ink_lang as ink; /// type E2EResult = std::result::Result>; /// #[ink::e2e_test(ws_url = "ws://localhost:9944")] /// async fn e2e_contract_must_transfer_value_to_sender( @@ -902,7 +900,6 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// **Usage Example:** /// ```no_compile /// # // TODO(#xxx) Remove the `no_compile`. -/// # use ink_lang as ink; /// type E2EResult = std::result::Result>; /// #[ink::e2e_test(skip_build = true)] /// async fn e2e_contract_must_transfer_value_to_sender( @@ -918,8 +915,6 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// ```no_compile /// # // TODO(#xxx) Remove the `no_compile`. -/// use ink_lang as ink; -/// /// #[cfg(test)] /// mod tests { /// use ink_env::e2e::*; diff --git a/examples/contract-transfer/lib.rs b/examples/contract-transfer/lib.rs index f76344da5cd..7965f37f1f3 100644 --- a/examples/contract-transfer/lib.rs +++ b/examples/contract-transfer/lib.rs @@ -183,8 +183,7 @@ pub mod give_me { #[cfg(test)] mod e2e_tests { use super::*; - use ink_env::e2e::*; - use ink_lang as ink; + use ink::env::e2e::*; type E2EResult = std::result::Result>; From 1c0c2df3ed3266c80461e9d1209f3bf0b4cc7d39 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Mon, 19 Sep 2022 09:42:15 +0200 Subject: [PATCH 09/16] Add ToDo for `node_log_contains` --- crates/env/src/engine/e2e/client.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/env/src/engine/e2e/client.rs b/crates/env/src/engine/e2e/client.rs index 65960a99cb6..bc7b238c449 100644 --- a/crates/env/src/engine/e2e/client.rs +++ b/crates/env/src/engine/e2e/client.rs @@ -515,6 +515,8 @@ where /// Returns true if the `substrate-contracts-node` log under /// `/tmp/contracts-node.log` contains `msg`. + /// TODO(#xxx) Matches on any log entry currently, even if done + /// by a different test. pub fn node_log_contains(&self, msg: &str) -> bool { let output = std::process::Command::new("grep") .arg("-q") From aa7d31acc85eb739030f909606b225aa1112d1a6 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Mon, 19 Sep 2022 09:42:23 +0200 Subject: [PATCH 10/16] Update to `ink` entrance crate --- .../ink/codegen/src/generator/ink_e2e_test.rs | 18 +++++++++--------- crates/ink/macro/src/lib.rs | 18 +++++++++--------- examples/contract-transfer/lib.rs | 14 +++++++------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/crates/ink/codegen/src/generator/ink_e2e_test.rs b/crates/ink/codegen/src/generator/ink_e2e_test.rs index 5cfe84f5b57..8ea4556484d 100644 --- a/crates/ink/codegen/src/generator/ink_e2e_test.rs +++ b/crates/ink/codegen/src/generator/ink_e2e_test.rs @@ -143,29 +143,29 @@ impl GenerateCode for InkE2ETest<'_> { quote! { #( #attrs )* - #[ink_env::e2e::tokio::test] + #[ink::env::e2e::tokio::test] async #vis fn #fn_name () #ret { - use ink_env::e2e::log_info; - ink_env::e2e::LOG_PREFIX.with(|log_prefix| { + use ink::env::e2e::log_info; + ink::env::e2e::LOG_PREFIX.with(|log_prefix| { let str = format!("test: {}", stringify!(#fn_name)); *log_prefix.borrow_mut() = String::from(str); }); log_info("setting up e2e test"); - ink_env::e2e::INIT.call_once(|| { - ink_env::e2e::env_logger::init(); + ink::env::e2e::INIT.call_once(|| { + ink::env::e2e::env_logger::init(); }); log_info("extracting metadata"); // TODO(#xxx) `smart-bench_macro` needs to be forked. - ink_env::e2e::smart_bench_macro::contract!(#path); + ink::env::e2e::smart_bench_macro::contract!(#path); log_info("creating new client"); // TODO(#xxx) Make those two generic environments customizable. - let mut client = ink_env::e2e::Client::< - ink_env::e2e::PolkadotConfig, - ink_env::DefaultEnvironment + let mut client = ink::env::e2e::Client::< + ink::env::e2e::PolkadotConfig, + ink::env::DefaultEnvironment >::new(&#path, &#ws_url, &#node_log).await; let __ret = { diff --git a/crates/ink/macro/src/lib.rs b/crates/ink/macro/src/lib.rs index e1b6792fd8d..11f7b45307f 100644 --- a/crates/ink/macro/src/lib.rs +++ b/crates/ink/macro/src/lib.rs @@ -865,7 +865,7 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// type E2EResult = std::result::Result>; /// #[ink::e2e_test(ws_url = "ws://localhost:9944")] /// async fn e2e_contract_must_transfer_value_to_sender( -/// mut client: ink_env::e2e::Client, +/// mut client: ink::env::e2e::Client, /// ) -> E2EResult<()> { /// Ok(()) /// } @@ -883,7 +883,7 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// type E2EResult = std::result::Result>; /// #[ink::e2e_test(ws_url = "ws://localhost:9944")] /// async fn e2e_contract_must_transfer_value_to_sender( -/// mut client: ink_env::e2e::Client, +/// mut client: ink::env::e2e::Client, /// ) -> E2EResult<()> { /// assert!(client.node_log_contains("requested value: 100000000000000\n")); /// Ok(()) @@ -903,7 +903,7 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// type E2EResult = std::result::Result>; /// #[ink::e2e_test(skip_build = true)] /// async fn e2e_contract_must_transfer_value_to_sender( -/// mut client: ink_env::e2e::Client, +/// mut client: ink::env::e2e::Client, /// ) -> E2EResult<()> { /// Ok(()) /// } @@ -917,15 +917,15 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// # // TODO(#xxx) Remove the `no_compile`. /// #[cfg(test)] /// mod tests { -/// use ink_env::e2e::*; +/// use ink::env::e2e::*; /// type E2EResult = std::result::Result>; /// /// #[ink::e2e_test(skip_build = true)] -/// async fn e2e_test_2(mut client: ink_env::e2e::Client) -> E2EResult<()> { +/// async fn e2e_test_2(mut client: ink::env::e2e::Client) -> E2EResult<()> { /// // given /// let constructor = contract_transfer::constructors::new(); /// let contract_acc_id = client.instantiate( -/// &mut ink_env::e2e::alice(), +/// &mut ink::env::e2e::alice(), /// constructor, /// 1337, /// None, @@ -937,7 +937,7 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// // when /// let transfer = contract_transfer::messages::give_me(120); /// let call_res = client.call( -/// &mut ink_env::e2e::bob(), +/// &mut ink::env::e2e::bob(), /// contract_acc_id.clone(), /// transfer.into(), /// 10, @@ -956,8 +956,8 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream { /// the pre-defined functions: /// /// ```no_compile -/// let mut bob = ink_env::e2e::PairSigner::new( -/// ink_env::e2e::AccountKeyring::Bob.pair() +/// let mut bob = ink::env::e2e::PairSigner::new( +/// ink::env::e2e::AccountKeyring::Bob.pair() /// ); /// ``` #[proc_macro_attribute] diff --git a/examples/contract-transfer/lib.rs b/examples/contract-transfer/lib.rs index 7965f37f1f3..7667911d251 100644 --- a/examples/contract-transfer/lib.rs +++ b/examples/contract-transfer/lib.rs @@ -189,12 +189,12 @@ pub mod give_me { #[ink::e2e_test] async fn e2e_sending_value_to_give_me_must_fail( - mut client: ink_env::e2e::Client, + mut client: ink::env::e2e::Client, ) -> E2EResult<()> { // given let constructor = contract_transfer::constructors::new(); let contract_acc_id = client - .instantiate(&mut ink_env::e2e::alice(), constructor, 1000, None) + .instantiate(&mut ink::env::e2e::alice(), constructor, 1000, None) .await .expect("instantiate failed") .account_id; @@ -203,7 +203,7 @@ pub mod give_me { let transfer = contract_transfer::messages::give_me(120); let call_res = client .call( - &mut ink_env::e2e::bob(), + &mut ink::env::e2e::bob(), contract_acc_id.clone(), transfer.into(), 10, @@ -214,7 +214,7 @@ pub mod give_me { // then assert!(call_res.is_err()); let contains_err_msg = match call_res.unwrap_err() { - ink_env::e2e::Error::CallDryRun(dry_run) => { + ink::env::e2e::Error::CallDryRun(dry_run) => { String::from_utf8_lossy(&dry_run.debug_message) .contains("paid an unpayable message") } @@ -226,12 +226,12 @@ pub mod give_me { #[ink::e2e_test] async fn e2e_contract_must_transfer_value_to_sender( - mut client: ink_env::e2e::Client, + mut client: ink::env::e2e::Client, ) -> E2EResult<()> { // given let constructor = contract_transfer::constructors::new(); let contract_acc_id = client - .instantiate(&mut ink_env::e2e::bob(), constructor, 1337, None) + .instantiate(&mut ink::env::e2e::bob(), constructor, 1337, None) .await .expect("instantiate failed") .account_id; @@ -244,7 +244,7 @@ pub mod give_me { let transfer = contract_transfer::messages::give_me(120); let _ = client .call( - &mut ink_env::e2e::eve(), + &mut ink::env::e2e::eve(), contract_acc_id.clone(), transfer.into(), 0, From 5509604293b5af5b8d791a05a1587dd8e8bb38ea Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Tue, 20 Sep 2022 06:07:42 +0200 Subject: [PATCH 11/16] Migrate to `state_call` RPC --- crates/env/src/engine/e2e/client.rs | 20 ++--- crates/env/src/engine/e2e/mod.rs | 4 + crates/env/src/engine/e2e/xts.rs | 124 ++++++++++++---------------- 3 files changed, 64 insertions(+), 84 deletions(-) diff --git a/crates/env/src/engine/e2e/client.rs b/crates/env/src/engine/e2e/client.rs index bc7b238c449..2e50701c4fa 100644 --- a/crates/env/src/engine/e2e/client.rs +++ b/crates/env/src/engine/e2e/client.rs @@ -24,10 +24,10 @@ use super::{ self, api, Call, - ContractDryCallResult, - ContractDryInstantiateResult, InstantiateWithCode, }, + ContractExecResult, + ContractInstantiateResult, ContractsApi, InkConstructor, InkMessage, @@ -43,7 +43,6 @@ use sp_runtime::traits::{ use subxt::{ ext::bitvec::macros::internal::funty::Fundamental, metadata::DecodeStaticType, - rpc::NumberOrHex, storage::address::{ StorageHasher, StorageMapKey, @@ -82,7 +81,7 @@ pub struct InstantiationResult { pub account_id: C::AccountId, /// The result of the dry run, contains debug messages /// if there were any. - pub dry_run: ContractDryInstantiateResult, + pub dry_run: ContractInstantiateResult, /// Events that happened with the contract instantiation. pub events: TxEvents, } @@ -109,7 +108,7 @@ where pub struct CallResult { /// The result of the dry run, contains debug messages /// if there were any. - pub dry_run: ContractDryCallResult, + pub dry_run: ContractExecResult, /// Events that happened with the contract instantiation. pub events: TxEvents, } @@ -144,11 +143,11 @@ where ::Balance: core::fmt::Debug, { /// The `instantiate_with_code` dry run failed. - InstantiateDryRun(ContractDryInstantiateResult), + InstantiateDryRun(ContractInstantiateResult), /// The `instantiate_with_code` extrinsic failed. InstantiateExtrinsic(subxt::error::DispatchError), /// The `call` dry run failed. - CallDryRun(ContractDryCallResult), + CallDryRun(ContractExecResult), /// The `call` extrinsic failed. CallExtrinsic(subxt::error::DispatchError), } @@ -215,11 +214,7 @@ where sr25519::Signature: Into, E: Environment, - E::Balance: core::fmt::Debug - + scale::Encode - + TryFrom - + TryFrom, - NumberOrHex: From<::Balance>, + E::Balance: core::fmt::Debug + scale::Encode, Call: scale::Encode, InstantiateWithCode: scale::Encode, @@ -430,7 +425,6 @@ where "call dry run debug message: {}", String::from_utf8_lossy(&dry_run.debug_message) )); - if dry_run.result.is_err() { return Err(Error::CallDryRun(dry_run)) } diff --git a/crates/env/src/engine/e2e/mod.rs b/crates/env/src/engine/e2e/mod.rs index e93144f46b1..4f862cb8756 100644 --- a/crates/env/src/engine/e2e/mod.rs +++ b/crates/env/src/engine/e2e/mod.rs @@ -24,6 +24,10 @@ pub use client::{ }; pub use default_accounts::*; // TODO(#xxx) `smart-bench_macro` needs to be forked. +use pallet_contracts_primitives::{ + ContractExecResult, + ContractInstantiateResult, +}; pub use smart_bench_macro; use xts::ContractsApi; diff --git a/crates/env/src/engine/e2e/xts.rs b/crates/env/src/engine/e2e/xts.rs index 2973d4f37f7..43425ef0822 100644 --- a/crates/env/src/engine/e2e/xts.rs +++ b/crates/env/src/engine/e2e/xts.rs @@ -12,11 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::*; -use crate::{ - e2e::Signer, - Environment, +use super::{ + log_info, + sr25519, + ContractExecResult, + ContractInstantiateResult, + IdentifyAccount, + Signer, + Verify, }; +use crate::Environment; use core::marker::PhantomData; use jsonrpsee::{ @@ -27,17 +32,11 @@ use jsonrpsee::{ WsClientBuilder, }, }; -use pallet_contracts_primitives::{ - ContractResult, - ExecReturnValue, - InstantiateReturnValue, -}; use sp_core::{ Bytes, H256, }; use subxt::{ - rpc::NumberOrHex, tx::{ ExtrinsicParams, TxEvents, @@ -77,37 +76,25 @@ pub struct Call { data: Vec, } -/// Result of a contract call dry run. -pub(super) type ContractDryCallResult = ContractResult< - Result, - ::Balance, ->; - -/// Result of a contract instantiation dry run. -pub(super) type ContractDryInstantiateResult = ContractResult< - Result::AccountId>, serde_json::Value>, - ::Balance, ->; - /// A struct that encodes RPC parameters required to instantiate a new smart contract. -#[derive(serde::Serialize)] +#[derive(serde::Serialize, scale::Encode)] #[serde(rename_all = "camelCase")] -struct InstantiateRequest { +struct InstantiateRequest { origin: C::AccountId, - value: NumberOrHex, - gas_limit: NumberOrHex, - storage_deposit_limit: Option, + value: E::Balance, + gas_limit: crate::types::Gas, + storage_deposit_limit: Option, code: Code, - data: Bytes, - salt: Bytes, + data: Vec, + salt: Vec, } /// Reference to an existing code hash or a new Wasm module. -#[derive(serde::Serialize)] +#[derive(serde::Serialize, scale::Encode)] #[serde(rename_all = "camelCase")] enum Code { /// A Wasm module as raw bytes. - Upload(Bytes), + Upload(Vec), #[allow(unused)] /// The code hash of an on-chain Wasm blob. Existing(H256), @@ -116,15 +103,15 @@ enum Code { /// A struct that encodes RPC parameters required for a call to a smart contract. /// /// Copied from [`pallet-contracts-rpc`]. -#[derive(serde::Serialize)] +#[derive(serde::Serialize, scale::Encode)] #[serde(rename_all = "camelCase")] -struct RpcCallRequest { +struct RpcCallRequest { origin: C::AccountId, dest: C::AccountId, - value: NumberOrHex, - gas_limit: NumberOrHex, - storage_deposit_limit: Option, - input_data: Bytes, + value: E::Balance, + gas_limit: crate::types::Gas, + storage_deposit_limit: Option, + input_data: Vec, } /// Provides functions for interacting with the `pallet-contracts` API. @@ -147,9 +134,7 @@ where sr25519::Signature: Into, E: Environment, - E::Balance: - scale::Encode + TryFrom + TryFrom, - NumberOrHex: From<::Balance>, + E::Balance: scale::Encode, Call: scale::Encode, InstantiateWithCode: scale::Encode, @@ -180,29 +165,27 @@ where data: Vec, salt: Vec, signer: &Signer, - ) -> ContractDryInstantiateResult { - let code = Code::Upload(code.into()); - let value = value.try_into().ok().unwrap(); - let call_request = InstantiateRequest:: { + ) -> ContractInstantiateResult { + let code = Code::Upload(code); + let call_request = InstantiateRequest:: { origin: signer.account_id().clone(), value, - gas_limit: NumberOrHex::Number(DRY_RUN_GAS_LIMIT), - storage_deposit_limit: storage_deposit_limit.map(|l| { - l.try_into() - .expect("unable to convert `storage_deposit_limit`") - }), + gas_limit: DRY_RUN_GAS_LIMIT, + storage_deposit_limit, code, - data: data.into(), - salt: salt.into(), + data, + salt, }; - // TODO(#xxx) Dry-run has to use the `state_call` RPC. - let params = rpc_params![call_request]; - self.ws_client - .request("contracts_instantiate", params) + let func = "ContractsApi_instantiate"; + let params = rpc_params![func, Bytes(scale::Encode::encode(&call_request))]; + let bytes: Bytes = self + .ws_client + .request("state_call", params) .await .unwrap_or_else(|err| { panic!("error on ws request `contracts_instantiate`: {:?}", err); - }) + }); + scale::Decode::decode(&mut bytes.as_ref()).expect("decoding failed") } /// Submits an extrinsic to instantiate a contract with the given code. @@ -275,27 +258,26 @@ where contract: C::AccountId, value: E::Balance, storage_deposit_limit: Option, - data: Vec, - ) -> ContractDryCallResult { - let call_request = RpcCallRequest:: { + input_data: Vec, + ) -> ContractExecResult { + let call_request = RpcCallRequest:: { origin: contract.clone(), dest: contract, - value: value.try_into().expect("unable to convert `value`"), - gas_limit: DRY_RUN_GAS_LIMIT.into(), - storage_deposit_limit: storage_deposit_limit.map(|l| { - l.try_into() - .expect("unable to convert `storage_deposit_limit`") - }), - input_data: Bytes(data), + value, + gas_limit: DRY_RUN_GAS_LIMIT, + storage_deposit_limit, + input_data, }; - // TODO(#xxx) Dry-run has to use the `state_call` RPC. - let params = rpc_params![call_request]; - self.ws_client - .request("contracts_call", params) + let func = "ContractsApi_call"; + let params = rpc_params![func, Bytes(scale::Encode::encode(&call_request))]; + let bytes: Bytes = self + .ws_client + .request("state_call", params) .await .unwrap_or_else(|err| { panic!("error on ws request `contracts_call`: {:?}", err); - }) + }); + scale::Decode::decode(&mut bytes.as_ref()).expect("decoding failed") } /// Submits an extrinsic to call a contract with the given parameters. From cdd52e5cc0f1c34f65e549513ce33d28d5093685 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Tue, 20 Sep 2022 08:04:19 +0200 Subject: [PATCH 12/16] Always initialize `env_logger` --- crates/ink/codegen/src/generator/ink_e2e_test.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/ink/codegen/src/generator/ink_e2e_test.rs b/crates/ink/codegen/src/generator/ink_e2e_test.rs index 8ea4556484d..897946d0fee 100644 --- a/crates/ink/codegen/src/generator/ink_e2e_test.rs +++ b/crates/ink/codegen/src/generator/ink_e2e_test.rs @@ -128,6 +128,10 @@ impl GenerateCode for InkE2ETest<'_> { *metadata_path.borrow_mut() = Some(path.clone()); }); }); + } else { + BUILD_ONCE.call_once(|| { + env_logger::init(); + }); } log::info!("using metadata path: {:?}", path); From d28d234e7268af625099777774b9c3ab23a6ae9a Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Mon, 26 Sep 2022 17:03:50 +0200 Subject: [PATCH 13/16] Use latest `subxt` release --- crates/env/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/env/Cargo.toml b/crates/env/Cargo.toml index 26b394be808..68fb266c36d 100644 --- a/crates/env/Cargo.toml +++ b/crates/env/Cargo.toml @@ -59,8 +59,7 @@ serde_json = "1.0.81" tokio = { version = "1.18.2", features = ["rt-multi-thread"] } log = "0.4" env_logger = "0.8" -# TODO we need to use `subxt`'s `master` until the next release is published. -subxt = { git = "https://github.com/paritytech/subxt" } +subxt = "0.24.0" # Substrate sp-rpc = "6.0.0" From 772c8fbf1e5f1626e5f5f615f2b812eef5da8039 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Mon, 26 Sep 2022 18:59:45 +0200 Subject: [PATCH 14/16] Remove superfluous TODO --- crates/env/src/engine/e2e/xts.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/env/src/engine/e2e/xts.rs b/crates/env/src/engine/e2e/xts.rs index 43425ef0822..d51e80b5550 100644 --- a/crates/env/src/engine/e2e/xts.rs +++ b/crates/env/src/engine/e2e/xts.rs @@ -233,7 +233,6 @@ where err ); }) - // TODO(#xxx) It should be configurable to use `.wait_for_finalized_success` instead. .wait_for_in_block() .await .unwrap_or_else(|err| { From cc9b76a9884899fa861ead02e2658eb75b19cdf6 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Mon, 26 Sep 2022 19:03:46 +0200 Subject: [PATCH 15/16] Apply `cargo fmt` --- crates/env/src/engine/e2e/xts.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/crates/env/src/engine/e2e/xts.rs b/crates/env/src/engine/e2e/xts.rs index d51e80b5550..17e4b9e288b 100644 --- a/crates/env/src/engine/e2e/xts.rs +++ b/crates/env/src/engine/e2e/xts.rs @@ -218,13 +218,15 @@ where ) .unvalidated(); - self - .client + self.client .tx() .sign_and_submit_then_watch_default(&call, signer) .await .map(|tx_progress| { - log_info(&format!("signed and submitted tx with hash {:?}", tx_progress.extrinsic_hash())); + log_info(&format!( + "signed and submitted tx with hash {:?}", + tx_progress.extrinsic_hash() + )); tx_progress }) .unwrap_or_else(|err| { @@ -236,18 +238,12 @@ where .wait_for_in_block() .await .unwrap_or_else(|err| { - panic!( - "error on call `wait_for_in_block`: {:?}", - err - ); + panic!("error on call `wait_for_in_block`: {:?}", err); }) .fetch_events() .await .unwrap_or_else(|err| { - panic!( - "error on call `fetch_events`: {:?}", - err - ); + panic!("error on call `fetch_events`: {:?}", err); }) } From 312a9e688232655f2cbf11a54dfc08699332afd4 Mon Sep 17 00:00:00 2001 From: Michael Mueller Date: Tue, 27 Sep 2022 04:05:33 +0200 Subject: [PATCH 16/16] Adapt test fixtures --- .../tests/ui/contract/fail/constructor-self-receiver-03.stderr | 1 + .../storage_item/fail/packed_is_not_derived_automatically.stderr | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/ink/tests/ui/contract/fail/constructor-self-receiver-03.stderr b/crates/ink/tests/ui/contract/fail/constructor-self-receiver-03.stderr index e836a609d24..2b9e18abcb5 100644 --- a/crates/ink/tests/ui/contract/fail/constructor-self-receiver-03.stderr +++ b/crates/ink/tests/ui/contract/fail/constructor-self-receiver-03.stderr @@ -32,4 +32,5 @@ error[E0277]: the trait bound `&'static Contract: WrapperTypeDecode` is not sati Arc Box Rc + sp_core::Bytes = note: required because of the requirements on the impl of `parity_scale_codec::Decode` for `&'static Contract` diff --git a/crates/ink/tests/ui/storage_item/fail/packed_is_not_derived_automatically.stderr b/crates/ink/tests/ui/storage_item/fail/packed_is_not_derived_automatically.stderr index bdc30f81e02..ede532fa136 100644 --- a/crates/ink/tests/ui/storage_item/fail/packed_is_not_derived_automatically.stderr +++ b/crates/ink/tests/ui/storage_item/fail/packed_is_not_derived_automatically.stderr @@ -8,6 +8,7 @@ error[E0277]: the trait bound `NonPacked: WrapperTypeDecode` is not satisfied Arc Box Rc + sp_core::Bytes = note: required because of the requirements on the impl of `parity_scale_codec::Decode` for `NonPacked` = note: required because of the requirements on the impl of `Packed` for `NonPacked` note: required by a bound in `consume_packed`