Add swp token distribution (#1003)

* implement swp airdrop to usdx depositors to hard

* verify swp airdrop amount, total supply, affected users

* remove unused test code

* add map as go file

* update tests to use stored variable

* re-add snapshot test

* update snapshot testdata

* remove unused code for generating swp airdrop map

* remove snapshot test

* add swp token distribution

* bump test timeouts

* address review comments

* use global variables to simplify testing assertions on swp supply

* optional: swp migration test and small refactor (#1005)

* add test to ensure balances are not decreased

* simplify slice manipulation

* bump context deadline in circleci

Co-authored-by: Ruaridh <rhuairahrighairidh@users.noreply.github.com>
This commit is contained in:
Kevin Davis 2021-08-18 23:10:58 -06:00 committed by GitHub
parent 346501aa58
commit cac7398120
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 225 additions and 8 deletions

View File

@ -67,13 +67,14 @@ jobs:
- go-mod-v2-{{ checksum "go.sum" }} - go-mod-v2-{{ checksum "go.sum" }}
- run: - run:
name: Run tests name: Run tests
no_output_timeout: 30m
command: | command: |
export VERSION="$(git describe --tags --long | sed 's/v\(.*\)/\1/')" export VERSION="$(git describe --tags --long | sed 's/v\(.*\)/\1/')"
export GO111MODULE=on export GO111MODULE=on
mkdir -p /tmp/logs /tmp/workspace/profiles mkdir -p /tmp/logs /tmp/workspace/profiles
for pkg in $(go list ./... | grep -v -P 'simulation|migrate(?!\/v0_15)|contrib' | circleci tests split); do for pkg in $(go list ./... | grep -v -P 'simulation|migrate(?!\/v0_15)|contrib' | circleci tests split); do
id=$(echo "$pkg" | sed 's|[/.]|_|g') id=$(echo "$pkg" | sed 's|[/.]|_|g')
go test -mod=readonly -timeout 8m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic -tags='ledger test_ledger_mock' "$pkg" | tee "/tmp/logs/$id-$RANDOM.log" go test -mod=readonly -timeout 20m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic -tags='ledger test_ledger_mock' "$pkg" | tee "/tmp/logs/$id-$RANDOM.log"
done done
- persist_to_workspace: - persist_to_workspace:
root: /tmp/workspace root: /tmp/workspace

View File

@ -18,16 +18,15 @@ import (
v0_15committee "github.com/kava-labs/kava/x/committee/types" v0_15committee "github.com/kava-labs/kava/x/committee/types"
v0_14incentive "github.com/kava-labs/kava/x/incentive/legacy/v0_14" v0_14incentive "github.com/kava-labs/kava/x/incentive/legacy/v0_14"
v0_15incentive "github.com/kava-labs/kava/x/incentive/types" v0_15incentive "github.com/kava-labs/kava/x/incentive/types"
"github.com/kava-labs/kava/x/kavadist"
v0_15swap "github.com/kava-labs/kava/x/swap/types" v0_15swap "github.com/kava-labs/kava/x/swap/types"
v0_14validator_vesting "github.com/kava-labs/kava/x/validator-vesting" v0_14validator_vesting "github.com/kava-labs/kava/x/validator-vesting"
) )
var ( var (
// TODO: update GenesisTime and chain-id for kava-8 launch GenesisTime = time.Date(2021, 8, 30, 15, 0, 0, 0, time.UTC) // 1630335600
GenesisTime = time.Date(2021, 8, 30, 15, 0, 0, 0, time.UTC)
ChainID = "kava-8" ChainID = "kava-8"
// TODO: update SWP reward per second amount before production // TODO: update SWP reward per second amount before production
// TODO: add swap tokens to kavadist module account
// TODO: update SWP reward per second amount before production // TODO: update SWP reward per second amount before production
SwpDelegatorRewardsPerSecond = sdk.NewCoin("swp", sdk.OneInt()) SwpDelegatorRewardsPerSecond = sdk.NewCoin("swp", sdk.OneInt())
SwpLiquidityProviderRewardsPerSecond = sdk.NewCoin("swp", sdk.OneInt()) SwpLiquidityProviderRewardsPerSecond = sdk.NewCoin("swp", sdk.OneInt())
@ -112,10 +111,12 @@ func makeV014Codec() *codec.Codec {
func Auth(cdc *codec.Codec, genesisState auth.GenesisState, genesisTime time.Time) auth.GenesisState { func Auth(cdc *codec.Codec, genesisState auth.GenesisState, genesisTime time.Time) auth.GenesisState {
genesisStateWithAccountsMigrated := MigrateAccounts(genesisState, genesisTime) genesisStateWithAccountsMigrated := MigrateAccounts(genesisState, genesisTime)
genesisStateWithSwpAirdrop := ApplySwpAirdrop(cdc, genesisStateWithAccountsMigrated) genesisStateWithSwpAirdrop := ApplySwpAirdrop(cdc, genesisStateWithAccountsMigrated)
genesisStateWithSwpDistribution := DistributeSwpTokens(genesisStateWithSwpAirdrop)
return genesisStateWithSwpAirdrop return genesisStateWithSwpDistribution
} }
// ApplySwpAirdrop airdrops 1,000,000 SWP tokens to USDX depositors to HARD at block 1543671
func ApplySwpAirdrop(cdc *codec.Codec, genesisState auth.GenesisState) auth.GenesisState { func ApplySwpAirdrop(cdc *codec.Codec, genesisState auth.GenesisState) auth.GenesisState {
accounts := make([]authexported.GenesisAccount, len(genesisState.Accounts)) accounts := make([]authexported.GenesisAccount, len(genesisState.Accounts))
migratedGenesisState := auth.NewGenesisState(genesisState.Params, accounts) migratedGenesisState := auth.NewGenesisState(genesisState.Params, accounts)
@ -123,13 +124,17 @@ func ApplySwpAirdrop(cdc *codec.Codec, genesisState auth.GenesisState) auth.Gene
cdc.MustUnmarshalJSON([]byte(swpAirdropMap), &swpAirdrop) cdc.MustUnmarshalJSON([]byte(swpAirdropMap), &swpAirdrop)
for i, acc := range genesisState.Accounts { for i, acc := range genesisState.Accounts {
if swpReward, ok := swpAirdrop[acc.GetAddress().String()]; ok { if swpReward, ok := swpAirdrop[acc.GetAddress().String()]; ok {
acc.SetCoins(acc.GetCoins().Add(swpReward)) err := acc.SetCoins(acc.GetCoins().Add(swpReward))
if err != nil {
panic(err)
}
} }
accounts[i] = authexported.GenesisAccount(acc) accounts[i] = authexported.GenesisAccount(acc)
} }
return migratedGenesisState return migratedGenesisState
} }
// MigrateAccounts removes expired vesting periods from account state
func MigrateAccounts(genesisState auth.GenesisState, genesisTime time.Time) auth.GenesisState { func MigrateAccounts(genesisState auth.GenesisState, genesisTime time.Time) auth.GenesisState {
accounts := make([]authexported.GenesisAccount, len(genesisState.Accounts)) accounts := make([]authexported.GenesisAccount, len(genesisState.Accounts))
migratedGenesisState := auth.NewGenesisState(genesisState.Params, accounts) migratedGenesisState := auth.NewGenesisState(genesisState.Params, accounts)
@ -141,6 +146,104 @@ func MigrateAccounts(genesisState auth.GenesisState, genesisTime time.Time) auth
return migratedGenesisState return migratedGenesisState
} }
// global variables for swp distribution
var (
SwpEcoSystemAddr = mustAccAddressFromBech32("kava174crgzk006lj39s258gjxkh0p9tmmq8ugdcg0n")
SwpTreasuryAddr = mustAccAddressFromBech32("kava1w56wrusdnrv4tvn86eyam65wwhqatmsqg9fxjm")
SwpTeamAddr = mustAccAddressFromBech32("kava129eqnykzkc5ceyq9sv7ltxev22y8qwm94kr0ew")
KavaDistAddr = supply.NewModuleAddress(kavadist.KavaDistMacc)
SwpTreasuryCoins = sdk.NewCoin("swp", sdk.NewInt(62500000000000))
SwpTreasuryOriginalVesting = sdk.NewCoin("swp", sdk.NewInt(46875000000000))
KavaDistCoins = sdk.NewCoin("swp", sdk.NewInt(137500000e6))
EcoSystemCoins = sdk.NewCoin("swp", sdk.NewInt(11500000000000))
SwpTeamCoins = sdk.NewCoin("swp", sdk.NewInt(37500000000000))
)
// DistributeSwpTokens sets the initial distribution of swp tokens according to the proposed token supply schedule in prop 59
func DistributeSwpTokens(genesisState auth.GenesisState) auth.GenesisState {
// add SWP incentives (LP, Kava stakers) to kavadist module account
accounts := make([]authexported.GenesisAccount, len(genesisState.Accounts))
for i, acc := range genesisState.Accounts {
if acc.GetAddress().Equals(KavaDistAddr) {
err := acc.SetCoins(acc.GetCoins().Add(KavaDistCoins))
if err != nil {
panic(err)
}
}
accounts[i] = authexported.GenesisAccount(acc)
}
// Add ecosystem account
swpEcosystemBacc := auth.NewBaseAccountWithAddress(SwpEcoSystemAddr)
// 11.5M because 1M has already been airdropped
err := swpEcosystemBacc.SetCoins(sdk.NewCoins(EcoSystemCoins))
if err != nil {
panic(err)
}
// Add team vesting account
swpTeamBacc := auth.NewBaseAccountWithAddress(SwpTeamAddr)
err = swpTeamBacc.SetCoins(sdk.NewCoins(SwpTeamCoins))
if err != nil {
panic(err)
}
swpTeamBva, err := vesting.NewBaseVestingAccount(&swpTeamBacc, sdk.NewCoins(SwpTeamCoins), 1693407600)
if err != nil {
panic(err)
}
swpTeamTokenSchedule := []sdk.Coin{
sdk.NewCoin("swp", sdk.NewInt(18750000000000)),
sdk.NewCoin("swp", sdk.NewInt(4687500000000)),
sdk.NewCoin("swp", sdk.NewInt(4687500000000)),
sdk.NewCoin("swp", sdk.NewInt(4687500000000)),
sdk.NewCoin("swp", sdk.NewInt(4687500000000)),
}
swpTeamVestingLengths := []int64{31536000, 7948800, 7776000, 7948800, 7862400}
swpTeamVestingPeriods := vesting.Periods{}
for i, vestingCoin := range swpTeamTokenSchedule {
period := vesting.Period{Length: swpTeamVestingLengths[i], Amount: sdk.NewCoins(vestingCoin)}
swpTeamVestingPeriods = append(swpTeamVestingPeriods, period)
}
swpTeamVestingAccount := vesting.NewPeriodicVestingAccountRaw(swpTeamBva, GenesisTime.Unix(), swpTeamVestingPeriods)
// Add treasury vesting account
swpTreasuryBacc := auth.NewBaseAccountWithAddress(SwpTreasuryAddr)
err = swpTreasuryBacc.SetCoins(sdk.NewCoins(SwpTreasuryCoins))
if err != nil {
panic(err)
}
swpTreasuryBva, err := vesting.NewBaseVestingAccount(&swpTreasuryBacc, sdk.NewCoins(SwpTreasuryOriginalVesting), 1693407600)
if err != nil {
panic(err)
}
swpTreasuryTokenSchedule := []sdk.Coin{
sdk.NewCoin("swp", sdk.NewInt(5859375e6)),
sdk.NewCoin("swp", sdk.NewInt(5859375e6)),
sdk.NewCoin("swp", sdk.NewInt(5859375e6)),
sdk.NewCoin("swp", sdk.NewInt(5859375e6)),
sdk.NewCoin("swp", sdk.NewInt(5859375e6)),
sdk.NewCoin("swp", sdk.NewInt(5859375e6)),
sdk.NewCoin("swp", sdk.NewInt(5859375e6)),
sdk.NewCoin("swp", sdk.NewInt(5859375e6)),
}
swpTreasuryVestingLengths := []int64{7948800, 7776000, 7948800, 7862400, 7948800, 7776000, 7948800, 7862400}
swpTreasuryVestingPeriods := vesting.Periods{}
for i, vestingCoin := range swpTreasuryTokenSchedule {
period := vesting.Period{Length: swpTreasuryVestingLengths[i], Amount: sdk.NewCoins(vestingCoin)}
swpTreasuryVestingPeriods = append(swpTreasuryVestingPeriods, period)
}
swpTreasuryVestingAccount := vesting.NewPeriodicVestingAccountRaw(swpTreasuryBva, GenesisTime.Unix(), swpTreasuryVestingPeriods)
accounts = append(accounts, &swpEcosystemBacc, swpTeamVestingAccount, swpTreasuryVestingAccount)
return auth.NewGenesisState(genesisState.Params, accounts)
}
// Committee migrates from a v0.14 committee genesis state to a v0.15 committee genesis state // Committee migrates from a v0.14 committee genesis state to a v0.15 committee genesis state
func Committee(genesisState v0_14committee.GenesisState) v0_15committee.GenesisState { func Committee(genesisState v0_14committee.GenesisState) v0_15committee.GenesisState {
@ -379,3 +482,12 @@ func Swap() v0_15swap.GenesisState {
params := v0_15swap.NewParams(pools, fee) params := v0_15swap.NewParams(pools, fee)
return v0_15swap.NewGenesisState(params, v0_15swap.DefaultPoolRecords, v0_15swap.DefaultShareRecords) return v0_15swap.NewGenesisState(params, v0_15swap.DefaultPoolRecords, v0_15swap.DefaultShareRecords)
} }
func mustAccAddressFromBech32(bech32Addr string) sdk.AccAddress {
app.SetBech32AddressPrefixes(sdk.GetConfig())
addr, err := sdk.AccAddressFromBech32(bech32Addr)
if err != nil {
panic(err)
}
return addr
}

View File

@ -189,6 +189,33 @@ func TestAuth_AccountConversion(t *testing.T) {
} }
} }
func TestAuth_Regression_BalancesEqual(t *testing.T) {
bz, err := ioutil.ReadFile(filepath.Join("testdata", "block-1543671-auth-state.json"))
require.NoError(t, err)
cdc := app.MakeCodec()
var originalGenesisState auth.GenesisState
cdc.MustUnmarshalJSON(bz, &originalGenesisState)
var genesisState auth.GenesisState
cdc.MustUnmarshalJSON(bz, &genesisState)
genesisState = Auth(cdc, genesisState, GenesisTime)
for i, oldAcc := range originalGenesisState.Accounts {
acc := genesisState.Accounts[i]
// total owned coins does not change, despite additional swp tokens
swpBalance := sdk.NewCoins(sdk.NewCoin("swp", acc.GetCoins().AmountOf("swp")))
require.Equal(t, oldAcc.GetCoins(), acc.GetCoins().Sub(swpBalance), "expected base coins to not change")
// ensure spendable coins at genesis time is equal, despite additional swp tokens
swpBalance = sdk.NewCoins(sdk.NewCoin("swp", acc.SpendableCoins(GenesisTime).AmountOf("swp")))
require.Equal(t, oldAcc.SpendableCoins(GenesisTime), acc.SpendableCoins(GenesisTime).Sub(swpBalance), "expected spendable coins to not change")
}
}
func TestAuth_MakeAirdropMap(t *testing.T) { func TestAuth_MakeAirdropMap(t *testing.T) {
cdc := app.MakeCodec() cdc := app.MakeCodec()
aidropTokenAmount := sdk.NewInt(1000000000000) aidropTokenAmount := sdk.NewInt(1000000000000)
@ -225,9 +252,8 @@ func TestAuth_TestAllDepositorsIncluded(t *testing.T) {
require.Equal(t, depositorsInSnapShot, len(keys)) require.Equal(t, depositorsInSnapShot, len(keys))
} }
func TestAuth_SwpSupply(t *testing.T) { func TestAuth_SwpSupply_AfterAirdrop(t *testing.T) {
swpSupply := sdk.NewCoin("swp", sdk.ZeroInt()) swpSupply := sdk.NewCoin("swp", sdk.ZeroInt())
// TODO update when additional swp are added to migration, final supply should be 250M at genesis
expectedSwpSupply := sdk.NewCoin("swp", sdk.NewInt(1000000000000)) expectedSwpSupply := sdk.NewCoin("swp", sdk.NewInt(1000000000000))
bz, err := ioutil.ReadFile(filepath.Join("testdata", "block-1543671-auth-state.json")) bz, err := ioutil.ReadFile(filepath.Join("testdata", "block-1543671-auth-state.json"))
require.NoError(t, err) require.NoError(t, err)
@ -236,6 +262,28 @@ func TestAuth_SwpSupply(t *testing.T) {
cdc := app.MakeCodec() cdc := app.MakeCodec()
cdc.MustUnmarshalJSON(bz, &genesisState) cdc.MustUnmarshalJSON(bz, &genesisState)
migratedGenesisState := ApplySwpAirdrop(cdc, genesisState)
for _, acc := range migratedGenesisState.Accounts {
swpAmount := acc.GetCoins().AmountOf("swp")
if swpAmount.IsPositive() {
swpCoin := sdk.NewCoin("swp", swpAmount)
swpSupply = swpSupply.Add(swpCoin)
}
}
require.Equal(t, expectedSwpSupply, swpSupply)
}
func TestAuth_SwpSupply_TotalSupply(t *testing.T) {
swpSupply := sdk.NewCoin("swp", sdk.ZeroInt())
expectedSwpSupply := sdk.NewCoin("swp", sdk.NewInt(250000000e6))
bz, err := ioutil.ReadFile(filepath.Join("testdata", "block-1543671-auth-state.json"))
require.NoError(t, err)
var genesisState auth.GenesisState
cdc := app.MakeCodec()
cdc.MustUnmarshalJSON(bz, &genesisState)
migratedGenesisState := Auth(cdc, genesisState, GenesisTime) migratedGenesisState := Auth(cdc, genesisState, GenesisTime)
for _, acc := range migratedGenesisState.Accounts { for _, acc := range migratedGenesisState.Accounts {
@ -247,3 +295,59 @@ func TestAuth_SwpSupply(t *testing.T) {
} }
require.Equal(t, expectedSwpSupply, swpSupply) require.Equal(t, expectedSwpSupply, swpSupply)
} }
func TestAuth_SwpSupply_SpendableCoins(t *testing.T) {
bz, err := ioutil.ReadFile(filepath.Join("testdata", "block-1543671-auth-state.json"))
require.NoError(t, err)
var genesisState auth.GenesisState
cdc := app.MakeCodec()
cdc.MustUnmarshalJSON(bz, &genesisState)
swpTreasuryExpectedSpendableCoins := sdk.NewCoins(SwpTreasuryCoins.Sub(SwpTreasuryOriginalVesting))
foundTreasury := false
swpTeamExpectedSpendableCoins := sdk.Coins(nil)
foundTeam := false
kavaDistExpectedSpendableCoins := KavaDistCoins
foundKavadist := false
swpEcosystemExpectedSpendableCoins := sdk.NewCoins(EcoSystemCoins)
foundEcosystem := false
migratedGenesisState := Auth(cdc, genesisState, GenesisTime)
for _, acc := range migratedGenesisState.Accounts {
if acc.GetAddress().Equals(SwpTreasuryAddr) {
foundTreasury = true
pva, ok := acc.(*vesting.PeriodicVestingAccount)
require.True(t, ok)
spendableCoins := pva.SpendableCoins(GenesisTime)
require.Equal(t, swpTreasuryExpectedSpendableCoins, spendableCoins)
}
if acc.GetAddress().Equals(SwpTeamAddr) {
foundTeam = true
pva, ok := acc.(*vesting.PeriodicVestingAccount)
require.True(t, ok)
spendableCoins := pva.SpendableCoins(GenesisTime)
require.Equal(t, swpTeamExpectedSpendableCoins, spendableCoins)
}
if acc.GetAddress().Equals(KavaDistAddr) {
foundKavadist = true
spendableCoins := acc.SpendableCoins(GenesisTime)
spendableSwp := sdk.NewCoin("swp", spendableCoins.AmountOf("swp"))
require.Equal(t, kavaDistExpectedSpendableCoins, spendableSwp)
}
if acc.GetAddress().Equals(SwpEcoSystemAddr) {
foundEcosystem = true
spendableCoins := acc.SpendableCoins(GenesisTime)
require.Equal(t, swpEcosystemExpectedSpendableCoins, spendableCoins)
}
}
require.True(t, foundTreasury)
require.True(t, foundTeam)
require.True(t, foundKavadist)
require.True(t, foundEcosystem)
}