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

1fn analyze_contract_calls(function: &ir::Function) -> Vec<ContractCallRequirement> {
2    let mut has_contract_calls = false;
3    for block in &function.basic_blocks {
4        for instr in &block.instructions {
5            if matches!(
6                instr,
7                ir::Instruction::CallBuiltin {
8                    builtin: ir::BuiltinCall::ContractCall
9                        | ir::BuiltinCall::ContractCallWithFlags,
10                    ..
11                }
12            ) {
13                has_contract_calls = true;
14                break;
15            }
16            if let ir::Instruction::CallBuiltin {
17                builtin: ir::BuiltinCall::Syscall(name),
18                ..
19            } = instr
20            {
21                if name == "System.Contract.Call" {
22                    has_contract_calls = true;
23                    break;
24                }
25            }
26        }
27        if has_contract_calls {
28            break;
29        }
30    }
31
32    if !has_contract_calls {
33        return Vec::new();
34    }
35
36    let mut requirements = Vec::new();
37
38    for block in &function.basic_blocks {
39        // Conservative, block-local abstract interpretation.
40        //
41        // This avoids brittle full-CFG merging (which can fail around complex
42        // control-flow / exception constructs) while still extracting literal
43        // contract + method values at call sites that are built in the same block.
44        let mut state = AbstractState::new(function.local_count);
45
46        for instr in &block.instructions {
47            if let ir::Instruction::CallBuiltin { builtin, arg_count } = instr {
48                let (contract_from_end, method_from_end, expected_args) = match builtin {
49                    ir::BuiltinCall::ContractCall => (3usize, 2usize, 3usize),
50                    ir::BuiltinCall::ContractCallWithFlags => (4usize, 3usize, 4usize),
51                    ir::BuiltinCall::Syscall(name) if name == "System.Contract.Call" => {
52                        // Syscall stack order: [args, flags, method, hash]
53                        (1usize, 2usize, 4usize)
54                    }
55                    _ => (0usize, 0usize, 0usize),
56                };
57
58                if expected_args > 0
59                    && *arg_count == expected_args
60                    && state.stack.len() >= expected_args
61                {
62                    let stack_len = state.stack.len();
63                    let contract_value = &state.stack[stack_len - contract_from_end];
64                    if matches!(contract_value, AbstractValue::ExecutingScriptHash) {
65                        // Neo N3 always allows contracts to call themselves, but the contract
66                        // script hash is not known at compile time (it depends on the deployer).
67                        // Do not emit wildcard permissions for self-calls.
68                        //
69                        // Note: native contract calls are still tracked separately and will
70                        // emit explicit permissions as required.
71                        if apply_instruction(&mut state, instr).is_err() {
72                            state.stack.clear();
73                            for slot in &mut state.locals {
74                                *slot = AbstractValue::Unknown;
75                            }
76                        }
77                        continue;
78                    }
79
80                    let contract = match contract_value {
81                        AbstractValue::Literal(lit) => descriptor_from_literal(lit),
82                        AbstractValue::ExecutingScriptHash | AbstractValue::Unknown => None,
83                    };
84                    let method = match &state.stack[stack_len - method_from_end] {
85                        AbstractValue::Literal(lit) => method_name_from_literal(lit),
86                        AbstractValue::ExecutingScriptHash => None,
87                        AbstractValue::Unknown => None,
88                    };
89                    requirements.push(ContractCallRequirement { contract, method });
90                } else if expected_args > 0 {
91                    // If the stack model is out of sync, fall back to a fully dynamic
92                    // requirement (safe over-approximation).
93                    requirements.push(ContractCallRequirement {
94                        contract: None,
95                        method: None,
96                    });
97                }
98            }
99
100            if apply_instruction(&mut state, instr).is_err() {
101                // Keep analysis progressing even when the stack model diverges, but do not
102                // attempt to preserve literal information after an error.
103                state.stack.clear();
104                for slot in &mut state.locals {
105                    *slot = AbstractValue::Unknown;
106                }
107            }
108        }
109    }
110
111    requirements
112}