use crate::{
ast,
error::ExtError,
ir,
ir::idents_lint,
};
use core::slice::Iter as SliceIter;
use proc_macro2::TokenStream as TokenStream2;
use std::collections::HashMap;
use syn::{
spanned::Spanned as _,
Result,
};
#[derive(Debug, PartialEq, Eq)]
pub struct ChainExtension {
item: syn::ItemTrait,
config: Config,
error_code: syn::TraitItemType,
methods: Vec<ChainExtensionMethod>,
}
impl ChainExtension {
pub fn attrs(&self) -> Vec<syn::Attribute> {
let (_, attrs) = ir::partition_attributes(self.item.attrs.iter().cloned())
.unwrap_or_else(|err| panic!("encountered unexpected invalid attributes for ink! chain extension: {err}"));
attrs
}
pub fn span(&self) -> proc_macro2::Span {
self.item.span()
}
pub fn ident(&self) -> &proc_macro2::Ident {
&self.item.ident
}
pub fn iter_methods(&self) -> SliceIter<ChainExtensionMethod> {
self.methods.iter()
}
pub fn error_code(&self) -> &syn::Type {
self.error_code
.default
.as_ref()
.map(|(_token, ty)| ty)
.expect("unexpected missing default type for error code")
}
}
#[derive(Default, Debug, PartialEq, Eq)]
pub struct Config {
ext_id: ExtensionId,
}
impl TryFrom<ast::AttributeArgs> for Config {
type Error = syn::Error;
fn try_from(args: ast::AttributeArgs) -> Result<Self> {
let mut ext_id: Option<ExtensionId> = None;
for arg in args.clone().into_iter() {
if arg.name().is_ident("extension") {
if ext_id.is_some() {
return Err(format_err_spanned_value!(
arg,
"encountered duplicate ink! contract `extension` configuration argument",
));
}
if let Some(lit_int) = arg.value().and_then(ast::MetaValue::as_lit_int) {
let id = lit_int.base10_parse::<u16>()
.map_err(|error| {
format_err_spanned!(
lit_int,
"could not parse `N` in `extension = N` into a `u16` integer: {}", error)
})?;
ext_id = Some(ExtensionId::from_u16(id));
} else {
return Err(format_err_spanned_value!(
arg,
"expected `u16` integer type for `N` in `extension = N`",
));
}
} else {
return Err(format_err_spanned!(
arg,
"encountered unknown or unsupported chain extension configuration argument",
));
}
}
if let Some(ext_id) = ext_id {
Ok(Config { ext_id })
} else {
Err(format_err_spanned!(
args,
"missing required `extension = N: u16` argument on ink! chain extension",
))
}
}
}
impl Config {
pub fn ext_id(&self) -> ExtensionId {
self.ext_id
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct ChainExtensionMethod {
item: syn::TraitItemFn,
id: GlobalMethodId,
handle_status: bool,
}
impl ChainExtensionMethod {
pub fn attrs(&self) -> Vec<syn::Attribute> {
let (_, attrs) = ir::partition_attributes(self.item.attrs.iter().cloned())
.expect(
"encountered unexpected invalid attributes for ink! chain extension method",
);
attrs
}
pub fn span(&self) -> proc_macro2::Span {
self.item.span()
}
pub fn ident(&self) -> &proc_macro2::Ident {
&self.item.sig.ident
}
pub fn sig(&self) -> &syn::Signature {
&self.item.sig
}
pub fn id(&self) -> GlobalMethodId {
self.id
}
pub fn inputs(&self) -> ChainExtensionMethodInputs {
ChainExtensionMethodInputs {
iter: self.item.sig.inputs.iter(),
}
}
pub fn handle_status(&self) -> bool {
self.handle_status
}
}
pub struct ChainExtensionMethodInputs<'a> {
iter: syn::punctuated::Iter<'a, syn::FnArg>,
}
impl<'a> Iterator for ChainExtensionMethodInputs<'a> {
type Item = &'a syn::PatType;
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
fn next(&mut self) -> Option<Self::Item> {
let item = self.iter.next()?;
match item {
syn::FnArg::Receiver(receiver) => {
panic!("encountered unexpected receiver in chain extension method input: {receiver:?}")
}
syn::FnArg::Typed(pat_type) => Some(pat_type),
}
}
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ExtensionId {
index: u16,
}
impl ExtensionId {
pub fn from_u16(index: u16) -> Self {
Self { index }
}
pub fn into_u16(self) -> u16 {
self.index
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FunctionId {
index: u16,
}
impl FunctionId {
pub fn from_u16(index: u16) -> Self {
Self { index }
}
pub fn into_u16(self) -> u16 {
self.index
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GlobalMethodId {
index: u32,
}
impl GlobalMethodId {
pub fn new(ext_id: ExtensionId, func_id: FunctionId) -> Self {
Self {
index: ((ext_id.index as u32) << 16) | func_id.index as u32,
}
}
pub fn func_id(&self) -> FunctionId {
FunctionId::from_u16((self.index & 0x0000FFFF) as u16)
}
pub fn ext_id(&self) -> ExtensionId {
ExtensionId::from_u16((self.index >> 16) as u16)
}
pub fn into_u32(self) -> u32 {
self.index
}
}
impl ChainExtension {
pub fn try_from(
item_trait: syn::ItemTrait,
config: Config,
) -> core::result::Result<Self, syn::Error> {
idents_lint::ensure_no_ink_identifiers(&item_trait)?;
Self::analyse_properties(&item_trait)?;
let (error_code, methods) = Self::analyse_items(config.ext_id, &item_trait)?;
Ok(Self {
item: item_trait,
config,
error_code,
methods,
})
}
}
impl ChainExtension {
pub fn new(attr: TokenStream2, input: TokenStream2) -> Result<Self> {
let args = syn::parse2::<ast::AttributeArgs>(attr)?;
let config = Config::try_from(args)?;
let item_trait = syn::parse2::<syn::ItemTrait>(input)?;
ChainExtension::try_from(item_trait, config)
}
fn analyse_properties(item_trait: &syn::ItemTrait) -> Result<()> {
if let Some(unsafety) = &item_trait.unsafety {
return Err(format_err_spanned!(
unsafety,
"ink! chain extensions cannot be unsafe"
))
}
if let Some(auto) = &item_trait.auto_token {
return Err(format_err_spanned!(
auto,
"ink! chain extensions cannot be automatically implemented traits"
))
}
if !item_trait.generics.params.is_empty() {
return Err(format_err_spanned!(
item_trait.generics.params,
"ink! chain extensions must not be generic"
))
}
if !matches!(item_trait.vis, syn::Visibility::Public(_)) {
return Err(format_err_spanned!(
item_trait.vis,
"ink! chain extensions must have public visibility"
))
}
if !item_trait.supertraits.is_empty() {
return Err(format_err_spanned!(
item_trait.supertraits,
"ink! chain extensions with super-traits are not supported, yet"
))
}
Ok(())
}
fn analyse_error_code(
item_type: &syn::TraitItemType,
previous: &mut Option<syn::TraitItemType>,
) -> Result<()> {
if item_type.ident != "ErrorCode" {
return Err(format_err_spanned!(
item_type.ident,
"chain extensions expect an associated type with name `ErrorCode` but found {}",
item_type.ident,
));
}
if !item_type.generics.params.is_empty() {
return Err(format_err_spanned!(
item_type.generics,
"generic chain extension `ErrorCode` types are not supported",
))
}
if !item_type.bounds.is_empty() {
return Err(format_err_spanned!(
item_type.bounds,
"bounded chain extension `ErrorCode` types are not supported",
))
}
if item_type.default.is_none() {
return Err(format_err_spanned!(
item_type,
"expected a default type for the ink! chain extension ErrorCode",
))
}
match previous {
Some(previous_error_code) => {
return Err(format_err_spanned!(
item_type,
"encountered duplicate `ErrorCode` associated types for the chain extension",
)).map_err(|err| err.into_combine(format_err_spanned!(
previous_error_code,
"first `ErrorCode` associated type here",
)))
}
None => {
*previous = Some(item_type.clone());
}
}
Ok(())
}
fn analyse_items(
ext_id: ExtensionId,
item_trait: &syn::ItemTrait,
) -> Result<(syn::TraitItemType, Vec<ChainExtensionMethod>)> {
let mut methods = Vec::new();
let mut seen_ids = HashMap::new();
let mut error_code = None;
for trait_item in &item_trait.items {
match trait_item {
syn::TraitItem::Const(const_trait_item) => {
return Err(format_err_spanned!(
const_trait_item,
"associated constants in ink! chain extensions are not supported, yet"
))
}
syn::TraitItem::Macro(macro_trait_item) => {
return Err(format_err_spanned!(
macro_trait_item,
"macros in ink! chain extensions are not supported"
))
}
syn::TraitItem::Type(type_trait_item) => {
Self::analyse_error_code(type_trait_item, &mut error_code)?;
}
syn::TraitItem::Verbatim(verbatim) => {
return Err(format_err_spanned!(
verbatim,
"encountered unsupported item in ink! chain extensions"
))
}
syn::TraitItem::Fn(fn_trait_item) => {
let method = Self::analyse_methods(ext_id, fn_trait_item)?;
let method_id = method.id();
if let Some(previous) = seen_ids.get(&method_id) {
return Err(format_err!(
method.span(),
"encountered duplicate extension identifiers for the same chain extension",
).into_combine(format_err!(
*previous,
"previous duplicate extension identifier here",
)))
}
seen_ids.insert(method_id, method.span());
methods.push(method);
}
unknown => {
return Err(format_err_spanned!(
unknown,
"encountered unknown or unsupported item in ink! chain extensions"
))
}
}
}
let error_code = match error_code {
Some(error_code) => error_code,
None => {
return Err(format_err_spanned!(
item_trait,
"missing ErrorCode associated type from ink! chain extension",
))
}
};
Ok((error_code, methods))
}
fn analyse_methods(
ext_id: ExtensionId,
method: &syn::TraitItemFn,
) -> Result<ChainExtensionMethod> {
if let Some(default_impl) = &method.default {
return Err(format_err_spanned!(
default_impl,
"ink! chain extension methods with default implementations are not supported"
));
}
if let Some(constness) = &method.sig.constness {
return Err(format_err_spanned!(
constness,
"const ink! chain extension methods are not supported"
))
}
if let Some(asyncness) = &method.sig.asyncness {
return Err(format_err_spanned!(
asyncness,
"async ink! chain extension methods are not supported"
))
}
if let Some(unsafety) = &method.sig.unsafety {
return Err(format_err_spanned!(
unsafety,
"unsafe ink! chain extension methods are not supported"
))
}
if let Some(abi) = &method.sig.abi {
return Err(format_err_spanned!(
abi,
"ink! chain extension methods with non default ABI are not supported"
))
}
if let Some(variadic) = &method.sig.variadic {
return Err(format_err_spanned!(
variadic,
"variadic ink! chain extension methods are not supported"
))
}
if !method.sig.generics.params.is_empty() {
return Err(format_err_spanned!(
method.sig.generics.params,
"generic ink! chain extension methods are not supported"
))
}
match ir::first_ink_attribute(&method.attrs)?
.map(|attr| attr.first().kind().clone()) {
Some(ir::AttributeArg::Function(func_id)) => {
Self::analyse_chain_extension_method(method, ext_id, func_id)
}
Some(_unsupported) => {
Err(format_err_spanned!(
method,
"encountered unsupported ink! attribute for ink! chain extension method. expected #[ink(function = N: u16)] attribute"
))
}
None => {
Err(format_err_spanned!(
method,
"missing #[ink(function = N: u16)] flag on ink! chain extension method"
))
}
}
}
fn analyse_chain_extension_method(
item_method: &syn::TraitItemFn,
ext_id: ExtensionId,
func_id: FunctionId,
) -> Result<ChainExtensionMethod> {
let (ink_attrs, _) = ir::sanitize_attributes(
item_method.span(),
item_method.attrs.clone(),
&ir::AttributeArgKind::Function,
|arg| {
match arg.kind() {
ir::AttributeArg::Function(_) | ir::AttributeArg::HandleStatus(_) => {
Ok(())
}
_ => Err(None),
}
},
)?;
if let Some(receiver) = item_method.sig.receiver() {
return Err(format_err_spanned!(
receiver,
"ink! chain extension method must not have a `self` receiver",
))
}
let result = ChainExtensionMethod {
id: GlobalMethodId::new(ext_id, func_id),
item: item_method.clone(),
handle_status: ink_attrs.is_handle_status(),
};
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! assert_ink_chain_extension_eq_err {
( error: $err_str:literal, $($chain_extension:tt)* ) => {
assert_eq!(
ChainExtension::try_from(
syn::parse_quote! {
$( $chain_extension )*
},
Config::default()
)
.map_err(|err| err.to_string()),
Err(
$err_str.to_string()
)
)
};
}
#[test]
fn unsafe_chain_extension_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "ink! chain extensions cannot be unsafe",
pub unsafe trait MyChainExtension {}
);
}
#[test]
fn auto_chain_extension_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "ink! chain extensions cannot be automatically implemented traits",
pub auto trait MyChainExtension {}
);
}
#[test]
fn non_pub_chain_extension_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "ink! chain extensions must have public visibility",
trait MyChainExtension {}
);
assert_ink_chain_extension_eq_err!(
error: "ink! chain extensions must have public visibility",
pub(crate) trait MyChainExtension {}
);
}
#[test]
fn generic_chain_extension_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "ink! chain extensions must not be generic",
pub trait MyChainExtension<T> {}
);
}
#[test]
fn chain_extension_with_supertraits_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "ink! chain extensions with super-traits are not supported, yet",
pub trait MyChainExtension: SuperChainExtension {}
);
}
#[test]
fn chain_extension_containing_const_item_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "associated constants in ink! chain extensions are not supported, yet",
pub trait MyChainExtension {
const T: i32;
}
);
}
#[test]
fn chain_extension_containing_invalid_associated_type_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "chain extensions expect an associated type with name `ErrorCode` but found Type",
pub trait MyChainExtension {
type Type;
}
);
}
#[test]
fn chain_extension_with_invalid_error_code() {
assert_ink_chain_extension_eq_err!(
error: "chain extensions expect an associated type with name `ErrorCode` but found IncorrectName",
pub trait MyChainExtension {
type IncorrectName = ();
}
);
assert_ink_chain_extension_eq_err!(
error: "generic chain extension `ErrorCode` types are not supported",
pub trait MyChainExtension {
type ErrorCode<T> = ();
}
);
assert_ink_chain_extension_eq_err!(
error: "bounded chain extension `ErrorCode` types are not supported",
pub trait MyChainExtension {
type ErrorCode: Copy = ();
}
);
assert_ink_chain_extension_eq_err!(
error: "expected a default type for the ink! chain extension ErrorCode",
pub trait MyChainExtension {
type ErrorCode;
}
);
assert_ink_chain_extension_eq_err!(
error: "encountered duplicate `ErrorCode` associated types for the chain extension",
pub trait MyChainExtension {
type ErrorCode = ();
type ErrorCode = ();
}
);
}
#[test]
fn chain_extension_containing_macro_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "macros in ink! chain extensions are not supported",
pub trait MyChainExtension {
my_macro_call!();
}
);
}
#[test]
fn chain_extension_containing_non_flagged_method_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "missing #[ink(function = N: u16)] flag on ink! chain extension method",
pub trait MyChainExtension {
fn non_flagged_1(&self);
}
);
assert_ink_chain_extension_eq_err!(
error: "missing #[ink(function = N: u16)] flag on ink! chain extension method",
pub trait MyChainExtension {
fn non_flagged_2(&mut self);
}
);
assert_ink_chain_extension_eq_err!(
error: "missing #[ink(function = N: u16)] flag on ink! chain extension method",
pub trait MyChainExtension {
fn non_flagged_3() -> Self;
}
);
}
#[test]
fn chain_extension_containing_default_implemented_methods_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "ink! chain extension methods with default implementations are not supported",
pub trait MyChainExtension {
#[ink(constructor)]
fn default_implemented() -> Self {}
}
);
}
#[test]
fn chain_extension_containing_const_methods_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "const ink! chain extension methods are not supported",
pub trait MyChainExtension {
#[ink(function = 1)]
const fn const_constructor() -> Self;
}
);
}
#[test]
fn chain_extension_containing_async_methods_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "async ink! chain extension methods are not supported",
pub trait MyChainExtension {
#[ink(function = 1)]
async fn const_constructor() -> Self;
}
);
}
#[test]
fn chain_extension_containing_unsafe_methods_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "unsafe ink! chain extension methods are not supported",
pub trait MyChainExtension {
#[ink(function = 1)]
unsafe fn const_constructor() -> Self;
}
);
}
#[test]
fn chain_extension_containing_methods_using_explicit_abi_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "ink! chain extension methods with non default ABI are not supported",
pub trait MyChainExtension {
#[ink(function = 1)]
extern fn const_constructor() -> Self;
}
);
}
#[test]
fn chain_extension_containing_variadic_methods_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "variadic ink! chain extension methods are not supported",
pub trait MyChainExtension {
#[ink(function = 1)]
fn const_constructor(...) -> Self;
}
);
}
#[test]
fn chain_extension_containing_generic_methods_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "generic ink! chain extension methods are not supported",
pub trait MyChainExtension {
#[ink(function = 1)]
fn const_constructor<T>() -> Self;
}
);
}
#[test]
fn chain_extension_containing_method_with_unsupported_ink_attribute_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "\
encountered unsupported ink! attribute for ink! chain extension method. \
expected #[ink(function = N: u16)] attribute",
pub trait MyChainExtension {
#[ink(message)]
fn unsupported_ink_attribute(&self);
}
);
assert_ink_chain_extension_eq_err!(
error: "encountered unknown ink! attribute argument: unknown",
pub trait MyChainExtension {
#[ink(unknown)]
fn unknown_ink_attribute(&self);
}
);
}
#[test]
fn chain_extension_containing_method_with_invalid_marker() {
assert_ink_chain_extension_eq_err!(
error: "could not parse `N` in `#[ink(function = N)]` into a `u16` integer: \
invalid digit found in string",
pub trait MyChainExtension {
#[ink(function = -1)]
fn has_self_receiver();
}
);
let too_large = (u16::MAX as u64) + 1;
assert_ink_chain_extension_eq_err!(
error: "could not parse `N` in `#[ink(function = N)]` into a `u16` integer: \
number too large to fit in target type",
pub trait MyChainExtension {
#[ink(function = #too_large)]
fn has_self_receiver();
}
);
assert_ink_chain_extension_eq_err!(
error: "expected `u16` integer type for `N` in #[ink(function = N)]",
pub trait MyChainExtension {
#[ink(function = "Hello, World!")]
fn has_self_receiver();
}
);
assert_ink_chain_extension_eq_err!(
error: "encountered #[ink(function)] that is missing its `id` parameter. \
Did you mean #[ink(function = id: u16)] ?",
pub trait MyChainExtension {
#[ink(function)]
fn has_self_receiver();
}
);
assert_ink_chain_extension_eq_err!(
error: "encountered duplicate ink! attribute",
pub trait MyChainExtension {
#[ink(function = 42)]
#[ink(function = 42)]
fn duplicate_attributes() -> Self;
}
);
assert_ink_chain_extension_eq_err!(
error: "encountered ink! attribute arguments with equal kinds",
pub trait MyChainExtension {
#[ink(function = 1)]
#[ink(function = 2)]
fn duplicate_attributes() -> Self;
}
);
assert_ink_chain_extension_eq_err!(
error: "encountered conflicting ink! attribute argument",
pub trait MyChainExtension {
#[ink(function = 1)]
#[ink(message)]
fn conflicting_attributes() -> Self;
}
);
}
#[test]
fn chain_extension_containing_method_with_self_receiver_is_denied() {
assert_ink_chain_extension_eq_err!(
error: "ink! chain extension method must not have a `self` receiver",
pub trait MyChainExtension {
type ErrorCode = ();
#[ink(function = 1)]
fn has_self_receiver(&self) -> Self;
}
);
assert_ink_chain_extension_eq_err!(
error: "ink! chain extension method must not have a `self` receiver",
pub trait MyChainExtension {
type ErrorCode = ();
#[ink(function = 1)]
fn has_self_receiver(&mut self) -> Self;
}
);
assert_ink_chain_extension_eq_err!(
error: "ink! chain extension method must not have a `self` receiver",
pub trait MyChainExtension {
type ErrorCode = ();
#[ink(function = 1)]
fn has_self_receiver(self) -> Self;
}
);
assert_ink_chain_extension_eq_err!(
error: "ink! chain extension method must not have a `self` receiver",
pub trait MyChainExtension {
type ErrorCode = ();
#[ink(function = 1)]
fn has_self_receiver(self: &Self) -> Self;
}
);
assert_ink_chain_extension_eq_err!(
error: "ink! chain extension method must not have a `self` receiver",
pub trait MyChainExtension {
type ErrorCode = ();
#[ink(function = 1)]
fn has_self_receiver(self: Self) -> Self;
}
);
}
#[test]
fn chain_extension_with_overlapping_extension_ids() {
assert_ink_chain_extension_eq_err!(
error: "encountered duplicate extension identifiers for the same chain extension",
pub trait MyChainExtension {
#[ink(function = 1)]
fn same_id_1();
#[ink(function = 1)]
fn same_id_2();
}
);
}
#[test]
fn chain_extension_is_ok() {
let chain_extension = ChainExtension::try_from(syn::parse_quote! {
pub trait MyChainExtension {
type ErrorCode = ();
#[ink(function = 1)]
fn extension_1();
#[ink(function = 2)]
fn extension_2(input: i32);
#[ink(function = 3)]
fn extension_3() -> i32;
#[ink(function = 4)]
fn extension_4(input: i32) -> i32;
#[ink(function = 5)]
fn extension_5(in1: i8, in2: i16, in3: i32, in4: i64) -> (u8, u16, u32, u64);
}
}, Config::default()).unwrap();
assert_eq!(chain_extension.methods.len(), 5);
for (actual, expected) in chain_extension
.methods
.iter()
.map(|method| method.id())
.zip(1..=5u32)
{
assert_eq!(actual.index, expected);
}
for (actual, expected) in chain_extension
.methods
.iter()
.map(|method| method.ident().to_string())
.zip(
[
"extension_1",
"extension_2",
"extension_3",
"extension_4",
"extension_5",
]
.iter()
.map(ToString::to_string),
)
{
assert_eq!(actual, expected);
}
}
#[test]
fn chain_extension_with_params_is_ok() {
let chain_extension = ChainExtension::try_from(
syn::parse_quote! {
pub trait MyChainExtension {
type ErrorCode = ();
#[ink(function = 1, handle_status = false)]
fn extension_a();
#[ink(function = 2)]
fn extension_b();
#[ink(function = 3, handle_status = false)]
fn extension_c();
#[ink(function = 4)]
#[ink(handle_status = false)]
fn extension_d();
#[ink(function = 5)]
fn extension_e();
#[ink(function = 6)]
#[ink(handle_status = false)]
fn extension_f();
}
},
Config::default(),
)
.unwrap();
let expected_methods = 6;
assert_eq!(chain_extension.methods.len(), expected_methods);
for (actual, expected) in chain_extension
.methods
.iter()
.map(|method| method.id())
.zip(1..=expected_methods as u32)
{
assert_eq!(actual.index, expected);
}
for (actual, expected) in chain_extension
.methods
.iter()
.map(|method| method.ident().to_string())
.zip(
[
"extension_a",
"extension_b",
"extension_c",
"extension_d",
"extension_e",
"extension_f",
]
.iter()
.map(ToString::to_string),
)
{
assert_eq!(actual, expected);
}
}
fn assert_config(
input: ast::AttributeArgs,
expected: core::result::Result<Config, &'static str>,
) {
assert_eq!(
<Config as TryFrom<ast::AttributeArgs>>::try_from(input)
.map_err(|err| err.to_string()),
expected.map_err(ToString::to_string),
);
}
#[test]
fn empty_config_fails() {
assert_config(
syn::parse_quote! {},
Err("missing required `extension = N: u16` argument on ink! chain extension"),
)
}
#[test]
fn extension_works() {
assert_config(
syn::parse_quote! {
extension = 13
},
Ok(Config {
ext_id: ExtensionId::from_u16(13),
}),
)
}
#[test]
fn extension_invalid_value_fails() {
assert_config(
syn::parse_quote! { extension = "invalid" },
Err("expected `u16` integer type for `N` in `extension = N`"),
);
}
#[test]
fn unknown_arg_fails() {
assert_config(
syn::parse_quote! { unknown = argument },
Err("encountered unknown or unsupported chain extension configuration argument"),
);
}
#[test]
fn duplicate_args_fails() {
assert_config(
syn::parse_quote! {
extension = 13,
extension = 123,
},
Err("encountered duplicate ink! contract `extension` configuration argument"),
);
}
}