diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration_item.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_item.rs index dcd92012b02..def46953e52 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration_item.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration_item.rs @@ -66,6 +66,62 @@ pub enum TokenConfigurationChangeItem { MainControlGroup(Option), } impl TokenConfigurationChangeItem { + pub fn payload_serialization(&self) -> Result>, ProtocolError> { + Ok(match self { + TokenConfigurationChangeItem::TokenConfigurationNoChange => None, + TokenConfigurationChangeItem::Conventions(convention) => Some( + bincode::encode_to_vec(convention, bincode::config::standard()) + .map_err(|e| ProtocolError::EncodingError(e.to_string()))?, + ), + TokenConfigurationChangeItem::ConventionsControlGroup(a) + | TokenConfigurationChangeItem::ConventionsAdminGroup(a) + | TokenConfigurationChangeItem::MaxSupplyControlGroup(a) + | TokenConfigurationChangeItem::MaxSupplyAdminGroup(a) + | TokenConfigurationChangeItem::PerpetualDistributionControlGroup(a) + | TokenConfigurationChangeItem::PerpetualDistributionAdminGroup(a) + | TokenConfigurationChangeItem::NewTokensDestinationIdentityControlGroup(a) + | TokenConfigurationChangeItem::NewTokensDestinationIdentityAdminGroup(a) + | TokenConfigurationChangeItem::MintingAllowChoosingDestinationControlGroup(a) + | TokenConfigurationChangeItem::MintingAllowChoosingDestinationAdminGroup(a) + | TokenConfigurationChangeItem::ManualMinting(a) + | TokenConfigurationChangeItem::ManualMintingAdminGroup(a) + | TokenConfigurationChangeItem::ManualBurning(a) + | TokenConfigurationChangeItem::ManualBurningAdminGroup(a) + | TokenConfigurationChangeItem::Freeze(a) + | TokenConfigurationChangeItem::FreezeAdminGroup(a) + | TokenConfigurationChangeItem::Unfreeze(a) + | TokenConfigurationChangeItem::UnfreezeAdminGroup(a) + | TokenConfigurationChangeItem::DestroyFrozenFunds(a) + | TokenConfigurationChangeItem::DestroyFrozenFundsAdminGroup(a) + | TokenConfigurationChangeItem::EmergencyAction(a) + | TokenConfigurationChangeItem::EmergencyActionAdminGroup(a) + | TokenConfigurationChangeItem::MarketplaceTradeModeControlGroup(a) + | TokenConfigurationChangeItem::MarketplaceTradeModeAdminGroup(a) => Some(a.to_bytes()), + TokenConfigurationChangeItem::MaxSupply(max_supply) => { + max_supply.map(|amount| amount.to_be_bytes().to_vec()) + } + TokenConfigurationChangeItem::PerpetualDistribution(distribution) => distribution + .as_ref() + .map(|dist| { + bincode::encode_to_vec(dist, bincode::config::standard()) + .map_err(|e| ProtocolError::EncodingError(e.to_string())) + }) + .transpose()?, + TokenConfigurationChangeItem::NewTokensDestinationIdentity(identity) => { + identity.map(|id| id.to_vec()) + } + TokenConfigurationChangeItem::MintingAllowChoosingDestination(allow) => { + Some(vec![*allow as u8]) + } + TokenConfigurationChangeItem::MarketplaceTradeMode(mode) => Some( + bincode::encode_to_vec(mode, bincode::config::standard()) + .map_err(|e| ProtocolError::EncodingError(e.to_string()))?, + ), + TokenConfigurationChangeItem::MainControlGroup(position) => { + position.map(|pos| pos.to_be_bytes().to_vec()) + } + }) + } pub fn u8_item_index(&self) -> u8 { match self { TokenConfigurationChangeItem::TokenConfigurationNoChange => 0, diff --git a/packages/rs-dpp/src/data_contract/change_control_rules/authorized_action_takers.rs b/packages/rs-dpp/src/data_contract/change_control_rules/authorized_action_takers.rs index 7912d3b5360..a980d0549c3 100644 --- a/packages/rs-dpp/src/data_contract/change_control_rules/authorized_action_takers.rs +++ b/packages/rs-dpp/src/data_contract/change_control_rules/authorized_action_takers.rs @@ -2,6 +2,7 @@ use crate::data_contract::group::accessors::v0::GroupV0Getters; use crate::data_contract::group::{Group, GroupMemberPower}; use crate::data_contract::GroupContractPosition; use crate::group::action_taker::{ActionGoal, ActionTaker}; +use crate::ProtocolError; use bincode::{Decode, Encode}; use platform_value::Identifier; use serde::{Deserialize, Serialize}; @@ -33,6 +34,61 @@ impl fmt::Display for AuthorizedActionTakers { } impl AuthorizedActionTakers { + pub fn to_bytes(&self) -> Vec { + match self { + AuthorizedActionTakers::NoOne => vec![0], + AuthorizedActionTakers::ContractOwner => vec![1], + AuthorizedActionTakers::Identity(identifier) => { + let mut bytes = vec![2]; + bytes.extend_from_slice(identifier.as_bytes()); + bytes + } + AuthorizedActionTakers::MainGroup => vec![3], + AuthorizedActionTakers::Group(position) => { + let mut bytes = vec![4]; + bytes.extend_from_slice(&position.to_be_bytes()); + bytes + } + } + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + let Some(&tag) = bytes.first() else { + return Err(ProtocolError::DecodingError( + "empty bytes for AuthorizedActionTakers".to_string(), + )); + }; + match tag { + 0 => Ok(AuthorizedActionTakers::NoOne), + 1 => Ok(AuthorizedActionTakers::ContractOwner), + 2 => { + if bytes.len() != 33 { + return Err(ProtocolError::DecodingError(format!( + "expected 33 bytes for AuthorizedActionTakers::Identity, got {}", + bytes.len() + ))); + } + let identifier = Identifier::from_bytes(&bytes[1..]) + .map_err(|e| ProtocolError::DecodingError(e.to_string()))?; + Ok(AuthorizedActionTakers::Identity(identifier)) + } + 3 => Ok(AuthorizedActionTakers::MainGroup), + 4 => { + if bytes.len() != 3 { + return Err(ProtocolError::DecodingError(format!( + "expected 3 bytes for AuthorizedActionTakers::Group, got {}", + bytes.len() + ))); + } + let position = u16::from_be_bytes([bytes[1], bytes[2]]); + Ok(AuthorizedActionTakers::Group(position)) + } + other => Err(ProtocolError::DecodingError(format!( + "unknown AuthorizedActionTakers tag: {}", + other + ))), + } + } pub fn allowed_for_action_taker( &self, contract_owner_id: &Identifier, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/multi_party_action.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/multi_party_action.rs index 52f7cf4e7f4..3aedf6d3cbc 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/multi_party_action.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/multi_party_action.rs @@ -1,5 +1,11 @@ +use crate::ProtocolError; use platform_value::Identifier; +use platform_version::version::PlatformVersion; pub trait AllowedAsMultiPartyAction { - fn calculate_action_id(&self, owner_id: Identifier) -> Identifier; + fn calculate_action_id( + &self, + owner_id: Identifier, + platform_version: &PlatformVersion, + ) -> Result; } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_burn_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_burn_transition/v0/v0_methods.rs index ee24438ecf4..9925f2cf675 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_burn_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_burn_transition/v0/v0_methods.rs @@ -1,4 +1,6 @@ use platform_value::Identifier; +use platform_version::version::PlatformVersion; +use crate::ProtocolError; use crate::state_transition::batch_transition::batched_transition::multi_party_action::AllowedAsMultiPartyAction; use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition; @@ -60,16 +62,20 @@ impl TokenBurnTransitionV0Methods for TokenBurnTransitionV0 { } impl AllowedAsMultiPartyAction for TokenBurnTransitionV0 { - fn calculate_action_id(&self, owner_id: Identifier) -> Identifier { + fn calculate_action_id( + &self, + owner_id: Identifier, + _platform_version: &PlatformVersion, + ) -> Result { let TokenBurnTransitionV0 { base, burn_amount, .. } = self; - TokenBurnTransition::calculate_action_id_with_fields( + Ok(TokenBurnTransition::calculate_action_id_with_fields( base.token_id().as_bytes(), owner_id.as_bytes(), base.identity_contract_nonce(), *burn_amount, - ) + )) } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_burn_transition/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_burn_transition/v0_methods.rs index d37a08463fe..edd576753aa 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_burn_transition/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_burn_transition/v0_methods.rs @@ -1,6 +1,8 @@ use platform_value::Identifier; +use platform_version::version::PlatformVersion; use crate::balances::credits::TokenAmount; use crate::prelude::IdentityNonce; +use crate::ProtocolError; use crate::state_transition::batch_transition::batched_transition::multi_party_action::AllowedAsMultiPartyAction; use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition; @@ -61,9 +63,13 @@ impl TokenBurnTransitionV0Methods for TokenBurnTransition { } impl AllowedAsMultiPartyAction for TokenBurnTransition { - fn calculate_action_id(&self, owner_id: Identifier) -> Identifier { + fn calculate_action_id( + &self, + owner_id: Identifier, + platform_version: &PlatformVersion, + ) -> Result { match self { - TokenBurnTransition::V0(v0) => v0.calculate_action_id(owner_id), + TokenBurnTransition::V0(v0) => v0.calculate_action_id(owner_id, platform_version), } } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_config_update_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_config_update_transition/v0/v0_methods.rs index a9e24acc377..c4fe6cff67b 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_config_update_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_config_update_transition/v0/v0_methods.rs @@ -1,5 +1,7 @@ use platform_value::Identifier; +use platform_version::version::PlatformVersion; use crate::data_contract::associated_token::token_configuration_item::TokenConfigurationChangeItem; +use crate::ProtocolError; use crate::state_transition::batch_transition::batched_transition::multi_party_action::AllowedAsMultiPartyAction; use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition; @@ -69,7 +71,12 @@ impl TokenConfigUpdateTransitionV0Methods for TokenConfigUpdateTransitionV0 { } impl AllowedAsMultiPartyAction for TokenConfigUpdateTransitionV0 { - fn calculate_action_id(&self, owner_id: Identifier) -> Identifier { + /// v0 action_id: uses only the u8 discriminant (kept for backward compat). + fn calculate_action_id( + &self, + owner_id: Identifier, + platform_version: &PlatformVersion, + ) -> Result { let TokenConfigUpdateTransitionV0 { base, update_token_configuration_item, @@ -80,7 +87,8 @@ impl AllowedAsMultiPartyAction for TokenConfigUpdateTransitionV0 { base.token_id().as_bytes(), owner_id.as_bytes(), base.identity_contract_nonce(), - update_token_configuration_item.u8_item_index(), + update_token_configuration_item, + platform_version, ) } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_config_update_transition/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_config_update_transition/v0_methods.rs index b16720f136c..c2a65faad69 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_config_update_transition/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_config_update_transition/v0_methods.rs @@ -1,6 +1,8 @@ use platform_value::Identifier; +use platform_version::version::PlatformVersion; use crate::data_contract::associated_token::token_configuration_item::TokenConfigurationChangeItem; use crate::prelude::IdentityNonce; +use crate::ProtocolError; use crate::state_transition::batch_transition::batched_transition::multi_party_action::AllowedAsMultiPartyAction; use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition; @@ -66,15 +68,58 @@ impl TokenConfigUpdateTransitionV0Methods for TokenConfigUpdateTransition { } impl AllowedAsMultiPartyAction for TokenConfigUpdateTransition { - fn calculate_action_id(&self, owner_id: Identifier) -> Identifier { + fn calculate_action_id( + &self, + owner_id: Identifier, + platform_version: &PlatformVersion, + ) -> Result { match self { - TokenConfigUpdateTransition::V0(v0) => v0.calculate_action_id(owner_id), + TokenConfigUpdateTransition::V0(v0) => { + v0.calculate_action_id(owner_id, platform_version) + } } } } impl TokenConfigUpdateTransition { pub fn calculate_action_id_with_fields( + token_id: &[u8; 32], + owner_id: &[u8; 32], + identity_contract_nonce: IdentityNonce, + token_configuration_change_item: &TokenConfigurationChangeItem, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .dpp + .token_versions + .token_config_update_action_id_version + { + 0 => Ok(Self::calculate_action_id_with_fields_v0( + token_id, + owner_id, + identity_contract_nonce, + token_configuration_change_item.u8_item_index(), + )), + 1 => { + let payload = token_configuration_change_item.payload_serialization()?; + Ok(Self::calculate_action_id_with_fields_v1( + token_id, + owner_id, + identity_contract_nonce, + token_configuration_change_item.u8_item_index(), + payload.as_deref(), + )) + } + version => Err(ProtocolError::UnknownVersionMismatch { + method: "calculate_action_id_with_fields".to_string(), + known_versions: vec![0, 1], + received: version, + }), + } + } + /// v0: action_id uses only the u8 discriminant of the config change item. + /// This is kept for backward compatibility with existing production data. + fn calculate_action_id_with_fields_v0( token_id: &[u8; 32], owner_id: &[u8; 32], identity_contract_nonce: IdentityNonce, @@ -88,4 +133,287 @@ impl TokenConfigUpdateTransition { hash_double(bytes).into() } + + /// v1: action_id includes the u8 discriminant plus an optional serialized + /// payload, binding the voted-on value into the hash and preventing + /// vote-swap attacks. + fn calculate_action_id_with_fields_v1( + token_id: &[u8; 32], + owner_id: &[u8; 32], + identity_contract_nonce: IdentityNonce, + update_token_config_item: u8, + payload: Option<&[u8]>, + ) -> Identifier { + let mut bytes = b"action_token_config_update".to_vec(); + bytes.extend_from_slice(token_id); + bytes.extend_from_slice(owner_id); + bytes.extend_from_slice(&identity_contract_nonce.to_be_bytes()); + bytes.push(update_token_config_item); + if let Some(payload) = payload { + bytes.extend_from_slice(payload); + } + + hash_double(bytes).into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::data_contract::associated_token::token_configuration_item::TokenConfigurationChangeItem; + use crate::state_transition::batch_transition::token_base_transition::v0::v0_methods::TokenBaseTransitionV0Methods; + use crate::state_transition::batch_transition::token_base_transition::v0::TokenBaseTransitionV0; + use crate::state_transition::batch_transition::token_config_update_transition::TokenConfigUpdateTransitionV0; + + fn make_transition(item: TokenConfigurationChangeItem) -> TokenConfigUpdateTransition { + TokenConfigUpdateTransition::V0(TokenConfigUpdateTransitionV0 { + base: TokenBaseTransition::V0(TokenBaseTransitionV0 { + identity_contract_nonce: 1, + token_contract_position: 0, + data_contract_id: Identifier::new([1u8; 32]), + token_id: Identifier::new([2u8; 32]), + using_group_info: None, + }), + update_token_configuration_item: item, + public_note: None, + }) + } + + #[test] + fn v0_action_id_same_discriminant_different_values_produces_same_id_vulnerability() { + // This test documents the v0 vulnerability: two different MaxSupply + // values produce the same action_id because only the u8 discriminant + // is hashed, not the actual value. + let owner_id = Identifier::new([3u8; 32]); + + let t_small = make_transition(TokenConfigurationChangeItem::MaxSupply(Some(100))); + let t_large = make_transition(TokenConfigurationChangeItem::MaxSupply(Some( + 999_999_999_999, + ))); + + let platform_version = PlatformVersion::first(); + let id_small = t_small + .calculate_action_id(owner_id, platform_version) + .expect("expected action id"); + let id_large = t_large + .calculate_action_id(owner_id, platform_version) + .expect("expected action id"); + + // v0: these are EQUAL -- the vulnerability + assert_eq!( + id_small, id_large, + "v0 should produce the same action_id for different MaxSupply values (vulnerability)" + ); + } + + #[test] + fn v1_same_discriminant_different_values_produces_different_ids_through_production_path() { + // This is the core regression test for the vote-swap fix. + // Uses the full production calculate_action_id path on the latest + // platform version (v1) to prove that different MaxSupply values + // produce different action_ids. + let owner_id = Identifier::new([3u8; 32]); + let platform_version = PlatformVersion::latest(); + + let t_small = make_transition(TokenConfigurationChangeItem::MaxSupply(Some(100))); + let t_large = make_transition(TokenConfigurationChangeItem::MaxSupply(Some( + 999_999_999_999, + ))); + + let id_small = t_small + .calculate_action_id(owner_id, platform_version) + .expect("expected action id"); + let id_large = t_large + .calculate_action_id(owner_id, platform_version) + .expect("expected action id"); + + // v1: these must be DIFFERENT -- the fix + assert_ne!( + id_small, id_large, + "v1 should produce different action_ids for different MaxSupply values" + ); + } + + #[test] + fn v1_different_item_types_produces_different_ids_through_production_path() { + let owner_id = Identifier::new([3u8; 32]); + let platform_version = PlatformVersion::latest(); + + let t_max = make_transition(TokenConfigurationChangeItem::MaxSupply(Some(100))); + let t_dest = + make_transition(TokenConfigurationChangeItem::MintingAllowChoosingDestination(true)); + + let id_max = t_max + .calculate_action_id(owner_id, platform_version) + .expect("expected action id"); + let id_dest = t_dest + .calculate_action_id(owner_id, platform_version) + .expect("expected action id"); + + assert_ne!( + id_max, id_dest, + "v1 should produce different action_ids for different config item types" + ); + } + + #[test] + fn v0_and_v1_produce_different_ids_for_same_input() { + // Verify v0 (first platform version) and v1 (latest) produce + // different action_ids for the same config item. + let owner_id = Identifier::new([3u8; 32]); + + let t = make_transition(TokenConfigurationChangeItem::MaxSupply(Some(100))); + + let id_v0 = t + .calculate_action_id(owner_id, PlatformVersion::first()) + .expect("expected action id"); + let id_v1 = t + .calculate_action_id(owner_id, PlatformVersion::latest()) + .expect("expected action id"); + + assert_ne!( + id_v0, id_v1, + "v0 and v1 should produce different action_ids for the same config item" + ); + } + + #[test] + fn versioned_dispatch_uses_v1_on_current_platform_version() { + // On the current platform version (v12), token_config_update_action_id_version + // is 1, so the versioned method should produce the v1 result (which includes + // the full config item payload), NOT the v0 result (discriminant only). + let owner_id = Identifier::new([3u8; 32]); + let t = make_transition(TokenConfigurationChangeItem::MaxSupply(Some(100))); + + let platform_version = PlatformVersion::latest(); + + let id_plain_v0 = t + .calculate_action_id(owner_id, PlatformVersion::first()) + .expect("expected action id"); + let id_versioned = t + .calculate_action_id(owner_id, platform_version) + .expect("expected action id"); + + // v1 produces a different id from v0 because it hashes the full payload + assert_ne!( + id_plain_v0, id_versioned, + "on current platform version (v1), versioned should differ from plain (v0)" + ); + + // Verify the versioned result matches v1 directly + let base = t.base(); + let item = t.update_token_configuration_item(); + let id_v1 = TokenConfigUpdateTransition::calculate_action_id_with_fields_v1( + base.token_id().as_bytes(), + owner_id.as_bytes(), + base.identity_contract_nonce(), + item.u8_item_index(), + item.payload_serialization() + .expect("expected to serialize payload") + .as_deref(), + ); + assert_eq!( + id_versioned, id_v1, + "versioned dispatch should use v1 on current platform version" + ); + } + + #[test] + fn v1_identical_items_produces_same_id_through_production_path() { + // Sanity check: identical config items should produce the same + // action_id under v1, through the production path. + let owner_id = Identifier::new([3u8; 32]); + let platform_version = PlatformVersion::latest(); + + let t1 = make_transition(TokenConfigurationChangeItem::MaxSupply(Some(42))); + let t2 = make_transition(TokenConfigurationChangeItem::MaxSupply(Some(42))); + + let id1 = t1 + .calculate_action_id(owner_id, platform_version) + .expect("expected action id"); + let id2 = t2 + .calculate_action_id(owner_id, platform_version) + .expect("expected action id"); + + assert_eq!( + id1, id2, + "v1 should produce the same action_id for identical config items" + ); + } + + #[test] + fn v1_authorized_action_takers_variant_differentiates_values() { + // Exercises payload_serialization() for AuthorizedActionTakers-based + // variants, which use to_bytes() internally. A regression in + // AuthorizedActionTakers::to_bytes() or payload_serialization() + // would be caught here. + use crate::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; + + let owner_id = Identifier::new([3u8; 32]); + let platform_version = PlatformVersion::latest(); + + let t_group5 = make_transition(TokenConfigurationChangeItem::ManualMinting( + AuthorizedActionTakers::Group(5), + )); + let t_group9 = make_transition(TokenConfigurationChangeItem::ManualMinting( + AuthorizedActionTakers::Group(9), + )); + let t_no_one = make_transition(TokenConfigurationChangeItem::ManualMinting( + AuthorizedActionTakers::NoOne, + )); + + let id_group5 = t_group5 + .calculate_action_id(owner_id, platform_version) + .expect("expected action id"); + let id_group9 = t_group9 + .calculate_action_id(owner_id, platform_version) + .expect("expected action id"); + let id_no_one = t_no_one + .calculate_action_id(owner_id, platform_version) + .expect("expected action id"); + + assert_ne!( + id_group5, id_group9, + "ManualMinting(Group(5)) and ManualMinting(Group(9)) must differ" + ); + assert_ne!( + id_group5, id_no_one, + "ManualMinting(Group(5)) and ManualMinting(NoOne) must differ" + ); + } + + #[test] + fn v1_none_payload_variants_still_differentiated_by_discriminant() { + // MaxSupply(None), NewTokensDestinationIdentity(None), + // PerpetualDistribution(None), and MainControlGroup(None) all + // return None from payload_serialization(). They must still produce + // different action_ids because of their different u8 discriminants. + let owner_id = Identifier::new([3u8; 32]); + let platform_version = PlatformVersion::latest(); + + let t_max = make_transition(TokenConfigurationChangeItem::MaxSupply(None)); + let t_dest = make_transition(TokenConfigurationChangeItem::NewTokensDestinationIdentity( + None, + )); + let t_dist = make_transition(TokenConfigurationChangeItem::PerpetualDistribution(None)); + let t_ctrl = make_transition(TokenConfigurationChangeItem::MainControlGroup(None)); + + let ids: Vec<_> = [t_max, t_dest, t_dist, t_ctrl] + .iter() + .map(|t| { + t.calculate_action_id(owner_id, platform_version) + .expect("expected action id") + }) + .collect(); + + // All 4 must be unique + for i in 0..ids.len() { + for j in (i + 1)..ids.len() { + assert_ne!( + ids[i], ids[j], + "None-payload variants at indices {i} and {j} must have different action_ids" + ); + } + } + } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_destroy_frozen_funds_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_destroy_frozen_funds_transition/v0/v0_methods.rs index ccd6c0b24ce..46d66bf0490 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_destroy_frozen_funds_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_destroy_frozen_funds_transition/v0/v0_methods.rs @@ -1,4 +1,6 @@ use platform_value::Identifier; +use platform_version::version::PlatformVersion; +use crate::ProtocolError; use crate::state_transition::batch_transition::batched_transition::multi_party_action::AllowedAsMultiPartyAction; use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition; @@ -61,18 +63,24 @@ impl TokenDestroyFrozenFundsTransitionV0Methods for TokenDestroyFrozenFundsTrans } impl AllowedAsMultiPartyAction for TokenDestroyFrozenFundsTransitionV0 { - fn calculate_action_id(&self, owner_id: Identifier) -> Identifier { + fn calculate_action_id( + &self, + owner_id: Identifier, + _platform_version: &PlatformVersion, + ) -> Result { let TokenDestroyFrozenFundsTransitionV0 { base, frozen_identity_id, .. } = self; - TokenDestroyFrozenFundsTransition::calculate_action_id_with_fields( - base.token_id().as_bytes(), - owner_id.as_bytes(), - base.identity_contract_nonce(), - frozen_identity_id.as_bytes(), + Ok( + TokenDestroyFrozenFundsTransition::calculate_action_id_with_fields( + base.token_id().as_bytes(), + owner_id.as_bytes(), + base.identity_contract_nonce(), + frozen_identity_id.as_bytes(), + ), ) } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_destroy_frozen_funds_transition/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_destroy_frozen_funds_transition/v0_methods.rs index e0560b49498..ce0ee2672cb 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_destroy_frozen_funds_transition/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_destroy_frozen_funds_transition/v0_methods.rs @@ -1,5 +1,7 @@ use platform_value::Identifier; +use platform_version::version::PlatformVersion; use crate::prelude::IdentityNonce; +use crate::ProtocolError; use crate::state_transition::batch_transition::batched_transition::multi_party_action::AllowedAsMultiPartyAction; use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition; @@ -62,9 +64,15 @@ impl TokenDestroyFrozenFundsTransitionV0Methods for TokenDestroyFrozenFundsTrans } impl AllowedAsMultiPartyAction for TokenDestroyFrozenFundsTransition { - fn calculate_action_id(&self, owner_id: Identifier) -> Identifier { + fn calculate_action_id( + &self, + owner_id: Identifier, + platform_version: &PlatformVersion, + ) -> Result { match self { - TokenDestroyFrozenFundsTransition::V0(v0) => v0.calculate_action_id(owner_id), + TokenDestroyFrozenFundsTransition::V0(v0) => { + v0.calculate_action_id(owner_id, platform_version) + } } } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_emergency_action_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_emergency_action_transition/v0/v0_methods.rs index 02abb8e415e..4b9c5823e56 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_emergency_action_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_emergency_action_transition/v0/v0_methods.rs @@ -1,4 +1,6 @@ use platform_value::Identifier; +use platform_version::version::PlatformVersion; +use crate::ProtocolError; use crate::state_transition::batch_transition::batched_transition::multi_party_action::AllowedAsMultiPartyAction; use crate::state_transition::batch_transition::batched_transition::token_emergency_action_transition::TokenEmergencyActionTransitionV0; use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; @@ -63,18 +65,24 @@ impl TokenEmergencyActionTransitionV0Methods for TokenEmergencyActionTransitionV } impl AllowedAsMultiPartyAction for TokenEmergencyActionTransitionV0 { - fn calculate_action_id(&self, owner_id: Identifier) -> Identifier { + fn calculate_action_id( + &self, + owner_id: Identifier, + _platform_version: &PlatformVersion, + ) -> Result { let TokenEmergencyActionTransitionV0 { base, emergency_action, .. } = self; - TokenEmergencyActionTransition::calculate_action_id_with_fields( - base.token_id().as_bytes(), - owner_id.as_bytes(), - base.identity_contract_nonce(), - *emergency_action, + Ok( + TokenEmergencyActionTransition::calculate_action_id_with_fields( + base.token_id().as_bytes(), + owner_id.as_bytes(), + base.identity_contract_nonce(), + *emergency_action, + ), ) } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_emergency_action_transition/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_emergency_action_transition/v0_methods.rs index c57cf214ae2..e49eed4a151 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_emergency_action_transition/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_emergency_action_transition/v0_methods.rs @@ -1,5 +1,7 @@ use platform_value::Identifier; +use platform_version::version::PlatformVersion; use crate::prelude::IdentityNonce; +use crate::ProtocolError; use crate::state_transition::batch_transition::batched_transition::multi_party_action::AllowedAsMultiPartyAction; use crate::state_transition::batch_transition::batched_transition::token_emergency_action_transition::v0::v0_methods::TokenEmergencyActionTransitionV0Methods; use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; @@ -61,9 +63,15 @@ impl TokenEmergencyActionTransitionV0Methods for TokenEmergencyActionTransition } impl AllowedAsMultiPartyAction for TokenEmergencyActionTransition { - fn calculate_action_id(&self, owner_id: Identifier) -> Identifier { + fn calculate_action_id( + &self, + owner_id: Identifier, + platform_version: &PlatformVersion, + ) -> Result { match self { - TokenEmergencyActionTransition::V0(v0) => v0.calculate_action_id(owner_id), + TokenEmergencyActionTransition::V0(v0) => { + v0.calculate_action_id(owner_id, platform_version) + } } } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_freeze_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_freeze_transition/v0/v0_methods.rs index 78a9555038c..428070b4f4b 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_freeze_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_freeze_transition/v0/v0_methods.rs @@ -1,4 +1,6 @@ use platform_value::Identifier; +use platform_version::version::PlatformVersion; +use crate::ProtocolError; use crate::state_transition::batch_transition::batched_transition::multi_party_action::AllowedAsMultiPartyAction; use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition; @@ -61,18 +63,22 @@ impl TokenFreezeTransitionV0Methods for TokenFreezeTransitionV0 { } impl AllowedAsMultiPartyAction for TokenFreezeTransitionV0 { - fn calculate_action_id(&self, owner_id: Identifier) -> Identifier { + fn calculate_action_id( + &self, + owner_id: Identifier, + _platform_version: &PlatformVersion, + ) -> Result { let TokenFreezeTransitionV0 { base, identity_to_freeze_id: frozen_identity_id, .. } = self; - TokenFreezeTransition::calculate_action_id_with_fields( + Ok(TokenFreezeTransition::calculate_action_id_with_fields( base.token_id().as_bytes(), owner_id.as_bytes(), base.identity_contract_nonce(), frozen_identity_id.as_bytes(), - ) + )) } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_freeze_transition/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_freeze_transition/v0_methods.rs index fc4642abefb..c5e79a0659e 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_freeze_transition/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_freeze_transition/v0_methods.rs @@ -1,5 +1,7 @@ use platform_value::Identifier; +use platform_version::version::PlatformVersion; use crate::prelude::IdentityNonce; +use crate::ProtocolError; use crate::state_transition::batch_transition::batched_transition::multi_party_action::AllowedAsMultiPartyAction; use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition; @@ -60,9 +62,13 @@ impl TokenFreezeTransitionV0Methods for TokenFreezeTransition { } impl AllowedAsMultiPartyAction for TokenFreezeTransition { - fn calculate_action_id(&self, owner_id: Identifier) -> Identifier { + fn calculate_action_id( + &self, + owner_id: Identifier, + platform_version: &PlatformVersion, + ) -> Result { match self { - TokenFreezeTransition::V0(v0) => v0.calculate_action_id(owner_id), + TokenFreezeTransition::V0(v0) => v0.calculate_action_id(owner_id, platform_version), } } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_mint_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_mint_transition/v0/v0_methods.rs index 3b3694a442d..41528e0022d 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_mint_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_mint_transition/v0/v0_methods.rs @@ -1,4 +1,5 @@ use platform_value::Identifier; +use platform_version::version::PlatformVersion; use crate::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Getters; use crate::data_contract::associated_token::token_configuration::TokenConfiguration; use crate::data_contract::associated_token::token_distribution_rules::accessors::v0::TokenDistributionRulesV0Getters; @@ -99,14 +100,18 @@ impl TokenMintTransitionV0Methods for TokenMintTransitionV0 { } impl AllowedAsMultiPartyAction for TokenMintTransitionV0 { - fn calculate_action_id(&self, owner_id: Identifier) -> Identifier { + fn calculate_action_id( + &self, + owner_id: Identifier, + _platform_version: &PlatformVersion, + ) -> Result { let TokenMintTransitionV0 { base, amount, .. } = self; - TokenMintTransition::calculate_action_id_with_fields( + Ok(TokenMintTransition::calculate_action_id_with_fields( base.token_id().as_bytes(), owner_id.as_bytes(), base.identity_contract_nonce(), *amount, - ) + )) } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_mint_transition/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_mint_transition/v0_methods.rs index b956f1f71c4..2fb4193eedf 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_mint_transition/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_mint_transition/v0_methods.rs @@ -1,4 +1,5 @@ use platform_value::Identifier; +use platform_version::version::PlatformVersion; use crate::balances::credits::TokenAmount; use crate::data_contract::associated_token::token_configuration::TokenConfiguration; use crate::prelude::IdentityNonce; @@ -84,9 +85,13 @@ impl TokenMintTransitionV0Methods for TokenMintTransition { } impl AllowedAsMultiPartyAction for TokenMintTransition { - fn calculate_action_id(&self, owner_id: Identifier) -> Identifier { + fn calculate_action_id( + &self, + owner_id: Identifier, + platform_version: &PlatformVersion, + ) -> Result { match self { - TokenMintTransition::V0(v0) => v0.calculate_action_id(owner_id), + TokenMintTransition::V0(v0) => v0.calculate_action_id(owner_id, platform_version), } } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_set_price_for_direct_purchase_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_set_price_for_direct_purchase_transition/v0/v0_methods.rs index 8ec1935292b..b987d4664e2 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_set_price_for_direct_purchase_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_set_price_for_direct_purchase_transition/v0/v0_methods.rs @@ -1,4 +1,6 @@ use platform_value::Identifier; +use platform_version::version::PlatformVersion; +use crate::ProtocolError; use crate::state_transition::batch_transition::batched_transition::multi_party_action::AllowedAsMultiPartyAction; use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition; @@ -63,14 +65,20 @@ impl TokenSetPriceForDirectPurchaseTransitionV0Methods } impl AllowedAsMultiPartyAction for TokenSetPriceForDirectPurchaseTransitionV0 { - fn calculate_action_id(&self, owner_id: Identifier) -> Identifier { + fn calculate_action_id( + &self, + owner_id: Identifier, + _platform_version: &PlatformVersion, + ) -> Result { let TokenSetPriceForDirectPurchaseTransitionV0 { base, price, .. } = self; - TokenSetPriceForDirectPurchaseTransition::calculate_action_id_with_fields( - base.token_id().as_bytes(), - owner_id.as_bytes(), - base.identity_contract_nonce(), - price.as_ref(), + Ok( + TokenSetPriceForDirectPurchaseTransition::calculate_action_id_with_fields( + base.token_id().as_bytes(), + owner_id.as_bytes(), + base.identity_contract_nonce(), + price.as_ref(), + ), ) } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_set_price_for_direct_purchase_transition/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_set_price_for_direct_purchase_transition/v0_methods.rs index 15409f6009f..dc838ac1e78 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_set_price_for_direct_purchase_transition/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_set_price_for_direct_purchase_transition/v0_methods.rs @@ -1,5 +1,7 @@ use platform_value::Identifier; +use platform_version::version::PlatformVersion; use crate::prelude::IdentityNonce; +use crate::ProtocolError; use crate::state_transition::batch_transition::batched_transition::multi_party_action::AllowedAsMultiPartyAction; use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition; @@ -63,9 +65,15 @@ impl TokenSetPriceForDirectPurchaseTransitionV0Methods } impl AllowedAsMultiPartyAction for TokenSetPriceForDirectPurchaseTransition { - fn calculate_action_id(&self, owner_id: Identifier) -> Identifier { + fn calculate_action_id( + &self, + owner_id: Identifier, + platform_version: &PlatformVersion, + ) -> Result { match self { - TokenSetPriceForDirectPurchaseTransition::V0(v0) => v0.calculate_action_id(owner_id), + TokenSetPriceForDirectPurchaseTransition::V0(v0) => { + v0.calculate_action_id(owner_id, platform_version) + } } } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transition.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transition.rs index 765574fc6e4..ba3df8c6620 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transition.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transition.rs @@ -213,7 +213,11 @@ pub trait TokenTransitionV0Methods { /// sets identity contract nonce fn set_identity_contract_nonce(&mut self, nonce: IdentityNonce); - fn calculate_action_id(&self, owner_id: Identifier) -> Option; + fn calculate_action_id( + &self, + owner_id: Identifier, + platform_version: &PlatformVersion, + ) -> Option>; fn can_calculate_action_id(&self) -> bool; /// Historical document type name for the token history contract @@ -279,19 +283,31 @@ impl TokenTransitionV0Methods for TokenTransition { self.base().data_contract_id() } - fn calculate_action_id(&self, owner_id: Identifier) -> Option { + fn calculate_action_id( + &self, + owner_id: Identifier, + platform_version: &PlatformVersion, + ) -> Option> { match self { - TokenTransition::Burn(t) => Some(t.calculate_action_id(owner_id)), - TokenTransition::Mint(t) => Some(t.calculate_action_id(owner_id)), - TokenTransition::Freeze(t) => Some(t.calculate_action_id(owner_id)), - TokenTransition::Unfreeze(t) => Some(t.calculate_action_id(owner_id)), + TokenTransition::Burn(t) => Some(t.calculate_action_id(owner_id, platform_version)), + TokenTransition::Mint(t) => Some(t.calculate_action_id(owner_id, platform_version)), + TokenTransition::Freeze(t) => Some(t.calculate_action_id(owner_id, platform_version)), + TokenTransition::Unfreeze(t) => Some(t.calculate_action_id(owner_id, platform_version)), TokenTransition::Transfer(_) => None, - TokenTransition::DestroyFrozenFunds(t) => Some(t.calculate_action_id(owner_id)), + TokenTransition::DestroyFrozenFunds(t) => { + Some(t.calculate_action_id(owner_id, platform_version)) + } TokenTransition::Claim(_) => None, - TokenTransition::EmergencyAction(t) => Some(t.calculate_action_id(owner_id)), - TokenTransition::ConfigUpdate(t) => Some(t.calculate_action_id(owner_id)), + TokenTransition::EmergencyAction(t) => { + Some(t.calculate_action_id(owner_id, platform_version)) + } + TokenTransition::ConfigUpdate(t) => { + Some(t.calculate_action_id(owner_id, platform_version)) + } TokenTransition::DirectPurchase(_) => None, - TokenTransition::SetPriceForDirectPurchase(t) => Some(t.calculate_action_id(owner_id)), + TokenTransition::SetPriceForDirectPurchase(t) => { + Some(t.calculate_action_id(owner_id, platform_version)) + } } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_unfreeze_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_unfreeze_transition/v0/v0_methods.rs index 0559542f7a7..4a05a1f965a 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_unfreeze_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_unfreeze_transition/v0/v0_methods.rs @@ -1,4 +1,6 @@ use platform_value::Identifier; +use platform_version::version::PlatformVersion; +use crate::ProtocolError; use crate::state_transition::batch_transition::batched_transition::multi_party_action::AllowedAsMultiPartyAction; use crate::state_transition::batch_transition::batched_transition::token_unfreeze_transition::TokenUnfreezeTransitionV0; use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; @@ -61,18 +63,22 @@ impl TokenUnfreezeTransitionV0Methods for TokenUnfreezeTransitionV0 { } impl AllowedAsMultiPartyAction for TokenUnfreezeTransitionV0 { - fn calculate_action_id(&self, owner_id: Identifier) -> Identifier { + fn calculate_action_id( + &self, + owner_id: Identifier, + _platform_version: &PlatformVersion, + ) -> Result { let TokenUnfreezeTransitionV0 { base, frozen_identity_id, .. } = self; - TokenUnfreezeTransition::calculate_action_id_with_fields( + Ok(TokenUnfreezeTransition::calculate_action_id_with_fields( base.token_id().as_bytes(), owner_id.as_bytes(), base.identity_contract_nonce(), frozen_identity_id.as_bytes(), - ) + )) } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_unfreeze_transition/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_unfreeze_transition/v0_methods.rs index b90cf25acbd..56b7ad6be3f 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_unfreeze_transition/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_unfreeze_transition/v0_methods.rs @@ -1,5 +1,7 @@ use platform_value::Identifier; +use platform_version::version::PlatformVersion; use crate::prelude::IdentityNonce; +use crate::ProtocolError; use crate::state_transition::batch_transition::batched_transition::multi_party_action::AllowedAsMultiPartyAction; use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition; @@ -60,9 +62,13 @@ impl TokenUnfreezeTransitionV0Methods for TokenUnfreezeTransition { } impl AllowedAsMultiPartyAction for TokenUnfreezeTransition { - fn calculate_action_id(&self, owner_id: Identifier) -> Identifier { + fn calculate_action_id( + &self, + owner_id: Identifier, + platform_version: &PlatformVersion, + ) -> Result { match self { - TokenUnfreezeTransition::V0(v0) => v0.calculate_action_id(owner_id), + TokenUnfreezeTransition::V0(v0) => v0.calculate_action_id(owner_id, platform_version), } } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/v1/v1_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/v1/v1_methods.rs index 1458ad8dcc2..f0dde7145c0 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/v1/v1_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/v1/v1_methods.rs @@ -92,7 +92,7 @@ impl DocumentsBatchTransitionMethodsV1 for BatchTransitionV1 { identity_contract_nonce: IdentityNonce, user_fee_increase: UserFeeIncrease, signer: &S, - _platform_version: &PlatformVersion, + platform_version: &PlatformVersion, options: Option, ) -> Result { let mut mint_transition = TokenMintTransition::V0(TokenMintTransitionV0 { @@ -113,7 +113,8 @@ impl DocumentsBatchTransitionMethodsV1 for BatchTransitionV1 { GroupStateTransitionInfoStatus::GroupStateTransitionInfoProposer( group_contract_position, ) => { - let action_id = mint_transition.calculate_action_id(owner_id); + let action_id = + mint_transition.calculate_action_id(owner_id, platform_version)?; mint_transition.base_mut().set_using_group_info(Some( GroupStateTransitionInfo { group_contract_position, @@ -168,7 +169,7 @@ impl DocumentsBatchTransitionMethodsV1 for BatchTransitionV1 { identity_contract_nonce: IdentityNonce, user_fee_increase: UserFeeIncrease, signer: &S, - _platform_version: &PlatformVersion, + platform_version: &PlatformVersion, options: Option, ) -> Result { let mut burn_transition = TokenBurnTransition::V0(TokenBurnTransitionV0 { @@ -188,7 +189,8 @@ impl DocumentsBatchTransitionMethodsV1 for BatchTransitionV1 { GroupStateTransitionInfoStatus::GroupStateTransitionInfoProposer( group_contract_position, ) => { - let action_id = burn_transition.calculate_action_id(owner_id); + let action_id = + burn_transition.calculate_action_id(owner_id, platform_version)?; burn_transition.base_mut().set_using_group_info(Some( GroupStateTransitionInfo { group_contract_position, @@ -309,7 +311,7 @@ impl DocumentsBatchTransitionMethodsV1 for BatchTransitionV1 { identity_contract_nonce: IdentityNonce, user_fee_increase: UserFeeIncrease, signer: &S, - _platform_version: &PlatformVersion, + platform_version: &PlatformVersion, options: Option, ) -> Result { let mut freeze_transition = TokenFreezeTransition::V0(TokenFreezeTransitionV0 { @@ -329,7 +331,8 @@ impl DocumentsBatchTransitionMethodsV1 for BatchTransitionV1 { GroupStateTransitionInfoStatus::GroupStateTransitionInfoProposer( group_contract_position, ) => { - let action_id = freeze_transition.calculate_action_id(owner_id); + let action_id = + freeze_transition.calculate_action_id(owner_id, platform_version)?; freeze_transition.base_mut().set_using_group_info(Some( GroupStateTransitionInfo { group_contract_position, @@ -386,7 +389,7 @@ impl DocumentsBatchTransitionMethodsV1 for BatchTransitionV1 { identity_contract_nonce: IdentityNonce, user_fee_increase: UserFeeIncrease, signer: &S, - _platform_version: &PlatformVersion, + platform_version: &PlatformVersion, options: Option, ) -> Result { let mut unfreeze_transition = TokenUnfreezeTransition::V0(TokenUnfreezeTransitionV0 { @@ -406,7 +409,8 @@ impl DocumentsBatchTransitionMethodsV1 for BatchTransitionV1 { GroupStateTransitionInfoStatus::GroupStateTransitionInfoProposer( group_contract_position, ) => { - let action_id = unfreeze_transition.calculate_action_id(owner_id); + let action_id = + unfreeze_transition.calculate_action_id(owner_id, platform_version)?; unfreeze_transition.base_mut().set_using_group_info(Some( GroupStateTransitionInfo { group_contract_position, @@ -463,7 +467,7 @@ impl DocumentsBatchTransitionMethodsV1 for BatchTransitionV1 { identity_contract_nonce: IdentityNonce, user_fee_increase: UserFeeIncrease, signer: &S, - _platform_version: &PlatformVersion, + platform_version: &PlatformVersion, options: Option, ) -> Result { let mut destroy_frozen_funds_transition = @@ -484,7 +488,8 @@ impl DocumentsBatchTransitionMethodsV1 for BatchTransitionV1 { GroupStateTransitionInfoStatus::GroupStateTransitionInfoProposer( group_contract_position, ) => { - let action_id = destroy_frozen_funds_transition.calculate_action_id(owner_id); + let action_id = destroy_frozen_funds_transition + .calculate_action_id(owner_id, platform_version)?; destroy_frozen_funds_transition .base_mut() .set_using_group_info(Some(GroupStateTransitionInfo { @@ -542,7 +547,7 @@ impl DocumentsBatchTransitionMethodsV1 for BatchTransitionV1 { identity_contract_nonce: IdentityNonce, user_fee_increase: UserFeeIncrease, signer: &S, - _platform_version: &PlatformVersion, + platform_version: &PlatformVersion, options: Option, ) -> Result { let mut emergency_action_transition = @@ -563,7 +568,8 @@ impl DocumentsBatchTransitionMethodsV1 for BatchTransitionV1 { GroupStateTransitionInfoStatus::GroupStateTransitionInfoProposer( group_contract_position, ) => { - let action_id = emergency_action_transition.calculate_action_id(owner_id); + let action_id = emergency_action_transition + .calculate_action_id(owner_id, platform_version)?; emergency_action_transition .base_mut() .set_using_group_info(Some(GroupStateTransitionInfo { @@ -619,7 +625,7 @@ impl DocumentsBatchTransitionMethodsV1 for BatchTransitionV1 { identity_contract_nonce: IdentityNonce, user_fee_increase: UserFeeIncrease, signer: &S, - _platform_version: &PlatformVersion, + platform_version: &PlatformVersion, options: Option, ) -> Result { let mut config_update_transition = @@ -640,7 +646,8 @@ impl DocumentsBatchTransitionMethodsV1 for BatchTransitionV1 { GroupStateTransitionInfoStatus::GroupStateTransitionInfoProposer( group_contract_position, ) => { - let action_id = config_update_transition.calculate_action_id(owner_id); + let action_id = + config_update_transition.calculate_action_id(owner_id, platform_version)?; config_update_transition .base_mut() .set_using_group_info(Some(GroupStateTransitionInfo { @@ -749,7 +756,7 @@ impl DocumentsBatchTransitionMethodsV1 for BatchTransitionV1 { identity_contract_nonce: IdentityNonce, user_fee_increase: UserFeeIncrease, signer: &S, - _platform_version: &PlatformVersion, + platform_version: &PlatformVersion, options: Option, ) -> Result { let mut change_direct_purchase_price_transition = @@ -772,8 +779,8 @@ impl DocumentsBatchTransitionMethodsV1 for BatchTransitionV1 { GroupStateTransitionInfoStatus::GroupStateTransitionInfoProposer( group_contract_position, ) => { - let action_id = - change_direct_purchase_price_transition.calculate_action_id(owner_id); + let action_id = change_direct_purchase_price_transition + .calculate_action_id(owner_id, platform_version)?; change_direct_purchase_price_transition .base_mut() .set_using_group_info(Some(GroupStateTransitionInfo { diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/validation/validate_basic_structure/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/validation/validate_basic_structure/v0/mod.rs index 559a26dcd4e..fbf9159b3e8 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/validation/validate_basic_structure/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/validation/validate_basic_structure/v0/mod.rs @@ -190,8 +190,9 @@ impl BatchTransition { // But only if we are the proposer if let Some(group_state_transition_info) = transition.base().using_group_info() { if group_state_transition_info.action_is_proposer { - if let Some(calculated_action_id) = - transition.calculate_action_id(self.owner_id()) + if let Some(calculated_action_id) = transition + .calculate_action_id(self.owner_id(), platform_version) + .transpose()? { if group_state_transition_info.action_id != calculated_action_id { result.add_error(BasicError::InvalidActionIdError( diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/config_update/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/config_update/mod.rs index 071d5e6555f..bb57d705ed7 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/config_update/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/config_update/mod.rs @@ -1082,8 +1082,10 @@ mod token_config_update_tests { token_id.as_bytes(), identity.id().as_bytes(), 2, - TokenConfigurationChangeItem::MaxSupply(Some(1000000)).u8_item_index(), - ); + &TokenConfigurationChangeItem::MaxSupply(Some(1000000)), + platform_version, + ) + .expect("expected to calculate action id"); let config_update_transition = BatchTransition::new_token_config_update_transition( token_id, @@ -1297,11 +1299,12 @@ mod token_config_update_tests { token_id.as_bytes(), identity.id().as_bytes(), 2, - TokenConfigurationChangeItem::MintingAllowChoosingDestinationControlGroup( + &TokenConfigurationChangeItem::MintingAllowChoosingDestinationControlGroup( AuthorizedActionTakers::Group(0), - ) - .u8_item_index(), - ); + ), + platform_version, + ) + .expect("expected to calculate action id"); let config_update_transition = BatchTransition::new_token_config_update_transition( token_id, @@ -1465,8 +1468,10 @@ mod token_config_update_tests { token_id.as_bytes(), identity.id().as_bytes(), 3, - TokenConfigurationChangeItem::MintingAllowChoosingDestination(true).u8_item_index(), - ); + &TokenConfigurationChangeItem::MintingAllowChoosingDestination(true), + platform_version, + ) + .expect("expected to calculate action id"); let config_update_transition = BatchTransition::new_token_config_update_transition( token_id, @@ -1688,11 +1693,12 @@ mod token_config_update_tests { token_id.as_bytes(), identity.id().as_bytes(), 2, - TokenConfigurationChangeItem::MintingAllowChoosingDestinationAdminGroup( + &TokenConfigurationChangeItem::MintingAllowChoosingDestinationAdminGroup( AuthorizedActionTakers::Group(1), - ) - .u8_item_index(), - ); + ), + platform_version, + ) + .expect("expected to calculate action id"); let config_update_transition = BatchTransition::new_token_config_update_transition( token_id, @@ -1913,11 +1919,12 @@ mod token_config_update_tests { token_id.as_bytes(), identity_3.id().as_bytes(), 2, - TokenConfigurationChangeItem::MintingAllowChoosingDestinationControlGroup( + &TokenConfigurationChangeItem::MintingAllowChoosingDestinationControlGroup( AuthorizedActionTakers::Group(2), - ) - .u8_item_index(), - ); + ), + platform_version, + ) + .expect("expected to calculate action id"); let config_update_transition = BatchTransition::new_token_config_update_transition( token_id, @@ -2150,11 +2157,12 @@ mod token_config_update_tests { token_id.as_bytes(), identity_3.id().as_bytes(), 2, - TokenConfigurationChangeItem::ConventionsAdminGroup(AuthorizedActionTakers::Group( - 0, - )) - .u8_item_index(), - ); + &TokenConfigurationChangeItem::ConventionsAdminGroup( + AuthorizedActionTakers::Group(0), + ), + platform_version, + ) + .expect("expected to calculate action id"); let config_update_transition = BatchTransition::new_token_config_update_transition( token_id, @@ -2437,11 +2445,12 @@ mod token_config_update_tests { token_id.as_bytes(), identity.id().as_bytes(), 2, - TokenConfigurationChangeItem::ConventionsControlGroup( + &TokenConfigurationChangeItem::ConventionsControlGroup( AuthorizedActionTakers::Identity(identity_2.id()), - ) - .u8_item_index(), - ); + ), + platform_version, + ) + .expect("expected to calculate action id"); let config_update_transition = BatchTransition::new_token_config_update_transition( token_id, @@ -2581,11 +2590,12 @@ mod token_config_update_tests { token_id.as_bytes(), identity.id().as_bytes(), 3, - TokenConfigurationChangeItem::ConventionsAdminGroup(AuthorizedActionTakers::Group( - 1, - )) - .u8_item_index(), - ); + &TokenConfigurationChangeItem::ConventionsAdminGroup( + AuthorizedActionTakers::Group(1), + ), + platform_version, + ) + .expect("expected to calculate action id"); let config_update_transition = BatchTransition::new_token_config_update_transition( token_id, diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_token_versions/mod.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_token_versions/mod.rs index 6fad1c1578b..ed2283c859a 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_token_versions/mod.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_token_versions/mod.rs @@ -1,4 +1,5 @@ pub mod v1; +pub mod v2; use versioned_feature_core::FeatureVersion; @@ -7,4 +8,8 @@ pub struct DPPTokenVersions { pub identity_token_info_default_structure_version: FeatureVersion, pub identity_token_status_default_structure_version: FeatureVersion, pub token_contract_info_default_structure_version: FeatureVersion, + /// Version for the token config update action_id calculation. + /// v0: uses only the u8 discriminant of the config change item (vulnerable to value swap) + /// v1: includes the full serialized config change item in the hash + pub token_config_update_action_id_version: FeatureVersion, } diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_token_versions/v1.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_token_versions/v1.rs index f2737f8b09c..4e3e8e3861a 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_token_versions/v1.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_token_versions/v1.rs @@ -4,4 +4,5 @@ pub const TOKEN_VERSIONS_V1: DPPTokenVersions = DPPTokenVersions { identity_token_info_default_structure_version: 0, identity_token_status_default_structure_version: 0, token_contract_info_default_structure_version: 0, + token_config_update_action_id_version: 0, }; diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_token_versions/v2.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_token_versions/v2.rs new file mode 100644 index 00000000000..32c2c0fdfa1 --- /dev/null +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_token_versions/v2.rs @@ -0,0 +1,8 @@ +use crate::version::dpp_versions::dpp_token_versions::DPPTokenVersions; + +pub const TOKEN_VERSIONS_V2: DPPTokenVersions = DPPTokenVersions { + identity_token_info_default_structure_version: 0, + identity_token_status_default_structure_version: 0, + token_contract_info_default_structure_version: 0, + token_config_update_action_id_version: 1, +}; diff --git a/packages/rs-platform-version/src/version/v12.rs b/packages/rs-platform-version/src/version/v12.rs index c14b80facb6..394260d2b49 100644 --- a/packages/rs-platform-version/src/version/v12.rs +++ b/packages/rs-platform-version/src/version/v12.rs @@ -10,7 +10,7 @@ use crate::version::dpp_versions::dpp_state_transition_conversion_versions::v2:: use crate::version::dpp_versions::dpp_state_transition_method_versions::v1::STATE_TRANSITION_METHOD_VERSIONS_V1; use crate::version::dpp_versions::dpp_state_transition_serialization_versions::v2::STATE_TRANSITION_SERIALIZATION_VERSIONS_V2; use crate::version::dpp_versions::dpp_state_transition_versions::v3::STATE_TRANSITION_VERSIONS_V3; -use crate::version::dpp_versions::dpp_token_versions::v1::TOKEN_VERSIONS_V1; +use crate::version::dpp_versions::dpp_token_versions::v2::TOKEN_VERSIONS_V2; use crate::version::dpp_versions::dpp_validation_versions::v3::DPP_VALIDATION_VERSIONS_V3; use crate::version::dpp_versions::dpp_voting_versions::v2::VOTING_VERSION_V2; use crate::version::dpp_versions::DPPVersion; @@ -55,7 +55,7 @@ pub const PLATFORM_V12: PlatformVersion = PlatformVersion { document_versions: DOCUMENT_VERSIONS_V3, identity_versions: IDENTITY_VERSIONS_V1, voting_versions: VOTING_VERSION_V2, - token_versions: TOKEN_VERSIONS_V1, + token_versions: TOKEN_VERSIONS_V2, // fixes issue with token config update action id asset_lock_versions: DPP_ASSET_LOCK_VERSIONS_V1, methods: DPP_METHOD_VERSIONS_V2, factory_versions: DPP_FACTORY_VERSIONS_V1,