sails_sol_gen/
lib.rs

1use anyhow::{Result, bail};
2use convert_case::{Case, Casing};
3use handlebars::Handlebars;
4use sails_idl_parser::ast::{Program, TypeDecl, TypeDef, parse_idl};
5use serde::Serialize;
6use typedecl_to_sol::TypeDeclToSol;
7
8mod consts;
9mod typedecl_to_sol;
10
11#[derive(Serialize)]
12struct ArgData {
13    pub ty: String,
14    pub name: String,
15    pub mem_location: Option<String>,
16}
17
18#[derive(Serialize)]
19struct FunctionData {
20    pub name: String,
21    pub args: Vec<ArgData>,
22    pub reply_type: Option<String>,
23    pub reply_mem_location: Option<String>,
24    pub payable: bool,
25    pub returns_value: bool,
26}
27
28#[derive(Serialize)]
29struct EventArgData {
30    pub ty: String,
31    pub indexed: bool,
32    pub name: Option<String>,
33}
34
35#[derive(Serialize)]
36struct EventData {
37    pub name: String,
38    pub args: Vec<EventArgData>,
39}
40
41#[derive(Serialize)]
42struct ContractData {
43    pub pragma_version: String,
44    pub contract_name: String,
45    pub functions: Vec<FunctionData>,
46    pub events: Vec<EventData>,
47}
48
49pub struct GenerateContractResult {
50    pub data: Vec<u8>,
51    pub name: String,
52}
53
54pub fn generate_solidity_contract(idl_content: &str, name: &str) -> Result<GenerateContractResult> {
55    let program = parse_idl(idl_content)?;
56
57    let contract_name = name.to_string().to_case(Case::UpperCamel);
58
59    let contract_data = ContractData {
60        contract_name: contract_name.clone(),
61        pragma_version: consts::PRAGMA_VERSION.to_string(),
62        functions: functions_from_idl(&program)?,
63        events: events_from_idl(&program)?,
64    };
65
66    let mut handlebars = Handlebars::new();
67    handlebars.register_template_string("contract", consts::CONTRACT_TEMPLATE)?;
68
69    let mut contract = Vec::new();
70
71    handlebars.render_to_write("contract", &contract_data, &mut contract)?;
72
73    Ok(GenerateContractResult {
74        data: contract,
75        name: contract_name,
76    })
77}
78
79fn functions_from_idl(program: &Program) -> Result<Vec<FunctionData>> {
80    let mut functions = Vec::new();
81
82    if let Some(ctor) = program.ctor() {
83        for func in ctor.funcs() {
84            let mut args = Vec::new();
85            for p in func.params() {
86                let arg = ArgData {
87                    ty: p.type_decl().get_ty()?,
88                    name: p.name().to_case(Case::Camel),
89                    mem_location: p.type_decl().get_mem_location(),
90                };
91                args.push(arg);
92            }
93            functions.push(FunctionData {
94                name: func.name().to_case(Case::Camel),
95                reply_type: None, // Constructors don't have replies in this sense
96                reply_mem_location: None,
97                payable: has_tag(func.docs(), "#[payable]"),
98                returns_value: false, // Constructors don't return CommandReply values
99                args,
100            });
101        }
102    }
103
104    for svc in program.services() {
105        for f in svc.funcs() {
106            let mut args = Vec::new();
107            for p in f.params() {
108                let arg = ArgData {
109                    ty: p.type_decl().get_ty()?,
110                    name: p.name().to_case(Case::Camel),
111                    mem_location: p.type_decl().get_mem_location(),
112                };
113                args.push(arg);
114            }
115            functions.push(FunctionData {
116                name: format!("{}{}", svc.name(), f.name())
117                    .as_str()
118                    .to_case(Case::Camel),
119                reply_type: f.output().get_ty().ok(),
120                reply_mem_location: f.output().get_mem_location(),
121                payable: has_tag(f.docs(), "#[payable]"),
122                returns_value: has_tag(f.docs(), "#[returns_value]"),
123                args,
124            });
125        }
126    }
127
128    Ok(functions)
129}
130
131fn events_from_idl(program: &Program) -> Result<Vec<EventData>> {
132    let mut events = Vec::new();
133
134    for svc in program.services() {
135        for e in svc.events() {
136            let mut args = Vec::new();
137            match e.type_decl().unwrap() {
138                TypeDecl::Def(TypeDef::Struct(def)) => {
139                    for f in def.fields() {
140                        let arg = EventArgData {
141                            ty: f.type_decl().get_ty()?,
142                            indexed: has_tag(f.docs(), "#[indexed]"),
143                            name: f.name().map(|name| name.to_case(Case::Camel)),
144                        };
145                        args.push(arg);
146                    }
147                }
148                _ => bail!("Unsupported type"),
149            }
150            events.push(EventData {
151                name: e.name().to_string(),
152                args,
153            });
154        }
155    }
156
157    Ok(events)
158}
159
160fn has_tag(docs: &[String], tag: &str) -> bool {
161    docs.iter().any(|doc| doc.contains(tag))
162}