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                #[unsafe(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.normalized_name();
154        let args = constructor.inputs().map(Self::generate_dispatch_argument);
155        let storage_ident = self.contract.module().storage().ident();
156        let ret_ty = Self::generate_constructor_return_type(storage_ident, selector_id);
157        let cfg_attrs = constructor.get_cfg_attrs(span);
158        quote_spanned!(span=>
159            #( #cfg_attrs )*
160            ::ink::metadata::ConstructorSpec::from_label(#name)
161                .selector([
162                    #( #selector_bytes ),*
163                ])
164                .args([
165                    #( #args ),*
166                ])
167                .payable(#is_payable)
168                .default(#is_default)
169                .returns(#ret_ty)
170                .docs([
171                    #( #docs ),*
172                ])
173                .done()
174        )
175    }
176
177    /// Generates the ink! metadata for the given parameter and parameter type.
178    fn generate_dispatch_argument(pat_type: &syn::PatType) -> TokenStream2 {
179        let ident = match &*pat_type.pat {
180            syn::Pat::Ident(ident) => &ident.ident,
181            _ => unreachable!("encountered ink! dispatch input with missing identifier"),
182        };
183        let type_spec = generate_type_spec(&pat_type.ty);
184        quote! {
185            ::ink::metadata::MessageParamSpec::new(::core::stringify!(#ident))
186                .of_type(#type_spec)
187                .done()
188        }
189    }
190    /// Generates the ink! metadata for all ink! smart contract messages.
191    fn generate_messages(&self) -> Vec<TokenStream2> {
192        let mut messages = Vec::new();
193        let inherent_messages = self.generate_inherent_messages();
194        let trait_messages = self.generate_trait_messages();
195        messages.extend(inherent_messages);
196        messages.extend(trait_messages);
197        messages
198    }
199
200    /// Generates the ink! metadata for all inherent ink! smart contract messages.
201    fn generate_inherent_messages(&self) -> Vec<TokenStream2> {
202        self.contract
203            .module()
204            .impls()
205            .filter(|item_impl| item_impl.trait_path().is_none())
206            .flat_map(|item_impl| item_impl.iter_messages())
207            .map(|message| {
208                let span = message.span();
209                let docs = message
210                    .attrs()
211                    .iter()
212                    .filter_map(|attr| attr.extract_docs());
213                let selector_bytes = message.composed_selector().hex_lits();
214                let is_payable = message.is_payable();
215                let is_default = message.is_default();
216                let message = message.callable();
217                let mutates = message.receiver().is_ref_mut();
218                let name = message.normalized_name();
219                let args = message.inputs().map(Self::generate_dispatch_argument);
220                let cfg_attrs = message.get_cfg_attrs(span);
221                let ret_ty =
222                    Self::generate_message_return_type(&message.wrapped_output());
223                quote_spanned!(span =>
224                    #( #cfg_attrs )*
225                    ::ink::metadata::MessageSpec::from_label(#name)
226                        .selector([
227                            #( #selector_bytes ),*
228                        ])
229                        .args([
230                            #( #args ),*
231                        ])
232                        .returns(#ret_ty)
233                        .mutates(#mutates)
234                        .payable(#is_payable)
235                        .default(#is_default)
236                        .docs([
237                            #( #docs ),*
238                        ])
239                        .done()
240                )
241            })
242            .collect()
243    }
244
245    /// Generates the ink! metadata for all inherent ink! smart contract messages.
246    fn generate_trait_messages(&self) -> Vec<TokenStream2> {
247        let storage_ident = self.contract.module().storage().ident();
248        self.contract
249            .module()
250            .impls()
251            .filter_map(|item_impl| {
252                item_impl
253                    .trait_path()
254                    .map(|trait_path| {
255                        let trait_ident = item_impl.trait_ident().expect(
256                            "must have an ink! trait identifier if it is an ink! trait implementation"
257                        );
258                        iter::repeat((trait_ident, trait_path)).zip(item_impl.iter_messages())
259                    })
260            })
261            .flatten()
262            .map(|((trait_ident, trait_path), message)| {
263                let message_span = message.span();
264                let message_name = message
265                    .normalized_name();
266                let message_docs = message
267                    .attrs()
268                    .iter()
269                    .filter_map(|attr| attr.extract_docs());
270                let message_args = message
271                    .inputs()
272                    .map(Self::generate_dispatch_argument);
273                let cfg_attrs = message.get_cfg_attrs(message_span);
274                let mutates = message.receiver().is_ref_mut();
275                let local_id = message.local_id().hex_padded_suffixed();
276                let is_payable = quote! {{
277                    <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env>
278                        as #trait_path>::__ink_TraitInfo
279                        as ::ink::reflect::TraitMessageInfo<#local_id>>::PAYABLE
280                }};
281                let selector = quote! {{
282                    <<::ink::reflect::TraitDefinitionRegistry<<#storage_ident as ::ink::env::ContractEnv>::Env>
283                        as #trait_path>::__ink_TraitInfo
284                        as ::ink::reflect::TraitMessageInfo<#local_id>>::SELECTOR
285                }};
286                let ret_ty = Self::generate_message_return_type(&message.wrapped_output());
287                let label = [trait_ident.to_string(), message_name].join("::");
288                quote_spanned!(message_span=>
289                    #( #cfg_attrs )*
290                    ::ink::metadata::MessageSpec::from_label(#label)
291                        .selector(#selector)
292                        .args([
293                            #( #message_args ),*
294                        ])
295                        .returns(#ret_ty)
296                        .mutates(#mutates)
297                        .payable(#is_payable)
298                        .docs([
299                            #( #message_docs ),*
300                        ])
301                        .done()
302                )
303            })
304            .collect()
305    }
306
307    /// Generates ink! metadata for the given return type.
308    fn generate_message_return_type(ret_ty: &syn::Type) -> TokenStream2 {
309        let type_spec = generate_type_spec(ret_ty);
310        quote! {
311            ::ink::metadata::ReturnTypeSpec::new(#type_spec)
312        }
313    }
314
315    /// Generates ink! metadata for the storage with given selector and ident.
316    fn generate_constructor_return_type(
317        storage_ident: &Ident,
318        selector_id: u32,
319    ) -> TokenStream2 {
320        let span = storage_ident.span();
321        let constructor_info = quote_spanned!(span =>
322            < #storage_ident as ::ink::reflect::DispatchableConstructorInfo<#selector_id>>
323        );
324
325        quote_spanned!(span=>
326            ::ink::metadata::ReturnTypeSpec::new(if #constructor_info::IS_RESULT {
327                ::ink::metadata::TypeSpec::with_name_str::<
328                    ::ink::ConstructorResult<::core::result::Result<(), #constructor_info::Error>>,
329                >("ink_primitives::ConstructorResult")
330            } else {
331                ::ink::metadata::TypeSpec::with_name_str::<
332                    ::ink::ConstructorResult<()>,
333                >("ink_primitives::ConstructorResult")
334            })
335        )
336    }
337
338    fn generate_environment(&self) -> TokenStream2 {
339        let span = self.contract.module().span();
340
341        let account_id: syn::Type = parse_quote!(AccountId);
342        let balance: syn::Type = parse_quote!(Balance);
343        let hash: syn::Type = parse_quote!(Hash);
344        let timestamp: syn::Type = parse_quote!(Timestamp);
345        let block_number: syn::Type = parse_quote!(BlockNumber);
346
347        let account_id = generate_type_spec(&account_id);
348        let balance = generate_type_spec(&balance);
349        let hash = generate_type_spec(&hash);
350        let timestamp = generate_type_spec(&timestamp);
351        let block_number = generate_type_spec(&block_number);
352        let buffer_size_const = quote!(::ink::env::BUFFER_SIZE);
353        quote_spanned!(span=>
354            ::ink::metadata::EnvironmentSpec::new()
355                .account_id(#account_id)
356                .balance(#balance)
357                .hash(#hash)
358                .timestamp(#timestamp)
359                .block_number(#block_number)
360                .native_to_eth_ratio(NATIVE_TO_ETH_RATIO)
361                .static_buffer_size(#buffer_size_const)
362                .done()
363        )
364    }
365}
366
367/// Generates the ink! metadata for the given type.
368pub fn generate_type_spec(ty: &syn::Type) -> TokenStream2 {
369    fn without_display_name(ty: &syn::Type) -> TokenStream2 {
370        quote! { ::ink::metadata::TypeSpec::of_type::<#ty>() }
371    }
372
373    if let syn::Type::Path(type_path) = ty {
374        if type_path.qself.is_some() {
375            return without_display_name(ty)
376        }
377        let path = &type_path.path;
378        if path.segments.is_empty() {
379            return without_display_name(ty)
380        }
381        let segs = path
382            .segments
383            .iter()
384            .map(|seg| &seg.ident)
385            .collect::<Vec<_>>();
386        quote! {
387            ::ink::metadata::TypeSpec::with_name_segs::<#ty, _>(
388                ::core::iter::Iterator::map(
389                    ::core::iter::IntoIterator::into_iter([ #( ::core::stringify!(#segs) ),* ]),
390                    ::core::convert::AsRef::as_ref
391                )
392            )
393        }
394    } else {
395        without_display_name(ty)
396    }
397}
398
399#[cfg(test)]
400mod tests {
401    use super::*;
402
403    /// Extracts and collects the contents of the Rust documentation attributes.
404    fn extract_doc_attributes(attrs: &[syn::Attribute]) -> Vec<String> {
405        attrs
406            .iter()
407            .filter_map(|attr| attr.extract_docs())
408            .collect()
409    }
410
411    #[test]
412    fn extract_doc_comments_works() {
413        assert_eq!(
414            extract_doc_attributes(&[syn::parse_quote!( #[doc = r"content"] )]),
415            vec!["content".to_string()],
416        );
417        assert_eq!(
418            extract_doc_attributes(&[syn::parse_quote!(
419                /// content
420            )]),
421            vec![" content".to_string()],
422        );
423        assert_eq!(
424            extract_doc_attributes(&[syn::parse_quote!(
425                /**
426                 * Multi-line comments
427                 * may span many,
428                 * many lines
429                 */
430            )]),
431            vec![
432                r"
433                 * Multi-line comments
434                 * may span many,
435                 * many lines
436                 "
437                .to_string()
438            ],
439        );
440        assert_eq!(
441            extract_doc_attributes(&[
442                syn::parse_quote!(
443                    /// multiple
444                ),
445                syn::parse_quote!(
446                    /// single
447                ),
448                syn::parse_quote!(
449                    /// line
450                ),
451                syn::parse_quote!(
452                    /// comments
453                ),
454            ]),
455            vec![
456                " multiple".to_string(),
457                " single".to_string(),
458                " line".to_string(),
459                " comments".to_string(),
460            ],
461        );
462        assert_eq!(
463            extract_doc_attributes(&[
464                syn::parse_quote!( #[doc = r"a"] ),
465                syn::parse_quote!( #[non_doc] ),
466                syn::parse_quote!( #[doc = r"b"] ),
467                syn::parse_quote!( #[derive(NonDoc)] ),
468                syn::parse_quote!( #[doc = r"c"] ),
469                syn::parse_quote!( #[docker = false] ),
470                syn::parse_quote!( #[doc = r"d"] ),
471                syn::parse_quote!( #[doc(Nope)] ),
472                syn::parse_quote!( #[doc = r"e"] ),
473            ]),
474            vec![
475                "a".to_string(),
476                "b".to_string(),
477                "c".to_string(),
478                "d".to_string(),
479                "e".to_string(),
480            ],
481        )
482    }
483}