From 09ca1176cf4c894ab0a191ffa26a8bae9c7d7b59 Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Fri, 24 May 2024 17:00:36 -0400 Subject: [PATCH 1/8] merge staked sui SIP --- sips/sip-merge-staked-sui.md | 136 +++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 sips/sip-merge-staked-sui.md diff --git a/sips/sip-merge-staked-sui.md b/sips/sip-merge-staked-sui.md new file mode 100644 index 0000000..10e77c4 --- /dev/null +++ b/sips/sip-merge-staked-sui.md @@ -0,0 +1,136 @@ +| SIP-Number | | +| ---: | :--- | +| Title | Merge StakedSui objects across different activation epochs | +| Description | Allow StakedSui objects to be transformed into an Lst object, which is epoch independent. | +| Author | ripleys <0xripleys@solend.fi> | +| Editor | | +| Type | Standard | +| Category | Framework | +| Created | 2024-05-23 | +| Comments-URI | | +| Status | | +| Requires | | + +## Abstract + +Allow StakedSui objects to be transformed into an Lst object, which are fungible and epoch independent. This improves efficiency for anyone working with a large amount of StakedSui objects, primarily liquid staking derivative contracts. + +## Motivation + +Currently, StakedSui objects are tied to a specific activation epoch. If two StakedSui objects are from different activation epochs, they cannot be merged together. + +This is annoying for anyone working with a large amount of StakedSui objects, primarily liquid staking derivative (LST) contracts. Currently, any LST contract now has to manage O(n) StakedSui objects _per_ validator, where n is the number of epochs since genesis. See [the vSui implementation](https://github.com/Sui-Volo/volo-liquid-staking-contracts/blob/main/liquid_staking/sources/validator_set.move#L45) for reference. This is inefficient, as any LST unstake operation can now touch a large amount of StakedSui objects. + +## Specification + +[staking_pool.move](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-system/sources/staking_pool.move) + +```move + + /// An Lst object with a value of 1 corresponds to 1 pool token in the Staking pool. + /// This can be a Coin! See rationale below. + public struct Lst has key, store { + id: UID, + /// ID of the staking pool we are staking with. + pool_id: ID, + /// The pool token amount. + value: u64, + } + + + /// Dynamic field on the StakingPool Struct. + public struct LstData has key, store { + id: UID, + /// lst supply + lst_supply: u64, + /// principal balance. Rewards are not stored here, they are withdrawn from the StakingPool's reward pool. + principal: Balance, + } + + // === dynamic field keys === + public struct LstDataKey has copy, store, drop {} + + // === Public getters === + public fun lst_value(lst: &Lst): u64 { + lst.value + } + + public fun lst_pool_id(lst: &Lst): ID { + lst.pool_id + } + + public fun lst_to_sui_amount(pool: &StakingPool, lst_amount: u64): u64; + public fun sui_to_lst_amount(pool: &StakingPool, sui_amount: u64): u64; + + /// Burn an Lst object to obtain the underlying SUI. + public(package) fun redeem_lst(pool: &mut StakingPool, lst: Lst, ctx: &TxContext) : Balance; + + /// Convert the given staked SUI to an Lst object + public(package) fun mint_lst(pool: &mut StakingPool, staked_sui: StakedSui, ctx: &mut TxContext) : Lst; + +``` + +[validator.move](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-system/sources/validator.move) + +```move + public(package) fun mint_lst(self: &mut Validator, staked_sui: StakedSui, ctx: &TxContext) : Lst; + public(package) fun redeem_lst(self: &mut Validator, lst: Lst, ctx: &TxContext) : Balance; +``` + +[validator_set.move](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-system/sources/validator_set.move) + +```move + public(package) fun mint_lst(self: &mut ValidatorSet, staked_sui: StakedSui, ctx: &TxContext) : Lst; + public(package) fun redeem_lst(self: &mut ValidatorSet, lst: Lst, ctx: &TxContext) : Balance; +``` + +[sui_system_state_inner.move](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-system/sources/sui_system_state_inner.move) + +```move + public(package) fun mint_lst(self: &mut SuiSystemStateInnerV2, staked_sui: StakedSui, ctx: &TxContext) : Lst; + public(package) fun redeem_lst(self: &mut SuiSystemStateInnerV2, lst: Lst, ctx: &TxContext) : Balance; +``` + +[sui_system.move](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-system/sources/sui_system.move) + +```move + public(package) fun mint_lst(self: &mut SuiSystemState, staked_sui: StakedSui, ctx: &TxContext) : Lst; + public(package) fun redeem_lst(self: &mut SuiSystemState, lst: Lst, ctx: &TxContext) : Balance; +``` + + +## Rationale + +#### Instead of the Lst object, why don't we just mint a new Coin? + +This is possible and I really like this idea. The main benefit of doing this is that it would immediately enable single-validator LSTs for the entire validator set, which would be pretty cool. + +The tricky part would be managing a unique Coin type + metadata per StakingPool. Some notes: +- We would need a one time witness per StakingPool to create the Coin type. This can be passed into the StakingPool on instantiation. +- For existing StakingPool objects, there would need to be a function to create this one time witness and pass it to the StakingPool. I strongly feel that this function should be able to be called permissionlessly. The effectiveness of this SIP is greatly diminished if each validator has to opt into this feature. I don't think this can be abused, as the StakingPool would be calling the `coin::create_currency` function anyways, so the CoinMetadata is completely under our control. + +The one caveat to this approach is that there is a warmup period (up to one epoch) before a newly created StakedSui object can be converted into an LST coin. UX-wise, this isn't great. However I don't think this is a dealbreaker, as existing StakedSui objects can be immediately converted into these tokens, and if LST minting is required, that can be done as a separate contract. + +#### Why can't the StakedSui principal be stored directly on the Lst object? Why is a dynamic field necessary? + +The first reason is that I really like the Coin approach, which would require a dynamic field to hold the principal anyways. + +The second reason is that splitting Lst objects now becomes potentially expoitable. Eg say you have an Lst object with value 1.2e10, and principal of 1e10. If you want to split the Lst object into 3 equal parts, where should the extra MIST of principal go? All answers feel unsatisfactory to me. + +## Backwards Compatibility + +No issues with backwards compatibility. This SIP only adds features, and does not change existing ones. + +## Reference Implementation + +See [here](https://github.com/0xripleys/sui/pull/1/files) for a reference implementation of how the staking pool code would look like. + +Note that this is just a Draft and is not production ready. + +## Security Considerations + +The potential damage of a bug in staking_pool.move is higher as we now store a nontrivial amount of Sui in this module. Also, we definitely need to be careful with the math around redeem_lst. Both can be mitigated with an audit. + +## Copyright + +TODO not sure what to put here yet. But please don't steal my work lol From 5a2678905d23bf6a740db92ef65794bbf092079b Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Fri, 24 May 2024 17:03:40 -0400 Subject: [PATCH 2/8] add join, split functions --- sips/sip-merge-staked-sui.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sips/sip-merge-staked-sui.md b/sips/sip-merge-staked-sui.md index 10e77c4..1d3023c 100644 --- a/sips/sip-merge-staked-sui.md +++ b/sips/sip-merge-staked-sui.md @@ -62,6 +62,9 @@ This is annoying for anyone working with a large amount of StakedSui objects, pr public fun lst_to_sui_amount(pool: &StakingPool, lst_amount: u64): u64; public fun sui_to_lst_amount(pool: &StakingPool, sui_amount: u64): u64; + public fun join_lst(self: &mut Lst, other: Lst); + public fun split_lst(self: &mut Lst, amount: u64): Lst; + /// Burn an Lst object to obtain the underlying SUI. public(package) fun redeem_lst(pool: &mut StakingPool, lst: Lst, ctx: &TxContext) : Balance; From cf5683c88ff991b0087ebfb9682101c7b2456cde Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Fri, 24 May 2024 17:10:30 -0400 Subject: [PATCH 3/8] change header size --- sips/sip-merge-staked-sui.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sips/sip-merge-staked-sui.md b/sips/sip-merge-staked-sui.md index 1d3023c..f4adbb2 100644 --- a/sips/sip-merge-staked-sui.md +++ b/sips/sip-merge-staked-sui.md @@ -104,7 +104,7 @@ This is annoying for anyone working with a large amount of StakedSui objects, pr ## Rationale -#### Instead of the Lst object, why don't we just mint a new Coin? +### Instead of the Lst object, why don't we just mint a new Coin? This is possible and I really like this idea. The main benefit of doing this is that it would immediately enable single-validator LSTs for the entire validator set, which would be pretty cool. @@ -114,7 +114,7 @@ The tricky part would be managing a unique Coin type + metadata per StakingPool. The one caveat to this approach is that there is a warmup period (up to one epoch) before a newly created StakedSui object can be converted into an LST coin. UX-wise, this isn't great. However I don't think this is a dealbreaker, as existing StakedSui objects can be immediately converted into these tokens, and if LST minting is required, that can be done as a separate contract. -#### Why can't the StakedSui principal be stored directly on the Lst object? Why is a dynamic field necessary? +### Why can't the StakedSui principal be stored directly on the Lst object? Why is a dynamic field necessary? The first reason is that I really like the Coin approach, which would require a dynamic field to hold the principal anyways. From 5c9adb5a7cb715bb9e9ef6056fdc1759acf77077 Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Fri, 31 May 2024 16:04:02 -0400 Subject: [PATCH 4/8] change the name to be less dumb --- sips/sip-merge-staked-sui.md | 76 ++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 43 deletions(-) diff --git a/sips/sip-merge-staked-sui.md b/sips/sip-merge-staked-sui.md index f4adbb2..e904c16 100644 --- a/sips/sip-merge-staked-sui.md +++ b/sips/sip-merge-staked-sui.md @@ -1,7 +1,7 @@ | SIP-Number | | | ---: | :--- | -| Title | Merge StakedSui objects across different activation epochs | -| Description | Allow StakedSui objects to be transformed into an Lst object, which is epoch independent. | +| Title | Fungible StakedSui objects | +| Description | Allow StakedSui objects to be transformed into a FungibleStake object, which is epoch independent. | | Author | ripleys <0xripleys@solend.fi> | | Editor | | | Type | Standard | @@ -13,7 +13,7 @@ ## Abstract -Allow StakedSui objects to be transformed into an Lst object, which are fungible and epoch independent. This improves efficiency for anyone working with a large amount of StakedSui objects, primarily liquid staking derivative contracts. +Allow StakedSui objects to be transformed into an Fungible object, which are fungible and epoch independent. This improves efficiency for anyone working with a large amount of StakedSui objects, primarily liquid staking derivative contracts. ## Motivation @@ -27,9 +27,9 @@ This is annoying for anyone working with a large amount of StakedSui objects, pr ```move - /// An Lst object with a value of 1 corresponds to 1 pool token in the Staking pool. - /// This can be a Coin! See rationale below. - public struct Lst has key, store { + /// An FungibleStake object with a value of 1 corresponds to 1 pool token in the Staking pool. + /// This can be a Coin! See the Rationale below. + public struct FungibleStake has key, store { id: UID, /// ID of the staking pool we are staking with. pool_id: ID, @@ -39,72 +39,66 @@ This is annoying for anyone working with a large amount of StakedSui objects, pr /// Dynamic field on the StakingPool Struct. - public struct LstData has key, store { + public struct FungibleStakeData has key, store { id: UID, - /// lst supply - lst_supply: u64, + /// fungible_stake supply. sum of values across all FungibleStake objects in the pool. + fungible_stake_supply: u64, /// principal balance. Rewards are not stored here, they are withdrawn from the StakingPool's reward pool. principal: Balance, } // === dynamic field keys === - public struct LstDataKey has copy, store, drop {} + public struct FungibleStakeDataKey has copy, store, drop {} // === Public getters === - public fun lst_value(lst: &Lst): u64 { - lst.value + public fun fungible_stake_value(fungible_stake: &FungibleStake): u64 { + fungible_stake.value } - public fun lst_pool_id(lst: &Lst): ID { - lst.pool_id + public fun fungible_stake_pool_id(fungible_stake: &FungibleStake): ID { + fungible_stake.pool_id } - public fun lst_to_sui_amount(pool: &StakingPool, lst_amount: u64): u64; - public fun sui_to_lst_amount(pool: &StakingPool, sui_amount: u64): u64; + public fun fungible_stake_to_sui_amount(pool: &StakingPool, fungible_stake_amount: u64): u64; + public fun sui_to_fungible_stake_amount(pool: &StakingPool, sui_amount: u64): u64; - public fun join_lst(self: &mut Lst, other: Lst); - public fun split_lst(self: &mut Lst, amount: u64): Lst; + public fun join_fungible_stake(self: &mut FungibleStake, other: FungibleStake); + public fun split_fungible_stake(self: &mut FungibleStake, amount: u64): FungibleStake; - /// Burn an Lst object to obtain the underlying SUI. - public(package) fun redeem_lst(pool: &mut StakingPool, lst: Lst, ctx: &TxContext) : Balance; + /// Burn an FungibleStake object to obtain the underlying SUI. + public(package) fun redeem_fungible_stake(pool: &mut StakingPool, fungible_stake: FungibleStake, ctx: &TxContext) : Balance; - /// Convert the given staked SUI to an Lst object - public(package) fun mint_lst(pool: &mut StakingPool, staked_sui: StakedSui, ctx: &mut TxContext) : Lst; + /// Convert the given staked SUI to an FungibleStake object + public(package) fun convert_to_fungible_stake(pool: &mut StakingPool, staked_sui: StakedSui, ctx: &mut TxContext) : FungibleStake; ``` [validator.move](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-system/sources/validator.move) ```move - public(package) fun mint_lst(self: &mut Validator, staked_sui: StakedSui, ctx: &TxContext) : Lst; - public(package) fun redeem_lst(self: &mut Validator, lst: Lst, ctx: &TxContext) : Balance; + public(package) fun convert_to_fungible_stake(self: &mut Validator, staked_sui: StakedSui, ctx: &TxContext) : FungibleStake; + public(package) fun redeem_fungible_stake(self: &mut Validator, fungible_stake: FungibleStake, ctx: &TxContext) : Balance; ``` [validator_set.move](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-system/sources/validator_set.move) ```move - public(package) fun mint_lst(self: &mut ValidatorSet, staked_sui: StakedSui, ctx: &TxContext) : Lst; - public(package) fun redeem_lst(self: &mut ValidatorSet, lst: Lst, ctx: &TxContext) : Balance; + public(package) fun convert_to_fungible_stake(self: &mut ValidatorSet, staked_sui: StakedSui, ctx: &TxContext) : FungibleStake; + public(package) fun redeem_fungible_stake(self: &mut ValidatorSet, fungible_stake: FungibleStake, ctx: &TxContext) : Balance; ``` [sui_system_state_inner.move](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-system/sources/sui_system_state_inner.move) ```move - public(package) fun mint_lst(self: &mut SuiSystemStateInnerV2, staked_sui: StakedSui, ctx: &TxContext) : Lst; - public(package) fun redeem_lst(self: &mut SuiSystemStateInnerV2, lst: Lst, ctx: &TxContext) : Balance; -``` - -[sui_system.move](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-system/sources/sui_system.move) - -```move - public(package) fun mint_lst(self: &mut SuiSystemState, staked_sui: StakedSui, ctx: &TxContext) : Lst; - public(package) fun redeem_lst(self: &mut SuiSystemState, lst: Lst, ctx: &TxContext) : Balance; + public(package) fun convert_to_fungible_stake(self: &mut SuiSystemStateInnerV2, staked_sui: StakedSui, ctx: &TxContext) : FungibleStake; + public(package) fun redeem_fungible_stake(self: &mut SuiSystemStateInnerV2, fungible_stake: FungibleStake, ctx: &TxContext) : Balance; ``` +It's possible I missed some getter functions, but I think this is the gist of it. ## Rationale -### Instead of the Lst object, why don't we just mint a new Coin? +### Instead of the FungibleStake object, why don't we just mint a new "LST" Coin per StakingPool? This is possible and I really like this idea. The main benefit of doing this is that it would immediately enable single-validator LSTs for the entire validator set, which would be pretty cool. @@ -114,11 +108,11 @@ The tricky part would be managing a unique Coin type + metadata per StakingPool. The one caveat to this approach is that there is a warmup period (up to one epoch) before a newly created StakedSui object can be converted into an LST coin. UX-wise, this isn't great. However I don't think this is a dealbreaker, as existing StakedSui objects can be immediately converted into these tokens, and if LST minting is required, that can be done as a separate contract. -### Why can't the StakedSui principal be stored directly on the Lst object? Why is a dynamic field necessary? +### Why can't the StakedSui principal be stored directly on the FungibleStake object? Why is a dynamic field necessary? The first reason is that I really like the Coin approach, which would require a dynamic field to hold the principal anyways. -The second reason is that splitting Lst objects now becomes potentially expoitable. Eg say you have an Lst object with value 1.2e10, and principal of 1e10. If you want to split the Lst object into 3 equal parts, where should the extra MIST of principal go? All answers feel unsatisfactory to me. +The second reason is that splitting FungibleStake objects now becomes potentially expoitable. Eg say you have an FungibleStake object with value 1.2e10, and principal of 1e10. If you want to split the FungibleStake object into 3 equal parts, where should the extra MIST of principal go? All answers feel unsatisfactory to me. ## Backwards Compatibility @@ -128,12 +122,8 @@ No issues with backwards compatibility. This SIP only adds features, and does no See [here](https://github.com/0xripleys/sui/pull/1/files) for a reference implementation of how the staking pool code would look like. -Note that this is just a Draft and is not production ready. +Note that this implementation just a Draft and is not production ready. ## Security Considerations The potential damage of a bug in staking_pool.move is higher as we now store a nontrivial amount of Sui in this module. Also, we definitely need to be careful with the math around redeem_lst. Both can be mitigated with an audit. - -## Copyright - -TODO not sure what to put here yet. But please don't steal my work lol From 4695b24fa28aeef606862be92320a10ba87cbe27 Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Fri, 31 May 2024 16:13:06 -0400 Subject: [PATCH 5/8] more notes --- sips/sip-merge-staked-sui.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sips/sip-merge-staked-sui.md b/sips/sip-merge-staked-sui.md index e904c16..6666e00 100644 --- a/sips/sip-merge-staked-sui.md +++ b/sips/sip-merge-staked-sui.md @@ -114,6 +114,10 @@ The first reason is that I really like the Coin approach, which would require a The second reason is that splitting FungibleStake objects now becomes potentially expoitable. Eg say you have an FungibleStake object with value 1.2e10, and principal of 1e10. If you want to split the FungibleStake object into 3 equal parts, where should the extra MIST of principal go? All answers feel unsatisfactory to me. +### Misc +- `redeem_fungible_stake` could return an StakedSui object instead. I need to double check the math here to make sure it's safe. I think just returning a StakedSui object that's activated in the current epoch is fine (ie all principal, no rewards). +- If we want these interfaces to be compatible with any future unbonding period implementation, we could return a LockedSui object instead of the Sui in `redeem_fungible_stake`. Or, just return the StakedSui object like I mentioned above. + ## Backwards Compatibility No issues with backwards compatibility. This SIP only adds features, and does not change existing ones. From 307057380d9e7ea99aec7247027d824828d98538 Mon Sep 17 00:00:00 2001 From: amogh-sui <152476631+amogh-sui@users.noreply.github.com> Date: Fri, 31 May 2024 17:29:44 -0400 Subject: [PATCH 6/8] Changed status of SIP 31 to "Draft" --- sips/sip-merge-staked-sui.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sips/sip-merge-staked-sui.md b/sips/sip-merge-staked-sui.md index 6666e00..a3983bb 100644 --- a/sips/sip-merge-staked-sui.md +++ b/sips/sip-merge-staked-sui.md @@ -1,15 +1,15 @@ -| SIP-Number | | +| SIP-Number | 31 | | ---: | :--- | | Title | Fungible StakedSui objects | | Description | Allow StakedSui objects to be transformed into a FungibleStake object, which is epoch independent. | | Author | ripleys <0xripleys@solend.fi> | -| Editor | | +| Editor | Amogh Gupta | | Type | Standard | | Category | Framework | | Created | 2024-05-23 | -| Comments-URI | | -| Status | | -| Requires | | +| Comments-URI | https://sips.sui.io/comments-31 | +| Status | Draft | +| Requires | NA | ## Abstract From 667330066158a4146db8441cd06fa396ccb46d44 Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Sun, 23 Jun 2024 23:48:32 -0400 Subject: [PATCH 7/8] rename FungibleStake --- sips/sip-merge-staked-sui.md | 46 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/sips/sip-merge-staked-sui.md b/sips/sip-merge-staked-sui.md index a3983bb..20f9a50 100644 --- a/sips/sip-merge-staked-sui.md +++ b/sips/sip-merge-staked-sui.md @@ -1,7 +1,7 @@ | SIP-Number | 31 | | ---: | :--- | | Title | Fungible StakedSui objects | -| Description | Allow StakedSui objects to be transformed into a FungibleStake object, which is epoch independent. | +| Description | Allow StakedSui objects to be transformed into a FungibleStakedSui object, which is epoch independent. | | Author | ripleys <0xripleys@solend.fi> | | Editor | Amogh Gupta | | Type | Standard | @@ -27,9 +27,9 @@ This is annoying for anyone working with a large amount of StakedSui objects, pr ```move - /// An FungibleStake object with a value of 1 corresponds to 1 pool token in the Staking pool. + /// An FungibleStakedSui object with a value of 1 corresponds to 1 pool token in the Staking pool. /// This can be a Coin! See the Rationale below. - public struct FungibleStake has key, store { + public struct FungibleStakedSui has key, store { id: UID, /// ID of the staking pool we are staking with. pool_id: ID, @@ -39,66 +39,66 @@ This is annoying for anyone working with a large amount of StakedSui objects, pr /// Dynamic field on the StakingPool Struct. - public struct FungibleStakeData has key, store { + public struct FungibleStakedSuiData has key, store { id: UID, - /// fungible_stake supply. sum of values across all FungibleStake objects in the pool. + /// fungible_stake supply. sum of values across all FungibleStakedSui objects in the pool. fungible_stake_supply: u64, /// principal balance. Rewards are not stored here, they are withdrawn from the StakingPool's reward pool. principal: Balance, } // === dynamic field keys === - public struct FungibleStakeDataKey has copy, store, drop {} + public struct FungibleStakedSuiDataKey has copy, store, drop {} // === Public getters === - public fun fungible_stake_value(fungible_stake: &FungibleStake): u64 { + public fun fungible_stake_value(fungible_stake: &FungibleStakedSui): u64 { fungible_stake.value } - public fun fungible_stake_pool_id(fungible_stake: &FungibleStake): ID { + public fun fungible_stake_pool_id(fungible_stake: &FungibleStakedSui): ID { fungible_stake.pool_id } public fun fungible_stake_to_sui_amount(pool: &StakingPool, fungible_stake_amount: u64): u64; public fun sui_to_fungible_stake_amount(pool: &StakingPool, sui_amount: u64): u64; - public fun join_fungible_stake(self: &mut FungibleStake, other: FungibleStake); - public fun split_fungible_stake(self: &mut FungibleStake, amount: u64): FungibleStake; + public fun join_fungible_stake(self: &mut FungibleStakedSui, other: FungibleStakedSui); + public fun split_fungible_stake(self: &mut FungibleStakedSui, amount: u64): FungibleStakedSui; - /// Burn an FungibleStake object to obtain the underlying SUI. - public(package) fun redeem_fungible_stake(pool: &mut StakingPool, fungible_stake: FungibleStake, ctx: &TxContext) : Balance; + /// Burn an FungibleStakedSui object to obtain the underlying SUI. + public(package) fun redeem_fungible_stake(pool: &mut StakingPool, fungible_stake: FungibleStakedSui, ctx: &TxContext) : Balance; - /// Convert the given staked SUI to an FungibleStake object - public(package) fun convert_to_fungible_stake(pool: &mut StakingPool, staked_sui: StakedSui, ctx: &mut TxContext) : FungibleStake; + /// Convert the given staked SUI to an FungibleStakedSui object + public(package) fun convert_to_fungible_stake(pool: &mut StakingPool, staked_sui: StakedSui, ctx: &mut TxContext) : FungibleStakedSui; ``` [validator.move](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-system/sources/validator.move) ```move - public(package) fun convert_to_fungible_stake(self: &mut Validator, staked_sui: StakedSui, ctx: &TxContext) : FungibleStake; - public(package) fun redeem_fungible_stake(self: &mut Validator, fungible_stake: FungibleStake, ctx: &TxContext) : Balance; + public(package) fun convert_to_fungible_stake(self: &mut Validator, staked_sui: StakedSui, ctx: &TxContext) : FungibleStakedSui; + public(package) fun redeem_fungible_stake(self: &mut Validator, fungible_stake: FungibleStakedSui, ctx: &TxContext) : Balance; ``` [validator_set.move](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-system/sources/validator_set.move) ```move - public(package) fun convert_to_fungible_stake(self: &mut ValidatorSet, staked_sui: StakedSui, ctx: &TxContext) : FungibleStake; - public(package) fun redeem_fungible_stake(self: &mut ValidatorSet, fungible_stake: FungibleStake, ctx: &TxContext) : Balance; + public(package) fun convert_to_fungible_stake(self: &mut ValidatorSet, staked_sui: StakedSui, ctx: &TxContext) : FungibleStakedSui; + public(package) fun redeem_fungible_stake(self: &mut ValidatorSet, fungible_stake: FungibleStakedSui, ctx: &TxContext) : Balance; ``` [sui_system_state_inner.move](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-system/sources/sui_system_state_inner.move) ```move - public(package) fun convert_to_fungible_stake(self: &mut SuiSystemStateInnerV2, staked_sui: StakedSui, ctx: &TxContext) : FungibleStake; - public(package) fun redeem_fungible_stake(self: &mut SuiSystemStateInnerV2, fungible_stake: FungibleStake, ctx: &TxContext) : Balance; + public(package) fun convert_to_fungible_stake(self: &mut SuiSystemStateInnerV2, staked_sui: StakedSui, ctx: &TxContext) : FungibleStakedSui; + public(package) fun redeem_fungible_stake(self: &mut SuiSystemStateInnerV2, fungible_stake: FungibleStakedSui, ctx: &TxContext) : Balance; ``` It's possible I missed some getter functions, but I think this is the gist of it. ## Rationale -### Instead of the FungibleStake object, why don't we just mint a new "LST" Coin per StakingPool? +### Instead of the FungibleStakedSui object, why don't we just mint a new "LST" Coin per StakingPool? This is possible and I really like this idea. The main benefit of doing this is that it would immediately enable single-validator LSTs for the entire validator set, which would be pretty cool. @@ -108,11 +108,11 @@ The tricky part would be managing a unique Coin type + metadata per StakingPool. The one caveat to this approach is that there is a warmup period (up to one epoch) before a newly created StakedSui object can be converted into an LST coin. UX-wise, this isn't great. However I don't think this is a dealbreaker, as existing StakedSui objects can be immediately converted into these tokens, and if LST minting is required, that can be done as a separate contract. -### Why can't the StakedSui principal be stored directly on the FungibleStake object? Why is a dynamic field necessary? +### Why can't the StakedSui principal be stored directly on the FungibleStakedSui object? Why is a dynamic field necessary? The first reason is that I really like the Coin approach, which would require a dynamic field to hold the principal anyways. -The second reason is that splitting FungibleStake objects now becomes potentially expoitable. Eg say you have an FungibleStake object with value 1.2e10, and principal of 1e10. If you want to split the FungibleStake object into 3 equal parts, where should the extra MIST of principal go? All answers feel unsatisfactory to me. +The second reason is that splitting FungibleStakedSui objects now becomes potentially expoitable. Eg say you have an FungibleStakedSui object with value 1.2e10, and principal of 1e10. If you want to split the FungibleStakedSui object into 3 equal parts, where should the extra MIST of principal go? All answers feel unsatisfactory to me. ### Misc - `redeem_fungible_stake` could return an StakedSui object instead. I need to double check the math here to make sure it's safe. I think just returning a StakedSui object that's activated in the current epoch is fine (ie all principal, no rewards). From 5383cccdbb339bfbe841c858c8913bf83204ec22 Mon Sep 17 00:00:00 2001 From: amogh-sui <152476631+amogh-sui@users.noreply.github.com> Date: Mon, 24 Jun 2024 15:54:56 -0400 Subject: [PATCH 8/8] Changed status to "Review" --- sips/sip-merge-staked-sui.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sips/sip-merge-staked-sui.md b/sips/sip-merge-staked-sui.md index 20f9a50..c33c831 100644 --- a/sips/sip-merge-staked-sui.md +++ b/sips/sip-merge-staked-sui.md @@ -8,7 +8,7 @@ | Category | Framework | | Created | 2024-05-23 | | Comments-URI | https://sips.sui.io/comments-31 | -| Status | Draft | +| Status | Review | | Requires | NA | ## Abstract