use crate::{
types::{self as sdk_types, smart_contracts::ContractContext},
v2::{BlockIdentifier, QueryResponse},
};
use concordium_base::contracts_common::{
Amount, ContractName, EntrypointName, OwnedReceiveName, ParseError,
};
use sdk_types::{smart_contracts, ContractAddress};
use smart_contracts::concordium_contracts_common as contracts_common;
use thiserror::*;
#[derive(Debug, Clone)]
pub enum SupportResult {
NoSupport,
Support,
SupportBy(Vec<ContractAddress>),
}
impl SupportResult {
pub fn is_support(&self) -> bool {
matches!(self, &Self::Support)
}
}
impl contracts_common::Serial for SupportResult {
fn serial<W: contracts_common::Write>(&self, out: &mut W) -> Result<(), W::Err> {
match self {
SupportResult::NoSupport => out.write_u8(0u8),
SupportResult::Support => out.write_u8(1u8),
SupportResult::SupportBy(addrs) => {
out.write_u8(2)?;
out.write_u8(addrs.len() as u8)?;
for addr in addrs {
addr.serial(out)?;
}
Ok(())
}
}
}
}
impl contracts_common::Deserial for SupportResult {
fn deserial<R: contracts_common::Read>(source: &mut R) -> contracts_common::ParseResult<Self> {
match source.read_u8()? {
0u8 => Ok(Self::NoSupport),
1u8 => Ok(Self::Support),
2u8 => {
let len = source.read_u8()?;
let mut out = Vec::new();
for _ in 0..len {
out.push(ContractAddress::deserial(source)?);
}
Ok(Self::SupportBy(out))
}
_ => Err(contracts_common::ParseError {}),
}
}
}
#[derive(Debug, Clone)]
pub struct SupportsQueryResponse {
pub results: Vec<SupportResult>,
}
impl contracts_common::Deserial for SupportsQueryResponse {
fn deserial<R: contracts_common::Read>(source: &mut R) -> contracts_common::ParseResult<Self> {
let len = u16::deserial(source)?;
let mut results = Vec::new();
for _ in 0..len {
results.push(SupportResult::deserial(source)?)
}
Ok(Self { results })
}
}
impl contracts_common::Serial for SupportsQueryResponse {
fn serial<W: contracts_common::Write>(&self, out: &mut W) -> Result<(), W::Err> {
(self.results.len() as u16).serial(out)?;
for result in &self.results {
result.serial(out)?;
}
Ok(())
}
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum StandardIdentifier {
CIS0,
CIS1,
CIS2,
CIS3,
CIS4,
Other(String),
}
impl std::fmt::Display for StandardIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StandardIdentifier::CIS0 => f.write_str("CIS-0"),
StandardIdentifier::CIS1 => f.write_str("CIS-1"),
StandardIdentifier::CIS2 => f.write_str("CIS-2"),
StandardIdentifier::CIS3 => f.write_str("CIS-3"),
StandardIdentifier::CIS4 => f.write_str("CIS-4"),
StandardIdentifier::Other(s) => f.write_str(s),
}
}
}
#[derive(Error, Debug, Clone)]
#[error("Invalid CIS standard name.")]
pub struct InvalidStandardName;
impl std::str::FromStr for StandardIdentifier {
type Err = InvalidStandardName;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"CIS-0" => Ok(Self::CIS0),
"CIS-1" => Ok(Self::CIS1),
"CIS-2" => Ok(Self::CIS2),
"CIS-3" => Ok(Self::CIS3),
"CIS-4" => Ok(Self::CIS4),
other if other.is_ascii() && other.len() <= 255 => Ok(Self::Other(other.into())),
_ => Err(InvalidStandardName),
}
}
}
impl contracts_common::Serial for StandardIdentifier {
fn serial<W: contracts_common::Write>(&self, out: &mut W) -> Result<(), W::Err> {
match self {
StandardIdentifier::CIS0 => {
out.write_u8(5)?; out.write_all(b"CIS-0")
}
StandardIdentifier::CIS1 => {
out.write_u8(5)?; out.write_all(b"CIS-1")
}
StandardIdentifier::CIS2 => {
out.write_u8(5)?; out.write_all(b"CIS-2")
}
StandardIdentifier::CIS3 => {
out.write_u8(5)?; out.write_all(b"CIS-3")
}
StandardIdentifier::CIS4 => {
out.write_u8(5)?; out.write_all(b"CIS-4")
}
StandardIdentifier::Other(s) => {
out.write_u8(s.len() as u8)?;
out.write_all(s.as_bytes())
}
}
}
}
#[derive(Error, Debug)]
pub enum SupportsError {
#[error("The name of the contract is not valid and thus the contract does not support CIS-0.")]
ContractNameInvalid,
#[error("Parameter size exceeds maximum allowed. Too many ids.")]
InvalidParameter,
#[error("Query error: {0}")]
QueryError(#[from] super::v2::QueryError),
#[error("Contract reject.")]
ContractReject,
#[error("No return. This is a V0 contract, and V0 contracts do not support CIS-0.")]
NoReturn,
#[error("Parsing result failed.")]
ParseError(#[from] ParseError),
#[error("The contract return an inconsistent result.")]
InvalidResponse,
}
pub async fn supports_multi(
client: &mut super::v2::Client,
bi: &BlockIdentifier,
addr: ContractAddress,
name: ContractName<'_>,
ids: &[StandardIdentifier],
) -> Result<super::v2::QueryResponse<SupportsQueryResponse>, SupportsError> {
use contracts_common::{Deserial, Serial};
let method = OwnedReceiveName::construct(name, EntrypointName::new_unchecked("supports"))
.map_err(|_| SupportsError::ContractNameInvalid)?;
let mut parameters = Vec::new();
(ids.len() as u16)
.serial(&mut parameters)
.map_err(|_| SupportsError::InvalidParameter)?;
for id in ids {
id.serial(&mut parameters)
.map_err(|_| SupportsError::InvalidParameter)?;
}
let parameter = smart_contracts::OwnedParameter::try_from(parameters)
.map_err(|_| SupportsError::InvalidParameter)?;
let ctx = ContractContext {
invoker: None,
contract: addr,
amount: Amount::from_micro_ccd(0),
method,
parameter,
energy: None,
};
let res = client.invoke_instance(bi, &ctx).await?;
match res.response {
smart_contracts::InvokeContractResult::Success { return_value, .. } => match return_value {
Some(rv) => {
let response =
SupportsQueryResponse::deserial(&mut contracts_common::Cursor::new(&rv.value))?;
if response.results.len() != ids.len() {
return Err(SupportsError::InvalidResponse);
}
Ok(QueryResponse {
block_hash: res.block_hash,
response,
})
}
None => Err(SupportsError::NoReturn),
},
smart_contracts::InvokeContractResult::Failure { .. } => Err(SupportsError::ContractReject),
}
}
pub async fn supports(
client: &mut super::v2::Client,
bi: &BlockIdentifier,
addr: ContractAddress,
name: ContractName<'_>,
ids: StandardIdentifier,
) -> Result<super::v2::QueryResponse<SupportResult>, SupportsError> {
let mut response = supports_multi(client, bi, addr, name, &[ids]).await?;
if let Some(r) = response.response.results.pop() {
Ok(QueryResponse {
block_hash: response.block_hash,
response: r,
})
} else {
Err(SupportsError::InvalidResponse)
}
}