1use std::{
2 collections::{HashMap, HashSet},
3 hash::{DefaultHasher, Hash as StdHash, Hasher},
4 path::{Path, PathBuf},
5 process::Stdio,
6 sync::Arc,
7 time::Instant,
8};
9
10use ave_actors::{Actor, ActorContext, ActorError, ActorPath, Response};
11use ave_common::{
12 ValueWrapper,
13 identity::{DigestIdentifier, HashAlgorithm, hash_borsh},
14};
15use base64::{Engine as Base64Engine, prelude::BASE64_STANDARD};
16use borsh::{BorshDeserialize, BorshSerialize, to_vec};
17use serde::{Deserialize, Serialize};
18use serde_json::Value;
19use tokio::{fs, process::Command, sync::RwLock};
20use tracing::debug;
21use wasmtime::{ExternType, Module, Store};
22
23use crate::model::common::contract::{
24 MAX_FUEL_COMPILATION, MemoryManager, WasmLimits, WasmRuntime,
25 generate_linker,
26};
27use crate::{
28 governance::contract_register::{
29 ContractRegister, ContractRegisterMessage, ContractRegisterResponse,
30 },
31 metrics::try_core_metrics,
32};
33
34pub mod contract_compiler;
35pub mod error;
36pub mod temp_compiler;
37
38pub use contract_compiler::{ContractCompiler, ContractCompilerMessage};
39pub use temp_compiler::{TempCompiler, TempCompilerMessage};
40
41use error::*;
42
43#[derive(
44 Serialize, Deserialize, BorshSerialize, BorshDeserialize, Debug, Clone,
45)]
46pub struct ContractResult {
47 pub success: bool,
48 pub error: String,
49}
50
51#[derive(
52 Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
53)]
54pub struct ContractArtifactRecord {
55 pub contract_hash: DigestIdentifier,
56 pub manifest_hash: DigestIdentifier,
57 pub wasm_hash: DigestIdentifier,
58 pub cwasm_hash: DigestIdentifier,
59 pub engine_fingerprint: DigestIdentifier,
60 pub toolchain_fingerprint: DigestIdentifier,
61}
62
63#[derive(Debug, Clone)]
64pub enum CompilerResponse {
65 Ok,
66 Error(CompilerError),
67}
68
69impl Response for CompilerResponse {}
70
71struct CompilerSupport;
72
73impl CompilerSupport {
74 const SHARED_TARGET_DIR: &'static str = "target";
75 const VENDOR_DIR: &'static str = "vendor";
76 const ARTIFACT_WASM: &'static str = "contract.wasm";
77 const ARTIFACT_PRECOMPILED: &'static str = "contract.cwasm";
78 const LEGACY_ARTIFACT_METADATA: &'static str = "contract.json";
79
80 fn observe_contract_prepare(
81 kind: &'static str,
82 result: &'static str,
83 started_at: Instant,
84 ) {
85 if let Some(metrics) = try_core_metrics() {
86 metrics.observe_contract_prepare(
87 kind,
88 result,
89 started_at.elapsed(),
90 );
91 }
92 }
93
94 fn compilation_toml() -> String {
95 include_str!("contract_Cargo.toml").to_owned()
96 }
97
98 fn contracts_root(contract_path: &Path) -> Result<PathBuf, CompilerError> {
99 contract_path
100 .parent()
101 .and_then(Path::parent)
102 .map(Path::to_path_buf)
103 .ok_or_else(|| CompilerError::InvalidContractPath {
104 path: contract_path.to_string_lossy().to_string(),
105 details:
106 "expected contract path under <contracts_path>/contracts/<name>"
107 .to_owned(),
108 })
109 }
110
111 fn artifact_wasm_path(contract_path: &Path) -> PathBuf {
112 contract_path.join(Self::ARTIFACT_WASM)
113 }
114
115 fn artifact_precompiled_path(contract_path: &Path) -> PathBuf {
116 contract_path.join(Self::ARTIFACT_PRECOMPILED)
117 }
118
119 fn legacy_artifact_metadata_path(contract_path: &Path) -> PathBuf {
120 contract_path.join(Self::LEGACY_ARTIFACT_METADATA)
121 }
122
123 fn cargo_config_path(contract_path: &Path) -> PathBuf {
124 contract_path.join(".cargo").join("config.toml")
125 }
126
127 fn build_output_wasm_path(contracts_root: &Path) -> PathBuf {
128 contracts_root
129 .join(Self::SHARED_TARGET_DIR)
130 .join("wasm32-unknown-unknown")
131 .join("release")
132 .join(Self::ARTIFACT_WASM)
133 }
134
135 fn cargo_config(
136 shared_target_dir: &Path,
137 vendor_dir: Option<&Path>,
138 ) -> String {
139 let mut config = format!(
140 "[build]\ntarget-dir = \"{}\"\n",
141 shared_target_dir.to_string_lossy()
142 );
143
144 if let Some(vendor_dir) = vendor_dir {
145 config.push_str(&format!(
146 "\n[net]\noffline = true\n\n[source.crates-io]\nreplace-with = \"vendored-sources\"\n\n[source.vendored-sources]\ndirectory = \"{}\"\n",
147 vendor_dir.to_string_lossy()
148 ));
149 }
150
151 config
152 }
153
154 async fn build_contract(
155 contract_path: &Path,
156 offline: bool,
157 ) -> Result<(), CompilerError> {
158 let cargo = contract_path.join("Cargo.toml");
159 let cargo_config = Self::cargo_config_path(contract_path);
160 let mut command = Command::new("cargo");
161 command
162 .arg("build")
163 .arg(format!("--manifest-path={}", cargo.to_string_lossy()))
164 .arg("--target")
165 .arg("wasm32-unknown-unknown")
166 .arg("--release")
167 .env("CARGO_HOME", contract_path.join(".cargo"))
168 .env("CARGO_CONFIG", &cargo_config)
169 .stdout(Stdio::null())
170 .stderr(Stdio::null());
171
172 if offline {
173 command.arg("--offline");
174 }
175
176 let status = command.status().await.map_err(|e| {
177 CompilerError::CargoBuildFailed {
178 details: e.to_string(),
179 }
180 })?;
181
182 if !status.success() {
183 return Err(CompilerError::CompilationFailed);
184 }
185
186 Ok(())
187 }
188
189 async fn prepare_contract_project(
190 contract: &str,
191 contract_path: &Path,
192 ) -> Result<(), CompilerError> {
193 let decode_base64 = BASE64_STANDARD.decode(contract).map_err(|e| {
194 CompilerError::Base64DecodeFailed {
195 details: format!(
196 "{} (path: {})",
197 e,
198 contract_path.to_string_lossy()
199 ),
200 }
201 })?;
202
203 let contracts_root = Self::contracts_root(contract_path)?;
204 let dir = contract_path.join("src");
205 if !Path::new(&dir).exists() {
206 fs::create_dir_all(&dir).await.map_err(|e| {
207 CompilerError::DirectoryCreationFailed {
208 path: dir.to_string_lossy().to_string(),
209 details: e.to_string(),
210 }
211 })?;
212 }
213
214 let cargo_config_dir = contract_path.join(".cargo");
215 if !Path::new(&cargo_config_dir).exists() {
216 fs::create_dir_all(&cargo_config_dir).await.map_err(|e| {
217 CompilerError::DirectoryCreationFailed {
218 path: cargo_config_dir.to_string_lossy().to_string(),
219 details: e.to_string(),
220 }
221 })?;
222 }
223
224 let toml = Self::compilation_toml();
225 let cargo = contract_path.join("Cargo.toml");
226 fs::write(&cargo, toml).await.map_err(|e| {
227 CompilerError::FileWriteFailed {
228 path: cargo.to_string_lossy().to_string(),
229 details: e.to_string(),
230 }
231 })?;
232
233 let lib_rs = contract_path.join("src").join("lib.rs");
234 fs::write(&lib_rs, decode_base64).await.map_err(|e| {
235 CompilerError::FileWriteFailed {
236 path: lib_rs.to_string_lossy().to_string(),
237 details: e.to_string(),
238 }
239 })?;
240
241 let vendor_dir = contracts_root.join(Self::VENDOR_DIR);
242 let cargo_config = Self::cargo_config(
243 &contracts_root.join(Self::SHARED_TARGET_DIR),
244 vendor_dir.exists().then_some(vendor_dir.as_path()),
245 );
246 let cargo_config_path = Self::cargo_config_path(contract_path);
247 fs::write(&cargo_config_path, cargo_config)
248 .await
249 .map_err(|e| CompilerError::FileWriteFailed {
250 path: cargo_config_path.to_string_lossy().to_string(),
251 details: e.to_string(),
252 })?;
253
254 Ok(())
255 }
256
257 async fn load_artifact_wasm(
258 contract_path: &Path,
259 ) -> Result<Vec<u8>, CompilerError> {
260 let wasm_path = Self::artifact_wasm_path(contract_path);
261 fs::read(&wasm_path)
262 .await
263 .map_err(|e| CompilerError::FileReadFailed {
264 path: wasm_path.to_string_lossy().to_string(),
265 details: e.to_string(),
266 })
267 }
268
269 async fn load_artifact_precompiled(
270 contract_path: &Path,
271 ) -> Result<Vec<u8>, CompilerError> {
272 let precompiled_path = Self::artifact_precompiled_path(contract_path);
273 fs::read(&precompiled_path).await.map_err(|e| {
274 CompilerError::FileReadFailed {
275 path: precompiled_path.to_string_lossy().to_string(),
276 details: e.to_string(),
277 }
278 })
279 }
280
281 async fn load_compiled_wasm(
282 contracts_root: &Path,
283 ) -> Result<Vec<u8>, CompilerError> {
284 let wasm_path = Self::build_output_wasm_path(contracts_root);
285 fs::read(&wasm_path)
286 .await
287 .map_err(|e| CompilerError::FileReadFailed {
288 path: wasm_path.to_string_lossy().to_string(),
289 details: e.to_string(),
290 })
291 }
292
293 async fn persist_artifact(
294 contract_path: &Path,
295 wasm_bytes: &[u8],
296 precompiled_bytes: &[u8],
297 ) -> Result<(), CompilerError> {
298 let artifact_path = Self::artifact_wasm_path(contract_path);
299 fs::write(&artifact_path, wasm_bytes).await.map_err(|e| {
300 CompilerError::FileWriteFailed {
301 path: artifact_path.to_string_lossy().to_string(),
302 details: e.to_string(),
303 }
304 })?;
305
306 let precompiled_path = Self::artifact_precompiled_path(contract_path);
307 fs::write(&precompiled_path, precompiled_bytes)
308 .await
309 .map_err(|e| CompilerError::FileWriteFailed {
310 path: precompiled_path.to_string_lossy().to_string(),
311 details: e.to_string(),
312 })?;
313
314 let legacy_metadata_path =
315 Self::legacy_artifact_metadata_path(contract_path);
316 let _ = fs::remove_file(&legacy_metadata_path).await;
317
318 Ok(())
319 }
320
321 fn deserialize_precompiled(
322 wasm_runtime: &WasmRuntime,
323 precompiled_bytes: &[u8],
324 ) -> Result<Module, CompilerError> {
325 unsafe {
326 Module::deserialize(&wasm_runtime.engine, precompiled_bytes)
327 .map_err(|e| CompilerError::WasmDeserializationFailed {
328 details: e.to_string(),
329 })
330 }
331 }
332
333 fn precompile_module(
334 wasm_runtime: &WasmRuntime,
335 wasm_bytes: &[u8],
336 ) -> Result<(Vec<u8>, Module), CompilerError> {
337 let precompiled_bytes = wasm_runtime
338 .engine
339 .precompile_module(wasm_bytes)
340 .map_err(|e| CompilerError::WasmPrecompileFailed {
341 details: e.to_string(),
342 })?;
343
344 let module =
345 Self::deserialize_precompiled(wasm_runtime, &precompiled_bytes)?;
346
347 Ok((precompiled_bytes, module))
348 }
349
350 async fn validate_module<A: Actor>(
351 ctx: &ActorContext<A>,
352 module: &Module,
353 state: ValueWrapper,
354 ) -> Result<(), CompilerError> {
355 let wasm_runtime = Self::wasm_runtime(ctx).await?;
356
357 let imports = module.imports();
358 let mut pending_sdk = Self::get_sdk_functions_identifier();
359
360 for import in imports {
361 match import.ty() {
362 ExternType::Func(_) => {
363 if !pending_sdk.remove(import.name()) {
364 return Err(CompilerError::InvalidModule {
365 kind: InvalidModuleKind::UnknownImportFunction {
366 name: import.name().to_string(),
367 },
368 });
369 }
370 }
371 extern_type => {
372 return Err(CompilerError::InvalidModule {
373 kind: InvalidModuleKind::NonFunctionImport {
374 import_type: format!("{:?}", extern_type),
375 },
376 });
377 }
378 }
379 }
380 if !pending_sdk.is_empty() {
381 return Err(CompilerError::InvalidModule {
382 kind: InvalidModuleKind::MissingImports {
383 missing: pending_sdk
384 .into_iter()
385 .map(|s| s.to_string())
386 .collect(),
387 },
388 });
389 }
390
391 let (context, state_ptr) =
392 Self::generate_context(state, &wasm_runtime.limits)?;
393 let mut store = Store::new(&wasm_runtime.engine, context);
394
395 store.limiter(|data| &mut data.store_limits);
396 store.set_fuel(MAX_FUEL_COMPILATION).map_err(|e| {
397 CompilerError::FuelLimitError {
398 details: e.to_string(),
399 }
400 })?;
401
402 let linker = generate_linker(&wasm_runtime.engine)?;
403 let instance = linker.instantiate(&mut store, module).map_err(|e| {
404 CompilerError::InstantiationFailed {
405 details: e.to_string(),
406 }
407 })?;
408
409 let _main_contract_entrypoint = instance
410 .get_typed_func::<(u32, u32, u32, u32), u32>(
411 &mut store,
412 "main_function",
413 )
414 .map_err(|_e| CompilerError::EntryPointNotFound {
415 function: "main_function",
416 })?;
417
418 let init_contract_entrypoint = instance
419 .get_typed_func::<u32, u32>(&mut store, "init_check_function")
420 .map_err(|_e| CompilerError::EntryPointNotFound {
421 function: "init_check_function",
422 })?;
423
424 let result_ptr =
425 init_contract_entrypoint
426 .call(&mut store, state_ptr)
427 .map_err(|e| CompilerError::ContractExecutionFailed {
428 details: e.to_string(),
429 })?;
430
431 Self::check_result(&store, result_ptr)?;
432
433 Ok(())
434 }
435
436 async fn wasm_runtime<A: Actor>(
437 ctx: &ActorContext<A>,
438 ) -> Result<Arc<WasmRuntime>, CompilerError> {
439 ctx.system()
440 .get_helper::<Arc<WasmRuntime>>("wasm_runtime")
441 .await
442 .ok_or(CompilerError::MissingHelper {
443 name: "wasm_runtime",
444 })
445 }
446
447 async fn contracts_helper<A: Actor>(
448 ctx: &ActorContext<A>,
449 ) -> Result<Arc<RwLock<HashMap<String, Arc<Module>>>>, ActorError> {
450 ctx.system()
451 .get_helper::<Arc<RwLock<HashMap<String, Arc<Module>>>>>(
452 "contracts",
453 )
454 .await
455 .ok_or_else(|| ActorError::Helper {
456 name: "contracts".to_owned(),
457 reason: "Not found".to_owned(),
458 })
459 }
460
461 fn build_contract_record(
462 hash: HashAlgorithm,
463 contract_hash: DigestIdentifier,
464 manifest_hash: DigestIdentifier,
465 wasm_bytes: &[u8],
466 precompiled_bytes: &[u8],
467 engine_fingerprint: DigestIdentifier,
468 toolchain_fingerprint: DigestIdentifier,
469 ) -> Result<ContractArtifactRecord, CompilerError> {
470 let wasm_hash = Self::hash_bytes(hash, wasm_bytes, "wasm artifact")?;
471 let cwasm_hash =
472 Self::hash_bytes(hash, precompiled_bytes, "cwasm artifact")?;
473
474 Ok(ContractArtifactRecord {
475 contract_hash,
476 manifest_hash,
477 wasm_hash,
478 cwasm_hash,
479 engine_fingerprint,
480 toolchain_fingerprint,
481 })
482 }
483
484 fn hash_bytes(
485 hash: HashAlgorithm,
486 bytes: &[u8],
487 context: &'static str,
488 ) -> Result<DigestIdentifier, CompilerError> {
489 hash_borsh(&*hash.hasher(), &bytes.to_vec()).map_err(|e| {
490 CompilerError::SerializationError {
491 context,
492 details: e.to_string(),
493 }
494 })
495 }
496
497 fn engine_fingerprint(
498 hash: HashAlgorithm,
499 wasm_runtime: &WasmRuntime,
500 ) -> Result<DigestIdentifier, CompilerError> {
501 let mut hasher = DefaultHasher::new();
502 wasm_runtime
503 .engine
504 .precompile_compatibility_hash()
505 .hash(&mut hasher);
506 hash_borsh(&*hash.hasher(), &hasher.finish()).map_err(|e| {
507 CompilerError::SerializationError {
508 context: "engine fingerprint",
509 details: e.to_string(),
510 }
511 })
512 }
513
514 async fn toolchain_fingerprint(
515 hash: HashAlgorithm,
516 ) -> Result<DigestIdentifier, CompilerError> {
517 let output = Command::new("rustc")
518 .arg("--version")
519 .arg("--verbose")
520 .output()
521 .await
522 .map_err(|e| CompilerError::ToolchainFingerprintFailed {
523 details: e.to_string(),
524 })?;
525
526 if !output.status.success() {
527 return Err(CompilerError::ToolchainFingerprintFailed {
528 details: String::from_utf8_lossy(&output.stderr).to_string(),
529 });
530 }
531
532 let fingerprint_input =
533 String::from_utf8_lossy(&output.stdout).to_string();
534 hash_borsh(&*hash.hasher(), &fingerprint_input).map_err(|e| {
535 CompilerError::SerializationError {
536 context: "toolchain fingerprint",
537 details: e.to_string(),
538 }
539 })
540 }
541
542 async fn contract_environment(
543 hash: HashAlgorithm,
544 wasm_runtime: &Arc<WasmRuntime>,
545 ) -> Result<(DigestIdentifier, DigestIdentifier), CompilerError> {
546 let engine_fingerprint = Self::engine_fingerprint(hash, wasm_runtime)?;
547 let toolchain_fingerprint = Self::toolchain_fingerprint(hash).await?;
548 Ok((engine_fingerprint, toolchain_fingerprint))
549 }
550
551 fn metadata_matches(
552 persisted: &ContractArtifactRecord,
553 expected_contract_hash: &DigestIdentifier,
554 expected_manifest_hash: &DigestIdentifier,
555 expected_engine_fingerprint: &DigestIdentifier,
556 expected_toolchain_fingerprint: &DigestIdentifier,
557 ) -> bool {
558 persisted.contract_hash == *expected_contract_hash
559 && persisted.manifest_hash == *expected_manifest_hash
560 && persisted.engine_fingerprint == *expected_engine_fingerprint
561 && persisted.toolchain_fingerprint
562 == *expected_toolchain_fingerprint
563 }
564
565 async fn compile_fresh<A: Actor>(
566 hash: HashAlgorithm,
567 ctx: &ActorContext<A>,
568 contract: &str,
569 contract_path: &Path,
570 initial_value: Value,
571 ) -> Result<(Arc<Module>, ContractArtifactRecord), CompilerError> {
572 let contract_hash =
573 hash_borsh(&*hash.hasher(), &contract).map_err(|e| {
574 CompilerError::SerializationError {
575 context: "contract hash",
576 details: e.to_string(),
577 }
578 })?;
579 let manifest = Self::compilation_toml();
580 let manifest_hash =
581 hash_borsh(&*hash.hasher(), &manifest).map_err(|e| {
582 CompilerError::SerializationError {
583 context: "contract manifest hash",
584 details: e.to_string(),
585 }
586 })?;
587
588 Self::prepare_contract_project(contract, contract_path).await?;
589
590 let wasm_runtime = Self::wasm_runtime(ctx).await?;
591 let (engine_fingerprint, toolchain_fingerprint) =
592 Self::contract_environment(hash, &wasm_runtime).await?;
593
594 let contracts_root = Self::contracts_root(contract_path)?;
595 Self::build_contract(
596 contract_path,
597 contracts_root.join(Self::VENDOR_DIR).exists(),
598 )
599 .await?;
600
601 let wasm_bytes = Self::load_compiled_wasm(&contracts_root).await?;
602 let (precompiled_bytes, module) =
603 Self::precompile_module(&wasm_runtime, &wasm_bytes)?;
604 Self::validate_module(ctx, &module, ValueWrapper(initial_value))
605 .await?;
606 Self::persist_artifact(contract_path, &wasm_bytes, &precompiled_bytes)
607 .await?;
608
609 let metadata = Self::build_contract_record(
610 hash,
611 contract_hash,
612 manifest_hash,
613 &wasm_bytes,
614 &precompiled_bytes,
615 engine_fingerprint,
616 toolchain_fingerprint,
617 )?;
618
619 Ok((Arc::new(module), metadata))
620 }
621
622 async fn compile_or_load_registered<A: Actor>(
623 hash: HashAlgorithm,
624 ctx: &ActorContext<A>,
625 contract_name: &str,
626 contract: &str,
627 contract_path: &Path,
628 initial_value: Value,
629 ) -> Result<(Arc<Module>, ContractArtifactRecord), CompilerError> {
630 let started_at = Instant::now();
631 let result = async {
632 let contract_hash =
633 hash_borsh(&*hash.hasher(), &contract).map_err(|e| {
634 CompilerError::SerializationError {
635 context: "contract hash",
636 details: e.to_string(),
637 }
638 })?;
639 let manifest = Self::compilation_toml();
640 let manifest_hash =
641 hash_borsh(&*hash.hasher(), &manifest).map_err(|e| {
642 CompilerError::SerializationError {
643 context: "contract manifest hash",
644 details: e.to_string(),
645 }
646 })?;
647
648 Self::prepare_contract_project(contract, contract_path).await?;
649
650 let wasm_runtime = Self::wasm_runtime(ctx).await?;
651 let (engine_fingerprint, toolchain_fingerprint) =
652 Self::contract_environment(hash, &wasm_runtime).await?;
653
654 let parent_path = ctx.path().parent();
655 let register_path =
656 ActorPath::from(format!("{}/contract_register", parent_path));
657 let register = ctx
658 .system()
659 .get_actor::<ContractRegister>(®ister_path)
660 .await
661 .map_err(|e| CompilerError::ContractRegisterFailed {
662 details: e.to_string(),
663 })?;
664
665 let persisted = match register
666 .ask(ContractRegisterMessage::GetMetadata {
667 contract_name: contract_name.to_owned(),
668 })
669 .await
670 {
671 Ok(ContractRegisterResponse::Metadata(metadata)) => metadata,
672 Ok(ContractRegisterResponse::Contracts(_)) => None,
673 Ok(ContractRegisterResponse::Ok) => None,
674 Err(e) => {
675 return Err(CompilerError::ContractRegisterFailed {
676 details: e.to_string(),
677 });
678 }
679 };
680
681 if let Some(persisted) = persisted
682 && Self::metadata_matches(
683 &persisted,
684 &contract_hash,
685 &manifest_hash,
686 &engine_fingerprint,
687 &toolchain_fingerprint,
688 )
689 {
690 match Self::load_artifact_precompiled(contract_path).await {
691 Ok(precompiled_bytes) => {
692 let precompiled_hash = Self::hash_bytes(
693 hash,
694 &precompiled_bytes,
695 "persisted cwasm artifact",
696 )?;
697 if precompiled_hash == persisted.cwasm_hash {
698 match Self::deserialize_precompiled(
699 &wasm_runtime,
700 &precompiled_bytes,
701 ) {
702 Ok(module) => {
703 match Self::validate_module(
704 ctx,
705 &module,
706 ValueWrapper(initial_value.clone()),
707 )
708 .await
709 {
710 Ok(()) => {
711 return Ok((
712 Arc::new(module),
713 persisted,
714 "cwasm_hit",
715 ));
716 }
717 Err(error) => {
718 debug!(
719 error = %error,
720 path = %contract_path.display(),
721 "Persisted precompiled contract is invalid, retrying from wasm artifact"
722 );
723 }
724 }
725 }
726 Err(error) => {
727 debug!(
728 error = %error,
729 path = %contract_path.display(),
730 "Persisted precompiled contract can not be deserialized, retrying from wasm artifact"
731 );
732 }
733 }
734 } else {
735 debug!(
736 expected = %persisted.cwasm_hash,
737 actual = %precompiled_hash,
738 path = %contract_path.display(),
739 "Persisted precompiled artifact hash mismatch, retrying from wasm artifact"
740 );
741 }
742 }
743 Err(error) => {
744 debug!(
745 error = %error,
746 path = %contract_path.display(),
747 "Persisted precompiled artifact can not be read, retrying from wasm artifact"
748 );
749 }
750 }
751
752 match Self::load_artifact_wasm(contract_path).await {
753 Ok(wasm_bytes) => {
754 let wasm_hash = Self::hash_bytes(
755 hash,
756 &wasm_bytes,
757 "persisted wasm artifact",
758 )?;
759 if wasm_hash == persisted.wasm_hash {
760 match Self::precompile_module(
761 &wasm_runtime,
762 &wasm_bytes,
763 ) {
764 Ok((precompiled_bytes, module)) => {
765 match Self::validate_module(
766 ctx,
767 &module,
768 ValueWrapper(initial_value.clone()),
769 )
770 .await
771 {
772 Ok(()) => {
773 Self::persist_artifact(
774 contract_path,
775 &wasm_bytes,
776 &precompiled_bytes,
777 )
778 .await?;
779 let refreshed_record =
780 Self::build_contract_record(
781 hash,
782 contract_hash.clone(),
783 manifest_hash.clone(),
784 &wasm_bytes,
785 &precompiled_bytes,
786 engine_fingerprint.clone(),
787 toolchain_fingerprint
788 .clone(),
789 )?;
790
791 register
792 .tell(
793 ContractRegisterMessage::SetMetadata {
794 contract_name: contract_name
795 .to_owned(),
796 metadata: refreshed_record
797 .clone(),
798 },
799 )
800 .await
801 .map_err(|e| {
802 CompilerError::ContractRegisterFailed {
803 details: e.to_string(),
804 }
805 })?;
806
807 return Ok((
808 Arc::new(module),
809 refreshed_record,
810 "wasm_hit",
811 ));
812 }
813 Err(error) => {
814 debug!(
815 error = %error,
816 path = %contract_path.display(),
817 "Persisted wasm artifact is invalid, recompiling"
818 );
819 }
820 }
821 }
822 Err(error) => {
823 debug!(
824 error = %error,
825 path = %contract_path.display(),
826 "Persisted wasm artifact can not be precompiled, recompiling"
827 );
828 }
829 }
830 } else {
831 debug!(
832 expected = %persisted.wasm_hash,
833 actual = %wasm_hash,
834 path = %contract_path.display(),
835 "Persisted wasm artifact hash mismatch, recompiling"
836 );
837 }
838 }
839 Err(error) => {
840 debug!(
841 error = %error,
842 path = %contract_path.display(),
843 "Persisted contract artifact can not be read, recompiling"
844 );
845 }
846 }
847 }
848
849 let (module, metadata) = Self::compile_fresh(
850 hash,
851 ctx,
852 contract,
853 contract_path,
854 initial_value,
855 )
856 .await?;
857
858 register
859 .tell(ContractRegisterMessage::SetMetadata {
860 contract_name: contract_name.to_owned(),
861 metadata: metadata.clone(),
862 })
863 .await
864 .map_err(|e| CompilerError::ContractRegisterFailed {
865 details: e.to_string(),
866 })?;
867
868 Ok((module, metadata, "recompiled"))
869 }
870 .await;
871
872 match result {
873 Ok((module, metadata, prepare_result)) => {
874 Self::observe_contract_prepare(
875 "registered",
876 prepare_result,
877 started_at,
878 );
879 Ok((module, metadata))
880 }
881 Err(error) => {
882 Self::observe_contract_prepare(
883 "registered",
884 "error",
885 started_at,
886 );
887 Err(error)
888 }
889 }
890 }
891
892 fn check_result(
893 store: &Store<MemoryManager>,
894 pointer: u32,
895 ) -> Result<(), CompilerError> {
896 let bytes = store.data().read_data(pointer as usize)?;
897 let contract_result: ContractResult =
898 BorshDeserialize::try_from_slice(bytes).map_err(|e| {
899 CompilerError::InvalidContractOutput {
900 details: e.to_string(),
901 }
902 })?;
903
904 if contract_result.success {
905 Ok(())
906 } else {
907 Err(CompilerError::ContractCheckFailed {
908 error: contract_result.error,
909 })
910 }
911 }
912
913 fn generate_context(
914 state: ValueWrapper,
915 limits: &WasmLimits,
916 ) -> Result<(MemoryManager, u32), CompilerError> {
917 let mut context = MemoryManager::from_limits(limits);
918 let state_bytes =
919 to_vec(&state).map_err(|e| CompilerError::SerializationError {
920 context: "state serialization",
921 details: e.to_string(),
922 })?;
923 let state_ptr = context.add_data_raw(&state_bytes)?;
924 Ok((context, state_ptr as u32))
925 }
926
927 fn get_sdk_functions_identifier() -> HashSet<&'static str> {
928 ["alloc", "write_byte", "pointer_len", "read_byte"]
929 .into_iter()
930 .collect()
931 }
932}