pchain_runtime/execution/
contract.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 structs for contract instantiation and contract call which are used in executing Commands Phase.
7
8use std::sync::{Arc, Mutex};
9
10use pchain_types::cryptography::PublicAddress;
11use pchain_world_state::storage::WorldStateStorage;
12use wasmer::Store;
13
14use crate::{
15    contract::{
16        self, ContractBinaryFunctions, ContractValidateError, MethodCallError, ModuleBuildError,
17        SmartContractContext,
18    },
19    cost::CostChange,
20    read_write_set::ReadWriteSet,
21    transition::TransitionContext,
22    types::CallTx,
23    wasmer::{wasmer_env, wasmer_store},
24    BlockchainParams, Cache,
25};
26
27/// ContractModule stores the intermediate data related to Contract in Commands Phase.
28pub(crate) struct ContractModule {
29    store: Store,
30    module: contract::Module,
31    /// Gas cost for getting contract code
32    pub gas_cost: CostChange,
33}
34
35impl ContractModule {
36    pub(crate) fn new(
37        contract_code: &Vec<u8>,
38        memory_limit: Option<usize>,
39    ) -> Result<Self, ModuleBuildError> {
40        let wasmer_store = wasmer_store::instantiate_store(u64::MAX, memory_limit);
41        // Load the contract module from raw bytes here because it is not expected to save into sc_cache at this point of time.
42        let module = contract::Module::from_wasm_bytecode(
43            contract::CBI_VERSION,
44            contract_code,
45            &wasmer_store,
46        )?;
47
48        Ok(Self {
49            store: wasmer_store,
50            module,
51            gas_cost: CostChange::default(),
52        })
53    }
54
55    pub(crate) fn build_contract<S>(
56        contract_address: PublicAddress,
57        sc_ctx: &SmartContractContext,
58        rw_set: &ReadWriteSet<S>,
59    ) -> Result<Self, ()>
60    where
61        S: WorldStateStorage + Send + Sync + Clone + 'static,
62    {
63        let (module, store, gas_cost) = {
64            let (result, gas_cost) = rw_set.code_from_sc_cache(contract_address, sc_ctx);
65            match result {
66                Some((module, store)) => (module, store, gas_cost),
67                None => return Err(()),
68            }
69        };
70
71        Ok(Self {
72            store,
73            module,
74            gas_cost,
75        })
76    }
77
78    pub(crate) fn validate(&self) -> Result<(), ContractValidateError> {
79        self.module.validate_contract(&self.store)
80    }
81
82    pub(crate) fn cache(&self, contract_address: PublicAddress, cache: &mut Cache) {
83        self.module.cache_to(contract_address, cache)
84    }
85
86    pub(crate) fn instantiate<S>(
87        self,
88        ctx: Arc<Mutex<TransitionContext<S>>>,
89        call_counter: u32,
90        is_view: bool,
91        tx: CallTx,
92        bd: BlockchainParams,
93    ) -> Result<ContractInstance<S>, ()>
94    where
95        S: WorldStateStorage + Send + Sync + Clone + 'static,
96    {
97        let gas_limit = tx.gas_limit;
98        let environment = wasmer_env::Env::new(ctx, call_counter, is_view, tx, bd);
99
100        let importable = if is_view {
101            contract::create_importable_view::<wasmer_env::Env<S>, ContractBinaryFunctions>(
102                &self.store,
103                &environment,
104            )
105        } else {
106            contract::create_importable::<wasmer_env::Env<S>, ContractBinaryFunctions>(
107                &self.store,
108                &environment,
109            )
110        };
111
112        let instance = self
113            .module
114            .instantiate(&importable, gas_limit)
115            .map_err(|_| ())?;
116
117        Ok(ContractInstance {
118            environment,
119            instance,
120        })
121    }
122}
123
124/// ContractInstance contains contract instance which is prepared to be called in Commands Phase.
125pub(crate) struct ContractInstance<S>
126where
127    S: WorldStateStorage + Send + Sync + Clone + 'static,
128{
129    environment: wasmer_env::Env<S>,
130    instance: contract::Instance,
131}
132
133impl<S> ContractInstance<S>
134where
135    S: WorldStateStorage + Send + Sync + Clone + 'static,
136{
137    pub(crate) fn call(self) -> (TransitionContext<S>, u64, Option<MethodCallError>) {
138        // initialize the variable of wasmer remaining gas
139        self.environment
140            .init_wasmer_remaining_points(self.instance.remaining_points());
141
142        // Invoke Wasm Execution
143        let call_result = unsafe { self.instance.call_method() };
144
145        let non_wasmer_gas_amount = self.environment.get_non_wasm_gas_amount();
146
147        // drop the variable of wasmer remaining gas
148        self.environment.drop_wasmer_remaining_points();
149
150        let (remaining_gas, call_error) = match call_result {
151            Ok(remaining_gas) => (remaining_gas, None),
152            Err((remaining_gas, call_error)) => (remaining_gas, Some(call_error)),
153        };
154
155        let total_gas = self
156            .environment
157            .call_tx
158            .gas_limit
159            .saturating_sub(remaining_gas)
160            .saturating_sub(non_wasmer_gas_amount); // add back the non_wasmer gas because it is already accounted in read write set.
161
162        // Get the updated TransitionContext
163        let ctx = self.environment.context.lock().unwrap().clone();
164        (ctx, total_gas, call_error)
165    }
166}