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