From 4568e4792b5fbae38ddc5611f1cc9bb9ac05749d Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Fri, 20 Feb 2026 16:06:26 -0600 Subject: [PATCH 01/14] feat(sdk): add client-side validate_base_structure for document and token transitions Add structural validation to all document and token SDK transition builders, matching the pattern from PR #3096 (identity/address transitions). Calls validate_base_structure() on BatchTransition after construction but before broadcast, catching invalid transitions early. Applied to: - Document transitions: create, delete, replace, purchase, set_price, transfer - Token builders: burn, claim, config_update, destroy, purchase, emergency_action, freeze, mint, set_price, transfer, unfreeze - Enabled dpp 'validation' feature for dash-sdk crate --- .gitignore | 1 + packages/rs-sdk/Cargo.toml | 1 + .../src/platform/documents/transitions/create.rs | 16 ++++++++++++++++ .../src/platform/documents/transitions/delete.rs | 16 ++++++++++++++++ .../platform/documents/transitions/purchase.rs | 16 ++++++++++++++++ .../platform/documents/transitions/replace.rs | 16 ++++++++++++++++ .../platform/documents/transitions/set_price.rs | 16 ++++++++++++++++ .../platform/documents/transitions/transfer.rs | 16 ++++++++++++++++ .../rs-sdk/src/platform/tokens/builders/burn.rs | 16 ++++++++++++++++ .../rs-sdk/src/platform/tokens/builders/claim.rs | 16 ++++++++++++++++ .../platform/tokens/builders/config_update.rs | 16 ++++++++++++++++ .../src/platform/tokens/builders/destroy.rs | 16 ++++++++++++++++ .../platform/tokens/builders/emergency_action.rs | 16 ++++++++++++++++ .../src/platform/tokens/builders/freeze.rs | 16 ++++++++++++++++ .../rs-sdk/src/platform/tokens/builders/mint.rs | 16 ++++++++++++++++ .../src/platform/tokens/builders/purchase.rs | 16 ++++++++++++++++ .../src/platform/tokens/builders/set_price.rs | 16 ++++++++++++++++ .../src/platform/tokens/builders/transfer.rs | 16 ++++++++++++++++ .../src/platform/tokens/builders/unfreeze.rs | 16 ++++++++++++++++ 19 files changed, 274 insertions(+) diff --git a/.gitignore b/.gitignore index 040d4c2079e..0675601009f 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,4 @@ book/book/ # gRPC coverage report grpc-coverage-report.txt +worktrees/ diff --git a/packages/rs-sdk/Cargo.toml b/packages/rs-sdk/Cargo.toml index ea97932d737..3b8a1d96de5 100644 --- a/packages/rs-sdk/Cargo.toml +++ b/packages/rs-sdk/Cargo.toml @@ -9,6 +9,7 @@ arc-swap = { version = "1.7.1" } chrono = { version = "0.4.38" } dpp = { path = "../rs-dpp", default-features = false, features = [ "dash-sdk-features", + "validation", ] } dapi-grpc = { path = "../dapi-grpc", default-features = false } rs-dapi-client = { path = "../rs-dapi-client", default-features = false } diff --git a/packages/rs-sdk/src/platform/documents/transitions/create.rs b/packages/rs-sdk/src/platform/documents/transitions/create.rs index 450836ae167..65e68c234e4 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/create.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/create.rs @@ -166,6 +166,22 @@ impl DocumentCreateTransitionBuilder { self.state_transition_creation_options, )?; + // Validate the transition structure before returning + let validation_result = match &state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if !validation_result.is_valid() { + let first_error = validation_result.errors.into_iter().next().unwrap(); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + } + Ok(state_transition) } } diff --git a/packages/rs-sdk/src/platform/documents/transitions/delete.rs b/packages/rs-sdk/src/platform/documents/transitions/delete.rs index 2f1a8c005c3..f3158ac22fc 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/delete.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/delete.rs @@ -207,6 +207,22 @@ impl DocumentDeleteTransitionBuilder { self.state_transition_creation_options, )?; + // Validate the transition structure before returning + let validation_result = match &state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if !validation_result.is_valid() { + let first_error = validation_result.errors.into_iter().next().unwrap(); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + } + Ok(state_transition) } } diff --git a/packages/rs-sdk/src/platform/documents/transitions/purchase.rs b/packages/rs-sdk/src/platform/documents/transitions/purchase.rs index b7832cb7782..4bc860585ba 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/purchase.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/purchase.rs @@ -221,6 +221,22 @@ impl DocumentPurchaseTransitionBuilder { self.state_transition_creation_options, )?; + // Validate the transition structure before returning + let validation_result = match &state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if !validation_result.is_valid() { + let first_error = validation_result.errors.into_iter().next().unwrap(); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + } + Ok(state_transition) } } diff --git a/packages/rs-sdk/src/platform/documents/transitions/replace.rs b/packages/rs-sdk/src/platform/documents/transitions/replace.rs index aacfc2f9624..e2e979db293 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/replace.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/replace.rs @@ -160,6 +160,22 @@ impl DocumentReplaceTransitionBuilder { self.state_transition_creation_options, )?; + // Validate the transition structure before returning + let validation_result = match &state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if !validation_result.is_valid() { + let first_error = validation_result.errors.into_iter().next().unwrap(); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + } + Ok(state_transition) } } diff --git a/packages/rs-sdk/src/platform/documents/transitions/set_price.rs b/packages/rs-sdk/src/platform/documents/transitions/set_price.rs index 61316f3b5c5..005b0f47eb2 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/set_price.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/set_price.rs @@ -208,6 +208,22 @@ impl DocumentSetPriceTransitionBuilder { self.state_transition_creation_options, )?; + // Validate the transition structure before returning + let validation_result = match &state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if !validation_result.is_valid() { + let first_error = validation_result.errors.into_iter().next().unwrap(); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + } + Ok(state_transition) } } diff --git a/packages/rs-sdk/src/platform/documents/transitions/transfer.rs b/packages/rs-sdk/src/platform/documents/transitions/transfer.rs index ae1f2afbb04..4f7b9c1d438 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/transfer.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/transfer.rs @@ -207,6 +207,22 @@ impl DocumentTransferTransitionBuilder { self.state_transition_creation_options, )?; + // Validate the transition structure before returning + let validation_result = match &state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if !validation_result.is_valid() { + let first_error = validation_result.errors.into_iter().next().unwrap(); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + } + Ok(state_transition) } } diff --git a/packages/rs-sdk/src/platform/tokens/builders/burn.rs b/packages/rs-sdk/src/platform/tokens/builders/burn.rs index e714a1d4a27..8a6f815c860 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/burn.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/burn.rs @@ -179,6 +179,22 @@ impl TokenBurnTransitionBuilder { self.state_transition_creation_options, )?; + // Validate the transition structure before returning + let validation_result = match &state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if !validation_result.is_valid() { + let first_error = validation_result.errors.into_iter().next().unwrap(); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + } + Ok(state_transition) } } diff --git a/packages/rs-sdk/src/platform/tokens/builders/claim.rs b/packages/rs-sdk/src/platform/tokens/builders/claim.rs index bde33938446..1d148027d4e 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/claim.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/claim.rs @@ -165,6 +165,22 @@ impl TokenClaimTransitionBuilder { self.state_transition_creation_options, )?; + // Validate the transition structure before returning + let validation_result = match &state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if !validation_result.is_valid() { + let first_error = validation_result.errors.into_iter().next().unwrap(); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + } + Ok(state_transition) } } diff --git a/packages/rs-sdk/src/platform/tokens/builders/config_update.rs b/packages/rs-sdk/src/platform/tokens/builders/config_update.rs index 960ad87470c..8b1faf66f7a 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/config_update.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/config_update.rs @@ -186,6 +186,22 @@ impl TokenConfigUpdateTransitionBuilder { self.state_transition_creation_options, )?; + // Validate the transition structure before returning + let validation_result = match &state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if !validation_result.is_valid() { + let first_error = validation_result.errors.into_iter().next().unwrap(); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + } + Ok(state_transition) } } diff --git a/packages/rs-sdk/src/platform/tokens/builders/destroy.rs b/packages/rs-sdk/src/platform/tokens/builders/destroy.rs index b1686785a9b..fea8ac5dded 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/destroy.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/destroy.rs @@ -185,6 +185,22 @@ impl TokenDestroyFrozenFundsTransitionBuilder { self.state_transition_creation_options, )?; + // Validate the transition structure before returning + let validation_result = match &state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if !validation_result.is_valid() { + let first_error = validation_result.errors.into_iter().next().unwrap(); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + } + Ok(state_transition) } } diff --git a/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs b/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs index 00b052b1145..b644e121691 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs @@ -213,6 +213,22 @@ impl TokenEmergencyActionTransitionBuilder { self.state_transition_creation_options, )?; + // Validate the transition structure before returning + let validation_result = match &state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if !validation_result.is_valid() { + let first_error = validation_result.errors.into_iter().next().unwrap(); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + } + Ok(state_transition) } } diff --git a/packages/rs-sdk/src/platform/tokens/builders/freeze.rs b/packages/rs-sdk/src/platform/tokens/builders/freeze.rs index 12c4c9ad229..973350ab2c2 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/freeze.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/freeze.rs @@ -185,6 +185,22 @@ impl TokenFreezeTransitionBuilder { self.state_transition_creation_options, )?; + // Validate the transition structure before returning + let validation_result = match &state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if !validation_result.is_valid() { + let first_error = validation_result.errors.into_iter().next().unwrap(); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + } + Ok(state_transition) } } diff --git a/packages/rs-sdk/src/platform/tokens/builders/mint.rs b/packages/rs-sdk/src/platform/tokens/builders/mint.rs index 45614d06bd3..e5656075503 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/mint.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/mint.rs @@ -206,6 +206,22 @@ impl TokenMintTransitionBuilder { self.state_transition_creation_options, )?; + // Validate the transition structure before returning + let validation_result = match &state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if !validation_result.is_valid() { + let first_error = validation_result.errors.into_iter().next().unwrap(); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + } + Ok(state_transition) } } diff --git a/packages/rs-sdk/src/platform/tokens/builders/purchase.rs b/packages/rs-sdk/src/platform/tokens/builders/purchase.rs index 67771fc1ae9..d7af4c6600d 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/purchase.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/purchase.rs @@ -154,6 +154,22 @@ impl TokenDirectPurchaseTransitionBuilder { self.state_transition_creation_options, )?; + // Validate the transition structure before returning + let validation_result = match &state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if !validation_result.is_valid() { + let first_error = validation_result.errors.into_iter().next().unwrap(); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + } + Ok(state_transition) } } diff --git a/packages/rs-sdk/src/platform/tokens/builders/set_price.rs b/packages/rs-sdk/src/platform/tokens/builders/set_price.rs index 57c298d3b61..e081c486d8b 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/set_price.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/set_price.rs @@ -230,6 +230,22 @@ impl TokenChangeDirectPurchasePriceTransitionBuilder { self.state_transition_creation_options, )?; + // Validate the transition structure before returning + let validation_result = match &state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if !validation_result.is_valid() { + let first_error = validation_result.errors.into_iter().next().unwrap(); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + } + Ok(state_transition) } } diff --git a/packages/rs-sdk/src/platform/tokens/builders/transfer.rs b/packages/rs-sdk/src/platform/tokens/builders/transfer.rs index a8cf0514e5c..0533877b96f 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/transfer.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/transfer.rs @@ -211,6 +211,22 @@ impl TokenTransferTransitionBuilder { self.state_transition_creation_options, )?; + // Validate the transition structure before returning + let validation_result = match &state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if !validation_result.is_valid() { + let first_error = validation_result.errors.into_iter().next().unwrap(); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + } + Ok(state_transition) } } diff --git a/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs b/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs index 8cf605f36a9..1ae315ab242 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs @@ -185,6 +185,22 @@ impl TokenUnfreezeTransitionBuilder { self.state_transition_creation_options, )?; + // Validate the transition structure before returning + let validation_result = match &state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if !validation_result.is_valid() { + let first_error = validation_result.errors.into_iter().next().unwrap(); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + } + Ok(state_transition) } } From e52eb1f4ec019c43c0c9d09716800a3e0668423f Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Fri, 20 Feb 2026 17:31:45 -0600 Subject: [PATCH 02/14] fix: replace unwrap() with safe if-let in validation error handling CodeRabbit correctly flagged that validation_result.errors.into_iter() .next().unwrap() could panic if is_valid() returns false but the errors vec is somehow empty. Use if-let pattern instead for safe handling. --- packages/rs-sdk/src/platform/documents/transitions/create.rs | 3 +-- packages/rs-sdk/src/platform/documents/transitions/delete.rs | 3 +-- packages/rs-sdk/src/platform/documents/transitions/purchase.rs | 3 +-- packages/rs-sdk/src/platform/documents/transitions/replace.rs | 3 +-- .../rs-sdk/src/platform/documents/transitions/set_price.rs | 3 +-- packages/rs-sdk/src/platform/documents/transitions/transfer.rs | 3 +-- packages/rs-sdk/src/platform/tokens/builders/burn.rs | 3 +-- packages/rs-sdk/src/platform/tokens/builders/claim.rs | 3 +-- packages/rs-sdk/src/platform/tokens/builders/config_update.rs | 3 +-- packages/rs-sdk/src/platform/tokens/builders/destroy.rs | 3 +-- .../rs-sdk/src/platform/tokens/builders/emergency_action.rs | 3 +-- packages/rs-sdk/src/platform/tokens/builders/freeze.rs | 3 +-- packages/rs-sdk/src/platform/tokens/builders/mint.rs | 3 +-- packages/rs-sdk/src/platform/tokens/builders/purchase.rs | 3 +-- packages/rs-sdk/src/platform/tokens/builders/set_price.rs | 3 +-- packages/rs-sdk/src/platform/tokens/builders/transfer.rs | 3 +-- packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs | 3 +-- 17 files changed, 17 insertions(+), 34 deletions(-) diff --git a/packages/rs-sdk/src/platform/documents/transitions/create.rs b/packages/rs-sdk/src/platform/documents/transitions/create.rs index 65e68c234e4..97088adfc3a 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/create.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/create.rs @@ -177,8 +177,7 @@ impl DocumentCreateTransitionBuilder { ))); } }; - if !validation_result.is_valid() { - let first_error = validation_result.errors.into_iter().next().unwrap(); + if let Some(first_error) = validation_result.errors.into_iter().next() { return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); } diff --git a/packages/rs-sdk/src/platform/documents/transitions/delete.rs b/packages/rs-sdk/src/platform/documents/transitions/delete.rs index f3158ac22fc..dc254ce76a5 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/delete.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/delete.rs @@ -218,8 +218,7 @@ impl DocumentDeleteTransitionBuilder { ))); } }; - if !validation_result.is_valid() { - let first_error = validation_result.errors.into_iter().next().unwrap(); + if let Some(first_error) = validation_result.errors.into_iter().next() { return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); } diff --git a/packages/rs-sdk/src/platform/documents/transitions/purchase.rs b/packages/rs-sdk/src/platform/documents/transitions/purchase.rs index 4bc860585ba..8fd93529d14 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/purchase.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/purchase.rs @@ -232,8 +232,7 @@ impl DocumentPurchaseTransitionBuilder { ))); } }; - if !validation_result.is_valid() { - let first_error = validation_result.errors.into_iter().next().unwrap(); + if let Some(first_error) = validation_result.errors.into_iter().next() { return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); } diff --git a/packages/rs-sdk/src/platform/documents/transitions/replace.rs b/packages/rs-sdk/src/platform/documents/transitions/replace.rs index e2e979db293..e85fb551c6a 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/replace.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/replace.rs @@ -171,8 +171,7 @@ impl DocumentReplaceTransitionBuilder { ))); } }; - if !validation_result.is_valid() { - let first_error = validation_result.errors.into_iter().next().unwrap(); + if let Some(first_error) = validation_result.errors.into_iter().next() { return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); } diff --git a/packages/rs-sdk/src/platform/documents/transitions/set_price.rs b/packages/rs-sdk/src/platform/documents/transitions/set_price.rs index 005b0f47eb2..435e9ebee0b 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/set_price.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/set_price.rs @@ -219,8 +219,7 @@ impl DocumentSetPriceTransitionBuilder { ))); } }; - if !validation_result.is_valid() { - let first_error = validation_result.errors.into_iter().next().unwrap(); + if let Some(first_error) = validation_result.errors.into_iter().next() { return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); } diff --git a/packages/rs-sdk/src/platform/documents/transitions/transfer.rs b/packages/rs-sdk/src/platform/documents/transitions/transfer.rs index 4f7b9c1d438..dfc60854b36 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/transfer.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/transfer.rs @@ -218,8 +218,7 @@ impl DocumentTransferTransitionBuilder { ))); } }; - if !validation_result.is_valid() { - let first_error = validation_result.errors.into_iter().next().unwrap(); + if let Some(first_error) = validation_result.errors.into_iter().next() { return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); } diff --git a/packages/rs-sdk/src/platform/tokens/builders/burn.rs b/packages/rs-sdk/src/platform/tokens/builders/burn.rs index 8a6f815c860..917fcdcd204 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/burn.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/burn.rs @@ -190,8 +190,7 @@ impl TokenBurnTransitionBuilder { ))); } }; - if !validation_result.is_valid() { - let first_error = validation_result.errors.into_iter().next().unwrap(); + if let Some(first_error) = validation_result.errors.into_iter().next() { return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); } diff --git a/packages/rs-sdk/src/platform/tokens/builders/claim.rs b/packages/rs-sdk/src/platform/tokens/builders/claim.rs index 1d148027d4e..378caa2baf1 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/claim.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/claim.rs @@ -176,8 +176,7 @@ impl TokenClaimTransitionBuilder { ))); } }; - if !validation_result.is_valid() { - let first_error = validation_result.errors.into_iter().next().unwrap(); + if let Some(first_error) = validation_result.errors.into_iter().next() { return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); } diff --git a/packages/rs-sdk/src/platform/tokens/builders/config_update.rs b/packages/rs-sdk/src/platform/tokens/builders/config_update.rs index 8b1faf66f7a..1d230418616 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/config_update.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/config_update.rs @@ -197,8 +197,7 @@ impl TokenConfigUpdateTransitionBuilder { ))); } }; - if !validation_result.is_valid() { - let first_error = validation_result.errors.into_iter().next().unwrap(); + if let Some(first_error) = validation_result.errors.into_iter().next() { return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); } diff --git a/packages/rs-sdk/src/platform/tokens/builders/destroy.rs b/packages/rs-sdk/src/platform/tokens/builders/destroy.rs index fea8ac5dded..55465557e0a 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/destroy.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/destroy.rs @@ -196,8 +196,7 @@ impl TokenDestroyFrozenFundsTransitionBuilder { ))); } }; - if !validation_result.is_valid() { - let first_error = validation_result.errors.into_iter().next().unwrap(); + if let Some(first_error) = validation_result.errors.into_iter().next() { return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); } diff --git a/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs b/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs index b644e121691..aebb2880c44 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs @@ -224,8 +224,7 @@ impl TokenEmergencyActionTransitionBuilder { ))); } }; - if !validation_result.is_valid() { - let first_error = validation_result.errors.into_iter().next().unwrap(); + if let Some(first_error) = validation_result.errors.into_iter().next() { return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); } diff --git a/packages/rs-sdk/src/platform/tokens/builders/freeze.rs b/packages/rs-sdk/src/platform/tokens/builders/freeze.rs index 973350ab2c2..61d88cb312c 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/freeze.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/freeze.rs @@ -196,8 +196,7 @@ impl TokenFreezeTransitionBuilder { ))); } }; - if !validation_result.is_valid() { - let first_error = validation_result.errors.into_iter().next().unwrap(); + if let Some(first_error) = validation_result.errors.into_iter().next() { return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); } diff --git a/packages/rs-sdk/src/platform/tokens/builders/mint.rs b/packages/rs-sdk/src/platform/tokens/builders/mint.rs index e5656075503..b0e602ff74c 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/mint.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/mint.rs @@ -217,8 +217,7 @@ impl TokenMintTransitionBuilder { ))); } }; - if !validation_result.is_valid() { - let first_error = validation_result.errors.into_iter().next().unwrap(); + if let Some(first_error) = validation_result.errors.into_iter().next() { return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); } diff --git a/packages/rs-sdk/src/platform/tokens/builders/purchase.rs b/packages/rs-sdk/src/platform/tokens/builders/purchase.rs index d7af4c6600d..62db4bdd7b1 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/purchase.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/purchase.rs @@ -165,8 +165,7 @@ impl TokenDirectPurchaseTransitionBuilder { ))); } }; - if !validation_result.is_valid() { - let first_error = validation_result.errors.into_iter().next().unwrap(); + if let Some(first_error) = validation_result.errors.into_iter().next() { return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); } diff --git a/packages/rs-sdk/src/platform/tokens/builders/set_price.rs b/packages/rs-sdk/src/platform/tokens/builders/set_price.rs index e081c486d8b..bd7007f57b4 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/set_price.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/set_price.rs @@ -241,8 +241,7 @@ impl TokenChangeDirectPurchasePriceTransitionBuilder { ))); } }; - if !validation_result.is_valid() { - let first_error = validation_result.errors.into_iter().next().unwrap(); + if let Some(first_error) = validation_result.errors.into_iter().next() { return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); } diff --git a/packages/rs-sdk/src/platform/tokens/builders/transfer.rs b/packages/rs-sdk/src/platform/tokens/builders/transfer.rs index 0533877b96f..336938e3c48 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/transfer.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/transfer.rs @@ -222,8 +222,7 @@ impl TokenTransferTransitionBuilder { ))); } }; - if !validation_result.is_valid() { - let first_error = validation_result.errors.into_iter().next().unwrap(); + if let Some(first_error) = validation_result.errors.into_iter().next() { return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); } diff --git a/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs b/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs index 1ae315ab242..471c41a1a6c 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs @@ -196,8 +196,7 @@ impl TokenUnfreezeTransitionBuilder { ))); } }; - if !validation_result.is_valid() { - let first_error = validation_result.errors.into_iter().next().unwrap(); + if let Some(first_error) = validation_result.errors.into_iter().next() { return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); } From 1ac31c32b846c11bf33c09de1c05d15392f349bf Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Fri, 20 Feb 2026 19:27:59 -0600 Subject: [PATCH 03/14] style: apply cargo fmt to rs-sdk and rs-dapi --- .../src/platform/documents/transitions/create.rs | 12 ++++++++---- .../src/platform/documents/transitions/delete.rs | 12 ++++++++---- .../src/platform/documents/transitions/purchase.rs | 12 ++++++++---- .../src/platform/documents/transitions/replace.rs | 12 ++++++++---- .../src/platform/documents/transitions/set_price.rs | 12 ++++++++---- .../src/platform/documents/transitions/transfer.rs | 12 ++++++++---- packages/rs-sdk/src/platform/tokens/builders/burn.rs | 12 ++++++++---- .../rs-sdk/src/platform/tokens/builders/claim.rs | 12 ++++++++---- .../src/platform/tokens/builders/config_update.rs | 12 ++++++++---- .../rs-sdk/src/platform/tokens/builders/destroy.rs | 12 ++++++++---- .../src/platform/tokens/builders/emergency_action.rs | 12 ++++++++---- .../rs-sdk/src/platform/tokens/builders/freeze.rs | 12 ++++++++---- packages/rs-sdk/src/platform/tokens/builders/mint.rs | 12 ++++++++---- .../rs-sdk/src/platform/tokens/builders/purchase.rs | 12 ++++++++---- .../rs-sdk/src/platform/tokens/builders/set_price.rs | 12 ++++++++---- .../rs-sdk/src/platform/tokens/builders/transfer.rs | 12 ++++++++---- .../rs-sdk/src/platform/tokens/builders/unfreeze.rs | 12 ++++++++---- 17 files changed, 136 insertions(+), 68 deletions(-) diff --git a/packages/rs-sdk/src/platform/documents/transitions/create.rs b/packages/rs-sdk/src/platform/documents/transitions/create.rs index 97088adfc3a..fca6bda77f2 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/create.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/create.rs @@ -172,13 +172,17 @@ impl DocumentCreateTransitionBuilder { batch_transition.validate_base_structure(platform_version)? } _ => { - return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ))); + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )); } }; if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); } Ok(state_transition) diff --git a/packages/rs-sdk/src/platform/documents/transitions/delete.rs b/packages/rs-sdk/src/platform/documents/transitions/delete.rs index dc254ce76a5..9a1862b98af 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/delete.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/delete.rs @@ -213,13 +213,17 @@ impl DocumentDeleteTransitionBuilder { batch_transition.validate_base_structure(platform_version)? } _ => { - return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ))); + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )); } }; if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); } Ok(state_transition) diff --git a/packages/rs-sdk/src/platform/documents/transitions/purchase.rs b/packages/rs-sdk/src/platform/documents/transitions/purchase.rs index 8fd93529d14..6b33bc2bf3b 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/purchase.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/purchase.rs @@ -227,13 +227,17 @@ impl DocumentPurchaseTransitionBuilder { batch_transition.validate_base_structure(platform_version)? } _ => { - return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ))); + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )); } }; if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); } Ok(state_transition) diff --git a/packages/rs-sdk/src/platform/documents/transitions/replace.rs b/packages/rs-sdk/src/platform/documents/transitions/replace.rs index e85fb551c6a..27dede0bb3f 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/replace.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/replace.rs @@ -166,13 +166,17 @@ impl DocumentReplaceTransitionBuilder { batch_transition.validate_base_structure(platform_version)? } _ => { - return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ))); + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )); } }; if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); } Ok(state_transition) diff --git a/packages/rs-sdk/src/platform/documents/transitions/set_price.rs b/packages/rs-sdk/src/platform/documents/transitions/set_price.rs index 435e9ebee0b..537c09283db 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/set_price.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/set_price.rs @@ -214,13 +214,17 @@ impl DocumentSetPriceTransitionBuilder { batch_transition.validate_base_structure(platform_version)? } _ => { - return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ))); + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )); } }; if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); } Ok(state_transition) diff --git a/packages/rs-sdk/src/platform/documents/transitions/transfer.rs b/packages/rs-sdk/src/platform/documents/transitions/transfer.rs index dfc60854b36..53eef22c776 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/transfer.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/transfer.rs @@ -213,13 +213,17 @@ impl DocumentTransferTransitionBuilder { batch_transition.validate_base_structure(platform_version)? } _ => { - return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ))); + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )); } }; if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); } Ok(state_transition) diff --git a/packages/rs-sdk/src/platform/tokens/builders/burn.rs b/packages/rs-sdk/src/platform/tokens/builders/burn.rs index 917fcdcd204..83f7f3719b7 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/burn.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/burn.rs @@ -185,13 +185,17 @@ impl TokenBurnTransitionBuilder { batch_transition.validate_base_structure(platform_version)? } _ => { - return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ))); + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )); } }; if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); } Ok(state_transition) diff --git a/packages/rs-sdk/src/platform/tokens/builders/claim.rs b/packages/rs-sdk/src/platform/tokens/builders/claim.rs index 378caa2baf1..d68923841f4 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/claim.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/claim.rs @@ -171,13 +171,17 @@ impl TokenClaimTransitionBuilder { batch_transition.validate_base_structure(platform_version)? } _ => { - return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ))); + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )); } }; if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); } Ok(state_transition) diff --git a/packages/rs-sdk/src/platform/tokens/builders/config_update.rs b/packages/rs-sdk/src/platform/tokens/builders/config_update.rs index 1d230418616..1d20a26d2b3 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/config_update.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/config_update.rs @@ -192,13 +192,17 @@ impl TokenConfigUpdateTransitionBuilder { batch_transition.validate_base_structure(platform_version)? } _ => { - return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ))); + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )); } }; if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); } Ok(state_transition) diff --git a/packages/rs-sdk/src/platform/tokens/builders/destroy.rs b/packages/rs-sdk/src/platform/tokens/builders/destroy.rs index 55465557e0a..a89f2416b8c 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/destroy.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/destroy.rs @@ -191,13 +191,17 @@ impl TokenDestroyFrozenFundsTransitionBuilder { batch_transition.validate_base_structure(platform_version)? } _ => { - return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ))); + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )); } }; if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); } Ok(state_transition) diff --git a/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs b/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs index aebb2880c44..f292d08d764 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs @@ -219,13 +219,17 @@ impl TokenEmergencyActionTransitionBuilder { batch_transition.validate_base_structure(platform_version)? } _ => { - return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ))); + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )); } }; if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); } Ok(state_transition) diff --git a/packages/rs-sdk/src/platform/tokens/builders/freeze.rs b/packages/rs-sdk/src/platform/tokens/builders/freeze.rs index 61d88cb312c..bf6a0868096 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/freeze.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/freeze.rs @@ -191,13 +191,17 @@ impl TokenFreezeTransitionBuilder { batch_transition.validate_base_structure(platform_version)? } _ => { - return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ))); + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )); } }; if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); } Ok(state_transition) diff --git a/packages/rs-sdk/src/platform/tokens/builders/mint.rs b/packages/rs-sdk/src/platform/tokens/builders/mint.rs index b0e602ff74c..1595592033b 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/mint.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/mint.rs @@ -212,13 +212,17 @@ impl TokenMintTransitionBuilder { batch_transition.validate_base_structure(platform_version)? } _ => { - return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ))); + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )); } }; if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); } Ok(state_transition) diff --git a/packages/rs-sdk/src/platform/tokens/builders/purchase.rs b/packages/rs-sdk/src/platform/tokens/builders/purchase.rs index 62db4bdd7b1..069f19f6dcf 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/purchase.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/purchase.rs @@ -160,13 +160,17 @@ impl TokenDirectPurchaseTransitionBuilder { batch_transition.validate_base_structure(platform_version)? } _ => { - return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ))); + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )); } }; if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); } Ok(state_transition) diff --git a/packages/rs-sdk/src/platform/tokens/builders/set_price.rs b/packages/rs-sdk/src/platform/tokens/builders/set_price.rs index bd7007f57b4..1933532689f 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/set_price.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/set_price.rs @@ -236,13 +236,17 @@ impl TokenChangeDirectPurchasePriceTransitionBuilder { batch_transition.validate_base_structure(platform_version)? } _ => { - return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ))); + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )); } }; if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); } Ok(state_transition) diff --git a/packages/rs-sdk/src/platform/tokens/builders/transfer.rs b/packages/rs-sdk/src/platform/tokens/builders/transfer.rs index 336938e3c48..0822f8d924d 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/transfer.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/transfer.rs @@ -217,13 +217,17 @@ impl TokenTransferTransitionBuilder { batch_transition.validate_base_structure(platform_version)? } _ => { - return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ))); + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )); } }; if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); } Ok(state_transition) diff --git a/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs b/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs index 471c41a1a6c..109a2e19e44 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs @@ -191,13 +191,17 @@ impl TokenUnfreezeTransitionBuilder { batch_transition.validate_base_structure(platform_version)? } _ => { - return Err(Error::Protocol(dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ))); + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )); } }; if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError(Box::new(first_error)))); + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); } Ok(state_transition) From e47606bb833bb2129baef9b2a7d2e47de60e513f Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Fri, 20 Feb 2026 19:28:04 -0600 Subject: [PATCH 04/14] fix(wasm-sdk): add maxLength to indexed rarity field in test fixture The validation feature added to rs-sdk's dpp dependency now enforces that indexed string properties must have maxLength <= 63. The rarity field in the crypto card game test fixture was missing this constraint, causing all DataContract tests to fail. --- .../tests/unit/fixtures/data-contract-v0-crypto-card-game.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/wasm-sdk/tests/unit/fixtures/data-contract-v0-crypto-card-game.ts b/packages/wasm-sdk/tests/unit/fixtures/data-contract-v0-crypto-card-game.ts index bbbf2915719..411c0c1fdf0 100644 --- a/packages/wasm-sdk/tests/unit/fixtures/data-contract-v0-crypto-card-game.ts +++ b/packages/wasm-sdk/tests/unit/fixtures/data-contract-v0-crypto-card-game.ts @@ -52,6 +52,7 @@ const contract = { rarity: { type: 'string', description: 'Rarity level of the card', + maxLength: 9, enum: [ 'common', 'uncommon', From a39718c666938008f53bb2daf38738033a0695ab Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 21 Feb 2026 13:56:26 -0600 Subject: [PATCH 05/14] test(sdk): add validate_base_structure error path tests for document and token builders Tests cover: - InvalidTokenAmountError via TokenMintTransitionBuilder with amount=0 - InvalidActionIdError via TokenMintTransitionBuilder with mismatched group action ID - Document nonce masking (validates nonce out-of-bounds is unreachable via builder API) Documents that several error paths (empty transitions, max exceeded, duplicate transitions, invalid token ID) are unreachable through the single-transition builder API by design. --- .../src/platform/documents/transitions/mod.rs | 2 + .../platform/documents/transitions/tests.rs | 374 ++++++++++++++++++ .../src/platform/tokens/builders/mod.rs | 2 + .../src/platform/tokens/builders/tests.rs | 190 +++++++++ 4 files changed, 568 insertions(+) create mode 100644 packages/rs-sdk/src/platform/documents/transitions/tests.rs create mode 100644 packages/rs-sdk/src/platform/tokens/builders/tests.rs diff --git a/packages/rs-sdk/src/platform/documents/transitions/mod.rs b/packages/rs-sdk/src/platform/documents/transitions/mod.rs index 200a2164267..a93fda02beb 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/mod.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/mod.rs @@ -3,6 +3,8 @@ pub mod delete; pub mod purchase; pub mod replace; pub mod set_price; +#[cfg(test)] +mod tests; pub mod transfer; pub use create::{DocumentCreateResult, DocumentCreateTransitionBuilder}; diff --git a/packages/rs-sdk/src/platform/documents/transitions/tests.rs b/packages/rs-sdk/src/platform/documents/transitions/tests.rs new file mode 100644 index 00000000000..87057371e21 --- /dev/null +++ b/packages/rs-sdk/src/platform/documents/transitions/tests.rs @@ -0,0 +1,374 @@ +use super::delete::DocumentDeleteTransitionBuilder; +use crate::{Error, Sdk, SdkBuilder}; +use dpp::address_funds::AddressWitness; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::data_contract::config::DataContractConfig; +use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; +use dpp::data_contract::document_type::DocumentType; +use dpp::data_contract::DataContractFactory; +use dpp::document::{Document, DocumentV0}; +use dpp::identity::identity_public_key::v0::IdentityPublicKeyV0; +use dpp::identity::signer::Signer; +use dpp::identity::{IdentityPublicKey, KeyType, Purpose, SecurityLevel}; +use dpp::platform_value::{platform_value, BinaryData, Value}; +use dpp::prelude::Identifier; +use dpp::state_transition::batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0; +use dpp::state_transition::batch_transition::BatchTransition; +use dpp::state_transition::StateTransition; +use dpp::ProtocolError; +use drive_proof_verifier::types::IdentityContractNonceFetcher; +use std::collections::BTreeMap; +use std::sync::Arc; + +#[derive(Debug)] +struct TestSigner; + +impl Signer for TestSigner { + fn sign(&self, _key: &IdentityPublicKey, _data: &[u8]) -> Result { + Ok(BinaryData::from(vec![1; 65])) + } + + fn sign_create_witness( + &self, + _key: &IdentityPublicKey, + _data: &[u8], + ) -> Result { + Err(ProtocolError::CorruptedCodeExecution( + "sign_create_witness is not used in these tests".to_string(), + )) + } + + fn can_sign_with(&self, _key: &IdentityPublicKey) -> bool { + true + } +} + +fn test_identity_public_key() -> IdentityPublicKey { + IdentityPublicKey::V0(IdentityPublicKeyV0 { + id: 1, + purpose: Purpose::AUTHENTICATION, + security_level: SecurityLevel::CRITICAL, + contract_bounds: None, + key_type: KeyType::ECDSA_SECP256K1, + read_only: false, + data: BinaryData::from(vec![2; 33]), + disabled_at: None, + }) +} + +fn test_data_contract(document_type_name: &str) -> Arc { + let platform_version = dpp::version::PlatformVersion::latest(); + let config = + DataContractConfig::default_for_version(platform_version).expect("create contract config"); + + let schema = platform_value!({ + "type": "object", + "properties": { + "a": { + "type": "string", + "maxLength": 10, + "position": 0 + } + }, + "additionalProperties": false, + }); + + let document_type = DocumentType::try_from_schema( + Identifier::random(), + 1, + config.version(), + document_type_name, + schema, + None, + &BTreeMap::new(), + &config, + true, + &mut vec![], + platform_version, + ) + .expect("create test document type"); + + let mut document_types: BTreeMap = BTreeMap::new(); + document_types.insert( + document_type.name().to_string(), + document_type.schema().clone(), + ); + + let contract = DataContractFactory::new(platform_version.protocol_version) + .expect("create data contract factory") + .create( + Identifier::random(), + 0, + platform_value!(document_types), + None, + None, + ) + .expect("create test data contract") + .data_contract_owned(); + + Arc::new(contract) +} + +const TEST_DOCUMENT_TYPE_NAME: &str = "testDoc"; +const INVALID_NONCE: u64 = 1_u64 << 50; + +fn test_document(owner_id: Identifier) -> Document { + Document::V0(DocumentV0 { + id: Identifier::random(), + owner_id, + properties: Default::default(), + revision: Some(1), + created_at: None, + updated_at: None, + transferred_at: None, + created_at_block_height: None, + updated_at_block_height: None, + transferred_at_block_height: None, + created_at_core_block_height: None, + updated_at_core_block_height: None, + transferred_at_core_block_height: None, + creator_id: None, + }) +} + +fn validate_transition_like_builder(state_transition: &StateTransition) -> Result<(), Error> { + let platform_version = dpp::version::PlatformVersion::latest(); + let validation_result = match state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )) + } + }; + if let Some(first_error) = validation_result.errors.into_iter().next() { + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); + } + Ok(()) +} + +pub(super) fn assert_document_create_validate_base_structure_error() { + let platform_version = dpp::version::PlatformVersion::latest(); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let owner_id = Identifier::random(); + let document = test_document(owner_id); + let document_type = data_contract + .document_type_for_name(TEST_DOCUMENT_TYPE_NAME) + .expect("expected test document type"); + let transition = BatchTransition::new_document_creation_transition_from_document( + document, + document_type, + [7; 32], + &test_identity_public_key(), + INVALID_NONCE, + 0, + None, + &TestSigner, + platform_version, + None, + ) + .expect("transition should build"); + + let result = validate_transition_like_builder(&transition); + assert!(result.is_err(), "expected validation error, got {result:?}"); +} + +pub(super) fn assert_document_delete_validate_base_structure_error() { + let platform_version = dpp::version::PlatformVersion::latest(); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let owner_id = Identifier::random(); + let document = test_document(owner_id); + let document_type = data_contract + .document_type_for_name(TEST_DOCUMENT_TYPE_NAME) + .expect("expected test document type"); + let transition = BatchTransition::new_document_deletion_transition_from_document( + document, + document_type, + &test_identity_public_key(), + INVALID_NONCE, + 0, + None, + &TestSigner, + platform_version, + None, + ) + .expect("transition should build"); + + let result = validate_transition_like_builder(&transition); + assert!(result.is_err(), "expected validation error, got {result:?}"); +} + +pub(super) fn assert_document_purchase_validate_base_structure_error() { + let platform_version = dpp::version::PlatformVersion::latest(); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let owner_id = Identifier::random(); + let document = test_document(owner_id); + let purchaser_id = Identifier::random(); + let document_type = data_contract + .document_type_for_name(TEST_DOCUMENT_TYPE_NAME) + .expect("expected test document type"); + let transition = BatchTransition::new_document_purchase_transition_from_document( + document, + document_type, + purchaser_id, + 100, + &test_identity_public_key(), + INVALID_NONCE, + 0, + None, + &TestSigner, + platform_version, + None, + ) + .expect("transition should build"); + + let result = validate_transition_like_builder(&transition); + assert!(result.is_err(), "expected validation error, got {result:?}"); +} + +pub(super) fn assert_document_replace_validate_base_structure_error() { + let platform_version = dpp::version::PlatformVersion::latest(); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let owner_id = Identifier::random(); + let document = test_document(owner_id); + let document_type = data_contract + .document_type_for_name(TEST_DOCUMENT_TYPE_NAME) + .expect("expected test document type"); + let transition = BatchTransition::new_document_replacement_transition_from_document( + document, + document_type, + &test_identity_public_key(), + INVALID_NONCE, + 0, + None, + &TestSigner, + platform_version, + None, + ) + .expect("transition should build"); + + let result = validate_transition_like_builder(&transition); + assert!(result.is_err(), "expected validation error, got {result:?}"); +} + +pub(super) fn assert_document_set_price_validate_base_structure_error() { + let platform_version = dpp::version::PlatformVersion::latest(); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let owner_id = Identifier::random(); + let document = test_document(owner_id); + let document_type = data_contract + .document_type_for_name(TEST_DOCUMENT_TYPE_NAME) + .expect("expected test document type"); + let transition = BatchTransition::new_document_update_price_transition_from_document( + document, + document_type, + 200, + &test_identity_public_key(), + INVALID_NONCE, + 0, + None, + &TestSigner, + platform_version, + None, + ) + .expect("transition should build"); + + let result = validate_transition_like_builder(&transition); + assert!(result.is_err(), "expected validation error, got {result:?}"); +} + +pub(super) fn assert_document_transfer_validate_base_structure_error() { + let platform_version = dpp::version::PlatformVersion::latest(); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let owner_id = Identifier::random(); + let document = test_document(owner_id); + let recipient_id = Identifier::random(); + let document_type = data_contract + .document_type_for_name(TEST_DOCUMENT_TYPE_NAME) + .expect("expected test document type"); + let transition = BatchTransition::new_document_transfer_transition_from_document( + document, + document_type, + recipient_id, + &test_identity_public_key(), + INVALID_NONCE, + 0, + None, + &TestSigner, + platform_version, + None, + ) + .expect("transition should build"); + + let result = validate_transition_like_builder(&transition); + assert!(result.is_err(), "expected validation error, got {result:?}"); +} + +async fn new_mock_sdk_with_contract_nonce( + identity_id: Identifier, + contract_id: Identifier, + fetched_nonce: u64, +) -> Sdk { + let mut sdk = SdkBuilder::new_mock().build().expect("build mock sdk"); + + sdk.mock() + .expect_fetch::( + (identity_id, contract_id), + Some(IdentityContractNonceFetcher(fetched_nonce)), + ) + .await + .expect("set nonce fetch expectation"); + + sdk +} + +#[tokio::test] +async fn document_delete_sign_masks_nonce_and_does_not_hit_nonce_out_of_bounds_validation() { + // Document builders always create exactly one transition and obtain nonce through + // `Sdk::get_identity_contract_nonce`, which masks out-of-bounds bits. + // This makes `validate_base_structure` nonce-out-of-bounds errors unreachable here. + let document_type_name = "testDoc"; + let data_contract = test_data_contract(document_type_name); + let owner_id = Identifier::random(); + + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 1_u64 << 50).await; + + let builder = DocumentDeleteTransitionBuilder::new( + Arc::clone(&data_contract), + document_type_name.to_string(), + Identifier::random(), + owner_id, + ); + + let result = builder + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "unexpected error while signing document delete transition: {:?}", + result.err() + ); + + assert!( + !matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(consensus_error))) + if matches!(*consensus_error, dpp::consensus::ConsensusError::BasicError( + dpp::consensus::basic::BasicError::NonceOutOfBoundsError(_) + )) + ), + "nonce out-of-bounds should be unreachable via document builders" + ); +} diff --git a/packages/rs-sdk/src/platform/tokens/builders/mod.rs b/packages/rs-sdk/src/platform/tokens/builders/mod.rs index eec8c140062..bc675dcdfc9 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/mod.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/mod.rs @@ -9,5 +9,7 @@ pub mod freeze; pub mod mint; pub mod purchase; pub mod set_price; +#[cfg(test)] +mod tests; pub mod transfer; pub mod unfreeze; diff --git a/packages/rs-sdk/src/platform/tokens/builders/tests.rs b/packages/rs-sdk/src/platform/tokens/builders/tests.rs new file mode 100644 index 00000000000..4eccf101d8b --- /dev/null +++ b/packages/rs-sdk/src/platform/tokens/builders/tests.rs @@ -0,0 +1,190 @@ +use super::mint::TokenMintTransitionBuilder; +use crate::{Error, Sdk, SdkBuilder}; +use dpp::address_funds::AddressWitness; +use dpp::consensus::basic::BasicError; +use dpp::consensus::ConsensusError; +use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::data_contract::config::DataContractConfig; +use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; +use dpp::data_contract::document_type::DocumentType; +use dpp::data_contract::DataContractFactory; +use dpp::group::{GroupStateTransitionInfo, GroupStateTransitionInfoStatus}; +use dpp::identity::identity_public_key::v0::IdentityPublicKeyV0; +use dpp::identity::signer::Signer; +use dpp::identity::{IdentityPublicKey, KeyType, Purpose, SecurityLevel}; +use dpp::platform_value::{platform_value, BinaryData, Value}; +use dpp::prelude::Identifier; +use dpp::ProtocolError; +use drive_proof_verifier::types::IdentityContractNonceFetcher; +use std::collections::BTreeMap; +use std::sync::Arc; + +#[derive(Debug)] +struct TestSigner; + +impl Signer for TestSigner { + fn sign(&self, _key: &IdentityPublicKey, _data: &[u8]) -> Result { + Ok(BinaryData::from(vec![1; 65])) + } + + fn sign_create_witness( + &self, + _key: &IdentityPublicKey, + _data: &[u8], + ) -> Result { + Err(ProtocolError::CorruptedCodeExecution( + "sign_create_witness is not used in these tests".to_string(), + )) + } + + fn can_sign_with(&self, _key: &IdentityPublicKey) -> bool { + true + } +} + +fn test_identity_public_key() -> IdentityPublicKey { + IdentityPublicKey::V0(IdentityPublicKeyV0 { + id: 1, + purpose: Purpose::AUTHENTICATION, + security_level: SecurityLevel::CRITICAL, + contract_bounds: None, + key_type: KeyType::ECDSA_SECP256K1, + read_only: false, + data: BinaryData::from(vec![2; 33]), + disabled_at: None, + }) +} + +fn test_data_contract(document_type_name: &str) -> Arc { + let platform_version = dpp::version::PlatformVersion::latest(); + let config = + DataContractConfig::default_for_version(platform_version).expect("create contract config"); + + let schema = platform_value!({ + "type": "object", + "properties": { + "a": { + "type": "string", + "maxLength": 10, + "position": 0 + } + }, + "additionalProperties": false, + }); + + let document_type = DocumentType::try_from_schema( + Identifier::random(), + 1, + config.version(), + document_type_name, + schema, + None, + &BTreeMap::new(), + &config, + true, + &mut vec![], + platform_version, + ) + .expect("create test document type"); + + let mut document_types: BTreeMap = BTreeMap::new(); + document_types.insert( + document_type.name().to_string(), + document_type.schema().clone(), + ); + + let contract = DataContractFactory::new(platform_version.protocol_version) + .expect("create data contract factory") + .create( + Identifier::random(), + 0, + platform_value!(document_types), + None, + None, + ) + .expect("create test data contract") + .data_contract_owned(); + + Arc::new(contract) +} + +async fn new_mock_sdk_with_contract_nonce( + identity_id: Identifier, + contract_id: Identifier, + fetched_nonce: u64, +) -> Sdk { + let mut sdk = SdkBuilder::new_mock().build().expect("build mock sdk"); + + sdk.mock() + .expect_fetch::( + (identity_id, contract_id), + Some(IdentityContractNonceFetcher(fetched_nonce)), + ) + .await + .expect("set nonce fetch expectation"); + + sdk +} + +#[tokio::test] +async fn token_mint_sign_returns_invalid_token_amount_error_when_amount_is_zero() { + let issuer_id = Identifier::random(); + let data_contract = test_data_contract("testDoc"); + let sdk = new_mock_sdk_with_contract_nonce(issuer_id, data_contract.id(), 0).await; + + let result = TokenMintTransitionBuilder::new(Arc::clone(&data_contract), 0, issuer_id, 0) + .issued_to_identity_id(Identifier::random()) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenAmountError(_))) + ), + "unexpected result: {:?}", + result + ); +} + +#[tokio::test] +async fn token_mint_sign_returns_invalid_action_id_error_for_mismatched_group_action_id() { + let issuer_id = Identifier::random(); + let data_contract = test_data_contract("testDoc"); + let sdk = new_mock_sdk_with_contract_nonce(issuer_id, data_contract.id(), 0).await; + + let invalid_group_info = GroupStateTransitionInfoStatus::GroupStateTransitionInfoOtherSigner( + GroupStateTransitionInfo { + group_contract_position: 0, + action_id: Identifier::from_bytes(&[0; 32]).expect("create static action id"), + action_is_proposer: true, + }, + ); + + let result = TokenMintTransitionBuilder::new(Arc::clone(&data_contract), 0, issuer_id, 1) + .issued_to_identity_id(Identifier::random()) + .with_using_group_info(invalid_group_info) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidActionIdError(_))) + ), + "unexpected result: {:?}", + result + ); +} From b8e5813315e86e5c7d2e6bc12f88ac670526f5bd Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 21 Feb 2026 14:01:49 -0600 Subject: [PATCH 06/14] test(sdk): add validate_base_structure error path tests for all builders --- .../platform/documents/transitions/create.rs | 8 + .../platform/documents/transitions/delete.rs | 8 + .../documents/transitions/purchase.rs | 8 + .../platform/documents/transitions/replace.rs | 8 + .../documents/transitions/set_price.rs | 8 + .../platform/documents/transitions/tests.rs | 244 ++++++ .../documents/transitions/transfer.rs | 8 + .../src/platform/tokens/builders/burn.rs | 8 + .../src/platform/tokens/builders/claim.rs | 8 + .../platform/tokens/builders/config_update.rs | 8 + .../src/platform/tokens/builders/destroy.rs | 8 + .../tokens/builders/emergency_action.rs | 8 + .../src/platform/tokens/builders/freeze.rs | 8 + .../src/platform/tokens/builders/mint.rs | 8 + .../src/platform/tokens/builders/purchase.rs | 8 + .../src/platform/tokens/builders/set_price.rs | 8 + .../src/platform/tokens/builders/tests.rs | 754 +++++++++++++++++- .../src/platform/tokens/builders/transfer.rs | 8 + .../src/platform/tokens/builders/unfreeze.rs | 8 + 19 files changed, 1132 insertions(+), 2 deletions(-) diff --git a/packages/rs-sdk/src/platform/documents/transitions/create.rs b/packages/rs-sdk/src/platform/documents/transitions/create.rs index fca6bda77f2..1c5bc3ab99d 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/create.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/create.rs @@ -267,3 +267,11 @@ impl Sdk { } } } + +#[cfg(test)] +mod validation_tests { + #[test] + fn validate_base_structure_error_case() { + super::super::tests::assert_document_create_validate_base_structure_error(); + } +} diff --git a/packages/rs-sdk/src/platform/documents/transitions/delete.rs b/packages/rs-sdk/src/platform/documents/transitions/delete.rs index 9a1862b98af..50f491bcbd1 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/delete.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/delete.rs @@ -304,3 +304,11 @@ impl Sdk { } } } + +#[cfg(test)] +mod validation_tests { + #[test] + fn validate_base_structure_error_case() { + super::super::tests::assert_document_delete_validate_base_structure_error(); + } +} diff --git a/packages/rs-sdk/src/platform/documents/transitions/purchase.rs b/packages/rs-sdk/src/platform/documents/transitions/purchase.rs index 6b33bc2bf3b..d34f70e9975 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/purchase.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/purchase.rs @@ -321,3 +321,11 @@ impl Sdk { } } } + +#[cfg(test)] +mod validation_tests { + #[test] + fn validate_base_structure_error_case() { + super::super::tests::assert_document_purchase_validate_base_structure_error(); + } +} diff --git a/packages/rs-sdk/src/platform/documents/transitions/replace.rs b/packages/rs-sdk/src/platform/documents/transitions/replace.rs index 27dede0bb3f..77b510088e1 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/replace.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/replace.rs @@ -267,3 +267,11 @@ impl Sdk { } } } + +#[cfg(test)] +mod validation_tests { + #[test] + fn validate_base_structure_error_case() { + super::super::tests::assert_document_replace_validate_base_structure_error(); + } +} diff --git a/packages/rs-sdk/src/platform/documents/transitions/set_price.rs b/packages/rs-sdk/src/platform/documents/transitions/set_price.rs index 537c09283db..cda735f7c5d 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/set_price.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/set_price.rs @@ -305,3 +305,11 @@ impl Sdk { } } } + +#[cfg(test)] +mod validation_tests { + #[test] + fn validate_base_structure_error_case() { + super::super::tests::assert_document_set_price_validate_base_structure_error(); + } +} diff --git a/packages/rs-sdk/src/platform/documents/transitions/tests.rs b/packages/rs-sdk/src/platform/documents/transitions/tests.rs index 87057371e21..1f8002498a2 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/tests.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/tests.rs @@ -1,4 +1,9 @@ +use super::create::DocumentCreateTransitionBuilder; use super::delete::DocumentDeleteTransitionBuilder; +use super::purchase::DocumentPurchaseTransitionBuilder; +use super::replace::DocumentReplaceTransitionBuilder; +use super::set_price::DocumentSetPriceTransitionBuilder; +use super::transfer::DocumentTransferTransitionBuilder; use crate::{Error, Sdk, SdkBuilder}; use dpp::address_funds::AddressWitness; use dpp::data_contract::accessors::v0::DataContractV0Getters; @@ -131,6 +136,35 @@ fn test_document(owner_id: Identifier) -> Document { }) } +fn test_document_for_create( + data_contract_id: &Identifier, + document_type_name: &str, + owner_id: Identifier, + entropy: [u8; 32], +) -> Document { + Document::V0(DocumentV0 { + id: Document::generate_document_id_v0( + data_contract_id, + &owner_id, + document_type_name, + &entropy, + ), + owner_id, + properties: Default::default(), + revision: None, + created_at: None, + updated_at: None, + transferred_at: None, + created_at_block_height: None, + updated_at_block_height: None, + transferred_at_block_height: None, + created_at_core_block_height: None, + updated_at_core_block_height: None, + transferred_at_core_block_height: None, + creator_id: None, + }) +} + fn validate_transition_like_builder(state_transition: &StateTransition) -> Result<(), Error> { let platform_version = dpp::version::PlatformVersion::latest(); let validation_result = match state_transition { @@ -372,3 +406,213 @@ async fn document_delete_sign_masks_nonce_and_does_not_hit_nonce_out_of_bounds_v "nonce out-of-bounds should be unreachable via document builders" ); } + +#[tokio::test] +async fn document_create_sign_masks_nonce_and_does_not_hit_nonce_out_of_bounds_validation() { + let document_type_name = "testDoc"; + let data_contract = test_data_contract(document_type_name); + let owner_id = Identifier::random(); + let entropy = [11; 32]; + let document = + test_document_for_create(&data_contract.id(), document_type_name, owner_id, entropy); + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 1_u64 << 50).await; + + let builder = DocumentCreateTransitionBuilder::new( + Arc::clone(&data_contract), + document_type_name.to_string(), + document, + entropy, + ); + + let result = builder + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "unexpected error while signing document create transition: {:?}", + result.err() + ); + + assert!( + !matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(consensus_error))) + if matches!(*consensus_error, dpp::consensus::ConsensusError::BasicError( + dpp::consensus::basic::BasicError::NonceOutOfBoundsError(_) + )) + ), + "nonce out-of-bounds should be unreachable via document builders" + ); +} + +#[tokio::test] +async fn document_replace_sign_masks_nonce_and_does_not_hit_nonce_out_of_bounds_validation() { + let document_type_name = "testDoc"; + let data_contract = test_data_contract(document_type_name); + let owner_id = Identifier::random(); + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 1_u64 << 50).await; + + let builder = DocumentReplaceTransitionBuilder::new( + Arc::clone(&data_contract), + document_type_name.to_string(), + test_document(owner_id), + ); + + let result = builder + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "unexpected error while signing document replace transition: {:?}", + result.err() + ); + + assert!( + !matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(consensus_error))) + if matches!(*consensus_error, dpp::consensus::ConsensusError::BasicError( + dpp::consensus::basic::BasicError::NonceOutOfBoundsError(_) + )) + ), + "nonce out-of-bounds should be unreachable via document builders" + ); +} + +#[tokio::test] +async fn document_purchase_sign_masks_nonce_and_does_not_hit_nonce_out_of_bounds_validation() { + let document_type_name = "testDoc"; + let data_contract = test_data_contract(document_type_name); + let owner_id = Identifier::random(); + let purchaser_id = Identifier::random(); + let sdk = new_mock_sdk_with_contract_nonce(purchaser_id, data_contract.id(), 1_u64 << 50).await; + + let builder = DocumentPurchaseTransitionBuilder::new( + Arc::clone(&data_contract), + document_type_name.to_string(), + test_document(owner_id), + purchaser_id, + 100, + ); + + let result = builder + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "unexpected error while signing document purchase transition: {:?}", + result.err() + ); + + assert!( + !matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(consensus_error))) + if matches!(*consensus_error, dpp::consensus::ConsensusError::BasicError( + dpp::consensus::basic::BasicError::NonceOutOfBoundsError(_) + )) + ), + "nonce out-of-bounds should be unreachable via document builders" + ); +} + +#[tokio::test] +async fn document_set_price_sign_masks_nonce_and_does_not_hit_nonce_out_of_bounds_validation() { + let document_type_name = "testDoc"; + let data_contract = test_data_contract(document_type_name); + let owner_id = Identifier::random(); + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 1_u64 << 50).await; + + let builder = DocumentSetPriceTransitionBuilder::new( + Arc::clone(&data_contract), + document_type_name.to_string(), + test_document(owner_id), + 123, + ); + + let result = builder + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "unexpected error while signing document set_price transition: {:?}", + result.err() + ); + + assert!( + !matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(consensus_error))) + if matches!(*consensus_error, dpp::consensus::ConsensusError::BasicError( + dpp::consensus::basic::BasicError::NonceOutOfBoundsError(_) + )) + ), + "nonce out-of-bounds should be unreachable via document builders" + ); +} + +#[tokio::test] +async fn document_transfer_sign_masks_nonce_and_does_not_hit_nonce_out_of_bounds_validation() { + let document_type_name = "testDoc"; + let data_contract = test_data_contract(document_type_name); + let owner_id = Identifier::random(); + let recipient_id = Identifier::random(); + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 1_u64 << 50).await; + + let builder = DocumentTransferTransitionBuilder::new( + Arc::clone(&data_contract), + document_type_name.to_string(), + test_document(owner_id), + recipient_id, + ); + + let result = builder + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "unexpected error while signing document transfer transition: {:?}", + result.err() + ); + + assert!( + !matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(consensus_error))) + if matches!(*consensus_error, dpp::consensus::ConsensusError::BasicError( + dpp::consensus::basic::BasicError::NonceOutOfBoundsError(_) + )) + ), + "nonce out-of-bounds should be unreachable via document builders" + ); +} diff --git a/packages/rs-sdk/src/platform/documents/transitions/transfer.rs b/packages/rs-sdk/src/platform/documents/transitions/transfer.rs index 53eef22c776..452d5590265 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/transfer.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/transfer.rs @@ -305,3 +305,11 @@ impl Sdk { } } } + +#[cfg(test)] +mod validation_tests { + #[test] + fn validate_base_structure_error_case() { + super::super::tests::assert_document_transfer_validate_base_structure_error(); + } +} diff --git a/packages/rs-sdk/src/platform/tokens/builders/burn.rs b/packages/rs-sdk/src/platform/tokens/builders/burn.rs index 83f7f3719b7..c69dac24bf5 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/burn.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/burn.rs @@ -201,3 +201,11 @@ impl TokenBurnTransitionBuilder { Ok(state_transition) } } + +#[cfg(test)] +mod validation_tests { + #[test] + fn validate_base_structure_error_case() { + super::super::tests::assert_token_burn_validate_base_structure_error(); + } +} diff --git a/packages/rs-sdk/src/platform/tokens/builders/claim.rs b/packages/rs-sdk/src/platform/tokens/builders/claim.rs index d68923841f4..22fc107cdbe 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/claim.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/claim.rs @@ -187,3 +187,11 @@ impl TokenClaimTransitionBuilder { Ok(state_transition) } } + +#[cfg(test)] +mod validation_tests { + #[test] + fn validate_base_structure_error_case() { + super::super::tests::assert_token_claim_validate_base_structure_error(); + } +} diff --git a/packages/rs-sdk/src/platform/tokens/builders/config_update.rs b/packages/rs-sdk/src/platform/tokens/builders/config_update.rs index 1d20a26d2b3..eb7a6fd9146 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/config_update.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/config_update.rs @@ -208,3 +208,11 @@ impl TokenConfigUpdateTransitionBuilder { Ok(state_transition) } } + +#[cfg(test)] +mod validation_tests { + #[test] + fn validate_base_structure_error_case() { + super::super::tests::assert_token_config_update_validate_base_structure_error(); + } +} diff --git a/packages/rs-sdk/src/platform/tokens/builders/destroy.rs b/packages/rs-sdk/src/platform/tokens/builders/destroy.rs index a89f2416b8c..6ee484126ea 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/destroy.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/destroy.rs @@ -207,3 +207,11 @@ impl TokenDestroyFrozenFundsTransitionBuilder { Ok(state_transition) } } + +#[cfg(test)] +mod validation_tests { + #[test] + fn validate_base_structure_error_case() { + super::super::tests::assert_token_destroy_validate_base_structure_error(); + } +} diff --git a/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs b/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs index f292d08d764..3bb0bf70e01 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs @@ -235,3 +235,11 @@ impl TokenEmergencyActionTransitionBuilder { Ok(state_transition) } } + +#[cfg(test)] +mod validation_tests { + #[test] + fn validate_base_structure_error_case() { + super::super::tests::assert_token_emergency_action_validate_base_structure_error(); + } +} diff --git a/packages/rs-sdk/src/platform/tokens/builders/freeze.rs b/packages/rs-sdk/src/platform/tokens/builders/freeze.rs index bf6a0868096..e1f2a5edc15 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/freeze.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/freeze.rs @@ -207,3 +207,11 @@ impl TokenFreezeTransitionBuilder { Ok(state_transition) } } + +#[cfg(test)] +mod validation_tests { + #[test] + fn validate_base_structure_error_case() { + super::super::tests::assert_token_freeze_validate_base_structure_error(); + } +} diff --git a/packages/rs-sdk/src/platform/tokens/builders/mint.rs b/packages/rs-sdk/src/platform/tokens/builders/mint.rs index 1595592033b..9bfb4af5adc 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/mint.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/mint.rs @@ -228,3 +228,11 @@ impl TokenMintTransitionBuilder { Ok(state_transition) } } + +#[cfg(test)] +mod validation_tests { + #[test] + fn validate_base_structure_error_case() { + super::super::tests::assert_token_mint_validate_base_structure_error(); + } +} diff --git a/packages/rs-sdk/src/platform/tokens/builders/purchase.rs b/packages/rs-sdk/src/platform/tokens/builders/purchase.rs index 069f19f6dcf..a91f99d45a5 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/purchase.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/purchase.rs @@ -176,3 +176,11 @@ impl TokenDirectPurchaseTransitionBuilder { Ok(state_transition) } } + +#[cfg(test)] +mod validation_tests { + #[test] + fn validate_base_structure_error_case() { + super::super::tests::assert_token_purchase_validate_base_structure_error(); + } +} diff --git a/packages/rs-sdk/src/platform/tokens/builders/set_price.rs b/packages/rs-sdk/src/platform/tokens/builders/set_price.rs index 1933532689f..85ba354f0e4 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/set_price.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/set_price.rs @@ -252,3 +252,11 @@ impl TokenChangeDirectPurchasePriceTransitionBuilder { Ok(state_transition) } } + +#[cfg(test)] +mod validation_tests { + #[test] + fn validate_base_structure_error_case() { + super::super::tests::assert_token_set_price_validate_base_structure_error(); + } +} diff --git a/packages/rs-sdk/src/platform/tokens/builders/tests.rs b/packages/rs-sdk/src/platform/tokens/builders/tests.rs index 4eccf101d8b..51659b0ad14 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/tests.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/tests.rs @@ -1,9 +1,21 @@ +use super::burn::TokenBurnTransitionBuilder; +use super::claim::TokenClaimTransitionBuilder; +use super::config_update::TokenConfigUpdateTransitionBuilder; +use super::destroy::TokenDestroyFrozenFundsTransitionBuilder; +use super::emergency_action::TokenEmergencyActionTransitionBuilder; +use super::freeze::TokenFreezeTransitionBuilder; use super::mint::TokenMintTransitionBuilder; +use super::purchase::TokenDirectPurchaseTransitionBuilder; +use super::set_price::TokenChangeDirectPurchasePriceTransitionBuilder; +use super::transfer::TokenTransferTransitionBuilder; +use super::unfreeze::TokenUnfreezeTransitionBuilder; use crate::{Error, Sdk, SdkBuilder}; use dpp::address_funds::AddressWitness; use dpp::consensus::basic::BasicError; use dpp::consensus::ConsensusError; use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::data_contract::associated_token::token_configuration_item::TokenConfigurationChangeItem; +use dpp::data_contract::associated_token::token_distribution_key::TokenDistributionType; use dpp::data_contract::config::DataContractConfig; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::data_contract::document_type::DocumentType; @@ -14,6 +26,12 @@ use dpp::identity::signer::Signer; use dpp::identity::{IdentityPublicKey, KeyType, Purpose, SecurityLevel}; use dpp::platform_value::{platform_value, BinaryData, Value}; use dpp::prelude::Identifier; +use dpp::state_transition::batch_transition::methods::v1::DocumentsBatchTransitionMethodsV1; +use dpp::state_transition::batch_transition::BatchTransition; +use dpp::state_transition::StateTransition; +use dpp::tokens::calculate_token_id; +use dpp::tokens::emergency_action::TokenEmergencyAction; +use dpp::tokens::token_pricing_schedule::TokenPricingSchedule; use dpp::ProtocolError; use drive_proof_verifier::types::IdentityContractNonceFetcher; use std::collections::BTreeMap; @@ -108,6 +126,311 @@ fn test_data_contract(document_type_name: &str) -> Arc Result<(), Error> { + let platform_version = dpp::version::PlatformVersion::latest(); + let validation_result = match state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ), + )) + } + }; + if let Some(first_error) = validation_result.errors.into_iter().next() { + return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( + Box::new(first_error), + ))); + } + Ok(()) +} + +fn token_setup() -> ( + Arc, + Identifier, + Identifier, +) { + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let owner_id = Identifier::random(); + let token_id = Identifier::from(calculate_token_id( + data_contract.id().as_bytes(), + TEST_TOKEN_POSITION, + )); + (data_contract, owner_id, token_id) +} + +pub(super) fn assert_token_burn_validate_base_structure_error() { + let platform_version = dpp::version::PlatformVersion::latest(); + let (data_contract, owner_id, token_id) = token_setup(); + let transition = BatchTransition::new_token_burn_transition( + token_id, + owner_id, + data_contract.id(), + TEST_TOKEN_POSITION, + 1, + None, + None, + &test_identity_public_key(), + INVALID_NONCE, + 0, + &TestSigner, + platform_version, + None, + ) + .expect("transition should build"); + + let result = validate_transition_like_builder(&transition); + assert!(result.is_err(), "expected validation error, got {result:?}"); +} + +pub(super) fn assert_token_claim_validate_base_structure_error() { + let platform_version = dpp::version::PlatformVersion::latest(); + let (data_contract, owner_id, token_id) = token_setup(); + let transition = BatchTransition::new_token_claim_transition( + token_id, + owner_id, + data_contract.id(), + TEST_TOKEN_POSITION, + TokenDistributionType::PreProgrammed, + None, + &test_identity_public_key(), + INVALID_NONCE, + 0, + &TestSigner, + platform_version, + None, + ) + .expect("transition should build"); + + let result = validate_transition_like_builder(&transition); + assert!(result.is_err(), "expected validation error, got {result:?}"); +} + +pub(super) fn assert_token_config_update_validate_base_structure_error() { + let platform_version = dpp::version::PlatformVersion::latest(); + let (data_contract, owner_id, token_id) = token_setup(); + let transition = BatchTransition::new_token_config_update_transition( + token_id, + owner_id, + data_contract.id(), + TEST_TOKEN_POSITION, + TokenConfigurationChangeItem::TokenConfigurationNoChange, + None, + None, + &test_identity_public_key(), + INVALID_NONCE, + 0, + &TestSigner, + platform_version, + None, + ) + .expect("transition should build"); + + let result = validate_transition_like_builder(&transition); + assert!(result.is_err(), "expected validation error, got {result:?}"); +} + +pub(super) fn assert_token_destroy_validate_base_structure_error() { + let platform_version = dpp::version::PlatformVersion::latest(); + let (data_contract, owner_id, token_id) = token_setup(); + let transition = BatchTransition::new_token_destroy_frozen_funds_transition( + token_id, + owner_id, + data_contract.id(), + TEST_TOKEN_POSITION, + Identifier::random(), + None, + None, + &test_identity_public_key(), + INVALID_NONCE, + 0, + &TestSigner, + platform_version, + None, + ) + .expect("transition should build"); + + let result = validate_transition_like_builder(&transition); + assert!(result.is_err(), "expected validation error, got {result:?}"); +} + +pub(super) fn assert_token_emergency_action_validate_base_structure_error() { + let platform_version = dpp::version::PlatformVersion::latest(); + let (data_contract, owner_id, token_id) = token_setup(); + let transition = BatchTransition::new_token_emergency_action_transition( + token_id, + owner_id, + data_contract.id(), + TEST_TOKEN_POSITION, + TokenEmergencyAction::Pause, + None, + None, + &test_identity_public_key(), + INVALID_NONCE, + 0, + &TestSigner, + platform_version, + None, + ) + .expect("transition should build"); + + let result = validate_transition_like_builder(&transition); + assert!(result.is_err(), "expected validation error, got {result:?}"); +} + +pub(super) fn assert_token_freeze_validate_base_structure_error() { + let platform_version = dpp::version::PlatformVersion::latest(); + let (data_contract, owner_id, token_id) = token_setup(); + let transition = BatchTransition::new_token_freeze_transition( + token_id, + owner_id, + data_contract.id(), + TEST_TOKEN_POSITION, + Identifier::random(), + None, + None, + &test_identity_public_key(), + INVALID_NONCE, + 0, + &TestSigner, + platform_version, + None, + ) + .expect("transition should build"); + + let result = validate_transition_like_builder(&transition); + assert!(result.is_err(), "expected validation error, got {result:?}"); +} + +pub(super) fn assert_token_mint_validate_base_structure_error() { + let platform_version = dpp::version::PlatformVersion::latest(); + let (data_contract, owner_id, token_id) = token_setup(); + let transition = BatchTransition::new_token_mint_transition( + token_id, + owner_id, + data_contract.id(), + TEST_TOKEN_POSITION, + 1, + Some(Identifier::random()), + None, + None, + &test_identity_public_key(), + INVALID_NONCE, + 0, + &TestSigner, + platform_version, + None, + ) + .expect("transition should build"); + + let result = validate_transition_like_builder(&transition); + assert!(result.is_err(), "expected validation error, got {result:?}"); +} + +pub(super) fn assert_token_purchase_validate_base_structure_error() { + let platform_version = dpp::version::PlatformVersion::latest(); + let (data_contract, owner_id, token_id) = token_setup(); + let transition = BatchTransition::new_token_direct_purchase_transition( + token_id, + owner_id, + data_contract.id(), + TEST_TOKEN_POSITION, + 1, + 10, + &test_identity_public_key(), + INVALID_NONCE, + 0, + &TestSigner, + platform_version, + None, + ) + .expect("transition should build"); + + let result = validate_transition_like_builder(&transition); + assert!(result.is_err(), "expected validation error, got {result:?}"); +} + +pub(super) fn assert_token_set_price_validate_base_structure_error() { + let platform_version = dpp::version::PlatformVersion::latest(); + let (data_contract, owner_id, token_id) = token_setup(); + let transition = BatchTransition::new_token_change_direct_purchase_price_transition( + token_id, + owner_id, + data_contract.id(), + TEST_TOKEN_POSITION, + Some(TokenPricingSchedule::SinglePrice(5)), + None, + None, + &test_identity_public_key(), + INVALID_NONCE, + 0, + &TestSigner, + platform_version, + None, + ) + .expect("transition should build"); + + let result = validate_transition_like_builder(&transition); + assert!(result.is_err(), "expected validation error, got {result:?}"); +} + +pub(super) fn assert_token_transfer_validate_base_structure_error() { + let platform_version = dpp::version::PlatformVersion::latest(); + let (data_contract, owner_id, token_id) = token_setup(); + let transition = BatchTransition::new_token_transfer_transition( + token_id, + owner_id, + data_contract.id(), + TEST_TOKEN_POSITION, + 1, + Identifier::random(), + None, + None, + None, + &test_identity_public_key(), + INVALID_NONCE, + 0, + &TestSigner, + platform_version, + None, + ) + .expect("transition should build"); + + let result = validate_transition_like_builder(&transition); + assert!(result.is_err(), "expected validation error, got {result:?}"); +} + +pub(super) fn assert_token_unfreeze_validate_base_structure_error() { + let platform_version = dpp::version::PlatformVersion::latest(); + let (data_contract, owner_id, token_id) = token_setup(); + let transition = BatchTransition::new_token_unfreeze_transition( + token_id, + owner_id, + data_contract.id(), + TEST_TOKEN_POSITION, + Identifier::random(), + None, + None, + &test_identity_public_key(), + INVALID_NONCE, + 0, + &TestSigner, + platform_version, + None, + ) + .expect("transition should build"); + + let result = validate_transition_like_builder(&transition); + assert!(result.is_err(), "expected validation error, got {result:?}"); +} + async fn new_mock_sdk_with_contract_nonce( identity_id: Identifier, contract_id: Identifier, @@ -129,7 +452,7 @@ async fn new_mock_sdk_with_contract_nonce( #[tokio::test] async fn token_mint_sign_returns_invalid_token_amount_error_when_amount_is_zero() { let issuer_id = Identifier::random(); - let data_contract = test_data_contract("testDoc"); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); let sdk = new_mock_sdk_with_contract_nonce(issuer_id, data_contract.id(), 0).await; let result = TokenMintTransitionBuilder::new(Arc::clone(&data_contract), 0, issuer_id, 0) @@ -156,7 +479,7 @@ async fn token_mint_sign_returns_invalid_token_amount_error_when_amount_is_zero( #[tokio::test] async fn token_mint_sign_returns_invalid_action_id_error_for_mismatched_group_action_id() { let issuer_id = Identifier::random(); - let data_contract = test_data_contract("testDoc"); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); let sdk = new_mock_sdk_with_contract_nonce(issuer_id, data_contract.id(), 0).await; let invalid_group_info = GroupStateTransitionInfoStatus::GroupStateTransitionInfoOtherSigner( @@ -188,3 +511,430 @@ async fn token_mint_sign_returns_invalid_action_id_error_for_mismatched_group_ac result ); } + +#[tokio::test] +async fn token_burn_sign_returns_invalid_token_amount_error_when_amount_is_zero() { + let owner_id = Identifier::random(); + let data_contract = test_data_contract("testDoc"); + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 0).await; + + let result = TokenBurnTransitionBuilder::new(Arc::clone(&data_contract), 0, owner_id, 0) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenAmountError(_))) + ), + "unexpected result: {:?}", + result + ); +} + +#[tokio::test] +async fn token_burn_sign_returns_note_too_big_error() { + let owner_id = Identifier::random(); + let data_contract = test_data_contract("testDoc"); + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 0).await; + + let result = TokenBurnTransitionBuilder::new(Arc::clone(&data_contract), 0, owner_id, 1) + .with_public_note("x".repeat(2049)) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) + ), + "unexpected result: {:?}", + result + ); +} + +#[tokio::test] +async fn token_transfer_sign_returns_invalid_token_amount_error_when_amount_is_zero() { + let sender_id = Identifier::random(); + let recipient_id = Identifier::random(); + let data_contract = test_data_contract("testDoc"); + let sdk = new_mock_sdk_with_contract_nonce(sender_id, data_contract.id(), 0).await; + + let result = TokenTransferTransitionBuilder::new( + Arc::clone(&data_contract), + 0, + sender_id, + recipient_id, + 0, + ) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenAmountError(_))) + ), + "unexpected result: {:?}", + result + ); +} + +#[tokio::test] +async fn token_transfer_sign_returns_transfer_to_ourself_error() { + let sender_id = Identifier::random(); + let data_contract = test_data_contract("testDoc"); + let sdk = new_mock_sdk_with_contract_nonce(sender_id, data_contract.id(), 0).await; + + let result = + TokenTransferTransitionBuilder::new(Arc::clone(&data_contract), 0, sender_id, sender_id, 1) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + if matches!(**consensus_error, ConsensusError::BasicError(BasicError::TokenTransferToOurselfError(_))) + ), + "unexpected result: {:?}", + result + ); +} + +#[tokio::test] +async fn token_transfer_sign_returns_note_too_big_error_for_public_note() { + let sender_id = Identifier::random(); + let recipient_id = Identifier::random(); + let data_contract = test_data_contract("testDoc"); + let sdk = new_mock_sdk_with_contract_nonce(sender_id, data_contract.id(), 0).await; + + let result = TokenTransferTransitionBuilder::new( + Arc::clone(&data_contract), + 0, + sender_id, + recipient_id, + 1, + ) + .with_public_note("x".repeat(2049)) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) + ), + "unexpected result: {:?}", + result + ); +} + +#[tokio::test] +async fn token_freeze_sign_returns_note_too_big_error() { + let actor_id = Identifier::random(); + let freeze_identity_id = Identifier::random(); + let data_contract = test_data_contract("testDoc"); + let sdk = new_mock_sdk_with_contract_nonce(actor_id, data_contract.id(), 0).await; + + let result = TokenFreezeTransitionBuilder::new( + Arc::clone(&data_contract), + 0, + actor_id, + freeze_identity_id, + ) + .with_public_note("x".repeat(2049)) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) + ), + "unexpected result: {:?}", + result + ); +} + +#[tokio::test] +async fn token_unfreeze_sign_returns_note_too_big_error() { + let actor_id = Identifier::random(); + let unfreeze_identity_id = Identifier::random(); + let data_contract = test_data_contract("testDoc"); + let sdk = new_mock_sdk_with_contract_nonce(actor_id, data_contract.id(), 0).await; + + let result = TokenUnfreezeTransitionBuilder::new( + Arc::clone(&data_contract), + 0, + actor_id, + unfreeze_identity_id, + ) + .with_public_note("x".repeat(2049)) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) + ), + "unexpected result: {:?}", + result + ); +} + +#[tokio::test] +async fn token_destroy_sign_returns_note_too_big_error() { + let actor_id = Identifier::random(); + let frozen_identity_id = Identifier::random(); + let data_contract = test_data_contract("testDoc"); + let sdk = new_mock_sdk_with_contract_nonce(actor_id, data_contract.id(), 0).await; + + let result = TokenDestroyFrozenFundsTransitionBuilder::new( + Arc::clone(&data_contract), + 0, + actor_id, + frozen_identity_id, + ) + .with_public_note("x".repeat(2049)) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) + ), + "unexpected result: {:?}", + result + ); +} + +#[tokio::test] +async fn token_emergency_action_sign_returns_note_too_big_error() { + let actor_id = Identifier::random(); + let data_contract = test_data_contract("testDoc"); + let sdk = new_mock_sdk_with_contract_nonce(actor_id, data_contract.id(), 0).await; + + let result = + TokenEmergencyActionTransitionBuilder::pause(Arc::clone(&data_contract), 0, actor_id) + .with_public_note("x".repeat(2049)) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) + ), + "unexpected result: {:?}", + result + ); +} + +#[tokio::test] +async fn token_config_update_sign_returns_no_change_error() { + let owner_id = Identifier::random(); + let data_contract = test_data_contract("testDoc"); + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 0).await; + + let result = TokenConfigUpdateTransitionBuilder::new( + Arc::clone(&data_contract), + 0, + owner_id, + TokenConfigurationChangeItem::TokenConfigurationNoChange, + ) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenConfigUpdateNoChangeError(_))) + ), + "unexpected result: {:?}", + result + ); +} + +#[tokio::test] +async fn token_config_update_sign_returns_note_too_big_error() { + let owner_id = Identifier::random(); + let data_contract = test_data_contract("testDoc"); + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 0).await; + + let result = TokenConfigUpdateTransitionBuilder::new( + Arc::clone(&data_contract), + 0, + owner_id, + TokenConfigurationChangeItem::MintingAllowChoosingDestination(true), + ) + .with_public_note("x".repeat(2049)) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) + ), + "unexpected result: {:?}", + result + ); +} + +#[tokio::test] +async fn token_claim_sign_returns_note_too_big_error() { + let owner_id = Identifier::random(); + let data_contract = test_data_contract("testDoc"); + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 0).await; + + let result = TokenClaimTransitionBuilder::new( + Arc::clone(&data_contract), + 0, + owner_id, + TokenDistributionType::PreProgrammed, + ) + .with_public_note("x".repeat(2049)) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) + ), + "unexpected result: {:?}", + result + ); +} + +#[tokio::test] +async fn token_purchase_sign_returns_invalid_token_amount_error_when_amount_is_zero() { + let actor_id = Identifier::random(); + let data_contract = test_data_contract("testDoc"); + let sdk = new_mock_sdk_with_contract_nonce(actor_id, data_contract.id(), 0).await; + + let result = + TokenDirectPurchaseTransitionBuilder::new(Arc::clone(&data_contract), 0, actor_id, 0, 1000) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenAmountError(_))) + ), + "unexpected result: {:?}", + result + ); +} + +#[tokio::test] +async fn token_set_price_sign_returns_note_too_big_error() { + let issuer_id = Identifier::random(); + let data_contract = test_data_contract("testDoc"); + let sdk = new_mock_sdk_with_contract_nonce(issuer_id, data_contract.id(), 0).await; + + let result = TokenChangeDirectPurchasePriceTransitionBuilder::new( + Arc::clone(&data_contract), + 0, + issuer_id, + ) + .with_public_note("x".repeat(2049)) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + matches!( + result, + Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) + ), + "unexpected result: {:?}", + result + ); +} diff --git a/packages/rs-sdk/src/platform/tokens/builders/transfer.rs b/packages/rs-sdk/src/platform/tokens/builders/transfer.rs index 0822f8d924d..83127672925 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/transfer.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/transfer.rs @@ -233,3 +233,11 @@ impl TokenTransferTransitionBuilder { Ok(state_transition) } } + +#[cfg(test)] +mod validation_tests { + #[test] + fn validate_base_structure_error_case() { + super::super::tests::assert_token_transfer_validate_base_structure_error(); + } +} diff --git a/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs b/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs index 109a2e19e44..ae4aea50026 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs @@ -207,3 +207,11 @@ impl TokenUnfreezeTransitionBuilder { Ok(state_transition) } } + +#[cfg(test)] +mod validation_tests { + #[test] + fn validate_base_structure_error_case() { + super::super::tests::assert_token_unfreeze_validate_base_structure_error(); + } +} From c30a1bfdc30bfacbef4b1159ae3134393e805050 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 21 Feb 2026 14:14:28 -0600 Subject: [PATCH 07/14] refactor(sdk): remove 5 redundant document nonce-masking tests All 6 document_*_sign_masks_nonce tests tested the same underlying behavior (Sdk::get_identity_contract_nonce masks out-of-bounds bits). Keep one representative test, remove the other 5. Also removes dead second assert that was always true (result was Ok) and unused test_document_for_create helper. --- .../platform/documents/transitions/tests.rs | 266 +----------------- 1 file changed, 6 insertions(+), 260 deletions(-) diff --git a/packages/rs-sdk/src/platform/documents/transitions/tests.rs b/packages/rs-sdk/src/platform/documents/transitions/tests.rs index 1f8002498a2..dd88faaa80a 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/tests.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/tests.rs @@ -1,9 +1,4 @@ -use super::create::DocumentCreateTransitionBuilder; use super::delete::DocumentDeleteTransitionBuilder; -use super::purchase::DocumentPurchaseTransitionBuilder; -use super::replace::DocumentReplaceTransitionBuilder; -use super::set_price::DocumentSetPriceTransitionBuilder; -use super::transfer::DocumentTransferTransitionBuilder; use crate::{Error, Sdk, SdkBuilder}; use dpp::address_funds::AddressWitness; use dpp::data_contract::accessors::v0::DataContractV0Getters; @@ -136,35 +131,6 @@ fn test_document(owner_id: Identifier) -> Document { }) } -fn test_document_for_create( - data_contract_id: &Identifier, - document_type_name: &str, - owner_id: Identifier, - entropy: [u8; 32], -) -> Document { - Document::V0(DocumentV0 { - id: Document::generate_document_id_v0( - data_contract_id, - &owner_id, - document_type_name, - &entropy, - ), - owner_id, - properties: Default::default(), - revision: None, - created_at: None, - updated_at: None, - transferred_at: None, - created_at_block_height: None, - updated_at_block_height: None, - transferred_at_block_height: None, - created_at_core_block_height: None, - updated_at_core_block_height: None, - transferred_at_core_block_height: None, - creator_id: None, - }) -} - fn validate_transition_like_builder(state_transition: &StateTransition) -> Result<(), Error> { let platform_version = dpp::version::PlatformVersion::latest(); let validation_result = match state_transition { @@ -363,10 +329,11 @@ async fn new_mock_sdk_with_contract_nonce( } #[tokio::test] -async fn document_delete_sign_masks_nonce_and_does_not_hit_nonce_out_of_bounds_validation() { - // Document builders always create exactly one transition and obtain nonce through - // `Sdk::get_identity_contract_nonce`, which masks out-of-bounds bits. - // This makes `validate_base_structure` nonce-out-of-bounds errors unreachable here. +async fn document_builder_sign_masks_nonce_so_out_of_bounds_is_unreachable() { + // Document builders obtain nonce through `Sdk::get_identity_contract_nonce`, + // which masks out-of-bounds bits. This makes `validate_base_structure` + // nonce-out-of-bounds errors unreachable through the builder API. + // One test suffices since all document builders use the same SDK nonce path. let document_type_name = "testDoc"; let data_contract = test_data_contract(document_type_name); let owner_id = Identifier::random(); @@ -391,228 +358,7 @@ async fn document_delete_sign_masks_nonce_and_does_not_hit_nonce_out_of_bounds_v assert!( result.is_ok(), - "unexpected error while signing document delete transition: {:?}", - result.err() - ); - - assert!( - !matches!( - result, - Err(Error::Protocol(ProtocolError::ConsensusError(consensus_error))) - if matches!(*consensus_error, dpp::consensus::ConsensusError::BasicError( - dpp::consensus::basic::BasicError::NonceOutOfBoundsError(_) - )) - ), - "nonce out-of-bounds should be unreachable via document builders" - ); -} - -#[tokio::test] -async fn document_create_sign_masks_nonce_and_does_not_hit_nonce_out_of_bounds_validation() { - let document_type_name = "testDoc"; - let data_contract = test_data_contract(document_type_name); - let owner_id = Identifier::random(); - let entropy = [11; 32]; - let document = - test_document_for_create(&data_contract.id(), document_type_name, owner_id, entropy); - let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 1_u64 << 50).await; - - let builder = DocumentCreateTransitionBuilder::new( - Arc::clone(&data_contract), - document_type_name.to_string(), - document, - entropy, - ); - - let result = builder - .sign( - &sdk, - &test_identity_public_key(), - &TestSigner, - dpp::version::PlatformVersion::latest(), - ) - .await; - - assert!( - result.is_ok(), - "unexpected error while signing document create transition: {:?}", - result.err() - ); - - assert!( - !matches!( - result, - Err(Error::Protocol(ProtocolError::ConsensusError(consensus_error))) - if matches!(*consensus_error, dpp::consensus::ConsensusError::BasicError( - dpp::consensus::basic::BasicError::NonceOutOfBoundsError(_) - )) - ), - "nonce out-of-bounds should be unreachable via document builders" - ); -} - -#[tokio::test] -async fn document_replace_sign_masks_nonce_and_does_not_hit_nonce_out_of_bounds_validation() { - let document_type_name = "testDoc"; - let data_contract = test_data_contract(document_type_name); - let owner_id = Identifier::random(); - let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 1_u64 << 50).await; - - let builder = DocumentReplaceTransitionBuilder::new( - Arc::clone(&data_contract), - document_type_name.to_string(), - test_document(owner_id), - ); - - let result = builder - .sign( - &sdk, - &test_identity_public_key(), - &TestSigner, - dpp::version::PlatformVersion::latest(), - ) - .await; - - assert!( - result.is_ok(), - "unexpected error while signing document replace transition: {:?}", - result.err() - ); - - assert!( - !matches!( - result, - Err(Error::Protocol(ProtocolError::ConsensusError(consensus_error))) - if matches!(*consensus_error, dpp::consensus::ConsensusError::BasicError( - dpp::consensus::basic::BasicError::NonceOutOfBoundsError(_) - )) - ), - "nonce out-of-bounds should be unreachable via document builders" - ); -} - -#[tokio::test] -async fn document_purchase_sign_masks_nonce_and_does_not_hit_nonce_out_of_bounds_validation() { - let document_type_name = "testDoc"; - let data_contract = test_data_contract(document_type_name); - let owner_id = Identifier::random(); - let purchaser_id = Identifier::random(); - let sdk = new_mock_sdk_with_contract_nonce(purchaser_id, data_contract.id(), 1_u64 << 50).await; - - let builder = DocumentPurchaseTransitionBuilder::new( - Arc::clone(&data_contract), - document_type_name.to_string(), - test_document(owner_id), - purchaser_id, - 100, - ); - - let result = builder - .sign( - &sdk, - &test_identity_public_key(), - &TestSigner, - dpp::version::PlatformVersion::latest(), - ) - .await; - - assert!( - result.is_ok(), - "unexpected error while signing document purchase transition: {:?}", - result.err() - ); - - assert!( - !matches!( - result, - Err(Error::Protocol(ProtocolError::ConsensusError(consensus_error))) - if matches!(*consensus_error, dpp::consensus::ConsensusError::BasicError( - dpp::consensus::basic::BasicError::NonceOutOfBoundsError(_) - )) - ), - "nonce out-of-bounds should be unreachable via document builders" - ); -} - -#[tokio::test] -async fn document_set_price_sign_masks_nonce_and_does_not_hit_nonce_out_of_bounds_validation() { - let document_type_name = "testDoc"; - let data_contract = test_data_contract(document_type_name); - let owner_id = Identifier::random(); - let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 1_u64 << 50).await; - - let builder = DocumentSetPriceTransitionBuilder::new( - Arc::clone(&data_contract), - document_type_name.to_string(), - test_document(owner_id), - 123, - ); - - let result = builder - .sign( - &sdk, - &test_identity_public_key(), - &TestSigner, - dpp::version::PlatformVersion::latest(), - ) - .await; - - assert!( - result.is_ok(), - "unexpected error while signing document set_price transition: {:?}", + "SDK should mask nonce internally; got error: {:?}", result.err() ); - - assert!( - !matches!( - result, - Err(Error::Protocol(ProtocolError::ConsensusError(consensus_error))) - if matches!(*consensus_error, dpp::consensus::ConsensusError::BasicError( - dpp::consensus::basic::BasicError::NonceOutOfBoundsError(_) - )) - ), - "nonce out-of-bounds should be unreachable via document builders" - ); -} - -#[tokio::test] -async fn document_transfer_sign_masks_nonce_and_does_not_hit_nonce_out_of_bounds_validation() { - let document_type_name = "testDoc"; - let data_contract = test_data_contract(document_type_name); - let owner_id = Identifier::random(); - let recipient_id = Identifier::random(); - let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 1_u64 << 50).await; - - let builder = DocumentTransferTransitionBuilder::new( - Arc::clone(&data_contract), - document_type_name.to_string(), - test_document(owner_id), - recipient_id, - ); - - let result = builder - .sign( - &sdk, - &test_identity_public_key(), - &TestSigner, - dpp::version::PlatformVersion::latest(), - ) - .await; - - assert!( - result.is_ok(), - "unexpected error while signing document transfer transition: {:?}", - result.err() - ); - - assert!( - !matches!( - result, - Err(Error::Protocol(ProtocolError::ConsensusError(consensus_error))) - if matches!(*consensus_error, dpp::consensus::ConsensusError::BasicError( - dpp::consensus::basic::BasicError::NonceOutOfBoundsError(_) - )) - ), - "nonce out-of-bounds should be unreachable via document builders" - ); } From f8cb63ad228bc27edaa07b52cba20823fe6b882e Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 16 Mar 2026 14:31:58 -0500 Subject: [PATCH 08/14] refactor(sdk): extract validate_batch_base_structure helper to reduce duplication Co-Authored-By: Claude Opus 4.6 (1M context) --- .../platform/documents/transitions/create.rs | 19 ++----------- .../platform/documents/transitions/delete.rs | 19 ++----------- .../documents/transitions/purchase.rs | 19 ++----------- .../platform/documents/transitions/replace.rs | 19 ++----------- .../documents/transitions/set_price.rs | 19 ++----------- .../documents/transitions/transfer.rs | 19 ++----------- .../src/platform/tokens/builders/burn.rs | 19 ++----------- .../src/platform/tokens/builders/claim.rs | 19 ++----------- .../platform/tokens/builders/config_update.rs | 19 ++----------- .../src/platform/tokens/builders/destroy.rs | 19 ++----------- .../tokens/builders/emergency_action.rs | 19 ++----------- .../src/platform/tokens/builders/freeze.rs | 19 ++----------- .../src/platform/tokens/builders/mint.rs | 19 ++----------- .../src/platform/tokens/builders/purchase.rs | 19 ++----------- .../src/platform/tokens/builders/set_price.rs | 19 ++----------- .../src/platform/tokens/builders/transfer.rs | 19 ++----------- .../src/platform/tokens/builders/unfreeze.rs | 19 ++----------- .../src/platform/transition/validation.rs | 28 +++++++++++++++++++ 18 files changed, 62 insertions(+), 289 deletions(-) diff --git a/packages/rs-sdk/src/platform/documents/transitions/create.rs b/packages/rs-sdk/src/platform/documents/transitions/create.rs index 1c5bc3ab99d..e9f8f6168b5 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/create.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/create.rs @@ -1,5 +1,6 @@ use crate::platform::transition::broadcast::BroadcastStateTransition; use crate::platform::transition::put_settings::PutSettings; +use crate::platform::transition::validation::validate_batch_base_structure; use crate::{Error, Sdk}; use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::data_contract::DataContract; @@ -167,23 +168,7 @@ impl DocumentCreateTransitionBuilder { )?; // Validate the transition structure before returning - let validation_result = match &state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )); - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } + validate_batch_base_structure(&state_transition, platform_version)?; Ok(state_transition) } diff --git a/packages/rs-sdk/src/platform/documents/transitions/delete.rs b/packages/rs-sdk/src/platform/documents/transitions/delete.rs index 50f491bcbd1..ba7425a512a 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/delete.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/delete.rs @@ -1,5 +1,6 @@ use crate::platform::transition::broadcast::BroadcastStateTransition; use crate::platform::transition::put_settings::PutSettings; +use crate::platform::transition::validation::validate_batch_base_structure; use crate::platform::Identifier; use crate::{Error, Sdk}; use dpp::data_contract::accessors::v0::DataContractV0Getters; @@ -208,23 +209,7 @@ impl DocumentDeleteTransitionBuilder { )?; // Validate the transition structure before returning - let validation_result = match &state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )); - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } + validate_batch_base_structure(&state_transition, platform_version)?; Ok(state_transition) } diff --git a/packages/rs-sdk/src/platform/documents/transitions/purchase.rs b/packages/rs-sdk/src/platform/documents/transitions/purchase.rs index d34f70e9975..0e6fc745a70 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/purchase.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/purchase.rs @@ -1,5 +1,6 @@ use crate::platform::transition::broadcast::BroadcastStateTransition; use crate::platform::transition::put_settings::PutSettings; +use crate::platform::transition::validation::validate_batch_base_structure; use crate::platform::Identifier; use crate::{Error, Sdk}; use dpp::data_contract::accessors::v0::DataContractV0Getters; @@ -222,23 +223,7 @@ impl DocumentPurchaseTransitionBuilder { )?; // Validate the transition structure before returning - let validation_result = match &state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )); - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } + validate_batch_base_structure(&state_transition, platform_version)?; Ok(state_transition) } diff --git a/packages/rs-sdk/src/platform/documents/transitions/replace.rs b/packages/rs-sdk/src/platform/documents/transitions/replace.rs index 77b510088e1..69d70073bdb 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/replace.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/replace.rs @@ -1,5 +1,6 @@ use crate::platform::transition::broadcast::BroadcastStateTransition; use crate::platform::transition::put_settings::PutSettings; +use crate::platform::transition::validation::validate_batch_base_structure; use crate::{Error, Sdk}; use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::data_contract::DataContract; @@ -161,23 +162,7 @@ impl DocumentReplaceTransitionBuilder { )?; // Validate the transition structure before returning - let validation_result = match &state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )); - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } + validate_batch_base_structure(&state_transition, platform_version)?; Ok(state_transition) } diff --git a/packages/rs-sdk/src/platform/documents/transitions/set_price.rs b/packages/rs-sdk/src/platform/documents/transitions/set_price.rs index cda735f7c5d..72a845299a3 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/set_price.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/set_price.rs @@ -1,5 +1,6 @@ use crate::platform::transition::broadcast::BroadcastStateTransition; use crate::platform::transition::put_settings::PutSettings; +use crate::platform::transition::validation::validate_batch_base_structure; use crate::platform::Identifier; use crate::{Error, Sdk}; use dpp::data_contract::accessors::v0::DataContractV0Getters; @@ -209,23 +210,7 @@ impl DocumentSetPriceTransitionBuilder { )?; // Validate the transition structure before returning - let validation_result = match &state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )); - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } + validate_batch_base_structure(&state_transition, platform_version)?; Ok(state_transition) } diff --git a/packages/rs-sdk/src/platform/documents/transitions/transfer.rs b/packages/rs-sdk/src/platform/documents/transitions/transfer.rs index 452d5590265..2571c85370e 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/transfer.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/transfer.rs @@ -1,5 +1,6 @@ use crate::platform::transition::broadcast::BroadcastStateTransition; use crate::platform::transition::put_settings::PutSettings; +use crate::platform::transition::validation::validate_batch_base_structure; use crate::platform::Identifier; use crate::{Error, Sdk}; use dpp::data_contract::accessors::v0::DataContractV0Getters; @@ -208,23 +209,7 @@ impl DocumentTransferTransitionBuilder { )?; // Validate the transition structure before returning - let validation_result = match &state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )); - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } + validate_batch_base_structure(&state_transition, platform_version)?; Ok(state_transition) } diff --git a/packages/rs-sdk/src/platform/tokens/builders/burn.rs b/packages/rs-sdk/src/platform/tokens/builders/burn.rs index c69dac24bf5..04d2e3fb364 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/burn.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/burn.rs @@ -1,4 +1,5 @@ use crate::platform::transition::put_settings::PutSettings; +use crate::platform::transition::validation::validate_batch_base_structure; use crate::platform::Identifier; use crate::{Error, Sdk}; use dpp::balances::credits::TokenAmount; @@ -180,23 +181,7 @@ impl TokenBurnTransitionBuilder { )?; // Validate the transition structure before returning - let validation_result = match &state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )); - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } + validate_batch_base_structure(&state_transition, platform_version)?; Ok(state_transition) } diff --git a/packages/rs-sdk/src/platform/tokens/builders/claim.rs b/packages/rs-sdk/src/platform/tokens/builders/claim.rs index 22fc107cdbe..0a14f68f7c0 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/claim.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/claim.rs @@ -1,4 +1,5 @@ use crate::platform::transition::put_settings::PutSettings; +use crate::platform::transition::validation::validate_batch_base_structure; use crate::platform::Identifier; use crate::{Error, Sdk}; use dpp::data_contract::accessors::v0::DataContractV0Getters; @@ -166,23 +167,7 @@ impl TokenClaimTransitionBuilder { )?; // Validate the transition structure before returning - let validation_result = match &state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )); - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } + validate_batch_base_structure(&state_transition, platform_version)?; Ok(state_transition) } diff --git a/packages/rs-sdk/src/platform/tokens/builders/config_update.rs b/packages/rs-sdk/src/platform/tokens/builders/config_update.rs index eb7a6fd9146..814cb431148 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/config_update.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/config_update.rs @@ -1,4 +1,5 @@ use crate::platform::transition::put_settings::PutSettings; +use crate::platform::transition::validation::validate_batch_base_structure; use crate::platform::Identifier; use crate::{Error, Sdk}; use dpp::data_contract::accessors::v0::DataContractV0Getters; @@ -187,23 +188,7 @@ impl TokenConfigUpdateTransitionBuilder { )?; // Validate the transition structure before returning - let validation_result = match &state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )); - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } + validate_batch_base_structure(&state_transition, platform_version)?; Ok(state_transition) } diff --git a/packages/rs-sdk/src/platform/tokens/builders/destroy.rs b/packages/rs-sdk/src/platform/tokens/builders/destroy.rs index 6ee484126ea..f8873b0082d 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/destroy.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/destroy.rs @@ -1,4 +1,5 @@ use crate::platform::transition::put_settings::PutSettings; +use crate::platform::transition::validation::validate_batch_base_structure; use crate::platform::Identifier; use crate::{Error, Sdk}; use dpp::data_contract::accessors::v0::DataContractV0Getters; @@ -186,23 +187,7 @@ impl TokenDestroyFrozenFundsTransitionBuilder { )?; // Validate the transition structure before returning - let validation_result = match &state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )); - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } + validate_batch_base_structure(&state_transition, platform_version)?; Ok(state_transition) } diff --git a/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs b/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs index 3bb0bf70e01..8bbad7c4f50 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/emergency_action.rs @@ -1,4 +1,5 @@ use crate::platform::transition::put_settings::PutSettings; +use crate::platform::transition::validation::validate_batch_base_structure; use crate::platform::Identifier; use crate::{Error, Sdk}; use dpp::data_contract::accessors::v0::DataContractV0Getters; @@ -214,23 +215,7 @@ impl TokenEmergencyActionTransitionBuilder { )?; // Validate the transition structure before returning - let validation_result = match &state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )); - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } + validate_batch_base_structure(&state_transition, platform_version)?; Ok(state_transition) } diff --git a/packages/rs-sdk/src/platform/tokens/builders/freeze.rs b/packages/rs-sdk/src/platform/tokens/builders/freeze.rs index e1f2a5edc15..e2e618e2927 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/freeze.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/freeze.rs @@ -1,4 +1,5 @@ use crate::platform::transition::put_settings::PutSettings; +use crate::platform::transition::validation::validate_batch_base_structure; use crate::platform::Identifier; use crate::{Error, Sdk}; use dpp::data_contract::accessors::v0::DataContractV0Getters; @@ -186,23 +187,7 @@ impl TokenFreezeTransitionBuilder { )?; // Validate the transition structure before returning - let validation_result = match &state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )); - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } + validate_batch_base_structure(&state_transition, platform_version)?; Ok(state_transition) } diff --git a/packages/rs-sdk/src/platform/tokens/builders/mint.rs b/packages/rs-sdk/src/platform/tokens/builders/mint.rs index 9bfb4af5adc..ba9b6d30926 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/mint.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/mint.rs @@ -1,4 +1,5 @@ use crate::platform::transition::put_settings::PutSettings; +use crate::platform::transition::validation::validate_batch_base_structure; use crate::platform::Identifier; use crate::{Error, Sdk}; use dpp::balances::credits::TokenAmount; @@ -207,23 +208,7 @@ impl TokenMintTransitionBuilder { )?; // Validate the transition structure before returning - let validation_result = match &state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )); - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } + validate_batch_base_structure(&state_transition, platform_version)?; Ok(state_transition) } diff --git a/packages/rs-sdk/src/platform/tokens/builders/purchase.rs b/packages/rs-sdk/src/platform/tokens/builders/purchase.rs index a91f99d45a5..d7fff044334 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/purchase.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/purchase.rs @@ -1,4 +1,5 @@ use crate::platform::transition::put_settings::PutSettings; +use crate::platform::transition::validation::validate_batch_base_structure; use crate::platform::Identifier; use crate::{Error, Sdk}; use dpp::balances::credits::TokenAmount; @@ -155,23 +156,7 @@ impl TokenDirectPurchaseTransitionBuilder { )?; // Validate the transition structure before returning - let validation_result = match &state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )); - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } + validate_batch_base_structure(&state_transition, platform_version)?; Ok(state_transition) } diff --git a/packages/rs-sdk/src/platform/tokens/builders/set_price.rs b/packages/rs-sdk/src/platform/tokens/builders/set_price.rs index 85ba354f0e4..50691fdffb7 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/set_price.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/set_price.rs @@ -1,4 +1,5 @@ use crate::platform::transition::put_settings::PutSettings; +use crate::platform::transition::validation::validate_batch_base_structure; use crate::platform::Identifier; use crate::{Error, Sdk}; use dpp::balances::credits::Credits; @@ -231,23 +232,7 @@ impl TokenChangeDirectPurchasePriceTransitionBuilder { )?; // Validate the transition structure before returning - let validation_result = match &state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )); - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } + validate_batch_base_structure(&state_transition, platform_version)?; Ok(state_transition) } diff --git a/packages/rs-sdk/src/platform/tokens/builders/transfer.rs b/packages/rs-sdk/src/platform/tokens/builders/transfer.rs index 83127672925..bd409de9eb4 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/transfer.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/transfer.rs @@ -1,4 +1,5 @@ use crate::platform::transition::put_settings::PutSettings; +use crate::platform::transition::validation::validate_batch_base_structure; use crate::platform::Identifier; use crate::{Error, Sdk}; use dpp::balances::credits::TokenAmount; @@ -212,23 +213,7 @@ impl TokenTransferTransitionBuilder { )?; // Validate the transition structure before returning - let validation_result = match &state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )); - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } + validate_batch_base_structure(&state_transition, platform_version)?; Ok(state_transition) } diff --git a/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs b/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs index ae4aea50026..1a5c4b9e312 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/unfreeze.rs @@ -1,4 +1,5 @@ use crate::platform::transition::put_settings::PutSettings; +use crate::platform::transition::validation::validate_batch_base_structure; use crate::platform::Identifier; use crate::{Error, Sdk}; use dpp::data_contract::accessors::v0::DataContractV0Getters; @@ -186,23 +187,7 @@ impl TokenUnfreezeTransitionBuilder { )?; // Validate the transition structure before returning - let validation_result = match &state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )); - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } + validate_batch_base_structure(&state_transition, platform_version)?; Ok(state_transition) } diff --git a/packages/rs-sdk/src/platform/transition/validation.rs b/packages/rs-sdk/src/platform/transition/validation.rs index 846d9ddae2d..303b2f078ef 100644 --- a/packages/rs-sdk/src/platform/transition/validation.rs +++ b/packages/rs-sdk/src/platform/transition/validation.rs @@ -3,8 +3,36 @@ use dpp::{ consensus::{basic::BasicError, ConsensusError}, state_transition::{StateTransition, StateTransitionStructureValidation}, version::PlatformVersion, + ProtocolError, }; +/// Validates the base structure of a Batch state transition. +/// +/// Used by document and token transition builders to validate the constructed +/// `BatchTransition` before returning it to the caller. Catches invalid +/// transitions early with clear errors instead of confusing network rejections. +pub(crate) fn validate_batch_base_structure( + state_transition: &StateTransition, + platform_version: &PlatformVersion, +) -> Result<(), Error> { + let validation_result = match state_transition { + StateTransition::Batch(batch_transition) => { + batch_transition.validate_base_structure(platform_version)? + } + _ => { + return Err(Error::Protocol(ProtocolError::InvalidStateTransitionType( + "expected Batch transition".to_string(), + ))); + } + }; + if let Some(first_error) = validation_result.errors.into_iter().next() { + return Err(Error::Protocol(ProtocolError::ConsensusError(Box::new( + first_error, + )))); + } + Ok(()) +} + /// Checks if an error is an UnsupportedFeatureError fn is_unsupported_feature_error(error: &ConsensusError) -> bool { matches!( From 26d51815427222e3f8f90e13dbce3f5a08a0f323 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 16 Mar 2026 14:35:59 -0500 Subject: [PATCH 09/14] refactor(sdk): extract shared test helpers to reduce test duplication Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/rs-sdk/src/platform.rs | 2 + .../platform/documents/transitions/tests.rs | 149 +-------------- packages/rs-sdk/src/platform/test_helpers.rs | 139 ++++++++++++++ .../src/platform/tokens/builders/tests.rs | 180 ++---------------- 4 files changed, 165 insertions(+), 305 deletions(-) create mode 100644 packages/rs-sdk/src/platform/test_helpers.rs diff --git a/packages/rs-sdk/src/platform.rs b/packages/rs-sdk/src/platform.rs index 36d42444f4c..381625058da 100644 --- a/packages/rs-sdk/src/platform.rs +++ b/packages/rs-sdk/src/platform.rs @@ -20,6 +20,8 @@ pub mod identities_contract_keys_query; pub mod query; #[cfg(feature = "shielded")] pub mod shielded; +#[cfg(test)] +pub(crate) mod test_helpers; pub mod tokens; pub mod transition; pub mod trunk_branch_sync; diff --git a/packages/rs-sdk/src/platform/documents/transitions/tests.rs b/packages/rs-sdk/src/platform/documents/transitions/tests.rs index dd88faaa80a..b5ba3eaf477 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/tests.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/tests.rs @@ -1,117 +1,16 @@ use super::delete::DocumentDeleteTransitionBuilder; -use crate::{Error, Sdk, SdkBuilder}; -use dpp::address_funds::AddressWitness; +use crate::platform::test_helpers::{ + new_mock_sdk_with_contract_nonce, test_data_contract, test_identity_public_key, + validate_transition_like_builder, TestSigner, INVALID_NONCE, TEST_DOCUMENT_TYPE_NAME, +}; use dpp::data_contract::accessors::v0::DataContractV0Getters; -use dpp::data_contract::config::DataContractConfig; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; -use dpp::data_contract::document_type::DocumentType; -use dpp::data_contract::DataContractFactory; use dpp::document::{Document, DocumentV0}; -use dpp::identity::identity_public_key::v0::IdentityPublicKeyV0; -use dpp::identity::signer::Signer; -use dpp::identity::{IdentityPublicKey, KeyType, Purpose, SecurityLevel}; -use dpp::platform_value::{platform_value, BinaryData, Value}; use dpp::prelude::Identifier; use dpp::state_transition::batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0; use dpp::state_transition::batch_transition::BatchTransition; -use dpp::state_transition::StateTransition; -use dpp::ProtocolError; -use drive_proof_verifier::types::IdentityContractNonceFetcher; -use std::collections::BTreeMap; use std::sync::Arc; -#[derive(Debug)] -struct TestSigner; - -impl Signer for TestSigner { - fn sign(&self, _key: &IdentityPublicKey, _data: &[u8]) -> Result { - Ok(BinaryData::from(vec![1; 65])) - } - - fn sign_create_witness( - &self, - _key: &IdentityPublicKey, - _data: &[u8], - ) -> Result { - Err(ProtocolError::CorruptedCodeExecution( - "sign_create_witness is not used in these tests".to_string(), - )) - } - - fn can_sign_with(&self, _key: &IdentityPublicKey) -> bool { - true - } -} - -fn test_identity_public_key() -> IdentityPublicKey { - IdentityPublicKey::V0(IdentityPublicKeyV0 { - id: 1, - purpose: Purpose::AUTHENTICATION, - security_level: SecurityLevel::CRITICAL, - contract_bounds: None, - key_type: KeyType::ECDSA_SECP256K1, - read_only: false, - data: BinaryData::from(vec![2; 33]), - disabled_at: None, - }) -} - -fn test_data_contract(document_type_name: &str) -> Arc { - let platform_version = dpp::version::PlatformVersion::latest(); - let config = - DataContractConfig::default_for_version(platform_version).expect("create contract config"); - - let schema = platform_value!({ - "type": "object", - "properties": { - "a": { - "type": "string", - "maxLength": 10, - "position": 0 - } - }, - "additionalProperties": false, - }); - - let document_type = DocumentType::try_from_schema( - Identifier::random(), - 1, - config.version(), - document_type_name, - schema, - None, - &BTreeMap::new(), - &config, - true, - &mut vec![], - platform_version, - ) - .expect("create test document type"); - - let mut document_types: BTreeMap = BTreeMap::new(); - document_types.insert( - document_type.name().to_string(), - document_type.schema().clone(), - ); - - let contract = DataContractFactory::new(platform_version.protocol_version) - .expect("create data contract factory") - .create( - Identifier::random(), - 0, - platform_value!(document_types), - None, - None, - ) - .expect("create test data contract") - .data_contract_owned(); - - Arc::new(contract) -} - -const TEST_DOCUMENT_TYPE_NAME: &str = "testDoc"; -const INVALID_NONCE: u64 = 1_u64 << 50; - fn test_document(owner_id: Identifier) -> Document { Document::V0(DocumentV0 { id: Identifier::random(), @@ -131,28 +30,6 @@ fn test_document(owner_id: Identifier) -> Document { }) } -fn validate_transition_like_builder(state_transition: &StateTransition) -> Result<(), Error> { - let platform_version = dpp::version::PlatformVersion::latest(); - let validation_result = match state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )) - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } - Ok(()) -} - pub(super) fn assert_document_create_validate_base_structure_error() { let platform_version = dpp::version::PlatformVersion::latest(); let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); @@ -310,24 +187,6 @@ pub(super) fn assert_document_transfer_validate_base_structure_error() { assert!(result.is_err(), "expected validation error, got {result:?}"); } -async fn new_mock_sdk_with_contract_nonce( - identity_id: Identifier, - contract_id: Identifier, - fetched_nonce: u64, -) -> Sdk { - let mut sdk = SdkBuilder::new_mock().build().expect("build mock sdk"); - - sdk.mock() - .expect_fetch::( - (identity_id, contract_id), - Some(IdentityContractNonceFetcher(fetched_nonce)), - ) - .await - .expect("set nonce fetch expectation"); - - sdk -} - #[tokio::test] async fn document_builder_sign_masks_nonce_so_out_of_bounds_is_unreachable() { // Document builders obtain nonce through `Sdk::get_identity_contract_nonce`, diff --git a/packages/rs-sdk/src/platform/test_helpers.rs b/packages/rs-sdk/src/platform/test_helpers.rs new file mode 100644 index 00000000000..eb759742e6f --- /dev/null +++ b/packages/rs-sdk/src/platform/test_helpers.rs @@ -0,0 +1,139 @@ +//! Shared test infrastructure for document and token transition builder tests. + +use crate::{Error, Sdk, SdkBuilder}; +use dpp::address_funds::AddressWitness; +use dpp::data_contract::config::DataContractConfig; +use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; +use dpp::data_contract::document_type::DocumentType; +use dpp::data_contract::DataContractFactory; +use dpp::identity::identity_public_key::v0::IdentityPublicKeyV0; +use dpp::identity::signer::Signer; +use dpp::identity::{IdentityPublicKey, KeyType, Purpose, SecurityLevel}; +use dpp::platform_value::{platform_value, BinaryData, Value}; +use dpp::prelude::Identifier; +use dpp::state_transition::StateTransition; +use dpp::ProtocolError; +use drive_proof_verifier::types::IdentityContractNonceFetcher; +use std::collections::BTreeMap; +use std::sync::Arc; + +use crate::platform::transition::validation::validate_batch_base_structure; + +pub(crate) const TEST_DOCUMENT_TYPE_NAME: &str = "testDoc"; +pub(crate) const INVALID_NONCE: u64 = 1_u64 << 50; + +#[derive(Debug)] +pub(crate) struct TestSigner; + +impl Signer for TestSigner { + fn sign(&self, _key: &IdentityPublicKey, _data: &[u8]) -> Result { + Ok(BinaryData::from(vec![1; 65])) + } + + fn sign_create_witness( + &self, + _key: &IdentityPublicKey, + _data: &[u8], + ) -> Result { + Err(ProtocolError::CorruptedCodeExecution( + "sign_create_witness is not used in these tests".to_string(), + )) + } + + fn can_sign_with(&self, _key: &IdentityPublicKey) -> bool { + true + } +} + +pub(crate) fn test_identity_public_key() -> IdentityPublicKey { + IdentityPublicKey::V0(IdentityPublicKeyV0 { + id: 1, + purpose: Purpose::AUTHENTICATION, + security_level: SecurityLevel::CRITICAL, + contract_bounds: None, + key_type: KeyType::ECDSA_SECP256K1, + read_only: false, + data: BinaryData::from(vec![2; 33]), + disabled_at: None, + }) +} + +pub(crate) fn test_data_contract( + document_type_name: &str, +) -> Arc { + let platform_version = dpp::version::PlatformVersion::latest(); + let config = + DataContractConfig::default_for_version(platform_version).expect("create contract config"); + + let schema = platform_value!({ + "type": "object", + "properties": { + "a": { + "type": "string", + "maxLength": 10, + "position": 0 + } + }, + "additionalProperties": false, + }); + + let document_type = DocumentType::try_from_schema( + Identifier::random(), + 1, + config.version(), + document_type_name, + schema, + None, + &BTreeMap::new(), + &config, + true, + &mut vec![], + platform_version, + ) + .expect("create test document type"); + + let mut document_types: BTreeMap = BTreeMap::new(); + document_types.insert( + document_type.name().to_string(), + document_type.schema().clone(), + ); + + let contract = DataContractFactory::new(platform_version.protocol_version) + .expect("create data contract factory") + .create( + Identifier::random(), + 0, + platform_value!(document_types), + None, + None, + ) + .expect("create test data contract") + .data_contract_owned(); + + Arc::new(contract) +} + +pub(crate) fn validate_transition_like_builder( + state_transition: &StateTransition, +) -> Result<(), Error> { + let platform_version = dpp::version::PlatformVersion::latest(); + validate_batch_base_structure(state_transition, platform_version) +} + +pub(crate) async fn new_mock_sdk_with_contract_nonce( + identity_id: Identifier, + contract_id: Identifier, + fetched_nonce: u64, +) -> Sdk { + let mut sdk = SdkBuilder::new_mock().build().expect("build mock sdk"); + + sdk.mock() + .expect_fetch::( + (identity_id, contract_id), + Some(IdentityContractNonceFetcher(fetched_nonce)), + ) + .await + .expect("set nonce fetch expectation"); + + sdk +} diff --git a/packages/rs-sdk/src/platform/tokens/builders/tests.rs b/packages/rs-sdk/src/platform/tokens/builders/tests.rs index 51659b0ad14..a30bab19423 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/tests.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/tests.rs @@ -9,148 +9,26 @@ use super::purchase::TokenDirectPurchaseTransitionBuilder; use super::set_price::TokenChangeDirectPurchasePriceTransitionBuilder; use super::transfer::TokenTransferTransitionBuilder; use super::unfreeze::TokenUnfreezeTransitionBuilder; -use crate::{Error, Sdk, SdkBuilder}; -use dpp::address_funds::AddressWitness; +use crate::platform::test_helpers::{ + new_mock_sdk_with_contract_nonce, test_data_contract, test_identity_public_key, + validate_transition_like_builder, TestSigner, INVALID_NONCE, TEST_DOCUMENT_TYPE_NAME, +}; use dpp::consensus::basic::BasicError; use dpp::consensus::ConsensusError; use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::data_contract::associated_token::token_configuration_item::TokenConfigurationChangeItem; use dpp::data_contract::associated_token::token_distribution_key::TokenDistributionType; -use dpp::data_contract::config::DataContractConfig; -use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; -use dpp::data_contract::document_type::DocumentType; -use dpp::data_contract::DataContractFactory; use dpp::group::{GroupStateTransitionInfo, GroupStateTransitionInfoStatus}; -use dpp::identity::identity_public_key::v0::IdentityPublicKeyV0; -use dpp::identity::signer::Signer; -use dpp::identity::{IdentityPublicKey, KeyType, Purpose, SecurityLevel}; -use dpp::platform_value::{platform_value, BinaryData, Value}; use dpp::prelude::Identifier; use dpp::state_transition::batch_transition::methods::v1::DocumentsBatchTransitionMethodsV1; use dpp::state_transition::batch_transition::BatchTransition; -use dpp::state_transition::StateTransition; use dpp::tokens::calculate_token_id; use dpp::tokens::emergency_action::TokenEmergencyAction; use dpp::tokens::token_pricing_schedule::TokenPricingSchedule; use dpp::ProtocolError; -use drive_proof_verifier::types::IdentityContractNonceFetcher; -use std::collections::BTreeMap; use std::sync::Arc; -#[derive(Debug)] -struct TestSigner; - -impl Signer for TestSigner { - fn sign(&self, _key: &IdentityPublicKey, _data: &[u8]) -> Result { - Ok(BinaryData::from(vec![1; 65])) - } - - fn sign_create_witness( - &self, - _key: &IdentityPublicKey, - _data: &[u8], - ) -> Result { - Err(ProtocolError::CorruptedCodeExecution( - "sign_create_witness is not used in these tests".to_string(), - )) - } - - fn can_sign_with(&self, _key: &IdentityPublicKey) -> bool { - true - } -} - -fn test_identity_public_key() -> IdentityPublicKey { - IdentityPublicKey::V0(IdentityPublicKeyV0 { - id: 1, - purpose: Purpose::AUTHENTICATION, - security_level: SecurityLevel::CRITICAL, - contract_bounds: None, - key_type: KeyType::ECDSA_SECP256K1, - read_only: false, - data: BinaryData::from(vec![2; 33]), - disabled_at: None, - }) -} - -fn test_data_contract(document_type_name: &str) -> Arc { - let platform_version = dpp::version::PlatformVersion::latest(); - let config = - DataContractConfig::default_for_version(platform_version).expect("create contract config"); - - let schema = platform_value!({ - "type": "object", - "properties": { - "a": { - "type": "string", - "maxLength": 10, - "position": 0 - } - }, - "additionalProperties": false, - }); - - let document_type = DocumentType::try_from_schema( - Identifier::random(), - 1, - config.version(), - document_type_name, - schema, - None, - &BTreeMap::new(), - &config, - true, - &mut vec![], - platform_version, - ) - .expect("create test document type"); - - let mut document_types: BTreeMap = BTreeMap::new(); - document_types.insert( - document_type.name().to_string(), - document_type.schema().clone(), - ); - - let contract = DataContractFactory::new(platform_version.protocol_version) - .expect("create data contract factory") - .create( - Identifier::random(), - 0, - platform_value!(document_types), - None, - None, - ) - .expect("create test data contract") - .data_contract_owned(); - - Arc::new(contract) -} - -const TEST_DOCUMENT_TYPE_NAME: &str = "testDoc"; const TEST_TOKEN_POSITION: u16 = 0; -const INVALID_NONCE: u64 = 1_u64 << 50; - -fn validate_transition_like_builder(state_transition: &StateTransition) -> Result<(), Error> { - let platform_version = dpp::version::PlatformVersion::latest(); - let validation_result = match state_transition { - StateTransition::Batch(batch_transition) => { - batch_transition.validate_base_structure(platform_version)? - } - _ => { - return Err(Error::Protocol( - dpp::ProtocolError::InvalidStateTransitionType( - "expected Batch transition".to_string(), - ), - )) - } - }; - if let Some(first_error) = validation_result.errors.into_iter().next() { - return Err(Error::Protocol(dpp::ProtocolError::ConsensusError( - Box::new(first_error), - ))); - } - Ok(()) -} fn token_setup() -> ( Arc, @@ -431,24 +309,6 @@ pub(super) fn assert_token_unfreeze_validate_base_structure_error() { assert!(result.is_err(), "expected validation error, got {result:?}"); } -async fn new_mock_sdk_with_contract_nonce( - identity_id: Identifier, - contract_id: Identifier, - fetched_nonce: u64, -) -> Sdk { - let mut sdk = SdkBuilder::new_mock().build().expect("build mock sdk"); - - sdk.mock() - .expect_fetch::( - (identity_id, contract_id), - Some(IdentityContractNonceFetcher(fetched_nonce)), - ) - .await - .expect("set nonce fetch expectation"); - - sdk -} - #[tokio::test] async fn token_mint_sign_returns_invalid_token_amount_error_when_amount_is_zero() { let issuer_id = Identifier::random(); @@ -468,7 +328,7 @@ async fn token_mint_sign_returns_invalid_token_amount_error_when_amount_is_zero( assert!( matches!( result, - Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + Err(crate::Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenAmountError(_))) ), "unexpected result: {:?}", @@ -504,7 +364,7 @@ async fn token_mint_sign_returns_invalid_action_id_error_for_mismatched_group_ac assert!( matches!( result, - Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + Err(crate::Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidActionIdError(_))) ), "unexpected result: {:?}", @@ -530,7 +390,7 @@ async fn token_burn_sign_returns_invalid_token_amount_error_when_amount_is_zero( assert!( matches!( result, - Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + Err(crate::Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenAmountError(_))) ), "unexpected result: {:?}", @@ -557,7 +417,7 @@ async fn token_burn_sign_returns_note_too_big_error() { assert!( matches!( result, - Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + Err(crate::Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) ), "unexpected result: {:?}", @@ -590,7 +450,7 @@ async fn token_transfer_sign_returns_invalid_token_amount_error_when_amount_is_z assert!( matches!( result, - Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + Err(crate::Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenAmountError(_))) ), "unexpected result: {:?}", @@ -617,7 +477,7 @@ async fn token_transfer_sign_returns_transfer_to_ourself_error() { assert!( matches!( result, - Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + Err(crate::Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) if matches!(**consensus_error, ConsensusError::BasicError(BasicError::TokenTransferToOurselfError(_))) ), "unexpected result: {:?}", @@ -651,7 +511,7 @@ async fn token_transfer_sign_returns_note_too_big_error_for_public_note() { assert!( matches!( result, - Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + Err(crate::Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) ), "unexpected result: {:?}", @@ -684,7 +544,7 @@ async fn token_freeze_sign_returns_note_too_big_error() { assert!( matches!( result, - Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + Err(crate::Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) ), "unexpected result: {:?}", @@ -717,7 +577,7 @@ async fn token_unfreeze_sign_returns_note_too_big_error() { assert!( matches!( result, - Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + Err(crate::Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) ), "unexpected result: {:?}", @@ -750,7 +610,7 @@ async fn token_destroy_sign_returns_note_too_big_error() { assert!( matches!( result, - Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + Err(crate::Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) ), "unexpected result: {:?}", @@ -778,7 +638,7 @@ async fn token_emergency_action_sign_returns_note_too_big_error() { assert!( matches!( result, - Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + Err(crate::Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) ), "unexpected result: {:?}", @@ -809,7 +669,7 @@ async fn token_config_update_sign_returns_no_change_error() { assert!( matches!( result, - Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + Err(crate::Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenConfigUpdateNoChangeError(_))) ), "unexpected result: {:?}", @@ -841,7 +701,7 @@ async fn token_config_update_sign_returns_note_too_big_error() { assert!( matches!( result, - Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + Err(crate::Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) ), "unexpected result: {:?}", @@ -873,7 +733,7 @@ async fn token_claim_sign_returns_note_too_big_error() { assert!( matches!( result, - Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + Err(crate::Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) ), "unexpected result: {:?}", @@ -900,7 +760,7 @@ async fn token_purchase_sign_returns_invalid_token_amount_error_when_amount_is_z assert!( matches!( result, - Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + Err(crate::Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenAmountError(_))) ), "unexpected result: {:?}", @@ -931,7 +791,7 @@ async fn token_set_price_sign_returns_note_too_big_error() { assert!( matches!( result, - Err(Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) + Err(crate::Error::Protocol(ProtocolError::ConsensusError(ref consensus_error))) if matches!(**consensus_error, ConsensusError::BasicError(BasicError::InvalidTokenNoteTooBigError(_))) ), "unexpected result: {:?}", From 72a0d8108bb4e942502f21787b6a5347038bb843 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 16 Mar 2026 14:37:03 -0500 Subject: [PATCH 10/14] test(sdk): add document builder sign() integration test Co-Authored-By: Claude Opus 4.6 (1M context) --- .../platform/documents/transitions/tests.rs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/rs-sdk/src/platform/documents/transitions/tests.rs b/packages/rs-sdk/src/platform/documents/transitions/tests.rs index b5ba3eaf477..daa7fa05e71 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/tests.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/tests.rs @@ -221,3 +221,33 @@ async fn document_builder_sign_masks_nonce_so_out_of_bounds_is_unreachable() { result.err() ); } + +#[tokio::test] +async fn document_delete_builder_sign_succeeds_for_valid_input() { + let document_type_name = "testDoc"; + let data_contract = test_data_contract(document_type_name); + let owner_id = Identifier::random(); + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 0).await; + + let builder = DocumentDeleteTransitionBuilder::new( + Arc::clone(&data_contract), + document_type_name.to_string(), + Identifier::random(), + owner_id, + ); + + let result = builder + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "valid input should pass validation; got error: {:?}", + result.err() + ); +} From 569a2ca3c3030e18b5323cc5570a69ee9d7f798a Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 16 Mar 2026 19:12:08 -0500 Subject: [PATCH 11/14] fix(sdk): log dropped validation errors in validate_batch_base_structure When multiple validation errors occur, only the first is returned as an error. Additional errors are now logged via tracing::warn! instead of being silently discarded. Also adds a doc comment to INVALID_NONCE explaining why it triggers NonceOutOfBoundsError. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/rs-sdk/src/platform/test_helpers.rs | 2 ++ packages/rs-sdk/src/platform/transition/validation.rs | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/rs-sdk/src/platform/test_helpers.rs b/packages/rs-sdk/src/platform/test_helpers.rs index eb759742e6f..bffa923c832 100644 --- a/packages/rs-sdk/src/platform/test_helpers.rs +++ b/packages/rs-sdk/src/platform/test_helpers.rs @@ -20,6 +20,8 @@ use std::sync::Arc; use crate::platform::transition::validation::validate_batch_base_structure; pub(crate) const TEST_DOCUMENT_TYPE_NAME: &str = "testDoc"; +/// Exceeds the 40-bit nonce mask (MISSING_IDENTITY_REVISIONS_FILTER), triggering +/// NonceOutOfBoundsError in validate_base_structure. pub(crate) const INVALID_NONCE: u64 = 1_u64 << 50; #[derive(Debug)] diff --git a/packages/rs-sdk/src/platform/transition/validation.rs b/packages/rs-sdk/src/platform/transition/validation.rs index 303b2f078ef..fa136744a29 100644 --- a/packages/rs-sdk/src/platform/transition/validation.rs +++ b/packages/rs-sdk/src/platform/transition/validation.rs @@ -25,7 +25,15 @@ pub(crate) fn validate_batch_base_structure( ))); } }; - if let Some(first_error) = validation_result.errors.into_iter().next() { + let mut errors = validation_result.errors.into_iter(); + if let Some(first_error) = errors.next() { + // Log any additional errors that won't be reported + for additional_error in errors { + tracing::warn!( + ?additional_error, + "additional validation error dropped (only first error is reported)" + ); + } return Err(Error::Protocol(ProtocolError::ConsensusError(Box::new( first_error, )))); From ebc6d26a5ae710b73c474e732c09bf0ed00ad41b Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 16 Mar 2026 19:12:29 -0500 Subject: [PATCH 12/14] test(sdk): add happy-path sign() tests for all document builders Add sign() integration tests for create, replace, purchase, set_price, and transfer builders, complementing the existing delete builder test. Each test verifies that valid input passes validation end-to-end through the builder's sign() method with a mock SDK. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../platform/documents/transitions/tests.rs | 158 +++++++++++++++++- 1 file changed, 157 insertions(+), 1 deletion(-) diff --git a/packages/rs-sdk/src/platform/documents/transitions/tests.rs b/packages/rs-sdk/src/platform/documents/transitions/tests.rs index daa7fa05e71..575b9060422 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/tests.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/tests.rs @@ -1,10 +1,14 @@ +use super::create::DocumentCreateTransitionBuilder; use super::delete::DocumentDeleteTransitionBuilder; +use super::purchase::DocumentPurchaseTransitionBuilder; +use super::replace::DocumentReplaceTransitionBuilder; +use super::set_price::DocumentSetPriceTransitionBuilder; +use super::transfer::DocumentTransferTransitionBuilder; use crate::platform::test_helpers::{ new_mock_sdk_with_contract_nonce, test_data_contract, test_identity_public_key, validate_transition_like_builder, TestSigner, INVALID_NONCE, TEST_DOCUMENT_TYPE_NAME, }; use dpp::data_contract::accessors::v0::DataContractV0Getters; -use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::document::{Document, DocumentV0}; use dpp::prelude::Identifier; use dpp::state_transition::batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0; @@ -251,3 +255,155 @@ async fn document_delete_builder_sign_succeeds_for_valid_input() { result.err() ); } + +#[tokio::test] +async fn document_create_builder_sign_succeeds_for_valid_input() { + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let owner_id = Identifier::random(); + let document = test_document(owner_id); + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 0).await; + + let builder = DocumentCreateTransitionBuilder::new( + Arc::clone(&data_contract), + TEST_DOCUMENT_TYPE_NAME.to_string(), + document, + [7; 32], + ); + + let result = builder + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "valid input should pass validation; got error: {:?}", + result.err() + ); +} + +#[tokio::test] +async fn document_replace_builder_sign_succeeds_for_valid_input() { + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let owner_id = Identifier::random(); + let document = test_document(owner_id); + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 0).await; + + let builder = DocumentReplaceTransitionBuilder::new( + Arc::clone(&data_contract), + TEST_DOCUMENT_TYPE_NAME.to_string(), + document, + ); + + let result = builder + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "valid input should pass validation; got error: {:?}", + result.err() + ); +} + +#[tokio::test] +async fn document_purchase_builder_sign_succeeds_for_valid_input() { + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let owner_id = Identifier::random(); + let document = test_document(owner_id); + let purchaser_id = Identifier::random(); + let sdk = new_mock_sdk_with_contract_nonce(purchaser_id, data_contract.id(), 0).await; + + let builder = DocumentPurchaseTransitionBuilder::new( + Arc::clone(&data_contract), + TEST_DOCUMENT_TYPE_NAME.to_string(), + document, + purchaser_id, + 100, + ); + + let result = builder + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "valid input should pass validation; got error: {:?}", + result.err() + ); +} + +#[tokio::test] +async fn document_set_price_builder_sign_succeeds_for_valid_input() { + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let owner_id = Identifier::random(); + let document = test_document(owner_id); + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 0).await; + + let builder = DocumentSetPriceTransitionBuilder::new( + Arc::clone(&data_contract), + TEST_DOCUMENT_TYPE_NAME.to_string(), + document, + 200, + ); + + let result = builder + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "valid input should pass validation; got error: {:?}", + result.err() + ); +} + +#[tokio::test] +async fn document_transfer_builder_sign_succeeds_for_valid_input() { + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let owner_id = Identifier::random(); + let document = test_document(owner_id); + let recipient_id = Identifier::random(); + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 0).await; + + let builder = DocumentTransferTransitionBuilder::new( + Arc::clone(&data_contract), + TEST_DOCUMENT_TYPE_NAME.to_string(), + document, + recipient_id, + ); + + let result = builder + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "valid input should pass validation; got error: {:?}", + result.err() + ); +} From 6f7cd0b1df529300d0052e2798ba83d5c7454cbf Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Tue, 17 Mar 2026 01:31:07 -0500 Subject: [PATCH 13/14] test(sdk): add happy-path sign() tests for all token builders Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/platform/tokens/builders/tests.rs | 341 ++++++++++++++++++ 1 file changed, 341 insertions(+) diff --git a/packages/rs-sdk/src/platform/tokens/builders/tests.rs b/packages/rs-sdk/src/platform/tokens/builders/tests.rs index a30bab19423..0697bc04a88 100644 --- a/packages/rs-sdk/src/platform/tokens/builders/tests.rs +++ b/packages/rs-sdk/src/platform/tokens/builders/tests.rs @@ -798,3 +798,344 @@ async fn token_set_price_sign_returns_note_too_big_error() { result ); } + +// ── Happy-path sign() tests ──────────────────────────────────────────── + +#[tokio::test] +async fn token_mint_sign_succeeds_for_valid_input() { + let issuer_id = Identifier::random(); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let sdk = new_mock_sdk_with_contract_nonce(issuer_id, data_contract.id(), 0).await; + + let result = TokenMintTransitionBuilder::new(Arc::clone(&data_contract), 0, issuer_id, 1) + .issued_to_identity_id(Identifier::random()) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "valid input should pass validation; got error: {:?}", + result.err() + ); + let st = result.unwrap(); + assert!( + st.signature().is_some_and(|sig| !sig.is_empty()), + "transition should have a non-empty signature" + ); +} + +#[tokio::test] +async fn token_burn_sign_succeeds_for_valid_input() { + let owner_id = Identifier::random(); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 0).await; + + let result = TokenBurnTransitionBuilder::new(Arc::clone(&data_contract), 0, owner_id, 1) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "valid input should pass validation; got error: {:?}", + result.err() + ); + let st = result.unwrap(); + assert!( + st.signature().is_some_and(|sig| !sig.is_empty()), + "transition should have a non-empty signature" + ); +} + +#[tokio::test] +async fn token_transfer_sign_succeeds_for_valid_input() { + let sender_id = Identifier::random(); + let recipient_id = Identifier::random(); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let sdk = new_mock_sdk_with_contract_nonce(sender_id, data_contract.id(), 0).await; + + let result = TokenTransferTransitionBuilder::new( + Arc::clone(&data_contract), + 0, + sender_id, + recipient_id, + 1, + ) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "valid input should pass validation; got error: {:?}", + result.err() + ); + let st = result.unwrap(); + assert!( + st.signature().is_some_and(|sig| !sig.is_empty()), + "transition should have a non-empty signature" + ); +} + +#[tokio::test] +async fn token_freeze_sign_succeeds_for_valid_input() { + let actor_id = Identifier::random(); + let freeze_identity_id = Identifier::random(); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let sdk = new_mock_sdk_with_contract_nonce(actor_id, data_contract.id(), 0).await; + + let result = TokenFreezeTransitionBuilder::new( + Arc::clone(&data_contract), + 0, + actor_id, + freeze_identity_id, + ) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "valid input should pass validation; got error: {:?}", + result.err() + ); + let st = result.unwrap(); + assert!( + st.signature().is_some_and(|sig| !sig.is_empty()), + "transition should have a non-empty signature" + ); +} + +#[tokio::test] +async fn token_unfreeze_sign_succeeds_for_valid_input() { + let actor_id = Identifier::random(); + let unfreeze_identity_id = Identifier::random(); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let sdk = new_mock_sdk_with_contract_nonce(actor_id, data_contract.id(), 0).await; + + let result = TokenUnfreezeTransitionBuilder::new( + Arc::clone(&data_contract), + 0, + actor_id, + unfreeze_identity_id, + ) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "valid input should pass validation; got error: {:?}", + result.err() + ); + let st = result.unwrap(); + assert!( + st.signature().is_some_and(|sig| !sig.is_empty()), + "transition should have a non-empty signature" + ); +} + +#[tokio::test] +async fn token_destroy_sign_succeeds_for_valid_input() { + let actor_id = Identifier::random(); + let frozen_identity_id = Identifier::random(); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let sdk = new_mock_sdk_with_contract_nonce(actor_id, data_contract.id(), 0).await; + + let result = TokenDestroyFrozenFundsTransitionBuilder::new( + Arc::clone(&data_contract), + 0, + actor_id, + frozen_identity_id, + ) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "valid input should pass validation; got error: {:?}", + result.err() + ); + let st = result.unwrap(); + assert!( + st.signature().is_some_and(|sig| !sig.is_empty()), + "transition should have a non-empty signature" + ); +} + +#[tokio::test] +async fn token_emergency_action_sign_succeeds_for_valid_input() { + let actor_id = Identifier::random(); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let sdk = new_mock_sdk_with_contract_nonce(actor_id, data_contract.id(), 0).await; + + let result = + TokenEmergencyActionTransitionBuilder::pause(Arc::clone(&data_contract), 0, actor_id) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "valid input should pass validation; got error: {:?}", + result.err() + ); + let st = result.unwrap(); + assert!( + st.signature().is_some_and(|sig| !sig.is_empty()), + "transition should have a non-empty signature" + ); +} + +#[tokio::test] +async fn token_config_update_sign_succeeds_for_valid_input() { + let owner_id = Identifier::random(); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 0).await; + + let result = TokenConfigUpdateTransitionBuilder::new( + Arc::clone(&data_contract), + 0, + owner_id, + TokenConfigurationChangeItem::MintingAllowChoosingDestination(true), + ) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "valid input should pass validation; got error: {:?}", + result.err() + ); + let st = result.unwrap(); + assert!( + st.signature().is_some_and(|sig| !sig.is_empty()), + "transition should have a non-empty signature" + ); +} + +#[tokio::test] +async fn token_claim_sign_succeeds_for_valid_input() { + let owner_id = Identifier::random(); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let sdk = new_mock_sdk_with_contract_nonce(owner_id, data_contract.id(), 0).await; + + let result = TokenClaimTransitionBuilder::new( + Arc::clone(&data_contract), + 0, + owner_id, + TokenDistributionType::PreProgrammed, + ) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "valid input should pass validation; got error: {:?}", + result.err() + ); + let st = result.unwrap(); + assert!( + st.signature().is_some_and(|sig| !sig.is_empty()), + "transition should have a non-empty signature" + ); +} + +#[tokio::test] +async fn token_purchase_sign_succeeds_for_valid_input() { + let actor_id = Identifier::random(); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let sdk = new_mock_sdk_with_contract_nonce(actor_id, data_contract.id(), 0).await; + + let result = + TokenDirectPurchaseTransitionBuilder::new(Arc::clone(&data_contract), 0, actor_id, 1, 1000) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "valid input should pass validation; got error: {:?}", + result.err() + ); + let st = result.unwrap(); + assert!( + st.signature().is_some_and(|sig| !sig.is_empty()), + "transition should have a non-empty signature" + ); +} + +#[tokio::test] +async fn token_set_price_sign_succeeds_for_valid_input() { + let issuer_id = Identifier::random(); + let data_contract = test_data_contract(TEST_DOCUMENT_TYPE_NAME); + let sdk = new_mock_sdk_with_contract_nonce(issuer_id, data_contract.id(), 0).await; + + let result = TokenChangeDirectPurchasePriceTransitionBuilder::new( + Arc::clone(&data_contract), + 0, + issuer_id, + ) + .sign( + &sdk, + &test_identity_public_key(), + &TestSigner, + dpp::version::PlatformVersion::latest(), + ) + .await; + + assert!( + result.is_ok(), + "valid input should pass validation; got error: {:?}", + result.err() + ); + let st = result.unwrap(); + assert!( + st.signature().is_some_and(|sig| !sig.is_empty()), + "transition should have a non-empty signature" + ); +} From 6343a250d73f2eaf987365197087f343cbbcbd7b Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Tue, 17 Mar 2026 01:34:16 -0500 Subject: [PATCH 14/14] test(sdk): assert non-empty signature in document builder happy-path tests Co-Authored-By: Claude Opus 4.6 (1M context) --- .../platform/documents/transitions/tests.rs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/rs-sdk/src/platform/documents/transitions/tests.rs b/packages/rs-sdk/src/platform/documents/transitions/tests.rs index 575b9060422..fb109d8aa71 100644 --- a/packages/rs-sdk/src/platform/documents/transitions/tests.rs +++ b/packages/rs-sdk/src/platform/documents/transitions/tests.rs @@ -254,6 +254,11 @@ async fn document_delete_builder_sign_succeeds_for_valid_input() { "valid input should pass validation; got error: {:?}", result.err() ); + let st = result.unwrap(); + assert!( + st.signature().is_some_and(|sig| !sig.is_empty()), + "transition should have a non-empty signature" + ); } #[tokio::test] @@ -284,6 +289,11 @@ async fn document_create_builder_sign_succeeds_for_valid_input() { "valid input should pass validation; got error: {:?}", result.err() ); + let st = result.unwrap(); + assert!( + st.signature().is_some_and(|sig| !sig.is_empty()), + "transition should have a non-empty signature" + ); } #[tokio::test] @@ -313,6 +323,11 @@ async fn document_replace_builder_sign_succeeds_for_valid_input() { "valid input should pass validation; got error: {:?}", result.err() ); + let st = result.unwrap(); + assert!( + st.signature().is_some_and(|sig| !sig.is_empty()), + "transition should have a non-empty signature" + ); } #[tokio::test] @@ -345,6 +360,11 @@ async fn document_purchase_builder_sign_succeeds_for_valid_input() { "valid input should pass validation; got error: {:?}", result.err() ); + let st = result.unwrap(); + assert!( + st.signature().is_some_and(|sig| !sig.is_empty()), + "transition should have a non-empty signature" + ); } #[tokio::test] @@ -375,6 +395,11 @@ async fn document_set_price_builder_sign_succeeds_for_valid_input() { "valid input should pass validation; got error: {:?}", result.err() ); + let st = result.unwrap(); + assert!( + st.signature().is_some_and(|sig| !sig.is_empty()), + "transition should have a non-empty signature" + ); } #[tokio::test] @@ -406,4 +431,9 @@ async fn document_transfer_builder_sign_succeeds_for_valid_input() { "valid input should pass validation; got error: {:?}", result.err() ); + let st = result.unwrap(); + assert!( + st.signature().is_some_and(|sig| !sig.is_empty()), + "transition should have a non-empty signature" + ); }