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

L2 Staking Changes #1

Merged
merged 23 commits into from
Jan 6, 2025
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
2 changes: 1 addition & 1 deletion testutil/sims/app_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ func GenesisStateWithValSet(
// add bonded amount to bonded pool module account
balances = append(balances, banktypes.Balance{
Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(),
Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt)},
Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt.MulRaw(int64(len(delegations))))},
dillu24 marked this conversation as resolved.
Show resolved Hide resolved
})

// update total supply
Expand Down
14 changes: 14 additions & 0 deletions x/distribution/keeper/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,17 @@ func (h Hooks) BeforeDelegationRemoved(_ context.Context, _ sdk.AccAddress, _ sd
func (h Hooks) AfterUnbondingInitiated(_ context.Context, _ uint64) error {
return nil
}

func (h Hooks) AfterUnbondingDelegationSlashed(_ context.Context, _ sdk.ValAddress, _ sdk.AccAddress, _ sdkmath.Int) error {
return nil
}

func (h Hooks) AfterRedelegationSlashed(_ context.Context, _ sdk.ValAddress, _ sdk.AccAddress, _ sdkmath.Int) error {
return nil
}

func (h Hooks) CustomBeforeValidatorSlashed(
_ context.Context, _ sdk.ValAddress, _ sdkmath.LegacyDec, _ sdkmath.Int,
) error {
return nil
}
14 changes: 14 additions & 0 deletions x/slashing/keeper/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,17 @@ func (h Hooks) BeforeValidatorSlashed(_ context.Context, _ sdk.ValAddress, _ sdk
func (h Hooks) AfterUnbondingInitiated(_ context.Context, _ uint64) error {
return nil
}

func (h Hooks) AfterUnbondingDelegationSlashed(_ context.Context, _ sdk.ValAddress, _ sdk.AccAddress, _ sdkmath.Int) error {
return nil
}

func (h Hooks) AfterRedelegationSlashed(_ context.Context, _ sdk.ValAddress, _ sdk.AccAddress, _ sdkmath.Int) error {
return nil
}

func (h Hooks) CustomBeforeValidatorSlashed(
_ context.Context, _ sdk.ValAddress, _ sdkmath.LegacyDec, _ sdkmath.Int,
) error {
return nil
}
42 changes: 42 additions & 0 deletions x/slashing/testutil/expected_keepers_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion x/slashing/types/expected_keepers.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package types

import (
context "context"
"context"

"cosmossdk.io/core/address"
"cosmossdk.io/math"
Expand Down Expand Up @@ -77,4 +77,15 @@ type StakingHooks interface {
BeforeDelegationRemoved(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) error // Must be called when a delegation is removed
AfterDelegationModified(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) error
BeforeValidatorSlashed(ctx context.Context, valAddr sdk.ValAddress, fraction math.LegacyDec) error

// These are custom hooks
AfterUnbondingDelegationSlashed(
ctx context.Context, valAddr sdk.ValAddress, delAddr sdk.AccAddress, slashedAmount math.Int,
) error
AfterRedelegationSlashed(
ctx context.Context, valAddr sdk.ValAddress, delAddr sdk.AccAddress, slashedAmount math.Int,
) error
CustomBeforeValidatorSlashed(
ctx context.Context, valAddr sdk.ValAddress, fraction math.LegacyDec, totalSlashedAmt math.Int,
) error
}
37 changes: 37 additions & 0 deletions x/staking/keeper/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,43 @@ func (k Keeper) GetValidatorDelegations(ctx context.Context, valAddr sdk.ValAddr
return delegations, nil
}

// IterateValidatorDelegations iterates through all validator delegations.
// NOTE: This is a custom function
func (k Keeper) IterateValidatorDelegations(
ctx context.Context, valAddr sdk.ValAddress, cb func(delegation types.Delegation) (stop bool),
) error {
store := k.storeService.OpenKVStore(ctx)
prefix := types.GetDelegationsByValPrefixKey(valAddr)
iterator, err := store.Iterator(prefix, storetypes.PrefixEndBytes(prefix))
if err != nil {
return err
}
defer iterator.Close()

for ; iterator.Valid(); iterator.Next() {
var delegation types.Delegation
valAddr, delAddr, err := types.ParseDelegationsByValKey(iterator.Key())
if err != nil {
return err
}

bz, err := store.Get(types.GetDelegationKey(delAddr, valAddr))
if err != nil {
return err
}

if err := k.cdc.Unmarshal(bz, &delegation); err != nil {
return err
}

if cb(delegation) {
break
}
}

return nil
}

// GetDelegatorDelegations returns a given amount of all the delegations from a
// delegator.
func (k Keeper) GetDelegatorDelegations(ctx context.Context, delegator sdk.AccAddress, maxRetrieve uint16) (delegations []types.Delegation, err error) {
Expand Down
45 changes: 45 additions & 0 deletions x/staking/keeper/delegation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ func (s *KeeperTestSuite) TestDelegation() {
ctx, keeper := s.ctx, s.stakingKeeper
require := s.Require()

getAllFromIterateValidatorDelegations := func(valAddr sdk.ValAddress) []stakingtypes.Delegation {
var delegations []stakingtypes.Delegation
err := keeper.IterateValidatorDelegations(ctx, valAddr, func(delegation stakingtypes.Delegation) (stop bool) {
delegations = append(delegations, delegation)
return false
})
require.NoError(err)
return delegations
}

addrDels, valAddrs := createValAddrs(3)

s.accountKeeper.EXPECT().AddressCodec().Return(address.NewBech32Codec("cosmos")).AnyTimes()
Expand All @@ -48,19 +58,30 @@ func (s *KeeperTestSuite) TestDelegation() {
_, err := keeper.GetDelegation(ctx, addrDels[0], valAddrs[0])
require.ErrorIs(err, stakingtypes.ErrNoDelegation)

// check empty iterator
require.Empty(getAllFromIterateValidatorDelegations(valAddrs[0]))

// set and retrieve a record
require.NoError(keeper.SetDelegation(ctx, bond1to1))
resBond, err := keeper.GetDelegation(ctx, addrDels[0], valAddrs[0])
require.NoError(err)
require.Equal(bond1to1, resBond)

// check iterator only has one value
delegations := getAllFromIterateValidatorDelegations(valAddrs[0])
require.EqualValues([]stakingtypes.Delegation{bond1to1}, delegations)

// modify a records, save, and retrieve
bond1to1.Shares = math.LegacyNewDec(99)
require.NoError(keeper.SetDelegation(ctx, bond1to1))
resBond, err = keeper.GetDelegation(ctx, addrDels[0], valAddrs[0])
require.NoError(err)
require.Equal(bond1to1, resBond)

// check iterator only has one value
delegations = getAllFromIterateValidatorDelegations(valAddrs[0])
require.EqualValues([]stakingtypes.Delegation{bond1to1}, delegations)

// add some more records
bond1to2 := stakingtypes.NewDelegation(addrDels[0].String(), valAddrs[1].String(), math.LegacyNewDec(9))
bond1to3 := stakingtypes.NewDelegation(addrDels[0].String(), valAddrs[2].String(), math.LegacyNewDec(9))
Expand All @@ -73,6 +94,14 @@ func (s *KeeperTestSuite) TestDelegation() {
require.NoError(keeper.SetDelegation(ctx, bond2to2))
require.NoError(keeper.SetDelegation(ctx, bond2to3))

// check iterator has multiple records
delegations = getAllFromIterateValidatorDelegations(valAddrs[0])
require.EqualValues([]stakingtypes.Delegation{bond1to1, bond2to1}, delegations)
delegations = getAllFromIterateValidatorDelegations(valAddrs[1])
require.EqualValues([]stakingtypes.Delegation{bond1to2, bond2to2}, delegations)
delegations = getAllFromIterateValidatorDelegations(valAddrs[2])
require.EqualValues([]stakingtypes.Delegation{bond1to3, bond2to3}, delegations)

// test all bond retrieve capabilities
resBonds, err := keeper.GetDelegatorDelegations(ctx, addrDels[0], 5)
require.NoError(err)
Expand Down Expand Up @@ -139,6 +168,14 @@ func (s *KeeperTestSuite) TestDelegation() {
require.Equal(bond2to1, resBonds[0])
require.Equal(bond2to2, resBonds[1])

// check iterator has multiple records
delegations = getAllFromIterateValidatorDelegations(valAddrs[0])
require.EqualValues([]stakingtypes.Delegation{bond1to1, bond2to1}, delegations)
delegations = getAllFromIterateValidatorDelegations(valAddrs[1])
require.EqualValues([]stakingtypes.Delegation{bond1to2, bond2to2}, delegations)
delegations = getAllFromIterateValidatorDelegations(valAddrs[2])
require.EqualValues([]stakingtypes.Delegation{bond1to3}, delegations) // 1 record less

resBonds, err = keeper.GetAllDelegatorDelegations(ctx, addrDels[1])
require.NoError(err)
require.Equal(2, len(resBonds))
Expand All @@ -153,6 +190,14 @@ func (s *KeeperTestSuite) TestDelegation() {
resBonds, err = keeper.GetDelegatorDelegations(ctx, addrDels[1], 5)
require.NoError(err)
require.Equal(0, len(resBonds))

// check iterator has multiple records
delegations = getAllFromIterateValidatorDelegations(valAddrs[0])
require.EqualValues([]stakingtypes.Delegation{bond1to1}, delegations) // 1 record less
delegations = getAllFromIterateValidatorDelegations(valAddrs[1])
require.EqualValues([]stakingtypes.Delegation{bond1to2}, delegations) // 1 record less
delegations = getAllFromIterateValidatorDelegations(valAddrs[2])
require.EqualValues([]stakingtypes.Delegation{bond1to3}, delegations) // 1 record less
}

func (s *KeeperTestSuite) TestDelegationsByValIndex() {
Expand Down
69 changes: 68 additions & 1 deletion x/staking/keeper/slash.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"cosmossdk.io/math"

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

// Slash a validator for an infraction committed at a known height
Expand Down Expand Up @@ -160,6 +160,10 @@ func (k Keeper) Slash(ctx context.Context, consAddr sdk.ConsAddress, infractionH
}

// we need to calculate the *effective* slash fraction for distribution
//
// Note: `validator.Tokens` are always positive at this stage, otherwise, they would have caused tokensToBurn to be
// zero and hence the tokensToBurn.IsZero() condition would have returned execution to the calling function.
// In other words we will always be computing an effectiveFraction, and it should never be zero or infinite.
if validator.Tokens.IsPositive() {
effectiveFraction := math.LegacyNewDecFromInt(tokensToBurn).QuoRoundUp(math.LegacyNewDecFromInt(validator.Tokens))
// possible if power has changed
Expand All @@ -170,6 +174,25 @@ func (k Keeper) Slash(ctx context.Context, consAddr sdk.ConsAddress, infractionH
if err := k.Hooks().BeforeValidatorSlashed(ctx, operatorAddress, effectiveFraction); err != nil {
k.Logger(ctx).Error("failed to call before validator slashed hook", "error", err)
}

// Call the custom-before-validator-slashed hook. Due to previous checks we are sure that tokensToBurn is
// positive i.e. that slashing will occur.
//
// Notes:
// 1. We are implementing a custom BeforeValidatorSlashedHook to make sure that if slashing occurs, the
// (Custom)BeforeValidatorSlashed hook for the reports module will still be called. If the reports module made
// use of the standard BeforeValidatorSlashed hook and another module's BeforeValidatorSlashed hook errored,
// the reports module hook logic would be skipped because of how the error handling is implemented above.
// 2. The CustomBeforeValidatorSlashed should be used by Sequencer-native modules only.
if err := k.Hooks().CustomBeforeValidatorSlashed(
ctx, operatorAddress, effectiveFraction, tokensToBurn,
); err != nil {
// To maintain a general implementation in the staking module, the responsibility for halting chain
// execution is delegated to the individual modules that implement this hook. Errors are logged if they
// occur, as not all modules may require the chain to halt upon encountering an error. If a module needs to
// stop execution, it should explicitly trigger a panic within the logic of CustomBeforeValidatorSlashed.
k.Logger(ctx).Error("failed to call after validator slashed hook", "error", err)
}
}

// Deduct from validator's bonded tokens and update the validator.
Expand Down Expand Up @@ -242,6 +265,18 @@ func (k Keeper) SlashUnbondingDelegation(ctx context.Context, unbondingDelegatio
totalSlashAmount = math.ZeroInt()
burnedAmount := math.ZeroInt()

// compute operator address; to be used later when calling hooks
validatorAddress, err := k.ValidatorAddressCodec().StringToBytes(unbondingDelegation.ValidatorAddress)
if err != nil {
return math.ZeroInt(), fmt.Errorf("SlashUnbondingDelegation: could not parse validator address: %w", err)
}

// compute delegator address; to be used later when calling hooks
delegatorAddress, err := k.authKeeper.AddressCodec().StringToBytes(unbondingDelegation.DelegatorAddress)
if err != nil {
return math.ZeroInt(), fmt.Errorf("SlashUnbondingDelegation: could not parse delegator address: %w", err)
}

// perform slashing on all entries within the unbonding delegation
for i, entry := range unbondingDelegation.Entries {
// If unbonding started before this height, stake didn't contribute to infraction
Expand Down Expand Up @@ -282,6 +317,21 @@ func (k Keeper) SlashUnbondingDelegation(ctx context.Context, unbondingDelegatio
return math.ZeroInt(), err
}

// Call the after-unbonding-delegation-slashed hook if the burned amount is greater than zero. The burned amount is
// used instead of the slashed amount because it represents the actual number of tokens that were slashed. In other
// words, if no tokens were burned, the unbonding delegation was not slashed.
if burnedAmount.IsPositive() {
if err := k.Hooks().AfterUnbondingDelegationSlashed(
ctx, validatorAddress, delegatorAddress, burnedAmount,
); err != nil {
// To maintain a general implementation in the staking module, the responsibility for halting chain
// execution is delegated to the individual modules that implement this hook. Errors are logged if they
// occur, as not all modules may require the chain to halt upon encountering an error. If a module needs to
// stop execution, it should explicitly trigger a panic within the logic of AfterUnbondingDelegationSlashed.
k.Logger(ctx).Error("failed to call after unbonding delegation slashed hook", "error", err)
}
}

return totalSlashAmount, nil
}

Expand Down Expand Up @@ -411,5 +461,22 @@ func (k Keeper) SlashRedelegation(ctx context.Context, srcValidator types.Valida
return math.ZeroInt(), err
}

// Call the after-redelegation-slashed hook if the sum of burned amounts is greater than zero. We are using the
// burned amounts instead of the slashed amount because it represents the actual number of tokens that were slashed.
// In other words, if no tokens were burned, the redelegation was not slashed.
//
// Note: In a redelegation we are actually slashing the validator receiving the redelegation, therefore, the valAddr
// in the hook must be set to the dst validator.
burnedAmount := bondedBurnedAmount.Add(notBondedBurnedAmount)
if burnedAmount.IsPositive() {
if err := k.Hooks().AfterRedelegationSlashed(ctx, valDstAddr, delegatorAddress, burnedAmount); err != nil {
// To maintain a general implementation in the staking module, the responsibility for halting chain
// execution is delegated to the individual modules that implement this hook. Errors are logged if they
// occur, as not all modules may require the chain to halt upon encountering an error. If a module needs to
// stop execution, it should explicitly trigger a panic within the logic of AfterRedelegationSlashed.
k.Logger(ctx).Error("failed to call after redelegation slashed hook", "error", err)
}
}

return totalSlashAmount, nil
}
Loading