Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add superfluid unbond partial amount #4107

Merged
merged 9 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#3843](https://github.com/osmosis-labs/osmosis/pull/3843) Cli support and tested SetValSet, DelValSet, UnDelValSet
* [#3810](https://github.com/osmosis-labs/osmosis/pull/3810) Allow migration of x/lockup uosmo to staking to a valset preference
* [#3966](https://github.com/osmosis-labs/osmosis/pull/3966) Add Redelegate, Withdraw cli commands and sim_msgs
* [#4107](https://github.com/osmosis-labs/osmosis/pull/4107) Add superfluid unbond partial amount

### API breaks

Expand Down
14 changes: 14 additions & 0 deletions proto/osmosis/superfluid/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ service Msg {
rpc SuperfluidUnbondLock(MsgSuperfluidUnbondLock)
returns (MsgSuperfluidUnbondLockResponse);

// Superfluid undelegate and unbond partial amount of the underlying lock.
rpc SuperfluidUndelegateAndUnbondLock(MsgSuperfluidUndelegateAndUnbondLock)
returns (MsgSuperfluidUndelegateAndUnbondLockResponse);

// Execute lockup lock and superfluid delegation in a single msg
rpc LockAndSuperfluidDelegate(MsgLockAndSuperfluidDelegate)
returns (MsgLockAndSuperfluidDelegateResponse);
Expand Down Expand Up @@ -54,6 +58,16 @@ message MsgSuperfluidUnbondLock {
}
message MsgSuperfluidUnbondLockResponse {}

message MsgSuperfluidUndelegateAndUnbondLock {
string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ];
uint64 lock_id = 2;
cosmos.base.v1beta1.Coin coin = 3 [
mattverse marked this conversation as resolved.
Show resolved Hide resolved
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
stackman27 marked this conversation as resolved.
Show resolved Hide resolved
}
message MsgSuperfluidUndelegateAndUnbondLockResponse {}

// message MsgSuperfluidRedelegate {
// string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ];
// uint64 lock_id = 2;
Expand Down
17 changes: 17 additions & 0 deletions x/superfluid/keeper/internal/events/emit.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,23 @@ func newSuperfluidUnbondLockEvent(lockId uint64) sdk.Event {
)
}

func EmitSuperfluidUndelegateAndUnbondLockEvent(ctx sdk.Context, lockId uint64) {
if ctx.EventManager() == nil {
return
}

ctx.EventManager().EmitEvents(sdk.Events{
newSuperfluidUndelegateAndUnbondLockEvent(lockId),
})
}

func newSuperfluidUndelegateAndUnbondLockEvent(lockId uint64) sdk.Event {
return sdk.NewEvent(
types.TypeEvtSuperfluidUndelegateAndUnbondLock,
sdk.NewAttribute(types.AttributeLockId, fmt.Sprintf("%d", lockId)),
stackman27 marked this conversation as resolved.
Show resolved Hide resolved
)
}

func EmitUnpoolIdEvent(ctx sdk.Context, sender string, lpShareDenom string, allExitedLockIDsSerialized []byte) {
if ctx.EventManager() == nil {
return
Expand Down
41 changes: 41 additions & 0 deletions x/superfluid/keeper/internal/events/emit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,47 @@ func (suite *SuperfluidEventsTestSuite) TestEmitSuperfluidUnbondLockEvent() {
}
}

func (suite *SuperfluidEventsTestSuite) TestEmitSuperfluidUndelegateAndUnbondLockEvent() {
testcases := map[string]struct {
ctx sdk.Context
lockID uint64
}{
"basic valid": {
ctx: suite.CreateTestContext(),
lockID: 1,
},
"context with no event manager": {
ctx: sdk.Context{},
},
}

for name, tc := range testcases {
suite.Run(name, func() {
expectedEvents := sdk.Events{
sdk.NewEvent(
types.TypeEvtSuperfluidUndelegateAndUnbondLock,
sdk.NewAttribute(types.AttributeLockId, fmt.Sprintf("%d", tc.lockID)),
),
}

hasNoEventManager := tc.ctx.EventManager() == nil

// System under test.
events.EmitSuperfluidUndelegateAndUnbondLockEvent(tc.ctx, tc.lockID)

// Assertions
if hasNoEventManager {
// If there is no event manager on context, this is a no-op.
return
}

eventManager := tc.ctx.EventManager()
actualEvents := eventManager.Events()
suite.Equal(expectedEvents, actualEvents)
})
}
}

func (suite *SuperfluidEventsTestSuite) TestEmitUnpoolIdEvent() {
testAllExitedLockIDsSerialized, _ := json.Marshal([]uint64{1})

Expand Down
13 changes: 13 additions & 0 deletions x/superfluid/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ func (server msgServer) SuperfluidUnbondLock(goCtx context.Context, msg *types.M
return &types.MsgSuperfluidUnbondLockResponse{}, err
}

// SuperfluidUndelegateAndUnbondLock undelegates and unbonds partial amount from a lock.
func (server msgServer) SuperfluidUndelegateAndUnbondLock(goCtx context.Context, msg *types.MsgSuperfluidUndelegateAndUnbondLock) (
*types.MsgSuperfluidUndelegateAndUnbondLockResponse, error,
) {
ctx := sdk.UnwrapSDKContext(goCtx)

_, err := server.keeper.SuperfluidUndelegateAndUnbondLock(ctx, msg.LockId, msg.Sender, msg.Coin.Amount)
if err == nil {
events.EmitSuperfluidUndelegateAndUnbondLockEvent(ctx, msg.LockId)
}
return &types.MsgSuperfluidUndelegateAndUnbondLockResponse{}, err
}

// LockAndSuperfluidDelegate locks and superfluid delegates given tokens in a single message.
// This method consists of multiple messages, `LockTokens` from the lockup module msg server, and
// `SuperfluidDelegate` from the superfluid module msg server.
Expand Down
47 changes: 47 additions & 0 deletions x/superfluid/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,53 @@ func (suite *KeeperTestSuite) TestMsgSuperfluidUnbondLock() {
}
}

func (suite *KeeperTestSuite) TestMsgSuperfluidUndelegateAndUnbondLock() {
type param struct {
coinsToLock sdk.Coins
amountToUnlock sdk.Coin
lockOwner sdk.AccAddress
duration time.Duration
coinsInOwnerAddress sdk.Coins
}

tests := []struct {
name string
param param
expectPass bool
}{
{
name: "superfluid unbond lock that is not superfluid lockup",
param: param{
coinsToLock: sdk.Coins{sdk.NewInt64Coin("stake", 10)},
amountToUnlock: sdk.NewInt64Coin("stake", 10),
lockOwner: sdk.AccAddress([]byte("addr1---------------")),
duration: time.Second,
coinsInOwnerAddress: sdk.Coins{sdk.NewInt64Coin("stake", 10)},
},
expectPass: false,
},
}

for _, test := range tests {
suite.SetupTest()

suite.FundAcc(test.param.lockOwner, test.param.coinsInOwnerAddress)

lockupMsgServer := lockupkeeper.NewMsgServerImpl(suite.App.LockupKeeper)
c := sdk.WrapSDKContext(suite.Ctx)
resp, err := lockupMsgServer.LockTokens(c, lockuptypes.NewMsgLockTokens(test.param.lockOwner, test.param.duration, test.param.coinsToLock))

msgServer := keeper.NewMsgServerImpl(suite.App.SuperfluidKeeper)
_, err = msgServer.SuperfluidUndelegateAndUnbondLock(c, types.NewMsgSuperfluidUndelegateAndUnbondLock(test.param.lockOwner, resp.ID, test.param.amountToUnlock))

if test.expectPass {
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
}
}
}

func (suite *KeeperTestSuite) TestMsgLockAndSuperfluidDelegate() {
type param struct {
coinsToLock sdk.Coins
Expand Down
85 changes: 79 additions & 6 deletions x/superfluid/keeper/stake.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,23 +269,96 @@ func (k Keeper) SuperfluidUndelegate(ctx sdk.Context, sender string, lockID uint
// SuperfluidUnbondLock unbonds the lock that has been used for superfluid staking.
// This method would return an error if the underlying lock is not superfluid undelegating.
func (k Keeper) SuperfluidUnbondLock(ctx sdk.Context, underlyingLockId uint64, sender string) error {
_, err := k.unbondLock(ctx, underlyingLockId, sender, sdk.Coins{})
return err
stackman27 marked this conversation as resolved.
Show resolved Hide resolved
}

// SuperfluidUndelegateAndUnbondLock unbonds partial or full amount from the
mattverse marked this conversation as resolved.
Show resolved Hide resolved
// underlying lock that has been used for superfluid staking.
// This method returns the lock id, same lock id if unlock amount is equal to the
// underlying lock amount. Otherwise it returns the newly created lock id.
func (k Keeper) SuperfluidUndelegateAndUnbondLock(ctx sdk.Context, lockID uint64, sender string, amount sdk.Int) (uint64, error) {
mattverse marked this conversation as resolved.
Show resolved Hide resolved
lock, err := k.lk.GetLockByID(ctx, lockID)
if err != nil {
return 0, err
}

coins := sdk.Coins{sdk.NewCoin(lock.Coins[0].Denom, amount)}
if coins[0].IsZero() {
return 0, fmt.Errorf("amount to unlock must be greater than 0")
}
if lock.Coins[0].IsLT(coins[0]) {
return 0, fmt.Errorf("requested amount to unlock exceeds locked tokens")
}

// get intermediary account before connection is deleted in SuperfluidUndelegate
intermediaryAcc, found := k.GetIntermediaryAccountFromLockId(ctx, lockID)
if !found {
return 0, types.ErrNotSuperfluidUsedLockup
}

// undelegate all
err = k.SuperfluidUndelegate(ctx, sender, lockID)
if err != nil {
return 0, err
}

// unbond partial or full locked amount
newLockID, err := k.unbondLock(ctx, lockID, sender, coins)
if err != nil {
return 0, err
}

// check new lock id, return if unbond amount = locked amount
if lock.Coins[0].IsEqual(coins[0]) {
if newLockID != lockID {
panic(fmt.Errorf("expected new lock id %v to = lock id %v", newLockID, lockID))
}
return lock.ID, nil
} else {
if newLockID == lockID {
panic(fmt.Errorf("expected new lock id %v to != lock id %v", newLockID, lockID))
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we do not need this check this looks simply double checking the returned value from unbondLock, would like to hear other reviewer's opinions on this


// delete synthetic unlocking lock created in the last step of SuperfluidUndelegate
synthdenom := unstakingSyntheticDenom(lock.Coins[0].Denom, intermediaryAcc.ValAddr)
err = k.lk.DeleteSyntheticLockup(ctx, lockID, synthdenom)
if err != nil {
return 0, err
}

// re-delegate remainder
mattverse marked this conversation as resolved.
Show resolved Hide resolved
err = k.SuperfluidDelegate(ctx, sender, lockID, intermediaryAcc.ValAddr)
mattverse marked this conversation as resolved.
Show resolved Hide resolved
stackman27 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return 0, err
}

// Create synthetic unlocking lock for newLockID
err = k.createSyntheticLockup(ctx, newLockID, intermediaryAcc, unlockingStatus)
stackman27 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return 0, err
}
return newLockID, nil
}

func (k Keeper) unbondLock(ctx sdk.Context, underlyingLockId uint64, sender string, coins sdk.Coins) (uint64, error) {
mattverse marked this conversation as resolved.
Show resolved Hide resolved
lock, err := k.lk.GetLockByID(ctx, underlyingLockId)
if err != nil {
return err
return 0, err
}
err = k.validateLockForSF(ctx, lock, sender)
if err != nil {
return err
return 0, err
}
synthLocks := k.lk.GetAllSyntheticLockupsByLockup(ctx, underlyingLockId)
if len(synthLocks) != 1 {
return types.ErrNotSuperfluidUsedLockup
return 0, types.ErrNotSuperfluidUsedLockup
}
if !synthLocks[0].IsUnlocking() {
return types.ErrBondingLockupNotSupported
return 0, types.ErrBondingLockupNotSupported
}
_, err = k.lk.BeginForceUnlock(ctx, underlyingLockId, sdk.Coins{})
return err
return k.lk.BeginForceUnlock(ctx, underlyingLockId, coins)
}

// alreadySuperfluidStaking returns true if underlying lock used in superfluid staking.
Expand Down
Loading