1use serde::{Deserialize, Serialize};
6
7use crate::codegen::emit::{ConstructorSlot, SourceMapping};
8use crate::ir::ANFProgram;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct ABIParam {
16 pub name: String,
17 #[serde(rename = "type")]
18 pub param_type: String,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct ABIConstructor {
23 pub params: Vec<ABIParam>,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ABIMethod {
28 pub name: String,
29 pub params: Vec<ABIParam>,
30 #[serde(rename = "isPublic")]
31 pub is_public: bool,
32 #[serde(rename = "isTerminal", skip_serializing_if = "Option::is_none")]
33 pub is_terminal: Option<bool>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct ABI {
38 pub constructor: ABIConstructor,
39 pub methods: Vec<ABIMethod>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct StateField {
48 pub name: String,
49 #[serde(rename = "type")]
50 pub field_type: String,
51 pub index: usize,
52 #[serde(rename = "initialValue", skip_serializing_if = "Option::is_none")]
53 pub initial_value: Option<serde_json::Value>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct SourceMapData {
63 pub mappings: Vec<SourceMapping>,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct IRDebug {
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub anf: Option<ANFProgram>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct RunarArtifact {
79 pub version: String,
80 #[serde(rename = "compilerVersion")]
81 pub compiler_version: String,
82 #[serde(rename = "contractName")]
83 pub contract_name: String,
84 pub abi: ABI,
85 pub script: String,
86 pub asm: String,
87 #[serde(rename = "sourceMap", skip_serializing_if = "Option::is_none")]
88 pub source_map: Option<SourceMapData>,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub ir: Option<IRDebug>,
91 #[serde(rename = "stateFields", skip_serializing_if = "Vec::is_empty")]
92 pub state_fields: Vec<StateField>,
93 #[serde(rename = "constructorSlots", skip_serializing_if = "Vec::is_empty", default)]
94 pub constructor_slots: Vec<ConstructorSlot>,
95 #[serde(rename = "codeSeparatorIndex", skip_serializing_if = "Option::is_none")]
96 pub code_separator_index: Option<usize>,
97 #[serde(rename = "codeSeparatorIndices", skip_serializing_if = "Option::is_none")]
98 pub code_separator_indices: Option<Vec<usize>>,
99 #[serde(rename = "buildTimestamp")]
100 pub build_timestamp: String,
101 #[serde(skip_serializing_if = "Option::is_none")]
102 pub anf: Option<ANFProgram>,
103}
104
105const SCHEMA_VERSION: &str = "runar-v0.4.4";
110const COMPILER_VERSION: &str = "0.4.4-rust";
111
112pub fn assemble_artifact(
114 program: &ANFProgram,
115 script_hex: &str,
116 script_asm: &str,
117 constructor_slots: Vec<ConstructorSlot>,
118 code_separator_index: i64,
119 code_separator_indices: Vec<usize>,
120 include_anf: bool,
121 source_mappings: Vec<SourceMapping>,
122) -> RunarArtifact {
123 let constructor_params: Vec<ABIParam> = program
126 .properties
127 .iter()
128 .filter(|p| p.initial_value.is_none())
129 .map(|p| ABIParam {
130 name: p.name.clone(),
131 param_type: p.prop_type.clone(),
132 })
133 .collect();
134
135 let mut state_fields = Vec::new();
138 for (i, prop) in program.properties.iter().enumerate() {
139 if !prop.readonly {
140 state_fields.push(StateField {
141 name: prop.name.clone(),
142 field_type: prop.prop_type.clone(),
143 index: i,
144 initial_value: prop.initial_value.clone(),
145 });
146 }
147 }
148 let is_stateful = !state_fields.is_empty();
149
150 let methods: Vec<ABIMethod> = program
152 .methods
153 .iter()
154 .filter(|m| m.name != "constructor")
155 .map(|m| {
156 let is_terminal = if is_stateful && m.is_public {
158 let has_change = m.params.iter().any(|p| p.name == "_changePKH");
159 if !has_change { Some(true) } else { None }
160 } else {
161 None
162 };
163 ABIMethod {
164 name: m.name.clone(),
165 params: m
166 .params
167 .iter()
168 .map(|p| ABIParam {
169 name: p.name.clone(),
170 param_type: p.param_type.clone(),
171 })
172 .collect(),
173 is_public: m.is_public,
174 is_terminal,
175 }
176 })
177 .collect();
178
179 let now = chrono_lite_utc_now();
181
182 let cs_index = if code_separator_index >= 0 {
183 Some(code_separator_index as usize)
184 } else {
185 None
186 };
187 let cs_indices = if code_separator_indices.is_empty() {
188 None
189 } else {
190 Some(code_separator_indices)
191 };
192
193 let anf = if include_anf {
194 Some(program.clone())
195 } else {
196 None
197 };
198
199 let source_map = if source_mappings.is_empty() {
200 None
201 } else {
202 Some(SourceMapData {
203 mappings: source_mappings,
204 })
205 };
206
207 let ir = if include_anf {
208 Some(IRDebug {
209 anf: Some(program.clone()),
210 })
211 } else {
212 None
213 };
214
215 RunarArtifact {
216 version: SCHEMA_VERSION.to_string(),
217 compiler_version: COMPILER_VERSION.to_string(),
218 contract_name: program.contract_name.clone(),
219 abi: ABI {
220 constructor: ABIConstructor {
221 params: constructor_params,
222 },
223 methods,
224 },
225 script: script_hex.to_string(),
226 asm: script_asm.to_string(),
227 source_map,
228 ir,
229 state_fields,
230 constructor_slots,
231 code_separator_index: cs_index,
232 code_separator_indices: cs_indices,
233 build_timestamp: now,
234 anf,
235 }
236}
237
238fn chrono_lite_utc_now() -> String {
240 use std::time::{SystemTime, UNIX_EPOCH};
241
242 let duration = SystemTime::now()
243 .duration_since(UNIX_EPOCH)
244 .unwrap_or_default();
245 let secs = duration.as_secs();
246
247 let days = secs / 86400;
250 let time_of_day = secs % 86400;
251 let hours = time_of_day / 3600;
252 let minutes = (time_of_day % 3600) / 60;
253 let seconds = time_of_day % 60;
254
255 let (year, month, day) = epoch_days_to_ymd(days);
257
258 format!(
259 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
260 year, month, day, hours, minutes, seconds
261 )
262}
263
264fn epoch_days_to_ymd(days: u64) -> (u64, u64, u64) {
265 let z = days + 719468;
267 let era = z / 146097;
268 let doe = z - era * 146097;
269 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
270 let y = yoe + era * 400;
271 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
272 let mp = (5 * doy + 2) / 153;
273 let d = doy - (153 * mp + 2) / 5 + 1;
274 let m = if mp < 10 { mp + 3 } else { mp - 9 };
275 let year = if m <= 2 { y + 1 } else { y };
276 (year, m, d)
277}