neo_solidity/cli/standard_json/
standard_json_output.rs

1pub(crate) fn build_compiled_contract_value(
2    file_name: &str,
3    artifact: &CompilationArtifacts,
4    abi_entries: &[Value],
5    settings: &Value,
6    source_keccak: Option<&str>,
7    nef_source: Option<&str>,
8) -> Result<Value, String> {
9    let raw_source = nef_source.unwrap_or(file_name);
10    let (source_field, _) = clamp_nef_source_with_flag(raw_source);
11    let script_hex = hex::encode(&artifact.bytecode);
12    let bytecode_object = format!("0x{script_hex}");
13    let metadata_blob = build_metadata_blob(
14        &artifact.metadata.name,
15        abi_entries,
16        file_name,
17        settings,
18        source_keccak,
19    );
20    let method_identifiers = build_method_identifiers(&artifact.metadata);
21
22    let storage_map = build_storage_map(&artifact.metadata);
23    let manifest = artifact.manifest.clone();
24    let tokens_json: Vec<Value> = artifact
25        .tokens
26        .iter()
27        .map(|token| {
28            let hash_be: Vec<u8> = token.hash.iter().rev().copied().collect();
29            json!({
30                "hash": format!("0x{}", hex::encode(hash_be)),
31                "method": token.method.clone(),
32                "paramcount": token.parameters_count,
33                "hasreturnvalue": token.has_return_value,
34                "callflags": token.call_flags,
35            })
36        })
37        .collect();
38
39    let nef_bytes = build_nef_with_tokens(
40        &artifact.bytecode,
41        COMPILER_ID,
42        source_field.as_ref(),
43        &artifact.tokens,
44    )
45    .map_err(|err| format!("failed to build NEF image for standard JSON output: {err}"))?;
46    let checksum = if nef_bytes.len() >= 4 {
47        hex::encode(&nef_bytes[nef_bytes.len() - 4..])
48    } else {
49        "00000000".to_string()
50    };
51    let nef_image = hex::encode(nef_bytes);
52
53    Ok(json!({
54        "abi": abi_entries,
55        "metadata": metadata_blob,
56        "evm": {
57            "bytecode": {
58                "object": bytecode_object,
59                "opcodes": "",
60                "sourceMap": "",
61                "linkReferences": {}
62            },
63            "deployedBytecode": {
64                "object": bytecode_object,
65                "opcodes": "",
66                "sourceMap": "",
67                "linkReferences": {}
68            },
69            "methodIdentifiers": method_identifiers,
70        },
71        "neo": {
72            "nef": {
73                "magic": "NEF3",
74                "compiler": COMPILER_ID,
75                "source": source_field.as_ref(),
76                "tokens": tokens_json,
77                "script": script_hex,
78                "image": nef_image,
79                "checksum": checksum,
80            },
81            "manifest": manifest,
82            "storageMap": storage_map,
83            "gasEstimates": {
84                "creation": zero_gas_estimate_value(),
85                "functions": Value::Object(Map::new())
86            }
87        }
88    }))
89}
90
91pub(crate) fn build_metadata_blob(
92    contract_name: &str,
93    abi_entries: &[Value],
94    file_name: &str,
95    settings: &Value,
96    keccak_hex: Option<&str>,
97) -> String {
98    let keccak_field = keccak_hex.map(hex_prefixed).unwrap_or_default();
99    let metadata = json!({
100        "compiler": {
101            "name": COMPILER_ID,
102            "version": super::compiler_version_string_4()
103        },
104        "language": "Solidity",
105        "output": {
106            "abi": abi_entries,
107            "contractName": contract_name,
108        },
109        "settings": settings,
110        "sources": {
111            file_name: {
112                "keccak256": keccak_field,
113                "urls": []
114            }
115        },
116        "version": 1
117    });
118
119    serde_json::to_string(&metadata).unwrap_or_else(|_| "{}".to_string())
120}
121
122pub(crate) fn build_storage_map(metadata: &ContractMetadata) -> Value {
123    let mut entries = Map::new();
124    // Constant state variables are inlined and do not occupy storage.
125    for (slot, variable) in metadata
126        .state_variables
127        .iter()
128        .filter(|var| !var.is_constant)
129        .enumerate()
130    {
131        let name = variable
132            .name
133            .clone()
134            .unwrap_or_else(|| format!("slot_{slot}"));
135        entries.insert(
136            name,
137            json!({
138                "slot": slot,
139                "type": variable.ty,
140                "description": variable.visibility.clone().unwrap_or_default(),
141            }),
142        );
143    }
144    Value::Object(entries)
145}
146
147pub(crate) fn zero_gas_estimate_value() -> Value {
148    json!({
149        "gas": "0",
150        "systemFee": "0",
151        "networkFee": "0",
152    })
153}
154
155pub(crate) fn state_mutability_label(state: StateMutability) -> &'static str {
156    match state {
157        StateMutability::Pure => "pure",
158        StateMutability::View => "view",
159        StateMutability::Payable => "payable",
160        StateMutability::NonPayable => "nonpayable",
161    }
162}
163
164pub(crate) fn sanitize_contract_name(name: &str) -> Option<String> {
165    let filtered: String = name
166        .chars()
167        .map(|c| {
168            if c.is_ascii_alphanumeric() || c == '_' || c == '-' {
169                c
170            } else {
171                '_'
172            }
173        })
174        .collect();
175
176    if filtered.trim_matches('_').is_empty() {
177        None
178    } else {
179        Some(filtered.trim_matches('_').to_string())
180    }
181}
182
183pub(crate) fn solidity_to_manifest_type(solidity_type: &str) -> &'static str {
184    let ty = solidity_type.trim().to_ascii_lowercase();
185
186    // Array types must be checked FIRST (before checking base types)
187    // This ensures uint256[] returns "Array" not "Integer"
188    if ty.ends_with("[]") {
189        return "Array";
190    }
191
192    // Mapping types
193    if ty.starts_with("mapping") {
194        return "Map";
195    }
196
197    // Integer types (uint8-256, int8-256)
198    if ty.starts_with("uint") || ty.starts_with("int") {
199        return "Integer";
200    }
201
202    // Boolean
203    if ty == "bool" || ty == "boolean" {
204        return "Boolean";
205    }
206
207    // String
208    if ty == "string" {
209        return "String";
210    }
211
212    // Address types (Neo uses Hash160 for 20-byte addresses)
213    if ty == "address" || ty == "address payable" || ty == "bytes20" || ty == "hash160" {
214        return "Hash160";
215    }
216
217    // Hash types (must check before generic bytes handling)
218    if ty == "bytes32" || ty == "hash256" {
219        return "Hash256";
220    }
221
222    // Fixed-size byte arrays (bytes1-32)
223    if ty == "bytes" {
224        return "ByteArray";
225    }
226    if ty.starts_with("bytes") {
227        // bytes1, bytes2, ..., bytes32 are fixed-size
228        if let Some(size_str) = ty.strip_prefix("bytes") {
229            if size_str.parse::<u8>().is_ok() {
230                return if size_str == "32" {
231                    "Hash256"
232                } else {
233                    "ByteArray"
234                };
235            }
236        }
237        return "ByteArray";
238    }
239
240    // Void/empty return type
241    if ty == "void" || ty.is_empty() {
242        return "Void";
243    }
244
245    // Struct and other complex types
246    "Any"
247}