neo_solidity/cli/cli_parts/cli_compile/
permissions.rs

1fn parse_manifest_permissions_mode(value: &str) -> Result<ManifestPermissionsMode, String> {
2    match value.trim() {
3        "merge" => Ok(ManifestPermissionsMode::Merge),
4        "replace-wildcards" => Ok(ManifestPermissionsMode::ReplaceWildcards),
5        other => Err(format!(
6            "invalid manifest permissions mode '{other}' (expected 'merge' or 'replace-wildcards')"
7        )),
8    }
9}
10
11fn load_manifest_permissions_override(
12    path: &str,
13    mode: &str,
14) -> Result<ManifestPermissionsOverride, String> {
15    let mode = parse_manifest_permissions_mode(mode)?;
16    let content =
17        fs::read_to_string(path).map_err(|err| format!("Failed to read manifest permissions file '{path}': {err}"))?;
18    let root: Value =
19        serde_json::from_str(&content).map_err(|err| format!("Failed to parse manifest permissions JSON: {err}"))?;
20    let permissions_value = match root {
21        Value::Array(_) => root,
22        Value::Object(mut map) => map
23            .remove("permissions")
24            .ok_or_else(|| "manifest permissions JSON object must contain a 'permissions' array".to_string())?,
25        _ => {
26            return Err(
27                "manifest permissions JSON must be either an array or an object containing a 'permissions' array"
28                    .to_string(),
29            )
30        }
31    };
32
33    let permissions = parse_manifest_permissions_array(&permissions_value)?;
34
35    Ok(ManifestPermissionsOverride { mode, permissions })
36}
37
38fn parse_manifest_permissions_array(value: &Value) -> Result<ManifestPermissionMap, String> {
39    let arr = value.as_array().ok_or_else(|| {
40        "manifest permissions must be an array of objects like {\"contract\":\"0x...\",\"methods\":[...]}"
41            .to_string()
42    })?;
43
44    let mut out: ManifestPermissionMap = ManifestPermissionMap::new();
45    for (index, entry) in arr.iter().enumerate() {
46        let obj = entry.as_object().ok_or_else(|| {
47            format!("manifest permission entry #{index} must be an object")
48        })?;
49
50        let contract_value = obj.get("contract").ok_or_else(|| {
51            format!("manifest permission entry #{index} is missing 'contract'")
52        })?;
53        let contract_raw = contract_value.as_str().ok_or_else(|| {
54            format!("manifest permission entry #{index} field 'contract' must be a string")
55        })?;
56        let contract = if contract_raw.trim() == "*" {
57            "*".to_string()
58        } else {
59            let parsed = neo_solidity::neo::parse_uint160_hex_be(contract_raw).map_err(|err| {
60                format!("manifest permission entry #{index} has invalid 'contract': {err}")
61            })?;
62            neo_solidity::neo::format_uint160_hex_be(&parsed)
63        };
64
65        let methods_value = obj.get("methods").ok_or_else(|| {
66            format!("manifest permission entry #{index} is missing 'methods'")
67        })?;
68        let methods = match methods_value {
69            Value::String(s) if s.trim() == "*" => ManifestPermissionMethods::All,
70            Value::Array(list) => {
71                let mut set = BTreeSet::new();
72                for (method_index, method) in list.iter().enumerate() {
73                    let method_str = method.as_str().ok_or_else(|| {
74                        format!(
75                            "manifest permission entry #{index} methods[{method_index}] must be a string"
76                        )
77                    })?;
78                    let trimmed = method_str.trim();
79                    if trimmed.is_empty() {
80                        return Err(format!(
81                            "manifest permission entry #{index} methods[{method_index}] must not be empty"
82                        ));
83                    }
84                    set.insert(trimmed.to_string());
85                }
86                ManifestPermissionMethods::Some(set)
87            }
88            _ => {
89                return Err(format!(
90                    "manifest permission entry #{index} field 'methods' must be \"*\" or an array of strings"
91                ))
92            }
93        };
94
95        out.entry(contract)
96            .and_modify(|existing| existing.merge_in(methods.clone()))
97            .or_insert(methods);
98    }
99
100    Ok(out)
101}
102
103fn manifest_permissions_to_json(permissions: ManifestPermissionMap) -> Value {
104    Value::Array(
105        permissions
106            .into_iter()
107            .map(|(contract, methods)| {
108                let methods_json = match methods {
109                    ManifestPermissionMethods::All => json!("*"),
110                    ManifestPermissionMethods::Some(set) => json!(set.into_iter().collect::<Vec<_>>()),
111                };
112                json!({
113                    "contract": contract,
114                    "methods": methods_json,
115                })
116            })
117            .collect(),
118    )
119}
120
121fn parse_manifest_permissions_from_manifest(manifest: &Value) -> Result<ManifestPermissionMap, String> {
122    match manifest.get("permissions") {
123        Some(Value::Array(_)) => parse_manifest_permissions_array(&manifest["permissions"]),
124        Some(other) => Err(format!(
125            "manifest 'permissions' must be an array (got {})",
126            other
127        )),
128        None => Ok(ManifestPermissionMap::new()),
129    }
130}
131
132fn merge_manifest_permissions(into: &mut ManifestPermissionMap, other: &ManifestPermissionMap) {
133    for (contract, methods) in other {
134        into.entry(contract.clone())
135            .and_modify(|existing| existing.merge_in(methods.clone()))
136            .or_insert_with(|| methods.clone());
137    }
138}