use crate::common::upward::CborUpward;
use crate::{
common::cbor::{self, CborSerializationResult},
protocol_level_tokens::{CborHolderAccount, CoinInfo, RawCbor, TokenAmount, TokenId},
transactions::Memo,
};
use concordium_base_derive::{CborDeserialize, CborSerialize};
use concordium_contracts_common::AccountAddress;
pub mod operations {
use super::*;
pub fn transfer_tokens(receiver: AccountAddress, amount: TokenAmount) -> TokenOperation {
TokenOperation::Transfer(TokenTransfer {
amount,
recipient: CborHolderAccount {
coin_info: Some(CoinInfo::CCD),
address: receiver,
},
memo: None,
})
}
pub fn transfer_tokens_with_memo(
receiver: AccountAddress,
amount: TokenAmount,
memo: CborMemo,
) -> TokenOperation {
TokenOperation::Transfer(TokenTransfer {
amount,
recipient: CborHolderAccount {
coin_info: Some(CoinInfo::CCD),
address: receiver,
},
memo: Some(memo),
})
}
pub fn mint_tokens(amount: TokenAmount) -> TokenOperation {
TokenOperation::Mint(TokenSupplyUpdateDetails { amount })
}
pub fn burn_tokens(amount: TokenAmount) -> TokenOperation {
TokenOperation::Burn(TokenSupplyUpdateDetails { amount })
}
pub fn add_token_allow_list(target: AccountAddress) -> TokenOperation {
TokenOperation::AddAllowList(TokenListUpdateDetails {
target: CborHolderAccount {
coin_info: Some(CoinInfo::CCD),
address: target,
},
})
}
pub fn remove_token_allow_list(target: AccountAddress) -> TokenOperation {
TokenOperation::RemoveAllowList(TokenListUpdateDetails {
target: CborHolderAccount {
coin_info: Some(CoinInfo::CCD),
address: target,
},
})
}
pub fn add_token_deny_list(target: AccountAddress) -> TokenOperation {
TokenOperation::AddDenyList(TokenListUpdateDetails {
target: CborHolderAccount {
coin_info: Some(CoinInfo::CCD),
address: target,
},
})
}
pub fn remove_token_deny_list(target: AccountAddress) -> TokenOperation {
TokenOperation::RemoveDenyList(TokenListUpdateDetails {
target: CborHolderAccount {
coin_info: Some(CoinInfo::CCD),
address: target,
},
})
}
pub fn pause() -> TokenOperation {
TokenOperation::Pause(TokenPauseDetails {})
}
pub fn unpause() -> TokenOperation {
TokenOperation::Unpause(TokenPauseDetails {})
}
}
const CBOR_TAG: u64 = 24;
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenOperationsPayload {
pub token_id: TokenId,
pub operations: RawCbor,
}
impl TokenOperationsPayload {
pub fn decode_operations(&self) -> CborSerializationResult<TokenOperations> {
cbor::cbor_decode(&self.operations)
}
}
#[derive(Debug, Clone, PartialEq, CborSerialize, CborDeserialize)]
#[cbor(transparent)]
pub struct TokenOperations {
pub operations: Vec<CborUpward<TokenOperation>>,
}
impl FromIterator<TokenOperation> for TokenOperations {
fn from_iter<T: IntoIterator<Item = TokenOperation>>(iter: T) -> Self {
Self {
operations: iter.into_iter().map(CborUpward::Known).collect(),
}
}
}
impl TokenOperations {
pub fn new(operations: Vec<CborUpward<TokenOperation>>) -> Self {
Self { operations }
}
}
#[derive(Debug, Clone, PartialEq, CborSerialize, CborDeserialize)]
#[cbor(map)]
pub enum TokenOperation {
Transfer(TokenTransfer),
Mint(TokenSupplyUpdateDetails),
Burn(TokenSupplyUpdateDetails),
AddAllowList(TokenListUpdateDetails),
RemoveAllowList(TokenListUpdateDetails),
AddDenyList(TokenListUpdateDetails),
RemoveDenyList(TokenListUpdateDetails),
Pause(TokenPauseDetails),
Unpause(TokenPauseDetails),
}
#[derive(
Debug,
Clone,
Eq,
PartialEq,
serde::Serialize,
serde::Deserialize,
CborSerialize,
CborDeserialize,
)]
#[serde(rename_all = "camelCase")]
pub struct TokenSupplyUpdateDetails {
pub amount: TokenAmount,
}
#[derive(
Debug,
Clone,
Eq,
PartialEq,
CborSerialize,
CborDeserialize,
serde::Serialize,
serde::Deserialize,
)]
#[serde(rename_all = "camelCase")]
pub struct TokenPauseDetails {}
#[derive(
Debug,
Clone,
Eq,
PartialEq,
serde::Serialize,
serde::Deserialize,
CborSerialize,
CborDeserialize,
)]
#[serde(rename_all = "camelCase")]
pub struct TokenListUpdateDetails {
pub target: CborHolderAccount,
}
#[derive(
Debug,
Clone,
Eq,
PartialEq,
serde::Serialize,
serde::Deserialize,
CborSerialize,
CborDeserialize,
)]
#[serde(rename_all = "camelCase")]
pub struct TokenTransfer {
pub amount: TokenAmount,
pub recipient: CborHolderAccount,
#[serde(skip_serializing_if = "Option::is_none")]
pub memo: Option<CborMemo>,
}
#[derive(
Debug,
Clone,
Eq,
PartialEq,
serde::Serialize,
serde::Deserialize,
CborSerialize,
CborDeserialize,
)]
#[serde(rename_all = "camelCase")]
#[cbor(tagged)]
pub enum CborMemo {
Raw(Memo),
#[cbor(tag = CBOR_TAG)]
Cbor(Memo),
}
#[cfg(test)]
pub mod test {
use super::*;
use crate::{
common::cbor::{self, value},
protocol_level_tokens::{token_holder::test_fixtures::ADDRESS, CborHolderAccount},
};
use assert_matches::assert_matches;
#[test]
fn test_cbor_memo_cbor() {
let memo = CborMemo::Raw(Memo::try_from(vec![0x01, 0x02, 0x03, 0x04]).unwrap());
let cbor = cbor::cbor_encode(&memo).unwrap();
assert_eq!(hex::encode(&cbor), "4401020304");
let memo_decoded: CborMemo = cbor::cbor_decode(&cbor).unwrap();
assert_eq!(memo_decoded, memo);
let memo = CborMemo::Cbor(Memo::try_from(vec![0x01, 0x02, 0x03, 0x04]).unwrap());
let cbor = cbor::cbor_encode(&memo).unwrap();
assert_eq!(hex::encode(&cbor), "d8184401020304");
let memo_decoded: CborMemo = cbor::cbor_decode(&cbor).unwrap();
assert_eq!(memo_decoded, memo);
}
#[test]
fn test_token_operations_cbor() {
let operations = TokenOperations {
operations: vec![CborUpward::Known(TokenOperation::Transfer(TokenTransfer {
amount: TokenAmount::from_raw(12300, 3),
recipient: CborHolderAccount {
address: ADDRESS,
coin_info: None,
},
memo: None,
}))],
};
let cbor = cbor::cbor_encode(&operations).unwrap();
assert_eq!(hex::encode(&cbor), "81a1687472616e73666572a266616d6f756e74c4822219300c69726563697069656e74d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20");
let operations_decoded: TokenOperations = cbor::cbor_decode(&cbor).unwrap();
assert_eq!(operations_decoded, operations);
}
#[test]
fn test_token_operation_cbor_transfer() {
let operation = TokenOperation::Transfer(TokenTransfer {
amount: TokenAmount::from_raw(12300, 3),
recipient: CborHolderAccount {
address: ADDRESS,
coin_info: None,
},
memo: None,
});
let cbor = cbor::cbor_encode(&operation).unwrap();
assert_eq!(hex::encode(&cbor), "a1687472616e73666572a266616d6f756e74c4822219300c69726563697069656e74d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20");
let operation_decoded: TokenOperation = cbor::cbor_decode(&cbor).unwrap();
assert_eq!(operation_decoded, operation);
}
#[test]
fn test_token_operation_cbor_mint() {
let operation = TokenOperation::Mint(TokenSupplyUpdateDetails {
amount: TokenAmount::from_raw(12300, 3),
});
let cbor = cbor::cbor_encode(&operation).unwrap();
assert_eq!(
hex::encode(&cbor),
"a1646d696e74a166616d6f756e74c4822219300c"
);
let operation_decoded: TokenOperation = cbor::cbor_decode(&cbor).unwrap();
assert_eq!(operation_decoded, operation);
}
#[test]
fn test_token_operation_cbor_burn() {
let operation = TokenOperation::Burn(TokenSupplyUpdateDetails {
amount: TokenAmount::from_raw(12300, 3),
});
let cbor = cbor::cbor_encode(&operation).unwrap();
assert_eq!(
hex::encode(&cbor),
"a1646275726ea166616d6f756e74c4822219300c"
);
let operation_decoded: TokenOperation = cbor::cbor_decode(&cbor).unwrap();
assert_eq!(operation_decoded, operation);
}
#[test]
fn test_token_operation_cbor_add_allow_list() {
let operation = TokenOperation::AddAllowList(TokenListUpdateDetails {
target: CborHolderAccount {
address: ADDRESS,
coin_info: None,
},
});
let cbor = cbor::cbor_encode(&operation).unwrap();
assert_eq!(hex::encode(&cbor), "a16c616464416c6c6f774c697374a166746172676574d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20");
let operation_decoded: TokenOperation = cbor::cbor_decode(&cbor).unwrap();
assert_eq!(operation_decoded, operation);
}
#[test]
fn test_token_operation_cbor_remove_allow_list() {
let operation = TokenOperation::RemoveAllowList(TokenListUpdateDetails {
target: CborHolderAccount {
address: ADDRESS,
coin_info: None,
},
});
let cbor = cbor::cbor_encode(&operation).unwrap();
assert_eq!(hex::encode(&cbor), "a16f72656d6f7665416c6c6f774c697374a166746172676574d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20");
let operation_decoded: TokenOperation = cbor::cbor_decode(&cbor).unwrap();
assert_eq!(operation_decoded, operation);
}
#[test]
fn test_token_operation_cbor_add_deny_list() {
let operation = TokenOperation::AddDenyList(TokenListUpdateDetails {
target: CborHolderAccount {
address: ADDRESS,
coin_info: None,
},
});
let cbor = cbor::cbor_encode(&operation).unwrap();
assert_eq!(hex::encode(&cbor), "a16b61646444656e794c697374a166746172676574d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20");
let operation_decoded: TokenOperation = cbor::cbor_decode(&cbor).unwrap();
assert_eq!(operation_decoded, operation);
}
#[test]
fn test_token_operation_cbor_remove_deny_list() {
let operation = TokenOperation::RemoveDenyList(TokenListUpdateDetails {
target: CborHolderAccount {
address: ADDRESS,
coin_info: None,
},
});
let cbor = cbor::cbor_encode(&operation).unwrap();
assert_eq!(hex::encode(&cbor), "a16e72656d6f766544656e794c697374a166746172676574d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20");
let operation_decoded: TokenOperation = cbor::cbor_decode(&cbor).unwrap();
assert_eq!(operation_decoded, operation);
}
#[test]
fn test_token_operation_cbor_pause() {
let operation = TokenOperation::Pause(TokenPauseDetails {});
let cbor = cbor::cbor_encode(&operation).unwrap();
assert_eq!(hex::encode(&cbor), "a1657061757365a0");
let operation_decoded: TokenOperation = cbor::cbor_decode(&cbor).unwrap();
assert_eq!(operation_decoded, operation);
}
#[test]
fn test_token_operation_cbor_unpause() {
let operation = TokenOperation::Unpause(TokenPauseDetails {});
let cbor = cbor::cbor_encode(&operation).unwrap();
assert_eq!(hex::encode(&cbor), "a167756e7061757365a0");
let operation_decoded: TokenOperation = cbor::cbor_decode(&cbor).unwrap();
assert_eq!(operation_decoded, operation);
}
#[test]
fn test_token_operation_cbor_unknown_variant() {
let cbor = hex::decode("a172736f6d65556e6b6e6f776e56617269616e74a266616d6f756e74c4822219300c69726563697069656e74d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20").unwrap();
let operation_decoded: CborUpward<TokenOperation> = cbor::cbor_decode(&cbor).unwrap();
assert_matches!(
operation_decoded,
CborUpward::Unknown(value::Value::Map(v)) if matches!(
v.as_slice(),
[(value::Value::Text(s), _), ..] if s == "someUnknownVariant"
)
);
}
}