diff --git a/Cargo.toml b/Cargo.toml index c5969f24660..21bf8b44466 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ cargo_metadata = { version = "0.17.0" } cfg-if = { version = "1.0" } contract-build = { version = "3.2.0" } derive_more = { version = "0.99.17", default-features = false } -drink = { version = "=0.1.3" } +drink = { version = "=0.1.6" } either = { version = "1.5", default-features = false } funty = { version = "2.0.0" } heck = { version = "0.4.0" } diff --git a/crates/e2e/macro/src/codegen.rs b/crates/e2e/macro/src/codegen.rs index 5ab34fb8eb5..94b75ff7d7d 100644 --- a/crates/e2e/macro/src/codegen.rs +++ b/crates/e2e/macro/src/codegen.rs @@ -69,7 +69,9 @@ impl InkE2ETest { let client_building = match self.test.config.backend() { Backend::Full => build_full_client(&environment, exec_build_contracts), #[cfg(any(test, feature = "drink"))] - Backend::RuntimeOnly => build_runtime_client(exec_build_contracts), + Backend::RuntimeOnly => { + build_runtime_client(exec_build_contracts, self.test.config.runtime()) + } }; quote! { @@ -146,9 +148,14 @@ fn build_full_client(environment: &syn::Path, contracts: TokenStream2) -> TokenS } #[cfg(any(test, feature = "drink"))] -fn build_runtime_client(contracts: TokenStream2) -> TokenStream2 { +fn build_runtime_client( + contracts: TokenStream2, + runtime: Option, +) -> TokenStream2 { + let runtime = + runtime.unwrap_or_else(|| syn::parse_quote! { ::ink_e2e::MinimalRuntime }); quote! { let contracts = #contracts; - let mut client = ::ink_e2e::DrinkClient::new(contracts); + let mut client = ::ink_e2e::DrinkClient::<_, _, #runtime>::new(contracts); } } diff --git a/crates/e2e/macro/src/config.rs b/crates/e2e/macro/src/config.rs index 917fc50e90b..123cec21deb 100644 --- a/crates/e2e/macro/src/config.rs +++ b/crates/e2e/macro/src/config.rs @@ -73,6 +73,9 @@ pub struct E2EConfig { environment: Option, /// The type of the architecture that should be used to run test. backend: Backend, + /// The runtime to use for the runtime-only test. + #[cfg(any(test, feature = "drink"))] + runtime: Option, } impl TryFrom for E2EConfig { @@ -82,6 +85,8 @@ impl TryFrom for E2EConfig { let mut additional_contracts: Option<(syn::LitStr, ast::MetaNameValue)> = None; let mut environment: Option<(syn::Path, ast::MetaNameValue)> = None; let mut backend: Option<(syn::LitStr, ast::MetaNameValue)> = None; + #[cfg(any(test, feature = "drink"))] + let mut runtime: Option<(syn::Path, ast::MetaNameValue)> = None; for arg in args.into_iter() { if arg.name.is_ident("additional_contracts") { @@ -125,6 +130,28 @@ impl TryFrom for E2EConfig { "expected a string literal for `backend` ink! E2E test configuration argument", )); } + } else if arg.name.is_ident("runtime") { + #[cfg(any(test, feature = "drink"))] + { + if let Some((_, ast)) = runtime { + return Err(duplicate_config_err(ast, arg, "runtime", "E2E test")) + } + if let ast::MetaValue::Path(path) = &arg.value { + runtime = Some((path.clone(), arg)) + } else { + return Err(format_err_spanned!( + arg, + "expected a path for `runtime` ink! E2E test configuration argument", + )); + } + } + #[cfg(not(any(test, feature = "drink")))] + { + return Err(format_err_spanned!( + arg, + "the `runtime` ink! E2E test configuration argument is not available because the `drink` feature is not enabled", + )); + } } else { return Err(format_err_spanned!( arg, @@ -141,10 +168,22 @@ impl TryFrom for E2EConfig { .transpose()? .unwrap_or_default(); + #[cfg(any(test, feature = "drink"))] + { + if backend == Backend::Full && runtime.is_some() { + return Err(format_err_spanned!( + runtime.unwrap().1, + "ink! E2E test `runtime` configuration argument is available for `runtime-only` backend only", + )); + } + } + Ok(E2EConfig { additional_contracts, environment, backend, + #[cfg(any(test, feature = "drink"))] + runtime: runtime.map(|(path, _)| path), }) } } @@ -165,6 +204,12 @@ impl E2EConfig { pub fn backend(&self) -> Backend { self.backend } + + /// The runtime to use for the runtime-only test. + #[cfg(any(test, feature = "drink"))] + pub fn runtime(&self) -> Option { + self.runtime.clone() + } } #[cfg(test)] @@ -276,13 +321,59 @@ mod tests { ); } + #[test] + fn runtime_must_be_path() { + assert_try_from( + syn::parse_quote! { runtime = "MinimalRuntime" }, + Err("expected a path for `runtime` ink! E2E test configuration argument"), + ); + } + + #[test] + fn duplicate_runtime_fails() { + assert_try_from( + syn::parse_quote! { + runtime = ::drink::MinimalRuntime, + runtime = ::drink::MaximalRuntime, + }, + Err("encountered duplicate ink! E2E test `runtime` configuration argument"), + ); + } + + #[test] + fn runtime_is_for_runtime_only_backend_only() { + assert_try_from( + syn::parse_quote! { + backend = "full", + runtime = ::drink::MinimalRuntime + }, + Err("ink! E2E test `runtime` configuration argument is available for `runtime-only` backend only"), + ); + } + + #[test] + fn specifying_runtime_works() { + assert_try_from( + syn::parse_quote! { + backend = "runtime-only", + runtime = ::drink::MinimalRuntime + }, + Ok(E2EConfig { + backend: Backend::RuntimeOnly, + runtime: Some(syn::parse_quote! { ::drink::MinimalRuntime }), + ..Default::default() + }), + ); + } + #[test] fn full_config_works() { assert_try_from( syn::parse_quote! { additional_contracts = "adder/Cargo.toml flipper/Cargo.toml", environment = crate::CustomEnvironment, - backend = "full", + backend = "runtime-only", + runtime = ::drink::MinimalRuntime, }, Ok(E2EConfig { additional_contracts: vec![ @@ -290,7 +381,8 @@ mod tests { "flipper/Cargo.toml".into(), ], environment: Some(syn::parse_quote! { crate::CustomEnvironment }), - backend: Backend::Full, + backend: Backend::RuntimeOnly, + runtime: Some(syn::parse_quote! { ::drink::MinimalRuntime }), }), ); } diff --git a/crates/e2e/src/drink_client.rs b/crates/e2e/src/drink_client.rs index a1e6eaec71f..b39ab2d62ee 100644 --- a/crates/e2e/src/drink_client.rs +++ b/crates/e2e/src/drink_client.rs @@ -23,10 +23,7 @@ use drink::{ RuntimeCall, }, contract_api::ContractApi, - runtime::{ - MinimalRuntime, - Runtime, - }, + runtime::Runtime as RuntimeT, Sandbox, DEFAULT_GAS_LIMIT, }; @@ -43,7 +40,6 @@ use scale::{ Encode, }; use sp_core::{ - crypto::AccountId32, sr25519::Pair, Pair as _, }; @@ -57,15 +53,26 @@ use subxt::{ }; use subxt_signer::sr25519::Keypair; -pub struct Client { - sandbox: Sandbox, +pub struct Client { + sandbox: Sandbox, contracts: ContractsRegistry, _phantom: PhantomData<(AccountId, Hash)>, } -unsafe impl Send for Client {} +// While it is not necessary true that `Client` is `Send`, it will not be used in a way +// that would violate this bound. In particular, all `Client` instances will be operating +// synchronously. +unsafe impl Send + for Client +{ +} + +type RuntimeAccountId = ::AccountId; -impl Client { +impl Client +where + RuntimeAccountId: From<[u8; 32]>, +{ pub fn new>(contracts: impl IntoIterator) -> Self { let mut sandbox = Sandbox::new().expect("Failed to initialize Drink! sandbox"); Self::fund_accounts(&mut sandbox); @@ -77,7 +84,7 @@ impl Client { } } - fn fund_accounts(sandbox: &mut Sandbox) { + fn fund_accounts(sandbox: &mut Sandbox) { const TOKENS: u128 = 1_000_000_000_000_000; let accounts = [ @@ -91,7 +98,7 @@ impl Client { crate::two(), ] .map(|kp| kp.public_key().0) - .map(AccountId32::new); + .map(From::from); for account in accounts.into_iter() { sandbox.add_tokens(account, TOKENS); } @@ -99,7 +106,11 @@ impl Client { } #[async_trait] -impl + Send, Hash> ChainBackend for Client { +impl + Send, Hash, Runtime: RuntimeT> ChainBackend + for Client +where + RuntimeAccountId: From<[u8; 32]>, +{ type AccountId = AccountId; type Balance = u128; type Error = (); @@ -121,7 +132,7 @@ impl + Send, Hash> ChainBackend for Client Result { - let account = AccountId32::new(*account.as_ref()); + let account = RuntimeAccountId::::from(*account.as_ref()); Ok(self.sandbox.balance(&account)) } @@ -139,7 +150,7 @@ impl + Send, Hash> ChainBackend for Client = MinimalRuntime::metadata().into(); + let raw_metadata: Vec = Runtime::get_metadata().into(); let metadata = subxt_metadata::Metadata::decode(&mut raw_metadata.as_slice()) .expect("Failed to decode metadata"); @@ -150,13 +161,15 @@ impl + Send, Hash> ChainBackend for Client::decode(&mut encoded_call.as_slice()) - .expect("Failed to decode runtime call"); + let decoded_call = RuntimeCall::::decode(&mut encoded_call.as_slice()) + .expect("Failed to decode runtime call"); // Execute the call. self.sandbox - .runtime_call(decoded_call, Some(keypair_to_account(origin)).into()) + .runtime_call( + decoded_call, + Runtime::convert_account_to_origin(keypair_to_account(origin)), + ) .map_err(|_| ())?; Ok(()) @@ -166,9 +179,12 @@ impl + Send, Hash> ChainBackend for Client + AsRef<[u8; 32]>, - Hash: From<[u8; 32]>, + Hash: Copy + From<[u8; 32]>, + Runtime: RuntimeT, E: Environment + 'static, - > ContractsBackend for Client + > ContractsBackend for Client +where + RuntimeAccountId: From<[u8; 32]> + AsRef<[u8; 32]>, { type Error = (); type EventLog = (); @@ -254,10 +270,16 @@ impl< } }; + let code_hash_raw: [u8; 32] = result + .code_hash + .as_ref() + .try_into() + .expect("Invalid code hash"); + let code_hash = Hash::from(code_hash_raw); Ok(UploadResult { - code_hash: result.code_hash.0.into(), + code_hash, dry_run: Ok(CodeUploadReturnValue { - code_hash: result.code_hash.0.into(), + code_hash, deposit: result.deposit, }), events: (), @@ -320,12 +342,15 @@ impl< impl< AccountId: Clone + Send + Sync + From<[u8; 32]> + AsRef<[u8; 32]>, - Hash: From<[u8; 32]>, + Hash: Copy + From<[u8; 32]>, + Runtime: RuntimeT, E: Environment + 'static, - > E2EBackend for Client + > E2EBackend for Client +where + RuntimeAccountId: From<[u8; 32]> + AsRef<[u8; 32]>, { } -fn keypair_to_account(keypair: &Keypair) -> AccountId32 { - AccountId32::from(keypair.public_key().0) +fn keypair_to_account>(keypair: &Keypair) -> AccountId { + AccountId::from(keypair.public_key().0) } diff --git a/crates/e2e/src/lib.rs b/crates/e2e/src/lib.rs index 8cfcf81ac80..bc3f49a19e8 100644 --- a/crates/e2e/src/lib.rs +++ b/crates/e2e/src/lib.rs @@ -47,8 +47,6 @@ pub use contract_results::{ InstantiationResult, UploadResult, }; -#[cfg(feature = "drink")] -pub use drink_client::Client as DrinkClient; pub use ink_e2e_macro::test; pub use node_proc::{ TestNodeProcess, @@ -69,6 +67,11 @@ pub use subxt_signer::sr25519::{ }; pub use tokio; pub use tracing_subscriber; +#[cfg(feature = "drink")] +pub use { + drink::runtime::MinimalRuntime, + drink_client::Client as DrinkClient, +}; use pallet_contracts_primitives::{ ContractExecResult, diff --git a/crates/ink/macro/src/scale.rs b/crates/ink/macro/src/scale.rs index 58683428503..52bce8a2eaa 100644 --- a/crates/ink/macro/src/scale.rs +++ b/crates/ink/macro/src/scale.rs @@ -36,7 +36,7 @@ pub fn derive(attr: TokenStream2, item: TokenStream2) -> syn::Result(mut client: Client) -> E2EResult<()> { + client + .instantiate( + "e2e-runtime-only-backend", + &ink_e2e::alice(), + FlipperRef::new(false), + 0, + None, + ) + .await + .expect("instantiate failed"); + + Ok(()) + } } }