mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-27 08:45:19 +00:00
0efe7f2281
* add annualized_reward query proto * use sdkmath.LegacyDec to match RPS param... * add AnnualizedRewards grpc query * add changelog entry * simplify calculation & expand test cases
99 lines
3.9 KiB
Go
99 lines
3.9 KiB
Go
package keeper
|
|
|
|
import (
|
|
"time"
|
|
|
|
sdkmath "cosmossdk.io/math"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
|
"github.com/kava-labs/kava/x/community/types"
|
|
)
|
|
|
|
const nanosecondsInOneSecond = int64(1000000000)
|
|
|
|
// PayoutAccumulatedStakingRewards calculates and transfers taking rewards to the fee collector address
|
|
func (k Keeper) PayoutAccumulatedStakingRewards(ctx sdk.Context) {
|
|
// get module parameters which define the amount of rewards to payout per second
|
|
params := k.mustGetParams(ctx)
|
|
currentBlockTime := ctx.BlockTime()
|
|
state := k.GetStakingRewardsState(ctx)
|
|
|
|
// we have un-initialized state -- set accumulation time and exit since there is nothing to do
|
|
if state.LastAccumulationTime.IsZero() {
|
|
state.LastAccumulationTime = currentBlockTime
|
|
|
|
k.SetStakingRewardsState(ctx, state)
|
|
|
|
return
|
|
}
|
|
|
|
// get the denom for staking
|
|
stakingRewardDenom := k.stakingKeeper.BondDenom(ctx)
|
|
|
|
// we fetch the community pool balance to ensure only accumulate rewards up to the current balance
|
|
communityPoolBalance := sdkmath.LegacyNewDecFromInt(k.bankKeeper.GetBalance(ctx, k.moduleAddress, stakingRewardDenom).Amount)
|
|
|
|
// calculate staking reward payout capped to community pool balance
|
|
truncatedRewards, truncationError := calculateStakingRewards(
|
|
currentBlockTime,
|
|
state.LastAccumulationTime,
|
|
state.LastTruncationError,
|
|
params.StakingRewardsPerSecond,
|
|
communityPoolBalance,
|
|
)
|
|
|
|
// only payout if the truncated rewards are non-zero
|
|
if !truncatedRewards.IsZero() {
|
|
transferAmount := sdk.NewCoins(sdk.NewCoin(stakingRewardDenom, truncatedRewards))
|
|
|
|
if err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleAccountName, authtypes.FeeCollectorName, transferAmount); err != nil {
|
|
// we check for a valid balance and rewards can never be negative so panic since this will only
|
|
// occur in cases where the chain is running in an invalid state
|
|
panic(err)
|
|
}
|
|
|
|
// emit event with amount transferred
|
|
ctx.EventManager().EmitEvent(
|
|
sdk.NewEvent(
|
|
types.EventTypeStakingRewardsPaid,
|
|
sdk.NewAttribute(types.AttributeKeyStakingRewardAmount, transferAmount.String()),
|
|
),
|
|
)
|
|
|
|
}
|
|
|
|
// update accumulation state
|
|
state.LastAccumulationTime = currentBlockTime
|
|
// if the community pool balance is zero, this also resets the truncation error
|
|
state.LastTruncationError = truncationError
|
|
|
|
// save state
|
|
k.SetStakingRewardsState(ctx, state)
|
|
}
|
|
|
|
// calculateStakingRewards takes the currentBlockTime, state of last accumulation, rewards per second, and the community pool balance
|
|
// in order to calculate the total payout since the last accumulation time. It returns the truncated payout amount and the truncation error.
|
|
func calculateStakingRewards(currentBlockTime, lastAccumulationTime time.Time, lastTruncationError, stakingRewardsPerSecond, communityPoolBalance sdkmath.LegacyDec) (sdkmath.Int, sdkmath.LegacyDec) {
|
|
// we get the duration since we last accumulated, then use nanoseconds for full precision available
|
|
durationSinceLastPayout := currentBlockTime.Sub(lastAccumulationTime)
|
|
nanosecondsSinceLastPayout := sdkmath.LegacyNewDec(durationSinceLastPayout.Nanoseconds())
|
|
|
|
// We multiply by nanoseconds first, then divide by conversion to avoid loss of precision.
|
|
// This multiplication is also tested against very large values so we are safe from overflow
|
|
// in normal operations.
|
|
accumulatedRewards := nanosecondsSinceLastPayout.Mul(stakingRewardsPerSecond).QuoInt64(nanosecondsInOneSecond)
|
|
// Ensure we add any error from previous truncations
|
|
accumulatedRewards = accumulatedRewards.Add(lastTruncationError)
|
|
|
|
if communityPoolBalance.LT(accumulatedRewards) {
|
|
accumulatedRewards = communityPoolBalance
|
|
}
|
|
|
|
// we truncate since we can only transfer whole units
|
|
truncatedRewards := accumulatedRewards.TruncateDec()
|
|
// the truncation error to carry over to the next accumulation
|
|
truncationError := accumulatedRewards.Sub(truncatedRewards)
|
|
|
|
return truncatedRewards.TruncateInt(), truncationError
|
|
}
|