Skip to main content

dpp/data_contract/change_control_rules/
mod.rs

1pub mod authorized_action_takers;
2pub mod v0;
3
4use crate::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers;
5use crate::data_contract::change_control_rules::v0::ChangeControlRulesV0;
6use crate::data_contract::group::Group;
7use crate::data_contract::GroupContractPosition;
8use crate::group::action_taker::{ActionGoal, ActionTaker};
9#[cfg(feature = "json-conversion")]
10use crate::serialization::JsonConvertible;
11#[cfg(feature = "value-conversion")]
12use crate::serialization::ValueConvertible;
13use bincode::{Decode, DecodeUntrusted, Encode};
14use derive_more::From;
15use platform_value::Identifier;
16use serde::{Deserialize, Serialize};
17use std::collections::BTreeMap;
18use std::fmt;
19
20#[cfg_attr(feature = "json-conversion", derive(JsonConvertible))]
21#[cfg_attr(feature = "value-conversion", derive(ValueConvertible))]
22#[derive(
23    Serialize, Deserialize, Decode, Encode, Debug, Clone, PartialEq, Eq, From, DecodeUntrusted,
24)]
25#[serde(tag = "$formatVersion")]
26pub enum ChangeControlRules {
27    #[serde(rename = "0")]
28    V0(ChangeControlRulesV0),
29}
30
31impl ChangeControlRules {
32    pub fn admin_action_takers(&self) -> &AuthorizedActionTakers {
33        match self {
34            ChangeControlRules::V0(v0) => &v0.admin_action_takers,
35        }
36    }
37    pub fn authorized_to_make_change_action_takers(&self) -> &AuthorizedActionTakers {
38        match self {
39            ChangeControlRules::V0(v0) => &v0.authorized_to_make_change,
40        }
41    }
42
43    pub fn set_admin_action_takers(&mut self, admin_action_takers: AuthorizedActionTakers) {
44        match self {
45            ChangeControlRules::V0(v0) => {
46                v0.admin_action_takers = admin_action_takers;
47            }
48        }
49    }
50
51    pub fn set_authorized_to_make_change_action_takers(
52        &mut self,
53        authorized_to_make_change: AuthorizedActionTakers,
54    ) {
55        match self {
56            ChangeControlRules::V0(v0) => {
57                v0.authorized_to_make_change = authorized_to_make_change;
58            }
59        }
60    }
61
62    pub fn can_make_change(
63        &self,
64        contract_owner_id: &Identifier,
65        main_group: Option<GroupContractPosition>,
66        groups: &BTreeMap<GroupContractPosition, Group>,
67        action_taker: &ActionTaker,
68        goal: ActionGoal,
69    ) -> bool {
70        match self {
71            ChangeControlRules::V0(v0) => {
72                v0.can_make_change(contract_owner_id, main_group, groups, action_taker, goal)
73            }
74        }
75    }
76
77    pub fn can_change_authorized_action_takers(
78        &self,
79        controlling_action_takers: &AuthorizedActionTakers,
80        contract_owner_id: &Identifier,
81        main_group: Option<GroupContractPosition>,
82        groups: &BTreeMap<GroupContractPosition, Group>,
83        action_taker: &ActionTaker,
84        goal: ActionGoal,
85    ) -> bool {
86        match self {
87            ChangeControlRules::V0(v0) => v0.can_change_authorized_action_takers(
88                controlling_action_takers,
89                contract_owner_id,
90                main_group,
91                groups,
92                action_taker,
93                goal,
94            ),
95        }
96    }
97
98    pub fn can_change_admin_action_takers(
99        &self,
100        admin_action_takers: &AuthorizedActionTakers,
101        contract_owner_id: &Identifier,
102        main_group: Option<GroupContractPosition>,
103        groups: &BTreeMap<GroupContractPosition, Group>,
104        action_taker: &ActionTaker,
105        goal: ActionGoal,
106    ) -> bool {
107        match self {
108            ChangeControlRules::V0(v0) => v0.can_change_admin_action_takers(
109                admin_action_takers,
110                contract_owner_id,
111                main_group,
112                groups,
113                action_taker,
114                goal,
115            ),
116        }
117    }
118    pub fn can_change_to(
119        &self,
120        other: &ChangeControlRules,
121        contract_owner_id: &Identifier,
122        main_group: Option<GroupContractPosition>,
123        groups: &BTreeMap<GroupContractPosition, Group>,
124        action_taker: &ActionTaker,
125        goal: ActionGoal,
126    ) -> bool {
127        match (self, other) {
128            (ChangeControlRules::V0(v0), ChangeControlRules::V0(v0_other)) => v0.can_change_to(
129                v0_other,
130                contract_owner_id,
131                main_group,
132                groups,
133                action_taker,
134                goal,
135            ),
136        }
137    }
138}
139
140impl fmt::Display for ChangeControlRules {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        match self {
143            ChangeControlRules::V0(v0) => {
144                write!(f, "{}", v0) //just pass through
145            }
146        }
147    }
148}
149
150#[cfg(all(test, feature = "json-conversion"))]
151mod tests {
152    use super::*;
153    use crate::serialization::JsonConvertible;
154
155    #[test]
156    fn change_control_rules_json_round_trip() {
157        let rules = ChangeControlRules::V0(ChangeControlRulesV0 {
158            authorized_to_make_change: AuthorizedActionTakers::ContractOwner,
159            admin_action_takers: AuthorizedActionTakers::NoOne,
160            changing_authorized_action_takers_to_no_one_allowed: true,
161            changing_admin_action_takers_to_no_one_allowed: false,
162            self_changing_admin_action_takers_allowed: true,
163        });
164
165        let json = rules.to_json().expect("to_json should succeed");
166
167        // Verify boolean fields
168        assert!(json["changingAuthorizedActionTakersToNoOneAllowed"]
169            .as_bool()
170            .unwrap());
171        assert!(!json["changingAdminActionTakersToNoOneAllowed"]
172            .as_bool()
173            .unwrap());
174        assert!(json["selfChangingAdminActionTakersAllowed"]
175            .as_bool()
176            .unwrap());
177
178        // round-trip
179        let restored = ChangeControlRules::from_json(json).expect("from_json should succeed");
180        assert_eq!(rules, restored);
181    }
182
183    #[test]
184    fn change_control_rules_with_group_json_round_trip() {
185        let rules = ChangeControlRules::V0(ChangeControlRulesV0 {
186            authorized_to_make_change: AuthorizedActionTakers::Group(3),
187            admin_action_takers: AuthorizedActionTakers::Identity(Identifier::from([0xFFu8; 32])),
188            changing_authorized_action_takers_to_no_one_allowed: false,
189            changing_admin_action_takers_to_no_one_allowed: false,
190            self_changing_admin_action_takers_allowed: false,
191        });
192
193        let json = rules.to_json().expect("to_json should succeed");
194        let restored = ChangeControlRules::from_json(json).expect("from_json should succeed");
195        assert_eq!(rules, restored);
196    }
197}
198
199#[cfg(all(
200    test,
201    feature = "json-conversion",
202    feature = "value-conversion",
203    feature = "serde-conversion"
204))]
205mod json_convertible_tests {
206    use super::*;
207    use crate::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers;
208    use crate::data_contract::change_control_rules::v0::ChangeControlRulesV0;
209    use platform_value::platform_value;
210    use serde_json::json;
211
212    /// Non-default values per field so the wire-shape assertion catches any
213    /// silent zero-out / flip on round-trip.
214    fn fixture() -> ChangeControlRules {
215        ChangeControlRules::V0(ChangeControlRulesV0 {
216            authorized_to_make_change: AuthorizedActionTakers::ContractOwner,
217            admin_action_takers: AuthorizedActionTakers::MainGroup,
218            changing_authorized_action_takers_to_no_one_allowed: true,
219            changing_admin_action_takers_to_no_one_allowed: false,
220            self_changing_admin_action_takers_allowed: true,
221        })
222    }
223
224    #[test]
225    fn json_round_trip_with_full_wire_shape() {
226        use crate::serialization::JsonConvertible;
227        let original = fixture();
228        let json = original.to_json().expect("to_json");
229        // `AuthorizedActionTakers` uses a custom internally-tagged serde impl
230        // (`{"$type": ...}` maps — see authorized_action_takers.rs); unit and
231        // payload variants share the same flat map shape.
232        assert_eq!(
233            json,
234            json!({
235                "$formatVersion": "0",
236                "authorizedToMakeChange": {"$type": "contractOwner"},
237                "adminActionTakers": {"$type": "mainGroup"},
238                "changingAuthorizedActionTakersToNoOneAllowed": true,
239                "changingAdminActionTakersToNoOneAllowed": false,
240                "selfChangingAdminActionTakersAllowed": true,
241            })
242        );
243        let recovered = ChangeControlRules::from_json(json).expect("from_json");
244        assert_eq!(original, recovered);
245    }
246
247    #[test]
248    fn value_round_trip_with_full_wire_shape() {
249        use crate::serialization::ValueConvertible;
250        let original = fixture();
251        let value = original.to_object().expect("to_object");
252        // No sized integers in this fixture — only Text + Bool. The custom
253        // `AuthorizedActionTakers` impl emits `{"$type": ...}` maps on both
254        // wire formats.
255        assert_eq!(
256            value,
257            platform_value!({
258                "$formatVersion": "0",
259                "authorizedToMakeChange": {"$type": "contractOwner"},
260                "adminActionTakers": {"$type": "mainGroup"},
261                "changingAuthorizedActionTakersToNoOneAllowed": true,
262                "changingAdminActionTakersToNoOneAllowed": false,
263                "selfChangingAdminActionTakersAllowed": true,
264            })
265        );
266        let recovered = ChangeControlRules::from_object(value).expect("from_object");
267        assert_eq!(original, recovered);
268    }
269}