ink_codegen/generator/
metadata.rs

1// Copyright (C) Use Ink (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::GenerateCode;
16use ::core::iter;
17use derive_more::From;
18use ir::{
19    Callable as _,
20    HexLiteral,
21    IsDocAttribute,
22};
23use proc_macro2::{
24    Ident,
25    TokenStream as TokenStream2,
26};
27use quote::{
28    quote,
29    quote_spanned,
30};
31use syn::{
32    parse_quote,
33    spanned::Spanned as _,
34};
35
36/// Generates code to generate the metadata of the contract.
37#[derive(From)]
38pub struct Metadata<'a> {
39    /// The contract to generate code for.
40    contract: &'a ir::Contract,
41}
42impl_as_ref_for_generator!(Metadata);
43
44impl GenerateCode for Metadata<'_> {
45    fn generate_code(&self) -> TokenStream2 {
46        let contract = self.generate_contract();
47        let layout = self.generate_layout();
48
49        quote! {
50            #[cfg(feature = "std")]
51            #[cfg(not(feature = "ink-as-dependency"))]
52            #[cfg(not(ink_abi = "sol"))]
53            const _: () = {
54                #[no_mangle]
55                pub fn __ink_generate_metadata() -> ::ink::metadata::InkProject  {
56                    let layout = #layout;
57                    ::ink::metadata::layout::ValidateLayout::validate(&layout).unwrap_or_else(|error| {
58                        ::core::panic!("metadata ink! generation failed: {}", error)
59                    });
60                    ::ink::metadata::InkProject::new(layout, #contract)
61                }
62            };
63        }
64    }
65}
66
67impl Metadata<'_> {
68    fn generate_layout(&self) -> TokenStream2 {
69        let storage_span = self.contract.module().storage().span();
70        let storage_ident = self.contract.module().storage().ident();
71        let key = quote! { <#storage_ident as ::ink::storage::traits::StorageKey>::KEY };
72
73        let layout_key = quote! {
74            <::ink::metadata::layout::LayoutKey
75                as ::core::convert::From<::ink::primitives::Key>>::from(#key)
76        };
77        quote_spanned!(storage_span=>
78            // Wrap the layout of the contract into the `RootLayout`, because
79            // contract storage key is reserved for all packed fields
80            ::ink::metadata::layout::Layout::Root(::ink::metadata::layout::RootLayout::new(
81                #layout_key,
82                <#storage_ident as ::ink::storage::traits::StorageLayout>::layout(
83                    &#key,
84                ),
85                ::ink::scale_info::meta_type::<#storage_ident>(),
86            ))
87        )
88    }
89
90    fn generate_contract(&self) -> TokenStream2 {
91        let constructors = self.generate_constructors();
92        let messages = self.generate_messages();
93        let docs = self
94            .contract
95            .module()
96            .attrs()
97            .iter()
98            .filter_map(|attr| attr.extract_docs());
99        let error_ty = syn::parse_quote! {
100            ::ink::LangError
101        };
102        let error = generate_type_spec(&error_ty);
103        let environment = self.generate_environment();
104        quote! {
105            ::ink::metadata::ContractSpec::new()
106                .constructors([
107                    #( #constructors ),*
108                ])
109                .messages([
110                    #( #messages ),*
111                ])
112                .events(
113                    ::ink::collect_events()
114                )
115                .docs([
116                    #( #docs ),*
117                ])
118                .lang_error(
119                     #error
120                )
121                .environment(
122                    #environment
123                )
124                .done()
125        }
126    }
127
128    /// Generates ink! metadata for all ink! smart contract constructors.
129    #[allow(clippy::redundant_closure)] // We are getting arcane lifetime errors otherwise.
130    fn generate_constructors(&self) -> impl Iterator<Item = TokenStream2> + '_ {
131        self.contract
132            .module()
133            .impls()
134            .flat_map(|item_impl| item_impl.iter_constructors())
135            .map(|constructor| self.generate_constructor(constructor))
136    }
137
138    /// Generates ink! metadata for a single ink! constructor.
139    fn generate_constructor(
140        &self,
141        constructor: ir::CallableWithSelector<ir::Constructor>,
142    ) -> TokenStream2 {
143        let span = constructor.span();
144        let docs = constructor
145            .attrs()
146            .iter()
147            .filter_map(|attr| attr.extract_docs());
148        let selector_bytes = constructor.composed_selector().hex_lits();
149        let selector_id = constructor.composed_selector().into_be_u32();
150        let is_payable = constructor.is_payable();
151        let is_default = constructor.is_default();
152        let constructor = constructor.callable();
153        let name = constructor
154            .name()
155            .map(ToString::to_string)
156            .unwrap_or_else(|| constructor.ident().to_string());
157        let args = constructor.inputs().map(Self::generate_dispatch_argument);
158        let storage_ident = self.contract.module().storage().ident();
159        let ret_ty = Self::generate_constructor_return_type(storage_ident, selector_id);
160        let cfg_attrs = constructor.get_cfg_attrs(span);
161        quote_spanned!(span=>
162            #( #cfg_attrs )*
163            ::ink::metadata::ConstructorSpec::from_label(#name)
164                .selector([
165                    #( #selector_bytes ),*
166                ])
167                .args([
168                    #( #args ),*
169                ])
170                .payable(#is_payable)
171                .default(#is_default)
172                .returns(#ret_ty)
173                .docs([
174                    #( #docs ),*
175                ])
176                .done()
177        )
178    }
179
180    /// Generates the ink! metadata for the given parameter and parameter type.
181    fn generate_dispatch_argument(pat_type: &syn::PatType) -> TokenStream2 {
182        let ident = match &*pat_type.pat {
183            syn::Pat::Ident(ident) => &ident.ident,
184            _ => unreachable!("encountered ink! dispatch input with missing identifier"),
185        };
186        let type_spec = generate_type_spec(&pat_type.ty);
187        quote! {
188            ::ink::metadata::MessageParamSpec::new(::core::stringify!(#ident))
189                .of_type(#type_spec)
190                .done()
191        }
192    }
193    /// Generates the ink! metadata for all ink! smart contract messages.
194    fn generate_messages(&self) -> Vec<TokenStream2> {
195        let mut messages = Vec::new();
196        let inherent_messages = self.generate_inherent_messages();
197        let trait_messages = self.generate_trait_messages();
198        messages.extend(inherent_messages);
199        messages.extend(trait_messages);
200        messages
201    }
202
203    /// Generates the ink! metadata for all inherent ink! smart contract messages.
204    fn generate_inherent_messages(&self) -> Vec<TokenStream2> {
205        self.contract
206            .module()
207            .impls()
208            .filter(|item_impl| item_impl.trait_path().is_none())
209            .flat_map(|item_impl| item_impl.iter_messages())
210            .map(|message| {
211                let span = message.span();
212                let docs = message
213                    .attrs()
214                    .iter()
215                    .filter_map(|attr| attr.extract_docs());
216                let selector_bytes = message.composed_selector().hex_lits();
217                let is_payable = message.is_payable();
218                let is_default = message.is_default();
219                let message = message.callable();
220                let mutates = message.receiver().is_ref_mut();
221                let name = message
222                    .name()
223                    .map(ToString::to_string)
224                    .unwrap_or_else(|| message.ident().to_string());
225                let args = message.inputs().map(Self::generate_dispatch_argument);
226                let cfg_attrs = message.get_cfg_attrs(span);
227                let ret_ty =
228                    Self::generate_message_return_type(&message.wrapped_output());
229                quote_spanned!(span =>
230                    #( #cfg_attrs )*
231                    ::ink::metadata::MessageSpec::from_label(#name)
232                        .selector([
233                            #( #selector_bytes ),*
234                        ])
235                        .args([
236                            #( #args ),*
237                        ])
238                        .returns(#ret_ty)
239                        .mutates(#mutates)
240                        .payable(#is_payable)
241                        .default(#is_default)
242                        .docs([
243                            #( #docs ),*
244                        ])
245                        .done()
246                )
247            })
248            .collect()
249    }
250
251    /// Generates the ink! metadata for all inherent ink! smart contract messages.
252    fn generate_trait_messages(&self) -> Vec<TokenStream2> {
253        let storage_ident = self.contract.module().storage().ident();
254        self.contract
255            .module()
256            .impls()
257            .filter_map(|item_impl| {
258                item_impl
259                    .trait_path()
260                    .map(|trait_path| {
261                        let trait_ident = item_impl.trait_ident().expect(
262                            "must have an ink! trait identifier if it is an ink! trait implementation"
263                        );
264                        iter::repeat((trait_ident, trait_path)).zip(item_impl.iter_messages())
265                    })
266            })
267            .flatten()
268            .map(|((trait_ident, trait_path), message)| {
269                let message_span = message.span();
270                let message_name = message
271                    .name()
272                    .map(ToString::to_string)
273                    .unwrap_or_else(|| message.ident().to_string());
274                let message_docs = message
275                    .attrs()
276                    .iter()
277                    .filter_map(|attr| attr.extract_docs());
278                let message_args = message
279                    .inputs()
280                    .map(Self::generate_dispatch_argument);
281                let cfg_attrs = message.get_cfg_attrs(message_span);
282                let mutates = message.receiver().is_ref_mut();
283                let local_id = message.local_id().hex_padded_suffixed();
284                let is_payable = quote! {{
285                    <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env>
286                        as #trait_path>::__ink_TraitInfo
287                        as ::ink::reflect::TraitMessageInfo<#local_id>>::PAYABLE
288                }};
289                let selector = quote! {{
290                    <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env>
291                        as #trait_path>::__ink_TraitInfo
292                        as ::ink::reflect::TraitMessageInfo<#local_id>>::SELECTOR
293                }};
294                let ret_ty = Self::generate_message_return_type(&message.wrapped_output());
295                let label = [trait_ident.to_string(), message_name].join("::");
296                quote_spanned!(message_span=>
297                    #( #cfg_attrs )*
298                    ::ink::metadata::MessageSpec::from_label(#label)
299                        .selector(#selector)
300                        .args([
301                            #( #message_args ),*
302                        ])
303                        .returns(#ret_ty)
304                        .mutates(#mutates)
305                        .payable(#is_payable)
306                        .docs([
307                            #( #message_docs ),*
308                        ])
309                        .done()
310                )
311            })
312            .collect()
313    }
314
315    /// Generates ink! metadata for the given return type.
316    fn generate_message_return_type(ret_ty: &syn::Type) -> TokenStream2 {
317        let type_spec = generate_type_spec(ret_ty);
318        quote! {
319            ::ink::metadata::ReturnTypeSpec::new(#type_spec)
320        }
321    }
322
323    /// Generates ink! metadata for the storage with given selector and ident.
324    fn generate_constructor_return_type(
325        storage_ident: &Ident,
326        selector_id: u32,
327    ) -> TokenStream2 {
328        let span = storage_ident.span();
329        let constructor_info = quote_spanned!(span =>
330            < #storage_ident as ::ink::reflect::DispatchableConstructorInfo<#selector_id>>
331        );
332
333        quote_spanned!(span=>
334            ::ink::metadata::ReturnTypeSpec::new(if #constructor_info::IS_RESULT {
335                ::ink::metadata::TypeSpec::with_name_str::<
336                    ::ink::ConstructorResult<::core::result::Result<(), #constructor_info::Error>>,
337                >("ink_primitives::ConstructorResult")
338            } else {
339                ::ink::metadata::TypeSpec::with_name_str::<
340                    ::ink::ConstructorResult<()>,
341                >("ink_primitives::ConstructorResult")
342            })
343        )
344    }
345
346    fn generate_environment(&self) -> TokenStream2 {
347        let span = self.contract.module().span();
348
349        let account_id: syn::Type = parse_quote!(AccountId);
350        let balance: syn::Type = parse_quote!(Balance);
351        let hash: syn::Type = parse_quote!(Hash);
352        let timestamp: syn::Type = parse_quote!(Timestamp);
353        let block_number: syn::Type = parse_quote!(BlockNumber);
354        let chain_extension: syn::Type = parse_quote!(ChainExtension);
355
356        let account_id = generate_type_spec(&account_id);
357        let balance = generate_type_spec(&balance);
358        let hash = generate_type_spec(&hash);
359        let timestamp = generate_type_spec(&timestamp);
360        let block_number = generate_type_spec(&block_number);
361        let chain_extension = generate_type_spec(&chain_extension);
362        let buffer_size_const = quote!(::ink::env::BUFFER_SIZE);
363        quote_spanned!(span=>
364            ::ink::metadata::EnvironmentSpec::new()
365                .account_id(#account_id)
366                .balance(#balance)
367                .hash(#hash)
368                .timestamp(#timestamp)
369                .block_number(#block_number)
370                .chain_extension(#chain_extension)
371                .max_event_topics(MAX_EVENT_TOPICS)
372                .static_buffer_size(#buffer_size_const)
373                .done()
374        )
375    }
376}
377
378/// Generates the ink! metadata for the given type.
379pub fn generate_type_spec(ty: &syn::Type) -> TokenStream2 {
380    fn without_display_name(ty: &syn::Type) -> TokenStream2 {
381        quote! { ::ink::metadata::TypeSpec::of_type::<#ty>() }
382    }
383
384    if let syn::Type::Path(type_path) = ty {
385        if type_path.qself.is_some() {
386            return without_display_name(ty)
387        }
388        let path = &type_path.path;
389        if path.segments.is_empty() {
390            return without_display_name(ty)
391        }
392        let segs = path
393            .segments
394            .iter()
395            .map(|seg| &seg.ident)
396            .collect::<Vec<_>>();
397        quote! {
398            ::ink::metadata::TypeSpec::with_name_segs::<#ty, _>(
399                ::core::iter::Iterator::map(
400                    ::core::iter::IntoIterator::into_iter([ #( ::core::stringify!(#segs) ),* ]),
401                    ::core::convert::AsRef::as_ref
402                )
403            )
404        }
405    } else {
406        without_display_name(ty)
407    }
408}
409
410#[cfg(test)]
411mod tests {
412    use super::*;
413
414    /// Extracts and collects the contents of the Rust documentation attributes.
415    fn extract_doc_attributes(attrs: &[syn::Attribute]) -> Vec<String> {
416        attrs
417            .iter()
418            .filter_map(|attr| attr.extract_docs())
419            .collect()
420    }
421
422    #[test]
423    fn extract_doc_comments_works() {
424        assert_eq!(
425            extract_doc_attributes(&[syn::parse_quote!( #[doc = r"content"] )]),
426            vec!["content".to_string()],
427        );
428        assert_eq!(
429            extract_doc_attributes(&[syn::parse_quote!(
430                /// content
431            )]),
432            vec![" content".to_string()],
433        );
434        assert_eq!(
435            extract_doc_attributes(&[syn::parse_quote!(
436                /**
437                 * Multi-line comments
438                 * may span many,
439                 * many lines
440                 */
441            )]),
442            vec![r"
443                 * Multi-line comments
444                 * may span many,
445                 * many lines
446                 "
447            .to_string()],
448        );
449        assert_eq!(
450            extract_doc_attributes(&[
451                syn::parse_quote!(
452                    /// multiple
453                ),
454                syn::parse_quote!(
455                    /// single
456                ),
457                syn::parse_quote!(
458                    /// line
459                ),
460                syn::parse_quote!(
461                    /// comments
462                ),
463            ]),
464            vec![
465                " multiple".to_string(),
466                " single".to_string(),
467                " line".to_string(),
468                " comments".to_string(),
469            ],
470        );
471        assert_eq!(
472            extract_doc_attributes(&[
473                syn::parse_quote!( #[doc = r"a"] ),
474                syn::parse_quote!( #[non_doc] ),
475                syn::parse_quote!( #[doc = r"b"] ),
476                syn::parse_quote!( #[derive(NonDoc)] ),
477                syn::parse_quote!( #[doc = r"c"] ),
478                syn::parse_quote!( #[docker = false] ),
479                syn::parse_quote!( #[doc = r"d"] ),
480                syn::parse_quote!( #[doc(Nope)] ),
481                syn::parse_quote!( #[doc = r"e"] ),
482            ]),
483            vec![
484                "a".to_string(),
485                "b".to_string(),
486                "c".to_string(),
487                "d".to_string(),
488                "e".to_string(),
489            ],
490        )
491    }
492}