concordium_rust_sdk/
contract_client.rs

1//! This module contains a generic client that provides conveniences for
2//! interacting with any smart contract instance, as well as for creating new
3//! ones.
4//!
5//! The key types in this module are
6//! [`ContractClient`],
7//! [`ContractInitBuilder`]
8//! and [`ModuleDeployBuilder`].
9use crate::{
10    indexer::ContractUpdateInfo,
11    types::{
12        smart_contracts::{self, ContractContext, InvokeContractResult, ReturnValue},
13        transactions, AccountTransactionEffects, ContractInitializedEvent, RejectReason,
14    },
15    v2::{
16        self,
17        dry_run::{self, DryRunTransaction},
18        BlockIdentifier, Client, Upward,
19    },
20};
21use concordium_base::{
22    base::{Energy, Nonce},
23    common::types::{self, TransactionTime},
24    contracts_common::{
25        self, schema::VersionedModuleSchema, AccountAddress, Address, Amount, ContractAddress,
26        Cursor, NewContractNameError, NewReceiveNameError,
27    },
28    hashes::TransactionHash,
29    smart_contracts::{
30        ContractEvent, ContractTraceElement, ExceedsParameterSize, ModuleReference,
31        OwnedContractName, OwnedParameter, OwnedReceiveName, WasmModule, WasmVersion,
32    },
33    transactions::{
34        construct::TRANSACTION_HEADER_SIZE, AccountTransaction, EncodedPayload,
35        InitContractPayload, PayloadLike, UpdateContractPayload,
36    },
37};
38pub use concordium_base::{cis2_types::MetadataUrl, cis4_types::*};
39use concordium_smart_contract_engine::utils;
40use serde_json::Value;
41use std::{fmt, marker::PhantomData, sync::Arc};
42use v2::{QueryError, RPCError};
43
44/// A contract client that handles some of the boilerplate such as serialization
45/// and parsing of responses when sending transactions, or invoking smart
46/// contracts.
47///
48/// Note that cloning is cheap and is, therefore, the intended way of sharing
49/// values of this type between multiple tasks.
50#[derive(Debug)]
51pub struct ContractClient<Type> {
52    /// The underlying network client.
53    pub client: Client,
54    /// The address of the instance.
55    pub address: ContractAddress,
56    /// The name of the contract at the address.
57    pub contract_name: Arc<contracts_common::OwnedContractName>,
58    /// The schema of the contract at the address.
59    pub schema: Arc<Option<VersionedModuleSchema>>,
60    phantom: PhantomData<Type>,
61}
62
63impl<Type> Clone for ContractClient<Type> {
64    fn clone(&self) -> Self {
65        Self {
66            client: self.client.clone(),
67            address: self.address,
68            contract_name: self.contract_name.clone(),
69            phantom: PhantomData,
70            schema: self.schema.clone(),
71        }
72    }
73}
74
75/// Transaction metadata for CIS-4 update transactions.
76#[derive(Debug, Clone, Copy)]
77pub struct ContractTransactionMetadata {
78    /// The account address sending the transaction.
79    pub sender_address: AccountAddress,
80    /// The nonce to use for the transaction.
81    pub nonce: Nonce,
82    /// Expiry date of the transaction.
83    pub expiry: types::TransactionTime,
84    /// The limit on energy to use for the transaction.
85    pub energy: transactions::send::GivenEnergy,
86    /// The amount of CCD to include in the transaction.
87    pub amount: types::Amount,
88}
89
90#[derive(Debug, thiserror::Error)]
91/// An error that can be used as the error for the
92/// [`view`](ContractClient::view) family of functions.
93pub enum ViewError {
94    #[error("Invalid receive name: {0}")]
95    InvalidName(#[from] NewReceiveNameError),
96    #[error("Node rejected with reason: {0:#?}")]
97    QueryFailed(v2::Upward<RejectReason>),
98    #[error("Response was not as expected: {0}")]
99    InvalidResponse(#[from] contracts_common::ParseError),
100    #[error("Network error: {0}")]
101    NetworkError(#[from] v2::QueryError),
102    #[error("Parameter is too large: {0}")]
103    ParameterError(#[from] ExceedsParameterSize),
104}
105
106impl From<v2::Upward<RejectReason>> for ViewError {
107    fn from(value: v2::Upward<RejectReason>) -> Self {
108        Self::QueryFailed(value)
109    }
110}
111
112/// A builder of transactions out of minimal data typically obtained by
113/// dry-running.
114///
115/// The concrete instances of this type, [`ContractInitBuilder`] and
116/// [`ContractUpdateBuilder`] have more detailed information on usage.
117///
118/// The `ADD_ENERGY` constant is used to indicate whether the builder should
119/// allow adding extra energy. This is only useful for transactions that have
120/// dynamic cost, namely contract initializations and updates.
121pub struct TransactionBuilder<const ADD_ENERGY: bool, Inner> {
122    client: v2::Client,
123    sender: AccountAddress,
124    energy: Energy,
125    add_energy: Option<Energy>,
126    expiry: Option<TransactionTime>,
127    nonce: Option<Nonce>,
128    payload: transactions::Payload,
129    inner: Inner,
130}
131
132impl<const ADD: bool, Inner> TransactionBuilder<ADD, Inner> {
133    fn new(
134        client: v2::Client,
135        sender: AccountAddress,
136        energy: Energy,
137        payload: transactions::Payload,
138        inner: Inner,
139    ) -> Self {
140        Self {
141            client,
142            sender,
143            energy,
144            add_energy: None,
145            expiry: None,
146            nonce: None,
147            payload,
148            inner,
149        }
150    }
151
152    /// Set the expiry time for the transaction. If not set the default is one
153    /// hour from the time the transaction is signed.
154    pub fn expiry(mut self, expiry: TransactionTime) -> Self {
155        self.expiry = Some(expiry);
156        self
157    }
158
159    /// Set the nonce for the transaction. If not set the default behaviour is
160    /// to get the nonce from the connected [`Client`] at the
161    /// time the transaction is sent.
162    pub fn nonce(mut self, nonce: Nonce) -> Self {
163        self.nonce = Some(nonce);
164        self
165    }
166
167    /// Return the amount of [`Energy`] that will be allowed for the transaction
168    /// if the transaction was sent with the current parameters.
169    pub fn current_energy(&self) -> Energy {
170        if ADD {
171            // Add 10% to the call, or at least 50.
172            self.energy
173                + self
174                    .add_energy
175                    .unwrap_or_else(|| std::cmp::max(50, self.energy.energy / 10).into())
176        } else {
177            self.energy
178        }
179    }
180
181    /// Send the transaction and return a handle that can be queried
182    /// for the status.
183    pub async fn send_inner<A>(
184        mut self,
185        signer: &impl transactions::ExactSizeTransactionSigner,
186        k: impl FnOnce(TransactionHash, v2::Client) -> A,
187    ) -> v2::QueryResult<A> {
188        let nonce = if let Some(nonce) = self.nonce {
189            nonce
190        } else {
191            self.client
192                .get_next_account_sequence_number(&self.sender)
193                .await?
194                .nonce
195        };
196        let expiry = self
197            .expiry
198            .unwrap_or_else(|| TransactionTime::hours_after(1));
199        let energy = self.current_energy();
200        let tx = transactions::send::make_and_sign_transaction(
201            signer,
202            self.sender,
203            nonce,
204            expiry,
205            transactions::send::GivenEnergy::Add(energy),
206            self.payload,
207        );
208        let tx_hash = self.client.send_account_transaction(tx).await?;
209        Ok(k(tx_hash, self.client))
210    }
211}
212
213impl<Inner> TransactionBuilder<true, Inner> {
214    /// Add extra energy to the call.
215    /// The default amount is 10%, or at least 50.
216    /// This should be sufficient in most cases, but for specific
217    /// contracts no extra energy might be needed, or a greater safety margin
218    /// could be desired.
219    pub fn extra_energy(mut self, energy: Energy) -> Self {
220        self.add_energy = Some(energy);
221        self
222    }
223}
224
225/// A helper type to construct [`ContractInitBuilder`].
226/// Users do not directly interact with values of this type.
227pub struct ContractInitInner<Type> {
228    /// The event generated from dry running.
229    event: ContractInitializedEvent,
230    phantom: PhantomData<Type>,
231}
232
233impl<Type> ContractInitInner<Type> {
234    fn new(event: ContractInitializedEvent) -> Self {
235        Self {
236            event,
237            phantom: PhantomData,
238        }
239    }
240}
241
242/// Builder for initializing a new smart contract instance.
243///
244/// The builder is intended to be constructed using
245/// [`dry_run_new_instance`](ContractInitBuilder::dry_run_new_instance)
246/// or [`dry_run_new_instance_raw`](ContractInitBuilder::dry_run_new_instance_raw) methods.
247/// and the transaction is intended to be sent using the
248/// [`send`](ContractInitBuilder::send) method.
249pub type ContractInitBuilder<Type> = TransactionBuilder<true, ContractInitInner<Type>>;
250
251/// A handle returned when sending a smart contract init transaction.
252/// This can be used to get the response of the initialization.
253///
254/// Note that this handle retains a connection to the node. So if it is not
255/// going to be used it should be dropped.
256pub struct ContractInitHandle<Type> {
257    tx_hash: TransactionHash,
258    client: v2::Client,
259    phantom: PhantomData<Type>,
260}
261
262/// The [`Display`](std::fmt::Display) implementation displays the hash of the
263/// transaction.
264impl<Type> std::fmt::Display for ContractInitHandle<Type> {
265    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266        self.tx_hash.fmt(f)
267    }
268}
269
270#[derive(Debug, thiserror::Error)]
271/// An error that may occur when querying the result of a smart contract update
272/// transaction.
273pub enum ContractInitError {
274    #[error("The status of the transaction could not be ascertained: {0}")]
275    Query(#[from] QueryError),
276    #[error("Contract update failed with reason: {0:?}")]
277    Failed(v2::Upward<RejectReason>),
278}
279
280impl<Type> ContractInitHandle<Type> {
281    /// Extract the hash of the transaction underlying this handle.
282    pub fn hash(&self) -> TransactionHash {
283        self.tx_hash
284    }
285
286    /// Wait until the transaction is finalized and return the client for the
287    /// contract together with a list of events generated by the contract
288    /// during initialization.
289    ///
290    /// Note that this can potentially wait indefinitely.
291    pub async fn wait_for_finalization(
292        mut self,
293    ) -> Result<(ContractClient<Type>, Vec<ContractEvent>), ContractInitError> {
294        let (_, result) = self.client.wait_until_finalized(&self.tx_hash).await?;
295
296        let mk_error = |msg| {
297            Err(ContractInitError::from(QueryError::RPCError(
298                RPCError::CallError(tonic::Status::invalid_argument(msg)),
299            )))
300        };
301        let v2::upward::Upward::Known(details) = result.details else {
302            return mk_error(
303                "Expected smart contract initialization status, but received unknown block item \
304                 details.",
305            );
306        };
307        match details {
308            crate::types::BlockItemSummaryDetails::AccountTransaction(at) => {
309                let v2::upward::Upward::Known(effects) = at.effects else {
310                    return mk_error(
311                        "Expected smart contract initialization status, but received unknown \
312                         block item details.",
313                    );
314                };
315                match effects {
316                    AccountTransactionEffects::ContractInitialized { data } => {
317                        let contract_client =
318                            ContractClient::create(self.client, data.address).await?;
319                        Ok((contract_client, data.events))
320                    }
321                    AccountTransactionEffects::None {
322                        transaction_type: _,
323                        reject_reason,
324                    } => Err(ContractInitError::Failed(reject_reason)),
325                    _ => mk_error(
326                        "Expected smart contract initialization status, but did not receive it.",
327                    ),
328                }
329            }
330            crate::types::BlockItemSummaryDetails::AccountCreation(_) => mk_error(
331                "Expected smart contract initialization status, but received account creation.",
332            ),
333            crate::types::BlockItemSummaryDetails::Update(_) => mk_error(
334                "Expected smart contract initialization status, but received chain update \
335                 instruction.",
336            ),
337            crate::types::BlockItemSummaryDetails::TokenCreationDetails(_) => mk_error(
338                "Expected smart contract initialization status, but received token creation chain \
339                 update instruction.",
340            ),
341        }
342    }
343
344    /// Wait until the transaction is finalized or until the timeout has elapsed
345    /// and return the result.
346    pub async fn wait_for_finalization_timeout(
347        self,
348        timeout: std::time::Duration,
349    ) -> Result<(ContractClient<Type>, Vec<ContractEvent>), ContractInitError> {
350        let result = tokio::time::timeout(timeout, self.wait_for_finalization()).await;
351        match result {
352            Ok(r) => r,
353            Err(_elapsed) => Err(ContractInitError::Query(QueryError::RPCError(
354                RPCError::CallError(tonic::Status::deadline_exceeded(
355                    "Deadline waiting for result of transaction is exceeded.",
356                )),
357            ))),
358        }
359    }
360}
361
362#[derive(thiserror::Error, Debug)]
363/// An error that may occur when attempting to dry run a new instance creation.
364pub enum DryRunNewInstanceError {
365    #[error("Dry run succeeded, but contract initialization failed due to {0:#?}.")]
366    Failed(v2::Upward<RejectReason>),
367    #[error("Dry run failed: {0}")]
368    DryRun(#[from] dry_run::DryRunError),
369    #[error("Parameter too large: {0}")]
370    ExceedsParameterSize(#[from] ExceedsParameterSize),
371    #[error("Node query error: {0}")]
372    Query(#[from] v2::QueryError),
373    #[error("Contract name not valid: {0}")]
374    InvalidContractName(#[from] NewContractNameError),
375    #[error("The reported energy consumed for the dry run is less than expected ({min}).")]
376    InvalidEnergy {
377        /// Minimum amount of energy expected
378        min: Energy,
379    },
380}
381
382impl From<v2::Upward<RejectReason>> for DryRunNewInstanceError {
383    fn from(value: v2::Upward<RejectReason>) -> Self {
384        Self::Failed(value)
385    }
386}
387
388impl<Type> ContractInitBuilder<Type> {
389    /// Attempt to dry run a smart contract initialization transaction.
390    ///
391    /// In contrast to
392    /// [`dry_run_new_instance_raw`](Self::dry_run_new_instance_raw) this
393    /// automatically serializes the provided parameter.
394    pub async fn dry_run_new_instance<P: contracts_common::Serial>(
395        client: Client,
396        sender: AccountAddress,
397        mod_ref: ModuleReference,
398        name: &str,
399        amount: Amount,
400        parameter: &P,
401    ) -> Result<Self, DryRunNewInstanceError> {
402        let parameter = OwnedParameter::from_serial(parameter)?;
403        Self::dry_run_new_instance_raw(client, sender, mod_ref, name, amount, parameter).await
404    }
405
406    /// Attempt to dry run a smart contract initialization transaction.
407    /// In case of success the resulting value can be used to extract
408    /// the generated events from the dry-run, and sign and send the
409    /// transaction.
410    ///
411    /// The arguments are
412    /// - `client` - the client to connect to the node
413    /// - `sender` - the account that will be sending the transaction
414    /// - `mod_ref` - the reference to the module on chain from which the
415    ///   instance is to be created
416    /// - `name` - the name of the contract (NB: without the `init_` prefix)
417    /// - `amount` - the amount of CCD to initialize the instance with
418    /// - `parameter` - the parameter to send to the initialization method of
419    ///   the contract.
420    pub async fn dry_run_new_instance_raw(
421        mut client: Client,
422        sender: AccountAddress,
423        mod_ref: ModuleReference,
424        name: &str,
425        amount: Amount,
426        parameter: OwnedParameter,
427    ) -> Result<Self, DryRunNewInstanceError> {
428        let name = OwnedContractName::new(format!("init_{name}"))?;
429        let mut dr = client.dry_run(BlockIdentifier::LastFinal).await?;
430        let payload = InitContractPayload {
431            amount,
432            mod_ref,
433            init_name: name,
434            param: parameter,
435        };
436        let payload = transactions::Payload::InitContract { payload };
437        let encoded_payload = payload.encode();
438        let payload_size = encoded_payload.size();
439        let tx = DryRunTransaction {
440            sender,
441            energy_amount: dr.inner.0.energy_quota(),
442            payload: encoded_payload,
443            signatures: Vec::new(),
444        };
445        let result = dr
446            .inner
447            .0
448            .begin_run_transaction(tx)
449            .await
450            .map_err(dry_run::DryRunError::from)?
451            .await?
452            .inner;
453
454        let data = match result.details.effects.known_or_else(|_unknown| {
455            dry_run::DryRunError::CallError(tonic::Status::invalid_argument(
456                "Unexpected response from dry-running a contract initialization.",
457            ))
458        })? {
459            AccountTransactionEffects::None {
460                transaction_type: _,
461                reject_reason,
462            } => return Err(reject_reason.into()),
463            AccountTransactionEffects::ContractInitialized { data } => data,
464            _ => {
465                return Err(
466                    dry_run::DryRunError::CallError(tonic::Status::invalid_argument(
467                        "Unexpected response from dry-running a contract initialization.",
468                    ))
469                    .into(),
470                )
471            }
472        };
473        let base_cost = transactions::cost::base_cost(
474            TRANSACTION_HEADER_SIZE + u64::from(u32::from(payload_size)),
475            1,
476        );
477        let energy = result
478            .energy_cost
479            .checked_sub(base_cost)
480            .ok_or(DryRunNewInstanceError::InvalidEnergy { min: base_cost })?;
481
482        Ok(ContractInitBuilder::new(
483            client,
484            sender,
485            energy,
486            payload,
487            ContractInitInner::new(data),
488        ))
489    }
490
491    /// Access to the generated events.
492    ///
493    /// Note that these are events generated as part of a dry run.
494    /// Since time passes between the dry run and the actual transaction
495    /// the transaction might behave differently.
496    pub fn event(&self) -> &ContractInitializedEvent {
497        &self.inner.event
498    }
499
500    /// Send the transaction and return a handle that can be queried
501    /// for the status.
502    pub async fn send(
503        self,
504        signer: &impl transactions::ExactSizeTransactionSigner,
505    ) -> v2::QueryResult<ContractInitHandle<Type>> {
506        let phantom = self.inner.phantom;
507        self.send_inner(signer, |tx_hash, client| ContractInitHandle {
508            tx_hash,
509            client,
510            phantom,
511        })
512        .await
513    }
514}
515
516pub type ModuleDeployBuilder = TransactionBuilder<false, ModuleReference>;
517
518#[derive(thiserror::Error, Debug)]
519/// An error that may occur when attempting to dry run a smart contract module
520/// deployment.
521pub enum DryRunModuleDeployError {
522    #[error("Dry run succeeded, but module deployment failed due to {0:#?}.")]
523    Failed(v2::Upward<RejectReason>),
524    #[error("Dry run failed: {0}")]
525    DryRun(#[from] dry_run::DryRunError),
526    #[error("Node query error: {0}")]
527    Query(#[from] v2::QueryError),
528    #[error("The reported energy consumed for the dry run is less than expected ({min}).")]
529    InvalidEnergy {
530        /// Minimum amount of energy expected
531        min: Energy,
532    },
533}
534
535impl DryRunModuleDeployError {
536    /// Check whether dry-run failed because the module already exists.
537    pub fn already_exists(&self) -> bool {
538        let Self::Failed(reason) = self else {
539            return false;
540        };
541        matches!(
542            reason,
543            v2::Upward::Known(RejectReason::ModuleHashAlreadyExists { .. })
544        )
545    }
546}
547
548impl From<v2::Upward<RejectReason>> for DryRunModuleDeployError {
549    fn from(value: v2::Upward<RejectReason>) -> Self {
550        Self::Failed(value)
551    }
552}
553
554impl ModuleDeployBuilder {
555    /// Attempt to dry run a module deployment transaction.
556    ///
557    /// In case of success the return value can be used to send the transaction
558    /// to affect the module deployment.
559    pub async fn dry_run_module_deploy(
560        mut client: Client,
561        sender: AccountAddress,
562        module: WasmModule,
563    ) -> Result<Self, DryRunModuleDeployError> {
564        let mut dr = client.dry_run(BlockIdentifier::LastFinal).await?;
565        let payload = transactions::Payload::DeployModule { module };
566        let encoded_payload = payload.encode();
567        let payload_size = encoded_payload.size();
568        let tx = DryRunTransaction {
569            sender,
570            energy_amount: dr.inner.0.energy_quota(),
571            payload: encoded_payload,
572            signatures: Vec::new(),
573        };
574        let result = dr
575            .inner
576            .0
577            .begin_run_transaction(tx)
578            .await
579            .map_err(dry_run::DryRunError::from)?
580            .await?
581            .inner;
582
583        let module_ref = match result.details.effects.known_or_else(|_unknown| {
584            dry_run::DryRunError::CallError(tonic::Status::invalid_argument(
585                "Unexpected response from dry-running a module deployment.",
586            ))
587        })? {
588            AccountTransactionEffects::None {
589                transaction_type: _,
590                reject_reason,
591            } => return Err(reject_reason.into()),
592            AccountTransactionEffects::ModuleDeployed { module_ref } => module_ref,
593            _ => {
594                return Err(
595                    dry_run::DryRunError::CallError(tonic::Status::invalid_argument(
596                        "Unexpected response from dry-running a module deployment.",
597                    ))
598                    .into(),
599                )
600            }
601        };
602        let base_cost = transactions::cost::base_cost(
603            TRANSACTION_HEADER_SIZE + u64::from(u32::from(payload_size)),
604            1,
605        );
606        let energy = result
607            .energy_cost
608            .checked_sub(base_cost)
609            .ok_or(DryRunModuleDeployError::InvalidEnergy { min: base_cost })?;
610        Ok(Self::new(client, sender, energy, payload, module_ref))
611    }
612}
613
614impl ModuleDeployBuilder {
615    /// Send the transaction and return a handle that can be queried
616    /// for the status.
617    pub async fn send(
618        self,
619        signer: &impl transactions::ExactSizeTransactionSigner,
620    ) -> v2::QueryResult<ModuleDeployHandle> {
621        self.send_inner(signer, |tx_hash, client| ModuleDeployHandle {
622            tx_hash,
623            client,
624        })
625        .await
626    }
627}
628
629pub struct ModuleDeployHandle {
630    tx_hash: TransactionHash,
631    client: v2::Client,
632}
633
634/// The [`Display`](std::fmt::Display) implementation displays the hash of the
635/// transaction.
636impl std::fmt::Display for ModuleDeployHandle {
637    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
638        self.tx_hash.fmt(f)
639    }
640}
641
642#[derive(Debug, thiserror::Error)]
643/// An error that may occur when querying the result of a module deploy
644/// transaction.
645pub enum ModuleDeployError {
646    #[error("The status of the transaction could not be ascertained: {0}")]
647    Query(#[from] QueryError),
648    #[error("Module deployment failed with reason: {0:?}")]
649    Failed(v2::Upward<RejectReason>),
650}
651
652#[derive(Debug, Clone, Copy)]
653/// Result of successful module deployment.
654pub struct ModuleDeployData {
655    /// Energy used for the transaction.
656    pub energy: Energy,
657    /// The CCD cost of the transaction.
658    pub cost: Amount,
659    /// The module reference.
660    pub module_reference: ModuleReference,
661}
662
663impl ModuleDeployHandle {
664    /// Extract the hash of the transaction underlying this handle.
665    pub fn hash(&self) -> TransactionHash {
666        self.tx_hash
667    }
668
669    /// Wait until the transaction is finalized and return the result.
670    /// Note that this can potentially wait indefinitely.
671    pub async fn wait_for_finalization(mut self) -> Result<ModuleDeployData, ModuleDeployError> {
672        let (_, result) = self.client.wait_until_finalized(&self.tx_hash).await?;
673
674        let mk_error = |msg| {
675            Err(ModuleDeployError::from(QueryError::RPCError(
676                RPCError::CallError(tonic::Status::invalid_argument(msg)),
677            )))
678        };
679
680        let v2::upward::Upward::Known(details) = result.details else {
681            return mk_error(
682                "Expected  module deploy status, but received unknown block item details.",
683            );
684        };
685        match details {
686            crate::types::BlockItemSummaryDetails::AccountTransaction(at) => {
687                let v2::upward::Upward::Known(effects) = at.effects else {
688                    return mk_error(
689                        "Expected  module deploy status, but received unknown block item effect.",
690                    );
691                };
692                match effects {
693                    AccountTransactionEffects::ModuleDeployed { module_ref } => {
694                        Ok(ModuleDeployData {
695                            energy: result.energy_cost,
696                            cost: at.cost,
697                            module_reference: module_ref,
698                        })
699                    }
700                    AccountTransactionEffects::None {
701                        transaction_type: _,
702                        reject_reason,
703                    } => Err(ModuleDeployError::Failed(reject_reason)),
704                    _ => mk_error("Expected module deploy status, but did not receive it."),
705                }
706            }
707            crate::types::BlockItemSummaryDetails::AccountCreation(_) => {
708                mk_error("Expected module deploy status, but received account creation.")
709            }
710            crate::types::BlockItemSummaryDetails::Update(_) => {
711                mk_error("Expected module deploy status, but received chain update instruction.")
712            }
713            crate::types::BlockItemSummaryDetails::TokenCreationDetails(_) => mk_error(
714                "Expected module deploy status, but received token creation chain update \
715                 instruction.",
716            ),
717        }
718    }
719
720    /// Wait until the transaction is finalized or until the timeout has elapsed
721    /// and return the result.
722    pub async fn wait_for_finalization_timeout(
723        self,
724        timeout: std::time::Duration,
725    ) -> Result<ModuleDeployData, ModuleDeployError> {
726        let result = tokio::time::timeout(timeout, self.wait_for_finalization()).await;
727        match result {
728            Ok(r) => r,
729            Err(_elapsed) => Err(ModuleDeployError::Query(QueryError::RPCError(
730                RPCError::CallError(tonic::Status::deadline_exceeded(
731                    "Deadline waiting for result of transaction is exceeded.",
732                )),
733            ))),
734        }
735    }
736}
737
738/// Define a newtype wrapper around the error schema type.
739#[derive(Debug, Clone)]
740pub struct ErrorSchema(pub Value);
741
742/// Write a custom display implementation for the error schema type.
743/// This implementation displays nested errors meaningfully.
744/// For example the nested error: `Object {\"Custom\": Array
745/// [Object {\"Unauthorized\": Array []}]}` is displayed as
746/// `Custom::Unauthorized`.
747impl std::fmt::Display for ErrorSchema {
748    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
749        match &self.0 {
750            Value::Object(map) => {
751                if let Some(key) = map.keys().next() {
752                    write!(f, "{}", key)?;
753                    if let Some(value) = map.values().next() {
754                        if value.is_array() {
755                            write!(f, "{}", ErrorSchema(value.clone()))?;
756                        }
757                    }
758                }
759            }
760            Value::Array(arr) => {
761                if let Some(value) = arr.iter().next() {
762                    write!(f, "::{}", ErrorSchema(value.clone()))?;
763                }
764            }
765            _ => write!(f, "{}", self.0)?,
766        }
767        Ok(())
768    }
769}
770
771/// A human-readable decoded error for the reject reason of a reverted
772/// transaction.
773#[derive(Debug, Clone)]
774pub enum DecodedReason {
775    Std {
776        /// The error code of the transaction.
777        reject_reason: i32,
778        /// The decoded human-readable error.
779        parsed: ConcordiumStdRejectReason,
780    },
781    Custom {
782        /// The return value of the transaction.
783        return_value: ReturnValue,
784        /// The error code of the transaction.
785        reject_reason: i32,
786        /// The decoded human-readable error.
787        /// For example:
788        /// Object {\"Unauthorized\": Array []} or
789        /// Object {\"Custom\": Array [Object {\"Unauthorized\": Array []}]}
790        /// (e.g. the cis2_library error)
791        parsed: ErrorSchema,
792    },
793}
794
795/// Write a custom display implementation for the decoded human-readable error
796/// of the `DecodedReason` type.
797impl std::fmt::Display for DecodedReason {
798    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
799        match self {
800            DecodedReason::Std { parsed, .. } => {
801                write!(f, "{}", parsed)
802            }
803            DecodedReason::Custom { parsed, .. } => {
804                write!(f, "{}", parsed)
805            }
806        }
807    }
808}
809
810/// The outcome of invoking (dry-running) a transaction to update a smart
811/// contract instance. The variants describe the two cases of successfully
812/// simulating the transaction and rejecting the transaction due to some
813/// reverts.
814pub enum InvokeContractOutcome {
815    /// The simulation of the transaction was successful.
816    Success(SimulatedTransaction),
817    /// The transaction was rejected due to some reverts.
818    Failure(RejectedTransaction),
819}
820
821/// The `SimulatedTransaction` type is an alias for the `ContractUpdateBuilder`
822/// type. This type is used when an invoke (dry-run) of a transaction succeeds.
823/// This type includes a convenient send method to send and execute the
824/// transaction on-chain in a subsequent action. As such, it is a builder to
825/// simplify sending smart contract updates.
826pub type SimulatedTransaction = ContractUpdateBuilder;
827
828/// This type is used when an invoke (dry-run) of a transaction gets rejected.
829#[derive(Debug, Clone)]
830pub struct RejectedTransaction {
831    /// The return value of the transaction.
832    pub return_value: Option<ReturnValue>,
833    /// The reject reason of the transaction.
834    pub reason: Upward<RejectReason>,
835    /// An optional human-readable decoded reason for the reject reason.
836    /// This is only available if the reject reason is a smart contract logical
837    /// revert and a valid error schema is available for decoding, or the
838    /// reject reason originates from the `concordium-std` crate.
839    pub decoded_reason: Option<DecodedReason>,
840    /// The energy used by the transaction.
841    pub used_energy: Energy,
842    /// The payload of the transaction.
843    pub payload: transactions::Payload,
844}
845
846impl InvokeContractOutcome {
847    /// This converts `InvokeContractOutcome` into a result type.
848    pub fn success(self) -> Result<SimulatedTransaction, RejectedTransaction> {
849        match self {
850            InvokeContractOutcome::Success(simulated_transaction) => Ok(simulated_transaction),
851            InvokeContractOutcome::Failure(rejected_transaction) => Err(rejected_transaction),
852        }
853    }
854}
855
856#[derive(thiserror::Error, Debug, Clone, serde::Serialize, serde::Deserialize)]
857#[repr(i32)]
858pub enum ConcordiumStdRejectReason {
859    #[error("[Unspecified (Default reject)]")]
860    Unspecified = -2147483648, // i32::MIN
861    #[error("[Error ()]")]
862    Unit = -2147483647, // i32::MIN + 1
863    #[error("[ParseError]")]
864    Parse = -2147483646, // ...
865    #[error("[LogError::Full]")]
866    LogFull = -2147483645,
867    #[error("[LogError::Malformed]")]
868    LogMalformed = -2147483644,
869    #[error("[NewContractNameError::MissingInitPrefix]")]
870    NewContractNameMissingInitPrefix = -2147483643,
871    #[error("[NewContractNameError::TooLong]")]
872    NewContractNameTooLong = -2147483642,
873    #[error("[NewReceiveNameError::MissingDotSeparator]")]
874    NewReceiveNameMissingDotSeparator = -2147483641,
875    #[error("[NewReceiveNameError::TooLong]")]
876    NewReceiveNameTooLong = -2147483640,
877    #[error("[NewContractNameError::ContainsDot]")]
878    NewContractNameContainsDot = -2147483639,
879    #[error("[NewContractNameError::InvalidCharacters]")]
880    NewContractNameInvalidCharacters = -2147483638,
881    #[error("[NewReceiveNameError::InvalidCharacters]")]
882    NewReceiveNameInvalidCharacters = -2147483637,
883    #[error("[NotPayableError]")]
884    NotPayableError = -2147483636,
885    #[error("[TransferError::AmountTooLarge]")]
886    TransferAmountTooLarge = -2147483635,
887    #[error("[TransferError::MissingAccount]")]
888    TransferMissingAccount = -2147483634,
889    #[error("[CallContractError::AmountTooLarge]")]
890    CallContractAmountTooLarge = -2147483633,
891    #[error("[CallContractError::MissingAccount]")]
892    CallContractMissingAccount = -2147483632,
893    #[error("[CallContractError::MissingContract]")]
894    CallContractMissingContract = -2147483631,
895    #[error("[CallContractError::MissingEntrypoint]")]
896    CallContractMissingEntrypoint = -2147483630,
897    #[error("[CallContractError::MessageFailed]")]
898    CallContractMessageFailed = -2147483629,
899    #[error("[CallContractError::LogicReject]")]
900    CallContractLogicReject = -2147483628,
901    #[error("[CallContractError::Trap]")]
902    CallContractTrap = -2147483627,
903    #[error("[UpgradeError::MissingModule]")]
904    UpgradeMissingModule = -2147483626,
905    #[error("[UpgradeError::MissingContract]")]
906    UpgradeMissingContract = -2147483625,
907    #[error("[UpgradeError::UnsupportedModuleVersion]")]
908    UpgradeUnsupportedModuleVersion = -2147483624,
909    #[error("[QueryAccountBalanceError]")]
910    QueryAccountBalanceError = -2147483623,
911    #[error("[QueryContractBalanceError]")]
912    QueryContractBalanceError = -2147483622,
913}
914
915/// Decode the `reject_reason` into a human-readable error based on the error
916/// code definition in the `concordium-std` crate.
917pub fn decode_concordium_std_error(reject_reason: i32) -> Option<ConcordiumStdRejectReason> {
918    if (-2147483648..=-2147483622).contains(&reject_reason) {
919        let reason: ConcordiumStdRejectReason = unsafe { ::std::mem::transmute(reject_reason) };
920        Some(reason)
921    } else {
922        None
923    }
924}
925
926/// Decode the smart contract logical revert reason and return a human-readable
927/// error.
928///
929/// If the error is NOT caused by a smart contract logical revert, the
930/// `reject_reason` is already a human-readable error. No further decoding of
931/// the error is needed and as such this function returns `None`.
932/// An example of such a failure (rejected transaction) is the case when the
933/// transaction runs out of energy which is represented by the human-readable
934/// error variant "OutOfEnergy".
935///
936/// Step 1: If the error matches a smart contract logical revert code coming
937/// from the `concordium-std` crate, this function decodes the error based on
938/// the error code definition in the `concordium-std` crate.
939///
940/// Step 2: If the error is caused by a smart contract logical revert coming
941/// from the smart contract itself, this function uses the provided
942/// `error_schema` and `return_value` to decode the `reject_reason` into a
943/// human-readable error.
944///
945/// `Return_values` vs. `error_codes` in rejected transactions:
946/// Historically, Concordium had smart contracts V0 which had no `retun_value`.
947/// Error codes (negative values used commonly in computer science to represent
948/// errors) were chosen as a method to report the reject reason. Smart
949/// contracts could only revert using one "[Unspecified (Default reject)]" error
950/// and the error code (i32::MIN) was used to represent this error. This
951/// "[Unspecified (Default reject)]" error definition still exists in the
952/// `concordium-std` crate.
953///
954/// `Return_values` were introduced in smart contracts V1 and smart contracts
955/// were fitted with features to revert with different reasons (as defined in
956/// the smart contract logic). The `return_value` is used to distinguish between
957/// the different reasons for the revert coming from the smart contract logic.
958///
959/// There are some historical types used by the node which spread into this Rust
960/// SDK code base (such as `RejectReason`) that only include the `error_code`
961/// but not the `return_value`. The reason can be explained by the historical
962/// development of smart contracts on Concordium (types were not expanded to
963/// avoid breaking changes) as well as to keep the size of the node as small as
964/// possible since the `return_value` does not need to be saved by the node for
965/// achieving consensus.
966///
967/// As a result, this decoding function needs to have access to both the
968/// `return_value` and the `error_code` to decode the `reject_reason` of a
969/// reverted transaction into a human-readable error.
970///
971/// How is the `return_value` and `error_code` assigned in rejected
972/// transactions:
973/// - If the transaction reverts due to an error in the `concordium-std` crate,
974///   the `return_value` is None and the `error_code` is assigned as defined in
975///   the `concordium-std` crate.
976/// - If the transaction reverts due to an error in the smart contract logic:
977///   A smart contract V1 needs to implement a conversion to `Reject` for its
978///   smart contract errors.
979///   `<https://docs.rs/concordium-std/latest/concordium_std/struct.Reject.html>`
980///
981/// 1. Example: Deriving `Reject` in the smart contract.
982///
983/// The simplest way to implement `Reject` in the smart contract is by deriving
984/// it.
985///
986/// ```ignore
987/// /// The custom errors the contract can produce.
988/// #[derive(Serialize, Debug, Reject, SchemaType)]
989/// pub enum CustomContractError {
990///     /// CustomError1 defined in the smart contract logic.
991///     CustomError1, // return_value: 00; error_code: -1
992///     /// CustomError2 defined in the smart contract logic.
993///     CustomError2, // return_value: 01; error_code: -2
994/// }
995/// ```
996///
997/// The `Reject` macro assigns the `error_codes` starting from `-1` to the enum
998/// variants and assigns the `return_values` by serializing the enum variants.
999/// The `return_values` are equivalent to the enum tags in the above example.
1000///
1001/// The JSON value returned by this function for the above `CustomError1` is:
1002/// ```json
1003/// {"CustomError1":[]}
1004/// ```
1005///
1006/// 2. Example: Deriving `Reject` in the smart contract with nested errors.
1007///
1008/// Nested errors are often used to inherit the errors from a smart
1009/// contract library such as the cis2-library.
1010/// `<https://github.com/Concordium/concordium-rust-smart-contracts/blob/dde42fa62254a55b46a4c9c52c32bbe661127001/concordium-cis2/src/lib.rs#L1093>`
1011///
1012/// Parent Smart contract:
1013/// ```ignore
1014/// /// The custom errors the contract/library can produce.
1015/// #[derive(Serialize, Debug, Reject, SchemaType)]
1016/// pub enum ParentError<R> {
1017///     /// ParentCustomError1 defined in the smart contract logic.
1018///     ParentCustomError1, // return_value: 00; error_code: -1
1019///     /// Nested error variant.
1020///     Custom(R),
1021///     // ChildCustomError1 -> return_value: 0100; error_code: -1
1022///     // ChildCustomError2 -> return_value: 0101; error_code: -2
1023///     // ChildCustomError3 -> return_value: 0102; error_code: -3
1024///     // ...
1025///     /// ParentCustomError2 defined in the smart contract logic.
1026///     ParentCustomError2, // return_value: 02; error_code: -3
1027/// }
1028/// ```
1029///
1030/// Child Smart contract:
1031/// ```ignore
1032/// /// The different errors the contract/library can produce.
1033/// #[derive(Serialize, Debug, PartialEq, Eq, Reject, SchemaType)]
1034/// pub enum ChildError {
1035///     /// ChildCustomError1 defined in the smart contract logic.
1036///     ChildCustomError1, // return_value: 0100; error_code: -1
1037///     /// ChildCustomError2 defined in the smart contract logic.
1038///     ChildCustomError2, // return_value: 0101; error_code: -2
1039///     /// ChildCustomError3 defined in the smart contract logic.
1040///     ChildCustomError3, // return_value: 0102; error_code: -2
1041/// }
1042///
1043/// /// Mapping ChildError to ContractError
1044/// impl From<ChildError> for ContractError {
1045///     fn from(c: ChildError) -> Self { ParentError::Custom(c) }
1046/// }
1047///
1048/// pub type ContractError = ParentError<ChildError>;
1049///
1050/// pub type ContractResult<A> = Result<A, ContractError>;
1051/// ```
1052///
1053/// The `Reject` macro assigns the `error_codes` starting from `-1` to the enum
1054/// variants and assigns the `return_values` by serializing the enum variants
1055/// starting with the topmost enum. The `return_values` are equivalent to the
1056/// enum tags of all enums in the nested chain.
1057///
1058/// The JSON value returned by this function for the above `CustomError1` is:
1059/// ```json
1060/// {"Custom":[{"CustomError1":[]}]}
1061/// ```
1062///
1063/// 3. Example: `Reject::default()`.
1064///
1065/// The historical `Reject::default()` can be used by implementing the
1066/// conversion to `Reject` manually.
1067///
1068/// ```ignore
1069/// /// The custom errors the contract can produce.
1070/// #[derive(Serialize, Debug, SchemaType)]
1071/// pub enum CustomContractError {
1072///     /// CustomError1 defined in the smart contract logic.
1073///     CustomError1, // return_value: None; error_code: -2147483648 (i32::MIN)
1074/// }
1075///
1076/// impl From<CustomContractError> for Reject {
1077///     fn from(error: CustomContractError) -> Self {
1078///         match error {
1079///             _ => Reject::default(),
1080///         }
1081///     }
1082/// }
1083/// ```
1084///
1085/// `Reject::default()` assigns `-2147483648` as `error_code` and `None` to the
1086/// `return_value`.
1087///
1088/// The JSON value returned by this function for the above `CustomError1` is:
1089/// ```json
1090/// {"[Unspecified (Default reject)]":[]}
1091/// ```
1092///
1093/// 4. Example: Implementing the conversion to `Reject` manually.
1094///
1095/// A smart contract can implement the conversion to `Reject` manually and
1096/// define custom error codes. The convention for the `return_value` is to set
1097/// the value to the serialization of the enum variants so that decoding of the
1098/// error is possible.
1099///
1100/// ```ignore
1101/// /// The custom errors the contract can produce.
1102/// #[derive(Serialize, Debug, SchemaType)]
1103/// pub enum CustomContractError {
1104///     /// CustomError1 defined in the smart contract logic.
1105///     CustomError1, // return_value: 00; error_code: -123
1106///     /// CustomError2 defined in the smart contract logic.
1107///     CustomError2, // return_value: 01; error_code: -124
1108/// }
1109///
1110/// impl From<CustomContractError> for Reject {
1111///     fn from(error: CustomContractError) -> Self {
1112///         match error {
1113///             CustomContractError::CustomError1 => Reject {
1114///                 error_code:   NonZeroI32::new(-123).unwrap(),
1115///                 return_value: Some(vec![00]),
1116///             },
1117///             CustomContractError::CustomError2 => Reject {
1118///                 error_code:   NonZeroI32::new(-124).unwrap(),
1119///                 return_value: Some(vec![01]),
1120///             },
1121///         }
1122///     }
1123/// }
1124/// ```
1125///
1126/// The JSON value returned by this function for the above `CustomError1` is:
1127/// ```json
1128/// {"CustomError1":[]}
1129/// ```
1130///
1131/// Disclaimer: A smart contract can have logic to overwrite/change/reuse the
1132/// meaning of the error codes as defined in the `concordium-std` crate (see
1133/// Example 4 above). While it is not advised to reuse these error codes and is
1134/// rather unusual to do so, this function decodes the error codes based on the
1135/// definitions in the `concordium-std` crate (assuming they have not been
1136/// reused with other meanings in the smart contract logic). No guarantee
1137/// are given as such that the meaning of the decoded reject reason hasn't been
1138/// altered by the smart contract logic. The main reason for setting the
1139/// `concordium-std` crate errors to `i32::MIN`,`i32::MIN+1`, etc., is to avoid
1140/// conflicts/reuse of the error codes used in the smart contract logic.
1141pub fn decode_smart_contract_revert(
1142    return_value: Option<&ReturnValue>,
1143    reject_reason: &RejectReason,
1144    schema: Option<&VersionedModuleSchema>,
1145) -> Option<DecodedReason> {
1146    match reject_reason {
1147        RejectReason::RejectedReceive {
1148            reject_reason: error_code,
1149            contract_address: _,
1150            receive_name,
1151            parameter: _,
1152        } => {
1153            let receive_name = receive_name.as_receive_name();
1154
1155            // Step 1: Try to decode the `reject_reason` using the `concordium-std`
1156            // error codes.
1157            if let Some(decoded_error) = decode_concordium_std_error(*error_code) {
1158                return Some(DecodedReason::Std {
1159                    reject_reason: *error_code,
1160                    parsed: decoded_error,
1161                });
1162            }
1163
1164            // Step 2: Try to decode the `reject_reason` using the `error_schema` and the
1165            // `return_value`.
1166
1167            // If no `schema` is provided, the
1168            // `reject_reason` can not be decoded further.
1169            let schema = schema?;
1170
1171            let (Some(error_schema), Some(return_value)) = (
1172                schema
1173                    .get_receive_error_schema(
1174                        receive_name.contract_name(),
1175                        receive_name.entrypoint_name().into(),
1176                    )
1177                    .ok(),
1178                return_value,
1179            ) else {
1180                // If no `error_schema` and/or `return_value` is provided, the
1181                // `reject_reason` can not be decoded further.
1182                return None;
1183            };
1184
1185            let mut cursor = Cursor::new(&return_value.value);
1186
1187            error_schema
1188                .to_json(&mut cursor)
1189                .ok()
1190                .map(|decoded_reason| DecodedReason::Custom {
1191                    return_value: return_value.clone(),
1192                    reject_reason: *error_code,
1193                    parsed: ErrorSchema(decoded_reason),
1194                })
1195        }
1196        // If the error is NOT caused by a smart contract logical revert, the
1197        // `reject_reason` is already a human-readable error. No
1198        // further decoding of the error is needed. An example of
1199        // such a transaction is the error variant "OutOfEnergy".
1200        _ => None,
1201    }
1202}
1203
1204impl<Type> ContractClient<Type> {
1205    /// Construct a [`ContractClient`] by looking up metadata from the chain
1206    /// (such as the contract_name and the embedded schema).
1207    ///
1208    /// # Arguments
1209    ///
1210    /// * `client` - The RPC client for the concordium node.
1211    /// * `address` - The contract address of the smart contract instance.
1212    pub async fn create(mut client: Client, address: ContractAddress) -> v2::QueryResult<Self> {
1213        // Get the smart contract instance info.
1214        let contract_instance_info = client
1215            .get_instance_info(address, BlockIdentifier::LastFinal)
1216            .await?
1217            .response;
1218
1219        let contract_name = contract_instance_info.name().clone();
1220        let module_reference = contract_instance_info.source_module();
1221
1222        // Get the wasm module associated to the contract instance.
1223        let wasm_module = client
1224            .get_module_source(&module_reference, BlockIdentifier::LastFinal)
1225            .await?
1226            .response;
1227
1228        // Get the schema associated to the contract instance.
1229        let schema = match wasm_module.version {
1230            WasmVersion::V0 => utils::get_embedded_schema_v0(wasm_module.source.as_ref()).ok(),
1231            WasmVersion::V1 => utils::get_embedded_schema_v1(wasm_module.source.as_ref()).ok(),
1232        };
1233
1234        Ok(Self {
1235            client,
1236            address,
1237            contract_name: Arc::new(contract_name),
1238            phantom: PhantomData,
1239            schema: Arc::new(schema),
1240        })
1241    }
1242
1243    /// Construct a [`ContractClient`] locally. In comparison to
1244    /// [`create`](Self::create) this always succeeds and does not check
1245    /// existence of the contract.
1246    ///
1247    /// # Arguments
1248    ///
1249    /// * `client` - The RPC client for the concordium node. Note that cloning
1250    ///   [`Client`] is cheap and is therefore the intended way of sharing.
1251    /// * `address` - The contract address of the smart contract.
1252    /// * `contract_name` - The name of the contract. This must match the name
1253    ///   on the chain, otherwise the constructed client will not work.
1254    pub fn new(client: Client, address: ContractAddress, contract_name: OwnedContractName) -> Self {
1255        Self {
1256            client,
1257            address,
1258            contract_name: Arc::new(contract_name),
1259            phantom: PhantomData,
1260            schema: Arc::new(None),
1261        }
1262    }
1263
1264    /// Construct a [`ContractClient`] locally. In comparison to
1265    /// [`create`](Self::create) this always succeeds and does not check
1266    /// existence of the contract and does not look up metadata from the chain
1267    /// (such as embedded schemas). In comparison to
1268    /// [`new`](Self::new) this constructor also takes a versioned module
1269    /// schema.
1270    ///
1271    /// # Arguments
1272    ///
1273    /// * `client` - The RPC client for the concordium node. Note that cloning
1274    ///   [`Client`] is cheap and is therefore the intended way of sharing.
1275    /// * `address` - The contract address of the smart contract.
1276    /// * `contract_name` - The name of the contract. This must match the name
1277    ///   on the chain, otherwise the constructed client will not work.
1278    /// * `schema` - A versioned module schema of the contract. It is used by
1279    ///   the client to decode the error codes in rejected transactions.
1280    pub fn new_with_schema(
1281        client: Client,
1282        address: ContractAddress,
1283        contract_name: OwnedContractName,
1284        schema: VersionedModuleSchema,
1285    ) -> Self {
1286        Self {
1287            client,
1288            address,
1289            contract_name: Arc::new(contract_name),
1290            phantom: PhantomData,
1291            schema: Arc::new(Some(schema)),
1292        }
1293    }
1294
1295    /// Invoke a contract and return the response.
1296    ///
1297    /// This will always fail for a V0 contract, and for V1 contracts it will
1298    /// attempt to deserialize the response into the provided type `A`.
1299    ///
1300    /// The error `E` is left generic in order to support specialized errors
1301    /// such as CIS2 or CIS4 specific errors for more specialized view functions
1302    /// defined by those standards.
1303    ///
1304    /// For a general contract [`ViewError`] can be used as a concrete error
1305    /// type `E`.
1306    pub async fn view<P: contracts_common::Serial, A: contracts_common::Deserial, E>(
1307        &mut self,
1308        entrypoint: &str,
1309        parameter: &P,
1310        bi: impl v2::IntoBlockIdentifier,
1311    ) -> Result<A, E>
1312    where
1313        E: From<NewReceiveNameError>
1314            + From<v2::Upward<RejectReason>>
1315            + From<contracts_common::ParseError>
1316            + From<v2::QueryError>
1317            + From<ExceedsParameterSize>,
1318    {
1319        let parameter = OwnedParameter::from_serial(parameter)?;
1320        self.view_raw::<A, E>(entrypoint, parameter, bi).await
1321    }
1322
1323    /// Like [`view`](Self::view) but expects an already serialized parameter.
1324    pub async fn view_raw<A: contracts_common::Deserial, E>(
1325        &mut self,
1326        entrypoint: &str,
1327        parameter: OwnedParameter,
1328        bi: impl v2::IntoBlockIdentifier,
1329    ) -> Result<A, E>
1330    where
1331        E: From<NewReceiveNameError>
1332            + From<v2::Upward<RejectReason>>
1333            + From<contracts_common::ParseError>
1334            + From<v2::QueryError>,
1335    {
1336        let ir = self
1337            .invoke_raw::<E>(entrypoint, Amount::zero(), None, parameter, bi)
1338            .await?;
1339        match ir {
1340            smart_contracts::InvokeContractResult::Success { return_value, .. } => {
1341                let Some(bytes) = return_value else {
1342                    return Err(contracts_common::ParseError {}.into());
1343                };
1344                let response: A = contracts_common::from_bytes(&bytes.value)?;
1345                Ok(response)
1346            }
1347            smart_contracts::InvokeContractResult::Failure { reason, .. } => Err(reason.into()),
1348        }
1349    }
1350
1351    /// Invoke a contract instance and return the response without any
1352    /// processing.
1353    pub async fn invoke_raw<E>(
1354        &mut self,
1355        entrypoint: &str,
1356        amount: Amount,
1357        invoker: Option<Address>,
1358        parameter: OwnedParameter,
1359        bi: impl v2::IntoBlockIdentifier,
1360    ) -> Result<InvokeContractResult, E>
1361    where
1362        E: From<NewReceiveNameError> + From<v2::Upward<RejectReason>> + From<v2::QueryError>,
1363    {
1364        let contract_name = self.contract_name.as_contract_name().contract_name();
1365        let method = OwnedReceiveName::try_from(format!("{contract_name}.{entrypoint}"))?;
1366
1367        let context = ContractContext {
1368            invoker,
1369            contract: self.address,
1370            amount,
1371            method,
1372            parameter,
1373            energy: None,
1374        };
1375
1376        let invoke_result = self.client.invoke_instance(bi, &context).await?.response;
1377        Ok(invoke_result)
1378    }
1379
1380    /// Dry run an update. If the dry run succeeds the return value is an object
1381    /// that has a send method to send the transaction that was simulated during
1382    /// the dry run.
1383    ///
1384    /// The arguments are
1385    /// - `entrypoint` the name of the entrypoint to be invoked. Note that this
1386    ///   is just the entrypoint name without the contract name.
1387    /// - `amount` the amount of CCD to send to the contract instance
1388    /// - `sender` the account that will be sending the transaction
1389    /// - `message` the parameter to the smart contract entrypoint.
1390    pub async fn dry_run_update<P: contracts_common::Serial, E>(
1391        &mut self,
1392        entrypoint: &str,
1393        amount: Amount,
1394        sender: AccountAddress,
1395        message: &P,
1396    ) -> Result<ContractUpdateBuilder, E>
1397    where
1398        E: From<NewReceiveNameError>
1399            + From<v2::Upward<RejectReason>>
1400            + From<v2::QueryError>
1401            + From<ExceedsParameterSize>,
1402    {
1403        let message = OwnedParameter::from_serial(message)?;
1404        self.dry_run_update_raw(entrypoint, amount, sender, message)
1405            .await
1406    }
1407
1408    /// Dry run an update. In comparison to
1409    /// [`dry_run_update`](Self::dry_run_update) this function does not throw an
1410    /// error when the transaction reverts and instead tries to decode the
1411    /// reject reason into a human-readable error. If the dry run succeeds the
1412    /// return value is an object that has a send method to send the
1413    /// transaction that was simulated during the dry run.
1414    ///
1415    /// The arguments are
1416    /// - `entrypoint` the name of the entrypoint to be invoked. Note that this
1417    ///   is just the entrypoint name without the contract name.
1418    /// - `amount` the amount of CCD to send to the contract instance
1419    /// - `sender` the account that will be sending the transaction
1420    /// - `message` the parameter to the smart contract entrypoint.
1421    pub async fn dry_run_update_with_reject_reason_info<P: contracts_common::Serial, E>(
1422        &mut self,
1423        entrypoint: &str,
1424        amount: Amount,
1425        sender: AccountAddress,
1426        message: &P,
1427    ) -> Result<InvokeContractOutcome, E>
1428    where
1429        E: From<NewReceiveNameError> + From<v2::QueryError> + From<ExceedsParameterSize>,
1430    {
1431        let message = OwnedParameter::from_serial(message)?;
1432        self.dry_run_update_raw_with_reject_reason_info(entrypoint, amount, sender, message)
1433            .await
1434    }
1435
1436    /// Like [`dry_run_update`](Self::dry_run_update) but expects an already
1437    /// formed parameter.
1438    pub async fn dry_run_update_raw<E>(
1439        &mut self,
1440        entrypoint: &str,
1441        amount: Amount,
1442        sender: AccountAddress,
1443        message: OwnedParameter,
1444    ) -> Result<ContractUpdateBuilder, E>
1445    where
1446        E: From<NewReceiveNameError> + From<v2::Upward<RejectReason>> + From<v2::QueryError>,
1447    {
1448        let contract_name = self.contract_name.as_contract_name().contract_name();
1449        let receive_name = OwnedReceiveName::try_from(format!("{contract_name}.{entrypoint}"))?;
1450
1451        let payload = UpdateContractPayload {
1452            amount,
1453            address: self.address,
1454            receive_name,
1455            message,
1456        };
1457
1458        let context = ContractContext::new_from_payload(sender, None, payload);
1459
1460        let invoke_result = self
1461            .client
1462            .invoke_instance(BlockIdentifier::LastFinal, &context)
1463            .await?
1464            .response;
1465        let payload = UpdateContractPayload {
1466            amount,
1467            address: context.contract,
1468            receive_name: context.method,
1469            message: context.parameter,
1470        };
1471
1472        match invoke_result {
1473            InvokeContractResult::Success {
1474                used_energy,
1475                return_value,
1476                events,
1477            } => Ok(ContractUpdateBuilder::new(
1478                self.client.clone(),
1479                sender,
1480                used_energy,
1481                transactions::Payload::Update { payload },
1482                ContractUpdateInner {
1483                    return_value,
1484                    events,
1485                },
1486            )),
1487            InvokeContractResult::Failure { reason, .. } => Err(reason.into()),
1488        }
1489    }
1490
1491    /// Like [`dry_run_update_with_reject_reason_info`](Self::dry_run_update_with_reject_reason_info) but expects an already
1492    /// formed parameter.
1493    pub async fn dry_run_update_raw_with_reject_reason_info<E>(
1494        &mut self,
1495        entrypoint: &str,
1496        amount: Amount,
1497        sender: AccountAddress,
1498        message: OwnedParameter,
1499    ) -> Result<InvokeContractOutcome, E>
1500    where
1501        E: From<NewReceiveNameError> + From<v2::QueryError>,
1502    {
1503        let contract_name = self.contract_name.as_contract_name().contract_name();
1504        let receive_name = OwnedReceiveName::try_from(format!("{contract_name}.{entrypoint}"))?;
1505
1506        let payload = UpdateContractPayload {
1507            amount,
1508            address: self.address,
1509            receive_name: receive_name.clone(),
1510            message,
1511        };
1512
1513        let context = ContractContext::new_from_payload(sender, None, payload.clone());
1514
1515        let invoke_result = self
1516            .client
1517            .invoke_instance(BlockIdentifier::LastFinal, &context)
1518            .await?
1519            .response;
1520
1521        match invoke_result {
1522            InvokeContractResult::Success {
1523                used_energy,
1524                return_value,
1525                events,
1526            } => Ok(InvokeContractOutcome::Success(SimulatedTransaction::new(
1527                self.client.clone(),
1528                sender,
1529                used_energy,
1530                transactions::Payload::Update { payload },
1531                ContractUpdateInner {
1532                    return_value,
1533                    events,
1534                },
1535            ))),
1536            InvokeContractResult::Failure {
1537                reason,
1538                return_value,
1539                used_energy,
1540            } => {
1541                let decoded_reason = reason.as_ref().known().and_then(|reason| {
1542                    decode_smart_contract_revert(
1543                        return_value.as_ref(),
1544                        reason,
1545                        (*self.schema).as_ref(),
1546                    )
1547                });
1548
1549                Ok(InvokeContractOutcome::Failure(RejectedTransaction {
1550                    payload: transactions::Payload::Update { payload },
1551                    return_value,
1552                    used_energy,
1553                    reason,
1554                    decoded_reason,
1555                }))
1556            }
1557        }
1558    }
1559
1560    /// Make the payload of a contract update with the specified parameter.
1561    pub fn make_update<P: contracts_common::Serial, E>(
1562        &self,
1563        signer: &impl transactions::ExactSizeTransactionSigner,
1564        metadata: &ContractTransactionMetadata,
1565        entrypoint: &str,
1566        message: &P,
1567    ) -> Result<AccountTransaction<EncodedPayload>, E>
1568    where
1569        E: From<NewReceiveNameError> + From<ExceedsParameterSize>,
1570    {
1571        let message = OwnedParameter::from_serial(message)?;
1572        self.make_update_raw::<E>(signer, metadata, entrypoint, message)
1573    }
1574
1575    /// Make **and send** a transaction with the specified parameter.
1576    pub async fn update<P: contracts_common::Serial, E>(
1577        &mut self,
1578        signer: &impl transactions::ExactSizeTransactionSigner,
1579        metadata: &ContractTransactionMetadata,
1580        entrypoint: &str,
1581        message: &P,
1582    ) -> Result<TransactionHash, E>
1583    where
1584        E: From<NewReceiveNameError> + From<v2::RPCError> + From<ExceedsParameterSize>,
1585    {
1586        let message = OwnedParameter::from_serial(message)?;
1587        self.update_raw::<E>(signer, metadata, entrypoint, message)
1588            .await
1589    }
1590
1591    /// Like [`update`](Self::update) but expects a serialized parameter.
1592    pub async fn update_raw<E>(
1593        &mut self,
1594        signer: &impl transactions::ExactSizeTransactionSigner,
1595        metadata: &ContractTransactionMetadata,
1596        entrypoint: &str,
1597        message: OwnedParameter,
1598    ) -> Result<TransactionHash, E>
1599    where
1600        E: From<NewReceiveNameError> + From<v2::RPCError>,
1601    {
1602        let tx = self.make_update_raw::<E>(signer, metadata, entrypoint, message)?;
1603        let hash = self.client.send_account_transaction(tx).await?;
1604        Ok(hash)
1605    }
1606
1607    /// Like [`make_update`](Self::make_update) but expects a serialized
1608    /// parameter.
1609    pub fn make_update_raw<E>(
1610        &self,
1611        signer: &impl transactions::ExactSizeTransactionSigner,
1612        metadata: &ContractTransactionMetadata,
1613        entrypoint: &str,
1614        message: OwnedParameter,
1615    ) -> Result<AccountTransaction<EncodedPayload>, E>
1616    where
1617        E: From<NewReceiveNameError>,
1618    {
1619        let contract_name = self.contract_name.as_contract_name().contract_name();
1620        let receive_name = OwnedReceiveName::try_from(format!("{contract_name}.{entrypoint}"))?;
1621
1622        let payload = UpdateContractPayload {
1623            amount: metadata.amount,
1624            address: self.address,
1625            receive_name,
1626            message,
1627        };
1628
1629        let tx = transactions::send::make_and_sign_transaction(
1630            signer,
1631            metadata.sender_address,
1632            metadata.nonce,
1633            metadata.expiry,
1634            metadata.energy,
1635            transactions::Payload::Update { payload },
1636        );
1637        Ok(tx)
1638    }
1639}
1640
1641/// A helper type to construct [`ContractUpdateBuilder`].
1642/// Users do not directly interact with values of this type.
1643pub struct ContractUpdateInner {
1644    return_value: Option<ReturnValue>,
1645    events: Vec<Upward<ContractTraceElement>>,
1646}
1647
1648/// A builder to simplify sending smart contract updates.
1649pub type ContractUpdateBuilder = TransactionBuilder<true, ContractUpdateInner>;
1650
1651impl ContractUpdateBuilder {
1652    /// Send the transaction and return a handle that can be queried
1653    /// for the status.
1654    pub async fn send(
1655        self,
1656        signer: &impl transactions::ExactSizeTransactionSigner,
1657    ) -> v2::QueryResult<ContractUpdateHandle> {
1658        self.send_inner(signer, |tx_hash, client| ContractUpdateHandle {
1659            tx_hash,
1660            client,
1661        })
1662        .await
1663    }
1664
1665    /// Get the return value from dry-running.
1666    pub fn return_value(&self) -> Option<&ReturnValue> {
1667        self.inner.return_value.as_ref()
1668    }
1669
1670    /// Get the events generated from the dry-run.
1671    ///
1672    /// Since newer versions of the Concordium Node API might introduce new
1673    /// variants of [`ContractTraceElement`] the result might contain
1674    /// [`Upward::Unknown`].
1675    pub fn events(&self) -> &[Upward<ContractTraceElement>] {
1676        &self.inner.events
1677    }
1678}
1679
1680/// A handle returned when sending a smart contract update transaction.
1681/// This can be used to get the response of the update.
1682///
1683/// Note that this handle retains a connection to the node. So if it is not
1684/// going to be used it should be dropped.
1685pub struct ContractUpdateHandle {
1686    tx_hash: TransactionHash,
1687    client: v2::Client,
1688}
1689
1690/// The [`Display`](std::fmt::Display) implementation displays the hash of the
1691/// transaction.
1692impl std::fmt::Display for ContractUpdateHandle {
1693    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1694        self.tx_hash.fmt(f)
1695    }
1696}
1697
1698#[derive(Debug, thiserror::Error)]
1699/// An error that may occur when querying the result of a smart contract update
1700/// transaction.
1701pub enum ContractUpdateError {
1702    #[error("The status of the transaction could not be ascertained: {0}")]
1703    Query(#[from] QueryError),
1704    #[error("Contract update failed with reason: {0:?}")]
1705    Failed(v2::Upward<RejectReason>),
1706}
1707
1708impl ContractUpdateHandle {
1709    /// Extract the hash of the transaction underlying this handle.
1710    pub fn hash(&self) -> TransactionHash {
1711        self.tx_hash
1712    }
1713
1714    /// Wait until the transaction is finalized and return the result.
1715    /// Note that this can potentially wait indefinitely.
1716    pub async fn wait_for_finalization(
1717        mut self,
1718    ) -> Result<ContractUpdateInfo, ContractUpdateError> {
1719        let (_, result) = self.client.wait_until_finalized(&self.tx_hash).await?;
1720
1721        let mk_error = |msg| {
1722            Err(ContractUpdateError::from(QueryError::RPCError(
1723                RPCError::CallError(tonic::Status::invalid_argument(msg)),
1724            )))
1725        };
1726        let v2::upward::Upward::Known(details) = result.details else {
1727            return mk_error(
1728                "Expected smart contract update status, but received unknown block item details.",
1729            );
1730        };
1731        match details {
1732            crate::types::BlockItemSummaryDetails::AccountTransaction(at) => {
1733                let v2::upward::Upward::Known(effects) = at.effects else {
1734                    return mk_error(
1735                        "Expected smart contract update status, but received unknown block item \
1736                         effects.",
1737                    );
1738                };
1739                match effects {
1740                    AccountTransactionEffects::ContractUpdateIssued { effects } => {
1741                        let Some(execution_tree) = crate::types::execution_tree(effects) else {
1742                            return mk_error(
1743                                "Expected smart contract update, but received invalid execution \
1744                                 tree.",
1745                            );
1746                        };
1747                        Ok(ContractUpdateInfo {
1748                            execution_tree,
1749                            energy_cost: result.energy_cost,
1750                            cost: at.cost,
1751                            transaction_hash: self.tx_hash,
1752                            sender: at.sender,
1753                        })
1754                    }
1755                    AccountTransactionEffects::None {
1756                        transaction_type: _,
1757                        reject_reason,
1758                    } => Err(ContractUpdateError::Failed(reject_reason)),
1759                    _ => mk_error("Expected smart contract update status, but did not receive it."),
1760                }
1761            }
1762            crate::types::BlockItemSummaryDetails::AccountCreation(_) => {
1763                mk_error("Expected smart contract update status, but received account creation.")
1764            }
1765            crate::types::BlockItemSummaryDetails::Update(_) => mk_error(
1766                "Expected smart contract update status, but received chain update instruction.",
1767            ),
1768            crate::types::BlockItemSummaryDetails::TokenCreationDetails(_) => mk_error(
1769                "Expected smart contract update status, but received token creation chain update \
1770                 instruction.",
1771            ),
1772        }
1773    }
1774
1775    /// Wait until the transaction is finalized or until the timeout has elapsed
1776    /// and return the result.
1777    pub async fn wait_for_finalization_timeout(
1778        self,
1779        timeout: std::time::Duration,
1780    ) -> Result<ContractUpdateInfo, ContractUpdateError> {
1781        let result = tokio::time::timeout(timeout, self.wait_for_finalization()).await;
1782        match result {
1783            Ok(r) => r,
1784            Err(_elapsed) => Err(ContractUpdateError::Query(QueryError::RPCError(
1785                RPCError::CallError(tonic::Status::deadline_exceeded(
1786                    "Deadline waiting for result of transaction is exceeded.",
1787                )),
1788            ))),
1789        }
1790    }
1791}