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, reply_mem_location: None,
161 payable: ctor_func
162 .annotations
163 .iter()
164 .any(|(key, _)| key == "payable"),
165 returns_value: false, 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}