neo_solidity/cli/cli_parts/cli_manifest/
build.rs1fn parse_json_value(raw: &str, tag: &str) -> Option<Value> {
2 match serde_json::from_str::<Value>(raw) {
3 Ok(value) => Some(value),
4 Err(err) => {
5 eprintln!(
6 "warning: ignoring @custom:{tag} because its value is not valid JSON: {err}"
7 );
8 None
9 }
10 }
11}
12
13fn parse_json_or_string(raw: &str) -> Value {
14 serde_json::from_str::<Value>(raw).unwrap_or_else(|_| Value::String(raw.to_string()))
15}
16
17fn upsert_manifest_extra(manifest: &mut Value) -> Option<&mut serde_json::Map<String, Value>> {
18 let object = manifest.as_object_mut()?;
19 let entry = object
20 .entry("extra".to_string())
21 .or_insert_with(|| Value::Object(serde_json::Map::new()));
22
23 if !entry.is_object() {
24 *entry = Value::Object(serde_json::Map::new());
25 }
26
27 entry.as_object_mut()
28}
29
30fn apply_manifest_custom_overrides(manifest: &mut Value, metadata: &ContractMetadata) {
31 for (tag, raw_value) in &metadata.documentation.custom {
32 let raw = raw_value.trim();
33 if raw.is_empty() {
34 continue;
35 }
36
37 let Some(field) = tag
38 .strip_prefix("neo.manifest.")
39 .or_else(|| tag.strip_prefix("manifest."))
40 else {
41 continue;
42 };
43
44 match field {
45 "name" => {
46 let parsed = parse_json_or_string(raw);
47 let name = parsed
48 .as_str()
49 .map(|s| s.to_string())
50 .unwrap_or_else(|| raw.to_string());
51 manifest["name"] = Value::String(name);
52 }
53 "groups" => {
54 let Some(value) = parse_json_value(raw, tag) else {
55 continue;
56 };
57 if value.is_array() {
58 manifest["groups"] = value;
59 } else {
60 eprintln!(
61 "warning: ignoring @custom:{tag}; expected a JSON array for manifest.groups"
62 );
63 }
64 }
65 "features" => {
66 let Some(value) = parse_json_value(raw, tag) else {
67 continue;
68 };
69 if !value.is_object() {
70 eprintln!(
71 "warning: ignoring @custom:{tag}; expected a JSON object for manifest.features"
72 );
73 } else if value
74 .as_object()
75 .is_some_and(|features| features.is_empty())
76 {
77 manifest["features"] = value;
78 } else {
79 eprintln!(
80 "warning: ignoring @custom:{tag}; Neo N3 requires manifest.features to be an empty object"
81 );
82 }
83 }
84 "supportedstandards" => {
85 let Some(value) = parse_json_value(raw, tag) else {
86 continue;
87 };
88 if value.is_array() {
89 manifest["supportedstandards"] = value;
90 } else {
91 eprintln!(
92 "warning: ignoring @custom:{tag}; expected a JSON array for manifest.supportedstandards"
93 );
94 }
95 }
96 "trusts" => {
97 let Some(value) = parse_json_value(raw, tag) else {
98 continue;
99 };
100 if value.is_array() || value.is_string() {
101 manifest["trusts"] = value;
102 } else {
103 eprintln!(
104 "warning: ignoring @custom:{tag}; expected a JSON array or string for manifest.trusts"
105 );
106 }
107 }
108 _ => {
109 if let Some(extra_key) = field.strip_prefix("extra.") {
110 if extra_key.is_empty() {
111 continue;
112 }
113 if let Some(extra) = upsert_manifest_extra(manifest) {
114 extra.insert(extra_key.to_string(), parse_json_or_string(raw));
115 }
116 }
117 }
118 }
119 }
120}
121
122fn build_manifest(metadata: &ContractMetadata, ir_module: &ir::Module) -> serde_json::Value {
123 fn neotype_to_manifest_type(neotype: Option<&NeoType>, solidity_type: &str) -> &'static str {
124 match neotype {
125 Some(NeoType::Integer { .. }) => "Integer",
126 Some(NeoType::Boolean) => "Boolean",
127 Some(NeoType::String) => "String",
128 Some(NeoType::Address) => "Hash160",
129 Some(NeoType::ByteArray { fixed_len: Some(32) }) => "Hash256",
130 Some(NeoType::ByteArray { .. }) => "ByteArray",
131 Some(NeoType::Array(_)) => "Array",
132 Some(NeoType::Mapping { .. }) => "Map",
133 Some(NeoType::Struct { .. }) => "Array",
134 Some(NeoType::Any) | None => standard_json::solidity_to_manifest_type(solidity_type),
135 }
136 }
137
138 let abi_methods: Vec<&FunctionMetadata> = metadata
139 .methods
140 .iter()
141 .filter(|method| {
142 !matches!(method.kind, FunctionKind::Constructor)
143 && matches!(
144 method.visibility,
145 VisibilityKind::Public | VisibilityKind::External
146 )
147 })
148 .collect();
149
150 let mut overload_groups: HashMap<&str, Vec<&FunctionMetadata>> = HashMap::new();
159 for method in &abi_methods {
160 overload_groups
161 .entry(method.name.as_str())
162 .or_default()
163 .push(*method);
164 }
165
166 let mut primary_overload: HashMap<&str, &FunctionMetadata> = HashMap::new();
167 for (name, group) in &overload_groups {
168 if group.len() <= 1 {
169 continue;
170 }
171
172 let mut best = group[0];
173 for candidate in group.iter().skip(1) {
174 let best_params = best.parameters.len();
175 let cand_params = candidate.parameters.len();
176 if cand_params > best_params {
177 best = candidate;
178 continue;
179 }
180 if cand_params == best_params && candidate.neo_name < best.neo_name {
181 best = candidate;
182 }
183 }
184 primary_overload.insert(*name, best);
185 }
186
187 let methods_json: Vec<_> = abi_methods
188 .iter()
189 .map(|method| {
190 let is_overloaded = overload_groups
191 .get(method.name.as_str())
192 .is_some_and(|group| group.len() > 1);
193
194 let is_primary_overload = primary_overload
195 .get(method.name.as_str())
196 .is_some_and(|primary| primary.neo_name == method.neo_name)
197 && is_overloaded;
198
199 let abi_name = if !is_overloaded || is_primary_overload {
200 method.name.clone()
201 } else {
202 method.neo_name.clone()
203 };
204
205 let params_json: Vec<_> = method
206 .parameters
207 .iter()
208 .enumerate()
209 .map(|(param_index, param)| {
210 json!({
211 "name": param
212 .name
213 .clone()
214 .unwrap_or_else(|| format!("arg{}", param_index)),
215 "type": neotype_to_manifest_type(param.neo_type.as_ref(), ¶m.ty),
216 })
217 })
218 .collect();
219
220 json!({
221 "name": abi_name,
222 "offset": method.offset,
223 "parameters": params_json,
224 "returntype": if method.return_parameters.len() > 1 {
225 "Array"
226 } else {
227 method
228 .return_parameters
229 .first()
230 .map(|param| neotype_to_manifest_type(param.neo_type.as_ref(), ¶m.ty))
231 .unwrap_or("Void")
232 },
233 "safe": method.state_mutability.is_safe(),
234 })
235 })
236 .collect();
237
238 let events_json: Vec<_> = metadata
239 .events
240 .iter()
241 .map(|event| {
242 let params: Vec<_> = event
243 .parameters
244 .iter()
245 .enumerate()
246 .map(|(idx, param)| {
247 json!({
248 "name": param
249 .name
250 .clone()
251 .unwrap_or_else(|| format!("param{}", idx)),
252 "type": standard_json::solidity_to_manifest_type(¶m.ty),
253 })
254 })
255 .collect();
256
257 json!({
258 "name": event.name,
259 "parameters": params,
260 })
261 })
262 .collect();
263
264 let detection = detect_supported_standards(&metadata.methods, &metadata.events);
265 let supported_standards = detection.standards;
266 for diag in &detection.diagnostics {
267 let prefix = match diag.level {
268 StandardsDiagnosticLevel::Warning => "warning",
269 StandardsDiagnosticLevel::Info => "info",
270 };
271 eprintln!(
272 "[{prefix}][{standard}] {msg}",
273 standard = diag.standard,
274 msg = diag.message
275 );
276 }
277 let permissions = infer_permissions(metadata, ir_module);
278 let features = serde_json::Map::new();
281
282 let mut manifest = json!({
283 "name": metadata.name,
284 "groups": [],
285 "features": features,
286 "supportedstandards": supported_standards,
287 "abi": {
288 "methods": methods_json,
289 "events": events_json,
290 },
291 "permissions": permissions,
292 "trusts": [],
293 "extra": {
294 "Author": COMPILER_EMAIL,
295 "Description": format!("Solidity contract '{}' compiled to NeoVM", metadata.name),
296 "Version": compiler_version_string_4(),
297 "Compiler": COMPILER_ID,
298 }
299 });
300
301 apply_manifest_custom_overrides(&mut manifest, metadata);
302 manifest
303}