Skip to main content

dpp/state_transition/
mod.rs

1use derive_more::From;
2#[cfg(feature = "serde-conversion")]
3use serde::{Deserialize, Serialize};
4use state_transitions::document::batch_transition::batched_transition::document_transition::DocumentTransition;
5use std::collections::BTreeMap;
6use std::ops::RangeInclusive;
7
8pub use abstract_state_transition::state_transition_helpers;
9
10use platform_value::{BinaryData, Identifier};
11pub use state_transition_types::*;
12
13use bincode::{Decode, Encode};
14#[cfg(any(
15    feature = "state-transition-signing",
16    feature = "state-transition-validation"
17))]
18use dashcore::signer;
19#[cfg(feature = "state-transition-validation")]
20use dashcore::signer::double_sha;
21use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable};
22use platform_version::version::{PlatformVersion, ProtocolVersion, ALL_VERSIONS, LATEST_VERSION};
23
24mod abstract_state_transition;
25#[cfg(any(
26    feature = "state-transition-signing",
27    feature = "state-transition-validation"
28))]
29use crate::BlsModule;
30use crate::ProtocolError;
31
32mod state_transition_types;
33
34pub mod state_transition_factory;
35
36pub mod errors;
37#[cfg(feature = "state-transition-signing")]
38use crate::util::hash::ripemd160_sha256;
39use crate::util::hash::{hash_double_to_vec, hash_single};
40
41pub mod proof_result;
42mod serialization;
43pub mod state_transitions;
44mod traits;
45
46// pub mod state_transition_fee;
47
48#[cfg(feature = "state-transition-validation")]
49use crate::consensus::basic::UnsupportedFeatureError;
50#[cfg(feature = "state-transition-signing")]
51use crate::consensus::signature::InvalidSignaturePublicKeySecurityLevelError;
52#[cfg(feature = "state-transition-validation")]
53use crate::consensus::signature::{
54    InvalidStateTransitionSignatureError, PublicKeyIsDisabledError, SignatureError,
55};
56#[cfg(feature = "state-transition-validation")]
57use crate::consensus::ConsensusError;
58pub use traits::*;
59
60use crate::address_funds::PlatformAddress;
61use crate::data_contract::serialized_version::DataContractInSerializationFormat;
62use crate::fee::Credits;
63#[cfg(any(
64    feature = "state-transition-signing",
65    feature = "state-transition-validation"
66))]
67use crate::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0;
68#[cfg(feature = "state-transition-signing")]
69use crate::identity::signer::Signer;
70use crate::identity::state_transition::OptionallyAssetLockProved;
71use crate::identity::Purpose;
72#[cfg(any(
73    feature = "state-transition-signing",
74    feature = "state-transition-validation"
75))]
76use crate::identity::{IdentityPublicKey, KeyType};
77use crate::identity::{KeyID, SecurityLevel};
78use crate::prelude::{AddressNonce, AssetLockProof, UserFeeIncrease};
79use crate::serialization::{PlatformDeserializable, Signable};
80use crate::state_transition::address_credit_withdrawal_transition::{
81    AddressCreditWithdrawalTransition, AddressCreditWithdrawalTransitionSignable,
82};
83use crate::state_transition::address_funding_from_asset_lock_transition::{
84    AddressFundingFromAssetLockTransition, AddressFundingFromAssetLockTransitionSignable,
85};
86use crate::state_transition::address_funds_transfer_transition::{
87    AddressFundsTransferTransition, AddressFundsTransferTransitionSignable,
88};
89use crate::state_transition::batch_transition::accessors::DocumentsBatchTransitionAccessorsV0;
90use crate::state_transition::batch_transition::batched_transition::BatchedTransitionRef;
91#[cfg(feature = "state-transition-signing")]
92use crate::state_transition::batch_transition::resolvers::v0::BatchTransitionResolversV0;
93use crate::state_transition::batch_transition::{BatchTransition, BatchTransitionSignable};
94use crate::state_transition::data_contract_create_transition::accessors::DataContractCreateTransitionAccessorsV0;
95use crate::state_transition::data_contract_create_transition::{
96    DataContractCreateTransition, DataContractCreateTransitionSignable,
97};
98use crate::state_transition::data_contract_update_transition::accessors::DataContractUpdateTransitionAccessorsV0;
99use crate::state_transition::data_contract_update_transition::{
100    DataContractUpdateTransition, DataContractUpdateTransitionSignable,
101};
102#[cfg(feature = "state-transition-signing")]
103use crate::state_transition::errors::InvalidSignaturePublicKeyError;
104#[cfg(all(feature = "state-transitions", feature = "validation"))]
105use crate::state_transition::errors::StateTransitionError::StateTransitionIsNotActiveError;
106#[cfg(feature = "state-transition-signing")]
107use crate::state_transition::errors::WrongPublicKeyPurposeError;
108#[cfg(feature = "state-transition-validation")]
109use crate::state_transition::errors::{
110    InvalidIdentityPublicKeyTypeError, PublicKeyMismatchError, StateTransitionIsNotSignedError,
111};
112use crate::state_transition::identity_create_from_addresses_transition::{
113    IdentityCreateFromAddressesTransition, IdentityCreateFromAddressesTransitionSignable,
114};
115use crate::state_transition::identity_create_transition::{
116    IdentityCreateTransition, IdentityCreateTransitionSignable,
117};
118use crate::state_transition::identity_credit_transfer_to_addresses_transition::{
119    IdentityCreditTransferToAddressesTransition,
120    IdentityCreditTransferToAddressesTransitionSignable,
121};
122use crate::state_transition::identity_credit_transfer_transition::{
123    IdentityCreditTransferTransition, IdentityCreditTransferTransitionSignable,
124};
125use crate::state_transition::identity_credit_withdrawal_transition::{
126    IdentityCreditWithdrawalTransition, IdentityCreditWithdrawalTransitionSignable,
127};
128use crate::state_transition::identity_topup_from_addresses_transition::{
129    IdentityTopUpFromAddressesTransition, IdentityTopUpFromAddressesTransitionSignable,
130};
131use crate::state_transition::identity_topup_transition::{
132    IdentityTopUpTransition, IdentityTopUpTransitionSignable,
133};
134use crate::state_transition::identity_update_transition::{
135    IdentityUpdateTransition, IdentityUpdateTransitionSignable,
136};
137use crate::state_transition::masternode_vote_transition::MasternodeVoteTransition;
138use crate::state_transition::masternode_vote_transition::MasternodeVoteTransitionSignable;
139use crate::state_transition::shield_from_asset_lock_transition::{
140    ShieldFromAssetLockTransition, ShieldFromAssetLockTransitionSignable,
141};
142use crate::state_transition::shield_transition::{ShieldTransition, ShieldTransitionSignable};
143use crate::state_transition::shielded_transfer_transition::{
144    ShieldedTransferTransition, ShieldedTransferTransitionSignable,
145};
146use crate::state_transition::shielded_withdrawal_transition::{
147    ShieldedWithdrawalTransition, ShieldedWithdrawalTransitionSignable,
148};
149#[cfg(feature = "state-transition-signing")]
150use crate::state_transition::state_transitions::document::batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0;
151use crate::state_transition::unshield_transition::{
152    UnshieldTransition, UnshieldTransitionSignable,
153};
154use state_transitions::document::batch_transition::batched_transition::token_transition::TokenTransition;
155pub use state_transitions::*;
156
157pub type GetDataContractSecurityLevelRequirementFn =
158    fn(Identifier, String) -> Result<SecurityLevel, ProtocolError>;
159
160macro_rules! call_method {
161    ($state_transition:expr, $method:ident, $args:tt ) => {
162        match $state_transition {
163            StateTransition::DataContractCreate(st) => st.$method($args),
164            StateTransition::DataContractUpdate(st) => st.$method($args),
165            StateTransition::Batch(st) => st.$method($args),
166            StateTransition::IdentityCreate(st) => st.$method($args),
167            StateTransition::IdentityTopUp(st) => st.$method($args),
168            StateTransition::IdentityCreditWithdrawal(st) => st.$method($args),
169            StateTransition::IdentityUpdate(st) => st.$method($args),
170            StateTransition::IdentityCreditTransfer(st) => st.$method($args),
171            StateTransition::MasternodeVote(st) => st.$method($args),
172            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method($args),
173            StateTransition::IdentityCreateFromAddresses(st) => st.$method($args),
174            StateTransition::IdentityTopUpFromAddresses(st) => st.$method($args),
175            StateTransition::AddressFundsTransfer(st) => st.$method($args),
176            StateTransition::AddressFundingFromAssetLock(st) => st.$method($args),
177            StateTransition::AddressCreditWithdrawal(st) => st.$method($args),
178            StateTransition::Shield(st) => st.$method($args),
179            StateTransition::ShieldedTransfer(st) => st.$method($args),
180            StateTransition::Unshield(st) => st.$method($args),
181            StateTransition::ShieldFromAssetLock(st) => st.$method($args),
182            StateTransition::ShieldedWithdrawal(st) => st.$method($args),
183        }
184    };
185    ($state_transition:expr, $method:ident ) => {
186        match $state_transition {
187            StateTransition::DataContractCreate(st) => st.$method(),
188            StateTransition::DataContractUpdate(st) => st.$method(),
189            StateTransition::Batch(st) => st.$method(),
190            StateTransition::IdentityCreate(st) => st.$method(),
191            StateTransition::IdentityTopUp(st) => st.$method(),
192            StateTransition::IdentityCreditWithdrawal(st) => st.$method(),
193            StateTransition::IdentityUpdate(st) => st.$method(),
194            StateTransition::IdentityCreditTransfer(st) => st.$method(),
195            StateTransition::MasternodeVote(st) => st.$method(),
196            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method(),
197            StateTransition::IdentityCreateFromAddresses(st) => st.$method(),
198            StateTransition::IdentityTopUpFromAddresses(st) => st.$method(),
199            StateTransition::AddressFundsTransfer(st) => st.$method(),
200            StateTransition::AddressFundingFromAssetLock(st) => st.$method(),
201            StateTransition::AddressCreditWithdrawal(st) => st.$method(),
202            StateTransition::Shield(st) => st.$method(),
203            StateTransition::ShieldedTransfer(st) => st.$method(),
204            StateTransition::Unshield(st) => st.$method(),
205            StateTransition::ShieldFromAssetLock(st) => st.$method(),
206            StateTransition::ShieldedWithdrawal(st) => st.$method(),
207        }
208    };
209}
210
211macro_rules! call_getter_method_identity_signed {
212    ($state_transition:expr, $method:ident, $args:tt ) => {
213        match $state_transition {
214            StateTransition::DataContractCreate(st) => Some(st.$method($args)),
215            StateTransition::DataContractUpdate(st) => Some(st.$method($args)),
216            StateTransition::Batch(st) => Some(st.$method($args)),
217            StateTransition::IdentityCreate(_) => None,
218            StateTransition::IdentityTopUp(_) => None,
219            StateTransition::IdentityCreditWithdrawal(st) => Some(st.$method($args)),
220            StateTransition::IdentityUpdate(st) => Some(st.$method($args)),
221            StateTransition::IdentityCreditTransfer(st) => Some(st.$method($args)),
222            StateTransition::MasternodeVote(st) => Some(st.$method($args)),
223            StateTransition::IdentityCreditTransferToAddresses(st) => Some(st.$method($args)),
224            StateTransition::IdentityCreateFromAddresses(_) => None,
225            StateTransition::IdentityTopUpFromAddresses(_) => None,
226            StateTransition::AddressFundsTransfer(_) => None,
227            StateTransition::AddressFundingFromAssetLock(_) => None,
228            StateTransition::AddressCreditWithdrawal(_) => None,
229            StateTransition::Shield(_) => None,
230            StateTransition::ShieldedTransfer(_) => None,
231            StateTransition::Unshield(_) => None,
232            StateTransition::ShieldFromAssetLock(_) => None,
233            StateTransition::ShieldedWithdrawal(_) => None,
234        }
235    };
236    ($state_transition:expr, $method:ident ) => {
237        match $state_transition {
238            StateTransition::DataContractCreate(st) => Some(st.$method()),
239            StateTransition::DataContractUpdate(st) => Some(st.$method()),
240            StateTransition::Batch(st) => Some(st.$method()),
241            StateTransition::IdentityCreate(_) => None,
242            StateTransition::IdentityTopUp(_) => None,
243            StateTransition::IdentityCreditWithdrawal(st) => Some(st.$method()),
244            StateTransition::IdentityUpdate(st) => Some(st.$method()),
245            StateTransition::IdentityCreditTransfer(st) => Some(st.$method()),
246            StateTransition::MasternodeVote(st) => Some(st.$method()),
247            StateTransition::IdentityCreditTransferToAddresses(st) => Some(st.$method()),
248            StateTransition::IdentityCreateFromAddresses(_) => None,
249            StateTransition::IdentityTopUpFromAddresses(_) => None,
250            StateTransition::AddressFundsTransfer(_) => None,
251            StateTransition::AddressFundingFromAssetLock(_) => None,
252            StateTransition::AddressCreditWithdrawal(_) => None,
253            StateTransition::Shield(_) => None,
254            StateTransition::ShieldedTransfer(_) => None,
255            StateTransition::Unshield(_) => None,
256            StateTransition::ShieldFromAssetLock(_) => None,
257            StateTransition::ShieldedWithdrawal(_) => None,
258        }
259    };
260}
261
262macro_rules! call_method_identity_signed {
263    ($state_transition:expr, $method:ident, $args:tt ) => {
264        match $state_transition {
265            StateTransition::DataContractCreate(st) => st.$method($args),
266            StateTransition::DataContractUpdate(st) => st.$method($args),
267            StateTransition::Batch(st) => st.$method($args),
268            StateTransition::IdentityCreate(_st) => {}
269            StateTransition::IdentityTopUp(_st) => {}
270            StateTransition::IdentityCreditWithdrawal(st) => st.$method($args),
271            StateTransition::IdentityUpdate(st) => st.$method($args),
272            StateTransition::IdentityCreditTransfer(st) => st.$method($args),
273            StateTransition::MasternodeVote(st) => st.$method($args),
274            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method($args),
275            StateTransition::IdentityCreateFromAddresses(_) => {}
276            StateTransition::IdentityTopUpFromAddresses(_) => {}
277            StateTransition::AddressFundsTransfer(_) => {}
278            StateTransition::AddressFundingFromAssetLock(_) => {}
279            StateTransition::AddressCreditWithdrawal(_) => {}
280            StateTransition::Shield(_) => {}
281            StateTransition::ShieldedTransfer(_) => {}
282            StateTransition::Unshield(_) => {}
283            StateTransition::ShieldFromAssetLock(_) => {}
284            StateTransition::ShieldedWithdrawal(_) => {}
285        }
286    };
287    ($state_transition:expr, $method:ident ) => {
288        match $state_transition {
289            StateTransition::DataContractCreate(st) => st.$method(),
290            StateTransition::DataContractUpdate(st) => st.$method(),
291            StateTransition::Batch(st) => st.$method(),
292            StateTransition::IdentityCreate(st) => {}
293            StateTransition::IdentityTopUp(st) => {}
294            StateTransition::IdentityCreditWithdrawal(st) => st.$method(),
295            StateTransition::IdentityUpdate(st) => st.$method(),
296            StateTransition::IdentityCreditTransfer(st) => st.$method(),
297            StateTransition::MasternodeVote(st) => st.$method(),
298            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method(),
299            StateTransition::IdentityCreateFromAddresses(_) => {}
300            StateTransition::IdentityTopUpFromAddresses(_) => {}
301            StateTransition::AddressFundsTransfer(_) => {}
302            StateTransition::AddressFundingFromAssetLock(_) => {}
303            StateTransition::AddressCreditWithdrawal(_) => {}
304            StateTransition::Shield(_) => {}
305            StateTransition::ShieldedTransfer(_) => {}
306            StateTransition::Unshield(_) => {}
307            StateTransition::ShieldFromAssetLock(_) => {}
308            StateTransition::ShieldedWithdrawal(_) => {}
309        }
310    };
311}
312
313#[cfg(feature = "state-transition-signing")]
314macro_rules! call_errorable_method_identity_signed {
315    ($state_transition:expr, $method:ident, $( $arg:expr ),* ) => {
316        match $state_transition {
317            StateTransition::DataContractCreate(st) => st.$method($( $arg ),*),
318            StateTransition::DataContractUpdate(st) => st.$method($( $arg ),*),
319            StateTransition::Batch(st) => st.$method($( $arg ),*),
320            StateTransition::IdentityCreate(_) => Err(ProtocolError::CorruptedCodeExecution(
321                "identity create can not be called for identity signing".to_string(),
322            )),
323            StateTransition::IdentityTopUp(_) => Err(ProtocolError::CorruptedCodeExecution(
324                "identity top up can not be called for identity signing".to_string(),
325            )),
326            StateTransition::IdentityCreditWithdrawal(st) => st.$method($( $arg ),*),
327            StateTransition::IdentityUpdate(st) => st.$method($( $arg ),*),
328            StateTransition::IdentityCreditTransfer(st) => st.$method($( $arg ),*),
329            StateTransition::MasternodeVote(st) => st.$method($( $arg ),*),
330            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method($( $arg ),*),
331            StateTransition::IdentityCreateFromAddresses(_) => Err(ProtocolError::CorruptedCodeExecution(
332                "identity create from addresses can not be called for identity signing".to_string(),
333            )),
334            StateTransition::IdentityTopUpFromAddresses(_) => Err(ProtocolError::CorruptedCodeExecution(
335                "identity top up from addresses can not be called for identity signing".to_string(),
336            )),
337            StateTransition::AddressFundsTransfer(_) => Err(ProtocolError::CorruptedCodeExecution(
338                "address funds transfer can not be called for identity signing".to_string(),
339            )),
340            StateTransition::AddressFundingFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution(
341                "address funding from asset lock can not be called for identity signing".to_string(),
342            )),
343            StateTransition::AddressCreditWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution(
344                "address credit withdrawal can not be called for identity signing".to_string(),
345            )),
346            StateTransition::Shield(_) => Err(ProtocolError::CorruptedCodeExecution(
347                "shield transition can not be called for identity signing".to_string(),
348            )),
349            StateTransition::ShieldedTransfer(_) => Err(ProtocolError::CorruptedCodeExecution(
350                "shielded transfer transition can not be called for identity signing".to_string(),
351            )),
352            StateTransition::Unshield(_) => Err(ProtocolError::CorruptedCodeExecution(
353                "unshield transition can not be called for identity signing".to_string(),
354            )),
355            StateTransition::ShieldFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution(
356                "shield from asset lock transition can not be called for identity signing".to_string(),
357            )),
358            StateTransition::ShieldedWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution(
359                "shielded withdrawal transition can not be called for identity signing".to_string(),
360            )),
361        }
362    };
363    ($state_transition:expr, $method:ident) => {
364        match $state_transition {
365            StateTransition::DataContractCreate(st) => st.$method(),
366            StateTransition::DataContractUpdate(st) => st.$method(),
367            StateTransition::Batch(st) => st.$method(),
368            StateTransition::IdentityCreate(_) => Err(ProtocolError::CorruptedCodeExecution(
369                "identity create can not be called for identity signing".to_string(),
370            )),
371            StateTransition::IdentityTopUp(_) => Err(ProtocolError::CorruptedCodeExecution(
372                "identity top up can not be called for identity signing".to_string(),
373            )),
374            StateTransition::IdentityCreditWithdrawal(st) => st.$method(),
375            StateTransition::IdentityUpdate(st) => st.$method(),
376            StateTransition::IdentityCreditTransfer(st) => st.$method(),
377            StateTransition::MasternodeVote(st) => st.$method(),
378            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method(),
379            StateTransition::IdentityCreateFromAddresses(st) => Err(ProtocolError::CorruptedCodeExecution(
380                "identity create from addresses can not be called for identity signing".to_string(),
381            )),
382            StateTransition::IdentityTopUpFromAddresses(_) => Err(ProtocolError::CorruptedCodeExecution(
383                "identity top up from addresses can not be called for identity signing".to_string(),
384            )),
385            StateTransition::AddressFundsTransfer(_) => Err(ProtocolError::CorruptedCodeExecution(
386                "address funds transfer can not be called for identity signing".to_string(),
387            )),
388            StateTransition::AddressFundingFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution(
389                "address funding from asset lock can not be called for identity signing".to_string(),
390            )),
391            StateTransition::AddressCreditWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution(
392                "address credit withdrawal can not be called for identity signing".to_string(),
393            )),
394            StateTransition::Shield(_) => Err(ProtocolError::CorruptedCodeExecution(
395                "shield transition can not be called for identity signing".to_string(),
396            )),
397            StateTransition::ShieldedTransfer(_) => Err(ProtocolError::CorruptedCodeExecution(
398                "shielded transfer transition can not be called for identity signing".to_string(),
399            )),
400            StateTransition::Unshield(_) => Err(ProtocolError::CorruptedCodeExecution(
401                "unshield transition can not be called for identity signing".to_string(),
402            )),
403            StateTransition::ShieldFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution(
404                "shield from asset lock transition can not be called for identity signing".to_string(),
405            )),
406            StateTransition::ShieldedWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution(
407                "shielded withdrawal transition can not be called for identity signing".to_string(),
408            )),
409        }
410    };
411}
412
413#[derive(
414    Debug,
415    Clone,
416    Encode,
417    Decode,
418    PlatformSerialize,
419    PlatformDeserialize,
420    PlatformSignable,
421    From,
422    PartialEq,
423)]
424#[cfg_attr(
425    feature = "serde-conversion",
426    derive(Serialize, Deserialize),
427    serde(untagged)
428)]
429#[platform_serialize(unversioned)] //versioned directly, no need to use platform_version
430#[platform_serialize(limit = 100000)]
431pub enum StateTransition {
432    DataContractCreate(DataContractCreateTransition),
433    DataContractUpdate(DataContractUpdateTransition),
434    Batch(BatchTransition),
435    IdentityCreate(IdentityCreateTransition),
436    IdentityTopUp(IdentityTopUpTransition),
437    IdentityCreditWithdrawal(IdentityCreditWithdrawalTransition),
438    IdentityUpdate(IdentityUpdateTransition),
439    IdentityCreditTransfer(IdentityCreditTransferTransition),
440    MasternodeVote(MasternodeVoteTransition),
441    IdentityCreditTransferToAddresses(IdentityCreditTransferToAddressesTransition),
442    IdentityCreateFromAddresses(IdentityCreateFromAddressesTransition),
443    IdentityTopUpFromAddresses(IdentityTopUpFromAddressesTransition),
444    AddressFundsTransfer(AddressFundsTransferTransition),
445    AddressFundingFromAssetLock(AddressFundingFromAssetLockTransition),
446    AddressCreditWithdrawal(AddressCreditWithdrawalTransition),
447    Shield(ShieldTransition),
448    ShieldedTransfer(ShieldedTransferTransition),
449    Unshield(UnshieldTransition),
450    ShieldFromAssetLock(ShieldFromAssetLockTransition),
451    ShieldedWithdrawal(ShieldedWithdrawalTransition),
452}
453
454impl OptionallyAssetLockProved for StateTransition {
455    fn optional_asset_lock_proof(&self) -> Option<&AssetLockProof> {
456        match self {
457            StateTransition::IdentityCreate(st) => st.optional_asset_lock_proof(),
458            StateTransition::IdentityTopUp(st) => st.optional_asset_lock_proof(),
459            StateTransition::ShieldFromAssetLock(st) => st.optional_asset_lock_proof(),
460            _ => None,
461        }
462    }
463}
464
465/// The state transition signing options
466#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
467pub struct StateTransitionSigningOptions {
468    /// This will allow signing with any security level for debugging purposes
469    pub allow_signing_with_any_security_level: bool,
470    /// This will allow signing with any purpose for debugging purposes
471    pub allow_signing_with_any_purpose: bool,
472}
473
474impl StateTransition {
475    #[allow(unused_variables)]
476    pub fn deserialize_from_bytes_in_version(
477        bytes: &[u8],
478        platform_version: &PlatformVersion,
479    ) -> Result<Self, ProtocolError> {
480        let state_transition = StateTransition::deserialize_from_bytes(bytes)?;
481        #[cfg(all(feature = "state-transitions", feature = "validation"))]
482        {
483            let active_version_range = state_transition.active_version_range();
484
485            // Tests are done with very high protocol ranges, while we could put this behind a feature,
486            // that would probably be overkill.
487            if active_version_range.contains(&platform_version.protocol_version)
488                || platform_version.protocol_version > 268435456
489            {
490                Ok(state_transition)
491            } else {
492                Err(ProtocolError::StateTransitionError(
493                    StateTransitionIsNotActiveError {
494                        state_transition_type: state_transition.name(),
495                        active_version_range,
496                        current_protocol_version: platform_version.protocol_version,
497                    },
498                ))
499            }
500        }
501        #[cfg(not(all(feature = "state-transitions", feature = "validation")))]
502        Ok(state_transition)
503    }
504
505    pub fn active_version_range(&self) -> RangeInclusive<ProtocolVersion> {
506        match self {
507            StateTransition::DataContractCreate(data_contract_create_transition) => {
508                match data_contract_create_transition.data_contract() {
509                    DataContractInSerializationFormat::V0(_) => ALL_VERSIONS,
510                    DataContractInSerializationFormat::V1(_) => 9..=LATEST_VERSION,
511                }
512            }
513            StateTransition::DataContractUpdate(data_contract_update_transition) => {
514                match data_contract_update_transition.data_contract() {
515                    DataContractInSerializationFormat::V0(_) => ALL_VERSIONS,
516                    DataContractInSerializationFormat::V1(_) => 9..=LATEST_VERSION,
517                }
518            }
519            StateTransition::Batch(batch_transition) => match batch_transition {
520                BatchTransition::V0(_) => ALL_VERSIONS,
521                BatchTransition::V1(_) => 9..=LATEST_VERSION,
522            },
523            StateTransition::IdentityCreate(_)
524            | StateTransition::IdentityTopUp(_)
525            | StateTransition::IdentityCreditWithdrawal(_)
526            | StateTransition::IdentityUpdate(_)
527            | StateTransition::IdentityCreditTransfer(_)
528            | StateTransition::MasternodeVote(_) => ALL_VERSIONS,
529            StateTransition::IdentityCreditTransferToAddresses(_)
530            | StateTransition::IdentityCreateFromAddresses(_)
531            | StateTransition::IdentityTopUpFromAddresses(_)
532            | StateTransition::AddressFundsTransfer(_)
533            | StateTransition::AddressFundingFromAssetLock(_)
534            | StateTransition::AddressCreditWithdrawal(_) => 11..=LATEST_VERSION,
535            StateTransition::Shield(_)
536            | StateTransition::ShieldedTransfer(_)
537            | StateTransition::Unshield(_)
538            | StateTransition::ShieldFromAssetLock(_)
539            | StateTransition::ShieldedWithdrawal(_) => 12..=LATEST_VERSION,
540        }
541    }
542
543    pub fn is_identity_signed(&self) -> bool {
544        !matches!(
545            self,
546            StateTransition::IdentityCreate(_)
547                | StateTransition::IdentityTopUp(_)
548                | StateTransition::Shield(_)
549                | StateTransition::ShieldedTransfer(_)
550                | StateTransition::Unshield(_)
551                | StateTransition::ShieldFromAssetLock(_)
552                | StateTransition::ShieldedWithdrawal(_)
553        )
554    }
555
556    pub fn required_asset_lock_balance_for_processing_start(
557        &self,
558        platform_version: &PlatformVersion,
559    ) -> Result<Credits, ProtocolError> {
560        match self {
561            StateTransition::IdentityCreate(st) => {
562                st.calculate_min_required_fee(platform_version)
563            }
564            StateTransition::IdentityTopUp(st) => {
565                st.calculate_min_required_fee(platform_version)
566            }
567            StateTransition::AddressFundingFromAssetLock(st) => {
568                st.calculate_min_required_fee(platform_version)
569            }
570            StateTransition::ShieldFromAssetLock(st) => {
571                st.calculate_min_required_fee(platform_version)
572            }
573            st => Err(ProtocolError::CorruptedCodeExecution(format!("{} is not an asset lock transaction, but we are calling required_asset_lock_balance_for_processing_start", st.name()))),
574        }
575    }
576
577    fn hash(&self, skip_signature: bool) -> Result<Vec<u8>, ProtocolError> {
578        if skip_signature {
579            Ok(hash_double_to_vec(self.signable_bytes()?))
580        } else {
581            Ok(hash_double_to_vec(
582                crate::serialization::PlatformSerializable::serialize_to_bytes(self)?,
583            ))
584        }
585    }
586
587    /// Returns state transition name
588    pub fn name(&self) -> String {
589        match self {
590            Self::DataContractCreate(_) => "DataContractCreate".to_string(),
591            Self::DataContractUpdate(_) => "DataContractUpdate".to_string(),
592            Self::Batch(batch_transition) => {
593                let mut document_transition_types = vec![];
594                for transition in batch_transition.transitions_iter() {
595                    let document_transition_name = match transition {
596                        BatchedTransitionRef::Document(DocumentTransition::Create(_)) => "Create",
597                        BatchedTransitionRef::Document(DocumentTransition::Replace(_)) => "Replace",
598                        BatchedTransitionRef::Document(DocumentTransition::Delete(_)) => "Delete",
599                        BatchedTransitionRef::Document(DocumentTransition::Transfer(_)) => {
600                            "Transfer"
601                        }
602                        BatchedTransitionRef::Document(DocumentTransition::UpdatePrice(_)) => {
603                            "UpdatePrice"
604                        }
605                        BatchedTransitionRef::Document(DocumentTransition::Purchase(_)) => {
606                            "Purchase"
607                        }
608                        BatchedTransitionRef::Token(TokenTransition::Transfer(_)) => {
609                            "TokenTransfer"
610                        }
611                        BatchedTransitionRef::Token(TokenTransition::Mint(_)) => "TokenMint",
612                        BatchedTransitionRef::Token(TokenTransition::Burn(_)) => "TokenBurn",
613                        BatchedTransitionRef::Token(TokenTransition::Freeze(_)) => "TokenFreeze",
614                        BatchedTransitionRef::Token(TokenTransition::Unfreeze(_)) => {
615                            "TokenUnfreeze"
616                        }
617                        BatchedTransitionRef::Token(TokenTransition::DestroyFrozenFunds(_)) => {
618                            "TokenDestroyFrozenFunds"
619                        }
620                        BatchedTransitionRef::Token(TokenTransition::EmergencyAction(_)) => {
621                            "TokenEmergencyAction"
622                        }
623                        BatchedTransitionRef::Token(TokenTransition::ConfigUpdate(_)) => {
624                            "TokenConfigUpdate"
625                        }
626                        BatchedTransitionRef::Token(TokenTransition::Claim(_)) => "TokenClaim",
627                        BatchedTransitionRef::Token(TokenTransition::DirectPurchase(_)) => {
628                            "TokenDirectPurchase"
629                        }
630                        BatchedTransitionRef::Token(
631                            TokenTransition::SetPriceForDirectPurchase(_),
632                        ) => "SetPriceForDirectPurchase",
633                    };
634                    document_transition_types.push(document_transition_name);
635                }
636                format!("DocumentsBatch([{}])", document_transition_types.join(", "))
637            }
638            Self::IdentityCreate(_) => "IdentityCreate".to_string(),
639            Self::IdentityTopUp(_) => "IdentityTopUp".to_string(),
640            Self::IdentityCreditWithdrawal(_) => "IdentityCreditWithdrawal".to_string(),
641            Self::IdentityUpdate(_) => "IdentityUpdate".to_string(),
642            Self::IdentityCreditTransfer(_) => "IdentityCreditTransfer".to_string(),
643            Self::MasternodeVote(_) => "MasternodeVote".to_string(),
644            Self::IdentityCreditTransferToAddresses(_) => {
645                "IdentityCreditTransferToAddresses".to_string()
646            }
647            Self::IdentityCreateFromAddresses(_) => "IdentityCreateFromAddresses".to_string(),
648            Self::IdentityTopUpFromAddresses(_) => "IdentityTopUpFromAddresses".to_string(),
649            Self::AddressFundsTransfer(_) => "AddressFundsTransfer".to_string(),
650            Self::AddressFundingFromAssetLock(_) => "AddressFundingFromAssetLock".to_string(),
651            Self::AddressCreditWithdrawal(_) => "AddressCreditWithdrawal".to_string(),
652            Self::Shield(_) => "Shield".to_string(),
653            Self::ShieldedTransfer(_) => "ShieldedTransfer".to_string(),
654            Self::Unshield(_) => "Unshield".to_string(),
655            Self::ShieldFromAssetLock(_) => "ShieldFromAssetLock".to_string(),
656            Self::ShieldedWithdrawal(_) => "ShieldedWithdrawal".to_string(),
657        }
658    }
659
660    /// returns the signature as a byte-array
661    pub fn signature(&self) -> Option<&BinaryData> {
662        match self {
663            StateTransition::DataContractCreate(st) => Some(st.signature()),
664            StateTransition::DataContractUpdate(st) => Some(st.signature()),
665            StateTransition::Batch(st) => Some(st.signature()),
666            StateTransition::IdentityCreate(st) => Some(st.signature()),
667            StateTransition::IdentityTopUp(st) => Some(st.signature()),
668            StateTransition::IdentityCreditWithdrawal(st) => Some(st.signature()),
669            StateTransition::IdentityUpdate(st) => Some(st.signature()),
670            StateTransition::IdentityCreditTransfer(st) => Some(st.signature()),
671            StateTransition::MasternodeVote(st) => Some(st.signature()),
672            StateTransition::IdentityCreditTransferToAddresses(st) => Some(st.signature()),
673            StateTransition::IdentityCreateFromAddresses(_) => None,
674            StateTransition::IdentityTopUpFromAddresses(_) => None,
675            StateTransition::AddressFundsTransfer(_) => None,
676            StateTransition::AddressFundingFromAssetLock(st) => Some(st.signature()),
677            StateTransition::AddressCreditWithdrawal(_) => None,
678            StateTransition::Shield(_) => None,
679            StateTransition::ShieldedTransfer(_) => None,
680            StateTransition::Unshield(_) => None,
681            StateTransition::ShieldFromAssetLock(st) => Some(st.signature()),
682            StateTransition::ShieldedWithdrawal(_) => None,
683        }
684    }
685
686    /// returns the number of private keys
687    pub fn required_number_of_private_keys(&self) -> u16 {
688        match self {
689            StateTransition::IdentityCreateFromAddresses(st) => st.inputs().len() as u16,
690            StateTransition::IdentityTopUpFromAddresses(st) => st.inputs().len() as u16,
691            StateTransition::AddressFundsTransfer(st) => st.inputs().len() as u16,
692            StateTransition::AddressCreditWithdrawal(st) => st.inputs().len() as u16,
693            StateTransition::Shield(st) => st.inputs().len() as u16,
694            StateTransition::ShieldedTransfer(_) => 0,
695            StateTransition::Unshield(_) => 0,
696            StateTransition::ShieldFromAssetLock(_) => 0,
697            StateTransition::ShieldedWithdrawal(_) => 0,
698            _ => 1,
699        }
700    }
701
702    /// returns the fee_increase additional percentage multiplier, it affects only processing costs
703    pub fn user_fee_increase(&self) -> UserFeeIncrease {
704        match self {
705            StateTransition::DataContractCreate(st) => st.user_fee_increase(),
706            StateTransition::DataContractUpdate(st) => st.user_fee_increase(),
707            StateTransition::Batch(st) => st.user_fee_increase(),
708            StateTransition::IdentityCreate(st) => st.user_fee_increase(),
709            StateTransition::IdentityTopUp(st) => st.user_fee_increase(),
710            StateTransition::IdentityCreditWithdrawal(st) => st.user_fee_increase(),
711            StateTransition::IdentityUpdate(st) => st.user_fee_increase(),
712            StateTransition::IdentityCreditTransfer(st) => st.user_fee_increase(),
713            StateTransition::IdentityCreditTransferToAddresses(st) => st.user_fee_increase(),
714            StateTransition::IdentityCreateFromAddresses(st) => st.user_fee_increase(),
715            StateTransition::IdentityTopUpFromAddresses(st) => st.user_fee_increase(),
716            StateTransition::AddressFundsTransfer(st) => st.user_fee_increase(),
717            StateTransition::AddressFundingFromAssetLock(st) => st.user_fee_increase(),
718            StateTransition::AddressCreditWithdrawal(st) => st.user_fee_increase(),
719            StateTransition::Shield(st) => st.user_fee_increase(),
720            // These transitions don't support user fee adjustment
721            StateTransition::ShieldFromAssetLock(_) => 0,
722            StateTransition::MasternodeVote(_) => 0,
723            StateTransition::ShieldedTransfer(_) => 0,
724            StateTransition::Unshield(_) => 0,
725            StateTransition::ShieldedWithdrawal(_) => 0,
726        }
727    }
728
729    /// Calculates the estimated minimum fee required for this state transition.
730    ///
731    /// The fee is calculated based on the number of inputs, outputs, and any
732    /// transition-specific costs (e.g., key creation costs for identity creation).
733    ///
734    /// # Arguments
735    ///
736    /// * `platform_version` - The platform version containing fee configuration.
737    ///
738    /// # Returns
739    ///
740    /// The estimated fee in credits.
741    fn calculate_estimated_fee(
742        &self,
743        platform_version: &PlatformVersion,
744    ) -> Result<Credits, ProtocolError> {
745        call_method!(self, calculate_min_required_fee, platform_version)
746    }
747
748    /// The transaction id is a single hash of the data with the signature
749    pub fn transaction_id(&self) -> Result<[u8; 32], ProtocolError> {
750        Ok(hash_single(
751            crate::serialization::PlatformSerializable::serialize_to_bytes(self)?,
752        ))
753    }
754
755    /// returns the signature as a byte-array
756    pub fn signature_public_key_id(&self) -> Option<KeyID> {
757        call_getter_method_identity_signed!(self, signature_public_key_id)
758    }
759
760    /// returns the key security level requirement for the state transition
761    pub fn security_level_requirement(&self, purpose: Purpose) -> Option<Vec<SecurityLevel>> {
762        call_getter_method_identity_signed!(self, security_level_requirement, purpose)
763    }
764
765    /// returns the key purpose requirement for the state transition
766    pub fn purpose_requirement(&self) -> Option<Vec<Purpose>> {
767        call_getter_method_identity_signed!(self, purpose_requirement)
768    }
769
770    /// returns the signature as a byte-array
771    pub fn owner_id(&self) -> Option<Identifier> {
772        match self {
773            StateTransition::DataContractCreate(st) => Some(st.owner_id()),
774            StateTransition::DataContractUpdate(st) => Some(st.owner_id()),
775            StateTransition::Batch(st) => Some(st.owner_id()),
776            StateTransition::IdentityCreate(st) => Some(st.owner_id()),
777            StateTransition::IdentityTopUp(st) => Some(st.owner_id()),
778            StateTransition::IdentityCreditWithdrawal(st) => Some(st.owner_id()),
779            StateTransition::IdentityUpdate(st) => Some(st.owner_id()),
780            StateTransition::IdentityCreditTransfer(st) => Some(st.owner_id()),
781            StateTransition::MasternodeVote(st) => Some(st.owner_id()),
782            StateTransition::IdentityCreditTransferToAddresses(st) => Some(st.owner_id()),
783            StateTransition::IdentityCreateFromAddresses(_) => None,
784            StateTransition::IdentityTopUpFromAddresses(_) => None,
785            StateTransition::AddressFundsTransfer(_) => None,
786            StateTransition::AddressFundingFromAssetLock(_) => None,
787            StateTransition::AddressCreditWithdrawal(_) => None,
788            StateTransition::Shield(_) => None,
789            StateTransition::ShieldedTransfer(_) => None,
790            StateTransition::Unshield(_) => None,
791            StateTransition::ShieldFromAssetLock(_) => None,
792            StateTransition::ShieldedWithdrawal(_) => None,
793        }
794    }
795
796    /// returns the signature as a byte-array
797    pub fn inputs(&self) -> Option<&BTreeMap<PlatformAddress, (AddressNonce, Credits)>> {
798        match self {
799            StateTransition::DataContractCreate(_)
800            | StateTransition::DataContractUpdate(_)
801            | StateTransition::Batch(_)
802            | StateTransition::IdentityCreate(_)
803            | StateTransition::IdentityTopUp(_)
804            | StateTransition::IdentityCreditWithdrawal(_)
805            | StateTransition::IdentityUpdate(_)
806            | StateTransition::IdentityCreditTransfer(_)
807            | StateTransition::MasternodeVote(_)
808            | StateTransition::IdentityCreditTransferToAddresses(_) => None,
809            StateTransition::IdentityCreateFromAddresses(st) => Some(st.inputs()),
810            StateTransition::IdentityTopUpFromAddresses(st) => Some(st.inputs()),
811            StateTransition::AddressFundsTransfer(st) => Some(st.inputs()),
812            StateTransition::AddressFundingFromAssetLock(st) => Some(st.inputs()),
813            StateTransition::AddressCreditWithdrawal(st) => Some(st.inputs()),
814            StateTransition::Shield(st) => Some(st.inputs()),
815            StateTransition::ShieldedTransfer(_) => None,
816            StateTransition::Unshield(_) => None,
817            StateTransition::ShieldFromAssetLock(_) => None,
818            StateTransition::ShieldedWithdrawal(_) => None,
819        }
820    }
821
822    /// returns the state transition type
823    pub fn state_transition_type(&self) -> StateTransitionType {
824        call_method!(self, state_transition_type)
825    }
826
827    /// returns the unique identifiers for the state transition
828    pub fn unique_identifiers(&self) -> Vec<String> {
829        call_method!(self, unique_identifiers)
830    }
831
832    /// set a new signature
833    pub fn set_signature(&mut self, signature: BinaryData) -> bool {
834        match self {
835            StateTransition::DataContractCreate(st) => {
836                st.set_signature(signature);
837                true
838            }
839            StateTransition::DataContractUpdate(st) => {
840                st.set_signature(signature);
841                true
842            }
843            StateTransition::Batch(st) => {
844                st.set_signature(signature);
845                true
846            }
847            StateTransition::IdentityCreate(st) => {
848                st.set_signature(signature);
849                true
850            }
851            StateTransition::IdentityTopUp(st) => {
852                st.set_signature(signature);
853                true
854            }
855            StateTransition::IdentityCreditWithdrawal(st) => {
856                st.set_signature(signature);
857                true
858            }
859            StateTransition::IdentityUpdate(st) => {
860                st.set_signature(signature);
861                true
862            }
863            StateTransition::IdentityCreditTransfer(st) => {
864                st.set_signature(signature);
865                true
866            }
867            StateTransition::MasternodeVote(st) => {
868                st.set_signature(signature);
869                true
870            }
871            StateTransition::IdentityCreditTransferToAddresses(st) => {
872                st.set_signature(signature);
873                true
874            }
875            StateTransition::IdentityCreateFromAddresses(_)
876            | StateTransition::IdentityTopUpFromAddresses(_)
877            | StateTransition::AddressFundsTransfer(_)
878            | StateTransition::Shield(_)
879            | StateTransition::ShieldedTransfer(_)
880            | StateTransition::Unshield(_)
881            | StateTransition::ShieldedWithdrawal(_) => false,
882            StateTransition::AddressFundingFromAssetLock(st) => {
883                st.set_signature(signature);
884                true
885            }
886            StateTransition::ShieldFromAssetLock(st) => {
887                st.set_signature(signature);
888                true
889            }
890            StateTransition::AddressCreditWithdrawal(_) => false,
891        }
892    }
893
894    /// set fee multiplier
895    pub fn set_user_fee_increase(&mut self, user_fee_increase: UserFeeIncrease) {
896        match self {
897            StateTransition::DataContractCreate(st) => st.set_user_fee_increase(user_fee_increase),
898            StateTransition::DataContractUpdate(st) => st.set_user_fee_increase(user_fee_increase),
899            StateTransition::Batch(st) => st.set_user_fee_increase(user_fee_increase),
900            StateTransition::IdentityCreate(st) => st.set_user_fee_increase(user_fee_increase),
901            StateTransition::IdentityTopUp(st) => st.set_user_fee_increase(user_fee_increase),
902            StateTransition::IdentityCreditWithdrawal(st) => {
903                st.set_user_fee_increase(user_fee_increase)
904            }
905            StateTransition::IdentityUpdate(st) => st.set_user_fee_increase(user_fee_increase),
906            StateTransition::IdentityCreditTransfer(st) => {
907                st.set_user_fee_increase(user_fee_increase)
908            }
909            StateTransition::IdentityCreditTransferToAddresses(st) => {
910                st.set_user_fee_increase(user_fee_increase)
911            }
912            StateTransition::IdentityCreateFromAddresses(st) => {
913                st.set_user_fee_increase(user_fee_increase)
914            }
915            StateTransition::IdentityTopUpFromAddresses(st) => {
916                st.set_user_fee_increase(user_fee_increase)
917            }
918            StateTransition::AddressFundsTransfer(st) => {
919                st.set_user_fee_increase(user_fee_increase)
920            }
921            StateTransition::AddressFundingFromAssetLock(st) => {
922                st.set_user_fee_increase(user_fee_increase)
923            }
924            StateTransition::AddressCreditWithdrawal(st) => {
925                st.set_user_fee_increase(user_fee_increase)
926            }
927            StateTransition::Shield(st) => st.set_user_fee_increase(user_fee_increase),
928            // These transitions don't support user fee adjustment — no-op
929            StateTransition::ShieldFromAssetLock(_) => {}
930            StateTransition::MasternodeVote(_) => {}
931            StateTransition::ShieldedTransfer(_) => {}
932            StateTransition::Unshield(_) => {}
933            StateTransition::ShieldedWithdrawal(_) => {}
934        }
935    }
936
937    /// set a new signature
938    pub fn set_signature_public_key_id(&mut self, public_key_id: KeyID) {
939        call_method_identity_signed!(self, set_signature_public_key_id, public_key_id)
940    }
941
942    #[cfg(feature = "state-transition-signing")]
943    pub async fn sign_external<S: Signer<IdentityPublicKey>>(
944        &mut self,
945        identity_public_key: &IdentityPublicKey,
946        signer: &S,
947        get_data_contract_security_level_requirement: Option<
948            impl Fn(Identifier, String) -> Result<SecurityLevel, ProtocolError>,
949        >,
950    ) -> Result<(), ProtocolError> {
951        self.sign_external_with_options(
952            identity_public_key,
953            signer,
954            get_data_contract_security_level_requirement,
955            StateTransitionSigningOptions::default(),
956        )
957        .await
958    }
959
960    #[cfg(feature = "state-transition-signing")]
961    pub async fn sign_external_with_options<S: Signer<IdentityPublicKey>>(
962        &mut self,
963        identity_public_key: &IdentityPublicKey,
964        signer: &S,
965        get_data_contract_security_level_requirement: Option<
966            impl Fn(Identifier, String) -> Result<SecurityLevel, ProtocolError>,
967        >,
968        options: StateTransitionSigningOptions,
969    ) -> Result<(), ProtocolError> {
970        match self {
971            StateTransition::DataContractCreate(st) => {
972                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
973                st.verify_public_key_is_enabled(identity_public_key)?;
974            }
975            StateTransition::DataContractUpdate(st) => {
976                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
977                st.verify_public_key_is_enabled(identity_public_key)?;
978            }
979            StateTransition::Batch(st) => {
980                let allow_token_transfer_keys = st.transitions_len() == 1
981                    && (st
982                        .first_transition()
983                        .expect("expected first transition with len 1")
984                        .as_transition_token_claim()
985                        .is_some()
986                        || st
987                            .first_transition()
988                            .expect("expected first transition with len 1")
989                            .as_transition_token_transfer()
990                            .is_some());
991                let allowed_key_purposes = if allow_token_transfer_keys {
992                    vec![Purpose::AUTHENTICATION, Purpose::TRANSFER]
993                } else {
994                    vec![Purpose::AUTHENTICATION]
995                };
996                if !options.allow_signing_with_any_purpose
997                    && !allowed_key_purposes.contains(&identity_public_key.purpose())
998                {
999                    return Err(ProtocolError::WrongPublicKeyPurposeError(
1000                        WrongPublicKeyPurposeError::new(
1001                            identity_public_key.purpose(),
1002                            allowed_key_purposes,
1003                        ),
1004                    ));
1005                }
1006                if !options.allow_signing_with_any_security_level {
1007                    let security_level_requirement = st.combined_security_level_requirement(
1008                        get_data_contract_security_level_requirement,
1009                    )?;
1010                    if !security_level_requirement.contains(&identity_public_key.security_level()) {
1011                        return Err(ProtocolError::InvalidSignaturePublicKeySecurityLevelError(
1012                            InvalidSignaturePublicKeySecurityLevelError::new(
1013                                identity_public_key.security_level(),
1014                                security_level_requirement,
1015                            ),
1016                        ));
1017                    }
1018                }
1019                st.verify_public_key_is_enabled(identity_public_key)?;
1020            }
1021            StateTransition::IdentityCreditWithdrawal(st) => {
1022                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1023                st.verify_public_key_is_enabled(identity_public_key)?;
1024            }
1025            StateTransition::IdentityUpdate(st) => {
1026                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1027                st.verify_public_key_is_enabled(identity_public_key)?;
1028            }
1029            StateTransition::IdentityCreditTransfer(st) => {
1030                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1031                st.verify_public_key_is_enabled(identity_public_key)?;
1032            }
1033            StateTransition::IdentityCreate(_) => {
1034                return Err(ProtocolError::CorruptedCodeExecution(
1035                    "identity create can not be called for identity signing".to_string(),
1036                ))
1037            }
1038            StateTransition::IdentityTopUp(_) => {
1039                return Err(ProtocolError::CorruptedCodeExecution(
1040                    "identity top up can not be called for identity signing".to_string(),
1041                ))
1042            }
1043            StateTransition::MasternodeVote(st) => {
1044                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1045                st.verify_public_key_is_enabled(identity_public_key)?;
1046            }
1047            StateTransition::IdentityCreditTransferToAddresses(st) => {
1048                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1049                st.verify_public_key_is_enabled(identity_public_key)?;
1050            }
1051            StateTransition::IdentityCreateFromAddresses(_) => {
1052                return Err(ProtocolError::CorruptedCodeExecution(
1053                    "identity create from addresses can not be called for identity signing"
1054                        .to_string(),
1055                ))
1056            }
1057            StateTransition::IdentityTopUpFromAddresses(_) => {
1058                return Err(ProtocolError::CorruptedCodeExecution(
1059                    "identity top up from addresses can not be called for identity signing"
1060                        .to_string(),
1061                ))
1062            }
1063            StateTransition::AddressFundsTransfer(_) => {
1064                return Err(ProtocolError::CorruptedCodeExecution(
1065                    "address funds transfer transition can not be called for identity signing"
1066                        .to_string(),
1067                ))
1068            }
1069            StateTransition::AddressFundingFromAssetLock(_) => {
1070                return Err(ProtocolError::CorruptedCodeExecution(
1071                    "address funding from asset lock transition can not be called for identity signing"
1072                        .to_string(),
1073                ))
1074            }
1075            StateTransition::AddressCreditWithdrawal(_) => {
1076                return Err(ProtocolError::CorruptedCodeExecution(
1077                    "address credit withdrawal transition can not be called for identity signing"
1078                        .to_string(),
1079                ))
1080            }
1081            StateTransition::Shield(_) => {
1082                return Err(ProtocolError::CorruptedCodeExecution(
1083                    "shield transition can not be called for identity signing".to_string(),
1084                ))
1085            }
1086            StateTransition::ShieldedTransfer(_) => {
1087                return Err(ProtocolError::CorruptedCodeExecution(
1088                    "shielded transfer transition can not be called for identity signing"
1089                        .to_string(),
1090                ))
1091            }
1092            StateTransition::Unshield(_) => {
1093                return Err(ProtocolError::CorruptedCodeExecution(
1094                    "unshield transition can not be called for identity signing".to_string(),
1095                ))
1096            }
1097            StateTransition::ShieldFromAssetLock(_) => {
1098                return Err(ProtocolError::CorruptedCodeExecution(
1099                    "shield from asset lock transition can not be called for identity signing"
1100                        .to_string(),
1101                ))
1102            }
1103            StateTransition::ShieldedWithdrawal(_) => {
1104                return Err(ProtocolError::CorruptedCodeExecution(
1105                    "shielded withdrawal transition can not be called for identity signing"
1106                        .to_string(),
1107                ))
1108            }
1109        }
1110        let data = self.signable_bytes()?;
1111        self.set_signature(signer.sign(identity_public_key, data.as_slice()).await?);
1112        self.set_signature_public_key_id(identity_public_key.id());
1113        Ok(())
1114    }
1115
1116    #[cfg(feature = "state-transition-signing")]
1117    pub fn sign(
1118        &mut self,
1119        identity_public_key: &IdentityPublicKey,
1120        private_key: &[u8],
1121        bls: &impl BlsModule,
1122    ) -> Result<(), ProtocolError> {
1123        self.sign_with_options(
1124            identity_public_key,
1125            private_key,
1126            bls,
1127            StateTransitionSigningOptions::default(),
1128        )
1129    }
1130
1131    #[cfg(feature = "state-transition-signing")]
1132    pub fn sign_with_options(
1133        &mut self,
1134        identity_public_key: &IdentityPublicKey,
1135        private_key: &[u8],
1136        bls: &impl BlsModule,
1137        options: StateTransitionSigningOptions,
1138    ) -> Result<(), ProtocolError> {
1139        call_errorable_method_identity_signed!(
1140            self,
1141            verify_public_key_level_and_purpose,
1142            identity_public_key,
1143            options
1144        )?;
1145        call_errorable_method_identity_signed!(
1146            self,
1147            verify_public_key_is_enabled,
1148            identity_public_key
1149        )?;
1150
1151        match identity_public_key.key_type() {
1152            KeyType::ECDSA_SECP256K1 => {
1153                let public_key_compressed = get_compressed_public_ec_key(private_key)?;
1154
1155                // we store compressed public key in the identity ,
1156                // and here we compare the private key used to sing the state transition with
1157                // the compressed key stored in the identity
1158
1159                if public_key_compressed.as_slice() != identity_public_key.data().as_slice() {
1160                    return Err(ProtocolError::InvalidSignaturePublicKeyError(
1161                        InvalidSignaturePublicKeyError::new(identity_public_key.data().to_vec()),
1162                    ));
1163                }
1164
1165                self.sign_by_private_key(private_key, identity_public_key.key_type(), bls)
1166            }
1167            KeyType::ECDSA_HASH160 => {
1168                let public_key_compressed = get_compressed_public_ec_key(private_key)?;
1169                let pub_key_hash = ripemd160_sha256(&public_key_compressed);
1170
1171                if identity_public_key.data().as_slice() != pub_key_hash {
1172                    return Err(ProtocolError::InvalidSignaturePublicKeyError(
1173                        InvalidSignaturePublicKeyError::new(identity_public_key.data().to_vec()),
1174                    ));
1175                }
1176                self.sign_by_private_key(private_key, identity_public_key.key_type(), bls)
1177            }
1178            KeyType::BLS12_381 => {
1179                let public_key = bls.private_key_to_public_key(private_key)?;
1180
1181                if public_key != identity_public_key.data().as_slice() {
1182                    return Err(ProtocolError::InvalidSignaturePublicKeyError(
1183                        InvalidSignaturePublicKeyError::new(identity_public_key.data().to_vec()),
1184                    ));
1185                }
1186                self.sign_by_private_key(private_key, identity_public_key.key_type(), bls)
1187            }
1188
1189            // the default behavior from
1190            // https://github.com/dashevo/platform/blob/6b02b26e5cd3a7c877c5fdfe40c4a4385a8dda15/packages/js-dpp/lib/stateTransition/AbstractStateTransitionIdentitySigned.js#L108
1191            // is to return the error for the BIP13_SCRIPT_HASH
1192            KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => {
1193                Err(ProtocolError::InvalidIdentityPublicKeyTypeError(
1194                    InvalidIdentityPublicKeyTypeError::new(identity_public_key.key_type()),
1195                ))
1196            }
1197        }?;
1198
1199        self.set_signature_public_key_id(identity_public_key.id());
1200
1201        Ok(())
1202    }
1203
1204    #[cfg(feature = "state-transition-signing")]
1205    /// Signs data with the private key
1206    pub fn sign_by_private_key(
1207        &mut self,
1208        private_key: &[u8],
1209        key_type: KeyType,
1210        bls: &impl BlsModule,
1211    ) -> Result<(), ProtocolError> {
1212        let data = self.signable_bytes()?;
1213        match key_type {
1214            KeyType::BLS12_381 => {
1215                if !self.set_signature(bls.sign(&data, private_key)?.into()) {
1216                    return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
1217                        needed: self.required_number_of_private_keys(),
1218                        using: 1,
1219                        msg: "failed to set BLS signature",
1220                    });
1221                }
1222            }
1223
1224            // https://github.com/dashevo/platform/blob/9c8e6a3b6afbc330a6ab551a689de8ccd63f9120/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js#L169
1225            KeyType::ECDSA_SECP256K1 | KeyType::ECDSA_HASH160 => {
1226                let signature = signer::sign(&data, private_key)?;
1227                if !self.set_signature(signature.to_vec().into()) {
1228                    return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
1229                        needed: self.required_number_of_private_keys(),
1230                        using: 1,
1231                        msg: "failed to set ECDSA signature",
1232                    });
1233                };
1234            }
1235
1236            // the default behavior from
1237            // https://github.com/dashevo/platform/blob/6b02b26e5cd3a7c877c5fdfe40c4a4385a8dda15/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js#L187
1238            // is to return the error for the BIP13_SCRIPT_HASH
1239            KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => {
1240                return Err(ProtocolError::InvalidIdentityPublicKeyTypeError(
1241                    InvalidIdentityPublicKeyTypeError::new(key_type),
1242                ))
1243            }
1244        };
1245        Ok(())
1246    }
1247
1248    #[cfg(feature = "state-transition-validation")]
1249    fn verify_by_raw_public_key<T: BlsModule>(
1250        &self,
1251        public_key: &[u8],
1252        public_key_type: KeyType,
1253        bls: &T,
1254    ) -> Result<(), ProtocolError> {
1255        match public_key_type {
1256            KeyType::ECDSA_SECP256K1 => self.verify_ecdsa_signature_by_public_key(public_key),
1257            KeyType::ECDSA_HASH160 => {
1258                self.verify_ecdsa_hash_160_signature_by_public_key_hash(public_key)
1259            }
1260            KeyType::BLS12_381 => self.verify_bls_signature_by_public_key(public_key, bls),
1261            KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => {
1262                Err(ProtocolError::InvalidIdentityPublicKeyTypeError(
1263                    InvalidIdentityPublicKeyTypeError::new(public_key_type),
1264                ))
1265            }
1266        }
1267    }
1268
1269    #[cfg(feature = "state-transition-validation")]
1270    pub fn verify_identity_signed_signature(
1271        &self,
1272        public_key: &IdentityPublicKey,
1273        bls: &impl BlsModule,
1274    ) -> Result<(), ProtocolError> {
1275        // self.verify_public_key_level_and_purpose(public_key)?;
1276        if public_key.disabled_at().is_some() {
1277            return Err(ProtocolError::PublicKeyIsDisabledError(
1278                PublicKeyIsDisabledError::new(public_key.id()),
1279            ));
1280        }
1281
1282        let Some(signature) = self.signature() else {
1283            return Err(ProtocolError::CorruptedCodeExecution("verifying identity signature for a state transition that doesn't use identity signatures".to_string()));
1284        };
1285        if signature.is_empty() {
1286            return Err(ProtocolError::StateTransitionIsNotSignedError(
1287                StateTransitionIsNotSignedError::new(self.clone()),
1288            ));
1289        }
1290
1291        if self.signature_public_key_id() != Some(public_key.id()) {
1292            return Err(ProtocolError::PublicKeyMismatchError(
1293                PublicKeyMismatchError::new(public_key.clone()),
1294            ));
1295        }
1296
1297        let public_key_bytes = public_key.data().as_slice();
1298        match public_key.key_type() {
1299            KeyType::ECDSA_HASH160 => {
1300                self.verify_ecdsa_hash_160_signature_by_public_key_hash(public_key_bytes)
1301            }
1302
1303            KeyType::ECDSA_SECP256K1 => self.verify_ecdsa_signature_by_public_key(public_key_bytes),
1304
1305            KeyType::BLS12_381 => self.verify_bls_signature_by_public_key(public_key_bytes, bls),
1306
1307            // per https://github.com/dashevo/platform/pull/353, signing and verification is not supported
1308            KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => Ok(()),
1309        }
1310    }
1311
1312    #[cfg(feature = "state-transition-validation")]
1313    fn verify_ecdsa_hash_160_signature_by_public_key_hash(
1314        &self,
1315        public_key_hash: &[u8],
1316    ) -> Result<(), ProtocolError> {
1317        let Some(signature) = self.signature() else {
1318            return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
1319                needed: self.required_number_of_private_keys(),
1320                using: 1,
1321                msg: "This state transition type should a single signature",
1322            });
1323        };
1324        if signature.is_empty() {
1325            return Err(ProtocolError::StateTransitionIsNotSignedError(
1326                StateTransitionIsNotSignedError::new(self.clone()),
1327            ));
1328        }
1329        let data = self.signable_bytes()?;
1330        let data_hash = double_sha(data);
1331        signer::verify_hash_signature(&data_hash, signature.as_slice(), public_key_hash).map_err(
1332            |e| {
1333                ProtocolError::from(ConsensusError::SignatureError(
1334                    SignatureError::InvalidStateTransitionSignatureError(
1335                        InvalidStateTransitionSignatureError::new(e.to_string()),
1336                    ),
1337                ))
1338            },
1339        )
1340    }
1341
1342    #[cfg(feature = "state-transition-validation")]
1343    /// Verifies an ECDSA signature with the public key
1344    fn verify_ecdsa_signature_by_public_key(&self, public_key: &[u8]) -> Result<(), ProtocolError> {
1345        let Some(signature) = self.signature() else {
1346            return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
1347                needed: self.required_number_of_private_keys(),
1348                using: 1,
1349                msg: "This state transition type should a single signature",
1350            });
1351        };
1352        if signature.is_empty() {
1353            return Err(ProtocolError::StateTransitionIsNotSignedError(
1354                StateTransitionIsNotSignedError::new(self.clone()),
1355            ));
1356        }
1357        let data = self.signable_bytes()?;
1358        signer::verify_data_signature(&data, signature.as_slice(), public_key).map_err(|e| {
1359            // TODO: it shouldn't respond with consensus error
1360
1361            ProtocolError::from(ConsensusError::SignatureError(
1362                SignatureError::InvalidStateTransitionSignatureError(
1363                    InvalidStateTransitionSignatureError::new(e.to_string()),
1364                ),
1365            ))
1366        })
1367    }
1368
1369    #[cfg(feature = "state-transition-validation")]
1370    /// Verifies a BLS signature with the public key
1371    fn verify_bls_signature_by_public_key<T: BlsModule>(
1372        &self,
1373        public_key: &[u8],
1374        bls: &T,
1375    ) -> Result<(), ProtocolError> {
1376        let Some(signature) = self.signature() else {
1377            return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
1378                needed: self.required_number_of_private_keys(),
1379                using: 1,
1380                msg: "This state transition type should a single signature",
1381            });
1382        };
1383        if signature.is_empty() {
1384            return Err(ProtocolError::StateTransitionIsNotSignedError(
1385                StateTransitionIsNotSignedError::new(self.clone()),
1386            ));
1387        }
1388
1389        let data = self.signable_bytes()?;
1390
1391        bls.verify_signature(signature.as_slice(), &data, public_key)
1392            .map(|_| ())
1393            .map_err(|e| {
1394                // TODO: it shouldn't respond with consensus error
1395                ProtocolError::from(ConsensusError::SignatureError(
1396                    SignatureError::InvalidStateTransitionSignatureError(
1397                        InvalidStateTransitionSignatureError::new(e.to_string()),
1398                    ),
1399                ))
1400            })
1401    }
1402}
1403
1404#[cfg(feature = "state-transition-validation")]
1405impl StateTransitionStructureValidation for StateTransition {
1406    fn validate_structure(
1407        &self,
1408        platform_version: &PlatformVersion,
1409    ) -> crate::validation::SimpleConsensusValidationResult {
1410        match self {
1411            StateTransition::DataContractCreate(_)
1412            | StateTransition::DataContractUpdate(_)
1413            | StateTransition::Batch(_)
1414            | StateTransition::IdentityCreate(_)
1415            | StateTransition::IdentityTopUp(_)
1416            | StateTransition::IdentityCreditWithdrawal(_)
1417            | StateTransition::IdentityUpdate(_)
1418            | StateTransition::IdentityCreditTransfer(_)
1419            | StateTransition::MasternodeVote(_) => {
1420                crate::validation::SimpleConsensusValidationResult::new_with_error(
1421                    UnsupportedFeatureError::new(
1422                        "structure validation for identity-based state transitions".to_string(),
1423                        platform_version.protocol_version,
1424                    )
1425                    .into(),
1426                )
1427            }
1428            StateTransition::IdentityCreditTransferToAddresses(transition) => {
1429                transition.validate_structure(platform_version)
1430            }
1431            StateTransition::IdentityCreateFromAddresses(transition) => {
1432                transition.validate_structure(platform_version)
1433            }
1434            StateTransition::IdentityTopUpFromAddresses(transition) => {
1435                transition.validate_structure(platform_version)
1436            }
1437            StateTransition::AddressFundsTransfer(transition) => {
1438                transition.validate_structure(platform_version)
1439            }
1440            StateTransition::AddressFundingFromAssetLock(transition) => {
1441                transition.validate_structure(platform_version)
1442            }
1443            StateTransition::AddressCreditWithdrawal(transition) => {
1444                transition.validate_structure(platform_version)
1445            }
1446            StateTransition::Shield(transition) => transition.validate_structure(platform_version),
1447            StateTransition::ShieldedTransfer(transition) => {
1448                transition.validate_structure(platform_version)
1449            }
1450            StateTransition::Unshield(transition) => {
1451                transition.validate_structure(platform_version)
1452            }
1453            StateTransition::ShieldFromAssetLock(transition) => {
1454                transition.validate_structure(platform_version)
1455            }
1456            StateTransition::ShieldedWithdrawal(transition) => {
1457                transition.validate_structure(platform_version)
1458            }
1459        }
1460    }
1461}
1462
1463#[cfg(test)]
1464mod tests {
1465    use super::*;
1466
1467    // -----------------------------------------------------------------------
1468    // StateTransitionSigningOptions tests
1469    // -----------------------------------------------------------------------
1470
1471    #[test]
1472    fn test_signing_options_default() {
1473        let opts = StateTransitionSigningOptions::default();
1474        assert!(!opts.allow_signing_with_any_security_level);
1475        assert!(!opts.allow_signing_with_any_purpose);
1476    }
1477
1478    #[test]
1479    fn test_signing_options_equality() {
1480        let a = StateTransitionSigningOptions {
1481            allow_signing_with_any_security_level: true,
1482            allow_signing_with_any_purpose: false,
1483        };
1484        let b = StateTransitionSigningOptions {
1485            allow_signing_with_any_security_level: true,
1486            allow_signing_with_any_purpose: false,
1487        };
1488        assert_eq!(a, b);
1489    }
1490
1491    #[test]
1492    fn test_signing_options_inequality() {
1493        let a = StateTransitionSigningOptions {
1494            allow_signing_with_any_security_level: true,
1495            allow_signing_with_any_purpose: false,
1496        };
1497        let b = StateTransitionSigningOptions {
1498            allow_signing_with_any_security_level: false,
1499            allow_signing_with_any_purpose: false,
1500        };
1501        assert_ne!(a, b);
1502    }
1503
1504    #[test]
1505    #[allow(clippy::clone_on_copy)]
1506    fn test_signing_options_clone() {
1507        let original = StateTransitionSigningOptions {
1508            allow_signing_with_any_security_level: true,
1509            allow_signing_with_any_purpose: true,
1510        };
1511        let cloned = original.clone();
1512        assert_eq!(original, cloned);
1513    }
1514
1515    #[test]
1516    fn test_signing_options_copy() {
1517        let original = StateTransitionSigningOptions {
1518            allow_signing_with_any_security_level: true,
1519            allow_signing_with_any_purpose: false,
1520        };
1521        let copied = original;
1522        assert_eq!(original, copied);
1523    }
1524
1525    #[test]
1526    fn test_signing_options_debug() {
1527        let opts = StateTransitionSigningOptions::default();
1528        let debug_str = format!("{:?}", opts);
1529        assert!(debug_str.contains("StateTransitionSigningOptions"));
1530        assert!(debug_str.contains("allow_signing_with_any_security_level"));
1531        assert!(debug_str.contains("allow_signing_with_any_purpose"));
1532    }
1533
1534    // -----------------------------------------------------------------------
1535    // StateTransition enum accessor / mutator / classification tests
1536    //
1537    // These exercise the non-trivial match arms across the large enum, using
1538    // the IdentityCreditTransfer, MasternodeVote, IdentityCreditWithdrawal and
1539    // DataContractCreate variants as representative signed / unsigned /
1540    // voting / contract cases. They intentionally do NOT use `sign`/`verify`
1541    // (those go through BLS/ECDSA and have their own coverage elsewhere).
1542    // -----------------------------------------------------------------------
1543    use crate::identity::core_script::CoreScript;
1544    use crate::identity::{Purpose, SecurityLevel};
1545    use crate::prelude::Identifier;
1546    use crate::state_transition::identity_credit_transfer_transition::v0::IdentityCreditTransferTransitionV0;
1547    use crate::state_transition::identity_credit_transfer_transition::IdentityCreditTransferTransition;
1548    use crate::state_transition::identity_credit_withdrawal_transition::v0::IdentityCreditWithdrawalTransitionV0;
1549    use crate::state_transition::identity_credit_withdrawal_transition::IdentityCreditWithdrawalTransition;
1550    use crate::state_transition::masternode_vote_transition::v0::MasternodeVoteTransitionV0;
1551    use crate::state_transition::masternode_vote_transition::MasternodeVoteTransition;
1552    use crate::withdrawal::Pooling;
1553
1554    fn sample_transfer_st() -> StateTransition {
1555        let v0 = IdentityCreditTransferTransitionV0 {
1556            identity_id: Identifier::from([1u8; 32]),
1557            recipient_id: Identifier::from([2u8; 32]),
1558            amount: 1_000,
1559            nonce: 7,
1560            user_fee_increase: 3,
1561            signature_public_key_id: 11,
1562            signature: BinaryData::new(vec![0u8; 65]),
1563        };
1564        StateTransition::IdentityCreditTransfer(IdentityCreditTransferTransition::V0(v0))
1565    }
1566
1567    fn sample_masternode_vote_st() -> StateTransition {
1568        let v0 = MasternodeVoteTransitionV0 {
1569            pro_tx_hash: Identifier::from([3u8; 32]),
1570            voter_identity_id: Identifier::from([4u8; 32]),
1571            vote: Default::default(),
1572            nonce: 2,
1573            signature_public_key_id: 5,
1574            signature: BinaryData::new(vec![9u8; 10]),
1575        };
1576        StateTransition::MasternodeVote(MasternodeVoteTransition::V0(v0))
1577    }
1578
1579    fn sample_withdrawal_st() -> StateTransition {
1580        let v0 = IdentityCreditWithdrawalTransitionV0 {
1581            identity_id: Identifier::from([5u8; 32]),
1582            amount: 42,
1583            core_fee_per_byte: 1,
1584            pooling: Pooling::Never,
1585            output_script: CoreScript::from_bytes(vec![0x76, 0xa9]),
1586            nonce: 4,
1587            user_fee_increase: 1,
1588            signature_public_key_id: 3,
1589            signature: BinaryData::new(vec![8u8; 65]),
1590        };
1591        StateTransition::IdentityCreditWithdrawal(IdentityCreditWithdrawalTransition::V0(v0))
1592    }
1593
1594    #[test]
1595    fn test_name_returns_variant_names() {
1596        assert_eq!(sample_transfer_st().name(), "IdentityCreditTransfer");
1597        assert_eq!(sample_masternode_vote_st().name(), "MasternodeVote");
1598        assert_eq!(sample_withdrawal_st().name(), "IdentityCreditWithdrawal");
1599    }
1600
1601    #[test]
1602    fn test_state_transition_type_matches_variant() {
1603        assert_eq!(
1604            sample_transfer_st().state_transition_type(),
1605            StateTransitionType::IdentityCreditTransfer
1606        );
1607        assert_eq!(
1608            sample_masternode_vote_st().state_transition_type(),
1609            StateTransitionType::MasternodeVote
1610        );
1611        assert_eq!(
1612            sample_withdrawal_st().state_transition_type(),
1613            StateTransitionType::IdentityCreditWithdrawal
1614        );
1615    }
1616
1617    #[test]
1618    fn test_is_identity_signed_excludes_asset_lock_and_shielded() {
1619        assert!(sample_transfer_st().is_identity_signed());
1620        assert!(sample_masternode_vote_st().is_identity_signed());
1621        assert!(sample_withdrawal_st().is_identity_signed());
1622    }
1623
1624    #[test]
1625    fn test_signature_accessor() {
1626        let st = sample_transfer_st();
1627        let sig = st.signature().expect("transfer should expose signature");
1628        assert_eq!(sig.len(), 65);
1629
1630        let st = sample_masternode_vote_st();
1631        let sig = st.signature().expect("masternode vote has signature");
1632        assert_eq!(sig.as_slice(), &[9u8; 10]);
1633    }
1634
1635    #[test]
1636    fn test_owner_id_accessor() {
1637        let transfer = sample_transfer_st();
1638        assert_eq!(transfer.owner_id(), Some(Identifier::from([1u8; 32])));
1639
1640        let vote = sample_masternode_vote_st();
1641        assert_eq!(vote.owner_id(), Some(Identifier::from([4u8; 32])));
1642
1643        let withdraw = sample_withdrawal_st();
1644        assert_eq!(withdraw.owner_id(), Some(Identifier::from([5u8; 32])));
1645    }
1646
1647    #[test]
1648    fn test_signature_public_key_id_accessor() {
1649        assert_eq!(sample_transfer_st().signature_public_key_id(), Some(11));
1650        assert_eq!(
1651            sample_masternode_vote_st().signature_public_key_id(),
1652            Some(5)
1653        );
1654        assert_eq!(sample_withdrawal_st().signature_public_key_id(), Some(3));
1655    }
1656
1657    #[test]
1658    fn test_user_fee_increase_for_various_variants() {
1659        // Transfer exposes its internal value.
1660        assert_eq!(sample_transfer_st().user_fee_increase(), 3);
1661        // Masternode vote returns 0 unconditionally.
1662        assert_eq!(sample_masternode_vote_st().user_fee_increase(), 0);
1663        // Withdrawal exposes its internal value.
1664        assert_eq!(sample_withdrawal_st().user_fee_increase(), 1);
1665    }
1666
1667    #[test]
1668    fn test_set_signature_returns_true_for_supported() {
1669        let mut st = sample_transfer_st();
1670        let ok = st.set_signature(BinaryData::new(vec![0xaa; 65]));
1671        assert!(ok);
1672        assert_eq!(st.signature().unwrap().as_slice(), &[0xaa; 65]);
1673    }
1674
1675    #[test]
1676    fn test_set_user_fee_increase_updates_value() {
1677        let mut st = sample_transfer_st();
1678        st.set_user_fee_increase(42);
1679        assert_eq!(st.user_fee_increase(), 42);
1680
1681        // Masternode vote ignores the setter (documented no-op) — still reads 0.
1682        let mut vote = sample_masternode_vote_st();
1683        vote.set_user_fee_increase(99);
1684        assert_eq!(vote.user_fee_increase(), 0);
1685    }
1686
1687    #[test]
1688    fn test_set_signature_public_key_id() {
1689        let mut st = sample_transfer_st();
1690        st.set_signature_public_key_id(1234);
1691        assert_eq!(st.signature_public_key_id(), Some(1234));
1692    }
1693
1694    #[test]
1695    fn test_required_number_of_private_keys_default() {
1696        // Non asset-lock transitions always require 1 key.
1697        assert_eq!(sample_transfer_st().required_number_of_private_keys(), 1);
1698        assert_eq!(
1699            sample_masternode_vote_st().required_number_of_private_keys(),
1700            1
1701        );
1702        assert_eq!(sample_withdrawal_st().required_number_of_private_keys(), 1);
1703    }
1704
1705    #[test]
1706    fn test_inputs_none_for_legacy_variants() {
1707        // All these variants have no PlatformAddress inputs.
1708        assert!(sample_transfer_st().inputs().is_none());
1709        assert!(sample_masternode_vote_st().inputs().is_none());
1710        assert!(sample_withdrawal_st().inputs().is_none());
1711    }
1712
1713    #[test]
1714    fn test_active_version_range_legacy_transitions() {
1715        // These all report ALL_VERSIONS per the mod.rs table.
1716        assert_eq!(sample_transfer_st().active_version_range(), ALL_VERSIONS);
1717        assert_eq!(
1718            sample_masternode_vote_st().active_version_range(),
1719            ALL_VERSIONS
1720        );
1721        assert_eq!(sample_withdrawal_st().active_version_range(), ALL_VERSIONS);
1722    }
1723
1724    #[test]
1725    fn test_unique_identifiers_non_empty() {
1726        let ids = sample_transfer_st().unique_identifiers();
1727        assert_eq!(ids.len(), 1);
1728        assert!(!ids[0].is_empty());
1729    }
1730
1731    #[test]
1732    fn test_required_asset_lock_balance_rejects_non_asset_lock() {
1733        let platform_version = PlatformVersion::latest();
1734        let st = sample_transfer_st();
1735        let err = st
1736            .required_asset_lock_balance_for_processing_start(platform_version)
1737            .expect_err("credit transfer is not an asset lock state transition");
1738        match err {
1739            ProtocolError::CorruptedCodeExecution(msg) => {
1740                assert!(
1741                    msg.contains("is not an asset lock transaction"),
1742                    "unexpected error message: {msg}"
1743                );
1744            }
1745            other => panic!("expected CorruptedCodeExecution, got {other:?}"),
1746        }
1747    }
1748
1749    #[test]
1750    fn test_security_level_requirement_for_transfer() {
1751        // IdentityCreditTransfer requires CRITICAL at TRANSFER purpose.
1752        let st = sample_transfer_st();
1753        let levels = st
1754            .security_level_requirement(Purpose::TRANSFER)
1755            .expect("transfer state transition should return a requirement");
1756        assert_eq!(levels, vec![SecurityLevel::CRITICAL]);
1757    }
1758
1759    #[test]
1760    fn test_purpose_requirement_for_transfer() {
1761        let st = sample_transfer_st();
1762        let purposes = st
1763            .purpose_requirement()
1764            .expect("transfer state transition should have a purpose");
1765        assert_eq!(purposes, vec![Purpose::TRANSFER]);
1766    }
1767
1768    #[test]
1769    fn test_optional_asset_lock_proof_none_for_transfer() {
1770        let st = sample_transfer_st();
1771        assert!(st.optional_asset_lock_proof().is_none());
1772    }
1773
1774    // -----------------------------------------------------------------------
1775    // Enum construction: From<V0 / outer enum> → StateTransition
1776    // -----------------------------------------------------------------------
1777
1778    #[test]
1779    fn test_from_outer_enum_into_state_transition() {
1780        let outer: IdentityCreditTransferTransition =
1781            IdentityCreditTransferTransition::V0(IdentityCreditTransferTransitionV0::default());
1782        let st: StateTransition = outer.into();
1783        assert!(matches!(st, StateTransition::IdentityCreditTransfer(_)));
1784    }
1785
1786    #[test]
1787    fn test_from_masternode_vote_outer_into_state_transition() {
1788        let outer: MasternodeVoteTransition =
1789            MasternodeVoteTransition::V0(MasternodeVoteTransitionV0::default());
1790        let st: StateTransition = outer.into();
1791        assert!(matches!(st, StateTransition::MasternodeVote(_)));
1792    }
1793
1794    // -----------------------------------------------------------------------
1795    // Serialization round-trip: platform serialize / deserialize via enum.
1796    // Exercises the top-level `StateTransition` (de)serialize glue.
1797    // -----------------------------------------------------------------------
1798
1799    #[test]
1800    fn test_state_transition_platform_serialize_roundtrip() {
1801        use crate::serialization::{PlatformDeserializable, PlatformSerializable};
1802        let original = sample_transfer_st();
1803        let bytes =
1804            PlatformSerializable::serialize_to_bytes(&original).expect("serialize should succeed");
1805        let restored =
1806            StateTransition::deserialize_from_bytes(&bytes).expect("deserialize should succeed");
1807        assert_eq!(original, restored);
1808    }
1809
1810    #[test]
1811    fn test_deserialize_from_bytes_in_version_succeeds_for_latest() {
1812        use crate::serialization::PlatformSerializable;
1813        let original = sample_transfer_st();
1814        let bytes =
1815            PlatformSerializable::serialize_to_bytes(&original).expect("serialize succeeds");
1816        let restored =
1817            StateTransition::deserialize_from_bytes_in_version(&bytes, PlatformVersion::latest())
1818                .expect("deserialize_from_bytes_in_version should succeed");
1819        assert_eq!(original, restored);
1820    }
1821
1822    #[test]
1823    fn test_transaction_id_is_deterministic() {
1824        let st = sample_transfer_st();
1825        let a = st.transaction_id().expect("hash should succeed");
1826        let b = st.transaction_id().expect("hash should succeed");
1827        assert_eq!(a, b);
1828        assert_eq!(a.len(), 32);
1829    }
1830
1831    #[test]
1832    fn test_transaction_id_changes_on_signature_change() {
1833        let mut st = sample_transfer_st();
1834        let before = st.transaction_id().expect("hash should succeed");
1835        st.set_signature(BinaryData::new(vec![0xbb; 65]));
1836        let after = st.transaction_id().expect("hash should succeed");
1837        // Different signatures produce a different serialized form.
1838        assert_ne!(before, after);
1839    }
1840
1841    #[test]
1842    fn test_clone_preserves_inner_state() {
1843        let st = sample_transfer_st();
1844        let cloned = st.clone();
1845        assert_eq!(st, cloned);
1846    }
1847
1848    // -----------------------------------------------------------------------
1849    // Additional coverage: enum arms that weren't previously exercised.
1850    //
1851    // The tests below intentionally target variants the earlier tests did not
1852    // touch (DataContractCreate, DataContractUpdate, Batch, IdentityCreate,
1853    // IdentityTopUp, IdentityUpdate, shielded / address variants) to cover
1854    // the remaining match-arm branches in accessor / mutator / classification
1855    // methods.
1856    // -----------------------------------------------------------------------
1857
1858    use crate::data_contract::serialized_version::DataContractInSerializationFormat;
1859    use crate::state_transition::batch_transition::document_base_transition::v0::DocumentBaseTransitionV0;
1860    use crate::state_transition::batch_transition::document_base_transition::DocumentBaseTransition;
1861    use crate::state_transition::batch_transition::document_delete_transition::{
1862        DocumentDeleteTransition, DocumentDeleteTransitionV0,
1863    };
1864    use crate::state_transition::batch_transition::{BatchTransition, BatchTransitionV0};
1865    use crate::state_transition::data_contract_create_transition::{
1866        DataContractCreateTransition, DataContractCreateTransitionV0,
1867    };
1868    use crate::state_transition::data_contract_update_transition::{
1869        DataContractUpdateTransition, DataContractUpdateTransitionV0,
1870    };
1871    use crate::state_transition::identity_create_transition::v0::IdentityCreateTransitionV0;
1872    use crate::state_transition::identity_create_transition::IdentityCreateTransition;
1873    use crate::state_transition::identity_topup_transition::v0::IdentityTopUpTransitionV0;
1874    use crate::state_transition::identity_topup_transition::IdentityTopUpTransition;
1875    use crate::state_transition::identity_update_transition::v0::IdentityUpdateTransitionV0;
1876    use crate::state_transition::identity_update_transition::IdentityUpdateTransition;
1877    use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0;
1878    use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition;
1879    use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0;
1880    use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition;
1881    use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0;
1882    use crate::state_transition::unshield_transition::UnshieldTransition;
1883
1884    /// Build a DataContractInSerializationFormat from a crate-private v0
1885    /// constructor via the public TryFromPlatformVersioned impl and DataContract V1.
1886    fn sample_data_contract_in_serialization_format() -> DataContractInSerializationFormat {
1887        use crate::data_contract::config::v0::DataContractConfigV0;
1888        use crate::data_contract::config::DataContractConfig;
1889        use crate::data_contract::v1::DataContractV1;
1890        use crate::data_contract::DataContract;
1891        use platform_version::TryIntoPlatformVersioned;
1892        use std::collections::BTreeMap;
1893
1894        let contract = DataContract::V1(DataContractV1 {
1895            id: Identifier::from([9u8; 32]),
1896            version: 1,
1897            owner_id: Identifier::from([7u8; 32]),
1898            document_types: BTreeMap::new(),
1899            config: DataContractConfig::V0(DataContractConfigV0 {
1900                can_be_deleted: false,
1901                readonly: false,
1902                keeps_history: false,
1903                documents_keep_history_contract_default: false,
1904                documents_mutable_contract_default: false,
1905                documents_can_be_deleted_contract_default: false,
1906                requires_identity_encryption_bounded_key: None,
1907                requires_identity_decryption_bounded_key: None,
1908            }),
1909            schema_defs: None,
1910            created_at: None,
1911            updated_at: None,
1912            created_at_block_height: None,
1913            updated_at_block_height: None,
1914            created_at_epoch: None,
1915            updated_at_epoch: None,
1916            groups: BTreeMap::new(),
1917            tokens: BTreeMap::new(),
1918            keywords: Vec::new(),
1919            description: None,
1920        });
1921
1922        contract
1923            .try_into_platform_versioned(PlatformVersion::latest())
1924            .expect("expected to serialize a trivial contract")
1925    }
1926
1927    fn sample_data_contract_create_st() -> StateTransition {
1928        StateTransition::DataContractCreate(DataContractCreateTransition::V0(
1929            DataContractCreateTransitionV0 {
1930                data_contract: sample_data_contract_in_serialization_format(),
1931                identity_nonce: 1,
1932                user_fee_increase: 5,
1933                signature_public_key_id: 2,
1934                signature: BinaryData::new(vec![0xAB; 65]),
1935            },
1936        ))
1937    }
1938
1939    fn sample_data_contract_update_st() -> StateTransition {
1940        StateTransition::DataContractUpdate(DataContractUpdateTransition::V0(
1941            DataContractUpdateTransitionV0 {
1942                identity_contract_nonce: 4,
1943                data_contract: sample_data_contract_in_serialization_format(),
1944                user_fee_increase: 9,
1945                signature_public_key_id: 6,
1946                signature: BinaryData::new(vec![0xCD; 65]),
1947            },
1948        ))
1949    }
1950
1951    fn sample_batch_st_with_delete() -> StateTransition {
1952        let base = DocumentBaseTransition::V0(DocumentBaseTransitionV0 {
1953            id: Identifier::from([1u8; 32]),
1954            identity_contract_nonce: 3,
1955            document_type_name: "preorder".to_string(),
1956            data_contract_id: Identifier::from([2u8; 32]),
1957        });
1958        let delete =
1959            DocumentTransition::Delete(DocumentDeleteTransition::V0(DocumentDeleteTransitionV0 {
1960                base,
1961            }));
1962        StateTransition::Batch(BatchTransition::V0(BatchTransitionV0 {
1963            owner_id: Identifier::from([8u8; 32]),
1964            transitions: vec![delete],
1965            user_fee_increase: 2,
1966            signature_public_key_id: 7,
1967            signature: BinaryData::new(vec![0xEE; 65]),
1968        }))
1969    }
1970
1971    fn sample_batch_st_empty() -> StateTransition {
1972        StateTransition::Batch(BatchTransition::V0(BatchTransitionV0 {
1973            owner_id: Identifier::from([1u8; 32]),
1974            transitions: vec![],
1975            user_fee_increase: 0,
1976            signature_public_key_id: 0,
1977            signature: BinaryData::new(vec![]),
1978        }))
1979    }
1980
1981    fn sample_identity_create_st() -> StateTransition {
1982        StateTransition::IdentityCreate(IdentityCreateTransition::V0(IdentityCreateTransitionV0 {
1983            identity_id: Identifier::from([3u8; 32]),
1984            ..Default::default()
1985        }))
1986    }
1987
1988    fn sample_identity_top_up_st() -> StateTransition {
1989        StateTransition::IdentityTopUp(IdentityTopUpTransition::V0(IdentityTopUpTransitionV0 {
1990            identity_id: Identifier::from([4u8; 32]),
1991            ..Default::default()
1992        }))
1993    }
1994
1995    fn sample_identity_update_st() -> StateTransition {
1996        StateTransition::IdentityUpdate(IdentityUpdateTransition::V0(IdentityUpdateTransitionV0 {
1997            identity_id: Identifier::from([5u8; 32]),
1998            revision: 1,
1999            nonce: 2,
2000            add_public_keys: vec![],
2001            disable_public_keys: vec![],
2002            user_fee_increase: 11,
2003            signature_public_key_id: 33,
2004            signature: BinaryData::new(vec![0xFF; 65]),
2005        }))
2006    }
2007
2008    fn sample_unshield_st() -> StateTransition {
2009        StateTransition::Unshield(UnshieldTransition::V0(UnshieldTransitionV0 {
2010            output_address: Default::default(),
2011            actions: vec![],
2012            unshielding_amount: 0,
2013            anchor: [0u8; 32],
2014            proof: vec![],
2015            binding_signature: [0u8; 64],
2016        }))
2017    }
2018
2019    fn sample_shielded_transfer_st() -> StateTransition {
2020        StateTransition::ShieldedTransfer(ShieldedTransferTransition::V0(
2021            ShieldedTransferTransitionV0 {
2022                actions: vec![],
2023                value_balance: 0,
2024                anchor: [0u8; 32],
2025                proof: vec![],
2026                binding_signature: [0u8; 64],
2027            },
2028        ))
2029    }
2030
2031    fn sample_shielded_withdrawal_st() -> StateTransition {
2032        use crate::identity::core_script::CoreScript;
2033        use crate::withdrawal::Pooling;
2034        StateTransition::ShieldedWithdrawal(ShieldedWithdrawalTransition::V0(
2035            ShieldedWithdrawalTransitionV0 {
2036                actions: vec![],
2037                unshielding_amount: 0,
2038                anchor: [0u8; 32],
2039                proof: vec![],
2040                binding_signature: [0u8; 64],
2041                core_fee_per_byte: 1,
2042                pooling: Pooling::Never,
2043                output_script: CoreScript::from_bytes(vec![]),
2044            },
2045        ))
2046    }
2047
2048    // --- name() covers all previously-untested arms, including the nested
2049    // match for Batch variants. ---
2050    #[test]
2051    fn test_name_for_newly_covered_variants() {
2052        assert_eq!(
2053            sample_data_contract_create_st().name(),
2054            "DataContractCreate"
2055        );
2056        assert_eq!(
2057            sample_data_contract_update_st().name(),
2058            "DataContractUpdate"
2059        );
2060        assert_eq!(sample_identity_create_st().name(), "IdentityCreate");
2061        assert_eq!(sample_identity_top_up_st().name(), "IdentityTopUp");
2062        assert_eq!(sample_identity_update_st().name(), "IdentityUpdate");
2063        assert_eq!(sample_unshield_st().name(), "Unshield");
2064        assert_eq!(sample_shielded_transfer_st().name(), "ShieldedTransfer");
2065        assert_eq!(sample_shielded_withdrawal_st().name(), "ShieldedWithdrawal");
2066
2067        // Batch with a single Delete – exercises the nested DocumentTransition
2068        // match arm in `name()`.
2069        let batch_name = sample_batch_st_with_delete().name();
2070        assert_eq!(batch_name, "DocumentsBatch([Delete])");
2071
2072        // Empty batch – still renders, with an empty list.
2073        let empty_name = sample_batch_st_empty().name();
2074        assert_eq!(empty_name, "DocumentsBatch([])");
2075    }
2076
2077    // --- state_transition_type covers the call_method! dispatch. ---
2078    #[test]
2079    fn test_state_transition_type_for_newly_covered_variants() {
2080        assert_eq!(
2081            sample_data_contract_create_st().state_transition_type(),
2082            StateTransitionType::DataContractCreate
2083        );
2084        assert_eq!(
2085            sample_data_contract_update_st().state_transition_type(),
2086            StateTransitionType::DataContractUpdate
2087        );
2088        assert_eq!(
2089            sample_batch_st_with_delete().state_transition_type(),
2090            StateTransitionType::Batch
2091        );
2092        assert_eq!(
2093            sample_identity_create_st().state_transition_type(),
2094            StateTransitionType::IdentityCreate
2095        );
2096        assert_eq!(
2097            sample_identity_top_up_st().state_transition_type(),
2098            StateTransitionType::IdentityTopUp
2099        );
2100        assert_eq!(
2101            sample_identity_update_st().state_transition_type(),
2102            StateTransitionType::IdentityUpdate
2103        );
2104        assert_eq!(
2105            sample_unshield_st().state_transition_type(),
2106            StateTransitionType::Unshield
2107        );
2108        assert_eq!(
2109            sample_shielded_transfer_st().state_transition_type(),
2110            StateTransitionType::ShieldedTransfer
2111        );
2112        assert_eq!(
2113            sample_shielded_withdrawal_st().state_transition_type(),
2114            StateTransitionType::ShieldedWithdrawal
2115        );
2116    }
2117
2118    // --- active_version_range uses different branches per transition
2119    // "group". Exercises the contract-format V1 branch for
2120    // DataContractCreate/Update (9..=LATEST), the BatchTransitionV0 branch
2121    // (ALL_VERSIONS), and the shielded range (12..=LATEST).
2122    #[test]
2123    fn test_active_version_range_contract_and_shielded_branches() {
2124        // DataContractCreate/Update on PlatformVersion::latest use the V1
2125        // contract serialization format, which restricts active range.
2126        let contract_v1_range = 9..=LATEST_VERSION;
2127        assert_eq!(
2128            sample_data_contract_create_st().active_version_range(),
2129            contract_v1_range
2130        );
2131        let contract_v1_range = 9..=LATEST_VERSION;
2132        assert_eq!(
2133            sample_data_contract_update_st().active_version_range(),
2134            contract_v1_range
2135        );
2136        // BatchTransition::V0 → ALL_VERSIONS
2137        assert_eq!(
2138            sample_batch_st_with_delete().active_version_range(),
2139            ALL_VERSIONS
2140        );
2141        // IdentityCreate/TopUp/Update are ALL_VERSIONS.
2142        assert_eq!(
2143            sample_identity_create_st().active_version_range(),
2144            ALL_VERSIONS
2145        );
2146        assert_eq!(
2147            sample_identity_top_up_st().active_version_range(),
2148            ALL_VERSIONS
2149        );
2150        assert_eq!(
2151            sample_identity_update_st().active_version_range(),
2152            ALL_VERSIONS
2153        );
2154        // Shielded variants report a shielded range (12..=LATEST_VERSION).
2155        let shielded_range = 12..=LATEST_VERSION;
2156        assert_eq!(
2157            sample_shielded_transfer_st().active_version_range(),
2158            shielded_range.clone()
2159        );
2160        assert_eq!(
2161            sample_unshield_st().active_version_range(),
2162            shielded_range.clone()
2163        );
2164        assert_eq!(
2165            sample_shielded_withdrawal_st().active_version_range(),
2166            shielded_range
2167        );
2168    }
2169
2170    // --- is_identity_signed exercises the inverted-match logic for the
2171    // shielded / identity-create / topup variants. ---
2172    #[test]
2173    fn test_is_identity_signed_false_for_identity_create_topup_and_shielded() {
2174        assert!(!sample_identity_create_st().is_identity_signed());
2175        assert!(!sample_identity_top_up_st().is_identity_signed());
2176        assert!(!sample_unshield_st().is_identity_signed());
2177        assert!(!sample_shielded_transfer_st().is_identity_signed());
2178        assert!(!sample_shielded_withdrawal_st().is_identity_signed());
2179    }
2180
2181    // --- signature accessor for each arm that returns Some/None; previously
2182    // only IdentityCreditTransfer / MasternodeVote / IdentityCreditWithdrawal
2183    // were covered.
2184    #[test]
2185    fn test_signature_accessor_for_other_variants() {
2186        // Some(_) arms
2187        assert_eq!(
2188            sample_data_contract_create_st().signature().unwrap().len(),
2189            65
2190        );
2191        assert_eq!(
2192            sample_data_contract_update_st().signature().unwrap().len(),
2193            65
2194        );
2195        assert_eq!(sample_batch_st_with_delete().signature().unwrap().len(), 65);
2196        assert_eq!(sample_identity_update_st().signature().unwrap().len(), 65);
2197
2198        // None arms for address / shielded variants.
2199        assert!(sample_unshield_st().signature().is_none());
2200        assert!(sample_shielded_transfer_st().signature().is_none());
2201        assert!(sample_shielded_withdrawal_st().signature().is_none());
2202    }
2203
2204    // --- owner_id accessor for each arm.
2205    #[test]
2206    fn test_owner_id_accessor_for_other_variants() {
2207        assert_eq!(
2208            sample_data_contract_create_st().owner_id(),
2209            Some(Identifier::from([7u8; 32]))
2210        );
2211        assert_eq!(
2212            sample_data_contract_update_st().owner_id(),
2213            Some(Identifier::from([7u8; 32]))
2214        );
2215        assert_eq!(
2216            sample_batch_st_with_delete().owner_id(),
2217            Some(Identifier::from([8u8; 32]))
2218        );
2219        assert_eq!(
2220            sample_identity_update_st().owner_id(),
2221            Some(Identifier::from([5u8; 32]))
2222        );
2223        // These variants unconditionally return None.
2224        assert!(sample_unshield_st().owner_id().is_none());
2225        assert!(sample_shielded_transfer_st().owner_id().is_none());
2226        assert!(sample_shielded_withdrawal_st().owner_id().is_none());
2227    }
2228
2229    // --- user_fee_increase accessor — includes arms that return 0
2230    // unconditionally (shielded/masternode) vs the variants' stored value.
2231    #[test]
2232    fn test_user_fee_increase_for_newly_covered_variants() {
2233        assert_eq!(sample_data_contract_create_st().user_fee_increase(), 5);
2234        assert_eq!(sample_data_contract_update_st().user_fee_increase(), 9);
2235        assert_eq!(sample_batch_st_with_delete().user_fee_increase(), 2);
2236        assert_eq!(sample_identity_update_st().user_fee_increase(), 11);
2237        // Unconditionally 0 for shielded.
2238        assert_eq!(sample_shielded_transfer_st().user_fee_increase(), 0);
2239        assert_eq!(sample_shielded_withdrawal_st().user_fee_increase(), 0);
2240        assert_eq!(sample_unshield_st().user_fee_increase(), 0);
2241    }
2242
2243    // --- set_user_fee_increase for the no-op shielded arms and for the
2244    // transitions that actually do store the value.
2245    #[test]
2246    fn test_set_user_fee_increase_for_newly_covered_variants() {
2247        let mut st = sample_data_contract_create_st();
2248        st.set_user_fee_increase(42);
2249        assert_eq!(st.user_fee_increase(), 42);
2250
2251        let mut st = sample_data_contract_update_st();
2252        st.set_user_fee_increase(13);
2253        assert_eq!(st.user_fee_increase(), 13);
2254
2255        let mut st = sample_batch_st_with_delete();
2256        st.set_user_fee_increase(101);
2257        assert_eq!(st.user_fee_increase(), 101);
2258
2259        let mut st = sample_identity_update_st();
2260        st.set_user_fee_increase(77);
2261        assert_eq!(st.user_fee_increase(), 77);
2262
2263        // Shielded no-ops: value stays 0.
2264        let mut shielded = sample_shielded_transfer_st();
2265        shielded.set_user_fee_increase(99);
2266        assert_eq!(shielded.user_fee_increase(), 0);
2267
2268        let mut withdrawal = sample_shielded_withdrawal_st();
2269        withdrawal.set_user_fee_increase(99);
2270        assert_eq!(withdrawal.user_fee_increase(), 0);
2271
2272        let mut unshield = sample_unshield_st();
2273        unshield.set_user_fee_increase(99);
2274        assert_eq!(unshield.user_fee_increase(), 0);
2275    }
2276
2277    // --- set_signature: exercises the `true` arms we didn't test before
2278    // (DataContractCreate/Update/Batch/IdentityUpdate) and the `false` arms
2279    // (shielded transitions).
2280    #[test]
2281    fn test_set_signature_false_for_shielded_and_identity_create_topup() {
2282        // `false` arms: shield*, shielded*, unshield, address* (no-op, returns false).
2283        let mut st = sample_unshield_st();
2284        assert!(!st.set_signature(BinaryData::new(vec![0xAB; 65])));
2285        let mut st = sample_shielded_transfer_st();
2286        assert!(!st.set_signature(BinaryData::new(vec![0xAB; 65])));
2287        let mut st = sample_shielded_withdrawal_st();
2288        assert!(!st.set_signature(BinaryData::new(vec![0xAB; 65])));
2289    }
2290
2291    #[test]
2292    fn test_set_signature_true_for_newly_covered_variants() {
2293        let mut st = sample_data_contract_create_st();
2294        assert!(st.set_signature(BinaryData::new(vec![0x11; 65])));
2295        assert_eq!(st.signature().unwrap().as_slice(), &[0x11; 65]);
2296
2297        let mut st = sample_data_contract_update_st();
2298        assert!(st.set_signature(BinaryData::new(vec![0x22; 65])));
2299        assert_eq!(st.signature().unwrap().as_slice(), &[0x22; 65]);
2300
2301        let mut st = sample_batch_st_with_delete();
2302        assert!(st.set_signature(BinaryData::new(vec![0x33; 65])));
2303        assert_eq!(st.signature().unwrap().as_slice(), &[0x33; 65]);
2304
2305        let mut st = sample_identity_update_st();
2306        assert!(st.set_signature(BinaryData::new(vec![0x44; 65])));
2307        assert_eq!(st.signature().unwrap().as_slice(), &[0x44; 65]);
2308    }
2309
2310    // --- signature_public_key_id: identity-signed arms return Some, others
2311    // (shielded/identity-create/topup/address) return None.
2312    #[test]
2313    fn test_signature_public_key_id_returns_none_for_non_signed() {
2314        // IdentityCreate / IdentityTopUp / shielded / address variants are all
2315        // "not identity-signed" and return None.
2316        assert!(sample_identity_create_st()
2317            .signature_public_key_id()
2318            .is_none());
2319        assert!(sample_identity_top_up_st()
2320            .signature_public_key_id()
2321            .is_none());
2322        assert!(sample_unshield_st().signature_public_key_id().is_none());
2323        assert!(sample_shielded_transfer_st()
2324            .signature_public_key_id()
2325            .is_none());
2326        assert!(sample_shielded_withdrawal_st()
2327            .signature_public_key_id()
2328            .is_none());
2329    }
2330
2331    #[test]
2332    fn test_signature_public_key_id_for_signed_variants() {
2333        assert_eq!(
2334            sample_data_contract_create_st().signature_public_key_id(),
2335            Some(2)
2336        );
2337        assert_eq!(
2338            sample_data_contract_update_st().signature_public_key_id(),
2339            Some(6)
2340        );
2341        assert_eq!(
2342            sample_batch_st_with_delete().signature_public_key_id(),
2343            Some(7)
2344        );
2345        assert_eq!(
2346            sample_identity_update_st().signature_public_key_id(),
2347            Some(33)
2348        );
2349    }
2350
2351    // --- set_signature_public_key_id: no-op for IdentityCreate/TopUp and
2352    // shielded variants; updates for identity-signed variants. ---
2353    #[test]
2354    fn test_set_signature_public_key_id_noop_for_non_signed() {
2355        // These variants are not identity-signed; setter is a no-op in the
2356        // call_method_identity_signed! macro.
2357        let mut st = sample_identity_create_st();
2358        st.set_signature_public_key_id(100);
2359        assert_eq!(st.signature_public_key_id(), None);
2360
2361        let mut st = sample_identity_top_up_st();
2362        st.set_signature_public_key_id(100);
2363        assert_eq!(st.signature_public_key_id(), None);
2364
2365        let mut st = sample_unshield_st();
2366        st.set_signature_public_key_id(100);
2367        assert_eq!(st.signature_public_key_id(), None);
2368    }
2369
2370    #[test]
2371    fn test_set_signature_public_key_id_updates_for_signed_variants() {
2372        let mut st = sample_data_contract_create_st();
2373        st.set_signature_public_key_id(42);
2374        assert_eq!(st.signature_public_key_id(), Some(42));
2375
2376        let mut st = sample_batch_st_with_delete();
2377        st.set_signature_public_key_id(43);
2378        assert_eq!(st.signature_public_key_id(), Some(43));
2379
2380        let mut st = sample_identity_update_st();
2381        st.set_signature_public_key_id(44);
2382        assert_eq!(st.signature_public_key_id(), Some(44));
2383    }
2384
2385    // --- required_number_of_private_keys defaults to 1 for "signed" variants
2386    // and 0 for shielded ones.
2387    #[test]
2388    fn test_required_number_of_private_keys_various_variants() {
2389        assert_eq!(
2390            sample_data_contract_create_st().required_number_of_private_keys(),
2391            1
2392        );
2393        assert_eq!(
2394            sample_data_contract_update_st().required_number_of_private_keys(),
2395            1
2396        );
2397        assert_eq!(
2398            sample_batch_st_with_delete().required_number_of_private_keys(),
2399            1
2400        );
2401        assert_eq!(
2402            sample_identity_update_st().required_number_of_private_keys(),
2403            1
2404        );
2405        assert_eq!(
2406            sample_identity_create_st().required_number_of_private_keys(),
2407            1
2408        );
2409        // Shielded variants return 0 unconditionally.
2410        assert_eq!(
2411            sample_shielded_transfer_st().required_number_of_private_keys(),
2412            0
2413        );
2414        assert_eq!(
2415            sample_shielded_withdrawal_st().required_number_of_private_keys(),
2416            0
2417        );
2418        assert_eq!(sample_unshield_st().required_number_of_private_keys(), 0);
2419    }
2420
2421    // --- inputs(): None for all these variants (covers the big
2422    // wildcard/None arm in the match).
2423    #[test]
2424    fn test_inputs_none_for_many_variants() {
2425        assert!(sample_data_contract_create_st().inputs().is_none());
2426        assert!(sample_data_contract_update_st().inputs().is_none());
2427        assert!(sample_batch_st_with_delete().inputs().is_none());
2428        assert!(sample_identity_create_st().inputs().is_none());
2429        assert!(sample_identity_top_up_st().inputs().is_none());
2430        assert!(sample_identity_update_st().inputs().is_none());
2431        // Shielded variants also return None for inputs().
2432        assert!(sample_unshield_st().inputs().is_none());
2433        assert!(sample_shielded_transfer_st().inputs().is_none());
2434        assert!(sample_shielded_withdrawal_st().inputs().is_none());
2435    }
2436
2437    // --- optional_asset_lock_proof: None for everything that isn't
2438    // IdentityCreate / IdentityTopUp / ShieldFromAssetLock. The IdentityCreate
2439    // default contains the asset lock proof field, so this forwards to its
2440    // implementation.
2441    #[test]
2442    fn test_optional_asset_lock_proof_returns_none_for_wildcard_arms() {
2443        assert!(sample_data_contract_create_st()
2444            .optional_asset_lock_proof()
2445            .is_none());
2446        assert!(sample_data_contract_update_st()
2447            .optional_asset_lock_proof()
2448            .is_none());
2449        assert!(sample_batch_st_with_delete()
2450            .optional_asset_lock_proof()
2451            .is_none());
2452        assert!(sample_identity_update_st()
2453            .optional_asset_lock_proof()
2454            .is_none());
2455        assert!(sample_unshield_st().optional_asset_lock_proof().is_none());
2456        assert!(sample_shielded_transfer_st()
2457            .optional_asset_lock_proof()
2458            .is_none());
2459        assert!(sample_shielded_withdrawal_st()
2460            .optional_asset_lock_proof()
2461            .is_none());
2462    }
2463
2464    // --- required_asset_lock_balance_for_processing_start returns an
2465    // CorruptedCodeExecution error for non asset-lock variants. Exercise
2466    // additional arms beyond what the original transfer test covered.
2467    #[test]
2468    fn test_required_asset_lock_balance_errors_for_other_non_asset_lock_variants() {
2469        let platform_version = PlatformVersion::latest();
2470
2471        let cases: Vec<(&str, StateTransition)> = vec![
2472            ("DataContractCreate", sample_data_contract_create_st()),
2473            ("DataContractUpdate", sample_data_contract_update_st()),
2474            ("Batch", sample_batch_st_with_delete()),
2475            ("IdentityUpdate", sample_identity_update_st()),
2476            ("MasternodeVote", sample_masternode_vote_st()),
2477            ("Unshield", sample_unshield_st()),
2478            ("ShieldedTransfer", sample_shielded_transfer_st()),
2479            ("ShieldedWithdrawal", sample_shielded_withdrawal_st()),
2480        ];
2481
2482        for (label, st) in cases {
2483            let err = st
2484                .required_asset_lock_balance_for_processing_start(platform_version)
2485                .expect_err(&format!("expected error for {label}"));
2486            match err {
2487                ProtocolError::CorruptedCodeExecution(msg) => {
2488                    assert!(
2489                        msg.contains("is not an asset lock transaction"),
2490                        "unexpected error for {label}: {msg}"
2491                    );
2492                }
2493                other => panic!("expected CorruptedCodeExecution for {label}, got {other:?}"),
2494            }
2495        }
2496    }
2497
2498    // --- unique_identifiers: covers the call_method! dispatch for arms
2499    // beyond credit transfer. Each variant's `unique_identifiers`
2500    // implementation returns a non-empty vector; the individual identifier
2501    // strings may be empty for some variants whose IDs are encoded as empty
2502    // (this method simply shouldn't panic or short-circuit).
2503    #[test]
2504    fn test_unique_identifiers_non_empty_for_other_variants() {
2505        for st in [
2506            sample_data_contract_create_st(),
2507            sample_data_contract_update_st(),
2508            sample_batch_st_with_delete(),
2509            sample_identity_create_st(),
2510            sample_identity_top_up_st(),
2511            sample_identity_update_st(),
2512        ] {
2513            let ids = st.unique_identifiers();
2514            assert!(!ids.is_empty(), "unique_identifiers should not be empty");
2515        }
2516    }
2517
2518    // --- security_level_requirement returns None for identity-create/topup
2519    // and for every shielded/address variant. This hits the None arms in
2520    // call_getter_method_identity_signed!.
2521    #[test]
2522    fn test_security_level_requirement_returns_none_for_non_signed_variants() {
2523        let purpose = Purpose::AUTHENTICATION;
2524        assert!(sample_identity_create_st()
2525            .security_level_requirement(purpose)
2526            .is_none());
2527        assert!(sample_identity_top_up_st()
2528            .security_level_requirement(purpose)
2529            .is_none());
2530        assert!(sample_unshield_st()
2531            .security_level_requirement(purpose)
2532            .is_none());
2533        assert!(sample_shielded_transfer_st()
2534            .security_level_requirement(purpose)
2535            .is_none());
2536        assert!(sample_shielded_withdrawal_st()
2537            .security_level_requirement(purpose)
2538            .is_none());
2539    }
2540
2541    #[test]
2542    fn test_purpose_requirement_returns_none_for_non_signed_variants() {
2543        assert!(sample_identity_create_st().purpose_requirement().is_none());
2544        assert!(sample_identity_top_up_st().purpose_requirement().is_none());
2545        assert!(sample_unshield_st().purpose_requirement().is_none());
2546        assert!(sample_shielded_transfer_st()
2547            .purpose_requirement()
2548            .is_none());
2549        assert!(sample_shielded_withdrawal_st()
2550            .purpose_requirement()
2551            .is_none());
2552    }
2553
2554    // --- From impls: each From<Outer> → StateTransition uses `derive_more::From`.
2555    #[test]
2556    fn test_from_outer_data_contract_create_into_state_transition() {
2557        let outer: DataContractCreateTransition =
2558            DataContractCreateTransition::V0(DataContractCreateTransitionV0 {
2559                data_contract: sample_data_contract_in_serialization_format(),
2560                identity_nonce: 1,
2561                user_fee_increase: 0,
2562                signature_public_key_id: 0,
2563                signature: Default::default(),
2564            });
2565        let st: StateTransition = outer.into();
2566        assert!(matches!(st, StateTransition::DataContractCreate(_)));
2567    }
2568
2569    #[test]
2570    fn test_from_outer_data_contract_update_into_state_transition() {
2571        let outer: DataContractUpdateTransition =
2572            DataContractUpdateTransition::V0(DataContractUpdateTransitionV0 {
2573                identity_contract_nonce: 2,
2574                data_contract: sample_data_contract_in_serialization_format(),
2575                user_fee_increase: 0,
2576                signature_public_key_id: 0,
2577                signature: Default::default(),
2578            });
2579        let st: StateTransition = outer.into();
2580        assert!(matches!(st, StateTransition::DataContractUpdate(_)));
2581    }
2582
2583    #[test]
2584    fn test_from_outer_batch_into_state_transition() {
2585        let outer: BatchTransition = BatchTransition::V0(BatchTransitionV0::default());
2586        let st: StateTransition = outer.into();
2587        assert!(matches!(st, StateTransition::Batch(_)));
2588    }
2589
2590    #[test]
2591    fn test_from_outer_identity_create_into_state_transition() {
2592        let outer: IdentityCreateTransition =
2593            IdentityCreateTransition::V0(IdentityCreateTransitionV0::default());
2594        let st: StateTransition = outer.into();
2595        assert!(matches!(st, StateTransition::IdentityCreate(_)));
2596    }
2597
2598    #[test]
2599    fn test_from_outer_identity_update_into_state_transition() {
2600        let outer: IdentityUpdateTransition =
2601            IdentityUpdateTransition::V0(IdentityUpdateTransitionV0::default());
2602        let st: StateTransition = outer.into();
2603        assert!(matches!(st, StateTransition::IdentityUpdate(_)));
2604    }
2605
2606    // --- transaction_id + clone for additional variants — triggers the
2607    // serialize path for each arm.
2608    #[test]
2609    fn test_transaction_id_and_clone_for_identity_update() {
2610        let st = sample_identity_update_st();
2611        let id_a = st.transaction_id().expect("hash should succeed");
2612        let cloned = st.clone();
2613        let id_b = cloned.transaction_id().expect("hash should succeed");
2614        assert_eq!(id_a, id_b);
2615        assert_eq!(id_a.len(), 32);
2616    }
2617
2618    #[test]
2619    fn test_transaction_id_and_clone_for_data_contract_create() {
2620        let st = sample_data_contract_create_st();
2621        let id_a = st.transaction_id().expect("hash should succeed");
2622        let cloned = st.clone();
2623        let id_b = cloned.transaction_id().expect("hash should succeed");
2624        assert_eq!(id_a, id_b);
2625        assert_eq!(id_a.len(), 32);
2626    }
2627
2628    // --- serialize round-trip for variants beyond credit transfer. ---
2629    #[test]
2630    fn test_serialize_roundtrip_identity_update() {
2631        use crate::serialization::{PlatformDeserializable, PlatformSerializable};
2632        let original = sample_identity_update_st();
2633        let bytes =
2634            PlatformSerializable::serialize_to_bytes(&original).expect("serialize should succeed");
2635        let restored =
2636            StateTransition::deserialize_from_bytes(&bytes).expect("deserialize should succeed");
2637        assert_eq!(original, restored);
2638    }
2639
2640    #[test]
2641    fn test_serialize_roundtrip_data_contract_update() {
2642        use crate::serialization::{PlatformDeserializable, PlatformSerializable};
2643        let original = sample_data_contract_update_st();
2644        let bytes =
2645            PlatformSerializable::serialize_to_bytes(&original).expect("serialize should succeed");
2646        let restored =
2647            StateTransition::deserialize_from_bytes(&bytes).expect("deserialize should succeed");
2648        assert_eq!(original, restored);
2649    }
2650
2651    #[test]
2652    fn test_serialize_roundtrip_batch_empty() {
2653        use crate::serialization::{PlatformDeserializable, PlatformSerializable};
2654        let original = sample_batch_st_empty();
2655        let bytes =
2656            PlatformSerializable::serialize_to_bytes(&original).expect("serialize should succeed");
2657        let restored =
2658            StateTransition::deserialize_from_bytes(&bytes).expect("deserialize should succeed");
2659        assert_eq!(original, restored);
2660    }
2661
2662    // --- deserialize_from_bytes_in_version error path: craft bytes for a
2663    // variant whose `active_version_range()` starts at 11 or 12 and then
2664    // attempt to deserialize them with a PlatformVersion whose protocol
2665    // version is below that range. Exercises the
2666    // `StateTransitionIsNotActiveError` arm.
2667    // ---
2668    #[cfg(all(feature = "state-transitions", feature = "validation"))]
2669    #[test]
2670    fn test_deserialize_from_bytes_in_version_returns_not_active_error() {
2671        use crate::serialization::PlatformSerializable;
2672
2673        // ShieldedTransfer has active_version_range = 12..=LATEST_VERSION.
2674        let original = sample_shielded_transfer_st();
2675        let bytes =
2676            PlatformSerializable::serialize_to_bytes(&original).expect("serialize succeeds");
2677
2678        // Find a real PlatformVersion whose protocol_version is < 12 so the
2679        // range check rejects it. PlatformVersion::get(1) corresponds to
2680        // protocol version 1 which is guaranteed below any shielded range.
2681        let low_version = PlatformVersion::get(1).expect("platform version 1 exists");
2682        assert!(
2683            low_version.protocol_version < 12,
2684            "expected sub-12 version for this test, got {}",
2685            low_version.protocol_version
2686        );
2687
2688        let err = StateTransition::deserialize_from_bytes_in_version(&bytes, low_version)
2689            .expect_err("expected StateTransitionIsNotActiveError for sub-12 protocol");
2690        match err {
2691            ProtocolError::StateTransitionError(
2692                crate::state_transition::errors::StateTransitionError::StateTransitionIsNotActiveError {
2693                    state_transition_type,
2694                    active_version_range,
2695                    current_protocol_version,
2696                },
2697            ) => {
2698                assert_eq!(state_transition_type, "ShieldedTransfer");
2699                assert_eq!(current_protocol_version, low_version.protocol_version);
2700                assert!(active_version_range.start() >= &12);
2701            }
2702            other => panic!("expected StateTransitionIsNotActiveError, got {other:?}"),
2703        }
2704    }
2705
2706    // -----------------------------------------------------------------------
2707    // Additional coverage: variants not yet exercised.
2708    //
2709    // The tests below target:
2710    //   * IdentityCreditWithdrawal::V1 (previously only V0 was covered).
2711    //   * IdentityCreditTransferToAddresses (its own top-level arm).
2712    //   * AddressFundingFromAssetLock (identity-signed + asset-lock arm).
2713    //   * AddressCreditWithdrawal (not identity-signed, address-funds arm).
2714    //   * ShieldFromAssetLock (asset-lock, non-identity-signed).
2715    //   * Batch with a token transition (nested enum, previously only Delete).
2716    // -----------------------------------------------------------------------
2717
2718    use crate::state_transition::identity_credit_transfer_to_addresses_transition::v0::IdentityCreditTransferToAddressesTransitionV0;
2719    use crate::state_transition::identity_credit_transfer_to_addresses_transition::IdentityCreditTransferToAddressesTransition;
2720    use crate::state_transition::identity_credit_withdrawal_transition::v1::IdentityCreditWithdrawalTransitionV1;
2721    use crate::withdrawal::Pooling as WithdrawalPooling;
2722
2723    fn sample_withdrawal_v1_st() -> StateTransition {
2724        let v1 = IdentityCreditWithdrawalTransitionV1 {
2725            identity_id: Identifier::from([12u8; 32]),
2726            amount: 777,
2727            core_fee_per_byte: 2,
2728            pooling: WithdrawalPooling::Standard,
2729            output_script: None,
2730            nonce: 9,
2731            user_fee_increase: 4,
2732            signature_public_key_id: 21,
2733            signature: BinaryData::new(vec![0x12; 65]),
2734        };
2735        StateTransition::IdentityCreditWithdrawal(IdentityCreditWithdrawalTransition::V1(v1))
2736    }
2737
2738    fn sample_credit_transfer_to_addresses_st() -> StateTransition {
2739        let v0 = IdentityCreditTransferToAddressesTransitionV0 {
2740            identity_id: Identifier::from([13u8; 32]),
2741            ..Default::default()
2742        };
2743        StateTransition::IdentityCreditTransferToAddresses(
2744            IdentityCreditTransferToAddressesTransition::V0(v0),
2745        )
2746    }
2747
2748    fn sample_address_credit_withdrawal_st() -> StateTransition {
2749        use crate::state_transition::address_credit_withdrawal_transition::v0::AddressCreditWithdrawalTransitionV0;
2750        StateTransition::AddressCreditWithdrawal(AddressCreditWithdrawalTransition::V0(
2751            AddressCreditWithdrawalTransitionV0::default(),
2752        ))
2753    }
2754
2755    fn sample_shield_from_asset_lock_st() -> StateTransition {
2756        use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0;
2757        StateTransition::ShieldFromAssetLock(ShieldFromAssetLockTransition::V0(
2758            ShieldFromAssetLockTransitionV0 {
2759                asset_lock_proof: Default::default(),
2760                actions: vec![],
2761                value_balance: 100,
2762                anchor: [0u8; 32],
2763                proof: vec![],
2764                binding_signature: [0u8; 64],
2765                signature: BinaryData::new(vec![0x55; 65]),
2766            },
2767        ))
2768    }
2769
2770    // ---------- IdentityCreditWithdrawal V1 accessors ----------
2771
2772    #[test]
2773    fn test_withdrawal_v1_name_and_type() {
2774        let st = sample_withdrawal_v1_st();
2775        assert_eq!(st.name(), "IdentityCreditWithdrawal");
2776        assert_eq!(
2777            st.state_transition_type(),
2778            StateTransitionType::IdentityCreditWithdrawal
2779        );
2780    }
2781
2782    #[test]
2783    fn test_withdrawal_v1_is_identity_signed_true() {
2784        assert!(sample_withdrawal_v1_st().is_identity_signed());
2785    }
2786
2787    #[test]
2788    fn test_withdrawal_v1_signature_and_owner_and_key_id() {
2789        let st = sample_withdrawal_v1_st();
2790        // signature accessor -> Some
2791        let sig = st.signature().expect("V1 withdrawal has a signature");
2792        assert_eq!(sig.as_slice(), &[0x12; 65]);
2793        // owner_id delegates to identity_id
2794        assert_eq!(st.owner_id(), Some(Identifier::from([12u8; 32])));
2795        assert_eq!(st.signature_public_key_id(), Some(21));
2796        assert_eq!(st.user_fee_increase(), 4);
2797    }
2798
2799    #[test]
2800    fn test_withdrawal_v1_set_signature_and_fee_and_key_id() {
2801        let mut st = sample_withdrawal_v1_st();
2802        assert!(st.set_signature(BinaryData::new(vec![0x99; 65])));
2803        assert_eq!(st.signature().unwrap().as_slice(), &[0x99; 65]);
2804
2805        st.set_user_fee_increase(33);
2806        assert_eq!(st.user_fee_increase(), 33);
2807
2808        st.set_signature_public_key_id(64);
2809        assert_eq!(st.signature_public_key_id(), Some(64));
2810    }
2811
2812    #[test]
2813    fn test_withdrawal_v1_serialize_roundtrip_via_state_transition() {
2814        use crate::serialization::{PlatformDeserializable, PlatformSerializable};
2815        let original = sample_withdrawal_v1_st();
2816        let bytes = PlatformSerializable::serialize_to_bytes(&original).expect("serialize ok");
2817        let restored = StateTransition::deserialize_from_bytes(&bytes).expect("deserialize ok");
2818        assert_eq!(original, restored);
2819        // The restored variant must still be V1, not V0 — exercises the
2820        // feature-version dispatch in deserialize.
2821        match restored {
2822            StateTransition::IdentityCreditWithdrawal(IdentityCreditWithdrawalTransition::V1(
2823                _,
2824            )) => {}
2825            other => panic!("expected V1 inner variant, got: {:?}", other),
2826        }
2827    }
2828
2829    #[test]
2830    fn test_withdrawal_v1_transaction_id_differs_from_v0() {
2831        // V0 and V1 carry different serialized forms → distinct transaction ids.
2832        let v0 = sample_withdrawal_st();
2833        let v1 = sample_withdrawal_v1_st();
2834        let id_v0 = v0.transaction_id().expect("v0 hash");
2835        let id_v1 = v1.transaction_id().expect("v1 hash");
2836        assert_ne!(id_v0, id_v1);
2837    }
2838
2839    #[test]
2840    fn test_withdrawal_v1_required_asset_lock_balance_errors() {
2841        let err = sample_withdrawal_v1_st()
2842            .required_asset_lock_balance_for_processing_start(PlatformVersion::latest())
2843            .expect_err("withdrawal is not an asset lock ST");
2844        matches!(err, ProtocolError::CorruptedCodeExecution(_));
2845    }
2846
2847    // ---------- IdentityCreditTransferToAddresses ----------
2848
2849    #[test]
2850    fn test_credit_transfer_to_addresses_name_and_type() {
2851        let st = sample_credit_transfer_to_addresses_st();
2852        assert_eq!(st.name(), "IdentityCreditTransferToAddresses");
2853        assert_eq!(
2854            st.state_transition_type(),
2855            StateTransitionType::IdentityCreditTransferToAddresses
2856        );
2857    }
2858
2859    #[test]
2860    fn test_credit_transfer_to_addresses_signature_some_and_owner_some() {
2861        let st = sample_credit_transfer_to_addresses_st();
2862        assert!(st.signature().is_some(), "has a signature field");
2863        assert_eq!(st.owner_id(), Some(Identifier::from([13u8; 32])));
2864    }
2865
2866    #[test]
2867    fn test_credit_transfer_to_addresses_is_identity_signed_true() {
2868        // Not in the "not identity signed" list → should be true.
2869        assert!(sample_credit_transfer_to_addresses_st().is_identity_signed());
2870    }
2871
2872    #[test]
2873    fn test_credit_transfer_to_addresses_inputs_none_active_range_11_latest() {
2874        let st = sample_credit_transfer_to_addresses_st();
2875        assert!(st.inputs().is_none());
2876        // Per mod.rs table: this variant is in the 11..=LATEST_VERSION group.
2877        let range = st.active_version_range();
2878        assert_eq!(*range.start(), 11);
2879        assert_eq!(*range.end(), LATEST_VERSION);
2880    }
2881
2882    #[test]
2883    fn test_credit_transfer_to_addresses_set_signature_returns_true() {
2884        let mut st = sample_credit_transfer_to_addresses_st();
2885        let ok = st.set_signature(BinaryData::new(vec![0x77; 65]));
2886        assert!(ok);
2887        assert_eq!(st.signature().unwrap().as_slice(), &[0x77; 65]);
2888    }
2889
2890    #[test]
2891    fn test_credit_transfer_to_addresses_from_outer_enum() {
2892        let outer = IdentityCreditTransferToAddressesTransition::V0(
2893            IdentityCreditTransferToAddressesTransitionV0::default(),
2894        );
2895        let st: StateTransition = outer.into();
2896        assert!(matches!(
2897            st,
2898            StateTransition::IdentityCreditTransferToAddresses(_)
2899        ));
2900    }
2901
2902    #[test]
2903    fn test_credit_transfer_to_addresses_user_fee_increase_setter() {
2904        let mut st = sample_credit_transfer_to_addresses_st();
2905        st.set_user_fee_increase(55);
2906        assert_eq!(st.user_fee_increase(), 55);
2907    }
2908
2909    // ---------- AddressCreditWithdrawal ----------
2910
2911    #[test]
2912    fn test_address_credit_withdrawal_name_type_and_accessors() {
2913        let st = sample_address_credit_withdrawal_st();
2914        assert_eq!(st.name(), "AddressCreditWithdrawal");
2915        assert_eq!(
2916            st.state_transition_type(),
2917            StateTransitionType::AddressCreditWithdrawal
2918        );
2919        // signature is None for AddressCreditWithdrawal (see mod.rs arm).
2920        assert!(st.signature().is_none());
2921        // owner_id is None for every address-* variant.
2922        assert!(st.owner_id().is_none());
2923        // inputs → Some (delegated to inner struct's inputs map, may be empty).
2924        assert!(st.inputs().is_some());
2925    }
2926
2927    #[test]
2928    fn test_address_credit_withdrawal_set_signature_returns_false() {
2929        let mut st = sample_address_credit_withdrawal_st();
2930        assert!(!st.set_signature(BinaryData::new(vec![0xAB; 65])));
2931    }
2932
2933    #[test]
2934    fn test_address_credit_withdrawal_is_identity_signed_true() {
2935        // Per mod.rs: `is_identity_signed` is !matches!(identity_create/topup/shield*/unshield/shielded*)
2936        // so address-* variants return true — even though signature() returns None.
2937        assert!(sample_address_credit_withdrawal_st().is_identity_signed());
2938    }
2939
2940    #[test]
2941    fn test_address_credit_withdrawal_active_range_is_11_latest() {
2942        let range = sample_address_credit_withdrawal_st().active_version_range();
2943        assert_eq!(*range.start(), 11);
2944        assert_eq!(*range.end(), LATEST_VERSION);
2945    }
2946
2947    // ---------- ShieldFromAssetLock ----------
2948
2949    #[test]
2950    fn test_shield_from_asset_lock_name_type_and_accessors() {
2951        let st = sample_shield_from_asset_lock_st();
2952        assert_eq!(st.name(), "ShieldFromAssetLock");
2953        assert_eq!(
2954            st.state_transition_type(),
2955            StateTransitionType::ShieldFromAssetLock
2956        );
2957        // signature IS present on ShieldFromAssetLock — Some arm.
2958        let sig = st
2959            .signature()
2960            .expect("shield-from-asset-lock has signature");
2961        assert_eq!(sig.as_slice(), &[0x55; 65]);
2962        // owner_id is always None for shielded-* arms.
2963        assert!(st.owner_id().is_none());
2964    }
2965
2966    #[test]
2967    fn test_shield_from_asset_lock_is_not_identity_signed() {
2968        assert!(!sample_shield_from_asset_lock_st().is_identity_signed());
2969    }
2970
2971    #[test]
2972    fn test_shield_from_asset_lock_optional_asset_lock_proof_some() {
2973        // Critical: this is one of the THREE arms where optional_asset_lock_proof
2974        // actually forwards to Some(_). Other Some arms are covered by
2975        // IdentityCreate and IdentityTopUp which have default asset lock proof.
2976        let st = sample_shield_from_asset_lock_st();
2977        assert!(st.optional_asset_lock_proof().is_some());
2978    }
2979
2980    #[test]
2981    fn test_shield_from_asset_lock_user_fee_increase_is_zero_and_setter_noop() {
2982        let mut st = sample_shield_from_asset_lock_st();
2983        assert_eq!(st.user_fee_increase(), 0);
2984        st.set_user_fee_increase(123);
2985        // Set is a no-op per mod.rs table.
2986        assert_eq!(st.user_fee_increase(), 0);
2987    }
2988
2989    #[test]
2990    fn test_shield_from_asset_lock_set_signature_returns_true() {
2991        let mut st = sample_shield_from_asset_lock_st();
2992        assert!(st.set_signature(BinaryData::new(vec![0x44; 65])));
2993        assert_eq!(st.signature().unwrap().as_slice(), &[0x44; 65]);
2994    }
2995
2996    #[test]
2997    fn test_shield_from_asset_lock_required_asset_lock_balance_succeeds() {
2998        // This is the only arm besides IdentityCreate/TopUp/AddressFundingFromAssetLock
2999        // that returns Ok from required_asset_lock_balance_for_processing_start.
3000        let st = sample_shield_from_asset_lock_st();
3001        let result = st.required_asset_lock_balance_for_processing_start(PlatformVersion::latest());
3002        assert!(
3003            result.is_ok(),
3004            "ShieldFromAssetLock should return Ok, got {:?}",
3005            result
3006        );
3007    }
3008
3009    #[test]
3010    fn test_shield_from_asset_lock_active_range_12_latest() {
3011        let range = sample_shield_from_asset_lock_st().active_version_range();
3012        assert_eq!(*range.start(), 12);
3013        assert_eq!(*range.end(), LATEST_VERSION);
3014    }
3015
3016    // ---------- Batch with Token transition exercises TokenTransfer arm
3017    //            in the name() nested match. ----------
3018
3019    #[test]
3020    fn test_batch_with_token_transfer_name_contains_token_transfer() {
3021        use crate::state_transition::batch_transition::batched_transition::token_transition::TokenTransition as TT;
3022        use crate::state_transition::batch_transition::batched_transition::BatchedTransition;
3023        use crate::state_transition::batch_transition::token_base_transition::v0::TokenBaseTransitionV0;
3024        use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition;
3025        use crate::state_transition::batch_transition::token_transfer_transition::v0::TokenTransferTransitionV0;
3026        use crate::state_transition::batch_transition::token_transfer_transition::TokenTransferTransition;
3027        use crate::state_transition::batch_transition::BatchTransitionV1;
3028
3029        let base = TokenBaseTransition::V0(TokenBaseTransitionV0 {
3030            identity_contract_nonce: 1,
3031            token_contract_position: 0,
3032            data_contract_id: Identifier::from([1u8; 32]),
3033            token_id: Identifier::from([2u8; 32]),
3034            using_group_info: None,
3035        });
3036        let token_transfer = TokenTransferTransition::V0(TokenTransferTransitionV0 {
3037            base,
3038            amount: 100,
3039            recipient_id: Identifier::from([3u8; 32]),
3040            public_note: None,
3041            shared_encrypted_note: None,
3042            private_encrypted_note: None,
3043        });
3044
3045        // BatchTransitionV1 is used for tokens. Build a single-token batch.
3046        let batch = BatchTransition::V1(BatchTransitionV1 {
3047            owner_id: Identifier::from([9u8; 32]),
3048            transitions: vec![BatchedTransition::Token(TT::Transfer(token_transfer))],
3049            user_fee_increase: 0,
3050            signature_public_key_id: 0,
3051            signature: BinaryData::new(vec![0u8; 65]),
3052        });
3053        let st = StateTransition::Batch(batch);
3054
3055        assert_eq!(st.name(), "DocumentsBatch([TokenTransfer])");
3056    }
3057
3058    // -----------------------------------------------------------------------
3059    // Cross-variant consistency: transaction_id is a 32-byte blake3/sha256 hash
3060    // of the serialized form. Make sure it's stable across clones for the newly
3061    // covered variants too.
3062    // -----------------------------------------------------------------------
3063
3064    #[test]
3065    fn test_transaction_id_length_32_for_new_variants() {
3066        for st in [
3067            sample_withdrawal_v1_st(),
3068            sample_credit_transfer_to_addresses_st(),
3069            sample_shield_from_asset_lock_st(),
3070        ] {
3071            let id = st.transaction_id().expect("hash");
3072            assert_eq!(id.len(), 32);
3073        }
3074    }
3075
3076    // -----------------------------------------------------------------------
3077    // Clone-and-equality coverage for newly added variants (PartialEq via
3078    // derived impl, exercises the top-level enum's PartialEq arms).
3079    // -----------------------------------------------------------------------
3080
3081    #[test]
3082    fn test_clone_eq_for_new_variants() {
3083        let cases = [
3084            sample_withdrawal_v1_st(),
3085            sample_credit_transfer_to_addresses_st(),
3086            sample_address_credit_withdrawal_st(),
3087            sample_shield_from_asset_lock_st(),
3088        ];
3089        for st in cases {
3090            let cloned = st.clone();
3091            assert_eq!(st, cloned, "clone must be equal for {}", st.name());
3092        }
3093    }
3094
3095    // -----------------------------------------------------------------------
3096    // unique_identifiers() for address-* variants: the implementation
3097    // dispatches via call_method! and each variant returns a non-empty Vec.
3098    // -----------------------------------------------------------------------
3099
3100    #[test]
3101    fn test_unique_identifiers_for_address_and_shielded_variants() {
3102        // The address variants compute identifiers from their `inputs` map.
3103        // With a default (empty inputs) transition, unique_identifiers is empty
3104        // — that's fine, but we still want to exercise the call_method!
3105        // dispatch for these arms without panicking.
3106        for st in [
3107            sample_address_credit_withdrawal_st(),
3108            sample_shield_from_asset_lock_st(),
3109            sample_credit_transfer_to_addresses_st(),
3110            sample_withdrawal_v1_st(),
3111        ] {
3112            // Just calling unique_identifiers exercises the match arm; the
3113            // result may be empty for default-constructed inputs-based
3114            // variants, non-empty for identity-based ones.
3115            let _ids = st.unique_identifiers();
3116        }
3117        // For the identity-based variants the result IS non-empty.
3118        assert!(!sample_withdrawal_v1_st().unique_identifiers().is_empty());
3119        assert!(!sample_credit_transfer_to_addresses_st()
3120            .unique_identifiers()
3121            .is_empty());
3122    }
3123}