Skip to main content

ave_core/evaluation/compiler/
mod.rs

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>(&register_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}