mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-10-06 01:15:17 +00:00
102cc0fff3
* add new field upgrade_time_set_staking_rewards_per_second with intention of integrating into the disable inflation logic to set an initial staking reward time * when the disable inflation upgrade time occurs, set the staking rewards per second to the value specified by the new upgrade_time_set_staking_rewards_per_second. This will allow a decoupled implementation between the ugprade switching logic, and the core functionality of paying staking rewards from the pool * add staking rewards state to community keeper and community module genesis that is required to calculate and track staking reward payouts accross blocks * add implementation of staking reward payouts * remove unused error * touch up tests and add a test case that fully tests behavior when pool is drained * add function comments * refactor and pull out main calculation to private pure function with no dependence on keeper * zero out default parameters -- these are too chain specific to have useful defaults * small touch ups on comments, test cases * use correct Int from sdkmath, not old sdk types; update protonet genesis for new parmater * fix copy pasta comment * use bond denom from staking keeper instead of referncing ukava directly * add staking reward state for valid genesis * update kvtool genesis for new params and rewards state
92 lines
3.7 KiB
Go
92 lines
3.7 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)
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
|
|
return
|
|
}
|
|
|
|
// calculateStakingRewards takees 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 multiplicaiton 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
|
|
}
|