fuels_contract/
execution_script.rs

1use anyhow::Result;
2use std::fmt::Debug;
3
4use fuel_gql_client::fuel_tx::{Receipt, Transaction};
5
6use fuel_tx::{AssetId, Checkable, ScriptExecutionResult};
7use fuels_core::parameters::TxParameters;
8use fuels_signers::provider::Provider;
9use fuels_signers::{Signer, WalletUnlocked};
10
11use fuels_types::errors::Error;
12
13use std::vec;
14
15use crate::contract::ContractCall;
16use crate::contract_calls_utils::{
17    build_script_data_from_contract_calls, calculate_required_asset_amounts, get_data_offset,
18    get_instructions, get_transaction_inputs_outputs,
19};
20
21/// [`TransactionExecution`] provides methods to create and call/simulate a transaction that carries
22/// out contract method calls or script calls
23#[derive(Debug)]
24pub struct ExecutableFuelCall {
25    pub tx: fuels_core::tx::Script,
26}
27
28impl ExecutableFuelCall {
29    pub fn new(tx: fuels_core::tx::Script) -> Self {
30        Self { tx }
31    }
32
33    /// Creates a [`TransactionExecution`] from contract calls. The internal [`Transaction`] is
34    /// initialized with the actual script instructions, script data needed to perform the call and
35    /// transaction inputs/outputs consisting of assets and contracts.
36    pub async fn from_contract_calls(
37        calls: &[ContractCall],
38        tx_parameters: &TxParameters,
39        wallet: &WalletUnlocked,
40    ) -> Result<Self, Error> {
41        let consensus_parameters = wallet.get_provider()?.consensus_parameters().await?;
42        let data_offset = get_data_offset(&consensus_parameters, calls.len());
43
44        let (script_data, call_param_offsets) =
45            build_script_data_from_contract_calls(calls, data_offset, tx_parameters.gas_limit);
46
47        let script = get_instructions(calls, call_param_offsets);
48
49        let required_asset_amounts = calculate_required_asset_amounts(calls);
50        let mut spendable_resources = vec![];
51
52        // Find the spendable resources required for those calls
53        for (asset_id, amount) in &required_asset_amounts {
54            let resources = wallet.get_spendable_resources(*asset_id, *amount).await?;
55            spendable_resources.extend(resources);
56        }
57
58        let (inputs, outputs) =
59            get_transaction_inputs_outputs(calls, wallet.address(), spendable_resources);
60
61        let mut tx = Transaction::script(
62            tx_parameters.gas_price,
63            tx_parameters.gas_limit,
64            tx_parameters.maturity,
65            script,
66            script_data,
67            inputs,
68            outputs,
69            vec![],
70        );
71
72        let base_asset_amount = required_asset_amounts
73            .iter()
74            .find(|(asset_id, _)| *asset_id == AssetId::default());
75        match base_asset_amount {
76            Some((_, base_amount)) => wallet.add_fee_coins(&mut tx, *base_amount, 0).await?,
77            None => wallet.add_fee_coins(&mut tx, 0, 0).await?,
78        }
79        wallet.sign_transaction(&mut tx).await.unwrap();
80
81        Ok(ExecutableFuelCall::new(tx))
82    }
83
84    /// Execute the transaction in a state-modifying manner.
85    pub async fn execute(&self, provider: &Provider) -> Result<Vec<Receipt>, Error> {
86        let chain_info = provider.chain_info().await?;
87
88        self.tx.check_without_signatures(
89            chain_info.latest_block.header.height,
90            &chain_info.consensus_parameters,
91        )?;
92
93        provider.send_transaction(&self.tx).await
94    }
95
96    /// Execute the transaction in a simulated manner, not modifying blockchain state
97    pub async fn simulate(&self, provider: &Provider) -> Result<Vec<Receipt>, Error> {
98        let chain_info = provider.chain_info().await?;
99
100        self.tx.check_without_signatures(
101            chain_info.latest_block.header.height,
102            &chain_info.consensus_parameters,
103        )?;
104
105        let receipts = provider.dry_run(&self.tx.clone().into()).await?;
106        if receipts
107            .iter()
108            .any(|r|
109                matches!(r, Receipt::ScriptResult { result, .. } if *result != ScriptExecutionResult::Success)
110        ) {
111            return Err(Error::RevertTransactionError(Default::default(), receipts));
112        }
113
114        Ok(receipts)
115    }
116}