Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drink backend: allow for arbitrary runtime #1892

Merged
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.2" }
drink = { version = "=0.1.3" }
either = { version = "1.5", default-features = false }
funty = { version = "2.0.0" }
heck = { version = "0.4.0" }
Expand Down
13 changes: 10 additions & 3 deletions crates/e2e/macro/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand Down Expand Up @@ -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<syn::Path>,
) -> 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);
}
}
96 changes: 94 additions & 2 deletions crates/e2e/macro/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ pub struct E2EConfig {
environment: Option<syn::Path>,
/// 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<syn::Path>,
}

impl TryFrom<ast::AttributeArgs> for E2EConfig {
Expand All @@ -82,6 +85,8 @@ impl TryFrom<ast::AttributeArgs> 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") {
Expand Down Expand Up @@ -125,6 +130,28 @@ impl TryFrom<ast::AttributeArgs> 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,
Expand All @@ -141,10 +168,22 @@ impl TryFrom<ast::AttributeArgs> 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),
})
}
}
Expand All @@ -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<syn::Path> {
self.runtime.clone()
}
}

#[cfg(test)]
Expand Down Expand Up @@ -276,21 +321,68 @@ 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![
"adder/Cargo.toml".into(),
"flipper/Cargo.toml".into(),
],
environment: Some(syn::parse_quote! { crate::CustomEnvironment }),
backend: Backend::Full,
backend: Backend::RuntimeOnly,
runtime: Some(syn::parse_quote! { ::drink::MinimalRuntime }),
}),
);
}
Expand Down
41 changes: 25 additions & 16 deletions crates/e2e/src/drink_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ use crate::{
use drink::{
chain_api::ChainApi,
contract_api::ContractApi,
runtime::{
MinimalRuntime,
Runtime,
},
runtime::Runtime as RuntimeT,
Sandbox,
DEFAULT_GAS_LIMIT,
};
Expand Down Expand Up @@ -51,15 +48,21 @@ use std::{
use subxt::dynamic::Value;
use subxt_signer::sr25519::Keypair;

pub struct Client<AccountId, Hash> {
sandbox: Sandbox<MinimalRuntime>,
pub struct Client<AccountId, Hash, Runtime: RuntimeT> {
sandbox: Sandbox<Runtime>,
contracts: ContractsRegistry,
_phantom: PhantomData<(AccountId, Hash)>,
}

unsafe impl<AccountId, Hash> Send for Client<AccountId, Hash> {}
// 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<AccountId, Hash, Runtime: RuntimeT> Send
for Client<AccountId, Hash, Runtime>
{
}

impl<AccountId, Hash> Client<AccountId, Hash> {
impl<AccountId, Hash, Runtime: RuntimeT> Client<AccountId, Hash, Runtime> {
pub fn new<P: Into<PathBuf>>(contracts: impl IntoIterator<Item = P>) -> Self {
let mut sandbox = Sandbox::new().expect("Failed to initialize Drink! sandbox");
Self::fund_accounts(&mut sandbox);
Expand All @@ -71,7 +74,7 @@ impl<AccountId, Hash> Client<AccountId, Hash> {
}
}

fn fund_accounts<R: Runtime>(sandbox: &mut Sandbox<R>) {
fn fund_accounts(sandbox: &mut Sandbox<Runtime>) {
const TOKENS: u128 = 1_000_000_000_000_000;

let accounts = [
Expand All @@ -93,7 +96,9 @@ impl<AccountId, Hash> Client<AccountId, Hash> {
}

#[async_trait]
impl<AccountId: AsRef<[u8; 32]> + Send, Hash> ChainBackend for Client<AccountId, Hash> {
impl<AccountId: AsRef<[u8; 32]> + Send, Hash, Runtime: RuntimeT> ChainBackend
for Client<AccountId, Hash, Runtime>
{
type AccountId = AccountId;
type Balance = u128;
type Error = ();
Expand Down Expand Up @@ -133,9 +138,10 @@ impl<AccountId: AsRef<[u8; 32]> + Send, Hash> ChainBackend for Client<AccountId,
#[async_trait]
impl<
AccountId: Clone + Send + Sync + From<[u8; 32]> + AsRef<[u8; 32]>,
Hash: From<[u8; 32]>,
Hash: Copy + From<[u8; 32]>,
Runtime: RuntimeT,
E: Environment<AccountId = AccountId, Balance = u128, Hash = Hash> + 'static,
> ContractsBackend<E> for Client<AccountId, Hash>
> ContractsBackend<E> for Client<AccountId, Hash, Runtime>
{
type Error = ();
type EventLog = ();
Expand Down Expand Up @@ -221,10 +227,12 @@ impl<
}
};

let code_hash_raw: [u8; 32] = result.code_hash.as_ref().try_into().expect("");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty string in expect("")

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

must have missed it; fixed

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: (),
Expand Down Expand Up @@ -287,9 +295,10 @@ 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<AccountId = AccountId, Balance = u128, Hash = Hash> + 'static,
> E2EBackend<E> for Client<AccountId, Hash>
> E2EBackend<E> for Client<AccountId, Hash, Runtime>
{
}

Expand Down
7 changes: 5 additions & 2 deletions crates/e2e/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
17 changes: 17 additions & 0 deletions integration-tests/e2e-runtime-only-backend/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,22 @@ pub mod flipper {

Ok(())
}

/// Just instantiate a contract using non-default runtime.
#[ink_e2e::test(backend = "runtime-only", runtime = ink_e2e::MinimalRuntime)]
async fn custom_runtime<Client: E2EBackend>(mut client: Client) -> E2EResult<()> {
client
.instantiate(
"e2e-runtime-only-backend",
&ink_e2e::alice(),
FlipperRef::new(false),
0,
None,
)
.await
.expect("instantiate failed");

Ok(())
}
}
}