use super::{cbor::RawCbor, CborHolderAccount, TokenAmount, TokenId};
use crate::common::upward::{CborUpward, Upward};
use crate::{
common::cbor::{self, CborSerializationResult},
transactions::Memo,
};
use concordium_base_derive::{CborDeserialize, CborSerialize};
use concordium_contracts_common::AccountAddress;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenEvent {
pub token_id: TokenId,
pub event: TokenEventDetails,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum TokenEventDetails {
Module(TokenModuleEvent),
Transfer(TokenTransferEvent),
Mint(TokenSupplyUpdateEvent),
Burn(TokenSupplyUpdateEvent),
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenModuleEvent {
#[serde(rename = "type")]
pub event_type: TokenModuleCborTypeDiscriminator,
pub details: RawCbor,
}
impl TokenModuleEvent {
pub fn decode_token_module_event(
&self,
) -> CborSerializationResult<CborUpward<TokenModuleEventType>> {
use TokenModuleEventType::*;
Ok(match self.event_type.as_ref() {
"addAllowList" => {
Upward::Known(AddAllowList(cbor::cbor_decode(self.details.as_ref())?))
}
"removeAllowList" => {
Upward::Known(RemoveAllowList(cbor::cbor_decode(self.details.as_ref())?))
}
"addDenyList" => Upward::Known(AddDenyList(cbor::cbor_decode(self.details.as_ref())?)),
"removeDenyList" => {
Upward::Known(RemoveDenyList(cbor::cbor_decode(self.details.as_ref())?))
}
"pause" => Upward::Known(Pause(cbor::cbor_decode(self.details.as_ref())?)),
"unpause" => Upward::Known(Unpause(cbor::cbor_decode(self.details.as_ref())?)),
_ => Upward::Unknown(cbor::cbor_decode(self.details.as_ref())?),
})
}
}
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum TokenModuleEventType {
AddAllowList(TokenListUpdateEventDetails),
RemoveAllowList(TokenListUpdateEventDetails),
AddDenyList(TokenListUpdateEventDetails),
RemoveDenyList(TokenListUpdateEventDetails),
Pause(TokenPauseEventDetails),
Unpause(TokenPauseEventDetails),
}
#[derive(
Debug,
Clone,
Eq,
PartialEq,
serde::Serialize,
serde::Deserialize,
CborSerialize,
CborDeserialize,
)]
#[serde(rename_all = "camelCase")]
pub struct TokenListUpdateEventDetails {
pub target: CborHolderAccount,
}
#[derive(
Debug,
Clone,
Eq,
PartialEq,
CborSerialize,
CborDeserialize,
serde::Serialize,
serde::Deserialize,
)]
#[serde(rename_all = "camelCase")]
pub struct TokenPauseEventDetails {}
#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "camelCase")]
pub enum TokenHolder {
Account { address: AccountAddress },
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenTransferEvent {
pub from: TokenHolder,
pub to: TokenHolder,
pub amount: TokenAmount,
pub memo: Option<Memo>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenSupplyUpdateEvent {
pub target: TokenHolder,
pub amount: TokenAmount,
}
const TYPE_MAX_BYTE_LEN: usize = 255;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(try_from = "String", into = "String")]
#[repr(transparent)]
pub struct TokenModuleCborTypeDiscriminator {
value: String,
}
#[derive(Debug, thiserror::Error)]
#[error(
"Byte encoding of TokenModuleTypeDiscriminator must be within {TYPE_MAX_BYTE_LEN} bytes, \
instead got {actual_size}"
)]
pub struct TypeFromStringError {
actual_size: usize,
}
impl AsRef<str> for TokenModuleCborTypeDiscriminator {
fn as_ref(&self) -> &str {
self.value.as_str()
}
}
impl std::str::FromStr for TokenModuleCborTypeDiscriminator {
type Err = TypeFromStringError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.to_owned().try_into()
}
}
impl TryFrom<String> for TokenModuleCborTypeDiscriminator {
type Error = TypeFromStringError;
fn try_from(event_type: String) -> Result<Self, Self::Error> {
let byte_len = event_type.as_bytes().len();
if byte_len > TYPE_MAX_BYTE_LEN {
Err(TypeFromStringError {
actual_size: byte_len,
})
} else {
Ok(Self { value: event_type })
}
}
}
impl From<TokenModuleCborTypeDiscriminator> for String {
fn from(event_type: TokenModuleCborTypeDiscriminator) -> Self {
event_type.value
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
common::cbor,
protocol_level_tokens::{token_holder, CborHolderAccount},
};
#[test]
fn test_decode_add_allow_list_event_cbor() {
let variant = TokenListUpdateEventDetails {
target: CborHolderAccount {
address: token_holder::test_fixtures::ADDRESS,
coin_info: None,
},
};
let cbor = cbor::cbor_encode(&variant).unwrap();
assert_eq!(hex::encode(&cbor), "a166746172676574d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20");
let module_event = TokenModuleEvent {
event_type: "addAllowList".to_string().try_into().unwrap(),
details: cbor.into(),
};
let module_event_type = module_event.decode_token_module_event().unwrap();
assert_eq!(
module_event_type,
Upward::Known(TokenModuleEventType::AddAllowList(variant))
);
}
#[test]
fn test_decode_remove_allow_list_event_cbor() {
let variant = TokenListUpdateEventDetails {
target: CborHolderAccount {
address: token_holder::test_fixtures::ADDRESS,
coin_info: None,
},
};
let cbor = cbor::cbor_encode(&variant).unwrap();
assert_eq!(hex::encode(&cbor), "a166746172676574d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20");
let module_event = TokenModuleEvent {
event_type: "removeAllowList".to_string().try_into().unwrap(),
details: cbor.into(),
};
let module_event_type = module_event.decode_token_module_event().unwrap();
assert_eq!(
module_event_type,
Upward::Known(TokenModuleEventType::RemoveAllowList(variant))
);
}
#[test]
fn test_decode_add_deny_list_event_cbor() {
let variant = TokenListUpdateEventDetails {
target: CborHolderAccount {
address: token_holder::test_fixtures::ADDRESS,
coin_info: None,
},
};
let cbor = cbor::cbor_encode(&variant).unwrap();
assert_eq!(hex::encode(&cbor), "a166746172676574d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20");
let module_event = TokenModuleEvent {
event_type: "addDenyList".to_string().try_into().unwrap(),
details: cbor.into(),
};
let module_event_type = module_event.decode_token_module_event().unwrap();
assert_eq!(
module_event_type,
Upward::Known(TokenModuleEventType::AddDenyList(variant))
);
}
#[test]
fn test_decode_remove_deny_list_event_cbor() {
let variant = TokenListUpdateEventDetails {
target: CborHolderAccount {
address: token_holder::test_fixtures::ADDRESS,
coin_info: None,
},
};
let cbor = cbor::cbor_encode(&variant).unwrap();
assert_eq!(hex::encode(&cbor), "a166746172676574d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20");
let module_event = TokenModuleEvent {
event_type: "removeDenyList".to_string().try_into().unwrap(),
details: cbor.into(),
};
let module_event_type = module_event.decode_token_module_event().unwrap();
assert_eq!(
module_event_type,
Upward::Known(TokenModuleEventType::RemoveDenyList(variant))
);
}
#[test]
fn test_decode_pause_event_cbor() {
let variant = TokenPauseEventDetails {};
let cbor = cbor::cbor_encode(&variant).unwrap();
assert_eq!(hex::encode(&cbor), "a0");
let module_event = TokenModuleEvent {
event_type: "pause".to_string().try_into().unwrap(),
details: cbor.into(),
};
let module_event_type = module_event.decode_token_module_event().unwrap();
assert_eq!(
module_event_type,
Upward::Known(TokenModuleEventType::Pause(variant))
);
}
#[test]
fn test_decode_unpause_event_cbor() {
let variant = TokenPauseEventDetails {};
let cbor = cbor::cbor_encode(&variant).unwrap();
assert_eq!(hex::encode(&cbor), "a0");
let module_event = TokenModuleEvent {
event_type: "unpause".to_string().try_into().unwrap(),
details: cbor.into(),
};
let module_event_type = module_event.decode_token_module_event().unwrap();
assert_eq!(
module_event_type,
Upward::Known(TokenModuleEventType::Unpause(variant))
);
}
}