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, 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 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 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 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 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 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 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 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}