Skip to main content

runar_compiler_rust/
artifact.rs

1//! Rúnar Artifact -- the final compiled output of a Rúnar compiler.
2//!
3//! This is what gets consumed by wallets, SDKs, and deployment tooling.
4
5use serde::{Deserialize, Serialize};
6
7use crate::codegen::emit::ConstructorSlot;
8use crate::ir::ANFProgram;
9
10// ---------------------------------------------------------------------------
11// ABI types
12// ---------------------------------------------------------------------------
13
14#[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// ---------------------------------------------------------------------------
43// State fields
44// ---------------------------------------------------------------------------
45
46#[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// ---------------------------------------------------------------------------
57// Top-level artifact
58// ---------------------------------------------------------------------------
59
60#[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
84// ---------------------------------------------------------------------------
85// Assembly
86// ---------------------------------------------------------------------------
87
88const SCHEMA_VERSION: &str = "runar-v0.1.0";
89const COMPILER_VERSION: &str = "0.1.0-rust";
90
91/// Build a RunarArtifact from the compilation products.
92pub 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    // Build constructor params from properties, excluding those with initializers
102    // (properties with default values are not constructor parameters).
103    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    // Build state fields for stateful contracts.
114    // Index = property position (matching constructor arg order), not sequential mutable index.
115    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    // Build method ABIs (exclude constructor — it's in abi.constructor, not methods)
129    let methods: Vec<ABIMethod> = program
130        .methods
131        .iter()
132        .filter(|m| m.name != "constructor")
133        .map(|m| {
134            // For stateful contracts, mark public methods without _changePKH as terminal
135            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    // Timestamp
158    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
198/// Simple UTC timestamp without pulling in the full chrono crate.
199fn 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    // Convert epoch seconds to a rough ISO-8601 string.
208    // This is a simplified implementation; for production use chrono.
209    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    // Days since epoch to Y-M-D (simplified leap-year-aware calculation)
216    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    // Civil date algorithm from Howard Hinnant
226    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}