neo_solidity/cli/cli_parts/cli_compile/
compile.rs1pub fn compile_contracts(
2 source: &str,
3 verbose: bool,
4 optimizer_level: u8,
5) -> Result<Vec<CompilationArtifacts>, CompileError> {
6 compile_contracts_with_options(
7 source,
8 verbose,
9 CompileOptions {
10 optimizer_level,
11 use_callt: false,
12 deny_wildcard_permissions: false,
13 deny_wildcard_contracts: false,
14 deny_wildcard_methods: false,
15 manifest_permissions: None,
16 },
17 )
18}
19
20fn compile_contracts_with_options(
21 source: &str,
22 verbose: bool,
23 options: CompileOptions,
24) -> Result<Vec<CompilationArtifacts>, CompileError> {
25 let metadatas =
26 analyse_all_sources(source).map_err(|err| CompileError::Message(err.to_string()))?;
27
28 metadatas
29 .into_iter()
30 .map(|metadata| compile_metadata(metadata, verbose, options.clone()))
31 .collect()
32}
33
34fn compile_metadata(
35 mut metadata: ContractMetadata,
36 verbose: bool,
37 options: CompileOptions,
38) -> Result<CompilationArtifacts, CompileError> {
39 fn manifest_allows_permission(manifest: &Value, contract: &str, method: &str) -> bool {
40 let permissions = manifest
41 .get("permissions")
42 .and_then(|v| v.as_array())
43 .map(|v| v.as_slice())
44 .unwrap_or(&[]);
45
46 permissions.iter().any(|entry| {
47 let Some(entry_contract) = entry.get("contract").and_then(|v| v.as_str()) else {
48 return false;
49 };
50 let methods_value = entry.get("methods").unwrap_or(&Value::Null);
51
52 let contract_matches =
53 entry_contract == "*" || entry_contract.eq_ignore_ascii_case(contract);
54 if !contract_matches {
55 return false;
56 }
57
58 match methods_value {
59 Value::String(s) => s == "*",
60 Value::Array(arr) => arr
61 .iter()
62 .filter_map(|v| v.as_str())
63 .any(|name| name == method),
64 _ => false,
65 }
66 })
67 }
68
69 let optimizer_level = options.optimizer_level.min(3);
70 let diagnostics = validate_contract(&metadata);
71 let mut errors = Vec::new();
72 let mut warnings = Vec::new();
73 for diagnostic in diagnostics {
74 match diagnostic.severity {
75 DiagnosticSeverity::Warning => warnings.push(diagnostic),
76 DiagnosticSeverity::Error => errors.push(diagnostic),
77 }
78 }
79
80 if !errors.is_empty() {
81 return Err(CompileError::Diagnostics(errors));
82 }
83
84 if let Err(diags) = build_semantic_model(&metadata) {
85 return Err(CompileError::Semantic(diags));
86 }
87
88 ensure_deploy_stub(&mut metadata)?;
89 let has_parameterised_constructor = metadata.methods.iter().any(|m| {
90 matches!(m.kind, FunctionKind::Constructor) && !m.parameters.is_empty()
91 });
92
93 let ir_module = ir::Module::from_contract(&metadata).map_err(CompileError::Ir)?;
94 let ir_module = optimize_ir(ir_module, optimizer_level);
95
96 if verbose {
97 println!(
98 "Semantic model built: {} functions, {} state variables",
99 ir_module.functions.len(),
100 ir_module.state_variables.len()
101 );
102
103 for function in &ir_module.functions {
104 let instruction_count: usize = function
105 .basic_blocks
106 .iter()
107 .map(|block| block.instructions.len())
108 .sum();
109 println!(
110 " • IR function '{}' (kind: {:?}) => {} instruction(s)",
111 function.name, function.kind, instruction_count
112 );
113 }
114 }
115
116 let bytecode_output = generate_contract_bytecode(
118 &mut metadata,
119 &ir_module,
120 verbose,
121 optimizer_level,
122 options.use_callt,
123 )
124 .map_err(CompileError::Message)?;
125 let mut manifest = build_manifest(&metadata, &ir_module);
126
127 if let Some(override_permissions) = &options.manifest_permissions {
128 let mut inferred = parse_manifest_permissions_from_manifest(&manifest).map_err(|err| {
129 CompileError::Message(format!("Failed to parse inferred manifest permissions: {err}"))
130 })?;
131
132 match override_permissions.mode {
133 ManifestPermissionsMode::Merge => {
134 merge_manifest_permissions(&mut inferred, &override_permissions.permissions);
135 }
136 ManifestPermissionsMode::ReplaceWildcards => {
137 inferred.retain(|contract, methods| contract != "*" && !methods.is_wildcard());
138 merge_manifest_permissions(&mut inferred, &override_permissions.permissions);
139 }
140 }
141
142 manifest["permissions"] = manifest_permissions_to_json(inferred);
143 }
144
145 if has_parameterised_constructor {
146 let stdlib_hash_le = bytecode::native_contract_hash(ir::NativeContract::StdLib);
147 let stdlib_hash_be = stdlib_hash_le.iter().rev().copied().collect::<Vec<_>>();
148 let stdlib_contract = format!("0x{}", hex::encode(stdlib_hash_be));
149
150 let has_json_deserialize =
151 manifest_allows_permission(&manifest, &stdlib_contract, "jsonDeserialize");
152 let has_deserialize = manifest_allows_permission(&manifest, &stdlib_contract, "deserialize");
153
154 if !(has_json_deserialize && has_deserialize) {
155 warnings.push(neo_solidity::solidity::Diagnostic::warning(format!(
156 "contract '{}' has a parameterised constructor; deploy it by passing constructor args through `_deploy(data, update)`. Neo-Express: pass a JSON array string via `-d '[7]'`; SDKs that support StackItems may pass an Array directly. The injected deploy prologue uses `StdLib.jsonDeserialize` with a `StdLib.deserialize` fallback, so the manifest must allow these methods.",
157 metadata.name
158 )));
159 }
160 }
161
162 let (
163 has_wildcard_contract,
164 has_wildcard_methods,
165 has_full_wildcard_permissions,
166 wildcard_contract_only_nep_callbacks,
167 ) = manifest["permissions"]
168 .as_array()
169 .map(|permissions| {
170 let mut wildcard_contract = false;
171 let mut wildcard_methods = false;
172 let mut full_wildcard = false;
173 let mut wildcard_contract_only_nep_callbacks = true;
174 const NEP_CALLBACK_METHODS: [&str; 3] = ["onNEP11Payment", "onNEP17Payment", "onOracleResponse"];
175 for entry in permissions {
176 let contract_is_wildcard = entry["contract"] == "*";
177 let methods_is_wildcard = entry["methods"] == "*";
178 wildcard_contract |= contract_is_wildcard;
179 wildcard_methods |= methods_is_wildcard;
180 full_wildcard |= contract_is_wildcard && methods_is_wildcard;
181
182 if contract_is_wildcard {
183 if methods_is_wildcard {
184 wildcard_contract_only_nep_callbacks = false;
185 } else if let Some(methods) = entry["methods"].as_array() {
186 for method in methods {
187 match method.as_str() {
188 Some(name) if NEP_CALLBACK_METHODS.contains(&name) => {}
189 _ => wildcard_contract_only_nep_callbacks = false,
190 }
191 }
192 } else {
193 wildcard_contract_only_nep_callbacks = false;
194 }
195 }
196 }
197 if !wildcard_contract {
198 wildcard_contract_only_nep_callbacks = false;
199 }
200 (
201 wildcard_contract,
202 wildcard_methods,
203 full_wildcard,
204 wildcard_contract_only_nep_callbacks,
205 )
206 })
207 .unwrap_or((false, false, false, false));
208
209 if has_full_wildcard_permissions {
210 let message = format!(
211 "contract '{}' requires full wildcard manifest permissions (contract='*', methods='*') because at least one contract call is fully dynamic (unknown target + method) or could not be statically analysed. This is usually not acceptable for production deployments; prefer static calls or restrict the call surface. Use --deny-wildcard-permissions to make this a hard error.",
212 metadata.name
213 );
214 if options.deny_wildcard_permissions
215 || options.deny_wildcard_contracts
216 || options.deny_wildcard_methods
217 {
218 return Err(CompileError::Manifest(message));
219 }
220 warnings.push(neo_solidity::solidity::Diagnostic::warning(message));
221 } else {
222 if has_wildcard_contract {
223 let message = format!(
224 "contract '{}' requires wildcard contract manifest permissions (contract='*') due to dynamic contract calls. This is riskier than fixed contract hashes; use --deny-wildcard-contracts to make this a hard error.",
225 metadata.name
226 );
227 if options.deny_wildcard_contracts && !wildcard_contract_only_nep_callbacks {
228 return Err(CompileError::Manifest(message));
229 }
230 if !wildcard_contract_only_nep_callbacks {
231 warnings.push(neo_solidity::solidity::Diagnostic::warning(message));
232 }
233 }
234
235 if has_wildcard_methods {
236 let message = format!(
237 "contract '{}' requires wildcard method manifest permissions (methods='*') due to dynamic method names in contract calls. This is riskier than calling fixed methods; use --deny-wildcard-methods to make this a hard error.",
238 metadata.name
239 );
240 if options.deny_wildcard_methods {
241 return Err(CompileError::Manifest(message));
242 }
243 warnings.push(neo_solidity::solidity::Diagnostic::warning(message));
244 }
245 }
246
247 Ok(CompilationArtifacts {
248 metadata,
249 bytecode: bytecode_output.script,
250 tokens: bytecode_output.tokens,
251 manifest,
252 warnings,
253 })
254}