dpp/data_contract/group/v0/
mod.rs

1use crate::consensus::basic::data_contract::{
2    GroupExceedsMaxMembersError, GroupHasTooFewMembersError, GroupMemberHasPowerOfZeroError,
3    GroupMemberHasPowerOverLimitError, GroupNonUnilateralMemberPowerHasLessThanRequiredPowerError,
4    GroupRequiredPowerIsInvalidError, GroupTotalPowerLessThanRequiredError,
5};
6use crate::data_contract::group::accessors::v0::{GroupV0Getters, GroupV0Setters};
7use crate::data_contract::group::methods::v0::GroupMethodsV0;
8use crate::data_contract::group::{GroupMemberPower, GroupRequiredPower};
9use crate::data_contract::GroupContractPosition;
10#[cfg(feature = "json-conversion")]
11use crate::serialization::json_safe_fields;
12use crate::validation::SimpleConsensusValidationResult;
13use crate::ProtocolError;
14use bincode::{Decode, Encode};
15use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize};
16use platform_value::Identifier;
17use platform_version::version::PlatformVersion;
18use serde::{Deserialize, Serialize};
19use std::collections::BTreeMap;
20
21#[cfg_attr(feature = "json-conversion", json_safe_fields)]
22#[derive(
23    Serialize,
24    Deserialize,
25    Decode,
26    Encode,
27    PlatformSerialize,
28    PlatformDeserialize,
29    Debug,
30    Clone,
31    PartialEq,
32    Eq,
33)]
34#[serde(rename_all = "camelCase")]
35#[platform_serialize(unversioned)]
36pub struct GroupV0 {
37    pub members: BTreeMap<Identifier, GroupMemberPower>,
38    pub required_power: GroupRequiredPower,
39}
40
41impl GroupV0Getters for GroupV0 {
42    fn member_power(&self, member_id: Identifier) -> Result<u32, ProtocolError> {
43        self.members
44            .get(&member_id)
45            .cloned()
46            .ok_or(ProtocolError::GroupMemberNotFound(format!(
47                "Group member {} not found",
48                member_id
49            )))
50    }
51
52    fn members(&self) -> &BTreeMap<Identifier, u32> {
53        &self.members
54    }
55
56    fn members_mut(&mut self) -> &mut BTreeMap<Identifier, u32> {
57        &mut self.members
58    }
59
60    fn required_power(&self) -> GroupRequiredPower {
61        self.required_power
62    }
63}
64
65impl GroupV0Setters for GroupV0 {
66    fn set_members(&mut self, members: BTreeMap<Identifier, u32>) {
67        self.members = members;
68    }
69
70    fn set_member_power(&mut self, member_id: Identifier, power: u32) {
71        self.members.insert(member_id, power);
72    }
73
74    fn remove_member(&mut self, member_id: &Identifier) -> bool {
75        self.members.remove(member_id).is_some()
76    }
77
78    fn set_required_power(&mut self, required_power: GroupRequiredPower) {
79        self.required_power = required_power;
80    }
81}
82
83impl GroupMethodsV0 for GroupV0 {
84    /// Validates the group to ensure:
85    /// - The sum of all group member powers is equal to or greater than the required power.
86    /// - No group member has a power of 0.
87    /// - The group does not exceed the maximum allowed members (256).
88    ///
89    /// # Returns
90    /// - `Ok(SimpleConsensusValidationResult)` if the group is valid.
91    /// - `Err(ProtocolError)` if validation fails due to an invalid group configuration.
92    fn validate(
93        &self,
94        group_contract_position: Option<GroupContractPosition>,
95        platform_version: &PlatformVersion,
96    ) -> Result<SimpleConsensusValidationResult, ProtocolError> {
97        let max_group_members = platform_version.system_limits.max_contract_group_size as u32;
98        const GROUP_POWER_LIMIT: GroupMemberPower = u16::MAX as GroupMemberPower;
99
100        // Check the number of members does not exceed the maximum allowed
101        if self.members.len() as u32 > max_group_members {
102            return Ok(SimpleConsensusValidationResult::new_with_error(
103                GroupExceedsMaxMembersError::new(max_group_members).into(),
104            ));
105        }
106
107        if self.members.len() < 2 {
108            return Ok(SimpleConsensusValidationResult::new_with_error(
109                GroupHasTooFewMembersError::new(group_contract_position).into(),
110            ));
111        }
112
113        let mut total_power: GroupMemberPower = 0;
114
115        let mut total_power_without_unilateral_members: GroupMemberPower = 0;
116
117        // Iterate over members to validate their power and calculate the total power
118        for (&member, &power) in &self.members {
119            if power == 0 {
120                return Ok(SimpleConsensusValidationResult::new_with_error(
121                    GroupMemberHasPowerOfZeroError::new(member).into(),
122                ));
123            }
124            if power > GROUP_POWER_LIMIT {
125                return Ok(SimpleConsensusValidationResult::new_with_error(
126                    GroupMemberHasPowerOverLimitError::new(member, power, GROUP_POWER_LIMIT).into(),
127                ));
128            }
129            if power > self.required_power {
130                return Ok(SimpleConsensusValidationResult::new_with_error(
131                    GroupMemberHasPowerOverLimitError::new(member, power, self.required_power)
132                        .into(),
133                ));
134            }
135            total_power = total_power
136                .checked_add(power)
137                .ok_or_else(|| ProtocolError::Overflow("Total power overflowed"))?;
138
139            if power < self.required_power {
140                total_power_without_unilateral_members = total_power_without_unilateral_members
141                    .checked_add(power)
142                    .ok_or_else(|| ProtocolError::Overflow("Total power overflowed"))?;
143            }
144        }
145
146        // Check if the total power meets the required power
147        if total_power < self.required_power {
148            return Ok(SimpleConsensusValidationResult::new_with_error(
149                GroupTotalPowerLessThanRequiredError::new(total_power, self.required_power).into(),
150            ));
151        }
152
153        // Check if the total power without unilateral members meets the required power
154        if total_power_without_unilateral_members < self.required_power
155            && total_power_without_unilateral_members > 0
156        {
157            return Ok(SimpleConsensusValidationResult::new_with_error(
158                GroupNonUnilateralMemberPowerHasLessThanRequiredPowerError::new(
159                    total_power_without_unilateral_members,
160                    self.required_power,
161                )
162                .into(),
163            ));
164        }
165
166        if self.required_power == 0 || self.required_power() > GROUP_POWER_LIMIT {
167            return Ok(SimpleConsensusValidationResult::new_with_error(
168                GroupRequiredPowerIsInvalidError::new(self.required_power, GROUP_POWER_LIMIT)
169                    .into(),
170            ));
171        }
172
173        // If all validations pass, return an empty validation result
174        Ok(SimpleConsensusValidationResult::new())
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    mod validate {
183        use super::*;
184        use crate::consensus::basic::BasicError;
185        use crate::consensus::ConsensusError;
186
187        #[test]
188        fn test_group_with_all_unilateral_members() {
189            let member1 = Identifier::random();
190            let member2 = Identifier::random();
191
192            let group = GroupV0 {
193                members: [(member1, 1), (member2, 1)].into(),
194                required_power: 1,
195            };
196
197            let platform_version = PlatformVersion::latest();
198
199            let result = group
200                .validate(None, platform_version)
201                .expect("group should be valid");
202
203            assert!(result.is_valid());
204        }
205
206        #[test]
207        fn test_group_exceeds_max_members() {
208            let platform_version = PlatformVersion::latest();
209            let max = platform_version.system_limits.max_contract_group_size as u32;
210
211            let mut members = BTreeMap::new();
212            for i in 0..=max {
213                let mut id_bytes = [0u8; 32];
214                id_bytes[0..4].copy_from_slice(&i.to_le_bytes());
215                members.insert(Identifier::new(id_bytes), 1);
216            }
217
218            let group = GroupV0 {
219                members,
220                required_power: 1,
221            };
222
223            let result = group
224                .validate(None, platform_version)
225                .expect("should not error");
226
227            let Some(ConsensusError::BasicError(BasicError::GroupExceedsMaxMembersError(_))) =
228                result.errors.first()
229            else {
230                panic!("expected GroupExceedsMaxMembersError");
231            };
232        }
233
234        #[test]
235        fn test_group_too_few_members_one() {
236            let group = GroupV0 {
237                members: [(Identifier::random(), 1)].into(),
238                required_power: 1,
239            };
240
241            let result = group
242                .validate(None, PlatformVersion::latest())
243                .expect("should not error");
244
245            let Some(ConsensusError::BasicError(BasicError::GroupHasTooFewMembersError(_))) =
246                result.errors.first()
247            else {
248                panic!("expected GroupHasTooFewMembersError");
249            };
250        }
251
252        #[test]
253        fn test_group_member_has_power_of_zero() {
254            let group = GroupV0 {
255                members: [(Identifier::random(), 0), (Identifier::random(), 1)].into(),
256                required_power: 1,
257            };
258
259            let result = group
260                .validate(None, PlatformVersion::latest())
261                .expect("should not error");
262
263            let Some(ConsensusError::BasicError(BasicError::GroupMemberHasPowerOfZeroError(_))) =
264                result.errors.first()
265            else {
266                panic!("expected GroupMemberHasPowerOfZeroError");
267            };
268        }
269
270        #[test]
271        fn test_group_member_power_over_limit() {
272            let group = GroupV0 {
273                members: [(Identifier::random(), 65_536), (Identifier::random(), 1)].into(),
274                required_power: 65_536,
275            };
276
277            let result = group
278                .validate(None, PlatformVersion::latest())
279                .expect("should not error");
280
281            let Some(ConsensusError::BasicError(BasicError::GroupMemberHasPowerOverLimitError(_))) =
282                result.errors.first()
283            else {
284                panic!("expected GroupMemberHasPowerOverLimitError");
285            };
286        }
287
288        #[test]
289        fn test_group_member_power_exceeds_required() {
290            let group = GroupV0 {
291                members: [(Identifier::random(), 6), (Identifier::random(), 5)].into(),
292                required_power: 5,
293            };
294
295            let result = group
296                .validate(None, PlatformVersion::latest())
297                .expect("should not error");
298
299            let Some(ConsensusError::BasicError(BasicError::GroupMemberHasPowerOverLimitError(_))) =
300                result.errors.first()
301            else {
302                panic!("expected GroupMemberHasPowerOverLimitError");
303            };
304        }
305
306        #[test]
307        fn test_group_total_power_less_than_required() {
308            let group = GroupV0 {
309                members: [(Identifier::random(), 2), (Identifier::random(), 2)].into(),
310                required_power: 5,
311            };
312
313            let result = group
314                .validate(None, PlatformVersion::latest())
315                .expect("should not error");
316
317            let Some(ConsensusError::BasicError(BasicError::GroupTotalPowerLessThanRequiredError(
318                _,
319            ))) = result.errors.first()
320            else {
321                panic!("expected GroupTotalPowerLessThanRequiredError");
322            };
323        }
324
325        #[test]
326        fn test_group_non_unilateral_member_power_less_than_required() {
327            let group = GroupV0 {
328                members: [(Identifier::random(), 10), (Identifier::random(), 5)].into(),
329                required_power: 10,
330            };
331
332            let result = group
333                .validate(None, PlatformVersion::latest())
334                .expect("should not error");
335
336            let Some(ConsensusError::BasicError(
337                BasicError::GroupNonUnilateralMemberPowerHasLessThanRequiredPowerError(_),
338            )) = result.errors.first()
339            else {
340                panic!("expected GroupNonUnilateralMemberPowerHasLessThanRequiredPowerError");
341            };
342        }
343
344        #[test]
345        fn test_group_required_power_zero() {
346            let group = GroupV0 {
347                members: [(Identifier::random(), 1), (Identifier::random(), 1)].into(),
348                required_power: 0,
349            };
350
351            let result = group
352                .validate(None, PlatformVersion::latest())
353                .expect("should not error");
354
355            // Required power of zero is currently intercepted by the per-member `power > required_power`
356            // check before `GroupRequiredPowerIsInvalidError` is evaluated.
357            let Some(ConsensusError::BasicError(BasicError::GroupMemberHasPowerOverLimitError(_))) =
358                result.errors.first()
359            else {
360                panic!("expected GroupMemberHasPowerOverLimitError");
361            };
362        }
363
364        #[test]
365        fn test_group_required_power_over_limit() {
366            let group = GroupV0 {
367                members: [
368                    (Identifier::random(), 65_535),
369                    (Identifier::random(), 65_535),
370                    (Identifier::random(), 65_535),
371                ]
372                .into(),
373                required_power: 65_536,
374            };
375
376            let result = group
377                .validate(None, PlatformVersion::latest())
378                .expect("should not error");
379
380            let Some(ConsensusError::BasicError(BasicError::GroupRequiredPowerIsInvalidError(_))) =
381                result.errors.first()
382            else {
383                panic!("expected GroupRequiredPowerIsInvalidError");
384            };
385        }
386    }
387}