diff --git a/crates/sui-framework/docs/sui-system/staking_pool.md b/crates/sui-framework/docs/sui-system/staking_pool.md
index 782ad81ed3cc1..f51eff50b2fa2 100644
--- a/crates/sui-framework/docs/sui-system/staking_pool.md
+++ b/crates/sui-framework/docs/sui-system/staking_pool.md
@@ -7,10 +7,16 @@ title: Module `0x3::staking_pool`
- [Resource `StakingPool`](#0x3_staking_pool_StakingPool)
- [Struct `PoolTokenExchangeRate`](#0x3_staking_pool_PoolTokenExchangeRate)
- [Resource `StakedSui`](#0x3_staking_pool_StakedSui)
+- [Resource `FungibleStakedSui`](#0x3_staking_pool_FungibleStakedSui)
+- [Resource `FungibleStakedSuiData`](#0x3_staking_pool_FungibleStakedSuiData)
+- [Struct `FungibleStakedSuiDataKey`](#0x3_staking_pool_FungibleStakedSuiDataKey)
- [Constants](#@Constants_0)
- [Function `new`](#0x3_staking_pool_new)
- [Function `request_add_stake`](#0x3_staking_pool_request_add_stake)
- [Function `request_withdraw_stake`](#0x3_staking_pool_request_withdraw_stake)
+- [Function `redeem_fungible_staked_sui`](#0x3_staking_pool_redeem_fungible_staked_sui)
+- [Function `calculate_fungible_staked_sui_withdraw_amount`](#0x3_staking_pool_calculate_fungible_staked_sui_withdraw_amount)
+- [Function `convert_to_fungible_staked_sui`](#0x3_staking_pool_convert_to_fungible_staked_sui)
- [Function `withdraw_from_principal`](#0x3_staking_pool_withdraw_from_principal)
- [Function `unwrap_staked_sui`](#0x3_staking_pool_unwrap_staked_sui)
- [Function `deposit_rewards`](#0x3_staking_pool_deposit_rewards)
@@ -22,10 +28,14 @@ title: Module `0x3::staking_pool`
- [Function `deactivate_staking_pool`](#0x3_staking_pool_deactivate_staking_pool)
- [Function `sui_balance`](#0x3_staking_pool_sui_balance)
- [Function `pool_id`](#0x3_staking_pool_pool_id)
+- [Function `fungible_staked_sui_pool_id`](#0x3_staking_pool_fungible_staked_sui_pool_id)
- [Function `staked_sui_amount`](#0x3_staking_pool_staked_sui_amount)
- [Function `stake_activation_epoch`](#0x3_staking_pool_stake_activation_epoch)
- [Function `is_preactive`](#0x3_staking_pool_is_preactive)
- [Function `is_inactive`](#0x3_staking_pool_is_inactive)
+- [Function `fungible_staked_sui_value`](#0x3_staking_pool_fungible_staked_sui_value)
+- [Function `split_fungible_staked_sui`](#0x3_staking_pool_split_fungible_staked_sui)
+- [Function `join_fungible_staked_sui`](#0x3_staking_pool_join_fungible_staked_sui)
- [Function `split`](#0x3_staking_pool_split)
- [Function `split_staked_sui`](#0x3_staking_pool_split_staked_sui)
- [Function `join_staked_sui`](#0x3_staking_pool_join_staked_sui)
@@ -228,6 +238,116 @@ A self-custodial object holding the staked SUI tokens.
+
+
+
+
+## Resource `FungibleStakedSui`
+
+An alternative to StakedSui
that holds the pool token amount instead of the SUI balance.
+StakedSui objects can be converted to FungibleStakedSuis after the initial warmup period.
+The advantage of this is that you can now merge multiple StakedSui objects from different
+activation epochs into a single FungibleStakedSui object.
+
+
+
struct FungibleStakedSui has store, key
+
+
+
+
+
+Fields
+
+
+
+-
+
id: object::UID
+
+-
+
+
+-
+
pool_id: object::ID
+
+-
+ ID of the staking pool we are staking with.
+
+-
+
value: u64
+
+-
+ The pool token amount.
+
+
+
+
+
+
+
+
+## Resource `FungibleStakedSuiData`
+
+Holds useful information
+
+
+struct FungibleStakedSuiData has store, key
+
+
+
+
+
+Fields
+
+
+
+-
+
id: object::UID
+
+-
+
+
+-
+
total_supply: u64
+
+-
+ fungible_staked_sui supply
+
+-
+
principal: balance::Balance<sui::SUI>
+
+-
+ principal balance. Rewards are withdrawn from the reward pool
+
+
+
+
+
+
+
+
+## Struct `FungibleStakedSuiDataKey`
+
+
+
+struct FungibleStakedSuiDataKey has copy, drop, store
+
+
+
+
+
+Fields
+
+
+
+-
+
dummy_field: bool
+
+-
+
+
+
+
+
@@ -244,6 +364,15 @@ A self-custodial object holding the staked SUI tokens.
+
+
+
+
+const ECannotMintFungibleStakedSuiYet: u64 = 19;
+
+
+
+
@@ -316,6 +445,15 @@ A self-custodial object holding the staked SUI tokens.
+
+
+
+
+const EInvariantFailure: u64 = 20;
+
+
+
+
@@ -548,6 +686,193 @@ A proportional amount of pool token withdraw is recorded and processed at epoch
+
+
+
+
+## Function `redeem_fungible_staked_sui`
+
+
+
+public(friend) fun redeem_fungible_staked_sui(pool: &mut staking_pool::StakingPool, fungible_staked_sui: staking_pool::FungibleStakedSui, ctx: &tx_context::TxContext): balance::Balance<sui::SUI>
+
+
+
+
+
+Implementation
+
+
+public(package) fun redeem_fungible_staked_sui(
+ pool: &mut StakingPool,
+ fungible_staked_sui: FungibleStakedSui,
+ ctx: &TxContext
+) : Balance<SUI> {
+ let FungibleStakedSui { id, pool_id, value } = fungible_staked_sui;
+ assert!(pool_id == object::id(pool), EWrongPool);
+
+ object::delete(id);
+
+ let latest_exchange_rate = pool_token_exchange_rate_at_epoch(pool, tx_context::epoch(ctx));
+ let fungible_staked_sui_data: &mut FungibleStakedSuiData = bag::borrow_mut(
+ &mut pool.extra_fields,
+ FungibleStakedSuiDataKey {}
+ );
+
+ let (principal_amount, rewards_amount) = calculate_fungible_staked_sui_withdraw_amount(
+ latest_exchange_rate,
+ value,
+ balance::value(&fungible_staked_sui_data.principal),
+ fungible_staked_sui_data.total_supply
+ );
+
+ fungible_staked_sui_data.total_supply = fungible_staked_sui_data.total_supply - value;
+
+ let mut sui_out = balance::split(&mut fungible_staked_sui_data.principal, principal_amount);
+ balance::join(
+ &mut sui_out,
+ balance::split(&mut pool.rewards_pool, rewards_amount)
+ );
+
+ pool.pending_total_sui_withdraw = pool.pending_total_sui_withdraw + balance::value(&sui_out);
+ pool.pending_pool_token_withdraw = pool.pending_pool_token_withdraw + value;
+
+ sui_out
+}
+
+
+
+
+
+
+
+
+## Function `calculate_fungible_staked_sui_withdraw_amount`
+
+written in separate function so i can test with random values
+returns (principal_withdraw_amount, rewards_withdraw_amount)
+
+
+fun calculate_fungible_staked_sui_withdraw_amount(latest_exchange_rate: staking_pool::PoolTokenExchangeRate, fungible_staked_sui_value: u64, fungible_staked_sui_data_principal_amount: u64, fungible_staked_sui_data_total_supply: u64): (u64, u64)
+
+
+
+
+
+Implementation
+
+
+fun calculate_fungible_staked_sui_withdraw_amount(
+ latest_exchange_rate: PoolTokenExchangeRate,
+ fungible_staked_sui_value: u64,
+ fungible_staked_sui_data_principal_amount: u64, // fungible_staked_sui_data.principal.value()
+ fungible_staked_sui_data_total_supply: u64, // fungible_staked_sui_data.total_supply
+) : (u64, u64) {
+ // 1. if the entire FungibleStakedSuiData supply is redeemed, how much sui should we receive?
+ let total_sui_amount = get_sui_amount(&latest_exchange_rate, fungible_staked_sui_data_total_supply);
+
+ // min with total_sui_amount to prevent underflow
+ let fungible_staked_sui_data_principal_amount = std::u64::min(
+ fungible_staked_sui_data_principal_amount,
+ total_sui_amount
+ );
+
+ // 2. how much do we need to withdraw from the rewards pool?
+ let total_rewards = total_sui_amount - fungible_staked_sui_data_principal_amount;
+
+ // 3. proportionally withdraw from both wrt the fungible_staked_sui_value.
+ let principal_withdraw_amount = ((fungible_staked_sui_value as u128)
+ * (fungible_staked_sui_data_principal_amount as u128)
+ / (fungible_staked_sui_data_total_supply as u128)) as u64;
+
+ let rewards_withdraw_amount = ((fungible_staked_sui_value as u128)
+ * (total_rewards as u128)
+ / (fungible_staked_sui_data_total_supply as u128)) as u64;
+
+ // invariant check, just in case
+ let expected_sui_amount = get_sui_amount(&latest_exchange_rate, fungible_staked_sui_value);
+ assert!(principal_withdraw_amount + rewards_withdraw_amount <= expected_sui_amount, EInvariantFailure);
+
+ (principal_withdraw_amount, rewards_withdraw_amount)
+}
+
+
+
+
+
+
+
+
+## Function `convert_to_fungible_staked_sui`
+
+Convert the given staked SUI to an FungibleStakedSui object
+
+
+public(friend) fun convert_to_fungible_staked_sui(pool: &mut staking_pool::StakingPool, staked_sui: staking_pool::StakedSui, ctx: &mut tx_context::TxContext): staking_pool::FungibleStakedSui
+
+
+
+
+
+Implementation
+
+
+public(package) fun convert_to_fungible_staked_sui(
+ pool: &mut StakingPool,
+ staked_sui: StakedSui,
+ ctx: &mut TxContext
+) : FungibleStakedSui {
+ let StakedSui { id, pool_id, stake_activation_epoch, principal } = staked_sui;
+
+ assert!(pool_id == object::id(pool), EWrongPool);
+ assert!(
+ tx_context::epoch(ctx) >= stake_activation_epoch,
+ ECannotMintFungibleStakedSuiYet
+ );
+
+ object::delete(id);
+
+
+ let exchange_rate_at_staking_epoch = pool_token_exchange_rate_at_epoch(
+ pool,
+ stake_activation_epoch
+ );
+
+ let pool_token_amount = get_token_amount(
+ &exchange_rate_at_staking_epoch,
+ balance::value(&principal)
+ );
+
+ if (!bag::contains(&pool.extra_fields, FungibleStakedSuiDataKey {})) {
+ bag::add(
+ &mut pool.extra_fields,
+ FungibleStakedSuiDataKey {},
+ FungibleStakedSuiData {
+ id: object::new(ctx),
+ total_supply: pool_token_amount,
+ principal
+ }
+ );
+ }
+ else {
+ let fungible_staked_sui_data: &mut FungibleStakedSuiData = bag::borrow_mut(
+ &mut pool.extra_fields,
+ FungibleStakedSuiDataKey {}
+ );
+ fungible_staked_sui_data.total_supply = fungible_staked_sui_data.total_supply + pool_token_amount;
+ balance::join(&mut fungible_staked_sui_data.principal, principal);
+ };
+
+ FungibleStakedSui {
+ id: object::new(ctx),
+ pool_id,
+ value: pool_token_amount,
+ }
+}
+
+
+
+
@@ -892,6 +1217,28 @@ withdraws can be made to the pool.
+
+
+
+
+## Function `fungible_staked_sui_pool_id`
+
+
+
+public fun fungible_staked_sui_pool_id(fungible_staked_sui: &staking_pool::FungibleStakedSui): object::ID
+
+
+
+
+
+Implementation
+
+
+public fun fungible_staked_sui_pool_id(fungible_staked_sui: &FungibleStakedSui): ID { fungible_staked_sui.pool_id }
+
+
+
+
@@ -988,6 +1335,93 @@ Returns true if the input staking pool is inactive.
+
+
+
+
+## Function `fungible_staked_sui_value`
+
+
+
+public fun fungible_staked_sui_value(fungible_staked_sui: &staking_pool::FungibleStakedSui): u64
+
+
+
+
+
+Implementation
+
+
+public fun fungible_staked_sui_value(fungible_staked_sui: &FungibleStakedSui): u64 { fungible_staked_sui.value }
+
+
+
+
+
+
+
+
+## Function `split_fungible_staked_sui`
+
+
+
+public fun split_fungible_staked_sui(fungible_staked_sui: &mut staking_pool::FungibleStakedSui, split_amount: u64, ctx: &mut tx_context::TxContext): staking_pool::FungibleStakedSui
+
+
+
+
+
+Implementation
+
+
+public fun split_fungible_staked_sui(
+ fungible_staked_sui: &mut FungibleStakedSui,
+ split_amount: u64,
+ ctx: &mut TxContext
+): FungibleStakedSui {
+ assert!(split_amount <= fungible_staked_sui.value, EInsufficientPoolTokenBalance);
+
+ fungible_staked_sui.value = fungible_staked_sui.value - split_amount;
+
+ FungibleStakedSui {
+ id: object::new(ctx),
+ pool_id: fungible_staked_sui.pool_id,
+ value: split_amount,
+ }
+}
+
+
+
+
+
+
+
+
+## Function `join_fungible_staked_sui`
+
+
+
+public fun join_fungible_staked_sui(self: &mut staking_pool::FungibleStakedSui, other: staking_pool::FungibleStakedSui)
+
+
+
+
+
+Implementation
+
+
+public fun join_fungible_staked_sui(self: &mut FungibleStakedSui, other: FungibleStakedSui) {
+ let FungibleStakedSui { id, pool_id, value } = other;
+ assert!(self.pool_id == pool_id, EWrongPool);
+
+ object::delete(id);
+
+ self.value = self.value + value;
+}
+
+
+
+
diff --git a/crates/sui-framework/docs/sui-system/sui_system.md b/crates/sui-framework/docs/sui-system/sui_system.md
index 9799ac326b842..ca151bd20b68c 100644
--- a/crates/sui-framework/docs/sui-system/sui_system.md
+++ b/crates/sui-framework/docs/sui-system/sui_system.md
@@ -55,6 +55,8 @@ the SuiSystemStateInner version, or vice versa.
- [Function `request_add_stake_non_entry`](#0x3_sui_system_request_add_stake_non_entry)
- [Function `request_add_stake_mul_coin`](#0x3_sui_system_request_add_stake_mul_coin)
- [Function `request_withdraw_stake`](#0x3_sui_system_request_withdraw_stake)
+- [Function `convert_to_fungible_staked_sui`](#0x3_sui_system_convert_to_fungible_staked_sui)
+- [Function `redeem_fungible_staked_sui`](#0x3_sui_system_redeem_fungible_staked_sui)
- [Function `request_withdraw_stake_non_entry`](#0x3_sui_system_request_withdraw_stake_non_entry)
- [Function `report_validator`](#0x3_sui_system_report_validator)
- [Function `undo_report_validator`](#0x3_sui_system_undo_report_validator)
@@ -616,6 +618,66 @@ Withdraw stake from a validator's staking pool.
+
+
+
+
+## Function `convert_to_fungible_staked_sui`
+
+Convert StakedSui into a FungibleStakedSui object.
+
+
+public fun convert_to_fungible_staked_sui(wrapper: &mut sui_system::SuiSystemState, staked_sui: staking_pool::StakedSui, ctx: &mut tx_context::TxContext): staking_pool::FungibleStakedSui
+
+
+
+
+
+Implementation
+
+
+public fun convert_to_fungible_staked_sui(
+ wrapper: &mut SuiSystemState,
+ staked_sui: StakedSui,
+ ctx: &mut TxContext,
+): FungibleStakedSui {
+ let self = load_system_state_mut(wrapper);
+ self.convert_to_fungible_staked_sui(staked_sui, ctx)
+}
+
+
+
+
+
+
+
+
+## Function `redeem_fungible_staked_sui`
+
+Convert FungibleStakedSui into a StakedSui object.
+
+
+public fun redeem_fungible_staked_sui(wrapper: &mut sui_system::SuiSystemState, fungible_staked_sui: staking_pool::FungibleStakedSui, ctx: &tx_context::TxContext): balance::Balance<sui::SUI>
+
+
+
+
+
+Implementation
+
+
+public fun redeem_fungible_staked_sui(
+ wrapper: &mut SuiSystemState,
+ fungible_staked_sui: FungibleStakedSui,
+ ctx: &TxContext,
+): Balance<SUI> {
+ let self = load_system_state_mut(wrapper);
+ self.redeem_fungible_staked_sui(fungible_staked_sui, ctx)
+}
+
+
+
+
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 c35887139f668..f4357743de41d 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
@@ -24,6 +24,8 @@ title: Module `0x3::sui_system_state_inner`
- [Function `request_add_stake`](#0x3_sui_system_state_inner_request_add_stake)
- [Function `request_add_stake_mul_coin`](#0x3_sui_system_state_inner_request_add_stake_mul_coin)
- [Function `request_withdraw_stake`](#0x3_sui_system_state_inner_request_withdraw_stake)
+- [Function `convert_to_fungible_staked_sui`](#0x3_sui_system_state_inner_convert_to_fungible_staked_sui)
+- [Function `redeem_fungible_staked_sui`](#0x3_sui_system_state_inner_redeem_fungible_staked_sui)
- [Function `report_validator`](#0x3_sui_system_state_inner_report_validator)
- [Function `undo_report_validator`](#0x3_sui_system_state_inner_undo_report_validator)
- [Function `report_validator_impl`](#0x3_sui_system_state_inner_report_validator_impl)
@@ -1300,6 +1302,62 @@ Withdraw some portion of a stake from a validator's staking pool.
+
+
+
+
+## Function `convert_to_fungible_staked_sui`
+
+
+
+public(friend) fun convert_to_fungible_staked_sui(self: &mut sui_system_state_inner::SuiSystemStateInnerV2, staked_sui: staking_pool::StakedSui, ctx: &mut tx_context::TxContext): staking_pool::FungibleStakedSui
+
+
+
+
+
+Implementation
+
+
+public(package) fun convert_to_fungible_staked_sui(
+ self: &mut SuiSystemStateInnerV2,
+ staked_sui: StakedSui,
+ ctx: &mut TxContext,
+) : FungibleStakedSui {
+ self.validators.convert_to_fungible_staked_sui(staked_sui, ctx)
+}
+
+
+
+
+
+
+
+
+## Function `redeem_fungible_staked_sui`
+
+
+
+public(friend) fun redeem_fungible_staked_sui(self: &mut sui_system_state_inner::SuiSystemStateInnerV2, fungible_staked_sui: staking_pool::FungibleStakedSui, ctx: &tx_context::TxContext): balance::Balance<sui::SUI>
+
+
+
+
+
+Implementation
+
+
+public(package) fun redeem_fungible_staked_sui(
+ self: &mut SuiSystemStateInnerV2,
+ fungible_staked_sui: FungibleStakedSui,
+ ctx: &TxContext,
+) : Balance<SUI> {
+ self.validators.redeem_fungible_staked_sui(fungible_staked_sui, ctx)
+}
+
+
+
+
diff --git a/crates/sui-framework/docs/sui-system/validator.md b/crates/sui-framework/docs/sui-system/validator.md
index bda8ac01894c0..db50fa3400a9d 100644
--- a/crates/sui-framework/docs/sui-system/validator.md
+++ b/crates/sui-framework/docs/sui-system/validator.md
@@ -8,6 +8,8 @@ title: Module `0x3::validator`
- [Struct `Validator`](#0x3_validator_Validator)
- [Struct `StakingRequestEvent`](#0x3_validator_StakingRequestEvent)
- [Struct `UnstakingRequestEvent`](#0x3_validator_UnstakingRequestEvent)
+- [Struct `ConvertingToFungibleStakedSuiEvent`](#0x3_validator_ConvertingToFungibleStakedSuiEvent)
+- [Struct `RedeemingFungibleStakedSuiEvent`](#0x3_validator_RedeemingFungibleStakedSuiEvent)
- [Constants](#@Constants_0)
- [Function `new_metadata`](#0x3_validator_new_metadata)
- [Function `new`](#0x3_validator_new)
@@ -15,6 +17,8 @@ title: Module `0x3::validator`
- [Function `activate`](#0x3_validator_activate)
- [Function `adjust_stake_and_gas_price`](#0x3_validator_adjust_stake_and_gas_price)
- [Function `request_add_stake`](#0x3_validator_request_add_stake)
+- [Function `convert_to_fungible_staked_sui`](#0x3_validator_convert_to_fungible_staked_sui)
+- [Function `redeem_fungible_staked_sui`](#0x3_validator_redeem_fungible_staked_sui)
- [Function `request_add_stake_at_genesis`](#0x3_validator_request_add_stake_at_genesis)
- [Function `request_withdraw_stake`](#0x3_validator_request_withdraw_stake)
- [Function `request_set_gas_price`](#0x3_validator_request_set_gas_price)
@@ -459,6 +463,92 @@ Event emitted when a new unstake request is received.
+
+
+
+
+## Struct `ConvertingToFungibleStakedSuiEvent`
+
+Event emitted when a staked SUI is converted to a fungible staked SUI.
+
+
+struct ConvertingToFungibleStakedSuiEvent has copy, drop
+
+
+
+
+
+Fields
+
+
+
+-
+
pool_id: object::ID
+
+-
+
+
+-
+
stake_activation_epoch: u64
+
+-
+
+
+-
+
staked_sui_principal_amount: u64
+
+-
+
+
+-
+
fungible_staked_sui_amount: u64
+
+-
+
+
+
+
+
+
+
+
+
+## Struct `RedeemingFungibleStakedSuiEvent`
+
+Event emitted when a fungible staked SUI is redeemed.
+
+
+struct RedeemingFungibleStakedSuiEvent has copy, drop
+
+
+
+
+
+Fields
+
+
+
+-
+
pool_id: object::ID
+
+-
+
+
+-
+
fungible_staked_sui_amount: u64
+
+-
+
+
+-
+
sui_amount: u64
+
+-
+
+
+
+
+
@@ -918,6 +1008,88 @@ Request to add stake to the validator's staking pool, processed at the end of th
+
+
+
+
+## Function `convert_to_fungible_staked_sui`
+
+
+
+public(friend) fun convert_to_fungible_staked_sui(self: &mut validator::Validator, staked_sui: staking_pool::StakedSui, ctx: &mut tx_context::TxContext): staking_pool::FungibleStakedSui
+
+
+
+
+
+Implementation
+
+
+public(package) fun convert_to_fungible_staked_sui(
+ self: &mut Validator,
+ staked_sui: StakedSui,
+ ctx: &mut TxContext,
+) : FungibleStakedSui {
+ let stake_activation_epoch = staked_sui.stake_activation_epoch();
+ let staked_sui_principal_amount = staked_sui.staked_sui_amount();
+
+ let fungible_staked_sui = self.staking_pool.convert_to_fungible_staked_sui(staked_sui, ctx);
+
+ event::emit(
+ ConvertingToFungibleStakedSuiEvent {
+ pool_id: self.staking_pool_id(),
+ stake_activation_epoch,
+ staked_sui_principal_amount,
+ fungible_staked_sui_amount: fungible_staked_sui.value(),
+ }
+ );
+
+ fungible_staked_sui
+}
+
+
+
+
+
+
+
+
+## Function `redeem_fungible_staked_sui`
+
+
+
+public(friend) fun redeem_fungible_staked_sui(self: &mut validator::Validator, fungible_staked_sui: staking_pool::FungibleStakedSui, ctx: &tx_context::TxContext): balance::Balance<sui::SUI>
+
+
+
+
+
+Implementation
+
+
+public(package) fun redeem_fungible_staked_sui(
+ self: &mut Validator,
+ fungible_staked_sui: FungibleStakedSui,
+ ctx: &TxContext,
+) : Balance<SUI> {
+ let fungible_staked_sui_amount = fungible_staked_sui.value();
+
+ let sui = self.staking_pool.redeem_fungible_staked_sui(fungible_staked_sui, ctx);
+
+ event::emit(
+ RedeemingFungibleStakedSuiEvent {
+ pool_id: self.staking_pool_id(),
+ fungible_staked_sui_amount,
+ sui_amount: sui.value(),
+ }
+ );
+
+ sui
+}
+
+
+
+
diff --git a/crates/sui-framework/docs/sui-system/validator_set.md b/crates/sui-framework/docs/sui-system/validator_set.md
index ed59f156c459d..5e38d48f25c0c 100644
--- a/crates/sui-framework/docs/sui-system/validator_set.md
+++ b/crates/sui-framework/docs/sui-system/validator_set.md
@@ -18,6 +18,8 @@ title: Module `0x3::validator_set`
- [Function `request_remove_validator`](#0x3_validator_set_request_remove_validator)
- [Function `request_add_stake`](#0x3_validator_set_request_add_stake)
- [Function `request_withdraw_stake`](#0x3_validator_set_request_withdraw_stake)
+- [Function `convert_to_fungible_staked_sui`](#0x3_validator_set_convert_to_fungible_staked_sui)
+- [Function `redeem_fungible_staked_sui`](#0x3_validator_set_redeem_fungible_staked_sui)
- [Function `request_set_commission_rate`](#0x3_validator_set_request_set_commission_rate)
- [Function `advance_epoch`](#0x3_validator_set_advance_epoch)
- [Function `update_and_process_low_stake_departures`](#0x3_validator_set_update_and_process_low_stake_departures)
@@ -949,6 +951,85 @@ the stake and any rewards corresponding to it will be immediately processed.
+
+
+
+
+## Function `convert_to_fungible_staked_sui`
+
+
+
+public(friend) fun convert_to_fungible_staked_sui(self: &mut validator_set::ValidatorSet, staked_sui: staking_pool::StakedSui, ctx: &mut tx_context::TxContext): staking_pool::FungibleStakedSui
+
+
+
+
+
+Implementation
+
+
+public(package) fun convert_to_fungible_staked_sui(
+ self: &mut ValidatorSet,
+ staked_sui: StakedSui,
+ ctx: &mut TxContext,
+) : FungibleStakedSui {
+ let staking_pool_id = pool_id(&staked_sui);
+ let validator =
+ if (self.staking_pool_mappings.contains(staking_pool_id)) { // This is an active validator.
+ let validator_address = self.staking_pool_mappings[staking_pool_id];
+ get_candidate_or_active_validator_mut(self, validator_address)
+ } else { // This is an inactive pool.
+ assert!(self.inactive_validators.contains(staking_pool_id), ENoPoolFound);
+ let wrapper = &mut self.inactive_validators[staking_pool_id];
+ wrapper.load_validator_maybe_upgrade()
+ };
+
+ validator.convert_to_fungible_staked_sui(staked_sui, ctx)
+}
+
+
+
+
+
+
+
+
+## Function `redeem_fungible_staked_sui`
+
+
+
+public(friend) fun redeem_fungible_staked_sui(self: &mut validator_set::ValidatorSet, fungible_staked_sui: staking_pool::FungibleStakedSui, ctx: &tx_context::TxContext): balance::Balance<sui::SUI>
+
+
+
+
+
+Implementation
+
+
+public(package) fun redeem_fungible_staked_sui(
+ self: &mut ValidatorSet,
+ fungible_staked_sui: FungibleStakedSui,
+ ctx: &TxContext,
+) : Balance<SUI> {
+ let staking_pool_id = fungible_staked_sui_pool_id(&fungible_staked_sui);
+
+ let validator =
+ if (self.staking_pool_mappings.contains(staking_pool_id)) { // This is an active validator.
+ let validator_address = self.staking_pool_mappings[staking_pool_id];
+ get_candidate_or_active_validator_mut(self, validator_address)
+ } else { // This is an inactive pool.
+ assert!(self.inactive_validators.contains(staking_pool_id), ENoPoolFound);
+ let wrapper = &mut self.inactive_validators[staking_pool_id];
+ wrapper.load_validator_maybe_upgrade()
+ };
+
+ validator.redeem_fungible_staked_sui(fungible_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 0295dbfa703a1..ba10693c3dccf 100644
--- a/crates/sui-framework/packages/sui-system/sources/staking_pool.move
+++ b/crates/sui-framework/packages/sui-system/sources/staking_pool.move
@@ -31,6 +31,8 @@ module sui_system::staking_pool {
const EActivationOfInactivePool: u64 = 16;
const EDelegationOfZeroSui: u64 = 17;
const EStakedSuiBelowThreshold: u64 = 18;
+ const ECannotMintFungibleStakedSuiYet: u64 = 19;
+ const EInvariantFailure: u64 = 20;
/// A staking pool embedded in each validator struct in the system state object.
public struct StakingPool has key, store {
@@ -80,6 +82,30 @@ module sui_system::staking_pool {
principal: Balance,
}
+ /// An alternative to `StakedSui` that holds the pool token amount instead of the SUI balance.
+ /// StakedSui objects can be converted to FungibleStakedSuis after the initial warmup period.
+ /// The advantage of this is that you can now merge multiple StakedSui objects from different
+ /// activation epochs into a single FungibleStakedSui object.
+ public struct FungibleStakedSui has key, store {
+ id: UID,
+ /// ID of the staking pool we are staking with.
+ pool_id: ID,
+ /// The pool token amount.
+ value: u64,
+ }
+
+ /// Holds useful information
+ public struct FungibleStakedSuiData has key, store {
+ id: UID,
+ /// fungible_staked_sui supply
+ total_supply: u64,
+ /// principal balance. Rewards are withdrawn from the reward pool
+ principal: Balance,
+ }
+
+ // === dynamic field keys ===
+ public struct FungibleStakedSuiDataKey has copy, store, drop {}
+
// ==== initializer ====
/// Create a new, empty staking pool.
@@ -158,6 +184,133 @@ module sui_system::staking_pool {
principal_withdraw
}
+ public(package) fun redeem_fungible_staked_sui(
+ pool: &mut StakingPool,
+ fungible_staked_sui: FungibleStakedSui,
+ ctx: &TxContext
+ ) : Balance {
+ let FungibleStakedSui { id, pool_id, value } = fungible_staked_sui;
+ assert!(pool_id == object::id(pool), EWrongPool);
+
+ object::delete(id);
+
+ let latest_exchange_rate = pool_token_exchange_rate_at_epoch(pool, tx_context::epoch(ctx));
+ let fungible_staked_sui_data: &mut FungibleStakedSuiData = bag::borrow_mut(
+ &mut pool.extra_fields,
+ FungibleStakedSuiDataKey {}
+ );
+
+ let (principal_amount, rewards_amount) = calculate_fungible_staked_sui_withdraw_amount(
+ latest_exchange_rate,
+ value,
+ balance::value(&fungible_staked_sui_data.principal),
+ fungible_staked_sui_data.total_supply
+ );
+
+ fungible_staked_sui_data.total_supply = fungible_staked_sui_data.total_supply - value;
+
+ let mut sui_out = balance::split(&mut fungible_staked_sui_data.principal, principal_amount);
+ balance::join(
+ &mut sui_out,
+ balance::split(&mut pool.rewards_pool, rewards_amount)
+ );
+
+ pool.pending_total_sui_withdraw = pool.pending_total_sui_withdraw + balance::value(&sui_out);
+ pool.pending_pool_token_withdraw = pool.pending_pool_token_withdraw + value;
+
+ sui_out
+ }
+
+ /// written in separate function so i can test with random values
+ /// returns (principal_withdraw_amount, rewards_withdraw_amount)
+ fun calculate_fungible_staked_sui_withdraw_amount(
+ latest_exchange_rate: PoolTokenExchangeRate,
+ fungible_staked_sui_value: u64,
+ fungible_staked_sui_data_principal_amount: u64, // fungible_staked_sui_data.principal.value()
+ fungible_staked_sui_data_total_supply: u64, // fungible_staked_sui_data.total_supply
+ ) : (u64, u64) {
+ // 1. if the entire FungibleStakedSuiData supply is redeemed, how much sui should we receive?
+ let total_sui_amount = get_sui_amount(&latest_exchange_rate, fungible_staked_sui_data_total_supply);
+
+ // min with total_sui_amount to prevent underflow
+ let fungible_staked_sui_data_principal_amount = std::u64::min(
+ fungible_staked_sui_data_principal_amount,
+ total_sui_amount
+ );
+
+ // 2. how much do we need to withdraw from the rewards pool?
+ let total_rewards = total_sui_amount - fungible_staked_sui_data_principal_amount;
+
+ // 3. proportionally withdraw from both wrt the fungible_staked_sui_value.
+ let principal_withdraw_amount = ((fungible_staked_sui_value as u128)
+ * (fungible_staked_sui_data_principal_amount as u128)
+ / (fungible_staked_sui_data_total_supply as u128)) as u64;
+
+ let rewards_withdraw_amount = ((fungible_staked_sui_value as u128)
+ * (total_rewards as u128)
+ / (fungible_staked_sui_data_total_supply as u128)) as u64;
+
+ // invariant check, just in case
+ let expected_sui_amount = get_sui_amount(&latest_exchange_rate, fungible_staked_sui_value);
+ assert!(principal_withdraw_amount + rewards_withdraw_amount <= expected_sui_amount, EInvariantFailure);
+
+ (principal_withdraw_amount, rewards_withdraw_amount)
+ }
+
+ /// Convert the given staked SUI to an FungibleStakedSui object
+ public(package) fun convert_to_fungible_staked_sui(
+ pool: &mut StakingPool,
+ staked_sui: StakedSui,
+ ctx: &mut TxContext
+ ) : FungibleStakedSui {
+ let StakedSui { id, pool_id, stake_activation_epoch, principal } = staked_sui;
+
+ assert!(pool_id == object::id(pool), EWrongPool);
+ assert!(
+ tx_context::epoch(ctx) >= stake_activation_epoch,
+ ECannotMintFungibleStakedSuiYet
+ );
+
+ object::delete(id);
+
+
+ let exchange_rate_at_staking_epoch = pool_token_exchange_rate_at_epoch(
+ pool,
+ stake_activation_epoch
+ );
+
+ let pool_token_amount = get_token_amount(
+ &exchange_rate_at_staking_epoch,
+ balance::value(&principal)
+ );
+
+ if (!bag::contains(&pool.extra_fields, FungibleStakedSuiDataKey {})) {
+ bag::add(
+ &mut pool.extra_fields,
+ FungibleStakedSuiDataKey {},
+ FungibleStakedSuiData {
+ id: object::new(ctx),
+ total_supply: pool_token_amount,
+ principal
+ }
+ );
+ }
+ else {
+ let fungible_staked_sui_data: &mut FungibleStakedSuiData = bag::borrow_mut(
+ &mut pool.extra_fields,
+ FungibleStakedSuiDataKey {}
+ );
+ fungible_staked_sui_data.total_supply = fungible_staked_sui_data.total_supply + pool_token_amount;
+ balance::join(&mut fungible_staked_sui_data.principal, principal);
+ };
+
+ FungibleStakedSui {
+ id: object::new(ctx),
+ pool_id,
+ value: pool_token_amount,
+ }
+ }
+
/// Withdraw the principal SUI stored in the StakedSui object, and calculate the corresponding amount of pool
/// tokens using exchange rate at staking epoch.
/// Returns values are amount of pool tokens withdrawn and withdrawn principal portion of SUI.
@@ -293,6 +446,9 @@ module sui_system::staking_pool {
public fun pool_id(staked_sui: &StakedSui): ID { staked_sui.pool_id }
+ public use fun fungible_staked_sui_pool_id as FungibleStakedSui.pool_id;
+ public fun fungible_staked_sui_pool_id(fungible_staked_sui: &FungibleStakedSui): ID { fungible_staked_sui.pool_id }
+
public fun staked_sui_amount(staked_sui: &StakedSui): u64 { staked_sui.principal.value() }
/// Allows calling `.amount()` on `StakedSui` to invoke `staked_sui_amount`
@@ -312,6 +468,36 @@ module sui_system::staking_pool {
pool.deactivation_epoch.is_some()
}
+ public use fun fungible_staked_sui_value as FungibleStakedSui.value;
+ public fun fungible_staked_sui_value(fungible_staked_sui: &FungibleStakedSui): u64 { fungible_staked_sui.value }
+
+ public use fun split_fungible_staked_sui as FungibleStakedSui.split;
+ public fun split_fungible_staked_sui(
+ fungible_staked_sui: &mut FungibleStakedSui,
+ split_amount: u64,
+ ctx: &mut TxContext
+ ): FungibleStakedSui {
+ assert!(split_amount <= fungible_staked_sui.value, EInsufficientPoolTokenBalance);
+
+ fungible_staked_sui.value = fungible_staked_sui.value - split_amount;
+
+ FungibleStakedSui {
+ id: object::new(ctx),
+ pool_id: fungible_staked_sui.pool_id,
+ value: split_amount,
+ }
+ }
+
+ public use fun join_fungible_staked_sui as FungibleStakedSui.join;
+ public fun join_fungible_staked_sui(self: &mut FungibleStakedSui, other: FungibleStakedSui) {
+ let FungibleStakedSui { id, pool_id, value } = other;
+ assert!(self.pool_id == pool_id, EWrongPool);
+
+ object::delete(id);
+
+ self.value = self.value + value;
+ }
+
/// Split StakedSui `self` to two parts, one with principal `split_amount`,
/// and the remaining principal is left in `self`.
/// All the other parameters of the StakedSui like `stake_activation_epoch` or `pool_id` remain the same.
@@ -473,4 +659,104 @@ module sui_system::staking_pool {
staked_amount + reward_withdraw_amount
}
+
+ #[test_only]
+ public(package) fun fungible_staked_sui_data(pool: &StakingPool): &FungibleStakedSuiData {
+ bag::borrow(&pool.extra_fields, FungibleStakedSuiDataKey {})
+ }
+
+ #[test_only]
+ public use fun fungible_staked_sui_data_total_supply as FungibleStakedSuiData.total_supply;
+
+ #[test_only]
+ public(package) fun fungible_staked_sui_data_total_supply(fungible_staked_sui_data: &FungibleStakedSuiData): u64 {
+ fungible_staked_sui_data.total_supply
+ }
+
+ #[test_only]
+ public use fun fungible_staked_sui_data_principal_value as FungibleStakedSuiData.principal_value;
+
+ #[test_only]
+ public(package) fun fungible_staked_sui_data_principal_value(fungible_staked_sui_data: &FungibleStakedSuiData): u64 {
+ fungible_staked_sui_data.principal.value()
+ }
+
+ #[test_only]
+ public(package) fun pending_pool_token_withdraw_amount(pool: &StakingPool): u64 {
+ pool.pending_pool_token_withdraw
+ }
+
+ #[test_only]
+ public(package) fun create_fungible_staked_sui_for_testing(
+ self: &StakingPool,
+ value: u64,
+ ctx: &mut TxContext
+ ) : FungibleStakedSui {
+ FungibleStakedSui {
+ id: object::new(ctx),
+ pool_id: object::id(self),
+ value,
+ }
+ }
+
+ // ==== tests ====
+
+ #[random_test]
+ fun test_calculate_fungible_staked_sui_withdraw_amount(
+ mut total_sui_amount: u64,
+ // these are all in basis points
+ mut pool_token_frac: u16,
+ mut fungible_staked_sui_data_total_supply_frac: u16,
+ mut fungible_staked_sui_data_principal_frac: u16,
+ mut fungible_staked_sui_value_bps: u16
+ ) {
+ use std::u128::max;
+
+ total_sui_amount = std::u64::max(total_sui_amount, 1);
+
+ pool_token_frac = pool_token_frac % 10000;
+ fungible_staked_sui_data_total_supply_frac = fungible_staked_sui_data_total_supply_frac % 10000;
+ fungible_staked_sui_data_principal_frac = fungible_staked_sui_data_principal_frac % 10000;
+ fungible_staked_sui_value_bps = fungible_staked_sui_value_bps % 10000;
+
+
+ let total_pool_token_amount = max(
+ (total_sui_amount as u128) * (pool_token_frac as u128) / 10000,
+ 1
+ );
+
+ let exchange_rate = PoolTokenExchangeRate {
+ sui_amount: total_sui_amount,
+ pool_token_amount: total_pool_token_amount as u64,
+ };
+
+ let fungible_staked_sui_data_total_supply = max(
+ total_pool_token_amount * (fungible_staked_sui_data_total_supply_frac as u128) / 10000,
+ 1
+ );
+ let fungible_staked_sui_value = fungible_staked_sui_data_total_supply
+ * (fungible_staked_sui_value_bps as u128) / 10000;
+
+ let max_principal = get_sui_amount(&exchange_rate, fungible_staked_sui_data_total_supply as u64);
+ let fungible_staked_sui_data_principal_amount = max(
+ (max_principal as u128) * (fungible_staked_sui_data_principal_frac as u128) / 10000,
+ 1
+ );
+
+ let (principal_amount, rewards_amount) = calculate_fungible_staked_sui_withdraw_amount(
+ exchange_rate,
+ fungible_staked_sui_value as u64,
+ fungible_staked_sui_data_principal_amount as u64,
+ fungible_staked_sui_data_total_supply as u64,
+ );
+
+ let expected_out = get_sui_amount(&exchange_rate, fungible_staked_sui_value as u64);
+
+ assert!(principal_amount + rewards_amount <= expected_out, 0);
+
+ let min_out = if (expected_out > 2) expected_out - 2 else 0;
+ assert!(principal_amount + rewards_amount >= min_out, 0);
+ }
+
+
}
diff --git a/crates/sui-framework/packages/sui-system/sources/sui_system.move b/crates/sui-framework/packages/sui-system/sources/sui_system.move
index fe46fff592e6a..916c2cd55b33b 100644
--- a/crates/sui-framework/packages/sui-system/sources/sui_system.move
+++ b/crates/sui-framework/packages/sui-system/sources/sui_system.move
@@ -42,7 +42,7 @@ module sui_system::sui_system {
use sui::balance::Balance;
use sui::coin::Coin;
- use sui_system::staking_pool::StakedSui;
+ use sui_system::staking_pool::{StakedSui, FungibleStakedSui};
use sui::sui::SUI;
use sui::table::Table;
use sui_system::validator::Validator;
@@ -265,6 +265,26 @@ module sui_system::sui_system {
transfer::public_transfer(withdrawn_stake.into_coin(ctx), ctx.sender());
}
+ /// Convert StakedSui into a FungibleStakedSui object.
+ public fun convert_to_fungible_staked_sui(
+ wrapper: &mut SuiSystemState,
+ staked_sui: StakedSui,
+ ctx: &mut TxContext,
+ ): FungibleStakedSui {
+ let self = load_system_state_mut(wrapper);
+ self.convert_to_fungible_staked_sui(staked_sui, ctx)
+ }
+
+ /// Convert FungibleStakedSui into a StakedSui object.
+ public fun redeem_fungible_staked_sui(
+ wrapper: &mut SuiSystemState,
+ fungible_staked_sui: FungibleStakedSui,
+ ctx: &TxContext,
+ ): Balance {
+ let self = load_system_state_mut(wrapper);
+ self.redeem_fungible_staked_sui(fungible_staked_sui, ctx)
+ }
+
/// Non-entry version of `request_withdraw_stake` that returns the withdrawn SUI instead of transferring it to the sender.
public fun request_withdraw_stake_non_entry(
wrapper: &mut SuiSystemState,
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 ae23b97b4fd40..121a12fc75b94 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::StakedSui;
+ use sui_system::staking_pool::{StakedSui, FungibleStakedSui};
use sui::sui::SUI;
use sui_system::validator::{Self, Validator};
use sui_system::validator_set::{Self, ValidatorSet};
@@ -518,6 +518,22 @@ module sui_system::sui_system_state_inner {
self.validators.request_withdraw_stake(staked_sui, ctx)
}
+ public(package) fun convert_to_fungible_staked_sui(
+ self: &mut SuiSystemStateInnerV2,
+ staked_sui: StakedSui,
+ ctx: &mut TxContext,
+ ) : FungibleStakedSui {
+ self.validators.convert_to_fungible_staked_sui(staked_sui, ctx)
+ }
+
+ public(package) fun redeem_fungible_staked_sui(
+ self: &mut SuiSystemStateInnerV2,
+ fungible_staked_sui: FungibleStakedSui,
+ ctx: &TxContext,
+ ) : Balance {
+ self.validators.redeem_fungible_staked_sui(fungible_staked_sui, ctx)
+ }
+
/// Report a validator as a bad or non-performant actor in the system.
/// Succeeds if all the following are satisfied:
/// 1. both the reporter in `cap` and the input `reportee_addr` are active validators.
diff --git a/crates/sui-framework/packages/sui-system/sources/validator.move b/crates/sui-framework/packages/sui-system/sources/validator.move
index 5c0a453ea779f..da6157014541e 100644
--- a/crates/sui-framework/packages/sui-system/sources/validator.move
+++ b/crates/sui-framework/packages/sui-system/sources/validator.move
@@ -8,7 +8,7 @@ module sui_system::validator {
use sui::balance::Balance;
use sui::sui::SUI;
use sui_system::validator_cap::{Self, ValidatorOperationCap};
- use sui_system::staking_pool::{Self, PoolTokenExchangeRate, StakedSui, StakingPool};
+ use sui_system::staking_pool::{Self, PoolTokenExchangeRate, StakedSui, StakingPool, FungibleStakedSui};
use std::string::String;
use sui::url::Url;
use sui::url;
@@ -160,6 +160,21 @@ module sui_system::validator {
reward_amount: u64,
}
+ /// Event emitted when a staked SUI is converted to a fungible staked SUI.
+ public struct ConvertingToFungibleStakedSuiEvent has copy, drop {
+ pool_id: ID,
+ stake_activation_epoch: u64,
+ staked_sui_principal_amount: u64,
+ fungible_staked_sui_amount: u64,
+ }
+
+ /// Event emitted when a fungible staked SUI is redeemed.
+ public struct RedeemingFungibleStakedSuiEvent has copy, drop {
+ pool_id: ID,
+ fungible_staked_sui_amount: u64,
+ sui_amount: u64,
+ }
+
public(package) fun new_metadata(
sui_address: address,
protocol_pubkey_bytes: vector,
@@ -306,6 +321,48 @@ module sui_system::validator {
staked_sui
}
+ public(package) fun convert_to_fungible_staked_sui(
+ self: &mut Validator,
+ staked_sui: StakedSui,
+ ctx: &mut TxContext,
+ ) : FungibleStakedSui {
+ let stake_activation_epoch = staked_sui.stake_activation_epoch();
+ let staked_sui_principal_amount = staked_sui.staked_sui_amount();
+
+ let fungible_staked_sui = self.staking_pool.convert_to_fungible_staked_sui(staked_sui, ctx);
+
+ event::emit(
+ ConvertingToFungibleStakedSuiEvent {
+ pool_id: self.staking_pool_id(),
+ stake_activation_epoch,
+ staked_sui_principal_amount,
+ fungible_staked_sui_amount: fungible_staked_sui.value(),
+ }
+ );
+
+ fungible_staked_sui
+ }
+
+ public(package) fun redeem_fungible_staked_sui(
+ self: &mut Validator,
+ fungible_staked_sui: FungibleStakedSui,
+ ctx: &TxContext,
+ ) : Balance {
+ let fungible_staked_sui_amount = fungible_staked_sui.value();
+
+ let sui = self.staking_pool.redeem_fungible_staked_sui(fungible_staked_sui, ctx);
+
+ event::emit(
+ RedeemingFungibleStakedSuiEvent {
+ pool_id: self.staking_pool_id(),
+ fungible_staked_sui_amount,
+ sui_amount: sui.value(),
+ }
+ );
+
+ sui
+ }
+
/// Request to add stake to the validator's staking pool at genesis
public(package) fun request_add_stake_at_genesis(
self: &mut Validator,
diff --git a/crates/sui-framework/packages/sui-system/sources/validator_set.move b/crates/sui-framework/packages/sui-system/sources/validator_set.move
index 2cc069f023b71..a5deb4e148a91 100644
--- a/crates/sui-framework/packages/sui-system/sources/validator_set.move
+++ b/crates/sui-framework/packages/sui-system/sources/validator_set.move
@@ -7,7 +7,7 @@ module sui_system::validator_set {
use sui::sui::SUI;
use sui_system::validator::{Validator, staking_pool_id, sui_address};
use sui_system::validator_cap::{Self, UnverifiedValidatorOperationCap, ValidatorOperationCap};
- use sui_system::staking_pool::{PoolTokenExchangeRate, StakedSui, pool_id};
+ use sui_system::staking_pool::{PoolTokenExchangeRate, StakedSui, pool_id, FungibleStakedSui, fungible_staked_sui_pool_id};
use sui::priority_queue as pq;
use sui::vec_map::{Self, VecMap};
use sui::vec_set::VecSet;
@@ -309,6 +309,45 @@ module sui_system::validator_set {
validator.request_withdraw_stake(staked_sui, ctx)
}
+ public(package) fun convert_to_fungible_staked_sui(
+ self: &mut ValidatorSet,
+ staked_sui: StakedSui,
+ ctx: &mut TxContext,
+ ) : FungibleStakedSui {
+ let staking_pool_id = pool_id(&staked_sui);
+ let validator =
+ if (self.staking_pool_mappings.contains(staking_pool_id)) { // This is an active validator.
+ let validator_address = self.staking_pool_mappings[staking_pool_id];
+ get_candidate_or_active_validator_mut(self, validator_address)
+ } else { // This is an inactive pool.
+ assert!(self.inactive_validators.contains(staking_pool_id), ENoPoolFound);
+ let wrapper = &mut self.inactive_validators[staking_pool_id];
+ wrapper.load_validator_maybe_upgrade()
+ };
+
+ validator.convert_to_fungible_staked_sui(staked_sui, ctx)
+ }
+
+ public(package) fun redeem_fungible_staked_sui(
+ self: &mut ValidatorSet,
+ fungible_staked_sui: FungibleStakedSui,
+ ctx: &TxContext,
+ ) : Balance {
+ let staking_pool_id = fungible_staked_sui_pool_id(&fungible_staked_sui);
+
+ let validator =
+ if (self.staking_pool_mappings.contains(staking_pool_id)) { // This is an active validator.
+ let validator_address = self.staking_pool_mappings[staking_pool_id];
+ get_candidate_or_active_validator_mut(self, validator_address)
+ } else { // This is an inactive pool.
+ assert!(self.inactive_validators.contains(staking_pool_id), ENoPoolFound);
+ let wrapper = &mut self.inactive_validators[staking_pool_id];
+ wrapper.load_validator_maybe_upgrade()
+ };
+
+ validator.redeem_fungible_staked_sui(fungible_staked_sui, ctx)
+ }
+
// ==== validator config setting functions ====
public(package) fun request_set_commission_rate(
diff --git a/crates/sui-framework/packages/sui-system/tests/staking_pool.move b/crates/sui-framework/packages/sui-system/tests/staking_pool.move
new file mode 100644
index 0000000000000..2a38e5f249aa8
--- /dev/null
+++ b/crates/sui-framework/packages/sui-system/tests/staking_pool.move
@@ -0,0 +1,317 @@
+// Copyright (c) Mysten Labs, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+#[test_only]
+module sui_system::staking_pool_tests {
+ use sui::test_scenario::{Self, Scenario};
+ use sui_system::staking_pool::{StakingPool, Self};
+ use sui::balance::{Self};
+
+ #[test]
+ fun test_join_fungible_staked_sui_happy() {
+ let mut scenario = test_scenario::begin(@0x0);
+ let staking_pool = staking_pool::new(scenario.ctx());
+
+ let mut fungible_staked_sui_1 = staking_pool.create_fungible_staked_sui_for_testing(100_000_000_000, scenario.ctx());
+ let fungible_staked_sui_2 = staking_pool.create_fungible_staked_sui_for_testing(200_000_000_000, scenario.ctx());
+
+ fungible_staked_sui_1.join(fungible_staked_sui_2);
+
+ assert!(fungible_staked_sui_1.value() == 300_000_000_000, 0);
+
+ sui::test_utils::destroy(staking_pool);
+ sui::test_utils::destroy(fungible_staked_sui_1);
+
+ scenario.end();
+ }
+
+ #[test]
+ #[expected_failure(abort_code = 1, location = sui_system::staking_pool)]
+ fun test_join_fungible_staked_sui_fail() {
+ let mut scenario = test_scenario::begin(@0x0);
+ let staking_pool_1 = staking_pool::new(scenario.ctx());
+ let staking_pool_2 = staking_pool::new(scenario.ctx());
+
+ let mut fungible_staked_sui_1 = staking_pool_1.create_fungible_staked_sui_for_testing(100_000_000_000, scenario.ctx());
+ let fungible_staked_sui_2 = staking_pool_2.create_fungible_staked_sui_for_testing(200_000_000_000, scenario.ctx());
+
+ fungible_staked_sui_1.join(fungible_staked_sui_2);
+
+ sui::test_utils::destroy(staking_pool_1);
+ sui::test_utils::destroy(staking_pool_2);
+ sui::test_utils::destroy(fungible_staked_sui_1);
+
+ scenario.end();
+ }
+
+ #[test]
+ fun test_split_fungible_staked_sui_happy() {
+ let mut scenario = test_scenario::begin(@0x0);
+ let staking_pool = staking_pool::new(scenario.ctx());
+
+ let mut fungible_staked_sui_1 = staking_pool.create_fungible_staked_sui_for_testing(100_000_000_000, scenario.ctx());
+
+ let fungible_staked_sui_2 = fungible_staked_sui_1.split(75_000_000_000, scenario.ctx());
+
+ assert!(fungible_staked_sui_1.value() == 25_000_000_000, 0);
+ assert!(fungible_staked_sui_2.value() == 75_000_000_000, 0);
+
+ sui::test_utils::destroy(staking_pool);
+ sui::test_utils::destroy(fungible_staked_sui_1);
+ sui::test_utils::destroy(fungible_staked_sui_2);
+
+ scenario.end();
+ }
+
+ #[test]
+ #[expected_failure(abort_code = 0, location = sui_system::staking_pool)]
+ fun test_split_fungible_staked_sui_fail_too_much() {
+ let mut scenario = test_scenario::begin(@0x0);
+ let staking_pool = staking_pool::new(scenario.ctx());
+
+ let mut fungible_staked_sui_1 = staking_pool.create_fungible_staked_sui_for_testing(100_000_000_000, scenario.ctx());
+
+ let fungible_staked_sui_2 = fungible_staked_sui_1.split(100_000_000_000 + 1, scenario.ctx());
+
+ sui::test_utils::destroy(staking_pool);
+ sui::test_utils::destroy(fungible_staked_sui_1);
+ sui::test_utils::destroy(fungible_staked_sui_2);
+
+ scenario.end();
+ }
+
+ #[test]
+ #[expected_failure(abort_code = 19, location = sui_system::staking_pool)]
+ fun test_convert_to_fungible_staked_sui_fail_too_early() {
+ let mut scenario = test_scenario::begin(@0x0);
+ let mut staking_pool = staking_pool::new(scenario.ctx());
+
+ let sui = balance::create_for_testing(1_000_000_000);
+ let staked_sui = staking_pool.request_add_stake(sui, scenario.ctx().epoch() + 1, scenario.ctx());
+ let fungible_staked_sui = staking_pool.convert_to_fungible_staked_sui(staked_sui, scenario.ctx());
+
+ sui::test_utils::destroy(staking_pool);
+ sui::test_utils::destroy(fungible_staked_sui);
+
+ scenario.end();
+ }
+
+ #[test]
+ #[expected_failure(abort_code = 1, location = sui_system::staking_pool)]
+ fun test_convert_to_fungible_staked_sui_fail_wrong_pool() {
+ let mut scenario = test_scenario::begin(@0x0);
+ let mut staking_pool_1 = staking_pool::new(scenario.ctx());
+ let mut staking_pool_2 = staking_pool::new(scenario.ctx());
+
+ let sui = balance::create_for_testing(1_000_000_000);
+ let staked_sui = staking_pool_1.request_add_stake(sui, scenario.ctx().epoch() + 1, scenario.ctx());
+
+ let fungible_staked_sui = staking_pool_2.convert_to_fungible_staked_sui(staked_sui, scenario.ctx());
+
+ sui::test_utils::destroy(staking_pool_1);
+ sui::test_utils::destroy(staking_pool_2);
+ sui::test_utils::destroy(fungible_staked_sui);
+
+ scenario.end();
+ }
+
+ #[test]
+ fun test_convert_to_fungible_staked_sui_happy() {
+ let mut scenario = test_scenario::begin(@0x0);
+ let mut staking_pool = staking_pool::new(scenario.ctx());
+ staking_pool.activate_staking_pool(0);
+
+ // setup
+
+ let sui = balance::create_for_testing(1_000_000_000);
+ let staked_sui_1 = staking_pool.request_add_stake(sui, scenario.ctx().epoch() + 1, scenario.ctx());
+
+ assert!(distribute_rewards_and_advance_epoch(&mut staking_pool, &mut scenario, 0) == 1, 0);
+
+ let latest_exchange_rate = staking_pool.pool_token_exchange_rate_at_epoch(1);
+ assert!(latest_exchange_rate.sui_amount() == 1_000_000_000, 0);
+ assert!(latest_exchange_rate.pool_token_amount() == 1_000_000_000, 0);
+
+ let sui = balance::create_for_testing(1_000_000_000);
+ let staked_sui_2 = staking_pool.request_add_stake(sui, scenario.ctx().epoch() + 1, scenario.ctx());
+
+ assert!(distribute_rewards_and_advance_epoch(&mut staking_pool, &mut scenario, 1_000_000_000) == 2, 0);
+
+ let latest_exchange_rate = staking_pool.pool_token_exchange_rate_at_epoch(2);
+ assert!(latest_exchange_rate.sui_amount() == 3_000_000_000, 0);
+ assert!(latest_exchange_rate.pool_token_amount() == 1_500_000_000, 0);
+
+ // test basically starts from here.
+
+ let fungible_staked_sui_1 = staking_pool.convert_to_fungible_staked_sui(staked_sui_1, scenario.ctx());
+ assert!(fungible_staked_sui_1.value() == 1_000_000_000, 0);
+ assert!(fungible_staked_sui_1.pool_id() == object::id(&staking_pool), 0);
+
+ let fungible_staked_sui_data = staking_pool.fungible_staked_sui_data();
+ assert!(fungible_staked_sui_data.total_supply() == 1_000_000_000, 0);
+ assert!(fungible_staked_sui_data.principal_value() == 1_000_000_000, 0);
+
+ let fungible_staked_sui_2 = staking_pool.convert_to_fungible_staked_sui(staked_sui_2, scenario.ctx());
+ assert!(fungible_staked_sui_2.value() == 500_000_000, 0);
+ assert!(fungible_staked_sui_2.pool_id() == object::id(&staking_pool), 0);
+
+ let fungible_staked_sui_data = staking_pool.fungible_staked_sui_data();
+ assert!(fungible_staked_sui_data.total_supply() == 1_500_000_000, 0);
+ assert!(fungible_staked_sui_data.principal_value() == 2_000_000_000, 0);
+
+ sui::test_utils::destroy(staking_pool);
+ // sui::test_utils::destroy(fungible_staked_sui);
+ sui::test_utils::destroy(fungible_staked_sui_1);
+ sui::test_utils::destroy(fungible_staked_sui_2);
+
+ scenario.end();
+ }
+
+ #[test]
+ fun test_redeem_fungible_staked_sui_happy() {
+ let mut scenario = test_scenario::begin(@0x0);
+ let mut staking_pool = staking_pool::new(scenario.ctx());
+ staking_pool.activate_staking_pool(0);
+
+ // setup
+
+ let sui = balance::create_for_testing(1_000_000_000);
+ let staked_sui_1 = staking_pool.request_add_stake(sui, scenario.ctx().epoch() + 1, scenario.ctx());
+
+ assert!(distribute_rewards_and_advance_epoch(&mut staking_pool, &mut scenario, 0) == 1, 0);
+
+ let latest_exchange_rate = staking_pool.pool_token_exchange_rate_at_epoch(1);
+ assert!(latest_exchange_rate.sui_amount() == 1_000_000_000, 0);
+ assert!(latest_exchange_rate.pool_token_amount() == 1_000_000_000, 0);
+
+ let sui = balance::create_for_testing(1_000_000_000);
+ let staked_sui_2 = staking_pool.request_add_stake(sui, scenario.ctx().epoch() + 1, scenario.ctx());
+
+ assert!(distribute_rewards_and_advance_epoch(&mut staking_pool, &mut scenario, 1_000_000_000) == 2, 0);
+
+ let latest_exchange_rate = staking_pool.pool_token_exchange_rate_at_epoch(2);
+ assert!(latest_exchange_rate.sui_amount() == 3_000_000_000, 0);
+ assert!(latest_exchange_rate.pool_token_amount() == 1_500_000_000, 0);
+
+ let fungible_staked_sui_1 = staking_pool.convert_to_fungible_staked_sui(staked_sui_1, scenario.ctx());
+ assert!(fungible_staked_sui_1.value() == 1_000_000_000, 0);
+ assert!(fungible_staked_sui_1.pool_id() == object::id(&staking_pool), 0);
+
+ let fungible_staked_sui_data = staking_pool.fungible_staked_sui_data();
+ assert!(fungible_staked_sui_data.total_supply() == 1_000_000_000, 0);
+ assert!(fungible_staked_sui_data.principal_value() == 1_000_000_000, 0);
+
+ let fungible_staked_sui_2 = staking_pool.convert_to_fungible_staked_sui(staked_sui_2, scenario.ctx());
+ assert!(fungible_staked_sui_2.value() == 500_000_000, 0);
+ assert!(fungible_staked_sui_2.pool_id() == object::id(&staking_pool), 0);
+
+ let fungible_staked_sui_data = staking_pool.fungible_staked_sui_data();
+ assert!(fungible_staked_sui_data.total_supply() == 1_500_000_000, 0);
+ assert!(fungible_staked_sui_data.principal_value() == 2_000_000_000, 0);
+
+ // test starts here
+ assert!(distribute_rewards_and_advance_epoch(&mut staking_pool, &mut scenario, 3_000_000_000) == 3, 0);
+
+ let latest_exchange_rate = staking_pool.pool_token_exchange_rate_at_epoch(3);
+ assert!(latest_exchange_rate.sui_amount() == 6_000_000_000, 0);
+ assert!(latest_exchange_rate.pool_token_amount() == 1_500_000_000, 0);
+
+ assert!(staking_pool.pending_stake_withdraw_amount() == 0, 0);
+ assert!(staking_pool.pending_pool_token_withdraw_amount() == 0, 0);
+
+ let sui_1 = staking_pool.redeem_fungible_staked_sui(fungible_staked_sui_1, scenario.ctx());
+ assert!(sui_1.value() <= 4_000_000_000, 0);
+ assert!(sui_1.value() == 4_000_000_000 - 1, 0);
+
+ let fungible_staked_sui_data = staking_pool.fungible_staked_sui_data();
+ assert!(fungible_staked_sui_data.total_supply() == 500_000_000, 0);
+ assert!(fungible_staked_sui_data.principal_value() == 2_000_000_000 / 3 + 1, 0); // round against user
+
+ assert!(staking_pool.pending_stake_withdraw_amount() == 4_000_000_000 - 1, 0);
+ assert!(staking_pool.pending_pool_token_withdraw_amount() == 1_000_000_000, 0);
+
+ let sui_2 = staking_pool.redeem_fungible_staked_sui(fungible_staked_sui_2, scenario.ctx());
+ assert!(sui_2.value() == 2_000_000_000, 0);
+
+ let fungible_staked_sui_data = staking_pool.fungible_staked_sui_data();
+ assert!(fungible_staked_sui_data.total_supply() == 0, 0);
+ assert!(fungible_staked_sui_data.principal_value() == 0, 0);
+
+ assert!(staking_pool.pending_stake_withdraw_amount() == 6_000_000_000 - 1, 0);
+ assert!(staking_pool.pending_pool_token_withdraw_amount() == 1_500_000_000, 0);
+
+ sui::test_utils::destroy(staking_pool);
+ sui::test_utils::destroy(sui_1);
+ sui::test_utils::destroy(sui_2);
+
+ scenario.end();
+ }
+
+ #[test]
+ fun test_redeem_fungible_staked_sui_regression_rounding() {
+ let mut scenario = test_scenario::begin(@0x0);
+ let mut staking_pool = staking_pool::new(scenario.ctx());
+ staking_pool.activate_staking_pool(0);
+
+ // setup
+
+ let sui = balance::create_for_testing(1_000_000_000);
+ let staked_sui_1 = staking_pool.request_add_stake(sui, scenario.ctx().epoch() + 1, scenario.ctx());
+
+ assert!(distribute_rewards_and_advance_epoch(&mut staking_pool, &mut scenario, 0) == 1, 0);
+
+ let latest_exchange_rate = staking_pool.pool_token_exchange_rate_at_epoch(1);
+ assert!(latest_exchange_rate.sui_amount() == 1_000_000_000, 0);
+ assert!(latest_exchange_rate.pool_token_amount() == 1_000_000_000, 0);
+
+ let sui = balance::create_for_testing(1_000_000_001);
+ let staked_sui_2 = staking_pool.request_add_stake(sui, scenario.ctx().epoch() + 1, scenario.ctx());
+
+ assert!(distribute_rewards_and_advance_epoch(&mut staking_pool, &mut scenario, 1_000_000_000) == 2, 0);
+
+ let latest_exchange_rate = staking_pool.pool_token_exchange_rate_at_epoch(2);
+ assert!(latest_exchange_rate.sui_amount() == 3_000_000_001, 0);
+ assert!(latest_exchange_rate.pool_token_amount() == 1_500_000_000, 0);
+
+ let fungible_staked_sui = staking_pool.convert_to_fungible_staked_sui(staked_sui_2, scenario.ctx());
+ assert!(fungible_staked_sui.value() == 500_000_000, 0); // rounding!
+ assert!(fungible_staked_sui.pool_id() == object::id(&staking_pool), 0);
+
+ let fungible_staked_sui_data = staking_pool.fungible_staked_sui_data();
+ assert!(fungible_staked_sui_data.total_supply() == 500_000_000, 0);
+ assert!(fungible_staked_sui_data.principal_value() == 1_000_000_001, 0);
+
+ // this line used to error
+ let sui = staking_pool.redeem_fungible_staked_sui(fungible_staked_sui, scenario.ctx());
+ assert!(sui.value() == 1_000_000_000, 0);
+
+ let fungible_staked_sui_data = staking_pool.fungible_staked_sui_data();
+ assert!(fungible_staked_sui_data.total_supply() == 0, 0);
+ assert!(fungible_staked_sui_data.principal_value() == 1, 0);
+
+ sui::test_utils::destroy(staking_pool);
+ sui::test_utils::destroy(staked_sui_1);
+ sui::test_utils::destroy(sui);
+
+ scenario.end();
+ }
+
+ #[test_only]
+ fun distribute_rewards_and_advance_epoch(
+ staking_pool: &mut StakingPool,
+ scenario: &mut Scenario,
+ reward_amount: u64
+ ): u64 {
+ use sui::tx_context::{epoch};
+ use sui::coin::{Self};
+ use sui::sui::SUI;
+
+ let rewards = coin::mint_for_testing(reward_amount, scenario.ctx());
+ staking_pool.deposit_rewards(coin::into_balance(rewards));
+
+ staking_pool.process_pending_stakes_and_withdraws(scenario.ctx());
+ test_scenario::next_epoch(scenario, @0x0);
+
+ scenario.ctx().epoch()
+ }
+}
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 25c963d2aa9ea..d333f4e64a642 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,6 +9,7 @@
module sui_system::sui_system_tests {
use sui::test_scenario::{Self, Scenario};
use sui::sui::SUI;
+ use sui::coin::Self;
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;
@@ -1070,4 +1071,58 @@ module sui_system::sui_system_tests {
scenario_val.end();
}
+
+ #[test]
+ fun test_convert_to_fungible_staked_sui_and_redeem() {
+ 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);
+ };
+
+ scenario.next_tx(@0x0);
+ let mut system_state = scenario.take_shared();
+
+ let staked_sui = system_state.request_add_stake_non_entry(
+ coin::mint_for_testing(100_000_000_000, scenario.ctx()),
+ @0x1,
+ scenario.ctx()
+ );
+
+ assert!(staked_sui.amount() == 100_000_000_000, 0);
+
+ test_scenario::return_shared(system_state);
+ advance_epoch(scenario);
+
+ let mut system_state = scenario.take_shared();
+ let fungible_staked_sui = system_state.convert_to_fungible_staked_sui(
+ staked_sui,
+ scenario.ctx()
+ );
+
+ assert!(fungible_staked_sui.value() == 100_000_000_000, 0);
+
+ let sui = system_state.redeem_fungible_staked_sui(
+ fungible_staked_sui,
+ scenario.ctx()
+ );
+
+ assert!(sui.value() == 100_000_000_000, 0);
+
+ test_scenario::return_shared(system_state);
+ sui::test_utils::destroy(sui);
+ scenario_val.end();
+ }
+
}
diff --git a/crates/sui-framework/packages_compiled/sui-system b/crates/sui-framework/packages_compiled/sui-system
index d576a4d949d82..2f5cc8064c3d9 100644
Binary files a/crates/sui-framework/packages_compiled/sui-system and b/crates/sui-framework/packages_compiled/sui-system differ
diff --git a/crates/sui-framework/published_api.txt b/crates/sui-framework/published_api.txt
index 64879af4a2a3f..e6d0acfbb736b 100644
--- a/crates/sui-framework/published_api.txt
+++ b/crates/sui-framework/published_api.txt
@@ -25,6 +25,15 @@ PoolTokenExchangeRate
StakedSui
public struct
0x3::staking_pool
+FungibleStakedSui
+ public struct
+ 0x3::staking_pool
+FungibleStakedSuiData
+ public struct
+ 0x3::staking_pool
+FungibleStakedSuiDataKey
+ public struct
+ 0x3::staking_pool
new
public(package) fun
0x3::staking_pool
@@ -34,6 +43,15 @@ request_add_stake
request_withdraw_stake
public(package) fun
0x3::staking_pool
+redeem_fungible_staked_sui
+ public(package) fun
+ 0x3::staking_pool
+calculate_fungible_staked_sui_withdraw_amount
+ fun
+ 0x3::staking_pool
+convert_to_fungible_staked_sui
+ public(package) fun
+ 0x3::staking_pool
withdraw_from_principal
public(package) fun
0x3::staking_pool
@@ -67,6 +85,9 @@ sui_balance
pool_id
public fun
0x3::staking_pool
+fungible_staked_sui_pool_id
+ public fun
+ 0x3::staking_pool
staked_sui_amount
public fun
0x3::staking_pool
@@ -79,6 +100,15 @@ is_preactive
is_inactive
public fun
0x3::staking_pool
+fungible_staked_sui_value
+ public fun
+ 0x3::staking_pool
+split_fungible_staked_sui
+ public fun
+ 0x3::staking_pool
+join_fungible_staked_sui
+ public fun
+ 0x3::staking_pool
split
public fun
0x3::staking_pool
@@ -136,6 +166,12 @@ StakingRequestEvent
UnstakingRequestEvent
public struct
0x3::validator
+ConvertingToFungibleStakedSuiEvent
+ public struct
+ 0x3::validator
+RedeemingFungibleStakedSuiEvent
+ public struct
+ 0x3::validator
new_metadata
public(package) fun
0x3::validator
@@ -154,6 +190,12 @@ adjust_stake_and_gas_price
request_add_stake
public(package) fun
0x3::validator
+convert_to_fungible_staked_sui
+ public(package) fun
+ 0x3::validator
+redeem_fungible_staked_sui
+ public(package) fun
+ 0x3::validator
request_add_stake_at_genesis
public(package) fun
0x3::validator
@@ -457,6 +499,12 @@ request_add_stake
request_withdraw_stake
public(package) fun
0x3::validator_set
+convert_to_fungible_staked_sui
+ public(package) fun
+ 0x3::validator_set
+redeem_fungible_staked_sui
+ public(package) fun
+ 0x3::validator_set
request_set_commission_rate
public(package) fun
0x3::validator_set
@@ -700,6 +748,12 @@ request_add_stake_mul_coin
request_withdraw_stake
public(package) fun
0x3::sui_system_state_inner
+convert_to_fungible_staked_sui
+ public(package) fun
+ 0x3::sui_system_state_inner
+redeem_fungible_staked_sui
+ public(package) fun
+ 0x3::sui_system_state_inner
report_validator
public(package) fun
0x3::sui_system_state_inner
@@ -862,6 +916,12 @@ request_add_stake_mul_coin
request_withdraw_stake
public entry fun
0x3::sui_system
+convert_to_fungible_staked_sui
+ public fun
+ 0x3::sui_system
+redeem_fungible_staked_sui
+ public fun
+ 0x3::sui_system
request_withdraw_stake_non_entry
public fun
0x3::sui_system
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 ca9bc6e3d6d27..dbba45244911a 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: "0xc7e49487dc27703828d38516b64e1fe8841b678422efb3258c48cd1ab0f5fbc8"
+ id: "0x7f26b902d13d30b22172a17d566502252f3d5e1b2fbcb62830b0e57f628bace1"
size: 0
voting_power: 10000
- operation_cap_id: "0x037ae08f2fba1b711149b5f8502b7bc04d30caf3142f0ef0e0cffeb87f05a4ae"
+ operation_cap_id: "0x54279c3f967c7fca5839484ca0f47e7c03b3c944243ec1441026f9d8c3d174a9"
gas_price: 1000
staking_pool:
- id: "0x35b6eb200b1c83dcf097c7a1fdd55cf2ebe7e9f91348eeb61392754bf6b75505"
+ id: "0xac1b9af5c5da96a0cd0a8efd2e1112b494f7265fb1d656d568f7decb332781b0"
activation_epoch: 0
deactivation_epoch: ~
sui_balance: 20000000000000000
@@ -254,14 +254,14 @@ validators:
value: 0
pool_token_balance: 20000000000000000
exchange_rates:
- id: "0xdf7cb13037359145b99e75cd2c241c8ec54e27f3e2c3e5130d6f5330efeb9e89"
+ id: "0x3e2414a6732e6605ae258347e36f6c6c7d268af6ca3d8cc070d744a15b06ec87"
size: 1
pending_stake: 0
pending_total_sui_withdraw: 0
pending_pool_token_withdraw: 0
extra_fields:
id:
- id: "0x44d0fc1421e9f12c952de950ef4022de7f9dfa9a6c382b343ce3415465e85e0d"
+ id: "0x6b4794e72ae76f1c4680f29c8123b962b07d05128b6ea5d48f726fd09ded575e"
size: 0
commission_rate: 200
next_epoch_stake: 20000000000000000
@@ -269,27 +269,27 @@ validators:
next_epoch_commission_rate: 200
extra_fields:
id:
- id: "0xed5ac6e9315b7bfd93b9ae34abfef6f4b4fa75cbba83b95e216466a0be9c16a2"
+ id: "0xa48763deda8ed12d687c56744316bc2861e24c1ef3bccb3ee5ffb5ebc3610749"
size: 0
pending_active_validators:
contents:
- id: "0xb41cf445c30bedd43adda7389b9f26ff5761097a74aff8c6abc9ab8390bbc41d"
+ id: "0xe86894781a668264600f6c03a0451f34e5a3da852f223d404a45d253bd9aedd5"
size: 0
pending_removals: []
staking_pool_mappings:
- id: "0x6b5813504fb66e6aacd2f9ce7c84c656f770c7cbdb7b901a5dcbd620a02544aa"
+ id: "0x7e541610d3b1718495c89ff5f652ea474bb44077eb59fbcac8d329dc426fb9bd"
size: 1
inactive_validators:
- id: "0x6ad4dc51b60eb86ca8ee742c24d63752bed3dc4340ee11f4108f1d768445f67f"
+ id: "0xc721d0b70ff119f66360acff8acf1d67b686d74406527b2cfb4053b0c94ae9e4"
size: 0
validator_candidates:
- id: "0x2dceedb281425ca7eec8b2d0e65863a806cd0384a2a8eda97a88785c77d5d204"
+ id: "0x3fbdf26fe072eff58a488d6d87d58b8b0001e65bb7f8b795a03bbc00125ac8bf"
size: 0
at_risk_validators:
contents: []
extra_fields:
id:
- id: "0x064d5f61130f3938c57c13d9fa1e887a761a02fa375510f4f41fcd1e2d52997a"
+ id: "0x819e2eb9e011d08a696c9001882d0e7632da71b31415998d408bea36b10cfc5c"
size: 0
storage_fund:
total_object_storage_rebates:
@@ -306,7 +306,7 @@ parameters:
validator_low_stake_grace_period: 7
extra_fields:
id:
- id: "0x5cc2ff41ed3befe2a5e596841685eac8e584b4f14c196e41b7a63ae77ddd3c3a"
+ id: "0x6f1b019b4062b3000dea8d95af3d772ca1629d507c229a420afd9893027ae3c3"
size: 0
reference_gas_price: 1000
validator_report_records:
@@ -320,7 +320,7 @@ stake_subsidy:
stake_subsidy_decrease_rate: 1000
extra_fields:
id:
- id: "0xe8c2342b9e93ce6a55e58d422c8e3c228105287665b6c1c963535d8c6f1c7418"
+ id: "0x27e165381cd7d6926b35da3ce0cb9fa403c859722842a64829f3f0a7d74f2114"
size: 0
safe_mode: false
safe_mode_storage_rewards:
@@ -332,6 +332,5 @@ safe_mode_non_refundable_storage_fee: 0
epoch_start_timestamp_ms: 10
extra_fields:
id:
- id: "0x0055b6e98f166455d70a76c51bfa0ca92aae5fdf7636f116d6f3cb531906cbc8"
+ id: "0xfa9ad544627e85e5427fd2a4047797ad8f7d3e5455ccf37c89b5b85348e48780"
size: 0
-