ink_lang_ir/ir/
chain_extension.rs

1// Copyright 2018-2022 Parity Technologies (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::{
16    error::ExtError,
17    ir,
18    ir::idents_lint,
19};
20use core::slice::Iter as SliceIter;
21use proc_macro2::TokenStream as TokenStream2;
22use std::collections::HashMap;
23use syn::{
24    spanned::Spanned as _,
25    Result,
26};
27
28/// An ink! chain extension.
29#[derive(Debug, PartialEq, Eq)]
30pub struct ChainExtension {
31    item: syn::ItemTrait,
32    error_code: syn::TraitItemType,
33    methods: Vec<ChainExtensionMethod>,
34}
35
36impl ChainExtension {
37    /// Returns the Rust attributes of the ink! chain extension.
38    pub fn attrs(&self) -> Vec<syn::Attribute> {
39        let (_, attrs) = ir::partition_attributes(self.item.attrs.iter().cloned())
40            .expect("encountered unexpected invalid attributes for ink! chain extension");
41        attrs
42    }
43
44    /// Returns the span of the ink! chain extension.
45    pub fn span(&self) -> proc_macro2::Span {
46        self.item.span()
47    }
48
49    /// Returns the identifier of the ink! chain extension.
50    pub fn ident(&self) -> &proc_macro2::Ident {
51        &self.item.ident
52    }
53
54    /// Returns a slice over all the chain extension methods.
55    pub fn iter_methods(&self) -> SliceIter<ChainExtensionMethod> {
56        self.methods.iter()
57    }
58
59    /// Returns the type of the error code of the chain extension.
60    pub fn error_code(&self) -> &syn::Type {
61        self.error_code
62            .default
63            .as_ref()
64            .map(|(_token, ty)| ty)
65            .expect("unexpected missing default type for error code")
66    }
67}
68
69/// An ink! chain extension method.
70#[derive(Debug, PartialEq, Eq)]
71pub struct ChainExtensionMethod {
72    /// The underlying validated AST of the chain extension method.
73    item: syn::TraitItemMethod,
74    /// The unique identifier of the chain extension method.
75    id: ExtensionId,
76    /// If `false` the `u32` status code of the chain extension method call is going to be
77    /// ignored and assumed to be always successful. The output buffer in this case is going
78    /// to be queried and decoded into the chain extension method's output type.
79    ///
80    /// If `true` the returned `u32` status code `code` is queried and
81    /// `<Self::ErrorCode as ink_lang::FromStatusCode>::from_status_code(code)` is called.
82    /// The call to `from_status_code` returns `Result<(), Self::ErrorCode>`. If `Ok(())`
83    /// the output buffer is queried and decoded as described above.
84    /// If `Err(Self::ErrorCode)` the `Self::ErrorCode` is converted into `E` of `Result<T, E>`
85    /// if the chain extension method returns a `Result` type.
86    /// In case the chain extension method does _NOT_ return a `Result` type the call returns
87    /// `Result<T, Self::ErrorCode>` where `T` is the chain extension method's return type.
88    ///
89    /// The default for this flag is `true`.
90    handle_status: bool,
91    /// If `false` the procedural macro no longer tries to enforce that the returned type encoded
92    /// into the output buffer of the chain extension method call is of type `Result<T, E>`.
93    /// Also `E` is no longer required to implement `From<Self::ErrorCode>` in case `handle_status`
94    /// flag does not exist.
95    ///
96    /// The default for this flag is `true`.
97    returns_result: bool,
98}
99
100impl ChainExtensionMethod {
101    /// Returns the Rust attributes of the ink! chain extension method.
102    pub fn attrs(&self) -> Vec<syn::Attribute> {
103        let (_, attrs) = ir::partition_attributes(self.item.attrs.iter().cloned())
104            .expect(
105            "encountered unexpected invalid attributes for ink! chain extension method",
106        );
107        attrs
108    }
109
110    /// Returns the span of the ink! chain extension method.
111    pub fn span(&self) -> proc_macro2::Span {
112        self.item.span()
113    }
114
115    /// Returns the identifier of the ink! chain extension method.
116    pub fn ident(&self) -> &proc_macro2::Ident {
117        &self.item.sig.ident
118    }
119
120    /// Returns the method signature of the ink! chain extension method.
121    pub fn sig(&self) -> &syn::Signature {
122        &self.item.sig
123    }
124
125    /// Returns the unique ID of the chain extension method.
126    pub fn id(&self) -> ExtensionId {
127        self.id
128    }
129
130    /// Returns an iterator over the inputs of the chain extension method.
131    pub fn inputs(&self) -> ChainExtensionMethodInputs {
132        ChainExtensionMethodInputs {
133            iter: self.item.sig.inputs.iter(),
134        }
135    }
136
137    /// Returns `true` if the chain extension method was flagged with `#[ink(handle_status)]`.
138    pub fn handle_status(&self) -> bool {
139        self.handle_status
140    }
141
142    /// Returns `true` if the chain extension method was flagged with `#[ink(returns_result)]`.
143    pub fn returns_result(&self) -> bool {
144        self.returns_result
145    }
146}
147
148pub struct ChainExtensionMethodInputs<'a> {
149    iter: syn::punctuated::Iter<'a, syn::FnArg>,
150}
151
152impl<'a> Iterator for ChainExtensionMethodInputs<'a> {
153    type Item = &'a syn::PatType;
154
155    fn size_hint(&self) -> (usize, Option<usize>) {
156        self.iter.size_hint()
157    }
158
159    fn next(&mut self) -> Option<Self::Item> {
160        let item = self.iter.next()?;
161        match item {
162            syn::FnArg::Receiver(receiver) => {
163                panic!("encountered unexpected receiver in chain extension method input: {:?}", receiver)
164            }
165            syn::FnArg::Typed(pat_type) => Some(pat_type),
166        }
167    }
168}
169
170/// The unique ID of an ink! chain extension method.
171///
172/// # Note
173///
174/// The ink! attribute `#[ink(extension = N: u32)]` for chain extension methods.
175///
176/// Has a `func_id` extension ID to identify the associated chain extension method.
177#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
178pub struct ExtensionId {
179    index: u32,
180}
181
182impl ExtensionId {
183    /// Creates a new chain extension method ID from the given `u32`.
184    pub fn from_u32(index: u32) -> Self {
185        Self { index }
186    }
187
188    /// Returns the underlying raw `u32` index.
189    pub fn into_u32(self) -> u32 {
190        self.index
191    }
192}
193
194impl TryFrom<syn::ItemTrait> for ChainExtension {
195    type Error = syn::Error;
196
197    fn try_from(item_trait: syn::ItemTrait) -> core::result::Result<Self, Self::Error> {
198        idents_lint::ensure_no_ink_identifiers(&item_trait)?;
199        Self::analyse_properties(&item_trait)?;
200        let (error_code, methods) = Self::analyse_items(&item_trait)?;
201        Ok(Self {
202            item: item_trait,
203            error_code,
204            methods,
205        })
206    }
207}
208
209impl ChainExtension {
210    /// Returns `Ok` if the trait matches all requirements for an ink! chain extension.
211    pub fn new(attr: TokenStream2, input: TokenStream2) -> Result<Self> {
212        if !attr.is_empty() {
213            return Err(format_err_spanned!(
214                attr,
215                "unexpected attribute input for ink! chain extension"
216            ))
217        }
218        let item_trait = syn::parse2::<syn::ItemTrait>(input)?;
219        ChainExtension::try_from(item_trait)
220    }
221
222    /// Analyses the properties of the ink! chain extension.
223    ///
224    /// # Errors
225    ///
226    /// - If the input trait has been defined as `unsafe`.
227    /// - If the input trait is an automatically implemented trait (`auto trait`).
228    /// - If the input trait is generic over some set of types.
229    /// - If the input trait's visibility is not public (`pub`).
230    /// - If the input trait has super-traits.
231    fn analyse_properties(item_trait: &syn::ItemTrait) -> Result<()> {
232        if let Some(unsafety) = &item_trait.unsafety {
233            return Err(format_err_spanned!(
234                unsafety,
235                "ink! chain extensions cannot be unsafe"
236            ))
237        }
238        if let Some(auto) = &item_trait.auto_token {
239            return Err(format_err_spanned!(
240                auto,
241                "ink! chain extensions cannot be automatically implemented traits"
242            ))
243        }
244        if !item_trait.generics.params.is_empty() {
245            return Err(format_err_spanned!(
246                item_trait.generics.params,
247                "ink! chain extensions must not be generic"
248            ))
249        }
250        if !matches!(item_trait.vis, syn::Visibility::Public(_)) {
251            return Err(format_err_spanned!(
252                item_trait.vis,
253                "ink! chain extensions must have public visibility"
254            ))
255        }
256        if !item_trait.supertraits.is_empty() {
257            return Err(format_err_spanned!(
258                item_trait.supertraits,
259                "ink! chain extensions with super-traits are not supported, yet"
260            ))
261        }
262        Ok(())
263    }
264
265    /// Checks if the associated trait item type is a proper chain extension error code.
266    ///
267    /// # Errors
268    ///
269    /// - If the associated type is not called `ErrorCode`.
270    /// - If the associated type is generic, has where bounds or has a default type.
271    /// - If there are multiple associated `ErrorCode` types.
272    fn analyse_error_code(
273        item_type: &syn::TraitItemType,
274        previous: &mut Option<syn::TraitItemType>,
275    ) -> Result<()> {
276        if item_type.ident != "ErrorCode" {
277            return Err(format_err_spanned!(
278                item_type.ident,
279                "chain extensions expect an associated type with name `ErrorCode` but found {}",
280                item_type.ident,
281            ))
282        }
283        if !item_type.generics.params.is_empty() {
284            return Err(format_err_spanned!(
285                item_type.generics,
286                "generic chain extension `ErrorCode` types are not supported",
287            ))
288        }
289        if !item_type.bounds.is_empty() {
290            return Err(format_err_spanned!(
291                item_type.bounds,
292                "bounded chain extension `ErrorCode` types are not supported",
293            ))
294        }
295        if item_type.default.is_none() {
296            return Err(format_err_spanned!(
297                item_type,
298                "expected a default type for the ink! chain extension ErrorCode",
299            ))
300        }
301        match previous {
302            Some(previous_error_code) => {
303                return Err(format_err_spanned!(
304                    item_type,
305                    "encountered duplicate `ErrorCode` associated types for the chain extension",
306                )).map_err(|err| err.into_combine(format_err_spanned!(
307                    previous_error_code,
308                    "first `ErrorCode` associated type here",
309                )))
310            }
311            None => {
312                *previous = Some(item_type.clone());
313            }
314        }
315        Ok(())
316    }
317
318    /// Returns `Ok` if all trait items respect the requirements for an ink! chain extension.
319    ///
320    /// # Errors
321    ///
322    /// - If the trait contains an unsupported trait item such as
323    ///     - associated constants (`const`)
324    ///     - associated types (`type`)
325    ///     - macros definitions or usages
326    ///     - unknown token sequences (`Verbatim`s)
327    ///     - methods with default implementations
328    /// - If the trait contains methods which do not respect the ink! trait definition requirements:
329    ///     - All trait methods must not have a `self` receiver.
330    ///     - All trait methods must have an `#[ink(extension = N: u32)]` attribute that is the ID that
331    ///       corresponds with the function ID of the respective chain extension call.
332    ///
333    /// # Note
334    ///
335    /// The input Rust trait item is going to be replaced with a concrete chain extension type definition
336    /// as a result of this procedural macro invocation.
337    fn analyse_items(
338        item_trait: &syn::ItemTrait,
339    ) -> Result<(syn::TraitItemType, Vec<ChainExtensionMethod>)> {
340        let mut methods = Vec::new();
341        let mut seen_ids = HashMap::new();
342        let mut error_code = None;
343        for trait_item in &item_trait.items {
344            match trait_item {
345                syn::TraitItem::Const(const_trait_item) => {
346                    return Err(format_err_spanned!(
347                        const_trait_item,
348                        "associated constants in ink! chain extensions are not supported, yet"
349                    ))
350                }
351                syn::TraitItem::Macro(macro_trait_item) => {
352                    return Err(format_err_spanned!(
353                        macro_trait_item,
354                        "macros in ink! chain extensions are not supported"
355                    ))
356                }
357                syn::TraitItem::Type(type_trait_item) => {
358                    Self::analyse_error_code(type_trait_item, &mut error_code)?;
359                }
360                syn::TraitItem::Verbatim(verbatim) => {
361                    return Err(format_err_spanned!(
362                        verbatim,
363                        "encountered unsupported item in ink! chain extensions"
364                    ))
365                }
366                syn::TraitItem::Method(method_trait_item) => {
367                    let method = Self::analyse_methods(method_trait_item)?;
368                    let method_id = method.id();
369                    if let Some(previous) = seen_ids.get(&method_id) {
370                        return Err(format_err!(
371                            method.span(),
372                            "encountered duplicate extension identifiers for the same chain extension",
373                        ).into_combine(format_err!(
374                            *previous,
375                            "previous duplicate extension identifier here",
376                        )))
377                    }
378                    seen_ids.insert(method_id, method.span());
379                    methods.push(method);
380                }
381                unknown => {
382                    return Err(format_err_spanned!(
383                        unknown,
384                        "encountered unknown or unsupported item in ink! chain extensions"
385                    ))
386                }
387            }
388        }
389        let error_code = match error_code {
390            Some(error_code) => error_code,
391            None => {
392                return Err(format_err_spanned!(
393                    item_trait,
394                    "missing ErrorCode associated type from ink! chain extension",
395                ))
396            }
397        };
398        Ok((error_code, methods))
399    }
400
401    /// Analyses a chain extension method.
402    ///
403    /// # Errors
404    ///
405    /// - If the method is missing the `#[ink(extension = N: u32)]` attribute.
406    /// - If the method has a `self` receiver.
407    /// - If the method declared as `unsafe`, `const` or `async`.
408    /// - If the method has some explicit API.
409    /// - If the method is variadic or has generic parameters.
410    fn analyse_methods(method: &syn::TraitItemMethod) -> Result<ChainExtensionMethod> {
411        if let Some(default_impl) = &method.default {
412            return Err(format_err_spanned!(
413                default_impl,
414                "ink! chain extension methods with default implementations are not supported"
415            ))
416        }
417        if let Some(constness) = &method.sig.constness {
418            return Err(format_err_spanned!(
419                constness,
420                "const ink! chain extension methods are not supported"
421            ))
422        }
423        if let Some(asyncness) = &method.sig.asyncness {
424            return Err(format_err_spanned!(
425                asyncness,
426                "async ink! chain extension methods are not supported"
427            ))
428        }
429        if let Some(unsafety) = &method.sig.unsafety {
430            return Err(format_err_spanned!(
431                unsafety,
432                "unsafe ink! chain extension methods are not supported"
433            ))
434        }
435        if let Some(abi) = &method.sig.abi {
436            return Err(format_err_spanned!(
437                abi,
438                "ink! chain extension methods with non default ABI are not supported"
439            ))
440        }
441        if let Some(variadic) = &method.sig.variadic {
442            return Err(format_err_spanned!(
443                variadic,
444                "variadic ink! chain extension methods are not supported"
445            ))
446        }
447        if !method.sig.generics.params.is_empty() {
448            return Err(format_err_spanned!(
449                method.sig.generics.params,
450                "generic ink! chain extension methods are not supported"
451            ))
452        }
453        match ir::first_ink_attribute(&method.attrs)?
454                .map(|attr| attr.first().kind().clone()) {
455            Some(ir::AttributeArg::Extension(extension)) => {
456                Self::analyse_chain_extension_method(method, extension)
457            }
458            Some(_unsupported) => {
459                Err(format_err_spanned!(
460                    method,
461                    "encountered unsupported ink! attribute for ink! chain extension method. expected #[ink(extension = N: u32)] attribute"
462                ))
463            }
464            None => {
465                Err(format_err_spanned!(
466                    method,
467                    "missing #[ink(extension = N: u32)] flag on ink! chain extension method"
468                ))
469            }
470        }
471    }
472
473    /// Analyses the properties of an ink! chain extension method.
474    ///
475    /// # Errors
476    ///
477    /// - If the chain extension method has a `self` receiver as first argument.
478    fn analyse_chain_extension_method(
479        item_method: &syn::TraitItemMethod,
480        extension: ExtensionId,
481    ) -> Result<ChainExtensionMethod> {
482        let (ink_attrs, _) = ir::sanitize_attributes(
483            item_method.span(),
484            item_method.attrs.clone(),
485            &ir::AttributeArgKind::Extension,
486            |arg| {
487                match arg.kind() {
488                    ir::AttributeArg::Extension(_)
489                    | ir::AttributeArg::HandleStatus(_)
490                    | ir::AttributeArg::ReturnsResult(_) => Ok(()),
491                    _ => Err(None),
492                }
493            },
494        )?;
495        if let Some(receiver) = item_method.sig.receiver() {
496            return Err(format_err_spanned!(
497                receiver,
498                "ink! chain extension method must not have a `self` receiver",
499            ))
500        }
501        let result = ChainExtensionMethod {
502            id: extension,
503            item: item_method.clone(),
504            handle_status: ink_attrs.is_handle_status(),
505            returns_result: ink_attrs.is_returns_result(),
506        };
507        Ok(result)
508    }
509}
510
511#[cfg(test)]
512mod tests {
513    use super::*;
514
515    /// Checks if the token stream in `$chain_extension` results in the expected error message.
516    macro_rules! assert_ink_chain_extension_eq_err {
517        ( error: $err_str:literal, $($chain_extension:tt)* ) => {
518            assert_eq!(
519                <ChainExtension as TryFrom<syn::ItemTrait>>::try_from(syn::parse_quote! {
520                    $( $chain_extension )*
521                })
522                .map_err(|err| err.to_string()),
523                Err(
524                    $err_str.to_string()
525                )
526            )
527        };
528    }
529
530    #[test]
531    fn unsafe_chain_extension_is_denied() {
532        assert_ink_chain_extension_eq_err!(
533            error: "ink! chain extensions cannot be unsafe",
534            pub unsafe trait MyChainExtension {}
535        );
536    }
537
538    #[test]
539    fn auto_chain_extension_is_denied() {
540        assert_ink_chain_extension_eq_err!(
541            error: "ink! chain extensions cannot be automatically implemented traits",
542            pub auto trait MyChainExtension {}
543        );
544    }
545
546    #[test]
547    fn non_pub_chain_extension_is_denied() {
548        assert_ink_chain_extension_eq_err!(
549            error: "ink! chain extensions must have public visibility",
550            trait MyChainExtension {}
551        );
552        assert_ink_chain_extension_eq_err!(
553            error: "ink! chain extensions must have public visibility",
554            pub(crate) trait MyChainExtension {}
555        );
556    }
557
558    #[test]
559    fn generic_chain_extension_is_denied() {
560        assert_ink_chain_extension_eq_err!(
561            error: "ink! chain extensions must not be generic",
562            pub trait MyChainExtension<T> {}
563        );
564    }
565
566    #[test]
567    fn chain_extension_with_supertraits_is_denied() {
568        assert_ink_chain_extension_eq_err!(
569            error: "ink! chain extensions with super-traits are not supported, yet",
570            pub trait MyChainExtension: SuperChainExtension {}
571        );
572    }
573
574    #[test]
575    fn chain_extension_containing_const_item_is_denied() {
576        assert_ink_chain_extension_eq_err!(
577            error: "associated constants in ink! chain extensions are not supported, yet",
578            pub trait MyChainExtension {
579                const T: i32;
580            }
581        );
582    }
583
584    #[test]
585    fn chain_extension_containing_invalid_associated_type_is_denied() {
586        assert_ink_chain_extension_eq_err!(
587            error: "chain extensions expect an associated type with name `ErrorCode` but found Type",
588            pub trait MyChainExtension {
589                type Type;
590            }
591        );
592    }
593
594    #[test]
595    fn chain_extension_with_invalid_error_code() {
596        assert_ink_chain_extension_eq_err!(
597            error: "chain extensions expect an associated type with name `ErrorCode` but found IncorrectName",
598            pub trait MyChainExtension {
599                type IncorrectName = ();
600            }
601        );
602        assert_ink_chain_extension_eq_err!(
603            error: "generic chain extension `ErrorCode` types are not supported",
604            pub trait MyChainExtension {
605                type ErrorCode<T> = ();
606            }
607        );
608        assert_ink_chain_extension_eq_err!(
609            error: "bounded chain extension `ErrorCode` types are not supported",
610            pub trait MyChainExtension {
611                type ErrorCode: Copy = ();
612            }
613        );
614        assert_ink_chain_extension_eq_err!(
615            error: "expected a default type for the ink! chain extension ErrorCode",
616            pub trait MyChainExtension {
617                type ErrorCode;
618            }
619        );
620        assert_ink_chain_extension_eq_err!(
621            error: "encountered duplicate `ErrorCode` associated types for the chain extension",
622            pub trait MyChainExtension {
623                type ErrorCode = ();
624                type ErrorCode = ();
625            }
626        );
627    }
628
629    #[test]
630    fn chain_extension_containing_macro_is_denied() {
631        assert_ink_chain_extension_eq_err!(
632            error: "macros in ink! chain extensions are not supported",
633            pub trait MyChainExtension {
634                my_macro_call!();
635            }
636        );
637    }
638
639    #[test]
640    fn chain_extension_containing_non_flagged_method_is_denied() {
641        assert_ink_chain_extension_eq_err!(
642            error: "missing #[ink(extension = N: u32)] flag on ink! chain extension method",
643            pub trait MyChainExtension {
644                fn non_flagged_1(&self);
645            }
646        );
647        assert_ink_chain_extension_eq_err!(
648            error: "missing #[ink(extension = N: u32)] flag on ink! chain extension method",
649            pub trait MyChainExtension {
650                fn non_flagged_2(&mut self);
651            }
652        );
653        assert_ink_chain_extension_eq_err!(
654            error: "missing #[ink(extension = N: u32)] flag on ink! chain extension method",
655            pub trait MyChainExtension {
656                fn non_flagged_3() -> Self;
657            }
658        );
659    }
660
661    #[test]
662    fn chain_extension_containing_default_implemented_methods_is_denied() {
663        assert_ink_chain_extension_eq_err!(
664            error: "ink! chain extension methods with default implementations are not supported",
665            pub trait MyChainExtension {
666                #[ink(constructor)]
667                fn default_implemented() -> Self {}
668            }
669        );
670    }
671
672    #[test]
673    fn chain_extension_containing_const_methods_is_denied() {
674        assert_ink_chain_extension_eq_err!(
675            error: "const ink! chain extension methods are not supported",
676            pub trait MyChainExtension {
677                #[ink(extension = 1)]
678                const fn const_constructor() -> Self;
679            }
680        );
681    }
682
683    #[test]
684    fn chain_extension_containing_async_methods_is_denied() {
685        assert_ink_chain_extension_eq_err!(
686            error: "async ink! chain extension methods are not supported",
687            pub trait MyChainExtension {
688                #[ink(extension = 1)]
689                async fn const_constructor() -> Self;
690            }
691        );
692    }
693
694    #[test]
695    fn chain_extension_containing_unsafe_methods_is_denied() {
696        assert_ink_chain_extension_eq_err!(
697            error: "unsafe ink! chain extension methods are not supported",
698            pub trait MyChainExtension {
699                #[ink(extension = 1)]
700                unsafe fn const_constructor() -> Self;
701            }
702        );
703    }
704
705    #[test]
706    fn chain_extension_containing_methods_using_explicit_abi_is_denied() {
707        assert_ink_chain_extension_eq_err!(
708            error: "ink! chain extension methods with non default ABI are not supported",
709            pub trait MyChainExtension {
710                #[ink(extension = 1)]
711                extern fn const_constructor() -> Self;
712            }
713        );
714    }
715
716    #[test]
717    fn chain_extension_containing_variadic_methods_is_denied() {
718        assert_ink_chain_extension_eq_err!(
719            error: "variadic ink! chain extension methods are not supported",
720            pub trait MyChainExtension {
721                #[ink(extension = 1)]
722                fn const_constructor(...) -> Self;
723            }
724        );
725    }
726
727    #[test]
728    fn chain_extension_containing_generic_methods_is_denied() {
729        assert_ink_chain_extension_eq_err!(
730            error: "generic ink! chain extension methods are not supported",
731            pub trait MyChainExtension {
732                #[ink(extension = 1)]
733                fn const_constructor<T>() -> Self;
734            }
735        );
736    }
737
738    #[test]
739    fn chain_extension_containing_method_with_unsupported_ink_attribute_is_denied() {
740        assert_ink_chain_extension_eq_err!(
741            error: "\
742                encountered unsupported ink! attribute for ink! chain extension method. \
743                expected #[ink(extension = N: u32)] attribute",
744            pub trait MyChainExtension {
745                #[ink(message)]
746                fn unsupported_ink_attribute(&self);
747            }
748        );
749        assert_ink_chain_extension_eq_err!(
750            error: "unknown ink! attribute (path)",
751            pub trait MyChainExtension {
752                #[ink(unknown)]
753                fn unknown_ink_attribute(&self);
754            }
755        );
756    }
757
758    #[test]
759    fn chain_extension_containing_method_with_invalid_marker() {
760        assert_ink_chain_extension_eq_err!(
761            error: "could not parse `N` in `#[ink(extension = N)]` into a `u32` integer",
762            pub trait MyChainExtension {
763                #[ink(extension = -1)]
764                fn has_self_receiver();
765            }
766        );
767        let too_large = (u32::MAX as u64) + 1;
768        assert_ink_chain_extension_eq_err!(
769            error: "could not parse `N` in `#[ink(extension = N)]` into a `u32` integer",
770            pub trait MyChainExtension {
771                #[ink(extension = #too_large)]
772                fn has_self_receiver();
773            }
774        );
775        assert_ink_chain_extension_eq_err!(
776            error: "expected `u32` integer type for `N` in #[ink(extension = N)]",
777            pub trait MyChainExtension {
778                #[ink(extension = "Hello, World!")]
779                fn has_self_receiver();
780            }
781        );
782        assert_ink_chain_extension_eq_err!(
783            error: "encountered #[ink(extension)] that is missing its `id` parameter. \
784                    Did you mean #[ink(extension = id: u32)] ?",
785            pub trait MyChainExtension {
786                #[ink(extension)]
787                fn has_self_receiver();
788            }
789        );
790
791        assert_ink_chain_extension_eq_err!(
792            error: "encountered duplicate ink! attribute",
793            pub trait MyChainExtension {
794                #[ink(extension = 42)]
795                #[ink(extension = 42)]
796                fn duplicate_attributes() -> Self;
797            }
798        );
799        assert_ink_chain_extension_eq_err!(
800            error: "encountered ink! attribute arguments with equal kinds",
801            pub trait MyChainExtension {
802                #[ink(extension = 1)]
803                #[ink(extension = 2)]
804                fn duplicate_attributes() -> Self;
805            }
806        );
807        assert_ink_chain_extension_eq_err!(
808            error: "encountered conflicting ink! attribute argument",
809            pub trait MyChainExtension {
810                #[ink(extension = 1)]
811                #[ink(message)]
812                fn conflicting_attributes() -> Self;
813            }
814        );
815    }
816
817    #[test]
818    fn chain_extension_containing_method_with_self_receiver_is_denied() {
819        assert_ink_chain_extension_eq_err!(
820            error: "ink! chain extension method must not have a `self` receiver",
821            pub trait MyChainExtension {
822                type ErrorCode = ();
823
824                #[ink(extension = 1)]
825                fn has_self_receiver(&self) -> Self;
826            }
827        );
828        assert_ink_chain_extension_eq_err!(
829            error: "ink! chain extension method must not have a `self` receiver",
830            pub trait MyChainExtension {
831                type ErrorCode = ();
832
833                #[ink(extension = 1)]
834                fn has_self_receiver(&mut self) -> Self;
835            }
836        );
837        assert_ink_chain_extension_eq_err!(
838            error: "ink! chain extension method must not have a `self` receiver",
839            pub trait MyChainExtension {
840                type ErrorCode = ();
841
842                #[ink(extension = 1)]
843                fn has_self_receiver(self) -> Self;
844            }
845        );
846        assert_ink_chain_extension_eq_err!(
847            error: "ink! chain extension method must not have a `self` receiver",
848            pub trait MyChainExtension {
849                type ErrorCode = ();
850
851                #[ink(extension = 1)]
852                fn has_self_receiver(self: &Self) -> Self;
853            }
854        );
855        assert_ink_chain_extension_eq_err!(
856            error: "ink! chain extension method must not have a `self` receiver",
857            pub trait MyChainExtension {
858                type ErrorCode = ();
859
860                #[ink(extension = 1)]
861                fn has_self_receiver(self: Self) -> Self;
862            }
863        );
864    }
865
866    #[test]
867    fn chain_extension_with_overlapping_extension_ids() {
868        assert_ink_chain_extension_eq_err!(
869            error: "encountered duplicate extension identifiers for the same chain extension",
870            pub trait MyChainExtension {
871                #[ink(extension = 1)]
872                fn same_id_1();
873                #[ink(extension = 1)]
874                fn same_id_2();
875            }
876        );
877    }
878
879    #[test]
880    fn chain_extension_is_ok() {
881        let chain_extension = <ChainExtension as TryFrom<syn::ItemTrait>>::try_from(syn::parse_quote! {
882                pub trait MyChainExtension {
883                    type ErrorCode = ();
884
885                    #[ink(extension = 1)]
886                    fn extension_1();
887                    #[ink(extension = 2)]
888                    fn extension_2(input: i32);
889                    #[ink(extension = 3)]
890                    fn extension_3() -> i32;
891                    #[ink(extension = 4)]
892                    fn extension_4(input: i32) -> i32;
893                    #[ink(extension = 5)]
894                    fn extension_5(in1: i8, in2: i16, in3: i32, in4: i64) -> (u8, u16, u32, u64);
895                }
896            }).unwrap();
897        assert_eq!(chain_extension.methods.len(), 5);
898        for (actual, expected) in chain_extension
899            .methods
900            .iter()
901            .map(|method| method.id())
902            .zip(1..=5u32)
903        {
904            assert_eq!(actual.index, expected);
905        }
906        for (actual, expected) in chain_extension
907            .methods
908            .iter()
909            .map(|method| method.ident().to_string())
910            .zip(
911                [
912                    "extension_1",
913                    "extension_2",
914                    "extension_3",
915                    "extension_4",
916                    "extension_5",
917                ]
918                .iter()
919                .map(ToString::to_string),
920            )
921        {
922            assert_eq!(actual, expected);
923        }
924    }
925
926    #[test]
927    fn chain_extension_with_params_is_ok() {
928        let chain_extension =
929            <ChainExtension as TryFrom<syn::ItemTrait>>::try_from(syn::parse_quote! {
930                pub trait MyChainExtension {
931                    type ErrorCode = ();
932
933                    #[ink(extension = 1, handle_status = false)]
934                    fn extension_a();
935                    #[ink(extension = 2, returns_result = false)]
936                    fn extension_b();
937                    #[ink(extension = 3, handle_status = false, returns_result = false)]
938                    fn extension_c();
939
940                    #[ink(extension = 4)]
941                    #[ink(handle_status = false)]
942                    fn extension_d();
943                    #[ink(extension = 5)]
944                    #[ink(returns_result = false)]
945                    fn extension_e();
946                    #[ink(extension = 6)]
947                    #[ink(handle_status = false)]
948                    #[ink(returns_result = false)]
949                    fn extension_f();
950                }
951            })
952            .unwrap();
953        let expected_methods = 6;
954        assert_eq!(chain_extension.methods.len(), expected_methods);
955        for (actual, expected) in chain_extension
956            .methods
957            .iter()
958            .map(|method| method.id())
959            .zip(1..=expected_methods as u32)
960        {
961            assert_eq!(actual.index, expected);
962        }
963        for (actual, expected) in chain_extension
964            .methods
965            .iter()
966            .map(|method| method.ident().to_string())
967            .zip(
968                [
969                    "extension_a",
970                    "extension_b",
971                    "extension_c",
972                    "extension_d",
973                    "extension_e",
974                    "extension_f",
975                ]
976                .iter()
977                .map(ToString::to_string),
978            )
979        {
980            assert_eq!(actual, expected);
981        }
982    }
983}