From d0590c6d67232d50342461075267b280c134daf2 Mon Sep 17 00:00:00 2001 From: Thomas Dinsdale-Young Date: Tue, 11 Nov 2025 11:53:56 +0100 Subject: [PATCH 01/18] Add supporting functionality around PLT-related types. --- .../src/protocol_level_tokens/token_holder.rs | 9 +++++++++ .../src/protocol_level_tokens/token_metadata_url.rs | 10 ++++++++++ .../src/protocol_level_tokens/token_operations.rs | 9 +++++++++ 3 files changed, 28 insertions(+) diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_holder.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_holder.rs index 86d3695de..a2926f656 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_holder.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_holder.rs @@ -34,6 +34,15 @@ pub struct CborHolderAccount { pub address: AccountAddress, } +impl From for CborHolderAccount { + fn from(address: AccountAddress) -> Self { + CborHolderAccount { + coin_info: Some(CoinInfo::CCD), + address, + } + } +} + #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub enum CoinInfo { diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_metadata_url.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_metadata_url.rs index 541b575f0..d2c742de4 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_metadata_url.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_metadata_url.rs @@ -36,6 +36,16 @@ pub struct MetadataUrl { pub additional: HashMap, } +impl From for MetadataUrl { + fn from(url: String) -> Self { + Self{ + url, + checksum_sha_256: None, + additional: HashMap::new(), + } + } +} + /// Serialize `Bytes` as a hex string. fn serialize_hex_bytes(bytes: &Option, serializer: S) -> Result where diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs index 3bccd17aa..224c2a205 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs @@ -280,6 +280,15 @@ pub enum CborMemo { Cbor(Memo), } +impl From for Memo { + fn from(value: CborMemo) -> Self { + match value { + CborMemo::Raw(memo) => memo, + CborMemo::Cbor(memo) => memo, + } + } +} + #[cfg(test)] pub mod test { use super::*; From d4d81bac06852bb70fa5e2ff1787314e83f67584 Mon Sep 17 00:00:00 2001 From: Allan Rasmussen Date: Mon, 29 Dec 2025 14:13:30 +0100 Subject: [PATCH 02/18] remove Upward from PLT types --- .../src/protocol_level_tokens/token_event.rs | 79 ++++++------------- .../token_metadata_url.rs | 2 +- .../protocol_level_tokens/token_operations.rs | 54 ++++++++++--- .../token_reject_reason.rs | 63 ++++++--------- rust-src/concordium_base/src/transactions.rs | 19 ++--- 5 files changed, 102 insertions(+), 115 deletions(-) diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs index 3a8d66174..aeeee9542 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs @@ -1,37 +1,12 @@ -use super::{cbor::RawCbor, CborHolderAccount, TokenAmount, TokenId}; -use crate::common::upward::{CborUpward, Upward}; +use super::{cbor::RawCbor, CborHolderAccount, TokenAmount}; use crate::{ common::cbor::{self, CborSerializationResult}, transactions::Memo, }; +use anyhow::anyhow; use concordium_base_derive::{CborDeserialize, CborSerialize}; use concordium_contracts_common::AccountAddress; - -/// An event produced from the effect of a token transaction. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TokenEvent { - /// The unique symbol of the token, which produced this event. - pub token_id: TokenId, - /// The type of the event. - pub event: TokenEventDetails, -} - -/// The type of the token event. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum TokenEventDetails { - /// An event emitted by the token module. - Module(TokenModuleEvent), - /// An event emitted when a transfer of tokens is performed. - Transfer(TokenTransferEvent), - /// An event emitted when the token supply is updated by minting tokens to a - /// token holder. - Mint(TokenSupplyUpdateEvent), - /// An event emitted when the token supply is updated by burning tokens from - /// the balance of a token holder. - Burn(TokenSupplyUpdateEvent), -} +use std::fmt::{Display, Formatter}; /// Event produced from the effect of a token transaction. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -46,25 +21,17 @@ pub struct TokenModuleEvent { impl TokenModuleEvent { /// Decode token module event from CBOR - pub fn decode_token_module_event( - &self, - ) -> CborSerializationResult> { + pub fn decode_token_module_event(&self) -> CborSerializationResult { 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())?), + "addAllowList" => AddAllowList(cbor::cbor_decode(self.details.as_ref())?), + "removeAllowList" => RemoveAllowList(cbor::cbor_decode(self.details.as_ref())?), + "addDenyList" => AddDenyList(cbor::cbor_decode(self.details.as_ref())?), + "removeDenyList" => RemoveDenyList(cbor::cbor_decode(self.details.as_ref())?), + "pause" => Pause(cbor::cbor_decode(self.details.as_ref())?), + "unpause" => Unpause(cbor::cbor_decode(self.details.as_ref())?), + _ => return Err(anyhow!("unknown token module event: {}", self.event_type).into()), }) } } @@ -193,6 +160,12 @@ impl AsRef for TokenModuleCborTypeDiscriminator { } } +impl Display for TokenModuleCborTypeDiscriminator { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.value) + } +} + impl std::str::FromStr for TokenModuleCborTypeDiscriminator { type Err = TypeFromStringError; @@ -248,7 +221,7 @@ mod test { let module_event_type = module_event.decode_token_module_event().unwrap(); assert_eq!( module_event_type, - Upward::Known(TokenModuleEventType::AddAllowList(variant)) + TokenModuleEventType::AddAllowList(variant) ); } @@ -270,7 +243,7 @@ mod test { let module_event_type = module_event.decode_token_module_event().unwrap(); assert_eq!( module_event_type, - Upward::Known(TokenModuleEventType::RemoveAllowList(variant)) + TokenModuleEventType::RemoveAllowList(variant) ); } @@ -292,7 +265,7 @@ mod test { let module_event_type = module_event.decode_token_module_event().unwrap(); assert_eq!( module_event_type, - Upward::Known(TokenModuleEventType::AddDenyList(variant)) + TokenModuleEventType::AddDenyList(variant) ); } @@ -314,7 +287,7 @@ mod test { let module_event_type = module_event.decode_token_module_event().unwrap(); assert_eq!( module_event_type, - Upward::Known(TokenModuleEventType::RemoveDenyList(variant)) + TokenModuleEventType::RemoveDenyList(variant) ); } @@ -329,10 +302,7 @@ mod test { }; let module_event_type = module_event.decode_token_module_event().unwrap(); - assert_eq!( - module_event_type, - Upward::Known(TokenModuleEventType::Pause(variant)) - ); + assert_eq!(module_event_type, TokenModuleEventType::Pause(variant)); } #[test] @@ -346,9 +316,6 @@ mod test { }; let module_event_type = module_event.decode_token_module_event().unwrap(); - assert_eq!( - module_event_type, - Upward::Known(TokenModuleEventType::Unpause(variant)) - ); + assert_eq!(module_event_type, TokenModuleEventType::Unpause(variant)); } } diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_metadata_url.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_metadata_url.rs index d2c742de4..5d3e82cb7 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_metadata_url.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_metadata_url.rs @@ -38,7 +38,7 @@ pub struct MetadataUrl { impl From for MetadataUrl { fn from(url: String) -> Self { - Self{ + Self { url, checksum_sha_256: None, additional: HashMap::new(), diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs index 224c2a205..fd3e03a3d 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs @@ -1,4 +1,4 @@ -use crate::common::upward::CborUpward; +use crate::common::cbor::CborMaybeKnown; use crate::{ common::cbor::{self, CborSerializationResult}, protocol_level_tokens::{CborHolderAccount, CoinInfo, RawCbor, TokenAmount, TokenId}, @@ -124,7 +124,14 @@ pub struct TokenOperationsPayload { impl TokenOperationsPayload { /// Decode token operations from CBOR - pub fn decode_operations(&self) -> CborSerializationResult { + pub fn decode_operations( + &self, + ) -> CborSerializationResult>> { + cbor::cbor_decode(&self.operations) + } + + /// Decode token operations from CBOR + pub fn decode_operations_strict(&self) -> CborSerializationResult { cbor::cbor_decode(&self.operations) } } @@ -136,19 +143,19 @@ impl TokenOperationsPayload { #[cbor(transparent)] pub struct TokenOperations { /// List of protocol level token operations - pub operations: Vec>, + pub operations: Vec, } impl FromIterator for TokenOperations { fn from_iter>(iter: T) -> Self { Self { - operations: iter.into_iter().map(CborUpward::Known).collect(), + operations: iter.into_iter().collect(), } } } impl TokenOperations { - pub fn new(operations: Vec>) -> Self { + pub fn new(operations: Vec) -> Self { Self { operations } } } @@ -318,14 +325,14 @@ pub mod test { #[test] fn test_token_operations_cbor() { let operations = TokenOperations { - operations: vec![CborUpward::Known(TokenOperation::Transfer(TokenTransfer { + operations: vec![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(); @@ -464,13 +471,42 @@ pub mod test { #[test] fn test_token_operation_cbor_unknown_variant() { let cbor = hex::decode("a172736f6d65556e6b6e6f776e56617269616e74a266616d6f756e74c4822219300c69726563697069656e74d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20").unwrap(); - let operation_decoded: CborUpward = cbor::cbor_decode(&cbor).unwrap(); + let operation_decoded: CborMaybeKnown = cbor::cbor_decode(&cbor).unwrap(); assert_matches!( operation_decoded, - CborUpward::Unknown(value::Value::Map(v)) if matches!( + CborMaybeKnown::Unknown(value::Value::Map(v)) if matches!( v.as_slice(), [(value::Value::Text(s), _), ..] if s == "someUnknownVariant" ) ); } + + #[test] + fn test_token_operations_payload() { + let operations = TokenOperations { + operations: vec![TokenOperation::Transfer(TokenTransfer { + amount: TokenAmount::from_raw(12300, 3), + recipient: CborHolderAccount { + address: ADDRESS, + coin_info: None, + }, + memo: None, + })], + }; + let payload = TokenOperationsPayload { + token_id: "tk1".parse().unwrap(), + operations: RawCbor::from(cbor::cbor_encode(&operations).unwrap()), + }; + + let operations_decoded = payload.decode_operations_strict().unwrap(); + assert_eq!(operations_decoded, operations); + + let operations_known: Vec<_> = operations + .operations + .iter() + .map(|op| CborMaybeKnown::Known(op.clone())) + .collect(); + let operations_decoded = payload.decode_operations().unwrap(); + assert_eq!(operations_decoded, operations_known); + } } diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs index 3ca4c98d7..2c2e3a195 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs @@ -1,12 +1,10 @@ -use crate::common::upward::{CborUpward, Upward}; use crate::{ common::{cbor, cbor::CborSerializationResult}, protocol_level_tokens::{ - token_holder::CborHolderAccount, RawCbor, TokenAmount, TokenId, - TokenModuleCborTypeDiscriminator, + token_holder::CborHolderAccount, RawCbor, TokenAmount, TokenModuleCborTypeDiscriminator, }, }; -use anyhow::Context; +use anyhow::{anyhow, Context}; use concordium_base_derive::{CborDeserialize, CborSerialize}; /// Details provided by the token module in the event of rejecting a @@ -14,8 +12,6 @@ use concordium_base_derive::{CborDeserialize, CborSerialize}; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct TokenModuleRejectReason { - /// The unique symbol of the token, which produced this event. - pub token_id: TokenId, /// The type of the reject reason. #[serde(rename = "type")] pub reason_type: TokenModuleCborTypeDiscriminator, @@ -25,33 +21,33 @@ pub struct TokenModuleRejectReason { impl TokenModuleRejectReason { /// Decode reject reason from CBOR - pub fn decode_reject_reason( - &self, - ) -> CborSerializationResult> { + pub fn decode_reject_reason(&self) -> CborSerializationResult { use TokenModuleRejectReasonType::*; Ok(match self.reason_type.as_ref() { - "addressNotFound" => Upward::Known(AddressNotFound(cbor::cbor_decode( + "addressNotFound" => AddressNotFound(cbor::cbor_decode( self.details.as_ref().context("no CBOR details")?.as_ref(), - )?)), - "tokenBalanceInsufficient" => Upward::Known(TokenBalanceInsufficient( - cbor::cbor_decode(self.details.as_ref().context("no CBOR details")?.as_ref())?, - )), - "deserializationFailure" => Upward::Known(DeserializationFailure(cbor::cbor_decode( + )?), + "tokenBalanceInsufficient" => TokenBalanceInsufficient(cbor::cbor_decode( self.details.as_ref().context("no CBOR details")?.as_ref(), - )?)), - "unsupportedOperation" => Upward::Known(UnsupportedOperation(cbor::cbor_decode( + )?), + "deserializationFailure" => DeserializationFailure(cbor::cbor_decode( self.details.as_ref().context("no CBOR details")?.as_ref(), - )?)), - "operationNotPermitted" => Upward::Known(OperationNotPermitted(cbor::cbor_decode( + )?), + "unsupportedOperation" => UnsupportedOperation(cbor::cbor_decode( self.details.as_ref().context("no CBOR details")?.as_ref(), - )?)), - "mintWouldOverflow" => Upward::Known(MintWouldOverflow(cbor::cbor_decode( + )?), + "operationNotPermitted" => OperationNotPermitted(cbor::cbor_decode( self.details.as_ref().context("no CBOR details")?.as_ref(), - )?)), - _ => Upward::Unknown(cbor::cbor_decode( + )?), + "mintWouldOverflow" => MintWouldOverflow(cbor::cbor_decode( self.details.as_ref().context("no CBOR details")?.as_ref(), )?), + _ => { + return Err( + anyhow!("unknown token module reject reason: {}", self.reason_type).into(), + ) + } }) } } @@ -213,7 +209,6 @@ mod test { common::cbor, protocol_level_tokens::{token_holder, CborHolderAccount}, }; - use std::str::FromStr; #[test] fn test_address_not_found_reject_reason_cbor() { @@ -227,7 +222,6 @@ mod test { let cbor = cbor::cbor_encode(&variant).unwrap(); assert_eq!(hex::encode(&cbor), "a265696e646578036761646472657373d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"); let reject_reason = TokenModuleRejectReason { - token_id: TokenId::from_str("TK1").unwrap(), reason_type: "addressNotFound".to_string().try_into().unwrap(), details: Some(cbor.into()), }; @@ -235,7 +229,7 @@ mod test { let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); assert_eq!( reject_reason_type, - CborUpward::Known(TokenModuleRejectReasonType::AddressNotFound(variant)) + TokenModuleRejectReasonType::AddressNotFound(variant) ); } @@ -249,7 +243,6 @@ mod test { let cbor = cbor::cbor_encode(&variant).unwrap(); assert_eq!(hex::encode(&cbor), "a365696e646578036f726571756972656442616c616e6365c4822219571c70617661696c61626c6542616c616e6365c4822219300c"); let reject_reason = TokenModuleRejectReason { - token_id: TokenId::from_str("TK1").unwrap(), reason_type: "tokenBalanceInsufficient".to_string().try_into().unwrap(), details: Some(cbor.into()), }; @@ -257,9 +250,7 @@ mod test { let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); assert_eq!( reject_reason_type, - CborUpward::Known(TokenModuleRejectReasonType::TokenBalanceInsufficient( - variant - )) + TokenModuleRejectReasonType::TokenBalanceInsufficient(variant) ); } @@ -271,7 +262,6 @@ mod test { let cbor = cbor::cbor_encode(&variant).unwrap(); assert_eq!(hex::encode(&cbor), "a16563617573656b746573746661696c757265"); let reject_reason = TokenModuleRejectReason { - token_id: TokenId::from_str("TK1").unwrap(), reason_type: "deserializationFailure".to_string().try_into().unwrap(), details: Some(cbor.into()), }; @@ -279,7 +269,7 @@ mod test { let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); assert_eq!( reject_reason_type, - CborUpward::Known(TokenModuleRejectReasonType::DeserializationFailure(variant)) + TokenModuleRejectReasonType::DeserializationFailure(variant) ); } @@ -293,7 +283,6 @@ mod test { let cbor = cbor::cbor_encode(&variant).unwrap(); assert_eq!(hex::encode(&cbor), "a365696e6465780066726561736f6e6c746573746661696c747572656d6f7065726174696f6e547970656d746573746f7065726174696f6e"); let reject_reason = TokenModuleRejectReason { - token_id: TokenId::from_str("TK1").unwrap(), reason_type: "unsupportedOperation".to_string().try_into().unwrap(), details: Some(cbor.into()), }; @@ -301,7 +290,7 @@ mod test { let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); assert_eq!( reject_reason_type, - CborUpward::Known(TokenModuleRejectReasonType::UnsupportedOperation(variant)) + TokenModuleRejectReasonType::UnsupportedOperation(variant) ); } @@ -318,7 +307,6 @@ mod test { let cbor = cbor::cbor_encode(&variant).unwrap(); assert_eq!(hex::encode(&cbor), "a365696e6465780066726561736f6e6c746573746661696c747572656761646472657373d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"); let reject_reason = TokenModuleRejectReason { - token_id: TokenId::from_str("TK1").unwrap(), reason_type: "operationNotPermitted".to_string().try_into().unwrap(), details: Some(cbor.into()), }; @@ -326,7 +314,7 @@ mod test { let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); assert_eq!( reject_reason_type, - CborUpward::Known(TokenModuleRejectReasonType::OperationNotPermitted(variant)) + TokenModuleRejectReasonType::OperationNotPermitted(variant) ); } @@ -341,7 +329,6 @@ mod test { let cbor = cbor::cbor_encode(&variant).unwrap(); assert_eq!(hex::encode(&cbor), "a465696e646578006d63757272656e74537570706c79c482221927106f726571756573746564416d6f756e74c48222194e20766d6178526570726573656e7461626c65416d6f756e74c48222194e20"); let reject_reason = TokenModuleRejectReason { - token_id: TokenId::from_str("TK1").unwrap(), reason_type: "mintWouldOverflow".to_string().try_into().unwrap(), details: Some(cbor.into()), }; @@ -349,7 +336,7 @@ mod test { let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); assert_eq!( reject_reason_type, - CborUpward::Known(TokenModuleRejectReasonType::MintWouldOverflow(variant)) + TokenModuleRejectReasonType::MintWouldOverflow(variant) ); } } diff --git a/rust-src/concordium_base/src/transactions.rs b/rust-src/concordium_base/src/transactions.rs index 20afc0b11..7e560ab98 100644 --- a/rust-src/concordium_base/src/transactions.rs +++ b/rust-src/concordium_base/src/transactions.rs @@ -2331,7 +2331,6 @@ pub mod cost { /// See also the [send] module above which combines construction with signing. pub mod construct { use super::*; - use crate::common::upward::Upward; use crate::{ common::cbor, protocol_level_tokens::{RawCbor, TokenId, TokenOperation, TokenOperations}, @@ -2607,16 +2606,14 @@ pub mod construct { .operations .iter() .map(|op| match op { - Upward::Known(TokenOperation::Transfer(_)) => cost::PLT_TRANSFER, - Upward::Known(TokenOperation::Mint(_)) => cost::PLT_MINT, - Upward::Known(TokenOperation::Burn(_)) => cost::PLT_BURN, - Upward::Known(TokenOperation::AddAllowList(_)) - | Upward::Known(TokenOperation::RemoveAllowList(_)) - | Upward::Known(TokenOperation::AddDenyList(_)) - | Upward::Known(TokenOperation::RemoveDenyList(_)) => cost::PLT_LIST_UPDATE, - Upward::Known(TokenOperation::Pause(_)) - | Upward::Known(TokenOperation::Unpause(_)) => cost::PLT_PAUSE, - Upward::Unknown(_) => Default::default(), + TokenOperation::Transfer(_) => cost::PLT_TRANSFER, + TokenOperation::Mint(_) => cost::PLT_MINT, + TokenOperation::Burn(_) => cost::PLT_BURN, + TokenOperation::AddAllowList(_) + | TokenOperation::RemoveAllowList(_) + | TokenOperation::AddDenyList(_) + | TokenOperation::RemoveDenyList(_) => cost::PLT_LIST_UPDATE, + TokenOperation::Pause(_) | TokenOperation::Unpause(_) => cost::PLT_PAUSE, }) .sum() } From 04036098842e9745a86b57799857814efd1883c3 Mon Sep 17 00:00:00 2001 From: Allan Rasmussen Date: Tue, 30 Dec 2025 11:26:45 +0100 Subject: [PATCH 03/18] rejectreason --- .../token_reject_reason.rs | 82 ++++++++++++++++--- 1 file changed, 72 insertions(+), 10 deletions(-) diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs index 2c2e3a195..2989e94cf 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs @@ -6,6 +6,7 @@ use crate::{ }; use anyhow::{anyhow, Context}; use concordium_base_derive::{CborDeserialize, CborSerialize}; +use crate::common::cbor::CborSerialize; /// Details provided by the token module in the event of rejecting a /// transaction. @@ -19,28 +20,35 @@ pub struct TokenModuleRejectReason { pub details: Option, } +pub const ADDRESS_NOT_FOUND_REJECT_REASON_TYPE: &'static str = "addressNotFound"; +pub const TOKEN_BALANCE_INSUFFICIENT_REJECT_REASON_TYPE: &'static str = "tokenBalanceInsufficient"; +pub const DESERIALIZATION_FAILURE_REJECT_REASON_TYPE: &'static str = "deserializationFailure"; +pub const UNSUPPORTED_OPERATION_REJECT_REASON_TYPE: &'static str = "unsupportedOperation"; +pub const OPERATION_NOT_PERMITTED_REJECT_REASON_TYPE: &'static str = "operationNotPermitted"; +pub const MINT_WOULD_OVERFLOW_REJECT_REASON_TYPE: &'static str = "mintWouldOverflow"; + impl TokenModuleRejectReason { /// Decode reject reason from CBOR pub fn decode_reject_reason(&self) -> CborSerializationResult { use TokenModuleRejectReasonType::*; Ok(match self.reason_type.as_ref() { - "addressNotFound" => AddressNotFound(cbor::cbor_decode( - self.details.as_ref().context("no CBOR details")?.as_ref(), - )?), - "tokenBalanceInsufficient" => TokenBalanceInsufficient(cbor::cbor_decode( - self.details.as_ref().context("no CBOR details")?.as_ref(), - )?), - "deserializationFailure" => DeserializationFailure(cbor::cbor_decode( + ADDRESS_NOT_FOUND_REJECT_REASON_TYPE => AddressNotFound(cbor::cbor_decode( self.details.as_ref().context("no CBOR details")?.as_ref(), )?), - "unsupportedOperation" => UnsupportedOperation(cbor::cbor_decode( + TOKEN_BALANCE_INSUFFICIENT_REJECT_REASON_TYPE => TokenBalanceInsufficient( + cbor::cbor_decode(self.details.as_ref().context("no CBOR details")?.as_ref())?, + ), + DESERIALIZATION_FAILURE_REJECT_REASON_TYPE => DeserializationFailure( + cbor::cbor_decode(self.details.as_ref().context("no CBOR details")?.as_ref())?, + ), + UNSUPPORTED_OPERATION_REJECT_REASON_TYPE => UnsupportedOperation(cbor::cbor_decode( self.details.as_ref().context("no CBOR details")?.as_ref(), )?), - "operationNotPermitted" => OperationNotPermitted(cbor::cbor_decode( + OPERATION_NOT_PERMITTED_REJECT_REASON_TYPE => OperationNotPermitted(cbor::cbor_decode( self.details.as_ref().context("no CBOR details")?.as_ref(), )?), - "mintWouldOverflow" => MintWouldOverflow(cbor::cbor_decode( + MINT_WOULD_OVERFLOW_REJECT_REASON_TYPE => MintWouldOverflow(cbor::cbor_decode( self.details.as_ref().context("no CBOR details")?.as_ref(), )?), _ => { @@ -50,6 +58,60 @@ impl TokenModuleRejectReason { } }) } + + pub fn encode_reject_reason( + reject_reason: TokenModuleRejectReasonType, + ) -> CborSerializationResult { + fn cbor_encode(value: &impl CborSerialize) -> CborSerializationResult { + cbor::cbor_encode(value).map(RawCbor::from) + } + + // todo ar + Ok(match reject_reason { + TokenModuleRejectReasonType::AddressNotFound(reason) => Self { + reason_type: ADDRESS_NOT_FOUND_REJECT_REASON_TYPE + .to_string() + .try_into() + .unwrap(), + details: Some(cbor_encode(&reason)?), + }, + TokenModuleRejectReasonType::TokenBalanceInsufficient(reason) => Self { + reason_type: TOKEN_BALANCE_INSUFFICIENT_REJECT_REASON_TYPE + .to_string() + .try_into() + .unwrap(), + details: Some(cbor_encode(&reason)?), + }, + TokenModuleRejectReasonType::DeserializationFailure(reason) => Self { + reason_type: DESERIALIZATION_FAILURE_REJECT_REASON_TYPE + .to_string() + .try_into() + .unwrap(), + details: Some(cbor_encode(&reason)?), + }, + TokenModuleRejectReasonType::UnsupportedOperation(reason) => Self { + reason_type: UNSUPPORTED_OPERATION_REJECT_REASON_TYPE + .to_string() + .try_into() + .unwrap(), + details: Some(cbor_encode(&reason)?), + }, + TokenModuleRejectReasonType::OperationNotPermitted(reason) => Self { + reason_type: OPERATION_NOT_PERMITTED_REJECT_REASON_TYPE + .to_string() + .try_into() + .unwrap(), + details: Some(cbor_encode(&reason)?), + }, + TokenModuleRejectReasonType::MintWouldOverflow(reason) => Self { + reason_type: MINT_WOULD_OVERFLOW_REJECT_REASON_TYPE + .to_string() + .try_into() + .unwrap(), + details: Some(cbor_encode(&reason)?), + }, + }) + } } /// Token module reject reason parsed from type and CBOR if possible From ee9679e2889791644b33c62ef1a2db6d3d730819 Mon Sep 17 00:00:00 2001 From: Allan Rasmussen Date: Tue, 30 Dec 2025 14:39:15 +0100 Subject: [PATCH 04/18] rejectreason --- .../token_reject_reason.rs | 55 ------------------- 1 file changed, 55 deletions(-) diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs index 2989e94cf..cd104dab3 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs @@ -6,7 +6,6 @@ use crate::{ }; use anyhow::{anyhow, Context}; use concordium_base_derive::{CborDeserialize, CborSerialize}; -use crate::common::cbor::CborSerialize; /// Details provided by the token module in the event of rejecting a /// transaction. @@ -58,60 +57,6 @@ impl TokenModuleRejectReason { } }) } - - pub fn encode_reject_reason( - reject_reason: TokenModuleRejectReasonType, - ) -> CborSerializationResult { - fn cbor_encode(value: &impl CborSerialize) -> CborSerializationResult { - cbor::cbor_encode(value).map(RawCbor::from) - } - - // todo ar - Ok(match reject_reason { - TokenModuleRejectReasonType::AddressNotFound(reason) => Self { - reason_type: ADDRESS_NOT_FOUND_REJECT_REASON_TYPE - .to_string() - .try_into() - .unwrap(), - details: Some(cbor_encode(&reason)?), - }, - TokenModuleRejectReasonType::TokenBalanceInsufficient(reason) => Self { - reason_type: TOKEN_BALANCE_INSUFFICIENT_REJECT_REASON_TYPE - .to_string() - .try_into() - .unwrap(), - details: Some(cbor_encode(&reason)?), - }, - TokenModuleRejectReasonType::DeserializationFailure(reason) => Self { - reason_type: DESERIALIZATION_FAILURE_REJECT_REASON_TYPE - .to_string() - .try_into() - .unwrap(), - details: Some(cbor_encode(&reason)?), - }, - TokenModuleRejectReasonType::UnsupportedOperation(reason) => Self { - reason_type: UNSUPPORTED_OPERATION_REJECT_REASON_TYPE - .to_string() - .try_into() - .unwrap(), - details: Some(cbor_encode(&reason)?), - }, - TokenModuleRejectReasonType::OperationNotPermitted(reason) => Self { - reason_type: OPERATION_NOT_PERMITTED_REJECT_REASON_TYPE - .to_string() - .try_into() - .unwrap(), - details: Some(cbor_encode(&reason)?), - }, - TokenModuleRejectReasonType::MintWouldOverflow(reason) => Self { - reason_type: MINT_WOULD_OVERFLOW_REJECT_REASON_TYPE - .to_string() - .try_into() - .unwrap(), - details: Some(cbor_encode(&reason)?), - }, - }) - } } /// Token module reject reason parsed from type and CBOR if possible From 1fd3cc6fac53625d2287d1d3bf83dd67f3afd4f0 Mon Sep 17 00:00:00 2001 From: Allan Rasmussen Date: Tue, 30 Dec 2025 21:47:25 +0100 Subject: [PATCH 05/18] rejectreason --- .../protocol_level_tokens/token_operations.rs | 19 +- .../token_reject_reason.rs | 182 ++++++------------ 2 files changed, 75 insertions(+), 126 deletions(-) diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs index fd3e03a3d..32137f04e 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs @@ -124,14 +124,19 @@ pub struct TokenOperationsPayload { impl TokenOperationsPayload { /// Decode token operations from CBOR - pub fn decode_operations( - &self, - ) -> CborSerializationResult>> { + pub fn decode_operations(&self) -> CborSerializationResult { cbor::cbor_decode(&self.operations) } - /// Decode token operations from CBOR - pub fn decode_operations_strict(&self) -> CborSerializationResult { + /// Decode token operations from CBOR. Unknown operations are wrapped + /// in [`CborMaybeKnown::Unknown`]. + /// + /// Handling [`CborMaybeKnown::Unknown`] can be used to implement forwards compatability + /// with future token operations. Unknown token operations can e.g. be ignored, if the handler is only interested + /// in specific and known token operations. + pub fn decode_operations_maybe_known( + &self, + ) -> CborSerializationResult>> { cbor::cbor_decode(&self.operations) } } @@ -498,7 +503,7 @@ pub mod test { operations: RawCbor::from(cbor::cbor_encode(&operations).unwrap()), }; - let operations_decoded = payload.decode_operations_strict().unwrap(); + let operations_decoded = payload.decode_operations().unwrap(); assert_eq!(operations_decoded, operations); let operations_known: Vec<_> = operations @@ -506,7 +511,7 @@ pub mod test { .iter() .map(|op| CborMaybeKnown::Known(op.clone())) .collect(); - let operations_decoded = payload.decode_operations().unwrap(); + let operations_decoded = payload.decode_operations_maybe_known().unwrap(); assert_eq!(operations_decoded, operations_known); } } diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs index cd104dab3..0021959c5 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs @@ -1,68 +1,50 @@ -use crate::{ - common::{cbor, cbor::CborSerializationResult}, - protocol_level_tokens::{ - token_holder::CborHolderAccount, RawCbor, TokenAmount, TokenModuleCborTypeDiscriminator, - }, +use crate::protocol_level_tokens::{ + token_holder::CborHolderAccount, TokenAmount, TokenModuleCborTypeDiscriminator, }; -use anyhow::{anyhow, Context}; use concordium_base_derive::{CborDeserialize, CborSerialize}; +use std::str::FromStr; -/// Details provided by the token module in the event of rejecting a -/// transaction. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TokenModuleRejectReason { - /// The type of the reject reason. - #[serde(rename = "type")] - pub reason_type: TokenModuleCborTypeDiscriminator, - /// (Optional) CBOR-encoded details. - pub details: Option, +/// Token module reject reason type +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +pub enum TokenModuleRejectReasonType { + /// Address not found: [`AddressNotFoundRejectReason`] + AddressNotFound, + /// Token balance is insufficient ([`TokenBalanceInsufficient`]) + TokenBalanceInsufficient, + /// The transaction could not be deserialized ([`DeserializationFailureRejectReason`]) + DeserializationFailure, + /// The operation is not supported by the token module ([`UnsupportedOperationRejectReason`]) + UnsupportedOperation, + /// Operation authorization check failed ([`OperationNotPermittedRejectReason`]) + OperationNotPermitted, + /// Minting the requested amount would overflow the representable token + /// amount ([`MintWouldOverflowRejectReason`]) + MintWouldOverflow, } -pub const ADDRESS_NOT_FOUND_REJECT_REASON_TYPE: &'static str = "addressNotFound"; -pub const TOKEN_BALANCE_INSUFFICIENT_REJECT_REASON_TYPE: &'static str = "tokenBalanceInsufficient"; -pub const DESERIALIZATION_FAILURE_REJECT_REASON_TYPE: &'static str = "deserializationFailure"; -pub const UNSUPPORTED_OPERATION_REJECT_REASON_TYPE: &'static str = "unsupportedOperation"; -pub const OPERATION_NOT_PERMITTED_REJECT_REASON_TYPE: &'static str = "operationNotPermitted"; -pub const MINT_WOULD_OVERFLOW_REJECT_REASON_TYPE: &'static str = "mintWouldOverflow"; - -impl TokenModuleRejectReason { - /// Decode reject reason from CBOR - pub fn decode_reject_reason(&self) -> CborSerializationResult { - use TokenModuleRejectReasonType::*; +impl TokenModuleRejectReasonType { + /// String identifier for the reject reason type + const fn as_str(&self) -> &'static str { + match self { + TokenModuleRejectReasonType::AddressNotFound => "addressNotFound", + TokenModuleRejectReasonType::TokenBalanceInsufficient => "tokenBalanceInsufficient", + TokenModuleRejectReasonType::DeserializationFailure => "deserializationFailure", + TokenModuleRejectReasonType::UnsupportedOperation => "unsupportedOperation", + TokenModuleRejectReasonType::OperationNotPermitted => "operationNotPermitted", + TokenModuleRejectReasonType::MintWouldOverflow => "mintWouldOverflow", + } + } - Ok(match self.reason_type.as_ref() { - ADDRESS_NOT_FOUND_REJECT_REASON_TYPE => AddressNotFound(cbor::cbor_decode( - self.details.as_ref().context("no CBOR details")?.as_ref(), - )?), - TOKEN_BALANCE_INSUFFICIENT_REJECT_REASON_TYPE => TokenBalanceInsufficient( - cbor::cbor_decode(self.details.as_ref().context("no CBOR details")?.as_ref())?, - ), - DESERIALIZATION_FAILURE_REJECT_REASON_TYPE => DeserializationFailure( - cbor::cbor_decode(self.details.as_ref().context("no CBOR details")?.as_ref())?, - ), - UNSUPPORTED_OPERATION_REJECT_REASON_TYPE => UnsupportedOperation(cbor::cbor_decode( - self.details.as_ref().context("no CBOR details")?.as_ref(), - )?), - OPERATION_NOT_PERMITTED_REJECT_REASON_TYPE => OperationNotPermitted(cbor::cbor_decode( - self.details.as_ref().context("no CBOR details")?.as_ref(), - )?), - MINT_WOULD_OVERFLOW_REJECT_REASON_TYPE => MintWouldOverflow(cbor::cbor_decode( - self.details.as_ref().context("no CBOR details")?.as_ref(), - )?), - _ => { - return Err( - anyhow!("unknown token module reject reason: {}", self.reason_type).into(), - ) - } - }) + /// Convert to the "dynamic" representation of the reject reason type + pub fn to_type_discriminator(&self) -> TokenModuleCborTypeDiscriminator { + TokenModuleCborTypeDiscriminator::from_str(self.as_str()).expect("static length") } } /// Token module reject reason parsed from type and CBOR if possible #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] -pub enum TokenModuleRejectReasonType { +pub enum TokenModuleRejectReasonEnum { /// Address not found AddressNotFound(AddressNotFoundRejectReason), /// Token balance is insufficient @@ -219,91 +201,66 @@ mod test { #[test] fn test_address_not_found_reject_reason_cbor() { - let variant = AddressNotFoundRejectReason { + let reject_reason = AddressNotFoundRejectReason { index: 3, address: CborHolderAccount { address: token_holder::test_fixtures::ADDRESS, coin_info: None, }, }; - let cbor = cbor::cbor_encode(&variant).unwrap(); + let cbor = cbor::cbor_encode(&reject_reason).unwrap(); assert_eq!(hex::encode(&cbor), "a265696e646578036761646472657373d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"); - let reject_reason = TokenModuleRejectReason { - reason_type: "addressNotFound".to_string().try_into().unwrap(), - details: Some(cbor.into()), - }; - let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); - assert_eq!( - reject_reason_type, - TokenModuleRejectReasonType::AddressNotFound(variant) - ); + let reject_reason_decoded: AddressNotFoundRejectReason = cbor::cbor_decode(cbor).unwrap(); + assert_eq!(reject_reason_decoded, reject_reason); } #[test] fn test_token_balance_insufficient_reject_reason_cbor() { - let variant = TokenBalanceInsufficientRejectReason { + let reject_reason = TokenBalanceInsufficientRejectReason { index: 3, available_balance: TokenAmount::from_raw(12300, 3), required_balance: TokenAmount::from_raw(22300, 3), }; - let cbor = cbor::cbor_encode(&variant).unwrap(); + let cbor = cbor::cbor_encode(&reject_reason).unwrap(); assert_eq!(hex::encode(&cbor), "a365696e646578036f726571756972656442616c616e6365c4822219571c70617661696c61626c6542616c616e6365c4822219300c"); - let reject_reason = TokenModuleRejectReason { - reason_type: "tokenBalanceInsufficient".to_string().try_into().unwrap(), - details: Some(cbor.into()), - }; - let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); - assert_eq!( - reject_reason_type, - TokenModuleRejectReasonType::TokenBalanceInsufficient(variant) - ); + let reject_reason_decoded: TokenBalanceInsufficientRejectReason = + cbor::cbor_decode(cbor).unwrap(); + assert_eq!(reject_reason_decoded, reject_reason); } #[test] fn test_deserialization_failure_reject_reason_cbor() { - let variant = DeserializationFailureRejectReason { + let reject_reason = DeserializationFailureRejectReason { cause: Some("testfailure".to_string()), }; - let cbor = cbor::cbor_encode(&variant).unwrap(); + let cbor = cbor::cbor_encode(&reject_reason).unwrap(); assert_eq!(hex::encode(&cbor), "a16563617573656b746573746661696c757265"); - let reject_reason = TokenModuleRejectReason { - reason_type: "deserializationFailure".to_string().try_into().unwrap(), - details: Some(cbor.into()), - }; - let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); - assert_eq!( - reject_reason_type, - TokenModuleRejectReasonType::DeserializationFailure(variant) - ); + let reject_reason_decoded: DeserializationFailureRejectReason = + cbor::cbor_decode(cbor).unwrap(); + assert_eq!(reject_reason_decoded, reject_reason); } #[test] fn test_unsupported_operation_reject_reason_cbor() { - let variant = UnsupportedOperationRejectReason { + let reject_reason = UnsupportedOperationRejectReason { index: 0, operation_type: "testoperation".to_string(), reason: Some("testfailture".to_string()), }; - let cbor = cbor::cbor_encode(&variant).unwrap(); + let cbor = cbor::cbor_encode(&reject_reason).unwrap(); assert_eq!(hex::encode(&cbor), "a365696e6465780066726561736f6e6c746573746661696c747572656d6f7065726174696f6e547970656d746573746f7065726174696f6e"); - let reject_reason = TokenModuleRejectReason { - reason_type: "unsupportedOperation".to_string().try_into().unwrap(), - details: Some(cbor.into()), - }; - let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); - assert_eq!( - reject_reason_type, - TokenModuleRejectReasonType::UnsupportedOperation(variant) - ); + let reject_reason_decoded: UnsupportedOperationRejectReason = + cbor::cbor_decode(cbor).unwrap(); + assert_eq!(reject_reason_decoded, reject_reason); } #[test] fn test_operation_not_permitted_reject_reason_cbor() { - let variant = OperationNotPermittedRejectReason { + let reject_reason = OperationNotPermittedRejectReason { index: 0, address: Some(CborHolderAccount { address: token_holder::test_fixtures::ADDRESS, @@ -311,39 +268,26 @@ mod test { }), reason: Some("testfailture".to_string()), }; - let cbor = cbor::cbor_encode(&variant).unwrap(); + let cbor = cbor::cbor_encode(&reject_reason).unwrap(); assert_eq!(hex::encode(&cbor), "a365696e6465780066726561736f6e6c746573746661696c747572656761646472657373d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"); - let reject_reason = TokenModuleRejectReason { - reason_type: "operationNotPermitted".to_string().try_into().unwrap(), - details: Some(cbor.into()), - }; - let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); - assert_eq!( - reject_reason_type, - TokenModuleRejectReasonType::OperationNotPermitted(variant) - ); + let reject_reason_decoded: OperationNotPermittedRejectReason = + cbor::cbor_decode(cbor).unwrap(); + assert_eq!(reject_reason_decoded, reject_reason); } #[test] fn test_mint_would_overflow_reject_reason_cbor() { - let variant = MintWouldOverflowRejectReason { + let reject_reason = MintWouldOverflowRejectReason { index: 0, requested_amount: TokenAmount::from_raw(20000, 3), current_supply: TokenAmount::from_raw(10000, 3), max_representable_amount: TokenAmount::from_raw(20000, 3), }; - let cbor = cbor::cbor_encode(&variant).unwrap(); + let cbor = cbor::cbor_encode(&reject_reason).unwrap(); assert_eq!(hex::encode(&cbor), "a465696e646578006d63757272656e74537570706c79c482221927106f726571756573746564416d6f756e74c48222194e20766d6178526570726573656e7461626c65416d6f756e74c48222194e20"); - let reject_reason = TokenModuleRejectReason { - reason_type: "mintWouldOverflow".to_string().try_into().unwrap(), - details: Some(cbor.into()), - }; - let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); - assert_eq!( - reject_reason_type, - TokenModuleRejectReasonType::MintWouldOverflow(variant) - ); + let reject_reason_decoded: MintWouldOverflowRejectReason = cbor::cbor_decode(cbor).unwrap(); + assert_eq!(reject_reason_decoded, reject_reason); } } From 8e6d85e9ed753d86f71711124281e792bdad31ff Mon Sep 17 00:00:00 2001 From: Allan Rasmussen Date: Tue, 30 Dec 2025 21:59:31 +0100 Subject: [PATCH 06/18] event --- .../src/protocol_level_tokens/token_event.rs | 170 ++++++++---------- 1 file changed, 74 insertions(+), 96 deletions(-) diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs index aeeee9542..6fcfeeceb 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs @@ -1,61 +1,75 @@ -use super::{cbor::RawCbor, CborHolderAccount, TokenAmount}; -use crate::{ - common::cbor::{self, CborSerializationResult}, - transactions::Memo, -}; -use anyhow::anyhow; +use super::{CborHolderAccount, TokenAmount}; +use crate::transactions::Memo; use concordium_base_derive::{CborDeserialize, CborSerialize}; use concordium_contracts_common::AccountAddress; use std::fmt::{Display, Formatter}; +use std::str::FromStr; -/// Event produced from the effect of a token transaction. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TokenModuleEvent { - /// The type of event produced. - #[serde(rename = "type")] - pub event_type: TokenModuleCborTypeDiscriminator, - /// The details of the event produced, in the raw byte encoded form. - pub details: RawCbor, +/// Token module event parsed from type and CBOR +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +pub enum TokenModuleEventType { + /// An account was added to the allow list of a protocol level token ([`AddAllowListEvent`]) + AddAllowList, + /// An account was removed from the allow list of a protocol level token ([`RemoveAllowListEvent`]) + RemoveAllowList, + /// An account was added to the deny list of a protocol level token ([`AddDenyListEvent`]) + AddDenyList, + /// An account was removed from the deny list of a protocol level token ([`RemoveDenyListEvent`]) + RemoveDenyList, + /// Execution of certain operations on a protocol level token was + /// paused ([`PauseEvent`]) + Pause, + /// Execution of certain operations on a protocol level token was + /// unpaused ([`UnpauseEvent`]) + Unpause, } -impl TokenModuleEvent { - /// Decode token module event from CBOR - pub fn decode_token_module_event(&self) -> CborSerializationResult { - use TokenModuleEventType::*; - - Ok(match self.event_type.as_ref() { - "addAllowList" => AddAllowList(cbor::cbor_decode(self.details.as_ref())?), - "removeAllowList" => RemoveAllowList(cbor::cbor_decode(self.details.as_ref())?), - "addDenyList" => AddDenyList(cbor::cbor_decode(self.details.as_ref())?), - "removeDenyList" => RemoveDenyList(cbor::cbor_decode(self.details.as_ref())?), - "pause" => Pause(cbor::cbor_decode(self.details.as_ref())?), - "unpause" => Unpause(cbor::cbor_decode(self.details.as_ref())?), - _ => return Err(anyhow!("unknown token module event: {}", self.event_type).into()), - }) +impl TokenModuleEventType { + /// String identifier for the token module event + const fn as_str(&self) -> &'static str { + match self { + TokenModuleEventType::AddAllowList => "addAllowList", + TokenModuleEventType::RemoveAllowList => "removeAllowList", + TokenModuleEventType::AddDenyList => "addDenyList", + TokenModuleEventType::RemoveDenyList => "removeDenyList", + TokenModuleEventType::Pause => "pause", + TokenModuleEventType::Unpause => "unpause", + } + } + + /// Convert to the "dynamic" representation of the token module event + pub fn to_type_discriminator(&self) -> TokenModuleCborTypeDiscriminator { + TokenModuleCborTypeDiscriminator::from_str(self.as_str()).expect("static length") } } /// Token module event parsed from type and CBOR #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] -pub enum TokenModuleEventType { +pub enum TokenModuleEventEnum { /// An account was added to the allow list of a protocol level token - AddAllowList(TokenListUpdateEventDetails), + AddAllowList(AddAllowListEvent), /// An account was removed from the allow list of a protocol level token - RemoveAllowList(TokenListUpdateEventDetails), + RemoveAllowList(RemoveAllowListEvent), /// An account was added to the deny list of a protocol level token - AddDenyList(TokenListUpdateEventDetails), + AddDenyList(AddDenyListEvent), /// An account was removed from the deny list of a protocol level token - RemoveDenyList(TokenListUpdateEventDetails), + RemoveDenyList(RemoveDenyListEvent), /// Execution of certain operations on a protocol level token was /// paused - Pause(TokenPauseEventDetails), + Pause(PauseEvent), /// Execution of certain operations on a protocol level token was /// unpaused - Unpause(TokenPauseEventDetails), + Unpause(UnpauseEvent), } +pub type AddAllowListEvent = TokenListUpdateEventDetails; +pub type RemoveAllowListEvent = TokenListUpdateEventDetails; +pub type AddDenyListEvent = TokenListUpdateEventDetails; +pub type RemoveDenyListEvent = TokenListUpdateEventDetails; +pub type PauseEvent = TokenPauseEventDetails; +pub type UnpauseEvent = TokenPauseEventDetails; + /// Details of an event updating the allow or deny list of a protocol level /// token #[derive( @@ -205,117 +219,81 @@ mod test { #[test] fn test_decode_add_allow_list_event_cbor() { - let variant = TokenListUpdateEventDetails { + let event = TokenListUpdateEventDetails { target: CborHolderAccount { address: token_holder::test_fixtures::ADDRESS, coin_info: None, }, }; - let cbor = cbor::cbor_encode(&variant).unwrap(); + let cbor = cbor::cbor_encode(&event).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, - TokenModuleEventType::AddAllowList(variant) - ); + let event_decoded: TokenListUpdateEventDetails = cbor::cbor_decode(cbor).unwrap(); + assert_eq!(event_decoded, event); } #[test] fn test_decode_remove_allow_list_event_cbor() { - let variant = TokenListUpdateEventDetails { + let event = TokenListUpdateEventDetails { target: CborHolderAccount { address: token_holder::test_fixtures::ADDRESS, coin_info: None, }, }; - let cbor = cbor::cbor_encode(&variant).unwrap(); + let cbor = cbor::cbor_encode(&event).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, - TokenModuleEventType::RemoveAllowList(variant) - ); + let event_decoded: TokenListUpdateEventDetails = cbor::cbor_decode(cbor).unwrap(); + assert_eq!(event_decoded, event); } #[test] fn test_decode_add_deny_list_event_cbor() { - let variant = TokenListUpdateEventDetails { + let event = TokenListUpdateEventDetails { target: CborHolderAccount { address: token_holder::test_fixtures::ADDRESS, coin_info: None, }, }; - let cbor = cbor::cbor_encode(&variant).unwrap(); + let cbor = cbor::cbor_encode(&event).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, - TokenModuleEventType::AddDenyList(variant) - ); + let event_decoded: TokenListUpdateEventDetails = cbor::cbor_decode(cbor).unwrap(); + assert_eq!(event_decoded, event); } #[test] fn test_decode_remove_deny_list_event_cbor() { - let variant = TokenListUpdateEventDetails { + let event = TokenListUpdateEventDetails { target: CborHolderAccount { address: token_holder::test_fixtures::ADDRESS, coin_info: None, }, }; - let cbor = cbor::cbor_encode(&variant).unwrap(); + let cbor = cbor::cbor_encode(&event).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, - TokenModuleEventType::RemoveDenyList(variant) - ); + let event_decoded: TokenListUpdateEventDetails = cbor::cbor_decode(cbor).unwrap(); + assert_eq!(event_decoded, event); } #[test] fn test_decode_pause_event_cbor() { - let variant = TokenPauseEventDetails {}; - let cbor = cbor::cbor_encode(&variant).unwrap(); + let event = TokenPauseEventDetails {}; + let cbor = cbor::cbor_encode(&event).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, TokenModuleEventType::Pause(variant)); + let event_decoded: TokenPauseEventDetails = cbor::cbor_decode(cbor).unwrap(); + assert_eq!(event_decoded, event); } #[test] fn test_decode_unpause_event_cbor() { - let variant = TokenPauseEventDetails {}; - let cbor = cbor::cbor_encode(&variant).unwrap(); + let event = TokenPauseEventDetails {}; + let cbor = cbor::cbor_encode(&event).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, TokenModuleEventType::Unpause(variant)); + let event_decoded: TokenPauseEventDetails = cbor::cbor_decode(cbor).unwrap(); + assert_eq!(event_decoded, event); } } From e82b8b60bfeb75442a1cb9756a4a557f15236134 Mon Sep 17 00:00:00 2001 From: Allan Rasmussen Date: Tue, 30 Dec 2025 22:25:25 +0100 Subject: [PATCH 07/18] rejectreason --- .../src/protocol_level_tokens/token_event.rs | 16 +++++++++++- .../token_reject_reason.rs | 26 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs index 6fcfeeceb..c08022ca7 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs @@ -63,6 +63,20 @@ pub enum TokenModuleEventEnum { Unpause(UnpauseEvent), } +impl TokenModuleEventEnum { + /// Token module event type + pub fn event_type(&self) -> TokenModuleEventType { + match self { + TokenModuleEventEnum::AddAllowList(_) => TokenModuleEventType::AddAllowList, + TokenModuleEventEnum::RemoveAllowList(_) => TokenModuleEventType::RemoveAllowList, + TokenModuleEventEnum::AddDenyList(_) => TokenModuleEventType::AddDenyList, + TokenModuleEventEnum::RemoveDenyList(_) => TokenModuleEventType::RemoveDenyList, + TokenModuleEventEnum::Pause(_) => TokenModuleEventType::Pause, + TokenModuleEventEnum::Unpause(_) => TokenModuleEventType::Unpause, + } + } +} + pub type AddAllowListEvent = TokenListUpdateEventDetails; pub type RemoveAllowListEvent = TokenListUpdateEventDetails; pub type AddDenyListEvent = TokenListUpdateEventDetails; @@ -150,7 +164,7 @@ const TYPE_MAX_BYTE_LEN: usize = 255; /// reason type. /// /// Limited to 255 bytes in length and must be valid UTF-8. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] #[serde(try_from = "String", into = "String")] #[repr(transparent)] pub struct TokenModuleCborTypeDiscriminator { diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs index 0021959c5..9da436a8c 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs @@ -60,6 +60,32 @@ pub enum TokenModuleRejectReasonEnum { MintWouldOverflow(MintWouldOverflowRejectReason), } +impl TokenModuleRejectReasonEnum { + /// Token module reject reason type + pub fn reject_reason_type(&self) -> TokenModuleRejectReasonType { + match self { + TokenModuleRejectReasonEnum::AddressNotFound(_) => { + TokenModuleRejectReasonType::AddressNotFound + } + TokenModuleRejectReasonEnum::TokenBalanceInsufficient(_) => { + TokenModuleRejectReasonType::TokenBalanceInsufficient + } + TokenModuleRejectReasonEnum::DeserializationFailure(_) => { + TokenModuleRejectReasonType::DeserializationFailure + } + TokenModuleRejectReasonEnum::UnsupportedOperation(_) => { + TokenModuleRejectReasonType::UnsupportedOperation + } + TokenModuleRejectReasonEnum::OperationNotPermitted(_) => { + TokenModuleRejectReasonType::OperationNotPermitted + } + TokenModuleRejectReasonEnum::MintWouldOverflow(_) => { + TokenModuleRejectReasonType::MintWouldOverflow + } + } + } +} + /// A token holder address was not valid. #[derive( Debug, From 828e7650f26187e321b2a40bb11abc7d72b06177 Mon Sep 17 00:00:00 2001 From: Allan Rasmussen Date: Wed, 31 Dec 2025 10:19:45 +0100 Subject: [PATCH 08/18] discr --- .../src/protocol_level_tokens/token_event.rs | 24 +++++++++++++++++ .../protocol_level_tokens/token_operations.rs | 2 +- .../token_reject_reason.rs | 26 +++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs index c08022ca7..2af5154ed 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs @@ -24,6 +24,11 @@ pub enum TokenModuleEventType { Unpause, } +/// Unknown token module reject reason +#[derive(Debug, thiserror::Error)] +#[error("Unknown token module reject reason type: {0}")] +pub struct UnknownTokenModuleEventTypeError(String); + impl TokenModuleEventType { /// String identifier for the token module event const fn as_str(&self) -> &'static str { @@ -41,6 +46,25 @@ impl TokenModuleEventType { pub fn to_type_discriminator(&self) -> TokenModuleCborTypeDiscriminator { TokenModuleCborTypeDiscriminator::from_str(self.as_str()).expect("static length") } + + /// Convert from "dynamic" representation of the reject reason type to static + pub fn try_from_type_discriminator( + type_discriminator: &TokenModuleCborTypeDiscriminator, + ) -> Result { + Ok(match type_discriminator.as_ref() { + "addAllowList" => TokenModuleEventType::AddAllowList, + "removeAllowList" => TokenModuleEventType::RemoveAllowList, + "addDenyList" => TokenModuleEventType::AddDenyList, + "removeDenyList" => TokenModuleEventType::RemoveDenyList, + "pause" => TokenModuleEventType::Pause, + "unpause" => TokenModuleEventType::Unpause, + _ => { + return Err(UnknownTokenModuleEventTypeError( + type_discriminator.to_string(), + )) + } + }) + } } /// Token module event parsed from type and CBOR diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs index 32137f04e..b6c836e28 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs @@ -118,7 +118,7 @@ const CBOR_TAG: u64 = 24; pub struct TokenOperationsPayload { /// Id of the token pub token_id: TokenId, - /// Token operations in the transaction + /// Token operations in the transaction. CBOR encoding of [`TokenOperations`] pub operations: RawCbor, } diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs index 9da436a8c..7e96dcb48 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs @@ -22,6 +22,11 @@ pub enum TokenModuleRejectReasonType { MintWouldOverflow, } +/// Unknown token module reject reason +#[derive(Debug, thiserror::Error)] +#[error("Unknown token module reject reason type: {0}")] +pub struct UnknownTokenModuleRejectReasonTypeError(String); + impl TokenModuleRejectReasonType { /// String identifier for the reject reason type const fn as_str(&self) -> &'static str { @@ -39,6 +44,25 @@ impl TokenModuleRejectReasonType { pub fn to_type_discriminator(&self) -> TokenModuleCborTypeDiscriminator { TokenModuleCborTypeDiscriminator::from_str(self.as_str()).expect("static length") } + + /// Convert from "dynamic" representation of the reject reason type to static + pub fn try_from_type_discriminator( + type_discriminator: &TokenModuleCborTypeDiscriminator, + ) -> Result { + Ok(match type_discriminator.as_ref() { + "addressNotFound" => TokenModuleRejectReasonType::AddressNotFound, + "tokenBalanceInsufficient" => TokenModuleRejectReasonType::TokenBalanceInsufficient, + "deserializationFailure" => TokenModuleRejectReasonType::DeserializationFailure, + "unsupportedOperation" => TokenModuleRejectReasonType::UnsupportedOperation, + "operationNotPermitted" => TokenModuleRejectReasonType::OperationNotPermitted, + "mintWouldOverflow" => TokenModuleRejectReasonType::MintWouldOverflow, + _ => { + return Err(UnknownTokenModuleRejectReasonTypeError( + type_discriminator.to_string(), + )) + } + }) + } } /// Token module reject reason parsed from type and CBOR if possible @@ -84,6 +108,8 @@ impl TokenModuleRejectReasonEnum { } } } + + // todo ar encode+decode } /// A token holder address was not valid. From 489a98ab414bb371b248b92f06019b76a145429c Mon Sep 17 00:00:00 2001 From: Allan Rasmussen Date: Wed, 31 Dec 2025 10:25:20 +0100 Subject: [PATCH 09/18] fix --- .../src/protocol_level_tokens/token_event.rs | 34 ++++++++++++++++++- .../token_reject_reason.rs | 34 +++++++++++++++++-- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs index 2af5154ed..e234b05a0 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs @@ -1,4 +1,5 @@ -use super::{CborHolderAccount, TokenAmount}; +use super::{CborHolderAccount, RawCbor, TokenAmount}; +use crate::common::cbor; use crate::transactions::Memo; use concordium_base_derive::{CborDeserialize, CborSerialize}; use concordium_contracts_common::AccountAddress; @@ -99,6 +100,37 @@ impl TokenModuleEventEnum { TokenModuleEventEnum::Unpause(_) => TokenModuleEventType::Unpause, } } + + /// Encode event as CBOR. Returns the event type and its CBOR encoding. + pub fn encode_event(&self) -> (TokenModuleEventType, RawCbor) { + // todo ar unwrap + match self { + TokenModuleEventEnum::AddAllowList(event) => ( + TokenModuleEventType::AddAllowList, + RawCbor::from(cbor::cbor_encode(event).unwrap()), + ), + TokenModuleEventEnum::RemoveAllowList(event) => ( + TokenModuleEventType::RemoveAllowList, + RawCbor::from(cbor::cbor_encode(event).unwrap()), + ), + TokenModuleEventEnum::AddDenyList(event) => ( + TokenModuleEventType::AddDenyList, + RawCbor::from(cbor::cbor_encode(event).unwrap()), + ), + TokenModuleEventEnum::RemoveDenyList(event) => ( + TokenModuleEventType::RemoveDenyList, + RawCbor::from(cbor::cbor_encode(event).unwrap()), + ), + TokenModuleEventEnum::Pause(event) => ( + TokenModuleEventType::Pause, + RawCbor::from(cbor::cbor_encode(event).unwrap()), + ), + TokenModuleEventEnum::Unpause(event) => ( + TokenModuleEventType::Unpause, + RawCbor::from(cbor::cbor_encode(event).unwrap()), + ), + } + } } pub type AddAllowListEvent = TokenListUpdateEventDetails; diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs index 7e96dcb48..aad065db4 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs @@ -1,5 +1,6 @@ +use crate::common::cbor; use crate::protocol_level_tokens::{ - token_holder::CborHolderAccount, TokenAmount, TokenModuleCborTypeDiscriminator, + token_holder::CborHolderAccount, RawCbor, TokenAmount, TokenModuleCborTypeDiscriminator, }; use concordium_base_derive::{CborDeserialize, CborSerialize}; use std::str::FromStr; @@ -109,7 +110,36 @@ impl TokenModuleRejectReasonEnum { } } - // todo ar encode+decode + /// Encode reject reason as CBOR. Returns the reject reason type and its CBOR encoding. + pub fn encode_reject_reason(&self) -> (TokenModuleRejectReasonType, RawCbor) { + // todo ar unwrap + match self { + TokenModuleRejectReasonEnum::AddressNotFound(reject_reason) => ( + TokenModuleRejectReasonType::AddressNotFound, + RawCbor::from(cbor::cbor_encode(reject_reason).unwrap()), + ), + TokenModuleRejectReasonEnum::TokenBalanceInsufficient(reject_reason) => ( + TokenModuleRejectReasonType::TokenBalanceInsufficient, + RawCbor::from(cbor::cbor_encode(reject_reason).unwrap()), + ), + TokenModuleRejectReasonEnum::DeserializationFailure(reject_reason) => ( + TokenModuleRejectReasonType::DeserializationFailure, + RawCbor::from(cbor::cbor_encode(reject_reason).unwrap()), + ), + TokenModuleRejectReasonEnum::UnsupportedOperation(reject_reason) => ( + TokenModuleRejectReasonType::UnsupportedOperation, + RawCbor::from(cbor::cbor_encode(reject_reason).unwrap()), + ), + TokenModuleRejectReasonEnum::OperationNotPermitted(reject_reason) => ( + TokenModuleRejectReasonType::OperationNotPermitted, + RawCbor::from(cbor::cbor_encode(reject_reason).unwrap()), + ), + TokenModuleRejectReasonEnum::MintWouldOverflow(reject_reason) => ( + TokenModuleRejectReasonType::MintWouldOverflow, + RawCbor::from(cbor::cbor_encode(reject_reason).unwrap()), + ), + } + } } /// A token holder address was not valid. From df0e2b9591485b017e58f5b3b734dde448c7f19a Mon Sep 17 00:00:00 2001 From: Allan Rasmussen Date: Wed, 31 Dec 2025 10:33:54 +0100 Subject: [PATCH 10/18] cbor --- .../src/protocol_level_tokens/token_event.rs | 26 +++++++++++++++++ .../token_reject_reason.rs | 28 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs index e234b05a0..184edbe60 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs @@ -1,5 +1,6 @@ use super::{CborHolderAccount, RawCbor, TokenAmount}; use crate::common::cbor; +use crate::common::cbor::CborSerializationResult; use crate::transactions::Memo; use concordium_base_derive::{CborDeserialize, CborSerialize}; use concordium_contracts_common::AccountAddress; @@ -131,6 +132,31 @@ impl TokenModuleEventEnum { ), } } + + /// Decode event from CBOR encoding + pub fn decode_event( + event_type: TokenModuleEventType, + cbor: &RawCbor, + ) -> CborSerializationResult { + Ok(match event_type { + TokenModuleEventType::AddAllowList => { + TokenModuleEventEnum::AddAllowList(cbor::cbor_decode(cbor)?) + } + TokenModuleEventType::RemoveAllowList => { + TokenModuleEventEnum::RemoveAllowList(cbor::cbor_decode(cbor)?) + } + TokenModuleEventType::AddDenyList => { + TokenModuleEventEnum::AddDenyList(cbor::cbor_decode(cbor)?) + } + TokenModuleEventType::RemoveDenyList => { + TokenModuleEventEnum::RemoveDenyList(cbor::cbor_decode(cbor)?) + } + TokenModuleEventType::Pause => TokenModuleEventEnum::Pause(cbor::cbor_decode(cbor)?), + TokenModuleEventType::Unpause => { + TokenModuleEventEnum::Unpause(cbor::cbor_decode(cbor)?) + } + }) + } } pub type AddAllowListEvent = TokenListUpdateEventDetails; diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs index aad065db4..47ccef958 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs @@ -1,4 +1,5 @@ use crate::common::cbor; +use crate::common::cbor::CborSerializationResult; use crate::protocol_level_tokens::{ token_holder::CborHolderAccount, RawCbor, TokenAmount, TokenModuleCborTypeDiscriminator, }; @@ -140,6 +141,33 @@ impl TokenModuleRejectReasonEnum { ), } } + + /// Decode reject reason from CBOR encoding + pub fn decode_reject_reason( + reject_reason_type: TokenModuleRejectReasonType, + cbor: &RawCbor, + ) -> CborSerializationResult { + Ok(match reject_reason_type { + TokenModuleRejectReasonType::AddressNotFound => { + TokenModuleRejectReasonEnum::AddressNotFound(cbor::cbor_decode(cbor)?) + } + TokenModuleRejectReasonType::TokenBalanceInsufficient => { + TokenModuleRejectReasonEnum::TokenBalanceInsufficient(cbor::cbor_decode(cbor)?) + } + TokenModuleRejectReasonType::DeserializationFailure => { + TokenModuleRejectReasonEnum::DeserializationFailure(cbor::cbor_decode(cbor)?) + } + TokenModuleRejectReasonType::UnsupportedOperation => { + TokenModuleRejectReasonEnum::UnsupportedOperation(cbor::cbor_decode(cbor)?) + } + TokenModuleRejectReasonType::OperationNotPermitted => { + TokenModuleRejectReasonEnum::OperationNotPermitted(cbor::cbor_decode(cbor)?) + } + TokenModuleRejectReasonType::MintWouldOverflow => { + TokenModuleRejectReasonEnum::MintWouldOverflow(cbor::cbor_decode(cbor)?) + } + }) + } } /// A token holder address was not valid. From 27f16fbe879a14f70f525fff6dd5c3c52070c4df Mon Sep 17 00:00:00 2001 From: Allan Rasmussen Date: Thu, 8 Jan 2026 15:06:46 +0100 Subject: [PATCH 11/18] tokeniddisplay --- .../concordium_base/src/protocol_level_tokens/token_id.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_id.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_id.rs index 7dfe2fa07..3a64f48cb 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_id.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_id.rs @@ -1,4 +1,5 @@ use crate::common; +use std::fmt::{Display, Formatter}; /// The limit for the length of the byte encoding of a Token ID. pub const TOKEN_ID_MIN_BYTE_LEN: usize = 1; @@ -32,6 +33,12 @@ impl AsRef for TokenId { } } +impl Display for TokenId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.value) + } +} + impl std::str::FromStr for TokenId { type Err = TokenIdFromStringError; From e93c22dc3def1473c6244d68dd82309d444c9942 Mon Sep 17 00:00:00 2001 From: Allan Rasmussen Date: Thu, 8 Jan 2026 15:13:32 +0100 Subject: [PATCH 12/18] removeupward --- rust-src/concordium_base/src/common/mod.rs | 2 +- rust-src/concordium_base/src/common/upward.rs | 334 ------------------ 2 files changed, 1 insertion(+), 335 deletions(-) delete mode 100644 rust-src/concordium_base/src/common/upward.rs diff --git a/rust-src/concordium_base/src/common/mod.rs b/rust-src/concordium_base/src/common/mod.rs index 6beb574f9..378fceca3 100644 --- a/rust-src/concordium_base/src/common/mod.rs +++ b/rust-src/concordium_base/src/common/mod.rs @@ -53,4 +53,4 @@ pub type size_t = usize; /// Module that provides a simple API for symmetric encryption in the output /// formats used by Concordium. pub mod encryption; -pub mod upward; + diff --git a/rust-src/concordium_base/src/common/upward.rs b/rust-src/concordium_base/src/common/upward.rs deleted file mode 100644 index a156070a8..000000000 --- a/rust-src/concordium_base/src/common/upward.rs +++ /dev/null @@ -1,334 +0,0 @@ -use crate::common::cbor::{ - value, CborDecoder, CborDeserialize, CborEncoder, CborMaybeKnown, CborSerializationResult, - CborSerialize, -}; -use std::any::type_name; - -/// Type for forward-compatibility with the Concordium Node API. -/// -/// Wraps enum types which are expected to be extended some future version of -/// the Concordium Node API allowing the current SDK version to handle when new -/// variants are introduced in the API, unknown to this version of the SDK. -/// This is also used for helper methods extracting deeply nested information. -/// -/// # `serde` implementation (deprecated). -/// -/// To ensure some level of backwards-compatibility this implements -/// [`serde::Serialize`] and [`serde::Deserialize`], but serializing -/// `Upward::Unknown` will produce a runtime error and deserializing can only -/// produce `Upward::Known`. -/// The serde implementation should be considered deprecated and might be -/// removed in a future version. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Upward { - /// New unknown variant, the structure is not known to the current version - /// of this library. Consider updating the library if support is needed. - /// - /// For protocols that support decoding unknown data, the residual value is - /// a representation of unknown data (represented by a dynamic data type). - /// This is the case for CBOR e.g., but not possible for protobuf that is - /// not self-descriptive. - Unknown(R), - /// Known variant. - Known(A), -} - -impl Upward { - /// Returns the contained [`Upward::Known`] value, consuming the `self` - /// value. - /// - /// # Panics - /// - /// Panics if the self value equals [`Upward::Unknown`]. - pub fn unwrap(self) -> A { - match self { - Self::Known(value) => value, - Self::Unknown(_) => panic!( - "called `Upward::<{}>::unwrap()` on an `Unknown` value", - type_name::() - ), - } - } - - /// Transforms `Upward` into a [`Option`] where [`Option::Some`] - /// represents [`Upward::Known`] and [`Option::None`] represents - /// [`Upward::Unknown`]. - pub fn known(self) -> Option { - Option::from(self) - } - - /// Borrow `Upward` aa [`Option<&T>`] where [`Option::Some`] - /// represents [`Upward::Known`] and [`Option::None`] represents - /// [`Upward::Unknown`]. - pub fn as_known(&self) -> Option<&A> { - Option::from(self.as_ref_with_residual()) - } - - /// Require the data to be known, converting it from `Upward` to - /// `Result`. - /// - /// This is effectively opt out of forward-compatibility, forcing the - /// library to be up to date with the node version. - pub fn known_or_err(self) -> Result { - self.known_or(UnknownDataError { - type_name: type_name::(), - }) - } - - /// Transforms the `Upward` into a [`Result`], mapping - /// [`Known(v)`] to [`Ok(v)`] and [`Upward::Unknown`] to [`Err(err)`]. - /// - /// Arguments passed to `known_or` are eagerly evaluated; if you are passing - /// the result of a function call, it is recommended to use - /// [`known_or_else`], which is lazily evaluated. - /// - /// [`Ok(v)`]: Ok - /// [`Err(err)`]: Err - /// [`Known(v)`]: Upward::Known - /// [`known_or_else`]: Upward::known_or_else - pub fn known_or(self, error: E) -> Result { - Option::from(self).ok_or(error) - } - - /// Transforms the `Upward` into a [`Result`], mapping - /// [`Known(v)`] to [`Ok(v)`] and [`Upward::Unknown`] to [`Err(err())`]. - /// - /// [`Ok(v)`]: Ok - /// [`Err(err())`]: Err - /// [`Known(v)`]: Upward::Known - pub fn known_or_else(self, error: F) -> Result - where - F: FnOnce(R) -> E, - { - match self { - Upward::Unknown(residual) => Err(error(residual)), - Upward::Known(output) => Ok(output), - } - } - - /// Returns `true` if the Upward is a [`Upward::Known`] and the value inside - /// of it matches a predicate. - pub fn is_known_and(self, f: impl FnOnce(A) -> bool) -> bool { - Option::from(self).is_some_and(f) - } - - /// Maps an `Upward` to `Upward` by applying a function to a contained - /// value (if `Known`) or returns `Unknown` (if `Unknown`). - pub fn map(self, f: F) -> Upward - where - F: FnOnce(A) -> U, - { - match self { - Self::Known(x) => Upward::Known(f(x)), - Self::Unknown(r) => Upward::Unknown(r), - } - } - - /// Maps an `Upward` to `Upward` by applying a function to - /// the residual value in `Unknown`. - pub fn map_unknown(self, f: F) -> Upward - where - F: FnOnce(R) -> S, - { - match self { - Self::Known(x) => Upward::Known(x), - Self::Unknown(r) => Upward::Unknown(f(r)), - } - } - - /// Returns the provided default result (if [`Upward::Unknown`]), - /// or applies a function to the contained value (if [`Upward::Known`]). - /// - /// Arguments passed to `map_or` are eagerly evaluated; if you are passing - /// the result of a function call, it is recommended to use [`map_or_else`], - /// which is lazily evaluated. - /// - /// [`map_or_else`]: Upward::map_or_else - #[must_use = "if you don't need the returned value, use `if let` instead"] - pub fn map_or(self, default: U, f: F) -> U - where - F: FnOnce(A) -> U, - { - match self { - Upward::Known(a) => f(a), - Upward::Unknown(_) => default, - } - } - - /// Computes a default function result (if [`Upward::Unknown`]), or - /// applies a different function to the contained value (if - /// [`Upward::Known`]). - pub fn map_or_else(self, default: D, f: F) -> U - where - D: FnOnce() -> U, - F: FnOnce(A) -> U, - { - match self { - Upward::Known(t) => f(t), - Upward::Unknown(_) => default(), - } - } - - /// Converts from `&Upward` to `Upward<&A, &R>`. - pub const fn as_ref_with_residual(&self) -> Upward<&A, &R> { - match *self { - Self::Known(ref x) => Upward::Known(x), - Self::Unknown(ref r) => Upward::Unknown(r), - } - } - - /// Returns [`Upward::Unknown`] if the option is [`Upward::Unknown`], - /// otherwise calls `f` with the wrapped value and returns the result. - pub fn and_then(self, f: F) -> Upward - where - F: FnOnce(A) -> Upward, - { - match self { - Upward::Unknown(r) => Upward::Unknown(r), - Upward::Known(x) => f(x), - } - } -} - -/// Special case where the residual type is a CBOR value, so that CBOR can be deserialized -/// to an unknown variant in the case that the library version is behind. -pub type CborUpward = Upward; - -impl CborSerialize for CborUpward { - fn serialize(&self, encoder: C) -> Result<(), C::WriteError> { - match self { - Self::Unknown(value) => value.serialize(encoder), - Self::Known(value) => value.serialize(encoder), - } - } -} - -impl CborDeserialize for CborUpward { - fn deserialize(decoder: C) -> CborSerializationResult - where - Self: Sized, - { - Ok(match T::deserialize_maybe_known(decoder)? { - CborMaybeKnown::Unknown(r) => Self::Unknown(r), - CborMaybeKnown::Known(val) => Self::Known(val), - }) - } -} - -impl Upward { - /// Converts from `&Upward` to `Upward<&A>`. - pub const fn as_ref(&self) -> Upward<&A> { - match *self { - Self::Known(ref x) => Upward::Known(x), - Self::Unknown(_) => Upward::Unknown(()), - } - } -} - -impl Upward, R> { - /// Transposes an `Upward` of a [`Result`] into a [`Result`] of an `Upward`. - pub fn transpose(self) -> Result, E> { - match self { - Upward::Known(Ok(x)) => Ok(Upward::Known(x)), - Upward::Known(Err(e)) => Err(e), - Upward::Unknown(r) => Ok(Upward::Unknown(r)), - } - } -} - -impl From> for Upward { - fn from(value: Option) -> Self { - if let Some(n) = value { - Self::Known(n) - } else { - Self::Unknown(()) - } - } -} - -impl From> for Option { - fn from(value: Upward) -> Self { - if let Upward::Known(n) = value { - Some(n) - } else { - None - } - } -} - -impl<'de, A, R> serde::Deserialize<'de> for Upward -where - A: serde::Deserialize<'de>, -{ - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - A::deserialize(deserializer).map(Upward::Known) - } -} - -impl serde::Serialize for Upward -where - A: serde::Serialize, -{ - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - if let Upward::Known(a) = self { - a.serialize(serializer) - } else { - Err(serde::ser::Error::custom(format!( - "Serializing `Upward::<{}>::Unknown` is not supported", - type_name::() - ))) - } - } -} - -#[derive(Debug, thiserror::Error)] -#[error("Encountered unknown data from the Node API on type `Upward::<{type_name}>::Unknown`, which is required to be known.")] -pub struct UnknownDataError { - type_name: &'static str, -} - -impl UnknownDataError { - pub fn new(type_name: &'static str) -> Self { - Self { type_name } - } -} - -impl std::iter::FromIterator> for Upward> { - fn from_iter>>(iter: T) -> Self { - let mut vec = Vec::new(); - for a in iter { - if let Upward::Known(a) = a { - vec.push(a); - } else { - return Upward::Unknown(()); - } - } - Upward::Known(vec) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_upward_from_iterator_all_known() { - let list = vec![Upward::Known(42); 50]; - let res = list.into_iter().collect::>().unwrap(); - assert_eq!(vec![42; 50], res) - } - - #[test] - fn test_upward_from_iterator_some_unknown() { - let mut list = vec![Upward::Known(42); 50]; - list[25] = Upward::Unknown(()); - let res = list.into_iter().collect::>(); - assert_eq!(Upward::Unknown(()), res) - } -} From c975b4c4d43a2bd5a14833aa212610d74d40a627 Mon Sep 17 00:00:00 2001 From: Allan Rasmussen Date: Thu, 8 Jan 2026 15:13:43 +0100 Subject: [PATCH 13/18] fmt --- rust-src/concordium_base/src/common/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust-src/concordium_base/src/common/mod.rs b/rust-src/concordium_base/src/common/mod.rs index 378fceca3..148ff00a9 100644 --- a/rust-src/concordium_base/src/common/mod.rs +++ b/rust-src/concordium_base/src/common/mod.rs @@ -53,4 +53,3 @@ pub type size_t = usize; /// Module that provides a simple API for symmetric encryption in the output /// formats used by Concordium. pub mod encryption; - From b69f80382e4ca25695fdf49a9370d0b16cd93ab4 Mon Sep 17 00:00:00 2001 From: Allan Rasmussen Date: Thu, 8 Jan 2026 15:17:20 +0100 Subject: [PATCH 14/18] fix --- .../src/protocol_level_tokens/token_operations.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs index b44920872..891dc005b 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs @@ -292,15 +292,6 @@ pub enum CborMemo { Cbor(Memo), } -impl From for Memo { - fn from(value: CborMemo) -> Self { - match value { - CborMemo::Raw(memo) => memo, - CborMemo::Cbor(memo) => memo, - } - } -} - #[cfg(test)] pub mod test { use super::*; From 7317ca0c4f2da067cc501fd39c392de09539fd2d Mon Sep 17 00:00:00 2001 From: Allan Rasmussen Date: Thu, 8 Jan 2026 15:26:15 +0100 Subject: [PATCH 15/18] doc --- .../src/protocol_level_tokens/token_event.rs | 8 ++++---- .../src/protocol_level_tokens/token_reject_reason.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs index 1259fe2a5..36e94307f 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_event.rs @@ -7,7 +7,7 @@ use concordium_contracts_common::AccountAddress; use std::fmt::{Display, Formatter}; use std::str::FromStr; -/// Token module event parsed from type and CBOR +/// Token module event type #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum TokenModuleEventType { /// An account was added to the allow list of a protocol level token ([`AddAllowListEvent`]) @@ -26,9 +26,9 @@ pub enum TokenModuleEventType { Unpause, } -/// Unknown token module reject reason +/// Unknown token module event #[derive(Debug, thiserror::Error)] -#[error("Unknown token module reject reason type: {0}")] +#[error("Unknown token module event type: {0}")] pub struct UnknownTokenModuleEventTypeError(String); impl TokenModuleEventType { @@ -132,7 +132,7 @@ impl TokenModuleEventEnum { } } - /// Decode event from CBOR encoding + /// Decode event from CBOR encoding assuming type given by `event_type`. pub fn decode_event( event_type: TokenModuleEventType, cbor: &RawCbor, diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs index 589e2ee7f..d1e93ecf6 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs @@ -67,7 +67,7 @@ impl TokenModuleRejectReasonType { } } -/// Token module reject reason parsed from type and CBOR if possible +/// Token module reject reason parsed from type and CBOR #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub enum TokenModuleRejectReasonEnum { @@ -141,7 +141,7 @@ impl TokenModuleRejectReasonEnum { } } - /// Decode reject reason from CBOR encoding + /// Decode reject reason from CBOR encoding assuming it is of the type given by `reject_reason_type`. pub fn decode_reject_reason( reject_reason_type: TokenModuleRejectReasonType, cbor: &RawCbor, From 05c0336378607c7f6caa60fc5a37b4dda389e96f Mon Sep 17 00:00:00 2001 From: Allan Rasmussen Date: Thu, 8 Jan 2026 15:45:24 +0100 Subject: [PATCH 16/18] changelog --- rust-src/concordium_base/CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/rust-src/concordium_base/CHANGELOG.md b/rust-src/concordium_base/CHANGELOG.md index 38ea0e362..abf7fca4e 100644 --- a/rust-src/concordium_base/CHANGELOG.md +++ b/rust-src/concordium_base/CHANGELOG.md @@ -1,6 +1,24 @@ ## Unreleased - `cbor::cbor_encode` is now infallible and returns `Vec` instead of `CborSerializationResult>` +- Removed the module `upward`. This will be added to Rust SDK crate instead. +- Changes to `protocol_level_tokens` module: + - Removed the usage of `Upward` in `TokenOperations` type. To CBOR decode and allow unknown variants, + the new function `TokenOperationsPayload::decode_operations_maybe_known` can be used instead. + - Removed the types `TokenModuleRejectReason`, `TokenEvent`, `TokenEventDetails`, `TokenModuleEvent`. These types + will be moved to the Rust SDK crate. + - Renamed the existing type `TokenModuleEventType` that contains full token module events to `TokenModuleEventEnum` and + created a new `TokenModuleEventType` that is only the type of event. The new methods + `TokenModuleEventEnum::encode_event` and `TokenModuleEventEnum::decode_event` allows CBOR encoding from and CBOR decoding + to `TokenModuleEventEnum`. The new methods `TokenModuleEventType::to_type_discriminator` and + `TokenModuleEventType::try_from_type_discriminator` allows converting between `TokenModuleEventType` + and `TokenModuleCborTypeDiscriminator`. + - Renamed the existing type `TokenModuleRejectReasonType` that contains full token module reject reasons to `TokenModuleRejectReasonEnum` and + created a new `TokenModuleRejectReasonType` that is only the type of reject reason. The new methods + `TokenModuleRejectReasonEnum::encode_reject_reason` and `TokenModuleRejectReasonEnum::decode_reject_reason` allows CBOR encoding from and CBOR decoding + to `TokenModuleRejectReasonEnum`. The new methods `TokenModuleRejectReasonType::to_type_discriminator` and + `TokenModuleRejectReasonType::try_from_type_discriminator` allows converting between `TokenModuleRejectReasonType` + and `TokenModuleCborTypeDiscriminator`. ## 10.0.0-alpha.0 (2025-12-08) From a94cae8fe9286fd9465323f149b24f3c500b9793 Mon Sep 17 00:00:00 2001 From: Allan Rasmussen Date: Thu, 8 Jan 2026 17:12:22 +0100 Subject: [PATCH 17/18] fix --- .../src/protocol_level_tokens/token_reject_reason.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs index d1e93ecf6..d85b0f410 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_reject_reason.rs @@ -11,7 +11,7 @@ use std::str::FromStr; pub enum TokenModuleRejectReasonType { /// Address not found: [`AddressNotFoundRejectReason`] AddressNotFound, - /// Token balance is insufficient ([`TokenBalanceInsufficient`]) + /// Token balance is insufficient ([`TokenBalanceInsufficientRejectReason`]) TokenBalanceInsufficient, /// The transaction could not be deserialized ([`DeserializationFailureRejectReason`]) DeserializationFailure, From fabdc5dcddb13051dd4ab3ceab96c1a5e799f25a Mon Sep 17 00:00:00 2001 From: Allan Rasmussen Date: Fri, 9 Jan 2026 12:06:45 +0100 Subject: [PATCH 18/18] fix --- .../src/protocol_level_tokens/token_operations.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs b/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs index 891dc005b..b44920872 100644 --- a/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs +++ b/rust-src/concordium_base/src/protocol_level_tokens/token_operations.rs @@ -292,6 +292,15 @@ pub enum CborMemo { Cbor(Memo), } +impl From for Memo { + fn from(value: CborMemo) -> Self { + match value { + CborMemo::Raw(memo) => memo, + CborMemo::Cbor(memo) => memo, + } + } +} + #[cfg(test)] pub mod test { use super::*;