neo_solidity/cli/cli_parts/cli_manifest/permissions/native.rs
1fn require_native_method(
2 native_methods: &mut BTreeMap<String, BTreeSet<String>>,
3 contract: ir::NativeContract,
4 method: &str,
5) {
6 let hash_le = bytecode::native_contract_hash(contract);
7 let hash_be = hash_le.iter().rev().copied().collect::<Vec<_>>();
8 let contract_str = format!("0x{}", hex::encode(hash_be));
9 native_methods
10 .entry(contract_str)
11 .or_default()
12 .insert(method.to_string());
13}
14
15fn collect_native_permissions(ir_module: &ir::Module) -> BTreeMap<String, BTreeSet<String>> {
16 let mut native_methods: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
17
18 for function in &ir_module.functions {
19 for block in &function.basic_blocks {
20 for instr in &block.instructions {
21 match instr {
22 ir::Instruction::CallBuiltin { builtin, .. } => match builtin {
23 ir::BuiltinCall::ContractCall | ir::BuiltinCall::ContractCallWithFlags => {
24 // Builtin lowering always deserializes args and serializes return.
25 require_native_method(
26 &mut native_methods,
27 ir::NativeContract::StdLib,
28 "deserialize",
29 );
30 require_native_method(
31 &mut native_methods,
32 ir::NativeContract::StdLib,
33 "serialize",
34 );
35 }
36 ir::BuiltinCall::NativeCall { contract, method } => {
37 // Backwards-compatible alias: older devpacks used
38 // `ContractManagement.listContracts`, but the Neo native contract
39 // exposes this as `getContractHashes`.
40 if matches!(contract, ir::NativeContract::ContractManagement)
41 && method == "listContracts"
42 {
43 require_native_method(
44 &mut native_methods,
45 ir::NativeContract::ContractManagement,
46 "getContractHashes",
47 );
48 } else {
49 require_native_method(
50 &mut native_methods,
51 *contract,
52 method.as_str(),
53 );
54 }
55 }
56 ir::BuiltinCall::DeployContract => {
57 require_native_method(
58 &mut native_methods,
59 ir::NativeContract::ContractManagement,
60 "deploy",
61 );
62 }
63 ir::BuiltinCall::GetContract => {
64 require_native_method(
65 &mut native_methods,
66 ir::NativeContract::ContractManagement,
67 "getContract",
68 );
69 require_native_method(
70 &mut native_methods,
71 ir::NativeContract::StdLib,
72 "serialize",
73 );
74 }
75 ir::BuiltinCall::GetContractScript => {
76 require_native_method(
77 &mut native_methods,
78 ir::NativeContract::ContractManagement,
79 "getContract",
80 );
81 }
82 ir::BuiltinCall::GetNeoAccountState => {
83 require_native_method(
84 &mut native_methods,
85 ir::NativeContract::Neo,
86 "getAccountState",
87 );
88 }
89 ir::BuiltinCall::AbiEncode | ir::BuiltinCall::AbiEncodePacked => {
90 require_native_method(
91 &mut native_methods,
92 ir::NativeContract::StdLib,
93 "serialize",
94 );
95 }
96 ir::BuiltinCall::AbiDecode => {
97 require_native_method(
98 &mut native_methods,
99 ir::NativeContract::StdLib,
100 "deserialize",
101 );
102 }
103 ir::BuiltinCall::RuntimeNotify | ir::BuiltinCall::NotifySerialized => {
104 require_native_method(
105 &mut native_methods,
106 ir::NativeContract::StdLib,
107 "deserialize",
108 );
109 }
110 ir::BuiltinCall::Keccak256 => {
111 require_native_method(
112 &mut native_methods,
113 ir::NativeContract::CryptoLib,
114 "keccak256",
115 );
116 }
117 ir::BuiltinCall::Ecrecover => {
118 require_native_method(
119 &mut native_methods,
120 ir::NativeContract::CryptoLib,
121 "recoverSecp256K1",
122 );
123 }
124 ir::BuiltinCall::VerifySignature => {
125 require_native_method(
126 &mut native_methods,
127 ir::NativeContract::CryptoLib,
128 "verifyWithECDsa",
129 );
130 }
131 ir::BuiltinCall::Syscall(name) => {
132 let _ = name;
133 }
134 _ => {}
135 },
136 ir::Instruction::LoadRuntimeValue(value) => {
137 if matches!(value, ir::RuntimeValue::BlockNumber) {
138 require_native_method(
139 &mut native_methods,
140 ir::NativeContract::Ledger,
141 "currentIndex",
142 );
143 }
144 }
145 ir::Instruction::LoadMappingElement { key_types, .. }
146 | ir::Instruction::StoreMappingElement { key_types, .. } => {
147 // Mapping storage slots are derived via:
148 // StdLib.serialize(key) + CryptoLib.keccak256(...)
149 //
150 // When `key_types` is empty, the slot is the base slot and no hashing is
151 // performed, so avoid adding unnecessary permissions.
152 if !key_types.is_empty() {
153 require_native_method(
154 &mut native_methods,
155 ir::NativeContract::StdLib,
156 "serialize",
157 );
158 require_native_method(
159 &mut native_methods,
160 ir::NativeContract::CryptoLib,
161 "keccak256",
162 );
163 }
164 }
165 ir::Instruction::LoadStructField { key_types, .. }
166 | ir::Instruction::StoreStructField { key_types, .. } => {
167 // Struct field storage slots always require at least one keccak256
168 // (base slot + field key). If the struct is nested under a mapping,
169 // additional key serialization + hashing occurs.
170 require_native_method(
171 &mut native_methods,
172 ir::NativeContract::CryptoLib,
173 "keccak256",
174 );
175 if !key_types.is_empty() {
176 require_native_method(
177 &mut native_methods,
178 ir::NativeContract::StdLib,
179 "serialize",
180 );
181 }
182 }
183 ir::Instruction::LoadStructArrayElement { key_types, .. }
184 | ir::Instruction::StoreStructArrayElement { key_types, .. } => {
185 // Array elements stored under a struct field require:
186 // - keccak256 for the field-slot derivation and the element-slot derivation
187 // - StdLib.serialize for the element index (and any mapping keys)
188 require_native_method(
189 &mut native_methods,
190 ir::NativeContract::CryptoLib,
191 "keccak256",
192 );
193 require_native_method(
194 &mut native_methods,
195 ir::NativeContract::StdLib,
196 "serialize",
197 );
198 if !key_types.is_empty() {
199 // mapping keys also require serialize/keccak256; already covered above,
200 // but keep this for clarity in case the rules evolve.
201 require_native_method(
202 &mut native_methods,
203 ir::NativeContract::CryptoLib,
204 "keccak256",
205 );
206 }
207 }
208 _ => {}
209 }
210 }
211 }
212 }
213
214 native_methods
215}