Skip to main content

sails_sol_gen/
lib.rs

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, // Constructors don't have replies in this sense
115                reply_mem_location: None,
116                payable: func.annotations.iter().any(|(k, _)| k == "payable"),
117                returns_value: false, // Constructors don't return CommandReply values
118                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}