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