From cac7398120a9644531d859c691dc39af9a3d6586 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Wed, 18 Aug 2021 23:10:58 -0600 Subject: [PATCH] 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 --- .circleci/config.yml | 3 +- migrate/v0_15/migrate.go | 122 ++++++++++++++++++++++++++++++++-- migrate/v0_15/migrate_test.go | 108 +++++++++++++++++++++++++++++- 3 files changed, 225 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 57fbb847..bdd4db63 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,13 +67,14 @@ jobs: - go-mod-v2-{{ checksum "go.sum" }} - run: name: Run tests + no_output_timeout: 30m command: | export VERSION="$(git describe --tags --long | sed 's/v\(.*\)/\1/')" export GO111MODULE=on mkdir -p /tmp/logs /tmp/workspace/profiles for pkg in $(go list ./... | grep -v -P 'simulation|migrate(?!\/v0_15)|contrib' | circleci tests split); do 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 - persist_to_workspace: root: /tmp/workspace diff --git a/migrate/v0_15/migrate.go b/migrate/v0_15/migrate.go index 26b5091e..1c382b76 100644 --- a/migrate/v0_15/migrate.go +++ b/migrate/v0_15/migrate.go @@ -18,16 +18,15 @@ import ( v0_15committee "github.com/kava-labs/kava/x/committee/types" v0_14incentive "github.com/kava-labs/kava/x/incentive/legacy/v0_14" 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_14validator_vesting "github.com/kava-labs/kava/x/validator-vesting" ) var ( - // TODO: update GenesisTime and chain-id for kava-8 launch - GenesisTime = time.Date(2021, 8, 30, 15, 0, 0, 0, time.UTC) + GenesisTime = time.Date(2021, 8, 30, 15, 0, 0, 0, time.UTC) // 1630335600 ChainID = "kava-8" // 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 SwpDelegatorRewardsPerSecond = 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 { genesisStateWithAccountsMigrated := MigrateAccounts(genesisState, genesisTime) 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 { accounts := make([]authexported.GenesisAccount, len(genesisState.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) for i, acc := range genesisState.Accounts { 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) } return migratedGenesisState } +// MigrateAccounts removes expired vesting periods from account state func MigrateAccounts(genesisState auth.GenesisState, genesisTime time.Time) auth.GenesisState { accounts := make([]authexported.GenesisAccount, len(genesisState.Accounts)) migratedGenesisState := auth.NewGenesisState(genesisState.Params, accounts) @@ -141,6 +146,104 @@ func MigrateAccounts(genesisState auth.GenesisState, genesisTime time.Time) auth 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 func Committee(genesisState v0_14committee.GenesisState) v0_15committee.GenesisState { @@ -379,3 +482,12 @@ func Swap() v0_15swap.GenesisState { params := v0_15swap.NewParams(pools, fee) 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 +} diff --git a/migrate/v0_15/migrate_test.go b/migrate/v0_15/migrate_test.go index 2ecbaf1f..2956f66b 100644 --- a/migrate/v0_15/migrate_test.go +++ b/migrate/v0_15/migrate_test.go @@ -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) { cdc := app.MakeCodec() aidropTokenAmount := sdk.NewInt(1000000000000) @@ -225,9 +252,8 @@ func TestAuth_TestAllDepositorsIncluded(t *testing.T) { 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()) - // TODO update when additional swp are added to migration, final supply should be 250M at genesis expectedSwpSupply := sdk.NewCoin("swp", sdk.NewInt(1000000000000)) bz, err := ioutil.ReadFile(filepath.Join("testdata", "block-1543671-auth-state.json")) require.NoError(t, err) @@ -236,6 +262,28 @@ func TestAuth_SwpSupply(t *testing.T) { cdc := app.MakeCodec() 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) for _, acc := range migratedGenesisState.Accounts { @@ -247,3 +295,59 @@ func TestAuth_SwpSupply(t *testing.T) { } 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) + +}