1use anyhow::{Result, anyhow};
2use convert_case::{Case, Casing};
3use handlebars::Handlebars;
4use sails_idl_parser_v2::{
5 ast::{IdlDoc, PrimitiveType, Type, TypeDecl, codec::has_ethabi_codec},
6 parse_idl,
7};
8use serde::Serialize;
9use typedecl_to_sol::TypeDeclToSol;
10
11mod consts;
12mod typedecl_to_sol;
13
14#[derive(Serialize)]
15struct ArgData {
16 pub ty: String,
17 pub name: String,
18 pub mem_location: Option<String>,
19}
20
21#[derive(Serialize)]
22struct FunctionData {
23 pub name: String,
24 pub args: Vec<ArgData>,
25 pub reply_type: Option<String>,
26 pub reply_mem_location: Option<String>,
27 pub payable: bool,
28 pub returns_value: bool,
29}
30
31#[derive(Serialize)]
32struct EventArgData {
33 pub ty: String,
34 pub indexed: bool,
35 pub name: Option<String>,
36}
37
38#[derive(Serialize)]
39struct EventData {
40 pub name: String,
41 pub args: Vec<EventArgData>,
42}
43
44#[derive(Serialize)]
45struct ContractData {
46 pub pragma_version: String,
47 pub contract_name: String,
48 pub functions: Vec<FunctionData>,
49 pub events: Vec<EventData>,
50}
51
52pub struct GenerateContractResult {
53 pub data: Vec<u8>,
54 pub name: String,
55}
56
57fn resolve_type_decl(decl: &TypeDecl, types: &[Type]) -> Result<String> {
58 match decl {
59 TypeDecl::Named { name, .. } => types
60 .iter()
61 .find(|ty| ty.name == *name)
62 .and_then(|ty| ty.annotations.iter().find(|(k, _)| k == "sol_type"))
63 .and_then(|(_, v)| v.clone())
64 .ok_or_else(|| anyhow!("type is not supported")),
65 TypeDecl::Array { item, len } => {
66 Ok(format!("{}[{}]", resolve_type_decl(item, types)?, len))
67 }
68 TypeDecl::Slice { item } => Ok(format!("{}[]", resolve_type_decl(item, types)?)),
69 _ => decl.get_ty(),
70 }
71}
72
73pub fn generate_solidity_contract(idl_content: &str, name: &str) -> Result<GenerateContractResult> {
74 let doc = parse_idl(idl_content)?;
75
76 let contract_name = name.to_string().to_case(Case::UpperCamel);
77
78 let contract_data = ContractData {
79 contract_name: contract_name.clone(),
80 pragma_version: consts::PRAGMA_VERSION.to_string(),
81 functions: functions_from_idl(&doc)?,
82 events: events_from_idl(&doc)?,
83 };
84
85 let mut handlebars = Handlebars::new();
86 handlebars.register_template_string("contract", consts::CONTRACT_TEMPLATE)?;
87
88 let mut contract = Vec::new();
89
90 handlebars.render_to_write("contract", &contract_data, &mut contract)?;
91
92 Ok(GenerateContractResult {
93 data: contract,
94 name: contract_name,
95 })
96}
97
98fn functions_from_idl(doc: &IdlDoc) -> Result<Vec<FunctionData>> {
99 let mut functions = Vec::new();
100
101 if let Some(program) = &doc.program {
102 for func in &program.ctors {
103 let mut args = Vec::new();
104 for p in &func.params {
105 let arg = ArgData {
106 ty: resolve_type_decl(&p.type_decl, &program.types)?,
107 name: p.name.to_case(Case::Camel),
108 mem_location: p.type_decl.get_mem_location(),
109 };
110 args.push(arg);
111 }
112 functions.push(FunctionData {
113 name: func.name.to_case(Case::Camel),
114 reply_type: None, reply_mem_location: None,
116 payable: func.annotations.iter().any(|(k, _)| k == "payable"),
117 returns_value: false, args,
119 });
120 }
121 }
122
123 for svc in &doc.services {
124 for f in &svc.funcs {
125 if !has_ethabi_codec(&f.annotations) {
126 continue;
127 }
128 let mut args = Vec::new();
129 for p in &f.params {
130 let arg = ArgData {
131 ty: resolve_type_decl(&p.type_decl, &svc.types)?,
132 name: p.name.to_case(Case::Camel),
133 mem_location: p.type_decl.get_mem_location(),
134 };
135 args.push(arg);
136 }
137 let reply_type = if f.output != TypeDecl::Primitive(PrimitiveType::Void) {
138 Some(resolve_type_decl(&f.output, &svc.types)?)
139 } else {
140 None
141 };
142 functions.push(FunctionData {
143 name: format!("{}{}", svc.name.name, f.name)
144 .as_str()
145 .to_case(Case::Camel),
146 reply_type,
147 reply_mem_location: f.output.get_mem_location(),
148 payable: f.annotations.iter().any(|(k, _)| k == "payable"),
149 returns_value: f.annotations.iter().any(|(k, _)| k == "returns_value"),
150 args,
151 });
152 }
153 }
154
155 Ok(functions)
156}
157
158fn events_from_idl(doc: &IdlDoc) -> Result<Vec<EventData>> {
159 let mut events = Vec::new();
160
161 for svc in &doc.services {
162 for e in svc
163 .events
164 .iter()
165 .filter(|e| has_ethabi_codec(&e.annotations))
166 {
167 let mut args = Vec::new();
168 for f in &e.def.fields {
169 let arg = EventArgData {
170 ty: resolve_type_decl(&f.type_decl, &svc.types)?,
171 indexed: f.annotations.iter().any(|(k, _)| k == "indexed"),
172 name: f.name.as_ref().map(|name| name.to_case(Case::Camel)),
173 };
174 args.push(arg);
175 }
176 events.push(EventData {
177 name: e.name.to_string(),
178 args,
179 });
180 }
181 }
182
183 Ok(events)
184}