Currently, the native x/distribution
module within the Cosmos SDK contains support
for a built-in "tax", which is tunable via a ParamChangeProposal
, that is
distributed to what is known as a "community pool". The community pool can have
funds additionally sent to it via altruistic actors. However, the main purpose of
the community pool is to be source of funds to help aid in development and progression
of the Cosmos ecosystem. See this excellent post
for more detailed information on the community pool.
The exact means in which distributed tokens are taxed can be seen as follows:
func (k Keeper) AllocateTokens(
ctx sdk.Context, sumPreviousPrecommitPower, totalPreviousPower int64,
previousProposer sdk.ConsAddress, previousVotes []abci.VoteInfo,
) {
// ...
// calculate fraction votes
previousFractionVotes := sdk.NewDec(sumPreviousPrecommitPower).Quo(sdk.NewDec(totalPreviousPower))
// calculate previous proposer reward
baseProposerReward := k.GetBaseProposerReward(ctx)
bonusProposerReward := k.GetBonusProposerReward(ctx)
proposerMultiplier := baseProposerReward.Add(bonusProposerReward.MulTruncate(previousFractionVotes))
proposerReward := feesCollected.MulDecTruncate(proposerMultiplier)
// pay previous proposer
remaining := feesCollected
proposerValidator := k.stakingKeeper.ValidatorByConsAddr(ctx, previousProposer)
// calculate fraction allocated to validators
communityTax := k.GetCommunityTax(ctx)
voteMultiplier := sdk.OneDec().Sub(proposerMultiplier).Sub(communityTax)
// ...
for _, vote := range previousVotes {
validator := k.stakingKeeper.ValidatorByConsAddr(ctx, vote.Validator.Address)
powerFraction := sdk.NewDec(vote.Validator.Power).QuoTruncate(sdk.NewDec(totalPreviousPower))
reward := feesCollected.MulDecTruncate(voteMultiplier).MulDecTruncate(powerFraction)
k.AllocateTokensToValidator(ctx, validator, reward)
remaining = remaining.Sub(reward)
}
// allocate community funding
feePool.CommunityPool = feePool.CommunityPool.Add(remaining...)
k.SetFeePool(ctx, feePool)
}
While the community pool is an excellent tool used to fund the development of
the Cosmos ecosystem respective to the chain that it's executed on, the default
implementation can be limited for certain use-cases. Specifically, certain ledgers
may wish to have additional taxes or pools which distribution tokens are sent to.
All of these pooled accounts behave in a similar way, so we look to extending the
functionality of x/distribution
.
In the context of the Enigma protocol and ledger, x/distribution
will be forked
and modified to include the following changes:
- Add a new
foundation_tax
(sdk.Dec
) parameter to the existing parameter set - Add a new
foundation_tax_address
(sdk.AccAddress
) parameter to the existing parameter set - Add a new
SecretFoundationTaxProposal
governance proposal type that allows the tax and/or address to change
The SecretFoundationTaxProposal
is defined as follows:
type SecretFoundationTaxProposal struct {
Tax sdk.Dec
Address sdk.AccAddress
}
The proposal will be registered through the x/gov
module and will be used to change
either the tax and/or the address. If the Tax
is 0%, we consider the foundation tax
to be disabled. The trusting of the AccAddress
is done and verified out-of-band. We
can potentially consider introducing an additional field which can be used to verify
the address such as a Keybase ID or a signed message.
The following fields will be added to the x/distribution
genesis state:
type GenesisState struct {
// ...
SecretFoundationTax sdk.Dec
SecretFoundationAddress sdk.AccAddress
}
This will allow the network to be bootstrapped after an upgrade with these values pre-populated, perhaps with values decided on from a previous governance vote and would not require an additional governance vote post-upgrade.
The following changes will be made to the existing Keeper#AllocateTokens
:
--- a/x/distribution/keeper/allocation.go
+++ b/x/distribution/keeper/allocation.go
@@ -48,9 +48,9 @@ func (k Keeper) AllocateTokens(
bonusProposerReward := k.GetBonusProposerReward(ctx)
proposerMultiplier := baseProposerReward.Add(bonusProposerReward.MulTruncate(previousFractionVotes))
proposerReward := feesCollected.MulDecTruncate(proposerMultiplier)
+ feesCollected = feesCollected.Sub(proposerReward)
// pay previous proposer
- remaining := feesCollected
proposerValidator := k.stakingKeeper.ValidatorByConsAddr(ctx, previousProposer)
if proposerValidator != nil {
@@ -63,7 +63,6 @@ func (k Keeper) AllocateTokens(
)
k.AllocateTokensToValidator(ctx, proposerValidator, proposerReward)
- remaining = remaining.Sub(proposerReward)
} else {
// previous proposer can be unknown if say, the unbonding period is 1 block, so
// e.g. a validator undelegates at block X, it's removed entirely by
@@ -77,9 +76,13 @@ func (k Keeper) AllocateTokens(
previousProposer.String()))
}
- // calculate fraction allocated to validators
communityTax := k.GetCommunityTax(ctx)
- voteMultiplier := sdk.OneDec().Sub(proposerMultiplier).Sub(communityTax)
+ foundationTax := k.GetFoundationTax(ctx)
+
+ var (
+ communityTaxTotal sdk.DecCoins
+ foundationTaxTotal sdk.DecCoins
+ )
@@ -89,13 +92,23 @@ func (k Keeper) AllocateTokens(
powerFraction := sdk.NewDec(vote.Validator.Power).QuoTruncate(sdk.NewDec(totalPreviousPower))
- reward := feesCollected.MulDecTruncate(voteMultiplier).MulDecTruncate(powerFraction)
+ reward := feesCollected.MulDecTruncate(powerFraction)
+ rewardCommunityTax := reward.MulDecTruncate(communityTax)
+
+ rewardFoundationTax := sdk.NewDecCoins()
+ if !foundationTax.IsZero() {
+ rewardFoundationTax = reward.MulDecTruncate(foundationTax)
+ }
+
+ reward = reward.Sub(rewardFoundationTax).Sub(rewardCommunityTax)
+
k.AllocateTokensToValidator(ctx, validator, reward)
- remaining = remaining.Sub(reward)
+ foundationTaxTotal = foundationTaxTotal.Add(rewardFoundationTax...)
+ communityTaxTotal = communityTaxTotal.Add(rewardCommunityTax...)
}
// allocate community funding
- feePool.CommunityPool = feePool.CommunityPool.Add(remaining...)
+ feePool.CommunityPool = feePool.CommunityPool.Add(communityTaxTotal...)
k.SetFeePool(ctx, feePool)
}