diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/methods/authorized_action_takers_for_configuration_item/v0/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/methods/authorized_action_takers_for_configuration_item/v0/mod.rs index 183e6e97e75..8529b2335a7 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/methods/authorized_action_takers_for_configuration_item/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/methods/authorized_action_takers_for_configuration_item/v0/mod.rs @@ -105,7 +105,9 @@ impl TokenConfigurationV0 { TokenConfigurationChangeItem::EmergencyActionAdminGroup(_) => { *self.emergency_action_rules.admin_action_takers() } - TokenConfigurationChangeItem::MainControlGroup(_) => AuthorizedActionTakers::NoOne, + TokenConfigurationChangeItem::MainControlGroup(_) => { + self.main_control_group_can_be_modified + } TokenConfigurationChangeItem::MarketplaceTradeMode(_) => *self .marketplace_rules .trade_mode_change_rules() @@ -267,18 +269,35 @@ mod tests { } #[test] - fn main_control_group_always_returns_no_one() { - // Per implementation, MainControlGroup change items always return NoOne, - // regardless of config. This is important because modifying the main - // control group is governed by main_control_group_can_be_modified, not - // by any of the change_control_rules. - let c = config_with_all_owner_rules(); + fn main_control_group_returns_no_one_by_default() { + // default_most_restrictive leaves main_control_group_can_be_modified as NoOne + let c = TokenConfigurationV0::default_most_restrictive(); let result = c.authorized_action_takers_for_configuration_item( &TokenConfigurationChangeItem::MainControlGroup(Some(3)), ); assert_eq!(result, AuthorizedActionTakers::NoOne); } + #[test] + fn main_control_group_returns_configured_can_be_modified() { + // Modifying the main control group is governed by + // main_control_group_can_be_modified, not by any of the + // change_control_rules, so that is what must be reported here. + for takers in [ + AuthorizedActionTakers::ContractOwner, + AuthorizedActionTakers::MainGroup, + AuthorizedActionTakers::Group(0), + AuthorizedActionTakers::Identity(Identifier::from([7u8; 32])), + ] { + let mut c = TokenConfigurationV0::default_most_restrictive(); + c.set_main_control_group_can_be_modified(takers); + let result = c.authorized_action_takers_for_configuration_item( + &TokenConfigurationChangeItem::MainControlGroup(Some(3)), + ); + assert_eq!(result, takers); + } + } + #[test] fn marketplace_trade_mode_returns_authorized_to_make_change() { let c = TokenConfigurationV0::default_most_restrictive(); 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 a34403210f4..630c93b89dd 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 @@ -1240,6 +1240,199 @@ mod token_config_update_tests { assert_eq!(updated_token_config.max_supply(), Some(1000000)); } + #[tokio::test] + async fn test_token_config_update_by_group_member_changing_main_control_group() { + 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 (identity_2, signer_2, key_2) = + 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_main_control_group_can_be_modified(AuthorizedActionTakers::Group(0)); + }), + None, + Some( + [( + 0, + Group::V0(GroupV0 { + members: [(identity.id(), 1), (identity_2.id(), 1)].into(), + required_power: 2, + }), + )] + .into(), + ), + None, + platform_version, + ); + + let action_id = TokenConfigUpdateTransition::calculate_action_id_with_fields( + token_id.as_bytes(), + identity.id().as_bytes(), + 2, + &TokenConfigurationChangeItem::MainControlGroup(Some(0)), + platform_version, + ) + .expect("expected to calculate action id"); + + let config_update_transition = BatchTransition::new_token_config_update_transition( + token_id, + identity.id(), + contract.id(), + 0, + TokenConfigurationChangeItem::MainControlGroup(Some(0)), + None, + Some(GroupStateTransitionInfoStatus::GroupStateTransitionInfoProposer(0)), + &key, + 2, + 0, + &signer, + platform_version, + None, + ) + .await + .expect("expect to create documents batch transition"); + + let config_update_transition_serialized_transition = config_update_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( + &[config_update_transition_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 new_contract = platform + .drive + .fetch_contract( + contract.id().to_buffer(), + None, + None, + None, + platform_version, + ) + .unwrap() + .expect("expected to fetch contract") + .expect("expected contract"); + let updated_token_config = new_contract + .contract + .expected_token_configuration(0) + .expect("expected token configuration"); + // only proposed so far, group power 1 of required 2 + assert_eq!(updated_token_config.main_control_group(), None); + + let config_update_transition = BatchTransition::new_token_config_update_transition( + token_id, + identity_2.id(), + contract.id(), + 0, + TokenConfigurationChangeItem::MainControlGroup(Some(0)), + None, + Some( + GroupStateTransitionInfoStatus::GroupStateTransitionInfoOtherSigner( + GroupStateTransitionInfo { + group_contract_position: 0, + action_id, + action_is_proposer: false, + }, + ), + ), + &key_2, + 2, + 0, + &signer_2, + platform_version, + None, + ) + .await + .expect("expect to create documents batch transition"); + + let config_update_transition_serialized_transition = config_update_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( + &[config_update_transition_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 new_contract = platform + .drive + .fetch_contract( + contract.id().to_buffer(), + None, + None, + None, + platform_version, + ) + .unwrap() + .expect("expected to fetch contract") + .expect("expected contract"); + let updated_token_config = new_contract + .contract + .expected_token_configuration(0) + .expect("expected token configuration"); + assert_eq!(updated_token_config.main_control_group(), Some(0)); + } + #[tokio::test] async fn test_token_config_update_by_group_member_changing_minting_destination_group() { let platform_version = PlatformVersion::latest();