fuels_contract/
script_calls.rs

1use crate::{
2    abi_encoder::UnresolvedBytes,
3    call_response::FuelCallResponse,
4    contract::get_decoded_output,
5    contract_calls_utils::get_base_script_offset,
6    execution_script::ExecutableFuelCall,
7    logs::{decode_revert_error, LogDecoder},
8};
9use fuel_gql_client::{
10    fuel_tx::{Output, Receipt, Transaction},
11    fuel_types::bytes::padded_len_usize,
12};
13use fuel_tx::Input;
14use fuels_core::{
15    parameters::{CallParameters, TxParameters},
16    Tokenizable,
17};
18use fuels_signers::{provider::Provider, WalletUnlocked};
19use fuels_types::{errors::Error, param_types::ParamType};
20use std::{fmt::Debug, marker::PhantomData};
21
22#[derive(Debug)]
23/// Contains all data relevant to a single script call
24pub struct ScriptCall {
25    pub script_binary: Vec<u8>,
26    pub encoded_args: UnresolvedBytes,
27    pub inputs: Vec<Input>,
28    pub outputs: Vec<Output>,
29    // This field is not currently used but it will be in the future.
30    pub call_parameters: CallParameters,
31}
32
33impl ScriptCall {
34    pub fn with_outputs(mut self, outputs: Vec<Output>) -> Self {
35        self.outputs = outputs;
36        self
37    }
38
39    pub fn with_inputs(mut self, inputs: Vec<Input>) -> Self {
40        self.inputs = inputs;
41        self
42    }
43}
44
45#[derive(Debug)]
46#[must_use = "script calls do nothing unless you `call` them"]
47/// Helper that handles submitting a script call to a client and formatting the response
48pub struct ScriptCallHandler<D> {
49    pub script_call: ScriptCall,
50    pub tx_parameters: TxParameters,
51    pub wallet: WalletUnlocked,
52    pub provider: Provider,
53    pub output_param: ParamType,
54    pub datatype: PhantomData<D>,
55    pub log_decoder: LogDecoder,
56}
57
58impl<D> ScriptCallHandler<D>
59where
60    D: Tokenizable + Debug,
61{
62    pub fn new(
63        script_binary: Vec<u8>,
64        encoded_args: UnresolvedBytes,
65        wallet: WalletUnlocked,
66        provider: Provider,
67        output_param: ParamType,
68        log_decoder: LogDecoder,
69    ) -> Self {
70        let script_call = ScriptCall {
71            script_binary,
72            encoded_args,
73            inputs: vec![],
74            outputs: vec![],
75            call_parameters: Default::default(),
76        };
77        Self {
78            script_call,
79            tx_parameters: TxParameters::default(),
80            wallet,
81            provider,
82            output_param,
83            datatype: PhantomData,
84            log_decoder,
85        }
86    }
87
88    /// Sets the transaction parameters for a given transaction.
89    /// Note that this is a builder method, i.e. use it as a chain:
90    ///
91    /// ```ignore
92    /// let params = TxParameters { gas_price: 100, gas_limit: 1000000 };
93    /// instance.main(...).tx_params(params).call()
94    /// ```
95    pub fn tx_params(mut self, params: TxParameters) -> Self {
96        self.tx_parameters = params;
97        self
98    }
99
100    pub fn with_outputs(mut self, outputs: Vec<Output>) -> Self {
101        self.script_call = self.script_call.with_outputs(outputs);
102        self
103    }
104
105    pub fn with_inputs(mut self, inputs: Vec<Input>) -> Self {
106        self.script_call = self.script_call.with_inputs(inputs);
107        self
108    }
109
110    /// Compute the script data by calculating the script offset and resolving the encoded arguments
111    async fn compute_script_data(&self) -> Result<Vec<u8>, Error> {
112        let consensus_parameters = self.provider.consensus_parameters().await?;
113        let script_offset = get_base_script_offset(&consensus_parameters)
114            + padded_len_usize(self.script_call.script_binary.len());
115
116        Ok(self.script_call.encoded_args.resolve(script_offset as u64))
117    }
118
119    /// Call a script on the node. If `simulate == true`, then the call is done in a
120    /// read-only manner, using a `dry-run`. The [`FuelCallResponse`] struct contains the `main`'s value
121    /// in its `value` field as an actual typed value `D` (if your method returns `bool`,
122    /// it will be a bool, works also for structs thanks to the `abigen!()`).
123    /// The other field of [`FuelCallResponse`], `receipts`, contains the receipts of the transaction.
124    async fn call_or_simulate(&self, simulate: bool) -> Result<FuelCallResponse<D>, Error> {
125        let mut tx = Transaction::script(
126            self.tx_parameters.gas_price,
127            self.tx_parameters.gas_limit,
128            self.tx_parameters.maturity,
129            self.script_call.script_binary.clone(),
130            self.compute_script_data().await?,
131            self.script_call.inputs.clone(), // TODO(iqdecay): allow user to set inputs field
132            self.script_call.outputs.clone(), // TODO(iqdecay): allow user to set outputs field
133            vec![vec![0, 0].into()], //TODO(iqdecay): figure out how to have the right witnesses
134        );
135        self.wallet.add_fee_coins(&mut tx, 0, 0).await?;
136
137        let tx_execution = ExecutableFuelCall { tx };
138
139        let receipts = if simulate {
140            tx_execution.simulate(&self.provider).await?
141        } else {
142            tx_execution.execute(&self.provider).await?
143        };
144
145        self.get_response(receipts)
146    }
147
148    /// Call a script on the node, in a state-modifying manner.
149    pub async fn call(self) -> Result<FuelCallResponse<D>, Error> {
150        Self::call_or_simulate(&self, false)
151            .await
152            .map_err(|err| decode_revert_error(err, &self.log_decoder))
153    }
154
155    /// Call a script on the node, in a simulated manner, meaning the state of the
156    /// blockchain is *not* modified but simulated.
157    /// It is the same as the [`call`] method because the API is more user-friendly this way.
158    ///
159    /// [`call`]: Self::call
160    pub async fn simulate(self) -> Result<FuelCallResponse<D>, Error> {
161        Self::call_or_simulate(&self, true)
162            .await
163            .map_err(|err| decode_revert_error(err, &self.log_decoder))
164    }
165
166    /// Create a [`FuelCallResponse`] from call receipts
167    pub fn get_response(&self, mut receipts: Vec<Receipt>) -> Result<FuelCallResponse<D>, Error> {
168        let token = get_decoded_output(&mut receipts, None, &self.output_param)?;
169        Ok(FuelCallResponse::new(
170            D::from_token(token)?,
171            receipts,
172            self.log_decoder.clone(),
173        ))
174    }
175}