diff --git a/x/nodes/keeper/params.go b/x/nodes/keeper/params.go index 7dbe25b44..d6b3e80c2 100644 --- a/x/nodes/keeper/params.go +++ b/x/nodes/keeper/params.go @@ -164,6 +164,25 @@ func (k Keeper) splitRewards( return } +// Split feeCollector's cut into DAO and Propower +func (k Keeper) splitFeesCollected( + ctx sdk.Ctx, + feesCollected sdk.BigInt, +) (daoCut, proposerCut sdk.BigInt) { + daoAllocation := sdk.NewDec(k.DAOAllocation(ctx)) + proposerAllocation := sdk.NewDec(k.ProposerAllocation(ctx)) + + // get the new percentages of `dao / (dao + proposer)` + daoAllocation = daoAllocation.Quo(daoAllocation.Add(proposerAllocation)) + + // dao cut calculation truncates int ex: 1.99uPOKT = 1uPOKT + daoCut = feesCollected.ToDec().Mul(daoAllocation).TruncateInt() + + // proposer is whatever is left + proposerCut = feesCollected.Sub(daoCut) + return +} + // DAOAllocation - Retrieve DAO allocation func (k Keeper) DAOAllocation(ctx sdk.Ctx) (res int64) { k.Paramstore.Get(ctx, types.KeyDAOAllocation, &res) diff --git a/x/nodes/keeper/reward.go b/x/nodes/keeper/reward.go index 98b4bb4ed..78c1b880b 100644 --- a/x/nodes/keeper/reward.go +++ b/x/nodes/keeper/reward.go @@ -184,38 +184,62 @@ func (k Keeper) GetChainSpecificMultiplier(ctx sdk.Ctx, chain string) sdk.BigInt func (k Keeper) blockReward(ctx sdk.Ctx, previousProposer sdk.Address) { feesCollector := k.getFeePool(ctx) feesCollected := feesCollector.GetCoins().AmountOf(sdk.DefaultStakeDenom) - // check for zero fees if feesCollected.IsZero() { return } - // get the dao and proposer % ex DAO .1 or 10% Proposer .01 or 1% - daoAllocation := sdk.NewDec(k.DAOAllocation(ctx)) - proposerAllocation := sdk.NewDec(k.ProposerAllocation(ctx)) - daoAndProposerAllocation := daoAllocation.Add(proposerAllocation) - // get the new percentages based on the total. This is needed because the node (relayer) cut has already been allocated - daoAllocation = daoAllocation.Quo(daoAndProposerAllocation) - // dao cut calculation truncates int ex: 1.99uPOKT = 1uPOKT - daoCut := feesCollected.ToDec().Mul(daoAllocation).TruncateInt() - // proposer is whatever is left - proposerCut := feesCollected.Sub(daoCut) + + daoCut, proposerCut := k.splitFeesCollected(ctx, feesCollected) + // send to the two parties feeAddr := feesCollector.GetAddress() err := k.AccountKeeper.SendCoinsFromAccountToModule(ctx, feeAddr, govTypes.DAOAccountName, sdk.NewCoins(sdk.NewCoin(sdk.DefaultStakeDenom, daoCut))) if err != nil { - ctx.Logger().Error(fmt.Sprintf("unable to send %s cut of block reward to the dao: %s, at height %d", daoCut.String(), err.Error(), ctx.BlockHeight())) + ctx.Logger().Error("unable to send a DAO cut of block reward", + "height", ctx.BlockHeight(), + "cut", daoCut, + "err", err.Error(), + ) } + if k.Cdc.IsAfterNonCustodialUpgrade(ctx.BlockHeight()) { - outputAddress, found := k.GetValidatorOutputAddress(ctx, previousProposer) + validator, found := k.GetValidator(ctx, previousProposer) if !found { - ctx.Logger().Error(fmt.Sprintf("unable to send %s cut of block reward to the proposer: %s, with error %s, at height %d", proposerCut.String(), previousProposer, types.ErrNoValidatorForAddress(types.ModuleName), ctx.BlockHeight())) + ctx.Logger().Error("unable to find a validator to send a block reward to", + "height", ctx.BlockHeight(), + "addr", previousProposer, + ) return } - err = k.AccountKeeper.SendCoins(ctx, feeAddr, outputAddress, sdk.NewCoins(sdk.NewCoin(sdk.DefaultStakeDenom, proposerCut))) - if err != nil { - ctx.Logger().Error(fmt.Sprintf("unable to send %s cut of block reward to the proposer: %s, with error %s, at height %d", proposerCut.String(), previousProposer, err.Error(), ctx.BlockHeight())) + + if !k.Cdc.IsAfterDelegatorUpgrade(ctx.BlockHeight()) { + validator.Delegators = nil } + + SplitNodeRewards( + proposerCut, + k.GetOutputAddressFromValidator(validator), + validator.Delegators, + func(recipient sdk.Address, share sdk.BigInt) { + err = k.AccountKeeper.SendCoins( + ctx, + feeAddr, + recipient, + sdk.NewCoins(sdk.NewCoin(sdk.DefaultStakeDenom, share)), + ) + if err != nil { + ctx.Logger().Error("unable to send a cut of block reward", + "height", ctx.BlockHeight(), + "cut", share, + "addr", recipient, + "err", err.Error(), + ) + } + }, + ) + return } + err = k.AccountKeeper.SendCoins(ctx, feeAddr, previousProposer, sdk.NewCoins(sdk.NewCoin(sdk.DefaultStakeDenom, proposerCut))) if err != nil { ctx.Logger().Error(fmt.Sprintf("unable to send %s cut of block reward to the proposer: %s, with error %s, at height %d", proposerCut.String(), previousProposer, err.Error(), ctx.BlockHeight())) diff --git a/x/nodes/keeper/reward_test.go b/x/nodes/keeper/reward_test.go index dd616a394..41eb12cf3 100644 --- a/x/nodes/keeper/reward_test.go +++ b/x/nodes/keeper/reward_test.go @@ -142,6 +142,13 @@ func TestKeeper_rewardFromFees(t *testing.T) { fp = keeper.getFeePool(context) keeper.SetValidator(context, stakedValidator) assert.Equal(t, fees, fp.GetCoins()) + + _, proposerCut := keeper.splitFeesCollected(context, amount) + + totalSupplyPrev := keeper.AccountKeeper.GetSupply(context). + GetTotal(). + AmountOf("upokt") + tests := []struct { name string fields fields @@ -159,11 +166,14 @@ func TestKeeper_rewardFromFees(t *testing.T) { k := tt.fields.keeper ctx := tt.args.ctx k.blockReward(tt.args.ctx, tt.args.previousProposer) - acc := k.GetAccount(ctx, tt.args.Output) - assert.False(t, acc.Coins.IsZero()) - assert.True(t, acc.Coins.IsEqual(sdk.NewCoins(sdk.NewCoin("upokt", sdk.NewInt(910))))) - acc = k.GetAccount(ctx, tt.args.previousProposer) - assert.True(t, acc.Coins.IsZero()) + + verifyAccountBalance(t, k, ctx, tt.args.Output, proposerCut) + verifyAccountBalance(t, k, ctx, tt.args.previousProposer, sdk.ZeroInt()) + + totalSupply := k.AccountKeeper.GetSupply(ctx). + GetTotal(). + AmountOf("upokt") + assert.True(t, totalSupply.Equal(totalSupplyPrev)) }) } }