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}
53
54// ---------------------------------------------------------------------------
55// Top-level artifact
56// ---------------------------------------------------------------------------
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct RunarArtifact {
60    pub version: String,
61    #[serde(rename = "compilerVersion")]
62    pub compiler_version: String,
63    #[serde(rename = "contractName")]
64    pub contract_name: String,
65    pub abi: ABI,
66    pub script: String,
67    pub asm: String,
68    #[serde(rename = "stateFields", skip_serializing_if = "Vec::is_empty")]
69    pub state_fields: Vec<StateField>,
70    #[serde(rename = "constructorSlots", skip_serializing_if = "Vec::is_empty", default)]
71    pub constructor_slots: Vec<ConstructorSlot>,
72    #[serde(rename = "buildTimestamp")]
73    pub build_timestamp: String,
74}
75
76// ---------------------------------------------------------------------------
77// Assembly
78// ---------------------------------------------------------------------------
79
80const SCHEMA_VERSION: &str = "runar-v0.1.0";
81const COMPILER_VERSION: &str = "0.1.0-rust";
82
83/// Build a RunarArtifact from the compilation products.
84pub fn assemble_artifact(
85    program: &ANFProgram,
86    script_hex: &str,
87    script_asm: &str,
88    constructor_slots: Vec<ConstructorSlot>,
89) -> RunarArtifact {
90    // Build constructor params from properties
91    let constructor_params: Vec<ABIParam> = program
92        .properties
93        .iter()
94        .map(|p| ABIParam {
95            name: p.name.clone(),
96            param_type: p.prop_type.clone(),
97        })
98        .collect();
99
100    // Build state fields for stateful contracts.
101    // Index = property position (matching constructor arg order), not sequential mutable index.
102    let mut state_fields = Vec::new();
103    for (i, prop) in program.properties.iter().enumerate() {
104        if !prop.readonly {
105            state_fields.push(StateField {
106                name: prop.name.clone(),
107                field_type: prop.prop_type.clone(),
108                index: i,
109            });
110        }
111    }
112    let is_stateful = !state_fields.is_empty();
113
114    // Build method ABIs
115    let methods: Vec<ABIMethod> = program
116        .methods
117        .iter()
118        .map(|m| {
119            // For stateful contracts, mark public methods without _changePKH as terminal
120            let is_terminal = if is_stateful && m.is_public {
121                let has_change = m.params.iter().any(|p| p.name == "_changePKH");
122                if !has_change { Some(true) } else { None }
123            } else {
124                None
125            };
126            ABIMethod {
127                name: m.name.clone(),
128                params: m
129                    .params
130                    .iter()
131                    .map(|p| ABIParam {
132                        name: p.name.clone(),
133                        param_type: p.param_type.clone(),
134                    })
135                    .collect(),
136                is_public: m.is_public,
137                is_terminal,
138            }
139        })
140        .collect();
141
142    // Timestamp
143    let now = chrono_lite_utc_now();
144
145    RunarArtifact {
146        version: SCHEMA_VERSION.to_string(),
147        compiler_version: COMPILER_VERSION.to_string(),
148        contract_name: program.contract_name.clone(),
149        abi: ABI {
150            constructor: ABIConstructor {
151                params: constructor_params,
152            },
153            methods,
154        },
155        script: script_hex.to_string(),
156        asm: script_asm.to_string(),
157        state_fields,
158        constructor_slots,
159        build_timestamp: now,
160    }
161}
162
163/// Simple UTC timestamp without pulling in the full chrono crate.
164fn chrono_lite_utc_now() -> String {
165    use std::time::{SystemTime, UNIX_EPOCH};
166
167    let duration = SystemTime::now()
168        .duration_since(UNIX_EPOCH)
169        .unwrap_or_default();
170    let secs = duration.as_secs();
171
172    // Convert epoch seconds to a rough ISO-8601 string.
173    // This is a simplified implementation; for production use chrono.
174    let days = secs / 86400;
175    let time_of_day = secs % 86400;
176    let hours = time_of_day / 3600;
177    let minutes = (time_of_day % 3600) / 60;
178    let seconds = time_of_day % 60;
179
180    // Days since epoch to Y-M-D (simplified leap-year-aware calculation)
181    let (year, month, day) = epoch_days_to_ymd(days);
182
183    format!(
184        "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
185        year, month, day, hours, minutes, seconds
186    )
187}
188
189fn epoch_days_to_ymd(days: u64) -> (u64, u64, u64) {
190    // Civil date algorithm from Howard Hinnant
191    let z = days + 719468;
192    let era = z / 146097;
193    let doe = z - era * 146097;
194    let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
195    let y = yoe + era * 400;
196    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
197    let mp = (5 * doy + 2) / 153;
198    let d = doy - (153 * mp + 2) / 5 + 1;
199    let m = if mp < 10 { mp + 3 } else { mp - 9 };
200    let year = if m <= 2 { y + 1 } else { y };
201    (year, m, d)
202}