From 86601547df0edbab66a124a110499f792da0e610 Mon Sep 17 00:00:00 2001 From: Thomas Bruyelle Date: Wed, 20 Mar 2024 18:55:28 +0100 Subject: [PATCH] feat: replace blend with nonVotersMultiplier The idea is to ensure that non-voters don't get more than 33% of the supply. --- distribution.go | 90 ++++++++++++++++++------------------ distribution_test.go | 62 ++++++++++++------------- testdata/distribution.golden | 10 ++-- 3 files changed, 79 insertions(+), 83 deletions(-) diff --git a/distribution.go b/distribution.go index 8726f02..6d3c74e 100644 --- a/distribution.go +++ b/distribution.go @@ -31,9 +31,9 @@ var ( type airdrop struct { // addresses contains the airdrop amount per address. addresses map[string]sdk.Int - // blend is the neutral multiplier, for which the $ATOM is neither rewarded - // nor diluted. - blend sdk.Dec + // nonVotersMultiplier ensures that non-voters don't hold more than 1/3 of + // the supply + nonVotersMultiplier sdk.Dec // $ATOM distribution atom distrib // $ATONE distribution @@ -52,24 +52,20 @@ type distrib struct { } func distribution(accounts []Account) (airdrop, error) { - var ( - blend = computeBlend(accounts) - airdrop = airdrop{ - addresses: make(map[string]sdk.Int), - blend: blend, - icfSlash: sdk.ZeroDec(), - atom: distrib{ - supply: sdk.ZeroDec(), - votes: newVoteMap(), - unstaked: sdk.ZeroDec(), - }, - atone: distrib{ - supply: sdk.ZeroDec(), - votes: newVoteMap(), - unstaked: sdk.ZeroDec(), - }, - } - ) + airdrop := airdrop{ + addresses: make(map[string]sdk.Int), + icfSlash: sdk.ZeroDec(), + atom: distrib{ + supply: sdk.ZeroDec(), + votes: newVoteMap(), + unstaked: sdk.ZeroDec(), + }, + atone: distrib{ + supply: sdk.ZeroDec(), + votes: newVoteMap(), + unstaked: sdk.ZeroDec(), + }, + } for _, acc := range accounts { var ( voteWeights = acc.voteWeights() @@ -91,6 +87,22 @@ func distribution(accounts []Account) (airdrop, error) { airdrop.atom.supply = airdrop.atom.supply.Add(acc.StakedAmount.Add(acc.LiquidAmount)) airdrop.atom.unstaked = airdrop.atom.unstaked.Add(acc.LiquidAmount) + } + + // Compute nonVotersMultiplier to have non-voters <= 33% + var ( + yesAtoneTotalAmt = airdrop.atom.votes[govtypes.OptionYes].Mul(yesVotesMultiplier) + noAtoneTotalAmt = airdrop.atom.votes[govtypes.OptionNo].Add(airdrop.atom.votes[govtypes.OptionNoWithVeto]).Mul(noVotesMultiplier) + noVotersAtomTotalAmt = airdrop.atom.votes[govtypes.OptionAbstain].Add(airdrop.atom.votes[govtypes.OptionEmpty]).Add(airdrop.atom.unstaked) + targetNonVotersPerc = sdk.NewDecWithPrec(33, 2) + ) + // Formula is: + // nonVotersMultiplier = (t x (yesAtone + noAtone)) / ((1 - t) x nonVoterAtom) + // where t is the targetNonVotersPerc + airdrop.nonVotersMultiplier = targetNonVotersPerc.Mul(yesAtoneTotalAmt.Add(noAtoneTotalAmt)). + Quo((sdk.OneDec().Sub(targetNonVotersPerc)).Mul(noVotersAtomTotalAmt)) + + for _, acc := range accounts { if slices.Contains(icfWallets, acc.Address) { // Slash ICF airdrop.icfSlash = airdrop.icfSlash.Add(acc.LiquidAmount).Add(acc.StakedAmount) @@ -98,6 +110,12 @@ func distribution(accounts []Account) (airdrop, error) { } var ( + voteWeights = acc.voteWeights() + yesAtomAmt = voteWeights[govtypes.OptionYes].Mul(acc.StakedAmount) + noAtomAmt = voteWeights[govtypes.OptionNo].Mul(acc.StakedAmount) + noWithVetoAtomAmt = voteWeights[govtypes.OptionNoWithVeto].Mul(acc.StakedAmount) + abstainAtomAmt = voteWeights[govtypes.OptionAbstain].Mul(acc.StakedAmount) + noVoteAtomAmt = voteWeights[govtypes.OptionEmpty].Mul(acc.StakedAmount) // Apply airdrop multipliers: // Yes: x yesVotesMultiplier // No: x noVotesMultiplier @@ -107,11 +125,11 @@ func distribution(accounts []Account) (airdrop, error) { yesAirdropAmt = yesAtomAmt.Mul(yesVotesMultiplier) noAirdropAmt = noAtomAmt.Mul(noVotesMultiplier) noWithVetoAirdropAmt = noWithVetoAtomAmt.Mul(noVotesMultiplier).Mul(bonus) - abstainAirdropAmt = abstainAtomAmt.Mul(blend) - noVoteAirdropAmt = noVoteAtomAmt.Mul(blend).Mul(malus) + abstainAirdropAmt = abstainAtomAmt.Mul(airdrop.nonVotersMultiplier) + noVoteAirdropAmt = noVoteAtomAmt.Mul(airdrop.nonVotersMultiplier).Mul(malus) // Liquid amount gets the same multiplier as those who didn't vote. - liquidMultiplier = blend.Mul(malus) + liquidMultiplier = airdrop.nonVotersMultiplier.Mul(malus) // total airdrop for this account liquidAirdrop = acc.LiquidAmount.Mul(liquidMultiplier) @@ -134,26 +152,6 @@ func distribution(accounts []Account) (airdrop, error) { return airdrop, nil } -func computeBlend(accounts []Account) sdk.Dec { - activeVoteAmts := newVoteMap() - for _, acc := range accounts { - for voteOpt, weight := range acc.voteWeights() { - switch voteOpt { - case govtypes.OptionYes, govtypes.OptionNo, govtypes.OptionNoWithVeto: - activeVoteAmts.add(voteOpt, acc.StakedAmount.Mul(weight)) - } - } - } - // Compute percentage of Y, N and NWM amouts relative to activeVoteAmts - activePercs := activeVoteAmts.toPercentages() - - // Compute blend - blend := activePercs[govtypes.OptionYes].Mul(yesVotesMultiplier). - Add(activePercs[govtypes.OptionNo].Mul(noVotesMultiplier)). - Add(activePercs[govtypes.OptionNoWithVeto].Mul(noVotesMultiplier)) - return blend -} - // convenient type for manipulating vote counts. type voteMap map[govtypes.VoteOption]sdk.Dec @@ -232,9 +230,9 @@ func printAirdropStats(airdrop airdrop) { fmt.Println("$ATOM distribution") printDistrib(airdrop.atom) fmt.Println() - fmt.Printf("$ATONE distribution (ratio: x%.3f, blend: %.3f, IcfSlash: %s $ATOM)\n", + fmt.Printf("$ATONE distribution (ratio: x%.3f, nonVotersMultiplier: %.3f, icfSlash: %s $ATOM)\n", airdrop.atone.supply.Quo(airdrop.atom.supply).MustFloat64(), - airdrop.blend.MustFloat64(), + airdrop.nonVotersMultiplier.MustFloat64(), humand(airdrop.icfSlash), ) printDistrib(airdrop.atone) diff --git a/distribution_test.go b/distribution_test.go index 287e43c..77dece9 100644 --- a/distribution_test.go +++ b/distribution_test.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -75,21 +74,21 @@ func TestDistribution(t *testing.T) { }}, }, }, - expectedAddresses: func(blend sdk.Dec) map[string]sdk.Dec { + expectedAddresses: func(nonVotersMult sdk.Dec) map[string]sdk.Dec { return map[string]sdk.Dec{ - "yes": sdk.NewDec(1).Mul(blend.Mul(malus)).Add(sdk.NewDec(2)), - "abstain": sdk.NewDec(1).Mul(blend.Mul(malus)).Add(sdk.NewDec(2).Mul(blend)), - "no": sdk.NewDec(1).Mul(blend.Mul(malus)).Add(sdk.NewDec(2).Mul(noVotesMultiplier)), - "noWithVeto": sdk.NewDec(1).Mul(blend.Mul(malus)).Add(sdk.NewDec(2).Mul(noVotesMultiplier).Mul(bonus)), - "didntVote": sdk.NewDec(1).Mul(blend.Mul(malus)).Add(sdk.NewDec(2).Mul(blend).Mul(malus)), + "yes": sdk.NewDec(1).Mul(nonVotersMult.Mul(malus)).Add(sdk.NewDec(2)), + "abstain": sdk.NewDec(1).Mul(nonVotersMult.Mul(malus)).Add(sdk.NewDec(2).Mul(nonVotersMult)), + "no": sdk.NewDec(1).Mul(nonVotersMult.Mul(malus)).Add(sdk.NewDec(2).Mul(noVotesMultiplier)), + "noWithVeto": sdk.NewDec(1).Mul(nonVotersMult.Mul(malus)).Add(sdk.NewDec(2).Mul(noVotesMultiplier).Mul(bonus)), + "didntVote": sdk.NewDec(1).Mul(nonVotersMult.Mul(malus)).Add(sdk.NewDec(2).Mul(nonVotersMult).Mul(malus)), } }, - expectedTotal: 45, - expectedUnstaked: 15, + expectedTotal: 27, + expectedUnstaked: 5, expectedVotes: map[govtypes.VoteOption]int64{ - govtypes.OptionEmpty: 6, + govtypes.OptionEmpty: 2, govtypes.OptionYes: 2, - govtypes.OptionAbstain: 6, + govtypes.OptionAbstain: 2, govtypes.OptionNo: 8, govtypes.OptionNoWithVeto: 9, }, @@ -121,27 +120,27 @@ func TestDistribution(t *testing.T) { }, }, }, - expectedAddresses: func(blend sdk.Dec) map[string]sdk.Dec { + expectedAddresses: func(nonVotersMult sdk.Dec) map[string]sdk.Dec { return map[string]sdk.Dec{ "directWeightVote": // liquid amount - sdk.NewDec(1).Mul(blend.Mul(malus)). + sdk.NewDec(1).Mul(nonVotersMult.Mul(malus)). // voted yes Add(sdk.NewDec(18).Mul(sdk.NewDecWithPrec(1, 1))). // voted abstain - Add(sdk.NewDec(18).Mul(sdk.NewDecWithPrec(2, 1)).Mul(blend)). + Add(sdk.NewDec(18).Mul(sdk.NewDecWithPrec(2, 1)).Mul(nonVotersMult)). // voted no Add(sdk.NewDec(18).Mul(sdk.NewDecWithPrec(3, 1)).Mul(noVotesMultiplier)). // voted noWithVeto Add(sdk.NewDec(18).Mul(sdk.NewDecWithPrec(4, 1)).Mul(noVotesMultiplier).Mul(bonus)), } }, - expectedTotal: 70, - expectedUnstaked: 4, + expectedTotal: 79, + expectedUnstaked: 6, expectedVotes: map[govtypes.VoteOption]int64{ govtypes.OptionEmpty: 0, govtypes.OptionYes: 2, - govtypes.OptionAbstain: 14, + govtypes.OptionAbstain: 21, govtypes.OptionNo: 22, govtypes.OptionNoWithVeto: 30, }, @@ -183,17 +182,17 @@ func TestDistribution(t *testing.T) { }, }, }, - expectedAddresses: func(blend sdk.Dec) map[string]sdk.Dec { + expectedAddresses: func(nonVotersMult sdk.Dec) map[string]sdk.Dec { return map[string]sdk.Dec{ "indirectVote": // liquid amount - sdk.NewDec(1).Mul(blend.Mul(malus)). + sdk.NewDec(1).Mul(nonVotersMult.Mul(malus)). // from deleg who didn't vote - Add(sdk.NewDec(2).Mul(blend).Mul(malus)). + Add(sdk.NewDec(2).Mul(nonVotersMult).Mul(malus)). // from deleg who voted yes Add(sdk.NewDec(3)). // from deleg who voted abstain - Add(sdk.NewDec(4).Mul(blend)). + Add(sdk.NewDec(4).Mul(nonVotersMult)). // from deleg who voted no Add(sdk.NewDec(5).Mul(noVotesMultiplier)). // from deleg who voted noWithVeto @@ -268,32 +267,32 @@ func TestDistribution(t *testing.T) { }, }, }, - expectedAddresses: func(blend sdk.Dec) map[string]sdk.Dec { + expectedAddresses: func(nonVotersMult sdk.Dec) map[string]sdk.Dec { return map[string]sdk.Dec{ "directWeightVote": // liquid amount - sdk.NewDec(1).Mul(blend.Mul(malus)). + sdk.NewDec(1).Mul(nonVotersMult.Mul(malus)). // voted yes Add(sdk.NewDec(18).Mul(sdk.NewDecWithPrec(1, 1))). Add(sdk.NewDec(10).Mul(sdk.NewDecWithPrec(4, 1))). // voted abstain - Add(sdk.NewDec(18).Mul(sdk.NewDecWithPrec(2, 1)).Mul(blend)). - Add(sdk.NewDec(10).Mul(sdk.NewDecWithPrec(6, 1)).Mul(blend)). + Add(sdk.NewDec(18).Mul(sdk.NewDecWithPrec(2, 1)).Mul(nonVotersMult)). + Add(sdk.NewDec(10).Mul(sdk.NewDecWithPrec(6, 1)).Mul(nonVotersMult)). // voted no Add(sdk.NewDec(18).Mul(sdk.NewDecWithPrec(3, 1)).Mul(noVotesMultiplier)). Add(sdk.NewDec(2).Mul(noVotesMultiplier)). // voted noWithVeto Add(sdk.NewDec(18).Mul(sdk.NewDecWithPrec(4, 1)).Mul(noVotesMultiplier).Mul(bonus)). // empty vote - Add(sdk.NewDec(3).Mul(blend)), + Add(sdk.NewDec(3).Mul(nonVotersMult)), } }, - expectedTotal: 108, - expectedUnstaked: 4, + expectedTotal: 97, + expectedUnstaked: 3, expectedVotes: map[govtypes.VoteOption]int64{ - govtypes.OptionEmpty: 10, + govtypes.OptionEmpty: 7, govtypes.OptionYes: 6, - govtypes.OptionAbstain: 31, + govtypes.OptionAbstain: 23, govtypes.OptionNo: 30, govtypes.OptionNoWithVeto: 30, }, @@ -303,12 +302,11 @@ func TestDistribution(t *testing.T) { t.Run(tt.name, func(t *testing.T) { require := require.New(t) assert := assert.New(t) - fmt.Println(sdk.NewDecWithPrec(55, 1).Mul(malus)) airdrop, err := distribution(tt.accounts) require.NoError(err) - expectedRes := tt.expectedAddresses(airdrop.blend) + expectedRes := tt.expectedAddresses(airdrop.nonVotersMultiplier) assert.Equal(len(expectedRes), len(airdrop.addresses), "unexpected number of res") for k, v := range airdrop.addresses { ev, ok := expectedRes[k] diff --git a/testdata/distribution.golden b/testdata/distribution.golden index e7bf19d..ef8cb07 100644 --- a/testdata/distribution.golden +++ b/testdata/distribution.golden @@ -4,8 +4,8 @@ $ATOM distribution | Distributed | 342,834,268 | 66,855,758 | 70,428,501 | 55,519,213 | 11,664,818 | 35,679,919 | 102,686,059 | | Percentage over total | | 20% | 21% | 16% | 3% | 10% | 30% | -$ATONE distribution (ratio: x2.361, blend: 2.465, IcfSlash: 12,590,970 $ATOM) -| | TOTAL | DID NOT VOTE | YES | NO | NOWITHVETO | ABSTAIN | NOT STAKED | -|-----------------------|-------------|--------------|------------|-------------|------------|------------|-------------| -| Distributed | 809,415,611 | 158,897,406 | 63,746,761 | 213,404,392 | 47,911,135 | 86,287,988 | 239,167,930 | -| Percentage over total | | 20% | 8% | 26% | 6% | 11% | 30% | +$ATONE distribution (ratio: x1.415, nonVotersMultiplier: 0.814, icfSlash: 12,590,970 $ATOM) +| | TOTAL | DID NOT VOTE | YES | NO | NOWITHVETO | ABSTAIN | NOT STAKED | +|-----------------------|-------------|--------------|------------|-------------|------------|------------|------------| +| Distributed | 485,031,369 | 52,479,607 | 63,746,761 | 213,404,392 | 47,911,135 | 28,498,638 | 78,990,836 | +| Percentage over total | | 11% | 13% | 44% | 10% | 6% | 16% |