Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ pub(super) fn validate_max_depth_v0(

#[cfg(test)]
mod test {
use crate::consensus::basic::BasicError;
use crate::consensus::ConsensusError;
use serde_json::json;

use super::*;
Expand Down Expand Up @@ -340,4 +342,30 @@ mod test {

assert_eq!(found_depth, 4);
}

#[test]
fn should_return_error_when_max_depth_exceeded() {
let platform_version = PlatformVersion::first();
let max_depth = platform_version
.dpp
.contract_versions
.document_type_versions
.schema
.max_depth as usize;

let mut inner = json!({ "type": "string" });
for _ in 0..max_depth {
inner = json!({ "a": inner });
}
let schema: Value = inner.into();

let result = validate_max_depth_v0(&schema, platform_version);

let Some(ConsensusError::BasicError(BasicError::DataContractMaxDepthExceedError(e))) =
result.errors.first()
else {
panic!("expected DataContractMaxDepthExceedError");
};
assert_eq!(e.max_depth(), max_depth);
}
}
201 changes: 201 additions & 0 deletions packages/rs-dpp/src/data_contract/group/v0/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ mod tests {

mod validate {
use super::*;
use crate::consensus::basic::BasicError;
use crate::consensus::ConsensusError;

#[test]
fn test_group_with_all_unilateral_members() {
Expand All @@ -196,5 +198,204 @@ mod tests {

assert!(result.is_valid());
}

#[test]
fn test_group_exceeds_max_members() {
let platform_version = PlatformVersion::latest();
let max = platform_version.system_limits.max_contract_group_size as u32;

let mut members = BTreeMap::new();
for i in 0..=max {
let mut id_bytes = [0u8; 32];
id_bytes[0..4].copy_from_slice(&i.to_le_bytes());
members.insert(Identifier::new(id_bytes), 1);
}

let group = GroupV0 {
members,
required_power: 1,
};

let result = group
.validate(None, platform_version)
.expect("should not error");

let Some(ConsensusError::BasicError(BasicError::GroupExceedsMaxMembersError(_))) =
result.errors.first()
else {
panic!("expected GroupExceedsMaxMembersError");
};
}

#[test]
fn test_group_too_few_members_zero() {
let group = GroupV0 {
members: BTreeMap::new(),
required_power: 1,
};

let result = group
.validate(None, PlatformVersion::latest())
.expect("should not error");

let Some(ConsensusError::BasicError(BasicError::GroupHasTooFewMembersError(_))) =
result.errors.first()
else {
panic!("expected GroupHasTooFewMembersError");
};
}

#[test]
fn test_group_too_few_members_one() {
let group = GroupV0 {
members: [(Identifier::random(), 1)].into(),
required_power: 1,
};

let result = group
.validate(None, PlatformVersion::latest())
.expect("should not error");

let Some(ConsensusError::BasicError(BasicError::GroupHasTooFewMembersError(_))) =
result.errors.first()
else {
panic!("expected GroupHasTooFewMembersError");
};
}

#[test]
fn test_group_member_has_power_of_zero() {
let group = GroupV0 {
members: [(Identifier::random(), 0), (Identifier::random(), 1)].into(),
required_power: 1,
};

let result = group
.validate(None, PlatformVersion::latest())
.expect("should not error");

let Some(ConsensusError::BasicError(BasicError::GroupMemberHasPowerOfZeroError(_))) =
result.errors.first()
else {
panic!("expected GroupMemberHasPowerOfZeroError");
};
}

#[test]
fn test_group_member_power_over_limit() {
let group = GroupV0 {
members: [(Identifier::random(), 65_536), (Identifier::random(), 1)].into(),
required_power: 65_536,
};

let result = group
.validate(None, PlatformVersion::latest())
.expect("should not error");

let Some(ConsensusError::BasicError(BasicError::GroupMemberHasPowerOverLimitError(_))) =
result.errors.first()
else {
panic!("expected GroupMemberHasPowerOverLimitError");
};
}

#[test]
fn test_group_member_power_exceeds_required() {
let group = GroupV0 {
members: [(Identifier::random(), 6), (Identifier::random(), 5)].into(),
required_power: 5,
};

let result = group
.validate(None, PlatformVersion::latest())
.expect("should not error");

let Some(ConsensusError::BasicError(BasicError::GroupMemberHasPowerOverLimitError(_))) =
result.errors.first()
else {
panic!("expected GroupMemberHasPowerOverLimitError");
};
}

#[test]
fn test_group_total_power_less_than_required() {
let group = GroupV0 {
members: [(Identifier::random(), 2), (Identifier::random(), 2)].into(),
required_power: 5,
};

let result = group
.validate(None, PlatformVersion::latest())
.expect("should not error");

let Some(ConsensusError::BasicError(BasicError::GroupTotalPowerLessThanRequiredError(
_,
))) = result.errors.first()
else {
panic!("expected GroupTotalPowerLessThanRequiredError");
};
}

#[test]
fn test_group_non_unilateral_member_power_less_than_required() {
let group = GroupV0 {
members: [(Identifier::random(), 10), (Identifier::random(), 5)].into(),
required_power: 10,
};

let result = group
.validate(None, PlatformVersion::latest())
.expect("should not error");

let Some(ConsensusError::BasicError(
BasicError::GroupNonUnilateralMemberPowerHasLessThanRequiredPowerError(_),
)) = result.errors.first()
else {
panic!("expected GroupNonUnilateralMemberPowerHasLessThanRequiredPowerError");
};
}

#[test]
fn test_group_required_power_zero() {
let group = GroupV0 {
members: [(Identifier::random(), 1), (Identifier::random(), 1)].into(),
required_power: 0,
};

let result = group
.validate(None, PlatformVersion::latest())
.expect("should not error");

// Required power of zero is currently intercepted by the per-member `power > required_power`
// check before `GroupRequiredPowerIsInvalidError` is evaluated.
let Some(ConsensusError::BasicError(BasicError::GroupMemberHasPowerOverLimitError(_))) =
result.errors.first()
else {
panic!("expected GroupMemberHasPowerOverLimitError");
};
}
Comment on lines 358 to 376
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n packages/rs-dpp/src/data_contract/group/v0/mod.rs | sed -n '120,170p'

Repository: dashpay/platform

Length of output: 2686


🏁 Script executed:

cat -n packages/rs-dpp/src/data_contract/group/v0/mod.rs | sed -n '100,125p'

Repository: dashpay/platform

Length of output: 1256


The required_power == 0 check at line 162 is unreachable dead code.

When required_power == 0, the per-member validation at lines 125–129 (power > self.required_power) always triggers before line 162 is reached. Since every member must have power > 0 (line 115), and required_power == 0, the condition power > required_power is always true, causing an error to be returned immediately.

The self.required_power == 0 branch should either be removed from line 162 (since it cannot be reached), or the per-member check should be refactored to allow this branch to execute. This is a valid finding, though as a test-only addition it is out of scope for the current PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rs-dpp/src/data_contract/group/v0/mod.rs` around lines 360 - 379,
The check for self.required_power == 0 in GroupV0::validate is unreachable
because the per-member validation (the power > self.required_power check in the
member loop) will always fire first when required_power == 0; either remove the
unreachable branch that returns GroupRequiredPowerIsInvalidError or refactor the
per-member check so it doesn't preempt the required_power == 0 validation (e.g.,
validate required_power early in validate before iterating members); update
references in tests that assert GroupMemberHasPowerOverLimitError vs
GroupRequiredPowerIsInvalidError to match the chosen behavior.


#[test]
fn test_group_required_power_over_limit() {
let group = GroupV0 {
members: [
(Identifier::random(), 65_535),
(Identifier::random(), 65_535),
(Identifier::random(), 65_535),
]
.into(),
required_power: 65_536,
};

let result = group
.validate(None, PlatformVersion::latest())
.expect("should not error");

let Some(ConsensusError::BasicError(BasicError::GroupRequiredPowerIsInvalidError(_))) =
result.errors.first()
else {
panic!("expected GroupRequiredPowerIsInvalidError");
};
}
}
}
55 changes: 54 additions & 1 deletion packages/rs-dpp/src/identity/identity_nonce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,63 @@ mod tests {
use crate::consensus::state::state_error::StateError;
use crate::consensus::ConsensusError;
use crate::identity::identity_nonce::{
validate_identity_nonce_update, MergeIdentityNonceResult,
validate_identity_nonce_update, validate_new_identity_nonce, MergeIdentityNonceResult,
MISSING_IDENTITY_REVISIONS_MAX_BYTES,
};
use platform_value::Identifier;

#[test]
fn validate_new_identity_nonce_valid_zero() {
let result = validate_new_identity_nonce(0, Identifier::default());
assert!(result.errors.is_empty());
}

#[test]
fn validate_new_identity_nonce_valid_max_minus_one() {
let nonce = MISSING_IDENTITY_REVISIONS_MAX_BYTES - 1;
let result = validate_new_identity_nonce(nonce, Identifier::default());
assert!(result.errors.is_empty());
}

#[test]
fn validate_new_identity_nonce_invalid_at_max() {
let nonce = MISSING_IDENTITY_REVISIONS_MAX_BYTES;
let result = validate_new_identity_nonce(nonce, Identifier::default());

let Some(ConsensusError::StateError(StateError::InvalidIdentityNonceError(e))) =
result.errors.first()
else {
panic!("expected state error");
};
assert_eq!(e.error, MergeIdentityNonceResult::NonceTooFarInPast);
}

#[test]
fn validate_new_identity_nonce_invalid_above_max() {
let nonce = MISSING_IDENTITY_REVISIONS_MAX_BYTES + 1;
let result = validate_new_identity_nonce(nonce, Identifier::default());

let Some(ConsensusError::StateError(StateError::InvalidIdentityNonceError(e))) =
result.errors.first()
else {
panic!("expected state error");
};
assert_eq!(e.error, MergeIdentityNonceResult::NonceTooFarInPast);
}

#[test]
fn validate_new_identity_nonce_invalid_large() {
let nonce = 1000;
let result = validate_new_identity_nonce(nonce, Identifier::default());

let Some(ConsensusError::StateError(StateError::InvalidIdentityNonceError(e))) =
result.errors.first()
else {
panic!("expected state error");
};
assert_eq!(e.error, MergeIdentityNonceResult::NonceTooFarInPast);
}

#[test]
fn validate_identity_nonce_not_changed() {
let tip = 50;
Expand Down
Loading