Skip to content
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
8 changes: 4 additions & 4 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ ink! contracts are compiled to RISC-V bytecode for
This is how ink! smart contracts are executed on a blockchain:
they are uploaded to a blockchain that runs PolkaVM, PolkaVM then
interprets them.
As contracts are executed in a sandbox execution environment on the
As contracts are executed in an isolated runtime environment on the
blockchain itself we compile them to a `no_std` environment.
More specifically they are executed by the [`pallet-revive`](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/revive),
a module of the Polkadot SDK blockchain framework. This module takes ink!
smart contracts and runs them in a PolkaVM sandbox environment.
smart contracts and runs them in a PolkaVM execution environment.
It also provides an API to smart contracts for anything a smart contract
needs: storing + retrieving data, calling other contracts, sending value,
fetching the block number, ….
Expand Down Expand Up @@ -250,9 +250,9 @@ most smart-contract-specific events: `Called`, `ContractCodeUpdated, CodeStored`
The `Instantiated` event was brought back in a later PR.

(5) `pallet-revive` included `revm` as a non-optional dependency. As ink! has to
depend on `pallet-revive` for some features (e.g. sandboxed E2E testing), this
depend on `pallet-revive` for some features (e.g. runtime-only E2E testing), this
results in over 75 more child dependencies having to be build now. This increased
build times for sandboxed E2E tests significantly.
build times for runtime-only E2E tests significantly.
[We proposed](https://github.com/paritytech/polkadot-sdk/pull/9689) putting anything
`revm` behind a feature flag, but Parity is not open to it.

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ tracing-subscriber = { version = "0.3.20" }
trybuild = { version = "1.0.110" }
which = { version = "8.0.0" }
xxhash-rust = { version = "0.8" }
const_env = { version = "0.1" }
const_env = { version = "0.1.4" }
const-hex = { version = "1.17.0", default-features = false }

# Substrate dependencies
Expand Down
8 changes: 4 additions & 4 deletions RELEASES_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,18 @@ in the future.
- Release `cargo-contract` crates.
- Request update of `drink` client, which depends on the `cargo-contract` crates.
- Release `ink_e2e`.
1. The `ink_sandbox` crate depends on a git commit of `polkadot-sdk`, hence it
1. The `ink_runtime` crate depends on a git commit of `polkadot-sdk`, hence it
currently cannot be published to crates.io.
1. Do a dry run:
```bash
fd Cargo.toml crates/ | \
grep -v e2e | \
grep -v sandbox | \
grep -v crates/runtime/ | \
xargs -n1 cargo no-dev-deps publish --allow-dirty --dry-run --manifest-path
```
This command ignores the `e2e` and `sandbox` folder: The `e2e` crates depend on the `cargo-contract/contract-build`
This command ignores the `e2e` and `crates/runtime` folders: The `e2e` crates depend on the `cargo-contract/contract-build`
crate, so if you want to publish those, you need to publish `cargo-contract/contract-build` first.
The `sandbox` is ignored, as it depends on some crates via their `git` ref.
The runtime emulator is ignored, as it depends on some crates via their `git` ref.
It uses [`no-dev-deps`](https://crates.io/crates/cargo-no-dev-deps)
for publishing, so that the `dev-dependencies` of ink! are ignored for publishing.
They are not needed and due to a cycle it's also not possible to publish with them.
Expand Down
187 changes: 159 additions & 28 deletions crates/e2e/macro/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use darling::{
FromMeta,
ast::NestedMeta,
};
use proc_macro2::TokenStream as TokenStream2;
use syn::{
Meta,
punctuated::Punctuated,
spanned::Spanned,
};

/// The type of the architecture that should be used to run test.
#[derive(Clone, Eq, PartialEq, Debug, darling::FromMeta)]
#[darling(rename_all = "snake_case")]
Expand Down Expand Up @@ -65,16 +76,16 @@ impl Node {
/// The runtime emulator that should be used within `TestExternalities`
#[derive(Clone, Eq, PartialEq, Debug, darling::FromMeta)]
pub struct RuntimeOnly {
/// The sandbox runtime type (e.g., `ink_runtime::DefaultRuntime`)
pub sandbox: syn::Path,
/// The runtime type (e.g., `ink_runtime::DefaultRuntime`)
pub runtime: syn::Path,
/// The client type implementing the backend traits (e.g.,
/// `ink_runtime::RuntimeClient`)
pub client: syn::Path,
}

impl RuntimeOnly {
pub fn runtime_path(&self) -> syn::Path {
self.sandbox.clone()
self.runtime.clone()
}
pub fn client_path(&self) -> syn::Path {
self.client.clone()
Expand Down Expand Up @@ -129,25 +140,123 @@ impl E2EConfig {
pub fn replace_test_attr(&self) -> Option<String> {
self.replace_test_attr.clone()
}

/// Parses the attribute arguments passed to `ink_e2e::test`.
pub fn from_attr_tokens(attr: TokenStream2) -> Result<Self, syn::Error> {
let nested_meta = NestedMeta::parse_meta_list(attr)?;
Self::from_nested_meta(nested_meta)
}

/// Builds the configuration from already parsed meta items.
pub fn from_nested_meta(nested_meta: Vec<NestedMeta>) -> Result<Self, syn::Error> {
let normalized = normalize_runtime_meta(nested_meta)?;
Self::from_list(&normalized).map_err(syn::Error::from)
}
}

fn normalize_runtime_meta(
nested_meta: Vec<NestedMeta>,
) -> Result<Vec<NestedMeta>, syn::Error> {
let mut args = Vec::with_capacity(nested_meta.len());
let mut runtime = None;

for meta in nested_meta {
if let Some(found) = RuntimeBackendArg::from_nested_meta(&meta)? {
if runtime.replace(found).is_some() {
return Err(syn::Error::new(
meta.span(),
"only a single `runtime` attribute is allowed",
));
}
continue;
}
args.push(meta);
}

if let Some(runtime) = runtime {
args.push(runtime.into_backend_meta());
}

Ok(args)
}

struct RuntimeBackendArg {
runtime: Option<syn::Path>,
}

impl RuntimeBackendArg {
fn from_nested_meta(meta: &NestedMeta) -> Result<Option<Self>, syn::Error> {
let meta = match meta {
NestedMeta::Meta(meta) if meta.path().is_ident("runtime") => meta,
_ => return Ok(None),
};

match meta {
Meta::Path(_) => Ok(Some(Self { runtime: None })),
Meta::List(list) => {
let nested: Punctuated<NestedMeta, syn::Token![,]> =
list.parse_args_with(Punctuated::parse_terminated)?;
if nested.len() != 1 {
return Err(syn::Error::new(
list.span(),
"`runtime` expects zero or one runtime type",
));
}
match nested.first().unwrap() {
NestedMeta::Meta(Meta::Path(path)) => {
Ok(Some(Self {
runtime: Some(path.clone()),
}))
}
other => {
Err(syn::Error::new(
other.span(),
"`runtime` expects a runtime type path",
))
}
}
}
Meta::NameValue(name_value) => {
Err(syn::Error::new(
name_value.span(),
"`runtime` does not support name-value pairs",
))
}
}
}

fn runtime(&self) -> syn::Path {
self.runtime
.clone()
.unwrap_or_else(|| syn::parse_quote! { ::ink_runtime::DefaultRuntime })
}

fn into_backend_meta(self) -> NestedMeta {
let runtime = self.runtime();
syn::parse_quote! {
backend(runtime_only(runtime = #runtime, client = ::ink_runtime::RuntimeClient))
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use darling::{
FromMeta,
ast::NestedMeta,
};
use darling::ast::NestedMeta;
use quote::quote;

fn parse_config(input: TokenStream2) -> E2EConfig {
let nested = NestedMeta::parse_meta_list(input).unwrap();
E2EConfig::from_nested_meta(nested).unwrap()
}

#[test]
fn config_works_backend_runtime_only() {
let input = quote! {
environment = crate::CustomEnvironment,
backend(runtime_only(sandbox = ::ink_runtime::DefaultRuntime, client = ::ink_runtime::RuntimeClient)),
backend(runtime_only(runtime = ::ink_runtime::DefaultRuntime, client = ::ink_runtime::RuntimeClient)),
};
let config =
E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
let config = parse_config(input);

assert_eq!(
config.environment(),
Expand All @@ -157,25 +266,24 @@ mod tests {
assert_eq!(
config.backend(),
Backend::RuntimeOnly(RuntimeOnly {
sandbox: syn::parse_quote! { ::ink_runtime::DefaultRuntime },
runtime: syn::parse_quote! { ::ink_runtime::DefaultRuntime },
client: syn::parse_quote! { ::ink_runtime::RuntimeClient },
})
);
}

#[test]
#[should_panic(expected = "ErrorUnknownField")]
#[should_panic(expected = "Unknown field")]
fn config_backend_runtime_only_default_not_allowed() {
let input = quote! {
backend(runtime_only(default)),
};
let config =
E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
let config = parse_config(input);

assert_eq!(
config.backend(),
Backend::RuntimeOnly(RuntimeOnly {
sandbox: syn::parse_quote! { ::ink_runtime::DefaultRuntime },
runtime: syn::parse_quote! { ::ink_runtime::DefaultRuntime },
client: syn::parse_quote! { ::ink_runtime::RuntimeClient },
})
);
Expand All @@ -184,15 +292,42 @@ mod tests {
#[test]
fn config_works_runtime_only_with_custom_backend() {
let input = quote! {
backend(runtime_only(sandbox = ::ink_runtime::DefaultRuntime, client = ::ink_runtime::RuntimeClient)),
backend(runtime_only(runtime = ::ink_runtime::DefaultRuntime, client = ::ink_runtime::RuntimeClient)),
};
let config =
E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
let config = parse_config(input);

assert_eq!(
config.backend(),
Backend::RuntimeOnly(RuntimeOnly {
runtime: syn::parse_quote! { ::ink_runtime::DefaultRuntime },
client: syn::parse_quote! { ::ink_runtime::RuntimeClient },
})
);
}

#[test]
fn runtime_keyword_defaults() {
let input = quote! { runtime };
let config = parse_config(input);

assert_eq!(
config.backend(),
Backend::RuntimeOnly(RuntimeOnly {
runtime: syn::parse_quote! { ::ink_runtime::DefaultRuntime },
client: syn::parse_quote! { ::ink_runtime::RuntimeClient },
})
);
}

#[test]
fn runtime_keyword_custom_runtime() {
let input = quote! { runtime(crate::CustomRuntime) };
let config = parse_config(input);

assert_eq!(
config.backend(),
Backend::RuntimeOnly(RuntimeOnly {
sandbox: syn::parse_quote! { ::ink_runtime::DefaultRuntime },
runtime: syn::parse_quote! { crate::CustomRuntime },
client: syn::parse_quote! { ::ink_runtime::RuntimeClient },
})
);
Expand All @@ -203,8 +338,7 @@ mod tests {
let input = quote! {
backend(node),
};
let config =
E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
let config = parse_config(input);

assert_eq!(config.backend(), Backend::Node(Node::Auto));

Expand All @@ -231,13 +365,12 @@ mod tests {
}

#[test]
#[should_panic(expected = "ErrorUnknownField")]
#[should_panic(expected = "Unknown field")]
fn config_backend_node_auto_not_allowed() {
let input = quote! {
backend(node(auto)),
};
let config =
E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
let config = parse_config(input);

assert_eq!(config.backend(), Backend::Node(Node::Auto));
}
Expand All @@ -247,8 +380,7 @@ mod tests {
let input = quote! {
backend(node(url = "ws://0.0.0.0:9999")),
};
let config =
E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
let config = parse_config(input);

match config.backend() {
Backend::Node(node_config) => {
Expand Down Expand Up @@ -277,8 +409,7 @@ mod tests {
let input = quote! {
replace_test_attr = "#[quickcheck]"
};
let config =
E2EConfig::from_list(&NestedMeta::parse_meta_list(input).unwrap()).unwrap();
let config = parse_config(input);

assert_eq!(config.replace_test_attr(), Some("#[quickcheck]".to_owned()));
}
Expand Down
6 changes: 1 addition & 5 deletions crates/e2e/macro/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@
// limitations under the License.

use crate::config::E2EConfig;
use darling::{
FromMeta,
ast::NestedMeta,
};
use proc_macro2::TokenStream as TokenStream2;

/// The End-to-End test with all required information.
Expand All @@ -38,7 +34,7 @@ impl InkE2ETest {
/// Returns `Ok` if the test matches all requirements for an
/// ink! E2E test definition.
pub fn new(attrs: TokenStream2, input: TokenStream2) -> Result<Self, syn::Error> {
let e2e_config = E2EConfig::from_list(&NestedMeta::parse_meta_list(attrs)?)?;
let e2e_config = E2EConfig::from_attr_tokens(attrs)?;
let item_fn = syn::parse2::<syn::ItemFn>(input)?;
let e2e_fn = E2EFn::from(item_fn);
Ok(Self {
Expand Down
Loading
Loading