Skip to content

Commit

Permalink
fix: find avail for all validators, not just those with delegations (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Joe Bowman authored Dec 29, 2023
1 parent 385906d commit 3c0ae1d
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 10 deletions.
31 changes: 21 additions & 10 deletions x/interchainstaking/keeper/redemptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func (k *Keeper) processRedemptionForLsm(ctx sdk.Context, zone *types.Zone, send
msgs := make([]*lsmstakingtypes.MsgTokenizeShares, 0)
var err error
intents := intent.Intents

if !found || len(intents) == 0 {
// if user has no intent set (this can happen if redeeming tokens that were obtained offchain), use global intent.
// Note: this can be improved; user will receive a bunch of tokens.
Expand All @@ -39,7 +40,6 @@ func (k *Keeper) processRedemptionForLsm(ctx sdk.Context, zone *types.Zone, send
if err != nil {
return err
}

for _, intent := range intents.Sort() {
thisAmount := intent.Weight.MulInt(nativeTokens).TruncateInt()
if thisAmount.GT(availablePerValidator[intent.ValoperAddress]) {
Expand All @@ -51,6 +51,10 @@ func (k *Keeper) processRedemptionForLsm(ctx sdk.Context, zone *types.Zone, send

distribution[intents[0].ValoperAddress] += outstanding.Uint64()

if distribution[intents[0].ValoperAddress] > availablePerValidator[intents[0].ValoperAddress].Uint64() {
return errors.New("unable to satisfy unbond request (2); delegations may be locked")
}

for _, valoper := range utils.Keys(distribution) {
msgs = append(msgs, &lsmstakingtypes.MsgTokenizeShares{
DelegatorAddress: zone.DelegationAddress.Address,
Expand Down Expand Up @@ -109,20 +113,27 @@ func (k *Keeper) queueRedemption(
return nil
}

// GetUnlockedTokensForZone will iterate over all delegation records for a zone, and then remove the
// locked tokens (those actively being redelegated), returning a slice of int64 staking tokens that
// are unlocked and free to redelegate or unbond.
// GetUnlockedTokensForZone will iterate over all validators for a zone, summing delegated amounts,
// and then remove the locked tokens (those actively being redelegated), returning a slice of int64
// staking tokens that are unlocked and free to redelegate or unbond.
func (k *Keeper) GetUnlockedTokensForZone(ctx sdk.Context, zone *types.Zone) (map[string]math.Int, math.Int, error) {
availablePerValidator := make(map[string]math.Int, len(zone.Validators))
validators := k.GetValidators(ctx, zone.ChainId)

availablePerValidator := make(map[string]math.Int, len(validators))
total := sdk.ZeroInt()
for _, delegation := range k.GetAllDelegations(ctx, zone.ChainId) {
thisAvailable, found := availablePerValidator[delegation.ValidatorAddress]
// for each validator, fetch delegated amount.
for _, validator := range validators {
delegation, found := k.GetDelegation(ctx, zone.ChainId, zone.DelegationAddress.Address, validator.ValoperAddress)
if !found {
thisAvailable = sdk.ZeroInt()
availablePerValidator[validator.ValoperAddress] = sdk.ZeroInt()
} else {
availablePerValidator[validator.ValoperAddress] = delegation.Amount.Amount
total = total.Add(delegation.Amount.Amount)
}
availablePerValidator[delegation.ValidatorAddress] = thisAvailable.Add(delegation.Amount.Amount)
total = total.Add(delegation.Amount.Amount)
}

// for each redelegation, remove the amount being redelegated to from the destination,
// as this cannot be available for unbonding or redelegation.
for _, redelegation := range k.ZoneRedelegationRecords(ctx, zone.ChainId) {
thisAvailable, found := availablePerValidator[redelegation.Destination]
if found {
Expand Down
83 changes: 83 additions & 0 deletions x/interchainstaking/keeper/redemptions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package keeper_test

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

"github.com/quicksilver-zone/quicksilver/x/interchainstaking/types"
)

func (suite *KeeperTestSuite) TestGetUnlockedTokensForZoneAllHaveDelegation() {
suite.SetupTest()
suite.setupTestZones()

quicksilver := suite.GetQuicksilverApp(suite.chainA)
ctx := suite.chainA.GetContext()

zone, found := quicksilver.InterchainstakingKeeper.GetZone(ctx, suite.chainB.ChainID)
vals := quicksilver.InterchainstakingKeeper.GetValidators(ctx, zone.ChainId)
suite.True(found)

quicksilver.InterchainstakingKeeper.SetDelegation(ctx, zone.ChainId, types.NewDelegation(zone.DelegationAddress.Address, vals[0].ValoperAddress, sdk.NewCoin(zone.BaseDenom, sdk.NewInt(100))))
quicksilver.InterchainstakingKeeper.SetDelegation(ctx, zone.ChainId, types.NewDelegation(zone.DelegationAddress.Address, vals[1].ValoperAddress, sdk.NewCoin(zone.BaseDenom, sdk.NewInt(100))))
quicksilver.InterchainstakingKeeper.SetDelegation(ctx, zone.ChainId, types.NewDelegation(zone.DelegationAddress.Address, vals[2].ValoperAddress, sdk.NewCoin(zone.BaseDenom, sdk.NewInt(100))))
quicksilver.InterchainstakingKeeper.SetDelegation(ctx, zone.ChainId, types.NewDelegation(zone.DelegationAddress.Address, vals[3].ValoperAddress, sdk.NewCoin(zone.BaseDenom, sdk.NewInt(100))))

availPerVal, total, err := quicksilver.InterchainstakingKeeper.GetUnlockedTokensForZone(ctx, &zone)
suite.NoError(err)
suite.Equal(sdk.NewInt(400), total)
for _, x := range availPerVal {
suite.Equal(sdk.NewInt(100), x)
}
}

func (suite *KeeperTestSuite) TestGetUnlockedTokensForZoneNotAllHaveDelegation() {
suite.SetupTest()
suite.setupTestZones()

quicksilver := suite.GetQuicksilverApp(suite.chainA)
ctx := suite.chainA.GetContext()

zone, found := quicksilver.InterchainstakingKeeper.GetZone(ctx, suite.chainB.ChainID)
vals := quicksilver.InterchainstakingKeeper.GetValidators(ctx, zone.ChainId)
suite.True(found)

quicksilver.InterchainstakingKeeper.SetDelegation(ctx, zone.ChainId, types.NewDelegation(zone.DelegationAddress.Address, vals[0].ValoperAddress, sdk.NewCoin(zone.BaseDenom, sdk.NewInt(100))))
quicksilver.InterchainstakingKeeper.SetDelegation(ctx, zone.ChainId, types.NewDelegation(zone.DelegationAddress.Address, vals[1].ValoperAddress, sdk.NewCoin(zone.BaseDenom, sdk.NewInt(100))))
quicksilver.InterchainstakingKeeper.SetDelegation(ctx, zone.ChainId, types.NewDelegation(zone.DelegationAddress.Address, vals[2].ValoperAddress, sdk.NewCoin(zone.BaseDenom, sdk.NewInt(100))))

availPerVal, total, err := quicksilver.InterchainstakingKeeper.GetUnlockedTokensForZone(ctx, &zone)
suite.NoError(err)
suite.Equal(sdk.NewInt(300), total)

// ensure all vals exist in list, even if no delegation
for _, v := range vals {
_, found := availPerVal[v.ValoperAddress]
suite.True(found)
}
}

func (suite *KeeperTestSuite) TestGetUnlockedTokensForZoneWithRedelegation() {
suite.SetupTest()
suite.setupTestZones()

quicksilver := suite.GetQuicksilverApp(suite.chainA)
ctx := suite.chainA.GetContext()

zone, found := quicksilver.InterchainstakingKeeper.GetZone(ctx, suite.chainB.ChainID)
vals := quicksilver.InterchainstakingKeeper.GetValidators(ctx, zone.ChainId)
suite.True(found)

quicksilver.InterchainstakingKeeper.SetDelegation(ctx, zone.ChainId, types.NewDelegation(zone.DelegationAddress.Address, vals[0].ValoperAddress, sdk.NewCoin(zone.BaseDenom, sdk.NewInt(100))))
quicksilver.InterchainstakingKeeper.SetDelegation(ctx, zone.ChainId, types.NewDelegation(zone.DelegationAddress.Address, vals[1].ValoperAddress, sdk.NewCoin(zone.BaseDenom, sdk.NewInt(100))))
quicksilver.InterchainstakingKeeper.SetDelegation(ctx, zone.ChainId, types.NewDelegation(zone.DelegationAddress.Address, vals[2].ValoperAddress, sdk.NewCoin(zone.BaseDenom, sdk.NewInt(100))))
quicksilver.InterchainstakingKeeper.SetDelegation(ctx, zone.ChainId, types.NewDelegation(zone.DelegationAddress.Address, vals[3].ValoperAddress, sdk.NewCoin(zone.BaseDenom, sdk.NewInt(100))))

quicksilver.InterchainstakingKeeper.SetRedelegationRecord(ctx, types.RedelegationRecord{ChainId: zone.ChainId, EpochNumber: 1, Source: vals[0].ValoperAddress, Destination: vals[2].ValoperAddress, Amount: 5})
quicksilver.InterchainstakingKeeper.SetRedelegationRecord(ctx, types.RedelegationRecord{ChainId: zone.ChainId, EpochNumber: 2, Source: vals[0].ValoperAddress, Destination: vals[2].ValoperAddress, Amount: 5})
quicksilver.InterchainstakingKeeper.SetRedelegationRecord(ctx, types.RedelegationRecord{ChainId: zone.ChainId, EpochNumber: 2, Source: vals[1].ValoperAddress, Destination: vals[2].ValoperAddress, Amount: 5})

availPerVal, total, err := quicksilver.InterchainstakingKeeper.GetUnlockedTokensForZone(ctx, &zone)
suite.NoError(err)
suite.Equal(sdk.NewInt(385), total)
suite.Equal(sdk.NewInt(85), availPerVal[vals[2].ValoperAddress])
}

0 comments on commit 3c0ae1d

Please sign in to comment.