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 all 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.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