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

[ValSet-Pref] Allow migration of x/lockup uosmo to staking to a valset preference #3810

Merged
merged 5 commits into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from all 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 app/keepers/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers(
appKeepers.GetSubspace(valsetpreftypes.ModuleName),
appKeepers.StakingKeeper,
appKeepers.DistrKeeper,
appKeepers.LockupKeeper,
)

appKeepers.ValidatorSetPreferenceKeeper = &validatorSetPreferenceKeeper
Expand Down
17 changes: 17 additions & 0 deletions proto/osmosis/valset-pref/v1beta1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ service Msg {
// validator-set.
rpc WithdrawDelegationRewards(MsgWithdrawDelegationRewards)
returns (MsgWithdrawDelegationRewardsResponse);

// DelegateBondedTokens allows users to break the lockup bond and delegate
// osmo tokens to a predefined validator-set.
rpc DelegateBondedTokens(MsgDelegateBondedTokens)
returns (MsgDelegateBondedTokensResponse);
}

// MsgCreateValidatorSetPreference is a list that holds validator-set.
Expand Down Expand Up @@ -107,3 +112,15 @@ message MsgWithdrawDelegationRewards {
}

message MsgWithdrawDelegationRewardsResponse {}

// MsgDelegateBondedTokens breaks bonded lockup (by ID) of osmo, of
// length <= 2 weeks and takes all that osmo and delegates according to
// delegator's current validator set preference.
message MsgDelegateBondedTokens {
// delegator is the user who is trying to force unbond osmo and delegate.
string delegator = 1 [ (gogoproto.moretags) = "yaml:\"delegator\"" ];
// lockup id of osmo in the pool
uint64 lockID = 2;
}

message MsgDelegateBondedTokensResponse {}
11 changes: 11 additions & 0 deletions x/valset-pref/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package keeper

import (
sdk "github.com/cosmos/cosmos-sdk/types"

lockuptypes "github.com/osmosis-labs/osmosis/v14/x/lockup/types"
)

func (k Keeper) ValidateLockForForceUnlock(ctx sdk.Context, lockID uint64, delegatorAddr string) (*lockuptypes.PeriodLock, sdk.Int, error) {
return k.validateLockForForceUnlock(ctx, lockID, delegatorAddr)
}
3 changes: 3 additions & 0 deletions x/valset-pref/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ type Keeper struct {
paramSpace paramtypes.Subspace
stakingKeeper types.StakingInterface
distirbutionKeeper types.DistributionKeeper
lockupKeeper types.LockupKeeper
}

func NewKeeper(storeKey sdk.StoreKey,
paramSpace paramtypes.Subspace,
stakingKeeper types.StakingInterface,
distirbutionKeeper types.DistributionKeeper,
lockupKeeper types.LockupKeeper,
) Keeper {
return Keeper{
storeKey: storeKey,
paramSpace: paramSpace,
stakingKeeper: stakingKeeper,
distirbutionKeeper: distirbutionKeeper,
lockupKeeper: lockupKeeper,
}
}

Expand Down
71 changes: 69 additions & 2 deletions x/valset-pref/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ package keeper_test
import (
"fmt"
"testing"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

"github.com/stretchr/testify/suite"

"github.com/osmosis-labs/osmosis/v14/app/apptesting"
appParams "github.com/osmosis-labs/osmosis/v14/app/params"
lockuptypes "github.com/osmosis-labs/osmosis/v14/x/lockup/types"
"github.com/osmosis-labs/osmosis/v14/x/valset-pref/types"

"github.com/stretchr/testify/suite"

valPref "github.com/osmosis-labs/osmosis/v14/x/valset-pref"
)

Expand Down Expand Up @@ -199,7 +202,71 @@ func (suite *KeeperTestSuite) SetupValidatorsAndDelegations() ([]string, []types
amountToFund := sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 100_000_000)} // 100 osmo

return valAddrs, preferences, amountToFund
}

func (suite *KeeperTestSuite) SetupLocks(delegator sdk.AccAddress) []lockuptypes.PeriodLock {
// create a pool with uosmo
locks := []lockuptypes.PeriodLock{}
// Setup lock
coinsToLock := sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 10_000_000)}
osmoToLock := sdk.Coins{sdk.NewInt64Coin(appParams.BaseCoinUnit, 10_000_000)}
multipleCoinsToLock := sdk.Coins{coinsToLock[0], osmoToLock[0]}
suite.FundAcc(delegator, sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 100_000_000), sdk.NewInt64Coin(appParams.BaseCoinUnit, 100_000_000)})

// lock with osmo
twoWeekDuration, err := time.ParseDuration("336h")
suite.Require().NoError(err)
workingLock, err := suite.App.LockupKeeper.CreateLock(suite.Ctx, delegator, osmoToLock, twoWeekDuration)
suite.Require().NoError(err)

locks = append(locks, workingLock)

// locking with stake denom instead of osmo denom
stakeDenomLock, err := suite.App.LockupKeeper.CreateLock(suite.Ctx, delegator, coinsToLock, twoWeekDuration)
suite.Require().NoError(err)

locks = append(locks, stakeDenomLock)

// lock case where lock owner != delegator
suite.FundAcc(sdk.AccAddress([]byte("addr5---------------")), osmoToLock)
lockWithDifferentOwner, err := suite.App.LockupKeeper.CreateLock(suite.Ctx, sdk.AccAddress([]byte("addr5---------------")), osmoToLock, twoWeekDuration)
suite.Require().NoError(err)

locks = append(locks, lockWithDifferentOwner)

// lock case where the duration != <= 2 weeks
morethanTwoWeekDuration, err := time.ParseDuration("337h")
suite.Require().NoError(err)
maxDurationLock, err := suite.App.LockupKeeper.CreateLock(suite.Ctx, delegator, osmoToLock, morethanTwoWeekDuration)
suite.Require().NoError(err)

locks = append(locks, maxDurationLock)

// unbonding locks
unbondingLocks, err := suite.App.LockupKeeper.CreateLock(suite.Ctx, delegator, osmoToLock, twoWeekDuration)
suite.Require().NoError(err)

err = suite.App.LockupKeeper.BeginUnlock(suite.Ctx, unbondingLocks.ID, nil)
suite.Require().NoError(err)

locks = append(locks, unbondingLocks)

// synthetic locks
syntheticLocks, err := suite.App.LockupKeeper.CreateLock(suite.Ctx, delegator, osmoToLock, twoWeekDuration)
suite.Require().NoError(err)

err = suite.App.LockupKeeper.CreateSyntheticLockup(suite.Ctx, syntheticLocks.ID, "uosmo", time.Minute, true)
suite.Require().NoError(err)

locks = append(locks, syntheticLocks)

// multiple asset lock
multiassetLock, err := suite.App.LockupKeeper.CreateLock(suite.Ctx, delegator, multipleCoinsToLock, twoWeekDuration)
suite.Require().NoError(err)

locks = append(locks, multiassetLock)

return locks
}

func TestKeeperTestSuite(t *testing.T) {
Expand Down
35 changes: 35 additions & 0 deletions x/valset-pref/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/osmosis-labs/osmosis/v14/x/valset-pref/types"
)

Expand Down Expand Up @@ -34,6 +35,7 @@ func (server msgServer) SetValidatorSetPreference(goCtx context.Context, msg *ty
return &types.MsgSetValidatorSetPreferenceResponse{}, nil
}

// DelegateToValidatorSet delegates to a delegators existing validator-set.
func (server msgServer) DelegateToValidatorSet(goCtx context.Context, msg *types.MsgDelegateToValidatorSet) (*types.MsgDelegateToValidatorSetResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

Expand All @@ -45,6 +47,7 @@ func (server msgServer) DelegateToValidatorSet(goCtx context.Context, msg *types
return &types.MsgDelegateToValidatorSetResponse{}, nil
}

// UndelegateFromValidatorSet undelegates {coin} amount from the validator set.
func (server msgServer) UndelegateFromValidatorSet(goCtx context.Context, msg *types.MsgUndelegateFromValidatorSet) (*types.MsgUndelegateFromValidatorSetResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

Expand All @@ -56,6 +59,7 @@ func (server msgServer) UndelegateFromValidatorSet(goCtx context.Context, msg *t
return &types.MsgUndelegateFromValidatorSetResponse{}, nil
}

// RedelegateValidatorSet allows delegators to set a new validator set and switch validators.
func (server msgServer) RedelegateValidatorSet(goCtx context.Context, msg *types.MsgRedelegateValidatorSet) (*types.MsgRedelegateValidatorSetResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

Expand Down Expand Up @@ -87,6 +91,7 @@ func (server msgServer) RedelegateValidatorSet(goCtx context.Context, msg *types
return &types.MsgRedelegateValidatorSetResponse{}, nil
}

// WithdrawDelegationRewards withdraws all the delegation rewards from the validator in the val-set.
func (server msgServer) WithdrawDelegationRewards(goCtx context.Context, msg *types.MsgWithdrawDelegationRewards) (*types.MsgWithdrawDelegationRewardsResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

Expand All @@ -97,3 +102,33 @@ func (server msgServer) WithdrawDelegationRewards(goCtx context.Context, msg *ty

return &types.MsgWithdrawDelegationRewardsResponse{}, nil
}

// DelegateBondedTokens force unlocks bonded uosmo and stakes according to your current validator set preference.
func (server msgServer) DelegateBondedTokens(goCtx context.Context, msg *types.MsgDelegateBondedTokens) (*types.MsgDelegateBondedTokensResponse, error) {
mattverse marked this conversation as resolved.
Show resolved Hide resolved
ctx := sdk.UnwrapSDKContext(goCtx)

// get the existingValSet if it exists, if not check existingStakingPosition and return it
_, err := server.keeper.GetDelegationPreferences(ctx, msg.Delegator)
if err != nil {
return nil, fmt.Errorf("user %s doesn't have validator set", msg.Delegator)
}

// Message 1: force unlock bonded osmo tokens.
unlockedOsmoToken, err := server.keeper.ForceUnlockBondedOsmo(ctx, msg.LockID, msg.Delegator)
if err != nil {
return nil, err
}

delegator, err := sdk.AccAddressFromBech32(msg.Delegator)
if err != nil {
return nil, err
}

// Message 2: Perform osmo token delegation.
_, err = server.DelegateToValidatorSet(goCtx, types.NewMsgDelegateToValidatorSet(delegator, unlockedOsmoToken))
if err != nil {
return nil, err
}

return &types.MsgDelegateBondedTokensResponse{}, nil
}
112 changes: 112 additions & 0 deletions x/valset-pref/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package keeper_test
import (
sdk "github.com/cosmos/cosmos-sdk/types"

appParams "github.com/osmosis-labs/osmosis/v14/app/params"
valPref "github.com/osmosis-labs/osmosis/v14/x/valset-pref"
"github.com/osmosis-labs/osmosis/v14/x/valset-pref/types"
)
Expand Down Expand Up @@ -642,7 +643,118 @@ func (suite *KeeperTestSuite) TestWithdrawDelegationRewards() {

} else {
suite.Require().Error(err)
}
})
}
}

func (suite *KeeperTestSuite) TestDelegateBondedTokens() {
suite.SetupTest()

testLock := suite.SetupLocks(sdk.AccAddress([]byte("addr1---------------")))

tests := []struct {
mattverse marked this conversation as resolved.
Show resolved Hide resolved
name string
delegator sdk.AccAddress
lockId uint64
expectedUnlockedOsmo sdk.Coin
expectedDelegations []sdk.Dec
setValSet bool
expectPass bool
}{
{
name: "DelegateBondedTokens with existing osmo denom lockId, bonded and <= 2 weeks bond duration",
delegator: sdk.AccAddress([]byte("addr1---------------")),
lockId: testLock[0].ID,
expectedUnlockedOsmo: sdk.NewCoin(appParams.BaseCoinUnit, sdk.NewInt(60_000_000)), // delegator has 100osmo and creates 5 locks 10osmo each, forceUnlock only 1 lock
expectedDelegations: []sdk.Dec{sdk.NewDec(2_000_000), sdk.NewDec(3_300_000), sdk.NewDec(1_200_000), sdk.NewDec(3_500_000)},
setValSet: true,
expectPass: true,
},
{
name: "DelegateBondedTokens with existing stake denom lockId, bonded and <= 2 weeks bond duration",
delegator: sdk.AccAddress([]byte("addr1---------------")),
lockId: testLock[1].ID,
expectPass: false,
},
{
name: "DelegateBondedTokens with non existing lockId",
delegator: sdk.AccAddress([]byte("addr1---------------")),
lockId: 10,
expectPass: false,
},
{
name: "DelegateBondedTokens with lockOwner != delegatorOwner",
delegator: sdk.AccAddress([]byte("addr1---------------")),
lockId: testLock[2].ID,
expectPass: false,
},
{
name: "DelegateBondedTokens with lock duration > 2 weeks",
delegator: sdk.AccAddress([]byte("addr1---------------")),
lockId: testLock[3].ID,
expectPass: false,
},
{
name: "DelegateBondedTokens with non bonded lockId",
delegator: sdk.AccAddress([]byte("addr1---------------")),
lockId: testLock[4].ID,
expectPass: false,
},
{
name: "DelegateBondedTokens with synthetic locks",
delegator: sdk.AccAddress([]byte("addr1---------------")),
lockId: testLock[5].ID,
expectPass: false,
},
{
name: "DelegateBondedTokens with multiple asset lock",
delegator: sdk.AccAddress([]byte("addr1---------------")),
lockId: testLock[6].ID,
expectPass: false,
},
}

for _, test := range tests {
suite.Run(test.name, func() {
// setup message server
msgServer := valPref.NewMsgServerImpl(suite.App.ValidatorSetPreferenceKeeper)
c := sdk.WrapSDKContext(suite.Ctx)

// creates a validator preference list to delegate to
preferences := suite.PrepareDelegateToValidatorSet()

if test.setValSet {
// SetValidatorSetPreference sets a new list of val-set
_, err := msgServer.SetValidatorSetPreference(c, types.NewMsgSetValidatorSetPreference(test.delegator, preferences))
suite.Require().NoError(err)
}

_, err := msgServer.DelegateBondedTokens(c, types.NewMsgDelegateBondedTokens(test.delegator, test.lockId))
if test.expectPass {
suite.Require().NoError(err)

// check that the lock has been successfully unlocked
// existingLocks should not contain the current lock
existingLocks, err := suite.App.LockupKeeper.GetPeriodLocks(suite.Ctx)

suite.Require().NoError(err)
suite.Require().Equal(len(existingLocks), len(testLock)-1)

balance := suite.App.BankKeeper.GetBalance(suite.Ctx, test.delegator, appParams.BaseCoinUnit)
suite.Require().Equal(balance, test.expectedUnlockedOsmo)

// check if delegation has been done by checking if expectedDelegations matches after delegation
for i, val := range preferences {
valAddr, err := sdk.ValAddressFromBech32(val.ValOperAddress)
suite.Require().NoError(err)

// guarantees that the delegator exists because we check it in DelegateToValidatorSet
del, _ := suite.App.StakingKeeper.GetDelegation(suite.Ctx, test.delegator, valAddr)
suite.Require().Equal(del.Shares, test.expectedDelegations[i])
}
} else {
suite.Require().Error(err)
}
mattverse marked this conversation as resolved.
Show resolved Hide resolved
})
}
Expand Down
10 changes: 9 additions & 1 deletion x/valset-pref/types/expected_interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (

sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

lockuptypes "github.com/osmosis-labs/osmosis/v14/x/lockup/types"
)

// StakingInterface expected staking keeper.
Expand All @@ -23,10 +25,16 @@ type BankKeeper interface {
GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin
}

// For testing only
type DistributionKeeper interface {
WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error)
IncrementValidatorPeriod(ctx sdk.Context, val stakingtypes.ValidatorI) uint64
CalculateDelegationRewards(ctx sdk.Context, val stakingtypes.ValidatorI, del stakingtypes.DelegationI, endingPeriod uint64) (rewards sdk.DecCoins)
AllocateTokensToValidator(ctx sdk.Context, val stakingtypes.ValidatorI, tokens sdk.DecCoins)
}
type LockupKeeper interface {
GetLockByID(ctx sdk.Context, lockID uint64) (*lockuptypes.PeriodLock, error)
GetAllSyntheticLockupsByLockup(ctx sdk.Context, lockID uint64) []lockuptypes.SyntheticLock
ForceUnlock(ctx sdk.Context, lock lockuptypes.PeriodLock) error
BeginUnlock(ctx sdk.Context, lockID uint64, coins sdk.Coins) error
GetPeriodLocks(ctx sdk.Context) ([]lockuptypes.PeriodLock, error)
}
Loading