diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/mod.rs index de7763b8c0b..53aa0098976 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/mod.rs @@ -194,6 +194,13 @@ impl TokenConfigurationV0Setters for TokenConfiguration { } } + /// Sets if we should start as paused. Meaning transfers will not work till unpaused + fn set_start_as_paused(&mut self, start_as_paused: bool) { + match self { + TokenConfiguration::V0(v0) => v0.set_start_as_paused(start_as_paused), + } + } + /// Sets the maximum supply. fn set_max_supply(&mut self, max_supply: Option) { match self { diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs index e158a26d26d..0dd315cd4a2 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs @@ -89,6 +89,9 @@ pub trait TokenConfigurationV0Setters { /// Sets the base supply. fn set_base_supply(&mut self, base_supply: TokenAmount); + /// Sets if we should start as paused. Meaning transfers will not work till unpaused + fn set_start_as_paused(&mut self, start_as_paused: bool); + /// Sets the maximum supply. fn set_max_supply(&mut self, max_supply: Option); diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/accessors.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/accessors.rs index ada9e92d36e..5466529cc63 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/accessors.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/accessors.rs @@ -188,6 +188,11 @@ impl TokenConfigurationV0Setters for TokenConfigurationV0 { self.base_supply = base_supply; } + /// Sets if we should start as paused. Meaning transfers will not work till unpaused + fn set_start_as_paused(&mut self, start_as_paused: bool) { + self.start_as_paused = start_as_paused; + } + /// Sets the maximum supply. fn set_max_supply(&mut self, max_supply: Option) { self.max_supply = max_supply; diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/mod.rs index 369c90a684c..b1d90d62cb2 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/mod.rs @@ -3160,6 +3160,10 @@ mod token_tests { use dpp::state_transition::batch_transition::batched_transition::BatchedTransitionMutRef; use dpp::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; use dpp::state_transition::batch_transition::token_base_transition::v0::v0_methods::TokenBaseTransitionV0Methods; + use dpp::tokens::emergency_action::TokenEmergencyAction; + use dpp::tokens::info::v0::IdentityTokenInfoV0Accessors; + use dpp::tokens::status::TokenStatus; + use dpp::tokens::status::v0::TokenStatusV0; use super::*; #[test] @@ -3265,6 +3269,582 @@ mod token_tests { assert_eq!(token_balance, Some(expected_amount)); } + #[test] + fn test_token_transfer_should_fail_if_token_started_paused() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut rng = StdRng::seed_from_u64(49853); + + let platform_state = platform.state.load(); + + let (identity, signer, key) = + setup_identity(&mut platform, rng.gen(), dash_to_credits!(0.5)); + + let (recipient, _, _) = setup_identity(&mut platform, rng.gen(), dash_to_credits!(0.5)); + + let (contract, token_id) = create_token_contract_with_owner_identity( + &mut platform, + identity.id(), + Some(|token_configuration: &mut TokenConfiguration| { + token_configuration.set_start_as_paused(true); + token_configuration.set_emergency_action_rules(ChangeControlRules::V0( + ChangeControlRulesV0 { + authorized_to_make_change: AuthorizedActionTakers::ContractOwner, + admin_action_takers: AuthorizedActionTakers::ContractOwner, + changing_authorized_action_takers_to_no_one_allowed: false, + changing_admin_action_takers_to_no_one_allowed: false, + self_changing_admin_action_takers_allowed: false, + }, + )); + }), + None, + None, + platform_version, + ); + + let token_transfer_transition = BatchTransition::new_token_transfer_transition( + token_id, + identity.id(), + contract.id(), + 0, + 1337, + recipient.id(), + None, + None, + None, + &key, + 2, + 0, + &signer, + platform_version, + None, + None, + None, + ) + .expect("expect to create documents batch transition"); + + let token_transfer_serialized_transition = token_transfer_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![token_transfer_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::PaidConsensusError( + ConsensusError::StateError(StateError::TokenIsPausedError(_)), + _ + )] + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + let token_balance = platform + .drive + .fetch_identity_token_balance( + token_id.to_buffer(), + identity.id().to_buffer(), + None, + platform_version, + ) + .expect("expected to fetch token balance"); + assert_eq!(token_balance, Some(100000)); + + let token_balance = platform + .drive + .fetch_identity_token_balance( + token_id.to_buffer(), + recipient.id().to_buffer(), + None, + platform_version, + ) + .expect("expected to fetch token balance"); + assert_eq!(token_balance, None); + + let token_status = platform + .drive + .fetch_token_status(token_id.to_buffer(), None, platform_version) + .expect("expected to fetch token status"); + assert_eq!( + token_status, + Some(TokenStatus::V0(TokenStatusV0 { paused: true })) + ); + + // now let's let the token be transferable with an emergency action + + let resume_transition = BatchTransition::new_token_emergency_action_transition( + token_id, + identity.id(), + contract.id(), + 0, + TokenEmergencyAction::Resume, + None, + None, + &key, + 3, + 0, + &signer, + platform_version, + None, + None, + None, + ) + .expect("expect to create documents batch transition"); + + let resume_transition_transition = resume_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[resume_transition_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution(_, _)] + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + let token_status = platform + .drive + .fetch_token_status(token_id.to_buffer(), None, platform_version) + .expect("expected to fetch token status"); + assert_eq!( + token_status, + Some(TokenStatus::V0(TokenStatusV0 { paused: false })) + ); + + // the transfer should now work + + let token_transfer_transition = BatchTransition::new_token_transfer_transition( + token_id, + identity.id(), + contract.id(), + 0, + 1337, + recipient.id(), + None, + None, + None, + &key, + 4, + 0, + &signer, + platform_version, + None, + None, + None, + ) + .expect("expect to create documents batch transition"); + + let token_transfer_serialized_transition = token_transfer_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![token_transfer_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution(_, _)] + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + let token_balance = platform + .drive + .fetch_identity_token_balance( + token_id.to_buffer(), + identity.id().to_buffer(), + None, + platform_version, + ) + .expect("expected to fetch token balance"); + let expected_amount = 100000 - 1337; + assert_eq!(token_balance, Some(expected_amount)); + + let token_balance = platform + .drive + .fetch_identity_token_balance( + token_id.to_buffer(), + recipient.id().to_buffer(), + None, + platform_version, + ) + .expect("expected to fetch token balance"); + let expected_amount = 1337; + assert_eq!(token_balance, Some(expected_amount)); + } + + #[test] + fn test_token_transfer_should_fail_if_token_becomes_paused() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut rng = StdRng::seed_from_u64(49853); + + let platform_state = platform.state.load(); + + let (identity, signer, key) = + setup_identity(&mut platform, rng.gen(), dash_to_credits!(0.5)); + + let (recipient, _, _) = setup_identity(&mut platform, rng.gen(), dash_to_credits!(0.5)); + + let (contract, token_id) = create_token_contract_with_owner_identity( + &mut platform, + identity.id(), + Some(|token_configuration: &mut TokenConfiguration| { + token_configuration.set_start_as_paused(false); + token_configuration.set_emergency_action_rules(ChangeControlRules::V0( + ChangeControlRulesV0 { + authorized_to_make_change: AuthorizedActionTakers::ContractOwner, + admin_action_takers: AuthorizedActionTakers::ContractOwner, + changing_authorized_action_takers_to_no_one_allowed: false, + changing_admin_action_takers_to_no_one_allowed: false, + self_changing_admin_action_takers_allowed: false, + }, + )); + }), + None, + None, + platform_version, + ); + + let resume_transition = BatchTransition::new_token_emergency_action_transition( + token_id, + identity.id(), + contract.id(), + 0, + TokenEmergencyAction::Pause, + None, + None, + &key, + 2, + 0, + &signer, + platform_version, + None, + None, + None, + ) + .expect("expect to create documents batch transition"); + + let resume_transition_transition = resume_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[resume_transition_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution(_, _)] + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + let token_status = platform + .drive + .fetch_token_status(token_id.to_buffer(), None, platform_version) + .expect("expected to fetch token status"); + assert_eq!( + token_status, + Some(TokenStatus::V0(TokenStatusV0 { paused: true })) + ); + + let token_transfer_transition = BatchTransition::new_token_transfer_transition( + token_id, + identity.id(), + contract.id(), + 0, + 1337, + recipient.id(), + None, + None, + None, + &key, + 3, + 0, + &signer, + platform_version, + None, + None, + None, + ) + .expect("expect to create documents batch transition"); + + let token_transfer_serialized_transition = token_transfer_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[token_transfer_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::PaidConsensusError( + ConsensusError::StateError(StateError::TokenIsPausedError(_)), + _ + )] + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + let token_balance = platform + .drive + .fetch_identity_token_balance( + token_id.to_buffer(), + identity.id().to_buffer(), + None, + platform_version, + ) + .expect("expected to fetch token balance"); + assert_eq!(token_balance, Some(100000)); + + let token_balance = platform + .drive + .fetch_identity_token_balance( + token_id.to_buffer(), + recipient.id().to_buffer(), + None, + platform_version, + ) + .expect("expected to fetch token balance"); + assert_eq!(token_balance, None); + + // now let's make the token be transferable again + + let resume_transition = BatchTransition::new_token_emergency_action_transition( + token_id, + identity.id(), + contract.id(), + 0, + TokenEmergencyAction::Resume, + None, + None, + &key, + 4, + 0, + &signer, + platform_version, + None, + None, + None, + ) + .expect("expect to create documents batch transition"); + + let resume_transition_transition = resume_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[resume_transition_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution(_, _)] + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + let token_status = platform + .drive + .fetch_token_status(token_id.to_buffer(), None, platform_version) + .expect("expected to fetch token status"); + assert_eq!( + token_status, + Some(TokenStatus::V0(TokenStatusV0 { paused: false })) + ); + + // the transfer should now work + + let token_transfer_transition = BatchTransition::new_token_transfer_transition( + token_id, + identity.id(), + contract.id(), + 0, + 1337, + recipient.id(), + None, + None, + None, + &key, + 5, + 0, + &signer, + platform_version, + None, + None, + None, + ) + .expect("expect to create documents batch transition"); + + let token_transfer_serialized_transition = token_transfer_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![token_transfer_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution(_, _)] + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + let token_balance = platform + .drive + .fetch_identity_token_balance( + token_id.to_buffer(), + identity.id().to_buffer(), + None, + platform_version, + ) + .expect("expected to fetch token balance"); + let expected_amount = 100000 - 1337; + assert_eq!(token_balance, Some(expected_amount)); + + let token_balance = platform + .drive + .fetch_identity_token_balance( + token_id.to_buffer(), + recipient.id().to_buffer(), + None, + platform_version, + ) + .expect("expected to fetch token balance"); + let expected_amount = 1337; + assert_eq!(token_balance, Some(expected_amount)); + } + #[test] fn test_token_transfer_to_ourself_should_fail() { let platform_version = PlatformVersion::latest(); diff --git a/packages/rs-drive/src/drive/contract/insert/insert_contract/v1/mod.rs b/packages/rs-drive/src/drive/contract/insert/insert_contract/v1/mod.rs index 9776452ba6b..e195982cd1a 100644 --- a/packages/rs-drive/src/drive/contract/insert/insert_contract/v1/mod.rs +++ b/packages/rs-drive/src/drive/contract/insert/insert_contract/v1/mod.rs @@ -12,14 +12,16 @@ use dpp::fee::fee_result::FeeResult; use crate::drive::balances::total_tokens_root_supply_path_vec; use crate::drive::tokens::paths::{ token_balances_path_vec, token_balances_root_path, token_identity_infos_root_path, + token_statuses_root_path, }; use crate::error::contract::DataContractError; -use crate::util::object_size_info::DriveKeyInfo; use crate::util::object_size_info::PathKeyElementInfo::PathKeyElement; +use crate::util::object_size_info::{DriveKeyInfo, PathKeyElementInfo}; use dpp::data_contract::accessors::v1::DataContractV1Getters; use dpp::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Getters; use dpp::data_contract::associated_token::token_distribution_rules::accessors::v0::TokenDistributionRulesV0Getters; -use dpp::serialization::PlatformSerializableWithPlatformVersion; +use dpp::serialization::{PlatformSerializable, PlatformSerializableWithPlatformVersion}; +use dpp::tokens::status::TokenStatus; use dpp::version::PlatformVersion; use dpp::ProtocolError; use grovedb::batch::KeyInfoPath; @@ -230,6 +232,22 @@ impl Drive { )?; } + if token_config.start_as_paused() { + // no status also means active. + let starting_status = TokenStatus::new(true, platform_version)?; + let token_status_bytes = starting_status.serialize_consume_to_bytes()?; + + self.batch_insert( + PathKeyElementInfo::PathFixedSizeKeyRefElement::<2>(( + token_statuses_root_path(), + token_id.as_slice(), + Element::Item(token_status_bytes, None), + )), + &mut batch_operations, + &platform_version.drive, + )?; + } + if let Some(pre_programmed_distribution) = token_config .distribution_rules() .pre_programmed_distribution() diff --git a/packages/rs-drive/src/util/batch/drive_op_batch/contract.rs b/packages/rs-drive/src/util/batch/drive_op_batch/contract.rs index 59d2b05c670..7f976a769ac 100644 --- a/packages/rs-drive/src/util/batch/drive_op_batch/contract.rs +++ b/packages/rs-drive/src/util/batch/drive_op_batch/contract.rs @@ -34,7 +34,7 @@ pub enum DataContractOperationType<'a> { // TODO: split into create and update /// Applies a contract without serialization. ApplyContract { - // this is Cow because we want allow the contract to be owned or not + // this is Cow because we want to allow the contract to be owned or not // ownership is interesting because you can easily create the contract // in sub functions // borrowing is interesting because you can create the contract and then