Skip to content

Commit

Permalink
Drink backend: allow for arbitrary runtime (#1892)
Browse files Browse the repository at this point in the history
* Configuration

* Change client implementation

* Update codegen

* Verify in test

* Fmt, ui

* Explicit syn::Path

* No need for drink dependency

* Fix drink version

* Fix codec version

* revert: Fix codec version

* Expect with reason

* Lol

* wtf

* Update drink

* Trigger CI

* fmt
  • Loading branch information
pmikolajczyk41 authored Sep 8, 2023
1 parent d540cdd commit 5657a22
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 35 deletions.
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.3" }
drink = { version = "=0.1.6" }
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
77 changes: 51 additions & 26 deletions crates/e2e/src/drink_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@ use drink::{
RuntimeCall,
},
contract_api::ContractApi,
runtime::{
MinimalRuntime,
Runtime,
},
runtime::Runtime as RuntimeT,
Sandbox,
DEFAULT_GAS_LIMIT,
};
Expand All @@ -43,7 +40,6 @@ use scale::{
Encode,
};
use sp_core::{
crypto::AccountId32,
sr25519::Pair,
Pair as _,
};
Expand All @@ -57,15 +53,26 @@ use subxt::{
};
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>
{
}

type RuntimeAccountId<R> = <R as drink::runtime::frame_system::Config>::AccountId;

impl<AccountId, Hash> Client<AccountId, Hash> {
impl<AccountId, Hash, Runtime: RuntimeT> Client<AccountId, Hash, Runtime>
where
RuntimeAccountId<Runtime>: From<[u8; 32]>,
{
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 @@ -77,7 +84,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 @@ -91,15 +98,19 @@ impl<AccountId, Hash> Client<AccountId, Hash> {
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);
}
}
}

#[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>
where
RuntimeAccountId<Runtime>: From<[u8; 32]>,
{
type AccountId = AccountId;
type Balance = u128;
type Error = ();
Expand All @@ -121,7 +132,7 @@ impl<AccountId: AsRef<[u8; 32]> + Send, Hash> ChainBackend for Client<AccountId,
&mut self,
account: Self::AccountId,
) -> Result<Self::Balance, Self::Error> {
let account = AccountId32::new(*account.as_ref());
let account = RuntimeAccountId::<Runtime>::from(*account.as_ref());
Ok(self.sandbox.balance(&account))
}

Expand All @@ -139,7 +150,7 @@ impl<AccountId: AsRef<[u8; 32]> + Send, Hash> ChainBackend for Client<AccountId,
// Get metadata of the drink! runtime, so that we can encode the call object.
// Panic on error - metadata of the static im-memory runtime should always be
// available.
let raw_metadata: Vec<u8> = MinimalRuntime::metadata().into();
let raw_metadata: Vec<u8> = Runtime::get_metadata().into();
let metadata = subxt_metadata::Metadata::decode(&mut raw_metadata.as_slice())
.expect("Failed to decode metadata");

Expand All @@ -150,13 +161,15 @@ impl<AccountId: AsRef<[u8; 32]> + Send, Hash> ChainBackend for Client<AccountId,
// Decode the call object.
// Panic on error - we just encoded a validated call object, so it should be
// decodable.
let decoded_call =
RuntimeCall::<MinimalRuntime>::decode(&mut encoded_call.as_slice())
.expect("Failed to decode runtime call");
let decoded_call = RuntimeCall::<Runtime>::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(())
Expand All @@ -166,9 +179,12 @@ 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>
where
RuntimeAccountId<Runtime>: From<[u8; 32]> + AsRef<[u8; 32]>,
{
type Error = ();
type EventLog = ();
Expand Down Expand Up @@ -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: (),
Expand Down Expand Up @@ -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<AccountId = AccountId, Balance = u128, Hash = Hash> + 'static,
> E2EBackend<E> for Client<AccountId, Hash>
> E2EBackend<E> for Client<AccountId, Hash, Runtime>
where
RuntimeAccountId<Runtime>: From<[u8; 32]> + AsRef<[u8; 32]>,
{
}

fn keypair_to_account(keypair: &Keypair) -> AccountId32 {
AccountId32::from(keypair.public_key().0)
fn keypair_to_account<AccountId: From<[u8; 32]>>(keypair: &Keypair) -> AccountId {
AccountId::from(keypair.public_key().0)
}
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
2 changes: 1 addition & 1 deletion crates/ink/macro/src/scale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub fn derive(attr: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream
))
}
}),
attr.clone(),
attr,
)?;

let codec_crate =
Expand Down
Loading

0 comments on commit 5657a22

Please sign in to comment.