Skip to content

Commit

Permalink
feat: replace blend with nonVotersMultiplier
Browse files Browse the repository at this point in the history
The idea is to ensure that non-voters don't get more than 33% of the
supply.
  • Loading branch information
tbruyelle committed Mar 20, 2024
1 parent 21cbf8c commit 8660154
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 83 deletions.
90 changes: 44 additions & 46 deletions distribution.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -91,13 +87,35 @@ 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)
continue
}

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
Expand All @@ -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)
Expand All @@ -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

Expand Down Expand Up @@ -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)
Expand Down
62 changes: 30 additions & 32 deletions distribution_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -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,
},
Expand Down Expand Up @@ -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,
},
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
},
Expand All @@ -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]
Expand Down
10 changes: 5 additions & 5 deletions testdata/distribution.golden
Original file line number Diff line number Diff line change
Expand Up @@ -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% |

0 comments on commit 8660154

Please sign in to comment.