Skip to content

Implemented AfterDelegationModified Hook for pse module#9

Merged
miladz68 merged 3 commits into
masterfrom
milad/pse-bonding-snapshot
Oct 28, 2025
Merged

Implemented AfterDelegationModified Hook for pse module#9
miladz68 merged 3 commits into
masterfrom
milad/pse-bonding-snapshot

Conversation

@miladz68

@miladz68 miladz68 commented Oct 27, 2025

Copy link
Copy Markdown
Contributor

Description

Reviewers checklist:

  • Try to write more meaningful comments with clear actions to be taken.
  • Nit-picking should be unblocking. Focus on core issues.

Authors checklist

  • Provide a concise and meaningful description
  • Review the code yourself first, before making the PR.
  • Annotate your PR in places that require explanation.
  • Think and try to split the PR to smaller PR if it is big.

This change is Reviewable

@miladz68 miladz68 requested a review from a team as a code owner October 27, 2025 08:43

@TxCorpi0x TxCorpi0x left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewable status: 0 of 6 files reviewed, 3 unresolved discussions


x/pse/keeper/hooks.go line 22 at r1 (raw file):

var _ stakingtypes.StakingHooks = Hooks{}

// Hooks Create new distribution hooks.

nit: staking/pse hooks


x/pse/keeper/hooks.go line 74 at r1 (raw file):

// BeforeDelegationRemoved implements the staking hooks interface.
func (h Hooks) BeforeDelegationRemoved(_ context.Context, _ sdk.AccAddress, _ sdk.ValAddress) error {
	return nil

If a user undeleagte/unbond a previously staked value, don't we need to deduct the unbonded value's corresponding score?
This is crucial for redelegation as well, otherwise the score will be given Twice (for a certain user, The first delegation score, and if we do not finalize the score in the removal, we will add the redelegated amount again in the `AfterDelegationModified.)


x/pse/keeper/hooks.go line 103 at r1 (raw file):

// BeforeValidatorSlashed implements the staking hooks interface.
func (h Hooks) BeforeValidatorSlashed(ctx context.Context, valAddr sdk.ValAddress, fraction sdkmath.LegacyDec) error {
	return nil

In case of validators slashing, the score remains the same and the user won't get any loss, is it the current design? if it is the case, we need to track the scores per-user-per-validator to be able to normalize the scores after slashing.

@ysv ysv requested review from masihyeganeh and ysv October 27, 2025 17:25

@ysv ysv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@ysv reviewed 6 of 6 files at r1, all commit messages.
Reviewable status: all files reviewed, 10 unresolved discussions (waiting on @masihyeganeh and @miladz68)


x/pse/keeper/hooks.go line 28 at r1 (raw file):

// AfterDelegationModified implements the staking hooks interface.
func (h Hooks) AfterDelegationModified(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) error {

to make sure that logic is correct would be nice to understand in which cases all these hooks are triggered.
If AfterDelegationModified is called for any delegation change then impl is correct.

Is it called also when: delegation is created, deleted ?


x/pse/keeper/hooks.go line 50 at r1 (raw file):

	}

	oldScore, err := h.k.AccountScore.Get(ctx, delAddr)

I'm thinking if AccountScore is the best name here.
Because I would expect AccountScore query to return live score so maybe it makes sense to differentiated these 2 inside naming also.

My proposals:

  • AccountScoreSnapshot
  • AccountCheckpointScore
  • AccountCommitedScore

And then full formula is:
AccountScore = AccountScoreSnapshot + SUM(Score(DelegationTimeEntry))

Because currently we probably use AccountScore name for both AccountScore & AccountScoreSnapshot


x/pse/keeper/hooks.go line 50 at r1 (raw file):

	}

	oldScore, err := h.k.AccountScore.Get(ctx, delAddr)

nit: I would change old -> last/previous


x/pse/keeper/hooks.go line 59 at r1 (raw file):

	oldDelegatedTokens := val.TokensFromShares(delegationTimeEntry.Shares).TruncateInt()
	delegationDuration := blockTimeUnixSeconds - delegationTimeEntry.LastChangedUnixSec
	addedScore := oldDelegatedTokens.MulRaw(delegationDuration)

nit: not critical but ideas for better name: delegationScore, currentDelegationScore, scoreFromDelegation


x/pse/keeper/hooks.go line 64 at r1 (raw file):

	if err := h.k.SetDelegationTimeEntry(ctx, valAddr, delAddr, types.DelegationTimeEntry{
		LastChangedUnixSec: blockTimeUnixSeconds,
		Shares:             delegation.Shares,

what if delegation.Shares == 0, shouldn't we skip SetDelegationTimeEntry in this case ?


x/pse/keeper/hooks.go line 102 at r1 (raw file):

// BeforeValidatorSlashed implements the staking hooks interface.
func (h Hooks) BeforeValidatorSlashed(ctx context.Context, valAddr sdk.ValAddress, fraction sdkmath.LegacyDec) error {

not sure if validator slashing is handled correctly because:

before slashing: 10 ucore
after slashing: 9 ucore

if we use 10ucore * period - it will be incorrect
if we use 9ucore * period - also incorrect

full formula should be 10ucore * period_1 + 9ucore * period_2.

However, if this is complicated to handle we can shortcut now, leave TODO and postpone


x/pse/keeper/hooks_test.go line 50 at r1 (raw file):

		sdk.NewInt64Coin(sdk.DefaultBondDenom, 11),
	))
	ctx, _, err = testApp.BeginNextBlockAtTime(ctx.BlockTime().Add(time.Second * 8))

nit: maybe more realistic would be to call BeginNextBlock(+time.Second) 8 times but this is really minor

@miladz68 miladz68 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reviewable status: all files reviewed, 7 unresolved discussions (waiting on @masihyeganeh, @TxCorpi0x, and @ysv)


x/pse/keeper/hooks.go line 22 at r1 (raw file):

Previously, TxCorpi0x wrote…

nit: staking/pse hooks

Done.


x/pse/keeper/hooks.go line 28 at r1 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

to make sure that logic is correct would be nice to understand in which cases all these hooks are triggered.
If AfterDelegationModified is called for any delegation change then impl is correct.

Is it called also when: delegation is created, deleted ?

This hook is called at creation and modifican(partial redelegation and partial unbond).


x/pse/keeper/hooks.go line 50 at r1 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

I'm thinking if AccountScore is the best name here.
Because I would expect AccountScore query to return live score so maybe it makes sense to differentiated these 2 inside naming also.

My proposals:

  • AccountScoreSnapshot
  • AccountCheckpointScore
  • AccountCommitedScore

And then full formula is:
AccountScore = AccountScoreSnapshot + SUM(Score(DelegationTimeEntry))

Because currently we probably use AccountScore name for both AccountScore & AccountScoreSnapshot

Adopted AccountScoreSnapshot


x/pse/keeper/hooks.go line 50 at r1 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

nit: I would change old -> last/previous

Done.


x/pse/keeper/hooks.go line 59 at r1 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

nit: not critical but ideas for better name: delegationScore, currentDelegationScore, scoreFromDelegation

Done.


x/pse/keeper/hooks.go line 64 at r1 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

what if delegation.Shares == 0, shouldn't we skip SetDelegationTimeEntry in this case ?

that logic will not rise, since delegation can be zero only in case DelegationRemoval which is a different hook.


x/pse/keeper/hooks.go line 74 at r1 (raw file):

Previously, TxCorpi0x wrote…

If a user undeleagte/unbond a previously staked value, don't we need to deduct the unbonded value's corresponding score?
This is crucial for redelegation as well, otherwise the score will be given Twice (for a certain user, The first delegation score, and if we do not finalize the score in the removal, we will add the redelegated amount again in the `AfterDelegationModified.)

I am not fully understanding the scenario. we should discuss in call.


x/pse/keeper/hooks.go line 102 at r1 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

not sure if validator slashing is handled correctly because:

before slashing: 10 ucore
after slashing: 9 ucore

if we use 10ucore * period - it will be incorrect
if we use 9ucore * period - also incorrect

full formula should be 10ucore * period_1 + 9ucore * period_2.

However, if this is complicated to handle we can shortcut now, leave TODO and postpone

you are correct, we are using the second approach which is not super accurate, and yes, implementing the most accurate solution is hard. I will leave a TODO


x/pse/keeper/hooks.go line 103 at r1 (raw file):

Previously, TxCorpi0x wrote…

In case of validators slashing, the score remains the same and the user won't get any loss, is it the current design? if it is the case, we need to track the scores per-user-per-validator to be able to normalize the scores after slashing.

take a look at the discussion from yaroslav, I answered our approach there. let's discuss it in the call if you still have questions/suggestions.


x/pse/keeper/hooks_test.go line 50 at r1 (raw file):

Previously, ysv (Yaroslav Savchuk) wrote…

nit: maybe more realistic would be to call BeginNextBlock(+time.Second) 8 times but this is really minor

I think the current approach is good enough, but it should be easy to change it if you think it is needed.

@TxCorpi0x TxCorpi0x left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewable status: 3 of 8 files reviewed, 7 unresolved discussions (waiting on @masihyeganeh, @miladz68, and @ysv)


x/pse/keeper/hooks.go line 74 at r1 (raw file):

Previously, miladz68 (milad) wrote…

I am not fully understanding the scenario. we should discuss in call.

I am asking about this special scenario, which user will unblond/undelegate the entire delegation, means the shares become zero. please look into the this line, the delegation will be remove from staking module state but AfterDelegationModified will not be fired according to the staking module's logic. instead the other hooks BeforeDelegationRemoved will be called here, so we need to handle that scenario and subtract the score accordingly.

Let's discuss in the call.

@miladz68 miladz68 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reviewable status: 3 of 8 files reviewed, 7 unresolved discussions (waiting on @masihyeganeh, @TxCorpi0x, and @ysv)


x/pse/keeper/hooks.go line 74 at r1 (raw file):

Previously, TxCorpi0x wrote…

I am asking about this special scenario, which user will unblond/undelegate the entire delegation, means the shares become zero. please look into the this line, the delegation will be remove from staking module state but AfterDelegationModified will not be fired according to the staking module's logic. instead the other hooks BeforeDelegationRemoved will be called here, so we need to handle that scenario and subtract the score accordingly.

Let's discuss in the call.

Sure, there is a separte task to handle it.

@TxCorpi0x TxCorpi0x left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:lgtm:

@TxCorpi0x reviewed 3 of 6 files at r1, 5 of 5 files at r2, all commit messages.
Reviewable status: all files reviewed, 6 unresolved discussions (waiting on @masihyeganeh and @ysv)

@ysv ysv requested a review from TxCorpi0x October 28, 2025 14:36

@ysv ysv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@ysv reviewed 5 of 5 files at r2, all commit messages.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on @masihyeganeh and @TxCorpi0x)

@TxCorpi0x TxCorpi0x left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on @masihyeganeh)

@miladz68 miladz68 merged commit 2dbcaf1 into master Oct 28, 2025
15 of 16 checks passed
@miladz68 miladz68 deleted the milad/pse-bonding-snapshot branch October 28, 2025 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants