diff --git a/rust-src/concordium_base/CHANGELOG.md b/rust-src/concordium_base/CHANGELOG.md index be7266fa3..5f0409e0a 100644 --- a/rust-src/concordium_base/CHANGELOG.md +++ b/rust-src/concordium_base/CHANGELOG.md @@ -1,5 +1,23 @@ ## 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 (2026-01-09) diff --git a/rust-src/concordium_base/src/common/mod.rs b/rust-src/concordium_base/src/common/mod.rs index 6beb574f9..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; -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) - } -} 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 dd754cdd3..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 @@ -1,70 +1,70 @@ -use super::{cbor::RawCbor, CborHolderAccount, TokenAmount, TokenId}; -use crate::common::upward::{CborUpward, Upward}; -use crate::{ - common::cbor::{self, CborSerializationResult}, - transactions::Memo, -}; +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; +use std::fmt::{Display, Formatter}; +use std::str::FromStr; -/// 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), +/// 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`]) + 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, } -/// 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, -} +/// Unknown token module event +#[derive(Debug, thiserror::Error)] +#[error("Unknown token module event type: {0}")] +pub struct UnknownTokenModuleEventTypeError(String); + +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", + } + } -impl TokenModuleEvent { - /// Decode token module event from CBOR - pub fn decode_token_module_event( - &self, - ) -> CborSerializationResult> { - use TokenModuleEventType::*; + /// 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") + } - 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())?)) + /// 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(), + )) } - "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())?), }) } } @@ -72,23 +72,99 @@ impl TokenModuleEvent { /// 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), } +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, + } + } + + /// Encode event as CBOR. Returns the event type and its CBOR encoding. + pub fn encode_event(&self) -> (TokenModuleEventType, RawCbor) { + match self { + TokenModuleEventEnum::AddAllowList(event) => ( + TokenModuleEventType::AddAllowList, + RawCbor::from(cbor::cbor_encode(event)), + ), + TokenModuleEventEnum::RemoveAllowList(event) => ( + TokenModuleEventType::RemoveAllowList, + RawCbor::from(cbor::cbor_encode(event)), + ), + TokenModuleEventEnum::AddDenyList(event) => ( + TokenModuleEventType::AddDenyList, + RawCbor::from(cbor::cbor_encode(event)), + ), + TokenModuleEventEnum::RemoveDenyList(event) => ( + TokenModuleEventType::RemoveDenyList, + RawCbor::from(cbor::cbor_encode(event)), + ), + TokenModuleEventEnum::Pause(event) => ( + TokenModuleEventType::Pause, + RawCbor::from(cbor::cbor_encode(event)), + ), + TokenModuleEventEnum::Unpause(event) => ( + TokenModuleEventType::Unpause, + RawCbor::from(cbor::cbor_encode(event)), + ), + } + } + + /// Decode event from CBOR encoding assuming type given by `event_type`. + 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; +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( @@ -169,7 +245,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 { @@ -193,6 +269,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; @@ -232,123 +314,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); + let cbor = cbor::cbor_encode(&event); assert_eq!(hex::encode(&cbor), "a166746172676574d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"); - let module_event = TokenModuleEvent { - event_type: "addAllowList".to_string().try_into().unwrap(), - details: cbor.into(), - }; - let module_event_type = module_event.decode_token_module_event().unwrap(); - assert_eq!( - module_event_type, - Upward::Known(TokenModuleEventType::AddAllowList(variant)) - ); + 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); + let cbor = cbor::cbor_encode(&event); assert_eq!(hex::encode(&cbor), "a166746172676574d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"); - let module_event = TokenModuleEvent { - event_type: "removeAllowList".to_string().try_into().unwrap(), - details: cbor.into(), - }; - let module_event_type = module_event.decode_token_module_event().unwrap(); - assert_eq!( - module_event_type, - Upward::Known(TokenModuleEventType::RemoveAllowList(variant)) - ); + 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); + let cbor = cbor::cbor_encode(&event); assert_eq!(hex::encode(&cbor), "a166746172676574d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"); - let module_event = TokenModuleEvent { - event_type: "addDenyList".to_string().try_into().unwrap(), - details: cbor.into(), - }; - let module_event_type = module_event.decode_token_module_event().unwrap(); - assert_eq!( - module_event_type, - Upward::Known(TokenModuleEventType::AddDenyList(variant)) - ); + 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); + let cbor = cbor::cbor_encode(&event); assert_eq!(hex::encode(&cbor), "a166746172676574d99d73a10358200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"); - let module_event = TokenModuleEvent { - event_type: "removeDenyList".to_string().try_into().unwrap(), - details: cbor.into(), - }; - let module_event_type = module_event.decode_token_module_event().unwrap(); - assert_eq!( - module_event_type, - Upward::Known(TokenModuleEventType::RemoveDenyList(variant)) - ); + 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); + let event = TokenPauseEventDetails {}; + let cbor = cbor::cbor_encode(&event); assert_eq!(hex::encode(&cbor), "a0"); - let module_event = TokenModuleEvent { - event_type: "pause".to_string().try_into().unwrap(), - details: cbor.into(), - }; - let module_event_type = module_event.decode_token_module_event().unwrap(); - assert_eq!( - module_event_type, - Upward::Known(TokenModuleEventType::Pause(variant)) - ); + 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); + let event = TokenPauseEventDetails {}; + let cbor = cbor::cbor_encode(&event); assert_eq!(hex::encode(&cbor), "a0"); - let module_event = TokenModuleEvent { - event_type: "unpause".to_string().try_into().unwrap(), - details: cbor.into(), - }; - let module_event_type = module_event.decode_token_module_event().unwrap(); - assert_eq!( - module_event_type, - Upward::Known(TokenModuleEventType::Unpause(variant)) - ); + let event_decoded: TokenPauseEventDetails = cbor::cbor_decode(cbor).unwrap(); + assert_eq!(event_decoded, event); } } 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; 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 2d139ace9..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 @@ -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}, @@ -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, } @@ -127,6 +127,18 @@ impl TokenOperationsPayload { pub fn decode_operations(&self) -> CborSerializationResult { cbor::cbor_decode(&self.operations) } + + /// 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) + } } /// A list of protocol level token operations. Can be composed to a protocol @@ -136,19 +148,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 } } } @@ -280,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::*; @@ -309,14 +330,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); @@ -455,13 +476,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)), + }; + + let operations_decoded = payload.decode_operations().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_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 5986b062f..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 @@ -1,65 +1,76 @@ -use crate::common::upward::{CborUpward, Upward}; -use crate::{ - common::{cbor, cbor::CborSerializationResult}, - protocol_level_tokens::{ - token_holder::CborHolderAccount, RawCbor, TokenAmount, TokenId, - TokenModuleCborTypeDiscriminator, - }, +use crate::common::cbor; +use crate::common::cbor::CborSerializationResult; +use crate::protocol_level_tokens::{ + token_holder::CborHolderAccount, RawCbor, TokenAmount, TokenModuleCborTypeDiscriminator, }; -use 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 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, - /// (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 ([`TokenBalanceInsufficientRejectReason`]) + 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, } -impl TokenModuleRejectReason { - /// Decode reject reason from CBOR - pub fn decode_reject_reason( - &self, - ) -> CborSerializationResult> { - use TokenModuleRejectReasonType::*; +/// 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 { + 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() { - "addressNotFound" => Upward::Known(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( - self.details.as_ref().context("no CBOR details")?.as_ref(), - )?)), - "unsupportedOperation" => Upward::Known(UnsupportedOperation(cbor::cbor_decode( - self.details.as_ref().context("no CBOR details")?.as_ref(), - )?)), - "operationNotPermitted" => Upward::Known(OperationNotPermitted(cbor::cbor_decode( - self.details.as_ref().context("no CBOR details")?.as_ref(), - )?)), - "mintWouldOverflow" => Upward::Known(MintWouldOverflow(cbor::cbor_decode( - self.details.as_ref().context("no CBOR details")?.as_ref(), - )?)), - _ => Upward::Unknown(cbor::cbor_decode( - self.details.as_ref().context("no CBOR details")?.as_ref(), - )?), + /// 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") + } + + /// 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 +/// Token module reject reason parsed from type and CBOR #[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 @@ -75,6 +86,89 @@ pub enum TokenModuleRejectReasonType { 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 + } + } + } + + /// Encode reject reason as CBOR. Returns the reject reason type and its CBOR encoding. + pub fn encode_reject_reason(&self) -> (TokenModuleRejectReasonType, RawCbor) { + match self { + TokenModuleRejectReasonEnum::AddressNotFound(reject_reason) => ( + TokenModuleRejectReasonType::AddressNotFound, + RawCbor::from(cbor::cbor_encode(reject_reason)), + ), + TokenModuleRejectReasonEnum::TokenBalanceInsufficient(reject_reason) => ( + TokenModuleRejectReasonType::TokenBalanceInsufficient, + RawCbor::from(cbor::cbor_encode(reject_reason)), + ), + TokenModuleRejectReasonEnum::DeserializationFailure(reject_reason) => ( + TokenModuleRejectReasonType::DeserializationFailure, + RawCbor::from(cbor::cbor_encode(reject_reason)), + ), + TokenModuleRejectReasonEnum::UnsupportedOperation(reject_reason) => ( + TokenModuleRejectReasonType::UnsupportedOperation, + RawCbor::from(cbor::cbor_encode(reject_reason)), + ), + TokenModuleRejectReasonEnum::OperationNotPermitted(reject_reason) => ( + TokenModuleRejectReasonType::OperationNotPermitted, + RawCbor::from(cbor::cbor_encode(reject_reason)), + ), + TokenModuleRejectReasonEnum::MintWouldOverflow(reject_reason) => ( + TokenModuleRejectReasonType::MintWouldOverflow, + RawCbor::from(cbor::cbor_encode(reject_reason)), + ), + } + } + + /// 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, + ) -> 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. #[derive( Debug, @@ -213,101 +307,69 @@ mod test { common::cbor, protocol_level_tokens::{token_holder, CborHolderAccount}, }; - use std::str::FromStr; #[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); + let cbor = cbor::cbor_encode(&reject_reason); 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()), - }; - let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); - assert_eq!( - reject_reason_type, - CborUpward::Known(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); + let cbor = cbor::cbor_encode(&reject_reason); 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()), - }; - let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); - assert_eq!( - reject_reason_type, - CborUpward::Known(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); + let cbor = cbor::cbor_encode(&reject_reason); 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()), - }; - let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); - assert_eq!( - reject_reason_type, - CborUpward::Known(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); + let cbor = cbor::cbor_encode(&reject_reason); 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()), - }; - let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); - assert_eq!( - reject_reason_type, - CborUpward::Known(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, @@ -315,41 +377,26 @@ mod test { }), reason: Some("testfailture".to_string()), }; - let cbor = cbor::cbor_encode(&variant); + let cbor = cbor::cbor_encode(&reject_reason); 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()), - }; - let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); - assert_eq!( - reject_reason_type, - CborUpward::Known(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); + let cbor = cbor::cbor_encode(&reject_reason); 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()), - }; - let reject_reason_type = reject_reason.decode_reject_reason().unwrap(); - assert_eq!( - reject_reason_type, - CborUpward::Known(TokenModuleRejectReasonType::MintWouldOverflow(variant)) - ); + let reject_reason_decoded: MintWouldOverflowRejectReason = cbor::cbor_decode(cbor).unwrap(); + assert_eq!(reject_reason_decoded, reject_reason); } } diff --git a/rust-src/concordium_base/src/transactions.rs b/rust-src/concordium_base/src/transactions.rs index a63574564..d8188a12a 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() }