neo_solidity/cli/cli_parts/cli_manifest/permissions/
build.rs

1fn collect_contract_call_permissions(
2    ir_module: &ir::Module,
3) -> (BTreeMap<String, PermissionMethods>, bool) {
4    let mut needs_wildcard = false;
5    let mut contract_methods: BTreeMap<String, PermissionMethods> = BTreeMap::new();
6
7    for function in &ir_module.functions {
8        if needs_wildcard {
9            break;
10        }
11
12        for req in analyze_contract_calls(function) {
13            let contract = req.contract.unwrap_or_else(|| "*".to_string());
14            let methods = if let Some(method) = req.method {
15                let mut set = std::collections::BTreeSet::new();
16                set.insert(method);
17                PermissionMethods::Some(set)
18            } else {
19                PermissionMethods::All
20            };
21
22            contract_methods
23                .entry(contract.clone())
24                .and_modify(|existing| existing.merge_in(methods.clone()))
25                .or_insert(methods);
26
27            if contract == "*" && matches!(contract_methods.get("*"), Some(PermissionMethods::All))
28            {
29                needs_wildcard = true;
30                break;
31            }
32        }
33    }
34
35    (contract_methods, needs_wildcard)
36}
37
38/// Infer contract permissions based on method signatures and behavior
39fn infer_permissions(
40    _metadata: &ContractMetadata,
41    ir_module: &ir::Module,
42) -> Vec<serde_json::Value> {
43    // Neo N3 enforces contract call permissions via the manifest. Native contract
44    // calls (StdLib/CryptoLib/etc) are executed through `System.Contract.Call` and
45    // therefore also require explicit permissions, otherwise the contract will
46    // fault on-chain.
47    //
48    // We keep manifests minimal by emitting:
49    // - Exact permissions for native contracts when the contract only calls native contracts.
50    // - Precise permissions for contract calls (`Syscalls.contractCall`) when the
51    //   contract hash and/or method name are compile-time constants.
52    // - Wildcard permissions only when the contract performs fully dynamic
53    //   contract calls (both target + method unknown at compile time), because the
54    //   destination can be user-controlled.
55    let (contract_methods, needs_wildcard) = collect_contract_call_permissions(ir_module);
56    let native_methods = collect_native_permissions(ir_module);
57
58    if needs_wildcard {
59        return vec![json!({ "contract": "*", "methods": "*" })];
60    }
61
62    let mut merged = contract_methods;
63
64    for (contract, methods) in native_methods {
65        let methods = PermissionMethods::Some(methods);
66        merged
67            .entry(contract)
68            .and_modify(|existing| existing.merge_in(methods.clone()))
69            .or_insert(methods);
70    }
71
72    merged
73        .into_iter()
74        .map(|(contract, methods)| {
75            let methods_json = match methods {
76                PermissionMethods::All => json!("*"),
77                PermissionMethods::Some(set) => json!(set.into_iter().collect::<Vec<_>>()),
78            };
79            json!({
80                "contract": contract,
81                "methods": methods_json,
82            })
83        })
84        .collect()
85}