Skip to main content

dpp/data_contract/associated_token/token_distribution_rules/
mod.rs

1#[cfg(feature = "json-conversion")]
2use crate::serialization::JsonConvertible;
3#[cfg(feature = "value-conversion")]
4use crate::serialization::ValueConvertible;
5use bincode::{Decode, DecodeUntrusted, Encode};
6use derive_more::From;
7use serde::{Deserialize, Serialize};
8
9pub mod accessors;
10mod methods;
11pub mod v0;
12pub mod v1;
13
14#[cfg_attr(feature = "json-conversion", derive(JsonConvertible))]
15#[cfg_attr(feature = "value-conversion", derive(ValueConvertible))]
16#[derive(
17    Serialize, Deserialize, Encode, Decode, Debug, Clone, PartialEq, Eq, From, DecodeUntrusted,
18)]
19#[serde(tag = "$formatVersion")]
20pub enum TokenDistributionRules {
21    #[serde(rename = "0")]
22    V0(TokenDistributionRulesV0),
23    /// Version 0 plus the once-per-identity distribution (protocol version 14).
24    #[serde(rename = "1")]
25    V1(TokenDistributionRulesV1),
26}
27
28use crate::data_contract::associated_token::token_distribution_rules::v0::TokenDistributionRulesV0;
29use crate::data_contract::associated_token::token_distribution_rules::v1::TokenDistributionRulesV1;
30use std::fmt;
31
32impl fmt::Display for TokenDistributionRules {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        match self {
35            TokenDistributionRules::V0(v0) => {
36                write!(f, "{}", v0) //just pass through
37            }
38            TokenDistributionRules::V1(v1) => {
39                write!(f, "{}", v1) //just pass through
40            }
41        }
42    }
43}
44
45#[cfg(all(
46    test,
47    feature = "json-conversion",
48    feature = "value-conversion",
49    feature = "serde-conversion"
50))]
51mod json_convertible_tests {
52    use super::*;
53    use crate::data_contract::associated_token::token_distribution_rules::v0::TokenDistributionRulesV0;
54    use crate::data_contract::change_control_rules::v0::ChangeControlRulesV0;
55    use crate::data_contract::change_control_rules::ChangeControlRules;
56    use platform_value::{platform_value, Identifier, Value};
57    use serde_json::json;
58
59    /// Non-default values per inner field (set destination_identity to a
60    /// specific identifier and `minting_allow_choosing_destination` to true)
61    /// so the wire-shape assertion catches silent zero-out / flip on round-trip.
62    fn fixture() -> TokenDistributionRules {
63        let ccr = || ChangeControlRules::V0(ChangeControlRulesV0::default());
64        TokenDistributionRules::V0(TokenDistributionRulesV0 {
65            perpetual_distribution: None,
66            perpetual_distribution_rules: ccr(),
67            pre_programmed_distribution: None,
68            new_tokens_destination_identity: Some(Identifier::new([0x42; 32])),
69            new_tokens_destination_identity_rules: ccr(),
70            minting_allow_choosing_destination: true,
71            minting_allow_choosing_destination_rules: ccr(),
72            change_direct_purchase_pricing_rules: ccr(),
73        })
74    }
75
76    fn default_ccr_json() -> serde_json::Value {
77        json!({
78            "$formatVersion": "0",
79            "authorizedToMakeChange": {"$type": "noOne"},
80            "adminActionTakers": {"$type": "noOne"},
81            "changingAuthorizedActionTakersToNoOneAllowed": false,
82            "changingAdminActionTakersToNoOneAllowed": false,
83            "selfChangingAdminActionTakersAllowed": false,
84        })
85    }
86
87    fn default_ccr_value() -> Value {
88        platform_value!({
89            "$formatVersion": "0",
90            "authorizedToMakeChange": {"$type": "noOne"},
91            "adminActionTakers": {"$type": "noOne"},
92            "changingAuthorizedActionTakersToNoOneAllowed": false,
93            "changingAdminActionTakersToNoOneAllowed": false,
94            "selfChangingAdminActionTakersAllowed": false,
95        })
96    }
97
98    #[test]
99    fn json_round_trip_with_full_wire_shape() {
100        use crate::serialization::JsonConvertible;
101        let original = fixture();
102        let json = original.to_json().expect("to_json");
103        // `Identifier` renders as base58 string in JSON. None Options become
104        // `null`. Inner `ChangeControlRules` round-trips its own envelope.
105        // No sized integers in this fixture.
106        assert_eq!(
107            json,
108            json!({
109                "$formatVersion": "0",
110                "perpetualDistribution": null,
111                "perpetualDistributionRules": default_ccr_json(),
112                "preProgrammedDistribution": null,
113                "newTokensDestinationIdentity": "5TeWSsjg2gbxCyWVniXeCmwM7UtHTCK7svzJr5xYJzHf",
114                "newTokensDestinationIdentityRules": default_ccr_json(),
115                "mintingAllowChoosingDestination": true,
116                "mintingAllowChoosingDestinationRules": default_ccr_json(),
117                "changeDirectPurchasePricingRules": default_ccr_json(),
118            })
119        );
120        let recovered = TokenDistributionRules::from_json(json).expect("from_json");
121        assert_eq!(original, recovered);
122    }
123
124    #[test]
125    fn v1_json_round_trip_with_once_per_identity_distribution() {
126        use crate::data_contract::associated_token::token_distribution_rules::accessors::v1::{
127            TokenDistributionRulesV1Getters, TokenDistributionRulesV1Setters,
128        };
129        use crate::data_contract::associated_token::token_once_per_identity_distribution::v0::TokenOncePerIdentityDistributionV0;
130        use crate::data_contract::associated_token::token_once_per_identity_distribution::TokenOncePerIdentityDistribution;
131        use crate::serialization::JsonConvertible;
132        // Setting the once-per-identity distribution on V0 rules upgrades them to V1 in place.
133        let mut rules = fixture();
134        let distribution =
135            TokenOncePerIdentityDistribution::V0(TokenOncePerIdentityDistributionV0 {
136                amount: 100,
137            });
138        rules.set_once_per_identity_distribution(Some(distribution.clone()));
139        assert!(matches!(rules, TokenDistributionRules::V1(_)));
140        assert_eq!(rules.once_per_identity_distribution(), Some(&distribution));
141
142        let json = rules.to_json().expect("to_json");
143        assert_eq!(
144            json,
145            json!({
146                "$formatVersion": "1",
147                "perpetualDistribution": null,
148                "perpetualDistributionRules": default_ccr_json(),
149                "preProgrammedDistribution": null,
150                "newTokensDestinationIdentity": "5TeWSsjg2gbxCyWVniXeCmwM7UtHTCK7svzJr5xYJzHf",
151                "newTokensDestinationIdentityRules": default_ccr_json(),
152                "mintingAllowChoosingDestination": true,
153                "mintingAllowChoosingDestinationRules": default_ccr_json(),
154                "changeDirectPurchasePricingRules": default_ccr_json(),
155                "oncePerIdentityDistribution": {
156                    "$formatVersion": "0",
157                    "amount": 100,
158                },
159            })
160        );
161        let recovered = TokenDistributionRules::from_json(json).expect("from_json");
162        assert_eq!(rules, recovered);
163    }
164
165    #[test]
166    fn clearing_once_per_identity_distribution_downgrades_v1_rules_to_v0() {
167        use crate::data_contract::associated_token::token_distribution_rules::accessors::v1::TokenDistributionRulesV1Setters;
168        use crate::data_contract::associated_token::token_once_per_identity_distribution::v0::TokenOncePerIdentityDistributionV0;
169        use crate::data_contract::associated_token::token_once_per_identity_distribution::TokenOncePerIdentityDistribution;
170        // Version 0 stays the wire format whenever the distribution is unset, so clearing it
171        // takes the rules back to the exact version 0 they were upgraded from.
172        let mut rules = fixture();
173        rules.set_once_per_identity_distribution(Some(TokenOncePerIdentityDistribution::V0(
174            TokenOncePerIdentityDistributionV0 { amount: 100 },
175        )));
176        assert!(matches!(rules, TokenDistributionRules::V1(_)));
177        rules.set_once_per_identity_distribution(None);
178        assert_eq!(rules, fixture());
179    }
180
181    #[test]
182    fn v0_rules_stay_v0_when_clearing_once_per_identity_distribution() {
183        use crate::data_contract::associated_token::token_distribution_rules::accessors::v1::{
184            TokenDistributionRulesV1Getters, TokenDistributionRulesV1Setters,
185        };
186        let mut rules = fixture();
187        rules.set_once_per_identity_distribution(None);
188        assert_eq!(rules, fixture());
189        assert_eq!(rules.once_per_identity_distribution(), None);
190    }
191
192    #[test]
193    fn value_round_trip_with_full_wire_shape() {
194        use crate::serialization::ValueConvertible;
195        let original = fixture();
196        let value = original.to_object().expect("to_object");
197        // `Identifier`'s Serialize emits `Value::Identifier`; interpolating the
198        // Identifier through `platform_value!{...}` runs Serialize and produces
199        // the typed variant. None becomes `Value::Null`.
200        let id = Identifier::new([0x42; 32]);
201        assert_eq!(
202            value,
203            platform_value!({
204                "$formatVersion": "0",
205                "perpetualDistribution": Value::Null,
206                "perpetualDistributionRules": default_ccr_value(),
207                "preProgrammedDistribution": Value::Null,
208                "newTokensDestinationIdentity": id,
209                "newTokensDestinationIdentityRules": default_ccr_value(),
210                "mintingAllowChoosingDestination": true,
211                "mintingAllowChoosingDestinationRules": default_ccr_value(),
212                "changeDirectPurchasePricingRules": default_ccr_value(),
213            })
214        );
215        let recovered = TokenDistributionRules::from_object(value).expect("from_object");
216        assert_eq!(original, recovered);
217    }
218}