Skip to main content

ave_core/evaluation/compiler/
mod.rs

1#[cfg(feature = "test")]
2use std::env;
3#[cfg(feature = "test")]
4use std::io::ErrorKind;
5use std::{
6    collections::{HashMap, HashSet},
7    hash::{DefaultHasher, Hash as StdHash, Hasher},
8    path::{Path, PathBuf},
9    process::Stdio,
10    sync::Arc,
11    time::Instant,
12};
13
14use ave_actors::{Actor, ActorContext, ActorError, ActorPath, Response};
15use ave_common::{
16    ValueWrapper,
17    identity::{DigestIdentifier, HashAlgorithm, hash_borsh},
18};
19use base64::{Engine as Base64Engine, prelude::BASE64_STANDARD};
20use borsh::{BorshDeserialize, BorshSerialize, to_vec};
21use serde::{Deserialize, Serialize};
22use serde_json::Value;
23#[cfg(feature = "test")]
24use tokio::time::{Duration, sleep};
25use tokio::{fs, process::Command, sync::RwLock};
26use tracing::debug;
27use wasmtime::{ExternType, Module, Store};
28
29use crate::model::common::contract::{
30    MAX_FUEL_COMPILATION, MemoryManager, WasmLimits, WasmRuntime,
31    generate_linker,
32};
33use crate::{
34    governance::contract_register::{
35        ContractRegister, ContractRegisterMessage, ContractRegisterResponse,
36    },
37    metrics::try_core_metrics,
38};
39
40pub mod contract_compiler;
41pub mod error;
42pub mod temp_compiler;
43
44pub use contract_compiler::{ContractCompiler, ContractCompilerMessage};
45pub use temp_compiler::{TempCompiler, TempCompilerMessage};
46
47use error::*;
48
49#[derive(
50    Serialize, Deserialize, BorshSerialize, BorshDeserialize, Debug, Clone,
51)]
52pub struct ContractResult {
53    pub success: bool,
54    pub error: String,
55}
56
57#[derive(
58    Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
59)]
60pub struct ContractArtifactRecord {
61    pub contract_hash: DigestIdentifier,
62    pub manifest_hash: DigestIdentifier,
63    pub wasm_hash: DigestIdentifier,
64    pub cwasm_hash: DigestIdentifier,
65    pub engine_fingerprint: DigestIdentifier,
66    pub toolchain_fingerprint: DigestIdentifier,
67}
68
69#[derive(Debug, Clone)]
70pub enum CompilerResponse {
71    Ok,
72    Error(CompilerError),
73}
74
75impl Response for CompilerResponse {}
76
77struct CompilerSupport;
78
79impl CompilerSupport {
80    const SHARED_TARGET_DIR: &'static str = "target";
81    const VENDOR_DIR: &'static str = "vendor";
82    const ARTIFACT_WASM: &'static str = "contract.wasm";
83    const ARTIFACT_PRECOMPILED: &'static str = "contract.cwasm";
84    const LEGACY_ARTIFACT_METADATA: &'static str = "contract.json";
85    #[cfg(feature = "test")]
86    const GLOBAL_CACHE_DIR: &'static str = "ave-contract-artifacts";
87    #[cfg(feature = "test")]
88    const GLOBAL_CACHE_METADATA: &'static str = "metadata.borsh";
89
90    fn observe_contract_prepare(
91        kind: &'static str,
92        result: &'static str,
93        started_at: Instant,
94    ) {
95        if let Some(metrics) = try_core_metrics() {
96            metrics.observe_contract_prepare(
97                kind,
98                result,
99                started_at.elapsed(),
100            );
101        }
102    }
103
104    fn compilation_toml() -> String {
105        include_str!("contract_Cargo.toml").to_owned()
106    }
107
108    fn contracts_root(contract_path: &Path) -> Result<PathBuf, CompilerError> {
109        contract_path
110            .parent()
111            .and_then(Path::parent)
112            .map(Path::to_path_buf)
113            .ok_or_else(|| CompilerError::InvalidContractPath {
114                path: contract_path.to_string_lossy().to_string(),
115                details:
116                    "expected contract path under <contracts_path>/contracts/<name>"
117                        .to_owned(),
118            })
119    }
120
121    #[cfg(feature = "test")]
122    fn artifact_wasm_path_in(base_path: &Path) -> PathBuf {
123        base_path.join(Self::ARTIFACT_WASM)
124    }
125
126    fn artifact_wasm_path(contract_path: &Path) -> PathBuf {
127        contract_path.join(Self::ARTIFACT_WASM)
128    }
129
130    #[cfg(feature = "test")]
131    fn artifact_precompiled_path_in(base_path: &Path) -> PathBuf {
132        base_path.join(Self::ARTIFACT_PRECOMPILED)
133    }
134
135    fn artifact_precompiled_path(contract_path: &Path) -> PathBuf {
136        contract_path.join(Self::ARTIFACT_PRECOMPILED)
137    }
138
139    fn legacy_artifact_metadata_path(contract_path: &Path) -> PathBuf {
140        contract_path.join(Self::LEGACY_ARTIFACT_METADATA)
141    }
142
143    #[cfg(feature = "test")]
144    fn global_cache_root() -> PathBuf {
145        env::temp_dir().join(Self::GLOBAL_CACHE_DIR)
146    }
147
148    #[cfg(feature = "test")]
149    fn global_cache_entry_dir(
150        contract_hash: &DigestIdentifier,
151        manifest_hash: &DigestIdentifier,
152        engine_fingerprint: &DigestIdentifier,
153        toolchain_fingerprint: &DigestIdentifier,
154    ) -> PathBuf {
155        Self::global_cache_root().join(format!(
156            "{contract_hash}_{manifest_hash}_{engine_fingerprint}_{toolchain_fingerprint}"
157        ))
158    }
159
160    #[cfg(feature = "test")]
161    fn global_cache_metadata_path(cache_dir: &Path) -> PathBuf {
162        cache_dir.join(Self::GLOBAL_CACHE_METADATA)
163    }
164
165    #[cfg(feature = "test")]
166    fn global_cache_lock_path(cache_dir: &Path) -> PathBuf {
167        cache_dir.with_extension("lock")
168    }
169
170    fn cargo_config_path(contract_path: &Path) -> PathBuf {
171        contract_path.join(".cargo").join("config.toml")
172    }
173
174    fn build_output_wasm_path(contracts_root: &Path) -> PathBuf {
175        contracts_root
176            .join(Self::SHARED_TARGET_DIR)
177            .join("wasm32-unknown-unknown")
178            .join("release")
179            .join(Self::ARTIFACT_WASM)
180    }
181
182    fn cargo_config(
183        shared_target_dir: &Path,
184        vendor_dir: Option<&Path>,
185    ) -> String {
186        let mut config = format!(
187            "[build]\ntarget-dir = \"{}\"\n",
188            shared_target_dir.to_string_lossy()
189        );
190
191        if let Some(vendor_dir) = vendor_dir {
192            config.push_str(&format!(
193                "\n[net]\noffline = true\n\n[source.crates-io]\nreplace-with = \"vendored-sources\"\n\n[source.vendored-sources]\ndirectory = \"{}\"\n",
194                vendor_dir.to_string_lossy()
195            ));
196        }
197
198        config
199    }
200
201    async fn build_contract(
202        contract_path: &Path,
203        offline: bool,
204    ) -> Result<(), CompilerError> {
205        let cargo = contract_path.join("Cargo.toml");
206        let cargo_config = Self::cargo_config_path(contract_path);
207        let mut command = Command::new("cargo");
208        command
209            .arg("build")
210            .arg(format!("--manifest-path={}", cargo.to_string_lossy()))
211            .arg("--target")
212            .arg("wasm32-unknown-unknown")
213            .arg("--release")
214            .env("CARGO_HOME", contract_path.join(".cargo"))
215            .env("CARGO_CONFIG", &cargo_config)
216            .stdout(Stdio::null())
217            .stderr(Stdio::null());
218
219        if offline {
220            command.arg("--offline");
221        }
222
223        let status = command.status().await.map_err(|e| {
224            CompilerError::CargoBuildFailed {
225                details: e.to_string(),
226            }
227        })?;
228
229        if !status.success() {
230            return Err(CompilerError::CompilationFailed);
231        }
232
233        Ok(())
234    }
235
236    async fn prepare_contract_project(
237        contract: &str,
238        contract_path: &Path,
239    ) -> Result<(), CompilerError> {
240        let decode_base64 = BASE64_STANDARD.decode(contract).map_err(|e| {
241            CompilerError::Base64DecodeFailed {
242                details: format!(
243                    "{} (path: {})",
244                    e,
245                    contract_path.to_string_lossy()
246                ),
247            }
248        })?;
249
250        let contracts_root = Self::contracts_root(contract_path)?;
251        let dir = contract_path.join("src");
252        if !Path::new(&dir).exists() {
253            fs::create_dir_all(&dir).await.map_err(|e| {
254                CompilerError::DirectoryCreationFailed {
255                    path: dir.to_string_lossy().to_string(),
256                    details: e.to_string(),
257                }
258            })?;
259        }
260
261        let cargo_config_dir = contract_path.join(".cargo");
262        if !Path::new(&cargo_config_dir).exists() {
263            fs::create_dir_all(&cargo_config_dir).await.map_err(|e| {
264                CompilerError::DirectoryCreationFailed {
265                    path: cargo_config_dir.to_string_lossy().to_string(),
266                    details: e.to_string(),
267                }
268            })?;
269        }
270
271        let toml = Self::compilation_toml();
272        let cargo = contract_path.join("Cargo.toml");
273        fs::write(&cargo, toml).await.map_err(|e| {
274            CompilerError::FileWriteFailed {
275                path: cargo.to_string_lossy().to_string(),
276                details: e.to_string(),
277            }
278        })?;
279
280        let lib_rs = contract_path.join("src").join("lib.rs");
281        fs::write(&lib_rs, decode_base64).await.map_err(|e| {
282            CompilerError::FileWriteFailed {
283                path: lib_rs.to_string_lossy().to_string(),
284                details: e.to_string(),
285            }
286        })?;
287
288        let vendor_dir = contracts_root.join(Self::VENDOR_DIR);
289        let cargo_config = Self::cargo_config(
290            &contracts_root.join(Self::SHARED_TARGET_DIR),
291            vendor_dir.exists().then_some(vendor_dir.as_path()),
292        );
293        let cargo_config_path = Self::cargo_config_path(contract_path);
294        fs::write(&cargo_config_path, cargo_config)
295            .await
296            .map_err(|e| CompilerError::FileWriteFailed {
297                path: cargo_config_path.to_string_lossy().to_string(),
298                details: e.to_string(),
299            })?;
300
301        Ok(())
302    }
303
304    async fn load_artifact_wasm(
305        contract_path: &Path,
306    ) -> Result<Vec<u8>, CompilerError> {
307        let wasm_path = Self::artifact_wasm_path(contract_path);
308        fs::read(&wasm_path)
309            .await
310            .map_err(|e| CompilerError::FileReadFailed {
311                path: wasm_path.to_string_lossy().to_string(),
312                details: e.to_string(),
313            })
314    }
315
316    #[cfg(feature = "test")]
317    async fn load_artifact_wasm_from(
318        base_path: &Path,
319    ) -> Result<Vec<u8>, CompilerError> {
320        let wasm_path = Self::artifact_wasm_path_in(base_path);
321        fs::read(&wasm_path)
322            .await
323            .map_err(|e| CompilerError::FileReadFailed {
324                path: wasm_path.to_string_lossy().to_string(),
325                details: e.to_string(),
326            })
327    }
328
329    async fn load_artifact_precompiled(
330        contract_path: &Path,
331    ) -> Result<Vec<u8>, CompilerError> {
332        let precompiled_path = Self::artifact_precompiled_path(contract_path);
333        fs::read(&precompiled_path).await.map_err(|e| {
334            CompilerError::FileReadFailed {
335                path: precompiled_path.to_string_lossy().to_string(),
336                details: e.to_string(),
337            }
338        })
339    }
340
341    #[cfg(feature = "test")]
342    async fn load_artifact_precompiled_from(
343        base_path: &Path,
344    ) -> Result<Vec<u8>, CompilerError> {
345        let precompiled_path = Self::artifact_precompiled_path_in(base_path);
346        fs::read(&precompiled_path).await.map_err(|e| {
347            CompilerError::FileReadFailed {
348                path: precompiled_path.to_string_lossy().to_string(),
349                details: e.to_string(),
350            }
351        })
352    }
353
354    async fn load_compiled_wasm(
355        contracts_root: &Path,
356    ) -> Result<Vec<u8>, CompilerError> {
357        let wasm_path = Self::build_output_wasm_path(contracts_root);
358        fs::read(&wasm_path)
359            .await
360            .map_err(|e| CompilerError::FileReadFailed {
361                path: wasm_path.to_string_lossy().to_string(),
362                details: e.to_string(),
363            })
364    }
365
366    async fn persist_artifact(
367        contract_path: &Path,
368        wasm_bytes: &[u8],
369        precompiled_bytes: &[u8],
370    ) -> Result<(), CompilerError> {
371        let artifact_path = Self::artifact_wasm_path(contract_path);
372        fs::write(&artifact_path, wasm_bytes).await.map_err(|e| {
373            CompilerError::FileWriteFailed {
374                path: artifact_path.to_string_lossy().to_string(),
375                details: e.to_string(),
376            }
377        })?;
378
379        let precompiled_path = Self::artifact_precompiled_path(contract_path);
380        fs::write(&precompiled_path, precompiled_bytes)
381            .await
382            .map_err(|e| CompilerError::FileWriteFailed {
383                path: precompiled_path.to_string_lossy().to_string(),
384                details: e.to_string(),
385            })?;
386
387        let legacy_metadata_path =
388            Self::legacy_artifact_metadata_path(contract_path);
389        let _ = fs::remove_file(&legacy_metadata_path).await;
390
391        Ok(())
392    }
393
394    #[cfg(feature = "test")]
395    async fn persist_global_cache_artifact(
396        cache_dir: &Path,
397        metadata: &ContractArtifactRecord,
398        wasm_bytes: &[u8],
399        precompiled_bytes: &[u8],
400    ) -> Result<(), CompilerError> {
401        fs::create_dir_all(cache_dir).await.map_err(|e| {
402            CompilerError::DirectoryCreationFailed {
403                path: cache_dir.to_string_lossy().to_string(),
404                details: e.to_string(),
405            }
406        })?;
407
408        let artifact_path = Self::artifact_wasm_path_in(cache_dir);
409        fs::write(&artifact_path, wasm_bytes).await.map_err(|e| {
410            CompilerError::FileWriteFailed {
411                path: artifact_path.to_string_lossy().to_string(),
412                details: e.to_string(),
413            }
414        })?;
415
416        let precompiled_path = Self::artifact_precompiled_path_in(cache_dir);
417        fs::write(&precompiled_path, precompiled_bytes)
418            .await
419            .map_err(|e| CompilerError::FileWriteFailed {
420                path: precompiled_path.to_string_lossy().to_string(),
421                details: e.to_string(),
422            })?;
423
424        let metadata_path = Self::global_cache_metadata_path(cache_dir);
425        fs::write(
426            &metadata_path,
427            to_vec(metadata).map_err(|e| {
428                CompilerError::SerializationError {
429                    context: "global cache metadata",
430                    details: e.to_string(),
431                }
432            })?,
433        )
434        .await
435        .map_err(|e| CompilerError::FileWriteFailed {
436            path: metadata_path.to_string_lossy().to_string(),
437            details: e.to_string(),
438        })?;
439
440        Ok(())
441    }
442
443    #[cfg(feature = "test")]
444    async fn try_acquire_global_cache_lock(
445        cache_dir: &Path,
446    ) -> Result<Option<GlobalCacheLock>, CompilerError> {
447        fs::create_dir_all(Self::global_cache_root())
448            .await
449            .map_err(|e| CompilerError::DirectoryCreationFailed {
450                path: Self::global_cache_root().to_string_lossy().to_string(),
451                details: e.to_string(),
452            })?;
453
454        let lock_path = Self::global_cache_lock_path(cache_dir);
455        match fs::OpenOptions::new()
456            .write(true)
457            .create_new(true)
458            .open(&lock_path)
459            .await
460        {
461            Ok(_) => Ok(Some(GlobalCacheLock { path: lock_path })),
462            Err(error) if error.kind() == ErrorKind::AlreadyExists => Ok(None),
463            Err(error) => Err(CompilerError::FileWriteFailed {
464                path: lock_path.to_string_lossy().to_string(),
465                details: error.to_string(),
466            }),
467        }
468    }
469
470    #[cfg(feature = "test")]
471    async fn wait_for_global_cache<A: Actor>(
472        hash: HashAlgorithm,
473        ctx: &ActorContext<A>,
474        initial_value: Value,
475        contract_hash: &DigestIdentifier,
476        manifest_hash: &DigestIdentifier,
477        engine_fingerprint: &DigestIdentifier,
478        toolchain_fingerprint: &DigestIdentifier,
479    ) -> Result<
480        Option<(
481            Arc<Module>,
482            ContractArtifactRecord,
483            &'static str,
484            Vec<u8>,
485            Vec<u8>,
486        )>,
487        CompilerError,
488    > {
489        let cache_dir = Self::global_cache_entry_dir(
490            contract_hash,
491            manifest_hash,
492            engine_fingerprint,
493            toolchain_fingerprint,
494        );
495        let lock_path = Self::global_cache_lock_path(&cache_dir);
496
497        loop {
498            if let Some(hit) = Self::try_load_global_cache(
499                hash,
500                ctx,
501                initial_value.clone(),
502                contract_hash,
503                manifest_hash,
504                engine_fingerprint,
505                toolchain_fingerprint,
506            )
507            .await?
508            {
509                return Ok(Some(hit));
510            }
511
512            if fs::metadata(&lock_path).await.is_err() {
513                return Ok(None);
514            }
515
516            sleep(Duration::from_millis(50)).await;
517        }
518    }
519
520    #[cfg(feature = "test")]
521    async fn load_global_cache_metadata(
522        cache_dir: &Path,
523    ) -> Result<ContractArtifactRecord, CompilerError> {
524        let metadata_path = Self::global_cache_metadata_path(cache_dir);
525        let metadata_bytes = fs::read(&metadata_path).await.map_err(|e| {
526            CompilerError::FileReadFailed {
527                path: metadata_path.to_string_lossy().to_string(),
528                details: e.to_string(),
529            }
530        })?;
531
532        ContractArtifactRecord::try_from_slice(&metadata_bytes).map_err(|e| {
533            CompilerError::SerializationError {
534                context: "global cache metadata",
535                details: e.to_string(),
536            }
537        })
538    }
539
540    fn deserialize_precompiled(
541        wasm_runtime: &WasmRuntime,
542        precompiled_bytes: &[u8],
543    ) -> Result<Module, CompilerError> {
544        unsafe {
545            Module::deserialize(&wasm_runtime.engine, precompiled_bytes)
546                .map_err(|e| CompilerError::WasmDeserializationFailed {
547                    details: e.to_string(),
548                })
549        }
550    }
551
552    fn precompile_module(
553        wasm_runtime: &WasmRuntime,
554        wasm_bytes: &[u8],
555    ) -> Result<(Vec<u8>, Module), CompilerError> {
556        let precompiled_bytes = wasm_runtime
557            .engine
558            .precompile_module(wasm_bytes)
559            .map_err(|e| CompilerError::WasmPrecompileFailed {
560                details: e.to_string(),
561            })?;
562
563        let module =
564            Self::deserialize_precompiled(wasm_runtime, &precompiled_bytes)?;
565
566        Ok((precompiled_bytes, module))
567    }
568
569    async fn validate_module<A: Actor>(
570        ctx: &ActorContext<A>,
571        module: &Module,
572        state: ValueWrapper,
573    ) -> Result<(), CompilerError> {
574        let wasm_runtime = Self::wasm_runtime(ctx).await?;
575
576        let imports = module.imports();
577        let mut pending_sdk = Self::get_sdk_functions_identifier();
578
579        for import in imports {
580            match import.ty() {
581                ExternType::Func(_) => {
582                    if !pending_sdk.remove(import.name()) {
583                        return Err(CompilerError::InvalidModule {
584                            kind: InvalidModuleKind::UnknownImportFunction {
585                                name: import.name().to_string(),
586                            },
587                        });
588                    }
589                }
590                extern_type => {
591                    return Err(CompilerError::InvalidModule {
592                        kind: InvalidModuleKind::NonFunctionImport {
593                            import_type: format!("{:?}", extern_type),
594                        },
595                    });
596                }
597            }
598        }
599        if !pending_sdk.is_empty() {
600            return Err(CompilerError::InvalidModule {
601                kind: InvalidModuleKind::MissingImports {
602                    missing: pending_sdk
603                        .into_iter()
604                        .map(|s| s.to_string())
605                        .collect(),
606                },
607            });
608        }
609
610        let (context, state_ptr) =
611            Self::generate_context(state, &wasm_runtime.limits)?;
612        let mut store = Store::new(&wasm_runtime.engine, context);
613
614        store.limiter(|data| &mut data.store_limits);
615        store.set_fuel(MAX_FUEL_COMPILATION).map_err(|e| {
616            CompilerError::FuelLimitError {
617                details: e.to_string(),
618            }
619        })?;
620
621        let linker = generate_linker(&wasm_runtime.engine)?;
622        let instance = linker.instantiate(&mut store, module).map_err(|e| {
623            CompilerError::InstantiationFailed {
624                details: e.to_string(),
625            }
626        })?;
627
628        let _main_contract_entrypoint = instance
629            .get_typed_func::<(u32, u32, u32, u32), u32>(
630                &mut store,
631                "main_function",
632            )
633            .map_err(|_e| CompilerError::EntryPointNotFound {
634                function: "main_function",
635            })?;
636
637        let init_contract_entrypoint = instance
638            .get_typed_func::<u32, u32>(&mut store, "init_check_function")
639            .map_err(|_e| CompilerError::EntryPointNotFound {
640                function: "init_check_function",
641            })?;
642
643        let result_ptr =
644            init_contract_entrypoint
645                .call(&mut store, state_ptr)
646                .map_err(|e| CompilerError::ContractExecutionFailed {
647                    details: e.to_string(),
648                })?;
649
650        Self::check_result(&store, result_ptr)?;
651
652        Ok(())
653    }
654
655    async fn wasm_runtime<A: Actor>(
656        ctx: &ActorContext<A>,
657    ) -> Result<Arc<WasmRuntime>, CompilerError> {
658        ctx.system()
659            .get_helper::<Arc<WasmRuntime>>("wasm_runtime")
660            .await
661            .ok_or(CompilerError::MissingHelper {
662                name: "wasm_runtime",
663            })
664    }
665
666    async fn contracts_helper<A: Actor>(
667        ctx: &ActorContext<A>,
668    ) -> Result<Arc<RwLock<HashMap<String, Arc<Module>>>>, ActorError> {
669        ctx.system()
670            .get_helper::<Arc<RwLock<HashMap<String, Arc<Module>>>>>(
671                "contracts",
672            )
673            .await
674            .ok_or_else(|| ActorError::Helper {
675                name: "contracts".to_owned(),
676                reason: "Not found".to_owned(),
677            })
678    }
679
680    fn build_contract_record(
681        hash: HashAlgorithm,
682        contract_hash: DigestIdentifier,
683        manifest_hash: DigestIdentifier,
684        wasm_bytes: &[u8],
685        precompiled_bytes: &[u8],
686        engine_fingerprint: DigestIdentifier,
687        toolchain_fingerprint: DigestIdentifier,
688    ) -> Result<ContractArtifactRecord, CompilerError> {
689        let wasm_hash = Self::hash_bytes(hash, wasm_bytes, "wasm artifact")?;
690        let cwasm_hash =
691            Self::hash_bytes(hash, precompiled_bytes, "cwasm artifact")?;
692
693        Ok(ContractArtifactRecord {
694            contract_hash,
695            manifest_hash,
696            wasm_hash,
697            cwasm_hash,
698            engine_fingerprint,
699            toolchain_fingerprint,
700        })
701    }
702
703    fn hash_bytes(
704        hash: HashAlgorithm,
705        bytes: &[u8],
706        context: &'static str,
707    ) -> Result<DigestIdentifier, CompilerError> {
708        hash_borsh(&*hash.hasher(), &bytes.to_vec()).map_err(|e| {
709            CompilerError::SerializationError {
710                context,
711                details: e.to_string(),
712            }
713        })
714    }
715
716    fn engine_fingerprint(
717        hash: HashAlgorithm,
718        wasm_runtime: &WasmRuntime,
719    ) -> Result<DigestIdentifier, CompilerError> {
720        let mut hasher = DefaultHasher::new();
721        wasm_runtime
722            .engine
723            .precompile_compatibility_hash()
724            .hash(&mut hasher);
725        hash_borsh(&*hash.hasher(), &hasher.finish()).map_err(|e| {
726            CompilerError::SerializationError {
727                context: "engine fingerprint",
728                details: e.to_string(),
729            }
730        })
731    }
732
733    async fn toolchain_fingerprint(
734        hash: HashAlgorithm,
735    ) -> Result<DigestIdentifier, CompilerError> {
736        let output = Command::new("rustc")
737            .arg("--version")
738            .arg("--verbose")
739            .output()
740            .await
741            .map_err(|e| CompilerError::ToolchainFingerprintFailed {
742                details: e.to_string(),
743            })?;
744
745        if !output.status.success() {
746            return Err(CompilerError::ToolchainFingerprintFailed {
747                details: String::from_utf8_lossy(&output.stderr).to_string(),
748            });
749        }
750
751        let fingerprint_input =
752            String::from_utf8_lossy(&output.stdout).to_string();
753        hash_borsh(&*hash.hasher(), &fingerprint_input).map_err(|e| {
754            CompilerError::SerializationError {
755                context: "toolchain fingerprint",
756                details: e.to_string(),
757            }
758        })
759    }
760
761    async fn contract_environment(
762        hash: HashAlgorithm,
763        wasm_runtime: &Arc<WasmRuntime>,
764    ) -> Result<(DigestIdentifier, DigestIdentifier), CompilerError> {
765        let engine_fingerprint = Self::engine_fingerprint(hash, wasm_runtime)?;
766        let toolchain_fingerprint = Self::toolchain_fingerprint(hash).await?;
767        Ok((engine_fingerprint, toolchain_fingerprint))
768    }
769
770    fn metadata_matches(
771        persisted: &ContractArtifactRecord,
772        expected_contract_hash: &DigestIdentifier,
773        expected_manifest_hash: &DigestIdentifier,
774        expected_engine_fingerprint: &DigestIdentifier,
775        expected_toolchain_fingerprint: &DigestIdentifier,
776    ) -> bool {
777        persisted.contract_hash == *expected_contract_hash
778            && persisted.manifest_hash == *expected_manifest_hash
779            && persisted.engine_fingerprint == *expected_engine_fingerprint
780            && persisted.toolchain_fingerprint
781                == *expected_toolchain_fingerprint
782    }
783
784    async fn compile_fresh<A: Actor>(
785        hash: HashAlgorithm,
786        ctx: &ActorContext<A>,
787        contract: &str,
788        contract_path: &Path,
789        initial_value: Value,
790    ) -> Result<(Arc<Module>, ContractArtifactRecord), CompilerError> {
791        let contract_hash =
792            hash_borsh(&*hash.hasher(), &contract).map_err(|e| {
793                CompilerError::SerializationError {
794                    context: "contract hash",
795                    details: e.to_string(),
796                }
797            })?;
798        let manifest = Self::compilation_toml();
799        let manifest_hash =
800            hash_borsh(&*hash.hasher(), &manifest).map_err(|e| {
801                CompilerError::SerializationError {
802                    context: "contract manifest hash",
803                    details: e.to_string(),
804                }
805            })?;
806
807        Self::prepare_contract_project(contract, contract_path).await?;
808
809        let wasm_runtime = Self::wasm_runtime(ctx).await?;
810        let (engine_fingerprint, toolchain_fingerprint) =
811            Self::contract_environment(hash, &wasm_runtime).await?;
812
813        let contracts_root = Self::contracts_root(contract_path)?;
814        Self::build_contract(
815            contract_path,
816            contracts_root.join(Self::VENDOR_DIR).exists(),
817        )
818        .await?;
819
820        let wasm_bytes = Self::load_compiled_wasm(&contracts_root).await?;
821        let (precompiled_bytes, module) =
822            Self::precompile_module(&wasm_runtime, &wasm_bytes)?;
823        Self::validate_module(ctx, &module, ValueWrapper(initial_value))
824            .await?;
825        Self::persist_artifact(contract_path, &wasm_bytes, &precompiled_bytes)
826            .await?;
827
828        let metadata = Self::build_contract_record(
829            hash,
830            contract_hash,
831            manifest_hash,
832            &wasm_bytes,
833            &precompiled_bytes,
834            engine_fingerprint,
835            toolchain_fingerprint,
836        )?;
837
838        #[cfg(feature = "test")]
839        {
840            let global_cache_dir = Self::global_cache_entry_dir(
841                &metadata.contract_hash,
842                &metadata.manifest_hash,
843                &metadata.engine_fingerprint,
844                &metadata.toolchain_fingerprint,
845            );
846            if let Err(error) = Self::persist_global_cache_artifact(
847                &global_cache_dir,
848                &metadata,
849                &wasm_bytes,
850                &precompiled_bytes,
851            )
852            .await
853            {
854                debug!(
855                    error = %error,
856                    path = %global_cache_dir.display(),
857                    "Failed to persist global contract cache artifact"
858                );
859            }
860        }
861
862        Ok((Arc::new(module), metadata))
863    }
864
865    #[cfg(feature = "test")]
866    async fn try_load_global_cache<A: Actor>(
867        hash: HashAlgorithm,
868        ctx: &ActorContext<A>,
869        initial_value: Value,
870        contract_hash: &DigestIdentifier,
871        manifest_hash: &DigestIdentifier,
872        engine_fingerprint: &DigestIdentifier,
873        toolchain_fingerprint: &DigestIdentifier,
874    ) -> Result<
875        Option<(
876            Arc<Module>,
877            ContractArtifactRecord,
878            &'static str,
879            Vec<u8>,
880            Vec<u8>,
881        )>,
882        CompilerError,
883    > {
884        let cache_dir = Self::global_cache_entry_dir(
885            contract_hash,
886            manifest_hash,
887            engine_fingerprint,
888            toolchain_fingerprint,
889        );
890
891        let persisted = match Self::load_global_cache_metadata(&cache_dir).await
892        {
893            Ok(metadata) => metadata,
894            Err(error) => {
895                debug!(
896                    error = %error,
897                    path = %cache_dir.display(),
898                    "Global contract cache metadata unavailable"
899                );
900                return Ok(None);
901            }
902        };
903
904        if !Self::metadata_matches(
905            &persisted,
906            contract_hash,
907            manifest_hash,
908            engine_fingerprint,
909            toolchain_fingerprint,
910        ) {
911            return Ok(None);
912        }
913
914        let wasm_runtime = Self::wasm_runtime(ctx).await?;
915        let wasm_bytes = match Self::load_artifact_wasm_from(&cache_dir).await {
916            Ok(bytes) => bytes,
917            Err(error) => {
918                debug!(
919                    error = %error,
920                    path = %cache_dir.display(),
921                    "Global contract cache wasm artifact unavailable"
922                );
923                return Ok(None);
924            }
925        };
926
927        let wasm_hash =
928            Self::hash_bytes(hash, &wasm_bytes, "global cache wasm artifact")?;
929        if wasm_hash != persisted.wasm_hash {
930            debug!(
931                expected = %persisted.wasm_hash,
932                actual = %wasm_hash,
933                path = %cache_dir.display(),
934                "Global cache wasm artifact hash mismatch"
935            );
936            return Ok(None);
937        }
938
939        if let Ok(precompiled_bytes) =
940            Self::load_artifact_precompiled_from(&cache_dir).await
941        {
942            let precompiled_hash = Self::hash_bytes(
943                hash,
944                &precompiled_bytes,
945                "global cache cwasm artifact",
946            )?;
947            if precompiled_hash == persisted.cwasm_hash
948                && let Ok(module) = Self::deserialize_precompiled(
949                    &wasm_runtime,
950                    &precompiled_bytes,
951                )
952                && Self::validate_module(
953                    ctx,
954                    &module,
955                    ValueWrapper(initial_value.clone()),
956                )
957                .await
958                .is_ok()
959            {
960                return Ok(Some((
961                    Arc::new(module),
962                    persisted,
963                    "global_cwasm_hit",
964                    wasm_bytes,
965                    precompiled_bytes,
966                )));
967            }
968        }
969
970        if let Ok((precompiled_bytes, module)) =
971            Self::precompile_module(&wasm_runtime, &wasm_bytes)
972            && Self::validate_module(ctx, &module, ValueWrapper(initial_value))
973                .await
974                .is_ok()
975        {
976            let refreshed_record = Self::build_contract_record(
977                hash,
978                contract_hash.clone(),
979                manifest_hash.clone(),
980                &wasm_bytes,
981                &precompiled_bytes,
982                engine_fingerprint.clone(),
983                toolchain_fingerprint.clone(),
984            )?;
985
986            if let Err(error) = Self::persist_global_cache_artifact(
987                &cache_dir,
988                &refreshed_record,
989                &wasm_bytes,
990                &precompiled_bytes,
991            )
992            .await
993            {
994                debug!(
995                    error = %error,
996                    path = %cache_dir.display(),
997                    "Failed to refresh global contract cache artifact"
998                );
999            }
1000
1001            return Ok(Some((
1002                Arc::new(module),
1003                refreshed_record,
1004                "global_wasm_hit",
1005                wasm_bytes,
1006                precompiled_bytes,
1007            )));
1008        }
1009
1010        Ok(None)
1011    }
1012
1013    async fn compile_or_load_registered<A: Actor>(
1014        hash: HashAlgorithm,
1015        ctx: &ActorContext<A>,
1016        contract_name: &str,
1017        contract: &str,
1018        contract_path: &Path,
1019        initial_value: Value,
1020    ) -> Result<(Arc<Module>, ContractArtifactRecord), CompilerError> {
1021        let started_at = Instant::now();
1022        let result = async {
1023            let contract_hash =
1024                hash_borsh(&*hash.hasher(), &contract).map_err(|e| {
1025                    CompilerError::SerializationError {
1026                        context: "contract hash",
1027                        details: e.to_string(),
1028                    }
1029                })?;
1030            let manifest = Self::compilation_toml();
1031            let manifest_hash =
1032                hash_borsh(&*hash.hasher(), &manifest).map_err(|e| {
1033                    CompilerError::SerializationError {
1034                        context: "contract manifest hash",
1035                        details: e.to_string(),
1036                    }
1037                })?;
1038
1039            Self::prepare_contract_project(contract, contract_path).await?;
1040
1041            let wasm_runtime = Self::wasm_runtime(ctx).await?;
1042            let (engine_fingerprint, toolchain_fingerprint) =
1043                Self::contract_environment(hash, &wasm_runtime).await?;
1044
1045            let parent_path = ctx.path().parent();
1046            let register_path =
1047                ActorPath::from(format!("{}/contract_register", parent_path));
1048            let register = ctx
1049                .system()
1050                .get_actor::<ContractRegister>(&register_path)
1051                .await
1052                .map_err(|e| CompilerError::ContractRegisterFailed {
1053                    details: e.to_string(),
1054                })?;
1055
1056            let persisted = match register
1057                .ask(ContractRegisterMessage::GetMetadata {
1058                    contract_name: contract_name.to_owned(),
1059                })
1060                .await
1061            {
1062                Ok(ContractRegisterResponse::Metadata(metadata)) => metadata,
1063                Ok(ContractRegisterResponse::Contracts(_)) => None,
1064                Ok(ContractRegisterResponse::Ok) => None,
1065                Err(e) => {
1066                    return Err(CompilerError::ContractRegisterFailed {
1067                        details: e.to_string(),
1068                    });
1069                }
1070            };
1071
1072            if let Some(persisted) = persisted
1073                && Self::metadata_matches(
1074                    &persisted,
1075                    &contract_hash,
1076                    &manifest_hash,
1077                    &engine_fingerprint,
1078                    &toolchain_fingerprint,
1079                )
1080            {
1081                match Self::load_artifact_precompiled(contract_path).await {
1082                    Ok(precompiled_bytes) => {
1083                        let precompiled_hash = Self::hash_bytes(
1084                            hash,
1085                            &precompiled_bytes,
1086                            "persisted cwasm artifact",
1087                        )?;
1088                        if precompiled_hash == persisted.cwasm_hash {
1089                            match Self::deserialize_precompiled(
1090                                &wasm_runtime,
1091                                &precompiled_bytes,
1092                            ) {
1093                                Ok(module) => {
1094                                    match Self::validate_module(
1095                                        ctx,
1096                                        &module,
1097                                        ValueWrapper(initial_value.clone()),
1098                                    )
1099                                    .await
1100                                    {
1101                                        Ok(()) => {
1102                                            return Ok((
1103                                                Arc::new(module),
1104                                                persisted,
1105                                                "cwasm_hit",
1106                                            ));
1107                                        }
1108                                        Err(error) => {
1109                                            debug!(
1110                                                error = %error,
1111                                                path = %contract_path.display(),
1112                                                "Persisted precompiled contract is invalid, retrying from wasm artifact"
1113                                            );
1114                                        }
1115                                    }
1116                                }
1117                                Err(error) => {
1118                                    debug!(
1119                                        error = %error,
1120                                        path = %contract_path.display(),
1121                                        "Persisted precompiled contract can not be deserialized, retrying from wasm artifact"
1122                                    );
1123                                }
1124                            }
1125                        } else {
1126                            debug!(
1127                                expected = %persisted.cwasm_hash,
1128                                actual = %precompiled_hash,
1129                                path = %contract_path.display(),
1130                                "Persisted precompiled artifact hash mismatch, retrying from wasm artifact"
1131                            );
1132                        }
1133                    }
1134                    Err(error) => {
1135                        debug!(
1136                            error = %error,
1137                            path = %contract_path.display(),
1138                            "Persisted precompiled artifact can not be read, retrying from wasm artifact"
1139                        );
1140                    }
1141                }
1142
1143                match Self::load_artifact_wasm(contract_path).await {
1144                    Ok(wasm_bytes) => {
1145                        let wasm_hash = Self::hash_bytes(
1146                            hash,
1147                            &wasm_bytes,
1148                            "persisted wasm artifact",
1149                        )?;
1150                        if wasm_hash == persisted.wasm_hash {
1151                            match Self::precompile_module(
1152                                &wasm_runtime,
1153                                &wasm_bytes,
1154                            ) {
1155                                Ok((precompiled_bytes, module)) => {
1156                                    match Self::validate_module(
1157                                        ctx,
1158                                        &module,
1159                                        ValueWrapper(initial_value.clone()),
1160                                    )
1161                                    .await
1162                                    {
1163                                        Ok(()) => {
1164                                            Self::persist_artifact(
1165                                                contract_path,
1166                                                &wasm_bytes,
1167                                                &precompiled_bytes,
1168                                            )
1169                                            .await?;
1170                                            let refreshed_record =
1171                                                Self::build_contract_record(
1172                                                    hash,
1173                                                    contract_hash.clone(),
1174                                                    manifest_hash.clone(),
1175                                                    &wasm_bytes,
1176                                                    &precompiled_bytes,
1177                                                    engine_fingerprint.clone(),
1178                                                    toolchain_fingerprint
1179                                                        .clone(),
1180                                                )?;
1181
1182                                            register
1183                                                .tell(
1184                                                    ContractRegisterMessage::SetMetadata {
1185                                                        contract_name: contract_name
1186                                                            .to_owned(),
1187                                                        metadata: refreshed_record
1188                                                            .clone(),
1189                                                    },
1190                                                )
1191                                                .await
1192                                                .map_err(|e| {
1193                                                    CompilerError::ContractRegisterFailed {
1194                                                        details: e.to_string(),
1195                                                    }
1196                                                })?;
1197
1198                                            return Ok((
1199                                                Arc::new(module),
1200                                                refreshed_record,
1201                                                "wasm_hit",
1202                                            ));
1203                                        }
1204                                        Err(error) => {
1205                                            debug!(
1206                                                error = %error,
1207                                                path = %contract_path.display(),
1208                                                "Persisted wasm artifact is invalid, recompiling"
1209                                            );
1210                                        }
1211                                    }
1212                                }
1213                                Err(error) => {
1214                                    debug!(
1215                                        error = %error,
1216                                        path = %contract_path.display(),
1217                                        "Persisted wasm artifact can not be precompiled, recompiling"
1218                                    );
1219                                }
1220                            }
1221                        } else {
1222                            debug!(
1223                                expected = %persisted.wasm_hash,
1224                                actual = %wasm_hash,
1225                                path = %contract_path.display(),
1226                                "Persisted wasm artifact hash mismatch, recompiling"
1227                            );
1228                        }
1229                    }
1230                    Err(error) => {
1231                        debug!(
1232                            error = %error,
1233                            path = %contract_path.display(),
1234                            "Persisted contract artifact can not be read, recompiling"
1235                        );
1236                    }
1237                }
1238            }
1239
1240            #[cfg(feature = "test")]
1241            let cache_dir = Self::global_cache_entry_dir(
1242                &contract_hash,
1243                &manifest_hash,
1244                &engine_fingerprint,
1245                &toolchain_fingerprint,
1246            );
1247
1248            #[cfg(feature = "test")]
1249            if let Some((
1250                module,
1251                metadata,
1252                prepare_result,
1253                wasm_bytes,
1254                precompiled_bytes,
1255            )) = Self::try_load_global_cache(
1256                hash,
1257                ctx,
1258                initial_value.clone(),
1259                &contract_hash,
1260                &manifest_hash,
1261                &engine_fingerprint,
1262                &toolchain_fingerprint,
1263            )
1264            .await?
1265            {
1266                Self::persist_artifact(
1267                    contract_path,
1268                    &wasm_bytes,
1269                    &precompiled_bytes,
1270                )
1271                .await?;
1272
1273                register
1274                    .tell(ContractRegisterMessage::SetMetadata {
1275                        contract_name: contract_name.to_owned(),
1276                        metadata: metadata.clone(),
1277                    })
1278                    .await
1279                    .map_err(|e| CompilerError::ContractRegisterFailed {
1280                        details: e.to_string(),
1281                    })?;
1282
1283                return Ok((module, metadata, prepare_result));
1284            }
1285
1286            #[cfg(feature = "test")]
1287            let global_cache_lock =
1288                match Self::try_acquire_global_cache_lock(&cache_dir).await? {
1289                    Some(lock) => Some(lock),
1290                    None => {
1291                        if let Some((
1292                            module,
1293                            metadata,
1294                            prepare_result,
1295                            wasm_bytes,
1296                            precompiled_bytes,
1297                        )) = Self::wait_for_global_cache(
1298                            hash,
1299                            ctx,
1300                            initial_value.clone(),
1301                            &contract_hash,
1302                            &manifest_hash,
1303                            &engine_fingerprint,
1304                            &toolchain_fingerprint,
1305                        )
1306                        .await?
1307                        {
1308                            Self::persist_artifact(
1309                                contract_path,
1310                                &wasm_bytes,
1311                                &precompiled_bytes,
1312                            )
1313                            .await?;
1314
1315                            register
1316                                .tell(ContractRegisterMessage::SetMetadata {
1317                                    contract_name: contract_name.to_owned(),
1318                                    metadata: metadata.clone(),
1319                                })
1320                                .await
1321                                .map_err(|e| {
1322                                    CompilerError::ContractRegisterFailed {
1323                                        details: e.to_string(),
1324                                    }
1325                                })?;
1326
1327                            return Ok((module, metadata, prepare_result));
1328                        }
1329
1330                        Self::try_acquire_global_cache_lock(&cache_dir).await?
1331                    }
1332                };
1333
1334            let (module, metadata) = Self::compile_fresh(
1335                hash,
1336                ctx,
1337                contract,
1338                contract_path,
1339                initial_value,
1340            )
1341            .await?;
1342
1343            #[cfg(feature = "test")]
1344            drop(global_cache_lock);
1345
1346            register
1347                .tell(ContractRegisterMessage::SetMetadata {
1348                    contract_name: contract_name.to_owned(),
1349                    metadata: metadata.clone(),
1350                })
1351                .await
1352                .map_err(|e| CompilerError::ContractRegisterFailed {
1353                    details: e.to_string(),
1354                })?;
1355
1356            Ok((module, metadata, "recompiled"))
1357        }
1358        .await;
1359
1360        match result {
1361            Ok((module, metadata, prepare_result)) => {
1362                Self::observe_contract_prepare(
1363                    "registered",
1364                    prepare_result,
1365                    started_at,
1366                );
1367                Ok((module, metadata))
1368            }
1369            Err(error) => {
1370                Self::observe_contract_prepare(
1371                    "registered",
1372                    "error",
1373                    started_at,
1374                );
1375                Err(error)
1376            }
1377        }
1378    }
1379
1380    fn check_result(
1381        store: &Store<MemoryManager>,
1382        pointer: u32,
1383    ) -> Result<(), CompilerError> {
1384        let bytes = store.data().read_data(pointer as usize)?;
1385        let contract_result: ContractResult =
1386            BorshDeserialize::try_from_slice(bytes).map_err(|e| {
1387                CompilerError::InvalidContractOutput {
1388                    details: e.to_string(),
1389                }
1390            })?;
1391
1392        if contract_result.success {
1393            Ok(())
1394        } else {
1395            Err(CompilerError::ContractCheckFailed {
1396                error: contract_result.error,
1397            })
1398        }
1399    }
1400
1401    fn generate_context(
1402        state: ValueWrapper,
1403        limits: &WasmLimits,
1404    ) -> Result<(MemoryManager, u32), CompilerError> {
1405        let mut context = MemoryManager::from_limits(limits);
1406        let state_bytes =
1407            to_vec(&state).map_err(|e| CompilerError::SerializationError {
1408                context: "state serialization",
1409                details: e.to_string(),
1410            })?;
1411        let state_ptr = context.add_data_raw(&state_bytes)?;
1412        Ok((context, state_ptr as u32))
1413    }
1414
1415    fn get_sdk_functions_identifier() -> HashSet<&'static str> {
1416        ["alloc", "write_byte", "pointer_len", "read_byte"]
1417            .into_iter()
1418            .collect()
1419    }
1420}
1421
1422#[cfg(feature = "test")]
1423struct GlobalCacheLock {
1424    path: PathBuf,
1425}
1426
1427#[cfg(feature = "test")]
1428impl Drop for GlobalCacheLock {
1429    fn drop(&mut self) {
1430        let _ = std::fs::remove_file(&self.path);
1431    }
1432}