ink_codegen/generator/sol/
metadata.rs

1// Copyright (C) ink! contributors.
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 derive_more::From;
16use ir::{
17    Callable as _,
18    InputsIter,
19};
20use proc_macro2::TokenStream as TokenStream2;
21use quote::quote;
22use syn::Pat;
23
24use super::utils::{
25    extract_docs,
26    sol_return_type,
27    sol_type,
28};
29use crate::GenerateCode;
30
31/// Generates code for generating Solidity ABI compatibility metadata for the contract.
32#[derive(From)]
33pub struct SolidityMetadata<'a> {
34    /// The contract to generate code for.
35    contract: &'a ir::Contract,
36}
37impl_as_ref_for_generator!(SolidityMetadata);
38
39impl GenerateCode for SolidityMetadata<'_> {
40    fn generate_code(&self) -> TokenStream2 {
41        let ident = self.contract.module().storage().ident();
42        let name = ident.to_string();
43        let ctors = self.constructors();
44        let msgs = self.messages();
45        let docs = extract_docs(self.contract.module().attrs());
46
47        quote! {
48            #[cfg(feature = "std")]
49            #[cfg(not(feature = "ink-as-dependency"))]
50            #[cfg(any(ink_abi = "sol", ink_abi = "all"))]
51            const _: () = {
52                #[unsafe(no_mangle)]
53                pub fn __ink_generate_solidity_metadata() -> ::ink::metadata::sol::ContractMetadata  {
54                    ::ink::metadata::sol::ContractMetadata {
55                        name: #name.into(),
56                        constructors: vec![ #( #ctors ),* ],
57                        functions: vec![ #( #msgs ),* ],
58                        events: ::ink::collect_events_sol(),
59                        errors: ::ink::collect_errors_sol(),
60                        docs: #docs.into(),
61                    }
62                }
63            };
64        }
65    }
66}
67
68impl SolidityMetadata<'_> {
69    /// Generates Solidity ABI compatible metadata for all ink! constructors.
70    fn constructors(&self) -> impl Iterator<Item = TokenStream2> + '_ {
71        self.contract
72            .module()
73            .impls()
74            .flat_map(|item_impl| item_impl.iter_constructors())
75            .map(|ctor| {
76                let name = ctor
77                    .name()
78                    .map(ToString::to_string)
79                    .unwrap_or_else(|| ctor.ident().to_string());
80                let inputs = params_info(ctor.inputs());
81                let is_payable = ctor.is_payable();
82                let is_default = ctor.is_default();
83                let docs = extract_docs(ctor.attrs());
84
85                quote! {
86                    ::ink::metadata::sol::ConstructorMetadata {
87                        name: #name.into(),
88                        inputs: vec![ #( #inputs ),* ],
89                        is_payable: #is_payable,
90                        is_default: #is_default,
91                        docs: #docs.into(),
92                    }
93                }
94            })
95    }
96
97    /// Generates Solidity ABI compatible metadata for all ink! messages.
98    fn messages(&self) -> impl Iterator<Item = TokenStream2> + '_ {
99        self.contract
100            .module()
101            .impls()
102            .flat_map(|item_impl| item_impl.iter_messages())
103            .map(|msg| {
104                let name = msg
105                    .name()
106                    .map(ToString::to_string)
107                    .unwrap_or_else(|| msg.ident().to_string());
108                let inputs = params_info(msg.inputs());
109                let output = msg
110                    .output()
111                    .map(|ty| {
112                        let sol_ty = sol_return_type(ty);
113                        quote! { ::core::option::Option::Some(#sol_ty.into()) }
114                    })
115                    .unwrap_or_else(|| {
116                        quote! { ::core::option::Option::None }
117                    });
118                let mutates = msg.receiver().is_ref_mut();
119                let is_payable = msg.is_payable();
120                let is_default = msg.is_default();
121                let docs = extract_docs(msg.attrs());
122
123                quote! {
124                    ::ink::metadata::sol::FunctionMetadata {
125                        name: #name.into(),
126                        inputs: vec![ #( #inputs ),* ],
127                        output: #output,
128                        mutates: #mutates,
129                        is_payable: #is_payable,
130                        is_default: #is_default,
131                        docs: #docs.into(),
132                    }
133                }
134            })
135    }
136}
137
138/// Returns the Solidity ABI compatible parameter type and name for the given inputs.
139fn params_info(inputs: InputsIter<'_>) -> impl Iterator<Item = TokenStream2> + '_ {
140    inputs.map(|input| {
141        let ty = &*input.ty;
142        let sol_ty = sol_type(ty);
143        let ident = match &*input.pat {
144            Pat::Ident(ident) => &ident.ident,
145            _ => unreachable!("Expected an input identifier"),
146        };
147        let name = ident.to_string();
148        quote! {
149            ::ink::metadata::sol::ParamMetadata {
150                name: #name.into(),
151                ty: #sol_ty.into(),
152            }
153        }
154    })
155}