1use serde::{Deserialize, Serialize};
6
7use crate::codegen::emit::ConstructorSlot;
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)]
61pub struct RunarArtifact {
62 pub version: String,
63 #[serde(rename = "compilerVersion")]
64 pub compiler_version: String,
65 #[serde(rename = "contractName")]
66 pub contract_name: String,
67 pub abi: ABI,
68 pub script: String,
69 pub asm: String,
70 #[serde(rename = "stateFields", skip_serializing_if = "Vec::is_empty")]
71 pub state_fields: Vec<StateField>,
72 #[serde(rename = "constructorSlots", skip_serializing_if = "Vec::is_empty", default)]
73 pub constructor_slots: Vec<ConstructorSlot>,
74 #[serde(rename = "codeSeparatorIndex", skip_serializing_if = "Option::is_none")]
75 pub code_separator_index: Option<usize>,
76 #[serde(rename = "codeSeparatorIndices", skip_serializing_if = "Option::is_none")]
77 pub code_separator_indices: Option<Vec<usize>>,
78 #[serde(rename = "buildTimestamp")]
79 pub build_timestamp: String,
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub anf: Option<ANFProgram>,
82}
83
84const SCHEMA_VERSION: &str = "runar-v0.1.0";
89const COMPILER_VERSION: &str = "0.1.0-rust";
90
91pub fn assemble_artifact(
93 program: &ANFProgram,
94 script_hex: &str,
95 script_asm: &str,
96 constructor_slots: Vec<ConstructorSlot>,
97 code_separator_index: i64,
98 code_separator_indices: Vec<usize>,
99 include_anf: bool,
100) -> RunarArtifact {
101 let constructor_params: Vec<ABIParam> = program
104 .properties
105 .iter()
106 .filter(|p| p.initial_value.is_none())
107 .map(|p| ABIParam {
108 name: p.name.clone(),
109 param_type: p.prop_type.clone(),
110 })
111 .collect();
112
113 let mut state_fields = Vec::new();
116 for (i, prop) in program.properties.iter().enumerate() {
117 if !prop.readonly {
118 state_fields.push(StateField {
119 name: prop.name.clone(),
120 field_type: prop.prop_type.clone(),
121 index: i,
122 initial_value: prop.initial_value.clone(),
123 });
124 }
125 }
126 let is_stateful = !state_fields.is_empty();
127
128 let methods: Vec<ABIMethod> = program
130 .methods
131 .iter()
132 .filter(|m| m.name != "constructor")
133 .map(|m| {
134 let is_terminal = if is_stateful && m.is_public {
136 let has_change = m.params.iter().any(|p| p.name == "_changePKH");
137 if !has_change { Some(true) } else { None }
138 } else {
139 None
140 };
141 ABIMethod {
142 name: m.name.clone(),
143 params: m
144 .params
145 .iter()
146 .map(|p| ABIParam {
147 name: p.name.clone(),
148 param_type: p.param_type.clone(),
149 })
150 .collect(),
151 is_public: m.is_public,
152 is_terminal,
153 }
154 })
155 .collect();
156
157 let now = chrono_lite_utc_now();
159
160 let cs_index = if code_separator_index >= 0 {
161 Some(code_separator_index as usize)
162 } else {
163 None
164 };
165 let cs_indices = if code_separator_indices.is_empty() {
166 None
167 } else {
168 Some(code_separator_indices)
169 };
170
171 let anf = if include_anf {
172 Some(program.clone())
173 } else {
174 None
175 };
176
177 RunarArtifact {
178 version: SCHEMA_VERSION.to_string(),
179 compiler_version: COMPILER_VERSION.to_string(),
180 contract_name: program.contract_name.clone(),
181 abi: ABI {
182 constructor: ABIConstructor {
183 params: constructor_params,
184 },
185 methods,
186 },
187 script: script_hex.to_string(),
188 asm: script_asm.to_string(),
189 state_fields,
190 constructor_slots,
191 code_separator_index: cs_index,
192 code_separator_indices: cs_indices,
193 build_timestamp: now,
194 anf,
195 }
196}
197
198fn chrono_lite_utc_now() -> String {
200 use std::time::{SystemTime, UNIX_EPOCH};
201
202 let duration = SystemTime::now()
203 .duration_since(UNIX_EPOCH)
204 .unwrap_or_default();
205 let secs = duration.as_secs();
206
207 let days = secs / 86400;
210 let time_of_day = secs % 86400;
211 let hours = time_of_day / 3600;
212 let minutes = (time_of_day % 3600) / 60;
213 let seconds = time_of_day % 60;
214
215 let (year, month, day) = epoch_days_to_ymd(days);
217
218 format!(
219 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
220 year, month, day, hours, minutes, seconds
221 )
222}
223
224fn epoch_days_to_ymd(days: u64) -> (u64, u64, u64) {
225 let z = days + 719468;
227 let era = z / 146097;
228 let doe = z - era * 146097;
229 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
230 let y = yoe + era * 400;
231 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
232 let mp = (5 * doy + 2) / 153;
233 let d = doy - (153 * mp + 2) / 5 + 1;
234 let m = if mp < 10 { mp + 3 } else { mp - 9 };
235 let year = if m <= 2 { y + 1 } else { y };
236 (year, m, d)
237}