Skip to main content

sails_sol_gen/
generator.rs

1use alloc::{string::String, vec::Vec};
2
3use crate::{
4    error::Result,
5    sol_conversion::{ConversionError, TypeDeclExt},
6};
7use askama::Template;
8use convert_case::{Case, Casing};
9use sails_idl_parser_v2::{
10    ast::{IdlDoc, PrimitiveType, Type, TypeDecl, codec::has_ethabi_codec},
11    parse_idl,
12};
13
14struct Arg {
15    ty: String,
16    name: String,
17    mem_location: Option<String>,
18}
19
20struct Function {
21    name: String,
22    args: Vec<Arg>,
23    reply_type: Option<String>,
24    reply_mem_location: Option<String>,
25    payable: bool,
26    returns_value: bool,
27}
28
29struct EventArg {
30    ty: String,
31    indexed: bool,
32    name: Option<String>,
33}
34
35struct Event {
36    name: String,
37    args: Vec<EventArg>,
38}
39
40#[repr(C)]
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum SolidityFile {
43    SingleFile,
44    InterfaceFile,
45    AbiInterfaceFile,
46    CallbacksInterfaceFile,
47    CallerFile,
48}
49
50struct ContractData {
51    license_identifier: String,
52    solidity_version: String,
53    contract_name: String,
54    functions: Vec<Function>,
55    events: Vec<Event>,
56}
57
58macro_rules! define_template {
59    ($name:ident, $path:literal) => {
60        #[derive(Template)]
61        #[template(path = $path)]
62        #[allow(dead_code)]
63        struct $name {
64            license_identifier: String,
65            solidity_version: String,
66            contract_name: String,
67            functions: Vec<Function>,
68            events: Vec<Event>,
69        }
70
71        impl From<ContractData> for $name {
72            fn from(data: ContractData) -> Self {
73                Self {
74                    license_identifier: data.license_identifier,
75                    solidity_version: data.solidity_version,
76                    contract_name: data.contract_name,
77                    functions: data.functions,
78                    events: data.events,
79                }
80            }
81        }
82    };
83}
84
85define_template!(SingleFile, "single_file.askama");
86define_template!(InterfaceFile, "interface_file.askama");
87define_template!(AbiInterfaceFile, "abi_interface_file.askama");
88define_template!(CallbacksInterfaceFile, "callbacks_interface_file.askama");
89define_template!(CallerFile, "caller_file.askama");
90
91pub const LICENSE_IDENTIFIER: &str = "MIT";
92pub const SOLIDITY_VERSION: &str = "0.8.35";
93
94pub fn generate_solidity_contract(
95    contract_name: &str,
96    idl_content: &str,
97    solidity_file: SolidityFile,
98) -> Result<Vec<u8>> {
99    let idl_doc = parse_idl(idl_content)?;
100
101    let contract_data = ContractData {
102        license_identifier: LICENSE_IDENTIFIER.into(),
103        solidity_version: SOLIDITY_VERSION.into(),
104        contract_name: contract_name.into(),
105        functions: functions_from_idl(&idl_doc)?,
106        events: events_from_idl(&idl_doc)?,
107    };
108
109    let rendered = match solidity_file {
110        SolidityFile::SingleFile => SingleFile::from(contract_data).render()?,
111        SolidityFile::InterfaceFile => InterfaceFile::from(contract_data).render()?,
112        SolidityFile::AbiInterfaceFile => AbiInterfaceFile::from(contract_data).render()?,
113        SolidityFile::CallbacksInterfaceFile => {
114            CallbacksInterfaceFile::from(contract_data).render()?
115        }
116        SolidityFile::CallerFile => CallerFile::from(contract_data).render()?,
117    };
118
119    Ok(rendered.into_bytes())
120}
121
122fn resolve_type_decl(decl: &TypeDecl, types: &[Type]) -> Result<String, ConversionError> {
123    match decl {
124        TypeDecl::Named { name, .. } => types
125            .iter()
126            .find(|ty| ty.name == *name)
127            .and_then(|ty| ty.annotations.iter().find(|(key, _)| key == "sol_type"))
128            .and_then(|(_, value)| value.clone())
129            .ok_or(ConversionError::UnsupportedType),
130        TypeDecl::Array { item, len } => {
131            let ty = resolve_type_decl(item, types)?;
132            Ok(format!("{ty}[{len}]"))
133        }
134        TypeDecl::Slice { item } => {
135            let ty = resolve_type_decl(item, types)?;
136            Ok(format!("{ty}[]"))
137        }
138        _ => decl.get_ty(),
139    }
140}
141
142fn functions_from_idl(idl_doc: &IdlDoc) -> Result<Vec<Function>> {
143    let mut functions = vec![];
144
145    if let Some(program) = &idl_doc.program {
146        for ctor_func in &program.ctors {
147            let mut args = vec![];
148
149            for func_param in &ctor_func.params {
150                args.push(Arg {
151                    ty: resolve_type_decl(&func_param.type_decl, &program.types)?,
152                    name: func_param.name.to_case(Case::Camel),
153                    mem_location: func_param.type_decl.get_mem_location(),
154                });
155            }
156
157            functions.push(Function {
158                name: ctor_func.name.to_case(Case::Camel),
159                reply_type: None, // Constructors don't have replies in this sense
160                reply_mem_location: None,
161                payable: ctor_func
162                    .annotations
163                    .iter()
164                    .any(|(key, _)| key == "payable"),
165                returns_value: false, // Constructors don't return CommandReply values
166                args,
167            });
168        }
169    }
170
171    for service_unit in &idl_doc.services {
172        for service_func in &service_unit.funcs {
173            if !has_ethabi_codec(&service_func.annotations) {
174                continue;
175            }
176
177            let mut args = vec![];
178
179            for func_param in &service_func.params {
180                args.push(Arg {
181                    ty: resolve_type_decl(&func_param.type_decl, &service_unit.types)?,
182                    name: func_param.name.to_case(Case::Camel),
183                    mem_location: func_param.type_decl.get_mem_location(),
184                });
185            }
186
187            let reply_type = match &service_func.output {
188                TypeDecl::Primitive(PrimitiveType::Void) => None,
189                output => Some(resolve_type_decl(output, &service_unit.types)?),
190            };
191
192            let service_name = &service_unit.name.name;
193            let service_func_name = &service_func.name;
194
195            functions.push(Function {
196                name: format!("{service_name}{service_func_name}").to_case(Case::Camel),
197                reply_type,
198                reply_mem_location: service_func.output.get_mem_location(),
199                payable: service_func
200                    .annotations
201                    .iter()
202                    .any(|(key, _)| key == "payable"),
203                returns_value: service_func
204                    .annotations
205                    .iter()
206                    .any(|(key, _)| key == "returns_value"),
207                args,
208            });
209        }
210    }
211
212    Ok(functions)
213}
214
215fn events_from_idl(idl_doc: &IdlDoc) -> Result<Vec<Event>> {
216    let mut events = vec![];
217
218    for service_unit in &idl_doc.services {
219        for enum_variant in &service_unit.events {
220            if !has_ethabi_codec(&enum_variant.annotations) {
221                continue;
222            }
223
224            let mut args = vec![];
225
226            for struct_field in &enum_variant.def.fields {
227                args.push(EventArg {
228                    ty: resolve_type_decl(&struct_field.type_decl, &service_unit.types)?,
229                    indexed: struct_field
230                        .annotations
231                        .iter()
232                        .any(|(key, _)| key == "indexed"),
233                    name: struct_field
234                        .name
235                        .as_ref()
236                        .map(|name| name.to_case(Case::Camel)),
237                });
238            }
239
240            events.push(Event {
241                name: enum_variant.name.clone(),
242                args,
243            });
244        }
245    }
246
247    Ok(events)
248}