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, DecodeUntrusted, 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::{
20    PlatformDeserializeTrusted, PlatformDeserializeUntrusted, PlatformSerialize, PlatformSignable,
21};
22use platform_version::version::feature_initial_protocol_versions::YES_NO_VOTE_POLL_INITIAL_PROTOCOL_VERSION;
23use platform_version::version::{PlatformVersion, ProtocolVersion, ALL_VERSIONS, LATEST_VERSION};
24
25#[cfg(any(
26    feature = "state-transition-signing",
27    feature = "state-transition-validation"
28))]
29use crate::BlsModule;
30use crate::ProtocolError;
31
32mod state_transition_types;
33
34pub mod state_transition_factory;
35
36pub mod errors;
37#[cfg(feature = "state-transition-signing")]
38use crate::util::hash::ripemd160_sha256;
39use crate::util::hash::{hash_double_to_vec, hash_single};
40
41pub mod proof_result;
42mod serialization;
43pub mod state_transitions;
44mod traits;
45#[cfg(feature = "state-transition-validation")]
46mod verify_identity_signed_signature;
47
48// pub mod state_transition_fee;
49
50use crate::consensus::basic::state_transition::StateTransitionMaxSizeExceededError;
51#[cfg(feature = "state-transition-validation")]
52use crate::consensus::basic::UnsupportedFeatureError;
53#[cfg(feature = "state-transition-signing")]
54use crate::consensus::signature::InvalidSignaturePublicKeySecurityLevelError;
55#[cfg(feature = "state-transition-signing")]
56use crate::consensus::signature::{
57    ContractBoundedKeyNonBatchError, ContractBoundedKeyOutOfBoundsError,
58};
59#[cfg(feature = "state-transition-validation")]
60use crate::consensus::signature::{InvalidStateTransitionSignatureError, SignatureError};
61#[cfg(feature = "state-transition-validation")]
62use crate::consensus::ConsensusError;
63pub use traits::*;
64
65use crate::address_funds::PlatformAddress;
66use crate::data_contract::config::DataContractConfig;
67use crate::data_contract::serialized_version::DataContractInSerializationFormat;
68use crate::fee::Credits;
69#[cfg(feature = "state-transition-signing")]
70use crate::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0;
71#[cfg(feature = "state-transition-signing")]
72use crate::identity::identity_public_key::contract_bounds::BatchedTransitionBoundsCheck;
73#[cfg(feature = "state-transition-signing")]
74use crate::identity::signer::Signer;
75use crate::identity::state_transition::OptionallyAssetLockProved;
76use crate::identity::Purpose;
77#[cfg(feature = "state-transition-signing")]
78use crate::identity::{IdentityPublicKey, KeyType};
79use crate::identity::{KeyID, SecurityLevel};
80use crate::prelude::{AddressNonce, AssetLockProof, UserFeeIncrease};
81#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
82use crate::serialization::JsonConvertible;
83#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
84use crate::serialization::ValueConvertible;
85use crate::serialization::{PlatformDeserializableUntrusted, Signable};
86use crate::state_transition::address_credit_withdrawal_transition::{
87    AddressCreditWithdrawalTransition, AddressCreditWithdrawalTransitionSignable,
88};
89use crate::state_transition::address_funding_from_asset_lock_transition::{
90    AddressFundingFromAssetLockTransition, AddressFundingFromAssetLockTransitionSignable,
91};
92use crate::state_transition::address_funds_transfer_transition::{
93    AddressFundsTransferTransition, AddressFundsTransferTransitionSignable,
94};
95use crate::state_transition::batch_transition::accessors::DocumentsBatchTransitionAccessorsV0;
96use crate::state_transition::batch_transition::batched_transition::document_transition::DocumentTransitionV0Methods;
97use crate::state_transition::batch_transition::batched_transition::BatchedTransitionRef;
98#[cfg(feature = "state-transition-signing")]
99use crate::state_transition::batch_transition::resolvers::v0::BatchTransitionResolversV0;
100use crate::state_transition::batch_transition::{BatchTransition, BatchTransitionSignable};
101use crate::state_transition::contract_fee_claim_transition::{
102    ContractFeeClaimTransition, ContractFeeClaimTransitionSignable,
103};
104use crate::state_transition::contract_user_moderation_transition::{
105    ContractUserModerationTransition, ContractUserModerationTransitionSignable,
106};
107use crate::state_transition::data_contract_create_transition::accessors::DataContractCreateTransitionAccessorsV0;
108use crate::state_transition::data_contract_create_transition::{
109    DataContractCreateTransition, DataContractCreateTransitionSignable,
110};
111use crate::state_transition::data_contract_update_transition::accessors::DataContractUpdateTransitionAccessorsV0;
112use crate::state_transition::data_contract_update_transition::{
113    DataContractUpdateTransition, DataContractUpdateTransitionSignable,
114};
115#[cfg(feature = "state-transition-signing")]
116use crate::state_transition::errors::InvalidIdentityPublicKeyTypeError;
117#[cfg(feature = "state-transition-signing")]
118use crate::state_transition::errors::InvalidSignaturePublicKeyError;
119#[cfg(all(feature = "state-transitions", feature = "validation"))]
120use crate::state_transition::errors::StateTransitionError::StateTransitionIsNotActiveError;
121#[cfg(feature = "state-transition-validation")]
122use crate::state_transition::errors::StateTransitionIsNotSignedError;
123#[cfg(feature = "state-transition-signing")]
124use crate::state_transition::errors::WrongPublicKeyPurposeError;
125use crate::state_transition::identity_create_from_addresses_transition::accessors::IdentityCreateFromAddressesTransitionAccessorsV0;
126use crate::state_transition::identity_create_from_addresses_transition::{
127    IdentityCreateFromAddressesTransition, IdentityCreateFromAddressesTransitionSignable,
128};
129use crate::state_transition::identity_create_from_shielded_pool_transition::accessors::IdentityCreateFromShieldedPoolTransitionAccessorsV0;
130use crate::state_transition::identity_create_from_shielded_pool_transition::{
131    IdentityCreateFromShieldedPoolTransition, IdentityCreateFromShieldedPoolTransitionSignable,
132};
133use crate::state_transition::identity_create_transition::accessors::IdentityCreateTransitionAccessorsV0;
134use crate::state_transition::identity_create_transition::{
135    IdentityCreateTransition, IdentityCreateTransitionSignable,
136};
137use crate::state_transition::identity_credit_transfer_to_addresses_transition::{
138    IdentityCreditTransferToAddressesTransition,
139    IdentityCreditTransferToAddressesTransitionSignable,
140};
141use crate::state_transition::identity_credit_transfer_transition::{
142    IdentityCreditTransferTransition, IdentityCreditTransferTransitionSignable,
143};
144use crate::state_transition::identity_credit_withdrawal_transition::{
145    IdentityCreditWithdrawalTransition, IdentityCreditWithdrawalTransitionSignable,
146};
147use crate::state_transition::identity_key_limits_update_transition::{
148    IdentityKeyLimitsUpdateTransition, IdentityKeyLimitsUpdateTransitionSignable,
149};
150use crate::state_transition::identity_top_up_from_shielded_pool_transition::{
151    IdentityTopUpFromShieldedPoolTransition, IdentityTopUpFromShieldedPoolTransitionSignable,
152};
153use crate::state_transition::identity_topup_from_addresses_transition::{
154    IdentityTopUpFromAddressesTransition, IdentityTopUpFromAddressesTransitionSignable,
155};
156use crate::state_transition::identity_topup_transition::{
157    IdentityTopUpTransition, IdentityTopUpTransitionSignable,
158};
159use crate::state_transition::identity_update_transition::accessors::IdentityUpdateTransitionAccessorsV0;
160use crate::state_transition::identity_update_transition::{
161    IdentityUpdateTransition, IdentityUpdateTransitionSignable,
162};
163use crate::state_transition::masternode_vote_transition::accessors::MasternodeVoteTransitionAccessorsV0;
164use crate::state_transition::masternode_vote_transition::MasternodeVoteTransition;
165use crate::state_transition::masternode_vote_transition::MasternodeVoteTransitionSignable;
166use crate::state_transition::public_key_in_creation::IdentityPublicKeyInCreation;
167use crate::state_transition::shield_from_asset_lock_transition::{
168    ShieldFromAssetLockTransition, ShieldFromAssetLockTransitionSignable,
169};
170use crate::state_transition::shield_from_identity_transition::{
171    ShieldFromIdentityTransition, ShieldFromIdentityTransitionSignable,
172};
173use crate::state_transition::shield_transition::{ShieldTransition, ShieldTransitionSignable};
174use crate::state_transition::shielded_transfer_transition::{
175    ShieldedTransferTransition, ShieldedTransferTransitionSignable,
176};
177use crate::state_transition::shielded_withdrawal_transition::{
178    ShieldedWithdrawalTransition, ShieldedWithdrawalTransitionSignable,
179};
180#[cfg(feature = "state-transition-signing")]
181use crate::state_transition::state_transitions::document::batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0;
182use crate::state_transition::token_purchase_from_shielded_pool_transition::{
183    TokenPurchaseFromShieldedPoolTransition, TokenPurchaseFromShieldedPoolTransitionSignable,
184};
185use crate::state_transition::token_shielded_transfer_with_shielded_fee_transition::{
186    TokenShieldedTransferWithShieldedFeeTransition,
187    TokenShieldedTransferWithShieldedFeeTransitionSignable,
188};
189use crate::state_transition::token_unshield_with_shielded_fee_transition::{
190    TokenUnshieldWithShieldedFeeTransition, TokenUnshieldWithShieldedFeeTransitionSignable,
191};
192use crate::state_transition::unshield_transition::{
193    UnshieldTransition, UnshieldTransitionSignable,
194};
195use crate::voting::vote_polls::VotePoll;
196use crate::voting::votes::resource_vote::accessors::v0::ResourceVoteGettersV0;
197use crate::voting::votes::Vote;
198use state_transitions::document::batch_transition::batched_transition::token_transition::TokenTransition;
199pub use state_transitions::*;
200
201pub type GetDataContractSecurityLevelRequirementFn =
202    fn(Identifier, String) -> Result<SecurityLevel, ProtocolError>;
203
204macro_rules! call_method {
205    ($state_transition:expr, $method:ident, $args:tt ) => {
206        match $state_transition {
207            StateTransition::DataContractCreate(st) => st.$method($args),
208            StateTransition::DataContractUpdate(st) => st.$method($args),
209            StateTransition::ContractUserModeration(st) => st.$method($args),
210            StateTransition::ContractFeeClaim(st) => st.$method($args),
211            StateTransition::Batch(st) => st.$method($args),
212            StateTransition::IdentityCreate(st) => st.$method($args),
213            StateTransition::IdentityTopUp(st) => st.$method($args),
214            StateTransition::IdentityCreditWithdrawal(st) => st.$method($args),
215            StateTransition::IdentityUpdate(st) => st.$method($args),
216            StateTransition::IdentityKeyLimitsUpdate(st) => st.$method($args),
217            StateTransition::IdentityCreditTransfer(st) => st.$method($args),
218            StateTransition::MasternodeVote(st) => st.$method($args),
219            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method($args),
220            StateTransition::IdentityCreateFromAddresses(st) => st.$method($args),
221            StateTransition::IdentityTopUpFromAddresses(st) => st.$method($args),
222            StateTransition::AddressFundsTransfer(st) => st.$method($args),
223            StateTransition::AddressFundingFromAssetLock(st) => st.$method($args),
224            StateTransition::AddressCreditWithdrawal(st) => st.$method($args),
225            StateTransition::Shield(st) => st.$method($args),
226            StateTransition::ShieldedTransfer(st) => st.$method($args),
227            StateTransition::Unshield(st) => st.$method($args),
228            StateTransition::IdentityTopUpFromShieldedPool(st) => st.$method($args),
229            StateTransition::TokenShieldedTransferWithShieldedFee(st) => st.$method($args),
230            StateTransition::TokenUnshieldWithShieldedFee(st) => st.$method($args),
231            StateTransition::TokenPurchaseFromShieldedPool(st) => st.$method($args),
232            StateTransition::ShieldFromAssetLock(st) => st.$method($args),
233            StateTransition::ShieldedWithdrawal(st) => st.$method($args),
234            StateTransition::IdentityCreateFromShieldedPool(st) => st.$method($args),
235            StateTransition::ShieldFromIdentity(st) => st.$method($args),
236        }
237    };
238    ($state_transition:expr, $method:ident ) => {
239        match $state_transition {
240            StateTransition::DataContractCreate(st) => st.$method(),
241            StateTransition::DataContractUpdate(st) => st.$method(),
242            StateTransition::ContractUserModeration(st) => st.$method(),
243            StateTransition::ContractFeeClaim(st) => st.$method(),
244            StateTransition::Batch(st) => st.$method(),
245            StateTransition::IdentityCreate(st) => st.$method(),
246            StateTransition::IdentityTopUp(st) => st.$method(),
247            StateTransition::IdentityCreditWithdrawal(st) => st.$method(),
248            StateTransition::IdentityUpdate(st) => st.$method(),
249            StateTransition::IdentityKeyLimitsUpdate(st) => st.$method(),
250            StateTransition::IdentityCreditTransfer(st) => st.$method(),
251            StateTransition::MasternodeVote(st) => st.$method(),
252            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method(),
253            StateTransition::IdentityCreateFromAddresses(st) => st.$method(),
254            StateTransition::IdentityTopUpFromAddresses(st) => st.$method(),
255            StateTransition::AddressFundsTransfer(st) => st.$method(),
256            StateTransition::AddressFundingFromAssetLock(st) => st.$method(),
257            StateTransition::AddressCreditWithdrawal(st) => st.$method(),
258            StateTransition::Shield(st) => st.$method(),
259            StateTransition::ShieldedTransfer(st) => st.$method(),
260            StateTransition::Unshield(st) => st.$method(),
261            StateTransition::IdentityTopUpFromShieldedPool(st) => st.$method(),
262            StateTransition::TokenShieldedTransferWithShieldedFee(st) => st.$method(),
263            StateTransition::TokenUnshieldWithShieldedFee(st) => st.$method(),
264            StateTransition::TokenPurchaseFromShieldedPool(st) => st.$method(),
265            StateTransition::ShieldFromAssetLock(st) => st.$method(),
266            StateTransition::ShieldedWithdrawal(st) => st.$method(),
267            StateTransition::IdentityCreateFromShieldedPool(st) => st.$method(),
268            StateTransition::ShieldFromIdentity(st) => st.$method(),
269        }
270    };
271}
272
273macro_rules! call_getter_method_identity_signed {
274    ($state_transition:expr, $method:ident, $args:tt ) => {
275        match $state_transition {
276            StateTransition::DataContractCreate(st) => Some(st.$method($args)),
277            StateTransition::DataContractUpdate(st) => Some(st.$method($args)),
278            StateTransition::ContractUserModeration(st) => Some(st.$method($args)),
279            StateTransition::ContractFeeClaim(st) => Some(st.$method($args)),
280            StateTransition::Batch(st) => Some(st.$method($args)),
281            StateTransition::IdentityCreate(_) => None,
282            StateTransition::IdentityTopUp(_) => None,
283            StateTransition::IdentityCreditWithdrawal(st) => Some(st.$method($args)),
284            StateTransition::IdentityUpdate(st) => Some(st.$method($args)),
285            StateTransition::IdentityKeyLimitsUpdate(st) => Some(st.$method($args)),
286            StateTransition::IdentityCreditTransfer(st) => Some(st.$method($args)),
287            StateTransition::MasternodeVote(st) => Some(st.$method($args)),
288            StateTransition::IdentityCreditTransferToAddresses(st) => Some(st.$method($args)),
289            StateTransition::IdentityCreateFromAddresses(_) => None,
290            StateTransition::IdentityTopUpFromAddresses(_) => None,
291            StateTransition::AddressFundsTransfer(_) => None,
292            StateTransition::AddressFundingFromAssetLock(_) => None,
293            StateTransition::AddressCreditWithdrawal(_) => None,
294            StateTransition::Shield(_) => None,
295            StateTransition::ShieldedTransfer(_) => None,
296            StateTransition::Unshield(_) => None,
297            StateTransition::IdentityTopUpFromShieldedPool(_) => None,
298            StateTransition::TokenShieldedTransferWithShieldedFee(_) => None,
299            StateTransition::TokenUnshieldWithShieldedFee(_) => None,
300            StateTransition::TokenPurchaseFromShieldedPool(_) => None,
301            StateTransition::ShieldFromAssetLock(_) => None,
302            StateTransition::ShieldedWithdrawal(_) => None,
303            StateTransition::IdentityCreateFromShieldedPool(_) => None,
304            StateTransition::ShieldFromIdentity(st) => Some(st.$method($args)),
305        }
306    };
307    ($state_transition:expr, $method:ident ) => {
308        match $state_transition {
309            StateTransition::DataContractCreate(st) => Some(st.$method()),
310            StateTransition::DataContractUpdate(st) => Some(st.$method()),
311            StateTransition::ContractUserModeration(st) => Some(st.$method()),
312            StateTransition::ContractFeeClaim(st) => Some(st.$method()),
313            StateTransition::Batch(st) => Some(st.$method()),
314            StateTransition::IdentityCreate(_) => None,
315            StateTransition::IdentityTopUp(_) => None,
316            StateTransition::IdentityCreditWithdrawal(st) => Some(st.$method()),
317            StateTransition::IdentityUpdate(st) => Some(st.$method()),
318            StateTransition::IdentityKeyLimitsUpdate(st) => Some(st.$method()),
319            StateTransition::IdentityCreditTransfer(st) => Some(st.$method()),
320            StateTransition::MasternodeVote(st) => Some(st.$method()),
321            StateTransition::IdentityCreditTransferToAddresses(st) => Some(st.$method()),
322            StateTransition::IdentityCreateFromAddresses(_) => None,
323            StateTransition::IdentityTopUpFromAddresses(_) => None,
324            StateTransition::AddressFundsTransfer(_) => None,
325            StateTransition::AddressFundingFromAssetLock(_) => None,
326            StateTransition::AddressCreditWithdrawal(_) => None,
327            StateTransition::Shield(_) => None,
328            StateTransition::ShieldedTransfer(_) => None,
329            StateTransition::Unshield(_) => None,
330            StateTransition::IdentityTopUpFromShieldedPool(_) => None,
331            StateTransition::TokenShieldedTransferWithShieldedFee(_) => None,
332            StateTransition::TokenUnshieldWithShieldedFee(_) => None,
333            StateTransition::TokenPurchaseFromShieldedPool(_) => None,
334            StateTransition::ShieldFromAssetLock(_) => None,
335            StateTransition::ShieldedWithdrawal(_) => None,
336            StateTransition::IdentityCreateFromShieldedPool(_) => None,
337            StateTransition::ShieldFromIdentity(st) => Some(st.$method()),
338        }
339    };
340}
341
342macro_rules! call_method_identity_signed {
343    ($state_transition:expr, $method:ident, $args:tt ) => {
344        match $state_transition {
345            StateTransition::DataContractCreate(st) => st.$method($args),
346            StateTransition::DataContractUpdate(st) => st.$method($args),
347            StateTransition::ContractUserModeration(st) => st.$method($args),
348            StateTransition::ContractFeeClaim(st) => st.$method($args),
349            StateTransition::Batch(st) => st.$method($args),
350            StateTransition::IdentityCreate(_st) => {}
351            StateTransition::IdentityTopUp(_st) => {}
352            StateTransition::IdentityCreditWithdrawal(st) => st.$method($args),
353            StateTransition::IdentityUpdate(st) => st.$method($args),
354            StateTransition::IdentityKeyLimitsUpdate(st) => st.$method($args),
355            StateTransition::IdentityCreditTransfer(st) => st.$method($args),
356            StateTransition::MasternodeVote(st) => st.$method($args),
357            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method($args),
358            StateTransition::IdentityCreateFromAddresses(_) => {}
359            StateTransition::IdentityTopUpFromAddresses(_) => {}
360            StateTransition::AddressFundsTransfer(_) => {}
361            StateTransition::AddressFundingFromAssetLock(_) => {}
362            StateTransition::AddressCreditWithdrawal(_) => {}
363            StateTransition::Shield(_) => {}
364            StateTransition::ShieldedTransfer(_) => {}
365            StateTransition::Unshield(_) => {}
366            StateTransition::IdentityTopUpFromShieldedPool(_) => {}
367            StateTransition::TokenShieldedTransferWithShieldedFee(_) => {}
368            StateTransition::TokenUnshieldWithShieldedFee(_) => {}
369            StateTransition::TokenPurchaseFromShieldedPool(_) => {}
370            StateTransition::ShieldFromAssetLock(_) => {}
371            StateTransition::ShieldedWithdrawal(_) => {}
372            StateTransition::IdentityCreateFromShieldedPool(_) => {}
373            StateTransition::ShieldFromIdentity(st) => st.$method($args),
374        }
375    };
376    ($state_transition:expr, $method:ident ) => {
377        match $state_transition {
378            StateTransition::DataContractCreate(st) => st.$method(),
379            StateTransition::DataContractUpdate(st) => st.$method(),
380            StateTransition::ContractUserModeration(st) => st.$method(),
381            StateTransition::ContractFeeClaim(st) => st.$method(),
382            StateTransition::Batch(st) => st.$method(),
383            StateTransition::IdentityCreate(st) => {}
384            StateTransition::IdentityTopUp(st) => {}
385            StateTransition::IdentityCreditWithdrawal(st) => st.$method(),
386            StateTransition::IdentityUpdate(st) => st.$method(),
387            StateTransition::IdentityKeyLimitsUpdate(st) => st.$method(),
388            StateTransition::IdentityCreditTransfer(st) => st.$method(),
389            StateTransition::MasternodeVote(st) => st.$method(),
390            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method(),
391            StateTransition::IdentityCreateFromAddresses(_) => {}
392            StateTransition::IdentityTopUpFromAddresses(_) => {}
393            StateTransition::AddressFundsTransfer(_) => {}
394            StateTransition::AddressFundingFromAssetLock(_) => {}
395            StateTransition::AddressCreditWithdrawal(_) => {}
396            StateTransition::Shield(_) => {}
397            StateTransition::ShieldedTransfer(_) => {}
398            StateTransition::Unshield(_) => {}
399            StateTransition::IdentityTopUpFromShieldedPool(_) => {}
400            StateTransition::TokenShieldedTransferWithShieldedFee(_) => {}
401            StateTransition::TokenUnshieldWithShieldedFee(_) => {}
402            StateTransition::TokenPurchaseFromShieldedPool(_) => {}
403            StateTransition::ShieldFromAssetLock(_) => {}
404            StateTransition::ShieldedWithdrawal(_) => {}
405            StateTransition::IdentityCreateFromShieldedPool(_) => {}
406            StateTransition::ShieldFromIdentity(st) => st.$method(),
407        }
408    };
409}
410
411#[cfg(feature = "state-transition-signing")]
412macro_rules! call_errorable_method_identity_signed {
413    ($state_transition:expr, $method:ident, $( $arg:expr ),* ) => {
414        match $state_transition {
415            StateTransition::DataContractCreate(st) => st.$method($( $arg ),*),
416            StateTransition::DataContractUpdate(st) => st.$method($( $arg ),*),
417            StateTransition::ContractUserModeration(st) => st.$method($( $arg ),*),
418            StateTransition::ContractFeeClaim(st) => st.$method($( $arg ),*),
419            StateTransition::Batch(st) => st.$method($( $arg ),*),
420            StateTransition::IdentityCreate(_) => Err(ProtocolError::CorruptedCodeExecution(
421                "identity create can not be called for identity signing".to_string(),
422            )),
423            StateTransition::IdentityTopUp(_) => Err(ProtocolError::CorruptedCodeExecution(
424                "identity top up can not be called for identity signing".to_string(),
425            )),
426            StateTransition::IdentityCreditWithdrawal(st) => st.$method($( $arg ),*),
427            StateTransition::IdentityUpdate(st) => st.$method($( $arg ),*),
428            StateTransition::IdentityKeyLimitsUpdate(st) => st.$method($( $arg ),*),
429            StateTransition::IdentityCreditTransfer(st) => st.$method($( $arg ),*),
430            StateTransition::MasternodeVote(st) => st.$method($( $arg ),*),
431            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method($( $arg ),*),
432            StateTransition::IdentityCreateFromAddresses(_) => Err(ProtocolError::CorruptedCodeExecution(
433                "identity create from addresses can not be called for identity signing".to_string(),
434            )),
435            StateTransition::IdentityTopUpFromAddresses(_) => Err(ProtocolError::CorruptedCodeExecution(
436                "identity top up from addresses can not be called for identity signing".to_string(),
437            )),
438            StateTransition::AddressFundsTransfer(_) => Err(ProtocolError::CorruptedCodeExecution(
439                "address funds transfer can not be called for identity signing".to_string(),
440            )),
441            StateTransition::AddressFundingFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution(
442                "address funding from asset lock can not be called for identity signing".to_string(),
443            )),
444            StateTransition::AddressCreditWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution(
445                "address credit withdrawal can not be called for identity signing".to_string(),
446            )),
447            StateTransition::Shield(_) => Err(ProtocolError::CorruptedCodeExecution(
448                "shield transition can not be called for identity signing".to_string(),
449            )),
450            StateTransition::ShieldedTransfer(_) => Err(ProtocolError::CorruptedCodeExecution(
451                "shielded transfer transition can not be called for identity signing".to_string(),
452            )),
453            StateTransition::Unshield(_) => Err(ProtocolError::CorruptedCodeExecution(
454                "unshield transition can not be called for identity signing".to_string(),
455            )),
456            StateTransition::IdentityTopUpFromShieldedPool(_) => Err(ProtocolError::CorruptedCodeExecution(
457                "identity top up from shielded pool transition can not be called for identity signing".to_string(),
458            )),
459            StateTransition::TokenShieldedTransferWithShieldedFee(_) => Err(ProtocolError::CorruptedCodeExecution(
460                "token shielded transfer with shielded fee transition can not be called for identity signing".to_string(),
461            )),
462            StateTransition::TokenUnshieldWithShieldedFee(_) => Err(ProtocolError::CorruptedCodeExecution(
463                "token unshield with shielded fee transition can not be called for identity signing".to_string(),
464            )),
465            StateTransition::TokenPurchaseFromShieldedPool(_) => Err(ProtocolError::CorruptedCodeExecution(
466                "token purchase from shielded pool transition can not be called for identity signing".to_string(),
467            )),
468            StateTransition::ShieldFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution(
469                "shield from asset lock transition can not be called for identity signing".to_string(),
470            )),
471            StateTransition::ShieldedWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution(
472                "shielded withdrawal transition can not be called for identity signing".to_string(),
473            )),
474            StateTransition::IdentityCreateFromShieldedPool(_) => Err(ProtocolError::CorruptedCodeExecution(
475                "identity create from shielded pool transition can not be called for identity signing".to_string(),
476            )),
477            StateTransition::ShieldFromIdentity(st) => st.$method($( $arg ),*),
478        }
479    };
480    ($state_transition:expr, $method:ident) => {
481        match $state_transition {
482            StateTransition::DataContractCreate(st) => st.$method(),
483            StateTransition::DataContractUpdate(st) => st.$method(),
484            StateTransition::ContractUserModeration(st) => st.$method(),
485            StateTransition::ContractFeeClaim(st) => st.$method(),
486            StateTransition::Batch(st) => st.$method(),
487            StateTransition::IdentityCreate(_) => Err(ProtocolError::CorruptedCodeExecution(
488                "identity create can not be called for identity signing".to_string(),
489            )),
490            StateTransition::IdentityTopUp(_) => Err(ProtocolError::CorruptedCodeExecution(
491                "identity top up can not be called for identity signing".to_string(),
492            )),
493            StateTransition::IdentityCreditWithdrawal(st) => st.$method(),
494            StateTransition::IdentityUpdate(st) => st.$method(),
495            StateTransition::IdentityKeyLimitsUpdate(st) => st.$method(),
496            StateTransition::IdentityCreditTransfer(st) => st.$method(),
497            StateTransition::MasternodeVote(st) => st.$method(),
498            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method(),
499            StateTransition::IdentityCreateFromAddresses(st) => Err(ProtocolError::CorruptedCodeExecution(
500                "identity create from addresses can not be called for identity signing".to_string(),
501            )),
502            StateTransition::IdentityTopUpFromAddresses(_) => Err(ProtocolError::CorruptedCodeExecution(
503                "identity top up from addresses can not be called for identity signing".to_string(),
504            )),
505            StateTransition::AddressFundsTransfer(_) => Err(ProtocolError::CorruptedCodeExecution(
506                "address funds transfer can not be called for identity signing".to_string(),
507            )),
508            StateTransition::AddressFundingFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution(
509                "address funding from asset lock can not be called for identity signing".to_string(),
510            )),
511            StateTransition::AddressCreditWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution(
512                "address credit withdrawal can not be called for identity signing".to_string(),
513            )),
514            StateTransition::Shield(_) => Err(ProtocolError::CorruptedCodeExecution(
515                "shield transition can not be called for identity signing".to_string(),
516            )),
517            StateTransition::ShieldedTransfer(_) => Err(ProtocolError::CorruptedCodeExecution(
518                "shielded transfer transition can not be called for identity signing".to_string(),
519            )),
520            StateTransition::Unshield(_) => Err(ProtocolError::CorruptedCodeExecution(
521                "unshield transition can not be called for identity signing".to_string(),
522            )),
523            StateTransition::IdentityTopUpFromShieldedPool(_) => Err(ProtocolError::CorruptedCodeExecution(
524                "identity top up from shielded pool transition can not be called for identity signing".to_string(),
525            )),
526            StateTransition::TokenShieldedTransferWithShieldedFee(_) => Err(ProtocolError::CorruptedCodeExecution(
527                "token shielded transfer with shielded fee transition can not be called for identity signing".to_string(),
528            )),
529            StateTransition::TokenUnshieldWithShieldedFee(_) => Err(ProtocolError::CorruptedCodeExecution(
530                "token unshield with shielded fee transition can not be called for identity signing".to_string(),
531            )),
532            StateTransition::TokenPurchaseFromShieldedPool(_) => Err(ProtocolError::CorruptedCodeExecution(
533                "token purchase from shielded pool transition can not be called for identity signing".to_string(),
534            )),
535            StateTransition::ShieldFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution(
536                "shield from asset lock transition can not be called for identity signing".to_string(),
537            )),
538            StateTransition::ShieldedWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution(
539                "shielded withdrawal transition can not be called for identity signing".to_string(),
540            )),
541            StateTransition::IdentityCreateFromShieldedPool(_) => Err(ProtocolError::CorruptedCodeExecution(
542                "identity create from shielded pool transition can not be called for identity signing".to_string(),
543            )),
544            StateTransition::ShieldFromIdentity(st) => st.$method(),
545        }
546    };
547}
548
549/// Byte budget of a serialized [`StateTransition`], as the `limit` of its `platform_serialize`
550/// attribute below declares it; the attribute takes a literal, so the number is repeated here.
551pub const STATE_TRANSITION_MAX_ENCODED_BYTES: usize = 100_000;
552
553/// Checks the serialized length of a state transition against the protocol's
554/// `max_state_transition_size`: a transition of `size_bytes` is refused when
555/// it is larger (the limit itself passes), with the error Drive reports for
556/// one. Drive's decoders keep their own copy of this check; the drive-abci
557/// test `should_agree_with_the_client_size_check_at_every_protocol_version`
558/// keeps the two in step.
559///
560/// DAPI refuses an oversized transition before Drive sees it with an untyped
561/// `InvalidArgument`, so clients that need the typed error (an asset-lock
562/// flow switching from a growing InstantSend proof to a fixed-size ChainLock
563/// proof) check it with this before sending.
564pub fn validate_state_transition_size(
565    size_bytes: u64,
566    platform_version: &PlatformVersion,
567) -> Result<(), StateTransitionMaxSizeExceededError> {
568    let max_size_bytes = platform_version.system_limits.max_state_transition_size;
569    if size_bytes > max_size_bytes {
570        return Err(StateTransitionMaxSizeExceededError::new(
571            size_bytes,
572            max_size_bytes,
573        ));
574    }
575    Ok(())
576}
577
578#[derive(
579    Debug,
580    Clone,
581    Encode,
582    Decode,
583    PlatformSerialize,
584    PlatformDeserializeTrusted,
585    PlatformDeserializeUntrusted,
586    PlatformSignable,
587    From,
588    PartialEq,
589    DecodeUntrusted,
590)]
591// `tag = "$type"` matches the system-field convention: every serde-injected
592// discriminator key in this crate carries a `$` prefix so it never collides
593// with user-data field names. Discriminates between **semantically
594// different variants** of the same kind (rather than **versions** of one
595// logical type, which use `tag = "$formatVersion"`).
596//
597// `$type` here is at the OUTERMOST level — there's no flatten path that
598// would put it next to a base's `document_type_name` (renamed to `$type`
599// in the wire). Inner umbrellas (`DocumentTransition`, `TokenTransition`)
600// use `$action` instead because they DO flatten the document base.
601//
602// Was previously `serde(untagged)`, which made deserialize ambiguous (each
603// variant tried in order until one matched structurally). The new
604// self-describing wire shape is `{"$type": "dataContractCreate", ...inner
605// fields...}`.
606//
607// The binary wire path (`PlatformSerialize`) is unchanged — only JSON/Value
608// consumers see the new shape, and there are no rs-drive / rs-drive-abci /
609// rs-sdk callers that route the umbrella through to_json/to_object today.
610#[cfg_attr(
611    feature = "serde-conversion",
612    derive(Serialize, Deserialize),
613    serde(tag = "$type", rename_all = "camelCase")
614)]
615#[platform_serialize(unversioned)] //versioned directly, no need to use platform_version
616#[platform_serialize(limit = 100000)]
617pub enum StateTransition {
618    DataContractCreate(DataContractCreateTransition),
619    DataContractUpdate(DataContractUpdateTransition),
620    Batch(BatchTransition),
621    IdentityCreate(IdentityCreateTransition),
622    IdentityTopUp(IdentityTopUpTransition),
623    IdentityCreditWithdrawal(IdentityCreditWithdrawalTransition),
624    IdentityUpdate(IdentityUpdateTransition),
625    IdentityCreditTransfer(IdentityCreditTransferTransition),
626    MasternodeVote(MasternodeVoteTransition),
627    IdentityCreditTransferToAddresses(IdentityCreditTransferToAddressesTransition),
628    IdentityCreateFromAddresses(IdentityCreateFromAddressesTransition),
629    IdentityTopUpFromAddresses(IdentityTopUpFromAddressesTransition),
630    AddressFundsTransfer(AddressFundsTransferTransition),
631    AddressFundingFromAssetLock(AddressFundingFromAssetLockTransition),
632    AddressCreditWithdrawal(AddressCreditWithdrawalTransition),
633    Shield(ShieldTransition),
634    ShieldedTransfer(ShieldedTransferTransition),
635    Unshield(UnshieldTransition),
636    ShieldFromAssetLock(ShieldFromAssetLockTransition),
637    ShieldedWithdrawal(ShieldedWithdrawalTransition),
638    IdentityCreateFromShieldedPool(IdentityCreateFromShieldedPoolTransition),
639    ShieldFromIdentity(ShieldFromIdentityTransition),
640    IdentityTopUpFromShieldedPool(IdentityTopUpFromShieldedPoolTransition),
641    IdentityKeyLimitsUpdate(IdentityKeyLimitsUpdateTransition),
642    ContractUserModeration(ContractUserModerationTransition),
643    ContractFeeClaim(ContractFeeClaimTransition),
644    TokenShieldedTransferWithShieldedFee(TokenShieldedTransferWithShieldedFeeTransition),
645    TokenUnshieldWithShieldedFee(TokenUnshieldWithShieldedFeeTransition),
646    TokenPurchaseFromShieldedPool(TokenPurchaseFromShieldedPoolTransition),
647}
648
649#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
650impl JsonConvertible for StateTransition {}
651
652#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
653impl ValueConvertible for StateTransition {}
654
655#[cfg(all(
656    test,
657    feature = "json-conversion",
658    feature = "value-conversion",
659    feature = "serde-conversion"
660))]
661mod json_convertible_tests {
662    use super::*;
663
664    /// Round-trip a StateTransition through both JSON and Value, asserting:
665    /// 1. The wire emits `{"$type": "<expected_tag>", ...}` (umbrella's
666    ///    `tag = "$type", rename_all = "camelCase"` is correctly applied).
667    /// 2. Round-trip preserves the variant.
668    /// 3. Round-trip preserves structural equality (PartialEq on the inner).
669    ///
670    /// Inner field shapes are covered by each inner type's dedicated
671    /// `*_with_full_wire_shape` test — this helper only exercises the
672    /// umbrella's tag-dispatch boundary. The risk it catches: an inner
673    /// variant whose serde body conflicts with the umbrella's `"$type"` key,
674    /// or a serde rename that resolves to something other than the
675    /// expected camelCase form.
676    ///
677    /// `lossy_json_int_variants`: when true, the JSON-side equality assertion
678    /// runs after `normalize_integer_variants_for_json_round_trip` on both
679    /// sides. Required for variants that embed a `DataContract` —
680    /// `document_schemas` carry sized integer variants (`U32`/`I32`) that
681    /// JSON's single Number type cannot preserve. See commit 7397c73f31.
682    fn assert_umbrella_round_trip_inner(
683        original: StateTransition,
684        expected_type_tag: &str,
685        lossy_json_int_variants: bool,
686    ) {
687        use crate::serialization::{JsonConvertible, ValueConvertible};
688
689        // JSON
690        let json = original.to_json().expect("to_json");
691        assert_eq!(
692            json["$type"], expected_type_tag,
693            "json type tag for {expected_type_tag}",
694        );
695        let recovered = StateTransition::from_json(json).expect("from_json round-trip");
696        assert_eq!(
697            std::mem::discriminant(&original),
698            std::mem::discriminant(&recovered),
699            "json round-trip variant for {expected_type_tag}",
700        );
701        if lossy_json_int_variants {
702            use crate::tests::utils::normalize_integer_variants_for_json_round_trip;
703            let mut original_canon = original.to_object().expect("to_object");
704            let mut recovered_canon = recovered.to_object().expect("to_object");
705            normalize_integer_variants_for_json_round_trip(&mut original_canon);
706            normalize_integer_variants_for_json_round_trip(&mut recovered_canon);
707            assert_eq!(
708                original_canon, recovered_canon,
709                "json round-trip equality (modulo int-variant) for {expected_type_tag}",
710            );
711        } else {
712            assert_eq!(
713                original, recovered,
714                "json round-trip equality for {expected_type_tag}"
715            );
716        }
717
718        // Value
719        let value = original.to_object().expect("to_object");
720        let map = value.as_map().expect("Value::Map");
721        let tag = map
722            .iter()
723            .find(|(k, _)| k.as_text() == Some("$type"))
724            .map(|(_, v)| v)
725            .unwrap_or_else(|| panic!("type tag missing for {expected_type_tag}"));
726        assert_eq!(
727            *tag,
728            platform_value::Value::Text(expected_type_tag.to_string()),
729            "value type tag for {expected_type_tag}",
730        );
731        let recovered = StateTransition::from_object(value).expect("from_object round-trip");
732        assert_eq!(
733            std::mem::discriminant(&original),
734            std::mem::discriminant(&recovered),
735            "value round-trip variant for {expected_type_tag}",
736        );
737        assert_eq!(
738            original, recovered,
739            "value round-trip equality for {expected_type_tag}"
740        );
741    }
742
743    fn assert_umbrella_round_trip(original: StateTransition, expected_type_tag: &str) {
744        assert_umbrella_round_trip_inner(original, expected_type_tag, false);
745    }
746
747    /// Variant of `assert_umbrella_round_trip` for transitions that embed a
748    /// `DataContract` (`DataContractCreate`, `DataContractUpdate`). JSON's
749    /// single Number type collapses sized-int variants in the embedded
750    /// `document_schemas` tree, so the JSON-side equality assertion is
751    /// run modulo integer-variant normalization. The Value path keeps its
752    /// strict bit-exact assertion (platform_value preserves sized ints).
753    fn assert_umbrella_round_trip_lossy_json_int_variants(
754        original: StateTransition,
755        expected_type_tag: &str,
756    ) {
757        assert_umbrella_round_trip_inner(original, expected_type_tag, true);
758    }
759
760    // Per-variant umbrella round-trip tests. Inner fixtures are reused from
761    // each transition's own `json_convertible_tests::fixture()` (made
762    // `pub(crate)` for this purpose) — keeps the umbrella tests in sync
763    // with the inner-type tests automatically.
764
765    #[test]
766    fn umbrella_data_contract_create() {
767        let inner = crate::state_transition::data_contract_create_transition::json_convertible_tests::fixture();
768        assert_umbrella_round_trip_lossy_json_int_variants(
769            StateTransition::DataContractCreate(inner),
770            "dataContractCreate",
771        );
772    }
773
774    #[test]
775    fn umbrella_data_contract_update() {
776        let inner = crate::state_transition::data_contract_update_transition::json_convertible_tests::fixture();
777        assert_umbrella_round_trip_lossy_json_int_variants(
778            StateTransition::DataContractUpdate(inner),
779            "dataContractUpdate",
780        );
781    }
782
783    #[test]
784    fn umbrella_batch() {
785        let inner = crate::state_transition::batch_transition::json_convertible_tests::fixture();
786        assert_umbrella_round_trip(StateTransition::Batch(inner), "batch");
787    }
788
789    #[test]
790    fn umbrella_identity_create() {
791        let inner =
792            crate::state_transition::identity_create_transition::json_convertible_tests::fixture();
793        assert_umbrella_round_trip(StateTransition::IdentityCreate(inner), "identityCreate");
794    }
795
796    #[test]
797    fn umbrella_identity_top_up() {
798        let inner =
799            crate::state_transition::identity_topup_transition::json_convertible_tests::fixture();
800        assert_umbrella_round_trip(StateTransition::IdentityTopUp(inner), "identityTopUp");
801    }
802
803    #[test]
804    fn umbrella_identity_credit_withdrawal() {
805        let inner = crate::state_transition::identity_credit_withdrawal_transition::json_convertible_tests::fixture();
806        assert_umbrella_round_trip(
807            StateTransition::IdentityCreditWithdrawal(inner),
808            "identityCreditWithdrawal",
809        );
810    }
811
812    #[test]
813    fn umbrella_identity_update() {
814        let inner =
815            crate::state_transition::identity_update_transition::json_convertible_tests::fixture();
816        assert_umbrella_round_trip(StateTransition::IdentityUpdate(inner), "identityUpdate");
817    }
818
819    #[test]
820    fn umbrella_identity_key_limits_update() {
821        let inner = crate::state_transition::identity_key_limits_update_transition::json_convertible_tests::fixture();
822        assert_umbrella_round_trip(
823            StateTransition::IdentityKeyLimitsUpdate(inner),
824            "identityKeyLimitsUpdate",
825        );
826    }
827
828    #[test]
829    fn umbrella_round_trip_contract_user_moderation() {
830        let inner = crate::state_transition::contract_user_moderation_transition::json_convertible_tests::fixture();
831        assert_umbrella_round_trip(
832            StateTransition::ContractUserModeration(inner),
833            "contractUserModeration",
834        );
835    }
836
837    #[test]
838    fn umbrella_round_trip_contract_fee_claim() {
839        let inner =
840            crate::state_transition::contract_fee_claim_transition::json_convertible_tests::fixture(
841            );
842        assert_umbrella_round_trip(StateTransition::ContractFeeClaim(inner), "contractFeeClaim");
843    }
844
845    #[test]
846    fn umbrella_identity_credit_transfer() {
847        let inner = crate::state_transition::identity_credit_transfer_transition::json_convertible_tests::fixture();
848        assert_umbrella_round_trip(
849            StateTransition::IdentityCreditTransfer(inner),
850            "identityCreditTransfer",
851        );
852    }
853
854    #[test]
855    fn umbrella_masternode_vote() {
856        let inner =
857            crate::state_transition::masternode_vote_transition::json_convertible_tests::fixture();
858        assert_umbrella_round_trip(StateTransition::MasternodeVote(inner), "masternodeVote");
859    }
860
861    #[test]
862    fn umbrella_identity_credit_transfer_to_addresses() {
863        let inner = crate::state_transition::identity_credit_transfer_to_addresses_transition::json_convertible_tests::fixture();
864        assert_umbrella_round_trip(
865            StateTransition::IdentityCreditTransferToAddresses(inner),
866            "identityCreditTransferToAddresses",
867        );
868    }
869
870    #[test]
871    fn umbrella_identity_create_from_addresses() {
872        let inner = crate::state_transition::identity_create_from_addresses_transition::json_convertible_tests::fixture();
873        assert_umbrella_round_trip(
874            StateTransition::IdentityCreateFromAddresses(inner),
875            "identityCreateFromAddresses",
876        );
877    }
878
879    #[test]
880    fn umbrella_identity_top_up_from_addresses() {
881        let inner = crate::state_transition::identity_topup_from_addresses_transition::json_convertible_tests::fixture();
882        assert_umbrella_round_trip(
883            StateTransition::IdentityTopUpFromAddresses(inner),
884            "identityTopUpFromAddresses",
885        );
886    }
887
888    #[test]
889    fn umbrella_address_funds_transfer() {
890        let inner = crate::state_transition::address_funds_transfer_transition::json_convertible_tests::fixture();
891        assert_umbrella_round_trip(
892            StateTransition::AddressFundsTransfer(inner),
893            "addressFundsTransfer",
894        );
895    }
896
897    #[test]
898    fn umbrella_address_funding_from_asset_lock() {
899        let inner = crate::state_transition::address_funding_from_asset_lock_transition::json_convertible_tests::fixture();
900        assert_umbrella_round_trip(
901            StateTransition::AddressFundingFromAssetLock(inner),
902            "addressFundingFromAssetLock",
903        );
904    }
905
906    #[test]
907    fn umbrella_address_credit_withdrawal() {
908        let inner = crate::state_transition::address_credit_withdrawal_transition::json_convertible_tests::fixture();
909        assert_umbrella_round_trip(
910            StateTransition::AddressCreditWithdrawal(inner),
911            "addressCreditWithdrawal",
912        );
913    }
914
915    #[test]
916    fn umbrella_shield() {
917        let inner = crate::state_transition::shield_transition::json_convertible_tests::fixture();
918        assert_umbrella_round_trip(StateTransition::Shield(inner), "shield");
919    }
920
921    #[test]
922    fn umbrella_shielded_transfer() {
923        let inner =
924            crate::state_transition::shielded_transfer_transition::json_convertible_tests::fixture(
925            );
926        assert_umbrella_round_trip(StateTransition::ShieldedTransfer(inner), "shieldedTransfer");
927    }
928
929    #[test]
930    fn umbrella_unshield() {
931        let inner = crate::state_transition::unshield_transition::json_convertible_tests::fixture();
932        assert_umbrella_round_trip(StateTransition::Unshield(inner), "unshield");
933    }
934
935    #[test]
936    fn umbrella_shield_from_asset_lock() {
937        let inner = crate::state_transition::shield_from_asset_lock_transition::json_convertible_tests::fixture();
938        assert_umbrella_round_trip(
939            StateTransition::ShieldFromAssetLock(inner),
940            "shieldFromAssetLock",
941        );
942    }
943
944    #[test]
945    fn umbrella_shielded_withdrawal() {
946        let inner = crate::state_transition::shielded_withdrawal_transition::json_convertible_tests::fixture();
947        assert_umbrella_round_trip(
948            StateTransition::ShieldedWithdrawal(inner),
949            "shieldedWithdrawal",
950        );
951    }
952
953    #[test]
954    fn umbrella_identity_create_from_shielded_pool() {
955        let inner = crate::state_transition::identity_create_from_shielded_pool_transition::json_convertible_tests::fixture();
956        assert_umbrella_round_trip(
957            StateTransition::IdentityCreateFromShieldedPool(inner),
958            "identityCreateFromShieldedPool",
959        );
960    }
961
962    #[test]
963    fn umbrella_identity_top_up_from_shielded_pool() {
964        let inner = crate::state_transition::identity_top_up_from_shielded_pool_transition::json_convertible_tests::fixture();
965        assert_umbrella_round_trip(
966            StateTransition::IdentityTopUpFromShieldedPool(inner),
967            "identityTopUpFromShieldedPool",
968        );
969    }
970    #[test]
971    fn umbrella_token_shielded_transfer_with_shielded_fee() {
972        let inner = crate::state_transition::token_shielded_transfer_with_shielded_fee_transition::json_convertible_tests::fixture();
973        assert_umbrella_round_trip(
974            StateTransition::TokenShieldedTransferWithShieldedFee(inner),
975            "tokenShieldedTransferWithShieldedFee",
976        );
977    }
978    #[test]
979    fn umbrella_token_unshield_with_shielded_fee() {
980        let inner = crate::state_transition::token_unshield_with_shielded_fee_transition::json_convertible_tests::fixture();
981        assert_umbrella_round_trip(
982            StateTransition::TokenUnshieldWithShieldedFee(inner),
983            "tokenUnshieldWithShieldedFee",
984        );
985    }
986    #[test]
987    fn umbrella_token_purchase_from_shielded_pool() {
988        let inner = crate::state_transition::token_purchase_from_shielded_pool_transition::json_convertible_tests::fixture();
989        assert_umbrella_round_trip(
990            StateTransition::TokenPurchaseFromShieldedPool(inner),
991            "tokenPurchaseFromShieldedPool",
992        );
993    }
994
995    #[test]
996    fn umbrella_shield_from_identity() {
997        let inner = crate::state_transition::shield_from_identity_transition::json_convertible_tests::fixture();
998        assert_umbrella_round_trip(
999            StateTransition::ShieldFromIdentity(inner),
1000            "shieldFromIdentity",
1001        );
1002    }
1003
1004    /// Every kind decodes from its untagged bytes (the inner transition serialized on its own)
1005    /// into the same transition, and from its tagged bytes exactly.
1006    #[test]
1007    fn every_kind_decodes_untagged_and_exactly() {
1008        use crate::serialization::{PlatformDeserializableUntrusted, PlatformSerializable};
1009
1010        let transitions = [
1011            StateTransition::DataContractCreate(crate::state_transition::data_contract_create_transition::json_convertible_tests::fixture()),
1012            StateTransition::DataContractUpdate(crate::state_transition::data_contract_update_transition::json_convertible_tests::fixture()),
1013            StateTransition::Batch(crate::state_transition::batch_transition::json_convertible_tests::fixture()),
1014            StateTransition::IdentityCreate(crate::state_transition::identity_create_transition::json_convertible_tests::fixture()),
1015            StateTransition::IdentityTopUp(crate::state_transition::identity_topup_transition::json_convertible_tests::fixture()),
1016            StateTransition::IdentityCreditWithdrawal(crate::state_transition::identity_credit_withdrawal_transition::json_convertible_tests::fixture()),
1017            StateTransition::IdentityUpdate(crate::state_transition::identity_update_transition::json_convertible_tests::fixture()),
1018            StateTransition::IdentityKeyLimitsUpdate(crate::state_transition::identity_key_limits_update_transition::json_convertible_tests::fixture()),
1019            StateTransition::ContractUserModeration(crate::state_transition::contract_user_moderation_transition::json_convertible_tests::fixture()),
1020            StateTransition::ContractFeeClaim(crate::state_transition::contract_fee_claim_transition::json_convertible_tests::fixture()),
1021            StateTransition::IdentityCreditTransfer(crate::state_transition::identity_credit_transfer_transition::json_convertible_tests::fixture()),
1022            StateTransition::MasternodeVote(crate::state_transition::masternode_vote_transition::json_convertible_tests::fixture()),
1023            StateTransition::IdentityCreditTransferToAddresses(crate::state_transition::identity_credit_transfer_to_addresses_transition::json_convertible_tests::fixture()),
1024            StateTransition::IdentityCreateFromAddresses(crate::state_transition::identity_create_from_addresses_transition::json_convertible_tests::fixture()),
1025            StateTransition::IdentityTopUpFromAddresses(crate::state_transition::identity_topup_from_addresses_transition::json_convertible_tests::fixture()),
1026            StateTransition::AddressFundsTransfer(crate::state_transition::address_funds_transfer_transition::json_convertible_tests::fixture()),
1027            StateTransition::AddressFundingFromAssetLock(crate::state_transition::address_funding_from_asset_lock_transition::json_convertible_tests::fixture()),
1028            StateTransition::AddressCreditWithdrawal(crate::state_transition::address_credit_withdrawal_transition::json_convertible_tests::fixture()),
1029            StateTransition::Shield(crate::state_transition::shield_transition::json_convertible_tests::fixture()),
1030            StateTransition::ShieldedTransfer(crate::state_transition::shielded_transfer_transition::json_convertible_tests::fixture()),
1031            StateTransition::Unshield(crate::state_transition::unshield_transition::json_convertible_tests::fixture()),
1032            StateTransition::ShieldFromAssetLock(crate::state_transition::shield_from_asset_lock_transition::json_convertible_tests::fixture()),
1033            StateTransition::ShieldedWithdrawal(crate::state_transition::shielded_withdrawal_transition::json_convertible_tests::fixture()),
1034            StateTransition::IdentityCreateFromShieldedPool(crate::state_transition::identity_create_from_shielded_pool_transition::json_convertible_tests::fixture()),
1035            StateTransition::IdentityTopUpFromShieldedPool(crate::state_transition::identity_top_up_from_shielded_pool_transition::json_convertible_tests::fixture()),
1036            StateTransition::ShieldFromIdentity(crate::state_transition::shield_from_identity_transition::json_convertible_tests::fixture()),
1037        ];
1038        for transition in transitions {
1039            let tagged = transition.serialize_to_bytes().expect("serializes");
1040            assert_eq!(
1041                StateTransition::deserialize_from_bytes_untrusted_exact(&tagged).expect("tagged"),
1042                transition
1043            );
1044            // The variant tags are all below 251, so bincode's varint writes them in one byte.
1045            let untagged = &tagged[1..];
1046            assert_eq!(
1047                StateTransition::deserialize_untagged_untrusted_exact(
1048                    transition.state_transition_type(),
1049                    untagged
1050                )
1051                .unwrap_or_else(|e| panic!("{} untagged: {e}", transition.name())),
1052                transition
1053            );
1054        }
1055    }
1056}
1057
1058impl OptionallyAssetLockProved for StateTransition {
1059    fn optional_asset_lock_proof(&self) -> Option<&AssetLockProof> {
1060        match self {
1061            StateTransition::IdentityCreate(st) => st.optional_asset_lock_proof(),
1062            StateTransition::IdentityTopUp(st) => st.optional_asset_lock_proof(),
1063            StateTransition::ShieldFromAssetLock(st) => st.optional_asset_lock_proof(),
1064            _ => None,
1065        }
1066    }
1067}
1068
1069/// The state transition signing options
1070#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
1071pub struct StateTransitionSigningOptions {
1072    /// This will allow signing with any security level for debugging purposes
1073    pub allow_signing_with_any_security_level: bool,
1074    /// This will allow signing with any purpose for debugging purposes
1075    pub allow_signing_with_any_purpose: bool,
1076}
1077
1078/// The active range of a transition carrying `keys`: from protocol version 14 when one of them
1079/// is bound to a contract group or is a version 1 key (the format that can carry a budget or an
1080/// expiry), `otherwise` when none is.
1081fn active_version_range_for_keys_in_creation(
1082    keys: &[IdentityPublicKeyInCreation],
1083    otherwise: RangeInclusive<ProtocolVersion>,
1084) -> RangeInclusive<ProtocolVersion> {
1085    if IdentityPublicKeyInCreation::first_bound_to_a_contract_group(keys).is_some()
1086        || IdentityPublicKeyInCreation::first_in_version_1_format(keys).is_some()
1087    {
1088        14..=LATEST_VERSION
1089    } else {
1090        otherwise
1091    }
1092}
1093
1094/// The active range of a transition carrying `contract`: from protocol version 14 when its
1095/// config is version 2 (the format that can declare moderation), otherwise whatever the
1096/// contract's own format admits. Binaries from before protocol version 14 cannot decode a
1097/// version 2 config, so an earlier version must reject the transition without charging as they
1098/// do; admitting it would also store a moderated contract whose list trees the earlier storage
1099/// writer never creates.
1100///
1101/// Format version 2, the stored format, is active at no protocol version, whatever its config:
1102/// only state holds it, and decoding a contract transition refuses one carrying it.
1103fn active_version_range_for_contract(
1104    contract: &DataContractInSerializationFormat,
1105) -> RangeInclusive<ProtocolVersion> {
1106    match contract {
1107        DataContractInSerializationFormat::V2(_) => NO_VERSION,
1108        _ if matches!(contract.config(), DataContractConfig::V2(_)) => 14..=LATEST_VERSION,
1109        DataContractInSerializationFormat::V0(_) => ALL_VERSIONS,
1110        DataContractInSerializationFormat::V1(_) => 9..=LATEST_VERSION,
1111    }
1112}
1113
1114/// The active range of a format no state transition may carry: it contains no protocol
1115/// version.
1116#[allow(clippy::reversed_empty_ranges)]
1117const NO_VERSION: RangeInclusive<ProtocolVersion> = 1..=0;
1118
1119/// Whether a document transition of `batch_transition` has a base of version 2 or later, the
1120/// format that can carry an action fee agreement.
1121fn batch_carries_a_version_2_document_base(batch_transition: &BatchTransition) -> bool {
1122    batch_transition
1123        .transitions_iter()
1124        .any(|transition| match transition {
1125            BatchedTransitionRef::Document(document_transition) => {
1126                document_transition.base().feature_version() >= 2
1127            }
1128            BatchedTransitionRef::Token(_) => false,
1129        })
1130}
1131
1132impl StateTransition {
1133    /// Decodes a transition from bytes that arrived from outside this node and refuses one
1134    /// whose version is not active at `platform_version`. Bytes left over after the transition
1135    /// are ignored; [`Self::deserialize_from_bytes_untrusted_exact_in_version`] refuses them.
1136    pub fn deserialize_from_bytes_untrusted_in_version(
1137        bytes: &[u8],
1138        platform_version: &PlatformVersion,
1139    ) -> Result<Self, ProtocolError> {
1140        Self::decode_untrusted_in_version(
1141            bytes,
1142            StateTransition::deserialize_from_bytes_untrusted,
1143            platform_version,
1144        )
1145    }
1146
1147    /// [`Self::deserialize_from_bytes_untrusted_in_version`] that also refuses bytes left over
1148    /// after the transition, as a `PlatformDeserializationError`. Leftover bytes are checked
1149    /// before the transition's active version range.
1150    pub fn deserialize_from_bytes_untrusted_exact_in_version(
1151        bytes: &[u8],
1152        platform_version: &PlatformVersion,
1153    ) -> Result<Self, ProtocolError> {
1154        Self::decode_untrusted_in_version(
1155            bytes,
1156            StateTransition::deserialize_from_bytes_untrusted_exact,
1157            platform_version,
1158        )
1159    }
1160
1161    #[allow(unused_variables)]
1162    fn decode_untrusted_in_version(
1163        bytes: &[u8],
1164        decode: fn(&[u8]) -> Result<Self, ProtocolError>,
1165        platform_version: &PlatformVersion,
1166    ) -> Result<Self, ProtocolError> {
1167        let max_value_depth = platform_version
1168            .system_limits
1169            .max_document_value_depth
1170            .map(usize::from);
1171        let state_transition =
1172            platform_value::with_value_decode_depth_limit(max_value_depth, || decode(bytes))?;
1173        #[cfg(all(feature = "state-transitions", feature = "validation"))]
1174        {
1175            let active_version_range = state_transition.active_version_range();
1176
1177            // Tests are done with very high protocol ranges, while we could put this behind a feature,
1178            // that would probably be overkill.
1179            if active_version_range.contains(&platform_version.protocol_version)
1180                || platform_version.protocol_version > 268435456
1181            {
1182                Ok(state_transition)
1183            } else {
1184                Err(ProtocolError::StateTransitionError(
1185                    StateTransitionIsNotActiveError {
1186                        state_transition_type: state_transition.name(),
1187                        active_version_range,
1188                        current_protocol_version: platform_version.protocol_version,
1189                    },
1190                ))
1191            }
1192        }
1193        #[cfg(not(all(feature = "state-transitions", feature = "validation")))]
1194        Ok(state_transition)
1195    }
1196
1197    pub fn active_version_range(&self) -> RangeInclusive<ProtocolVersion> {
1198        match self {
1199            StateTransition::DataContractCreate(data_contract_create_transition) => {
1200                match data_contract_create_transition {
1201                    // Version 1 carries contract groups, which exist from protocol version 14.
1202                    // The embedded contract format alone would admit it at 9 and above, where
1203                    // the group data would be silently dropped. Within 14 and later, the
1204                    // contract's format still decides: it is active nowhere for the stored
1205                    // format, and from 14 for every other.
1206                    DataContractCreateTransition::V1(_) => {
1207                        let contract = active_version_range_for_contract(
1208                            data_contract_create_transition.data_contract(),
1209                        );
1210                        (*contract.start()).max(14)..=(*contract.end()).min(LATEST_VERSION)
1211                    }
1212                    DataContractCreateTransition::V0(_) => active_version_range_for_contract(
1213                        data_contract_create_transition.data_contract(),
1214                    ),
1215                }
1216            }
1217            StateTransition::DataContractUpdate(data_contract_update_transition) => {
1218                active_version_range_for_contract(data_contract_update_transition.data_contract())
1219            }
1220            StateTransition::Batch(batch_transition) => {
1221                // Version 2 of the document base (the action fee agreement) exists from
1222                // protocol version 14. Binaries from before it cannot decode one, so an
1223                // earlier version rejects the batch without charging, exactly as they do.
1224                if batch_carries_a_version_2_document_base(batch_transition) {
1225                    return 14..=LATEST_VERSION;
1226                }
1227                match batch_transition {
1228                    BatchTransition::V0(_) => ALL_VERSIONS,
1229                    BatchTransition::V1(_) => 9..=LATEST_VERSION,
1230                }
1231            }
1232            // A key bound to a contract group, and a version 1 key, exist from protocol version
1233            // 14, so a transition carrying one is inactive before that: an earlier version
1234            // rejects it without charging, exactly as a binary that cannot decode it does.
1235            StateTransition::IdentityCreate(st) => {
1236                active_version_range_for_keys_in_creation(st.public_keys(), ALL_VERSIONS)
1237            }
1238            StateTransition::IdentityUpdate(st) => {
1239                active_version_range_for_keys_in_creation(st.public_keys_to_add(), ALL_VERSIONS)
1240            }
1241            StateTransition::IdentityCreateFromAddresses(st) => {
1242                active_version_range_for_keys_in_creation(st.public_keys(), 11..=LATEST_VERSION)
1243            }
1244            StateTransition::IdentityCreateFromShieldedPool(st) => {
1245                active_version_range_for_keys_in_creation(st.public_keys(), 12..=LATEST_VERSION)
1246            }
1247            StateTransition::IdentityTopUp(_)
1248            | StateTransition::IdentityCreditWithdrawal(_)
1249            | StateTransition::IdentityCreditTransfer(_) => ALL_VERSIONS,
1250            // A vote that names a yes/no poll, as a yes/no vote or as a resource vote, exists
1251            // from protocol version 15. Binaries from before it cannot decode one, so an earlier
1252            // version rejects it without charging, exactly as they do, and no masternode vote
1253            // validator selected before 15 ever sees a yes/no poll.
1254            StateTransition::MasternodeVote(masternode_vote) => match masternode_vote.vote() {
1255                Vote::ResourceVote(resource_vote) => match resource_vote.vote_poll() {
1256                    VotePoll::ContestedDocumentResourceVotePoll(_) => ALL_VERSIONS,
1257                    VotePoll::YesNoVotePoll(_) => {
1258                        YES_NO_VOTE_POLL_INITIAL_PROTOCOL_VERSION..=LATEST_VERSION
1259                    }
1260                },
1261                Vote::YesNoVote(_) => YES_NO_VOTE_POLL_INITIAL_PROTOCOL_VERSION..=LATEST_VERSION,
1262            },
1263            StateTransition::IdentityCreditTransferToAddresses(_)
1264            | StateTransition::IdentityTopUpFromAddresses(_)
1265            | StateTransition::AddressFundsTransfer(_)
1266            | StateTransition::AddressFundingFromAssetLock(_)
1267            | StateTransition::AddressCreditWithdrawal(_) => 11..=LATEST_VERSION,
1268            StateTransition::Shield(_)
1269            | StateTransition::ShieldedTransfer(_)
1270            | StateTransition::Unshield(_)
1271            | StateTransition::ShieldedWithdrawal(_) => 12..=LATEST_VERSION,
1272            // From protocol version 14 the bundle must bind its kind and its asset lock, which
1273            // only version 1 does. Version 0 is refused there on its version byte, before any
1274            // proof is verified: uncharged, with its asset lock left unspent, so a version 0 still
1275            // waiting when 14 activates costs its sender nothing. There is no window in which 14
1276            // accepts version 0.
1277            StateTransition::ShieldFromAssetLock(st) => match st {
1278                ShieldFromAssetLockTransition::V0(_) => 12..=13,
1279                ShieldFromAssetLockTransition::V1(_) => 14..=LATEST_VERSION,
1280            },
1281            StateTransition::ShieldFromIdentity(_)
1282            | StateTransition::IdentityTopUpFromShieldedPool(_)
1283            | StateTransition::IdentityKeyLimitsUpdate(_)
1284            | StateTransition::ContractUserModeration(_)
1285            | StateTransition::ContractFeeClaim(_)
1286            | StateTransition::TokenShieldedTransferWithShieldedFee(_)
1287            | StateTransition::TokenUnshieldWithShieldedFee(_)
1288            | StateTransition::TokenPurchaseFromShieldedPool(_) => 14..=LATEST_VERSION,
1289        }
1290    }
1291
1292    pub fn is_identity_signed(&self) -> bool {
1293        !matches!(
1294            self,
1295            StateTransition::IdentityCreate(_)
1296                | StateTransition::IdentityTopUp(_)
1297                | StateTransition::Shield(_)
1298                | StateTransition::ShieldedTransfer(_)
1299                | StateTransition::Unshield(_)
1300                | StateTransition::ShieldFromAssetLock(_)
1301                | StateTransition::ShieldedWithdrawal(_)
1302                | StateTransition::IdentityCreateFromShieldedPool(_)
1303                | StateTransition::IdentityTopUpFromShieldedPool(_)
1304                | StateTransition::TokenShieldedTransferWithShieldedFee(_)
1305                | StateTransition::TokenUnshieldWithShieldedFee(_)
1306                | StateTransition::TokenPurchaseFromShieldedPool(_)
1307        )
1308    }
1309
1310    pub fn required_asset_lock_balance_for_processing_start(
1311        &self,
1312        platform_version: &PlatformVersion,
1313    ) -> Result<Credits, ProtocolError> {
1314        match self {
1315            StateTransition::IdentityCreate(st) => {
1316                st.calculate_min_required_fee(platform_version)
1317            }
1318            StateTransition::IdentityTopUp(st) => {
1319                st.calculate_min_required_fee(platform_version)
1320            }
1321            StateTransition::AddressFundingFromAssetLock(st) => {
1322                st.calculate_min_required_fee(platform_version)
1323            }
1324            StateTransition::ShieldFromAssetLock(st) => {
1325                st.calculate_min_required_fee(platform_version)
1326            }
1327            st => Err(ProtocolError::CorruptedCodeExecution(format!("{} is not an asset lock transaction, but we are calling required_asset_lock_balance_for_processing_start", st.name()))),
1328        }
1329    }
1330
1331    fn hash(&self, skip_signature: bool) -> Result<Vec<u8>, ProtocolError> {
1332        if skip_signature {
1333            Ok(hash_double_to_vec(self.signable_bytes()?))
1334        } else {
1335            Ok(hash_double_to_vec(
1336                crate::serialization::PlatformSerializable::serialize_to_bytes(self)?,
1337            ))
1338        }
1339    }
1340
1341    /// Returns state transition name
1342    pub fn name(&self) -> String {
1343        match self {
1344            Self::DataContractCreate(_) => "DataContractCreate".to_string(),
1345            Self::DataContractUpdate(_) => "DataContractUpdate".to_string(),
1346            Self::ContractUserModeration(_) => "ContractUserModeration".to_string(),
1347            Self::ContractFeeClaim(_) => "ContractFeeClaim".to_string(),
1348            Self::Batch(batch_transition) => {
1349                let mut document_transition_types = vec![];
1350                for transition in batch_transition.transitions_iter() {
1351                    let document_transition_name = match transition {
1352                        BatchedTransitionRef::Document(DocumentTransition::Create(_)) => "Create",
1353                        BatchedTransitionRef::Document(DocumentTransition::Replace(_)) => "Replace",
1354                        BatchedTransitionRef::Document(DocumentTransition::Delete(_)) => "Delete",
1355                        BatchedTransitionRef::Document(DocumentTransition::Transfer(_)) => {
1356                            "Transfer"
1357                        }
1358                        BatchedTransitionRef::Document(DocumentTransition::UpdatePrice(_)) => {
1359                            "UpdatePrice"
1360                        }
1361                        BatchedTransitionRef::Document(DocumentTransition::Purchase(_)) => {
1362                            "Purchase"
1363                        }
1364                        BatchedTransitionRef::Document(DocumentTransition::IndexOnlyDelete(_)) => {
1365                            "IndexOnlyDelete"
1366                        }
1367                        BatchedTransitionRef::Token(TokenTransition::Transfer(_)) => {
1368                            "TokenTransfer"
1369                        }
1370                        BatchedTransitionRef::Token(TokenTransition::Mint(_)) => "TokenMint",
1371                        BatchedTransitionRef::Token(TokenTransition::Burn(_)) => "TokenBurn",
1372                        BatchedTransitionRef::Token(TokenTransition::Freeze(_)) => "TokenFreeze",
1373                        BatchedTransitionRef::Token(TokenTransition::Unfreeze(_)) => {
1374                            "TokenUnfreeze"
1375                        }
1376                        BatchedTransitionRef::Token(TokenTransition::DestroyFrozenFunds(_)) => {
1377                            "TokenDestroyFrozenFunds"
1378                        }
1379                        BatchedTransitionRef::Token(TokenTransition::EmergencyAction(_)) => {
1380                            "TokenEmergencyAction"
1381                        }
1382                        BatchedTransitionRef::Token(TokenTransition::ConfigUpdate(_)) => {
1383                            "TokenConfigUpdate"
1384                        }
1385                        BatchedTransitionRef::Token(TokenTransition::Claim(_)) => "TokenClaim",
1386                        BatchedTransitionRef::Token(TokenTransition::DirectPurchase(_)) => {
1387                            "TokenDirectPurchase"
1388                        }
1389                        BatchedTransitionRef::Token(
1390                            TokenTransition::SetPriceForDirectPurchase(_),
1391                        ) => "SetPriceForDirectPurchase",
1392                        BatchedTransitionRef::Token(TokenTransition::Shield(_)) => "TokenShield",
1393                        BatchedTransitionRef::Token(TokenTransition::Unshield(_)) => {
1394                            "TokenUnshield"
1395                        }
1396                        BatchedTransitionRef::Token(TokenTransition::ShieldedTransfer(_)) => {
1397                            "TokenShieldedTransfer"
1398                        }
1399                        BatchedTransitionRef::Token(TokenTransition::MintToPool(_)) => {
1400                            "TokenMintToPool"
1401                        }
1402                        BatchedTransitionRef::Token(TokenTransition::BurnFromPool(_)) => {
1403                            "TokenBurnFromPool"
1404                        }
1405                        BatchedTransitionRef::Token(TokenTransition::ClaimToPool(_)) => {
1406                            "TokenClaimToPool"
1407                        }
1408                        BatchedTransitionRef::Token(TokenTransition::DirectPurchaseToPool(_)) => {
1409                            "TokenDirectPurchaseToPool"
1410                        }
1411                    };
1412                    document_transition_types.push(document_transition_name);
1413                }
1414                format!("DocumentsBatch([{}])", document_transition_types.join(", "))
1415            }
1416            Self::IdentityCreate(_) => "IdentityCreate".to_string(),
1417            Self::IdentityTopUp(_) => "IdentityTopUp".to_string(),
1418            Self::IdentityCreditWithdrawal(_) => "IdentityCreditWithdrawal".to_string(),
1419            Self::IdentityUpdate(_) => "IdentityUpdate".to_string(),
1420            Self::IdentityKeyLimitsUpdate(_) => "IdentityKeyLimitsUpdate".to_string(),
1421            Self::IdentityCreditTransfer(_) => "IdentityCreditTransfer".to_string(),
1422            Self::MasternodeVote(_) => "MasternodeVote".to_string(),
1423            Self::IdentityCreditTransferToAddresses(_) => {
1424                "IdentityCreditTransferToAddresses".to_string()
1425            }
1426            Self::IdentityCreateFromAddresses(_) => "IdentityCreateFromAddresses".to_string(),
1427            Self::IdentityTopUpFromAddresses(_) => "IdentityTopUpFromAddresses".to_string(),
1428            Self::AddressFundsTransfer(_) => "AddressFundsTransfer".to_string(),
1429            Self::AddressFundingFromAssetLock(_) => "AddressFundingFromAssetLock".to_string(),
1430            Self::AddressCreditWithdrawal(_) => "AddressCreditWithdrawal".to_string(),
1431            Self::Shield(_) => "Shield".to_string(),
1432            Self::ShieldedTransfer(_) => "ShieldedTransfer".to_string(),
1433            Self::Unshield(_) => "Unshield".to_string(),
1434            Self::IdentityTopUpFromShieldedPool(_) => "IdentityTopUpFromShieldedPool".to_string(),
1435            Self::TokenShieldedTransferWithShieldedFee(_) => {
1436                "TokenShieldedTransferWithShieldedFee".to_string()
1437            }
1438            Self::TokenUnshieldWithShieldedFee(_) => "TokenUnshieldWithShieldedFee".to_string(),
1439            Self::TokenPurchaseFromShieldedPool(_) => "TokenPurchaseFromShieldedPool".to_string(),
1440            Self::ShieldFromAssetLock(_) => "ShieldFromAssetLock".to_string(),
1441            Self::ShieldedWithdrawal(_) => "ShieldedWithdrawal".to_string(),
1442            Self::IdentityCreateFromShieldedPool(_) => "IdentityCreateFromShieldedPool".to_string(),
1443            Self::ShieldFromIdentity(_) => "ShieldFromIdentity".to_string(),
1444        }
1445    }
1446
1447    /// returns the signature as a byte-array
1448    pub fn signature(&self) -> Option<&BinaryData> {
1449        match self {
1450            StateTransition::DataContractCreate(st) => Some(st.signature()),
1451            StateTransition::DataContractUpdate(st) => Some(st.signature()),
1452            StateTransition::ContractUserModeration(st) => Some(st.signature()),
1453            StateTransition::ContractFeeClaim(st) => Some(st.signature()),
1454            StateTransition::Batch(st) => Some(st.signature()),
1455            StateTransition::IdentityCreate(st) => Some(st.signature()),
1456            StateTransition::IdentityTopUp(st) => Some(st.signature()),
1457            StateTransition::IdentityCreditWithdrawal(st) => Some(st.signature()),
1458            StateTransition::IdentityUpdate(st) => Some(st.signature()),
1459            StateTransition::IdentityKeyLimitsUpdate(st) => Some(st.signature()),
1460            StateTransition::IdentityCreditTransfer(st) => Some(st.signature()),
1461            StateTransition::MasternodeVote(st) => Some(st.signature()),
1462            StateTransition::IdentityCreditTransferToAddresses(st) => Some(st.signature()),
1463            StateTransition::IdentityCreateFromAddresses(_) => None,
1464            StateTransition::IdentityTopUpFromAddresses(_) => None,
1465            StateTransition::AddressFundsTransfer(_) => None,
1466            StateTransition::AddressFundingFromAssetLock(st) => Some(st.signature()),
1467            StateTransition::AddressCreditWithdrawal(_) => None,
1468            StateTransition::Shield(_) => None,
1469            StateTransition::ShieldedTransfer(_) => None,
1470            StateTransition::Unshield(_) => None,
1471            StateTransition::IdentityTopUpFromShieldedPool(_) => None,
1472            StateTransition::TokenShieldedTransferWithShieldedFee(_) => None,
1473            StateTransition::TokenUnshieldWithShieldedFee(_) => None,
1474            StateTransition::TokenPurchaseFromShieldedPool(_) => None,
1475            StateTransition::ShieldFromAssetLock(st) => Some(st.signature()),
1476            StateTransition::ShieldedWithdrawal(_) => None,
1477            StateTransition::IdentityCreateFromShieldedPool(_) => None,
1478            StateTransition::ShieldFromIdentity(st) => Some(st.signature()),
1479        }
1480    }
1481
1482    /// returns the number of private keys
1483    pub fn required_number_of_private_keys(&self) -> u16 {
1484        match self {
1485            StateTransition::IdentityCreateFromAddresses(st) => st.inputs().len() as u16,
1486            StateTransition::IdentityTopUpFromAddresses(st) => st.inputs().len() as u16,
1487            StateTransition::AddressFundsTransfer(st) => st.inputs().len() as u16,
1488            StateTransition::AddressCreditWithdrawal(st) => st.inputs().len() as u16,
1489            StateTransition::Shield(st) => st.inputs().len() as u16,
1490            StateTransition::ShieldedTransfer(_) => 0,
1491            StateTransition::Unshield(_) => 0,
1492            StateTransition::IdentityTopUpFromShieldedPool(_) => 0,
1493            StateTransition::TokenShieldedTransferWithShieldedFee(_) => 0,
1494            StateTransition::TokenUnshieldWithShieldedFee(_) => 0,
1495            StateTransition::TokenPurchaseFromShieldedPool(_) => 0,
1496            StateTransition::ShieldFromAssetLock(_) => 0,
1497            StateTransition::ShieldedWithdrawal(_) => 0,
1498            StateTransition::IdentityCreateFromShieldedPool(_) => 0,
1499            _ => 1,
1500        }
1501    }
1502
1503    /// returns the fee_increase additional percentage multiplier, it affects only processing costs
1504    pub fn user_fee_increase(&self) -> UserFeeIncrease {
1505        match self {
1506            StateTransition::DataContractCreate(st) => st.user_fee_increase(),
1507            StateTransition::DataContractUpdate(st) => st.user_fee_increase(),
1508            StateTransition::ContractUserModeration(st) => st.user_fee_increase(),
1509            StateTransition::ContractFeeClaim(st) => st.user_fee_increase(),
1510            StateTransition::Batch(st) => st.user_fee_increase(),
1511            StateTransition::IdentityCreate(st) => st.user_fee_increase(),
1512            StateTransition::IdentityTopUp(st) => st.user_fee_increase(),
1513            StateTransition::IdentityCreditWithdrawal(st) => st.user_fee_increase(),
1514            StateTransition::IdentityUpdate(st) => st.user_fee_increase(),
1515            StateTransition::IdentityKeyLimitsUpdate(st) => st.user_fee_increase(),
1516            StateTransition::IdentityCreditTransfer(st) => st.user_fee_increase(),
1517            StateTransition::IdentityCreditTransferToAddresses(st) => st.user_fee_increase(),
1518            StateTransition::IdentityCreateFromAddresses(st) => st.user_fee_increase(),
1519            StateTransition::IdentityTopUpFromAddresses(st) => st.user_fee_increase(),
1520            StateTransition::AddressFundsTransfer(st) => st.user_fee_increase(),
1521            StateTransition::AddressFundingFromAssetLock(st) => st.user_fee_increase(),
1522            StateTransition::AddressCreditWithdrawal(st) => st.user_fee_increase(),
1523            StateTransition::Shield(st) => st.user_fee_increase(),
1524            // These transitions don't support user fee adjustment
1525            StateTransition::ShieldFromAssetLock(_) => 0,
1526            StateTransition::MasternodeVote(_) => 0,
1527            StateTransition::ShieldedTransfer(_) => 0,
1528            StateTransition::Unshield(_) => 0,
1529            StateTransition::IdentityTopUpFromShieldedPool(_) => 0,
1530            StateTransition::TokenShieldedTransferWithShieldedFee(_) => 0,
1531            StateTransition::TokenUnshieldWithShieldedFee(_) => 0,
1532            StateTransition::TokenPurchaseFromShieldedPool(_) => 0,
1533            StateTransition::ShieldedWithdrawal(_) => 0,
1534            StateTransition::IdentityCreateFromShieldedPool(_) => 0,
1535            StateTransition::ShieldFromIdentity(st) => st.user_fee_increase(),
1536        }
1537    }
1538
1539    /// Calculates the estimated minimum fee required for this state transition.
1540    ///
1541    /// The fee is calculated based on the number of inputs, outputs, and any
1542    /// transition-specific costs (e.g., key creation costs for identity creation).
1543    ///
1544    /// # Arguments
1545    ///
1546    /// * `platform_version` - The platform version containing fee configuration.
1547    ///
1548    /// # Returns
1549    ///
1550    /// The estimated fee in credits.
1551    fn calculate_estimated_fee(
1552        &self,
1553        platform_version: &PlatformVersion,
1554    ) -> Result<Credits, ProtocolError> {
1555        call_method!(self, calculate_min_required_fee, platform_version)
1556    }
1557
1558    /// The transaction id is a single hash of the data with the signature
1559    pub fn transaction_id(&self) -> Result<[u8; 32], ProtocolError> {
1560        Ok(hash_single(
1561            crate::serialization::PlatformSerializable::serialize_to_bytes(self)?,
1562        ))
1563    }
1564
1565    /// returns the signature as a byte-array
1566    pub fn signature_public_key_id(&self) -> Option<KeyID> {
1567        call_getter_method_identity_signed!(self, signature_public_key_id)
1568    }
1569
1570    /// returns the key security level requirement for the state transition
1571    pub fn security_level_requirement(&self, purpose: Purpose) -> Option<Vec<SecurityLevel>> {
1572        call_getter_method_identity_signed!(self, security_level_requirement, purpose)
1573    }
1574
1575    /// returns the key purpose requirement for the state transition
1576    pub fn purpose_requirement(&self) -> Option<Vec<Purpose>> {
1577        call_getter_method_identity_signed!(self, purpose_requirement)
1578    }
1579
1580    /// returns the signature as a byte-array
1581    pub fn owner_id(&self) -> Option<Identifier> {
1582        match self {
1583            StateTransition::DataContractCreate(st) => Some(st.owner_id()),
1584            StateTransition::DataContractUpdate(st) => Some(st.owner_id()),
1585            StateTransition::ContractUserModeration(st) => Some(st.owner_id()),
1586            StateTransition::ContractFeeClaim(st) => Some(st.owner_id()),
1587            StateTransition::Batch(st) => Some(st.owner_id()),
1588            StateTransition::IdentityCreate(st) => Some(st.owner_id()),
1589            StateTransition::IdentityTopUp(st) => Some(st.owner_id()),
1590            StateTransition::IdentityCreditWithdrawal(st) => Some(st.owner_id()),
1591            StateTransition::IdentityUpdate(st) => Some(st.owner_id()),
1592            StateTransition::IdentityKeyLimitsUpdate(st) => Some(st.owner_id()),
1593            StateTransition::IdentityCreditTransfer(st) => Some(st.owner_id()),
1594            StateTransition::MasternodeVote(st) => Some(st.owner_id()),
1595            StateTransition::IdentityCreditTransferToAddresses(st) => Some(st.owner_id()),
1596            StateTransition::IdentityCreateFromAddresses(_) => None,
1597            StateTransition::IdentityTopUpFromAddresses(_) => None,
1598            StateTransition::AddressFundsTransfer(_) => None,
1599            StateTransition::AddressFundingFromAssetLock(_) => None,
1600            StateTransition::AddressCreditWithdrawal(_) => None,
1601            StateTransition::Shield(_) => None,
1602            StateTransition::ShieldedTransfer(_) => None,
1603            StateTransition::Unshield(_) => None,
1604            StateTransition::IdentityTopUpFromShieldedPool(_) => None,
1605            StateTransition::TokenShieldedTransferWithShieldedFee(_) => None,
1606            StateTransition::TokenUnshieldWithShieldedFee(_) => None,
1607            StateTransition::TokenPurchaseFromShieldedPool(_) => None,
1608            StateTransition::ShieldFromAssetLock(_) => None,
1609            StateTransition::ShieldedWithdrawal(_) => None,
1610            StateTransition::IdentityCreateFromShieldedPool(_) => None,
1611            StateTransition::ShieldFromIdentity(st) => Some(st.owner_id()),
1612        }
1613    }
1614
1615    /// returns the signature as a byte-array
1616    pub fn inputs(&self) -> Option<&BTreeMap<PlatformAddress, (AddressNonce, Credits)>> {
1617        match self {
1618            StateTransition::DataContractCreate(_)
1619            | StateTransition::DataContractUpdate(_)
1620            | StateTransition::ContractUserModeration(_)
1621            | StateTransition::ContractFeeClaim(_)
1622            | StateTransition::Batch(_)
1623            | StateTransition::IdentityCreate(_)
1624            | StateTransition::IdentityTopUp(_)
1625            | StateTransition::IdentityCreditWithdrawal(_)
1626            | StateTransition::IdentityUpdate(_)
1627            | StateTransition::IdentityKeyLimitsUpdate(_)
1628            | StateTransition::IdentityCreditTransfer(_)
1629            | StateTransition::MasternodeVote(_)
1630            | StateTransition::IdentityCreditTransferToAddresses(_) => None,
1631            StateTransition::IdentityCreateFromAddresses(st) => Some(st.inputs()),
1632            StateTransition::IdentityTopUpFromAddresses(st) => Some(st.inputs()),
1633            StateTransition::AddressFundsTransfer(st) => Some(st.inputs()),
1634            StateTransition::AddressFundingFromAssetLock(st) => Some(st.inputs()),
1635            StateTransition::AddressCreditWithdrawal(st) => Some(st.inputs()),
1636            StateTransition::Shield(st) => Some(st.inputs()),
1637            StateTransition::ShieldedTransfer(_) => None,
1638            StateTransition::Unshield(_) => None,
1639            StateTransition::IdentityTopUpFromShieldedPool(_) => None,
1640            StateTransition::TokenShieldedTransferWithShieldedFee(_) => None,
1641            StateTransition::TokenUnshieldWithShieldedFee(_) => None,
1642            StateTransition::TokenPurchaseFromShieldedPool(_) => None,
1643            StateTransition::ShieldFromAssetLock(_) => None,
1644            StateTransition::ShieldedWithdrawal(_) => None,
1645            StateTransition::IdentityCreateFromShieldedPool(_) => None,
1646            StateTransition::ShieldFromIdentity(_) => None,
1647        }
1648    }
1649
1650    /// returns the state transition type
1651    pub fn state_transition_type(&self) -> StateTransitionType {
1652        call_method!(self, state_transition_type)
1653    }
1654
1655    /// returns the unique identifiers for the state transition
1656    pub fn unique_identifiers(&self) -> Vec<String> {
1657        call_method!(self, unique_identifiers)
1658    }
1659
1660    /// set a new signature
1661    pub fn set_signature(&mut self, signature: BinaryData) -> bool {
1662        match self {
1663            StateTransition::DataContractCreate(st) => {
1664                st.set_signature(signature);
1665                true
1666            }
1667            StateTransition::DataContractUpdate(st) => {
1668                st.set_signature(signature);
1669                true
1670            }
1671            StateTransition::ContractUserModeration(st) => {
1672                st.set_signature(signature);
1673                true
1674            }
1675            StateTransition::ContractFeeClaim(st) => {
1676                st.set_signature(signature);
1677                true
1678            }
1679            StateTransition::Batch(st) => {
1680                st.set_signature(signature);
1681                true
1682            }
1683            StateTransition::IdentityCreate(st) => {
1684                st.set_signature(signature);
1685                true
1686            }
1687            StateTransition::IdentityTopUp(st) => {
1688                st.set_signature(signature);
1689                true
1690            }
1691            StateTransition::IdentityCreditWithdrawal(st) => {
1692                st.set_signature(signature);
1693                true
1694            }
1695            StateTransition::IdentityUpdate(st) => {
1696                st.set_signature(signature);
1697                true
1698            }
1699            StateTransition::IdentityKeyLimitsUpdate(st) => {
1700                st.set_signature(signature);
1701                true
1702            }
1703            StateTransition::IdentityCreditTransfer(st) => {
1704                st.set_signature(signature);
1705                true
1706            }
1707            StateTransition::MasternodeVote(st) => {
1708                st.set_signature(signature);
1709                true
1710            }
1711            StateTransition::IdentityCreditTransferToAddresses(st) => {
1712                st.set_signature(signature);
1713                true
1714            }
1715            StateTransition::IdentityCreateFromAddresses(_)
1716            | StateTransition::IdentityTopUpFromAddresses(_)
1717            | StateTransition::AddressFundsTransfer(_)
1718            | StateTransition::Shield(_)
1719            | StateTransition::ShieldedTransfer(_)
1720            | StateTransition::Unshield(_)
1721            | StateTransition::ShieldedWithdrawal(_)
1722            | StateTransition::IdentityCreateFromShieldedPool(_)
1723            | StateTransition::TokenShieldedTransferWithShieldedFee(_)
1724            | StateTransition::TokenUnshieldWithShieldedFee(_)
1725            | StateTransition::TokenPurchaseFromShieldedPool(_)
1726            | StateTransition::IdentityTopUpFromShieldedPool(_) => false,
1727            StateTransition::ShieldFromIdentity(st) => {
1728                st.set_signature(signature);
1729                true
1730            }
1731            StateTransition::AddressFundingFromAssetLock(st) => {
1732                st.set_signature(signature);
1733                true
1734            }
1735            StateTransition::ShieldFromAssetLock(st) => {
1736                st.set_signature(signature);
1737                true
1738            }
1739            StateTransition::AddressCreditWithdrawal(_) => false,
1740        }
1741    }
1742
1743    /// set fee multiplier
1744    pub fn set_user_fee_increase(&mut self, user_fee_increase: UserFeeIncrease) {
1745        match self {
1746            StateTransition::DataContractCreate(st) => st.set_user_fee_increase(user_fee_increase),
1747            StateTransition::DataContractUpdate(st) => st.set_user_fee_increase(user_fee_increase),
1748            StateTransition::ContractUserModeration(st) => {
1749                st.set_user_fee_increase(user_fee_increase)
1750            }
1751            StateTransition::ContractFeeClaim(st) => st.set_user_fee_increase(user_fee_increase),
1752            StateTransition::Batch(st) => st.set_user_fee_increase(user_fee_increase),
1753            StateTransition::IdentityCreate(st) => st.set_user_fee_increase(user_fee_increase),
1754            StateTransition::IdentityTopUp(st) => st.set_user_fee_increase(user_fee_increase),
1755            StateTransition::IdentityCreditWithdrawal(st) => {
1756                st.set_user_fee_increase(user_fee_increase)
1757            }
1758            StateTransition::IdentityUpdate(st) => st.set_user_fee_increase(user_fee_increase),
1759            StateTransition::IdentityKeyLimitsUpdate(st) => {
1760                st.set_user_fee_increase(user_fee_increase)
1761            }
1762            StateTransition::IdentityCreditTransfer(st) => {
1763                st.set_user_fee_increase(user_fee_increase)
1764            }
1765            StateTransition::IdentityCreditTransferToAddresses(st) => {
1766                st.set_user_fee_increase(user_fee_increase)
1767            }
1768            StateTransition::IdentityCreateFromAddresses(st) => {
1769                st.set_user_fee_increase(user_fee_increase)
1770            }
1771            StateTransition::IdentityTopUpFromAddresses(st) => {
1772                st.set_user_fee_increase(user_fee_increase)
1773            }
1774            StateTransition::AddressFundsTransfer(st) => {
1775                st.set_user_fee_increase(user_fee_increase)
1776            }
1777            StateTransition::AddressFundingFromAssetLock(st) => {
1778                st.set_user_fee_increase(user_fee_increase)
1779            }
1780            StateTransition::AddressCreditWithdrawal(st) => {
1781                st.set_user_fee_increase(user_fee_increase)
1782            }
1783            StateTransition::Shield(st) => st.set_user_fee_increase(user_fee_increase),
1784            // These transitions don't support user fee adjustment — no-op
1785            StateTransition::ShieldFromAssetLock(_) => {}
1786            StateTransition::MasternodeVote(_) => {}
1787            StateTransition::ShieldedTransfer(_) => {}
1788            StateTransition::Unshield(_) => {}
1789            StateTransition::IdentityTopUpFromShieldedPool(_) => {}
1790            StateTransition::TokenShieldedTransferWithShieldedFee(_) => {}
1791            StateTransition::TokenUnshieldWithShieldedFee(_) => {}
1792            StateTransition::TokenPurchaseFromShieldedPool(_) => {}
1793            StateTransition::ShieldedWithdrawal(_) => {}
1794            StateTransition::IdentityCreateFromShieldedPool(_) => {}
1795            StateTransition::ShieldFromIdentity(st) => st.set_user_fee_increase(user_fee_increase),
1796        }
1797    }
1798
1799    /// set a new signature
1800    pub fn set_signature_public_key_id(&mut self, public_key_id: KeyID) {
1801        call_method_identity_signed!(self, set_signature_public_key_id, public_key_id)
1802    }
1803
1804    #[cfg(feature = "state-transition-signing")]
1805    pub async fn sign_external<S: Signer<IdentityPublicKey>>(
1806        &mut self,
1807        identity_public_key: &IdentityPublicKey,
1808        signer: &S,
1809        get_data_contract_security_level_requirement: Option<
1810            impl Fn(Identifier, String) -> Result<SecurityLevel, ProtocolError>,
1811        >,
1812    ) -> Result<(), ProtocolError> {
1813        self.sign_external_with_options(
1814            identity_public_key,
1815            signer,
1816            get_data_contract_security_level_requirement,
1817            StateTransitionSigningOptions::default(),
1818        )
1819        .await
1820    }
1821
1822    #[cfg(feature = "state-transition-signing")]
1823    pub async fn sign_external_with_options<S: Signer<IdentityPublicKey>>(
1824        &mut self,
1825        identity_public_key: &IdentityPublicKey,
1826        signer: &S,
1827        get_data_contract_security_level_requirement: Option<
1828            impl Fn(Identifier, String) -> Result<SecurityLevel, ProtocolError>,
1829        >,
1830        options: StateTransitionSigningOptions,
1831    ) -> Result<(), ProtocolError> {
1832        self.verify_identity_key_bounds(identity_public_key)?;
1833        match self {
1834            StateTransition::DataContractCreate(st) => {
1835                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1836                st.verify_public_key_is_enabled(identity_public_key)?;
1837            }
1838            StateTransition::DataContractUpdate(st) => {
1839                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1840                st.verify_public_key_is_enabled(identity_public_key)?;
1841            }
1842            StateTransition::ContractUserModeration(st) => {
1843                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1844                st.verify_public_key_is_enabled(identity_public_key)?;
1845            }
1846            StateTransition::ContractFeeClaim(st) => {
1847                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1848                st.verify_public_key_is_enabled(identity_public_key)?;
1849            }
1850            StateTransition::Batch(st) => {
1851                let allow_token_transfer_keys = st.transitions_len() == 1
1852                    && (st
1853                        .first_transition()
1854                        .expect("expected first transition with len 1")
1855                        .as_transition_token_claim()
1856                        .is_some()
1857                        || st
1858                            .first_transition()
1859                            .expect("expected first transition with len 1")
1860                            .as_transition_token_transfer()
1861                            .is_some());
1862                let allowed_key_purposes = if allow_token_transfer_keys {
1863                    vec![Purpose::AUTHENTICATION, Purpose::TRANSFER]
1864                } else {
1865                    vec![Purpose::AUTHENTICATION]
1866                };
1867                if !options.allow_signing_with_any_purpose
1868                    && !allowed_key_purposes.contains(&identity_public_key.purpose())
1869                {
1870                    return Err(ProtocolError::WrongPublicKeyPurposeError(
1871                        WrongPublicKeyPurposeError::new(
1872                            identity_public_key.purpose(),
1873                            allowed_key_purposes,
1874                        ),
1875                    ));
1876                }
1877                if !options.allow_signing_with_any_security_level {
1878                    let security_level_requirement = st.combined_security_level_requirement(
1879                        get_data_contract_security_level_requirement,
1880                    )?;
1881                    if !security_level_requirement.contains(&identity_public_key.security_level()) {
1882                        return Err(ProtocolError::InvalidSignaturePublicKeySecurityLevelError(
1883                            InvalidSignaturePublicKeySecurityLevelError::new(
1884                                identity_public_key.security_level(),
1885                                security_level_requirement,
1886                            ),
1887                        ));
1888                    }
1889                }
1890                st.verify_public_key_is_enabled(identity_public_key)?;
1891            }
1892            StateTransition::IdentityCreditWithdrawal(st) => {
1893                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1894                st.verify_public_key_is_enabled(identity_public_key)?;
1895            }
1896            StateTransition::IdentityUpdate(st) => {
1897                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1898                st.verify_public_key_is_enabled(identity_public_key)?;
1899            }
1900            StateTransition::IdentityKeyLimitsUpdate(st) => {
1901                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1902                st.verify_public_key_is_enabled(identity_public_key)?;
1903            }
1904            StateTransition::IdentityCreditTransfer(st) => {
1905                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1906                st.verify_public_key_is_enabled(identity_public_key)?;
1907            }
1908            StateTransition::IdentityCreate(_) => {
1909                return Err(ProtocolError::CorruptedCodeExecution(
1910                    "identity create can not be called for identity signing".to_string(),
1911                ))
1912            }
1913            StateTransition::IdentityTopUp(_) => {
1914                return Err(ProtocolError::CorruptedCodeExecution(
1915                    "identity top up can not be called for identity signing".to_string(),
1916                ))
1917            }
1918            StateTransition::MasternodeVote(st) => {
1919                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1920                st.verify_public_key_is_enabled(identity_public_key)?;
1921            }
1922            StateTransition::IdentityCreditTransferToAddresses(st) => {
1923                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1924                st.verify_public_key_is_enabled(identity_public_key)?;
1925            }
1926            StateTransition::IdentityCreateFromAddresses(_) => {
1927                return Err(ProtocolError::CorruptedCodeExecution(
1928                    "identity create from addresses can not be called for identity signing"
1929                        .to_string(),
1930                ))
1931            }
1932            StateTransition::IdentityTopUpFromAddresses(_) => {
1933                return Err(ProtocolError::CorruptedCodeExecution(
1934                    "identity top up from addresses can not be called for identity signing"
1935                        .to_string(),
1936                ))
1937            }
1938            StateTransition::AddressFundsTransfer(_) => {
1939                return Err(ProtocolError::CorruptedCodeExecution(
1940                    "address funds transfer transition can not be called for identity signing"
1941                        .to_string(),
1942                ))
1943            }
1944            StateTransition::AddressFundingFromAssetLock(_) => {
1945                return Err(ProtocolError::CorruptedCodeExecution(
1946                    "address funding from asset lock transition can not be called for identity signing"
1947                        .to_string(),
1948                ))
1949            }
1950            StateTransition::AddressCreditWithdrawal(_) => {
1951                return Err(ProtocolError::CorruptedCodeExecution(
1952                    "address credit withdrawal transition can not be called for identity signing"
1953                        .to_string(),
1954                ))
1955            }
1956            StateTransition::Shield(_) => {
1957                return Err(ProtocolError::CorruptedCodeExecution(
1958                    "shield transition can not be called for identity signing".to_string(),
1959                ))
1960            }
1961            StateTransition::ShieldedTransfer(_) => {
1962                return Err(ProtocolError::CorruptedCodeExecution(
1963                    "shielded transfer transition can not be called for identity signing"
1964                        .to_string(),
1965                ))
1966            }
1967            StateTransition::Unshield(_) => {
1968                return Err(ProtocolError::CorruptedCodeExecution(
1969                    "unshield transition can not be called for identity signing".to_string(),
1970                ))
1971            }
1972            StateTransition::IdentityTopUpFromShieldedPool(_) => {
1973                return Err(ProtocolError::CorruptedCodeExecution(
1974                    "identity top up from shielded pool transition can not be called for identity signing".to_string(),
1975                ))
1976            }
1977            StateTransition::TokenShieldedTransferWithShieldedFee(_) => {
1978                return Err(ProtocolError::CorruptedCodeExecution(
1979                    "token shielded transfer with shielded fee transition can not be called for identity signing".to_string(),
1980                ))
1981            }
1982            StateTransition::TokenUnshieldWithShieldedFee(_) => {
1983                return Err(ProtocolError::CorruptedCodeExecution(
1984                    "token unshield with shielded fee transition can not be called for identity signing".to_string(),
1985                ))
1986            }
1987            StateTransition::TokenPurchaseFromShieldedPool(_) => {
1988                return Err(ProtocolError::CorruptedCodeExecution(
1989                    "token purchase from shielded pool transition can not be called for identity signing".to_string(),
1990                ))
1991            }
1992            StateTransition::ShieldFromAssetLock(_) => {
1993                return Err(ProtocolError::CorruptedCodeExecution(
1994                    "shield from asset lock transition can not be called for identity signing"
1995                        .to_string(),
1996                ))
1997            }
1998            StateTransition::ShieldedWithdrawal(_) => {
1999                return Err(ProtocolError::CorruptedCodeExecution(
2000                    "shielded withdrawal transition can not be called for identity signing"
2001                        .to_string(),
2002                ))
2003            }
2004            StateTransition::IdentityCreateFromShieldedPool(_) => {
2005                return Err(ProtocolError::CorruptedCodeExecution(
2006                    "identity create from shielded pool transition can not be called for identity signing"
2007                        .to_string(),
2008                ))
2009            }
2010            StateTransition::ShieldFromIdentity(st) => {
2011                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
2012                st.verify_public_key_is_enabled(identity_public_key)?;
2013            }
2014        }
2015        let data = self.signable_bytes()?;
2016        self.set_signature(signer.sign(identity_public_key, data.as_slice()).await?);
2017        self.set_signature_public_key_id(identity_public_key.id());
2018        Ok(())
2019    }
2020
2021    /// A contract-bound AUTHENTICATION key may only sign a Batch whose members are all inside
2022    /// its bounds. Consensus enforces the same rule from the stored key; checking here saves the
2023    /// round trip when the signing API is handed the key metadata. A contract group bound is
2024    /// left to consensus, which reads the group's memberships.
2025    #[cfg(feature = "state-transition-signing")]
2026    fn verify_identity_key_bounds(
2027        &self,
2028        identity_public_key: &IdentityPublicKey,
2029    ) -> Result<(), ProtocolError> {
2030        if identity_public_key.purpose() != Purpose::AUTHENTICATION {
2031            return Ok(());
2032        }
2033        let Some(bounds) = identity_public_key.contract_bounds() else {
2034            return Ok(());
2035        };
2036        match self {
2037            StateTransition::Batch(batch) => {
2038                use crate::state_transition::batch_transition::accessors::DocumentsBatchTransitionAccessorsV0;
2039                if batch.transitions_iter().all(|member| {
2040                    bounds.check_batched_transition(member) != BatchedTransitionBoundsCheck::Denied
2041                }) {
2042                    Ok(())
2043                } else {
2044                    Err(ProtocolError::ConsensusError(Box::new(
2045                        ContractBoundedKeyOutOfBoundsError::new(identity_public_key.id()).into(),
2046                    )))
2047                }
2048            }
2049            _ => Err(ProtocolError::ConsensusError(Box::new(
2050                ContractBoundedKeyNonBatchError::new(identity_public_key.id()).into(),
2051            ))),
2052        }
2053    }
2054
2055    #[cfg(feature = "state-transition-signing")]
2056    pub fn sign(
2057        &mut self,
2058        identity_public_key: &IdentityPublicKey,
2059        private_key: &[u8],
2060        bls: &impl BlsModule,
2061    ) -> Result<(), ProtocolError> {
2062        self.sign_with_options(
2063            identity_public_key,
2064            private_key,
2065            bls,
2066            StateTransitionSigningOptions::default(),
2067        )
2068    }
2069
2070    #[cfg(feature = "state-transition-signing")]
2071    pub fn sign_with_options(
2072        &mut self,
2073        identity_public_key: &IdentityPublicKey,
2074        private_key: &[u8],
2075        bls: &impl BlsModule,
2076        options: StateTransitionSigningOptions,
2077    ) -> Result<(), ProtocolError> {
2078        self.verify_identity_key_bounds(identity_public_key)?;
2079        call_errorable_method_identity_signed!(
2080            self,
2081            verify_public_key_level_and_purpose,
2082            identity_public_key,
2083            options
2084        )?;
2085        call_errorable_method_identity_signed!(
2086            self,
2087            verify_public_key_is_enabled,
2088            identity_public_key
2089        )?;
2090
2091        match identity_public_key.key_type() {
2092            KeyType::ECDSA_SECP256K1 => {
2093                let public_key_compressed = get_compressed_public_ec_key(private_key)?;
2094
2095                // we store compressed public key in the identity ,
2096                // and here we compare the private key used to sing the state transition with
2097                // the compressed key stored in the identity
2098
2099                if public_key_compressed.as_slice() != identity_public_key.data().as_slice() {
2100                    return Err(ProtocolError::InvalidSignaturePublicKeyError(
2101                        InvalidSignaturePublicKeyError::new(identity_public_key.data().to_vec()),
2102                    ));
2103                }
2104
2105                self.sign_by_private_key(private_key, identity_public_key.key_type(), bls)
2106            }
2107            KeyType::ECDSA_HASH160 => {
2108                let public_key_compressed = get_compressed_public_ec_key(private_key)?;
2109                let pub_key_hash = ripemd160_sha256(&public_key_compressed);
2110
2111                if identity_public_key.data().as_slice() != pub_key_hash {
2112                    return Err(ProtocolError::InvalidSignaturePublicKeyError(
2113                        InvalidSignaturePublicKeyError::new(identity_public_key.data().to_vec()),
2114                    ));
2115                }
2116                self.sign_by_private_key(private_key, identity_public_key.key_type(), bls)
2117            }
2118            KeyType::BLS12_381 => {
2119                let public_key = bls.private_key_to_public_key(private_key)?;
2120
2121                if public_key != identity_public_key.data().as_slice() {
2122                    return Err(ProtocolError::InvalidSignaturePublicKeyError(
2123                        InvalidSignaturePublicKeyError::new(identity_public_key.data().to_vec()),
2124                    ));
2125                }
2126                self.sign_by_private_key(private_key, identity_public_key.key_type(), bls)
2127            }
2128
2129            // the default behavior from
2130            // https://github.com/dashevo/platform/blob/6b02b26e5cd3a7c877c5fdfe40c4a4385a8dda15/packages/js-dpp/lib/stateTransition/AbstractStateTransitionIdentitySigned.js#L108
2131            // is to return the error for the BIP13_SCRIPT_HASH
2132            KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => {
2133                Err(ProtocolError::InvalidIdentityPublicKeyTypeError(
2134                    InvalidIdentityPublicKeyTypeError::new(identity_public_key.key_type()),
2135                ))
2136            }
2137        }?;
2138
2139        self.set_signature_public_key_id(identity_public_key.id());
2140
2141        Ok(())
2142    }
2143
2144    #[cfg(feature = "state-transition-signing")]
2145    /// Signs data with the private key
2146    pub fn sign_by_private_key(
2147        &mut self,
2148        private_key: &[u8],
2149        key_type: KeyType,
2150        bls: &impl BlsModule,
2151    ) -> Result<(), ProtocolError> {
2152        let data = self.signable_bytes()?;
2153        match key_type {
2154            KeyType::BLS12_381 => {
2155                if !self.set_signature(bls.sign(&data, private_key)?.into()) {
2156                    return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
2157                        needed: self.required_number_of_private_keys(),
2158                        using: 1,
2159                        msg: "failed to set BLS signature",
2160                    });
2161                }
2162            }
2163
2164            // https://github.com/dashevo/platform/blob/9c8e6a3b6afbc330a6ab551a689de8ccd63f9120/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js#L169
2165            KeyType::ECDSA_SECP256K1 | KeyType::ECDSA_HASH160 => {
2166                let signature = signer::sign(&data, private_key)?;
2167                if !self.set_signature(signature.to_vec().into()) {
2168                    return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
2169                        needed: self.required_number_of_private_keys(),
2170                        using: 1,
2171                        msg: "failed to set ECDSA signature",
2172                    });
2173                };
2174            }
2175
2176            // the default behavior from
2177            // https://github.com/dashevo/platform/blob/6b02b26e5cd3a7c877c5fdfe40c4a4385a8dda15/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js#L187
2178            // is to return the error for the BIP13_SCRIPT_HASH
2179            KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => {
2180                return Err(ProtocolError::InvalidIdentityPublicKeyTypeError(
2181                    InvalidIdentityPublicKeyTypeError::new(key_type),
2182                ))
2183            }
2184        };
2185        Ok(())
2186    }
2187
2188    /// Sign `self.signable_bytes()` with an external Core-wallet signer and
2189    /// store the resulting Core-ECDSA signature in the transition's wrapper
2190    /// signature field.
2191    ///
2192    /// # Position in the signing-primitive family
2193    ///
2194    /// This is a **primitive** in the same family as
2195    /// [`Self::sign_by_private_key`] — it performs no validation of the
2196    /// transition variant, the key, or the relationship between them. It is
2197    /// the external-custody sibling of `sign_by_private_key`:
2198    ///
2199    /// | Primitive | Key source | Validation |
2200    /// |---|---|---|
2201    /// | [`Self::sign_by_private_key`] | raw `&[u8]` in host memory | none |
2202    /// | `sign_with_core_signer` | external signer (HSM / hardware wallet / secure enclave / remote signing service), key reached via BIP32 [`DerivationPath`] | none |
2203    ///
2204    /// Both produce **byte-identical** wrapper signatures over the same
2205    /// digest when given the same underlying private key (proven by
2206    /// `sign_with_signer_matches_sign_by_private_key_byte_for_byte` in this
2207    /// file's tests). The only difference is where the key bytes live: in
2208    /// host memory vs inside the signer's trust boundary. The signer
2209    /// performs the derive + sign + zeroise sequence atomically; this
2210    /// function never sees raw key material, only a 32-byte digest and the
2211    /// resulting signature.
2212    ///
2213    /// # Scope (what the BIP32 path means)
2214    ///
2215    /// The `path` parameter selects a key in the signer's Core wallet
2216    /// (BIP32-derived). For that path's signature to be **meaningful** the
2217    /// transition's wrapper signature field must itself carry a Core-key
2218    /// signature. Today that is exactly the four asset-lock-signed
2219    /// variants — `IdentityCreate`, `IdentityTopUp`,
2220    /// `AddressFundingFromAssetLock`, `ShieldFromAssetLock` — where the
2221    /// wrapper signature is the asset-lock proof signed by the credit
2222    /// output's Core key.
2223    ///
2224    /// For identity-signed variants (`DataContractCreate`, `Batch`,
2225    /// `IdentityCreditTransfer`, etc.) the wrapper signature is an
2226    /// identity-key signature paired with a `signature_public_key_id`,
2227    /// and the right external-signer entry point is [`Self::sign_external`]
2228    /// with a [`Signer<IdentityPublicKey>`](crate::identity::signer::Signer).
2229    /// Calling `sign_with_core_signer` on such a variant compiles and
2230    /// produces a structurally valid 65-byte signature, but the signature
2231    /// is **semantically meaningless** — Platform validation will reject
2232    /// the transition because the signature doesn't match the expected
2233    /// identity public key and `signature_public_key_id` isn't set. The
2234    /// same caveat applies to misusing `sign_by_private_key`, the sibling
2235    /// primitive — both rely on the caller passing a key the wrapper
2236    /// signature is *meant* to carry.
2237    ///
2238    /// # Wire-format parity with `sign_by_private_key`
2239    ///
2240    /// The byte layout of the stored signature mirrors
2241    /// `dashcore::signer::sign`:
2242    ///
2243    /// 1. `digest = double_sha256(self.signable_bytes()?)`
2244    /// 2. `signer.sign_ecdsa(path, digest).await` → non-recoverable
2245    ///    `(EcdsaSignature, EcdsaPublicKey)`.
2246    /// 3. Recover the recovery id by trying all four candidates against the
2247    ///    returned public key. Byte parity requires a signer that uses the
2248    ///    same deterministic nonce without low-R grinding.
2249    /// 4. Serialise as a 65-byte compact recoverable signature with the
2250    ///    `compressed` prefix convention used by Core signatures — i.e.
2251    ///    `[recovery_id + 27 + 4, r (32) || s (32)]`.
2252    ///
2253    /// # Errors
2254    ///
2255    /// - Returns [`ProtocolError::ExternalSignerError`] wrapping the signer's
2256    ///   `Display` error when the underlying signer fails.
2257    /// - Returns [`ProtocolError::ExternalSignerError`] if no recovery id
2258    ///   matches the public key returned by the signer — this should be
2259    ///   unreachable for a conformant signer (invariant violation by a
2260    ///   non-conformant signer) but is surfaced rather than panicked on.
2261    /// - Returns [`ProtocolError::Generic`] if the SHA-256 transform did not
2262    ///   yield a 32-byte digest (defensive — should never happen).
2263    /// - Returns [`ProtocolError::InvalidVerificationWrongNumberOfElements`] if
2264    ///   `set_signature` rejects the result (matches `sign_by_private_key`).
2265    #[cfg(all(feature = "state-transition-signing", feature = "core_key_wallet"))]
2266    pub async fn sign_with_core_signer<S: ::key_wallet::signer::Signer>(
2267        &mut self,
2268        path: &::key_wallet::bip32::DerivationPath,
2269        signer: &S,
2270    ) -> Result<(), ProtocolError> {
2271        use dashcore::crypto::ecdsa::EcdsaRecSignature;
2272        use dashcore::signer::double_sha;
2273        use secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
2274        use secp256k1::Message;
2275
2276        let data = self.signable_bytes()?;
2277        // Pre-image transform matches `dashcore::signer::sign`: double-SHA256
2278        // of the signable bytes is the actual ECDSA message digest.
2279        let data_hash = double_sha(&data);
2280        let digest: [u8; 32] = data_hash.as_slice().try_into().map_err(|_| {
2281            ProtocolError::Generic("double_sha did not return 32 bytes".to_string())
2282        })?;
2283
2284        let (signature, public_key) = signer
2285            .sign_ecdsa(path, digest)
2286            .await
2287            .map_err(|e| ProtocolError::ExternalSignerError(format!("signer failed: {}", e)))?;
2288
2289        // The signer returns a non-recoverable signature. The legacy path
2290        // stores a 65-byte recoverable compact signature, so we brute-force
2291        // the recovery id (0..3) by reconstructing a `RecoverableSignature`
2292        // and comparing the recovered public key with the one the signer
2293        // returned. Byte parity with `dashcore::signer::sign` requires the
2294        // same deterministic nonce without low-R grinding, as well as low-S
2295        // normalization. Core `sign_recoverable` meets that contract; `sign`
2296        // performs low-R grinding and may produce different bytes.
2297        let compact_64 = signature.to_bytes();
2298        let msg = Message::from_digest(digest);
2299
2300        let mut found: Option<RecoverableSignature> = None;
2301        for id in 0..4i32 {
2302            let recid = match RecoveryId::try_from(id) {
2303                Ok(r) => r,
2304                Err(_) => continue,
2305            };
2306            let candidate = match RecoverableSignature::from_compact(&compact_64, recid) {
2307                Ok(s) => s,
2308                Err(_) => continue,
2309            };
2310            if let Ok(recovered) = candidate.recover_ecdsa(msg) {
2311                if recovered == public_key.into() {
2312                    found = Some(candidate);
2313                    break;
2314                }
2315            }
2316        }
2317        let recoverable = found.ok_or_else(|| {
2318            // Invariant violation by a non-conformant signer: the
2319            // signature returned does not correspond to the public
2320            // key the signer claims. Surface as ExternalSignerError
2321            // (NOT Generic) so callers can distinguish signer-side
2322            // failures from protocol-level invariants.
2323            ProtocolError::ExternalSignerError(
2324                "signer returned a signature whose recovery id does not match the returned public key".to_string(),
2325            )
2326        })?;
2327
2328        // Compressed-pubkey convention matches `dashcore::signer::sign`, which
2329        // always passes `true` regardless of the underlying key encoding. The
2330        // signer's `sign_ecdsa` returns the compressed `secp256k1::PublicKey`,
2331        // so this is consistent.
2332        let (recovery_id, _) = recoverable.serialize_compact();
2333        let compact_65 = EcdsaRecSignature::from_parts(
2334            recoverable.to_standard().into(),
2335            recovery_id.to_u8(),
2336            true,
2337        )
2338        .map_err(|e| ProtocolError::ExternalSignerError(e.to_string()))?
2339        .to_bytes();
2340
2341        if !self.set_signature(compact_65.to_vec().into()) {
2342            return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
2343                needed: self.required_number_of_private_keys(),
2344                using: 1,
2345                msg: "failed to set ECDSA signature",
2346            });
2347        }
2348        Ok(())
2349    }
2350
2351    #[cfg(feature = "state-transition-validation")]
2352    fn verify_ecdsa_hash_160_signature_by_public_key_hash(
2353        &self,
2354        public_key_hash: &[u8],
2355    ) -> Result<(), ProtocolError> {
2356        let Some(signature) = self.signature() else {
2357            return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
2358                needed: self.required_number_of_private_keys(),
2359                using: 1,
2360                msg: "This state transition type should a single signature",
2361            });
2362        };
2363        if signature.is_empty() {
2364            return Err(ProtocolError::StateTransitionIsNotSignedError(
2365                StateTransitionIsNotSignedError::new(self.clone()),
2366            ));
2367        }
2368        let data = self.signable_bytes()?;
2369        let data_hash = double_sha(data);
2370        signer::verify_hash_signature(&data_hash, signature.as_slice(), public_key_hash).map_err(
2371            |e| {
2372                ProtocolError::from(ConsensusError::SignatureError(
2373                    SignatureError::InvalidStateTransitionSignatureError(
2374                        InvalidStateTransitionSignatureError::new(e.to_string()),
2375                    ),
2376                ))
2377            },
2378        )
2379    }
2380
2381    #[cfg(feature = "state-transition-validation")]
2382    /// Verifies an ECDSA signature with the public key
2383    fn verify_ecdsa_signature_by_public_key(&self, public_key: &[u8]) -> Result<(), ProtocolError> {
2384        let Some(signature) = self.signature() else {
2385            return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
2386                needed: self.required_number_of_private_keys(),
2387                using: 1,
2388                msg: "This state transition type should a single signature",
2389            });
2390        };
2391        if signature.is_empty() {
2392            return Err(ProtocolError::StateTransitionIsNotSignedError(
2393                StateTransitionIsNotSignedError::new(self.clone()),
2394            ));
2395        }
2396        let data = self.signable_bytes()?;
2397        signer::verify_data_signature(&data, signature.as_slice(), public_key).map_err(|e| {
2398            // TODO: it shouldn't respond with consensus error
2399
2400            ProtocolError::from(ConsensusError::SignatureError(
2401                SignatureError::InvalidStateTransitionSignatureError(
2402                    InvalidStateTransitionSignatureError::new(e.to_string()),
2403                ),
2404            ))
2405        })
2406    }
2407
2408    #[cfg(feature = "state-transition-validation")]
2409    /// Verifies a BLS signature with the public key: `Ok(false)` when the signature is well
2410    /// formed but does not verify, an error when the key or the signature cannot be read
2411    fn verify_bls_signature_by_public_key<T: BlsModule>(
2412        &self,
2413        public_key: &[u8],
2414        bls: &T,
2415    ) -> Result<bool, ProtocolError> {
2416        let Some(signature) = self.signature() else {
2417            return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
2418                needed: self.required_number_of_private_keys(),
2419                using: 1,
2420                msg: "This state transition type should a single signature",
2421            });
2422        };
2423        if signature.is_empty() {
2424            return Err(ProtocolError::StateTransitionIsNotSignedError(
2425                StateTransitionIsNotSignedError::new(self.clone()),
2426            ));
2427        }
2428
2429        let data = self.signable_bytes()?;
2430
2431        bls.verify_signature(signature.as_slice(), &data, public_key)
2432            .map_err(|e| {
2433                // TODO: it shouldn't respond with consensus error
2434                ProtocolError::from(ConsensusError::SignatureError(
2435                    SignatureError::InvalidStateTransitionSignatureError(
2436                        InvalidStateTransitionSignatureError::new(e.to_string()),
2437                    ),
2438                ))
2439            })
2440    }
2441}
2442
2443#[cfg(feature = "state-transition-validation")]
2444impl StateTransitionStructureValidation for StateTransition {
2445    fn validate_structure(
2446        &self,
2447        platform_version: &PlatformVersion,
2448    ) -> crate::validation::SimpleConsensusValidationResult {
2449        match self {
2450            StateTransition::DataContractCreate(_)
2451            | StateTransition::DataContractUpdate(_)
2452            | StateTransition::ContractUserModeration(_)
2453            | StateTransition::ContractFeeClaim(_)
2454            | StateTransition::Batch(_)
2455            | StateTransition::IdentityCreate(_)
2456            | StateTransition::IdentityTopUp(_)
2457            | StateTransition::IdentityCreditWithdrawal(_)
2458            | StateTransition::IdentityUpdate(_)
2459            | StateTransition::IdentityKeyLimitsUpdate(_)
2460            | StateTransition::IdentityCreditTransfer(_)
2461            | StateTransition::MasternodeVote(_) => {
2462                crate::validation::SimpleConsensusValidationResult::new_with_error(
2463                    UnsupportedFeatureError::new(
2464                        "structure validation for identity-based state transitions".to_string(),
2465                        platform_version.protocol_version,
2466                    )
2467                    .into(),
2468                )
2469            }
2470            StateTransition::IdentityCreditTransferToAddresses(transition) => {
2471                transition.validate_structure(platform_version)
2472            }
2473            StateTransition::IdentityCreateFromAddresses(transition) => {
2474                transition.validate_structure(platform_version)
2475            }
2476            StateTransition::IdentityTopUpFromAddresses(transition) => {
2477                transition.validate_structure(platform_version)
2478            }
2479            StateTransition::AddressFundsTransfer(transition) => {
2480                transition.validate_structure(platform_version)
2481            }
2482            StateTransition::AddressFundingFromAssetLock(transition) => {
2483                transition.validate_structure(platform_version)
2484            }
2485            StateTransition::AddressCreditWithdrawal(transition) => {
2486                transition.validate_structure(platform_version)
2487            }
2488            StateTransition::Shield(transition) => transition.validate_structure(platform_version),
2489            StateTransition::ShieldedTransfer(transition) => {
2490                transition.validate_structure(platform_version)
2491            }
2492            StateTransition::Unshield(transition) => {
2493                transition.validate_structure(platform_version)
2494            }
2495            StateTransition::IdentityTopUpFromShieldedPool(transition) => {
2496                transition.validate_structure(platform_version)
2497            }
2498            StateTransition::TokenShieldedTransferWithShieldedFee(transition) => {
2499                transition.validate_structure(platform_version)
2500            }
2501            StateTransition::TokenUnshieldWithShieldedFee(transition) => {
2502                transition.validate_structure(platform_version)
2503            }
2504            StateTransition::TokenPurchaseFromShieldedPool(transition) => {
2505                transition.validate_structure(platform_version)
2506            }
2507            StateTransition::ShieldFromAssetLock(transition) => {
2508                transition.validate_structure(platform_version)
2509            }
2510            StateTransition::ShieldedWithdrawal(transition) => {
2511                transition.validate_structure(platform_version)
2512            }
2513            StateTransition::IdentityCreateFromShieldedPool(transition) => {
2514                transition.validate_structure(platform_version)
2515            }
2516            StateTransition::ShieldFromIdentity(transition) => {
2517                transition.validate_structure(platform_version)
2518            }
2519        }
2520    }
2521}
2522
2523#[cfg(test)]
2524mod tests {
2525    use super::*;
2526
2527    /// At every protocol version the limit itself passes, and one byte more is refused with both
2528    /// numbers reported as they are.
2529    #[test]
2530    fn should_refuse_only_a_transition_larger_than_the_limit() {
2531        for protocol_version in 1..=LATEST_VERSION {
2532            let platform_version = PlatformVersion::get(protocol_version).expect("known version");
2533            let max_size = platform_version.system_limits.max_state_transition_size;
2534
2535            assert!(validate_state_transition_size(0, platform_version).is_ok());
2536            assert!(validate_state_transition_size(max_size, platform_version).is_ok());
2537            let error = validate_state_transition_size(max_size + 1, platform_version)
2538                .expect_err("one byte over the limit is refused");
2539            assert_eq!(error.actual_size_bytes(), max_size + 1);
2540            assert_eq!(error.max_size_bytes(), max_size);
2541        }
2542    }
2543
2544    // -----------------------------------------------------------------------
2545    // StateTransitionSigningOptions tests
2546    // -----------------------------------------------------------------------
2547
2548    #[test]
2549    fn test_signing_options_default() {
2550        let opts = StateTransitionSigningOptions::default();
2551        assert!(!opts.allow_signing_with_any_security_level);
2552        assert!(!opts.allow_signing_with_any_purpose);
2553    }
2554
2555    #[test]
2556    fn test_signing_options_equality() {
2557        let a = StateTransitionSigningOptions {
2558            allow_signing_with_any_security_level: true,
2559            allow_signing_with_any_purpose: false,
2560        };
2561        let b = StateTransitionSigningOptions {
2562            allow_signing_with_any_security_level: true,
2563            allow_signing_with_any_purpose: false,
2564        };
2565        assert_eq!(a, b);
2566    }
2567
2568    #[test]
2569    fn test_signing_options_inequality() {
2570        let a = StateTransitionSigningOptions {
2571            allow_signing_with_any_security_level: true,
2572            allow_signing_with_any_purpose: false,
2573        };
2574        let b = StateTransitionSigningOptions {
2575            allow_signing_with_any_security_level: false,
2576            allow_signing_with_any_purpose: false,
2577        };
2578        assert_ne!(a, b);
2579    }
2580
2581    #[test]
2582    #[allow(clippy::clone_on_copy)]
2583    fn test_signing_options_clone() {
2584        let original = StateTransitionSigningOptions {
2585            allow_signing_with_any_security_level: true,
2586            allow_signing_with_any_purpose: true,
2587        };
2588        let cloned = original.clone();
2589        assert_eq!(original, cloned);
2590    }
2591
2592    #[test]
2593    fn test_signing_options_copy() {
2594        let original = StateTransitionSigningOptions {
2595            allow_signing_with_any_security_level: true,
2596            allow_signing_with_any_purpose: false,
2597        };
2598        let copied = original;
2599        assert_eq!(original, copied);
2600    }
2601
2602    #[test]
2603    fn test_signing_options_debug() {
2604        let opts = StateTransitionSigningOptions::default();
2605        let debug_str = format!("{:?}", opts);
2606        assert!(debug_str.contains("StateTransitionSigningOptions"));
2607        assert!(debug_str.contains("allow_signing_with_any_security_level"));
2608        assert!(debug_str.contains("allow_signing_with_any_purpose"));
2609    }
2610
2611    // -----------------------------------------------------------------------
2612    // StateTransition enum accessor / mutator / classification tests
2613    //
2614    // These exercise the non-trivial match arms across the large enum, using
2615    // the IdentityCreditTransfer, MasternodeVote, IdentityCreditWithdrawal and
2616    // DataContractCreate variants as representative signed / unsigned /
2617    // voting / contract cases. They intentionally do NOT use `sign`/`verify`
2618    // (those go through BLS/ECDSA and have their own coverage elsewhere).
2619    // -----------------------------------------------------------------------
2620    use crate::identity::core_script::CoreScript;
2621    use crate::identity::{Purpose, SecurityLevel};
2622    use crate::prelude::Identifier;
2623    use crate::state_transition::identity_credit_transfer_transition::v0::IdentityCreditTransferTransitionV0;
2624    use crate::state_transition::identity_credit_transfer_transition::IdentityCreditTransferTransition;
2625    use crate::state_transition::identity_credit_withdrawal_transition::v0::IdentityCreditWithdrawalTransitionV0;
2626    use crate::state_transition::identity_credit_withdrawal_transition::IdentityCreditWithdrawalTransition;
2627    use crate::state_transition::masternode_vote_transition::v0::MasternodeVoteTransitionV0;
2628    use crate::state_transition::masternode_vote_transition::MasternodeVoteTransition;
2629    use crate::withdrawal::Pooling;
2630
2631    fn sample_transfer_st() -> StateTransition {
2632        let v0 = IdentityCreditTransferTransitionV0 {
2633            identity_id: Identifier::from([1u8; 32]),
2634            recipient_id: Identifier::from([2u8; 32]),
2635            amount: 1_000,
2636            nonce: 7,
2637            user_fee_increase: 3,
2638            signature_public_key_id: 11,
2639            signature: BinaryData::new(vec![0u8; 65]),
2640        };
2641        StateTransition::IdentityCreditTransfer(IdentityCreditTransferTransition::V0(v0))
2642    }
2643
2644    fn sample_masternode_vote_st() -> StateTransition {
2645        let v0 = MasternodeVoteTransitionV0 {
2646            pro_tx_hash: Identifier::from([3u8; 32]),
2647            voter_identity_id: Identifier::from([4u8; 32]),
2648            vote: Default::default(),
2649            nonce: 2,
2650            signature_public_key_id: 5,
2651            signature: BinaryData::new(vec![9u8; 10]),
2652        };
2653        StateTransition::MasternodeVote(MasternodeVoteTransition::V0(v0))
2654    }
2655
2656    fn sample_withdrawal_st() -> StateTransition {
2657        let v0 = IdentityCreditWithdrawalTransitionV0 {
2658            identity_id: Identifier::from([5u8; 32]),
2659            amount: 42,
2660            core_fee_per_byte: 1,
2661            pooling: Pooling::Never,
2662            output_script: CoreScript::from_bytes(vec![0x76, 0xa9]),
2663            nonce: 4,
2664            user_fee_increase: 1,
2665            signature_public_key_id: 3,
2666            signature: BinaryData::new(vec![8u8; 65]),
2667        };
2668        StateTransition::IdentityCreditWithdrawal(IdentityCreditWithdrawalTransition::V0(v0))
2669    }
2670
2671    #[test]
2672    fn test_name_returns_variant_names() {
2673        assert_eq!(sample_transfer_st().name(), "IdentityCreditTransfer");
2674        assert_eq!(sample_masternode_vote_st().name(), "MasternodeVote");
2675        assert_eq!(sample_withdrawal_st().name(), "IdentityCreditWithdrawal");
2676    }
2677
2678    #[test]
2679    fn test_state_transition_type_matches_variant() {
2680        assert_eq!(
2681            sample_transfer_st().state_transition_type(),
2682            StateTransitionType::IdentityCreditTransfer
2683        );
2684        assert_eq!(
2685            sample_masternode_vote_st().state_transition_type(),
2686            StateTransitionType::MasternodeVote
2687        );
2688        assert_eq!(
2689            sample_withdrawal_st().state_transition_type(),
2690            StateTransitionType::IdentityCreditWithdrawal
2691        );
2692    }
2693
2694    #[test]
2695    fn test_is_identity_signed_excludes_asset_lock_and_shielded() {
2696        assert!(sample_transfer_st().is_identity_signed());
2697        assert!(sample_masternode_vote_st().is_identity_signed());
2698        assert!(sample_withdrawal_st().is_identity_signed());
2699    }
2700
2701    #[test]
2702    fn test_signature_accessor() {
2703        let st = sample_transfer_st();
2704        let sig = st.signature().expect("transfer should expose signature");
2705        assert_eq!(sig.len(), 65);
2706
2707        let st = sample_masternode_vote_st();
2708        let sig = st.signature().expect("masternode vote has signature");
2709        assert_eq!(sig.as_slice(), &[9u8; 10]);
2710    }
2711
2712    #[test]
2713    fn test_owner_id_accessor() {
2714        let transfer = sample_transfer_st();
2715        assert_eq!(transfer.owner_id(), Some(Identifier::from([1u8; 32])));
2716
2717        let vote = sample_masternode_vote_st();
2718        assert_eq!(vote.owner_id(), Some(Identifier::from([4u8; 32])));
2719
2720        let withdraw = sample_withdrawal_st();
2721        assert_eq!(withdraw.owner_id(), Some(Identifier::from([5u8; 32])));
2722    }
2723
2724    #[test]
2725    fn test_signature_public_key_id_accessor() {
2726        assert_eq!(sample_transfer_st().signature_public_key_id(), Some(11));
2727        assert_eq!(
2728            sample_masternode_vote_st().signature_public_key_id(),
2729            Some(5)
2730        );
2731        assert_eq!(sample_withdrawal_st().signature_public_key_id(), Some(3));
2732    }
2733
2734    #[test]
2735    fn test_user_fee_increase_for_various_variants() {
2736        // Transfer exposes its internal value.
2737        assert_eq!(sample_transfer_st().user_fee_increase(), 3);
2738        // Masternode vote returns 0 unconditionally.
2739        assert_eq!(sample_masternode_vote_st().user_fee_increase(), 0);
2740        // Withdrawal exposes its internal value.
2741        assert_eq!(sample_withdrawal_st().user_fee_increase(), 1);
2742    }
2743
2744    #[test]
2745    fn test_set_signature_returns_true_for_supported() {
2746        let mut st = sample_transfer_st();
2747        let ok = st.set_signature(BinaryData::new(vec![0xaa; 65]));
2748        assert!(ok);
2749        assert_eq!(st.signature().unwrap().as_slice(), &[0xaa; 65]);
2750    }
2751
2752    #[test]
2753    fn test_set_user_fee_increase_updates_value() {
2754        let mut st = sample_transfer_st();
2755        st.set_user_fee_increase(42);
2756        assert_eq!(st.user_fee_increase(), 42);
2757
2758        // Masternode vote ignores the setter (documented no-op) — still reads 0.
2759        let mut vote = sample_masternode_vote_st();
2760        vote.set_user_fee_increase(99);
2761        assert_eq!(vote.user_fee_increase(), 0);
2762    }
2763
2764    #[test]
2765    fn test_set_signature_public_key_id() {
2766        let mut st = sample_transfer_st();
2767        st.set_signature_public_key_id(1234);
2768        assert_eq!(st.signature_public_key_id(), Some(1234));
2769    }
2770
2771    #[test]
2772    fn test_required_number_of_private_keys_default() {
2773        // Non asset-lock transitions always require 1 key.
2774        assert_eq!(sample_transfer_st().required_number_of_private_keys(), 1);
2775        assert_eq!(
2776            sample_masternode_vote_st().required_number_of_private_keys(),
2777            1
2778        );
2779        assert_eq!(sample_withdrawal_st().required_number_of_private_keys(), 1);
2780    }
2781
2782    #[test]
2783    fn test_inputs_none_for_legacy_variants() {
2784        // All these variants have no PlatformAddress inputs.
2785        assert!(sample_transfer_st().inputs().is_none());
2786        assert!(sample_masternode_vote_st().inputs().is_none());
2787        assert!(sample_withdrawal_st().inputs().is_none());
2788    }
2789
2790    #[test]
2791    fn test_active_version_range_legacy_transitions() {
2792        // These all report ALL_VERSIONS per the mod.rs table.
2793        assert_eq!(sample_transfer_st().active_version_range(), ALL_VERSIONS);
2794        assert_eq!(
2795            sample_masternode_vote_st().active_version_range(),
2796            ALL_VERSIONS
2797        );
2798        assert_eq!(sample_withdrawal_st().active_version_range(), ALL_VERSIONS);
2799    }
2800
2801    /// Both vote kinds can name a yes/no poll: a yes/no vote, and a resource vote (which
2802    /// protocol version 15 refuses as naming no contested poll). Before 15 neither decodes, so
2803    /// the masternode vote validators selected there never meet a yes/no poll.
2804    #[test]
2805    fn should_make_a_masternode_vote_on_a_yes_no_poll_active_from_protocol_version_15() {
2806        use crate::serialization::PlatformSerializable;
2807        use crate::voting::vote_choices::resource_vote_choice::ResourceVoteChoice;
2808        use crate::voting::vote_choices::yes_no_abstain_vote_choice::YesNoAbstainVoteChoice;
2809        use crate::voting::vote_polls::yes_no_vote_poll::{
2810            YesNoMinimumVotingPower, YesNoVotePoll, YesNoVotePollPurpose,
2811        };
2812        use crate::voting::votes::resource_vote::v0::ResourceVoteV0;
2813        use crate::voting::votes::resource_vote::ResourceVote;
2814        use crate::voting::votes::yes_no_vote::v0::YesNoVoteV0;
2815        use crate::voting::votes::yes_no_vote::YesNoVote;
2816
2817        let vote_poll = YesNoVotePoll {
2818            purpose: YesNoVotePollPurpose::DecisionProposal {
2819                document_id: Identifier::from([7u8; 32]),
2820            },
2821            supermajority_numerator: 2,
2822            supermajority_denominator: 3,
2823            minimum_voting_power: YesNoMinimumVotingPower::Absolute { voting_power: 400 },
2824        };
2825        let votes = [
2826            Vote::YesNoVote(YesNoVote::V0(YesNoVoteV0 {
2827                vote_poll_id: vote_poll
2828                    .unique_id(PlatformVersion::latest())
2829                    .expect("poll id"),
2830                vote_choice: YesNoAbstainVoteChoice::Yes,
2831            })),
2832            Vote::ResourceVote(ResourceVote::V0(ResourceVoteV0 {
2833                vote_poll: VotePoll::YesNoVotePoll(vote_poll),
2834                resource_vote_choice: ResourceVoteChoice::Abstain,
2835            })),
2836        ];
2837        let version_14 = PlatformVersion::get(14).expect("protocol version 14");
2838        let version_15 = PlatformVersion::get(15).expect("protocol version 15");
2839
2840        for vote in votes {
2841            let v0 = MasternodeVoteTransitionV0 {
2842                pro_tx_hash: Identifier::from([3u8; 32]),
2843                voter_identity_id: Identifier::from([4u8; 32]),
2844                vote,
2845                nonce: 2,
2846                signature_public_key_id: 5,
2847                signature: BinaryData::new(vec![9u8; 10]),
2848            };
2849            let state_transition =
2850                StateTransition::MasternodeVote(MasternodeVoteTransition::V0(v0));
2851            assert_eq!(
2852                state_transition.active_version_range(),
2853                YES_NO_VOTE_POLL_INITIAL_PROTOCOL_VERSION..=LATEST_VERSION
2854            );
2855
2856            let bytes = state_transition
2857                .serialize_to_bytes()
2858                .expect("expected to serialize the vote");
2859            assert!(matches!(
2860                StateTransition::deserialize_from_bytes_untrusted_in_version(&bytes, version_14),
2861                Err(ProtocolError::StateTransitionError(
2862                    StateTransitionIsNotActiveError { .. }
2863                ))
2864            ));
2865            assert_eq!(
2866                StateTransition::deserialize_from_bytes_untrusted_in_version(&bytes, version_15)
2867                    .expect("expected the vote to be active at 15"),
2868                state_transition
2869            );
2870        }
2871    }
2872
2873    #[test]
2874    fn test_unique_identifiers_non_empty() {
2875        let ids = sample_transfer_st().unique_identifiers();
2876        assert_eq!(ids.len(), 1);
2877        assert!(!ids[0].is_empty());
2878    }
2879
2880    #[test]
2881    fn test_required_asset_lock_balance_rejects_non_asset_lock() {
2882        let platform_version = PlatformVersion::latest();
2883        let st = sample_transfer_st();
2884        let err = st
2885            .required_asset_lock_balance_for_processing_start(platform_version)
2886            .expect_err("credit transfer is not an asset lock state transition");
2887        match err {
2888            ProtocolError::CorruptedCodeExecution(msg) => {
2889                assert!(
2890                    msg.contains("is not an asset lock transaction"),
2891                    "unexpected error message: {msg}"
2892                );
2893            }
2894            other => panic!("expected CorruptedCodeExecution, got {other:?}"),
2895        }
2896    }
2897
2898    #[test]
2899    fn test_security_level_requirement_for_transfer() {
2900        // IdentityCreditTransfer requires CRITICAL at TRANSFER purpose.
2901        let st = sample_transfer_st();
2902        let levels = st
2903            .security_level_requirement(Purpose::TRANSFER)
2904            .expect("transfer state transition should return a requirement");
2905        assert_eq!(levels, vec![SecurityLevel::CRITICAL]);
2906    }
2907
2908    #[test]
2909    fn test_purpose_requirement_for_transfer() {
2910        let st = sample_transfer_st();
2911        let purposes = st
2912            .purpose_requirement()
2913            .expect("transfer state transition should have a purpose");
2914        assert_eq!(purposes, vec![Purpose::TRANSFER]);
2915    }
2916
2917    #[test]
2918    fn test_optional_asset_lock_proof_none_for_transfer() {
2919        let st = sample_transfer_st();
2920        assert!(st.optional_asset_lock_proof().is_none());
2921    }
2922
2923    // -----------------------------------------------------------------------
2924    // Enum construction: From<V0 / outer enum> → StateTransition
2925    // -----------------------------------------------------------------------
2926
2927    #[test]
2928    fn test_from_outer_enum_into_state_transition() {
2929        let outer: IdentityCreditTransferTransition =
2930            IdentityCreditTransferTransition::V0(IdentityCreditTransferTransitionV0::default());
2931        let st: StateTransition = outer.into();
2932        assert!(matches!(st, StateTransition::IdentityCreditTransfer(_)));
2933    }
2934
2935    #[test]
2936    fn test_from_masternode_vote_outer_into_state_transition() {
2937        let outer: MasternodeVoteTransition =
2938            MasternodeVoteTransition::V0(MasternodeVoteTransitionV0::default());
2939        let st: StateTransition = outer.into();
2940        assert!(matches!(st, StateTransition::MasternodeVote(_)));
2941    }
2942
2943    // -----------------------------------------------------------------------
2944    // Serialization round-trip: platform serialize / deserialize via enum.
2945    // Exercises the top-level `StateTransition` (de)serialize glue.
2946    // -----------------------------------------------------------------------
2947
2948    #[test]
2949    fn test_state_transition_platform_serialize_roundtrip() {
2950        use crate::serialization::{PlatformDeserializableUntrusted, PlatformSerializable};
2951        let original = sample_transfer_st();
2952        let bytes =
2953            PlatformSerializable::serialize_to_bytes(&original).expect("serialize should succeed");
2954        let restored = StateTransition::deserialize_from_bytes_untrusted(&bytes)
2955            .expect("deserialize should succeed");
2956        assert_eq!(original, restored);
2957    }
2958
2959    #[test]
2960    fn test_deserialize_from_bytes_in_version_succeeds_for_latest() {
2961        use crate::serialization::PlatformSerializable;
2962        let original = sample_transfer_st();
2963        let bytes =
2964            PlatformSerializable::serialize_to_bytes(&original).expect("serialize succeeds");
2965        let restored = StateTransition::deserialize_from_bytes_untrusted_in_version(
2966            &bytes,
2967            PlatformVersion::latest(),
2968        )
2969        .expect("deserialize_from_bytes_in_version should succeed");
2970        assert_eq!(original, restored);
2971    }
2972
2973    #[test]
2974    fn should_refuse_bytes_after_the_transition_only_in_the_exact_in_version_decoder() {
2975        use crate::serialization::PlatformSerializable;
2976        let original = sample_transfer_st();
2977        let bytes =
2978            PlatformSerializable::serialize_to_bytes(&original).expect("serialize succeeds");
2979        let platform_version = PlatformVersion::latest();
2980
2981        assert_eq!(
2982            StateTransition::deserialize_from_bytes_untrusted_exact_in_version(
2983                &bytes,
2984                platform_version
2985            )
2986            .expect("the exact bytes decode"),
2987            original
2988        );
2989
2990        for left_over in [1usize, 100] {
2991            let mut padded = bytes.clone();
2992            padded.extend(std::iter::repeat_n(0xAB, left_over));
2993
2994            assert_eq!(
2995                StateTransition::deserialize_from_bytes_untrusted_in_version(
2996                    &padded,
2997                    platform_version
2998                )
2999                .expect("the loose decoder ignores the suffix"),
3000                original
3001            );
3002            match StateTransition::deserialize_from_bytes_untrusted_exact_in_version(
3003                &padded,
3004                platform_version,
3005            ) {
3006                Err(ProtocolError::PlatformDeserializationError(message)) => assert!(
3007                    message.contains(&format!("{left_over} bytes left over")),
3008                    "unexpected message: {message}"
3009                ),
3010                other => panic!("expected a deserialization error, got {other:?}"),
3011            }
3012        }
3013    }
3014
3015    #[test]
3016    fn test_transaction_id_is_deterministic() {
3017        let st = sample_transfer_st();
3018        let a = st.transaction_id().expect("hash should succeed");
3019        let b = st.transaction_id().expect("hash should succeed");
3020        assert_eq!(a, b);
3021        assert_eq!(a.len(), 32);
3022    }
3023
3024    #[test]
3025    fn test_transaction_id_changes_on_signature_change() {
3026        let mut st = sample_transfer_st();
3027        let before = st.transaction_id().expect("hash should succeed");
3028        st.set_signature(BinaryData::new(vec![0xbb; 65]));
3029        let after = st.transaction_id().expect("hash should succeed");
3030        // Different signatures produce a different serialized form.
3031        assert_ne!(before, after);
3032    }
3033
3034    #[test]
3035    fn test_clone_preserves_inner_state() {
3036        let st = sample_transfer_st();
3037        let cloned = st.clone();
3038        assert_eq!(st, cloned);
3039    }
3040
3041    // -----------------------------------------------------------------------
3042    // Additional coverage: enum arms that weren't previously exercised.
3043    //
3044    // The tests below intentionally target variants the earlier tests did not
3045    // touch (DataContractCreate, DataContractUpdate, Batch, IdentityCreate,
3046    // IdentityTopUp, IdentityUpdate, shielded / address variants) to cover
3047    // the remaining match-arm branches in accessor / mutator / classification
3048    // methods.
3049    // -----------------------------------------------------------------------
3050
3051    use crate::data_contract::serialized_version::DataContractInSerializationFormat;
3052    use crate::state_transition::batch_transition::document_base_transition::v0::DocumentBaseTransitionV0;
3053    use crate::state_transition::batch_transition::document_base_transition::DocumentBaseTransition;
3054    use crate::state_transition::batch_transition::document_delete_transition::{
3055        DocumentDeleteTransition, DocumentDeleteTransitionV0,
3056    };
3057    use crate::state_transition::batch_transition::{BatchTransition, BatchTransitionV0};
3058    use crate::state_transition::data_contract_create_transition::{
3059        DataContractCreateTransition, DataContractCreateTransitionV0,
3060        DataContractCreateTransitionV1,
3061    };
3062    use crate::state_transition::data_contract_update_transition::{
3063        DataContractUpdateTransition, DataContractUpdateTransitionV0,
3064    };
3065    use crate::state_transition::identity_create_transition::v0::IdentityCreateTransitionV0;
3066    use crate::state_transition::identity_create_transition::IdentityCreateTransition;
3067    use crate::state_transition::identity_topup_transition::v0::IdentityTopUpTransitionV0;
3068    use crate::state_transition::identity_topup_transition::IdentityTopUpTransition;
3069    use crate::state_transition::identity_update_transition::v0::IdentityUpdateTransitionV0;
3070    use crate::state_transition::identity_update_transition::IdentityUpdateTransition;
3071    use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0;
3072    use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition;
3073    use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0;
3074    use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition;
3075    use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0;
3076    use crate::state_transition::unshield_transition::UnshieldTransition;
3077
3078    /// Build a DataContractInSerializationFormat from a crate-private v0
3079    /// constructor via the public TryFromPlatformVersioned impl and DataContract V1.
3080    fn sample_data_contract_in_serialization_format() -> DataContractInSerializationFormat {
3081        use platform_version::TryIntoPlatformVersioned;
3082
3083        sample_data_contract()
3084            .try_into_platform_versioned(PlatformVersion::latest())
3085            .expect("expected to serialize a trivial contract")
3086    }
3087
3088    use crate::data_contract::DataContract;
3089
3090    /// A trivial contract with no document type.
3091    fn sample_data_contract() -> DataContract {
3092        use crate::data_contract::config::v0::DataContractConfigV0;
3093        use crate::data_contract::config::DataContractConfig;
3094        use crate::data_contract::v1::DataContractV1;
3095        use std::collections::BTreeMap;
3096
3097        DataContract::V1(DataContractV1 {
3098            id: Identifier::from([9u8; 32]),
3099            version: 1,
3100            owner_id: Identifier::from([7u8; 32]),
3101            document_types: BTreeMap::new(),
3102            config: DataContractConfig::V0(DataContractConfigV0 {
3103                can_be_deleted: false,
3104                readonly: false,
3105                keeps_history: false,
3106                documents_keep_history_contract_default: false,
3107                documents_mutable_contract_default: false,
3108                documents_can_be_deleted_contract_default: false,
3109                requires_identity_encryption_bounded_key: None,
3110                requires_identity_decryption_bounded_key: None,
3111            }),
3112            schema_defs: None,
3113            created_at: None,
3114            updated_at: None,
3115            created_at_block_height: None,
3116            updated_at_block_height: None,
3117            created_at_epoch: None,
3118            updated_at_epoch: None,
3119            groups: BTreeMap::new(),
3120            tokens: BTreeMap::new(),
3121            keywords: Vec::new(),
3122            description: None,
3123        })
3124    }
3125
3126    fn sample_data_contract_create_st() -> StateTransition {
3127        StateTransition::DataContractCreate(DataContractCreateTransition::V0(
3128            DataContractCreateTransitionV0 {
3129                data_contract: sample_data_contract_in_serialization_format(),
3130                identity_nonce: 1,
3131                user_fee_increase: 5,
3132                signature_public_key_id: 2,
3133                signature: BinaryData::new(vec![0xAB; 65]),
3134            },
3135        ))
3136    }
3137
3138    fn sample_data_contract_create_v1_st() -> StateTransition {
3139        StateTransition::DataContractCreate(DataContractCreateTransition::V1(
3140            DataContractCreateTransitionV1 {
3141                data_contract: sample_data_contract_in_serialization_format(),
3142                identity_nonce: 1,
3143                contract_group: None,
3144                contract_group_memberships: vec![],
3145                user_fee_increase: 5,
3146                signature_public_key_id: 2,
3147                signature: BinaryData::new(vec![0xAB; 65]),
3148            },
3149        ))
3150    }
3151
3152    fn sample_data_contract_update_st() -> StateTransition {
3153        StateTransition::DataContractUpdate(DataContractUpdateTransition::V0(
3154            DataContractUpdateTransitionV0 {
3155                identity_contract_nonce: 4,
3156                data_contract: sample_data_contract_in_serialization_format(),
3157                user_fee_increase: 9,
3158                signature_public_key_id: 6,
3159                signature: BinaryData::new(vec![0xCD; 65]),
3160            },
3161        ))
3162    }
3163
3164    #[cfg(all(feature = "state-transition-signing", feature = "bls-signatures"))]
3165    #[test]
3166    fn should_enforce_contract_bounds_before_private_key_signing() {
3167        use crate::consensus::signature::{
3168            ContractBoundedKeyNonBatchError, ContractBoundedKeyOutOfBoundsError,
3169        };
3170        use crate::identity::contract_bounds::ContractBounds;
3171        use crate::identity::identity_public_key::v0::IdentityPublicKeyV0;
3172
3173        let private_key = [1; 32];
3174        let bls = crate::bls::native_bls::NativeBlsModule;
3175        // `sample_batch_st_with_delete` deletes a "preorder" document of contract [2; 32].
3176        let mut key = IdentityPublicKeyV0 {
3177            id: 7,
3178            purpose: Purpose::AUTHENTICATION,
3179            security_level: SecurityLevel::HIGH,
3180            key_type: KeyType::ECDSA_SECP256K1,
3181            data: get_compressed_public_ec_key(&private_key)
3182                .unwrap()
3183                .to_vec()
3184                .into(),
3185            contract_bounds: Some(ContractBounds::SingleContract {
3186                id: Identifier::from([2; 32]),
3187            }),
3188            ..Default::default()
3189        };
3190        sample_batch_st_with_delete()
3191            .sign(&key.clone().into(), &private_key, &bls)
3192            .expect("a batch inside the bounds must sign");
3193        key.contract_bounds = Some(ContractBounds::SingleContractDocumentType {
3194            id: Identifier::from([2; 32]),
3195            document_type_name: "preorder".to_string(),
3196        });
3197        sample_batch_st_with_delete()
3198            .sign(&key.clone().into(), &private_key, &bls)
3199            .expect("a batch of the bound document type must sign");
3200
3201        let err = sample_transfer_st()
3202            .sign(&key.clone().into(), &private_key, &bls)
3203            .unwrap_err();
3204        assert!(matches!(err, ProtocolError::ConsensusError(error)
3205            if *error == ContractBoundedKeyNonBatchError::new(key.id).into()));
3206
3207        for denied in [
3208            ContractBounds::SingleContract {
3209                id: Identifier::from([3; 32]),
3210            },
3211            ContractBounds::SingleContractDocumentType {
3212                id: Identifier::from([2; 32]),
3213                document_type_name: "other".to_string(),
3214            },
3215        ] {
3216            key.contract_bounds = Some(denied.clone());
3217            let mut transition = sample_batch_st_with_delete();
3218            let original = transition.clone();
3219            let err = transition
3220                .sign(&key.clone().into(), &private_key, &bls)
3221                .unwrap_err();
3222            assert!(
3223                matches!(err, ProtocolError::ConsensusError(error)
3224                if *error == ContractBoundedKeyOutOfBoundsError::new(key.id).into()),
3225                "{denied:?}"
3226            );
3227            assert_eq!(
3228                transition, original,
3229                "rejection must preserve the transition"
3230            );
3231        }
3232
3233        key.contract_bounds = None;
3234        sample_batch_st_with_delete()
3235            .sign(&key.into(), &private_key, &bls)
3236            .expect("unbounded keys must still sign");
3237    }
3238
3239    // Generation 1 of `verify_identity_signed_signature` (protocol version 14) refuses a
3240    // BLS12_381 signature that does not verify; generation 0 keeps accepting it for replay.
3241    #[cfg(all(feature = "state-transition-signing", feature = "bls-signatures"))]
3242    #[test]
3243    fn should_refuse_a_bls_signature_that_does_not_verify_from_protocol_version_14() {
3244        use crate::identity::identity_public_key::v0::IdentityPublicKeyV0;
3245
3246        let bls = crate::bls::native_bls::NativeBlsModule;
3247        let private_key = [7; 32];
3248        let key: IdentityPublicKey = IdentityPublicKeyV0 {
3249            id: 11,
3250            purpose: Purpose::TRANSFER,
3251            security_level: SecurityLevel::CRITICAL,
3252            key_type: KeyType::BLS12_381,
3253            data: bls
3254                .private_key_to_public_key(&private_key)
3255                .expect("a valid BLS private key")
3256                .into(),
3257            ..Default::default()
3258        }
3259        .into();
3260
3261        let mut signed = sample_transfer_st();
3262        signed
3263            .sign(&key, &private_key, &bls)
3264            .expect("the key signs the transfer");
3265
3266        // The same transfer signed by another BLS key, and 96 bytes that are not a compressed
3267        // curve point
3268        let mut by_another_key = signed.clone();
3269        by_another_key
3270            .sign_by_private_key(&[8; 32], KeyType::BLS12_381, &bls)
3271            .expect("another key signs the transfer");
3272        let mut not_a_point = signed.clone();
3273        not_a_point.set_signature(BinaryData::new(vec![0; 96]));
3274
3275        let latest = PlatformVersion::latest();
3276        let last_without_the_check = PlatformVersion::get(13).expect("protocol version 13");
3277        for platform_version in [latest, last_without_the_check] {
3278            signed
3279                .verify_identity_signed_signature(&key, &bls, platform_version)
3280                .expect("a signature by the key verifies");
3281        }
3282
3283        for (transition, label) in [
3284            (by_another_key, "signed by another key"),
3285            (not_a_point, "not a curve point"),
3286        ] {
3287            let error = transition
3288                .verify_identity_signed_signature(&key, &bls, latest)
3289                .expect_err(label);
3290            assert!(
3291                matches!(&error, ProtocolError::ConsensusError(consensus_error)
3292                if matches!(&**consensus_error, ConsensusError::SignatureError(
3293                    SignatureError::InvalidStateTransitionSignatureError(e)
3294                ) if e.message() == "BLS12_381 signature does not verify")),
3295                "{label}: {error:?}"
3296            );
3297            transition
3298                .verify_identity_signed_signature(&key, &bls, last_without_the_check)
3299                .expect("protocol version 13 replays as it ran");
3300        }
3301    }
3302
3303    fn sample_batch_st_with_delete() -> StateTransition {
3304        let base = DocumentBaseTransition::V0(DocumentBaseTransitionV0 {
3305            id: Identifier::from([1u8; 32]),
3306            identity_contract_nonce: 3,
3307            document_type_name: "preorder".to_string(),
3308            data_contract_id: Identifier::from([2u8; 32]),
3309        });
3310        let delete =
3311            DocumentTransition::Delete(DocumentDeleteTransition::V0(DocumentDeleteTransitionV0 {
3312                base,
3313            }));
3314        StateTransition::Batch(BatchTransition::V0(BatchTransitionV0 {
3315            owner_id: Identifier::from([8u8; 32]),
3316            transitions: vec![delete],
3317            user_fee_increase: 2,
3318            signature_public_key_id: 7,
3319            signature: BinaryData::new(vec![0xEE; 65]),
3320        }))
3321    }
3322
3323    fn sample_batch_st_with_base(base: DocumentBaseTransition) -> StateTransition {
3324        let delete =
3325            DocumentTransition::Delete(DocumentDeleteTransition::V0(DocumentDeleteTransitionV0 {
3326                base,
3327            }));
3328        StateTransition::Batch(BatchTransition::V0(BatchTransitionV0 {
3329            owner_id: Identifier::from([8u8; 32]),
3330            transitions: vec![delete],
3331            user_fee_increase: 2,
3332            signature_public_key_id: 7,
3333            signature: BinaryData::new(vec![0xEE; 65]),
3334        }))
3335    }
3336
3337    // Format version 2 of a contract is the stored format, which only state holds. Software
3338    // without the format cannot decode a transition carrying one, so a contract transition's own
3339    // decoding refuses one too: every decoder of a transition, bincode at every protocol version
3340    // and serde.
3341    #[test]
3342    fn should_refuse_a_contract_transition_carrying_the_stored_format_in_every_decoder() {
3343        use crate::serialization::{
3344            PlatformDeserializableTrusted, PlatformDeserializableUntrusted, PlatformSerializable,
3345        };
3346        use crate::state_transition::state_transition_factory::StateTransitionFactory;
3347
3348        // Refused at the contract's format tag, as an unknown variant
3349        fn assert_refused<T: std::fmt::Debug>(decoded: Result<T, ProtocolError>, decoder: &str) {
3350            let refusal = format!("{decoded:?}");
3351            assert!(
3352                decoded.is_err()
3353                    && refusal.contains("UnexpectedVariant")
3354                    && refusal.contains("TransitionContractFormat"),
3355                "{decoder}: {refusal}"
3356            );
3357        }
3358
3359        let stored = DataContractInSerializationFormat::try_from_data_contract_for_storage(
3360            &sample_data_contract(),
3361            PlatformVersion::latest(),
3362        )
3363        .expect("expected the contract to compile");
3364        assert!(matches!(stored, DataContractInSerializationFormat::V2(_)));
3365
3366        let transitions = [
3367            StateTransition::DataContractCreate(DataContractCreateTransition::V0(
3368                DataContractCreateTransitionV0 {
3369                    data_contract: stored.clone(),
3370                    identity_nonce: 1,
3371                    user_fee_increase: 0,
3372                    signature_public_key_id: 2,
3373                    signature: BinaryData::new(vec![0xAB; 65]),
3374                },
3375            )),
3376            StateTransition::DataContractCreate(DataContractCreateTransition::V1(
3377                DataContractCreateTransitionV1 {
3378                    data_contract: stored.clone(),
3379                    identity_nonce: 1,
3380                    contract_group: None,
3381                    contract_group_memberships: vec![],
3382                    user_fee_increase: 0,
3383                    signature_public_key_id: 2,
3384                    signature: BinaryData::new(vec![0xAB; 65]),
3385                },
3386            )),
3387            StateTransition::DataContractUpdate(DataContractUpdateTransition::V0(
3388                DataContractUpdateTransitionV0 {
3389                    identity_contract_nonce: 4,
3390                    data_contract: stored,
3391                    user_fee_increase: 0,
3392                    signature_public_key_id: 6,
3393                    signature: BinaryData::new(vec![0xCD; 65]),
3394                },
3395            )),
3396        ];
3397        for transition in transitions {
3398            assert!(transition.active_version_range().is_empty());
3399            let bytes =
3400                PlatformSerializable::serialize_to_bytes(&transition).expect("serialize succeeds");
3401            assert_refused(
3402                StateTransition::deserialize_from_bytes_trusted(&bytes),
3403                "trusted",
3404            );
3405            assert_refused(
3406                StateTransition::deserialize_from_bytes_untrusted(&bytes),
3407                "untrusted",
3408            );
3409            assert_refused(
3410                StateTransition::deserialize_many_untrusted(std::slice::from_ref(&bytes)),
3411                "many",
3412            );
3413            assert_refused(StateTransitionFactory.create_from_buffer(&bytes), "factory");
3414            let untagged = match &transition {
3415                StateTransition::DataContractCreate(create) => {
3416                    PlatformSerializable::serialize_to_bytes(create)
3417                }
3418                StateTransition::DataContractUpdate(update) => {
3419                    PlatformSerializable::serialize_to_bytes(update)
3420                }
3421                other => panic!("expected a contract transition, got {other:?}"),
3422            }
3423            .expect("serialize succeeds");
3424            assert_refused(
3425                StateTransition::deserialize_untagged_untrusted_exact(
3426                    transition.state_transition_type(),
3427                    &untagged,
3428                ),
3429                "untagged",
3430            );
3431            // Serde (JSON, platform values) refuses it at its `$formatVersion`
3432            #[cfg(feature = "serde-conversion")]
3433            {
3434                let assert_refused_by_serde = |refusal: String, decoder: &str| {
3435                    assert!(
3436                        refusal.contains("unknown variant `2`"),
3437                        "{decoder}: {refusal}"
3438                    );
3439                };
3440                let json = serde_json::to_value(&transition).expect("serialize to JSON");
3441                assert_refused_by_serde(
3442                    serde_json::from_value::<StateTransition>(json)
3443                        .expect_err("expected JSON to refuse the stored format")
3444                        .to_string(),
3445                    "JSON",
3446                );
3447                let value = platform_value::to_value(&transition).expect("serialize to a value");
3448                assert_refused_by_serde(
3449                    platform_value::from_value::<StateTransition>(value)
3450                        .expect_err("expected a platform value to refuse the stored format")
3451                        .to_string(),
3452                    "platform value",
3453                );
3454            }
3455            for protocol_version in 1..=LATEST_VERSION {
3456                let platform_version =
3457                    PlatformVersion::get(protocol_version).expect("platform version exists");
3458                assert_refused(
3459                    StateTransition::deserialize_from_bytes_untrusted_in_version(
3460                        &bytes,
3461                        platform_version,
3462                    ),
3463                    "in version",
3464                );
3465                assert_refused(
3466                    StateTransition::deserialize_from_bytes_untrusted_exact_in_version(
3467                        &bytes,
3468                        platform_version,
3469                    ),
3470                    "exact in version",
3471                );
3472            }
3473        }
3474    }
3475
3476    // Version 2 of the document base carries the action fee agreement, which exists from
3477    // protocol version 14. Software from before it cannot decode the base, so new software
3478    // must refuse the batch while an earlier version is active; version 1, which every
3479    // version since 9 builds, stays active everywhere its batch is.
3480    #[test]
3481    fn should_not_activate_a_version_2_document_base_before_protocol_version_14() {
3482        use crate::serialization::PlatformSerializable;
3483        use crate::state_transition::batch_transition::document_base_transition::v1::DocumentBaseTransitionV1;
3484        use crate::state_transition::batch_transition::document_base_transition::v2::DocumentBaseTransitionV2;
3485
3486        let version_1 =
3487            sample_batch_st_with_base(DocumentBaseTransition::V1(DocumentBaseTransitionV1 {
3488                id: Identifier::from([1u8; 32]),
3489                identity_contract_nonce: 3,
3490                document_type_name: "preorder".to_string(),
3491                data_contract_id: Identifier::from([2u8; 32]),
3492                token_payment_info: None,
3493            }));
3494        assert_eq!(version_1.active_version_range(), ALL_VERSIONS);
3495
3496        let version_2 =
3497            sample_batch_st_with_base(DocumentBaseTransition::V2(DocumentBaseTransitionV2 {
3498                id: Identifier::from([1u8; 32]),
3499                identity_contract_nonce: 3,
3500                document_type_name: "preorder".to_string(),
3501                data_contract_id: Identifier::from([2u8; 32]),
3502                token_payment_info: None,
3503                action_fee_agreement: None,
3504            }));
3505        assert_eq!(version_2.active_version_range(), 14..=LATEST_VERSION);
3506
3507        let bytes =
3508            PlatformSerializable::serialize_to_bytes(&version_2).expect("serialize succeeds");
3509        let version_13 = PlatformVersion::get(13).expect("platform version 13 exists");
3510        assert!(matches!(
3511            StateTransition::deserialize_from_bytes_untrusted_in_version(&bytes, version_13),
3512            Err(ProtocolError::StateTransitionError(
3513                StateTransitionIsNotActiveError { .. }
3514            ))
3515        ));
3516        let recovered = StateTransition::deserialize_from_bytes_untrusted_in_version(
3517            &bytes,
3518            PlatformVersion::latest(),
3519        )
3520        .expect("expected protocol version 14 to decode a version 2 base");
3521        assert_eq!(recovered, version_2);
3522    }
3523
3524    fn sample_batch_st_empty() -> StateTransition {
3525        StateTransition::Batch(BatchTransition::V0(BatchTransitionV0 {
3526            owner_id: Identifier::from([1u8; 32]),
3527            transitions: vec![],
3528            user_fee_increase: 0,
3529            signature_public_key_id: 0,
3530            signature: BinaryData::new(vec![]),
3531        }))
3532    }
3533
3534    fn sample_identity_create_st() -> StateTransition {
3535        StateTransition::IdentityCreate(IdentityCreateTransition::V0(IdentityCreateTransitionV0 {
3536            identity_id: Identifier::from([3u8; 32]),
3537            ..Default::default()
3538        }))
3539    }
3540
3541    fn sample_identity_top_up_st() -> StateTransition {
3542        StateTransition::IdentityTopUp(IdentityTopUpTransition::V0(IdentityTopUpTransitionV0 {
3543            identity_id: Identifier::from([4u8; 32]),
3544            ..Default::default()
3545        }))
3546    }
3547
3548    fn sample_identity_update_st() -> StateTransition {
3549        StateTransition::IdentityUpdate(IdentityUpdateTransition::V0(IdentityUpdateTransitionV0 {
3550            identity_id: Identifier::from([5u8; 32]),
3551            revision: 1,
3552            nonce: 2,
3553            add_public_keys: vec![],
3554            disable_public_keys: vec![],
3555            user_fee_increase: 11,
3556            signature_public_key_id: 33,
3557            signature: BinaryData::new(vec![0xFF; 65]),
3558        }))
3559    }
3560
3561    fn sample_unshield_st() -> StateTransition {
3562        StateTransition::Unshield(UnshieldTransition::V0(UnshieldTransitionV0 {
3563            output_address: Default::default(),
3564            actions: vec![],
3565            unshielding_amount: 0,
3566            anchor: [0u8; 32],
3567            proof: vec![],
3568            binding_signature: [0u8; 64],
3569        }))
3570    }
3571
3572    fn sample_shielded_transfer_st() -> StateTransition {
3573        StateTransition::ShieldedTransfer(ShieldedTransferTransition::V0(
3574            ShieldedTransferTransitionV0 {
3575                actions: vec![],
3576                value_balance: 0,
3577                anchor: [0u8; 32],
3578                proof: vec![],
3579                binding_signature: [0u8; 64],
3580            },
3581        ))
3582    }
3583
3584    fn sample_shielded_withdrawal_st() -> StateTransition {
3585        use crate::identity::core_script::CoreScript;
3586        use crate::withdrawal::Pooling;
3587        StateTransition::ShieldedWithdrawal(ShieldedWithdrawalTransition::V0(
3588            ShieldedWithdrawalTransitionV0 {
3589                actions: vec![],
3590                unshielding_amount: 0,
3591                anchor: [0u8; 32],
3592                proof: vec![],
3593                binding_signature: [0u8; 64],
3594                core_fee_per_byte: 1,
3595                pooling: Pooling::Never,
3596                output_script: CoreScript::from_bytes(vec![]),
3597            },
3598        ))
3599    }
3600
3601    // --- name() covers all previously-untested arms, including the nested
3602    // match for Batch variants. ---
3603    #[test]
3604    fn test_name_for_newly_covered_variants() {
3605        assert_eq!(
3606            sample_data_contract_create_st().name(),
3607            "DataContractCreate"
3608        );
3609        assert_eq!(
3610            sample_data_contract_update_st().name(),
3611            "DataContractUpdate"
3612        );
3613        assert_eq!(sample_identity_create_st().name(), "IdentityCreate");
3614        assert_eq!(sample_identity_top_up_st().name(), "IdentityTopUp");
3615        assert_eq!(sample_identity_update_st().name(), "IdentityUpdate");
3616        assert_eq!(sample_unshield_st().name(), "Unshield");
3617        assert_eq!(sample_shielded_transfer_st().name(), "ShieldedTransfer");
3618        assert_eq!(sample_shielded_withdrawal_st().name(), "ShieldedWithdrawal");
3619
3620        // Batch with a single Delete – exercises the nested DocumentTransition
3621        // match arm in `name()`.
3622        let batch_name = sample_batch_st_with_delete().name();
3623        assert_eq!(batch_name, "DocumentsBatch([Delete])");
3624
3625        // Empty batch – still renders, with an empty list.
3626        let empty_name = sample_batch_st_empty().name();
3627        assert_eq!(empty_name, "DocumentsBatch([])");
3628    }
3629
3630    // --- state_transition_type covers the call_method! dispatch. ---
3631    #[test]
3632    fn test_state_transition_type_for_newly_covered_variants() {
3633        assert_eq!(
3634            sample_data_contract_create_st().state_transition_type(),
3635            StateTransitionType::DataContractCreate
3636        );
3637        assert_eq!(
3638            sample_data_contract_update_st().state_transition_type(),
3639            StateTransitionType::DataContractUpdate
3640        );
3641        assert_eq!(
3642            sample_batch_st_with_delete().state_transition_type(),
3643            StateTransitionType::Batch
3644        );
3645        assert_eq!(
3646            sample_identity_create_st().state_transition_type(),
3647            StateTransitionType::IdentityCreate
3648        );
3649        assert_eq!(
3650            sample_identity_top_up_st().state_transition_type(),
3651            StateTransitionType::IdentityTopUp
3652        );
3653        assert_eq!(
3654            sample_identity_update_st().state_transition_type(),
3655            StateTransitionType::IdentityUpdate
3656        );
3657        assert_eq!(
3658            sample_unshield_st().state_transition_type(),
3659            StateTransitionType::Unshield
3660        );
3661        assert_eq!(
3662            sample_shielded_transfer_st().state_transition_type(),
3663            StateTransitionType::ShieldedTransfer
3664        );
3665        assert_eq!(
3666            sample_shielded_withdrawal_st().state_transition_type(),
3667            StateTransitionType::ShieldedWithdrawal
3668        );
3669    }
3670
3671    // --- active_version_range uses different branches per transition
3672    // "group". Exercises the contract-format V1 branch for
3673    // DataContractCreate/Update (9..=LATEST), the BatchTransitionV0 branch
3674    // (ALL_VERSIONS), and the shielded range (12..=LATEST).
3675    #[test]
3676    fn test_active_version_range_contract_and_shielded_branches() {
3677        // DataContractCreate/Update on PlatformVersion::latest use the V1
3678        // contract serialization format, which restricts active range.
3679        let contract_v1_range = 9..=LATEST_VERSION;
3680        assert_eq!(
3681            sample_data_contract_create_st().active_version_range(),
3682            contract_v1_range
3683        );
3684        let contract_v1_range = 9..=LATEST_VERSION;
3685        assert_eq!(
3686            sample_data_contract_update_st().active_version_range(),
3687            contract_v1_range
3688        );
3689        // BatchTransition::V0 → ALL_VERSIONS
3690        assert_eq!(
3691            sample_batch_st_with_delete().active_version_range(),
3692            ALL_VERSIONS
3693        );
3694        // IdentityCreate/TopUp/Update are ALL_VERSIONS.
3695        assert_eq!(
3696            sample_identity_create_st().active_version_range(),
3697            ALL_VERSIONS
3698        );
3699        assert_eq!(
3700            sample_identity_top_up_st().active_version_range(),
3701            ALL_VERSIONS
3702        );
3703        assert_eq!(
3704            sample_identity_update_st().active_version_range(),
3705            ALL_VERSIONS
3706        );
3707        // Shielded variants report a shielded range (12..=LATEST_VERSION).
3708        let shielded_range = 12..=LATEST_VERSION;
3709        assert_eq!(
3710            sample_shielded_transfer_st().active_version_range(),
3711            shielded_range.clone()
3712        );
3713        assert_eq!(
3714            sample_unshield_st().active_version_range(),
3715            shielded_range.clone()
3716        );
3717        assert_eq!(
3718            sample_shielded_withdrawal_st().active_version_range(),
3719            shielded_range
3720        );
3721    }
3722
3723    fn sample_identity_update_st_adding(
3724        public_key: crate::state_transition::public_key_in_creation::IdentityPublicKeyInCreation,
3725    ) -> StateTransition {
3726        StateTransition::IdentityUpdate(IdentityUpdateTransition::V0(IdentityUpdateTransitionV0 {
3727            identity_id: Identifier::from([5u8; 32]),
3728            revision: 1,
3729            nonce: 2,
3730            add_public_keys: vec![public_key],
3731            disable_public_keys: vec![],
3732            user_fee_increase: 0,
3733            signature_public_key_id: 0,
3734            signature: BinaryData::new(vec![0xFF; 65]),
3735        }))
3736    }
3737
3738    #[test]
3739    fn should_only_admit_a_version_1_public_key_in_creation_from_protocol_version_14() {
3740        use crate::serialization::PlatformSerializable;
3741        use crate::state_transition::public_key_in_creation::v0::IdentityPublicKeyInCreationV0;
3742        use crate::state_transition::public_key_in_creation::v1::IdentityPublicKeyInCreationV1;
3743
3744        let version_0_key = IdentityPublicKeyInCreationV0 {
3745            id: 1,
3746            data: BinaryData::new(vec![2; 33]),
3747            ..Default::default()
3748        };
3749        let limited_key = IdentityPublicKeyInCreationV1::from_v0_with_limits(
3750            version_0_key.clone(),
3751            Some(1_000),
3752            None,
3753        );
3754
3755        let plain = sample_identity_update_st_adding(version_0_key.into());
3756        assert_eq!(plain.active_version_range(), ALL_VERSIONS);
3757
3758        let limited = sample_identity_update_st_adding(limited_key.into());
3759        assert_eq!(limited.active_version_range(), 14..=LATEST_VERSION);
3760
3761        // Protocol version 13 refuses it while decoding, the way a binary that does not know
3762        // the version 1 key does; protocol version 14 decodes it.
3763        let bytes = limited.serialize_to_bytes().expect("expected to serialize");
3764        let version_13 = PlatformVersion::get(13).expect("expected protocol version 13");
3765        assert!(matches!(
3766            StateTransition::deserialize_from_bytes_untrusted_in_version(&bytes, version_13),
3767            Err(ProtocolError::StateTransitionError(
3768                StateTransitionIsNotActiveError { .. }
3769            ))
3770        ));
3771        let version_14 = PlatformVersion::get(14).expect("expected protocol version 14");
3772        assert_eq!(
3773            StateTransition::deserialize_from_bytes_untrusted_in_version(&bytes, version_14)
3774                .expect("expected protocol version 14 to decode a limited key"),
3775            limited
3776        );
3777    }
3778
3779    // --- is_identity_signed exercises the inverted-match logic for the
3780    // shielded / identity-create / topup variants. ---
3781    #[test]
3782    fn test_is_identity_signed_false_for_identity_create_topup_and_shielded() {
3783        assert!(!sample_identity_create_st().is_identity_signed());
3784        assert!(!sample_identity_top_up_st().is_identity_signed());
3785        assert!(!sample_unshield_st().is_identity_signed());
3786        assert!(!sample_shielded_transfer_st().is_identity_signed());
3787        assert!(!sample_shielded_withdrawal_st().is_identity_signed());
3788    }
3789
3790    // --- signature accessor for each arm that returns Some/None; previously
3791    // only IdentityCreditTransfer / MasternodeVote / IdentityCreditWithdrawal
3792    // were covered.
3793    #[test]
3794    fn test_signature_accessor_for_other_variants() {
3795        // Some(_) arms
3796        assert_eq!(
3797            sample_data_contract_create_st().signature().unwrap().len(),
3798            65
3799        );
3800        assert_eq!(
3801            sample_data_contract_update_st().signature().unwrap().len(),
3802            65
3803        );
3804        assert_eq!(sample_batch_st_with_delete().signature().unwrap().len(), 65);
3805        assert_eq!(sample_identity_update_st().signature().unwrap().len(), 65);
3806
3807        // None arms for address / shielded variants.
3808        assert!(sample_unshield_st().signature().is_none());
3809        assert!(sample_shielded_transfer_st().signature().is_none());
3810        assert!(sample_shielded_withdrawal_st().signature().is_none());
3811    }
3812
3813    // --- owner_id accessor for each arm.
3814    #[test]
3815    fn test_owner_id_accessor_for_other_variants() {
3816        assert_eq!(
3817            sample_data_contract_create_st().owner_id(),
3818            Some(Identifier::from([7u8; 32]))
3819        );
3820        assert_eq!(
3821            sample_data_contract_update_st().owner_id(),
3822            Some(Identifier::from([7u8; 32]))
3823        );
3824        assert_eq!(
3825            sample_batch_st_with_delete().owner_id(),
3826            Some(Identifier::from([8u8; 32]))
3827        );
3828        assert_eq!(
3829            sample_identity_update_st().owner_id(),
3830            Some(Identifier::from([5u8; 32]))
3831        );
3832        // These variants unconditionally return None.
3833        assert!(sample_unshield_st().owner_id().is_none());
3834        assert!(sample_shielded_transfer_st().owner_id().is_none());
3835        assert!(sample_shielded_withdrawal_st().owner_id().is_none());
3836    }
3837
3838    // --- user_fee_increase accessor — includes arms that return 0
3839    // unconditionally (shielded/masternode) vs the variants' stored value.
3840    #[test]
3841    fn test_user_fee_increase_for_newly_covered_variants() {
3842        assert_eq!(sample_data_contract_create_st().user_fee_increase(), 5);
3843        assert_eq!(sample_data_contract_update_st().user_fee_increase(), 9);
3844        assert_eq!(sample_batch_st_with_delete().user_fee_increase(), 2);
3845        assert_eq!(sample_identity_update_st().user_fee_increase(), 11);
3846        // Unconditionally 0 for shielded.
3847        assert_eq!(sample_shielded_transfer_st().user_fee_increase(), 0);
3848        assert_eq!(sample_shielded_withdrawal_st().user_fee_increase(), 0);
3849        assert_eq!(sample_unshield_st().user_fee_increase(), 0);
3850    }
3851
3852    // --- set_user_fee_increase for the no-op shielded arms and for the
3853    // transitions that actually do store the value.
3854    #[test]
3855    fn test_set_user_fee_increase_for_newly_covered_variants() {
3856        let mut st = sample_data_contract_create_st();
3857        st.set_user_fee_increase(42);
3858        assert_eq!(st.user_fee_increase(), 42);
3859
3860        let mut st = sample_data_contract_update_st();
3861        st.set_user_fee_increase(13);
3862        assert_eq!(st.user_fee_increase(), 13);
3863
3864        let mut st = sample_batch_st_with_delete();
3865        st.set_user_fee_increase(101);
3866        assert_eq!(st.user_fee_increase(), 101);
3867
3868        let mut st = sample_identity_update_st();
3869        st.set_user_fee_increase(77);
3870        assert_eq!(st.user_fee_increase(), 77);
3871
3872        // Shielded no-ops: value stays 0.
3873        let mut shielded = sample_shielded_transfer_st();
3874        shielded.set_user_fee_increase(99);
3875        assert_eq!(shielded.user_fee_increase(), 0);
3876
3877        let mut withdrawal = sample_shielded_withdrawal_st();
3878        withdrawal.set_user_fee_increase(99);
3879        assert_eq!(withdrawal.user_fee_increase(), 0);
3880
3881        let mut unshield = sample_unshield_st();
3882        unshield.set_user_fee_increase(99);
3883        assert_eq!(unshield.user_fee_increase(), 0);
3884    }
3885
3886    // --- set_signature: exercises the `true` arms we didn't test before
3887    // (DataContractCreate/Update/Batch/IdentityUpdate) and the `false` arms
3888    // (shielded transitions).
3889    #[test]
3890    fn test_set_signature_false_for_shielded_and_identity_create_topup() {
3891        // `false` arms: shield*, shielded*, unshield, address* (no-op, returns false).
3892        let mut st = sample_unshield_st();
3893        assert!(!st.set_signature(BinaryData::new(vec![0xAB; 65])));
3894        let mut st = sample_shielded_transfer_st();
3895        assert!(!st.set_signature(BinaryData::new(vec![0xAB; 65])));
3896        let mut st = sample_shielded_withdrawal_st();
3897        assert!(!st.set_signature(BinaryData::new(vec![0xAB; 65])));
3898    }
3899
3900    #[test]
3901    fn test_set_signature_true_for_newly_covered_variants() {
3902        let mut st = sample_data_contract_create_st();
3903        assert!(st.set_signature(BinaryData::new(vec![0x11; 65])));
3904        assert_eq!(st.signature().unwrap().as_slice(), &[0x11; 65]);
3905
3906        let mut st = sample_data_contract_update_st();
3907        assert!(st.set_signature(BinaryData::new(vec![0x22; 65])));
3908        assert_eq!(st.signature().unwrap().as_slice(), &[0x22; 65]);
3909
3910        let mut st = sample_batch_st_with_delete();
3911        assert!(st.set_signature(BinaryData::new(vec![0x33; 65])));
3912        assert_eq!(st.signature().unwrap().as_slice(), &[0x33; 65]);
3913
3914        let mut st = sample_identity_update_st();
3915        assert!(st.set_signature(BinaryData::new(vec![0x44; 65])));
3916        assert_eq!(st.signature().unwrap().as_slice(), &[0x44; 65]);
3917    }
3918
3919    // --- signature_public_key_id: identity-signed arms return Some, others
3920    // (shielded/identity-create/topup/address) return None.
3921    #[test]
3922    fn test_signature_public_key_id_returns_none_for_non_signed() {
3923        // IdentityCreate / IdentityTopUp / shielded / address variants are all
3924        // "not identity-signed" and return None.
3925        assert!(sample_identity_create_st()
3926            .signature_public_key_id()
3927            .is_none());
3928        assert!(sample_identity_top_up_st()
3929            .signature_public_key_id()
3930            .is_none());
3931        assert!(sample_unshield_st().signature_public_key_id().is_none());
3932        assert!(sample_shielded_transfer_st()
3933            .signature_public_key_id()
3934            .is_none());
3935        assert!(sample_shielded_withdrawal_st()
3936            .signature_public_key_id()
3937            .is_none());
3938    }
3939
3940    #[test]
3941    fn test_signature_public_key_id_for_signed_variants() {
3942        assert_eq!(
3943            sample_data_contract_create_st().signature_public_key_id(),
3944            Some(2)
3945        );
3946        assert_eq!(
3947            sample_data_contract_update_st().signature_public_key_id(),
3948            Some(6)
3949        );
3950        assert_eq!(
3951            sample_batch_st_with_delete().signature_public_key_id(),
3952            Some(7)
3953        );
3954        assert_eq!(
3955            sample_identity_update_st().signature_public_key_id(),
3956            Some(33)
3957        );
3958    }
3959
3960    // --- set_signature_public_key_id: no-op for IdentityCreate/TopUp and
3961    // shielded variants; updates for identity-signed variants. ---
3962    #[test]
3963    fn test_set_signature_public_key_id_noop_for_non_signed() {
3964        // These variants are not identity-signed; setter is a no-op in the
3965        // call_method_identity_signed! macro.
3966        let mut st = sample_identity_create_st();
3967        st.set_signature_public_key_id(100);
3968        assert_eq!(st.signature_public_key_id(), None);
3969
3970        let mut st = sample_identity_top_up_st();
3971        st.set_signature_public_key_id(100);
3972        assert_eq!(st.signature_public_key_id(), None);
3973
3974        let mut st = sample_unshield_st();
3975        st.set_signature_public_key_id(100);
3976        assert_eq!(st.signature_public_key_id(), None);
3977    }
3978
3979    #[test]
3980    fn test_set_signature_public_key_id_updates_for_signed_variants() {
3981        let mut st = sample_data_contract_create_st();
3982        st.set_signature_public_key_id(42);
3983        assert_eq!(st.signature_public_key_id(), Some(42));
3984
3985        let mut st = sample_batch_st_with_delete();
3986        st.set_signature_public_key_id(43);
3987        assert_eq!(st.signature_public_key_id(), Some(43));
3988
3989        let mut st = sample_identity_update_st();
3990        st.set_signature_public_key_id(44);
3991        assert_eq!(st.signature_public_key_id(), Some(44));
3992    }
3993
3994    // --- required_number_of_private_keys defaults to 1 for "signed" variants
3995    // and 0 for shielded ones.
3996    #[test]
3997    fn test_required_number_of_private_keys_various_variants() {
3998        assert_eq!(
3999            sample_data_contract_create_st().required_number_of_private_keys(),
4000            1
4001        );
4002        assert_eq!(
4003            sample_data_contract_update_st().required_number_of_private_keys(),
4004            1
4005        );
4006        assert_eq!(
4007            sample_batch_st_with_delete().required_number_of_private_keys(),
4008            1
4009        );
4010        assert_eq!(
4011            sample_identity_update_st().required_number_of_private_keys(),
4012            1
4013        );
4014        assert_eq!(
4015            sample_identity_create_st().required_number_of_private_keys(),
4016            1
4017        );
4018        // Shielded variants return 0 unconditionally.
4019        assert_eq!(
4020            sample_shielded_transfer_st().required_number_of_private_keys(),
4021            0
4022        );
4023        assert_eq!(
4024            sample_shielded_withdrawal_st().required_number_of_private_keys(),
4025            0
4026        );
4027        assert_eq!(sample_unshield_st().required_number_of_private_keys(), 0);
4028    }
4029
4030    // --- inputs(): None for all these variants (covers the big
4031    // wildcard/None arm in the match).
4032    #[test]
4033    fn test_inputs_none_for_many_variants() {
4034        assert!(sample_data_contract_create_st().inputs().is_none());
4035        assert!(sample_data_contract_update_st().inputs().is_none());
4036        assert!(sample_batch_st_with_delete().inputs().is_none());
4037        assert!(sample_identity_create_st().inputs().is_none());
4038        assert!(sample_identity_top_up_st().inputs().is_none());
4039        assert!(sample_identity_update_st().inputs().is_none());
4040        // Shielded variants also return None for inputs().
4041        assert!(sample_unshield_st().inputs().is_none());
4042        assert!(sample_shielded_transfer_st().inputs().is_none());
4043        assert!(sample_shielded_withdrawal_st().inputs().is_none());
4044    }
4045
4046    // --- optional_asset_lock_proof: None for everything that isn't
4047    // IdentityCreate / IdentityTopUp / ShieldFromAssetLock. The IdentityCreate
4048    // default contains the asset lock proof field, so this forwards to its
4049    // implementation.
4050    #[test]
4051    fn test_optional_asset_lock_proof_returns_none_for_wildcard_arms() {
4052        assert!(sample_data_contract_create_st()
4053            .optional_asset_lock_proof()
4054            .is_none());
4055        assert!(sample_data_contract_update_st()
4056            .optional_asset_lock_proof()
4057            .is_none());
4058        assert!(sample_batch_st_with_delete()
4059            .optional_asset_lock_proof()
4060            .is_none());
4061        assert!(sample_identity_update_st()
4062            .optional_asset_lock_proof()
4063            .is_none());
4064        assert!(sample_unshield_st().optional_asset_lock_proof().is_none());
4065        assert!(sample_shielded_transfer_st()
4066            .optional_asset_lock_proof()
4067            .is_none());
4068        assert!(sample_shielded_withdrawal_st()
4069            .optional_asset_lock_proof()
4070            .is_none());
4071    }
4072
4073    // --- required_asset_lock_balance_for_processing_start returns an
4074    // CorruptedCodeExecution error for non asset-lock variants. Exercise
4075    // additional arms beyond what the original transfer test covered.
4076    #[test]
4077    fn test_required_asset_lock_balance_errors_for_other_non_asset_lock_variants() {
4078        let platform_version = PlatformVersion::latest();
4079
4080        let cases: Vec<(&str, StateTransition)> = vec![
4081            ("DataContractCreate", sample_data_contract_create_st()),
4082            ("DataContractUpdate", sample_data_contract_update_st()),
4083            ("Batch", sample_batch_st_with_delete()),
4084            ("IdentityUpdate", sample_identity_update_st()),
4085            ("MasternodeVote", sample_masternode_vote_st()),
4086            ("Unshield", sample_unshield_st()),
4087            ("ShieldedTransfer", sample_shielded_transfer_st()),
4088            ("ShieldedWithdrawal", sample_shielded_withdrawal_st()),
4089        ];
4090
4091        for (label, st) in cases {
4092            let err = st
4093                .required_asset_lock_balance_for_processing_start(platform_version)
4094                .expect_err(&format!("expected error for {label}"));
4095            match err {
4096                ProtocolError::CorruptedCodeExecution(msg) => {
4097                    assert!(
4098                        msg.contains("is not an asset lock transaction"),
4099                        "unexpected error for {label}: {msg}"
4100                    );
4101                }
4102                other => panic!("expected CorruptedCodeExecution for {label}, got {other:?}"),
4103            }
4104        }
4105    }
4106
4107    // --- unique_identifiers: covers the call_method! dispatch for arms
4108    // beyond credit transfer. Each variant's `unique_identifiers`
4109    // implementation returns a non-empty vector; the individual identifier
4110    // strings may be empty for some variants whose IDs are encoded as empty
4111    // (this method simply shouldn't panic or short-circuit).
4112    #[test]
4113    fn test_unique_identifiers_non_empty_for_other_variants() {
4114        for st in [
4115            sample_data_contract_create_st(),
4116            sample_data_contract_update_st(),
4117            sample_batch_st_with_delete(),
4118            sample_identity_create_st(),
4119            sample_identity_top_up_st(),
4120            sample_identity_update_st(),
4121        ] {
4122            let ids = st.unique_identifiers();
4123            assert!(!ids.is_empty(), "unique_identifiers should not be empty");
4124        }
4125    }
4126
4127    // --- security_level_requirement returns None for identity-create/topup
4128    // and for every shielded/address variant. This hits the None arms in
4129    // call_getter_method_identity_signed!.
4130    #[test]
4131    fn test_security_level_requirement_returns_none_for_non_signed_variants() {
4132        let purpose = Purpose::AUTHENTICATION;
4133        assert!(sample_identity_create_st()
4134            .security_level_requirement(purpose)
4135            .is_none());
4136        assert!(sample_identity_top_up_st()
4137            .security_level_requirement(purpose)
4138            .is_none());
4139        assert!(sample_unshield_st()
4140            .security_level_requirement(purpose)
4141            .is_none());
4142        assert!(sample_shielded_transfer_st()
4143            .security_level_requirement(purpose)
4144            .is_none());
4145        assert!(sample_shielded_withdrawal_st()
4146            .security_level_requirement(purpose)
4147            .is_none());
4148    }
4149
4150    #[test]
4151    fn test_purpose_requirement_returns_none_for_non_signed_variants() {
4152        assert!(sample_identity_create_st().purpose_requirement().is_none());
4153        assert!(sample_identity_top_up_st().purpose_requirement().is_none());
4154        assert!(sample_unshield_st().purpose_requirement().is_none());
4155        assert!(sample_shielded_transfer_st()
4156            .purpose_requirement()
4157            .is_none());
4158        assert!(sample_shielded_withdrawal_st()
4159            .purpose_requirement()
4160            .is_none());
4161    }
4162
4163    // --- From impls: each From<Outer> → StateTransition uses `derive_more::From`.
4164    #[test]
4165    fn test_from_outer_data_contract_create_into_state_transition() {
4166        let outer: DataContractCreateTransition =
4167            DataContractCreateTransition::V0(DataContractCreateTransitionV0 {
4168                data_contract: sample_data_contract_in_serialization_format(),
4169                identity_nonce: 1,
4170                user_fee_increase: 0,
4171                signature_public_key_id: 0,
4172                signature: Default::default(),
4173            });
4174        let st: StateTransition = outer.into();
4175        assert!(matches!(st, StateTransition::DataContractCreate(_)));
4176    }
4177
4178    #[test]
4179    fn test_from_outer_data_contract_update_into_state_transition() {
4180        let outer: DataContractUpdateTransition =
4181            DataContractUpdateTransition::V0(DataContractUpdateTransitionV0 {
4182                identity_contract_nonce: 2,
4183                data_contract: sample_data_contract_in_serialization_format(),
4184                user_fee_increase: 0,
4185                signature_public_key_id: 0,
4186                signature: Default::default(),
4187            });
4188        let st: StateTransition = outer.into();
4189        assert!(matches!(st, StateTransition::DataContractUpdate(_)));
4190    }
4191
4192    #[test]
4193    fn test_from_outer_batch_into_state_transition() {
4194        let outer: BatchTransition = BatchTransition::V0(BatchTransitionV0::default());
4195        let st: StateTransition = outer.into();
4196        assert!(matches!(st, StateTransition::Batch(_)));
4197    }
4198
4199    #[test]
4200    fn test_from_outer_identity_create_into_state_transition() {
4201        let outer: IdentityCreateTransition =
4202            IdentityCreateTransition::V0(IdentityCreateTransitionV0::default());
4203        let st: StateTransition = outer.into();
4204        assert!(matches!(st, StateTransition::IdentityCreate(_)));
4205    }
4206
4207    #[test]
4208    fn test_from_outer_identity_update_into_state_transition() {
4209        let outer: IdentityUpdateTransition =
4210            IdentityUpdateTransition::V0(IdentityUpdateTransitionV0::default());
4211        let st: StateTransition = outer.into();
4212        assert!(matches!(st, StateTransition::IdentityUpdate(_)));
4213    }
4214
4215    // --- transaction_id + clone for additional variants — triggers the
4216    // serialize path for each arm.
4217    #[test]
4218    fn test_transaction_id_and_clone_for_identity_update() {
4219        let st = sample_identity_update_st();
4220        let id_a = st.transaction_id().expect("hash should succeed");
4221        let cloned = st.clone();
4222        let id_b = cloned.transaction_id().expect("hash should succeed");
4223        assert_eq!(id_a, id_b);
4224        assert_eq!(id_a.len(), 32);
4225    }
4226
4227    #[test]
4228    fn test_transaction_id_and_clone_for_data_contract_create() {
4229        let st = sample_data_contract_create_st();
4230        let id_a = st.transaction_id().expect("hash should succeed");
4231        let cloned = st.clone();
4232        let id_b = cloned.transaction_id().expect("hash should succeed");
4233        assert_eq!(id_a, id_b);
4234        assert_eq!(id_a.len(), 32);
4235    }
4236
4237    // --- serialize round-trip for variants beyond credit transfer. ---
4238    #[test]
4239    fn test_serialize_roundtrip_identity_update() {
4240        use crate::serialization::{PlatformDeserializableUntrusted, PlatformSerializable};
4241        let original = sample_identity_update_st();
4242        let bytes =
4243            PlatformSerializable::serialize_to_bytes(&original).expect("serialize should succeed");
4244        let restored = StateTransition::deserialize_from_bytes_untrusted(&bytes)
4245            .expect("deserialize should succeed");
4246        assert_eq!(original, restored);
4247    }
4248
4249    #[test]
4250    fn test_serialize_roundtrip_data_contract_update() {
4251        use crate::serialization::{PlatformDeserializableUntrusted, PlatformSerializable};
4252        let original = sample_data_contract_update_st();
4253        let bytes =
4254            PlatformSerializable::serialize_to_bytes(&original).expect("serialize should succeed");
4255        let restored = StateTransition::deserialize_from_bytes_untrusted(&bytes)
4256            .expect("deserialize should succeed");
4257        assert_eq!(original, restored);
4258    }
4259
4260    #[test]
4261    fn test_serialize_roundtrip_batch_empty() {
4262        use crate::serialization::{PlatformDeserializableUntrusted, PlatformSerializable};
4263        let original = sample_batch_st_empty();
4264        let bytes =
4265            PlatformSerializable::serialize_to_bytes(&original).expect("serialize should succeed");
4266        let restored = StateTransition::deserialize_from_bytes_untrusted(&bytes)
4267            .expect("deserialize should succeed");
4268        assert_eq!(original, restored);
4269    }
4270
4271    // --- deserialize_from_bytes_in_version error path: craft bytes for a
4272    // variant whose `active_version_range()` starts at 11 or 12 and then
4273    // attempt to deserialize them with a PlatformVersion whose protocol
4274    // version is below that range. Exercises the
4275    // `StateTransitionIsNotActiveError` arm.
4276    // ---
4277    #[cfg(all(feature = "state-transitions", feature = "validation"))]
4278    #[test]
4279    fn test_deserialize_from_bytes_in_version_returns_not_active_error() {
4280        use crate::serialization::PlatformSerializable;
4281
4282        // ShieldedTransfer has active_version_range = 12..=LATEST_VERSION.
4283        let original = sample_shielded_transfer_st();
4284        let bytes =
4285            PlatformSerializable::serialize_to_bytes(&original).expect("serialize succeeds");
4286
4287        // Find a real PlatformVersion whose protocol_version is < 12 so the
4288        // range check rejects it. PlatformVersion::get(1) corresponds to
4289        // protocol version 1 which is guaranteed below any shielded range.
4290        let low_version = PlatformVersion::get(1).expect("platform version 1 exists");
4291        assert!(
4292            low_version.protocol_version < 12,
4293            "expected sub-12 version for this test, got {}",
4294            low_version.protocol_version
4295        );
4296
4297        let err = StateTransition::deserialize_from_bytes_untrusted_in_version(&bytes, low_version)
4298            .expect_err("expected StateTransitionIsNotActiveError for sub-12 protocol");
4299        match err {
4300            ProtocolError::StateTransitionError(
4301                crate::state_transition::errors::StateTransitionError::StateTransitionIsNotActiveError {
4302                    state_transition_type,
4303                    active_version_range,
4304                    current_protocol_version,
4305                },
4306            ) => {
4307                assert_eq!(state_transition_type, "ShieldedTransfer");
4308                assert_eq!(current_protocol_version, low_version.protocol_version);
4309                assert!(active_version_range.start() >= &12);
4310            }
4311            other => panic!("expected StateTransitionIsNotActiveError, got {other:?}"),
4312        }
4313    }
4314
4315    /// The exact decoder keeps the active version check: exact bytes of a transition that is
4316    /// not active yet are refused as not active, not as left over bytes.
4317    #[cfg(all(feature = "state-transitions", feature = "validation"))]
4318    #[test]
4319    fn should_refuse_an_inactive_transition_in_the_exact_in_version_decoder() {
4320        use crate::serialization::PlatformSerializable;
4321
4322        // ShieldedTransfer has active_version_range = 12..=LATEST_VERSION.
4323        let bytes = PlatformSerializable::serialize_to_bytes(&sample_shielded_transfer_st())
4324            .expect("serialize succeeds");
4325        let low_version = PlatformVersion::get(1).expect("platform version 1 exists");
4326
4327        assert!(matches!(
4328            StateTransition::deserialize_from_bytes_untrusted_exact_in_version(&bytes, low_version),
4329            Err(ProtocolError::StateTransitionError(
4330                crate::state_transition::errors::StateTransitionError::StateTransitionIsNotActiveError { .. }
4331            ))
4332        ));
4333    }
4334
4335    // A `ShieldFromAssetLock` is refused on its version byte at decode, before any proof work:
4336    // version 0 (whose bundle binds nothing) up to protocol version 13 only, version 1 (whose
4337    // bundle binds its kind and asset lock) from 14 only.
4338    #[cfg(all(feature = "state-transitions", feature = "validation"))]
4339    #[test]
4340    fn should_decode_each_shield_from_asset_lock_version_only_where_it_is_active() {
4341        use crate::serialization::PlatformSerializable;
4342        use crate::state_transition::shield_from_asset_lock_transition::v1::ShieldFromAssetLockTransitionV1;
4343
4344        let v0 = sample_shield_from_asset_lock_st();
4345        let StateTransition::ShieldFromAssetLock(ShieldFromAssetLockTransition::V0(body)) = &v0
4346        else {
4347            panic!("expected a version 0 shield from asset lock");
4348        };
4349        let v1 = StateTransition::ShieldFromAssetLock(ShieldFromAssetLockTransition::V1(
4350            ShieldFromAssetLockTransitionV1 {
4351                asset_lock_proof: body.asset_lock_proof.clone(),
4352                actions: body.actions.clone(),
4353                value_balance: body.value_balance,
4354                anchor: body.anchor,
4355                proof: body.proof.clone(),
4356                binding_signature: body.binding_signature,
4357                surplus_output: body.surplus_output,
4358                signature: body.signature.clone(),
4359            },
4360        ));
4361        assert_eq!(v0.active_version_range(), 12..=13);
4362        assert_eq!(v1.active_version_range(), 14..=LATEST_VERSION);
4363
4364        let decodes_at = |transition: &StateTransition, protocol_version: u32| {
4365            let bytes =
4366                PlatformSerializable::serialize_to_bytes(transition).expect("serialize succeeds");
4367            let platform_version =
4368                PlatformVersion::get(protocol_version).expect("known protocol version");
4369            match StateTransition::deserialize_from_bytes_untrusted_in_version(
4370                &bytes,
4371                platform_version,
4372            ) {
4373                Ok(decoded) => {
4374                    assert_eq!(&decoded, transition);
4375                    true
4376                }
4377                Err(ProtocolError::StateTransitionError(
4378                    crate::state_transition::errors::StateTransitionError::StateTransitionIsNotActiveError {
4379                        current_protocol_version,
4380                        ..
4381                    },
4382                )) => {
4383                    assert_eq!(current_protocol_version, protocol_version);
4384                    false
4385                }
4386                Err(other) => panic!("unexpected decode error: {other:?}"),
4387            }
4388        };
4389
4390        for protocol_version in [12, 13] {
4391            assert!(decodes_at(&v0, protocol_version));
4392            assert!(!decodes_at(&v1, protocol_version));
4393        }
4394        assert!(
4395            !decodes_at(&v0, 14),
4396            "protocol version 14 must refuse version 0"
4397        );
4398        assert!(decodes_at(&v1, 14));
4399    }
4400
4401    // A version 1 data contract create carries contract groups, which only exist from
4402    // protocol version 14. Below that a node must reject it rather than create the
4403    // contract and drop the group data.
4404    #[test]
4405    fn test_data_contract_create_v1_is_not_active_before_protocol_version_14() {
4406        use crate::serialization::PlatformSerializable;
4407
4408        let original = sample_data_contract_create_v1_st();
4409        assert_eq!(original.active_version_range(), 14..=LATEST_VERSION);
4410
4411        let bytes =
4412            PlatformSerializable::serialize_to_bytes(&original).expect("serialize succeeds");
4413
4414        let version_13 = PlatformVersion::get(13).expect("platform version 13 exists");
4415        let err = StateTransition::deserialize_from_bytes_untrusted_in_version(&bytes, version_13)
4416            .expect_err("expected StateTransitionIsNotActiveError at protocol version 13");
4417        match err {
4418            ProtocolError::StateTransitionError(
4419                crate::state_transition::errors::StateTransitionError::StateTransitionIsNotActiveError {
4420                    active_version_range,
4421                    current_protocol_version,
4422                    ..
4423                },
4424            ) => {
4425                assert_eq!(current_protocol_version, 13);
4426                assert_eq!(*active_version_range.start(), 14);
4427            }
4428            other => panic!("expected StateTransitionIsNotActiveError, got {other:?}"),
4429        }
4430
4431        let version_14 = PlatformVersion::get(14).expect("platform version 14 exists");
4432        StateTransition::deserialize_from_bytes_untrusted_in_version(&bytes, version_14)
4433            .expect("a version 1 create is active at protocol version 14");
4434    }
4435
4436    #[test]
4437    fn should_gate_identity_transitions_carrying_a_contract_group_bound_key_to_protocol_version_14()
4438    {
4439        use crate::identity::contract_bounds::ContractBounds;
4440        use crate::identity::{KeyType, Purpose, SecurityLevel};
4441        use crate::serialization::PlatformSerializable;
4442        use crate::state_transition::errors::StateTransitionError;
4443        use crate::state_transition::public_key_in_creation::v0::IdentityPublicKeyInCreationV0;
4444        use crate::state_transition::public_key_in_creation::IdentityPublicKeyInCreation;
4445
4446        let key = |contract_bounds| {
4447            IdentityPublicKeyInCreation::V0(IdentityPublicKeyInCreationV0 {
4448                id: 1,
4449                key_type: KeyType::ECDSA_SECP256K1,
4450                purpose: Purpose::AUTHENTICATION,
4451                security_level: SecurityLevel::HIGH,
4452                contract_bounds,
4453                read_only: false,
4454                data: BinaryData::new(vec![2u8; 33]),
4455                signature: BinaryData::default(),
4456            })
4457        };
4458        let contract_bound = Some(ContractBounds::SingleContract {
4459            id: Identifier::from([9u8; 32]),
4460        });
4461        let group_bound = Some(ContractBounds::ContractGroup {
4462            id: Identifier::from([9u8; 32]),
4463        });
4464        let create = |bounds| {
4465            StateTransition::IdentityCreate(IdentityCreateTransition::V0(
4466                IdentityCreateTransitionV0 {
4467                    identity_id: Identifier::from([3u8; 32]),
4468                    public_keys: vec![key(bounds)],
4469                    ..Default::default()
4470                },
4471            ))
4472        };
4473        let update = |bounds| {
4474            let StateTransition::IdentityUpdate(IdentityUpdateTransition::V0(mut v0)) =
4475                sample_identity_update_st()
4476            else {
4477                panic!("expected a version 0 identity update");
4478            };
4479            v0.add_public_keys = vec![key(bounds)];
4480            StateTransition::IdentityUpdate(IdentityUpdateTransition::V0(v0))
4481        };
4482
4483        assert_eq!(
4484            create(contract_bound.clone()).active_version_range(),
4485            ALL_VERSIONS
4486        );
4487        assert_eq!(
4488            create(group_bound.clone()).active_version_range(),
4489            14..=LATEST_VERSION
4490        );
4491        assert_eq!(update(contract_bound).active_version_range(), ALL_VERSIONS);
4492        assert_eq!(
4493            update(group_bound.clone()).active_version_range(),
4494            14..=LATEST_VERSION
4495        );
4496
4497        let bytes = PlatformSerializable::serialize_to_bytes(&update(group_bound))
4498            .expect("serialize succeeds");
4499        let version_13 = PlatformVersion::get(13).expect("platform version 13 exists");
4500        let err = StateTransition::deserialize_from_bytes_untrusted_in_version(&bytes, version_13)
4501            .expect_err("a contract group bound key is not active at protocol version 13");
4502        assert!(
4503            matches!(
4504                err,
4505                ProtocolError::StateTransitionError(
4506                    StateTransitionError::StateTransitionIsNotActiveError { .. }
4507                )
4508            ),
4509            "{err:?}"
4510        );
4511        let version_14 = PlatformVersion::get(14).expect("platform version 14 exists");
4512        StateTransition::deserialize_from_bytes_untrusted_in_version(&bytes, version_14)
4513            .expect("a contract group bound key is active at protocol version 14");
4514    }
4515
4516    // -----------------------------------------------------------------------
4517    // Additional coverage: variants not yet exercised.
4518    //
4519    // The tests below target:
4520    //   * IdentityCreditWithdrawal::V1 (previously only V0 was covered).
4521    //   * IdentityCreditTransferToAddresses (its own top-level arm).
4522    //   * AddressFundingFromAssetLock (identity-signed + asset-lock arm).
4523    //   * AddressCreditWithdrawal (not identity-signed, address-funds arm).
4524    //   * ShieldFromAssetLock (asset-lock, non-identity-signed).
4525    //   * Batch with a token transition (nested enum, previously only Delete).
4526    // -----------------------------------------------------------------------
4527
4528    use crate::state_transition::identity_credit_transfer_to_addresses_transition::v0::IdentityCreditTransferToAddressesTransitionV0;
4529    use crate::state_transition::identity_credit_transfer_to_addresses_transition::IdentityCreditTransferToAddressesTransition;
4530    use crate::state_transition::identity_credit_withdrawal_transition::v1::IdentityCreditWithdrawalTransitionV1;
4531    use crate::withdrawal::Pooling as WithdrawalPooling;
4532
4533    fn sample_withdrawal_v1_st() -> StateTransition {
4534        let v1 = IdentityCreditWithdrawalTransitionV1 {
4535            identity_id: Identifier::from([12u8; 32]),
4536            amount: 777,
4537            core_fee_per_byte: 2,
4538            pooling: WithdrawalPooling::Standard,
4539            output_script: None,
4540            nonce: 9,
4541            user_fee_increase: 4,
4542            signature_public_key_id: 21,
4543            signature: BinaryData::new(vec![0x12; 65]),
4544        };
4545        StateTransition::IdentityCreditWithdrawal(IdentityCreditWithdrawalTransition::V1(v1))
4546    }
4547
4548    fn sample_credit_transfer_to_addresses_st() -> StateTransition {
4549        let v0 = IdentityCreditTransferToAddressesTransitionV0 {
4550            identity_id: Identifier::from([13u8; 32]),
4551            ..Default::default()
4552        };
4553        StateTransition::IdentityCreditTransferToAddresses(
4554            IdentityCreditTransferToAddressesTransition::V0(v0),
4555        )
4556    }
4557
4558    fn sample_address_credit_withdrawal_st() -> StateTransition {
4559        use crate::state_transition::address_credit_withdrawal_transition::v0::AddressCreditWithdrawalTransitionV0;
4560        StateTransition::AddressCreditWithdrawal(AddressCreditWithdrawalTransition::V0(
4561            AddressCreditWithdrawalTransitionV0::default(),
4562        ))
4563    }
4564
4565    fn sample_shield_from_asset_lock_st() -> StateTransition {
4566        use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0;
4567        StateTransition::ShieldFromAssetLock(ShieldFromAssetLockTransition::V0(
4568            ShieldFromAssetLockTransitionV0 {
4569                asset_lock_proof: Default::default(),
4570                actions: vec![],
4571                value_balance: 100,
4572                anchor: [0u8; 32],
4573                proof: vec![],
4574                binding_signature: [0u8; 64],
4575                surplus_output: None,
4576                signature: BinaryData::new(vec![0x55; 65]),
4577            },
4578        ))
4579    }
4580
4581    // ---------- IdentityCreditWithdrawal V1 accessors ----------
4582
4583    #[test]
4584    fn test_withdrawal_v1_name_and_type() {
4585        let st = sample_withdrawal_v1_st();
4586        assert_eq!(st.name(), "IdentityCreditWithdrawal");
4587        assert_eq!(
4588            st.state_transition_type(),
4589            StateTransitionType::IdentityCreditWithdrawal
4590        );
4591    }
4592
4593    #[test]
4594    fn test_withdrawal_v1_is_identity_signed_true() {
4595        assert!(sample_withdrawal_v1_st().is_identity_signed());
4596    }
4597
4598    #[test]
4599    fn test_withdrawal_v1_signature_and_owner_and_key_id() {
4600        let st = sample_withdrawal_v1_st();
4601        // signature accessor -> Some
4602        let sig = st.signature().expect("V1 withdrawal has a signature");
4603        assert_eq!(sig.as_slice(), &[0x12; 65]);
4604        // owner_id delegates to identity_id
4605        assert_eq!(st.owner_id(), Some(Identifier::from([12u8; 32])));
4606        assert_eq!(st.signature_public_key_id(), Some(21));
4607        assert_eq!(st.user_fee_increase(), 4);
4608    }
4609
4610    #[test]
4611    fn test_withdrawal_v1_set_signature_and_fee_and_key_id() {
4612        let mut st = sample_withdrawal_v1_st();
4613        assert!(st.set_signature(BinaryData::new(vec![0x99; 65])));
4614        assert_eq!(st.signature().unwrap().as_slice(), &[0x99; 65]);
4615
4616        st.set_user_fee_increase(33);
4617        assert_eq!(st.user_fee_increase(), 33);
4618
4619        st.set_signature_public_key_id(64);
4620        assert_eq!(st.signature_public_key_id(), Some(64));
4621    }
4622
4623    #[test]
4624    fn test_withdrawal_v1_serialize_roundtrip_via_state_transition() {
4625        use crate::serialization::{PlatformDeserializableUntrusted, PlatformSerializable};
4626        let original = sample_withdrawal_v1_st();
4627        let bytes = PlatformSerializable::serialize_to_bytes(&original).expect("serialize ok");
4628        let restored =
4629            StateTransition::deserialize_from_bytes_untrusted(&bytes).expect("deserialize ok");
4630        assert_eq!(original, restored);
4631        // The restored variant must still be V1, not V0 — exercises the
4632        // feature-version dispatch in deserialize.
4633        match restored {
4634            StateTransition::IdentityCreditWithdrawal(IdentityCreditWithdrawalTransition::V1(
4635                _,
4636            )) => {}
4637            other => panic!("expected V1 inner variant, got: {:?}", other),
4638        }
4639    }
4640
4641    #[test]
4642    fn test_withdrawal_v1_transaction_id_differs_from_v0() {
4643        // V0 and V1 carry different serialized forms → distinct transaction ids.
4644        let v0 = sample_withdrawal_st();
4645        let v1 = sample_withdrawal_v1_st();
4646        let id_v0 = v0.transaction_id().expect("v0 hash");
4647        let id_v1 = v1.transaction_id().expect("v1 hash");
4648        assert_ne!(id_v0, id_v1);
4649    }
4650
4651    #[test]
4652    fn test_withdrawal_v1_required_asset_lock_balance_errors() {
4653        let err = sample_withdrawal_v1_st()
4654            .required_asset_lock_balance_for_processing_start(PlatformVersion::latest())
4655            .expect_err("withdrawal is not an asset lock ST");
4656        matches!(err, ProtocolError::CorruptedCodeExecution(_));
4657    }
4658
4659    // ---------- IdentityCreditTransferToAddresses ----------
4660
4661    #[test]
4662    fn test_credit_transfer_to_addresses_name_and_type() {
4663        let st = sample_credit_transfer_to_addresses_st();
4664        assert_eq!(st.name(), "IdentityCreditTransferToAddresses");
4665        assert_eq!(
4666            st.state_transition_type(),
4667            StateTransitionType::IdentityCreditTransferToAddresses
4668        );
4669    }
4670
4671    #[test]
4672    fn test_credit_transfer_to_addresses_signature_some_and_owner_some() {
4673        let st = sample_credit_transfer_to_addresses_st();
4674        assert!(st.signature().is_some(), "has a signature field");
4675        assert_eq!(st.owner_id(), Some(Identifier::from([13u8; 32])));
4676    }
4677
4678    #[test]
4679    fn test_credit_transfer_to_addresses_is_identity_signed_true() {
4680        // Not in the "not identity signed" list → should be true.
4681        assert!(sample_credit_transfer_to_addresses_st().is_identity_signed());
4682    }
4683
4684    #[test]
4685    fn test_credit_transfer_to_addresses_inputs_none_active_range_11_latest() {
4686        let st = sample_credit_transfer_to_addresses_st();
4687        assert!(st.inputs().is_none());
4688        // Per mod.rs table: this variant is in the 11..=LATEST_VERSION group.
4689        let range = st.active_version_range();
4690        assert_eq!(*range.start(), 11);
4691        assert_eq!(*range.end(), LATEST_VERSION);
4692    }
4693
4694    #[test]
4695    fn test_credit_transfer_to_addresses_set_signature_returns_true() {
4696        let mut st = sample_credit_transfer_to_addresses_st();
4697        let ok = st.set_signature(BinaryData::new(vec![0x77; 65]));
4698        assert!(ok);
4699        assert_eq!(st.signature().unwrap().as_slice(), &[0x77; 65]);
4700    }
4701
4702    #[test]
4703    fn test_credit_transfer_to_addresses_from_outer_enum() {
4704        let outer = IdentityCreditTransferToAddressesTransition::V0(
4705            IdentityCreditTransferToAddressesTransitionV0::default(),
4706        );
4707        let st: StateTransition = outer.into();
4708        assert!(matches!(
4709            st,
4710            StateTransition::IdentityCreditTransferToAddresses(_)
4711        ));
4712    }
4713
4714    #[test]
4715    fn test_credit_transfer_to_addresses_user_fee_increase_setter() {
4716        let mut st = sample_credit_transfer_to_addresses_st();
4717        st.set_user_fee_increase(55);
4718        assert_eq!(st.user_fee_increase(), 55);
4719    }
4720
4721    // ---------- AddressCreditWithdrawal ----------
4722
4723    #[test]
4724    fn test_address_credit_withdrawal_name_type_and_accessors() {
4725        let st = sample_address_credit_withdrawal_st();
4726        assert_eq!(st.name(), "AddressCreditWithdrawal");
4727        assert_eq!(
4728            st.state_transition_type(),
4729            StateTransitionType::AddressCreditWithdrawal
4730        );
4731        // signature is None for AddressCreditWithdrawal (see mod.rs arm).
4732        assert!(st.signature().is_none());
4733        // owner_id is None for every address-* variant.
4734        assert!(st.owner_id().is_none());
4735        // inputs → Some (delegated to inner struct's inputs map, may be empty).
4736        assert!(st.inputs().is_some());
4737    }
4738
4739    #[test]
4740    fn test_address_credit_withdrawal_set_signature_returns_false() {
4741        let mut st = sample_address_credit_withdrawal_st();
4742        assert!(!st.set_signature(BinaryData::new(vec![0xAB; 65])));
4743    }
4744
4745    #[test]
4746    fn test_address_credit_withdrawal_is_identity_signed_true() {
4747        // Per mod.rs: `is_identity_signed` is !matches!(identity_create/topup/shield*/unshield/shielded*)
4748        // so address-* variants return true — even though signature() returns None.
4749        assert!(sample_address_credit_withdrawal_st().is_identity_signed());
4750    }
4751
4752    #[test]
4753    fn test_address_credit_withdrawal_active_range_is_11_latest() {
4754        let range = sample_address_credit_withdrawal_st().active_version_range();
4755        assert_eq!(*range.start(), 11);
4756        assert_eq!(*range.end(), LATEST_VERSION);
4757    }
4758
4759    // ---------- ShieldFromAssetLock ----------
4760
4761    #[test]
4762    fn test_shield_from_asset_lock_name_type_and_accessors() {
4763        let st = sample_shield_from_asset_lock_st();
4764        assert_eq!(st.name(), "ShieldFromAssetLock");
4765        assert_eq!(
4766            st.state_transition_type(),
4767            StateTransitionType::ShieldFromAssetLock
4768        );
4769        // signature IS present on ShieldFromAssetLock — Some arm.
4770        let sig = st
4771            .signature()
4772            .expect("shield-from-asset-lock has signature");
4773        assert_eq!(sig.as_slice(), &[0x55; 65]);
4774        // owner_id is always None for shielded-* arms.
4775        assert!(st.owner_id().is_none());
4776    }
4777
4778    #[test]
4779    fn test_shield_from_asset_lock_is_not_identity_signed() {
4780        assert!(!sample_shield_from_asset_lock_st().is_identity_signed());
4781    }
4782
4783    #[test]
4784    fn test_shield_from_asset_lock_optional_asset_lock_proof_some() {
4785        // Critical: this is one of the THREE arms where optional_asset_lock_proof
4786        // actually forwards to Some(_). Other Some arms are covered by
4787        // IdentityCreate and IdentityTopUp which have default asset lock proof.
4788        let st = sample_shield_from_asset_lock_st();
4789        assert!(st.optional_asset_lock_proof().is_some());
4790    }
4791
4792    #[test]
4793    fn test_shield_from_asset_lock_user_fee_increase_is_zero_and_setter_noop() {
4794        let mut st = sample_shield_from_asset_lock_st();
4795        assert_eq!(st.user_fee_increase(), 0);
4796        st.set_user_fee_increase(123);
4797        // Set is a no-op per mod.rs table.
4798        assert_eq!(st.user_fee_increase(), 0);
4799    }
4800
4801    #[test]
4802    fn test_shield_from_asset_lock_set_signature_returns_true() {
4803        let mut st = sample_shield_from_asset_lock_st();
4804        assert!(st.set_signature(BinaryData::new(vec![0x44; 65])));
4805        assert_eq!(st.signature().unwrap().as_slice(), &[0x44; 65]);
4806    }
4807
4808    #[test]
4809    fn test_shield_from_asset_lock_required_asset_lock_balance_succeeds() {
4810        // This is the only arm besides IdentityCreate/TopUp/AddressFundingFromAssetLock
4811        // that returns Ok from required_asset_lock_balance_for_processing_start.
4812        let st = sample_shield_from_asset_lock_st();
4813        let result = st.required_asset_lock_balance_for_processing_start(PlatformVersion::latest());
4814        assert!(
4815            result.is_ok(),
4816            "ShieldFromAssetLock should return Ok, got {:?}",
4817            result
4818        );
4819    }
4820
4821    #[test]
4822    fn test_shield_from_asset_lock_version_0_active_range_12_to_13() {
4823        // Version 0's bundle binds nothing; protocol version 14 admits only version 1.
4824        let range = sample_shield_from_asset_lock_st().active_version_range();
4825        assert_eq!(range, 12..=13);
4826    }
4827
4828    // ---------- Batch with Token transition exercises TokenTransfer arm
4829    //            in the name() nested match. ----------
4830
4831    #[test]
4832    fn test_batch_with_token_transfer_name_contains_token_transfer() {
4833        use crate::state_transition::batch_transition::batched_transition::token_transition::TokenTransition as TT;
4834        use crate::state_transition::batch_transition::batched_transition::BatchedTransition;
4835        use crate::state_transition::batch_transition::token_base_transition::v0::TokenBaseTransitionV0;
4836        use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition;
4837        use crate::state_transition::batch_transition::token_transfer_transition::v0::TokenTransferTransitionV0;
4838        use crate::state_transition::batch_transition::token_transfer_transition::TokenTransferTransition;
4839        use crate::state_transition::batch_transition::BatchTransitionV1;
4840
4841        let base = TokenBaseTransition::V0(TokenBaseTransitionV0 {
4842            identity_contract_nonce: 1,
4843            token_contract_position: 0,
4844            data_contract_id: Identifier::from([1u8; 32]),
4845            token_id: Identifier::from([2u8; 32]),
4846            using_group_info: None,
4847        });
4848        let token_transfer = TokenTransferTransition::V0(TokenTransferTransitionV0 {
4849            base,
4850            amount: 100,
4851            recipient_id: Identifier::from([3u8; 32]),
4852            public_note: None,
4853            shared_encrypted_note: None,
4854            private_encrypted_note: None,
4855        });
4856
4857        // BatchTransitionV1 is used for tokens. Build a single-token batch.
4858        let batch = BatchTransition::V1(BatchTransitionV1 {
4859            owner_id: Identifier::from([9u8; 32]),
4860            transitions: vec![BatchedTransition::Token(TT::Transfer(token_transfer))],
4861            user_fee_increase: 0,
4862            signature_public_key_id: 0,
4863            signature: BinaryData::new(vec![0u8; 65]),
4864        });
4865        let st = StateTransition::Batch(batch);
4866
4867        assert_eq!(st.name(), "DocumentsBatch([TokenTransfer])");
4868    }
4869
4870    // -----------------------------------------------------------------------
4871    // Cross-variant consistency: transaction_id is a 32-byte blake3/sha256 hash
4872    // of the serialized form. Make sure it's stable across clones for the newly
4873    // covered variants too.
4874    // -----------------------------------------------------------------------
4875
4876    #[test]
4877    fn test_transaction_id_length_32_for_new_variants() {
4878        for st in [
4879            sample_withdrawal_v1_st(),
4880            sample_credit_transfer_to_addresses_st(),
4881            sample_shield_from_asset_lock_st(),
4882        ] {
4883            let id = st.transaction_id().expect("hash");
4884            assert_eq!(id.len(), 32);
4885        }
4886    }
4887
4888    // -----------------------------------------------------------------------
4889    // Clone-and-equality coverage for newly added variants (PartialEq via
4890    // derived impl, exercises the top-level enum's PartialEq arms).
4891    // -----------------------------------------------------------------------
4892
4893    #[test]
4894    fn test_clone_eq_for_new_variants() {
4895        let cases = [
4896            sample_withdrawal_v1_st(),
4897            sample_credit_transfer_to_addresses_st(),
4898            sample_address_credit_withdrawal_st(),
4899            sample_shield_from_asset_lock_st(),
4900        ];
4901        for st in cases {
4902            let cloned = st.clone();
4903            assert_eq!(st, cloned, "clone must be equal for {}", st.name());
4904        }
4905    }
4906
4907    // -----------------------------------------------------------------------
4908    // unique_identifiers() for address-* variants: the implementation
4909    // dispatches via call_method! and each variant returns a non-empty Vec.
4910    // -----------------------------------------------------------------------
4911
4912    #[test]
4913    fn test_unique_identifiers_for_address_and_shielded_variants() {
4914        // The address variants compute identifiers from their `inputs` map.
4915        // With a default (empty inputs) transition, unique_identifiers is empty
4916        // — that's fine, but we still want to exercise the call_method!
4917        // dispatch for these arms without panicking.
4918        for st in [
4919            sample_address_credit_withdrawal_st(),
4920            sample_shield_from_asset_lock_st(),
4921            sample_credit_transfer_to_addresses_st(),
4922            sample_withdrawal_v1_st(),
4923        ] {
4924            // Just calling unique_identifiers exercises the match arm; the
4925            // result may be empty for default-constructed inputs-based
4926            // variants, non-empty for identity-based ones.
4927            let _ids = st.unique_identifiers();
4928        }
4929        // For the identity-based variants the result IS non-empty.
4930        assert!(!sample_withdrawal_v1_st().unique_identifiers().is_empty());
4931        assert!(!sample_credit_transfer_to_addresses_st()
4932            .unique_identifiers()
4933            .is_empty());
4934    }
4935
4936    // -----------------------------------------------------------------------
4937    // sign_with_core_signer byte-parity test
4938    //
4939    // Proves that `StateTransition::sign_with_core_signer` produces a
4940    // byte-identical signature to the legacy `sign_by_private_key` ECDSA path
4941    // when both are driven by the same underlying secret. This is the on-wire
4942    // contract the Swift / external-signer flow depends on: changing the
4943    // digest pre-image or the recoverable-compact encoding would silently
4944    // break asset-lock verification on testnet/mainnet, so we pin both shapes
4945    // here.
4946    // -----------------------------------------------------------------------
4947    #[cfg(all(
4948        feature = "state-transition-signing",
4949        feature = "core_key_wallet",
4950        feature = "bls-signatures"
4951    ))]
4952    #[tokio::test]
4953    async fn sign_with_core_signer_matches_sign_by_private_key_byte_for_byte() {
4954        use async_trait::async_trait;
4955        use dashcore::crypto::ecdsa::{EcdsaPublicKey, EcdsaSignature};
4956        use key_wallet::bip32::{DerivationPath, ExtendedPubKey};
4957        use key_wallet::signer::{ExtendedPubKeySigner, Signer as KwSigner, SignerMethod};
4958        use secp256k1::{self, rand, Message, PublicKey, SecretKey};
4959
4960        /// Fixed-key in-memory signer used only by this test. Mirrors how a
4961        /// real KeychainSigner would behave: derive once, sign atomically,
4962        /// return non-recoverable `(Signature, PublicKey)`. The path is
4963        /// ignored — the wrapper holds exactly one key.
4964        #[derive(Debug)]
4965        struct FixedKeySigner {
4966            secret: SecretKey,
4967            public: PublicKey,
4968        }
4969
4970        #[async_trait]
4971        impl KwSigner for FixedKeySigner {
4972            type Error = String;
4973
4974            fn supported_methods(&self) -> &[SignerMethod] {
4975                &[SignerMethod::Digest]
4976            }
4977
4978            async fn sign_ecdsa(
4979                &self,
4980                _path: &DerivationPath,
4981                sighash: [u8; 32],
4982            ) -> Result<(EcdsaSignature, EcdsaPublicKey), Self::Error> {
4983                let msg = Message::from_digest(sighash);
4984                let sig = self.secret.sign_ecdsa(msg);
4985                Ok((sig.into(), self.public.into()))
4986            }
4987
4988            async fn public_key(
4989                &self,
4990                _path: &DerivationPath,
4991            ) -> Result<EcdsaPublicKey, Self::Error> {
4992                Ok(self.public.into())
4993            }
4994        }
4995
4996        #[async_trait]
4997        impl ExtendedPubKeySigner for FixedKeySigner {
4998            async fn extended_public_key(
4999                &self,
5000                _path: &DerivationPath,
5001            ) -> Result<ExtendedPubKey, Self::Error> {
5002                Err("FixedKeySigner does not derive extended public keys".to_string())
5003            }
5004        }
5005
5006        // Generate a single random key. Using the same key on both sides is
5007        // load-bearing: the legacy path signs raw bytes, the signer path
5008        // derives + signs inside the trust boundary. If the digest pre-image
5009        // or compact-encoding differs, the bytes will diverge.
5010        let (secret_key, public_key) = secp256k1::generate_keypair(&mut rand::rng());
5011        let private_key_bytes = secret_key.to_secret_bytes();
5012
5013        let signer = FixedKeySigner {
5014            secret: secret_key,
5015            public: public_key,
5016        };
5017        let path = DerivationPath::default();
5018
5019        // Use a sample state transition that exercises signable_bytes() —
5020        // any signable ST works since we're only comparing the signature
5021        // bytes the two paths produce over the SAME `signable_bytes()`.
5022        let mut st_legacy = sample_transfer_st();
5023        let mut st_signer = sample_transfer_st();
5024
5025        // Sanity: both copies must have identical signable_bytes before signing.
5026        assert_eq!(
5027            st_legacy.signable_bytes().expect("legacy signable_bytes"),
5028            st_signer.signable_bytes().expect("signer signable_bytes"),
5029            "signable_bytes pre-image must match across copies"
5030        );
5031
5032        // Legacy path: raw &[u8] private key → 65-byte recoverable compact.
5033        // BLS is only used by `sign_by_private_key` when key_type is BLS12_381 —
5034        // for the ECDSA path it's unused, but the function signature requires
5035        // it, so we pass the NativeBlsModule that's already in the workspace.
5036        let bls = crate::bls::native_bls::NativeBlsModule;
5037        st_legacy
5038            .sign_by_private_key(&private_key_bytes, KeyType::ECDSA_HASH160, &bls)
5039            .expect("sign_by_private_key");
5040
5041        // New signer-driven path: digest → external signer → recovered →
5042        // 65-byte recoverable compact. Byte-identical to the legacy result.
5043        st_signer
5044            .sign_with_core_signer(&path, &signer)
5045            .await
5046            .expect("sign_with_core_signer");
5047
5048        let sig_legacy = st_legacy.signature().expect("legacy signature set");
5049        let sig_signer = st_signer.signature().expect("signer signature set");
5050
5051        assert_eq!(
5052            sig_legacy.as_slice().len(),
5053            65,
5054            "legacy ECDSA signature must be 65 bytes (recoverable compact)"
5055        );
5056        assert_eq!(
5057            sig_signer.as_slice().len(),
5058            65,
5059            "signer ECDSA signature must be 65 bytes (recoverable compact)"
5060        );
5061        assert_eq!(
5062            sig_legacy.as_slice(),
5063            sig_signer.as_slice(),
5064            "sign_with_core_signer must produce byte-identical output to sign_by_private_key"
5065        );
5066    }
5067}