1use crate::ir::*;
6use crate::CodegenError;
7use serde::Serialize;
8
9pub struct IdlGenerator {
11 program_name: String,
12}
13
14impl Default for IdlGenerator {
15 fn default() -> Self {
16 Self::new()
17 }
18}
19
20impl IdlGenerator {
21 pub fn new() -> Self {
22 Self {
23 program_name: String::new(),
24 }
25 }
26
27 pub fn generate(&mut self, ir: &SolanaProgram) -> Result<String, CodegenError> {
29 self.program_name = to_snake_case(&ir.name);
30
31 let idl = Idl {
32 version: "0.1.0".to_string(),
33 name: self.program_name.clone(),
34 instructions: self.generate_instructions(ir)?,
35 accounts: self.generate_accounts(ir)?,
36 types: self.generate_types(ir)?,
37 events: self.generate_events(ir)?,
38 errors: self.generate_errors(ir)?,
39 metadata: IdlMetadata {
40 address: "11111111111111111111111111111111".to_string(),
41 },
42 };
43
44 serde_json::to_string_pretty(&idl)
45 .map_err(|e| CodegenError::GenerationFailed(format!("Failed to serialize IDL: {}", e)))
46 }
47
48 fn generate_instructions(
49 &self,
50 ir: &SolanaProgram,
51 ) -> Result<Vec<IdlInstruction>, CodegenError> {
52 let mut instructions = Vec::new();
53
54 for instr in &ir.instructions {
55 let args: Vec<IdlField> = instr
56 .params
57 .iter()
58 .map(|p| IdlField {
59 name: to_camel_case_lower(&p.name),
60 ty: self.solana_type_to_idl_type(&p.ty),
61 })
62 .collect();
63
64 let mut accounts = vec![
66 IdlAccount {
67 name: "state".to_string(),
68 is_mut: !instr.is_view,
69 is_signer: false,
70 },
71 IdlAccount {
72 name: "signer".to_string(),
73 is_mut: true,
74 is_signer: true,
75 },
76 ];
77
78 if instr.name.to_lowercase() == "initialize" {
80 accounts.push(IdlAccount {
81 name: "systemProgram".to_string(),
82 is_mut: false,
83 is_signer: false,
84 });
85 }
86
87 for (i, access) in instr.mapping_accesses.iter().enumerate() {
89 accounts.push(IdlAccount {
90 name: format!("{}_entry_{}", to_camel_case_lower(&access.mapping_name), i),
91 is_mut: !instr.is_view,
92 is_signer: false,
93 });
94 }
95
96 instructions.push(IdlInstruction {
97 name: to_camel_case_lower(&instr.name),
98 accounts,
99 args,
100 returns: instr
101 .returns
102 .as_ref()
103 .map(|t| self.solana_type_to_idl_type(t)),
104 });
105 }
106
107 Ok(instructions)
108 }
109
110 fn generate_accounts(&self, ir: &SolanaProgram) -> Result<Vec<IdlAccountDef>, CodegenError> {
111 let mut accounts = Vec::new();
112
113 let state_fields: Vec<IdlField> = ir
115 .state
116 .fields
117 .iter()
118 .map(|f| IdlField {
119 name: to_camel_case_lower(&f.name),
120 ty: self.solana_type_to_idl_type(&f.ty),
121 })
122 .collect();
123
124 accounts.push(IdlAccountDef {
125 name: format!("{}State", to_camel_case(&self.program_name)),
126 ty: IdlAccountType {
127 kind: "struct".to_string(),
128 fields: state_fields,
129 },
130 });
131
132 for mapping in &ir.mappings {
134 accounts.push(IdlAccountDef {
135 name: format!("{}Entry", to_camel_case(&mapping.name)),
136 ty: IdlAccountType {
137 kind: "struct".to_string(),
138 fields: vec![IdlField {
139 name: "value".to_string(),
140 ty: self.solana_type_to_idl_type(&mapping.value_ty),
141 }],
142 },
143 });
144 }
145
146 Ok(accounts)
147 }
148
149 fn generate_types(&self, ir: &SolanaProgram) -> Result<Vec<IdlTypeDef>, CodegenError> {
150 let mut types = Vec::new();
151
152 for s in &ir.structs {
154 let fields: Vec<IdlField> = s
155 .fields
156 .iter()
157 .map(|f| IdlField {
158 name: to_camel_case_lower(&f.name),
159 ty: self.solana_type_to_idl_type(&f.ty),
160 })
161 .collect();
162
163 types.push(IdlTypeDef {
164 name: s.name.clone(),
165 ty: IdlTypeDefType::Struct { fields },
166 });
167 }
168
169 for e in &ir.enums {
171 let variants: Vec<IdlEnumVariant> = e
172 .variants
173 .iter()
174 .map(|v| IdlEnumVariant { name: v.clone() })
175 .collect();
176
177 types.push(IdlTypeDef {
178 name: e.name.clone(),
179 ty: IdlTypeDefType::Enum { variants },
180 });
181 }
182
183 Ok(types)
184 }
185
186 fn generate_events(&self, ir: &SolanaProgram) -> Result<Vec<IdlEvent>, CodegenError> {
187 let mut events = Vec::new();
188
189 for event in &ir.events {
190 let fields: Vec<IdlEventField> = event
191 .fields
192 .iter()
193 .map(|f| IdlEventField {
194 name: to_camel_case_lower(&f.name),
195 ty: self.solana_type_to_idl_type(&f.ty),
196 index: f.indexed,
197 })
198 .collect();
199
200 events.push(IdlEvent {
201 name: event.name.clone(),
202 fields,
203 });
204 }
205
206 Ok(events)
207 }
208
209 fn generate_errors(&self, ir: &SolanaProgram) -> Result<Vec<IdlError>, CodegenError> {
210 let mut errors = Vec::new();
211
212 errors.push(IdlError {
214 code: 6000,
215 name: "RequireFailed".to_string(),
216 msg: "Requirement failed".to_string(),
217 });
218
219 for (i, error) in ir.errors.iter().enumerate() {
221 errors.push(IdlError {
222 code: 6001 + i as u32,
223 name: error.name.clone(),
224 msg: error.name.clone(),
225 });
226 }
227
228 Ok(errors)
229 }
230
231 fn solana_type_to_idl_type(&self, ty: &SolanaType) -> IdlType {
232 match ty {
233 SolanaType::U8 => IdlType::Primitive("u8".to_string()),
234 SolanaType::U16 => IdlType::Primitive("u16".to_string()),
235 SolanaType::U32 => IdlType::Primitive("u32".to_string()),
236 SolanaType::U64 => IdlType::Primitive("u64".to_string()),
237 SolanaType::U128 => IdlType::Primitive("u128".to_string()),
238 SolanaType::I8 => IdlType::Primitive("i8".to_string()),
239 SolanaType::I16 => IdlType::Primitive("i16".to_string()),
240 SolanaType::I32 => IdlType::Primitive("i32".to_string()),
241 SolanaType::I64 => IdlType::Primitive("i64".to_string()),
242 SolanaType::I128 => IdlType::Primitive("i128".to_string()),
243 SolanaType::Bool => IdlType::Primitive("bool".to_string()),
244 SolanaType::String => IdlType::Primitive("string".to_string()),
245 SolanaType::Pubkey => IdlType::Primitive("publicKey".to_string()),
246 SolanaType::Signer => IdlType::Primitive("publicKey".to_string()),
247 SolanaType::Bytes => IdlType::Primitive("bytes".to_string()),
248 SolanaType::FixedBytes(n) => IdlType::Array {
249 array: (Box::new(IdlType::Primitive("u8".to_string())), *n),
250 },
251 SolanaType::Array(inner, size) => IdlType::Array {
252 array: (Box::new(self.solana_type_to_idl_type(inner)), *size),
253 },
254 SolanaType::Vec(inner) => IdlType::Vec {
255 vec: Box::new(self.solana_type_to_idl_type(inner)),
256 },
257 SolanaType::Option(inner) => IdlType::Option {
258 option: Box::new(self.solana_type_to_idl_type(inner)),
259 },
260 SolanaType::Mapping(_, _) => IdlType::Primitive("bytes".to_string()), SolanaType::Custom(name) => IdlType::Defined(name.clone()),
262 }
263 }
264}
265
266#[derive(Serialize)]
268struct Idl {
269 version: String,
270 name: String,
271 instructions: Vec<IdlInstruction>,
272 accounts: Vec<IdlAccountDef>,
273 types: Vec<IdlTypeDef>,
274 events: Vec<IdlEvent>,
275 errors: Vec<IdlError>,
276 metadata: IdlMetadata,
277}
278
279#[derive(Serialize)]
280struct IdlMetadata {
281 address: String,
282}
283
284#[derive(Serialize)]
285struct IdlInstruction {
286 name: String,
287 accounts: Vec<IdlAccount>,
288 args: Vec<IdlField>,
289 #[serde(skip_serializing_if = "Option::is_none")]
290 returns: Option<IdlType>,
291}
292
293#[derive(Serialize)]
294struct IdlAccount {
295 name: String,
296 #[serde(rename = "isMut")]
297 is_mut: bool,
298 #[serde(rename = "isSigner")]
299 is_signer: bool,
300}
301
302#[derive(Serialize)]
303struct IdlField {
304 name: String,
305 #[serde(rename = "type")]
306 ty: IdlType,
307}
308
309#[derive(Serialize)]
310struct IdlAccountDef {
311 name: String,
312 #[serde(rename = "type")]
313 ty: IdlAccountType,
314}
315
316#[derive(Serialize)]
317struct IdlAccountType {
318 kind: String,
319 fields: Vec<IdlField>,
320}
321
322#[derive(Serialize)]
323struct IdlTypeDef {
324 name: String,
325 #[serde(rename = "type")]
326 ty: IdlTypeDefType,
327}
328
329#[derive(Serialize)]
330#[serde(untagged)]
331enum IdlTypeDefType {
332 Struct { fields: Vec<IdlField> },
333 Enum { variants: Vec<IdlEnumVariant> },
334}
335
336#[derive(Serialize)]
337struct IdlEnumVariant {
338 name: String,
339}
340
341#[derive(Serialize)]
342struct IdlEvent {
343 name: String,
344 fields: Vec<IdlEventField>,
345}
346
347#[derive(Serialize)]
348struct IdlEventField {
349 name: String,
350 #[serde(rename = "type")]
351 ty: IdlType,
352 index: bool,
353}
354
355#[derive(Serialize)]
356struct IdlError {
357 code: u32,
358 name: String,
359 msg: String,
360}
361
362#[derive(Serialize)]
363#[serde(untagged)]
364enum IdlType {
365 Primitive(String),
366 Defined(String),
367 Array { array: (Box<IdlType>, usize) },
368 Vec { vec: Box<IdlType> },
369 Option { option: Box<IdlType> },
370}
371
372fn to_camel_case(s: &str) -> String {
374 let mut result = String::new();
375 let mut capitalize_next = true;
376
377 for c in s.chars() {
378 if c == '_' || c == '-' {
379 capitalize_next = true;
380 } else if capitalize_next {
381 result.push(c.to_ascii_uppercase());
382 capitalize_next = false;
383 } else {
384 result.push(c);
385 }
386 }
387
388 result
389}
390
391fn to_camel_case_lower(s: &str) -> String {
392 let camel = to_camel_case(s);
393 let mut chars = camel.chars();
394 match chars.next() {
395 None => String::new(),
396 Some(c) => c.to_ascii_lowercase().to_string() + chars.as_str(),
397 }
398}
399
400fn to_snake_case(s: &str) -> String {
401 let mut result = String::new();
402 for (i, c) in s.chars().enumerate() {
403 if c.is_uppercase() && i > 0 {
404 result.push('_');
405 result.push(c.to_ascii_lowercase());
406 } else {
407 result.push(c.to_ascii_lowercase());
408 }
409 }
410 result
411}