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

R4R: Round down when calculating rewards (fixes F1 sim bug) #3359

Merged
merged 4 commits into from
Jan 23, 2019
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
1 change: 1 addition & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ BREAKING CHANGES
* [\#1894](https://github.com/cosmos/cosmos-sdk/pull/1894) `version` command now shows latest commit, vendor dir hash, and build machine info.

* SDK
* [distribution] \#3359 Always round down when calculating rewards-to-be-withdrawn in F1 fee distribution
* [staking] \#2513 Validator power type from Dec -> Int
* [staking] \#3233 key and value now contain duplicate fields to simplify code
* [\#3064](https://github.com/cosmos/cosmos-sdk/issues/3064) Sanitize `sdk.Coin` denom. Coins denoms are now case insensitive, i.e. 100fooToken equals to 100FOOTOKEN.
Expand Down
26 changes: 26 additions & 0 deletions types/dec_coin.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,19 @@ func (coins DecCoins) MulDec(d Dec) DecCoins {
return res
}

// multiply all the coins by a decimal, truncating
func (coins DecCoins) MulDecTruncate(d Dec) DecCoins {
res := make([]DecCoin, len(coins))
for i, coin := range coins {
product := DecCoin{
Denom: coin.Denom,
Amount: coin.Amount.MulTruncate(d),
}
res[i] = product
}
return res
}

// divide all the coins by a decimal
func (coins DecCoins) QuoDec(d Dec) DecCoins {
res := make([]DecCoin, len(coins))
Expand All @@ -214,6 +227,19 @@ func (coins DecCoins) QuoDec(d Dec) DecCoins {
return res
}

// divide all the coins by a decimal, truncating
func (coins DecCoins) QuoDecTruncate(d Dec) DecCoins {
res := make([]DecCoin, len(coins))
for i, coin := range coins {
quotient := DecCoin{
Denom: coin.Denom,
Amount: coin.Amount.QuoTruncate(d),
}
res[i] = quotient
}
return res
}

// returns the amount of a denom from deccoins
func (coins DecCoins) AmountOf(denom string) Dec {
switch len(coins) {
Expand Down
27 changes: 27 additions & 0 deletions types/decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,17 @@ func (d Dec) Mul(d2 Dec) Dec {
return Dec{chopped}
}

// multiplication truncate
func (d Dec) MulTruncate(d2 Dec) Dec {
mul := new(big.Int).Mul(d.Int, d2.Int)
chopped := chopPrecisionAndTruncate(mul)

if chopped.BitLen() > 255+DecimalPrecisionBits {
panic("Int overflow")
}
return Dec{chopped}
}

// multiplication
func (d Dec) MulInt(i Int) Dec {
mul := new(big.Int).Mul(d.Int, i.i)
Expand All @@ -254,6 +265,22 @@ func (d Dec) Quo(d2 Dec) Dec {
return Dec{chopped}
}

// quotient truncate
func (d Dec) QuoTruncate(d2 Dec) Dec {

// multiply precision twice
mul := new(big.Int).Mul(d.Int, precisionReuse)
mul.Mul(mul, precisionReuse)

quo := new(big.Int).Quo(mul, d2.Int)
chopped := chopPrecisionAndTruncate(quo)

if chopped.BitLen() > 255+DecimalPrecisionBits {
panic("Int overflow")
}
return Dec{chopped}
}

// quotient
func (d Dec) QuoInt(i Int) Dec {
mul := new(big.Int).Quo(d.Int, i.i)
Expand Down
11 changes: 7 additions & 4 deletions x/distribution/keeper/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ func (k Keeper) initializeDelegation(ctx sdk.Context, val sdk.ValAddress, del sd

// calculate delegation stake in tokens
// we don't store directly, so multiply delegation shares * (tokens per share)
stake := delegation.GetShares().Mul(validator.GetDelegatorShareExRate())
// note: necessary to truncate so we don't allow withdrawing more rewards than owed
stake := delegation.GetShares().MulTruncate(validator.GetDelegatorShareExRate())
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
k.SetDelegatorStartingInfo(ctx, val, del, types.NewDelegatorStartingInfo(previousPeriod, stake, uint64(ctx.BlockHeight())))
}

// calculate the rewards accrued by a delegation between two periods
func (k Keeper) calculateDelegationRewardsBetween(ctx sdk.Context, val sdk.Validator,
startingPeriod, endingPeriod uint64, staking sdk.Dec) (rewards sdk.DecCoins) {
startingPeriod, endingPeriod uint64, stake sdk.Dec) (rewards sdk.DecCoins) {
// sanity check
if startingPeriod > endingPeriod {
panic("startingPeriod cannot be greater than endingPeriod")
Expand All @@ -32,7 +33,8 @@ func (k Keeper) calculateDelegationRewardsBetween(ctx sdk.Context, val sdk.Valid
starting := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), startingPeriod)
ending := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), endingPeriod)
difference := ending.CumulativeRewardRatio.Minus(starting.CumulativeRewardRatio)
rewards = difference.MulDec(staking)
// note: necessary to truncate so we don't allow withdrawing more rewards than owed
rewards = difference.MulDecTruncate(stake)
return
}

Expand All @@ -54,7 +56,8 @@ func (k Keeper) calculateDelegationRewards(ctx sdk.Context, val sdk.Validator, d
func(height uint64, event types.ValidatorSlashEvent) (stop bool) {
endingPeriod := event.ValidatorPeriod
rewards = rewards.Plus(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake))
stake = stake.Mul(sdk.OneDec().Sub(event.Fraction))
// note: necessary to truncate so we don't allow withdrawing more rewards than owed
stake = stake.MulTruncate(sdk.OneDec().Sub(event.Fraction))
startingPeriod = endingPeriod
return false
},
Expand Down
3 changes: 2 additions & 1 deletion x/distribution/keeper/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ func (k Keeper) incrementValidatorPeriod(ctx sdk.Context, val sdk.Validator) uin

current = sdk.DecCoins{}
} else {
current = rewards.Rewards.QuoDec(sdk.NewDecFromInt(val.GetTokens()))
// note: necessary to truncate so we don't allow withdrawing more rewards than owed
current = rewards.Rewards.QuoDecTruncate(sdk.NewDecFromInt(val.GetTokens()))
}

// fetch historical rewards for last period
Expand Down