neo_solidity/cli/standard_json/
standard_json_output.rs1pub(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 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 if ty.ends_with("[]") {
189 return "Array";
190 }
191
192 if ty.starts_with("mapping") {
194 return "Map";
195 }
196
197 if ty.starts_with("uint") || ty.starts_with("int") {
199 return "Integer";
200 }
201
202 if ty == "bool" || ty == "boolean" {
204 return "Boolean";
205 }
206
207 if ty == "string" {
209 return "String";
210 }
211
212 if ty == "address" || ty == "address payable" || ty == "bytes20" || ty == "hash160" {
214 return "Hash160";
215 }
216
217 if ty == "bytes32" || ty == "hash256" {
219 return "Hash256";
220 }
221
222 if ty == "bytes" {
224 return "ByteArray";
225 }
226 if ty.starts_with("bytes") {
227 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 if ty == "void" || ty.is_empty() {
242 return "Void";
243 }
244
245 "Any"
247}