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
8use platform_value::{BinaryData, Identifier};
9pub use state_transition_types::*;
10
11use bincode::{Decode, Encode};
12#[cfg(any(
13    feature = "state-transition-signing",
14    feature = "state-transition-validation"
15))]
16use dashcore::signer;
17#[cfg(feature = "state-transition-validation")]
18use dashcore::signer::double_sha;
19use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable};
20use platform_version::version::{PlatformVersion, ProtocolVersion, ALL_VERSIONS, LATEST_VERSION};
21
22#[cfg(any(
23    feature = "state-transition-signing",
24    feature = "state-transition-validation"
25))]
26use crate::BlsModule;
27use crate::ProtocolError;
28
29mod state_transition_types;
30
31pub mod state_transition_factory;
32
33pub mod errors;
34#[cfg(feature = "state-transition-signing")]
35use crate::util::hash::ripemd160_sha256;
36use crate::util::hash::{hash_double_to_vec, hash_single};
37
38pub mod proof_result;
39mod serialization;
40pub mod state_transitions;
41mod traits;
42
43// pub mod state_transition_fee;
44
45#[cfg(feature = "state-transition-validation")]
46use crate::consensus::basic::UnsupportedFeatureError;
47#[cfg(feature = "state-transition-signing")]
48use crate::consensus::signature::InvalidSignaturePublicKeySecurityLevelError;
49#[cfg(feature = "state-transition-validation")]
50use crate::consensus::signature::{
51    InvalidStateTransitionSignatureError, PublicKeyIsDisabledError, SignatureError,
52};
53#[cfg(feature = "state-transition-validation")]
54use crate::consensus::ConsensusError;
55pub use traits::*;
56
57use crate::address_funds::PlatformAddress;
58use crate::data_contract::serialized_version::DataContractInSerializationFormat;
59use crate::fee::Credits;
60#[cfg(any(
61    feature = "state-transition-signing",
62    feature = "state-transition-validation"
63))]
64use crate::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0;
65#[cfg(feature = "state-transition-signing")]
66use crate::identity::signer::Signer;
67use crate::identity::state_transition::OptionallyAssetLockProved;
68use crate::identity::Purpose;
69#[cfg(any(
70    feature = "state-transition-signing",
71    feature = "state-transition-validation"
72))]
73use crate::identity::{IdentityPublicKey, KeyType};
74use crate::identity::{KeyID, SecurityLevel};
75use crate::prelude::{AddressNonce, AssetLockProof, UserFeeIncrease};
76use crate::serialization::{PlatformDeserializable, Signable};
77use crate::state_transition::address_credit_withdrawal_transition::{
78    AddressCreditWithdrawalTransition, AddressCreditWithdrawalTransitionSignable,
79};
80use crate::state_transition::address_funding_from_asset_lock_transition::{
81    AddressFundingFromAssetLockTransition, AddressFundingFromAssetLockTransitionSignable,
82};
83use crate::state_transition::address_funds_transfer_transition::{
84    AddressFundsTransferTransition, AddressFundsTransferTransitionSignable,
85};
86use crate::state_transition::batch_transition::accessors::DocumentsBatchTransitionAccessorsV0;
87use crate::state_transition::batch_transition::batched_transition::BatchedTransitionRef;
88#[cfg(feature = "state-transition-signing")]
89use crate::state_transition::batch_transition::resolvers::v0::BatchTransitionResolversV0;
90use crate::state_transition::batch_transition::{BatchTransition, BatchTransitionSignable};
91use crate::state_transition::data_contract_create_transition::accessors::DataContractCreateTransitionAccessorsV0;
92use crate::state_transition::data_contract_create_transition::{
93    DataContractCreateTransition, DataContractCreateTransitionSignable,
94};
95use crate::state_transition::data_contract_update_transition::accessors::DataContractUpdateTransitionAccessorsV0;
96use crate::state_transition::data_contract_update_transition::{
97    DataContractUpdateTransition, DataContractUpdateTransitionSignable,
98};
99#[cfg(feature = "state-transition-signing")]
100use crate::state_transition::errors::InvalidSignaturePublicKeyError;
101#[cfg(all(feature = "state-transitions", feature = "validation"))]
102use crate::state_transition::errors::StateTransitionError::StateTransitionIsNotActiveError;
103#[cfg(feature = "state-transition-signing")]
104use crate::state_transition::errors::WrongPublicKeyPurposeError;
105#[cfg(feature = "state-transition-validation")]
106use crate::state_transition::errors::{
107    InvalidIdentityPublicKeyTypeError, PublicKeyMismatchError, StateTransitionIsNotSignedError,
108};
109use crate::state_transition::identity_create_from_addresses_transition::{
110    IdentityCreateFromAddressesTransition, IdentityCreateFromAddressesTransitionSignable,
111};
112use crate::state_transition::identity_create_from_shielded_pool_transition::{
113    IdentityCreateFromShieldedPoolTransition, IdentityCreateFromShieldedPoolTransitionSignable,
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            StateTransition::IdentityCreateFromShieldedPool(st) => st.$method($args),
184        }
185    };
186    ($state_transition:expr, $method:ident ) => {
187        match $state_transition {
188            StateTransition::DataContractCreate(st) => st.$method(),
189            StateTransition::DataContractUpdate(st) => st.$method(),
190            StateTransition::Batch(st) => st.$method(),
191            StateTransition::IdentityCreate(st) => st.$method(),
192            StateTransition::IdentityTopUp(st) => st.$method(),
193            StateTransition::IdentityCreditWithdrawal(st) => st.$method(),
194            StateTransition::IdentityUpdate(st) => st.$method(),
195            StateTransition::IdentityCreditTransfer(st) => st.$method(),
196            StateTransition::MasternodeVote(st) => st.$method(),
197            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method(),
198            StateTransition::IdentityCreateFromAddresses(st) => st.$method(),
199            StateTransition::IdentityTopUpFromAddresses(st) => st.$method(),
200            StateTransition::AddressFundsTransfer(st) => st.$method(),
201            StateTransition::AddressFundingFromAssetLock(st) => st.$method(),
202            StateTransition::AddressCreditWithdrawal(st) => st.$method(),
203            StateTransition::Shield(st) => st.$method(),
204            StateTransition::ShieldedTransfer(st) => st.$method(),
205            StateTransition::Unshield(st) => st.$method(),
206            StateTransition::ShieldFromAssetLock(st) => st.$method(),
207            StateTransition::ShieldedWithdrawal(st) => st.$method(),
208            StateTransition::IdentityCreateFromShieldedPool(st) => st.$method(),
209        }
210    };
211}
212
213macro_rules! call_getter_method_identity_signed {
214    ($state_transition:expr, $method:ident, $args:tt ) => {
215        match $state_transition {
216            StateTransition::DataContractCreate(st) => Some(st.$method($args)),
217            StateTransition::DataContractUpdate(st) => Some(st.$method($args)),
218            StateTransition::Batch(st) => Some(st.$method($args)),
219            StateTransition::IdentityCreate(_) => None,
220            StateTransition::IdentityTopUp(_) => None,
221            StateTransition::IdentityCreditWithdrawal(st) => Some(st.$method($args)),
222            StateTransition::IdentityUpdate(st) => Some(st.$method($args)),
223            StateTransition::IdentityCreditTransfer(st) => Some(st.$method($args)),
224            StateTransition::MasternodeVote(st) => Some(st.$method($args)),
225            StateTransition::IdentityCreditTransferToAddresses(st) => Some(st.$method($args)),
226            StateTransition::IdentityCreateFromAddresses(_) => None,
227            StateTransition::IdentityTopUpFromAddresses(_) => None,
228            StateTransition::AddressFundsTransfer(_) => None,
229            StateTransition::AddressFundingFromAssetLock(_) => None,
230            StateTransition::AddressCreditWithdrawal(_) => None,
231            StateTransition::Shield(_) => None,
232            StateTransition::ShieldedTransfer(_) => None,
233            StateTransition::Unshield(_) => None,
234            StateTransition::ShieldFromAssetLock(_) => None,
235            StateTransition::ShieldedWithdrawal(_) => None,
236            StateTransition::IdentityCreateFromShieldedPool(_) => None,
237        }
238    };
239    ($state_transition:expr, $method:ident ) => {
240        match $state_transition {
241            StateTransition::DataContractCreate(st) => Some(st.$method()),
242            StateTransition::DataContractUpdate(st) => Some(st.$method()),
243            StateTransition::Batch(st) => Some(st.$method()),
244            StateTransition::IdentityCreate(_) => None,
245            StateTransition::IdentityTopUp(_) => None,
246            StateTransition::IdentityCreditWithdrawal(st) => Some(st.$method()),
247            StateTransition::IdentityUpdate(st) => Some(st.$method()),
248            StateTransition::IdentityCreditTransfer(st) => Some(st.$method()),
249            StateTransition::MasternodeVote(st) => Some(st.$method()),
250            StateTransition::IdentityCreditTransferToAddresses(st) => Some(st.$method()),
251            StateTransition::IdentityCreateFromAddresses(_) => None,
252            StateTransition::IdentityTopUpFromAddresses(_) => None,
253            StateTransition::AddressFundsTransfer(_) => None,
254            StateTransition::AddressFundingFromAssetLock(_) => None,
255            StateTransition::AddressCreditWithdrawal(_) => None,
256            StateTransition::Shield(_) => None,
257            StateTransition::ShieldedTransfer(_) => None,
258            StateTransition::Unshield(_) => None,
259            StateTransition::ShieldFromAssetLock(_) => None,
260            StateTransition::ShieldedWithdrawal(_) => None,
261            StateTransition::IdentityCreateFromShieldedPool(_) => None,
262        }
263    };
264}
265
266macro_rules! call_method_identity_signed {
267    ($state_transition:expr, $method:ident, $args:tt ) => {
268        match $state_transition {
269            StateTransition::DataContractCreate(st) => st.$method($args),
270            StateTransition::DataContractUpdate(st) => st.$method($args),
271            StateTransition::Batch(st) => st.$method($args),
272            StateTransition::IdentityCreate(_st) => {}
273            StateTransition::IdentityTopUp(_st) => {}
274            StateTransition::IdentityCreditWithdrawal(st) => st.$method($args),
275            StateTransition::IdentityUpdate(st) => st.$method($args),
276            StateTransition::IdentityCreditTransfer(st) => st.$method($args),
277            StateTransition::MasternodeVote(st) => st.$method($args),
278            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method($args),
279            StateTransition::IdentityCreateFromAddresses(_) => {}
280            StateTransition::IdentityTopUpFromAddresses(_) => {}
281            StateTransition::AddressFundsTransfer(_) => {}
282            StateTransition::AddressFundingFromAssetLock(_) => {}
283            StateTransition::AddressCreditWithdrawal(_) => {}
284            StateTransition::Shield(_) => {}
285            StateTransition::ShieldedTransfer(_) => {}
286            StateTransition::Unshield(_) => {}
287            StateTransition::ShieldFromAssetLock(_) => {}
288            StateTransition::ShieldedWithdrawal(_) => {}
289            StateTransition::IdentityCreateFromShieldedPool(_) => {}
290        }
291    };
292    ($state_transition:expr, $method:ident ) => {
293        match $state_transition {
294            StateTransition::DataContractCreate(st) => st.$method(),
295            StateTransition::DataContractUpdate(st) => st.$method(),
296            StateTransition::Batch(st) => st.$method(),
297            StateTransition::IdentityCreate(st) => {}
298            StateTransition::IdentityTopUp(st) => {}
299            StateTransition::IdentityCreditWithdrawal(st) => st.$method(),
300            StateTransition::IdentityUpdate(st) => st.$method(),
301            StateTransition::IdentityCreditTransfer(st) => st.$method(),
302            StateTransition::MasternodeVote(st) => st.$method(),
303            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method(),
304            StateTransition::IdentityCreateFromAddresses(_) => {}
305            StateTransition::IdentityTopUpFromAddresses(_) => {}
306            StateTransition::AddressFundsTransfer(_) => {}
307            StateTransition::AddressFundingFromAssetLock(_) => {}
308            StateTransition::AddressCreditWithdrawal(_) => {}
309            StateTransition::Shield(_) => {}
310            StateTransition::ShieldedTransfer(_) => {}
311            StateTransition::Unshield(_) => {}
312            StateTransition::ShieldFromAssetLock(_) => {}
313            StateTransition::ShieldedWithdrawal(_) => {}
314            StateTransition::IdentityCreateFromShieldedPool(_) => {}
315        }
316    };
317}
318
319#[cfg(feature = "state-transition-signing")]
320macro_rules! call_errorable_method_identity_signed {
321    ($state_transition:expr, $method:ident, $( $arg:expr ),* ) => {
322        match $state_transition {
323            StateTransition::DataContractCreate(st) => st.$method($( $arg ),*),
324            StateTransition::DataContractUpdate(st) => st.$method($( $arg ),*),
325            StateTransition::Batch(st) => st.$method($( $arg ),*),
326            StateTransition::IdentityCreate(_) => Err(ProtocolError::CorruptedCodeExecution(
327                "identity create can not be called for identity signing".to_string(),
328            )),
329            StateTransition::IdentityTopUp(_) => Err(ProtocolError::CorruptedCodeExecution(
330                "identity top up can not be called for identity signing".to_string(),
331            )),
332            StateTransition::IdentityCreditWithdrawal(st) => st.$method($( $arg ),*),
333            StateTransition::IdentityUpdate(st) => st.$method($( $arg ),*),
334            StateTransition::IdentityCreditTransfer(st) => st.$method($( $arg ),*),
335            StateTransition::MasternodeVote(st) => st.$method($( $arg ),*),
336            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method($( $arg ),*),
337            StateTransition::IdentityCreateFromAddresses(_) => Err(ProtocolError::CorruptedCodeExecution(
338                "identity create from addresses can not be called for identity signing".to_string(),
339            )),
340            StateTransition::IdentityTopUpFromAddresses(_) => Err(ProtocolError::CorruptedCodeExecution(
341                "identity top up from addresses can not be called for identity signing".to_string(),
342            )),
343            StateTransition::AddressFundsTransfer(_) => Err(ProtocolError::CorruptedCodeExecution(
344                "address funds transfer can not be called for identity signing".to_string(),
345            )),
346            StateTransition::AddressFundingFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution(
347                "address funding from asset lock can not be called for identity signing".to_string(),
348            )),
349            StateTransition::AddressCreditWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution(
350                "address credit withdrawal can not be called for identity signing".to_string(),
351            )),
352            StateTransition::Shield(_) => Err(ProtocolError::CorruptedCodeExecution(
353                "shield transition can not be called for identity signing".to_string(),
354            )),
355            StateTransition::ShieldedTransfer(_) => Err(ProtocolError::CorruptedCodeExecution(
356                "shielded transfer transition can not be called for identity signing".to_string(),
357            )),
358            StateTransition::Unshield(_) => Err(ProtocolError::CorruptedCodeExecution(
359                "unshield transition can not be called for identity signing".to_string(),
360            )),
361            StateTransition::ShieldFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution(
362                "shield from asset lock transition can not be called for identity signing".to_string(),
363            )),
364            StateTransition::ShieldedWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution(
365                "shielded withdrawal transition can not be called for identity signing".to_string(),
366            )),
367            StateTransition::IdentityCreateFromShieldedPool(_) => Err(ProtocolError::CorruptedCodeExecution(
368                "identity create from shielded pool transition can not be called for identity signing".to_string(),
369            )),
370        }
371    };
372    ($state_transition:expr, $method:ident) => {
373        match $state_transition {
374            StateTransition::DataContractCreate(st) => st.$method(),
375            StateTransition::DataContractUpdate(st) => st.$method(),
376            StateTransition::Batch(st) => st.$method(),
377            StateTransition::IdentityCreate(_) => Err(ProtocolError::CorruptedCodeExecution(
378                "identity create can not be called for identity signing".to_string(),
379            )),
380            StateTransition::IdentityTopUp(_) => Err(ProtocolError::CorruptedCodeExecution(
381                "identity top up can not be called for identity signing".to_string(),
382            )),
383            StateTransition::IdentityCreditWithdrawal(st) => st.$method(),
384            StateTransition::IdentityUpdate(st) => st.$method(),
385            StateTransition::IdentityCreditTransfer(st) => st.$method(),
386            StateTransition::MasternodeVote(st) => st.$method(),
387            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method(),
388            StateTransition::IdentityCreateFromAddresses(st) => Err(ProtocolError::CorruptedCodeExecution(
389                "identity create from addresses can not be called for identity signing".to_string(),
390            )),
391            StateTransition::IdentityTopUpFromAddresses(_) => Err(ProtocolError::CorruptedCodeExecution(
392                "identity top up from addresses can not be called for identity signing".to_string(),
393            )),
394            StateTransition::AddressFundsTransfer(_) => Err(ProtocolError::CorruptedCodeExecution(
395                "address funds transfer can not be called for identity signing".to_string(),
396            )),
397            StateTransition::AddressFundingFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution(
398                "address funding from asset lock can not be called for identity signing".to_string(),
399            )),
400            StateTransition::AddressCreditWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution(
401                "address credit withdrawal can not be called for identity signing".to_string(),
402            )),
403            StateTransition::Shield(_) => Err(ProtocolError::CorruptedCodeExecution(
404                "shield transition can not be called for identity signing".to_string(),
405            )),
406            StateTransition::ShieldedTransfer(_) => Err(ProtocolError::CorruptedCodeExecution(
407                "shielded transfer transition can not be called for identity signing".to_string(),
408            )),
409            StateTransition::Unshield(_) => Err(ProtocolError::CorruptedCodeExecution(
410                "unshield transition can not be called for identity signing".to_string(),
411            )),
412            StateTransition::ShieldFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution(
413                "shield from asset lock transition can not be called for identity signing".to_string(),
414            )),
415            StateTransition::ShieldedWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution(
416                "shielded withdrawal transition can not be called for identity signing".to_string(),
417            )),
418            StateTransition::IdentityCreateFromShieldedPool(_) => Err(ProtocolError::CorruptedCodeExecution(
419                "identity create from shielded pool transition can not be called for identity signing".to_string(),
420            )),
421        }
422    };
423}
424
425#[derive(
426    Debug,
427    Clone,
428    Encode,
429    Decode,
430    PlatformSerialize,
431    PlatformDeserialize,
432    PlatformSignable,
433    From,
434    PartialEq,
435)]
436// `tag = "$type"` matches the system-field convention: every serde-injected
437// discriminator key in this crate carries a `$` prefix so it never collides
438// with user-data field names. Discriminates between **semantically
439// different variants** of the same kind (rather than **versions** of one
440// logical type, which use `tag = "$formatVersion"`).
441//
442// `$type` here is at the OUTERMOST level — there's no flatten path that
443// would put it next to a base's `document_type_name` (renamed to `$type`
444// in the wire). Inner umbrellas (`DocumentTransition`, `TokenTransition`)
445// use `$action` instead because they DO flatten the document base.
446//
447// Was previously `serde(untagged)`, which made deserialize ambiguous (each
448// variant tried in order until one matched structurally). The new
449// self-describing wire shape is `{"$type": "dataContractCreate", ...inner
450// fields...}`.
451//
452// The binary wire path (`PlatformSerialize`) is unchanged — only JSON/Value
453// consumers see the new shape, and there are no rs-drive / rs-drive-abci /
454// rs-sdk callers that route the umbrella through to_json/to_object today.
455#[cfg_attr(
456    feature = "serde-conversion",
457    derive(Serialize, Deserialize),
458    serde(tag = "$type", rename_all = "camelCase")
459)]
460#[platform_serialize(unversioned)] //versioned directly, no need to use platform_version
461#[platform_serialize(limit = 100000)]
462pub enum StateTransition {
463    DataContractCreate(DataContractCreateTransition),
464    DataContractUpdate(DataContractUpdateTransition),
465    Batch(BatchTransition),
466    IdentityCreate(IdentityCreateTransition),
467    IdentityTopUp(IdentityTopUpTransition),
468    IdentityCreditWithdrawal(IdentityCreditWithdrawalTransition),
469    IdentityUpdate(IdentityUpdateTransition),
470    IdentityCreditTransfer(IdentityCreditTransferTransition),
471    MasternodeVote(MasternodeVoteTransition),
472    IdentityCreditTransferToAddresses(IdentityCreditTransferToAddressesTransition),
473    IdentityCreateFromAddresses(IdentityCreateFromAddressesTransition),
474    IdentityTopUpFromAddresses(IdentityTopUpFromAddressesTransition),
475    AddressFundsTransfer(AddressFundsTransferTransition),
476    AddressFundingFromAssetLock(AddressFundingFromAssetLockTransition),
477    AddressCreditWithdrawal(AddressCreditWithdrawalTransition),
478    Shield(ShieldTransition),
479    ShieldedTransfer(ShieldedTransferTransition),
480    Unshield(UnshieldTransition),
481    ShieldFromAssetLock(ShieldFromAssetLockTransition),
482    ShieldedWithdrawal(ShieldedWithdrawalTransition),
483    IdentityCreateFromShieldedPool(IdentityCreateFromShieldedPoolTransition),
484}
485
486#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
487impl crate::serialization::JsonConvertible for StateTransition {}
488
489#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
490impl crate::serialization::ValueConvertible for StateTransition {}
491
492#[cfg(all(
493    test,
494    feature = "json-conversion",
495    feature = "value-conversion",
496    feature = "serde-conversion"
497))]
498mod json_convertible_tests {
499    use super::*;
500
501    /// Round-trip a StateTransition through both JSON and Value, asserting:
502    /// 1. The wire emits `{"$type": "<expected_tag>", ...}` (umbrella's
503    ///    `tag = "$type", rename_all = "camelCase"` is correctly applied).
504    /// 2. Round-trip preserves the variant.
505    /// 3. Round-trip preserves structural equality (PartialEq on the inner).
506    ///
507    /// Inner field shapes are covered by each inner type's dedicated
508    /// `*_with_full_wire_shape` test — this helper only exercises the
509    /// umbrella's tag-dispatch boundary. The risk it catches: an inner
510    /// variant whose serde body conflicts with the umbrella's `"$type"` key,
511    /// or a serde rename that resolves to something other than the
512    /// expected camelCase form.
513    ///
514    /// `lossy_json_int_variants`: when true, the JSON-side equality assertion
515    /// runs after `normalize_integer_variants_for_json_round_trip` on both
516    /// sides. Required for variants that embed a `DataContract` —
517    /// `document_schemas` carry sized integer variants (`U32`/`I32`) that
518    /// JSON's single Number type cannot preserve. See commit 7397c73f31.
519    fn assert_umbrella_round_trip_inner(
520        original: StateTransition,
521        expected_type_tag: &str,
522        lossy_json_int_variants: bool,
523    ) {
524        use crate::serialization::{JsonConvertible, ValueConvertible};
525
526        // JSON
527        let json = original.to_json().expect("to_json");
528        assert_eq!(
529            json["$type"], expected_type_tag,
530            "json type tag for {expected_type_tag}",
531        );
532        let recovered = StateTransition::from_json(json).expect("from_json round-trip");
533        assert_eq!(
534            std::mem::discriminant(&original),
535            std::mem::discriminant(&recovered),
536            "json round-trip variant for {expected_type_tag}",
537        );
538        if lossy_json_int_variants {
539            use crate::tests::utils::normalize_integer_variants_for_json_round_trip;
540            let mut original_canon = original.to_object().expect("to_object");
541            let mut recovered_canon = recovered.to_object().expect("to_object");
542            normalize_integer_variants_for_json_round_trip(&mut original_canon);
543            normalize_integer_variants_for_json_round_trip(&mut recovered_canon);
544            assert_eq!(
545                original_canon, recovered_canon,
546                "json round-trip equality (modulo int-variant) for {expected_type_tag}",
547            );
548        } else {
549            assert_eq!(
550                original, recovered,
551                "json round-trip equality for {expected_type_tag}"
552            );
553        }
554
555        // Value
556        let value = original.to_object().expect("to_object");
557        let map = value.as_map().expect("Value::Map");
558        let tag = map
559            .iter()
560            .find(|(k, _)| k.as_text() == Some("$type"))
561            .map(|(_, v)| v)
562            .unwrap_or_else(|| panic!("type tag missing for {expected_type_tag}"));
563        assert_eq!(
564            *tag,
565            platform_value::Value::Text(expected_type_tag.to_string()),
566            "value type tag for {expected_type_tag}",
567        );
568        let recovered = StateTransition::from_object(value).expect("from_object round-trip");
569        assert_eq!(
570            std::mem::discriminant(&original),
571            std::mem::discriminant(&recovered),
572            "value round-trip variant for {expected_type_tag}",
573        );
574        assert_eq!(
575            original, recovered,
576            "value round-trip equality for {expected_type_tag}"
577        );
578    }
579
580    fn assert_umbrella_round_trip(original: StateTransition, expected_type_tag: &str) {
581        assert_umbrella_round_trip_inner(original, expected_type_tag, false);
582    }
583
584    /// Variant of `assert_umbrella_round_trip` for transitions that embed a
585    /// `DataContract` (`DataContractCreate`, `DataContractUpdate`). JSON's
586    /// single Number type collapses sized-int variants in the embedded
587    /// `document_schemas` tree, so the JSON-side equality assertion is
588    /// run modulo integer-variant normalization. The Value path keeps its
589    /// strict bit-exact assertion (platform_value preserves sized ints).
590    fn assert_umbrella_round_trip_lossy_json_int_variants(
591        original: StateTransition,
592        expected_type_tag: &str,
593    ) {
594        assert_umbrella_round_trip_inner(original, expected_type_tag, true);
595    }
596
597    // Per-variant umbrella round-trip tests. Inner fixtures are reused from
598    // each transition's own `json_convertible_tests::fixture()` (made
599    // `pub(crate)` for this purpose) — keeps the umbrella tests in sync
600    // with the inner-type tests automatically.
601
602    #[test]
603    fn umbrella_data_contract_create() {
604        let inner = crate::state_transition::data_contract_create_transition::json_convertible_tests::fixture();
605        assert_umbrella_round_trip_lossy_json_int_variants(
606            StateTransition::DataContractCreate(inner),
607            "dataContractCreate",
608        );
609    }
610
611    #[test]
612    fn umbrella_data_contract_update() {
613        let inner = crate::state_transition::data_contract_update_transition::json_convertible_tests::fixture();
614        assert_umbrella_round_trip_lossy_json_int_variants(
615            StateTransition::DataContractUpdate(inner),
616            "dataContractUpdate",
617        );
618    }
619
620    #[test]
621    fn umbrella_batch() {
622        let inner = crate::state_transition::batch_transition::json_convertible_tests::fixture();
623        assert_umbrella_round_trip(StateTransition::Batch(inner), "batch");
624    }
625
626    #[test]
627    fn umbrella_identity_create() {
628        let inner =
629            crate::state_transition::identity_create_transition::json_convertible_tests::fixture();
630        assert_umbrella_round_trip(StateTransition::IdentityCreate(inner), "identityCreate");
631    }
632
633    #[test]
634    fn umbrella_identity_top_up() {
635        let inner =
636            crate::state_transition::identity_topup_transition::json_convertible_tests::fixture();
637        assert_umbrella_round_trip(StateTransition::IdentityTopUp(inner), "identityTopUp");
638    }
639
640    #[test]
641    fn umbrella_identity_credit_withdrawal() {
642        let inner = crate::state_transition::identity_credit_withdrawal_transition::json_convertible_tests::fixture();
643        assert_umbrella_round_trip(
644            StateTransition::IdentityCreditWithdrawal(inner),
645            "identityCreditWithdrawal",
646        );
647    }
648
649    #[test]
650    fn umbrella_identity_update() {
651        let inner =
652            crate::state_transition::identity_update_transition::json_convertible_tests::fixture();
653        assert_umbrella_round_trip(StateTransition::IdentityUpdate(inner), "identityUpdate");
654    }
655
656    #[test]
657    fn umbrella_identity_credit_transfer() {
658        let inner = crate::state_transition::identity_credit_transfer_transition::json_convertible_tests::fixture();
659        assert_umbrella_round_trip(
660            StateTransition::IdentityCreditTransfer(inner),
661            "identityCreditTransfer",
662        );
663    }
664
665    #[test]
666    fn umbrella_masternode_vote() {
667        let inner =
668            crate::state_transition::masternode_vote_transition::json_convertible_tests::fixture();
669        assert_umbrella_round_trip(StateTransition::MasternodeVote(inner), "masternodeVote");
670    }
671
672    #[test]
673    fn umbrella_identity_credit_transfer_to_addresses() {
674        let inner = crate::state_transition::identity_credit_transfer_to_addresses_transition::json_convertible_tests::fixture();
675        assert_umbrella_round_trip(
676            StateTransition::IdentityCreditTransferToAddresses(inner),
677            "identityCreditTransferToAddresses",
678        );
679    }
680
681    #[test]
682    fn umbrella_identity_create_from_addresses() {
683        let inner = crate::state_transition::identity_create_from_addresses_transition::json_convertible_tests::fixture();
684        assert_umbrella_round_trip(
685            StateTransition::IdentityCreateFromAddresses(inner),
686            "identityCreateFromAddresses",
687        );
688    }
689
690    #[test]
691    fn umbrella_identity_top_up_from_addresses() {
692        let inner = crate::state_transition::identity_topup_from_addresses_transition::json_convertible_tests::fixture();
693        assert_umbrella_round_trip(
694            StateTransition::IdentityTopUpFromAddresses(inner),
695            "identityTopUpFromAddresses",
696        );
697    }
698
699    #[test]
700    fn umbrella_address_funds_transfer() {
701        let inner = crate::state_transition::address_funds_transfer_transition::json_convertible_tests::fixture();
702        assert_umbrella_round_trip(
703            StateTransition::AddressFundsTransfer(inner),
704            "addressFundsTransfer",
705        );
706    }
707
708    #[test]
709    fn umbrella_address_funding_from_asset_lock() {
710        let inner = crate::state_transition::address_funding_from_asset_lock_transition::json_convertible_tests::fixture();
711        assert_umbrella_round_trip(
712            StateTransition::AddressFundingFromAssetLock(inner),
713            "addressFundingFromAssetLock",
714        );
715    }
716
717    #[test]
718    fn umbrella_address_credit_withdrawal() {
719        let inner = crate::state_transition::address_credit_withdrawal_transition::json_convertible_tests::fixture();
720        assert_umbrella_round_trip(
721            StateTransition::AddressCreditWithdrawal(inner),
722            "addressCreditWithdrawal",
723        );
724    }
725
726    #[test]
727    fn umbrella_shield() {
728        let inner = crate::state_transition::shield_transition::json_convertible_tests::fixture();
729        assert_umbrella_round_trip(StateTransition::Shield(inner), "shield");
730    }
731
732    #[test]
733    fn umbrella_shielded_transfer() {
734        let inner =
735            crate::state_transition::shielded_transfer_transition::json_convertible_tests::fixture(
736            );
737        assert_umbrella_round_trip(StateTransition::ShieldedTransfer(inner), "shieldedTransfer");
738    }
739
740    #[test]
741    fn umbrella_unshield() {
742        let inner = crate::state_transition::unshield_transition::json_convertible_tests::fixture();
743        assert_umbrella_round_trip(StateTransition::Unshield(inner), "unshield");
744    }
745
746    #[test]
747    fn umbrella_shield_from_asset_lock() {
748        let inner = crate::state_transition::shield_from_asset_lock_transition::json_convertible_tests::fixture();
749        assert_umbrella_round_trip(
750            StateTransition::ShieldFromAssetLock(inner),
751            "shieldFromAssetLock",
752        );
753    }
754
755    #[test]
756    fn umbrella_shielded_withdrawal() {
757        let inner = crate::state_transition::shielded_withdrawal_transition::json_convertible_tests::fixture();
758        assert_umbrella_round_trip(
759            StateTransition::ShieldedWithdrawal(inner),
760            "shieldedWithdrawal",
761        );
762    }
763
764    #[test]
765    fn umbrella_identity_create_from_shielded_pool() {
766        let inner = crate::state_transition::identity_create_from_shielded_pool_transition::json_convertible_tests::fixture();
767        assert_umbrella_round_trip(
768            StateTransition::IdentityCreateFromShieldedPool(inner),
769            "identityCreateFromShieldedPool",
770        );
771    }
772}
773
774impl OptionallyAssetLockProved for StateTransition {
775    fn optional_asset_lock_proof(&self) -> Option<&AssetLockProof> {
776        match self {
777            StateTransition::IdentityCreate(st) => st.optional_asset_lock_proof(),
778            StateTransition::IdentityTopUp(st) => st.optional_asset_lock_proof(),
779            StateTransition::ShieldFromAssetLock(st) => st.optional_asset_lock_proof(),
780            _ => None,
781        }
782    }
783}
784
785/// The state transition signing options
786#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
787pub struct StateTransitionSigningOptions {
788    /// This will allow signing with any security level for debugging purposes
789    pub allow_signing_with_any_security_level: bool,
790    /// This will allow signing with any purpose for debugging purposes
791    pub allow_signing_with_any_purpose: bool,
792}
793
794impl StateTransition {
795    #[allow(unused_variables)]
796    pub fn deserialize_from_bytes_in_version(
797        bytes: &[u8],
798        platform_version: &PlatformVersion,
799    ) -> Result<Self, ProtocolError> {
800        let max_value_depth = platform_version
801            .system_limits
802            .max_document_value_depth
803            .map(usize::from);
804        let state_transition =
805            platform_value::with_value_decode_depth_limit(max_value_depth, || {
806                StateTransition::deserialize_from_bytes(bytes)
807            })?;
808        #[cfg(all(feature = "state-transitions", feature = "validation"))]
809        {
810            let active_version_range = state_transition.active_version_range();
811
812            // Tests are done with very high protocol ranges, while we could put this behind a feature,
813            // that would probably be overkill.
814            if active_version_range.contains(&platform_version.protocol_version)
815                || platform_version.protocol_version > 268435456
816            {
817                Ok(state_transition)
818            } else {
819                Err(ProtocolError::StateTransitionError(
820                    StateTransitionIsNotActiveError {
821                        state_transition_type: state_transition.name(),
822                        active_version_range,
823                        current_protocol_version: platform_version.protocol_version,
824                    },
825                ))
826            }
827        }
828        #[cfg(not(all(feature = "state-transitions", feature = "validation")))]
829        Ok(state_transition)
830    }
831
832    pub fn active_version_range(&self) -> RangeInclusive<ProtocolVersion> {
833        match self {
834            StateTransition::DataContractCreate(data_contract_create_transition) => {
835                match data_contract_create_transition.data_contract() {
836                    DataContractInSerializationFormat::V0(_) => ALL_VERSIONS,
837                    DataContractInSerializationFormat::V1(_) => 9..=LATEST_VERSION,
838                }
839            }
840            StateTransition::DataContractUpdate(data_contract_update_transition) => {
841                match data_contract_update_transition.data_contract() {
842                    DataContractInSerializationFormat::V0(_) => ALL_VERSIONS,
843                    DataContractInSerializationFormat::V1(_) => 9..=LATEST_VERSION,
844                }
845            }
846            StateTransition::Batch(batch_transition) => match batch_transition {
847                BatchTransition::V0(_) => ALL_VERSIONS,
848                BatchTransition::V1(_) => 9..=LATEST_VERSION,
849            },
850            StateTransition::IdentityCreate(_)
851            | StateTransition::IdentityTopUp(_)
852            | StateTransition::IdentityCreditWithdrawal(_)
853            | StateTransition::IdentityUpdate(_)
854            | StateTransition::IdentityCreditTransfer(_)
855            | StateTransition::MasternodeVote(_) => ALL_VERSIONS,
856            StateTransition::IdentityCreditTransferToAddresses(_)
857            | StateTransition::IdentityCreateFromAddresses(_)
858            | StateTransition::IdentityTopUpFromAddresses(_)
859            | StateTransition::AddressFundsTransfer(_)
860            | StateTransition::AddressFundingFromAssetLock(_)
861            | StateTransition::AddressCreditWithdrawal(_) => 11..=LATEST_VERSION,
862            StateTransition::Shield(_)
863            | StateTransition::ShieldedTransfer(_)
864            | StateTransition::Unshield(_)
865            | StateTransition::ShieldFromAssetLock(_)
866            | StateTransition::ShieldedWithdrawal(_)
867            | StateTransition::IdentityCreateFromShieldedPool(_) => 12..=LATEST_VERSION,
868        }
869    }
870
871    pub fn is_identity_signed(&self) -> bool {
872        !matches!(
873            self,
874            StateTransition::IdentityCreate(_)
875                | StateTransition::IdentityTopUp(_)
876                | StateTransition::Shield(_)
877                | StateTransition::ShieldedTransfer(_)
878                | StateTransition::Unshield(_)
879                | StateTransition::ShieldFromAssetLock(_)
880                | StateTransition::ShieldedWithdrawal(_)
881                | StateTransition::IdentityCreateFromShieldedPool(_)
882        )
883    }
884
885    pub fn required_asset_lock_balance_for_processing_start(
886        &self,
887        platform_version: &PlatformVersion,
888    ) -> Result<Credits, ProtocolError> {
889        match self {
890            StateTransition::IdentityCreate(st) => {
891                st.calculate_min_required_fee(platform_version)
892            }
893            StateTransition::IdentityTopUp(st) => {
894                st.calculate_min_required_fee(platform_version)
895            }
896            StateTransition::AddressFundingFromAssetLock(st) => {
897                st.calculate_min_required_fee(platform_version)
898            }
899            StateTransition::ShieldFromAssetLock(st) => {
900                st.calculate_min_required_fee(platform_version)
901            }
902            st => Err(ProtocolError::CorruptedCodeExecution(format!("{} is not an asset lock transaction, but we are calling required_asset_lock_balance_for_processing_start", st.name()))),
903        }
904    }
905
906    fn hash(&self, skip_signature: bool) -> Result<Vec<u8>, ProtocolError> {
907        if skip_signature {
908            Ok(hash_double_to_vec(self.signable_bytes()?))
909        } else {
910            Ok(hash_double_to_vec(
911                crate::serialization::PlatformSerializable::serialize_to_bytes(self)?,
912            ))
913        }
914    }
915
916    /// Returns state transition name
917    pub fn name(&self) -> String {
918        match self {
919            Self::DataContractCreate(_) => "DataContractCreate".to_string(),
920            Self::DataContractUpdate(_) => "DataContractUpdate".to_string(),
921            Self::Batch(batch_transition) => {
922                let mut document_transition_types = vec![];
923                for transition in batch_transition.transitions_iter() {
924                    let document_transition_name = match transition {
925                        BatchedTransitionRef::Document(DocumentTransition::Create(_)) => "Create",
926                        BatchedTransitionRef::Document(DocumentTransition::Replace(_)) => "Replace",
927                        BatchedTransitionRef::Document(DocumentTransition::Delete(_)) => "Delete",
928                        BatchedTransitionRef::Document(DocumentTransition::Transfer(_)) => {
929                            "Transfer"
930                        }
931                        BatchedTransitionRef::Document(DocumentTransition::UpdatePrice(_)) => {
932                            "UpdatePrice"
933                        }
934                        BatchedTransitionRef::Document(DocumentTransition::Purchase(_)) => {
935                            "Purchase"
936                        }
937                        BatchedTransitionRef::Document(DocumentTransition::IndexOnlyDelete(_)) => {
938                            "IndexOnlyDelete"
939                        }
940                        BatchedTransitionRef::Token(TokenTransition::Transfer(_)) => {
941                            "TokenTransfer"
942                        }
943                        BatchedTransitionRef::Token(TokenTransition::Mint(_)) => "TokenMint",
944                        BatchedTransitionRef::Token(TokenTransition::Burn(_)) => "TokenBurn",
945                        BatchedTransitionRef::Token(TokenTransition::Freeze(_)) => "TokenFreeze",
946                        BatchedTransitionRef::Token(TokenTransition::Unfreeze(_)) => {
947                            "TokenUnfreeze"
948                        }
949                        BatchedTransitionRef::Token(TokenTransition::DestroyFrozenFunds(_)) => {
950                            "TokenDestroyFrozenFunds"
951                        }
952                        BatchedTransitionRef::Token(TokenTransition::EmergencyAction(_)) => {
953                            "TokenEmergencyAction"
954                        }
955                        BatchedTransitionRef::Token(TokenTransition::ConfigUpdate(_)) => {
956                            "TokenConfigUpdate"
957                        }
958                        BatchedTransitionRef::Token(TokenTransition::Claim(_)) => "TokenClaim",
959                        BatchedTransitionRef::Token(TokenTransition::DirectPurchase(_)) => {
960                            "TokenDirectPurchase"
961                        }
962                        BatchedTransitionRef::Token(
963                            TokenTransition::SetPriceForDirectPurchase(_),
964                        ) => "SetPriceForDirectPurchase",
965                    };
966                    document_transition_types.push(document_transition_name);
967                }
968                format!("DocumentsBatch([{}])", document_transition_types.join(", "))
969            }
970            Self::IdentityCreate(_) => "IdentityCreate".to_string(),
971            Self::IdentityTopUp(_) => "IdentityTopUp".to_string(),
972            Self::IdentityCreditWithdrawal(_) => "IdentityCreditWithdrawal".to_string(),
973            Self::IdentityUpdate(_) => "IdentityUpdate".to_string(),
974            Self::IdentityCreditTransfer(_) => "IdentityCreditTransfer".to_string(),
975            Self::MasternodeVote(_) => "MasternodeVote".to_string(),
976            Self::IdentityCreditTransferToAddresses(_) => {
977                "IdentityCreditTransferToAddresses".to_string()
978            }
979            Self::IdentityCreateFromAddresses(_) => "IdentityCreateFromAddresses".to_string(),
980            Self::IdentityTopUpFromAddresses(_) => "IdentityTopUpFromAddresses".to_string(),
981            Self::AddressFundsTransfer(_) => "AddressFundsTransfer".to_string(),
982            Self::AddressFundingFromAssetLock(_) => "AddressFundingFromAssetLock".to_string(),
983            Self::AddressCreditWithdrawal(_) => "AddressCreditWithdrawal".to_string(),
984            Self::Shield(_) => "Shield".to_string(),
985            Self::ShieldedTransfer(_) => "ShieldedTransfer".to_string(),
986            Self::Unshield(_) => "Unshield".to_string(),
987            Self::ShieldFromAssetLock(_) => "ShieldFromAssetLock".to_string(),
988            Self::ShieldedWithdrawal(_) => "ShieldedWithdrawal".to_string(),
989            Self::IdentityCreateFromShieldedPool(_) => "IdentityCreateFromShieldedPool".to_string(),
990        }
991    }
992
993    /// returns the signature as a byte-array
994    pub fn signature(&self) -> Option<&BinaryData> {
995        match self {
996            StateTransition::DataContractCreate(st) => Some(st.signature()),
997            StateTransition::DataContractUpdate(st) => Some(st.signature()),
998            StateTransition::Batch(st) => Some(st.signature()),
999            StateTransition::IdentityCreate(st) => Some(st.signature()),
1000            StateTransition::IdentityTopUp(st) => Some(st.signature()),
1001            StateTransition::IdentityCreditWithdrawal(st) => Some(st.signature()),
1002            StateTransition::IdentityUpdate(st) => Some(st.signature()),
1003            StateTransition::IdentityCreditTransfer(st) => Some(st.signature()),
1004            StateTransition::MasternodeVote(st) => Some(st.signature()),
1005            StateTransition::IdentityCreditTransferToAddresses(st) => Some(st.signature()),
1006            StateTransition::IdentityCreateFromAddresses(_) => None,
1007            StateTransition::IdentityTopUpFromAddresses(_) => None,
1008            StateTransition::AddressFundsTransfer(_) => None,
1009            StateTransition::AddressFundingFromAssetLock(st) => Some(st.signature()),
1010            StateTransition::AddressCreditWithdrawal(_) => None,
1011            StateTransition::Shield(_) => None,
1012            StateTransition::ShieldedTransfer(_) => None,
1013            StateTransition::Unshield(_) => None,
1014            StateTransition::ShieldFromAssetLock(st) => Some(st.signature()),
1015            StateTransition::ShieldedWithdrawal(_) => None,
1016            StateTransition::IdentityCreateFromShieldedPool(_) => None,
1017        }
1018    }
1019
1020    /// returns the number of private keys
1021    pub fn required_number_of_private_keys(&self) -> u16 {
1022        match self {
1023            StateTransition::IdentityCreateFromAddresses(st) => st.inputs().len() as u16,
1024            StateTransition::IdentityTopUpFromAddresses(st) => st.inputs().len() as u16,
1025            StateTransition::AddressFundsTransfer(st) => st.inputs().len() as u16,
1026            StateTransition::AddressCreditWithdrawal(st) => st.inputs().len() as u16,
1027            StateTransition::Shield(st) => st.inputs().len() as u16,
1028            StateTransition::ShieldedTransfer(_) => 0,
1029            StateTransition::Unshield(_) => 0,
1030            StateTransition::ShieldFromAssetLock(_) => 0,
1031            StateTransition::ShieldedWithdrawal(_) => 0,
1032            StateTransition::IdentityCreateFromShieldedPool(_) => 0,
1033            _ => 1,
1034        }
1035    }
1036
1037    /// returns the fee_increase additional percentage multiplier, it affects only processing costs
1038    pub fn user_fee_increase(&self) -> UserFeeIncrease {
1039        match self {
1040            StateTransition::DataContractCreate(st) => st.user_fee_increase(),
1041            StateTransition::DataContractUpdate(st) => st.user_fee_increase(),
1042            StateTransition::Batch(st) => st.user_fee_increase(),
1043            StateTransition::IdentityCreate(st) => st.user_fee_increase(),
1044            StateTransition::IdentityTopUp(st) => st.user_fee_increase(),
1045            StateTransition::IdentityCreditWithdrawal(st) => st.user_fee_increase(),
1046            StateTransition::IdentityUpdate(st) => st.user_fee_increase(),
1047            StateTransition::IdentityCreditTransfer(st) => st.user_fee_increase(),
1048            StateTransition::IdentityCreditTransferToAddresses(st) => st.user_fee_increase(),
1049            StateTransition::IdentityCreateFromAddresses(st) => st.user_fee_increase(),
1050            StateTransition::IdentityTopUpFromAddresses(st) => st.user_fee_increase(),
1051            StateTransition::AddressFundsTransfer(st) => st.user_fee_increase(),
1052            StateTransition::AddressFundingFromAssetLock(st) => st.user_fee_increase(),
1053            StateTransition::AddressCreditWithdrawal(st) => st.user_fee_increase(),
1054            StateTransition::Shield(st) => st.user_fee_increase(),
1055            // These transitions don't support user fee adjustment
1056            StateTransition::ShieldFromAssetLock(_) => 0,
1057            StateTransition::MasternodeVote(_) => 0,
1058            StateTransition::ShieldedTransfer(_) => 0,
1059            StateTransition::Unshield(_) => 0,
1060            StateTransition::ShieldedWithdrawal(_) => 0,
1061            StateTransition::IdentityCreateFromShieldedPool(_) => 0,
1062        }
1063    }
1064
1065    /// Calculates the estimated minimum fee required for this state transition.
1066    ///
1067    /// The fee is calculated based on the number of inputs, outputs, and any
1068    /// transition-specific costs (e.g., key creation costs for identity creation).
1069    ///
1070    /// # Arguments
1071    ///
1072    /// * `platform_version` - The platform version containing fee configuration.
1073    ///
1074    /// # Returns
1075    ///
1076    /// The estimated fee in credits.
1077    fn calculate_estimated_fee(
1078        &self,
1079        platform_version: &PlatformVersion,
1080    ) -> Result<Credits, ProtocolError> {
1081        call_method!(self, calculate_min_required_fee, platform_version)
1082    }
1083
1084    /// The transaction id is a single hash of the data with the signature
1085    pub fn transaction_id(&self) -> Result<[u8; 32], ProtocolError> {
1086        Ok(hash_single(
1087            crate::serialization::PlatformSerializable::serialize_to_bytes(self)?,
1088        ))
1089    }
1090
1091    /// returns the signature as a byte-array
1092    pub fn signature_public_key_id(&self) -> Option<KeyID> {
1093        call_getter_method_identity_signed!(self, signature_public_key_id)
1094    }
1095
1096    /// returns the key security level requirement for the state transition
1097    pub fn security_level_requirement(&self, purpose: Purpose) -> Option<Vec<SecurityLevel>> {
1098        call_getter_method_identity_signed!(self, security_level_requirement, purpose)
1099    }
1100
1101    /// returns the key purpose requirement for the state transition
1102    pub fn purpose_requirement(&self) -> Option<Vec<Purpose>> {
1103        call_getter_method_identity_signed!(self, purpose_requirement)
1104    }
1105
1106    /// returns the signature as a byte-array
1107    pub fn owner_id(&self) -> Option<Identifier> {
1108        match self {
1109            StateTransition::DataContractCreate(st) => Some(st.owner_id()),
1110            StateTransition::DataContractUpdate(st) => Some(st.owner_id()),
1111            StateTransition::Batch(st) => Some(st.owner_id()),
1112            StateTransition::IdentityCreate(st) => Some(st.owner_id()),
1113            StateTransition::IdentityTopUp(st) => Some(st.owner_id()),
1114            StateTransition::IdentityCreditWithdrawal(st) => Some(st.owner_id()),
1115            StateTransition::IdentityUpdate(st) => Some(st.owner_id()),
1116            StateTransition::IdentityCreditTransfer(st) => Some(st.owner_id()),
1117            StateTransition::MasternodeVote(st) => Some(st.owner_id()),
1118            StateTransition::IdentityCreditTransferToAddresses(st) => Some(st.owner_id()),
1119            StateTransition::IdentityCreateFromAddresses(_) => None,
1120            StateTransition::IdentityTopUpFromAddresses(_) => None,
1121            StateTransition::AddressFundsTransfer(_) => None,
1122            StateTransition::AddressFundingFromAssetLock(_) => None,
1123            StateTransition::AddressCreditWithdrawal(_) => None,
1124            StateTransition::Shield(_) => None,
1125            StateTransition::ShieldedTransfer(_) => None,
1126            StateTransition::Unshield(_) => None,
1127            StateTransition::ShieldFromAssetLock(_) => None,
1128            StateTransition::ShieldedWithdrawal(_) => None,
1129            StateTransition::IdentityCreateFromShieldedPool(_) => None,
1130        }
1131    }
1132
1133    /// returns the signature as a byte-array
1134    pub fn inputs(&self) -> Option<&BTreeMap<PlatformAddress, (AddressNonce, Credits)>> {
1135        match self {
1136            StateTransition::DataContractCreate(_)
1137            | StateTransition::DataContractUpdate(_)
1138            | StateTransition::Batch(_)
1139            | StateTransition::IdentityCreate(_)
1140            | StateTransition::IdentityTopUp(_)
1141            | StateTransition::IdentityCreditWithdrawal(_)
1142            | StateTransition::IdentityUpdate(_)
1143            | StateTransition::IdentityCreditTransfer(_)
1144            | StateTransition::MasternodeVote(_)
1145            | StateTransition::IdentityCreditTransferToAddresses(_) => None,
1146            StateTransition::IdentityCreateFromAddresses(st) => Some(st.inputs()),
1147            StateTransition::IdentityTopUpFromAddresses(st) => Some(st.inputs()),
1148            StateTransition::AddressFundsTransfer(st) => Some(st.inputs()),
1149            StateTransition::AddressFundingFromAssetLock(st) => Some(st.inputs()),
1150            StateTransition::AddressCreditWithdrawal(st) => Some(st.inputs()),
1151            StateTransition::Shield(st) => Some(st.inputs()),
1152            StateTransition::ShieldedTransfer(_) => None,
1153            StateTransition::Unshield(_) => None,
1154            StateTransition::ShieldFromAssetLock(_) => None,
1155            StateTransition::ShieldedWithdrawal(_) => None,
1156            StateTransition::IdentityCreateFromShieldedPool(_) => None,
1157        }
1158    }
1159
1160    /// returns the state transition type
1161    pub fn state_transition_type(&self) -> StateTransitionType {
1162        call_method!(self, state_transition_type)
1163    }
1164
1165    /// returns the unique identifiers for the state transition
1166    pub fn unique_identifiers(&self) -> Vec<String> {
1167        call_method!(self, unique_identifiers)
1168    }
1169
1170    /// set a new signature
1171    pub fn set_signature(&mut self, signature: BinaryData) -> bool {
1172        match self {
1173            StateTransition::DataContractCreate(st) => {
1174                st.set_signature(signature);
1175                true
1176            }
1177            StateTransition::DataContractUpdate(st) => {
1178                st.set_signature(signature);
1179                true
1180            }
1181            StateTransition::Batch(st) => {
1182                st.set_signature(signature);
1183                true
1184            }
1185            StateTransition::IdentityCreate(st) => {
1186                st.set_signature(signature);
1187                true
1188            }
1189            StateTransition::IdentityTopUp(st) => {
1190                st.set_signature(signature);
1191                true
1192            }
1193            StateTransition::IdentityCreditWithdrawal(st) => {
1194                st.set_signature(signature);
1195                true
1196            }
1197            StateTransition::IdentityUpdate(st) => {
1198                st.set_signature(signature);
1199                true
1200            }
1201            StateTransition::IdentityCreditTransfer(st) => {
1202                st.set_signature(signature);
1203                true
1204            }
1205            StateTransition::MasternodeVote(st) => {
1206                st.set_signature(signature);
1207                true
1208            }
1209            StateTransition::IdentityCreditTransferToAddresses(st) => {
1210                st.set_signature(signature);
1211                true
1212            }
1213            StateTransition::IdentityCreateFromAddresses(_)
1214            | StateTransition::IdentityTopUpFromAddresses(_)
1215            | StateTransition::AddressFundsTransfer(_)
1216            | StateTransition::Shield(_)
1217            | StateTransition::ShieldedTransfer(_)
1218            | StateTransition::Unshield(_)
1219            | StateTransition::ShieldedWithdrawal(_)
1220            | StateTransition::IdentityCreateFromShieldedPool(_) => false,
1221            StateTransition::AddressFundingFromAssetLock(st) => {
1222                st.set_signature(signature);
1223                true
1224            }
1225            StateTransition::ShieldFromAssetLock(st) => {
1226                st.set_signature(signature);
1227                true
1228            }
1229            StateTransition::AddressCreditWithdrawal(_) => false,
1230        }
1231    }
1232
1233    /// set fee multiplier
1234    pub fn set_user_fee_increase(&mut self, user_fee_increase: UserFeeIncrease) {
1235        match self {
1236            StateTransition::DataContractCreate(st) => st.set_user_fee_increase(user_fee_increase),
1237            StateTransition::DataContractUpdate(st) => st.set_user_fee_increase(user_fee_increase),
1238            StateTransition::Batch(st) => st.set_user_fee_increase(user_fee_increase),
1239            StateTransition::IdentityCreate(st) => st.set_user_fee_increase(user_fee_increase),
1240            StateTransition::IdentityTopUp(st) => st.set_user_fee_increase(user_fee_increase),
1241            StateTransition::IdentityCreditWithdrawal(st) => {
1242                st.set_user_fee_increase(user_fee_increase)
1243            }
1244            StateTransition::IdentityUpdate(st) => st.set_user_fee_increase(user_fee_increase),
1245            StateTransition::IdentityCreditTransfer(st) => {
1246                st.set_user_fee_increase(user_fee_increase)
1247            }
1248            StateTransition::IdentityCreditTransferToAddresses(st) => {
1249                st.set_user_fee_increase(user_fee_increase)
1250            }
1251            StateTransition::IdentityCreateFromAddresses(st) => {
1252                st.set_user_fee_increase(user_fee_increase)
1253            }
1254            StateTransition::IdentityTopUpFromAddresses(st) => {
1255                st.set_user_fee_increase(user_fee_increase)
1256            }
1257            StateTransition::AddressFundsTransfer(st) => {
1258                st.set_user_fee_increase(user_fee_increase)
1259            }
1260            StateTransition::AddressFundingFromAssetLock(st) => {
1261                st.set_user_fee_increase(user_fee_increase)
1262            }
1263            StateTransition::AddressCreditWithdrawal(st) => {
1264                st.set_user_fee_increase(user_fee_increase)
1265            }
1266            StateTransition::Shield(st) => st.set_user_fee_increase(user_fee_increase),
1267            // These transitions don't support user fee adjustment — no-op
1268            StateTransition::ShieldFromAssetLock(_) => {}
1269            StateTransition::MasternodeVote(_) => {}
1270            StateTransition::ShieldedTransfer(_) => {}
1271            StateTransition::Unshield(_) => {}
1272            StateTransition::ShieldedWithdrawal(_) => {}
1273            StateTransition::IdentityCreateFromShieldedPool(_) => {}
1274        }
1275    }
1276
1277    /// set a new signature
1278    pub fn set_signature_public_key_id(&mut self, public_key_id: KeyID) {
1279        call_method_identity_signed!(self, set_signature_public_key_id, public_key_id)
1280    }
1281
1282    #[cfg(feature = "state-transition-signing")]
1283    pub async fn sign_external<S: Signer<IdentityPublicKey>>(
1284        &mut self,
1285        identity_public_key: &IdentityPublicKey,
1286        signer: &S,
1287        get_data_contract_security_level_requirement: Option<
1288            impl Fn(Identifier, String) -> Result<SecurityLevel, ProtocolError>,
1289        >,
1290    ) -> Result<(), ProtocolError> {
1291        self.sign_external_with_options(
1292            identity_public_key,
1293            signer,
1294            get_data_contract_security_level_requirement,
1295            StateTransitionSigningOptions::default(),
1296        )
1297        .await
1298    }
1299
1300    #[cfg(feature = "state-transition-signing")]
1301    pub async fn sign_external_with_options<S: Signer<IdentityPublicKey>>(
1302        &mut self,
1303        identity_public_key: &IdentityPublicKey,
1304        signer: &S,
1305        get_data_contract_security_level_requirement: Option<
1306            impl Fn(Identifier, String) -> Result<SecurityLevel, ProtocolError>,
1307        >,
1308        options: StateTransitionSigningOptions,
1309    ) -> Result<(), ProtocolError> {
1310        match self {
1311            StateTransition::DataContractCreate(st) => {
1312                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1313                st.verify_public_key_is_enabled(identity_public_key)?;
1314            }
1315            StateTransition::DataContractUpdate(st) => {
1316                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1317                st.verify_public_key_is_enabled(identity_public_key)?;
1318            }
1319            StateTransition::Batch(st) => {
1320                let allow_token_transfer_keys = st.transitions_len() == 1
1321                    && (st
1322                        .first_transition()
1323                        .expect("expected first transition with len 1")
1324                        .as_transition_token_claim()
1325                        .is_some()
1326                        || st
1327                            .first_transition()
1328                            .expect("expected first transition with len 1")
1329                            .as_transition_token_transfer()
1330                            .is_some());
1331                let allowed_key_purposes = if allow_token_transfer_keys {
1332                    vec![Purpose::AUTHENTICATION, Purpose::TRANSFER]
1333                } else {
1334                    vec![Purpose::AUTHENTICATION]
1335                };
1336                if !options.allow_signing_with_any_purpose
1337                    && !allowed_key_purposes.contains(&identity_public_key.purpose())
1338                {
1339                    return Err(ProtocolError::WrongPublicKeyPurposeError(
1340                        WrongPublicKeyPurposeError::new(
1341                            identity_public_key.purpose(),
1342                            allowed_key_purposes,
1343                        ),
1344                    ));
1345                }
1346                if !options.allow_signing_with_any_security_level {
1347                    let security_level_requirement = st.combined_security_level_requirement(
1348                        get_data_contract_security_level_requirement,
1349                    )?;
1350                    if !security_level_requirement.contains(&identity_public_key.security_level()) {
1351                        return Err(ProtocolError::InvalidSignaturePublicKeySecurityLevelError(
1352                            InvalidSignaturePublicKeySecurityLevelError::new(
1353                                identity_public_key.security_level(),
1354                                security_level_requirement,
1355                            ),
1356                        ));
1357                    }
1358                }
1359                st.verify_public_key_is_enabled(identity_public_key)?;
1360            }
1361            StateTransition::IdentityCreditWithdrawal(st) => {
1362                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1363                st.verify_public_key_is_enabled(identity_public_key)?;
1364            }
1365            StateTransition::IdentityUpdate(st) => {
1366                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1367                st.verify_public_key_is_enabled(identity_public_key)?;
1368            }
1369            StateTransition::IdentityCreditTransfer(st) => {
1370                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1371                st.verify_public_key_is_enabled(identity_public_key)?;
1372            }
1373            StateTransition::IdentityCreate(_) => {
1374                return Err(ProtocolError::CorruptedCodeExecution(
1375                    "identity create can not be called for identity signing".to_string(),
1376                ))
1377            }
1378            StateTransition::IdentityTopUp(_) => {
1379                return Err(ProtocolError::CorruptedCodeExecution(
1380                    "identity top up can not be called for identity signing".to_string(),
1381                ))
1382            }
1383            StateTransition::MasternodeVote(st) => {
1384                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1385                st.verify_public_key_is_enabled(identity_public_key)?;
1386            }
1387            StateTransition::IdentityCreditTransferToAddresses(st) => {
1388                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1389                st.verify_public_key_is_enabled(identity_public_key)?;
1390            }
1391            StateTransition::IdentityCreateFromAddresses(_) => {
1392                return Err(ProtocolError::CorruptedCodeExecution(
1393                    "identity create from addresses can not be called for identity signing"
1394                        .to_string(),
1395                ))
1396            }
1397            StateTransition::IdentityTopUpFromAddresses(_) => {
1398                return Err(ProtocolError::CorruptedCodeExecution(
1399                    "identity top up from addresses can not be called for identity signing"
1400                        .to_string(),
1401                ))
1402            }
1403            StateTransition::AddressFundsTransfer(_) => {
1404                return Err(ProtocolError::CorruptedCodeExecution(
1405                    "address funds transfer transition can not be called for identity signing"
1406                        .to_string(),
1407                ))
1408            }
1409            StateTransition::AddressFundingFromAssetLock(_) => {
1410                return Err(ProtocolError::CorruptedCodeExecution(
1411                    "address funding from asset lock transition can not be called for identity signing"
1412                        .to_string(),
1413                ))
1414            }
1415            StateTransition::AddressCreditWithdrawal(_) => {
1416                return Err(ProtocolError::CorruptedCodeExecution(
1417                    "address credit withdrawal transition can not be called for identity signing"
1418                        .to_string(),
1419                ))
1420            }
1421            StateTransition::Shield(_) => {
1422                return Err(ProtocolError::CorruptedCodeExecution(
1423                    "shield transition can not be called for identity signing".to_string(),
1424                ))
1425            }
1426            StateTransition::ShieldedTransfer(_) => {
1427                return Err(ProtocolError::CorruptedCodeExecution(
1428                    "shielded transfer transition can not be called for identity signing"
1429                        .to_string(),
1430                ))
1431            }
1432            StateTransition::Unshield(_) => {
1433                return Err(ProtocolError::CorruptedCodeExecution(
1434                    "unshield transition can not be called for identity signing".to_string(),
1435                ))
1436            }
1437            StateTransition::ShieldFromAssetLock(_) => {
1438                return Err(ProtocolError::CorruptedCodeExecution(
1439                    "shield from asset lock transition can not be called for identity signing"
1440                        .to_string(),
1441                ))
1442            }
1443            StateTransition::ShieldedWithdrawal(_) => {
1444                return Err(ProtocolError::CorruptedCodeExecution(
1445                    "shielded withdrawal transition can not be called for identity signing"
1446                        .to_string(),
1447                ))
1448            }
1449            StateTransition::IdentityCreateFromShieldedPool(_) => {
1450                return Err(ProtocolError::CorruptedCodeExecution(
1451                    "identity create from shielded pool transition can not be called for identity signing"
1452                        .to_string(),
1453                ))
1454            }
1455        }
1456        let data = self.signable_bytes()?;
1457        self.set_signature(signer.sign(identity_public_key, data.as_slice()).await?);
1458        self.set_signature_public_key_id(identity_public_key.id());
1459        Ok(())
1460    }
1461
1462    #[cfg(feature = "state-transition-signing")]
1463    pub fn sign(
1464        &mut self,
1465        identity_public_key: &IdentityPublicKey,
1466        private_key: &[u8],
1467        bls: &impl BlsModule,
1468    ) -> Result<(), ProtocolError> {
1469        self.sign_with_options(
1470            identity_public_key,
1471            private_key,
1472            bls,
1473            StateTransitionSigningOptions::default(),
1474        )
1475    }
1476
1477    #[cfg(feature = "state-transition-signing")]
1478    pub fn sign_with_options(
1479        &mut self,
1480        identity_public_key: &IdentityPublicKey,
1481        private_key: &[u8],
1482        bls: &impl BlsModule,
1483        options: StateTransitionSigningOptions,
1484    ) -> Result<(), ProtocolError> {
1485        call_errorable_method_identity_signed!(
1486            self,
1487            verify_public_key_level_and_purpose,
1488            identity_public_key,
1489            options
1490        )?;
1491        call_errorable_method_identity_signed!(
1492            self,
1493            verify_public_key_is_enabled,
1494            identity_public_key
1495        )?;
1496
1497        match identity_public_key.key_type() {
1498            KeyType::ECDSA_SECP256K1 => {
1499                let public_key_compressed = get_compressed_public_ec_key(private_key)?;
1500
1501                // we store compressed public key in the identity ,
1502                // and here we compare the private key used to sing the state transition with
1503                // the compressed key stored in the identity
1504
1505                if public_key_compressed.as_slice() != identity_public_key.data().as_slice() {
1506                    return Err(ProtocolError::InvalidSignaturePublicKeyError(
1507                        InvalidSignaturePublicKeyError::new(identity_public_key.data().to_vec()),
1508                    ));
1509                }
1510
1511                self.sign_by_private_key(private_key, identity_public_key.key_type(), bls)
1512            }
1513            KeyType::ECDSA_HASH160 => {
1514                let public_key_compressed = get_compressed_public_ec_key(private_key)?;
1515                let pub_key_hash = ripemd160_sha256(&public_key_compressed);
1516
1517                if identity_public_key.data().as_slice() != pub_key_hash {
1518                    return Err(ProtocolError::InvalidSignaturePublicKeyError(
1519                        InvalidSignaturePublicKeyError::new(identity_public_key.data().to_vec()),
1520                    ));
1521                }
1522                self.sign_by_private_key(private_key, identity_public_key.key_type(), bls)
1523            }
1524            KeyType::BLS12_381 => {
1525                let public_key = bls.private_key_to_public_key(private_key)?;
1526
1527                if public_key != identity_public_key.data().as_slice() {
1528                    return Err(ProtocolError::InvalidSignaturePublicKeyError(
1529                        InvalidSignaturePublicKeyError::new(identity_public_key.data().to_vec()),
1530                    ));
1531                }
1532                self.sign_by_private_key(private_key, identity_public_key.key_type(), bls)
1533            }
1534
1535            // the default behavior from
1536            // https://github.com/dashevo/platform/blob/6b02b26e5cd3a7c877c5fdfe40c4a4385a8dda15/packages/js-dpp/lib/stateTransition/AbstractStateTransitionIdentitySigned.js#L108
1537            // is to return the error for the BIP13_SCRIPT_HASH
1538            KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => {
1539                Err(ProtocolError::InvalidIdentityPublicKeyTypeError(
1540                    InvalidIdentityPublicKeyTypeError::new(identity_public_key.key_type()),
1541                ))
1542            }
1543        }?;
1544
1545        self.set_signature_public_key_id(identity_public_key.id());
1546
1547        Ok(())
1548    }
1549
1550    #[cfg(feature = "state-transition-signing")]
1551    /// Signs data with the private key
1552    pub fn sign_by_private_key(
1553        &mut self,
1554        private_key: &[u8],
1555        key_type: KeyType,
1556        bls: &impl BlsModule,
1557    ) -> Result<(), ProtocolError> {
1558        let data = self.signable_bytes()?;
1559        match key_type {
1560            KeyType::BLS12_381 => {
1561                if !self.set_signature(bls.sign(&data, private_key)?.into()) {
1562                    return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
1563                        needed: self.required_number_of_private_keys(),
1564                        using: 1,
1565                        msg: "failed to set BLS signature",
1566                    });
1567                }
1568            }
1569
1570            // https://github.com/dashevo/platform/blob/9c8e6a3b6afbc330a6ab551a689de8ccd63f9120/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js#L169
1571            KeyType::ECDSA_SECP256K1 | KeyType::ECDSA_HASH160 => {
1572                let signature = signer::sign(&data, private_key)?;
1573                if !self.set_signature(signature.to_vec().into()) {
1574                    return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
1575                        needed: self.required_number_of_private_keys(),
1576                        using: 1,
1577                        msg: "failed to set ECDSA signature",
1578                    });
1579                };
1580            }
1581
1582            // the default behavior from
1583            // https://github.com/dashevo/platform/blob/6b02b26e5cd3a7c877c5fdfe40c4a4385a8dda15/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js#L187
1584            // is to return the error for the BIP13_SCRIPT_HASH
1585            KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => {
1586                return Err(ProtocolError::InvalidIdentityPublicKeyTypeError(
1587                    InvalidIdentityPublicKeyTypeError::new(key_type),
1588                ))
1589            }
1590        };
1591        Ok(())
1592    }
1593
1594    /// Sign `self.signable_bytes()` with an external Core-wallet signer and
1595    /// store the resulting Core-ECDSA signature in the transition's wrapper
1596    /// signature field.
1597    ///
1598    /// # Position in the signing-primitive family
1599    ///
1600    /// This is a **primitive** in the same family as
1601    /// [`Self::sign_by_private_key`] — it performs no validation of the
1602    /// transition variant, the key, or the relationship between them. It is
1603    /// the external-custody sibling of `sign_by_private_key`:
1604    ///
1605    /// | Primitive | Key source | Validation |
1606    /// |---|---|---|
1607    /// | [`Self::sign_by_private_key`] | raw `&[u8]` in host memory | none |
1608    /// | `sign_with_core_signer` | external signer (HSM / hardware wallet / secure enclave / remote signing service), key reached via BIP32 [`DerivationPath`] | none |
1609    ///
1610    /// Both produce **byte-identical** wrapper signatures over the same
1611    /// digest when given the same underlying private key (proven by
1612    /// `sign_with_signer_matches_sign_by_private_key_byte_for_byte` in this
1613    /// file's tests). The only difference is where the key bytes live: in
1614    /// host memory vs inside the signer's trust boundary. The signer
1615    /// performs the derive + sign + zeroise sequence atomically; this
1616    /// function never sees raw key material, only a 32-byte digest and the
1617    /// resulting signature.
1618    ///
1619    /// # Scope (what the BIP32 path means)
1620    ///
1621    /// The `path` parameter selects a key in the signer's Core wallet
1622    /// (BIP32-derived). For that path's signature to be **meaningful** the
1623    /// transition's wrapper signature field must itself carry a Core-key
1624    /// signature. Today that is exactly the four asset-lock-signed
1625    /// variants — `IdentityCreate`, `IdentityTopUp`,
1626    /// `AddressFundingFromAssetLock`, `ShieldFromAssetLock` — where the
1627    /// wrapper signature is the asset-lock proof signed by the credit
1628    /// output's Core key.
1629    ///
1630    /// For identity-signed variants (`DataContractCreate`, `Batch`,
1631    /// `IdentityCreditTransfer`, etc.) the wrapper signature is an
1632    /// identity-key signature paired with a `signature_public_key_id`,
1633    /// and the right external-signer entry point is [`Self::sign_external`]
1634    /// with a [`Signer<IdentityPublicKey>`](crate::identity::signer::Signer).
1635    /// Calling `sign_with_core_signer` on such a variant compiles and
1636    /// produces a structurally valid 65-byte signature, but the signature
1637    /// is **semantically meaningless** — Platform validation will reject
1638    /// the transition because the signature doesn't match the expected
1639    /// identity public key and `signature_public_key_id` isn't set. The
1640    /// same caveat applies to misusing `sign_by_private_key`, the sibling
1641    /// primitive — both rely on the caller passing a key the wrapper
1642    /// signature is *meant* to carry.
1643    ///
1644    /// # Wire-format parity with `sign_by_private_key`
1645    ///
1646    /// The byte layout of the stored signature mirrors
1647    /// `dashcore::signer::sign`:
1648    ///
1649    /// 1. `digest = double_sha256(self.signable_bytes()?)`
1650    /// 2. `signer.sign_ecdsa(path, digest).await` → non-recoverable
1651    ///    `(secp256k1::ecdsa::Signature, secp256k1::PublicKey)`.
1652    /// 3. Recover the recovery id by trying all four candidates against the
1653    ///    returned public key (libsecp256k1 normalises both signing paths to
1654    ///    low-s form so the 64-byte `r||s` payload is bit-identical).
1655    /// 4. Serialise as a 65-byte compact recoverable signature with the
1656    ///    `compressed` prefix convention used by `CompactSignature` — i.e.
1657    ///    `[recovery_id + 27 + 4, r (32) || s (32)]`.
1658    ///
1659    /// # Errors
1660    ///
1661    /// - Returns [`ProtocolError::ExternalSignerError`] wrapping the signer's
1662    ///   `Display` error when the underlying signer fails.
1663    /// - Returns [`ProtocolError::ExternalSignerError`] if no recovery id
1664    ///   matches the public key returned by the signer — this should be
1665    ///   unreachable for a conformant signer (invariant violation by a
1666    ///   non-conformant signer) but is surfaced rather than panicked on.
1667    /// - Returns [`ProtocolError::Generic`] if the SHA-256 transform did not
1668    ///   yield a 32-byte digest (defensive — should never happen).
1669    /// - Returns [`ProtocolError::InvalidVerificationWrongNumberOfElements`] if
1670    ///   `set_signature` rejects the result (matches `sign_by_private_key`).
1671    #[cfg(all(feature = "state-transition-signing", feature = "core_key_wallet"))]
1672    pub async fn sign_with_core_signer<S: ::key_wallet::signer::Signer>(
1673        &mut self,
1674        path: &::key_wallet::bip32::DerivationPath,
1675        signer: &S,
1676    ) -> Result<(), ProtocolError> {
1677        use dashcore::secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
1678        use dashcore::secp256k1::{Message, Secp256k1};
1679        use dashcore::signer::{double_sha, CompactSignature};
1680
1681        let data = self.signable_bytes()?;
1682        // Pre-image transform matches `dashcore::signer::sign`: double-SHA256
1683        // of the signable bytes is the actual ECDSA message digest.
1684        let data_hash = double_sha(&data);
1685        let digest: [u8; 32] = data_hash.as_slice().try_into().map_err(|_| {
1686            ProtocolError::Generic("double_sha did not return 32 bytes".to_string())
1687        })?;
1688
1689        let (signature, public_key) = signer
1690            .sign_ecdsa(path, digest)
1691            .await
1692            .map_err(|e| ProtocolError::ExternalSignerError(format!("signer failed: {}", e)))?;
1693
1694        // The signer returns a non-recoverable signature. The legacy path
1695        // stores a 65-byte recoverable compact signature, so we brute-force
1696        // the recovery id (0..3) by reconstructing a `RecoverableSignature`
1697        // and comparing the recovered public key with the one the signer
1698        // returned. secp256k1 normalises both `sign_ecdsa` and
1699        // `sign_ecdsa_recoverable` outputs to low-s form, so the 64-byte
1700        // `r||s` payload is bit-identical to what `dashcore::signer::sign`
1701        // produces.
1702        let compact_64 = signature.serialize_compact();
1703        let secp = Secp256k1::new();
1704        let msg = Message::from_digest(digest);
1705
1706        let mut found: Option<RecoverableSignature> = None;
1707        for id in 0..4i32 {
1708            let recid = match RecoveryId::try_from(id) {
1709                Ok(r) => r,
1710                Err(_) => continue,
1711            };
1712            let candidate = match RecoverableSignature::from_compact(&compact_64, recid) {
1713                Ok(s) => s,
1714                Err(_) => continue,
1715            };
1716            if let Ok(recovered) = secp.recover_ecdsa(&msg, &candidate) {
1717                if recovered == public_key {
1718                    found = Some(candidate);
1719                    break;
1720                }
1721            }
1722        }
1723        let recoverable = found.ok_or_else(|| {
1724            // Invariant violation by a non-conformant signer: the
1725            // signature returned does not correspond to the public
1726            // key the signer claims. Surface as ExternalSignerError
1727            // (NOT Generic) so callers can distinguish signer-side
1728            // failures from protocol-level invariants.
1729            ProtocolError::ExternalSignerError(
1730                "signer returned a signature whose recovery id does not match the returned public key".to_string(),
1731            )
1732        })?;
1733
1734        // Compressed-pubkey convention matches `dashcore::signer::sign`, which
1735        // always passes `true` regardless of the underlying key encoding. The
1736        // signer's `sign_ecdsa` returns the compressed `secp256k1::PublicKey`,
1737        // so this is consistent.
1738        let compact_65 = recoverable.to_compact_signature(true);
1739
1740        if !self.set_signature(compact_65.to_vec().into()) {
1741            return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
1742                needed: self.required_number_of_private_keys(),
1743                using: 1,
1744                msg: "failed to set ECDSA signature",
1745            });
1746        }
1747        Ok(())
1748    }
1749
1750    #[cfg(feature = "state-transition-validation")]
1751    fn verify_by_raw_public_key<T: BlsModule>(
1752        &self,
1753        public_key: &[u8],
1754        public_key_type: KeyType,
1755        bls: &T,
1756    ) -> Result<(), ProtocolError> {
1757        match public_key_type {
1758            KeyType::ECDSA_SECP256K1 => self.verify_ecdsa_signature_by_public_key(public_key),
1759            KeyType::ECDSA_HASH160 => {
1760                self.verify_ecdsa_hash_160_signature_by_public_key_hash(public_key)
1761            }
1762            KeyType::BLS12_381 => self.verify_bls_signature_by_public_key(public_key, bls),
1763            KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => {
1764                Err(ProtocolError::InvalidIdentityPublicKeyTypeError(
1765                    InvalidIdentityPublicKeyTypeError::new(public_key_type),
1766                ))
1767            }
1768        }
1769    }
1770
1771    #[cfg(feature = "state-transition-validation")]
1772    pub fn verify_identity_signed_signature(
1773        &self,
1774        public_key: &IdentityPublicKey,
1775        bls: &impl BlsModule,
1776    ) -> Result<(), ProtocolError> {
1777        // self.verify_public_key_level_and_purpose(public_key)?;
1778        if public_key.disabled_at().is_some() {
1779            return Err(ProtocolError::PublicKeyIsDisabledError(
1780                PublicKeyIsDisabledError::new(public_key.id()),
1781            ));
1782        }
1783
1784        let Some(signature) = self.signature() else {
1785            return Err(ProtocolError::CorruptedCodeExecution("verifying identity signature for a state transition that doesn't use identity signatures".to_string()));
1786        };
1787        if signature.is_empty() {
1788            return Err(ProtocolError::StateTransitionIsNotSignedError(
1789                StateTransitionIsNotSignedError::new(self.clone()),
1790            ));
1791        }
1792
1793        if self.signature_public_key_id() != Some(public_key.id()) {
1794            return Err(ProtocolError::PublicKeyMismatchError(
1795                PublicKeyMismatchError::new(public_key.clone()),
1796            ));
1797        }
1798
1799        let public_key_bytes = public_key.data().as_slice();
1800        match public_key.key_type() {
1801            KeyType::ECDSA_HASH160 => {
1802                self.verify_ecdsa_hash_160_signature_by_public_key_hash(public_key_bytes)
1803            }
1804
1805            KeyType::ECDSA_SECP256K1 => self.verify_ecdsa_signature_by_public_key(public_key_bytes),
1806
1807            KeyType::BLS12_381 => self.verify_bls_signature_by_public_key(public_key_bytes, bls),
1808
1809            // per https://github.com/dashevo/platform/pull/353, signing and verification is not supported
1810            KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => Ok(()),
1811        }
1812    }
1813
1814    #[cfg(feature = "state-transition-validation")]
1815    fn verify_ecdsa_hash_160_signature_by_public_key_hash(
1816        &self,
1817        public_key_hash: &[u8],
1818    ) -> Result<(), ProtocolError> {
1819        let Some(signature) = self.signature() else {
1820            return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
1821                needed: self.required_number_of_private_keys(),
1822                using: 1,
1823                msg: "This state transition type should a single signature",
1824            });
1825        };
1826        if signature.is_empty() {
1827            return Err(ProtocolError::StateTransitionIsNotSignedError(
1828                StateTransitionIsNotSignedError::new(self.clone()),
1829            ));
1830        }
1831        let data = self.signable_bytes()?;
1832        let data_hash = double_sha(data);
1833        signer::verify_hash_signature(&data_hash, signature.as_slice(), public_key_hash).map_err(
1834            |e| {
1835                ProtocolError::from(ConsensusError::SignatureError(
1836                    SignatureError::InvalidStateTransitionSignatureError(
1837                        InvalidStateTransitionSignatureError::new(e.to_string()),
1838                    ),
1839                ))
1840            },
1841        )
1842    }
1843
1844    #[cfg(feature = "state-transition-validation")]
1845    /// Verifies an ECDSA signature with the public key
1846    fn verify_ecdsa_signature_by_public_key(&self, public_key: &[u8]) -> Result<(), ProtocolError> {
1847        let Some(signature) = self.signature() else {
1848            return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
1849                needed: self.required_number_of_private_keys(),
1850                using: 1,
1851                msg: "This state transition type should a single signature",
1852            });
1853        };
1854        if signature.is_empty() {
1855            return Err(ProtocolError::StateTransitionIsNotSignedError(
1856                StateTransitionIsNotSignedError::new(self.clone()),
1857            ));
1858        }
1859        let data = self.signable_bytes()?;
1860        signer::verify_data_signature(&data, signature.as_slice(), public_key).map_err(|e| {
1861            // TODO: it shouldn't respond with consensus error
1862
1863            ProtocolError::from(ConsensusError::SignatureError(
1864                SignatureError::InvalidStateTransitionSignatureError(
1865                    InvalidStateTransitionSignatureError::new(e.to_string()),
1866                ),
1867            ))
1868        })
1869    }
1870
1871    #[cfg(feature = "state-transition-validation")]
1872    /// Verifies a BLS signature with the public key
1873    fn verify_bls_signature_by_public_key<T: BlsModule>(
1874        &self,
1875        public_key: &[u8],
1876        bls: &T,
1877    ) -> Result<(), ProtocolError> {
1878        let Some(signature) = self.signature() else {
1879            return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
1880                needed: self.required_number_of_private_keys(),
1881                using: 1,
1882                msg: "This state transition type should a single signature",
1883            });
1884        };
1885        if signature.is_empty() {
1886            return Err(ProtocolError::StateTransitionIsNotSignedError(
1887                StateTransitionIsNotSignedError::new(self.clone()),
1888            ));
1889        }
1890
1891        let data = self.signable_bytes()?;
1892
1893        bls.verify_signature(signature.as_slice(), &data, public_key)
1894            .map(|_| ())
1895            .map_err(|e| {
1896                // TODO: it shouldn't respond with consensus error
1897                ProtocolError::from(ConsensusError::SignatureError(
1898                    SignatureError::InvalidStateTransitionSignatureError(
1899                        InvalidStateTransitionSignatureError::new(e.to_string()),
1900                    ),
1901                ))
1902            })
1903    }
1904}
1905
1906#[cfg(feature = "state-transition-validation")]
1907impl StateTransitionStructureValidation for StateTransition {
1908    fn validate_structure(
1909        &self,
1910        platform_version: &PlatformVersion,
1911    ) -> crate::validation::SimpleConsensusValidationResult {
1912        match self {
1913            StateTransition::DataContractCreate(_)
1914            | StateTransition::DataContractUpdate(_)
1915            | StateTransition::Batch(_)
1916            | StateTransition::IdentityCreate(_)
1917            | StateTransition::IdentityTopUp(_)
1918            | StateTransition::IdentityCreditWithdrawal(_)
1919            | StateTransition::IdentityUpdate(_)
1920            | StateTransition::IdentityCreditTransfer(_)
1921            | StateTransition::MasternodeVote(_) => {
1922                crate::validation::SimpleConsensusValidationResult::new_with_error(
1923                    UnsupportedFeatureError::new(
1924                        "structure validation for identity-based state transitions".to_string(),
1925                        platform_version.protocol_version,
1926                    )
1927                    .into(),
1928                )
1929            }
1930            StateTransition::IdentityCreditTransferToAddresses(transition) => {
1931                transition.validate_structure(platform_version)
1932            }
1933            StateTransition::IdentityCreateFromAddresses(transition) => {
1934                transition.validate_structure(platform_version)
1935            }
1936            StateTransition::IdentityTopUpFromAddresses(transition) => {
1937                transition.validate_structure(platform_version)
1938            }
1939            StateTransition::AddressFundsTransfer(transition) => {
1940                transition.validate_structure(platform_version)
1941            }
1942            StateTransition::AddressFundingFromAssetLock(transition) => {
1943                transition.validate_structure(platform_version)
1944            }
1945            StateTransition::AddressCreditWithdrawal(transition) => {
1946                transition.validate_structure(platform_version)
1947            }
1948            StateTransition::Shield(transition) => transition.validate_structure(platform_version),
1949            StateTransition::ShieldedTransfer(transition) => {
1950                transition.validate_structure(platform_version)
1951            }
1952            StateTransition::Unshield(transition) => {
1953                transition.validate_structure(platform_version)
1954            }
1955            StateTransition::ShieldFromAssetLock(transition) => {
1956                transition.validate_structure(platform_version)
1957            }
1958            StateTransition::ShieldedWithdrawal(transition) => {
1959                transition.validate_structure(platform_version)
1960            }
1961            StateTransition::IdentityCreateFromShieldedPool(transition) => {
1962                transition.validate_structure(platform_version)
1963            }
1964        }
1965    }
1966}
1967
1968#[cfg(test)]
1969mod tests {
1970    use super::*;
1971
1972    // -----------------------------------------------------------------------
1973    // StateTransitionSigningOptions tests
1974    // -----------------------------------------------------------------------
1975
1976    #[test]
1977    fn test_signing_options_default() {
1978        let opts = StateTransitionSigningOptions::default();
1979        assert!(!opts.allow_signing_with_any_security_level);
1980        assert!(!opts.allow_signing_with_any_purpose);
1981    }
1982
1983    #[test]
1984    fn test_signing_options_equality() {
1985        let a = StateTransitionSigningOptions {
1986            allow_signing_with_any_security_level: true,
1987            allow_signing_with_any_purpose: false,
1988        };
1989        let b = StateTransitionSigningOptions {
1990            allow_signing_with_any_security_level: true,
1991            allow_signing_with_any_purpose: false,
1992        };
1993        assert_eq!(a, b);
1994    }
1995
1996    #[test]
1997    fn test_signing_options_inequality() {
1998        let a = StateTransitionSigningOptions {
1999            allow_signing_with_any_security_level: true,
2000            allow_signing_with_any_purpose: false,
2001        };
2002        let b = StateTransitionSigningOptions {
2003            allow_signing_with_any_security_level: false,
2004            allow_signing_with_any_purpose: false,
2005        };
2006        assert_ne!(a, b);
2007    }
2008
2009    #[test]
2010    #[allow(clippy::clone_on_copy)]
2011    fn test_signing_options_clone() {
2012        let original = StateTransitionSigningOptions {
2013            allow_signing_with_any_security_level: true,
2014            allow_signing_with_any_purpose: true,
2015        };
2016        let cloned = original.clone();
2017        assert_eq!(original, cloned);
2018    }
2019
2020    #[test]
2021    fn test_signing_options_copy() {
2022        let original = StateTransitionSigningOptions {
2023            allow_signing_with_any_security_level: true,
2024            allow_signing_with_any_purpose: false,
2025        };
2026        let copied = original;
2027        assert_eq!(original, copied);
2028    }
2029
2030    #[test]
2031    fn test_signing_options_debug() {
2032        let opts = StateTransitionSigningOptions::default();
2033        let debug_str = format!("{:?}", opts);
2034        assert!(debug_str.contains("StateTransitionSigningOptions"));
2035        assert!(debug_str.contains("allow_signing_with_any_security_level"));
2036        assert!(debug_str.contains("allow_signing_with_any_purpose"));
2037    }
2038
2039    // -----------------------------------------------------------------------
2040    // StateTransition enum accessor / mutator / classification tests
2041    //
2042    // These exercise the non-trivial match arms across the large enum, using
2043    // the IdentityCreditTransfer, MasternodeVote, IdentityCreditWithdrawal and
2044    // DataContractCreate variants as representative signed / unsigned /
2045    // voting / contract cases. They intentionally do NOT use `sign`/`verify`
2046    // (those go through BLS/ECDSA and have their own coverage elsewhere).
2047    // -----------------------------------------------------------------------
2048    use crate::identity::core_script::CoreScript;
2049    use crate::identity::{Purpose, SecurityLevel};
2050    use crate::prelude::Identifier;
2051    use crate::state_transition::identity_credit_transfer_transition::v0::IdentityCreditTransferTransitionV0;
2052    use crate::state_transition::identity_credit_transfer_transition::IdentityCreditTransferTransition;
2053    use crate::state_transition::identity_credit_withdrawal_transition::v0::IdentityCreditWithdrawalTransitionV0;
2054    use crate::state_transition::identity_credit_withdrawal_transition::IdentityCreditWithdrawalTransition;
2055    use crate::state_transition::masternode_vote_transition::v0::MasternodeVoteTransitionV0;
2056    use crate::state_transition::masternode_vote_transition::MasternodeVoteTransition;
2057    use crate::withdrawal::Pooling;
2058
2059    fn sample_transfer_st() -> StateTransition {
2060        let v0 = IdentityCreditTransferTransitionV0 {
2061            identity_id: Identifier::from([1u8; 32]),
2062            recipient_id: Identifier::from([2u8; 32]),
2063            amount: 1_000,
2064            nonce: 7,
2065            user_fee_increase: 3,
2066            signature_public_key_id: 11,
2067            signature: BinaryData::new(vec![0u8; 65]),
2068        };
2069        StateTransition::IdentityCreditTransfer(IdentityCreditTransferTransition::V0(v0))
2070    }
2071
2072    fn sample_masternode_vote_st() -> StateTransition {
2073        let v0 = MasternodeVoteTransitionV0 {
2074            pro_tx_hash: Identifier::from([3u8; 32]),
2075            voter_identity_id: Identifier::from([4u8; 32]),
2076            vote: Default::default(),
2077            nonce: 2,
2078            signature_public_key_id: 5,
2079            signature: BinaryData::new(vec![9u8; 10]),
2080        };
2081        StateTransition::MasternodeVote(MasternodeVoteTransition::V0(v0))
2082    }
2083
2084    fn sample_withdrawal_st() -> StateTransition {
2085        let v0 = IdentityCreditWithdrawalTransitionV0 {
2086            identity_id: Identifier::from([5u8; 32]),
2087            amount: 42,
2088            core_fee_per_byte: 1,
2089            pooling: Pooling::Never,
2090            output_script: CoreScript::from_bytes(vec![0x76, 0xa9]),
2091            nonce: 4,
2092            user_fee_increase: 1,
2093            signature_public_key_id: 3,
2094            signature: BinaryData::new(vec![8u8; 65]),
2095        };
2096        StateTransition::IdentityCreditWithdrawal(IdentityCreditWithdrawalTransition::V0(v0))
2097    }
2098
2099    #[test]
2100    fn test_name_returns_variant_names() {
2101        assert_eq!(sample_transfer_st().name(), "IdentityCreditTransfer");
2102        assert_eq!(sample_masternode_vote_st().name(), "MasternodeVote");
2103        assert_eq!(sample_withdrawal_st().name(), "IdentityCreditWithdrawal");
2104    }
2105
2106    #[test]
2107    fn test_state_transition_type_matches_variant() {
2108        assert_eq!(
2109            sample_transfer_st().state_transition_type(),
2110            StateTransitionType::IdentityCreditTransfer
2111        );
2112        assert_eq!(
2113            sample_masternode_vote_st().state_transition_type(),
2114            StateTransitionType::MasternodeVote
2115        );
2116        assert_eq!(
2117            sample_withdrawal_st().state_transition_type(),
2118            StateTransitionType::IdentityCreditWithdrawal
2119        );
2120    }
2121
2122    #[test]
2123    fn test_is_identity_signed_excludes_asset_lock_and_shielded() {
2124        assert!(sample_transfer_st().is_identity_signed());
2125        assert!(sample_masternode_vote_st().is_identity_signed());
2126        assert!(sample_withdrawal_st().is_identity_signed());
2127    }
2128
2129    #[test]
2130    fn test_signature_accessor() {
2131        let st = sample_transfer_st();
2132        let sig = st.signature().expect("transfer should expose signature");
2133        assert_eq!(sig.len(), 65);
2134
2135        let st = sample_masternode_vote_st();
2136        let sig = st.signature().expect("masternode vote has signature");
2137        assert_eq!(sig.as_slice(), &[9u8; 10]);
2138    }
2139
2140    #[test]
2141    fn test_owner_id_accessor() {
2142        let transfer = sample_transfer_st();
2143        assert_eq!(transfer.owner_id(), Some(Identifier::from([1u8; 32])));
2144
2145        let vote = sample_masternode_vote_st();
2146        assert_eq!(vote.owner_id(), Some(Identifier::from([4u8; 32])));
2147
2148        let withdraw = sample_withdrawal_st();
2149        assert_eq!(withdraw.owner_id(), Some(Identifier::from([5u8; 32])));
2150    }
2151
2152    #[test]
2153    fn test_signature_public_key_id_accessor() {
2154        assert_eq!(sample_transfer_st().signature_public_key_id(), Some(11));
2155        assert_eq!(
2156            sample_masternode_vote_st().signature_public_key_id(),
2157            Some(5)
2158        );
2159        assert_eq!(sample_withdrawal_st().signature_public_key_id(), Some(3));
2160    }
2161
2162    #[test]
2163    fn test_user_fee_increase_for_various_variants() {
2164        // Transfer exposes its internal value.
2165        assert_eq!(sample_transfer_st().user_fee_increase(), 3);
2166        // Masternode vote returns 0 unconditionally.
2167        assert_eq!(sample_masternode_vote_st().user_fee_increase(), 0);
2168        // Withdrawal exposes its internal value.
2169        assert_eq!(sample_withdrawal_st().user_fee_increase(), 1);
2170    }
2171
2172    #[test]
2173    fn test_set_signature_returns_true_for_supported() {
2174        let mut st = sample_transfer_st();
2175        let ok = st.set_signature(BinaryData::new(vec![0xaa; 65]));
2176        assert!(ok);
2177        assert_eq!(st.signature().unwrap().as_slice(), &[0xaa; 65]);
2178    }
2179
2180    #[test]
2181    fn test_set_user_fee_increase_updates_value() {
2182        let mut st = sample_transfer_st();
2183        st.set_user_fee_increase(42);
2184        assert_eq!(st.user_fee_increase(), 42);
2185
2186        // Masternode vote ignores the setter (documented no-op) — still reads 0.
2187        let mut vote = sample_masternode_vote_st();
2188        vote.set_user_fee_increase(99);
2189        assert_eq!(vote.user_fee_increase(), 0);
2190    }
2191
2192    #[test]
2193    fn test_set_signature_public_key_id() {
2194        let mut st = sample_transfer_st();
2195        st.set_signature_public_key_id(1234);
2196        assert_eq!(st.signature_public_key_id(), Some(1234));
2197    }
2198
2199    #[test]
2200    fn test_required_number_of_private_keys_default() {
2201        // Non asset-lock transitions always require 1 key.
2202        assert_eq!(sample_transfer_st().required_number_of_private_keys(), 1);
2203        assert_eq!(
2204            sample_masternode_vote_st().required_number_of_private_keys(),
2205            1
2206        );
2207        assert_eq!(sample_withdrawal_st().required_number_of_private_keys(), 1);
2208    }
2209
2210    #[test]
2211    fn test_inputs_none_for_legacy_variants() {
2212        // All these variants have no PlatformAddress inputs.
2213        assert!(sample_transfer_st().inputs().is_none());
2214        assert!(sample_masternode_vote_st().inputs().is_none());
2215        assert!(sample_withdrawal_st().inputs().is_none());
2216    }
2217
2218    #[test]
2219    fn test_active_version_range_legacy_transitions() {
2220        // These all report ALL_VERSIONS per the mod.rs table.
2221        assert_eq!(sample_transfer_st().active_version_range(), ALL_VERSIONS);
2222        assert_eq!(
2223            sample_masternode_vote_st().active_version_range(),
2224            ALL_VERSIONS
2225        );
2226        assert_eq!(sample_withdrawal_st().active_version_range(), ALL_VERSIONS);
2227    }
2228
2229    #[test]
2230    fn test_unique_identifiers_non_empty() {
2231        let ids = sample_transfer_st().unique_identifiers();
2232        assert_eq!(ids.len(), 1);
2233        assert!(!ids[0].is_empty());
2234    }
2235
2236    #[test]
2237    fn test_required_asset_lock_balance_rejects_non_asset_lock() {
2238        let platform_version = PlatformVersion::latest();
2239        let st = sample_transfer_st();
2240        let err = st
2241            .required_asset_lock_balance_for_processing_start(platform_version)
2242            .expect_err("credit transfer is not an asset lock state transition");
2243        match err {
2244            ProtocolError::CorruptedCodeExecution(msg) => {
2245                assert!(
2246                    msg.contains("is not an asset lock transaction"),
2247                    "unexpected error message: {msg}"
2248                );
2249            }
2250            other => panic!("expected CorruptedCodeExecution, got {other:?}"),
2251        }
2252    }
2253
2254    #[test]
2255    fn test_security_level_requirement_for_transfer() {
2256        // IdentityCreditTransfer requires CRITICAL at TRANSFER purpose.
2257        let st = sample_transfer_st();
2258        let levels = st
2259            .security_level_requirement(Purpose::TRANSFER)
2260            .expect("transfer state transition should return a requirement");
2261        assert_eq!(levels, vec![SecurityLevel::CRITICAL]);
2262    }
2263
2264    #[test]
2265    fn test_purpose_requirement_for_transfer() {
2266        let st = sample_transfer_st();
2267        let purposes = st
2268            .purpose_requirement()
2269            .expect("transfer state transition should have a purpose");
2270        assert_eq!(purposes, vec![Purpose::TRANSFER]);
2271    }
2272
2273    #[test]
2274    fn test_optional_asset_lock_proof_none_for_transfer() {
2275        let st = sample_transfer_st();
2276        assert!(st.optional_asset_lock_proof().is_none());
2277    }
2278
2279    // -----------------------------------------------------------------------
2280    // Enum construction: From<V0 / outer enum> → StateTransition
2281    // -----------------------------------------------------------------------
2282
2283    #[test]
2284    fn test_from_outer_enum_into_state_transition() {
2285        let outer: IdentityCreditTransferTransition =
2286            IdentityCreditTransferTransition::V0(IdentityCreditTransferTransitionV0::default());
2287        let st: StateTransition = outer.into();
2288        assert!(matches!(st, StateTransition::IdentityCreditTransfer(_)));
2289    }
2290
2291    #[test]
2292    fn test_from_masternode_vote_outer_into_state_transition() {
2293        let outer: MasternodeVoteTransition =
2294            MasternodeVoteTransition::V0(MasternodeVoteTransitionV0::default());
2295        let st: StateTransition = outer.into();
2296        assert!(matches!(st, StateTransition::MasternodeVote(_)));
2297    }
2298
2299    // -----------------------------------------------------------------------
2300    // Serialization round-trip: platform serialize / deserialize via enum.
2301    // Exercises the top-level `StateTransition` (de)serialize glue.
2302    // -----------------------------------------------------------------------
2303
2304    #[test]
2305    fn test_state_transition_platform_serialize_roundtrip() {
2306        use crate::serialization::{PlatformDeserializable, PlatformSerializable};
2307        let original = sample_transfer_st();
2308        let bytes =
2309            PlatformSerializable::serialize_to_bytes(&original).expect("serialize should succeed");
2310        let restored =
2311            StateTransition::deserialize_from_bytes(&bytes).expect("deserialize should succeed");
2312        assert_eq!(original, restored);
2313    }
2314
2315    #[test]
2316    fn test_deserialize_from_bytes_in_version_succeeds_for_latest() {
2317        use crate::serialization::PlatformSerializable;
2318        let original = sample_transfer_st();
2319        let bytes =
2320            PlatformSerializable::serialize_to_bytes(&original).expect("serialize succeeds");
2321        let restored =
2322            StateTransition::deserialize_from_bytes_in_version(&bytes, PlatformVersion::latest())
2323                .expect("deserialize_from_bytes_in_version should succeed");
2324        assert_eq!(original, restored);
2325    }
2326
2327    #[test]
2328    fn test_transaction_id_is_deterministic() {
2329        let st = sample_transfer_st();
2330        let a = st.transaction_id().expect("hash should succeed");
2331        let b = st.transaction_id().expect("hash should succeed");
2332        assert_eq!(a, b);
2333        assert_eq!(a.len(), 32);
2334    }
2335
2336    #[test]
2337    fn test_transaction_id_changes_on_signature_change() {
2338        let mut st = sample_transfer_st();
2339        let before = st.transaction_id().expect("hash should succeed");
2340        st.set_signature(BinaryData::new(vec![0xbb; 65]));
2341        let after = st.transaction_id().expect("hash should succeed");
2342        // Different signatures produce a different serialized form.
2343        assert_ne!(before, after);
2344    }
2345
2346    #[test]
2347    fn test_clone_preserves_inner_state() {
2348        let st = sample_transfer_st();
2349        let cloned = st.clone();
2350        assert_eq!(st, cloned);
2351    }
2352
2353    // -----------------------------------------------------------------------
2354    // Additional coverage: enum arms that weren't previously exercised.
2355    //
2356    // The tests below intentionally target variants the earlier tests did not
2357    // touch (DataContractCreate, DataContractUpdate, Batch, IdentityCreate,
2358    // IdentityTopUp, IdentityUpdate, shielded / address variants) to cover
2359    // the remaining match-arm branches in accessor / mutator / classification
2360    // methods.
2361    // -----------------------------------------------------------------------
2362
2363    use crate::data_contract::serialized_version::DataContractInSerializationFormat;
2364    use crate::state_transition::batch_transition::document_base_transition::v0::DocumentBaseTransitionV0;
2365    use crate::state_transition::batch_transition::document_base_transition::DocumentBaseTransition;
2366    use crate::state_transition::batch_transition::document_delete_transition::{
2367        DocumentDeleteTransition, DocumentDeleteTransitionV0,
2368    };
2369    use crate::state_transition::batch_transition::{BatchTransition, BatchTransitionV0};
2370    use crate::state_transition::data_contract_create_transition::{
2371        DataContractCreateTransition, DataContractCreateTransitionV0,
2372    };
2373    use crate::state_transition::data_contract_update_transition::{
2374        DataContractUpdateTransition, DataContractUpdateTransitionV0,
2375    };
2376    use crate::state_transition::identity_create_transition::v0::IdentityCreateTransitionV0;
2377    use crate::state_transition::identity_create_transition::IdentityCreateTransition;
2378    use crate::state_transition::identity_topup_transition::v0::IdentityTopUpTransitionV0;
2379    use crate::state_transition::identity_topup_transition::IdentityTopUpTransition;
2380    use crate::state_transition::identity_update_transition::v0::IdentityUpdateTransitionV0;
2381    use crate::state_transition::identity_update_transition::IdentityUpdateTransition;
2382    use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0;
2383    use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition;
2384    use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0;
2385    use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition;
2386    use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0;
2387    use crate::state_transition::unshield_transition::UnshieldTransition;
2388
2389    /// Build a DataContractInSerializationFormat from a crate-private v0
2390    /// constructor via the public TryFromPlatformVersioned impl and DataContract V1.
2391    fn sample_data_contract_in_serialization_format() -> DataContractInSerializationFormat {
2392        use crate::data_contract::config::v0::DataContractConfigV0;
2393        use crate::data_contract::config::DataContractConfig;
2394        use crate::data_contract::v1::DataContractV1;
2395        use crate::data_contract::DataContract;
2396        use platform_version::TryIntoPlatformVersioned;
2397        use std::collections::BTreeMap;
2398
2399        let contract = DataContract::V1(DataContractV1 {
2400            id: Identifier::from([9u8; 32]),
2401            version: 1,
2402            owner_id: Identifier::from([7u8; 32]),
2403            document_types: BTreeMap::new(),
2404            config: DataContractConfig::V0(DataContractConfigV0 {
2405                can_be_deleted: false,
2406                readonly: false,
2407                keeps_history: false,
2408                documents_keep_history_contract_default: false,
2409                documents_mutable_contract_default: false,
2410                documents_can_be_deleted_contract_default: false,
2411                requires_identity_encryption_bounded_key: None,
2412                requires_identity_decryption_bounded_key: None,
2413            }),
2414            schema_defs: None,
2415            created_at: None,
2416            updated_at: None,
2417            created_at_block_height: None,
2418            updated_at_block_height: None,
2419            created_at_epoch: None,
2420            updated_at_epoch: None,
2421            groups: BTreeMap::new(),
2422            tokens: BTreeMap::new(),
2423            keywords: Vec::new(),
2424            description: None,
2425        });
2426
2427        contract
2428            .try_into_platform_versioned(PlatformVersion::latest())
2429            .expect("expected to serialize a trivial contract")
2430    }
2431
2432    fn sample_data_contract_create_st() -> StateTransition {
2433        StateTransition::DataContractCreate(DataContractCreateTransition::V0(
2434            DataContractCreateTransitionV0 {
2435                data_contract: sample_data_contract_in_serialization_format(),
2436                identity_nonce: 1,
2437                user_fee_increase: 5,
2438                signature_public_key_id: 2,
2439                signature: BinaryData::new(vec![0xAB; 65]),
2440            },
2441        ))
2442    }
2443
2444    fn sample_data_contract_update_st() -> StateTransition {
2445        StateTransition::DataContractUpdate(DataContractUpdateTransition::V0(
2446            DataContractUpdateTransitionV0 {
2447                identity_contract_nonce: 4,
2448                data_contract: sample_data_contract_in_serialization_format(),
2449                user_fee_increase: 9,
2450                signature_public_key_id: 6,
2451                signature: BinaryData::new(vec![0xCD; 65]),
2452            },
2453        ))
2454    }
2455
2456    fn sample_batch_st_with_delete() -> StateTransition {
2457        let base = DocumentBaseTransition::V0(DocumentBaseTransitionV0 {
2458            id: Identifier::from([1u8; 32]),
2459            identity_contract_nonce: 3,
2460            document_type_name: "preorder".to_string(),
2461            data_contract_id: Identifier::from([2u8; 32]),
2462        });
2463        let delete =
2464            DocumentTransition::Delete(DocumentDeleteTransition::V0(DocumentDeleteTransitionV0 {
2465                base,
2466            }));
2467        StateTransition::Batch(BatchTransition::V0(BatchTransitionV0 {
2468            owner_id: Identifier::from([8u8; 32]),
2469            transitions: vec![delete],
2470            user_fee_increase: 2,
2471            signature_public_key_id: 7,
2472            signature: BinaryData::new(vec![0xEE; 65]),
2473        }))
2474    }
2475
2476    fn sample_batch_st_empty() -> StateTransition {
2477        StateTransition::Batch(BatchTransition::V0(BatchTransitionV0 {
2478            owner_id: Identifier::from([1u8; 32]),
2479            transitions: vec![],
2480            user_fee_increase: 0,
2481            signature_public_key_id: 0,
2482            signature: BinaryData::new(vec![]),
2483        }))
2484    }
2485
2486    fn sample_identity_create_st() -> StateTransition {
2487        StateTransition::IdentityCreate(IdentityCreateTransition::V0(IdentityCreateTransitionV0 {
2488            identity_id: Identifier::from([3u8; 32]),
2489            ..Default::default()
2490        }))
2491    }
2492
2493    fn sample_identity_top_up_st() -> StateTransition {
2494        StateTransition::IdentityTopUp(IdentityTopUpTransition::V0(IdentityTopUpTransitionV0 {
2495            identity_id: Identifier::from([4u8; 32]),
2496            ..Default::default()
2497        }))
2498    }
2499
2500    fn sample_identity_update_st() -> StateTransition {
2501        StateTransition::IdentityUpdate(IdentityUpdateTransition::V0(IdentityUpdateTransitionV0 {
2502            identity_id: Identifier::from([5u8; 32]),
2503            revision: 1,
2504            nonce: 2,
2505            add_public_keys: vec![],
2506            disable_public_keys: vec![],
2507            user_fee_increase: 11,
2508            signature_public_key_id: 33,
2509            signature: BinaryData::new(vec![0xFF; 65]),
2510        }))
2511    }
2512
2513    fn sample_unshield_st() -> StateTransition {
2514        StateTransition::Unshield(UnshieldTransition::V0(UnshieldTransitionV0 {
2515            output_address: Default::default(),
2516            actions: vec![],
2517            unshielding_amount: 0,
2518            anchor: [0u8; 32],
2519            proof: vec![],
2520            binding_signature: [0u8; 64],
2521        }))
2522    }
2523
2524    fn sample_shielded_transfer_st() -> StateTransition {
2525        StateTransition::ShieldedTransfer(ShieldedTransferTransition::V0(
2526            ShieldedTransferTransitionV0 {
2527                actions: vec![],
2528                value_balance: 0,
2529                anchor: [0u8; 32],
2530                proof: vec![],
2531                binding_signature: [0u8; 64],
2532            },
2533        ))
2534    }
2535
2536    fn sample_shielded_withdrawal_st() -> StateTransition {
2537        use crate::identity::core_script::CoreScript;
2538        use crate::withdrawal::Pooling;
2539        StateTransition::ShieldedWithdrawal(ShieldedWithdrawalTransition::V0(
2540            ShieldedWithdrawalTransitionV0 {
2541                actions: vec![],
2542                unshielding_amount: 0,
2543                anchor: [0u8; 32],
2544                proof: vec![],
2545                binding_signature: [0u8; 64],
2546                core_fee_per_byte: 1,
2547                pooling: Pooling::Never,
2548                output_script: CoreScript::from_bytes(vec![]),
2549            },
2550        ))
2551    }
2552
2553    // --- name() covers all previously-untested arms, including the nested
2554    // match for Batch variants. ---
2555    #[test]
2556    fn test_name_for_newly_covered_variants() {
2557        assert_eq!(
2558            sample_data_contract_create_st().name(),
2559            "DataContractCreate"
2560        );
2561        assert_eq!(
2562            sample_data_contract_update_st().name(),
2563            "DataContractUpdate"
2564        );
2565        assert_eq!(sample_identity_create_st().name(), "IdentityCreate");
2566        assert_eq!(sample_identity_top_up_st().name(), "IdentityTopUp");
2567        assert_eq!(sample_identity_update_st().name(), "IdentityUpdate");
2568        assert_eq!(sample_unshield_st().name(), "Unshield");
2569        assert_eq!(sample_shielded_transfer_st().name(), "ShieldedTransfer");
2570        assert_eq!(sample_shielded_withdrawal_st().name(), "ShieldedWithdrawal");
2571
2572        // Batch with a single Delete – exercises the nested DocumentTransition
2573        // match arm in `name()`.
2574        let batch_name = sample_batch_st_with_delete().name();
2575        assert_eq!(batch_name, "DocumentsBatch([Delete])");
2576
2577        // Empty batch – still renders, with an empty list.
2578        let empty_name = sample_batch_st_empty().name();
2579        assert_eq!(empty_name, "DocumentsBatch([])");
2580    }
2581
2582    // --- state_transition_type covers the call_method! dispatch. ---
2583    #[test]
2584    fn test_state_transition_type_for_newly_covered_variants() {
2585        assert_eq!(
2586            sample_data_contract_create_st().state_transition_type(),
2587            StateTransitionType::DataContractCreate
2588        );
2589        assert_eq!(
2590            sample_data_contract_update_st().state_transition_type(),
2591            StateTransitionType::DataContractUpdate
2592        );
2593        assert_eq!(
2594            sample_batch_st_with_delete().state_transition_type(),
2595            StateTransitionType::Batch
2596        );
2597        assert_eq!(
2598            sample_identity_create_st().state_transition_type(),
2599            StateTransitionType::IdentityCreate
2600        );
2601        assert_eq!(
2602            sample_identity_top_up_st().state_transition_type(),
2603            StateTransitionType::IdentityTopUp
2604        );
2605        assert_eq!(
2606            sample_identity_update_st().state_transition_type(),
2607            StateTransitionType::IdentityUpdate
2608        );
2609        assert_eq!(
2610            sample_unshield_st().state_transition_type(),
2611            StateTransitionType::Unshield
2612        );
2613        assert_eq!(
2614            sample_shielded_transfer_st().state_transition_type(),
2615            StateTransitionType::ShieldedTransfer
2616        );
2617        assert_eq!(
2618            sample_shielded_withdrawal_st().state_transition_type(),
2619            StateTransitionType::ShieldedWithdrawal
2620        );
2621    }
2622
2623    // --- active_version_range uses different branches per transition
2624    // "group". Exercises the contract-format V1 branch for
2625    // DataContractCreate/Update (9..=LATEST), the BatchTransitionV0 branch
2626    // (ALL_VERSIONS), and the shielded range (12..=LATEST).
2627    #[test]
2628    fn test_active_version_range_contract_and_shielded_branches() {
2629        // DataContractCreate/Update on PlatformVersion::latest use the V1
2630        // contract serialization format, which restricts active range.
2631        let contract_v1_range = 9..=LATEST_VERSION;
2632        assert_eq!(
2633            sample_data_contract_create_st().active_version_range(),
2634            contract_v1_range
2635        );
2636        let contract_v1_range = 9..=LATEST_VERSION;
2637        assert_eq!(
2638            sample_data_contract_update_st().active_version_range(),
2639            contract_v1_range
2640        );
2641        // BatchTransition::V0 → ALL_VERSIONS
2642        assert_eq!(
2643            sample_batch_st_with_delete().active_version_range(),
2644            ALL_VERSIONS
2645        );
2646        // IdentityCreate/TopUp/Update are ALL_VERSIONS.
2647        assert_eq!(
2648            sample_identity_create_st().active_version_range(),
2649            ALL_VERSIONS
2650        );
2651        assert_eq!(
2652            sample_identity_top_up_st().active_version_range(),
2653            ALL_VERSIONS
2654        );
2655        assert_eq!(
2656            sample_identity_update_st().active_version_range(),
2657            ALL_VERSIONS
2658        );
2659        // Shielded variants report a shielded range (12..=LATEST_VERSION).
2660        let shielded_range = 12..=LATEST_VERSION;
2661        assert_eq!(
2662            sample_shielded_transfer_st().active_version_range(),
2663            shielded_range.clone()
2664        );
2665        assert_eq!(
2666            sample_unshield_st().active_version_range(),
2667            shielded_range.clone()
2668        );
2669        assert_eq!(
2670            sample_shielded_withdrawal_st().active_version_range(),
2671            shielded_range
2672        );
2673    }
2674
2675    // --- is_identity_signed exercises the inverted-match logic for the
2676    // shielded / identity-create / topup variants. ---
2677    #[test]
2678    fn test_is_identity_signed_false_for_identity_create_topup_and_shielded() {
2679        assert!(!sample_identity_create_st().is_identity_signed());
2680        assert!(!sample_identity_top_up_st().is_identity_signed());
2681        assert!(!sample_unshield_st().is_identity_signed());
2682        assert!(!sample_shielded_transfer_st().is_identity_signed());
2683        assert!(!sample_shielded_withdrawal_st().is_identity_signed());
2684    }
2685
2686    // --- signature accessor for each arm that returns Some/None; previously
2687    // only IdentityCreditTransfer / MasternodeVote / IdentityCreditWithdrawal
2688    // were covered.
2689    #[test]
2690    fn test_signature_accessor_for_other_variants() {
2691        // Some(_) arms
2692        assert_eq!(
2693            sample_data_contract_create_st().signature().unwrap().len(),
2694            65
2695        );
2696        assert_eq!(
2697            sample_data_contract_update_st().signature().unwrap().len(),
2698            65
2699        );
2700        assert_eq!(sample_batch_st_with_delete().signature().unwrap().len(), 65);
2701        assert_eq!(sample_identity_update_st().signature().unwrap().len(), 65);
2702
2703        // None arms for address / shielded variants.
2704        assert!(sample_unshield_st().signature().is_none());
2705        assert!(sample_shielded_transfer_st().signature().is_none());
2706        assert!(sample_shielded_withdrawal_st().signature().is_none());
2707    }
2708
2709    // --- owner_id accessor for each arm.
2710    #[test]
2711    fn test_owner_id_accessor_for_other_variants() {
2712        assert_eq!(
2713            sample_data_contract_create_st().owner_id(),
2714            Some(Identifier::from([7u8; 32]))
2715        );
2716        assert_eq!(
2717            sample_data_contract_update_st().owner_id(),
2718            Some(Identifier::from([7u8; 32]))
2719        );
2720        assert_eq!(
2721            sample_batch_st_with_delete().owner_id(),
2722            Some(Identifier::from([8u8; 32]))
2723        );
2724        assert_eq!(
2725            sample_identity_update_st().owner_id(),
2726            Some(Identifier::from([5u8; 32]))
2727        );
2728        // These variants unconditionally return None.
2729        assert!(sample_unshield_st().owner_id().is_none());
2730        assert!(sample_shielded_transfer_st().owner_id().is_none());
2731        assert!(sample_shielded_withdrawal_st().owner_id().is_none());
2732    }
2733
2734    // --- user_fee_increase accessor — includes arms that return 0
2735    // unconditionally (shielded/masternode) vs the variants' stored value.
2736    #[test]
2737    fn test_user_fee_increase_for_newly_covered_variants() {
2738        assert_eq!(sample_data_contract_create_st().user_fee_increase(), 5);
2739        assert_eq!(sample_data_contract_update_st().user_fee_increase(), 9);
2740        assert_eq!(sample_batch_st_with_delete().user_fee_increase(), 2);
2741        assert_eq!(sample_identity_update_st().user_fee_increase(), 11);
2742        // Unconditionally 0 for shielded.
2743        assert_eq!(sample_shielded_transfer_st().user_fee_increase(), 0);
2744        assert_eq!(sample_shielded_withdrawal_st().user_fee_increase(), 0);
2745        assert_eq!(sample_unshield_st().user_fee_increase(), 0);
2746    }
2747
2748    // --- set_user_fee_increase for the no-op shielded arms and for the
2749    // transitions that actually do store the value.
2750    #[test]
2751    fn test_set_user_fee_increase_for_newly_covered_variants() {
2752        let mut st = sample_data_contract_create_st();
2753        st.set_user_fee_increase(42);
2754        assert_eq!(st.user_fee_increase(), 42);
2755
2756        let mut st = sample_data_contract_update_st();
2757        st.set_user_fee_increase(13);
2758        assert_eq!(st.user_fee_increase(), 13);
2759
2760        let mut st = sample_batch_st_with_delete();
2761        st.set_user_fee_increase(101);
2762        assert_eq!(st.user_fee_increase(), 101);
2763
2764        let mut st = sample_identity_update_st();
2765        st.set_user_fee_increase(77);
2766        assert_eq!(st.user_fee_increase(), 77);
2767
2768        // Shielded no-ops: value stays 0.
2769        let mut shielded = sample_shielded_transfer_st();
2770        shielded.set_user_fee_increase(99);
2771        assert_eq!(shielded.user_fee_increase(), 0);
2772
2773        let mut withdrawal = sample_shielded_withdrawal_st();
2774        withdrawal.set_user_fee_increase(99);
2775        assert_eq!(withdrawal.user_fee_increase(), 0);
2776
2777        let mut unshield = sample_unshield_st();
2778        unshield.set_user_fee_increase(99);
2779        assert_eq!(unshield.user_fee_increase(), 0);
2780    }
2781
2782    // --- set_signature: exercises the `true` arms we didn't test before
2783    // (DataContractCreate/Update/Batch/IdentityUpdate) and the `false` arms
2784    // (shielded transitions).
2785    #[test]
2786    fn test_set_signature_false_for_shielded_and_identity_create_topup() {
2787        // `false` arms: shield*, shielded*, unshield, address* (no-op, returns false).
2788        let mut st = sample_unshield_st();
2789        assert!(!st.set_signature(BinaryData::new(vec![0xAB; 65])));
2790        let mut st = sample_shielded_transfer_st();
2791        assert!(!st.set_signature(BinaryData::new(vec![0xAB; 65])));
2792        let mut st = sample_shielded_withdrawal_st();
2793        assert!(!st.set_signature(BinaryData::new(vec![0xAB; 65])));
2794    }
2795
2796    #[test]
2797    fn test_set_signature_true_for_newly_covered_variants() {
2798        let mut st = sample_data_contract_create_st();
2799        assert!(st.set_signature(BinaryData::new(vec![0x11; 65])));
2800        assert_eq!(st.signature().unwrap().as_slice(), &[0x11; 65]);
2801
2802        let mut st = sample_data_contract_update_st();
2803        assert!(st.set_signature(BinaryData::new(vec![0x22; 65])));
2804        assert_eq!(st.signature().unwrap().as_slice(), &[0x22; 65]);
2805
2806        let mut st = sample_batch_st_with_delete();
2807        assert!(st.set_signature(BinaryData::new(vec![0x33; 65])));
2808        assert_eq!(st.signature().unwrap().as_slice(), &[0x33; 65]);
2809
2810        let mut st = sample_identity_update_st();
2811        assert!(st.set_signature(BinaryData::new(vec![0x44; 65])));
2812        assert_eq!(st.signature().unwrap().as_slice(), &[0x44; 65]);
2813    }
2814
2815    // --- signature_public_key_id: identity-signed arms return Some, others
2816    // (shielded/identity-create/topup/address) return None.
2817    #[test]
2818    fn test_signature_public_key_id_returns_none_for_non_signed() {
2819        // IdentityCreate / IdentityTopUp / shielded / address variants are all
2820        // "not identity-signed" and return None.
2821        assert!(sample_identity_create_st()
2822            .signature_public_key_id()
2823            .is_none());
2824        assert!(sample_identity_top_up_st()
2825            .signature_public_key_id()
2826            .is_none());
2827        assert!(sample_unshield_st().signature_public_key_id().is_none());
2828        assert!(sample_shielded_transfer_st()
2829            .signature_public_key_id()
2830            .is_none());
2831        assert!(sample_shielded_withdrawal_st()
2832            .signature_public_key_id()
2833            .is_none());
2834    }
2835
2836    #[test]
2837    fn test_signature_public_key_id_for_signed_variants() {
2838        assert_eq!(
2839            sample_data_contract_create_st().signature_public_key_id(),
2840            Some(2)
2841        );
2842        assert_eq!(
2843            sample_data_contract_update_st().signature_public_key_id(),
2844            Some(6)
2845        );
2846        assert_eq!(
2847            sample_batch_st_with_delete().signature_public_key_id(),
2848            Some(7)
2849        );
2850        assert_eq!(
2851            sample_identity_update_st().signature_public_key_id(),
2852            Some(33)
2853        );
2854    }
2855
2856    // --- set_signature_public_key_id: no-op for IdentityCreate/TopUp and
2857    // shielded variants; updates for identity-signed variants. ---
2858    #[test]
2859    fn test_set_signature_public_key_id_noop_for_non_signed() {
2860        // These variants are not identity-signed; setter is a no-op in the
2861        // call_method_identity_signed! macro.
2862        let mut st = sample_identity_create_st();
2863        st.set_signature_public_key_id(100);
2864        assert_eq!(st.signature_public_key_id(), None);
2865
2866        let mut st = sample_identity_top_up_st();
2867        st.set_signature_public_key_id(100);
2868        assert_eq!(st.signature_public_key_id(), None);
2869
2870        let mut st = sample_unshield_st();
2871        st.set_signature_public_key_id(100);
2872        assert_eq!(st.signature_public_key_id(), None);
2873    }
2874
2875    #[test]
2876    fn test_set_signature_public_key_id_updates_for_signed_variants() {
2877        let mut st = sample_data_contract_create_st();
2878        st.set_signature_public_key_id(42);
2879        assert_eq!(st.signature_public_key_id(), Some(42));
2880
2881        let mut st = sample_batch_st_with_delete();
2882        st.set_signature_public_key_id(43);
2883        assert_eq!(st.signature_public_key_id(), Some(43));
2884
2885        let mut st = sample_identity_update_st();
2886        st.set_signature_public_key_id(44);
2887        assert_eq!(st.signature_public_key_id(), Some(44));
2888    }
2889
2890    // --- required_number_of_private_keys defaults to 1 for "signed" variants
2891    // and 0 for shielded ones.
2892    #[test]
2893    fn test_required_number_of_private_keys_various_variants() {
2894        assert_eq!(
2895            sample_data_contract_create_st().required_number_of_private_keys(),
2896            1
2897        );
2898        assert_eq!(
2899            sample_data_contract_update_st().required_number_of_private_keys(),
2900            1
2901        );
2902        assert_eq!(
2903            sample_batch_st_with_delete().required_number_of_private_keys(),
2904            1
2905        );
2906        assert_eq!(
2907            sample_identity_update_st().required_number_of_private_keys(),
2908            1
2909        );
2910        assert_eq!(
2911            sample_identity_create_st().required_number_of_private_keys(),
2912            1
2913        );
2914        // Shielded variants return 0 unconditionally.
2915        assert_eq!(
2916            sample_shielded_transfer_st().required_number_of_private_keys(),
2917            0
2918        );
2919        assert_eq!(
2920            sample_shielded_withdrawal_st().required_number_of_private_keys(),
2921            0
2922        );
2923        assert_eq!(sample_unshield_st().required_number_of_private_keys(), 0);
2924    }
2925
2926    // --- inputs(): None for all these variants (covers the big
2927    // wildcard/None arm in the match).
2928    #[test]
2929    fn test_inputs_none_for_many_variants() {
2930        assert!(sample_data_contract_create_st().inputs().is_none());
2931        assert!(sample_data_contract_update_st().inputs().is_none());
2932        assert!(sample_batch_st_with_delete().inputs().is_none());
2933        assert!(sample_identity_create_st().inputs().is_none());
2934        assert!(sample_identity_top_up_st().inputs().is_none());
2935        assert!(sample_identity_update_st().inputs().is_none());
2936        // Shielded variants also return None for inputs().
2937        assert!(sample_unshield_st().inputs().is_none());
2938        assert!(sample_shielded_transfer_st().inputs().is_none());
2939        assert!(sample_shielded_withdrawal_st().inputs().is_none());
2940    }
2941
2942    // --- optional_asset_lock_proof: None for everything that isn't
2943    // IdentityCreate / IdentityTopUp / ShieldFromAssetLock. The IdentityCreate
2944    // default contains the asset lock proof field, so this forwards to its
2945    // implementation.
2946    #[test]
2947    fn test_optional_asset_lock_proof_returns_none_for_wildcard_arms() {
2948        assert!(sample_data_contract_create_st()
2949            .optional_asset_lock_proof()
2950            .is_none());
2951        assert!(sample_data_contract_update_st()
2952            .optional_asset_lock_proof()
2953            .is_none());
2954        assert!(sample_batch_st_with_delete()
2955            .optional_asset_lock_proof()
2956            .is_none());
2957        assert!(sample_identity_update_st()
2958            .optional_asset_lock_proof()
2959            .is_none());
2960        assert!(sample_unshield_st().optional_asset_lock_proof().is_none());
2961        assert!(sample_shielded_transfer_st()
2962            .optional_asset_lock_proof()
2963            .is_none());
2964        assert!(sample_shielded_withdrawal_st()
2965            .optional_asset_lock_proof()
2966            .is_none());
2967    }
2968
2969    // --- required_asset_lock_balance_for_processing_start returns an
2970    // CorruptedCodeExecution error for non asset-lock variants. Exercise
2971    // additional arms beyond what the original transfer test covered.
2972    #[test]
2973    fn test_required_asset_lock_balance_errors_for_other_non_asset_lock_variants() {
2974        let platform_version = PlatformVersion::latest();
2975
2976        let cases: Vec<(&str, StateTransition)> = vec![
2977            ("DataContractCreate", sample_data_contract_create_st()),
2978            ("DataContractUpdate", sample_data_contract_update_st()),
2979            ("Batch", sample_batch_st_with_delete()),
2980            ("IdentityUpdate", sample_identity_update_st()),
2981            ("MasternodeVote", sample_masternode_vote_st()),
2982            ("Unshield", sample_unshield_st()),
2983            ("ShieldedTransfer", sample_shielded_transfer_st()),
2984            ("ShieldedWithdrawal", sample_shielded_withdrawal_st()),
2985        ];
2986
2987        for (label, st) in cases {
2988            let err = st
2989                .required_asset_lock_balance_for_processing_start(platform_version)
2990                .expect_err(&format!("expected error for {label}"));
2991            match err {
2992                ProtocolError::CorruptedCodeExecution(msg) => {
2993                    assert!(
2994                        msg.contains("is not an asset lock transaction"),
2995                        "unexpected error for {label}: {msg}"
2996                    );
2997                }
2998                other => panic!("expected CorruptedCodeExecution for {label}, got {other:?}"),
2999            }
3000        }
3001    }
3002
3003    // --- unique_identifiers: covers the call_method! dispatch for arms
3004    // beyond credit transfer. Each variant's `unique_identifiers`
3005    // implementation returns a non-empty vector; the individual identifier
3006    // strings may be empty for some variants whose IDs are encoded as empty
3007    // (this method simply shouldn't panic or short-circuit).
3008    #[test]
3009    fn test_unique_identifiers_non_empty_for_other_variants() {
3010        for st in [
3011            sample_data_contract_create_st(),
3012            sample_data_contract_update_st(),
3013            sample_batch_st_with_delete(),
3014            sample_identity_create_st(),
3015            sample_identity_top_up_st(),
3016            sample_identity_update_st(),
3017        ] {
3018            let ids = st.unique_identifiers();
3019            assert!(!ids.is_empty(), "unique_identifiers should not be empty");
3020        }
3021    }
3022
3023    // --- security_level_requirement returns None for identity-create/topup
3024    // and for every shielded/address variant. This hits the None arms in
3025    // call_getter_method_identity_signed!.
3026    #[test]
3027    fn test_security_level_requirement_returns_none_for_non_signed_variants() {
3028        let purpose = Purpose::AUTHENTICATION;
3029        assert!(sample_identity_create_st()
3030            .security_level_requirement(purpose)
3031            .is_none());
3032        assert!(sample_identity_top_up_st()
3033            .security_level_requirement(purpose)
3034            .is_none());
3035        assert!(sample_unshield_st()
3036            .security_level_requirement(purpose)
3037            .is_none());
3038        assert!(sample_shielded_transfer_st()
3039            .security_level_requirement(purpose)
3040            .is_none());
3041        assert!(sample_shielded_withdrawal_st()
3042            .security_level_requirement(purpose)
3043            .is_none());
3044    }
3045
3046    #[test]
3047    fn test_purpose_requirement_returns_none_for_non_signed_variants() {
3048        assert!(sample_identity_create_st().purpose_requirement().is_none());
3049        assert!(sample_identity_top_up_st().purpose_requirement().is_none());
3050        assert!(sample_unshield_st().purpose_requirement().is_none());
3051        assert!(sample_shielded_transfer_st()
3052            .purpose_requirement()
3053            .is_none());
3054        assert!(sample_shielded_withdrawal_st()
3055            .purpose_requirement()
3056            .is_none());
3057    }
3058
3059    // --- From impls: each From<Outer> → StateTransition uses `derive_more::From`.
3060    #[test]
3061    fn test_from_outer_data_contract_create_into_state_transition() {
3062        let outer: DataContractCreateTransition =
3063            DataContractCreateTransition::V0(DataContractCreateTransitionV0 {
3064                data_contract: sample_data_contract_in_serialization_format(),
3065                identity_nonce: 1,
3066                user_fee_increase: 0,
3067                signature_public_key_id: 0,
3068                signature: Default::default(),
3069            });
3070        let st: StateTransition = outer.into();
3071        assert!(matches!(st, StateTransition::DataContractCreate(_)));
3072    }
3073
3074    #[test]
3075    fn test_from_outer_data_contract_update_into_state_transition() {
3076        let outer: DataContractUpdateTransition =
3077            DataContractUpdateTransition::V0(DataContractUpdateTransitionV0 {
3078                identity_contract_nonce: 2,
3079                data_contract: sample_data_contract_in_serialization_format(),
3080                user_fee_increase: 0,
3081                signature_public_key_id: 0,
3082                signature: Default::default(),
3083            });
3084        let st: StateTransition = outer.into();
3085        assert!(matches!(st, StateTransition::DataContractUpdate(_)));
3086    }
3087
3088    #[test]
3089    fn test_from_outer_batch_into_state_transition() {
3090        let outer: BatchTransition = BatchTransition::V0(BatchTransitionV0::default());
3091        let st: StateTransition = outer.into();
3092        assert!(matches!(st, StateTransition::Batch(_)));
3093    }
3094
3095    #[test]
3096    fn test_from_outer_identity_create_into_state_transition() {
3097        let outer: IdentityCreateTransition =
3098            IdentityCreateTransition::V0(IdentityCreateTransitionV0::default());
3099        let st: StateTransition = outer.into();
3100        assert!(matches!(st, StateTransition::IdentityCreate(_)));
3101    }
3102
3103    #[test]
3104    fn test_from_outer_identity_update_into_state_transition() {
3105        let outer: IdentityUpdateTransition =
3106            IdentityUpdateTransition::V0(IdentityUpdateTransitionV0::default());
3107        let st: StateTransition = outer.into();
3108        assert!(matches!(st, StateTransition::IdentityUpdate(_)));
3109    }
3110
3111    // --- transaction_id + clone for additional variants — triggers the
3112    // serialize path for each arm.
3113    #[test]
3114    fn test_transaction_id_and_clone_for_identity_update() {
3115        let st = sample_identity_update_st();
3116        let id_a = st.transaction_id().expect("hash should succeed");
3117        let cloned = st.clone();
3118        let id_b = cloned.transaction_id().expect("hash should succeed");
3119        assert_eq!(id_a, id_b);
3120        assert_eq!(id_a.len(), 32);
3121    }
3122
3123    #[test]
3124    fn test_transaction_id_and_clone_for_data_contract_create() {
3125        let st = sample_data_contract_create_st();
3126        let id_a = st.transaction_id().expect("hash should succeed");
3127        let cloned = st.clone();
3128        let id_b = cloned.transaction_id().expect("hash should succeed");
3129        assert_eq!(id_a, id_b);
3130        assert_eq!(id_a.len(), 32);
3131    }
3132
3133    // --- serialize round-trip for variants beyond credit transfer. ---
3134    #[test]
3135    fn test_serialize_roundtrip_identity_update() {
3136        use crate::serialization::{PlatformDeserializable, PlatformSerializable};
3137        let original = sample_identity_update_st();
3138        let bytes =
3139            PlatformSerializable::serialize_to_bytes(&original).expect("serialize should succeed");
3140        let restored =
3141            StateTransition::deserialize_from_bytes(&bytes).expect("deserialize should succeed");
3142        assert_eq!(original, restored);
3143    }
3144
3145    #[test]
3146    fn test_serialize_roundtrip_data_contract_update() {
3147        use crate::serialization::{PlatformDeserializable, PlatformSerializable};
3148        let original = sample_data_contract_update_st();
3149        let bytes =
3150            PlatformSerializable::serialize_to_bytes(&original).expect("serialize should succeed");
3151        let restored =
3152            StateTransition::deserialize_from_bytes(&bytes).expect("deserialize should succeed");
3153        assert_eq!(original, restored);
3154    }
3155
3156    #[test]
3157    fn test_serialize_roundtrip_batch_empty() {
3158        use crate::serialization::{PlatformDeserializable, PlatformSerializable};
3159        let original = sample_batch_st_empty();
3160        let bytes =
3161            PlatformSerializable::serialize_to_bytes(&original).expect("serialize should succeed");
3162        let restored =
3163            StateTransition::deserialize_from_bytes(&bytes).expect("deserialize should succeed");
3164        assert_eq!(original, restored);
3165    }
3166
3167    // --- deserialize_from_bytes_in_version error path: craft bytes for a
3168    // variant whose `active_version_range()` starts at 11 or 12 and then
3169    // attempt to deserialize them with a PlatformVersion whose protocol
3170    // version is below that range. Exercises the
3171    // `StateTransitionIsNotActiveError` arm.
3172    // ---
3173    #[cfg(all(feature = "state-transitions", feature = "validation"))]
3174    #[test]
3175    fn test_deserialize_from_bytes_in_version_returns_not_active_error() {
3176        use crate::serialization::PlatformSerializable;
3177
3178        // ShieldedTransfer has active_version_range = 12..=LATEST_VERSION.
3179        let original = sample_shielded_transfer_st();
3180        let bytes =
3181            PlatformSerializable::serialize_to_bytes(&original).expect("serialize succeeds");
3182
3183        // Find a real PlatformVersion whose protocol_version is < 12 so the
3184        // range check rejects it. PlatformVersion::get(1) corresponds to
3185        // protocol version 1 which is guaranteed below any shielded range.
3186        let low_version = PlatformVersion::get(1).expect("platform version 1 exists");
3187        assert!(
3188            low_version.protocol_version < 12,
3189            "expected sub-12 version for this test, got {}",
3190            low_version.protocol_version
3191        );
3192
3193        let err = StateTransition::deserialize_from_bytes_in_version(&bytes, low_version)
3194            .expect_err("expected StateTransitionIsNotActiveError for sub-12 protocol");
3195        match err {
3196            ProtocolError::StateTransitionError(
3197                crate::state_transition::errors::StateTransitionError::StateTransitionIsNotActiveError {
3198                    state_transition_type,
3199                    active_version_range,
3200                    current_protocol_version,
3201                },
3202            ) => {
3203                assert_eq!(state_transition_type, "ShieldedTransfer");
3204                assert_eq!(current_protocol_version, low_version.protocol_version);
3205                assert!(active_version_range.start() >= &12);
3206            }
3207            other => panic!("expected StateTransitionIsNotActiveError, got {other:?}"),
3208        }
3209    }
3210
3211    // -----------------------------------------------------------------------
3212    // Additional coverage: variants not yet exercised.
3213    //
3214    // The tests below target:
3215    //   * IdentityCreditWithdrawal::V1 (previously only V0 was covered).
3216    //   * IdentityCreditTransferToAddresses (its own top-level arm).
3217    //   * AddressFundingFromAssetLock (identity-signed + asset-lock arm).
3218    //   * AddressCreditWithdrawal (not identity-signed, address-funds arm).
3219    //   * ShieldFromAssetLock (asset-lock, non-identity-signed).
3220    //   * Batch with a token transition (nested enum, previously only Delete).
3221    // -----------------------------------------------------------------------
3222
3223    use crate::state_transition::identity_credit_transfer_to_addresses_transition::v0::IdentityCreditTransferToAddressesTransitionV0;
3224    use crate::state_transition::identity_credit_transfer_to_addresses_transition::IdentityCreditTransferToAddressesTransition;
3225    use crate::state_transition::identity_credit_withdrawal_transition::v1::IdentityCreditWithdrawalTransitionV1;
3226    use crate::withdrawal::Pooling as WithdrawalPooling;
3227
3228    fn sample_withdrawal_v1_st() -> StateTransition {
3229        let v1 = IdentityCreditWithdrawalTransitionV1 {
3230            identity_id: Identifier::from([12u8; 32]),
3231            amount: 777,
3232            core_fee_per_byte: 2,
3233            pooling: WithdrawalPooling::Standard,
3234            output_script: None,
3235            nonce: 9,
3236            user_fee_increase: 4,
3237            signature_public_key_id: 21,
3238            signature: BinaryData::new(vec![0x12; 65]),
3239        };
3240        StateTransition::IdentityCreditWithdrawal(IdentityCreditWithdrawalTransition::V1(v1))
3241    }
3242
3243    fn sample_credit_transfer_to_addresses_st() -> StateTransition {
3244        let v0 = IdentityCreditTransferToAddressesTransitionV0 {
3245            identity_id: Identifier::from([13u8; 32]),
3246            ..Default::default()
3247        };
3248        StateTransition::IdentityCreditTransferToAddresses(
3249            IdentityCreditTransferToAddressesTransition::V0(v0),
3250        )
3251    }
3252
3253    fn sample_address_credit_withdrawal_st() -> StateTransition {
3254        use crate::state_transition::address_credit_withdrawal_transition::v0::AddressCreditWithdrawalTransitionV0;
3255        StateTransition::AddressCreditWithdrawal(AddressCreditWithdrawalTransition::V0(
3256            AddressCreditWithdrawalTransitionV0::default(),
3257        ))
3258    }
3259
3260    fn sample_shield_from_asset_lock_st() -> StateTransition {
3261        use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0;
3262        StateTransition::ShieldFromAssetLock(ShieldFromAssetLockTransition::V0(
3263            ShieldFromAssetLockTransitionV0 {
3264                asset_lock_proof: Default::default(),
3265                actions: vec![],
3266                value_balance: 100,
3267                anchor: [0u8; 32],
3268                proof: vec![],
3269                binding_signature: [0u8; 64],
3270                surplus_output: None,
3271                signature: BinaryData::new(vec![0x55; 65]),
3272            },
3273        ))
3274    }
3275
3276    // ---------- IdentityCreditWithdrawal V1 accessors ----------
3277
3278    #[test]
3279    fn test_withdrawal_v1_name_and_type() {
3280        let st = sample_withdrawal_v1_st();
3281        assert_eq!(st.name(), "IdentityCreditWithdrawal");
3282        assert_eq!(
3283            st.state_transition_type(),
3284            StateTransitionType::IdentityCreditWithdrawal
3285        );
3286    }
3287
3288    #[test]
3289    fn test_withdrawal_v1_is_identity_signed_true() {
3290        assert!(sample_withdrawal_v1_st().is_identity_signed());
3291    }
3292
3293    #[test]
3294    fn test_withdrawal_v1_signature_and_owner_and_key_id() {
3295        let st = sample_withdrawal_v1_st();
3296        // signature accessor -> Some
3297        let sig = st.signature().expect("V1 withdrawal has a signature");
3298        assert_eq!(sig.as_slice(), &[0x12; 65]);
3299        // owner_id delegates to identity_id
3300        assert_eq!(st.owner_id(), Some(Identifier::from([12u8; 32])));
3301        assert_eq!(st.signature_public_key_id(), Some(21));
3302        assert_eq!(st.user_fee_increase(), 4);
3303    }
3304
3305    #[test]
3306    fn test_withdrawal_v1_set_signature_and_fee_and_key_id() {
3307        let mut st = sample_withdrawal_v1_st();
3308        assert!(st.set_signature(BinaryData::new(vec![0x99; 65])));
3309        assert_eq!(st.signature().unwrap().as_slice(), &[0x99; 65]);
3310
3311        st.set_user_fee_increase(33);
3312        assert_eq!(st.user_fee_increase(), 33);
3313
3314        st.set_signature_public_key_id(64);
3315        assert_eq!(st.signature_public_key_id(), Some(64));
3316    }
3317
3318    #[test]
3319    fn test_withdrawal_v1_serialize_roundtrip_via_state_transition() {
3320        use crate::serialization::{PlatformDeserializable, PlatformSerializable};
3321        let original = sample_withdrawal_v1_st();
3322        let bytes = PlatformSerializable::serialize_to_bytes(&original).expect("serialize ok");
3323        let restored = StateTransition::deserialize_from_bytes(&bytes).expect("deserialize ok");
3324        assert_eq!(original, restored);
3325        // The restored variant must still be V1, not V0 — exercises the
3326        // feature-version dispatch in deserialize.
3327        match restored {
3328            StateTransition::IdentityCreditWithdrawal(IdentityCreditWithdrawalTransition::V1(
3329                _,
3330            )) => {}
3331            other => panic!("expected V1 inner variant, got: {:?}", other),
3332        }
3333    }
3334
3335    #[test]
3336    fn test_withdrawal_v1_transaction_id_differs_from_v0() {
3337        // V0 and V1 carry different serialized forms → distinct transaction ids.
3338        let v0 = sample_withdrawal_st();
3339        let v1 = sample_withdrawal_v1_st();
3340        let id_v0 = v0.transaction_id().expect("v0 hash");
3341        let id_v1 = v1.transaction_id().expect("v1 hash");
3342        assert_ne!(id_v0, id_v1);
3343    }
3344
3345    #[test]
3346    fn test_withdrawal_v1_required_asset_lock_balance_errors() {
3347        let err = sample_withdrawal_v1_st()
3348            .required_asset_lock_balance_for_processing_start(PlatformVersion::latest())
3349            .expect_err("withdrawal is not an asset lock ST");
3350        matches!(err, ProtocolError::CorruptedCodeExecution(_));
3351    }
3352
3353    // ---------- IdentityCreditTransferToAddresses ----------
3354
3355    #[test]
3356    fn test_credit_transfer_to_addresses_name_and_type() {
3357        let st = sample_credit_transfer_to_addresses_st();
3358        assert_eq!(st.name(), "IdentityCreditTransferToAddresses");
3359        assert_eq!(
3360            st.state_transition_type(),
3361            StateTransitionType::IdentityCreditTransferToAddresses
3362        );
3363    }
3364
3365    #[test]
3366    fn test_credit_transfer_to_addresses_signature_some_and_owner_some() {
3367        let st = sample_credit_transfer_to_addresses_st();
3368        assert!(st.signature().is_some(), "has a signature field");
3369        assert_eq!(st.owner_id(), Some(Identifier::from([13u8; 32])));
3370    }
3371
3372    #[test]
3373    fn test_credit_transfer_to_addresses_is_identity_signed_true() {
3374        // Not in the "not identity signed" list → should be true.
3375        assert!(sample_credit_transfer_to_addresses_st().is_identity_signed());
3376    }
3377
3378    #[test]
3379    fn test_credit_transfer_to_addresses_inputs_none_active_range_11_latest() {
3380        let st = sample_credit_transfer_to_addresses_st();
3381        assert!(st.inputs().is_none());
3382        // Per mod.rs table: this variant is in the 11..=LATEST_VERSION group.
3383        let range = st.active_version_range();
3384        assert_eq!(*range.start(), 11);
3385        assert_eq!(*range.end(), LATEST_VERSION);
3386    }
3387
3388    #[test]
3389    fn test_credit_transfer_to_addresses_set_signature_returns_true() {
3390        let mut st = sample_credit_transfer_to_addresses_st();
3391        let ok = st.set_signature(BinaryData::new(vec![0x77; 65]));
3392        assert!(ok);
3393        assert_eq!(st.signature().unwrap().as_slice(), &[0x77; 65]);
3394    }
3395
3396    #[test]
3397    fn test_credit_transfer_to_addresses_from_outer_enum() {
3398        let outer = IdentityCreditTransferToAddressesTransition::V0(
3399            IdentityCreditTransferToAddressesTransitionV0::default(),
3400        );
3401        let st: StateTransition = outer.into();
3402        assert!(matches!(
3403            st,
3404            StateTransition::IdentityCreditTransferToAddresses(_)
3405        ));
3406    }
3407
3408    #[test]
3409    fn test_credit_transfer_to_addresses_user_fee_increase_setter() {
3410        let mut st = sample_credit_transfer_to_addresses_st();
3411        st.set_user_fee_increase(55);
3412        assert_eq!(st.user_fee_increase(), 55);
3413    }
3414
3415    // ---------- AddressCreditWithdrawal ----------
3416
3417    #[test]
3418    fn test_address_credit_withdrawal_name_type_and_accessors() {
3419        let st = sample_address_credit_withdrawal_st();
3420        assert_eq!(st.name(), "AddressCreditWithdrawal");
3421        assert_eq!(
3422            st.state_transition_type(),
3423            StateTransitionType::AddressCreditWithdrawal
3424        );
3425        // signature is None for AddressCreditWithdrawal (see mod.rs arm).
3426        assert!(st.signature().is_none());
3427        // owner_id is None for every address-* variant.
3428        assert!(st.owner_id().is_none());
3429        // inputs → Some (delegated to inner struct's inputs map, may be empty).
3430        assert!(st.inputs().is_some());
3431    }
3432
3433    #[test]
3434    fn test_address_credit_withdrawal_set_signature_returns_false() {
3435        let mut st = sample_address_credit_withdrawal_st();
3436        assert!(!st.set_signature(BinaryData::new(vec![0xAB; 65])));
3437    }
3438
3439    #[test]
3440    fn test_address_credit_withdrawal_is_identity_signed_true() {
3441        // Per mod.rs: `is_identity_signed` is !matches!(identity_create/topup/shield*/unshield/shielded*)
3442        // so address-* variants return true — even though signature() returns None.
3443        assert!(sample_address_credit_withdrawal_st().is_identity_signed());
3444    }
3445
3446    #[test]
3447    fn test_address_credit_withdrawal_active_range_is_11_latest() {
3448        let range = sample_address_credit_withdrawal_st().active_version_range();
3449        assert_eq!(*range.start(), 11);
3450        assert_eq!(*range.end(), LATEST_VERSION);
3451    }
3452
3453    // ---------- ShieldFromAssetLock ----------
3454
3455    #[test]
3456    fn test_shield_from_asset_lock_name_type_and_accessors() {
3457        let st = sample_shield_from_asset_lock_st();
3458        assert_eq!(st.name(), "ShieldFromAssetLock");
3459        assert_eq!(
3460            st.state_transition_type(),
3461            StateTransitionType::ShieldFromAssetLock
3462        );
3463        // signature IS present on ShieldFromAssetLock — Some arm.
3464        let sig = st
3465            .signature()
3466            .expect("shield-from-asset-lock has signature");
3467        assert_eq!(sig.as_slice(), &[0x55; 65]);
3468        // owner_id is always None for shielded-* arms.
3469        assert!(st.owner_id().is_none());
3470    }
3471
3472    #[test]
3473    fn test_shield_from_asset_lock_is_not_identity_signed() {
3474        assert!(!sample_shield_from_asset_lock_st().is_identity_signed());
3475    }
3476
3477    #[test]
3478    fn test_shield_from_asset_lock_optional_asset_lock_proof_some() {
3479        // Critical: this is one of the THREE arms where optional_asset_lock_proof
3480        // actually forwards to Some(_). Other Some arms are covered by
3481        // IdentityCreate and IdentityTopUp which have default asset lock proof.
3482        let st = sample_shield_from_asset_lock_st();
3483        assert!(st.optional_asset_lock_proof().is_some());
3484    }
3485
3486    #[test]
3487    fn test_shield_from_asset_lock_user_fee_increase_is_zero_and_setter_noop() {
3488        let mut st = sample_shield_from_asset_lock_st();
3489        assert_eq!(st.user_fee_increase(), 0);
3490        st.set_user_fee_increase(123);
3491        // Set is a no-op per mod.rs table.
3492        assert_eq!(st.user_fee_increase(), 0);
3493    }
3494
3495    #[test]
3496    fn test_shield_from_asset_lock_set_signature_returns_true() {
3497        let mut st = sample_shield_from_asset_lock_st();
3498        assert!(st.set_signature(BinaryData::new(vec![0x44; 65])));
3499        assert_eq!(st.signature().unwrap().as_slice(), &[0x44; 65]);
3500    }
3501
3502    #[test]
3503    fn test_shield_from_asset_lock_required_asset_lock_balance_succeeds() {
3504        // This is the only arm besides IdentityCreate/TopUp/AddressFundingFromAssetLock
3505        // that returns Ok from required_asset_lock_balance_for_processing_start.
3506        let st = sample_shield_from_asset_lock_st();
3507        let result = st.required_asset_lock_balance_for_processing_start(PlatformVersion::latest());
3508        assert!(
3509            result.is_ok(),
3510            "ShieldFromAssetLock should return Ok, got {:?}",
3511            result
3512        );
3513    }
3514
3515    #[test]
3516    fn test_shield_from_asset_lock_active_range_12_latest() {
3517        let range = sample_shield_from_asset_lock_st().active_version_range();
3518        assert_eq!(*range.start(), 12);
3519        assert_eq!(*range.end(), LATEST_VERSION);
3520    }
3521
3522    // ---------- Batch with Token transition exercises TokenTransfer arm
3523    //            in the name() nested match. ----------
3524
3525    #[test]
3526    fn test_batch_with_token_transfer_name_contains_token_transfer() {
3527        use crate::state_transition::batch_transition::batched_transition::token_transition::TokenTransition as TT;
3528        use crate::state_transition::batch_transition::batched_transition::BatchedTransition;
3529        use crate::state_transition::batch_transition::token_base_transition::v0::TokenBaseTransitionV0;
3530        use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition;
3531        use crate::state_transition::batch_transition::token_transfer_transition::v0::TokenTransferTransitionV0;
3532        use crate::state_transition::batch_transition::token_transfer_transition::TokenTransferTransition;
3533        use crate::state_transition::batch_transition::BatchTransitionV1;
3534
3535        let base = TokenBaseTransition::V0(TokenBaseTransitionV0 {
3536            identity_contract_nonce: 1,
3537            token_contract_position: 0,
3538            data_contract_id: Identifier::from([1u8; 32]),
3539            token_id: Identifier::from([2u8; 32]),
3540            using_group_info: None,
3541        });
3542        let token_transfer = TokenTransferTransition::V0(TokenTransferTransitionV0 {
3543            base,
3544            amount: 100,
3545            recipient_id: Identifier::from([3u8; 32]),
3546            public_note: None,
3547            shared_encrypted_note: None,
3548            private_encrypted_note: None,
3549        });
3550
3551        // BatchTransitionV1 is used for tokens. Build a single-token batch.
3552        let batch = BatchTransition::V1(BatchTransitionV1 {
3553            owner_id: Identifier::from([9u8; 32]),
3554            transitions: vec![BatchedTransition::Token(TT::Transfer(token_transfer))],
3555            user_fee_increase: 0,
3556            signature_public_key_id: 0,
3557            signature: BinaryData::new(vec![0u8; 65]),
3558        });
3559        let st = StateTransition::Batch(batch);
3560
3561        assert_eq!(st.name(), "DocumentsBatch([TokenTransfer])");
3562    }
3563
3564    // -----------------------------------------------------------------------
3565    // Cross-variant consistency: transaction_id is a 32-byte blake3/sha256 hash
3566    // of the serialized form. Make sure it's stable across clones for the newly
3567    // covered variants too.
3568    // -----------------------------------------------------------------------
3569
3570    #[test]
3571    fn test_transaction_id_length_32_for_new_variants() {
3572        for st in [
3573            sample_withdrawal_v1_st(),
3574            sample_credit_transfer_to_addresses_st(),
3575            sample_shield_from_asset_lock_st(),
3576        ] {
3577            let id = st.transaction_id().expect("hash");
3578            assert_eq!(id.len(), 32);
3579        }
3580    }
3581
3582    // -----------------------------------------------------------------------
3583    // Clone-and-equality coverage for newly added variants (PartialEq via
3584    // derived impl, exercises the top-level enum's PartialEq arms).
3585    // -----------------------------------------------------------------------
3586
3587    #[test]
3588    fn test_clone_eq_for_new_variants() {
3589        let cases = [
3590            sample_withdrawal_v1_st(),
3591            sample_credit_transfer_to_addresses_st(),
3592            sample_address_credit_withdrawal_st(),
3593            sample_shield_from_asset_lock_st(),
3594        ];
3595        for st in cases {
3596            let cloned = st.clone();
3597            assert_eq!(st, cloned, "clone must be equal for {}", st.name());
3598        }
3599    }
3600
3601    // -----------------------------------------------------------------------
3602    // unique_identifiers() for address-* variants: the implementation
3603    // dispatches via call_method! and each variant returns a non-empty Vec.
3604    // -----------------------------------------------------------------------
3605
3606    #[test]
3607    fn test_unique_identifiers_for_address_and_shielded_variants() {
3608        // The address variants compute identifiers from their `inputs` map.
3609        // With a default (empty inputs) transition, unique_identifiers is empty
3610        // — that's fine, but we still want to exercise the call_method!
3611        // dispatch for these arms without panicking.
3612        for st in [
3613            sample_address_credit_withdrawal_st(),
3614            sample_shield_from_asset_lock_st(),
3615            sample_credit_transfer_to_addresses_st(),
3616            sample_withdrawal_v1_st(),
3617        ] {
3618            // Just calling unique_identifiers exercises the match arm; the
3619            // result may be empty for default-constructed inputs-based
3620            // variants, non-empty for identity-based ones.
3621            let _ids = st.unique_identifiers();
3622        }
3623        // For the identity-based variants the result IS non-empty.
3624        assert!(!sample_withdrawal_v1_st().unique_identifiers().is_empty());
3625        assert!(!sample_credit_transfer_to_addresses_st()
3626            .unique_identifiers()
3627            .is_empty());
3628    }
3629
3630    // -----------------------------------------------------------------------
3631    // sign_with_core_signer byte-parity test
3632    //
3633    // Proves that `StateTransition::sign_with_core_signer` produces a
3634    // byte-identical signature to the legacy `sign_by_private_key` ECDSA path
3635    // when both are driven by the same underlying secret. This is the on-wire
3636    // contract the Swift / external-signer flow depends on: changing the
3637    // digest pre-image or the recoverable-compact encoding would silently
3638    // break asset-lock verification on testnet/mainnet, so we pin both shapes
3639    // here.
3640    // -----------------------------------------------------------------------
3641    #[cfg(all(
3642        feature = "state-transition-signing",
3643        feature = "core_key_wallet",
3644        feature = "bls-signatures"
3645    ))]
3646    #[tokio::test]
3647    async fn sign_with_core_signer_matches_sign_by_private_key_byte_for_byte() {
3648        use async_trait::async_trait;
3649        use dashcore::secp256k1::{
3650            ecdsa, rand::rngs::OsRng, Message, PublicKey, Secp256k1, SecretKey,
3651        };
3652        use key_wallet::bip32::{DerivationPath, ExtendedPubKey};
3653        use key_wallet::signer::{ExtendedPubKeySigner, Signer as KwSigner, SignerMethod};
3654
3655        /// Fixed-key in-memory signer used only by this test. Mirrors how a
3656        /// real KeychainSigner would behave: derive once, sign atomically,
3657        /// return non-recoverable `(Signature, PublicKey)`. The path is
3658        /// ignored — the wrapper holds exactly one key.
3659        #[derive(Debug)]
3660        struct FixedKeySigner {
3661            secret: SecretKey,
3662            public: PublicKey,
3663        }
3664
3665        #[async_trait]
3666        impl KwSigner for FixedKeySigner {
3667            type Error = String;
3668
3669            fn supported_methods(&self) -> &[SignerMethod] {
3670                &[SignerMethod::Digest]
3671            }
3672
3673            async fn sign_ecdsa(
3674                &self,
3675                _path: &DerivationPath,
3676                sighash: [u8; 32],
3677            ) -> Result<(ecdsa::Signature, PublicKey), Self::Error> {
3678                let secp = Secp256k1::new();
3679                let msg = Message::from_digest(sighash);
3680                let sig = secp.sign_ecdsa(&msg, &self.secret);
3681                Ok((sig, self.public))
3682            }
3683
3684            async fn public_key(&self, _path: &DerivationPath) -> Result<PublicKey, Self::Error> {
3685                Ok(self.public)
3686            }
3687        }
3688
3689        #[async_trait]
3690        impl ExtendedPubKeySigner for FixedKeySigner {
3691            async fn extended_public_key(
3692                &self,
3693                _path: &DerivationPath,
3694            ) -> Result<ExtendedPubKey, Self::Error> {
3695                Err("FixedKeySigner does not derive extended public keys".to_string())
3696            }
3697        }
3698
3699        // Generate a single random key. Using the same key on both sides is
3700        // load-bearing: the legacy path signs raw bytes, the signer path
3701        // derives + signs inside the trust boundary. If the digest pre-image
3702        // or compact-encoding differs, the bytes will diverge.
3703        let secp = Secp256k1::new();
3704        let (secret_key, public_key) = secp.generate_keypair(&mut OsRng);
3705        let private_key_bytes = secret_key.secret_bytes();
3706
3707        let signer = FixedKeySigner {
3708            secret: secret_key,
3709            public: public_key,
3710        };
3711        let path = DerivationPath::default();
3712
3713        // Use a sample state transition that exercises signable_bytes() —
3714        // any signable ST works since we're only comparing the signature
3715        // bytes the two paths produce over the SAME `signable_bytes()`.
3716        let mut st_legacy = sample_transfer_st();
3717        let mut st_signer = sample_transfer_st();
3718
3719        // Sanity: both copies must have identical signable_bytes before signing.
3720        assert_eq!(
3721            st_legacy.signable_bytes().expect("legacy signable_bytes"),
3722            st_signer.signable_bytes().expect("signer signable_bytes"),
3723            "signable_bytes pre-image must match across copies"
3724        );
3725
3726        // Legacy path: raw &[u8] private key → 65-byte recoverable compact.
3727        // BLS is only used by `sign_by_private_key` when key_type is BLS12_381 —
3728        // for the ECDSA path it's unused, but the function signature requires
3729        // it, so we pass the NativeBlsModule that's already in the workspace.
3730        let bls = crate::bls::native_bls::NativeBlsModule;
3731        st_legacy
3732            .sign_by_private_key(&private_key_bytes, KeyType::ECDSA_HASH160, &bls)
3733            .expect("sign_by_private_key");
3734
3735        // New signer-driven path: digest → external signer → recovered →
3736        // 65-byte recoverable compact. Byte-identical to the legacy result.
3737        st_signer
3738            .sign_with_core_signer(&path, &signer)
3739            .await
3740            .expect("sign_with_core_signer");
3741
3742        let sig_legacy = st_legacy.signature().expect("legacy signature set");
3743        let sig_signer = st_signer.signature().expect("signer signature set");
3744
3745        assert_eq!(
3746            sig_legacy.as_slice().len(),
3747            65,
3748            "legacy ECDSA signature must be 65 bytes (recoverable compact)"
3749        );
3750        assert_eq!(
3751            sig_signer.as_slice().len(),
3752            65,
3753            "signer ECDSA signature must be 65 bytes (recoverable compact)"
3754        );
3755        assert_eq!(
3756            sig_legacy.as_slice(),
3757            sig_signer.as_slice(),
3758            "sign_with_core_signer must produce byte-identical output to sign_by_private_key"
3759        );
3760    }
3761}