Skip to main content

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