dpp/data_contract/group/v0/
mod.rs1use 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 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 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 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 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 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 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 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}