Skip to content

Instantly share code, notes, and snippets.

@alexanderbez
Last active August 24, 2020 13:47
Show Gist options
  • Save alexanderbez/190884c73ffcd99e6d5786d6f32c0b5a to your computer and use it in GitHub Desktop.
Save alexanderbez/190884c73ffcd99e6d5786d6f32c0b5a to your computer and use it in GitHub Desktop.
Enigma Secret Foundation Tax Proposal

Secret Enigma Foundation Tax

Background

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.

Proposal

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

SecretFoundationTaxProposal

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.

Genesis State

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.

AllocateTokens

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)
 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment