Skip to main content

dpp/data_contract/associated_token/
token_configuration_item.rs

1use crate::balances::credits::TokenAmount;
2use crate::data_contract::associated_token::token_configuration_convention::TokenConfigurationConvention;
3use crate::data_contract::associated_token::token_marketplace_rules::v0::TokenTradeMode;
4use crate::data_contract::associated_token::token_perpetual_distribution::TokenPerpetualDistribution;
5use crate::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers;
6use crate::data_contract::GroupContractPosition;
7use crate::ProtocolError;
8use bincode::Encode;
9use platform_serialization::de::Decode;
10use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize};
11use platform_value::Identifier;
12#[cfg(feature = "serde-conversion")]
13use serde::{Deserialize, Serialize};
14use std::fmt;
15
16#[derive(
17    Debug,
18    Clone,
19    Default,
20    PartialOrd,
21    Encode,
22    Decode,
23    PlatformSerialize,
24    PlatformDeserialize,
25    PartialEq,
26    Eq,
27)]
28#[cfg_attr(
29    feature = "serde-conversion",
30    derive(Serialize, Deserialize),
31    serde(rename_all = "camelCase")
32)]
33pub enum TokenConfigurationChangeItem {
34    #[default]
35    TokenConfigurationNoChange,
36    Conventions(TokenConfigurationConvention),
37    ConventionsControlGroup(AuthorizedActionTakers),
38    ConventionsAdminGroup(AuthorizedActionTakers),
39    MaxSupply(Option<TokenAmount>),
40    MaxSupplyControlGroup(AuthorizedActionTakers),
41    MaxSupplyAdminGroup(AuthorizedActionTakers),
42    PerpetualDistribution(Option<TokenPerpetualDistribution>),
43    PerpetualDistributionControlGroup(AuthorizedActionTakers),
44    PerpetualDistributionAdminGroup(AuthorizedActionTakers),
45    NewTokensDestinationIdentity(Option<Identifier>),
46    NewTokensDestinationIdentityControlGroup(AuthorizedActionTakers),
47    NewTokensDestinationIdentityAdminGroup(AuthorizedActionTakers),
48    MintingAllowChoosingDestination(bool),
49    MintingAllowChoosingDestinationControlGroup(AuthorizedActionTakers),
50    MintingAllowChoosingDestinationAdminGroup(AuthorizedActionTakers),
51    ManualMinting(AuthorizedActionTakers),
52    ManualMintingAdminGroup(AuthorizedActionTakers),
53    ManualBurning(AuthorizedActionTakers),
54    ManualBurningAdminGroup(AuthorizedActionTakers),
55    Freeze(AuthorizedActionTakers),
56    FreezeAdminGroup(AuthorizedActionTakers),
57    Unfreeze(AuthorizedActionTakers),
58    UnfreezeAdminGroup(AuthorizedActionTakers),
59    DestroyFrozenFunds(AuthorizedActionTakers),
60    DestroyFrozenFundsAdminGroup(AuthorizedActionTakers),
61    EmergencyAction(AuthorizedActionTakers),
62    EmergencyActionAdminGroup(AuthorizedActionTakers),
63    MarketplaceTradeMode(TokenTradeMode),
64    MarketplaceTradeModeControlGroup(AuthorizedActionTakers),
65    MarketplaceTradeModeAdminGroup(AuthorizedActionTakers),
66    MainControlGroup(Option<GroupContractPosition>),
67}
68impl TokenConfigurationChangeItem {
69    pub fn payload_serialization(&self) -> Result<Option<Vec<u8>>, ProtocolError> {
70        Ok(match self {
71            TokenConfigurationChangeItem::TokenConfigurationNoChange => None,
72            TokenConfigurationChangeItem::Conventions(convention) => Some(
73                bincode::encode_to_vec(convention, bincode::config::standard())
74                    .map_err(|e| ProtocolError::EncodingError(e.to_string()))?,
75            ),
76            TokenConfigurationChangeItem::ConventionsControlGroup(a)
77            | TokenConfigurationChangeItem::ConventionsAdminGroup(a)
78            | TokenConfigurationChangeItem::MaxSupplyControlGroup(a)
79            | TokenConfigurationChangeItem::MaxSupplyAdminGroup(a)
80            | TokenConfigurationChangeItem::PerpetualDistributionControlGroup(a)
81            | TokenConfigurationChangeItem::PerpetualDistributionAdminGroup(a)
82            | TokenConfigurationChangeItem::NewTokensDestinationIdentityControlGroup(a)
83            | TokenConfigurationChangeItem::NewTokensDestinationIdentityAdminGroup(a)
84            | TokenConfigurationChangeItem::MintingAllowChoosingDestinationControlGroup(a)
85            | TokenConfigurationChangeItem::MintingAllowChoosingDestinationAdminGroup(a)
86            | TokenConfigurationChangeItem::ManualMinting(a)
87            | TokenConfigurationChangeItem::ManualMintingAdminGroup(a)
88            | TokenConfigurationChangeItem::ManualBurning(a)
89            | TokenConfigurationChangeItem::ManualBurningAdminGroup(a)
90            | TokenConfigurationChangeItem::Freeze(a)
91            | TokenConfigurationChangeItem::FreezeAdminGroup(a)
92            | TokenConfigurationChangeItem::Unfreeze(a)
93            | TokenConfigurationChangeItem::UnfreezeAdminGroup(a)
94            | TokenConfigurationChangeItem::DestroyFrozenFunds(a)
95            | TokenConfigurationChangeItem::DestroyFrozenFundsAdminGroup(a)
96            | TokenConfigurationChangeItem::EmergencyAction(a)
97            | TokenConfigurationChangeItem::EmergencyActionAdminGroup(a)
98            | TokenConfigurationChangeItem::MarketplaceTradeModeControlGroup(a)
99            | TokenConfigurationChangeItem::MarketplaceTradeModeAdminGroup(a) => Some(a.to_bytes()),
100            TokenConfigurationChangeItem::MaxSupply(max_supply) => {
101                max_supply.map(|amount| amount.to_be_bytes().to_vec())
102            }
103            TokenConfigurationChangeItem::PerpetualDistribution(distribution) => distribution
104                .as_ref()
105                .map(|dist| {
106                    bincode::encode_to_vec(dist, bincode::config::standard())
107                        .map_err(|e| ProtocolError::EncodingError(e.to_string()))
108                })
109                .transpose()?,
110            TokenConfigurationChangeItem::NewTokensDestinationIdentity(identity) => {
111                identity.map(|id| id.to_vec())
112            }
113            TokenConfigurationChangeItem::MintingAllowChoosingDestination(allow) => {
114                Some(vec![*allow as u8])
115            }
116            TokenConfigurationChangeItem::MarketplaceTradeMode(mode) => Some(
117                bincode::encode_to_vec(mode, bincode::config::standard())
118                    .map_err(|e| ProtocolError::EncodingError(e.to_string()))?,
119            ),
120            TokenConfigurationChangeItem::MainControlGroup(position) => {
121                position.map(|pos| pos.to_be_bytes().to_vec())
122            }
123        })
124    }
125    pub fn u8_item_index(&self) -> u8 {
126        match self {
127            TokenConfigurationChangeItem::TokenConfigurationNoChange => 0,
128            TokenConfigurationChangeItem::Conventions(_) => 1,
129            TokenConfigurationChangeItem::ConventionsControlGroup(_) => 2,
130            TokenConfigurationChangeItem::ConventionsAdminGroup(_) => 3,
131            TokenConfigurationChangeItem::MaxSupply(_) => 4,
132            TokenConfigurationChangeItem::MaxSupplyControlGroup(_) => 5,
133            TokenConfigurationChangeItem::MaxSupplyAdminGroup(_) => 6,
134            TokenConfigurationChangeItem::PerpetualDistribution(_) => 7,
135            TokenConfigurationChangeItem::PerpetualDistributionControlGroup(_) => 8,
136            TokenConfigurationChangeItem::PerpetualDistributionAdminGroup(_) => 9,
137            TokenConfigurationChangeItem::NewTokensDestinationIdentity(_) => 10,
138            TokenConfigurationChangeItem::NewTokensDestinationIdentityControlGroup(_) => 11,
139            TokenConfigurationChangeItem::NewTokensDestinationIdentityAdminGroup(_) => 12,
140            TokenConfigurationChangeItem::MintingAllowChoosingDestination(_) => 13,
141            TokenConfigurationChangeItem::MintingAllowChoosingDestinationControlGroup(_) => 14,
142            TokenConfigurationChangeItem::MintingAllowChoosingDestinationAdminGroup(_) => 15,
143            TokenConfigurationChangeItem::ManualMinting(_) => 16,
144            TokenConfigurationChangeItem::ManualMintingAdminGroup(_) => 17,
145            TokenConfigurationChangeItem::ManualBurning(_) => 18,
146            TokenConfigurationChangeItem::ManualBurningAdminGroup(_) => 19,
147            TokenConfigurationChangeItem::Freeze(_) => 20,
148            TokenConfigurationChangeItem::FreezeAdminGroup(_) => 21,
149            TokenConfigurationChangeItem::Unfreeze(_) => 22,
150            TokenConfigurationChangeItem::UnfreezeAdminGroup(_) => 23,
151            TokenConfigurationChangeItem::DestroyFrozenFunds(_) => 24,
152            TokenConfigurationChangeItem::DestroyFrozenFundsAdminGroup(_) => 25,
153            TokenConfigurationChangeItem::EmergencyAction(_) => 26,
154            TokenConfigurationChangeItem::EmergencyActionAdminGroup(_) => 27,
155            TokenConfigurationChangeItem::MarketplaceTradeMode(_) => 28,
156            TokenConfigurationChangeItem::MarketplaceTradeModeControlGroup(_) => 29,
157            TokenConfigurationChangeItem::MarketplaceTradeModeAdminGroup(_) => 30,
158            TokenConfigurationChangeItem::MainControlGroup(_) => 31,
159        }
160    }
161}
162
163impl fmt::Display for TokenConfigurationChangeItem {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        match self {
166            TokenConfigurationChangeItem::TokenConfigurationNoChange => {
167                write!(f, "No Change in Token Configuration")
168            }
169            TokenConfigurationChangeItem::Conventions(convention) => {
170                write!(f, "Conventions: {}", convention)
171            }
172            TokenConfigurationChangeItem::ConventionsControlGroup(control_group) => {
173                write!(f, "Conventions Control Group: {}", control_group)
174            }
175            TokenConfigurationChangeItem::ConventionsAdminGroup(admin_group) => {
176                write!(f, "Conventions Admin Group: {}", admin_group)
177            }
178            TokenConfigurationChangeItem::MaxSupply(max_supply) => match max_supply {
179                Some(amount) => write!(f, "Max Supply: {}", amount),
180                None => write!(f, "Max Supply: No Limit"),
181            },
182            TokenConfigurationChangeItem::MaxSupplyControlGroup(control_group) => {
183                write!(f, "Max Supply Control Group: {}", control_group)
184            }
185            TokenConfigurationChangeItem::MaxSupplyAdminGroup(admin_group) => {
186                write!(f, "Max Supply Admin Group: {}", admin_group)
187            }
188            TokenConfigurationChangeItem::PerpetualDistribution(distribution) => match distribution
189            {
190                Some(dist) => write!(f, "Perpetual Distribution: {}", dist),
191                None => write!(f, "Perpetual Distribution: None"),
192            },
193            TokenConfigurationChangeItem::PerpetualDistributionControlGroup(control_group) => {
194                write!(f, "Perpetual Distribution Control Group: {}", control_group)
195            }
196            TokenConfigurationChangeItem::PerpetualDistributionAdminGroup(admin_group) => {
197                write!(f, "Perpetual Distribution Admin Group: {}", admin_group)
198            }
199            TokenConfigurationChangeItem::NewTokensDestinationIdentity(identity) => {
200                match identity {
201                    Some(id) => write!(f, "New Tokens Destination Identity: {}", id),
202                    None => write!(f, "New Tokens Destination Identity: None"),
203                }
204            }
205            TokenConfigurationChangeItem::NewTokensDestinationIdentityControlGroup(
206                control_group,
207            ) => {
208                write!(
209                    f,
210                    "New Tokens Destination Identity Control Group: {}",
211                    control_group
212                )
213            }
214            TokenConfigurationChangeItem::NewTokensDestinationIdentityAdminGroup(admin_group) => {
215                write!(
216                    f,
217                    "New Tokens Destination Identity Admin Group: {}",
218                    admin_group
219                )
220            }
221            TokenConfigurationChangeItem::MintingAllowChoosingDestination(allow) => {
222                write!(f, "Minting Allow Choosing Destination: {}", allow)
223            }
224            TokenConfigurationChangeItem::MintingAllowChoosingDestinationControlGroup(
225                control_group,
226            ) => {
227                write!(
228                    f,
229                    "Minting Allow Choosing Destination Control Group: {}",
230                    control_group
231                )
232            }
233            TokenConfigurationChangeItem::MintingAllowChoosingDestinationAdminGroup(
234                admin_group,
235            ) => {
236                write!(
237                    f,
238                    "Minting Allow Choosing Destination Admin Group: {}",
239                    admin_group
240                )
241            }
242            TokenConfigurationChangeItem::ManualMinting(control_group) => {
243                write!(f, "Manual Minting: {}", control_group)
244            }
245            TokenConfigurationChangeItem::ManualMintingAdminGroup(admin_group) => {
246                write!(f, "Manual Minting Admin Group: {}", admin_group)
247            }
248            TokenConfigurationChangeItem::ManualBurning(control_group) => {
249                write!(f, "Manual Burning: {}", control_group)
250            }
251            TokenConfigurationChangeItem::ManualBurningAdminGroup(admin_group) => {
252                write!(f, "Manual Burning Admin Group: {}", admin_group)
253            }
254            TokenConfigurationChangeItem::Freeze(control_group) => {
255                write!(f, "Freeze: {}", control_group)
256            }
257            TokenConfigurationChangeItem::FreezeAdminGroup(admin_group) => {
258                write!(f, "Freeze Admin Group: {}", admin_group)
259            }
260            TokenConfigurationChangeItem::Unfreeze(control_group) => {
261                write!(f, "Unfreeze: {}", control_group)
262            }
263            TokenConfigurationChangeItem::UnfreezeAdminGroup(admin_group) => {
264                write!(f, "Unfreeze Admin Group: {}", admin_group)
265            }
266            TokenConfigurationChangeItem::DestroyFrozenFunds(control_group) => {
267                write!(f, "Destroy Frozen Funds: {}", control_group)
268            }
269            TokenConfigurationChangeItem::DestroyFrozenFundsAdminGroup(admin_group) => {
270                write!(f, "Destroy Frozen Funds Admin Group: {}", admin_group)
271            }
272            TokenConfigurationChangeItem::EmergencyAction(control_group) => {
273                write!(f, "Emergency Action: {}", control_group)
274            }
275            TokenConfigurationChangeItem::EmergencyActionAdminGroup(admin_group) => {
276                write!(f, "Emergency Action Admin Group: {}", admin_group)
277            }
278            TokenConfigurationChangeItem::MainControlGroup(position) => match position {
279                Some(pos) => write!(f, "Main Control Group: {}", pos),
280                None => write!(f, "Main Control Group: None"),
281            },
282            TokenConfigurationChangeItem::MarketplaceTradeMode(mode) => {
283                write!(f, "Marketplace Trade Mode: {:?}", mode)
284            }
285            TokenConfigurationChangeItem::MarketplaceTradeModeControlGroup(control_group) => {
286                write!(f, "Marketplace Trade Mode Control Group: {}", control_group)
287            }
288            TokenConfigurationChangeItem::MarketplaceTradeModeAdminGroup(admin_group) => {
289                write!(f, "Marketplace Trade Mode Admin Group: {}", admin_group)
290            }
291        }
292    }
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298    use std::collections::BTreeSet;
299
300    /// Helper: build one instance of every variant using default inner values.
301    fn all_variants() -> Vec<TokenConfigurationChangeItem> {
302        let aat = AuthorizedActionTakers::NoOne;
303        vec![
304            TokenConfigurationChangeItem::TokenConfigurationNoChange,
305            TokenConfigurationChangeItem::Conventions(
306                TokenConfigurationConvention::V0(
307                    crate::data_contract::associated_token::token_configuration_convention::v0::TokenConfigurationConventionV0::default(),
308                ),
309            ),
310            TokenConfigurationChangeItem::ConventionsControlGroup(aat),
311            TokenConfigurationChangeItem::ConventionsAdminGroup(aat),
312            TokenConfigurationChangeItem::MaxSupply(None),
313            TokenConfigurationChangeItem::MaxSupplyControlGroup(aat),
314            TokenConfigurationChangeItem::MaxSupplyAdminGroup(aat),
315            TokenConfigurationChangeItem::PerpetualDistribution(None),
316            TokenConfigurationChangeItem::PerpetualDistributionControlGroup(aat),
317            TokenConfigurationChangeItem::PerpetualDistributionAdminGroup(aat),
318            TokenConfigurationChangeItem::NewTokensDestinationIdentity(None),
319            TokenConfigurationChangeItem::NewTokensDestinationIdentityControlGroup(aat),
320            TokenConfigurationChangeItem::NewTokensDestinationIdentityAdminGroup(aat),
321            TokenConfigurationChangeItem::MintingAllowChoosingDestination(false),
322            TokenConfigurationChangeItem::MintingAllowChoosingDestinationControlGroup(aat),
323            TokenConfigurationChangeItem::MintingAllowChoosingDestinationAdminGroup(aat),
324            TokenConfigurationChangeItem::ManualMinting(aat),
325            TokenConfigurationChangeItem::ManualMintingAdminGroup(aat),
326            TokenConfigurationChangeItem::ManualBurning(aat),
327            TokenConfigurationChangeItem::ManualBurningAdminGroup(aat),
328            TokenConfigurationChangeItem::Freeze(aat),
329            TokenConfigurationChangeItem::FreezeAdminGroup(aat),
330            TokenConfigurationChangeItem::Unfreeze(aat),
331            TokenConfigurationChangeItem::UnfreezeAdminGroup(aat),
332            TokenConfigurationChangeItem::DestroyFrozenFunds(aat),
333            TokenConfigurationChangeItem::DestroyFrozenFundsAdminGroup(aat),
334            TokenConfigurationChangeItem::EmergencyAction(aat),
335            TokenConfigurationChangeItem::EmergencyActionAdminGroup(aat),
336            TokenConfigurationChangeItem::MarketplaceTradeMode(TokenTradeMode::default()),
337            TokenConfigurationChangeItem::MarketplaceTradeModeControlGroup(aat),
338            TokenConfigurationChangeItem::MarketplaceTradeModeAdminGroup(aat),
339            TokenConfigurationChangeItem::MainControlGroup(None),
340        ]
341    }
342
343    // ---- u8_item_index returns unique values 0..=31 ----
344
345    #[test]
346    fn u8_item_index_values_are_unique() {
347        let variants = all_variants();
348        let indices: Vec<u8> = variants.iter().map(|v| v.u8_item_index()).collect();
349        let unique: BTreeSet<u8> = indices.iter().cloned().collect();
350        assert_eq!(
351            indices.len(),
352            unique.len(),
353            "Duplicate u8_item_index values found: {:?}",
354            indices
355        );
356    }
357
358    #[test]
359    fn u8_item_index_covers_0_through_31() {
360        let variants = all_variants();
361        let indices: BTreeSet<u8> = variants.iter().map(|v| v.u8_item_index()).collect();
362        for i in 0u8..=31 {
363            assert!(indices.contains(&i), "Missing u8_item_index value: {}", i);
364        }
365    }
366
367    #[test]
368    fn u8_item_index_all_within_range() {
369        let variants = all_variants();
370        for v in &variants {
371            let idx = v.u8_item_index();
372            assert!(idx <= 31, "Index {} exceeds expected max of 31", idx);
373        }
374    }
375
376    #[test]
377    fn u8_item_index_specific_known_values() {
378        assert_eq!(
379            TokenConfigurationChangeItem::TokenConfigurationNoChange.u8_item_index(),
380            0
381        );
382        assert_eq!(
383            TokenConfigurationChangeItem::MaxSupply(Some(100)).u8_item_index(),
384            4
385        );
386        assert_eq!(
387            TokenConfigurationChangeItem::ManualMinting(AuthorizedActionTakers::NoOne)
388                .u8_item_index(),
389            16
390        );
391        assert_eq!(
392            TokenConfigurationChangeItem::MainControlGroup(Some(5)).u8_item_index(),
393            31
394        );
395    }
396
397    #[test]
398    fn u8_item_index_variant_count() {
399        // We expect exactly 32 variants (indices 0..=31)
400        let variants = all_variants();
401        assert_eq!(variants.len(), 32);
402    }
403
404    // --- payload_serialization ---
405
406    #[test]
407    fn payload_serialization_no_change_is_none() {
408        let item = TokenConfigurationChangeItem::TokenConfigurationNoChange;
409        assert!(item.payload_serialization().unwrap().is_none());
410    }
411
412    #[test]
413    fn payload_serialization_max_supply_none_is_none() {
414        let item = TokenConfigurationChangeItem::MaxSupply(None);
415        assert!(item.payload_serialization().unwrap().is_none());
416    }
417
418    #[test]
419    fn payload_serialization_max_supply_some_encodes_be_bytes() {
420        let amount: u64 = 0x0102_0304_0506_0708;
421        let item = TokenConfigurationChangeItem::MaxSupply(Some(amount));
422        let bytes = item.payload_serialization().unwrap().unwrap();
423        assert_eq!(bytes, amount.to_be_bytes().to_vec());
424        assert_eq!(bytes.len(), 8);
425    }
426
427    #[test]
428    fn payload_serialization_new_tokens_destination_identity_none_is_none() {
429        let item = TokenConfigurationChangeItem::NewTokensDestinationIdentity(None);
430        assert!(item.payload_serialization().unwrap().is_none());
431    }
432
433    #[test]
434    fn payload_serialization_new_tokens_destination_identity_some_is_32_bytes() {
435        let id = Identifier::from([0x77u8; 32]);
436        let item = TokenConfigurationChangeItem::NewTokensDestinationIdentity(Some(id));
437        let bytes = item.payload_serialization().unwrap().unwrap();
438        assert_eq!(bytes.len(), 32);
439        assert_eq!(bytes, vec![0x77u8; 32]);
440    }
441
442    #[test]
443    fn payload_serialization_perpetual_distribution_none_is_none() {
444        let item = TokenConfigurationChangeItem::PerpetualDistribution(None);
445        assert!(item.payload_serialization().unwrap().is_none());
446    }
447
448    #[test]
449    fn payload_serialization_main_control_group_none_is_none() {
450        let item = TokenConfigurationChangeItem::MainControlGroup(None);
451        assert!(item.payload_serialization().unwrap().is_none());
452    }
453
454    #[test]
455    fn payload_serialization_main_control_group_some_is_two_be_bytes() {
456        let pos: u16 = 0xABCD;
457        let item = TokenConfigurationChangeItem::MainControlGroup(Some(pos));
458        let bytes = item.payload_serialization().unwrap().unwrap();
459        assert_eq!(bytes, pos.to_be_bytes().to_vec());
460        assert_eq!(bytes.len(), 2);
461    }
462
463    #[test]
464    fn payload_serialization_minting_allow_choosing_destination_true() {
465        let item = TokenConfigurationChangeItem::MintingAllowChoosingDestination(true);
466        let bytes = item.payload_serialization().unwrap().unwrap();
467        assert_eq!(bytes, vec![1]);
468    }
469
470    #[test]
471    fn payload_serialization_minting_allow_choosing_destination_false() {
472        let item = TokenConfigurationChangeItem::MintingAllowChoosingDestination(false);
473        let bytes = item.payload_serialization().unwrap().unwrap();
474        assert_eq!(bytes, vec![0]);
475    }
476
477    #[test]
478    fn payload_serialization_authorized_action_takers_variants_use_to_bytes() {
479        // The big match arm has 23 variants that all serialize via AuthorizedActionTakers::to_bytes
480        // Sanity-check a few representative variants produce the expected tag bytes.
481        let aat = AuthorizedActionTakers::ContractOwner;
482        let expected = aat.to_bytes();
483
484        let variants = [
485            TokenConfigurationChangeItem::ConventionsControlGroup(aat),
486            TokenConfigurationChangeItem::ConventionsAdminGroup(aat),
487            TokenConfigurationChangeItem::MaxSupplyControlGroup(aat),
488            TokenConfigurationChangeItem::MaxSupplyAdminGroup(aat),
489            TokenConfigurationChangeItem::PerpetualDistributionControlGroup(aat),
490            TokenConfigurationChangeItem::PerpetualDistributionAdminGroup(aat),
491            TokenConfigurationChangeItem::NewTokensDestinationIdentityControlGroup(aat),
492            TokenConfigurationChangeItem::NewTokensDestinationIdentityAdminGroup(aat),
493            TokenConfigurationChangeItem::MintingAllowChoosingDestinationControlGroup(aat),
494            TokenConfigurationChangeItem::MintingAllowChoosingDestinationAdminGroup(aat),
495            TokenConfigurationChangeItem::ManualMinting(aat),
496            TokenConfigurationChangeItem::ManualMintingAdminGroup(aat),
497            TokenConfigurationChangeItem::ManualBurning(aat),
498            TokenConfigurationChangeItem::ManualBurningAdminGroup(aat),
499            TokenConfigurationChangeItem::Freeze(aat),
500            TokenConfigurationChangeItem::FreezeAdminGroup(aat),
501            TokenConfigurationChangeItem::Unfreeze(aat),
502            TokenConfigurationChangeItem::UnfreezeAdminGroup(aat),
503            TokenConfigurationChangeItem::DestroyFrozenFunds(aat),
504            TokenConfigurationChangeItem::DestroyFrozenFundsAdminGroup(aat),
505            TokenConfigurationChangeItem::EmergencyAction(aat),
506            TokenConfigurationChangeItem::EmergencyActionAdminGroup(aat),
507            TokenConfigurationChangeItem::MarketplaceTradeModeControlGroup(aat),
508            TokenConfigurationChangeItem::MarketplaceTradeModeAdminGroup(aat),
509        ];
510        for v in &variants {
511            let bytes = v.payload_serialization().unwrap().unwrap();
512            assert_eq!(bytes, expected, "mismatch for variant {:?}", v);
513        }
514    }
515
516    #[test]
517    fn payload_serialization_conventions_roundtrips_via_bincode() {
518        // We don't assert the exact bytes (bincode-dependent) but we assert
519        // (1) Some(..) is returned and (2) it decodes back to the same convention.
520        use crate::data_contract::associated_token::token_configuration_convention::v0::TokenConfigurationConventionV0;
521        let convention = TokenConfigurationConvention::V0(TokenConfigurationConventionV0 {
522            localizations: Default::default(),
523            decimals: 5,
524        });
525        let item = TokenConfigurationChangeItem::Conventions(convention.clone());
526        let bytes = item.payload_serialization().unwrap().unwrap();
527        assert!(!bytes.is_empty());
528        let (decoded, _): (TokenConfigurationConvention, _) =
529            bincode::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
530        assert_eq!(decoded, convention);
531    }
532
533    #[test]
534    fn payload_serialization_marketplace_trade_mode_produces_bytes() {
535        let item = TokenConfigurationChangeItem::MarketplaceTradeMode(TokenTradeMode::NotTradeable);
536        let bytes = item.payload_serialization().unwrap().unwrap();
537        assert!(!bytes.is_empty());
538        // Roundtrip decode
539        let (decoded, _): (TokenTradeMode, _) =
540            bincode::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
541        assert_eq!(decoded, TokenTradeMode::NotTradeable);
542    }
543
544    // --- Display ---
545
546    #[test]
547    fn display_no_change() {
548        let s = format!(
549            "{}",
550            TokenConfigurationChangeItem::TokenConfigurationNoChange
551        );
552        assert_eq!(s, "No Change in Token Configuration");
553    }
554
555    #[test]
556    fn display_max_supply_some_vs_none() {
557        let some = format!("{}", TokenConfigurationChangeItem::MaxSupply(Some(500)));
558        let none = format!("{}", TokenConfigurationChangeItem::MaxSupply(None));
559        assert!(some.contains("500"));
560        assert!(none.contains("No Limit"));
561    }
562
563    #[test]
564    fn display_perpetual_distribution_none_uses_none_marker() {
565        let s = format!(
566            "{}",
567            TokenConfigurationChangeItem::PerpetualDistribution(None)
568        );
569        assert!(s.contains("None"));
570    }
571
572    #[test]
573    fn display_new_tokens_destination_identity_none_uses_none_marker() {
574        let s = format!(
575            "{}",
576            TokenConfigurationChangeItem::NewTokensDestinationIdentity(None)
577        );
578        assert!(s.contains("None"));
579    }
580
581    #[test]
582    fn display_new_tokens_destination_identity_some_includes_id() {
583        let id = Identifier::from([3u8; 32]);
584        let s = format!(
585            "{}",
586            TokenConfigurationChangeItem::NewTokensDestinationIdentity(Some(id))
587        );
588        assert!(s.contains("New Tokens Destination Identity"));
589    }
590
591    #[test]
592    fn display_main_control_group_none_uses_none_marker() {
593        let s = format!("{}", TokenConfigurationChangeItem::MainControlGroup(None));
594        assert!(s.contains("None"));
595    }
596
597    #[test]
598    fn display_main_control_group_some_contains_position() {
599        let s = format!(
600            "{}",
601            TokenConfigurationChangeItem::MainControlGroup(Some(7))
602        );
603        assert!(s.contains("7"));
604    }
605
606    #[test]
607    fn display_minting_allow_choosing_destination_contains_value() {
608        let s_true = format!(
609            "{}",
610            TokenConfigurationChangeItem::MintingAllowChoosingDestination(true)
611        );
612        let s_false = format!(
613            "{}",
614            TokenConfigurationChangeItem::MintingAllowChoosingDestination(false)
615        );
616        assert!(s_true.contains("true"));
617        assert!(s_false.contains("false"));
618    }
619
620    #[test]
621    fn display_marketplace_trade_mode_uses_debug() {
622        let s = format!(
623            "{}",
624            TokenConfigurationChangeItem::MarketplaceTradeMode(TokenTradeMode::NotTradeable)
625        );
626        assert!(s.contains("NotTradeable"));
627    }
628
629    #[test]
630    fn display_action_takers_group_variants() {
631        // Exercise a handful of action-taker-carrying variants to confirm each
632        // writes its label.
633        let aat = AuthorizedActionTakers::ContractOwner;
634        let cases = vec![
635            (
636                format!("{}", TokenConfigurationChangeItem::ManualMinting(aat)),
637                "Manual Minting",
638            ),
639            (
640                format!(
641                    "{}",
642                    TokenConfigurationChangeItem::ManualMintingAdminGroup(aat)
643                ),
644                "Manual Minting Admin Group",
645            ),
646            (
647                format!("{}", TokenConfigurationChangeItem::ManualBurning(aat)),
648                "Manual Burning",
649            ),
650            (
651                format!("{}", TokenConfigurationChangeItem::Freeze(aat)),
652                "Freeze",
653            ),
654            (
655                format!("{}", TokenConfigurationChangeItem::Unfreeze(aat)),
656                "Unfreeze",
657            ),
658            (
659                format!("{}", TokenConfigurationChangeItem::DestroyFrozenFunds(aat)),
660                "Destroy Frozen Funds",
661            ),
662            (
663                format!("{}", TokenConfigurationChangeItem::EmergencyAction(aat)),
664                "Emergency Action",
665            ),
666            (
667                format!(
668                    "{}",
669                    TokenConfigurationChangeItem::MarketplaceTradeModeControlGroup(aat)
670                ),
671                "Marketplace Trade Mode Control Group",
672            ),
673            (
674                format!(
675                    "{}",
676                    TokenConfigurationChangeItem::ConventionsControlGroup(aat)
677                ),
678                "Conventions Control Group",
679            ),
680        ];
681        for (output, expected_prefix) in cases {
682            assert!(
683                output.contains(expected_prefix),
684                "expected {:?} in {:?}",
685                expected_prefix,
686                output
687            );
688        }
689    }
690
691    // --- Equality ---
692
693    #[test]
694    fn equality_same_variant_same_data() {
695        let a = TokenConfigurationChangeItem::MaxSupply(Some(42));
696        let b = TokenConfigurationChangeItem::MaxSupply(Some(42));
697        assert_eq!(a, b);
698    }
699
700    #[test]
701    fn equality_same_variant_different_data_unequal() {
702        let a = TokenConfigurationChangeItem::MaxSupply(Some(42));
703        let b = TokenConfigurationChangeItem::MaxSupply(Some(43));
704        assert_ne!(a, b);
705    }
706
707    #[test]
708    fn equality_different_variants_unequal() {
709        let a = TokenConfigurationChangeItem::MaxSupply(None);
710        let b = TokenConfigurationChangeItem::MainControlGroup(None);
711        assert_ne!(a, b);
712    }
713
714    #[test]
715    fn equality_authorized_action_takers_sensitivity() {
716        let a = TokenConfigurationChangeItem::ManualMinting(AuthorizedActionTakers::NoOne);
717        let b = TokenConfigurationChangeItem::ManualMinting(AuthorizedActionTakers::ContractOwner);
718        assert_ne!(a, b);
719    }
720
721    #[test]
722    fn equality_bool_variant_distinguishes_values() {
723        let a = TokenConfigurationChangeItem::MintingAllowChoosingDestination(true);
724        let b = TokenConfigurationChangeItem::MintingAllowChoosingDestination(false);
725        assert_ne!(a, b);
726    }
727
728    // --- Default + Clone ---
729
730    #[test]
731    fn default_is_no_change() {
732        let d: TokenConfigurationChangeItem = Default::default();
733        assert_eq!(d, TokenConfigurationChangeItem::TokenConfigurationNoChange);
734    }
735
736    #[test]
737    fn clone_preserves_variant_and_data() {
738        let aat = AuthorizedActionTakers::Identity(Identifier::from([5u8; 32]));
739        let a = TokenConfigurationChangeItem::Freeze(aat);
740        let b = a.clone();
741        assert_eq!(a, b);
742    }
743
744    // --- Debug ---
745
746    #[test]
747    fn debug_trait_contains_variant_name() {
748        let a = TokenConfigurationChangeItem::ManualMinting(AuthorizedActionTakers::NoOne);
749        let dbg = format!("{:?}", a);
750        assert!(dbg.contains("ManualMinting"));
751    }
752}