concordium_rust_sdk/
cis0.rs

1//! This module contains types and functions for interacting with smart
2//! contracts following the [CIS-0](https://proposals.concordium.software/CIS/cis-0.html) specification.
3use crate::{
4    types::{self as sdk_types, smart_contracts::ContractContext},
5    v2::{BlockIdentifier, QueryResponse},
6};
7use concordium_base::contracts_common::{
8    Amount, ContractName, EntrypointName, OwnedReceiveName, ParseError,
9};
10use sdk_types::{smart_contracts, ContractAddress};
11use smart_contracts::concordium_contracts_common as contracts_common;
12use thiserror::*;
13
14/// The query result type for whether a smart contract supports a standard.
15#[derive(Debug, Clone)]
16pub enum SupportResult {
17    /// The standard is not supported.
18    NoSupport,
19    /// The standard is supported by the current contract address.
20    Support,
21    /// The standard is supported by using another contract address.
22    SupportBy(Vec<ContractAddress>),
23}
24
25impl SupportResult {
26    /// Return whether the result is [`Support`](Self::Support) or not.
27    pub fn is_support(&self) -> bool { matches!(self, &Self::Support) }
28}
29
30impl contracts_common::Serial for SupportResult {
31    fn serial<W: contracts_common::Write>(&self, out: &mut W) -> Result<(), W::Err> {
32        match self {
33            SupportResult::NoSupport => out.write_u8(0u8),
34            SupportResult::Support => out.write_u8(1u8),
35            SupportResult::SupportBy(addrs) => {
36                out.write_u8(2)?;
37                out.write_u8(addrs.len() as u8)?;
38                for addr in addrs {
39                    addr.serial(out)?;
40                }
41                Ok(())
42            }
43        }
44    }
45}
46
47impl contracts_common::Deserial for SupportResult {
48    fn deserial<R: contracts_common::Read>(source: &mut R) -> contracts_common::ParseResult<Self> {
49        match source.read_u8()? {
50            0u8 => Ok(Self::NoSupport),
51            1u8 => Ok(Self::Support),
52            2u8 => {
53                let len = source.read_u8()?;
54                let mut out = Vec::new();
55                for _ in 0..len {
56                    out.push(ContractAddress::deserial(source)?);
57                }
58                Ok(Self::SupportBy(out))
59            }
60            _ => Err(contracts_common::ParseError {}),
61        }
62    }
63}
64
65/// The response which is sent back when calling the contract function
66/// `supports`. It consists of a list of results corresponding to the list of
67/// queries.
68#[derive(Debug, Clone)]
69pub struct SupportsQueryResponse {
70    /// List of support results corresponding to the list of queries.
71    pub results: Vec<SupportResult>,
72}
73
74impl contracts_common::Deserial for SupportsQueryResponse {
75    fn deserial<R: contracts_common::Read>(source: &mut R) -> contracts_common::ParseResult<Self> {
76        let len = u16::deserial(source)?;
77        let mut results = Vec::new();
78        for _ in 0..len {
79            results.push(SupportResult::deserial(source)?)
80        }
81        Ok(Self { results })
82    }
83}
84
85impl contracts_common::Serial for SupportsQueryResponse {
86    fn serial<W: contracts_common::Write>(&self, out: &mut W) -> Result<(), W::Err> {
87        (self.results.len() as u16).serial(out)?;
88        for result in &self.results {
89            result.serial(out)?;
90        }
91        Ok(())
92    }
93}
94
95/// Identifier for a smart contract standard.
96#[non_exhaustive]
97#[derive(Clone, Debug)]
98pub enum StandardIdentifier {
99    CIS0,
100    CIS1,
101    CIS2,
102    CIS3,
103    CIS4,
104    Other(String),
105}
106
107impl std::fmt::Display for StandardIdentifier {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        match self {
110            StandardIdentifier::CIS0 => f.write_str("CIS-0"),
111            StandardIdentifier::CIS1 => f.write_str("CIS-1"),
112            StandardIdentifier::CIS2 => f.write_str("CIS-2"),
113            StandardIdentifier::CIS3 => f.write_str("CIS-3"),
114            StandardIdentifier::CIS4 => f.write_str("CIS-4"),
115            StandardIdentifier::Other(s) => f.write_str(s),
116        }
117    }
118}
119
120#[derive(Error, Debug, Clone)]
121/// The standard name is invalid and could not be parsed.
122#[error("Invalid CIS standard name.")]
123pub struct InvalidStandardName;
124
125impl std::str::FromStr for StandardIdentifier {
126    type Err = InvalidStandardName;
127
128    fn from_str(s: &str) -> Result<Self, Self::Err> {
129        match s {
130            "CIS-0" => Ok(Self::CIS0),
131            "CIS-1" => Ok(Self::CIS1),
132            "CIS-2" => Ok(Self::CIS2),
133            "CIS-3" => Ok(Self::CIS3),
134            "CIS-4" => Ok(Self::CIS4),
135            other if other.is_ascii() && other.len() <= 255 => Ok(Self::Other(other.into())),
136            _ => Err(InvalidStandardName),
137        }
138    }
139}
140
141impl contracts_common::Serial for StandardIdentifier {
142    fn serial<W: contracts_common::Write>(&self, out: &mut W) -> Result<(), W::Err> {
143        match self {
144            StandardIdentifier::CIS0 => {
145                out.write_u8(5)?; // length
146                out.write_all(b"CIS-0")
147            }
148            StandardIdentifier::CIS1 => {
149                out.write_u8(5)?; // length
150                out.write_all(b"CIS-1")
151            }
152            StandardIdentifier::CIS2 => {
153                out.write_u8(5)?; // length
154                out.write_all(b"CIS-2")
155            }
156            StandardIdentifier::CIS3 => {
157                out.write_u8(5)?; // length
158                out.write_all(b"CIS-3")
159            }
160            StandardIdentifier::CIS4 => {
161                out.write_u8(5)?; // length
162                out.write_all(b"CIS-4")
163            }
164            StandardIdentifier::Other(s) => {
165                out.write_u8(s.len() as u8)?;
166                out.write_all(s.as_bytes())
167            }
168        }
169    }
170}
171
172#[derive(Error, Debug)]
173/// Errors that may occur when querying a contract.
174pub enum SupportsError {
175    #[error("The name of the contract is not valid and thus the contract does not support CIS-0.")]
176    ContractNameInvalid,
177    #[error("Parameter size exceeds maximum allowed. Too many ids.")]
178    InvalidParameter,
179    #[error("Query error: {0}")]
180    QueryError(#[from] super::v2::QueryError),
181    #[error("Contract reject.")]
182    ContractReject,
183    #[error("No return. This is a V0 contract, and V0 contracts do not support CIS-0.")]
184    NoReturn,
185    #[error("Parsing result failed.")]
186    ParseError(#[from] ParseError),
187    #[error("The contract return an inconsistent result.")]
188    InvalidResponse,
189}
190
191/// Return whether the contract supports standards in the list
192/// at the end of the given block.
193///
194/// In case of success the return list of [`SupportsQueryResponse`] values
195/// will have the same length as the input list of `ids`.
196pub async fn supports_multi(
197    client: &mut super::v2::Client,
198    bi: &BlockIdentifier,
199    addr: ContractAddress,
200    name: ContractName<'_>,
201    ids: &[StandardIdentifier],
202) -> Result<super::v2::QueryResponse<SupportsQueryResponse>, SupportsError> {
203    use contracts_common::{Deserial, Serial};
204    let method = OwnedReceiveName::construct(name, EntrypointName::new_unchecked("supports"))
205        .map_err(|_| SupportsError::ContractNameInvalid)?;
206    let mut parameters = Vec::new();
207    (ids.len() as u16)
208        .serial(&mut parameters)
209        .map_err(|_| SupportsError::InvalidParameter)?;
210    for id in ids {
211        id.serial(&mut parameters)
212            .map_err(|_| SupportsError::InvalidParameter)?;
213    }
214    let parameter = smart_contracts::OwnedParameter::try_from(parameters)
215        .map_err(|_| SupportsError::InvalidParameter)?;
216    let ctx = ContractContext {
217        invoker: None,
218        contract: addr,
219        amount: Amount::from_micro_ccd(0),
220        method,
221        parameter,
222        energy: None,
223    };
224    let res = client.invoke_instance(bi, &ctx).await?;
225    match res.response {
226        smart_contracts::InvokeContractResult::Success { return_value, .. } => match return_value {
227            Some(rv) => {
228                let response =
229                    SupportsQueryResponse::deserial(&mut contracts_common::Cursor::new(&rv.value))?;
230                if response.results.len() != ids.len() {
231                    return Err(SupportsError::InvalidResponse);
232                }
233                Ok(QueryResponse {
234                    block_hash: res.block_hash,
235                    response,
236                })
237            }
238            None => Err(SupportsError::NoReturn),
239        },
240        smart_contracts::InvokeContractResult::Failure { .. } => Err(SupportsError::ContractReject),
241    }
242}
243
244/// A simplified version of [`supports_multi`] that only supports
245/// querying for a single standard, but has a simpler API.
246pub async fn supports(
247    client: &mut super::v2::Client,
248    bi: &BlockIdentifier,
249    addr: ContractAddress,
250    name: ContractName<'_>,
251    ids: StandardIdentifier,
252) -> Result<super::v2::QueryResponse<SupportResult>, SupportsError> {
253    let mut response = supports_multi(client, bi, addr, name, &[ids]).await?;
254    if let Some(r) = response.response.results.pop() {
255        Ok(QueryResponse {
256            block_hash: response.block_hash,
257            response:   r,
258        })
259    } else {
260        Err(SupportsError::InvalidResponse)
261    }
262}