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