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    /// The set of attributes that can be passed to call builder in the codegen.
33    whitelisted_attributes: WhitelistedAttributes,
34}
35
36impl TryFrom<ast::AttributeArgs> for Config {
37    type Error = syn::Error;
38
39    fn try_from(args: ast::AttributeArgs) -> Result<Self, Self::Error> {
40        let mut env: Option<(Environment, ast::MetaNameValue)> = None;
41        let mut whitelisted_attributes = WhitelistedAttributes::default();
42
43        for arg in args.into_iter() {
44            if arg.name().is_ident("env") {
45                if let Some((_, ast)) = env {
46                    return Err(duplicate_config_err(ast, arg, "env", "contract"));
47                }
48                let env_info = arg
49                    .name_value()
50                    .zip(arg.value().and_then(ast::MetaValue::as_path));
51                if let Some((name_value, path)) = env_info {
52                    env = Some((Environment { path: path.clone() }, name_value.clone()))
53                } else {
54                    return Err(format_err_spanned!(
55                        arg,
56                        "expected a path value for `env` ink! configuration argument",
57                    ));
58                }
59            } else if arg.name().is_ident("keep_attr") {
60                if let Some(name_value) = arg.name_value() {
61                    whitelisted_attributes.parse_arg_value(name_value)?;
62                } else {
63                    return Err(format_err_spanned!(
64                        arg,
65                        "expected a string literal value for `keep_attr` ink! configuration argument",
66                    ));
67                }
68            } else {
69                return Err(format_err_spanned!(
70                    arg,
71                    "encountered unknown or unsupported ink! configuration argument",
72                ));
73            }
74        }
75        Ok(Config {
76            env: env.map(|(value, _)| value),
77            whitelisted_attributes,
78        })
79    }
80}
81
82impl Config {
83    /// Returns the environmental types definition if specified.
84    /// Otherwise returns the default environmental types definition provided
85    /// by ink!.
86    pub fn env(&self) -> syn::Path {
87        self.env
88            .as_ref()
89            .map(|env| &env.path)
90            .cloned()
91            .unwrap_or(Environment::default().path)
92    }
93
94    /// Return set of attributes that can be passed to call builder in the codegen.
95    pub fn whitelisted_attributes(&self) -> &WhitelistedAttributes {
96        &self.whitelisted_attributes
97    }
98}
99
100/// The environmental types definition.
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct Environment {
103    /// The underlying Rust type.
104    pub path: syn::Path,
105}
106
107impl Default for Environment {
108    fn default() -> Self {
109        Self {
110            path: syn::parse_quote! { ::ink::env::DefaultEnvironment },
111        }
112    }
113}
114
115/// ABI spec for encoding/decoding contract calls.
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
117pub enum Abi {
118    /// ink! ABI spec (the default, uses the SCALE codec for input/output encode/decode).
119    #[default]
120    Ink,
121    /// Solidity ABI spec.
122    Solidity,
123    /// Support both ink! and Solidity ABI specs for each contract entry point.
124    All,
125}
126
127impl Abi {
128    pub fn is_ink(&self) -> bool {
129        matches!(self, Self::Ink | Self::All)
130    }
131
132    pub fn is_solidity(&self) -> bool {
133        matches!(self, Self::Solidity | Self::All)
134    }
135
136    pub fn is_all(&self) -> bool {
137        matches!(self, Self::All)
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    /// Asserts that the given input configuration attribute argument are converted
146    /// into the expected ink! configuration or yields the expected error message.
147    fn assert_try_from(
148        input: ast::AttributeArgs,
149        expected: Result<Config, &'static str>,
150    ) {
151        assert_eq!(
152            <Config as TryFrom<ast::AttributeArgs>>::try_from(input)
153                .map_err(|err| err.to_string()),
154            expected.map_err(ToString::to_string),
155        );
156    }
157
158    #[test]
159    fn empty_config_works() {
160        assert_try_from(syn::parse_quote! {}, Ok(Config::default()))
161    }
162
163    #[test]
164    fn env_works() {
165        assert_try_from(
166            syn::parse_quote! {
167                env = ::my::env::Types
168            },
169            Ok(Config {
170                env: Some(Environment {
171                    path: syn::parse_quote! { ::my::env::Types },
172                }),
173                whitelisted_attributes: Default::default(),
174            }),
175        )
176    }
177
178    #[test]
179    fn env_invalid_value_fails() {
180        assert_try_from(
181            syn::parse_quote! { env = "invalid" },
182            Err("expected a path value for `env` ink! configuration argument"),
183        );
184    }
185
186    #[test]
187    fn env_missing_value_fails() {
188        assert_try_from(
189            syn::parse_quote! { env },
190            Err("expected a path value for `env` ink! configuration argument"),
191        );
192    }
193
194    #[test]
195    fn unknown_arg_fails() {
196        assert_try_from(
197            syn::parse_quote! { unknown = argument },
198            Err("encountered unknown or unsupported ink! configuration argument"),
199        );
200    }
201
202    #[test]
203    fn duplicate_args_fails() {
204        assert_try_from(
205            syn::parse_quote! {
206                env = ::my::env::Types,
207                env = ::my::other::env::Types,
208            },
209            Err("encountered duplicate ink! contract `env` configuration argument"),
210        );
211    }
212
213    #[test]
214    fn keep_attr_works() {
215        let mut attrs = WhitelistedAttributes::default();
216        attrs.0.insert("foo".to_string(), ());
217        attrs.0.insert("bar".to_string(), ());
218        assert_try_from(
219            syn::parse_quote! {
220                keep_attr = "foo, bar"
221            },
222            Ok(Config {
223                env: None,
224                whitelisted_attributes: attrs,
225            }),
226        )
227    }
228
229    #[test]
230    fn keep_attr_invalid_value_fails() {
231        assert_try_from(
232            syn::parse_quote! { keep_attr = 1u16 },
233            Err("expected a string with attributes separated by `,`"),
234        );
235    }
236
237    #[test]
238    fn keep_attr_missing_value_fails() {
239        assert_try_from(
240            syn::parse_quote! { keep_attr },
241            Err("expected a string literal value for `keep_attr` ink! configuration argument"),
242        );
243    }
244}