diff --git a/x/distribution/handler.go b/x/distribution/handler.go index 1ee7557b163..3203736257f 100644 --- a/x/distribution/handler.go +++ b/x/distribution/handler.go @@ -107,7 +107,7 @@ func NewCommunityPoolSpendProposalHandler(k Keeper) govtypes.Handler { return keeper.HandleCommunityPoolSpendProposal(ctx, k, c) default: - return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized distr proposal content type: %T", c) + return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s proposal content type: %T", types.ModuleName, c) } } } diff --git a/x/distribution/keeper/allocation.go b/x/distribution/keeper/allocation.go index 4495cfe6a1e..5ded1ee0f09 100644 --- a/x/distribution/keeper/allocation.go +++ b/x/distribution/keeper/allocation.go @@ -77,24 +77,48 @@ func (k Keeper) AllocateTokens( previousProposer.String())) } - // calculate fraction allocated to validators communityTax := k.GetCommunityTax(ctx) voteMultiplier := sdk.OneDec().Sub(proposerMultiplier).Sub(communityTax) - // allocate tokens proportionally to voting power - // TODO consider parallelizing later, ref https://github.com/enigmampc/cosmos-sdk/pull/3099#discussion_r246276376 + foundationTax := k.GetSecretFoundationTax(ctx) + foundationTaxAddr := k.GetSecretFoundationAddr(ctx) + + // only apply the secret foundation tax when the tax and address is non-zero + var foundationTaxSum sdk.DecCoins + if !foundationTax.IsZero() && !foundationTaxAddr.Empty() { + voteMultiplier = voteMultiplier.Sub(foundationTax) + + foundationTaxSum = feesCollected.MulDecTruncate(foundationTax) + remaining = remaining.Sub(foundationTaxSum) + } + + // allocate tokens proportionally to voting power minus any taxes for _, vote := range previousVotes { validator := k.stakingKeeper.ValidatorByConsAddr(ctx, vote.Validator.Address) - // TODO consider microslashing for missing votes. - // ref https://github.com/enigmampc/cosmos-sdk/issues/2525#issuecomment-430838701 powerFraction := sdk.NewDec(vote.Validator.Power).QuoTruncate(sdk.NewDec(totalPreviousPower)) reward := feesCollected.MulDecTruncate(voteMultiplier).MulDecTruncate(powerFraction) + + // allocate tokens to the validator k.AllocateTokensToValidator(ctx, validator, reward) + + // update the remaining allocation for the community pool remaining = remaining.Sub(reward) } - // allocate community funding + // Send the foundation tax sum to the foundation tax address. Note, the taxes + // collected are decimals and when coverted to integer coins, we must truncate. + // The remainder is given back to the community pool. + if !foundationTaxSum.IsZero() { + foundationTaxSumTrunc, rem := foundationTaxSum.TruncateDecimal() + remaining = remaining.Add(rem...) + + if err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, foundationTaxAddr, foundationTaxSumTrunc); err != nil { + panic(err) + } + } + + // allocate community funding minus the foundation tax feePool.CommunityPool = feePool.CommunityPool.Add(remaining...) k.SetFeePool(ctx, feePool) } diff --git a/x/distribution/keeper/params.go b/x/distribution/keeper/params.go index 8b543f2e2f9..8bba46de6d0 100644 --- a/x/distribution/keeper/params.go +++ b/x/distribution/keeper/params.go @@ -41,3 +41,15 @@ func (k Keeper) GetWithdrawAddrEnabled(ctx sdk.Context) (enabled bool) { k.paramSpace.Get(ctx, types.ParamStoreKeyWithdrawAddrEnabled, &enabled) return enabled } + +// GetSecretFoundationTax returns the current secret foundation tax. +func (k Keeper) GetSecretFoundationTax(ctx sdk.Context) (tax sdk.Dec) { + k.paramSpace.Get(ctx, types.ParamSecretFoundationTax, &tax) + return tax +} + +// GetSecretFoundationAddr returns the current secret foundation address. +func (k Keeper) GetSecretFoundationAddr(ctx sdk.Context) (addr sdk.AccAddress) { + k.paramSpace.Get(ctx, types.ParamSecretFoundationAddress, &addr) + return addr +} diff --git a/x/distribution/simulation/genesis.go b/x/distribution/simulation/genesis.go index 2c5ddf4e5f2..4999bb0afa7 100644 --- a/x/distribution/simulation/genesis.go +++ b/x/distribution/simulation/genesis.go @@ -11,6 +11,7 @@ import ( sdk "github.com/enigmampc/cosmos-sdk/types" "github.com/enigmampc/cosmos-sdk/types/module" "github.com/enigmampc/cosmos-sdk/x/distribution/types" + "github.com/enigmampc/cosmos-sdk/x/simulation" ) // Simulation parameter constants @@ -19,6 +20,7 @@ const ( BaseProposerReward = "base_proposer_reward" BonusProposerReward = "bonus_proposer_reward" WithdrawEnabled = "withdraw_enabled" + FoundationTax = "foundation_tax" ) // GenCommunityTax randomized CommunityTax @@ -41,6 +43,11 @@ func GenWithdrawEnabled(r *rand.Rand) bool { return r.Int63n(101) <= 95 // 95% chance of withdraws being enabled } +// GenSecretFoundationTax returns a randomized secret foundation tax parameter. +func GenSecretFoundationTax(r *rand.Rand) sdk.Dec { + return sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(int64(r.Intn(30)), 2)) +} + // RandomizedGenState generates a random GenesisState for distribution func RandomizedGenState(simState *module.SimulationState) { var communityTax sdk.Dec @@ -67,13 +74,23 @@ func RandomizedGenState(simState *module.SimulationState) { func(r *rand.Rand) { withdrawEnabled = GenWithdrawEnabled(r) }, ) + var foundationTax sdk.Dec + simState.AppParams.GetOrGenerate( + simState.Cdc, FoundationTax, &foundationTax, simState.Rand, + func(r *rand.Rand) { foundationTax = GenSecretFoundationTax(r) }, + ) + + foundationTaxAcc, _ := simulation.RandomAcc(simState.Rand, simState.Accounts) + distrGenesis := types.GenesisState{ FeePool: types.InitialFeePool(), Params: types.Params{ - CommunityTax: communityTax, - BaseProposerReward: baseProposerReward, - BonusProposerReward: bonusProposerReward, - WithdrawAddrEnabled: withdrawEnabled, + CommunityTax: communityTax, + SecretFoundationTax: foundationTax, + SecretFoundationAddress: foundationTaxAcc.Address, + BaseProposerReward: baseProposerReward, + BonusProposerReward: bonusProposerReward, + WithdrawAddrEnabled: withdrawEnabled, }, } diff --git a/x/distribution/simulation/params.go b/x/distribution/simulation/params.go index a4cd9f95370..a888396a427 100644 --- a/x/distribution/simulation/params.go +++ b/x/distribution/simulation/params.go @@ -14,10 +14,11 @@ const ( keyCommunityTax = "communitytax" keyBaseProposerReward = "baseproposerreward" keyBonusProposerReward = "bonusproposerreward" + keySecretFoundationTax = "secretfoundationtax" ) -// ParamChanges defines the parameters that can be modified by param change proposals -// on the simulation +// ParamChanges defines the parameters that can be modified by param change +// proposals in simulations. func ParamChanges(r *rand.Rand) []simulation.ParamChange { return []simulation.ParamChange{ simulation.NewSimParamChange(types.ModuleName, keyCommunityTax, @@ -25,6 +26,11 @@ func ParamChanges(r *rand.Rand) []simulation.ParamChange { return fmt.Sprintf("\"%s\"", GenCommunityTax(r)) }, ), + simulation.NewSimParamChange(types.ModuleName, keySecretFoundationTax, + func(r *rand.Rand) string { + return fmt.Sprintf("\"%s\"", GenSecretFoundationTax(r)) + }, + ), simulation.NewSimParamChange(types.ModuleName, keyBaseProposerReward, func(r *rand.Rand) string { return fmt.Sprintf("\"%s\"", GenBaseProposerReward(r)) diff --git a/x/distribution/types/genesis.go b/x/distribution/types/genesis.go index cc8f73a3bf3..a3d6dcbe215 100644 --- a/x/distribution/types/genesis.go +++ b/x/distribution/types/genesis.go @@ -66,9 +66,16 @@ type GenesisState struct { } func NewGenesisState( - params Params, fp FeePool, dwis []DelegatorWithdrawInfo, pp sdk.ConsAddress, r []ValidatorOutstandingRewardsRecord, - acc []ValidatorAccumulatedCommissionRecord, historical []ValidatorHistoricalRewardsRecord, - cur []ValidatorCurrentRewardsRecord, dels []DelegatorStartingInfoRecord, slashes []ValidatorSlashEventRecord, + params Params, + fp FeePool, + dwis []DelegatorWithdrawInfo, + pp sdk.ConsAddress, + r []ValidatorOutstandingRewardsRecord, + acc []ValidatorAccumulatedCommissionRecord, + historical []ValidatorHistoricalRewardsRecord, + cur []ValidatorCurrentRewardsRecord, + dels []DelegatorStartingInfoRecord, + slashes []ValidatorSlashEventRecord, ) GenesisState { return GenesisState{ diff --git a/x/distribution/types/params.go b/x/distribution/types/params.go index 212786b4ac9..9fb9bfebc5a 100644 --- a/x/distribution/types/params.go +++ b/x/distribution/types/params.go @@ -20,14 +20,18 @@ var ( ParamStoreKeyBaseProposerReward = []byte("baseproposerreward") ParamStoreKeyBonusProposerReward = []byte("bonusproposerreward") ParamStoreKeyWithdrawAddrEnabled = []byte("withdrawaddrenabled") + ParamSecretFoundationTax = []byte("secretfoundationtax") + ParamSecretFoundationAddress = []byte("secretfoundationaddress") ) // Params defines the set of distribution parameters. type Params struct { - CommunityTax sdk.Dec `json:"community_tax" yaml:"community_tax"` - BaseProposerReward sdk.Dec `json:"base_proposer_reward" yaml:"base_proposer_reward"` - BonusProposerReward sdk.Dec `json:"bonus_proposer_reward" yaml:"bonus_proposer_reward"` - WithdrawAddrEnabled bool `json:"withdraw_addr_enabled" yaml:"withdraw_addr_enabled"` + CommunityTax sdk.Dec `json:"community_tax" yaml:"community_tax"` + BaseProposerReward sdk.Dec `json:"base_proposer_reward" yaml:"base_proposer_reward"` + BonusProposerReward sdk.Dec `json:"bonus_proposer_reward" yaml:"bonus_proposer_reward"` + WithdrawAddrEnabled bool `json:"withdraw_addr_enabled" yaml:"withdraw_addr_enabled"` + SecretFoundationTax sdk.Dec `json:"secret_foundation_tax" yaml:"secret_foundation_tax"` + SecretFoundationAddress sdk.AccAddress `json:"secret_foundation_address" yaml:"secret_foundation_address"` } // ParamKeyTable returns the parameter key table. @@ -38,10 +42,12 @@ func ParamKeyTable() params.KeyTable { // DefaultParams returns default distribution parameters func DefaultParams() Params { return Params{ - CommunityTax: sdk.NewDecWithPrec(2, 2), // 2% - BaseProposerReward: sdk.NewDecWithPrec(1, 2), // 1% - BonusProposerReward: sdk.NewDecWithPrec(4, 2), // 4% - WithdrawAddrEnabled: true, + CommunityTax: sdk.NewDecWithPrec(2, 2), // 2% + SecretFoundationTax: sdk.ZeroDec(), // 0% + SecretFoundationAddress: sdk.AccAddress{}, + BaseProposerReward: sdk.NewDecWithPrec(1, 2), // 1% + BonusProposerReward: sdk.NewDecWithPrec(4, 2), // 4% + WithdrawAddrEnabled: true, } } @@ -57,6 +63,8 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs { params.NewParamSetPair(ParamStoreKeyBaseProposerReward, &p.BaseProposerReward, validateBaseProposerReward), params.NewParamSetPair(ParamStoreKeyBonusProposerReward, &p.BonusProposerReward, validateBonusProposerReward), params.NewParamSetPair(ParamStoreKeyWithdrawAddrEnabled, &p.WithdrawAddrEnabled, validateWithdrawAddrEnabled), + params.NewParamSetPair(ParamSecretFoundationTax, &p.SecretFoundationTax, validateSecretFoundationTax), + params.NewParamSetPair(ParamSecretFoundationAddress, &p.SecretFoundationAddress, validateSecretFoundationAddress), } } @@ -67,6 +75,11 @@ func (p Params) ValidateBasic() error { "community tax should non-negative and less than one: %s", p.CommunityTax, ) } + if p.SecretFoundationTax.IsNegative() || p.SecretFoundationTax.GT(sdk.OneDec()) { + return fmt.Errorf( + "secret foundation tax should non-negative and less than one: %s", p.SecretFoundationTax, + ) + } if p.BaseProposerReward.IsNegative() { return fmt.Errorf( "base proposer reward should be positive: %s", p.BaseProposerReward, @@ -105,6 +118,38 @@ func validateCommunityTax(i interface{}) error { return nil } +func validateSecretFoundationTax(i interface{}) error { + v, ok := i.(sdk.Dec) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if v.IsNil() { + return fmt.Errorf("secret foundation tax must be not nil") + } + if v.IsNegative() { + return fmt.Errorf("secret foundation tax must be positive: %s", v) + } + if v.GT(sdk.OneDec()) { + return fmt.Errorf("secret foundation tax too large: %s", v) + } + + return nil +} + +func validateSecretFoundationAddress(i interface{}) error { + v, ok := i.(sdk.AccAddress) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if !v.Empty() { + return sdk.VerifyAddressFormat(v) + } + + return nil +} + func validateBaseProposerReward(i interface{}) error { v, ok := i.(sdk.Dec) if !ok {