ink_ir/ir/
config.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::{
16    ast,
17    utils::{
18        duplicate_config_err,
19        WhitelistedAttributes,
20    },
21};
22
23/// The ink! configuration.
24#[derive(Debug, Default, PartialEq, Eq)]
25pub struct Config {
26    /// The environmental types definition.
27    ///
28    /// This must be a type that implements `ink_env::Environment` and can
29    /// be used to change the underlying environmental types of an ink! smart
30    /// contract.
31    env: Option<Environment>,
32    /// todo: docs
33    abi: Abi,
34    /// The set of attributes that can be passed to call builder in the codegen.
35    whitelisted_attributes: WhitelistedAttributes,
36}
37
38impl TryFrom<ast::AttributeArgs> for Config {
39    type Error = syn::Error;
40
41    fn try_from(args: ast::AttributeArgs) -> Result<Self, Self::Error> {
42        let mut env: Option<(Environment, ast::MetaNameValue)> = None;
43        let mut whitelisted_attributes = WhitelistedAttributes::default();
44        let mut abi: Option<(Abi, ast::MetaNameValue)> = None;
45
46        for arg in args.into_iter() {
47            if arg.name().is_ident("env") {
48                if let Some((_, ast)) = env {
49                    return Err(duplicate_config_err(ast, arg, "env", "contract"));
50                }
51                let env_info = arg
52                    .name_value()
53                    .zip(arg.value().and_then(ast::MetaValue::as_path));
54                if let Some((name_value, path)) = env_info {
55                    env = Some((Environment { path: path.clone() }, name_value.clone()))
56                } else {
57                    return Err(format_err_spanned!(
58                        arg,
59                        "expected a path value for `env` ink! configuration argument",
60                    ));
61                }
62            } else if arg.name().is_ident("abi") {
63                if let Some((_, ast)) = abi {
64                    return Err(duplicate_config_err(ast, arg, "abi", "contract"));
65                }
66                let encoding = arg
67                    .name_value()
68                    .zip(arg.value().and_then(ast::MetaValue::as_string));
69                if let Some((name_value, path)) = encoding {
70                    let encoding = match path.as_str() {
71                        "ink" => Abi::Ink,
72                        "solidity" | "sol" => Abi::Solidity,
73                        "all" => Abi::All,
74                        _ => {
75                            return Err(format_err_spanned!(
76                                arg,
77                                "expected one of `ink`, `sol` or `all` for `abi` ink! configuration argument",
78                            ));
79                        }
80                    };
81                    abi = Some((encoding, name_value.clone()))
82                } else {
83                    return Err(format_err_spanned!(
84                        arg,
85                        "expected a string literal value for `abi` ink! configuration argument",
86                    ));
87                }
88            } else if arg.name().is_ident("keep_attr") {
89                if let Some(name_value) = arg.name_value() {
90                    whitelisted_attributes.parse_arg_value(name_value)?;
91                } else {
92                    return Err(format_err_spanned!(
93                        arg,
94                        "expected a string literal value for `keep_attr` ink! configuration argument",
95                    ));
96                }
97            } else {
98                return Err(format_err_spanned!(
99                    arg,
100                    "encountered unknown or unsupported ink! configuration argument",
101                ));
102            }
103        }
104        Ok(Config {
105            env: env.map(|(value, _)| value),
106            abi: abi.map_or(Abi::default(), |(encoding, _)| encoding),
107            whitelisted_attributes,
108        })
109    }
110}
111
112impl Config {
113    /// Returns the environmental types definition if specified.
114    /// Otherwise returns the default environmental types definition provided
115    /// by ink!.
116    pub fn env(&self) -> syn::Path {
117        self.env
118            .as_ref()
119            .map(|env| &env.path)
120            .cloned()
121            .unwrap_or(Environment::default().path)
122    }
123
124    pub fn abi(&self) -> &Abi {
125        &self.abi
126    }
127
128    /// Return set of attributes that can be passed to call builder in the codegen.
129    pub fn whitelisted_attributes(&self) -> &WhitelistedAttributes {
130        &self.whitelisted_attributes
131    }
132}
133
134/// The environmental types definition.
135#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct Environment {
137    /// The underlying Rust type.
138    pub path: syn::Path,
139}
140
141impl Default for Environment {
142    fn default() -> Self {
143        Self {
144            path: syn::parse_quote! { ::ink::env::DefaultEnvironment },
145        }
146    }
147}
148
149/// ABI spec for encoding/decoding contract calls.
150#[derive(Debug, Clone, PartialEq, Eq, Default)]
151pub enum Abi {
152    /// ink! ABI spec (the default, uses the SCALE codec for input/output encode/decode).
153    #[default]
154    Ink,
155    /// Solidity ABI spec.
156    Solidity,
157    /// Support both ink! and Solidity ABI specs for each contract entry point.
158    All,
159}
160
161impl Abi {
162    pub fn is_ink(&self) -> bool {
163        matches!(self, Self::Ink | Self::All)
164    }
165
166    pub fn is_solidity(&self) -> bool {
167        matches!(self, Self::Solidity | Self::All)
168    }
169
170    pub fn is_all(&self) -> bool {
171        matches!(self, Self::All)
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    /// Asserts that the given input configuration attribute argument are converted
180    /// into the expected ink! configuration or yields the expected error message.
181    fn assert_try_from(
182        input: ast::AttributeArgs,
183        expected: Result<Config, &'static str>,
184    ) {
185        assert_eq!(
186            <Config as TryFrom<ast::AttributeArgs>>::try_from(input)
187                .map_err(|err| err.to_string()),
188            expected.map_err(ToString::to_string),
189        );
190    }
191
192    #[test]
193    fn empty_config_works() {
194        assert_try_from(syn::parse_quote! {}, Ok(Config::default()))
195    }
196
197    #[test]
198    fn env_works() {
199        assert_try_from(
200            syn::parse_quote! {
201                env = ::my::env::Types
202            },
203            Ok(Config {
204                env: Some(Environment {
205                    path: syn::parse_quote! { ::my::env::Types },
206                }),
207                whitelisted_attributes: Default::default(),
208                abi: Default::default(),
209            }),
210        )
211    }
212
213    #[test]
214    fn env_invalid_value_fails() {
215        assert_try_from(
216            syn::parse_quote! { env = "invalid" },
217            Err("expected a path value for `env` ink! configuration argument"),
218        );
219    }
220
221    #[test]
222    fn env_missing_value_fails() {
223        assert_try_from(
224            syn::parse_quote! { env },
225            Err("expected a path value for `env` ink! configuration argument"),
226        );
227    }
228
229    #[test]
230    fn unknown_arg_fails() {
231        assert_try_from(
232            syn::parse_quote! { unknown = argument },
233            Err("encountered unknown or unsupported ink! configuration argument"),
234        );
235    }
236
237    #[test]
238    fn duplicate_args_fails() {
239        assert_try_from(
240            syn::parse_quote! {
241                env = ::my::env::Types,
242                env = ::my::other::env::Types,
243            },
244            Err("encountered duplicate ink! contract `env` configuration argument"),
245        );
246    }
247
248    #[test]
249    fn keep_attr_works() {
250        let mut attrs = WhitelistedAttributes::default();
251        attrs.0.insert("foo".to_string(), ());
252        attrs.0.insert("bar".to_string(), ());
253        assert_try_from(
254            syn::parse_quote! {
255                keep_attr = "foo, bar"
256            },
257            Ok(Config {
258                env: None,
259                abi: Default::default(),
260                whitelisted_attributes: attrs,
261            }),
262        )
263    }
264
265    #[test]
266    fn keep_attr_invalid_value_fails() {
267        assert_try_from(
268            syn::parse_quote! { keep_attr = 1u16 },
269            Err("expected a string with attributes separated by `,`"),
270        );
271    }
272
273    #[test]
274    fn keep_attr_missing_value_fails() {
275        assert_try_from(
276            syn::parse_quote! { keep_attr },
277            Err("expected a string literal value for `keep_attr` ink! configuration argument"),
278        );
279    }
280
281    #[test]
282    fn abi_works() {
283        assert_try_from(
284            syn::parse_quote! {
285                abi = "ink"
286            },
287            Ok(Config {
288                env: None,
289                abi: Abi::Ink,
290                whitelisted_attributes: Default::default(),
291            }),
292        );
293        assert_try_from(
294            syn::parse_quote! {
295                abi = "sol"
296            },
297            Ok(Config {
298                env: None,
299                abi: Abi::Solidity,
300                whitelisted_attributes: Default::default(),
301            }),
302        );
303        assert_try_from(
304            syn::parse_quote! {
305                abi = "all"
306            },
307            Ok(Config {
308                env: None,
309                abi: Abi::All,
310                whitelisted_attributes: Default::default(),
311            }),
312        );
313    }
314
315    #[test]
316    fn abi_invalid_value_fails() {
317        assert_try_from(
318            syn::parse_quote! { abi = "move" },
319            Err("expected one of `ink`, `sol` or `all` for `abi` ink! configuration argument"),
320        );
321        assert_try_from(
322            syn::parse_quote! { abi = 1u8 },
323            Err("expected a string literal value for `abi` ink! configuration argument"),
324        );
325    }
326
327    #[test]
328    fn abi_missing_value_fails() {
329        assert_try_from(
330            syn::parse_quote! { abi },
331            Err("expected a string literal value for `abi` ink! configuration argument"),
332        );
333    }
334}