1use crate::data_contract::associated_token::token_perpetual_distribution::distribution_recipient::{TokenDistributionRecipient, TokenDistributionResolvedRecipient};
2use crate::errors::ProtocolError;
3use bincode::{Decode, Encode, DecodeUntrusted};
4use platform_serialization_derive::{PlatformDeserializeTrusted, PlatformDeserializeUntrusted, PlatformSerialize};
5use platform_value::Identifier;
6use serde::{Deserialize, Serialize};
7use std::fmt;
8use crate::data_contract::associated_token::token_perpetual_distribution::reward_distribution_moment::RewardDistributionMoment;
9use crate::prelude::TimestampMillis;
10#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
11use crate::serialization::JsonConvertible;
12#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
13use crate::serialization::ValueConvertible;
14
15#[derive(
20 Serialize,
21 Deserialize,
22 Decode,
23 Encode,
24 Debug,
25 Clone,
26 Copy,
27 PartialEq,
28 Eq,
29 PartialOrd,
30 Default,
31 DecodeUntrusted,
32)]
33pub enum TokenDistributionType {
34 #[default]
36 PreProgrammed = 0,
37
38 Perpetual = 1,
40
41 OncePerIdentity = 2,
43}
44
45#[derive(
50 Serialize, Deserialize, Decode, Encode, Debug, Clone, PartialEq, Eq, PartialOrd, DecodeUntrusted,
51)]
52#[serde(
53 into = "TokenDistributionTypeWithResolvedRecipientRepr",
54 from = "TokenDistributionTypeWithResolvedRecipientRepr"
55)]
56pub enum TokenDistributionTypeWithResolvedRecipient {
57 PreProgrammed(Identifier),
59
60 Perpetual(TokenDistributionResolvedRecipient),
62
63 OncePerIdentity(Identifier),
65}
66
67#[derive(Serialize, Deserialize)]
70#[serde(tag = "$type", rename_all = "camelCase")]
71enum TokenDistributionTypeWithResolvedRecipientRepr {
72 PreProgrammed {
73 value: Identifier,
74 },
75 Perpetual {
76 value: TokenDistributionResolvedRecipient,
77 },
78 OncePerIdentity {
79 value: Identifier,
80 },
81}
82
83impl From<TokenDistributionTypeWithResolvedRecipient>
84 for TokenDistributionTypeWithResolvedRecipientRepr
85{
86 fn from(m: TokenDistributionTypeWithResolvedRecipient) -> Self {
87 match m {
88 TokenDistributionTypeWithResolvedRecipient::PreProgrammed(value) => {
89 Self::PreProgrammed { value }
90 }
91 TokenDistributionTypeWithResolvedRecipient::Perpetual(value) => {
92 Self::Perpetual { value }
93 }
94 TokenDistributionTypeWithResolvedRecipient::OncePerIdentity(value) => {
95 Self::OncePerIdentity { value }
96 }
97 }
98 }
99}
100
101impl From<TokenDistributionTypeWithResolvedRecipientRepr>
102 for TokenDistributionTypeWithResolvedRecipient
103{
104 fn from(r: TokenDistributionTypeWithResolvedRecipientRepr) -> Self {
105 match r {
106 TokenDistributionTypeWithResolvedRecipientRepr::PreProgrammed { value } => {
107 Self::PreProgrammed(value)
108 }
109 TokenDistributionTypeWithResolvedRecipientRepr::Perpetual { value } => {
110 Self::Perpetual(value)
111 }
112 TokenDistributionTypeWithResolvedRecipientRepr::OncePerIdentity { value } => {
113 Self::OncePerIdentity(value)
114 }
115 }
116 }
117}
118
119#[derive(
125 Serialize, Deserialize, Decode, Encode, Debug, Clone, PartialEq, Eq, PartialOrd, DecodeUntrusted,
126)]
127#[serde(into = "TokenDistributionInfoRepr", from = "TokenDistributionInfoRepr")]
128pub enum TokenDistributionInfo {
129 PreProgrammed(TimestampMillis, Identifier),
132
133 Perpetual(RewardDistributionMoment, TokenDistributionResolvedRecipient),
137
138 OncePerIdentity(TimestampMillis, Identifier),
140}
141
142#[derive(Serialize, Deserialize)]
147#[serde(tag = "$type", rename_all = "camelCase")]
148enum TokenDistributionInfoRepr {
149 PreProgrammed {
150 #[cfg_attr(
151 feature = "json-conversion",
152 serde(with = "crate::serialization::json_safe_u64")
153 )]
154 timestamp: TimestampMillis,
155 identity: Identifier,
156 },
157 Perpetual {
158 moment: RewardDistributionMoment,
159 recipient: TokenDistributionResolvedRecipient,
160 },
161 OncePerIdentity {
162 #[cfg_attr(
163 feature = "json-conversion",
164 serde(with = "crate::serialization::json_safe_u64")
165 )]
166 timestamp: TimestampMillis,
167 identity: Identifier,
168 },
169}
170
171impl From<TokenDistributionInfo> for TokenDistributionInfoRepr {
172 fn from(m: TokenDistributionInfo) -> Self {
173 match m {
174 TokenDistributionInfo::PreProgrammed(timestamp, identity) => Self::PreProgrammed {
175 timestamp,
176 identity,
177 },
178 TokenDistributionInfo::Perpetual(moment, recipient) => {
179 Self::Perpetual { moment, recipient }
180 }
181 TokenDistributionInfo::OncePerIdentity(timestamp, identity) => Self::OncePerIdentity {
182 timestamp,
183 identity,
184 },
185 }
186 }
187}
188
189impl From<TokenDistributionInfoRepr> for TokenDistributionInfo {
190 fn from(r: TokenDistributionInfoRepr) -> Self {
191 match r {
192 TokenDistributionInfoRepr::PreProgrammed {
193 timestamp,
194 identity,
195 } => Self::PreProgrammed(timestamp, identity),
196 TokenDistributionInfoRepr::Perpetual { moment, recipient } => {
197 Self::Perpetual(moment, recipient)
198 }
199 TokenDistributionInfoRepr::OncePerIdentity {
200 timestamp,
201 identity,
202 } => Self::OncePerIdentity(timestamp, identity),
203 }
204 }
205}
206
207impl From<TokenDistributionInfo> for TokenDistributionTypeWithResolvedRecipient {
208 fn from(info: TokenDistributionInfo) -> Self {
209 match info {
210 TokenDistributionInfo::PreProgrammed(_, recipient) => {
211 TokenDistributionTypeWithResolvedRecipient::PreProgrammed(recipient)
212 }
213 TokenDistributionInfo::Perpetual(_, recipient) => {
214 TokenDistributionTypeWithResolvedRecipient::Perpetual(recipient)
215 }
216 TokenDistributionInfo::OncePerIdentity(_, recipient) => {
217 TokenDistributionTypeWithResolvedRecipient::OncePerIdentity(recipient)
218 }
219 }
220 }
221}
222
223impl From<&TokenDistributionInfo> for TokenDistributionTypeWithResolvedRecipient {
224 fn from(info: &TokenDistributionInfo) -> Self {
225 match info {
226 TokenDistributionInfo::PreProgrammed(_, recipient) => {
227 TokenDistributionTypeWithResolvedRecipient::PreProgrammed(*recipient)
228 }
229 TokenDistributionInfo::Perpetual(_, recipient) => {
230 TokenDistributionTypeWithResolvedRecipient::Perpetual(recipient.clone())
231 }
232 TokenDistributionInfo::OncePerIdentity(_, recipient) => {
233 TokenDistributionTypeWithResolvedRecipient::OncePerIdentity(*recipient)
234 }
235 }
236 }
237}
238
239impl fmt::Display for TokenDistributionType {
240 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
241 match self {
242 TokenDistributionType::PreProgrammed => write!(f, "PreProgrammed"),
243 TokenDistributionType::Perpetual => write!(f, "Perpetual"),
244 TokenDistributionType::OncePerIdentity => write!(f, "OncePerIdentity"),
245 }
246 }
247}
248
249#[derive(
250 Serialize,
251 Deserialize,
252 Decode,
253 Encode,
254 PlatformSerialize,
255 PlatformDeserializeTrusted,
256 PlatformDeserializeUntrusted,
257 Debug,
258 Clone,
259 PartialEq,
260 Eq,
261 DecodeUntrusted,
262)]
263#[platform_serialize(unversioned)]
264pub struct TokenDistributionKey {
265 pub token_id: Identifier,
266 pub recipient: TokenDistributionRecipient,
267 pub distribution_type: TokenDistributionType,
268}
269
270#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
272impl JsonConvertible for TokenDistributionTypeWithResolvedRecipient {}
273
274#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
275impl ValueConvertible for TokenDistributionTypeWithResolvedRecipient {}
276
277#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
278impl JsonConvertible for TokenDistributionInfo {}
279
280#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
281impl ValueConvertible for TokenDistributionInfo {}
282
283#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
284impl JsonConvertible for TokenDistributionType {}
285
286#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
287impl ValueConvertible for TokenDistributionType {}
288
289#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
290impl JsonConvertible for TokenDistributionKey {}
291
292#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
293impl ValueConvertible for TokenDistributionKey {}
294
295#[cfg(all(
296 test,
297 feature = "json-conversion",
298 feature = "value-conversion",
299 feature = "serde-conversion"
300))]
301mod json_convertible_tests_token_distribution_type_and_key {
302 use super::*;
303 use crate::serialization::{JsonConvertible, ValueConvertible};
304 use platform_value::{platform_value, Value};
305 use serde_json::json;
306
307 #[test]
308 fn token_distribution_type_round_trips_all_variants() {
309 let cases = [
312 (TokenDistributionType::PreProgrammed, "PreProgrammed"),
313 (TokenDistributionType::Perpetual, "Perpetual"),
314 (TokenDistributionType::OncePerIdentity, "OncePerIdentity"),
315 ];
316 for (original, expected) in cases {
317 let json_v = original.to_json().expect("to_json");
318 assert_eq!(json_v, json!(expected));
319 assert_eq!(
320 TokenDistributionType::from_json(json_v).expect("from_json"),
321 original
322 );
323 let value = original.to_object().expect("to_object");
324 assert_eq!(value, platform_value!(expected));
325 assert_eq!(
326 TokenDistributionType::from_object(value).expect("from_object"),
327 original
328 );
329 }
330 }
331
332 fn key_fixture() -> TokenDistributionKey {
333 TokenDistributionKey {
334 token_id: Identifier::new([0x42; 32]),
335 recipient: TokenDistributionRecipient::EvonodesByParticipation,
336 distribution_type: TokenDistributionType::Perpetual,
337 }
338 }
339
340 #[test]
341 fn token_distribution_key_json_round_trip_with_full_wire_shape() {
342 let original = key_fixture();
343 let json = original.to_json().expect("to_json");
344 assert_eq!(
348 json,
349 json!({
350 "token_id": "5TeWSsjg2gbxCyWVniXeCmwM7UtHTCK7svzJr5xYJzHf",
351 "recipient": {"$type": "evonodesByParticipation"},
352 "distribution_type": "Perpetual",
353 })
354 );
355 let recovered = TokenDistributionKey::from_json(json).expect("from_json");
356 assert_eq!(original, recovered);
357 }
358
359 #[test]
360 fn token_distribution_key_value_round_trip_with_full_wire_shape() {
361 let original = key_fixture();
362 let value = original.to_object().expect("to_object");
363 let expected = Value::Map(vec![
364 (
365 Value::Text("token_id".to_string()),
366 Value::Identifier([0x42; 32]),
367 ),
368 (
369 Value::Text("recipient".to_string()),
370 Value::Map(vec![(
371 Value::Text("$type".to_string()),
372 Value::Text("evonodesByParticipation".to_string()),
373 )]),
374 ),
375 (
376 Value::Text("distribution_type".to_string()),
377 Value::Text("Perpetual".to_string()),
378 ),
379 ]);
380 assert_eq!(value, expected);
381 let recovered = TokenDistributionKey::from_object(value).expect("from_object");
382 assert_eq!(original, recovered);
383 }
384}
385
386#[cfg(all(
387 test,
388 feature = "json-conversion",
389 feature = "value-conversion",
390 feature = "serde-conversion"
391))]
392mod json_convertible_tests_token_distribution_info {
393 use super::*;
394 use platform_value::{Identifier, Value};
395 use serde_json::json;
396
397 fn fixture() -> TokenDistributionInfo {
401 TokenDistributionInfo::PreProgrammed(1_700_000_000_000, Identifier::new([0x42; 32]))
402 }
403
404 #[test]
405 fn json_round_trip_with_full_wire_shape() {
406 use crate::serialization::JsonConvertible;
407 let original = fixture();
408 let json = original.to_json().expect("to_json");
409 assert_eq!(
415 json,
416 json!({
417 "$type": "preProgrammed",
418 "timestamp": 1_700_000_000_000u64,
419 "identity": "5TeWSsjg2gbxCyWVniXeCmwM7UtHTCK7svzJr5xYJzHf",
420 })
421 );
422 let recovered = TokenDistributionInfo::from_json(json).expect("from_json");
423 assert_eq!(original, recovered);
424 }
425
426 #[test]
427 fn value_round_trip_with_full_wire_shape() {
428 use crate::serialization::ValueConvertible;
429 let original = fixture();
430 let value = original.to_object().expect("to_object");
431 let expected = Value::Map(vec![
436 (
437 Value::Text("$type".to_string()),
438 Value::Text("preProgrammed".to_string()),
439 ),
440 (
441 Value::Text("timestamp".to_string()),
442 Value::U64(1_700_000_000_000),
443 ),
444 (
445 Value::Text("identity".to_string()),
446 Value::Identifier([0x42; 32]),
447 ),
448 ]);
449 assert_eq!(value, expected);
450 let recovered = TokenDistributionInfo::from_object(value).expect("from_object");
451 assert_eq!(original, recovered);
452 }
453
454 #[test]
455 fn json_round_trip_perpetual_variant() {
456 use crate::data_contract::associated_token::token_perpetual_distribution::distribution_recipient::TokenDistributionResolvedRecipient;
457 use crate::data_contract::associated_token::token_perpetual_distribution::reward_distribution_moment::RewardDistributionMoment;
458 use crate::serialization::JsonConvertible;
459 let original = TokenDistributionInfo::Perpetual(
463 RewardDistributionMoment::BlockBasedMoment(500),
464 TokenDistributionResolvedRecipient::Identity(Identifier::new([0x77; 32])),
465 );
466 let json = original.to_json().expect("to_json");
467 assert_eq!(json["$type"], json!("perpetual"));
468 let recovered = TokenDistributionInfo::from_json(json).expect("from_json");
469 assert_eq!(original, recovered);
470 }
471
472 #[test]
473 fn json_round_trip_once_per_identity_variant() {
474 use crate::serialization::JsonConvertible;
475 let original =
476 TokenDistributionInfo::OncePerIdentity(1_700_000_000_000, Identifier::new([0x42; 32]));
477 let json = original.to_json().expect("to_json");
478 assert_eq!(
479 json,
480 json!({
481 "$type": "oncePerIdentity",
482 "timestamp": 1_700_000_000_000u64,
483 "identity": "5TeWSsjg2gbxCyWVniXeCmwM7UtHTCK7svzJr5xYJzHf",
484 })
485 );
486 let recovered = TokenDistributionInfo::from_json(json).expect("from_json");
487 assert_eq!(original, recovered);
488
489 let resolved: TokenDistributionTypeWithResolvedRecipient = (&original).into();
490 assert_eq!(
491 resolved,
492 TokenDistributionTypeWithResolvedRecipient::OncePerIdentity(Identifier::new(
493 [0x42; 32]
494 ))
495 );
496 let resolved_json = resolved.to_json().expect("to_json");
497 assert_eq!(
498 resolved_json,
499 json!({
500 "$type": "oncePerIdentity",
501 "value": "5TeWSsjg2gbxCyWVniXeCmwM7UtHTCK7svzJr5xYJzHf",
502 })
503 );
504 let recovered = TokenDistributionTypeWithResolvedRecipient::from_json(resolved_json)
505 .expect("from_json");
506 assert_eq!(resolved, recovered);
507 }
508}