Skip to main content

dpp/group/
action_event.rs

1#[cfg(feature = "json-conversion")]
2use crate::serialization::JsonConvertible;
3#[cfg(feature = "value-conversion")]
4use crate::serialization::ValueConvertible;
5use crate::tokens::token_event::TokenEvent;
6use crate::ProtocolError;
7use bincode::{Decode, Encode};
8use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize};
9#[cfg(feature = "serde-conversion")]
10use serde::{Deserialize, Serialize};
11
12#[derive(
13    Debug, PartialEq, PartialOrd, Clone, Eq, Encode, Decode, PlatformDeserialize, PlatformSerialize,
14)]
15#[cfg_attr(
16    feature = "serde-conversion",
17    derive(Serialize, Deserialize),
18    // Internally tagged with `$kind`. The inner `TokenEvent` is itself
19    // internally tagged with `$type`, so its fields flatten alongside `$kind`
20    // into one object. `$kind` keeps the two discriminators distinct. Wire
21    // shape, e.g.:
22    //   {"$kind": "tokenEvent", "$type": "mint", "amount": <n>, "recipient": <id>}
23    serde(tag = "$kind", rename_all = "camelCase")
24)]
25#[cfg_attr(feature = "value-conversion", derive(ValueConvertible))]
26#[platform_serialize(unversioned)] //versioned directly, no need to use platform_version
27pub enum GroupActionEvent {
28    TokenEvent(TokenEvent),
29}
30
31// Manual impl because GroupActionEvent is a flat enum (not versioned V0/V1).
32// Its inner type TokenEvent also has a manual impl — see token_event.rs.
33#[cfg(feature = "json-conversion")]
34impl JsonConvertible for GroupActionEvent {}
35
36#[cfg(all(
37    test,
38    feature = "json-conversion",
39    feature = "value-conversion",
40    feature = "serde-conversion"
41))]
42mod json_convertible_tests {
43    use super::*;
44    use platform_value::platform_value;
45    use serde_json::json;
46
47    // `GroupActionEvent` uses `tag = "$kind"` (internal). Plain `kind`
48    // (no `$` prefix) because the wire level has no other `$`-prefixed
49    // fields. Distinct from the inner `TokenEvent`'s `type` discriminator
50    // — both keys coexist at the flattened top level without collision.
51
52    #[test]
53    fn json_round_trip_token_event_mint() {
54        use crate::serialization::JsonConvertible;
55        let original = GroupActionEvent::TokenEvent(
56            crate::tokens::token_event::json_convertible_tests::mint_fixture(),
57        );
58        let json = original.to_json().expect("to_json");
59        // Outer `kind: "tokenEvent"` from GroupActionEvent. Inner TokenEvent
60        // (custom serde) flattens its named fields at the same level.
61        assert_eq!(
62            json,
63            json!({
64                "$kind": "tokenEvent",
65                "$type": "mint",
66                "amount": 5_000,
67                "recipient": "Bswb3UyeD1pUTaGiE6WvqwFpJZsQSEY1xhJePCDTHdvp",
68                "publicNote": "genesis mint",
69            })
70        );
71        let recovered = GroupActionEvent::from_json(json).expect("from_json");
72        assert_eq!(original, recovered);
73    }
74
75    #[test]
76    fn value_round_trip_token_event_mint() {
77        use crate::serialization::ValueConvertible;
78        let original = GroupActionEvent::TokenEvent(
79            crate::tokens::token_event::json_convertible_tests::mint_fixture(),
80        );
81        let value = original.to_object().expect("to_object");
82        assert_eq!(
83            value,
84            platform_value!({
85                "$kind": "tokenEvent",
86                "$type": "mint",
87                "amount": 5_000u64,
88                "recipient": platform_value::Identifier::new([0xa1; 32]),
89                "publicNote": "genesis mint",
90            })
91        );
92        let recovered = GroupActionEvent::from_object(value).expect("from_object");
93        assert_eq!(original, recovered);
94    }
95
96    // The two tests below pin the deepest custom-serde composition in the
97    // crate: GroupActionEvent (`tag = "$kind"`, derive — buffers inner content
98    // through serde's ContentDeserializer) → TokenEvent (custom impl) →
99    // externally-tagged TokenConfigurationChangeItem /
100    // TokenDistributionTypeWithResolvedRecipient → the custom
101    // internally-tagged AuthorizedActionTakers /
102    // TokenDistributionResolvedRecipient. The Identity-carrying variants
103    // specifically exercise the Identifier dual-shape path (HR base58 string
104    // vs buffered bytes) under the ContentDeserializer HR-quirk. A one-sided
105    // edit to any custom Serialize/Deserialize pair in the chain fails here.
106
107    fn change_item_fixture() -> GroupActionEvent {
108        use crate::data_contract::associated_token::token_configuration_item::TokenConfigurationChangeItem;
109        use crate::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers;
110        GroupActionEvent::TokenEvent(TokenEvent::ConfigUpdate(
111            TokenConfigurationChangeItem::ConventionsControlGroup(
112                AuthorizedActionTakers::Identity(platform_value::Identifier::from([0x42u8; 32])),
113            ),
114            Some("rotate control".to_string()),
115        ))
116    }
117
118    fn claim_fixture() -> GroupActionEvent {
119        use crate::data_contract::associated_token::token_distribution_key::TokenDistributionTypeWithResolvedRecipient;
120        use crate::data_contract::associated_token::token_perpetual_distribution::distribution_recipient::TokenDistributionResolvedRecipient;
121        GroupActionEvent::TokenEvent(TokenEvent::Claim(
122            TokenDistributionTypeWithResolvedRecipient::Perpetual(
123                TokenDistributionResolvedRecipient::Evonode(platform_value::Identifier::from(
124                    [0x42u8; 32],
125                )),
126            ),
127            750,
128            Some("payout".to_string()),
129        ))
130    }
131
132    #[test]
133    fn json_round_trip_config_update_with_identity_action_taker() {
134        use crate::serialization::JsonConvertible;
135        let original = change_item_fixture();
136        let json = original.to_json().expect("to_json");
137        assert_eq!(
138            json,
139            json!({
140                "$kind": "tokenEvent",
141                "$type": "configUpdate",
142                "configurationChange": {
143                    "$type": "conventionsControlGroup",
144                    "value": {
145                        "$type": "identity",
146                        "identity": "5TeWSsjg2gbxCyWVniXeCmwM7UtHTCK7svzJr5xYJzHf",
147                    },
148                },
149                "publicNote": "rotate control",
150            })
151        );
152        let recovered = GroupActionEvent::from_json(json).expect("from_json");
153        assert_eq!(original, recovered);
154    }
155
156    #[test]
157    fn value_round_trip_config_update_with_identity_action_taker() {
158        use crate::serialization::ValueConvertible;
159        let original = change_item_fixture();
160        let value = original.to_object().expect("to_object");
161        assert_eq!(
162            value,
163            platform_value!({
164                "$kind": "tokenEvent",
165                "$type": "configUpdate",
166                "configurationChange": {
167                    "$type": "conventionsControlGroup",
168                    "value": {
169                        "$type": "identity",
170                        "identity": platform_value::Identifier::from([0x42u8; 32]),
171                    },
172                },
173                "publicNote": "rotate control",
174            })
175        );
176        let recovered = GroupActionEvent::from_object(value).expect("from_object");
177        assert_eq!(original, recovered);
178    }
179
180    #[test]
181    fn json_round_trip_claim_with_resolved_recipient() {
182        use crate::serialization::JsonConvertible;
183        let original = claim_fixture();
184        let json = original.to_json().expect("to_json");
185        assert_eq!(
186            json,
187            json!({
188                "$kind": "tokenEvent",
189                "$type": "claim",
190                "distributionType": {
191                    "$type": "perpetual",
192                    "value": {
193                        "$type": "evonode",
194                        "identity": "5TeWSsjg2gbxCyWVniXeCmwM7UtHTCK7svzJr5xYJzHf",
195                    },
196                },
197                "amount": 750,
198                "publicNote": "payout",
199            })
200        );
201        let recovered = GroupActionEvent::from_json(json).expect("from_json");
202        assert_eq!(original, recovered);
203    }
204
205    #[test]
206    fn value_round_trip_claim_with_resolved_recipient() {
207        use crate::serialization::ValueConvertible;
208        let original = claim_fixture();
209        let value = original.to_object().expect("to_object");
210        assert_eq!(
211            value,
212            platform_value!({
213                "$kind": "tokenEvent",
214                "$type": "claim",
215                "distributionType": {
216                    "$type": "perpetual",
217                    "value": {
218                        "$type": "evonode",
219                        "identity": platform_value::Identifier::from([0x42u8; 32]),
220                    },
221                },
222                "amount": 750u64,
223                "publicNote": "payout",
224            })
225        );
226        let recovered = GroupActionEvent::from_object(value).expect("from_object");
227        assert_eq!(original, recovered);
228    }
229}
230
231use std::fmt;
232
233impl fmt::Display for GroupActionEvent {
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        match self {
236            GroupActionEvent::TokenEvent(event) => write!(f, "Token event: {}", event),
237        }
238    }
239}
240
241impl GroupActionEvent {
242    /// Returns a reference to the public note if the variant includes one.
243    pub fn public_note(&self) -> Option<&str> {
244        match self {
245            GroupActionEvent::TokenEvent(token_event) => token_event.public_note(),
246        }
247    }
248
249    /// Returns a name of the event
250    pub fn event_name(&self) -> String {
251        match self {
252            GroupActionEvent::TokenEvent(token_event) => {
253                format!("Token: {}", token_event.associated_document_type_name())
254            }
255        }
256    }
257}