huff_neo_utils/artifact.rs
1//! ## Artifact
2//!
3//! The artifacts generated from codegen.
4
5use serde::{Deserialize, Serialize};
6use std::{collections::BTreeMap, fs, path::Path, sync::Arc};
7
8pub use crate::abi::Abi;
9use crate::file::file_source::FileSource;
10
11/// A bytecode source mapping entry
12#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
13#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
14#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
15pub struct SourceMapEntry {
16 /// Instruction Counter (IC) - the instruction index (0, 1, 2, ...)
17 /// This is used to index into the source map array
18 pub ic: usize,
19 /// Program Counter (PC) - the byte offset in the bytecode where this instruction starts
20 /// For example: if PUSH1 0x42 starts at byte 0, PC = 0
21 pub pc: usize,
22 /// Length of the bytecode segment for this instruction (in bytes)
23 /// For example: PUSH1 0x42 = 2 bytes, PUSH0 = 1 byte, SSTORE = 1 byte
24 pub bytecode_length: usize,
25 /// Start position in the source file (character offset)
26 pub source_start: usize,
27 /// Length of the source code that generated this instruction (in characters)
28 /// This is NOT the bytecode length, but the source text length
29 pub source_length: usize,
30 /// File ID for multi-file support (0 for main file, 1+ for includes)
31 #[serde(default)]
32 pub file_id: u32,
33}
34
35impl SourceMapEntry {
36 /// Generate a Solidity-compatible source map from a vector of SourceMapEntry
37 ///
38 /// Solidity format: "s:l:f:j" where:
39 /// - s = source start position
40 /// - l = length in source (end - start)
41 /// - f = source file index (0 for single file)
42 /// - j = jump type (i/o/- for into/out/regular)
43 ///
44 /// Multiple entries separated by semicolons, with omitted values if same as previous
45 pub fn generate_solidity_source_map(entries: &[SourceMapEntry]) -> String {
46 if entries.is_empty() {
47 return String::new();
48 }
49
50 let mut result = Vec::new();
51 let mut prev_start = None;
52 let mut prev_length = None;
53 let mut prev_file = None;
54
55 for entry in entries {
56 let mut parts = Vec::new();
57
58 // First field is the start position in the source file
59 // NOT the instruction counter! Solidity source maps use source offset
60 if prev_start != Some(entry.source_start) {
61 parts.push(entry.source_start.to_string());
62 prev_start = Some(entry.source_start);
63 } else {
64 parts.push(String::new());
65 }
66
67 // Length in source
68 if prev_length != Some(entry.source_length) {
69 parts.push(entry.source_length.to_string());
70 prev_length = Some(entry.source_length);
71 } else {
72 parts.push(String::new());
73 }
74
75 // File index for multi-file support
76 let file_index = entry.file_id;
77 if prev_file != Some(file_index) {
78 parts.push(file_index.to_string());
79 prev_file = Some(file_index);
80 } else {
81 parts.push(String::new());
82 }
83
84 // Jump type - for now always regular (-), could be enhanced
85 parts.push("-".to_string());
86
87 result.push(parts.join(":"));
88 }
89
90 result.join(";")
91 }
92}
93
94/// A Codegen Artifact
95#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
96pub struct Artifact {
97 /// The file source
98 pub file: Arc<FileSource>,
99 /// The deployed bytecode
100 pub bytecode: String,
101 /// The runtime bytecode
102 pub runtime: String,
103 /// The abi
104 pub abi: Option<Abi>,
105 /// Source map for constructor
106 pub constructor_map: Option<Vec<SourceMapEntry>>,
107 /// Source map for runtime
108 pub runtime_map: Option<Vec<SourceMapEntry>>,
109 /// True when the user's CONSTRUCTOR returns its own bytecode, suppressing the auto-bootstrap.
110 /// In that case the deployed runtime is whatever the constructor returns and is not
111 /// statically known to equal the compiled MAIN macro.
112 #[serde(default)]
113 pub has_custom_bootstrap: bool,
114 /// Constant overrides the artifact was compiled with, as hex values by constant name.
115 /// A cached artifact is only reused when these match the overrides of the current build.
116 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
117 pub constant_overrides: BTreeMap<String, String>,
118 /// Constructor arguments the artifact was compiled with, as given on the command line.
119 /// A cached artifact is only reused when these match the arguments of the current build.
120 #[serde(default, skip_serializing_if = "Vec::is_empty")]
121 pub constructor_args: Vec<String>,
122}
123
124impl Artifact {
125 /// Exports an artifact to a json file
126 pub fn export(&self, out: &str) -> Result<(), std::io::Error> {
127 let serialized_artifact = serde_json::to_string_pretty(self)?;
128 let file_path = Path::new(out);
129 if let Some(p) = file_path.parent() {
130 tracing::debug!(target: "abi", "Creating directory: \"{:?}\"", p);
131 fs::create_dir_all(p)?
132 }
133 fs::write(file_path, serialized_artifact)
134 }
135}