pchain_runtime/contract/
module.rs

1/*
2    Copyright © 2023, ParallelChain Lab
3    Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
4*/
5
6//! Defines a struct to use [wasmer::Module] as underlying WASM module to work with compiled contract bytecode in Parallelchain Mainnet.
7
8use pchain_types::cryptography::PublicAddress;
9
10use crate::{
11    contract::{blank, Importable},
12    wasmer::cache::ModuleMetadata,
13    Cache as SmartContractCache,
14};
15
16use super::{
17    instance::{ContractValidateError, Instance},
18    CONTRACT_METHOD,
19};
20
21/// Module is a structure representing a WebAssembly executable that has been compiled down to architecture-specific
22/// machine code in preparation for execution.
23pub(crate) struct Module(pub wasmer::Module, pub ModuleMetadata);
24
25impl Module {
26    /// from_cache returns the contract Module cached in smart contract cache.
27    pub fn from_cache(
28        address: PublicAddress,
29        cache: &SmartContractCache,
30        wasmer_store: &wasmer::Store,
31    ) -> Option<Module> {
32        match cache.load(address, wasmer_store) {
33            Ok((m, d)) => Some(Module(m, d)),
34            Err(_) => None,
35        }
36    }
37
38    /// cache_to caches contract module to smart contract cache.
39    pub fn cache_to(&self, address: PublicAddress, cache: &mut SmartContractCache) {
40        let _ = cache.store(address, &self.0, self.1.bytes_length);
41    }
42
43    /// from_wasm_bytecode returns the contract Module produced by compiling the wasm bytecode provided as an argument.
44    /// The bytecode will be validated and the process is slow. Err `ModuleBuildError::DisallowedOpcodePresent` could be returned.
45    pub fn from_wasm_bytecode(
46        cbi_version: u32,
47        bytecode: &Vec<u8>,
48        wasmer_store: &wasmer::Store,
49    ) -> Result<Module, ModuleBuildError> {
50        let wasmer_module = match wasmer::Module::from_binary(wasmer_store, bytecode) {
51            Ok(m) => m,
52            Err(e) => {
53                if e.to_string().contains("OpcodeError") {
54                    return Err(ModuleBuildError::DisallowedOpcodePresent);
55                }
56                return Err(ModuleBuildError::Else);
57            }
58        };
59
60        Ok(Module(
61            wasmer_module,
62            ModuleMetadata {
63                cbi_version,
64                bytes_length: bytecode.len(),
65            },
66        ))
67    }
68
69    /// from_wasm_bytecode_unchecked returns the contract Module produced by compiling the wasm bytecode provided as an argument.
70    /// The bytecode will NOT be validated. Use method `from_wasm_bytecode` if the bytecode should be validated. Err
71    /// `ModuleBuildError::DisallowedOpcodePresent` could be returned.
72    pub fn from_wasm_bytecode_unchecked(
73        cbi_version: u32,
74        bytecode: &Vec<u8>,
75        wasmer_store: &wasmer::Store,
76    ) -> Result<Module, ModuleBuildError> {
77        let wasmer_module =
78            match unsafe { wasmer::Module::from_binary_unchecked(wasmer_store, bytecode) } {
79                Ok(m) => m,
80                Err(e) => {
81                    if e.to_string().contains("OpcodeError") {
82                        return Err(ModuleBuildError::DisallowedOpcodePresent);
83                    }
84                    return Err(ModuleBuildError::Else);
85                }
86            };
87
88        Ok(Module(
89            wasmer_module,
90            ModuleMetadata {
91                cbi_version,
92                bytes_length: bytecode.len(),
93            },
94        ))
95    }
96
97    /// size of the wasm bytecode
98    pub fn bytes_length(&self) -> usize {
99        self.1.bytes_length
100    }
101
102    /// instantiate creates a new instance of this contract Module.
103    pub fn instantiate(
104        &self,
105        importable: &Importable,
106        gas_limit: u64,
107    ) -> Result<Instance, wasmer::InstantiationError> {
108        // instantiate wasmer::Instance
109        let wasmer_instance = wasmer::Instance::new(&self.0, &importable.0)?;
110        // Set the remaining points from metering middleware to wasmer environment
111        wasmer_middlewares::metering::set_remaining_points(&wasmer_instance, gas_limit);
112        Ok(Instance(wasmer_instance))
113    }
114
115    /// validate_contract returns whether this contract Module exports a function with the name METHOD_ACTIONS
116    /// and can be instantiated with calls() function.
117    pub fn validate_contract(
118        &self,
119        wasmer_store: &wasmer::Store,
120    ) -> Result<(), ContractValidateError> {
121        if !self
122            .0
123            .exports()
124            .functions()
125            .any(|f| f.name() == CONTRACT_METHOD)
126        {
127            return Err(ContractValidateError::MethodNotFound);
128        }
129        let imports_object = blank::imports(wasmer_store);
130        if let Ok(instance) = wasmer::Instance::new(&self.0, &imports_object) {
131            if instance
132                .exports
133                .get_native_function::<(), ()>(CONTRACT_METHOD)
134                .is_ok()
135            {
136                return Ok(());
137            }
138        }
139        Err(ContractValidateError::InstantiateError)
140    }
141}
142
143/// ModuleBuildError enumerates the possible reasons why arbitrary bytecode might fail to be interpreted as WASM and compiled
144/// down to machine code in preparation for execution.
145#[derive(Debug)]
146pub(crate) enum ModuleBuildError {
147    /// Contract contains opcodes what are not allowed.
148    DisallowedOpcodePresent,
149    /// Errors other than `DisallowedOpcodePresent`
150    Else,
151}