From b48d0d56239411788fd64d5ab287fbabc60b3aef Mon Sep 17 00:00:00 2001 From: Rigel Date: Fri, 19 Oct 2018 14:36:00 -0400 Subject: [PATCH] Merge PR #2527: Minting --- PENDING.md | 2 + client/lcd/lcd_test.go | 4 - cmd/gaia/app/app.go | 32 +++-- cmd/gaia/app/genesis.go | 16 +++ cmd/gaia/app/sim_test.go | 8 +- cmd/gaia/cli_test/cli_test.go | 3 - docs/spec/distribution/end_block.md | 2 +- docs/spec/mint/begin_block.md | 28 +++++ docs/spec/mint/state.md | 31 +++++ docs/spec/staking/state.md | 9 -- x/auth/ante.go | 2 +- x/auth/feekeeper.go | 3 +- x/auth/feekeeper_test.go | 4 +- x/distribution/abci_app.go | 2 +- x/distribution/keeper/allocation.go | 2 +- x/distribution/keeper/allocation_test.go | 12 +- x/distribution/keeper/delegation_test.go | 12 +- x/distribution/keeper/validator_test.go | 10 +- x/mint/abci_app.go | 25 ++++ x/mint/expected_keepers.go | 15 +++ x/mint/genesis.go | 55 +++++++++ x/mint/keeper.go | 85 +++++++++++++ x/mint/minter.go | 71 +++++++++++ x/mint/minter_test.go | 53 +++++++++ x/mint/params.go | 43 +++++++ x/stake/genesis.go | 11 -- x/stake/genesis_test.go | 10 -- x/stake/handler.go | 14 +-- x/stake/handler_test.go | 7 -- x/stake/keeper/params.go | 28 ----- x/stake/keeper/sdk_types.go | 13 ++ x/stake/keeper/test_common.go | 12 -- x/stake/simulation/msgs.go | 2 - x/stake/stake.go | 11 +- x/stake/types/inflation_test.go | 145 ----------------------- x/stake/types/params.go | 36 ++---- x/stake/types/pool.go | 70 +---------- x/stake/types/validator_test.go | 13 +- 38 files changed, 510 insertions(+), 391 deletions(-) create mode 100644 docs/spec/mint/begin_block.md create mode 100644 docs/spec/mint/state.md create mode 100644 x/mint/abci_app.go create mode 100644 x/mint/expected_keepers.go create mode 100644 x/mint/genesis.go create mode 100644 x/mint/keeper.go create mode 100644 x/mint/minter.go create mode 100644 x/mint/minter_test.go create mode 100644 x/mint/params.go delete mode 100644 x/stake/types/inflation_test.go diff --git a/PENDING.md b/PENDING.md index 2dc3013de..76c575368 100644 --- a/PENDING.md +++ b/PENDING.md @@ -82,6 +82,8 @@ BREAKING CHANGES * [x/stake] \#2500 Block conflicting redelegations until we add an index * [x/params] Global Paramstore refactored * [x/stake] \#2508 Utilize Tendermint power for validator power key + * [x/stake] \#2531 Remove all inflation logic + * [x/mint] \#2531 Add minting module and inflation logic * Tendermint * Update tendermint version from v0.23.0 to v0.25.0, notable changes diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index c1c018778..d4d038016 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -480,11 +480,7 @@ func TestPoolParamsQuery(t *testing.T) { var pool stake.Pool err = cdc.UnmarshalJSON([]byte(body), &pool) require.Nil(t, err) - require.Equal(t, initialPool.DateLastCommissionReset, pool.DateLastCommissionReset) - require.Equal(t, initialPool.PrevBondedShares, pool.PrevBondedShares) require.Equal(t, initialPool.BondedTokens, pool.BondedTokens) - require.Equal(t, initialPool.NextInflation(params), pool.Inflation) - initialPool = initialPool.ProcessProvisions(params) // provisions are added to the pool every hour require.Equal(t, initialPool.LooseTokens, pool.LooseTokens) } diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 6afeae793..8fba41f60 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" @@ -46,6 +47,7 @@ type GaiaApp struct { keyStake *sdk.KVStoreKey tkeyStake *sdk.TransientStoreKey keySlashing *sdk.KVStoreKey + keyMint *sdk.KVStoreKey keyDistr *sdk.KVStoreKey tkeyDistr *sdk.TransientStoreKey keyGov *sdk.KVStoreKey @@ -59,6 +61,7 @@ type GaiaApp struct { bankKeeper bank.Keeper stakeKeeper stake.Keeper slashingKeeper slashing.Keeper + mintKeeper mint.Keeper distrKeeper distr.Keeper govKeeper gov.Keeper paramsKeeper params.Keeper @@ -78,6 +81,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio keyAccount: sdk.NewKVStoreKey("acc"), keyStake: sdk.NewKVStoreKey("stake"), tkeyStake: sdk.NewTransientStoreKey("transient_stake"), + keyMint: sdk.NewKVStoreKey("mint"), keyDistr: sdk.NewKVStoreKey("distr"), tkeyDistr: sdk.NewTransientStoreKey("transient_distr"), keySlashing: sdk.NewKVStoreKey("slashing"), @@ -110,6 +114,10 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio app.bankKeeper, app.paramsKeeper.Subspace(stake.DefaultParamspace), app.RegisterCodespace(stake.DefaultCodespace), ) + app.mintKeeper = mint.NewKeeper(app.cdc, app.keyMint, + app.paramsKeeper.Subspace(mint.DefaultParamspace), + app.stakeKeeper, app.feeCollectionKeeper, + ) app.distrKeeper = distr.NewKeeper( app.cdc, app.keyDistr, @@ -147,11 +155,11 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio AddRoute("stake", stake.NewQuerier(app.stakeKeeper, app.cdc)) // initialize BaseApp + app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyStake, app.keyMint, app.keyDistr, + app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams) app.SetInitChainer(app.initChainer) app.SetBeginBlocker(app.BeginBlocker) app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) - app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyStake, app.keyDistr, - app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams) app.MountStoresTransient(app.tkeyParams, app.tkeyStake, app.tkeyDistr) app.SetEndBlocker(app.EndBlocker) @@ -184,6 +192,9 @@ func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) ab // distribute rewards from previous block distr.BeginBlocker(ctx, req, app.distrKeeper) + // mint new tokens for this new block + mint.BeginBlocker(ctx, app.mintKeeper) + return abci.ResponseBeginBlock{ Tags: tags.ToKVPairs(), } @@ -232,8 +243,8 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci // load the address to pubkey map slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakeData) - gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData) + mint.InitGenesis(ctx, app.mintKeeper, genesisState.MintData) distr.InitGenesis(ctx, app.distrKeeper, genesisState.DistrData) err = GaiaValidateGenesisState(genesisState) if err != nil { @@ -289,13 +300,14 @@ func (app *GaiaApp) ExportAppStateAndValidators() (appState json.RawMessage, val return false } app.accountMapper.IterateAccounts(ctx, appendAccount) - - genState := GenesisState{ - Accounts: accounts, - StakeData: stake.WriteGenesis(ctx, app.stakeKeeper), - DistrData: distr.WriteGenesis(ctx, app.distrKeeper), - GovData: gov.WriteGenesis(ctx, app.govKeeper), - } + genState := NewGenesisState( + accounts, + stake.WriteGenesis(ctx, app.stakeKeeper), + mint.WriteGenesis(ctx, app.mintKeeper), + distr.WriteGenesis(ctx, app.distrKeeper), + gov.WriteGenesis(ctx, app.govKeeper), + slashing.GenesisState{}, // TODO create write methods + ) appState, err = codec.MarshalJSONIndent(app.cdc, genState) if err != nil { return nil, nil, err diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 9d52c9a57..df28bcf6c 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -16,6 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" tmtypes "github.com/tendermint/tendermint/types" @@ -31,12 +32,26 @@ var ( type GenesisState struct { Accounts []GenesisAccount `json:"accounts"` StakeData stake.GenesisState `json:"stake"` + MintData mint.GenesisState `json:"mint"` DistrData distr.GenesisState `json:"distr"` GovData gov.GenesisState `json:"gov"` SlashingData slashing.GenesisState `json:"slashing"` GenTxs []json.RawMessage `json:"gentxs"` } +func NewGenesisState(accounts []GenesisAccount, stakeData stake.GenesisState, mintData mint.GenesisState, + distrData distr.GenesisState, govData gov.GenesisState, slashingData slashing.GenesisState) GenesisState { + + return GenesisState{ + Accounts: accounts, + StakeData: stakeData, + MintData: mintData, + DistrData: distrData, + GovData: govData, + SlashingData: slashingData, + } +} + // GenesisAccount doesn't need pubkey or sequence type GenesisAccount struct { Address sdk.AccAddress `json:"address"` @@ -110,6 +125,7 @@ func GaiaAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (genesisStat genesisState = GenesisState{ Accounts: genaccs, StakeData: stakeData, + MintData: mint.DefaultGenesisState(), DistrData: distr.DefaultGenesisState(), GovData: gov.DefaultGenesisState(), SlashingData: slashingData, diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 41b3da741..e24919e10 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -18,6 +18,7 @@ import ( distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/gov" govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation" + "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/mock/simulation" "github.com/cosmos/cosmos-sdk/x/slashing" slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation" @@ -79,13 +80,16 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { stakeGenesis.Pool.LooseTokens = sdk.NewDec(int64(100*250) + (numInitiallyBonded * 100)) stakeGenesis.Validators = validators stakeGenesis.Bonds = delegations + // No inflation, for now - stakeGenesis.Params.InflationMax = sdk.NewDec(0) - stakeGenesis.Params.InflationMin = sdk.NewDec(0) + mintGenesis := mint.DefaultGenesisState() + mintGenesis.Params.InflationMax = sdk.NewDec(0) + mintGenesis.Params.InflationMin = sdk.NewDec(0) genesis := GenesisState{ Accounts: genesisAccounts, StakeData: stakeGenesis, + MintData: mintGenesis, DistrData: distr.DefaultGenesisWithValidators(valAddrs), SlashingData: slashingGenesis, GovData: govGenesis, diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 1639e143e..2b717244c 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -231,7 +231,6 @@ func TestGaiaCLICreateValidator(t *testing.T) { defaultParams := stake.DefaultParams() initialPool := stake.InitialPool() initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewDec(100)) // Delegate tx on GaiaAppGenState - initialPool = initialPool.ProcessProvisions(defaultParams) // provisions are added to the pool every hour // create validator cvStr := fmt.Sprintf("gaiacli tx create-validator %v", flags) @@ -290,8 +289,6 @@ func TestGaiaCLICreateValidator(t *testing.T) { require.True(t, defaultParams.Equal(params)) pool := executeGetPool(t, fmt.Sprintf("gaiacli query pool --output=json %v", flags)) - require.Equal(t, initialPool.DateLastCommissionReset, pool.DateLastCommissionReset) - require.Equal(t, initialPool.PrevBondedShares, pool.PrevBondedShares) require.Equal(t, initialPool.BondedTokens, pool.BondedTokens) } diff --git a/docs/spec/distribution/end_block.md b/docs/spec/distribution/end_block.md index 7f54ee972..2a3db9d4b 100644 --- a/docs/spec/distribution/end_block.md +++ b/docs/spec/distribution/end_block.md @@ -15,7 +15,7 @@ pool which validator holds individually (`ValidatorDistribution.ProvisionsRewardPool`). ``` -func AllocateFees(feesCollected sdk.Coins, feePool FeePool, proposer ValidatorDistribution, +func AllocateTokens(feesCollected sdk.Coins, feePool FeePool, proposer ValidatorDistribution, sumPowerPrecommitValidators, totalBondedTokens, communityTax, proposerCommissionRate sdk.Dec) diff --git a/docs/spec/mint/begin_block.md b/docs/spec/mint/begin_block.md new file mode 100644 index 000000000..7588db38b --- /dev/null +++ b/docs/spec/mint/begin_block.md @@ -0,0 +1,28 @@ +# Begin-Block + +## Inflation + +Inflation occurs at the beginning of each block. + +### NextInflation + +The target annual inflation rate is recalculated for each provisions cycle. The +inflation is also subject to a rate change (positive or negative) depending on +the distance from the desired ratio (67%). The maximum rate change possible is +defined to be 13% per year, however the annual inflation is capped as between +7% and 20%. + +NextInflation(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) { + inflationRateChangePerYear = (1 - bondedRatio/params.GoalBonded) * params.InflationRateChange + inflationRateChange = inflationRateChangePerYear/hrsPerYr + + // increase the new annual inflation for this next cycle + inflation += inflationRateChange + if inflation > params.InflationMax { + inflation = params.InflationMax + } + if inflation < params.InflationMin { + inflation = params.InflationMin + } + + return inflation diff --git a/docs/spec/mint/state.md b/docs/spec/mint/state.md new file mode 100644 index 000000000..98e8e63dd --- /dev/null +++ b/docs/spec/mint/state.md @@ -0,0 +1,31 @@ +## State + +### Minter + +The minter is a space for holding current inflation information. + + - Minter: `0x00 -> amino(minter)` + +```golang +type Minter struct { + InflationLastTime time.Time // block time which the last inflation was processed + Inflation sdk.Dec // current annual inflation rate +} +``` + +### Params + +Minting params are held in the global params store. + + - Params: `mint/params -> amino(params)` + +```golang +type Params struct { + MintDenom string // type of coin to mint + InflationRateChange sdk.Dec // maximum annual change in inflation rate + InflationMax sdk.Dec // maximum inflation rate + InflationMin sdk.Dec // minimum inflation rate + GoalBonded sdk.Dec // goal of percent bonded atoms +} +``` + diff --git a/docs/spec/staking/state.md b/docs/spec/staking/state.md index 9454aca7d..6568293cc 100644 --- a/docs/spec/staking/state.md +++ b/docs/spec/staking/state.md @@ -12,10 +12,6 @@ inflation information, etc. type Pool struct { LooseTokens int64 // tokens not associated with any bonded validator BondedTokens int64 // reserve of bonded tokens - InflationLastTime int64 // block which the last inflation was processed // TODO make time - Inflation sdk.Dec // current annual inflation rate - - DateLastCommissionReset int64 // unix timestamp for last commission accounting reset (daily) } ``` @@ -28,11 +24,6 @@ overall functioning of the stake module. ```golang type Params struct { - InflationRateChange sdk.Dec // maximum annual change in inflation rate - InflationMax sdk.Dec // maximum inflation rate - InflationMin sdk.Dec // minimum inflation rate - GoalBonded sdk.Dec // Goal of percent bonded atoms - MaxValidators uint16 // maximum number of validators BondDenom string // bondable coin denomination } diff --git a/x/auth/ante.go b/x/auth/ante.go index 8a10a0239..3fd183a0c 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -92,7 +92,7 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { if !res.IsOK() { return newCtx, res, true } - fck.addCollectedFees(newCtx, stdTx.Fee.Amount) + fck.AddCollectedFees(newCtx, stdTx.Fee.Amount) } for i := 0; i < len(stdSigs); i++ { diff --git a/x/auth/feekeeper.go b/x/auth/feekeeper.go index 45894be1b..a6be2e12d 100644 --- a/x/auth/feekeeper.go +++ b/x/auth/feekeeper.go @@ -46,7 +46,8 @@ func (fck FeeCollectionKeeper) setCollectedFees(ctx sdk.Context, coins sdk.Coins store.Set(collectedFeesKey, bz) } -func (fck FeeCollectionKeeper) addCollectedFees(ctx sdk.Context, coins sdk.Coins) sdk.Coins { +// add to the fee pool +func (fck FeeCollectionKeeper) AddCollectedFees(ctx sdk.Context, coins sdk.Coins) sdk.Coins { newCoins := fck.GetCollectedFees(ctx).Plus(coins) fck.setCollectedFees(ctx, newCoins) diff --git a/x/auth/feekeeper_test.go b/x/auth/feekeeper_test.go index 82bbe9c35..d48151161 100644 --- a/x/auth/feekeeper_test.go +++ b/x/auth/feekeeper_test.go @@ -49,11 +49,11 @@ func TestFeeCollectionKeeperAdd(t *testing.T) { require.True(t, fck.GetCollectedFees(ctx).IsEqual(emptyCoins)) // add oneCoin and check that pool is now oneCoin - fck.addCollectedFees(ctx, oneCoin) + fck.AddCollectedFees(ctx, oneCoin) require.True(t, fck.GetCollectedFees(ctx).IsEqual(oneCoin)) // add oneCoin again and check that pool is now twoCoins - fck.addCollectedFees(ctx, oneCoin) + fck.AddCollectedFees(ctx, oneCoin) require.True(t, fck.GetCollectedFees(ctx).IsEqual(twoCoins)) } diff --git a/x/distribution/abci_app.go b/x/distribution/abci_app.go index 2bdcadb6a..800ede928 100644 --- a/x/distribution/abci_app.go +++ b/x/distribution/abci_app.go @@ -13,7 +13,7 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) if ctx.BlockHeight() > 1 { previousPercentPrecommitVotes := getPreviousPercentPrecommitVotes(req) previousProposer := k.GetPreviousProposerConsAddr(ctx) - k.AllocateFees(ctx, previousPercentPrecommitVotes, previousProposer) + k.AllocateTokens(ctx, previousPercentPrecommitVotes, previousProposer) } consAddr := sdk.ConsAddress(req.Header.ProposerAddress) diff --git a/x/distribution/keeper/allocation.go b/x/distribution/keeper/allocation.go index e6dd1c969..debf0eb62 100644 --- a/x/distribution/keeper/allocation.go +++ b/x/distribution/keeper/allocation.go @@ -8,7 +8,7 @@ import ( ) // Allocate fees handles distribution of the collected fees -func (k Keeper) AllocateFees(ctx sdk.Context, percentVotes sdk.Dec, proposer sdk.ConsAddress) { +func (k Keeper) AllocateTokens(ctx sdk.Context, percentVotes sdk.Dec, proposer sdk.ConsAddress) { ctx.Logger().With("module", "x/distribution").Error(fmt.Sprintf("allocation height: %v", ctx.BlockHeight())) // get the proposer of this block diff --git a/x/distribution/keeper/allocation_test.go b/x/distribution/keeper/allocation_test.go index 441739ebe..18dfe78e5 100644 --- a/x/distribution/keeper/allocation_test.go +++ b/x/distribution/keeper/allocation_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestAllocateFeesBasic(t *testing.T) { +func TestAllocateTokensBasic(t *testing.T) { // no community tax on inputs ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) @@ -41,7 +41,7 @@ func TestAllocateFeesBasic(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // verify that these fees have been received by the feePool percentProposer := sdk.NewDecWithPrec(5, 2) @@ -52,7 +52,7 @@ func TestAllocateFeesBasic(t *testing.T) { require.True(sdk.DecEq(t, expRes, feePool.Pool[0].Amount)) } -func TestAllocateFeesWithCommunityTax(t *testing.T) { +func TestAllocateTokensWithCommunityTax(t *testing.T) { communityTax := sdk.NewDecWithPrec(1, 2) //1% ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, communityTax) stakeHandler := stake.NewHandler(sk) @@ -68,7 +68,7 @@ func TestAllocateFeesWithCommunityTax(t *testing.T) { // allocate 100 denom of fees feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // verify that these fees have been received by the feePool feePool := keeper.GetFeePool(ctx) @@ -80,7 +80,7 @@ func TestAllocateFeesWithCommunityTax(t *testing.T) { require.True(sdk.DecEq(t, expRes, feePool.Pool[0].Amount)) } -func TestAllocateFeesWithPartialPrecommitPower(t *testing.T) { +func TestAllocateTokensWithPartialPrecommitPower(t *testing.T) { communityTax := sdk.NewDecWithPrec(1, 2) ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, communityTax) stakeHandler := stake.NewHandler(sk) @@ -97,7 +97,7 @@ func TestAllocateFeesWithPartialPrecommitPower(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) percentPrecommitVotes := sdk.NewDecWithPrec(25, 2) - keeper.AllocateFees(ctx, percentPrecommitVotes, valConsAddr1) + keeper.AllocateTokens(ctx, percentPrecommitVotes, valConsAddr1) // verify that these fees have been received by the feePool feePool := keeper.GetFeePool(ctx) diff --git a/x/distribution/keeper/delegation_test.go b/x/distribution/keeper/delegation_test.go index 8415c708a..3455d48c8 100644 --- a/x/distribution/keeper/delegation_test.go +++ b/x/distribution/keeper/delegation_test.go @@ -30,7 +30,7 @@ func TestWithdrawDelegationRewardBasic(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // withdraw delegation ctx = ctx.WithBlockHeight(1) @@ -64,7 +64,7 @@ func TestWithdrawDelegationRewardWithCommission(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // withdraw delegation ctx = ctx.WithBlockHeight(1) @@ -104,7 +104,7 @@ func TestWithdrawDelegationRewardTwoDelegators(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // delegator 1 withdraw delegation ctx = ctx.WithBlockHeight(1) @@ -146,7 +146,7 @@ func TestWithdrawDelegationRewardTwoDelegatorsUneven(t *testing.T) { feeInputs := sdk.NewInt(90) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) ctx = ctx.WithBlockHeight(1) // delegator 1 withdraw delegation early, delegator 2 just keeps it's accum @@ -160,7 +160,7 @@ func TestWithdrawDelegationRewardTwoDelegatorsUneven(t *testing.T) { feeInputs = sdk.NewInt(180) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) ctx = ctx.WithBlockHeight(2) // delegator 2 now withdraws everything it's entitled to @@ -228,7 +228,7 @@ func TestWithdrawDelegationRewardsAll(t *testing.T) { feeInputs := sdk.NewInt(1000) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // withdraw delegation ctx = ctx.WithBlockHeight(1) diff --git a/x/distribution/keeper/validator_test.go b/x/distribution/keeper/validator_test.go index 57c9a8b81..62638a4d2 100644 --- a/x/distribution/keeper/validator_test.go +++ b/x/distribution/keeper/validator_test.go @@ -23,7 +23,7 @@ func TestWithdrawValidatorRewardsAllNoDelegator(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // withdraw self-delegation reward ctx = ctx.WithBlockHeight(1) @@ -55,7 +55,7 @@ func TestWithdrawValidatorRewardsAllDelegatorNoCommission(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // withdraw self-delegation reward ctx = ctx.WithBlockHeight(1) @@ -89,7 +89,7 @@ func TestWithdrawValidatorRewardsAllDelegatorWithCommission(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // withdraw validator reward ctx = ctx.WithBlockHeight(1) @@ -129,7 +129,7 @@ func TestWithdrawValidatorRewardsAllMultipleValidator(t *testing.T) { feeInputs := sdk.NewInt(1000) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // withdraw validator reward ctx = ctx.WithBlockHeight(1) @@ -175,7 +175,7 @@ func TestWithdrawValidatorRewardsAllMultipleDelegator(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // withdraw validator reward ctx = ctx.WithBlockHeight(1) diff --git a/x/mint/abci_app.go b/x/mint/abci_app.go new file mode 100644 index 000000000..cb3bd44c5 --- /dev/null +++ b/x/mint/abci_app.go @@ -0,0 +1,25 @@ +package mint + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Called every block, process inflation on the first block of every hour +func BeginBlocker(ctx sdk.Context, k Keeper) { + + blockTime := ctx.BlockHeader().Time + minter := k.GetMinter(ctx) + if blockTime.Sub(minter.InflationLastTime) < time.Hour { // only mint on the hour! + return + } + + params := k.GetParams(ctx) + totalSupply := k.sk.TotalPower(ctx) + bondedRatio := k.sk.BondedRatio(ctx) + minter.InflationLastTime = blockTime + minter, mintedCoin := minter.ProcessProvisions(params, totalSupply, bondedRatio) + k.fck.AddCollectedFees(ctx, sdk.Coins{mintedCoin}) + k.SetMinter(ctx, minter) +} diff --git a/x/mint/expected_keepers.go b/x/mint/expected_keepers.go new file mode 100644 index 000000000..8daaaf7ac --- /dev/null +++ b/x/mint/expected_keepers.go @@ -0,0 +1,15 @@ +package mint + +import sdk "github.com/cosmos/cosmos-sdk/types" + +// expected stake keeper +type StakeKeeper interface { + TotalPower(ctx sdk.Context) sdk.Dec + BondedRatio(ctx sdk.Context) sdk.Dec + InflateSupply(ctx sdk.Context, newTokens sdk.Dec) +} + +// expected fee collection keeper interface +type FeeCollectionKeeper interface { + AddCollectedFees(sdk.Context, sdk.Coins) sdk.Coins +} diff --git a/x/mint/genesis.go b/x/mint/genesis.go new file mode 100644 index 000000000..9dab64628 --- /dev/null +++ b/x/mint/genesis.go @@ -0,0 +1,55 @@ +package mint + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GenesisState - all distribution state that must be provided at genesis +type GenesisState struct { + Minter Minter `json:"Minter"` // minter object + Params Params `json:"params"` // inflation params +} + +func NewGenesisState(minter Minter, params Params) GenesisState { + return GenesisState{ + Minter: minter, + Params: params, + } +} + +// get raw genesis raw message for testing +func DefaultGenesisState() GenesisState { + return GenesisState{ + Minter: InitialMinter(), + Params: DefaultParams(), + } +} + +// new mint genesis +func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { + keeper.SetMinter(ctx, data.Minter) + keeper.SetParams(ctx, data.Params) +} + +// WriteGenesis returns a GenesisState for a given context and keeper. The +// GenesisState will contain the pool, and validator/delegator distribution info's +func WriteGenesis(ctx sdk.Context, keeper Keeper) GenesisState { + + minter := keeper.GetMinter(ctx) + params := keeper.GetParams(ctx) + return NewGenesisState(minter, params) +} + +// ValidateGenesis validates the provided staking genesis state to ensure the +// expected invariants holds. (i.e. params in correct bounds, no duplicate validators) +func ValidateGenesis(data GenesisState) error { + err := validateParams(data.Params) + if err != nil { + return err + } + err = validateMinter(data.Minter) + if err != nil { + return err + } + return nil +} diff --git a/x/mint/keeper.go b/x/mint/keeper.go new file mode 100644 index 000000000..6e6a642f4 --- /dev/null +++ b/x/mint/keeper.go @@ -0,0 +1,85 @@ +package mint + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" +) + +// keeper of the stake store +type Keeper struct { + storeKey sdk.StoreKey + cdc *codec.Codec + paramSpace params.Subspace + sk StakeKeeper + fck FeeCollectionKeeper +} + +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, + paramSpace params.Subspace, sk StakeKeeper, fck FeeCollectionKeeper) Keeper { + + keeper := Keeper{ + storeKey: key, + cdc: cdc, + paramSpace: paramSpace.WithTypeTable(ParamTypeTable()), + sk: sk, + fck: fck, + } + return keeper +} + +//____________________________________________________________________ +// Keys + +var ( + minterKey = []byte{0x00} // the one key to use for the keeper store + + // params store for inflation params + ParamStoreKeyParams = []byte("params") +) + +// ParamTable for stake module +func ParamTypeTable() params.TypeTable { + return params.NewTypeTable( + ParamStoreKeyParams, Params{}, + ) +} + +const ( + // default paramspace for params keeper + DefaultParamspace = "mint" +) + +//______________________________________________________________________ + +// get the minter +func (k Keeper) GetMinter(ctx sdk.Context) (minter Minter) { + store := ctx.KVStore(k.storeKey) + b := store.Get(minterKey) + if b == nil { + panic("Stored fee pool should not have been nil") + } + k.cdc.MustUnmarshalBinary(b, &minter) + return +} + +// set the minter +func (k Keeper) SetMinter(ctx sdk.Context, minter Minter) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinary(minter) + store.Set(minterKey, b) +} + +//______________________________________________________________________ + +// get inflation params from the global param store +func (k Keeper) GetParams(ctx sdk.Context) Params { + var params Params + k.paramSpace.Get(ctx, ParamStoreKeyParams, ¶ms) + return params +} + +// set inflation params from the global param store +func (k Keeper) SetParams(ctx sdk.Context, params Params) { + k.paramSpace.Set(ctx, ParamStoreKeyParams, ¶ms) +} diff --git a/x/mint/minter.go b/x/mint/minter.go new file mode 100644 index 000000000..da2f6c5be --- /dev/null +++ b/x/mint/minter.go @@ -0,0 +1,71 @@ +package mint + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// current inflation state +type Minter struct { + InflationLastTime time.Time `json:"inflation_last_time"` // block time which the last inflation was processed + Inflation sdk.Dec `json:"inflation"` // current annual inflation rate +} + +// minter object for a new minter +func InitialMinter() Minter { + return Minter{ + InflationLastTime: time.Unix(0, 0), + Inflation: sdk.NewDecWithPrec(13, 2), + } +} + +func validateMinter(minter Minter) error { + if minter.Inflation.LT(sdk.ZeroDec()) { + return fmt.Errorf("mint parameter Inflation should be positive, is %s ", minter.Inflation.String()) + } + if minter.Inflation.GT(sdk.OneDec()) { + return fmt.Errorf("mint parameter Inflation must be <= 1, is %s", minter.Inflation.String()) + } + return nil +} + +var hrsPerYr = sdk.NewDec(8766) // as defined by a julian year of 365.25 days + +// process provisions for an hour period +func (m Minter) ProcessProvisions(params Params, totalSupply, bondedRatio sdk.Dec) ( + minter Minter, provisions sdk.Coin) { + + m.Inflation = m.NextInflation(params, bondedRatio) + provisionsDec := m.Inflation.Mul(totalSupply).Quo(hrsPerYr) + provisions = sdk.NewCoin(params.MintDenom, provisionsDec.TruncateInt()) + return m, provisions +} + +// get the next inflation rate for the hour +func (m Minter) NextInflation(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) { + + // The target annual inflation rate is recalculated for each previsions cycle. The + // inflation is also subject to a rate change (positive or negative) depending on + // the distance from the desired ratio (67%). The maximum rate change possible is + // defined to be 13% per year, however the annual inflation is capped as between + // 7% and 20%. + + // (1 - bondedRatio/GoalBonded) * InflationRateChange + inflationRateChangePerYear := sdk.OneDec(). + Sub(bondedRatio.Quo(params.GoalBonded)). + Mul(params.InflationRateChange) + inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYr) + + // increase the new annual inflation for this next cycle + inflation = m.Inflation.Add(inflationRateChange) + if inflation.GT(params.InflationMax) { + inflation = params.InflationMax + } + if inflation.LT(params.InflationMin) { + inflation = params.InflationMin + } + + return inflation +} diff --git a/x/mint/minter_test.go b/x/mint/minter_test.go new file mode 100644 index 000000000..b022b0ec8 --- /dev/null +++ b/x/mint/minter_test.go @@ -0,0 +1,53 @@ +package mint + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestNextInflation(t *testing.T) { + minter := InitialMinter() + params := DefaultParams() + + // Governing Mechanism: + // inflationRateChangePerYear = (1- BondedRatio/ GoalBonded) * MaxInflationRateChange + + tests := []struct { + bondedRatio, setInflation, expChange sdk.Dec + }{ + // with 0% bonded atom supply the inflation should increase by InflationRateChange + {sdk.ZeroDec(), sdk.NewDecWithPrec(7, 2), params.InflationRateChange.Quo(hrsPerYr)}, + + // 100% bonded, starting at 20% inflation and being reduced + // (1 - (1/0.67))*(0.13/8667) + {sdk.OneDec(), sdk.NewDecWithPrec(20, 2), + sdk.OneDec().Sub(sdk.OneDec().Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + + // 50% bonded, starting at 10% inflation and being increased + {sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(10, 2), + sdk.OneDec().Sub(sdk.NewDecWithPrec(5, 1).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + + // test 7% minimum stop (testing with 100% bonded) + {sdk.OneDec(), sdk.NewDecWithPrec(7, 2), sdk.ZeroDec()}, + {sdk.OneDec(), sdk.NewDecWithPrec(70001, 6), sdk.NewDecWithPrec(-1, 6)}, + + // test 20% maximum stop (testing with 0% bonded) + {sdk.ZeroDec(), sdk.NewDecWithPrec(20, 2), sdk.ZeroDec()}, + {sdk.ZeroDec(), sdk.NewDecWithPrec(199999, 6), sdk.NewDecWithPrec(1, 6)}, + + // perfect balance shouldn't change inflation + {sdk.NewDecWithPrec(67, 2), sdk.NewDecWithPrec(15, 2), sdk.ZeroDec()}, + } + for i, tc := range tests { + minter.Inflation = tc.setInflation + + inflation := minter.NextInflation(params, tc.bondedRatio) + diffInflation := inflation.Sub(tc.setInflation) + + require.True(t, diffInflation.Equal(tc.expChange), + "Test Index: %v\nDiff: %v\nExpected: %v\n", i, diffInflation, tc.expChange) + } +} diff --git a/x/mint/params.go b/x/mint/params.go new file mode 100644 index 000000000..05edc8fd9 --- /dev/null +++ b/x/mint/params.go @@ -0,0 +1,43 @@ +package mint + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// mint parameters +type Params struct { + MintDenom string `json:"mint_denom"` // type of coin to mint + InflationRateChange sdk.Dec `json:"inflation_rate_change"` // maximum annual change in inflation rate + InflationMax sdk.Dec `json:"inflation_max"` // maximum inflation rate + InflationMin sdk.Dec `json:"inflation_min"` // minimum inflation rate + GoalBonded sdk.Dec `json:"goal_bonded"` // goal of percent bonded atoms +} + +// default minting module parameters +func DefaultParams() Params { + return Params{ + MintDenom: "steak", + InflationRateChange: sdk.NewDecWithPrec(13, 2), + InflationMax: sdk.NewDecWithPrec(20, 2), + InflationMin: sdk.NewDecWithPrec(7, 2), + GoalBonded: sdk.NewDecWithPrec(67, 2), + } +} + +func validateParams(params Params) error { + if params.GoalBonded.LT(sdk.ZeroDec()) { + return fmt.Errorf("mint parameter GoalBonded should be positive, is %s ", params.GoalBonded.String()) + } + if params.GoalBonded.GT(sdk.OneDec()) { + return fmt.Errorf("mint parameter GoalBonded must be <= 1, is %s", params.GoalBonded.String()) + } + if params.InflationMax.LT(params.InflationMin) { + return fmt.Errorf("mint parameter Max inflation must be greater than or equal to min inflation") + } + if params.MintDenom == "" { + return fmt.Errorf("mint parameter MintDenom can't be an empty string") + } + return nil +} diff --git a/x/stake/genesis.go b/x/stake/genesis.go index 6e1e055f2..ff8f59d44 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -103,20 +103,9 @@ func ValidateGenesis(data types.GenesisState) error { } func validateParams(params types.Params) error { - if params.GoalBonded.LTE(sdk.ZeroDec()) { - bondedPercent := params.GoalBonded.MulInt(sdk.NewInt(100)).String() - return fmt.Errorf("staking parameter GoalBonded should be positive, instead got %s percent", bondedPercent) - } - if params.GoalBonded.GT(sdk.OneDec()) { - bondedPercent := params.GoalBonded.MulInt(sdk.NewInt(100)).String() - return fmt.Errorf("staking parameter GoalBonded should be less than 100 percent, instead got %s percent", bondedPercent) - } if params.BondDenom == "" { return fmt.Errorf("staking parameter BondDenom can't be an empty string") } - if params.InflationMax.LT(params.InflationMin) { - return fmt.Errorf("staking parameter Max inflation must be greater than or equal to min inflation") - } return nil } diff --git a/x/stake/genesis_test.go b/x/stake/genesis_test.go index 2bbd8e97a..7ee16a453 100644 --- a/x/stake/genesis_test.go +++ b/x/stake/genesis_test.go @@ -121,16 +121,6 @@ func TestValidateGenesis(t *testing.T) { wantErr bool }{ {"default", func(*types.GenesisState) {}, false}, - // validate params - {"200% goalbonded", func(data *types.GenesisState) { (*data).Params.GoalBonded = sdk.OneDec().Add(sdk.OneDec()) }, true}, - {"-67% goalbonded", func(data *types.GenesisState) { (*data).Params.GoalBonded = sdk.OneDec().Neg() }, true}, - {"no bond denom", func(data *types.GenesisState) { (*data).Params.BondDenom = "" }, true}, - {"min inflation > max inflation", func(data *types.GenesisState) { - (*data).Params.InflationMin = (*data).Params.InflationMax.Add(sdk.OneDec()) - }, true}, - {"min inflation = max inflation", func(data *types.GenesisState) { - (*data).Params.InflationMax = (*data).Params.InflationMin - }, false}, // validate genesis validators {"duplicate validator", func(data *types.GenesisState) { (*data).Validators = genValidators1 diff --git a/x/stake/handler.go b/x/stake/handler.go index aff38ac6e..a75c30055 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -2,7 +2,6 @@ package stake import ( "bytes" - "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/keeper" @@ -31,7 +30,7 @@ func NewHandler(k keeper.Keeper) sdk.Handler { } } -// Called every block, process inflation, update validator set +// Called every block, update validator set func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.ValidatorUpdate) { endBlockerTags := sdk.EmptyTags() @@ -64,17 +63,6 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Valid )) } - pool := k.GetPool(ctx) - - // Process provision inflation - blockTime := ctx.BlockHeader().Time - if blockTime.Sub(pool.InflationLastTime) >= time.Hour { - params := k.GetParams(ctx) - pool.InflationLastTime = blockTime - pool = pool.ProcessProvisions(params) - k.SetPool(ctx, pool) - } - // reset the intra-transaction counter k.SetIntraTxCounter(ctx, 0) diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 8755c91b0..3cd81202a 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -85,13 +85,6 @@ func TestValidatorByPowerIndex(t *testing.T) { power2 := GetValidatorsByPowerIndexKey(validator, pool) require.True(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power2)) - // inflate a bunch - params := keeper.GetParams(ctx) - for i := 0; i < 200; i++ { - pool = pool.ProcessProvisions(params) - keeper.SetPool(ctx, pool) - } - // now the new record power index should be the same as the original record power3 := GetValidatorsByPowerIndexKey(validator, pool) require.Equal(t, power2, power3) diff --git a/x/stake/keeper/params.go b/x/stake/keeper/params.go index 294c6dcd5..b62a7688f 100644 --- a/x/stake/keeper/params.go +++ b/x/stake/keeper/params.go @@ -18,30 +18,6 @@ func ParamTypeTable() params.TypeTable { return params.NewTypeTable().RegisterParamSet(&types.Params{}) } -// InflationRateChange - Maximum annual change in inflation rate -func (k Keeper) InflationRateChange(ctx sdk.Context) (res sdk.Dec) { - k.paramstore.Get(ctx, types.KeyInflationRateChange, &res) - return -} - -// InflationMax - Maximum inflation rate -func (k Keeper) InflationMax(ctx sdk.Context) (res sdk.Dec) { - k.paramstore.Get(ctx, types.KeyInflationMax, &res) - return -} - -// InflationMin - Minimum inflation rate -func (k Keeper) InflationMin(ctx sdk.Context) (res sdk.Dec) { - k.paramstore.Get(ctx, types.KeyInflationMin, &res) - return -} - -// GoalBonded - Goal of percent bonded atoms -func (k Keeper) GoalBonded(ctx sdk.Context) (res sdk.Dec) { - k.paramstore.Get(ctx, types.KeyGoalBonded, &res) - return -} - // UnbondingTime func (k Keeper) UnbondingTime(ctx sdk.Context) (res time.Duration) { k.paramstore.Get(ctx, types.KeyUnbondingTime, &res) @@ -62,10 +38,6 @@ func (k Keeper) BondDenom(ctx sdk.Context) (res string) { // Get all parameteras as types.Params func (k Keeper) GetParams(ctx sdk.Context) (res types.Params) { - res.InflationRateChange = k.InflationRateChange(ctx) - res.InflationMax = k.InflationMax(ctx) - res.InflationMin = k.InflationMin(ctx) - res.GoalBonded = k.GoalBonded(ctx) res.UnbondingTime = k.UnbondingTime(ctx) res.MaxValidators = k.MaxValidators(ctx) res.BondDenom = k.BondDenom(ctx) diff --git a/x/stake/keeper/sdk_types.go b/x/stake/keeper/sdk_types.go index d702e845d..667284356 100644 --- a/x/stake/keeper/sdk_types.go +++ b/x/stake/keeper/sdk_types.go @@ -72,6 +72,19 @@ func (k Keeper) TotalPower(ctx sdk.Context) sdk.Dec { return pool.BondedTokens } +// total power from the bond +func (k Keeper) BondedRatio(ctx sdk.Context) sdk.Dec { + pool := k.GetPool(ctx) + return pool.BondedRatio() +} + +// when minting new tokens +func (k Keeper) InflateSupply(ctx sdk.Context, newTokens sdk.Dec) { + pool := k.GetPool(ctx) + pool.LooseTokens = pool.LooseTokens.Add(newTokens) + k.SetPool(ctx, pool) +} + //__________________________________________________________________________ // Implements DelegationSet diff --git a/x/stake/keeper/test_common.go b/x/stake/keeper/test_common.go index aeeaf0c0d..d0ac4a282 100644 --- a/x/stake/keeper/test_common.go +++ b/x/stake/keeper/test_common.go @@ -73,18 +73,6 @@ func MakeTestCodec() *codec.Codec { return cdc } -// default params without inflation -func ParamsNoInflation() types.Params { - return types.Params{ - InflationRateChange: sdk.ZeroDec(), - InflationMax: sdk.ZeroDec(), - InflationMin: sdk.ZeroDec(), - GoalBonded: sdk.NewDecWithPrec(67, 2), - MaxValidators: 100, - BondDenom: "steak", - } -} - // hogpodge of all sorts of input required for testing func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context, auth.AccountMapper, Keeper) { diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index 76b996363..f2224c865 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -234,8 +234,6 @@ func Setup(mapp *mock.App, k stake.Keeper) simulation.RandSetup { return func(r *rand.Rand, accs []simulation.Account) { ctx := mapp.NewContext(false, abci.Header{}) gen := stake.DefaultGenesisState() - gen.Params.InflationMax = sdk.NewDec(0) - gen.Params.InflationMin = sdk.NewDec(0) stake.InitGenesis(ctx, k, gen) params := k.GetParams(ctx) denom := params.BondDenom diff --git a/x/stake/stake.go b/x/stake/stake.go index 527111d2c..c4aa54702 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -58,13 +58,10 @@ var ( GetREDsByDelToValDstIndexKey = keeper.GetREDsByDelToValDstIndexKey TestingUpdateValidator = keeper.TestingUpdateValidator - DefaultParamspace = keeper.DefaultParamspace - KeyInflationRateChange = types.KeyInflationRateChange - KeyInflationMax = types.KeyInflationMax - KeyGoalBonded = types.KeyGoalBonded - KeyUnbondingTime = types.KeyUnbondingTime - KeyMaxValidators = types.KeyMaxValidators - KeyBondDenom = types.KeyBondDenom + DefaultParamspace = keeper.DefaultParamspace + KeyUnbondingTime = types.KeyUnbondingTime + KeyMaxValidators = types.KeyMaxValidators + KeyBondDenom = types.KeyBondDenom DefaultParams = types.DefaultParams InitialPool = types.InitialPool diff --git a/x/stake/types/inflation_test.go b/x/stake/types/inflation_test.go deleted file mode 100644 index 159ecb4c4..000000000 --- a/x/stake/types/inflation_test.go +++ /dev/null @@ -1,145 +0,0 @@ -package types - -import ( - "math/rand" - "testing" - - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -//changing the int in NewSource will allow you to test different, deterministic, sets of operations -var r = rand.New(rand.NewSource(6595)) - -func TestGetInflation(t *testing.T) { - pool := InitialPool() - params := DefaultParams() - - // Governing Mechanism: - // BondedRatio = BondedTokens / TotalSupply - // inflationRateChangePerYear = (1- BondedRatio/ GoalBonded) * MaxInflationRateChange - - tests := []struct { - name string - setBondedTokens, setLooseTokens, - setInflation, expectedChange sdk.Dec - }{ - // with 0% bonded atom supply the inflation should increase by InflationRateChange - {"test 1", sdk.ZeroDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(7, 2), params.InflationRateChange.Quo(hrsPerYrDec)}, - - // 100% bonded, starting at 20% inflation and being reduced - // (1 - (1/0.67))*(0.13/8667) - {"test 2", sdk.OneDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(20, 2), - sdk.OneDec().Sub(sdk.OneDec().Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrDec)}, - - // 50% bonded, starting at 10% inflation and being increased - {"test 3", sdk.OneDec(), sdk.OneDec(), sdk.NewDecWithPrec(10, 2), - sdk.OneDec().Sub(sdk.NewDecWithPrec(5, 1).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrDec)}, - - // test 7% minimum stop (testing with 100% bonded) - {"test 4", sdk.OneDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(7, 2), sdk.ZeroDec()}, - {"test 5", sdk.OneDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(70001, 6), sdk.NewDecWithPrec(-1, 6)}, - - // test 20% maximum stop (testing with 0% bonded) - {"test 6", sdk.ZeroDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(20, 2), sdk.ZeroDec()}, - {"test 7", sdk.ZeroDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(199999, 6), sdk.NewDecWithPrec(1, 6)}, - - // perfect balance shouldn't change inflation - {"test 8", sdk.NewDec(67), sdk.NewDec(33), sdk.NewDecWithPrec(15, 2), sdk.ZeroDec()}, - } - for _, tc := range tests { - pool.BondedTokens, pool.LooseTokens = tc.setBondedTokens, tc.setLooseTokens - pool.Inflation = tc.setInflation - - inflation := pool.NextInflation(params) - diffInflation := inflation.Sub(tc.setInflation) - - require.True(t, diffInflation.Equal(tc.expectedChange), - "Name: %v\nDiff: %v\nExpected: %v\n", tc.name, diffInflation, tc.expectedChange) - } -} - -// Test that provisions are correctly added to the pool and validators each hour for 1 year -func TestProcessProvisions(t *testing.T) { - pool := InitialPool() - params := DefaultParams() - - var ( - initialTotalTokens int64 = 550000000 - cumulativeExpProvs = sdk.ZeroDec() - ) - pool.LooseTokens = sdk.NewDec(initialTotalTokens) - - // process the provisions for a year - for hr := 0; hr < 100; hr++ { - var expProvisions sdk.Dec - _, expProvisions, pool = updateProvisions(t, pool, params, hr) - cumulativeExpProvs = cumulativeExpProvs.Add(expProvisions) - } - - //get the pool and do the final value checks from checkFinalPoolValues - checkFinalPoolValues(t, pool, sdk.NewDec(initialTotalTokens), cumulativeExpProvs) -} - -//_________________________________________________________________________________________ -////////////////////////////////HELPER FUNCTIONS BELOW///////////////////////////////////// - -// Final check on the global pool values for what the total tokens accumulated from each hour of provisions -func checkFinalPoolValues(t *testing.T, pool Pool, initialTotalTokens, cumulativeExpProvs sdk.Dec) { - calculatedTotalTokens := initialTotalTokens.Add(cumulativeExpProvs) - require.True(sdk.DecEq(t, calculatedTotalTokens, pool.TokenSupply())) -} - -// Processes provisions are added to the pool correctly every hour -// Returns expected Provisions, expected Inflation, and pool, to help with cumulative calculations back in main Tests -func updateProvisions(t *testing.T, pool Pool, params Params, hr int) (sdk.Dec, sdk.Dec, Pool) { - - expInflation := pool.NextInflation(params) - expProvisions := expInflation.Mul(pool.TokenSupply()).Quo(hrsPerYrDec) - startTotalSupply := pool.TokenSupply() - pool = pool.ProcessProvisions(params) - - //check provisions were added to pool - require.True(sdk.DecEq(t, startTotalSupply.Add(expProvisions), pool.TokenSupply())) - - return expInflation, expProvisions, pool -} - -// Checks that The inflation will correctly increase or decrease after an update to the pool -func checkInflation(t *testing.T, pool Pool, previousInflation, updatedInflation sdk.Dec, msg string) { - inflationChange := updatedInflation.Sub(previousInflation) - - switch { - //BELOW 67% - Rate of change positive and increasing, while we are between 7% <= and < 20% inflation - case pool.BondedRatio().LT(sdk.NewDecWithPrec(67, 2)) && updatedInflation.LT(sdk.NewDecWithPrec(20, 2)): - require.Equal(t, true, inflationChange.GT(sdk.ZeroDec()), msg) - - //BELOW 67% - Rate of change should be 0 while inflation continually stays at 20% until we reach 67% bonded ratio - case pool.BondedRatio().LT(sdk.NewDecWithPrec(67, 2)) && updatedInflation.Equal(sdk.NewDecWithPrec(20, 2)): - if previousInflation.Equal(sdk.NewDecWithPrec(20, 2)) { - require.Equal(t, true, inflationChange.IsZero(), msg) - - //This else statement covers the one off case where we first hit 20%, but we still needed a positive ROC to get to 67% bonded ratio (i.e. we went from 19.99999% to 20%) - } else { - require.Equal(t, true, inflationChange.GT(sdk.ZeroDec()), msg) - } - - //ABOVE 67% - Rate of change should be negative while the bond is above 67, and should stay negative until we reach inflation of 7% - case pool.BondedRatio().GT(sdk.NewDecWithPrec(67, 2)) && - updatedInflation.LT(sdk.NewDecWithPrec(20, 2)) && updatedInflation.GT(sdk.NewDecWithPrec(7, 2)): - require.Equal(t, true, inflationChange.LT(sdk.ZeroDec()), msg) - - //ABOVE 67% - Rate of change should be 0 while inflation continually stays at 7%. - case pool.BondedRatio().GT(sdk.NewDecWithPrec(67, 2)) && - updatedInflation.Equal(sdk.NewDecWithPrec(7, 2)): - - if previousInflation.Equal(sdk.NewDecWithPrec(7, 2)) { - require.Equal(t, true, inflationChange.IsZero(), msg) - - //This else statement covers the one off case where we first hit 7%, but we still needed a negative ROC to continue to get down to 67%. (i.e. we went from 7.00001% to 7%) - } else { - require.Equal(t, true, inflationChange.LT(sdk.ZeroDec()), msg) - } - } -} diff --git a/x/stake/types/params.go b/x/stake/types/params.go index abc1db4d5..5915570c1 100644 --- a/x/stake/types/params.go +++ b/x/stake/types/params.go @@ -6,7 +6,6 @@ import ( "time" "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params" ) @@ -24,24 +23,15 @@ const ( // nolint - Keys for parameter access var ( - KeyInflationRateChange = []byte("InflationRateChange") - KeyInflationMax = []byte("InflationMax") - KeyInflationMin = []byte("InflationMin") - KeyGoalBonded = []byte("GoalBonded") - KeyUnbondingTime = []byte("UnbondingTime") - KeyMaxValidators = []byte("MaxValidators") - KeyBondDenom = []byte("BondDenom") + KeyUnbondingTime = []byte("UnbondingTime") + KeyMaxValidators = []byte("MaxValidators") + KeyBondDenom = []byte("BondDenom") ) var _ params.ParamSet = (*Params)(nil) // Params defines the high level settings for staking type Params struct { - InflationRateChange sdk.Dec `json:"inflation_rate_change"` // maximum annual change in inflation rate - InflationMax sdk.Dec `json:"inflation_max"` // maximum inflation rate - InflationMin sdk.Dec `json:"inflation_min"` // minimum inflation rate - GoalBonded sdk.Dec `json:"goal_bonded"` // Goal of percent bonded atoms - UnbondingTime time.Duration `json:"unbonding_time"` MaxValidators uint16 `json:"max_validators"` // maximum number of validators @@ -51,10 +41,6 @@ type Params struct { // Implements params.ParamSet func (p *Params) KeyValuePairs() params.KeyValuePairs { return params.KeyValuePairs{ - {KeyInflationRateChange, &p.InflationRateChange}, - {KeyInflationMax, &p.InflationMax}, - {KeyInflationMin, &p.InflationMin}, - {KeyGoalBonded, &p.GoalBonded}, {KeyUnbondingTime, &p.UnbondingTime}, {KeyMaxValidators, &p.MaxValidators}, {KeyBondDenom, &p.BondDenom}, @@ -71,13 +57,9 @@ func (p Params) Equal(p2 Params) bool { // DefaultParams returns a default set of parameters. func DefaultParams() Params { return Params{ - InflationRateChange: sdk.NewDecWithPrec(13, 2), - InflationMax: sdk.NewDecWithPrec(20, 2), - InflationMin: sdk.NewDecWithPrec(7, 2), - GoalBonded: sdk.NewDecWithPrec(67, 2), - UnbondingTime: defaultUnbondingTime, - MaxValidators: 100, - BondDenom: "steak", + UnbondingTime: defaultUnbondingTime, + MaxValidators: 100, + BondDenom: "steak", } } @@ -85,11 +67,7 @@ func DefaultParams() Params { // parameters. func (p Params) HumanReadableString() string { - resp := "Pool \n" - resp += fmt.Sprintf("Maximum Annual Inflation Rate Change: %s\n", p.InflationRateChange) - resp += fmt.Sprintf("Max Inflation Rate: %s\n", p.InflationMax) - resp += fmt.Sprintf("Min Inflation Tate: %s\n", p.InflationMin) - resp += fmt.Sprintf("Bonded Token Goal (%s): %s\n", "s", p.GoalBonded) + resp := "Params \n" resp += fmt.Sprintf("Unbonding Time: %s\n", p.UnbondingTime) resp += fmt.Sprintf("Max Validators: %d: \n", p.MaxValidators) resp += fmt.Sprintf("Bonded Coin Denomination: %s\n", p.BondDenom) diff --git a/x/stake/types/pool.go b/x/stake/types/pool.go index c7cb69748..e2015dcaf 100644 --- a/x/stake/types/pool.go +++ b/x/stake/types/pool.go @@ -3,7 +3,6 @@ package types import ( "bytes" "fmt" - "time" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -11,15 +10,8 @@ import ( // Pool - dynamic parameters of the current state type Pool struct { - LooseTokens sdk.Dec `json:"loose_tokens"` // tokens which are not bonded in a validator - BondedTokens sdk.Dec `json:"bonded_tokens"` // reserve of bonded tokens - InflationLastTime time.Time `json:"inflation_last_time"` // block which the last inflation was processed - Inflation sdk.Dec `json:"inflation"` // current annual inflation rate - - DateLastCommissionReset int64 `json:"date_last_commission_reset"` // unix timestamp for last commission accounting reset (daily) - - // Fee Related - PrevBondedShares sdk.Dec `json:"prev_bonded_shares"` // last recorded bonded shares - for fee calculations + LooseTokens sdk.Dec `json:"loose_tokens"` // tokens which are not bonded in a validator + BondedTokens sdk.Dec `json:"bonded_tokens"` // reserve of bonded tokens } // nolint @@ -32,12 +24,8 @@ func (p Pool) Equal(p2 Pool) bool { // initial pool for testing func InitialPool() Pool { return Pool{ - LooseTokens: sdk.ZeroDec(), - BondedTokens: sdk.ZeroDec(), - InflationLastTime: time.Unix(0, 0), - Inflation: sdk.NewDecWithPrec(7, 2), - DateLastCommissionReset: 0, - PrevBondedShares: sdk.ZeroDec(), + LooseTokens: sdk.ZeroDec(), + BondedTokens: sdk.ZeroDec(), } } @@ -79,52 +67,6 @@ func (p Pool) bondedTokensToLoose(bondedTokens sdk.Dec) Pool { return p } -//_______________________________________________________________________ -// Inflation - -const precision = 10000 // increased to this precision for accuracy -var hrsPerYrDec = sdk.NewDec(8766) // as defined by a julian year of 365.25 days - -// process provisions for an hour period -func (p Pool) ProcessProvisions(params Params) Pool { - p.Inflation = p.NextInflation(params) - provisions := p.Inflation. - Mul(p.TokenSupply()). - Quo(hrsPerYrDec) - - // TODO add to the fees provisions - p.LooseTokens = p.LooseTokens.Add(provisions) - return p -} - -// get the next inflation rate for the hour -func (p Pool) NextInflation(params Params) (inflation sdk.Dec) { - - // The target annual inflation rate is recalculated for each previsions cycle. The - // inflation is also subject to a rate change (positive or negative) depending on - // the distance from the desired ratio (67%). The maximum rate change possible is - // defined to be 13% per year, however the annual inflation is capped as between - // 7% and 20%. - - // (1 - bondedRatio/GoalBonded) * InflationRateChange - inflationRateChangePerYear := sdk.OneDec(). - Sub(p.BondedRatio(). - Quo(params.GoalBonded)). - Mul(params.InflationRateChange) - inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYrDec) - - // increase the new annual inflation for this next cycle - inflation = p.Inflation.Add(inflationRateChange) - if inflation.GT(params.InflationMax) { - inflation = params.InflationMax - } - if inflation.LT(params.InflationMin) { - inflation = params.InflationMin - } - - return inflation -} - // HumanReadableString returns a human readable string representation of a // pool. func (p Pool) HumanReadableString() string { @@ -134,10 +76,6 @@ func (p Pool) HumanReadableString() string { resp += fmt.Sprintf("Bonded Tokens: %s\n", p.BondedTokens) resp += fmt.Sprintf("Token Supply: %s\n", p.TokenSupply()) resp += fmt.Sprintf("Bonded Ratio: %v\n", p.BondedRatio()) - resp += fmt.Sprintf("Previous Inflation Block: %s\n", p.InflationLastTime) - resp += fmt.Sprintf("Inflation: %v\n", p.Inflation) - resp += fmt.Sprintf("Date of Last Commission Reset: %d\n", p.DateLastCommissionReset) - resp += fmt.Sprintf("Previous Bonded Shares: %v\n", p.PrevBondedShares) return resp } diff --git a/x/stake/types/validator_test.go b/x/stake/types/validator_test.go index 36ac0da57..b01b2b744 100644 --- a/x/stake/types/validator_test.go +++ b/x/stake/types/validator_test.go @@ -3,7 +3,6 @@ package types import ( "fmt" "testing" - "time" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -185,10 +184,8 @@ func TestRemoveDelShares(t *testing.T) { DelegatorShares: delShares, } pool := Pool{ - BondedTokens: sdk.NewDec(248305), - LooseTokens: sdk.NewDec(232147), - InflationLastTime: time.Unix(0, 0), - Inflation: sdk.NewDecWithPrec(7, 2), + BondedTokens: sdk.NewDec(248305), + LooseTokens: sdk.NewDec(232147), } shares := sdk.NewDec(29) _, newPool, tokens := validator.RemoveDelShares(pool, shares) @@ -238,10 +235,8 @@ func TestPossibleOverflow(t *testing.T) { DelegatorShares: delShares, } pool := Pool{ - LooseTokens: sdk.NewDec(100), - BondedTokens: poolTokens, - InflationLastTime: time.Unix(0, 0), - Inflation: sdk.NewDecWithPrec(7, 2), + LooseTokens: sdk.NewDec(100), + BondedTokens: poolTokens, } tokens := int64(71) msg := fmt.Sprintf("validator %#v", validator)