diff --git a/crates/sui-framework/docs/sui-system/staking_pool.md b/crates/sui-framework/docs/sui-system/staking_pool.md index 5401368971c0c..782ad81ed3cc1 100644 --- a/crates/sui-framework/docs/sui-system/staking_pool.md +++ b/crates/sui-framework/docs/sui-system/staking_pool.md @@ -517,6 +517,14 @@ A proportional amount of pool token withdraw is recorded and processed at epoch staked_sui: StakedSui, ctx: &TxContext ) : Balance<SUI> { + // stake is inactive + if (staked_sui.stake_activation_epoch > ctx.epoch()) { + let principal = unwrap_staked_sui(staked_sui); + pool.pending_stake = pool.pending_stake - principal.value(); + + return principal + }; + let (pool_token_withdraw_amount, mut principal_withdraw) = withdraw_from_principal(pool, staked_sui); let principal_withdraw_amount = principal_withdraw.value(); diff --git a/crates/sui-framework/docs/sui-system/sui_system_state_inner.md b/crates/sui-framework/docs/sui-system/sui_system_state_inner.md index 82baf3b957633..0a0f0ad82ac31 100644 --- a/crates/sui-framework/docs/sui-system/sui_system_state_inner.md +++ b/crates/sui-framework/docs/sui-system/sui_system_state_inner.md @@ -717,15 +717,6 @@ the epoch advancement transaction. - - - - -
const EStakeWithdrawBeforeActivation: u64 = 6;
-
- - - @@ -1302,10 +1293,6 @@ Withdraw some portion of a stake from a validator's staking pool. staked_sui: StakedSui, ctx: &TxContext, ) : Balance<SUI> { - assert!( - stake_activation_epoch(&staked_sui) <= ctx.epoch(), - EStakeWithdrawBeforeActivation - ); self.validators.request_withdraw_stake(staked_sui, ctx) } diff --git a/crates/sui-framework/packages/sui-system/sources/staking_pool.move b/crates/sui-framework/packages/sui-system/sources/staking_pool.move index 3cf43037d537c..0295dbfa703a1 100644 --- a/crates/sui-framework/packages/sui-system/sources/staking_pool.move +++ b/crates/sui-framework/packages/sui-system/sources/staking_pool.move @@ -130,6 +130,14 @@ module sui_system::staking_pool { staked_sui: StakedSui, ctx: &TxContext ) : Balance { + // stake is inactive + if (staked_sui.stake_activation_epoch > ctx.epoch()) { + let principal = unwrap_staked_sui(staked_sui); + pool.pending_stake = pool.pending_stake - principal.value(); + + return principal + }; + let (pool_token_withdraw_amount, mut principal_withdraw) = withdraw_from_principal(pool, staked_sui); let principal_withdraw_amount = principal_withdraw.value(); diff --git a/crates/sui-framework/packages/sui-system/sources/sui_system_state_inner.move b/crates/sui-framework/packages/sui-system/sources/sui_system_state_inner.move index 2b0385d7d7bc3..b0830c20e6fe0 100644 --- a/crates/sui-framework/packages/sui-system/sources/sui_system_state_inner.move +++ b/crates/sui-framework/packages/sui-system/sources/sui_system_state_inner.move @@ -4,7 +4,7 @@ module sui_system::sui_system_state_inner { use sui::balance::{Self, Balance}; use sui::coin::Coin; - use sui_system::staking_pool::{stake_activation_epoch, StakedSui}; + use sui_system::staking_pool::StakedSui; use sui::sui::SUI; use sui_system::validator::{Self, Validator}; use sui_system::validator_set::{Self, ValidatorSet}; @@ -214,7 +214,6 @@ module sui_system::sui_system_state_inner { const ECannotReportOneself: u64 = 3; const EReportRecordNotFound: u64 = 4; const EBpsTooLarge: u64 = 5; - const EStakeWithdrawBeforeActivation: u64 = 6; const ESafeModeGasNotProcessed: u64 = 7; const EAdvancedToWrongEpoch: u64 = 8; @@ -516,10 +515,6 @@ module sui_system::sui_system_state_inner { staked_sui: StakedSui, ctx: &TxContext, ) : Balance { - assert!( - stake_activation_epoch(&staked_sui) <= ctx.epoch(), - EStakeWithdrawBeforeActivation - ); self.validators.request_withdraw_stake(staked_sui, ctx) } diff --git a/crates/sui-framework/packages/sui-system/tests/sui_system_tests.move b/crates/sui-framework/packages/sui-system/tests/sui_system_tests.move index 8be089338330c..3d2f2d0543795 100644 --- a/crates/sui-framework/packages/sui-system/tests/sui_system_tests.move +++ b/crates/sui-framework/packages/sui-system/tests/sui_system_tests.move @@ -9,7 +9,7 @@ module sui_system::sui_system_tests { use sui::test_scenario::{Self, Scenario}; use sui::sui::SUI; - use sui_system::governance_test_utils::{add_validator_full_flow, advance_epoch, remove_validator, set_up_sui_system_state, create_sui_system_state_for_testing}; + use sui_system::governance_test_utils::{add_validator_full_flow, advance_epoch, remove_validator, set_up_sui_system_state, create_sui_system_state_for_testing, stake_with, unstake}; use sui_system::sui_system::SuiSystemState; use sui_system::sui_system_state_inner; use sui_system::validator::{Self, Validator}; @@ -1002,4 +1002,54 @@ module sui_system::sui_system_tests { test_scenario::return_shared(system_state); scenario.next_epoch(@0x0); } + + #[test] + fun test_withdraw_inactive_stake() { + let mut scenario_val = test_scenario::begin(@0x0); + let scenario = &mut scenario_val; + // Epoch duration is set to be 42 here. + set_up_sui_system_state(vector[@0x1, @0x2]); + + { + scenario.next_tx(@0x0); + let mut system_state = scenario.take_shared(); + let staking_pool = system_state.active_validator_by_address(@0x1).get_staking_pool_ref(); + + assert!(staking_pool.pending_stake_amount() == 0, 0); + assert!(staking_pool.pending_stake_withdraw_amount() == 0, 0); + assert!(staking_pool.sui_balance() == 100 * 1_000_000_000, 0); + + test_scenario::return_shared(system_state); + }; + + stake_with(@0x0, @0x1, 1, scenario); + + { + scenario.next_tx(@0x0); + let mut system_state = scenario.take_shared(); + let staking_pool = system_state.active_validator_by_address(@0x1).get_staking_pool_ref(); + + assert!(staking_pool.pending_stake_amount() == 1_000_000_000, 0); + assert!(staking_pool.pending_stake_withdraw_amount() == 0, 0); + assert!(staking_pool.sui_balance() == 100 * 1_000_000_000, 0); + + test_scenario::return_shared(system_state); + }; + + unstake(@0x0, 0, scenario); + + { + scenario.next_tx(@0x0); + let mut system_state = scenario.take_shared(); + let staking_pool = system_state.active_validator_by_address(@0x1).get_staking_pool_ref(); + + assert!(staking_pool.pending_stake_amount() == 0, 0); + assert!(staking_pool.pending_stake_withdraw_amount() == 0, 0); + assert!(staking_pool.sui_balance() == 100 * 1_000_000_000, 0); + + test_scenario::return_shared(system_state); + }; + + scenario_val.end(); + } } diff --git a/crates/sui-protocol-config/src/lib.rs b/crates/sui-protocol-config/src/lib.rs index 3321c74c2a408..a4d213a629c5c 100644 --- a/crates/sui-protocol-config/src/lib.rs +++ b/crates/sui-protocol-config/src/lib.rs @@ -148,6 +148,7 @@ const MAX_PROTOCOL_VERSION: u64 = 52; // Set number of leaders per round for Mysticeti commits. // Version 51: Switch to DKG V1. // Version 52: Emit `CommitteeMemberUrlUpdateEvent` when updating bridge node url. +// Modified sui-system package to enable withdrawal of stake before it becomes active. #[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct ProtocolVersion(u64); diff --git a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap index 8d0bf6fbbfb64..b70f77ea2bdf9 100644 --- a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap +++ b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap @@ -240,13 +240,13 @@ validators: next_epoch_worker_address: ~ extra_fields: id: - id: "0x5ee3815bd22bb164a92d2d9fa8e45c1ece868a4cec17af9f1a4f2b35e7e97612" + id: "0xee18e93d074b1e76fdd721caaa1f52c7a16f8736b3193f5236db390131f67286" size: 0 voting_power: 10000 - operation_cap_id: "0x5bb1af2dad062878b913e848b9091ed6f81f064ccd6884fc181706f41f331fec" + operation_cap_id: "0xd8322d5e52c7948c4542b7228f0f5756ae2c7013c29357668440b45806984425" gas_price: 1000 staking_pool: - id: "0x5e20976a9075d0e2c1a0bacb5aa475cb868222c9ff5069da0a6cf862d18a505c" + id: "0x3e684f142a70db097fa72b6dc6895e62c805b123c9ca73a3b46f6acde37c67f0" activation_epoch: 0 deactivation_epoch: ~ sui_balance: 20000000000000000 @@ -254,14 +254,14 @@ validators: value: 0 pool_token_balance: 20000000000000000 exchange_rates: - id: "0xb452b2f138c9eb4897a0bdb5bf8b49f16f4dfff5a0e32fc281304ab3fd3519b4" + id: "0xea9fbe1476736c2dea2ba0c6944567ad1cf8183724c645b501ba1c8bdc236c72" size: 1 pending_stake: 0 pending_total_sui_withdraw: 0 pending_pool_token_withdraw: 0 extra_fields: id: - id: "0xeddc255f7992dc59f21cc4687253baba7d67df4289d7363ad168e0be1176e2aa" + id: "0x3b04d2e0e34c5eb443d932500834b52aba31725d347b8e5f02d14ddd6ea03bea" size: 0 commission_rate: 200 next_epoch_stake: 20000000000000000 @@ -269,27 +269,27 @@ validators: next_epoch_commission_rate: 200 extra_fields: id: - id: "0xe3bb275be68081f76e6f243a9e2b66c88ee49870c9a0d7ebb64e597f61e9e141" + id: "0x3bae281bb2a7d5fa3bbed3b6172a337fc46019f90d183982054b04e9f32133fb" size: 0 pending_active_validators: contents: - id: "0xa5ce78594a6f3e8b98a766f93dc5016f80567b7d174305b2f1f5bdf648def400" + id: "0x9a771ce2dd53849c44948bc9eaadd8ed4ab6297808610e65b0176ca52e5c7817" size: 0 pending_removals: [] staking_pool_mappings: - id: "0x053838f3d4abadf5674b9b3dd267aa9754f5914edae3c0e260eb0447d4339272" + id: "0x0c771e8f738e428185e77491408570183f4285565ba98b8b62b2dbaa9930f02a" size: 1 inactive_validators: - id: "0xb6409731aacdc307c30a616a1ff94aec387ac220d5f3fd48d85812a5d2bf8306" + id: "0x8d0b80b0ec49ddac7fd6ad1467c78b5a28e88fe3fed7bd13d6495293d928559a" size: 0 validator_candidates: - id: "0xff35969e6260537114784d8654873c597d3828eb54f295ea978a80a81a512468" + id: "0xe6db5c73ef0829daf5f71782bd6837b0a2be1395a065638d64e491ed5b97dc55" size: 0 at_risk_validators: contents: [] extra_fields: id: - id: "0xbb3eb2d0cc5c03d7733596cc7924f7dfdf38dffffe9cf617057512e0621ff5cd" + id: "0x390b78ffa27f9465b475a9e3df0305f8a22284bb62ef362339415bc31cff79d5" size: 0 storage_fund: total_object_storage_rebates: @@ -306,7 +306,7 @@ parameters: validator_low_stake_grace_period: 7 extra_fields: id: - id: "0x94f44132137189cb0e9253c4f3d7b9491ad2de2f7dc4a26188cb9e426334f60b" + id: "0x3a66a39b4dc73ad58cc15074094cdfad890e567b311a27fbfa3d5cbd2a80258a" size: 0 reference_gas_price: 1000 validator_report_records: @@ -320,7 +320,7 @@ stake_subsidy: stake_subsidy_decrease_rate: 1000 extra_fields: id: - id: "0x6acc16d86b4dee5ba8485221215c59a16641467fdb2cbe95c41fe6533d31a130" + id: "0xac6c38f74f782e44ec0c0219c970f4b43e6ed899e90776e545d399a10b2f6eea" size: 0 safe_mode: false safe_mode_storage_rewards: @@ -332,6 +332,6 @@ safe_mode_non_refundable_storage_fee: 0 epoch_start_timestamp_ms: 10 extra_fields: id: - id: "0xf72863df91bf14efc27270ef17592ce3f3ecb9302099f8b5bf18303784558fa8" + id: "0x33f45d7789643796859e2cc2cae4765ba0cd89423f5819deab37244924a09caa" size: 0