pchain_runtime/execution/
internal.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//! Implementation of internal transactions such as transferring tokens from contract and invoking another contract from contract.
7
8use pchain_types::cryptography::PublicAddress;
9use pchain_world_state::storage::WorldStateStorage;
10use std::sync::{Arc, Mutex};
11
12use crate::{
13    contract::{self, FuncError},
14    cost::CostChange,
15    transition::TransitionContext,
16    types::CallTx,
17    BlockchainParams,
18};
19
20use super::contract::ContractModule;
21
22#[derive(Default)]
23pub(crate) struct InternalCallResult {
24    pub exec_gas: u64,
25    pub non_wasmer_gas: CostChange,
26    pub error: Option<FuncError>,
27}
28
29/// Execution logics for invoking another contract from a contract
30pub(crate) fn call_from_contract<S>(
31    mut tx_from_contract: CallTx,
32    bd: BlockchainParams,
33    txn_in_env: Arc<Mutex<TransitionContext<S>>>,
34    call_counter: u32,
35    is_view: bool,
36) -> InternalCallResult
37where
38    S: WorldStateStorage + Send + Sync + Clone + 'static,
39{
40    let mut ctx_locked = txn_in_env.lock().unwrap();
41    let mut internal_call_result = InternalCallResult::default();
42
43    // Transfer amount to address
44    if let Some(value) = tx_from_contract.amount {
45        let (from_balance, cost_change) = ctx_locked.balance(tx_from_contract.signer);
46        internal_call_result.non_wasmer_gas += cost_change;
47        if from_balance < value {
48            internal_call_result.error = Some(contract::FuncError::InsufficientBalance);
49            return internal_call_result;
50        }
51
52        let from_address_new_balance = from_balance - value;
53        let cost_change = ctx_locked.set_balance(tx_from_contract.signer, from_address_new_balance);
54        internal_call_result.non_wasmer_gas += cost_change;
55
56        // Safety: the balance of deployed contracts are always Some.
57        let (to_address_prev_balance, cost_change) = ctx_locked.balance(tx_from_contract.target);
58        internal_call_result.non_wasmer_gas += cost_change;
59        let to_address_new_balance = to_address_prev_balance.saturating_add(value);
60        let cost_change = ctx_locked.set_balance(tx_from_contract.target, to_address_new_balance);
61        internal_call_result.non_wasmer_gas += cost_change;
62    }
63
64    // Instantiate contract.
65    let contract_module = match ContractModule::build_contract(
66        tx_from_contract.target,
67        &ctx_locked.sc_context,
68        &ctx_locked.rw_set,
69    ) {
70        Ok(module) => module,
71        Err(_) => {
72            internal_call_result.error = Some(contract::FuncError::ContractNotFound);
73            return internal_call_result;
74        }
75    };
76    internal_call_result.non_wasmer_gas += contract_module.gas_cost;
77    drop(ctx_locked);
78
79    // limit the gas for child contract execution
80    tx_from_contract.gas_limit = tx_from_contract
81        .gas_limit
82        .saturating_sub(internal_call_result.non_wasmer_gas.values().0);
83
84    let instance = match contract_module.instantiate(
85        txn_in_env,
86        call_counter,
87        is_view,
88        tx_from_contract,
89        bd,
90    ) {
91        Ok(instance) => instance,
92        Err(_) => {
93            internal_call_result.error = Some(contract::FuncError::ContractNotFound);
94            return internal_call_result;
95        }
96    };
97
98    // Call the contract
99    let (_, gas_consumed, call_error) = instance.call();
100    internal_call_result.exec_gas = gas_consumed;
101
102    if let Some(call_error) = call_error {
103        internal_call_result.error = Some(contract::FuncError::MethodCallError(call_error));
104    }
105    internal_call_result
106}
107
108/// Execution logics for transferring tokens from a contract
109pub(crate) fn transfer_from_contract<S>(
110    signer: PublicAddress,
111    amount: u64,
112    recipient: PublicAddress,
113    txn_in_env: Arc<Mutex<TransitionContext<S>>>,
114) -> InternalCallResult
115where
116    S: WorldStateStorage + Send + Sync + Clone,
117{
118    let mut ctx_locked = txn_in_env.lock().unwrap();
119    let mut internal_call_result = InternalCallResult::default();
120
121    // 1. Verify that the caller's balance is >= amount
122    let (from_balance, cost_change) = ctx_locked.balance(signer);
123    internal_call_result.non_wasmer_gas += cost_change;
124
125    if from_balance < amount {
126        internal_call_result.error = Some(contract::FuncError::InsufficientBalance);
127        return internal_call_result;
128    }
129
130    // 2. Debit amount from from_address.
131    let from_address_new_balance = from_balance - amount;
132    let cost_change = ctx_locked.set_balance(signer, from_address_new_balance);
133    internal_call_result.non_wasmer_gas += cost_change;
134
135    // 3. Credit amount to recipient.
136    let (to_address_prev_balance, cost_change) = ctx_locked.balance(recipient);
137    internal_call_result.non_wasmer_gas += cost_change;
138    let to_address_new_balance = to_address_prev_balance.saturating_add(amount);
139    let cost_change = ctx_locked.set_balance(recipient, to_address_new_balance);
140    internal_call_result.non_wasmer_gas += cost_change;
141
142    internal_call_result
143}