-
Notifications
You must be signed in to change notification settings - Fork 2
/
keeper.go
189 lines (158 loc) · 6.44 KB
/
keeper.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/distribution/keeper"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
distributiontypes "github.com/cvn-network/cvn/v2/x/distribution/types"
)
type Keeper struct {
keeper.Keeper
bankKeeper types.BankKeeper
stakingKeeper types.StakingKeeper
hooks distributiontypes.DistributionHooks
}
// NewKeeper creates a new distribution Keeper instance
func NewKeeper(keeper keeper.Keeper, bankKeeper types.BankKeeper, stakingKeeper types.StakingKeeper) Keeper {
return Keeper{
Keeper: keeper,
bankKeeper: bankKeeper,
stakingKeeper: stakingKeeper,
}
}
// SetHooks sets the distribution hooks
func (k *Keeper) SetHooks(hooks distributiontypes.DistributionHooks) *Keeper {
if k.hooks != nil {
panic("cannot set distribution hooks twice")
}
k.hooks = hooks
return k
}
// WithdrawDelegationRewards withdraw rewards from a delegation
func (k Keeper) WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) {
val := k.stakingKeeper.Validator(ctx, valAddr)
if val == nil {
return nil, types.ErrNoValidatorDistInfo
}
del := k.stakingKeeper.Delegation(ctx, delAddr, valAddr)
if del == nil {
return nil, types.ErrEmptyDelegationDistInfo
}
// withdraw rewards
rewards, err := k.withdrawDelegationRewards(ctx, val, del)
if err != nil {
return nil, err
}
if rewards.IsZero() {
baseDenom, _ := sdk.GetBaseDenom()
rewards = sdk.Coins{sdk.Coin{
Denom: baseDenom,
Amount: sdk.ZeroInt(),
}}
}
if err = k.AfterWithdrawDelegationRewards(ctx, delAddr, valAddr, rewards); err != nil {
return nil, err
}
// reinitialize the delegation
k.initializeDelegation(ctx, valAddr, delAddr)
return rewards, nil
}
func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val stakingtypes.ValidatorI, del stakingtypes.DelegationI) (sdk.Coins, error) {
// check existence of delegator starting info
if !k.HasDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr()) {
return nil, types.ErrEmptyDelegationDistInfo
}
// end current period and calculate rewards
endingPeriod := k.IncrementValidatorPeriod(ctx, val)
rewardsRaw := k.CalculateDelegationRewards(ctx, val, del, endingPeriod)
outstanding := k.GetValidatorOutstandingRewardsCoins(ctx, del.GetValidatorAddr())
// defensive edge case may happen on the very final digits
// of the decCoins due to operation order of the distribution mechanism.
rewards := rewardsRaw.Intersect(outstanding)
if !rewards.IsEqual(rewardsRaw) {
logger := k.Logger(ctx)
logger.Info(
"rounding error withdrawing rewards from validator",
"delegator", del.GetDelegatorAddr().String(),
"validator", val.GetOperator().String(),
"got", rewards.String(),
"expected", rewardsRaw.String(),
)
}
// truncate reward dec coins, return remainder to community pool
finalRewards, remainder := rewards.TruncateDecimal()
// add coins to user account
if !finalRewards.IsZero() {
withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, del.GetDelegatorAddr())
err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, withdrawAddr, finalRewards)
if err != nil {
return nil, err
}
}
// update the outstanding rewards and the community pool only if the
// transaction was successful
k.SetValidatorOutstandingRewards(ctx, del.GetValidatorAddr(), types.ValidatorOutstandingRewards{Rewards: outstanding.Sub(rewards)})
feePool := k.GetFeePool(ctx)
feePool.CommunityPool = feePool.CommunityPool.Add(remainder...)
k.SetFeePool(ctx, feePool)
// decrement reference count of starting period
startingInfo := k.GetDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr())
startingPeriod := startingInfo.PreviousPeriod
k.decrementReferenceCount(ctx, del.GetValidatorAddr(), startingPeriod)
// remove delegator starting info
k.DeleteDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr())
emittedRewards := finalRewards
if finalRewards.IsZero() {
baseDenom, _ := sdk.GetBaseDenom()
if baseDenom == "" {
baseDenom = sdk.DefaultBondDenom
}
// Note, we do not call the NewCoins constructor as we do not want the zero
// coin removed.
emittedRewards = sdk.Coins{sdk.NewCoin(baseDenom, sdk.ZeroInt())}
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeWithdrawRewards,
sdk.NewAttribute(sdk.AttributeKeyAmount, emittedRewards.String()),
sdk.NewAttribute(types.AttributeKeyValidator, val.GetOperator().String()),
),
)
return finalRewards, nil
}
// decrement the reference count for a historical rewards value, and delete if zero references remain
func (k Keeper) decrementReferenceCount(ctx sdk.Context, valAddr sdk.ValAddress, period uint64) {
historical := k.GetValidatorHistoricalRewards(ctx, valAddr, period)
if historical.ReferenceCount == 0 {
panic("cannot set negative reference count")
}
historical.ReferenceCount--
if historical.ReferenceCount == 0 {
k.DeleteValidatorHistoricalReward(ctx, valAddr, period)
} else {
k.SetValidatorHistoricalRewards(ctx, valAddr, period, historical)
}
}
// initialize starting info for a new delegation
func (k Keeper) initializeDelegation(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress) {
// period has already been incremented - we want to store the period ended by this delegation action
previousPeriod := k.GetValidatorCurrentRewards(ctx, val).Period - 1
// increment reference count for the period we're going to track
k.incrementReferenceCount(ctx, val, previousPeriod)
validator := k.stakingKeeper.Validator(ctx, val)
delegation := k.stakingKeeper.Delegation(ctx, del, val)
// calculate delegation stake in tokens
// we don't store directly, so multiply delegation shares * (tokens per share)
// note: necessary to truncate so we don't allow withdrawing more rewards than owed
stake := validator.TokensFromSharesTruncated(delegation.GetShares())
k.SetDelegatorStartingInfo(ctx, val, del, types.NewDelegatorStartingInfo(previousPeriod, stake, uint64(ctx.BlockHeight())))
}
// increment the reference count for a historical rewards value
func (k Keeper) incrementReferenceCount(ctx sdk.Context, valAddr sdk.ValAddress, period uint64) {
historical := k.GetValidatorHistoricalRewards(ctx, valAddr, period)
if historical.ReferenceCount > 2 {
panic("reference count should never exceed 2")
}
historical.ReferenceCount++
k.SetValidatorHistoricalRewards(ctx, valAddr, period, historical)
}