1use crate::address_funds::PlatformAddress;
2use crate::asset_lock::StoredAssetLockInfo;
3use crate::balances::credits::TokenAmount;
4use crate::data_contract::config::moderation::{
5 ContractDocumentRemoval, ContractModerationListStatuses,
6};
7use crate::data_contract::document_type::action_fees::{ContractFeePot, ContractFeePotLastClaim};
8use crate::data_contract::group::GroupSumPower;
9use crate::data_contract::DataContract;
10use crate::document::Document;
11use crate::fee::Credits;
12use crate::group::group_action_status::GroupActionStatus;
13use crate::identity::{Identity, PartialIdentity};
14use crate::prelude::AddressNonce;
15#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
16use crate::serialization::JsonConvertible;
17#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
18use crate::serialization::ValueConvertible;
19use crate::tokens::info::IdentityTokenInfo;
20use crate::tokens::status::TokenStatus;
21use crate::tokens::token_pricing_schedule::TokenPricingSchedule;
22use crate::voting::votes::Vote;
23use platform_value::Identifier;
24use std::collections::BTreeMap;
25
26#[derive(Debug, PartialEq, strum::Display, derive_more::TryInto)]
27#[cfg_attr(
28 feature = "serde-conversion",
29 derive(serde::Serialize, serde::Deserialize)
30)]
31pub enum StateTransitionProofResult {
32 VerifiedDataContract(DataContract),
33 VerifiedIdentity(Identity),
34 VerifiedTokenBalanceAbsence(Identifier),
35 VerifiedTokenBalance(
41 Identifier,
42 #[cfg_attr(
43 feature = "json-conversion",
44 serde(with = "crate::serialization::json_safe_u64")
45 )]
46 TokenAmount,
47 ),
48 VerifiedTokenIdentityInfo(Identifier, IdentityTokenInfo),
49 VerifiedTokenPricingSchedule(Identifier, Option<TokenPricingSchedule>),
50 VerifiedTokenStatus(TokenStatus),
51 VerifiedTokenIdentitiesBalances(
52 #[cfg_attr(
53 feature = "json-conversion",
54 serde(
55 with = "crate::serialization::json::safe_integer_map::json_safe_identifier_u64_map"
56 )
57 )]
58 BTreeMap<Identifier, TokenAmount>,
59 ),
60 VerifiedPartialIdentity(PartialIdentity),
61 VerifiedBalanceTransfer(PartialIdentity, PartialIdentity), VerifiedDocuments(BTreeMap<Identifier, Option<Document>>),
63 VerifiedTokenActionWithDocument(Document),
64 VerifiedTokenGroupActionWithDocument(GroupSumPower, Option<Document>),
65 VerifiedTokenGroupActionWithTokenBalance(
66 GroupSumPower,
67 GroupActionStatus,
68 #[cfg_attr(
69 feature = "json-conversion",
70 serde(with = "crate::serialization::json_safe_option_u64")
71 )]
72 Option<TokenAmount>,
73 ),
74 VerifiedTokenGroupActionWithTokenIdentityInfo(
75 GroupSumPower,
76 GroupActionStatus,
77 Option<IdentityTokenInfo>,
78 ),
79 VerifiedTokenGroupActionWithTokenPricingSchedule(
80 GroupSumPower,
81 GroupActionStatus,
82 Option<TokenPricingSchedule>,
83 ),
84 VerifiedMasternodeVote(Vote),
85 VerifiedNextDistribution(Vote),
86 VerifiedAddressInfos(
87 #[cfg_attr(
88 feature = "json-conversion",
89 serde(with = "json_safe_address_info_map")
90 )]
91 BTreeMap<PlatformAddress, Option<(AddressNonce, Credits)>>,
92 ),
93 VerifiedIdentityFullWithAddressInfos(
94 Identity,
95 #[cfg_attr(
96 feature = "json-conversion",
97 serde(with = "json_safe_address_info_map")
98 )]
99 BTreeMap<PlatformAddress, Option<(AddressNonce, Credits)>>,
100 ),
101 VerifiedIdentityWithAddressInfos(
102 PartialIdentity,
103 #[cfg_attr(
104 feature = "json-conversion",
105 serde(with = "json_safe_address_info_map")
106 )]
107 BTreeMap<PlatformAddress, Option<(AddressNonce, Credits)>>,
108 ),
109 VerifiedAssetLockConsumed(StoredAssetLockInfo),
110 VerifiedShieldedNullifiers(Vec<(Vec<u8>, bool)>),
111 #[try_into(ignore)]
118 VerifiedTokenShieldedPoolBalance(
119 Identifier,
120 #[cfg_attr(
121 feature = "json-conversion",
122 serde(with = "crate::serialization::json_safe_u64")
123 )]
124 TokenAmount,
125 ),
126 VerifiedShieldedNullifiersWithAddressInfos(
127 Vec<(Vec<u8>, bool)>,
128 #[cfg_attr(
129 feature = "json-conversion",
130 serde(with = "json_safe_address_info_map")
131 )]
132 BTreeMap<PlatformAddress, Option<(AddressNonce, Credits)>>,
133 ),
134 VerifiedShieldedNullifiersWithWithdrawalDocument(
135 Vec<(Vec<u8>, bool)>,
136 BTreeMap<Identifier, Option<Document>>,
137 ),
138 VerifiedAssetLockConsumedWithAddressInfos(
146 StoredAssetLockInfo,
147 BTreeMap<PlatformAddress, Option<(AddressNonce, Credits)>>,
148 ),
149 VerifiedIdentityWithShieldedNullifiers(Identity, Vec<(Vec<u8>, bool)>),
154 VerifiedContractModerationListStatuses(Identifier, Identifier, ContractModerationListStatuses),
160 VerifiedContractFeeClaim(
165 Identifier,
166 ContractFeePot,
167 ContractFeePotLastClaim,
168 Credits,
169 #[cfg_attr(
170 feature = "json-conversion",
171 serde(
172 with = "crate::serialization::json::safe_integer_map::json_safe_identifier_u64_map"
173 )
174 )]
175 BTreeMap<Identifier, Credits>,
176 ),
177 VerifiedContractDocumentRemoval(Identifier, String, Identifier, ContractDocumentRemoval),
183 #[try_into(ignore)]
193 VerifiedTokenGroupActionWithShieldedPoolBalance(
194 GroupSumPower,
195 GroupActionStatus,
196 #[cfg_attr(
197 feature = "json-conversion",
198 serde(with = "crate::serialization::json_safe_option_u64")
199 )]
200 Option<TokenAmount>,
201 ),
202 VerifiedTokenGroupActionWithShieldedNullifiers(
208 GroupSumPower,
209 GroupActionStatus,
210 Vec<(Vec<u8>, bool)>,
211 ),
212 VerifiedContractTeamActionSignature(Identifier, Identifier, GroupActionStatus),
221}
222
223#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display)]
225#[cfg_attr(
226 feature = "serde-conversion",
227 derive(serde::Serialize, serde::Deserialize)
228)]
229pub enum StateTransitionProofGuarantee {
230 ExecutionProved,
233 AffectedState,
238}
239
240#[derive(Debug, PartialEq)]
258#[cfg_attr(
259 feature = "serde-conversion",
260 derive(serde::Serialize, serde::Deserialize)
261)]
262pub struct StateTransitionProofOutcome {
263 guarantee: StateTransitionProofGuarantee,
264 result: StateTransitionProofResult,
265 #[cfg_attr(
266 feature = "json-conversion",
267 serde(with = "crate::serialization::json_safe_option_u64")
268 )]
269 owner_balance: Option<Credits>,
270}
271
272impl StateTransitionProofOutcome {
273 pub fn execution_proved(result: StateTransitionProofResult) -> Self {
275 Self {
276 guarantee: StateTransitionProofGuarantee::ExecutionProved,
277 result,
278 owner_balance: None,
279 }
280 }
281
282 pub fn affected_state(result: StateTransitionProofResult) -> Self {
284 Self {
285 guarantee: StateTransitionProofGuarantee::AffectedState,
286 result,
287 owner_balance: None,
288 }
289 }
290
291 pub fn with_owner_balance(mut self, owner_balance: Option<Credits>) -> Self {
293 self.owner_balance = owner_balance;
294 self
295 }
296
297 pub fn guarantee(&self) -> StateTransitionProofGuarantee {
299 self.guarantee
300 }
301
302 pub fn is_execution_proved(&self) -> bool {
304 self.guarantee == StateTransitionProofGuarantee::ExecutionProved
305 }
306
307 pub fn result(&self) -> &StateTransitionProofResult {
309 &self.result
310 }
311
312 pub fn into_result(self) -> StateTransitionProofResult {
314 self.result
315 }
316
317 pub fn owner_balance(&self) -> Option<Credits> {
320 self.owner_balance
321 }
322
323 pub fn into_parts(
325 self,
326 ) -> (
327 StateTransitionProofGuarantee,
328 StateTransitionProofResult,
329 Option<Credits>,
330 ) {
331 (self.guarantee, self.result, self.owner_balance)
332 }
333}
334
335#[cfg(feature = "json-conversion")]
343mod json_safe_address_info_map {
344 use super::{AddressNonce, Credits, PlatformAddress};
345 use serde::de::Deserializer;
346 use serde::ser::{SerializeMap, Serializer};
347 use serde::{Deserialize, Serialize};
348 use std::collections::BTreeMap;
349
350 type AddressInfoMap = BTreeMap<PlatformAddress, Option<(AddressNonce, Credits)>>;
354
355 #[derive(Serialize, Deserialize)]
356 struct Entry(
357 AddressNonce,
358 #[serde(with = "crate::serialization::json_safe_u64")] Credits,
359 );
360
361 pub fn serialize<S: Serializer>(
362 map: &AddressInfoMap,
363 serializer: S,
364 ) -> Result<S::Ok, S::Error> {
365 let mut s = serializer.serialize_map(Some(map.len()))?;
366 for (k, v) in map {
367 let wrapped = v.map(|(nonce, credits)| Entry(nonce, credits));
368 s.serialize_entry(k, &wrapped)?;
369 }
370 s.end()
371 }
372
373 pub fn deserialize<'de, D: Deserializer<'de>>(
374 deserializer: D,
375 ) -> Result<AddressInfoMap, D::Error> {
376 let raw: BTreeMap<PlatformAddress, Option<Entry>> = BTreeMap::deserialize(deserializer)?;
377 Ok(raw
378 .into_iter()
379 .map(|(k, v)| (k, v.map(|Entry(nonce, credits)| (nonce, credits))))
380 .collect())
381 }
382}
383
384#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
386impl JsonConvertible for StateTransitionProofResult {}
387
388#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
389impl ValueConvertible for StateTransitionProofResult {}
390
391#[cfg(all(
392 test,
393 feature = "json-conversion",
394 feature = "value-conversion",
395 feature = "serde-conversion"
396))]
397mod json_convertible_tests {
398 use super::*;
399 use platform_value::{Identifier, Value};
400 use serde_json::json;
401
402 fn fixture() -> StateTransitionProofResult {
406 StateTransitionProofResult::VerifiedTokenBalance(
407 Identifier::new([0xab; 32]),
408 123_456_789u64,
409 )
410 }
411
412 #[test]
413 fn json_round_trip_with_full_wire_shape() {
414 use crate::serialization::JsonConvertible;
415 let original = fixture();
416 let json = original.to_json().expect("to_json");
417 assert_eq!(
423 json,
424 json!({
425 "VerifiedTokenBalance": [
426 "CZ8YUVdk7znjrUmnb5n7kgySk9yRAsQDYmyCxzfSky9t",
427 123_456_789u64,
428 ],
429 })
430 );
431 let recovered = StateTransitionProofResult::from_json(json).expect("from_json");
432 assert_eq!(original, recovered);
433 }
434
435 #[test]
436 fn value_round_trip_with_full_wire_shape() {
437 use crate::serialization::ValueConvertible;
438 let original = fixture();
439 let value = original.to_object().expect("to_object");
440 let expected = Value::Map(vec![(
447 Value::Text("VerifiedTokenBalance".to_string()),
448 Value::Array(vec![Value::Identifier([0xab; 32]), Value::U64(123_456_789)]),
449 )]);
450 assert_eq!(value, expected);
451 let recovered = StateTransitionProofResult::from_object(value).expect("from_object");
452 assert_eq!(original, recovered);
453 }
454
455 #[test]
456 fn verified_token_balance_large_amount_serializes_as_string() {
457 use crate::serialization::JsonConvertible;
458 let original = StateTransitionProofResult::VerifiedTokenBalance(
461 Identifier::new([0xab; 32]),
462 9_007_199_254_740_993, );
464 let json = original.to_json().expect("to_json");
465 assert_eq!(json["VerifiedTokenBalance"][1], json!("9007199254740993"));
466 let recovered = StateTransitionProofResult::from_json(json).expect("from_json");
467 assert_eq!(original, recovered);
468 }
469
470 #[test]
475 fn verified_token_shielded_pool_balance_large_amount_serializes_as_string() {
476 use crate::serialization::JsonConvertible;
477 let original = StateTransitionProofResult::VerifiedTokenShieldedPoolBalance(
478 Identifier::new([0xcd; 32]),
479 9_007_199_254_740_993, );
481 let json = original.to_json().expect("to_json");
482 assert_eq!(
483 json["VerifiedTokenShieldedPoolBalance"][1],
484 json!("9007199254740993")
485 );
486 let recovered = StateTransitionProofResult::from_json(json).expect("from_json");
487 assert_eq!(original, recovered);
488 }
489
490 #[test]
493 fn verified_token_group_action_with_shielded_pool_balance_large_amount_serializes_as_string() {
494 use crate::serialization::JsonConvertible;
495 let original = StateTransitionProofResult::VerifiedTokenGroupActionWithShieldedPoolBalance(
496 1,
497 GroupActionStatus::ActionActive,
498 Some(9_007_199_254_740_993), );
500 let json = original.to_json().expect("to_json");
501 assert_eq!(
502 json["VerifiedTokenGroupActionWithShieldedPoolBalance"][2],
503 json!("9007199254740993")
504 );
505 let recovered = StateTransitionProofResult::from_json(json).expect("from_json");
506 assert_eq!(original, recovered);
507 }
508
509 #[test]
514 fn verified_token_group_action_with_shielded_nullifiers_round_trips_unspent_flags() {
515 use crate::serialization::JsonConvertible;
516 let original = StateTransitionProofResult::VerifiedTokenGroupActionWithShieldedNullifiers(
517 1,
518 GroupActionStatus::ActionActive,
519 vec![(vec![1u8, 2, 3], false), (vec![4u8, 5, 6], false)],
520 );
521 let json = original.to_json().expect("to_json");
522 assert_eq!(
528 json,
529 json!({
530 "VerifiedTokenGroupActionWithShieldedNullifiers": [
531 1,
532 "actionActive",
533 [[[1, 2, 3], false], [[4, 5, 6], false]],
534 ],
535 })
536 );
537 let recovered = StateTransitionProofResult::from_json(json).expect("from_json");
538 assert_eq!(original, recovered);
539 }
540
541 #[test]
545 fn outcome_owner_balance_serializes_as_string_past_safe_integer() {
546 let outcome = StateTransitionProofOutcome::execution_proved(fixture())
547 .with_owner_balance(Some(9_007_199_254_740_993));
548 let json = serde_json::to_value(&outcome).expect("to json");
549 assert_eq!(json["owner_balance"], json!("9007199254740993"));
550 assert_eq!(json["guarantee"], json!("ExecutionProved"));
551 let recovered: StateTransitionProofOutcome =
552 serde_json::from_value(json).expect("from json");
553 assert_eq!(outcome, recovered);
554
555 let without_balance = StateTransitionProofOutcome::affected_state(fixture());
556 let json = serde_json::to_value(&without_balance).expect("to json");
557 assert_eq!(json["owner_balance"], serde_json::Value::Null);
558 let recovered: StateTransitionProofOutcome =
559 serde_json::from_value(json).expect("from json");
560 assert_eq!(without_balance, recovered);
561 }
562
563 #[test]
564 fn verified_address_infos_large_credits_serialize_as_string() {
565 use crate::serialization::{JsonConvertible, ValueConvertible};
566 use std::collections::BTreeMap;
567 let big_credits: Credits = 9_007_199_254_740_993; let mut infos: BTreeMap<PlatformAddress, Option<(AddressNonce, Credits)>> = BTreeMap::new();
572 infos.insert(PlatformAddress::P2pkh([0x11; 20]), Some((7, big_credits)));
573 infos.insert(PlatformAddress::P2sh([0x22; 20]), None);
574 let original = StateTransitionProofResult::VerifiedAddressInfos(infos);
575
576 let json = original.to_json().expect("to_json");
578 let map = json["VerifiedAddressInfos"].as_object().expect("object");
579 let non_null = map
580 .values()
581 .find(|v| !v.is_null())
582 .expect("one populated entry");
583 assert_eq!(non_null[0], json!(7));
584 assert_eq!(non_null[1], json!("9007199254740993"));
585 assert!(
586 map.values().any(|v| v.is_null()),
587 "the None entry must survive"
588 );
589 let recovered = StateTransitionProofResult::from_json(json).expect("from_json");
590 assert_eq!(original, recovered);
591
592 let value = original.to_object().expect("to_object");
594 let recovered = StateTransitionProofResult::from_object(value).expect("from_object");
595 assert_eq!(original, recovered);
596 }
597 #[test]
606 fn should_refuse_to_convert_a_pool_balance_through_a_pre_existing_variants_impl() {
607 let id = Identifier::from([7u8; 32]);
608
609 let balance = StateTransitionProofResult::VerifiedTokenBalance(id, 42);
610 let converted: (Identifier, u64) = balance
611 .try_into()
612 .expect("the pre-existing conversion still identifies a token balance");
613 assert_eq!(converted, (id, 42));
614
615 let pool = StateTransitionProofResult::VerifiedTokenShieldedPoolBalance(id, 42);
616 let refused: Result<(Identifier, u64), _> = pool.try_into();
617 assert!(
618 refused.is_err(),
619 "a pool balance must not pass for a token balance"
620 );
621 }
622}