use serde::{Deserialize, Serialize};
use crate::codegen::emit::{ConstructorSlot, SourceMapping};
use crate::ir::ANFProgram;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ABIParam {
pub name: String,
#[serde(rename = "type")]
pub param_type: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ABIConstructor {
pub params: Vec<ABIParam>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ABIMethod {
pub name: String,
pub params: Vec<ABIParam>,
#[serde(rename = "isPublic")]
pub is_public: bool,
#[serde(rename = "isTerminal", skip_serializing_if = "Option::is_none")]
pub is_terminal: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ABI {
pub constructor: ABIConstructor,
pub methods: Vec<ABIMethod>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateField {
pub name: String,
#[serde(rename = "type")]
pub field_type: String,
pub index: usize,
#[serde(rename = "initialValue", skip_serializing_if = "Option::is_none")]
pub initial_value: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceMapData {
pub mappings: Vec<SourceMapping>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IRDebug {
#[serde(skip_serializing_if = "Option::is_none")]
pub anf: Option<ANFProgram>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RunarArtifact {
pub version: String,
#[serde(rename = "compilerVersion")]
pub compiler_version: String,
#[serde(rename = "contractName")]
pub contract_name: String,
pub abi: ABI,
pub script: String,
pub asm: String,
#[serde(rename = "sourceMap", skip_serializing_if = "Option::is_none")]
pub source_map: Option<SourceMapData>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ir: Option<IRDebug>,
#[serde(rename = "stateFields", skip_serializing_if = "Vec::is_empty")]
pub state_fields: Vec<StateField>,
#[serde(rename = "constructorSlots", skip_serializing_if = "Vec::is_empty", default)]
pub constructor_slots: Vec<ConstructorSlot>,
#[serde(rename = "codeSeparatorIndex", skip_serializing_if = "Option::is_none")]
pub code_separator_index: Option<usize>,
#[serde(rename = "codeSeparatorIndices", skip_serializing_if = "Option::is_none")]
pub code_separator_indices: Option<Vec<usize>>,
#[serde(rename = "buildTimestamp")]
pub build_timestamp: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub anf: Option<ANFProgram>,
}
const SCHEMA_VERSION: &str = "runar-v0.4.6";
const COMPILER_VERSION: &str = "0.4.6-rust";
pub fn assemble_artifact(
program: &ANFProgram,
script_hex: &str,
script_asm: &str,
constructor_slots: Vec<ConstructorSlot>,
code_separator_index: i64,
code_separator_indices: Vec<usize>,
include_anf: bool,
source_mappings: Vec<SourceMapping>,
) -> RunarArtifact {
let constructor_params: Vec<ABIParam> = program
.properties
.iter()
.filter(|p| p.initial_value.is_none())
.map(|p| ABIParam {
name: p.name.clone(),
param_type: p.prop_type.clone(),
})
.collect();
let mut state_fields = Vec::new();
for (i, prop) in program.properties.iter().enumerate() {
if !prop.readonly {
state_fields.push(StateField {
name: prop.name.clone(),
field_type: prop.prop_type.clone(),
index: i,
initial_value: prop.initial_value.clone(),
});
}
}
let is_stateful = !state_fields.is_empty();
let methods: Vec<ABIMethod> = program
.methods
.iter()
.filter(|m| m.name != "constructor")
.map(|m| {
let is_terminal = if is_stateful && m.is_public {
let has_change = m.params.iter().any(|p| p.name == "_changePKH");
if !has_change { Some(true) } else { None }
} else {
None
};
ABIMethod {
name: m.name.clone(),
params: m
.params
.iter()
.map(|p| ABIParam {
name: p.name.clone(),
param_type: p.param_type.clone(),
})
.collect(),
is_public: m.is_public,
is_terminal,
}
})
.collect();
let now = chrono_lite_utc_now();
let cs_index = if code_separator_index >= 0 {
Some(code_separator_index as usize)
} else {
None
};
let cs_indices = if code_separator_indices.is_empty() {
None
} else {
Some(code_separator_indices)
};
let anf = if include_anf {
Some(program.clone())
} else {
None
};
let source_map = if source_mappings.is_empty() {
None
} else {
Some(SourceMapData {
mappings: source_mappings,
})
};
let ir = if include_anf {
Some(IRDebug {
anf: Some(program.clone()),
})
} else {
None
};
RunarArtifact {
version: SCHEMA_VERSION.to_string(),
compiler_version: COMPILER_VERSION.to_string(),
contract_name: program.contract_name.clone(),
abi: ABI {
constructor: ABIConstructor {
params: constructor_params,
},
methods,
},
script: script_hex.to_string(),
asm: script_asm.to_string(),
source_map,
ir,
state_fields,
constructor_slots,
code_separator_index: cs_index,
code_separator_indices: cs_indices,
build_timestamp: now,
anf,
}
}
fn chrono_lite_utc_now() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let duration = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
let secs = duration.as_secs();
let days = secs / 86400;
let time_of_day = secs % 86400;
let hours = time_of_day / 3600;
let minutes = (time_of_day % 3600) / 60;
let seconds = time_of_day % 60;
let (year, month, day) = epoch_days_to_ymd(days);
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
year, month, day, hours, minutes, seconds
)
}
fn epoch_days_to_ymd(days: u64) -> (u64, u64, u64) {
let z = days + 719468;
let era = z / 146097;
let doe = z - era * 146097;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let year = if m <= 2 { y + 1 } else { y };
(year, m, d)
}