Skip to main content

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}