neo_solidity/cli/standard_json/standard_json_process/
process.rs1pub(crate) fn process_standard_json(
2 input_path: &str,
3 output_path: Option<&str>,
4 options: StandardJsonOptions<'_>,
5) -> Result<(), String> {
6 let input_content = fs::read_to_string(input_path)
7 .map_err(|err| format!("Failed to read input file: {err}"))?;
8 process_standard_json_content(&input_content, output_path, options)
9}
10
11pub(crate) fn process_standard_json_content(
12 input_content: &str,
13 output_path: Option<&str>,
14 options: StandardJsonOptions<'_>,
15) -> Result<(), String> {
16 let request: StandardJsonInput = serde_json::from_str(input_content)
17 .map_err(|err| format!("Failed to parse standard JSON input: {err}"))?;
18
19 if request.sources.is_empty() {
20 return Err("Standard JSON input must contain at least one source".to_string());
21 }
22
23 if !request.language.trim().is_empty() && !request.language.eq_ignore_ascii_case("solidity") {
24 return Err(format!(
25 "Unsupported language '{}' in standard JSON input",
26 request.language
27 ));
28 }
29
30 let mut optimizer_level = options.optimizer_level.min(3);
31 optimizer_level = read_optimizer_level(&request.settings).unwrap_or(optimizer_level);
32 let mut contracts_output = Map::new();
33 let mut sources_output = Map::new();
34 let mut errors: Vec<Value> = Vec::new();
35 if let Some(warning) = unsupported_settings_warning(&request.settings) {
36 errors.push(warning);
37 }
38 let mut ordered_sources: Vec<(String, String, String)> = Vec::new();
39
40 for (index, (file_name, source)) in request.sources.iter().enumerate() {
41 sources_output.insert(file_name.clone(), json!({ "id": index as u32 }));
42 let Some(content) = source.content.as_ref() else {
43 let mut message = format!(
44 "Source '{file_name}' is missing inline content; URL imports are not supported."
45 );
46 if let Some(urls) = &source.urls {
47 if let Some(first_url) = urls.first() {
48 message.push_str(&format!(" First URL provided: '{first_url}'."));
49 }
50 }
51 errors.push(json!({
52 "component": "neo-solidity",
53 "severity": "error",
54 "type": "MissingSourceContent",
55 "sourceLocation": { "file": file_name },
56 "formattedMessage": message,
57 "message": message,
58 }));
59 continue;
60 };
61
62 let keccak_hex = keccak256_hex(content);
63 let keccak_prefixed = hex_prefixed(&keccak_hex);
64 sources_output.insert(
65 file_name.clone(),
66 json!({
67 "id": index as u32,
68 "keccak256": keccak_prefixed,
69 }),
70 );
71
72 ordered_sources.push((file_name.clone(), content.clone(), keccak_prefixed));
73 }
74
75 if !ordered_sources.is_empty() {
76 let combined_source =
77 build_combined_source_with_import_validation(&ordered_sources, &mut errors);
78
79 let contract_file_map = build_contract_file_map(&ordered_sources);
80
81 match compile_contracts_with_options(
82 &combined_source,
83 false,
84 CompileOptions {
85 optimizer_level,
86 use_callt: options.use_callt,
87 deny_wildcard_permissions: options.deny_wildcard_permissions,
88 deny_wildcard_contracts: options.deny_wildcard_contracts,
89 deny_wildcard_methods: options.deny_wildcard_methods,
90 manifest_permissions: options.manifest_permissions.clone(),
91 },
92 ) {
93 Ok(artifacts) => {
94 let artifacts: Vec<CompilationArtifacts> = if options.contract_names.is_empty() {
95 artifacts
96 } else {
97 artifacts
98 .into_iter()
99 .filter(|artifact| {
100 options
101 .contract_names
102 .iter()
103 .any(|name| name == &artifact.metadata.name)
104 })
105 .collect()
106 };
107
108 if artifacts.is_empty() {
109 let file_name = &ordered_sources[0].0;
110 if options.contract_names.is_empty() {
111 errors.push(json!({
112 "component": "neo-solidity",
113 "severity": "error",
114 "type": "NoContracts",
115 "sourceLocation": { "file": file_name },
116 "formattedMessage": format!("No contracts found in {file_name}"),
117 "message": format!("No contracts found in {file_name}"),
118 }));
119 } else {
120 let names = options.contract_names.join(", ");
121 errors.push(json!({
122 "component": "neo-solidity",
123 "severity": "error",
124 "type": "ContractNotFound",
125 "sourceLocation": { "file": file_name },
126 "formattedMessage": format!("No matching contract(s) found for --contract {names}"),
127 "message": format!("No matching contract(s) found for --contract {names}"),
128 }));
129 }
130 } else {
131 for artifact in artifacts {
132 let target_file = contract_file_map
133 .get(&artifact.metadata.name)
134 .cloned()
135 .unwrap_or_else(|| ordered_sources[0].0.clone());
136
137 let source_keccak = ordered_sources
138 .iter()
139 .find(|(name, _, _)| *name == target_file)
140 .map(|(_, _, keccak)| keccak.as_str());
141
142 let abi_entries = build_standard_abi(&artifact.metadata);
143 let raw_source = options.nef_source.unwrap_or(target_file.as_str());
144 let (source_field, truncated) = clamp_nef_source_with_flag(raw_source);
145 if truncated {
146 errors.push(json!({
147 "component": "neo-solidity",
148 "severity": "warning",
149 "type": "NefSourceTruncated",
150 "code": "NEF_SOURCE_TRUNCATED",
151 "sourceLocation": { "file": target_file },
152 "formattedMessage": format!(
153 "NEF source exceeds {} bytes and will be truncated",
154 NEF_SOURCE_MAX_BYTES
155 ),
156 "message": format!(
157 "NEF source exceeds {} bytes and will be truncated",
158 NEF_SOURCE_MAX_BYTES
159 ),
160 }));
161 }
162
163 let compiled_contract = match build_compiled_contract_value(
164 &target_file,
165 &artifact,
166 &abi_entries,
167 &request.settings,
168 source_keccak,
169 Some(source_field.as_ref()),
170 ) {
171 Ok(value) => value,
172 Err(message) => {
173 errors.push(json!({
174 "component": "neo-solidity",
175 "severity": "error",
176 "type": "StandardJsonOutput",
177 "sourceLocation": { "file": target_file },
178 "formattedMessage": message,
179 "message": message,
180 }));
181 continue;
182 }
183 };
184
185 let per_file_value = contracts_output
186 .entry(target_file.clone())
187 .or_insert_with(|| Value::Object(Map::new()));
188 let per_file = match per_file_value {
189 Value::Object(map) => map,
190 other => {
191 *other = Value::Object(Map::new());
192 match other {
193 Value::Object(map) => map,
194 _ => {
195 errors.push(json!({
196 "component": "neo-solidity",
197 "severity": "error",
198 "type": "StandardJsonOutput",
199 "sourceLocation": { "file": target_file },
200 "formattedMessage": "Failed to build standard JSON output: contracts entry is not an object",
201 "message": "Failed to build standard JSON output: contracts entry is not an object",
202 }));
203 continue;
204 }
205 }
206 }
207 };
208
209 per_file.insert(artifact.metadata.name.clone(), compiled_contract);
210
211 for warning in &artifact.warnings {
212 errors.push(diagnostic_to_standard_error(warning, &target_file));
213 }
214 }
215 }
216 }
217 Err(err) => {
218 let file_hint = ordered_sources
219 .first()
220 .map(|(name, _, _)| name.as_str())
221 .unwrap_or("unknown");
222 errors.extend(err.into_errors(file_hint));
223 }
224 }
225 }
226
227 let mut output = Map::new();
228 output.insert("contracts".into(), Value::Object(contracts_output));
229 output.insert("sources".into(), Value::Object(sources_output));
230 if !errors.is_empty() {
231 output.insert("errors".into(), Value::Array(errors));
232 }
233
234 let serialized = serde_json::to_string_pretty(&Value::Object(output))
235 .map_err(|err| format!("Failed to serialise standard JSON output: {err}"))?;
236 if let Some(path) = output_path {
237 fs::write(path, serialized)
238 .map_err(|err| format!("Failed to write standard JSON output: {err}"))?;
239 } else {
240 println!("{serialized}");
241 }
242
243 Ok(())
244}