diff --git a/packages/rs-dpp/src/data_contract/document_type/schema/validate_max_depth/v0/mod.rs b/packages/rs-dpp/src/data_contract/document_type/schema/validate_max_depth/v0/mod.rs index 990f5cf15cf..e3ce0d10cc8 100644 --- a/packages/rs-dpp/src/data_contract/document_type/schema/validate_max_depth/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/document_type/schema/validate_max_depth/v0/mod.rs @@ -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::*; @@ -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); + } } diff --git a/packages/rs-dpp/src/data_contract/group/v0/mod.rs b/packages/rs-dpp/src/data_contract/group/v0/mod.rs index 07bad515508..6b05a638041 100644 --- a/packages/rs-dpp/src/data_contract/group/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/group/v0/mod.rs @@ -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() { @@ -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"); + }; + } + + #[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"); + }; + } } } diff --git a/packages/rs-dpp/src/identity/identity_nonce.rs b/packages/rs-dpp/src/identity/identity_nonce.rs index fd98642a972..9cdd2c7af52 100644 --- a/packages/rs-dpp/src/identity/identity_nonce.rs +++ b/packages/rs-dpp/src/identity/identity_nonce.rs @@ -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;