1use crate::{
16 ast,
17 error::ExtError,
18 ir,
19 ir::idents_lint,
20};
21use core::slice::Iter as SliceIter;
22use proc_macro2::TokenStream as TokenStream2;
23use std::collections::HashMap;
24use syn::{
25 spanned::Spanned as _,
26 Result,
27};
28
29#[derive(Debug, PartialEq, Eq)]
31pub struct ChainExtension {
32 item: syn::ItemTrait,
33 config: Config,
34 error_code: syn::TraitItemType,
35 methods: Vec<ChainExtensionMethod>,
36}
37
38impl ChainExtension {
39 pub fn attrs(&self) -> Vec<syn::Attribute> {
41 let (_, attrs) = ir::partition_attributes(self.item.attrs.iter().cloned())
42 .unwrap_or_else(|err| panic!("encountered unexpected invalid attributes for ink! chain extension: {err}"));
43 attrs
44 }
45
46 pub fn span(&self) -> proc_macro2::Span {
48 self.item.span()
49 }
50
51 pub fn ident(&self) -> &proc_macro2::Ident {
53 &self.item.ident
54 }
55
56 pub fn iter_methods(&self) -> SliceIter<ChainExtensionMethod> {
58 self.methods.iter()
59 }
60
61 pub fn error_code(&self) -> &syn::Type {
63 self.error_code
64 .default
65 .as_ref()
66 .map(|(_token, ty)| ty)
67 .expect("unexpected missing default type for error code")
68 }
69}
70
71#[derive(Default, Debug, PartialEq, Eq)]
73pub struct Config {
74 ext_id: ExtensionId,
75}
76
77impl TryFrom<ast::AttributeArgs> for Config {
78 type Error = syn::Error;
79
80 fn try_from(args: ast::AttributeArgs) -> Result<Self> {
81 let mut ext_id: Option<ExtensionId> = None;
82
83 for arg in args.clone().into_iter() {
84 if arg.name().is_ident("extension") {
85 if ext_id.is_some() {
86 return Err(format_err_spanned_value!(
87 arg,
88 "encountered duplicate ink! contract `extension` configuration argument",
89 ));
90 }
91
92 if let Some(lit_int) = arg.value().and_then(ast::MetaValue::as_lit_int) {
93 let id = lit_int.base10_parse::<u16>()
94 .map_err(|error| {
95 format_err_spanned!(
96 lit_int,
97 "could not parse `N` in `extension = N` into a `u16` integer: {}", error)
98 })?;
99 ext_id = Some(ExtensionId::from_u16(id));
100 } else {
101 return Err(format_err_spanned_value!(
102 arg,
103 "expected `u16` integer type for `N` in `extension = N`",
104 ));
105 }
106 } else {
107 return Err(format_err_spanned!(
108 arg,
109 "encountered unknown or unsupported chain extension configuration argument",
110 ));
111 }
112 }
113
114 if let Some(ext_id) = ext_id {
115 Ok(Config { ext_id })
116 } else {
117 Err(format_err_spanned!(
118 args,
119 "missing required `extension = N: u16` argument on ink! chain extension",
120 ))
121 }
122 }
123}
124
125impl Config {
126 pub fn ext_id(&self) -> ExtensionId {
128 self.ext_id
129 }
130}
131
132#[derive(Debug, PartialEq, Eq)]
134pub struct ChainExtensionMethod {
135 item: syn::TraitItemFn,
137 id: GlobalMethodId,
139 handle_status: bool,
156}
157
158impl ChainExtensionMethod {
159 pub fn attrs(&self) -> Vec<syn::Attribute> {
161 let (_, attrs) = ir::partition_attributes(self.item.attrs.iter().cloned())
162 .expect(
163 "encountered unexpected invalid attributes for ink! chain extension method",
164 );
165 attrs
166 }
167
168 pub fn span(&self) -> proc_macro2::Span {
170 self.item.span()
171 }
172
173 pub fn ident(&self) -> &proc_macro2::Ident {
175 &self.item.sig.ident
176 }
177
178 pub fn sig(&self) -> &syn::Signature {
180 &self.item.sig
181 }
182
183 pub fn id(&self) -> GlobalMethodId {
185 self.id
186 }
187
188 pub fn inputs(&self) -> ChainExtensionMethodInputs {
190 ChainExtensionMethodInputs {
191 iter: self.item.sig.inputs.iter(),
192 }
193 }
194
195 pub fn handle_status(&self) -> bool {
198 self.handle_status
199 }
200}
201
202pub struct ChainExtensionMethodInputs<'a> {
203 iter: syn::punctuated::Iter<'a, syn::FnArg>,
204}
205
206impl<'a> Iterator for ChainExtensionMethodInputs<'a> {
207 type Item = &'a syn::PatType;
208
209 fn size_hint(&self) -> (usize, Option<usize>) {
210 self.iter.size_hint()
211 }
212
213 fn next(&mut self) -> Option<Self::Item> {
214 let item = self.iter.next()?;
215 match item {
216 syn::FnArg::Receiver(receiver) => {
217 panic!("encountered unexpected receiver in chain extension method input: {receiver:?}")
218 }
219 syn::FnArg::Typed(pat_type) => Some(pat_type),
220 }
221 }
222}
223
224#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
230pub struct ExtensionId {
231 index: u16,
232}
233
234impl ExtensionId {
235 pub fn from_u16(index: u16) -> Self {
237 Self { index }
238 }
239
240 pub fn into_u16(self) -> u16 {
242 self.index
243 }
244}
245
246#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
252pub struct FunctionId {
253 index: u16,
254}
255
256impl FunctionId {
257 pub fn from_u16(index: u16) -> Self {
259 Self { index }
260 }
261
262 pub fn into_u16(self) -> u16 {
264 self.index
265 }
266}
267
268#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
274pub struct GlobalMethodId {
275 index: u32,
276}
277
278impl GlobalMethodId {
279 pub fn new(ext_id: ExtensionId, func_id: FunctionId) -> Self {
281 Self {
282 index: ((ext_id.index as u32) << 16) | func_id.index as u32,
283 }
284 }
285
286 pub fn func_id(&self) -> FunctionId {
288 FunctionId::from_u16((self.index & 0x0000FFFF) as u16)
289 }
290
291 pub fn ext_id(&self) -> ExtensionId {
293 ExtensionId::from_u16((self.index >> 16) as u16)
294 }
295
296 pub fn into_u32(self) -> u32 {
298 self.index
299 }
300}
301
302impl ChainExtension {
303 pub fn try_from(
309 item_trait: syn::ItemTrait,
310 config: Config,
311 ) -> core::result::Result<Self, syn::Error> {
312 idents_lint::ensure_no_ink_identifiers(&item_trait)?;
313 Self::analyse_properties(&item_trait)?;
314 let (error_code, methods) = Self::analyse_items(config.ext_id, &item_trait)?;
315 Ok(Self {
316 item: item_trait,
317 config,
318 error_code,
319 methods,
320 })
321 }
322}
323
324impl ChainExtension {
325 pub fn new(attr: TokenStream2, input: TokenStream2) -> Result<Self> {
327 let args = syn::parse2::<ast::AttributeArgs>(attr)?;
328 let config = Config::try_from(args)?;
329 let item_trait = syn::parse2::<syn::ItemTrait>(input)?;
330 ChainExtension::try_from(item_trait, config)
331 }
332
333 fn analyse_properties(item_trait: &syn::ItemTrait) -> Result<()> {
343 if let Some(unsafety) = &item_trait.unsafety {
344 return Err(format_err_spanned!(
345 unsafety,
346 "ink! chain extensions cannot be unsafe"
347 ))
348 }
349 if let Some(auto) = &item_trait.auto_token {
350 return Err(format_err_spanned!(
351 auto,
352 "ink! chain extensions cannot be automatically implemented traits"
353 ))
354 }
355 if !item_trait.generics.params.is_empty() {
356 return Err(format_err_spanned!(
357 item_trait.generics.params,
358 "ink! chain extensions must not be generic"
359 ))
360 }
361 if !matches!(item_trait.vis, syn::Visibility::Public(_)) {
362 return Err(format_err_spanned!(
363 item_trait.vis,
364 "ink! chain extensions must have public visibility"
365 ))
366 }
367 if !item_trait.supertraits.is_empty() {
368 return Err(format_err_spanned!(
369 item_trait.supertraits,
370 "ink! chain extensions with super-traits are not supported, yet"
371 ))
372 }
373 Ok(())
374 }
375
376 fn analyse_error_code(
384 item_type: &syn::TraitItemType,
385 previous: &mut Option<syn::TraitItemType>,
386 ) -> Result<()> {
387 if item_type.ident != "ErrorCode" {
388 return Err(format_err_spanned!(
389 item_type.ident,
390 "chain extensions expect an associated type with name `ErrorCode` but found {}",
391 item_type.ident,
392 ));
393 }
394 if !item_type.generics.params.is_empty() {
395 return Err(format_err_spanned!(
396 item_type.generics,
397 "generic chain extension `ErrorCode` types are not supported",
398 ))
399 }
400 if !item_type.bounds.is_empty() {
401 return Err(format_err_spanned!(
402 item_type.bounds,
403 "bounded chain extension `ErrorCode` types are not supported",
404 ))
405 }
406 if item_type.default.is_none() {
407 return Err(format_err_spanned!(
408 item_type,
409 "expected a default type for the ink! chain extension ErrorCode",
410 ))
411 }
412 match previous {
413 Some(previous_error_code) => {
414 return Err(format_err_spanned!(
415 item_type,
416 "encountered duplicate `ErrorCode` associated types for the chain extension",
417 )).map_err(|err| err.into_combine(format_err_spanned!(
418 previous_error_code,
419 "first `ErrorCode` associated type here",
420 )))
421 }
422 None => {
423 *previous = Some(item_type.clone());
424 }
425 }
426 Ok(())
427 }
428
429 fn analyse_items(
452 ext_id: ExtensionId,
453 item_trait: &syn::ItemTrait,
454 ) -> Result<(syn::TraitItemType, Vec<ChainExtensionMethod>)> {
455 let mut methods = Vec::new();
456 let mut seen_ids = HashMap::new();
457 let mut error_code = None;
458 for trait_item in &item_trait.items {
459 match trait_item {
460 syn::TraitItem::Const(const_trait_item) => {
461 return Err(format_err_spanned!(
462 const_trait_item,
463 "associated constants in ink! chain extensions are not supported, yet"
464 ))
465 }
466 syn::TraitItem::Macro(macro_trait_item) => {
467 return Err(format_err_spanned!(
468 macro_trait_item,
469 "macros in ink! chain extensions are not supported"
470 ))
471 }
472 syn::TraitItem::Type(type_trait_item) => {
473 Self::analyse_error_code(type_trait_item, &mut error_code)?;
474 }
475 syn::TraitItem::Verbatim(verbatim) => {
476 return Err(format_err_spanned!(
477 verbatim,
478 "encountered unsupported item in ink! chain extensions"
479 ))
480 }
481 syn::TraitItem::Fn(fn_trait_item) => {
482 let method = Self::analyse_methods(ext_id, fn_trait_item)?;
483 let method_id = method.id();
484 if let Some(previous) = seen_ids.get(&method_id) {
485 return Err(format_err!(
486 method.span(),
487 "encountered duplicate extension identifiers for the same chain extension",
488 ).into_combine(format_err!(
489 *previous,
490 "previous duplicate extension identifier here",
491 )))
492 }
493 seen_ids.insert(method_id, method.span());
494 methods.push(method);
495 }
496 unknown => {
497 return Err(format_err_spanned!(
498 unknown,
499 "encountered unknown or unsupported item in ink! chain extensions"
500 ))
501 }
502 }
503 }
504 let error_code = match error_code {
505 Some(error_code) => error_code,
506 None => {
507 return Err(format_err_spanned!(
508 item_trait,
509 "missing ErrorCode associated type from ink! chain extension",
510 ))
511 }
512 };
513 Ok((error_code, methods))
514 }
515
516 fn analyse_methods(
526 ext_id: ExtensionId,
527 method: &syn::TraitItemFn,
528 ) -> Result<ChainExtensionMethod> {
529 if let Some(default_impl) = &method.default {
530 return Err(format_err_spanned!(
531 default_impl,
532 "ink! chain extension methods with default implementations are not supported"
533 ));
534 }
535 if let Some(constness) = &method.sig.constness {
536 return Err(format_err_spanned!(
537 constness,
538 "const ink! chain extension methods are not supported"
539 ))
540 }
541 if let Some(asyncness) = &method.sig.asyncness {
542 return Err(format_err_spanned!(
543 asyncness,
544 "async ink! chain extension methods are not supported"
545 ))
546 }
547 if let Some(unsafety) = &method.sig.unsafety {
548 return Err(format_err_spanned!(
549 unsafety,
550 "unsafe ink! chain extension methods are not supported"
551 ))
552 }
553 if let Some(abi) = &method.sig.abi {
554 return Err(format_err_spanned!(
555 abi,
556 "ink! chain extension methods with non default ABI are not supported"
557 ))
558 }
559 if let Some(variadic) = &method.sig.variadic {
560 return Err(format_err_spanned!(
561 variadic,
562 "variadic ink! chain extension methods are not supported"
563 ))
564 }
565 if !method.sig.generics.params.is_empty() {
566 return Err(format_err_spanned!(
567 method.sig.generics.params,
568 "generic ink! chain extension methods are not supported"
569 ))
570 }
571 match ir::first_ink_attribute(&method.attrs)?
572 .map(|attr| attr.first().kind().clone()) {
573 Some(ir::AttributeArg::Function(func_id)) => {
574 Self::analyse_chain_extension_method(method, ext_id, func_id)
575 }
576 Some(_unsupported) => {
577 Err(format_err_spanned!(
578 method,
579 "encountered unsupported ink! attribute for ink! chain extension method. expected #[ink(function = N: u16)] attribute"
580 ))
581 }
582 None => {
583 Err(format_err_spanned!(
584 method,
585 "missing #[ink(function = N: u16)] flag on ink! chain extension method"
586 ))
587 }
588 }
589 }
590
591 fn analyse_chain_extension_method(
597 item_method: &syn::TraitItemFn,
598 ext_id: ExtensionId,
599 func_id: FunctionId,
600 ) -> Result<ChainExtensionMethod> {
601 let (ink_attrs, _) = ir::sanitize_attributes(
602 item_method.span(),
603 item_method.attrs.clone(),
604 &ir::AttributeArgKind::Function,
605 |arg| {
606 match arg.kind() {
607 ir::AttributeArg::Function(_) | ir::AttributeArg::HandleStatus(_) => {
608 Ok(())
609 }
610 _ => Err(None),
611 }
612 },
613 )?;
614 if let Some(receiver) = item_method.sig.receiver() {
615 return Err(format_err_spanned!(
616 receiver,
617 "ink! chain extension method must not have a `self` receiver",
618 ))
619 }
620 let result = ChainExtensionMethod {
621 id: GlobalMethodId::new(ext_id, func_id),
622 item: item_method.clone(),
623 handle_status: ink_attrs.is_handle_status(),
624 };
625 Ok(result)
626 }
627}
628
629#[cfg(test)]
630mod tests {
631 use super::*;
632
633 macro_rules! assert_ink_chain_extension_eq_err {
636 ( error: $err_str:literal, $($chain_extension:tt)* ) => {
637 assert_eq!(
638 ChainExtension::try_from(
639 syn::parse_quote! {
640 $( $chain_extension )*
641 },
642 Config::default()
643 )
644 .map_err(|err| err.to_string()),
645 Err(
646 $err_str.to_string()
647 )
648 )
649 };
650 }
651
652 #[test]
653 fn unsafe_chain_extension_is_denied() {
654 assert_ink_chain_extension_eq_err!(
655 error: "ink! chain extensions cannot be unsafe",
656 pub unsafe trait MyChainExtension {}
657 );
658 }
659
660 #[test]
661 fn auto_chain_extension_is_denied() {
662 assert_ink_chain_extension_eq_err!(
663 error: "ink! chain extensions cannot be automatically implemented traits",
664 pub auto trait MyChainExtension {}
665 );
666 }
667
668 #[test]
669 fn non_pub_chain_extension_is_denied() {
670 assert_ink_chain_extension_eq_err!(
671 error: "ink! chain extensions must have public visibility",
672 trait MyChainExtension {}
673 );
674 assert_ink_chain_extension_eq_err!(
675 error: "ink! chain extensions must have public visibility",
676 pub(crate) trait MyChainExtension {}
677 );
678 }
679
680 #[test]
681 fn generic_chain_extension_is_denied() {
682 assert_ink_chain_extension_eq_err!(
683 error: "ink! chain extensions must not be generic",
684 pub trait MyChainExtension<T> {}
685 );
686 }
687
688 #[test]
689 fn chain_extension_with_supertraits_is_denied() {
690 assert_ink_chain_extension_eq_err!(
691 error: "ink! chain extensions with super-traits are not supported, yet",
692 pub trait MyChainExtension: SuperChainExtension {}
693 );
694 }
695
696 #[test]
697 fn chain_extension_containing_const_item_is_denied() {
698 assert_ink_chain_extension_eq_err!(
699 error: "associated constants in ink! chain extensions are not supported, yet",
700 pub trait MyChainExtension {
701 const T: i32;
702 }
703 );
704 }
705
706 #[test]
707 fn chain_extension_containing_invalid_associated_type_is_denied() {
708 assert_ink_chain_extension_eq_err!(
709 error: "chain extensions expect an associated type with name `ErrorCode` but found Type",
710 pub trait MyChainExtension {
711 type Type;
712 }
713 );
714 }
715
716 #[test]
717 fn chain_extension_with_invalid_error_code() {
718 assert_ink_chain_extension_eq_err!(
719 error: "chain extensions expect an associated type with name `ErrorCode` but found IncorrectName",
720 pub trait MyChainExtension {
721 type IncorrectName = ();
722 }
723 );
724 assert_ink_chain_extension_eq_err!(
725 error: "generic chain extension `ErrorCode` types are not supported",
726 pub trait MyChainExtension {
727 type ErrorCode<T> = ();
728 }
729 );
730 assert_ink_chain_extension_eq_err!(
731 error: "bounded chain extension `ErrorCode` types are not supported",
732 pub trait MyChainExtension {
733 type ErrorCode: Copy = ();
734 }
735 );
736 assert_ink_chain_extension_eq_err!(
737 error: "expected a default type for the ink! chain extension ErrorCode",
738 pub trait MyChainExtension {
739 type ErrorCode;
740 }
741 );
742 assert_ink_chain_extension_eq_err!(
743 error: "encountered duplicate `ErrorCode` associated types for the chain extension",
744 pub trait MyChainExtension {
745 type ErrorCode = ();
746 type ErrorCode = ();
747 }
748 );
749 }
750
751 #[test]
752 fn chain_extension_containing_macro_is_denied() {
753 assert_ink_chain_extension_eq_err!(
754 error: "macros in ink! chain extensions are not supported",
755 pub trait MyChainExtension {
756 my_macro_call!();
757 }
758 );
759 }
760
761 #[test]
762 fn chain_extension_containing_non_flagged_method_is_denied() {
763 assert_ink_chain_extension_eq_err!(
764 error: "missing #[ink(function = N: u16)] flag on ink! chain extension method",
765 pub trait MyChainExtension {
766 fn non_flagged_1(&self);
767 }
768 );
769 assert_ink_chain_extension_eq_err!(
770 error: "missing #[ink(function = N: u16)] flag on ink! chain extension method",
771 pub trait MyChainExtension {
772 fn non_flagged_2(&mut self);
773 }
774 );
775 assert_ink_chain_extension_eq_err!(
776 error: "missing #[ink(function = N: u16)] flag on ink! chain extension method",
777 pub trait MyChainExtension {
778 fn non_flagged_3() -> Self;
779 }
780 );
781 }
782
783 #[test]
784 fn chain_extension_containing_default_implemented_methods_is_denied() {
785 assert_ink_chain_extension_eq_err!(
786 error: "ink! chain extension methods with default implementations are not supported",
787 pub trait MyChainExtension {
788 #[ink(constructor)]
789 fn default_implemented() -> Self {}
790 }
791 );
792 }
793
794 #[test]
795 fn chain_extension_containing_const_methods_is_denied() {
796 assert_ink_chain_extension_eq_err!(
797 error: "const ink! chain extension methods are not supported",
798 pub trait MyChainExtension {
799 #[ink(function = 1)]
800 const fn const_constructor() -> Self;
801 }
802 );
803 }
804
805 #[test]
806 fn chain_extension_containing_async_methods_is_denied() {
807 assert_ink_chain_extension_eq_err!(
808 error: "async ink! chain extension methods are not supported",
809 pub trait MyChainExtension {
810 #[ink(function = 1)]
811 async fn const_constructor() -> Self;
812 }
813 );
814 }
815
816 #[test]
817 fn chain_extension_containing_unsafe_methods_is_denied() {
818 assert_ink_chain_extension_eq_err!(
819 error: "unsafe ink! chain extension methods are not supported",
820 pub trait MyChainExtension {
821 #[ink(function = 1)]
822 unsafe fn const_constructor() -> Self;
823 }
824 );
825 }
826
827 #[test]
828 fn chain_extension_containing_methods_using_explicit_abi_is_denied() {
829 assert_ink_chain_extension_eq_err!(
830 error: "ink! chain extension methods with non default ABI are not supported",
831 pub trait MyChainExtension {
832 #[ink(function = 1)]
833 extern fn const_constructor() -> Self;
834 }
835 );
836 }
837
838 #[test]
839 fn chain_extension_containing_variadic_methods_is_denied() {
840 assert_ink_chain_extension_eq_err!(
841 error: "variadic ink! chain extension methods are not supported",
842 pub trait MyChainExtension {
843 #[ink(function = 1)]
844 fn const_constructor(...) -> Self;
845 }
846 );
847 }
848
849 #[test]
850 fn chain_extension_containing_generic_methods_is_denied() {
851 assert_ink_chain_extension_eq_err!(
852 error: "generic ink! chain extension methods are not supported",
853 pub trait MyChainExtension {
854 #[ink(function = 1)]
855 fn const_constructor<T>() -> Self;
856 }
857 );
858 }
859
860 #[test]
861 fn chain_extension_containing_method_with_unsupported_ink_attribute_is_denied() {
862 assert_ink_chain_extension_eq_err!(
863 error: "\
864 encountered unsupported ink! attribute for ink! chain extension method. \
865 expected #[ink(function = N: u16)] attribute",
866 pub trait MyChainExtension {
867 #[ink(message)]
868 fn unsupported_ink_attribute(&self);
869 }
870 );
871 assert_ink_chain_extension_eq_err!(
872 error: "encountered unknown ink! attribute argument: unknown",
873 pub trait MyChainExtension {
874 #[ink(unknown)]
875 fn unknown_ink_attribute(&self);
876 }
877 );
878 }
879
880 #[test]
881 fn chain_extension_containing_method_with_invalid_marker() {
882 assert_ink_chain_extension_eq_err!(
883 error: "could not parse `N` in `#[ink(function = N)]` into a `u16` integer: \
884 invalid digit found in string",
885 pub trait MyChainExtension {
886 #[ink(function = -1)]
887 fn has_self_receiver();
888 }
889 );
890 let too_large = (u16::MAX as u64) + 1;
891 assert_ink_chain_extension_eq_err!(
892 error: "could not parse `N` in `#[ink(function = N)]` into a `u16` integer: \
893 number too large to fit in target type",
894 pub trait MyChainExtension {
895 #[ink(function = #too_large)]
896 fn has_self_receiver();
897 }
898 );
899 assert_ink_chain_extension_eq_err!(
900 error: "expected `u16` integer type for `N` in #[ink(function = N)]",
901 pub trait MyChainExtension {
902 #[ink(function = "Hello, World!")]
903 fn has_self_receiver();
904 }
905 );
906 assert_ink_chain_extension_eq_err!(
907 error: "encountered #[ink(function)] that is missing its `id` parameter. \
908 Did you mean #[ink(function = id: u16)] ?",
909 pub trait MyChainExtension {
910 #[ink(function)]
911 fn has_self_receiver();
912 }
913 );
914
915 assert_ink_chain_extension_eq_err!(
916 error: "encountered duplicate ink! attribute",
917 pub trait MyChainExtension {
918 #[ink(function = 42)]
919 #[ink(function = 42)]
920 fn duplicate_attributes() -> Self;
921 }
922 );
923 assert_ink_chain_extension_eq_err!(
924 error: "encountered ink! attribute arguments with equal kinds",
925 pub trait MyChainExtension {
926 #[ink(function = 1)]
927 #[ink(function = 2)]
928 fn duplicate_attributes() -> Self;
929 }
930 );
931 assert_ink_chain_extension_eq_err!(
932 error: "encountered conflicting ink! attribute argument",
933 pub trait MyChainExtension {
934 #[ink(function = 1)]
935 #[ink(message)]
936 fn conflicting_attributes() -> Self;
937 }
938 );
939 }
940
941 #[test]
942 fn chain_extension_containing_method_with_self_receiver_is_denied() {
943 assert_ink_chain_extension_eq_err!(
944 error: "ink! chain extension method must not have a `self` receiver",
945 pub trait MyChainExtension {
946 type ErrorCode = ();
947
948 #[ink(function = 1)]
949 fn has_self_receiver(&self) -> Self;
950 }
951 );
952 assert_ink_chain_extension_eq_err!(
953 error: "ink! chain extension method must not have a `self` receiver",
954 pub trait MyChainExtension {
955 type ErrorCode = ();
956
957 #[ink(function = 1)]
958 fn has_self_receiver(&mut self) -> Self;
959 }
960 );
961 assert_ink_chain_extension_eq_err!(
962 error: "ink! chain extension method must not have a `self` receiver",
963 pub trait MyChainExtension {
964 type ErrorCode = ();
965
966 #[ink(function = 1)]
967 fn has_self_receiver(self) -> Self;
968 }
969 );
970 assert_ink_chain_extension_eq_err!(
971 error: "ink! chain extension method must not have a `self` receiver",
972 pub trait MyChainExtension {
973 type ErrorCode = ();
974
975 #[ink(function = 1)]
976 fn has_self_receiver(self: &Self) -> Self;
977 }
978 );
979 assert_ink_chain_extension_eq_err!(
980 error: "ink! chain extension method must not have a `self` receiver",
981 pub trait MyChainExtension {
982 type ErrorCode = ();
983
984 #[ink(function = 1)]
985 fn has_self_receiver(self: Self) -> Self;
986 }
987 );
988 }
989
990 #[test]
991 fn chain_extension_with_overlapping_extension_ids() {
992 assert_ink_chain_extension_eq_err!(
993 error: "encountered duplicate extension identifiers for the same chain extension",
994 pub trait MyChainExtension {
995 #[ink(function = 1)]
996 fn same_id_1();
997 #[ink(function = 1)]
998 fn same_id_2();
999 }
1000 );
1001 }
1002
1003 #[test]
1004 fn chain_extension_is_ok() {
1005 let chain_extension = ChainExtension::try_from(syn::parse_quote! {
1006 pub trait MyChainExtension {
1007 type ErrorCode = ();
1008
1009 #[ink(function = 1)]
1010 fn extension_1();
1011 #[ink(function = 2)]
1012 fn extension_2(input: i32);
1013 #[ink(function = 3)]
1014 fn extension_3() -> i32;
1015 #[ink(function = 4)]
1016 fn extension_4(input: i32) -> i32;
1017 #[ink(function = 5)]
1018 fn extension_5(in1: i8, in2: i16, in3: i32, in4: i64) -> (u8, u16, u32, u64);
1019 }
1020 }, Config::default()).unwrap();
1021 assert_eq!(chain_extension.methods.len(), 5);
1022 for (actual, expected) in chain_extension
1023 .methods
1024 .iter()
1025 .map(|method| method.id())
1026 .zip(1..=5u32)
1027 {
1028 assert_eq!(actual.index, expected);
1029 }
1030 for (actual, expected) in chain_extension
1031 .methods
1032 .iter()
1033 .map(|method| method.ident().to_string())
1034 .zip(
1035 [
1036 "extension_1",
1037 "extension_2",
1038 "extension_3",
1039 "extension_4",
1040 "extension_5",
1041 ]
1042 .iter()
1043 .map(ToString::to_string),
1044 )
1045 {
1046 assert_eq!(actual, expected);
1047 }
1048 }
1049
1050 #[test]
1051 fn chain_extension_with_params_is_ok() {
1052 let chain_extension = ChainExtension::try_from(
1053 syn::parse_quote! {
1054 pub trait MyChainExtension {
1055 type ErrorCode = ();
1056
1057 #[ink(function = 1, handle_status = false)]
1058 fn extension_a();
1059 #[ink(function = 2)]
1060 fn extension_b();
1061 #[ink(function = 3, handle_status = false)]
1062 fn extension_c();
1063 #[ink(function = 4)]
1064 #[ink(handle_status = false)]
1065 fn extension_d();
1066 #[ink(function = 5)]
1067 fn extension_e();
1068 #[ink(function = 6)]
1069 #[ink(handle_status = false)]
1070 fn extension_f();
1071 }
1072 },
1073 Config::default(),
1074 )
1075 .unwrap();
1076 let expected_methods = 6;
1077 assert_eq!(chain_extension.methods.len(), expected_methods);
1078 for (actual, expected) in chain_extension
1079 .methods
1080 .iter()
1081 .map(|method| method.id())
1082 .zip(1..=expected_methods as u32)
1083 {
1084 assert_eq!(actual.index, expected);
1085 }
1086 for (actual, expected) in chain_extension
1087 .methods
1088 .iter()
1089 .map(|method| method.ident().to_string())
1090 .zip(
1091 [
1092 "extension_a",
1093 "extension_b",
1094 "extension_c",
1095 "extension_d",
1096 "extension_e",
1097 "extension_f",
1098 ]
1099 .iter()
1100 .map(ToString::to_string),
1101 )
1102 {
1103 assert_eq!(actual, expected);
1104 }
1105 }
1106
1107 fn assert_config(
1110 input: ast::AttributeArgs,
1111 expected: core::result::Result<Config, &'static str>,
1112 ) {
1113 assert_eq!(
1114 <Config as TryFrom<ast::AttributeArgs>>::try_from(input)
1115 .map_err(|err| err.to_string()),
1116 expected.map_err(ToString::to_string),
1117 );
1118 }
1119
1120 #[test]
1121 fn empty_config_fails() {
1122 assert_config(
1123 syn::parse_quote! {},
1124 Err("missing required `extension = N: u16` argument on ink! chain extension"),
1125 )
1126 }
1127
1128 #[test]
1129 fn extension_works() {
1130 assert_config(
1131 syn::parse_quote! {
1132 extension = 13
1133 },
1134 Ok(Config {
1135 ext_id: ExtensionId::from_u16(13),
1136 }),
1137 )
1138 }
1139
1140 #[test]
1141 fn extension_invalid_value_fails() {
1142 assert_config(
1143 syn::parse_quote! { extension = "invalid" },
1144 Err("expected `u16` integer type for `N` in `extension = N`"),
1145 );
1146 }
1147
1148 #[test]
1149 fn unknown_arg_fails() {
1150 assert_config(
1151 syn::parse_quote! { unknown = argument },
1152 Err("encountered unknown or unsupported chain extension configuration argument"),
1153 );
1154 }
1155
1156 #[test]
1157 fn duplicate_args_fails() {
1158 assert_config(
1159 syn::parse_quote! {
1160 extension = 13,
1161 extension = 123,
1162 },
1163 Err("encountered duplicate ink! contract `extension` configuration argument"),
1164 );
1165 }
1166}