concordium_rust_sdk/cis3.rs
1//! This module contains types and functions for interacting with smart
2//! contracts following the [CIS-3](https://proposals.concordium.software/CIS/cis-3.html) specification.
3use crate::{
4 contract_client::{ContractClient, ContractTransactionMetadata},
5 types as sdk_types,
6 types::transactions,
7 v2::IntoBlockIdentifier,
8};
9use concordium_base::{
10 base::Energy,
11 cis3_types::{
12 NewSupportsPermitQueryParamsError, PermitParams, SupportsPermitQueryParams,
13 SupportsPermitRepsonse,
14 },
15 contracts_common::{Address, Amount, OwnedEntrypointName},
16 smart_contracts::OwnedParameter,
17 transactions::{AccountTransaction, EncodedPayload},
18};
19use sdk_types::smart_contracts;
20use smart_contracts::concordium_contracts_common;
21use thiserror::Error;
22
23#[derive(Debug, Clone, Copy)]
24/// A marker type to indicate that a [`ContractClient`] is a client for a `CIS3`
25/// contract.
26pub enum Cis3Type {}
27
28/// A wrapper around the client representing a CIS3 token smart contract, which
29/// provides functions for interaction specific to CIS3 contracts.
30///
31/// Note that cloning is cheap and is, therefore, the intended way of sharing
32/// this type between multiple tasks.
33///
34/// See also [`ContractClient`] for generic methods available for any contract.
35pub type Cis3Contract = ContractClient<Cis3Type>;
36
37/// Error which can occur when calling [`permit`](Cis3Contract::permit).
38#[derive(Error, Debug)]
39pub enum Cis3PermitError {
40 /// The smart contract receive name is invalid.
41 #[error("Invalid receive name: {0}")]
42 InvalidReceiveName(#[from] concordium_contracts_common::NewReceiveNameError),
43
44 /// A general RPC error occured.
45 #[error("RPC error: {0}")]
46 RPCError(#[from] crate::endpoints::RPCError),
47}
48
49/// Error which can occur when calling [`permit`](Cis3Contract::permit_dry_run).
50#[derive(Error, Debug)]
51pub enum Cis3PermitDryRunError {
52 /// The smart contract receive name is invalid.
53 #[error("Invalid receive name: {0}")]
54 InvalidReceiveName(#[from] concordium_contracts_common::NewReceiveNameError),
55
56 /// A general RPC error occured.
57 #[error("RPC error: {0}")]
58 RPCError(#[from] crate::endpoints::RPCError),
59
60 /// An error occurred when querying the node.
61 #[error("RPC error: {0}")]
62 QueryError(#[from] crate::endpoints::QueryError),
63
64 /// The node rejected the invocation.
65 #[error("Rejected by the node: {0:?}.")]
66 NodeRejected(sdk_types::RejectReason),
67}
68
69/// Error which can occur when calling
70/// [`supportsPermit`](Cis3Contract::supports_permit).
71#[derive(Debug, Error)]
72pub enum Cis3SupportsPermitError {
73 /// The smart contract receive name is invalid.
74 #[error("Invalid receive name: {0}")]
75 InvalidReceiveName(#[from] concordium_contracts_common::NewReceiveNameError),
76
77 /// The parameter is invalid.
78 #[error("Invalid supportsPermit parameter: {0}")]
79 InvalidParams(#[from] NewSupportsPermitQueryParamsError),
80
81 /// A general RPC error occured.
82 #[error("RPC error: {0}")]
83 RPCError(#[from] super::v2::QueryError),
84
85 /// The returned bytes from invoking the smart contract could not be parsed.
86 #[error("Failed parsing the response.")]
87 ResponseParseError(#[from] concordium_contracts_common::ParseError),
88
89 /// The node rejected the invocation.
90 #[error("Rejected by the node: {0:?}.")]
91 NodeRejected(sdk_types::RejectReason),
92}
93
94// This is implemented manually, since deriving it using thiserror requires
95// `RejectReason` to implement std::error::Error.
96impl From<sdk_types::RejectReason> for Cis3SupportsPermitError {
97 fn from(err: sdk_types::RejectReason) -> Self { Self::NodeRejected(err) }
98}
99
100// This is implemented manually, since deriving it using thiserror requires
101// `RejectReason` to implement std::error::Error.
102impl From<sdk_types::RejectReason> for Cis3PermitDryRunError {
103 fn from(err: sdk_types::RejectReason) -> Self { Self::NodeRejected(err) }
104}
105
106impl Cis3Contract {
107 /// Like [`permit`](Self::permit) except it only dry-runs the transaction
108 /// to get the response and, in case of success, amount of energy used
109 /// for execution.
110 ///
111 /// # Arguments
112 ///
113 /// * `bi` - The block to query. The query will be executed in the state of
114 /// the chain at the end of the block.
115 /// * `sender` - The (sponsor) address that is invoking the entrypoint.
116 /// * `params` - The parameters for the permit invocation. Includes the
117 /// signature of the sponsoree, the address of the sponsoree, and the
118 /// signed message.
119 pub async fn permit_dry_run(
120 &mut self,
121 bi: impl IntoBlockIdentifier,
122 sender: Address,
123 params: PermitParams,
124 ) -> Result<Energy, Cis3PermitDryRunError> {
125 let parameter = OwnedParameter::from_serial(¶ms)
126 .expect("A PermitParams should always be serializable");
127 let ir = self
128 .invoke_raw::<Cis3PermitDryRunError>(
129 "permit",
130 Amount::zero(),
131 Some(sender),
132 parameter,
133 bi,
134 )
135 .await?;
136 match ir {
137 smart_contracts::InvokeContractResult::Success { used_energy, .. } => Ok(used_energy),
138 smart_contracts::InvokeContractResult::Failure { reason, .. } => Err(reason.into()),
139 }
140 }
141
142 /// Construct **and send** a CIS3 sponsored transaction. This function takes
143 /// a signature from the sponsoree along with their account address, and
144 /// the signed message to be executed by the contract. Returns a [`Result`]
145 /// with the transaction hash.
146 ///
147 /// # Arguments
148 ///
149 /// * `signer` - The account keys (of the sponsor) to use for signing the
150 /// smart contract update transaction.
151 /// * `metadata` - Metadata for constructing the transaction.
152 /// * `params` - The parameters for the permit invocation. Includes the
153 /// signature of the sponsoree, the address of the sponsoree, and the
154 /// signed message.
155 pub async fn permit(
156 &mut self,
157 signer: &impl transactions::ExactSizeTransactionSigner,
158 metadata: &ContractTransactionMetadata,
159 params: PermitParams,
160 ) -> Result<sdk_types::hashes::TransactionHash, Cis3PermitError> {
161 let permit = self.make_permit(signer, metadata, params)?;
162 let hash = self.client.send_account_transaction(permit).await?;
163 Ok(hash)
164 }
165
166 /// Construct a CIS3 sponsored transaction. This function takes a signature
167 /// from the sponsoree along with their account address, and the signed
168 /// message to be executed by the contract. Returns a [`Result`] with the
169 /// transaction hash.
170 ///
171 /// # Arguments
172 ///
173 /// * `signer` - The account keys (of the sponsor) to use for signing the
174 /// smart contract update transaction.
175 /// * `metadata` - Metadata for constructing the transaction.
176 /// * `params` - The parameters for the permit invocation. Includes the
177 /// signature of the sponsoree, the address of the sponsoree, and the
178 /// signed message.
179 pub fn make_permit(
180 &mut self,
181 signer: &impl transactions::ExactSizeTransactionSigner,
182 metadata: &ContractTransactionMetadata,
183 params: PermitParams,
184 ) -> Result<AccountTransaction<EncodedPayload>, Cis3PermitError> {
185 let message = smart_contracts::OwnedParameter::from_serial(¶ms)
186 .expect("A PermitParams should always be serializable");
187 self.make_update_raw(signer, metadata, "permit", message)
188 }
189
190 /// Invoke the CIS3 `supportsPermit` query given a list of entrypoints to
191 /// query.
192 ///
193 /// Note: the query is executed locally by the node and does not produce a
194 /// transaction on-chain.
195 ///
196 /// # Arguments
197 ///
198 /// * `bi` - The block to query. The query will be executed in the state of
199 /// the chain at the end of the block.
200 /// * `entrypoints` - A list queries to execute.
201 pub async fn supports_permit(
202 &mut self,
203 bi: impl IntoBlockIdentifier,
204 entrypoints: Vec<OwnedEntrypointName>,
205 ) -> Result<SupportsPermitRepsonse, Cis3SupportsPermitError> {
206 let parameter = SupportsPermitQueryParams::new(entrypoints)?;
207 let message = OwnedParameter::from_serial(¶meter).map_err(|_| {
208 Cis3SupportsPermitError::InvalidParams(NewSupportsPermitQueryParamsError)
209 })?;
210 self.view_raw("supportsPermit", message, bi).await
211 }
212
213 /// Like [`supports_permit`](Self::supports_permit), but only queries a
214 /// single entrypoint, and returns a bool indicating whether the entrypoint
215 /// is supported.
216 pub async fn supports_permit_single(
217 &mut self,
218 bi: impl IntoBlockIdentifier,
219 entrypoint: OwnedEntrypointName,
220 ) -> Result<bool, Cis3SupportsPermitError> {
221 only_one(self.supports_permit(bi, vec![entrypoint]).await?)
222 }
223}
224
225/// Extract an element from the given vector if the vector has exactly one
226/// element. Otherwise raise a parse error.
227fn only_one<A, V: AsRef<Vec<A>>>(res: V) -> Result<A, Cis3SupportsPermitError>
228where
229 Vec<A>: From<V>, {
230 let err =
231 Cis3SupportsPermitError::ResponseParseError(concordium_contracts_common::ParseError {});
232 if res.as_ref().len() > 1 {
233 Err(err)
234 } else {
235 Vec::from(res).pop().ok_or(err)
236 }
237}