contract_extrinsics/
call.rs

1// Copyright (C) Use Ink (UK) Ltd.
2// This file is part of cargo-contract.
3//
4// cargo-contract is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// cargo-contract is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with cargo-contract.  If not, see <http://www.gnu.org/licenses/>.
16
17use super::{
18    pallet_contracts_primitives::ContractExecResult,
19    state_call,
20    submit_extrinsic,
21    ContractMessageTranscoder,
22    ErrorVariant,
23};
24use crate::{
25    check_env_types,
26    extrinsic_calls::Call,
27    extrinsic_opts::ExtrinsicOpts,
28};
29
30use anyhow::{
31    anyhow,
32    Result,
33};
34use ink_env::Environment;
35use scale::Encode;
36use sp_weights::Weight;
37
38use subxt::{
39    backend::{
40        legacy::LegacyRpcMethods,
41        rpc::RpcClient,
42    },
43    blocks::ExtrinsicEvents,
44    config::{
45        DefaultExtrinsicParams,
46        ExtrinsicParams,
47    },
48    ext::{
49        scale_decode::IntoVisitor,
50        scale_encode::EncodeAsType,
51    },
52    tx,
53    Config,
54    OnlineClient,
55};
56
57/// A builder for the call command.
58pub struct CallCommandBuilder<C: Config, E: Environment, Signer: Clone> {
59    contract: C::AccountId,
60    message: String,
61    args: Vec<String>,
62    extrinsic_opts: ExtrinsicOpts<C, E, Signer>,
63    gas_limit: Option<u64>,
64    proof_size: Option<u64>,
65    value: E::Balance,
66}
67
68impl<C: Config, E: Environment, Signer> CallCommandBuilder<C, E, Signer>
69where
70    E::Balance: Default,
71    Signer: tx::Signer<C> + Clone,
72{
73    /// Returns a clean builder for [`CallExec`].
74    pub fn new(
75        contract: C::AccountId,
76        message: &str,
77        extrinsic_opts: ExtrinsicOpts<C, E, Signer>,
78    ) -> CallCommandBuilder<C, E, Signer> {
79        CallCommandBuilder {
80            contract,
81            message: message.to_string(),
82            args: Vec::new(),
83            extrinsic_opts,
84            gas_limit: None,
85            proof_size: None,
86            value: Default::default(),
87        }
88    }
89
90    /// Sets the arguments of the contract message to call.
91    pub fn args<T: ToString>(self, args: Vec<T>) -> Self {
92        let mut this = self;
93        this.args = args.into_iter().map(|arg| arg.to_string()).collect();
94        this
95    }
96
97    /// Sets the maximum amount of gas to be used for this command.
98    pub fn gas_limit(self, gas_limit: Option<u64>) -> Self {
99        let mut this = self;
100        this.gas_limit = gas_limit;
101        this
102    }
103
104    /// Sets the maximum proof size for this call.
105    pub fn proof_size(self, proof_size: Option<u64>) -> Self {
106        let mut this = self;
107        this.proof_size = proof_size;
108        this
109    }
110
111    /// Sets the value to be transferred as part of the call.
112    pub fn value(self, value: E::Balance) -> Self {
113        let mut this = self;
114        this.value = value;
115        this
116    }
117
118    /// Preprocesses contract artifacts and options for subsequent contract calls.
119    ///
120    /// This function prepares the necessary data for making a contract call based on the
121    /// provided contract artifacts, message, arguments, and options. It ensures that the
122    /// required contract code and message data are available, sets up the client,
123    /// and other relevant parameters, preparing for the contract call operation.
124    ///
125    /// Returns the `CallExec` containing the preprocessed data for the contract call,
126    /// or an error in case of failure.
127    pub async fn done(self) -> Result<CallExec<C, E, Signer>> {
128        let artifacts = self.extrinsic_opts.contract_artifacts()?;
129        let transcoder = artifacts.contract_transcoder()?;
130
131        let call_data = transcoder.encode(&self.message, &self.args)?;
132        tracing::debug!("Message data: {:?}", hex::encode(&call_data));
133
134        let url = self.extrinsic_opts.url();
135        let rpc = RpcClient::from_url(&url).await?;
136        let client = OnlineClient::from_rpc_client(rpc.clone()).await?;
137        let rpc = LegacyRpcMethods::new(rpc);
138        check_env_types(&client, &transcoder, self.extrinsic_opts.verbosity())?;
139
140        Ok(CallExec {
141            contract: self.contract,
142            message: self.message.clone(),
143            args: self.args.clone(),
144            opts: self.extrinsic_opts,
145            gas_limit: self.gas_limit,
146            proof_size: self.proof_size,
147            value: self.value,
148            rpc,
149            client,
150            transcoder,
151            call_data,
152        })
153    }
154}
155
156pub struct CallExec<C: Config, E: Environment, Signer: Clone> {
157    contract: C::AccountId,
158    message: String,
159    args: Vec<String>,
160    opts: ExtrinsicOpts<C, E, Signer>,
161    gas_limit: Option<u64>,
162    proof_size: Option<u64>,
163    value: E::Balance,
164    rpc: LegacyRpcMethods<C>,
165    client: OnlineClient<C>,
166    transcoder: ContractMessageTranscoder,
167    call_data: Vec<u8>,
168}
169
170impl<C: Config, E: Environment, Signer> CallExec<C, E, Signer>
171where
172    <C::ExtrinsicParams as ExtrinsicParams<C>>::Params:
173        From<<DefaultExtrinsicParams<C> as ExtrinsicParams<C>>::Params>,
174    C::AccountId: EncodeAsType + IntoVisitor,
175    E::Balance: EncodeAsType,
176    Signer: tx::Signer<C> + Clone,
177{
178    /// Simulates a contract call without modifying the blockchain.
179    ///
180    /// This function performs a dry run simulation of a contract call, capturing
181    /// essential information such as the contract address, gas consumption, and
182    /// storage deposit. The simulation is executed without actually executing the
183    /// call on the blockchain.
184    ///
185    /// Returns the dry run simulation result of type [`ContractExecResult`], which
186    /// includes information about the simulated call, or an error in case of failure.
187    pub async fn call_dry_run(&self) -> Result<ContractExecResult<E::Balance>> {
188        let storage_deposit_limit = self.opts.storage_deposit_limit();
189        let call_request = CallRequest {
190            origin: self.opts.signer().account_id(),
191            dest: self.contract.clone(),
192            value: self.value,
193            gas_limit: None,
194            storage_deposit_limit,
195            input_data: self.call_data.clone(),
196        };
197        state_call(&self.rpc, "ContractsApi_call", call_request).await
198    }
199
200    /// Calls a contract on the blockchain with a specified gas limit.
201    ///
202    /// This function facilitates the process of invoking a contract, specifying the gas
203    /// limit for the operation. It interacts with the blockchain's runtime API to
204    /// execute the contract call and provides the resulting events from the call.
205    ///
206    /// Returns the events generated from the contract call, or an error in case of
207    /// failure.
208    pub async fn call(
209        &self,
210        gas_limit: Option<Weight>,
211    ) -> Result<ExtrinsicEvents<C>, ErrorVariant> {
212        if !self
213            .transcoder()
214            .metadata()
215            .spec()
216            .messages()
217            .iter()
218            .find(|msg| msg.label() == &self.message)
219            .expect("message exist after calling CallExec::done()")
220            .mutates()
221        {
222            let inner = anyhow!(
223                "Tried to execute a call on the immutable contract message '{}'. Please do a dry-run instead.",
224                &self.message
225            );
226            return Err(inner.into())
227        }
228
229        // use user specified values where provided, otherwise estimate
230        let gas_limit = match gas_limit {
231            Some(gas_limit) => gas_limit,
232            None => self.estimate_gas().await?,
233        };
234        tracing::debug!("calling contract {:?}", self.contract);
235        let storage_deposit_limit = self.opts.storage_deposit_limit();
236
237        let call = Call::new(
238            self.contract.clone().into(),
239            self.value,
240            gas_limit,
241            storage_deposit_limit,
242            self.call_data.clone(),
243        )
244        .build();
245
246        let result =
247            submit_extrinsic(&self.client, &self.rpc, &call, self.opts.signer()).await?;
248
249        Ok(result)
250    }
251
252    /// Estimates the gas required for a contract call without modifying the blockchain.
253    ///
254    /// This function provides a gas estimation for contract calls, considering the
255    /// user-specified values or using estimates based on a dry run. The estimated gas
256    /// weight is returned, or an error is reported if the estimation fails.
257    ///
258    /// Returns the estimated gas weight of type [`Weight`] for contract calls, or an
259    /// error.
260    pub async fn estimate_gas(&self) -> Result<Weight> {
261        match (self.gas_limit, self.proof_size) {
262            (Some(ref_time), Some(proof_size)) => {
263                Ok(Weight::from_parts(ref_time, proof_size))
264            }
265            _ => {
266                let call_result = self.call_dry_run().await?;
267                match call_result.result {
268                    Ok(_) => {
269                        // use user specified values where provided, otherwise use the
270                        // estimates
271                        let ref_time = self
272                            .gas_limit
273                            .unwrap_or_else(|| call_result.gas_required.ref_time());
274                        let proof_size = self
275                            .proof_size
276                            .unwrap_or_else(|| call_result.gas_required.proof_size());
277                        Ok(Weight::from_parts(ref_time, proof_size))
278                    }
279                    Err(ref err) => {
280                        let object = ErrorVariant::from_dispatch_error(
281                            err,
282                            &self.client.metadata(),
283                        )?;
284                        Err(anyhow!("Pre-submission dry-run failed. Error: {}", object))
285                    }
286                }
287            }
288        }
289    }
290
291    /// Returns the address of the the contract to call.
292    pub fn contract(&self) -> &C::AccountId {
293        &self.contract
294    }
295
296    /// Returns the name of the contract message to call.
297    pub fn message(&self) -> &str {
298        &self.message
299    }
300
301    /// Returns the arguments of the contract message to call.
302    pub fn args(&self) -> &Vec<String> {
303        &self.args
304    }
305
306    /// Returns the extrinsic options.
307    pub fn opts(&self) -> &ExtrinsicOpts<C, E, Signer> {
308        &self.opts
309    }
310
311    /// Returns the maximum amount of gas to be used for this command.
312    pub fn gas_limit(&self) -> Option<u64> {
313        self.gas_limit
314    }
315
316    /// Returns the maximum proof size for this call.
317    pub fn proof_size(&self) -> Option<u64> {
318        self.proof_size
319    }
320
321    /// Returns the value to be transferred as part of the call.
322    pub fn value(&self) -> &E::Balance {
323        &self.value
324    }
325
326    /// Returns the client.
327    pub fn client(&self) -> &OnlineClient<C> {
328        &self.client
329    }
330
331    /// Returns the contract message transcoder.
332    pub fn transcoder(&self) -> &ContractMessageTranscoder {
333        &self.transcoder
334    }
335
336    /// Returns the call data.
337    pub fn call_data(&self) -> &Vec<u8> {
338        &self.call_data
339    }
340}
341
342/// A struct that encodes RPC parameters required for a call to a smart contract.
343///
344/// Copied from `pallet-contracts-rpc-runtime-api`.
345#[derive(Encode)]
346struct CallRequest<AccountId, Balance> {
347    origin: AccountId,
348    dest: AccountId,
349    value: Balance,
350    gas_limit: Option<Weight>,
351    storage_deposit_limit: Option<Balance>,
352    input_data: Vec<u8>,
353}