use crate::{
contract_client::*,
types::{transactions, RejectReason},
v2::{self, IntoBlockIdentifier},
};
pub use concordium_base::{cis2_types::MetadataUrl, cis4_types::*};
use concordium_base::{
constants::MAX_PARAMETER_LEN,
contracts_common,
hashes::TransactionHash,
smart_contracts::{ExceedsParameterSize, OwnedParameter},
transactions::{AccountTransaction, EncodedPayload},
web3id::{CredentialHolderId, Web3IdSigner, REVOKE_DOMAIN_STRING},
};
#[derive(thiserror::Error, Debug)]
pub enum Cis4QueryError {
#[error("Invalid receive name: {0}")]
InvalidReceiveName(#[from] contracts_common::NewReceiveNameError),
#[error("RPC error: {0}")]
RPCError(#[from] super::v2::QueryError),
#[error("Failed parsing the response.")]
ResponseParseError(#[from] contracts_common::ParseError),
#[error("Rejected by the node: {0:?}.")]
NodeRejected(v2::Upward<crate::types::RejectReason>),
}
impl From<v2::Upward<RejectReason>> for Cis4QueryError {
fn from(value: v2::Upward<RejectReason>) -> Self {
Self::NodeRejected(value)
}
}
impl From<RejectReason> for Cis4QueryError {
fn from(value: RejectReason) -> Self {
Self::NodeRejected(v2::Upward::Known(value))
}
}
impl Cis4QueryError {
pub fn is_contract_error(&self) -> Option<v2::Upward<&crate::types::RejectReason>> {
if let Self::NodeRejected(e) = self {
Some(e.as_ref())
} else {
None
}
}
}
#[derive(thiserror::Error, Debug)]
pub enum Cis4TransactionError {
#[error("Invalid receive name: {0}")]
InvalidReceiveName(#[from] contracts_common::NewReceiveNameError),
#[error("Parameter is too large: {0}")]
InvalidParams(#[from] ExceedsParameterSize),
#[error("RPC error: {0}")]
RPCError(#[from] super::v2::RPCError),
#[error("Rejected by the node: {0:?}.")]
NodeRejected(v2::Upward<crate::types::RejectReason>),
}
pub type Cis4TransactionMetadata = ContractTransactionMetadata;
#[derive(Debug, Clone, Copy)]
pub enum Cis4Type {}
pub type Cis4Contract = ContractClient<Cis4Type>;
impl Cis4Contract {
pub async fn credential_entry(
&mut self,
cred_id: CredentialHolderId,
bi: impl IntoBlockIdentifier,
) -> Result<CredentialEntry, Cis4QueryError> {
let parameter =
OwnedParameter::from_serial(&cred_id).expect("Credential ID is a valid parameter.");
self.view_raw("credentialEntry", parameter, bi).await
}
pub async fn credential_status(
&mut self,
cred_id: CredentialHolderId,
bi: impl IntoBlockIdentifier,
) -> Result<CredentialStatus, Cis4QueryError> {
let parameter =
OwnedParameter::from_serial(&cred_id).expect("Credential ID is a valid parameter.");
self.view_raw("credentialStatus", parameter, bi).await
}
pub async fn revocation_keys(
&mut self,
bi: impl IntoBlockIdentifier,
) -> Result<Vec<RevocationKeyWithNonce>, Cis4QueryError> {
let parameter = OwnedParameter::empty();
self.view_raw("revocationKeys", parameter, bi).await
}
pub async fn registry_metadata(
&mut self,
bi: impl IntoBlockIdentifier,
) -> Result<RegistryMetadata, Cis4QueryError> {
let parameter = OwnedParameter::empty();
self.view_raw("registryMetadata", parameter, bi).await
}
pub async fn issuer(
&mut self,
bi: impl IntoBlockIdentifier,
) -> Result<IssuerKey, Cis4QueryError> {
let parameter = OwnedParameter::empty();
self.view_raw("issuer", parameter, bi).await
}
pub fn make_register_credential(
&self,
signer: &impl transactions::ExactSizeTransactionSigner,
metadata: &Cis4TransactionMetadata,
cred_info: &CredentialInfo,
additional_data: &[u8],
) -> Result<AccountTransaction<EncodedPayload>, Cis4TransactionError> {
use contracts_common::Serial;
let mut payload = contracts_common::to_bytes(cred_info);
let actual = payload.len() + additional_data.len() + 2;
if payload.len() + additional_data.len() + 2 > MAX_PARAMETER_LEN {
return Err(Cis4TransactionError::InvalidParams(ExceedsParameterSize {
actual,
max: MAX_PARAMETER_LEN,
}));
}
(additional_data.len() as u16)
.serial(&mut payload)
.expect("We checked lengths above, so this must succeed.");
payload.extend_from_slice(additional_data);
let parameter = OwnedParameter::try_from(payload)?;
self.make_update_raw(signer, metadata, "registerCredential", parameter)
}
pub async fn register_credential(
&mut self,
signer: &impl transactions::ExactSizeTransactionSigner,
metadata: &Cis4TransactionMetadata,
cred_info: &CredentialInfo,
additional_data: &[u8],
) -> Result<TransactionHash, Cis4TransactionError> {
let tx = self.make_register_credential(signer, metadata, cred_info, additional_data)?;
let hash = self.client.send_account_transaction(tx).await?;
Ok(hash)
}
pub fn make_revoke_credential_as_issuer(
&self,
signer: &impl transactions::ExactSizeTransactionSigner,
metadata: &Cis4TransactionMetadata,
cred_id: CredentialHolderId,
reason: Option<Reason>,
) -> Result<AccountTransaction<EncodedPayload>, Cis4TransactionError> {
let parameter = OwnedParameter::from_serial(&(cred_id, reason))?;
self.make_update_raw(signer, metadata, "revokeCredentialIssuer", parameter)
}
pub async fn revoke_credential_as_issuer(
&mut self,
signer: &impl transactions::ExactSizeTransactionSigner,
metadata: &Cis4TransactionMetadata,
cred_id: CredentialHolderId,
reason: Option<Reason>,
) -> Result<TransactionHash, Cis4TransactionError> {
let tx = self.make_revoke_credential_as_issuer(signer, metadata, cred_id, reason)?;
let hash = self.client.send_account_transaction(tx).await?;
Ok(hash)
}
pub async fn revoke_credential_as_holder(
&mut self,
signer: &impl transactions::ExactSizeTransactionSigner,
metadata: &Cis4TransactionMetadata,
web3signer: impl Web3IdSigner, nonce: u64,
reason: Option<Reason>,
) -> Result<TransactionHash, Cis4TransactionError> {
let tx =
self.make_revoke_credential_as_holder(signer, metadata, web3signer, nonce, reason)?;
let hash = self.client.send_account_transaction(tx).await?;
Ok(hash)
}
pub fn make_revoke_credential_as_holder(
&self,
signer: &impl transactions::ExactSizeTransactionSigner,
metadata: &Cis4TransactionMetadata,
web3signer: impl Web3IdSigner, nonce: u64,
reason: Option<Reason>,
) -> Result<AccountTransaction<EncodedPayload>, Cis4TransactionError> {
use contracts_common::Serial;
let mut to_sign = REVOKE_DOMAIN_STRING.to_vec();
let cred_id: CredentialHolderId = web3signer.id().into();
cred_id
.serial(&mut to_sign)
.expect("Serialization to vector does not fail.");
self.address
.serial(&mut to_sign)
.expect("Serialization to vector does not fail.");
nonce
.serial(&mut to_sign)
.expect("Serialization to vector does not fail.");
metadata
.expiry
.seconds
.checked_mul(1000)
.unwrap_or(u64::MAX)
.serial(&mut to_sign)
.expect("Serialization to vector does not fail.");
reason
.serial(&mut to_sign)
.expect("Serialization to vector does not fail.");
let sig = web3signer.sign(&to_sign);
let mut parameter_vec = sig.to_bytes().to_vec();
parameter_vec.extend_from_slice(&to_sign[REVOKE_DOMAIN_STRING.len()..]);
let parameter = OwnedParameter::try_from(parameter_vec)?;
self.make_update_raw(signer, metadata, "revokeCredentialHolder", parameter)
}
pub async fn revoke_credential_other(
&mut self,
signer: &impl transactions::ExactSizeTransactionSigner,
metadata: &Cis4TransactionMetadata,
revoker: impl Web3IdSigner, nonce: u64,
cred_id: CredentialHolderId,
reason: Option<&Reason>,
) -> Result<TransactionHash, Cis4TransactionError> {
let tx =
self.make_revoke_credential_other(signer, metadata, revoker, nonce, cred_id, reason)?;
let hash = self.client.send_account_transaction(tx).await?;
Ok(hash)
}
pub fn make_revoke_credential_other(
&self,
signer: &impl transactions::ExactSizeTransactionSigner,
metadata: &Cis4TransactionMetadata,
revoker: impl Web3IdSigner, nonce: u64,
cred_id: CredentialHolderId,
reason: Option<&Reason>,
) -> Result<AccountTransaction<EncodedPayload>, Cis4TransactionError> {
use contracts_common::Serial;
let mut to_sign = REVOKE_DOMAIN_STRING.to_vec();
cred_id
.serial(&mut to_sign)
.expect("Serialization to vector does not fail.");
self.address
.serial(&mut to_sign)
.expect("Serialization to vector does not fail.");
nonce
.serial(&mut to_sign)
.expect("Serialization to vector does not fail.");
metadata
.expiry
.seconds
.checked_mul(1000)
.unwrap_or(u64::MAX)
.serial(&mut to_sign)
.expect("Serialization to vector does not fail.");
RevocationKey::from(revoker.id())
.serial(&mut to_sign)
.expect("Serialization to vector does not fail.");
reason
.serial(&mut to_sign)
.expect("Serialization to vector does not fail.");
let sig = revoker.sign(&to_sign);
let mut parameter_vec = sig.to_bytes().to_vec();
parameter_vec.extend_from_slice(&to_sign[REVOKE_DOMAIN_STRING.len()..]);
let parameter = OwnedParameter::try_from(parameter_vec)?;
self.make_update_raw(signer, metadata, "revokeCredentialOther", parameter)
}
}