Skip to main content

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