diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index 329b93ff6964..a69997ec6567 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -286,8 +286,11 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress accTokens := sdk.TokensFromTendermintPower(100) accAuth.Coins = sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, accTokens)} acc := gapp.NewGenesisAccount(&accAuth) + genesisState.Accounts = append(genesisState.Accounts, acc) genesisState.StakingData.Pool.NotBondedTokens = genesisState.StakingData.Pool.NotBondedTokens.Add(accTokens) + genesisState.SupplyData.Supplier.TotalSupply = genesisState.SupplyData.Supplier.TotalSupply.Add(accAuth.Coins) + genesisState.SupplyData.Supplier.TotalSupply = genesisState.SupplyData.Supplier.CirculatingSupply.Add(accAuth.Coins) } inflationMin := sdk.ZeroDec() diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 26d920d5ea8e..939f333fc301 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -23,6 +23,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/supply" ) const ( @@ -45,6 +46,7 @@ type GaiaApp struct { // keys to access the substores keyMain *sdk.KVStoreKey keyAccount *sdk.KVStoreKey + keySupply *sdk.KVStoreKey keyStaking *sdk.KVStoreKey tkeyStaking *sdk.TransientStoreKey keySlashing *sdk.KVStoreKey @@ -62,6 +64,7 @@ type GaiaApp struct { bankKeeper bank.Keeper stakingKeeper staking.Keeper slashingKeeper slashing.Keeper + supplyKeeper supply.Keeper mintKeeper mint.Keeper distrKeeper distr.Keeper govKeeper gov.Keeper @@ -85,6 +88,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b invCheckPeriod: invCheckPeriod, keyMain: sdk.NewKVStoreKey(bam.MainStoreKey), keyAccount: sdk.NewKVStoreKey(auth.StoreKey), + keySupply: sdk.NewKVStoreKey(supply.StoreKey), keyStaking: sdk.NewKVStoreKey(staking.StoreKey), tkeyStaking: sdk.NewTransientStoreKey(staking.TStoreKey), keyMint: sdk.NewKVStoreKey(mint.StoreKey), @@ -113,27 +117,31 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b app.paramsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, ) - app.feeCollectionKeeper = auth.NewFeeCollectionKeeper( - app.cdc, - app.keyFeeCollection, - ) stakingKeeper := staking.NewKeeper( app.cdc, app.keyStaking, app.tkeyStaking, app.bankKeeper, app.paramsKeeper.Subspace(staking.DefaultParamspace), staking.DefaultCodespace, ) - app.mintKeeper = mint.NewKeeper(app.cdc, app.keyMint, - app.paramsKeeper.Subspace(mint.DefaultParamspace), - &stakingKeeper, app.feeCollectionKeeper, + app.feeCollectionKeeper = auth.NewFeeCollectionKeeper( + app.cdc, + app.keyFeeCollection, + ) + app.supplyKeeper = supply.NewKeeper( + app.cdc, + app.keySupply, ) app.distrKeeper = distr.NewKeeper( app.cdc, app.keyDistr, app.paramsKeeper.Subspace(distr.DefaultParamspace), - app.bankKeeper, &stakingKeeper, app.feeCollectionKeeper, + app.bankKeeper, &stakingKeeper, app.feeCollectionKeeper, app.supplyKeeper, distr.DefaultCodespace, ) + app.mintKeeper = mint.NewKeeper(app.cdc, app.keyMint, + app.paramsKeeper.Subspace(mint.DefaultParamspace), + app.supplyKeeper, &stakingKeeper, app.feeCollectionKeeper, + ) app.slashingKeeper = slashing.NewKeeper( app.cdc, app.keySlashing, @@ -143,7 +151,8 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b app.govKeeper = gov.NewKeeper( app.cdc, app.keyGov, - app.paramsKeeper, app.paramsKeeper.Subspace(gov.DefaultParamspace), app.bankKeeper, &stakingKeeper, + app.paramsKeeper, app.paramsKeeper.Subspace(gov.DefaultParamspace), + app.bankKeeper, app.accountKeeper, app.supplyKeeper, &stakingKeeper, gov.DefaultCodespace, ) app.crisisKeeper = crisis.NewKeeper( @@ -163,9 +172,11 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b // register the crisis routes bank.RegisterInvariants(&app.crisisKeeper, app.accountKeeper) distr.RegisterInvariants(&app.crisisKeeper, app.distrKeeper, app.stakingKeeper) - staking.RegisterInvariants(&app.crisisKeeper, app.stakingKeeper, app.feeCollectionKeeper, app.distrKeeper, app.accountKeeper) + staking.RegisterInvariants(&app.crisisKeeper, app.stakingKeeper, app.supplyKeeper) + supply.RegisterInvariants(&app.crisisKeeper, app.supplyKeeper, app.accountKeeper, + app.distrKeeper, app.feeCollectionKeeper, app.stakingKeeper) - // register message routes + // register transaction messages routes app.Router(). AddRoute(bank.RouterKey, bank.NewHandler(app.bankKeeper)). AddRoute(staking.RouterKey, staking.NewHandler(app.stakingKeeper)). @@ -174,6 +185,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b AddRoute(gov.RouterKey, gov.NewHandler(app.govKeeper)). AddRoute(crisis.RouterKey, crisis.NewHandler(app.crisisKeeper)) + // register query routes app.QueryRouter(). AddRoute(auth.QuerierRoute, auth.NewQuerier(app.accountKeeper)). AddRoute(distr.QuerierRoute, distr.NewQuerier(app.distrKeeper)). @@ -184,7 +196,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b // initialize BaseApp app.MountStores(app.keyMain, app.keyAccount, app.keyStaking, app.keyMint, app.keyDistr, - app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams, + app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams, app.keySupply, app.tkeyParams, app.tkeyStaking, app.tkeyDistr, ) app.SetInitChainer(app.initChainer) @@ -281,6 +293,7 @@ func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisSt gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData) crisis.InitGenesis(ctx, app.crisisKeeper, genesisState.CrisisData) mint.InitGenesis(ctx, app.mintKeeper, genesisState.MintData) + supply.InitGenesis(ctx, app.supplyKeeper, genesisState.SupplyData) // validate genesis state if err := GaiaValidateGenesisState(genesisState); err != nil { diff --git a/cmd/gaia/app/app_test.go b/cmd/gaia/app/app_test.go index 95fa02119d68..de21f585ac3f 100644 --- a/cmd/gaia/app/app_test.go +++ b/cmd/gaia/app/app_test.go @@ -18,6 +18,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/supply" abci "github.com/tendermint/tendermint/abci/types" ) @@ -38,6 +39,7 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error { gov.DefaultGenesisState(), crisis.DefaultGenesisState(), slashing.DefaultGenesisState(), + supply.DefaultGenesisState(), ) stateBytes, err := codec.MarshalJSONIndent(gapp.cdc, genesisState) diff --git a/cmd/gaia/app/export.go b/cmd/gaia/app/export.go index 2cb110bf8926..aa52c2e627e7 100644 --- a/cmd/gaia/app/export.go +++ b/cmd/gaia/app/export.go @@ -17,6 +17,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/supply" ) // export the state of gaia for a genesis file @@ -49,6 +50,7 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteLis gov.ExportGenesis(ctx, app.govKeeper), crisis.ExportGenesis(ctx, app.crisisKeeper), slashing.ExportGenesis(ctx, app.slashingKeeper), + supply.ExportGenesis(ctx, app.supplyKeeper), ) appState, err = codec.MarshalJSONIndent(app.cdc, genState) if err != nil { diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 997cd131c751..1c1b365b2d01 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -23,6 +23,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/supply" ) var ( @@ -42,14 +43,22 @@ type GenesisState struct { GovData gov.GenesisState `json:"gov"` CrisisData crisis.GenesisState `json:"crisis"` SlashingData slashing.GenesisState `json:"slashing"` + SupplyData supply.GenesisState `json:"supply"` GenTxs []json.RawMessage `json:"gentxs"` } -func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState, +// NewGenesisState creates a new Gaia genesis state instance +func NewGenesisState( + accounts []GenesisAccount, + authData auth.GenesisState, bankData bank.GenesisState, - stakingData staking.GenesisState, mintData mint.GenesisState, - distrData distr.GenesisState, govData gov.GenesisState, crisisData crisis.GenesisState, - slashingData slashing.GenesisState) GenesisState { + stakingData staking.GenesisState, + mintData mint.GenesisState, + distrData distr.GenesisState, + govData gov.GenesisState, + crisisData crisis.GenesisState, + slashingData slashing.GenesisState, + supplyData supply.GenesisState) GenesisState { return GenesisState{ Accounts: accounts, @@ -61,6 +70,7 @@ func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState, GovData: govData, CrisisData: crisisData, SlashingData: slashingData, + SupplyData: supplyData, } } @@ -88,8 +98,12 @@ type GenesisAccount struct { DelegatedVesting sdk.Coins `json:"delegated_vesting"` // delegated vesting coins at time of delegation StartTime int64 `json:"start_time"` // vesting start time (UNIX Epoch time) EndTime int64 `json:"end_time"` // vesting end time (UNIX Epoch time) + + // module account fields + Module string `json:"module"` // name of the module } +// NewGenesisAccount creates a GenesisAccount instance from a BaseAccount func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount { return GenesisAccount{ Address: acc.Address, @@ -99,6 +113,7 @@ func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount { } } +// NewGenesisAccountI creates a GenesisAccount instance from an Account interface func NewGenesisAccountI(acc auth.Account) GenesisAccount { gacc := GenesisAccount{ Address: acc.GetAddress(), @@ -116,10 +131,15 @@ func NewGenesisAccountI(acc auth.Account) GenesisAccount { gacc.EndTime = vacc.GetEndTime() } + macc, ok := acc.(auth.ModuleAccount) + if ok { + gacc.Module = macc.GetModuleName() + } + return gacc } -// convert GenesisAccount to auth.BaseAccount +// ToAccount converts a GenesisAccount to an Account interface func (ga *GenesisAccount) ToAccount() auth.Account { bacc := &auth.BaseAccount{ Address: ga.Address, @@ -128,6 +148,7 @@ func (ga *GenesisAccount) ToAccount() auth.Account { Sequence: ga.Sequence, } + // vesting accounts if !ga.OriginalVesting.IsZero() { baseVestingAcc := &auth.BaseVestingAccount{ BaseAccount: bacc, @@ -151,10 +172,15 @@ func (ga *GenesisAccount) ToAccount() auth.Account { } } + // module accounts + if ga.Module != "" { + return auth.NewModuleHolderAccount(ga.Module) + } + return bacc } -// Create the core parameters for genesis initialization for gaia +// GaiaAppGenState creates the core parameters for genesis initialization for gaia // note that the pubkey input is this machines pubkey func GaiaAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( genesisState GenesisState, err error) { @@ -168,7 +194,6 @@ func GaiaAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []js return genesisState, errors.New("there must be at least one genesis tx") } - stakingData := genesisState.StakingData for i, genTx := range appGenTxs { var tx auth.StdTx if err := cdc.UnmarshalJSON(genTx, &tx); err != nil { @@ -187,16 +212,8 @@ func GaiaAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []js } } - for _, acc := range genesisState.Accounts { - for _, coin := range acc.Coins { - if coin.Denom == genesisState.StakingData.Params.BondDenom { - stakingData.Pool.NotBondedTokens = stakingData.Pool.NotBondedTokens. - Add(coin.Amount) // increase the supply - } - } - } + genesisState = updateSupply(genesisState, genDoc.GenesisTime) - genesisState.StakingData = stakingData genesisState.GenTxs = appGenTxs return genesisState, nil @@ -253,8 +270,11 @@ func GaiaValidateGenesisState(genesisState GenesisState) error { if err := crisis.ValidateGenesis(genesisState.CrisisData); err != nil { return err } + if err := slashing.ValidateGenesis(genesisState.SlashingData); err != nil { + return err + } - return slashing.ValidateGenesis(genesisState.SlashingData) + return supply.ValidateGenesis(genesisState.SupplyData) } // validateGenesisStateAccounts performs validation of genesis accounts. It @@ -292,7 +312,7 @@ func validateGenesisStateAccounts(accs []GenesisAccount) error { return nil } -// GaiaAppGenState but with JSON +// GaiaAppGenStateJSON GaiaAppGenState but with JSON func GaiaAppGenStateJSON(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( appState json.RawMessage, err error) { // create the final app state @@ -403,6 +423,71 @@ func CollectStdTxs(cdc *codec.Codec, moniker string, genTxsDir string, genDoc tm return appGenTxs, persistentPeers, nil } +// updateSupply updates the total, circulating, vesting, modules and staking (bonded and not bonded) +// supplies from the info provided on genesis accounts +func updateSupply(genesisState GenesisState, genesisTime time.Time) GenesisState { + var total, totalRewards sdk.Coins + var remainingRewards sdk.DecCoins + + notBondedTokens := genesisState.StakingData.Pool.NotBondedTokens + supplier := &genesisState.SupplyData.Supplier + + for _, genAcc := range genesisState.Accounts { + // update initial vesting, circulating and bonded supplies from vesting accounts + if genAcc.OriginalVesting.IsAllPositive() { + if genesisTime.Unix() < genAcc.EndTime { + supplier.Inflate(supply.TypeVesting, genAcc.OriginalVesting) + } + } + + // update modules accounts supply + if genAcc.Module != "" { + supplier.Inflate(supply.TypeModules, genAcc.Coins) + } else { + // update circulating supply from basic and vested accounts + supplier.Inflate(supply.TypeCirculating, genAcc.Coins) + } + + // update staking pool's not bonded supply + notBondedTokens = notBondedTokens.Add(genAcc.Coins.AmountOf(genesisState.StakingData.Params.BondDenom)) + + total = total.Add(genAcc.Coins) + } + + genesisState.StakingData.Pool.NotBondedTokens = notBondedTokens + + // update total supply + bondedTokens := sdk.NewCoins(sdk.NewCoin(genesisState.StakingData.Params.BondDenom, genesisState.StakingData.Pool.BondedTokens)) + collectedFees := genesisState.AuthData.CollectedFees + communityPool, remainingCommunityPool := genesisState.DistrData.FeePool.CommunityPool.TruncateDecimal() + + for _, outstandingReward := range genesisState.DistrData.OutstandingRewards { + reward, remainder := outstandingReward.OutstandingRewards.TruncateDecimal() + totalRewards = totalRewards.Add(reward) + remainingRewards = remainingRewards.Add(remainder) + } + + remaining, _ := remainingCommunityPool.Add(remainingRewards).TruncateDecimal() + + supplier.TotalSupply = total. + Add(bondedTokens). + Add(collectedFees). + Add(communityPool). + Add(totalRewards). + Add(remaining) + + // fmt.Printf(` + // total: %s + // bondedTokens: %s + // collectedFees: %s + // communityPool: %s + // totalRewards: %s + // remaining: %s + // `, total, bondedTokens, collectedFees, communityPool, totalRewards, remaining) + + return genesisState +} + func NewDefaultGenesisAccount(addr sdk.AccAddress) GenesisAccount { accAuth := auth.NewBaseAccountWithAddress(addr) coins := sdk.Coins{ diff --git a/types/staking.go b/types/staking.go index 54f28f97279d..9809d23618e7 100644 --- a/types/staking.go +++ b/types/staking.go @@ -109,7 +109,6 @@ type ValidatorSet interface { Validator(Context, ValAddress) Validator // get a particular validator by operator address ValidatorByConsAddr(Context, ConsAddress) Validator // get a particular validator by consensus address TotalBondedTokens(Context) Int // total bonded tokens within the validator set - TotalTokens(Context) Int // total token supply // slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction Slash(Context, ConsAddress, int64, int64, Dec) diff --git a/x/auth/account.go b/x/auth/account.go index 0c0e71a48c40..af63f3f79ae3 100644 --- a/x/auth/account.go +++ b/x/auth/account.go @@ -60,6 +60,13 @@ type VestingAccount interface { GetDelegatedVesting() sdk.Coins } +// ModuleAccount defines an account type for modules that hold tokens +type ModuleAccount interface { + Account + + GetModuleName() string +} + // AccountDecoder unmarshals account bytes // TODO: Think about removing type AccountDecoder func(accountBytes []byte) (Account, error) @@ -511,3 +518,48 @@ func (dva *DelayedVestingAccount) GetStartTime() int64 { func (dva *DelayedVestingAccount) GetEndTime() int64 { return dva.EndTime } + +//----------------------------------------------------------------------------- +// Module Minter Account + +var _ ModuleAccount = (*ModuleMinterAccount)(nil) + +// ModuleMinterAccount defines an account for modules that held coins on a pool +type ModuleMinterAccount struct { + *BaseAccount + + Module string `json:"module"` // name of the module +} + +// NewModuleMinterAccount creates a new BaseTokenHolder instance +func NewModuleMinterAccount(moduleName string) *ModuleMinterAccount { + moduleAddress := sdk.AccAddress(crypto.AddressHash([]byte(moduleName))) + + baseAcc := NewBaseAccountWithAddress(moduleAddress) + return &ModuleMinterAccount{ + BaseAccount: &baseAcc, + Module: moduleName, + } +} + +// GetModuleName returns the the name of the holder's module +func (mma ModuleMinterAccount) GetModuleName() string { + return mma.Module +} + +//----------------------------------------------------------------------------- +// Module Holder Account + +var _ ModuleAccount = (*ModuleHolderAccount)(nil) + +// ModuleHolderAccount defines an account for modules that held coins on a pool +type ModuleHolderAccount struct { + *ModuleMinterAccount +} + +// NewModuleHolderAccount creates a new BaseTokenHolder instance +func NewModuleHolderAccount(moduleName string) *ModuleHolderAccount { + moduleMinterAcc := NewModuleMinterAccount(moduleName) + + return &ModuleHolderAccount{ModuleMinterAccount: moduleMinterAcc} +} diff --git a/x/auth/codec.go b/x/auth/codec.go index 0d7694300f1f..6ee243c6b167 100644 --- a/x/auth/codec.go +++ b/x/auth/codec.go @@ -12,6 +12,9 @@ func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(&BaseVestingAccount{}, "auth/BaseVestingAccount", nil) cdc.RegisterConcrete(&ContinuousVestingAccount{}, "auth/ContinuousVestingAccount", nil) cdc.RegisterConcrete(&DelayedVestingAccount{}, "auth/DelayedVestingAccount", nil) + cdc.RegisterInterface((*ModuleAccount)(nil), nil) + cdc.RegisterConcrete(&ModuleHolderAccount{}, "auth/ModuleHolderAccount", nil) + cdc.RegisterConcrete(&ModuleMinterAccount{}, "auth/ModuleMinterAccount", nil) cdc.RegisterConcrete(StdTx{}, "auth/StdTx", nil) } @@ -23,6 +26,9 @@ func RegisterBaseAccount(cdc *codec.Codec) { cdc.RegisterConcrete(&BaseVestingAccount{}, "cosmos-sdk/BaseVestingAccount", nil) cdc.RegisterConcrete(&ContinuousVestingAccount{}, "cosmos-sdk/ContinuousVestingAccount", nil) cdc.RegisterConcrete(&DelayedVestingAccount{}, "cosmos-sdk/DelayedVestingAccount", nil) + cdc.RegisterInterface((*ModuleAccount)(nil), nil) + cdc.RegisterConcrete(&ModuleHolderAccount{}, "auth/ModuleHolderAccount", nil) + cdc.RegisterConcrete(&ModuleMinterAccount{}, "auth/ModuleMinterAccount", nil) codec.RegisterCrypto(cdc) } diff --git a/x/bank/codec.go b/x/bank/codec.go index 58c59d5f6ac9..192b3f0e7e50 100644 --- a/x/bank/codec.go +++ b/x/bank/codec.go @@ -4,7 +4,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" ) -// Register concrete types on codec codec +// RegisterCodec registers concrete types on codec func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgSend{}, "cosmos-sdk/MsgSend", nil) cdc.RegisterConcrete(MsgMultiSend{}, "cosmos-sdk/MsgMultiSend", nil) diff --git a/x/crisis/handler.go b/x/crisis/handler.go index da30d976aaa1..9ac13a163460 100644 --- a/x/crisis/handler.go +++ b/x/crisis/handler.go @@ -75,6 +75,7 @@ func handleMsgVerifyInvariant(ctx sdk.Context, msg MsgVerifyInvariant, k Keeper) } resTags := sdk.NewTags( + tags.Category, tags.TxCategory, tags.Sender, msg.Sender.String(), tags.Invariant, msg.InvariantRoute, ) diff --git a/x/crisis/handler_test.go b/x/crisis/handler_test.go index acc920e4a1ea..e4f512f174ce 100644 --- a/x/crisis/handler_test.go +++ b/x/crisis/handler_test.go @@ -23,7 +23,7 @@ var ( func CreateTestInput(t *testing.T) (sdk.Context, Keeper, auth.AccountKeeper, distr.Keeper) { communityTax := sdk.NewDecWithPrec(2, 2) - ctx, accKeeper, bankKeeper, distrKeeper, _, feeCollectionKeeper, paramsKeeper := + ctx, accKeeper, bankKeeper, distrKeeper, _, feeCollectionKeeper, _, paramsKeeper := distr.CreateTestInputAdvanced(t, false, 10, communityTax) paramSpace := paramsKeeper.Subspace(DefaultParamspace) diff --git a/x/crisis/keeper.go b/x/crisis/keeper.go index 95c7cf09c1a0..9fc11dadc9dc 100644 --- a/x/crisis/keeper.go +++ b/x/crisis/keeper.go @@ -29,7 +29,7 @@ func NewKeeper(paramSpace params.Subspace, } } -// register routes for the +// RegisterRoute register routes for the invant func (k *Keeper) RegisterRoute(moduleName, route string, invar sdk.Invariant) { invarRoute := NewInvarRoute(moduleName, route, invar) k.routes = append(k.routes, invarRoute) diff --git a/x/crisis/tags/tags.go b/x/crisis/tags/tags.go index da3c66793680..15b6222623c5 100644 --- a/x/crisis/tags/tags.go +++ b/x/crisis/tags/tags.go @@ -6,6 +6,9 @@ import ( // Crisis module tags var ( + TxCategory = "crisis" + Sender = sdk.TagSender + Category = sdk.TagCategory Invariant = "invariant" ) diff --git a/x/distribution/keeper/allocation_test.go b/x/distribution/keeper/allocation_test.go index 956121d581d5..ff84c548d6b5 100644 --- a/x/distribution/keeper/allocation_test.go +++ b/x/distribution/keeper/allocation_test.go @@ -105,7 +105,7 @@ func TestAllocateTokensToManyValidators(t *testing.T) { func TestAllocateTokensTruncation(t *testing.T) { communityTax := sdk.NewDec(0) - ctx, _, _, k, sk, fck, _ := CreateTestInputAdvanced(t, false, 1000000, communityTax) + ctx, _, _, k, sk, fck, _, _ := CreateTestInputAdvanced(t, false, 1000000, communityTax) sh := staking.NewHandler(sk) // create validator with 10% commission diff --git a/x/distribution/keeper/delegation.go b/x/distribution/keeper/delegation.go index 01b3ffaa64f4..5da0f8441e08 100644 --- a/x/distribution/keeper/delegation.go +++ b/x/distribution/keeper/delegation.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/cosmos/cosmos-sdk/x/supply" ) // initialize starting info for a new delegation @@ -155,6 +156,8 @@ func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val sdk.Validator, de if _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, coins); err != nil { return nil, err } + + k.supplyKeeper.InflateSupply(ctx, supply.TypeCirculating, coins) } // remove delegator starting info diff --git a/x/distribution/keeper/expected_keepers.go b/x/distribution/keeper/expected_keepers.go new file mode 100644 index 000000000000..6d978ba92616 --- /dev/null +++ b/x/distribution/keeper/expected_keepers.go @@ -0,0 +1,10 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// SupplyKeeper expected supply keeper +type SupplyKeeper interface { + InflateSupply(ctx sdk.Context, supplyType string, amount sdk.Coins) +} diff --git a/x/distribution/keeper/keeper.go b/x/distribution/keeper/keeper.go index 37ce7b859d9c..348af2fcda41 100644 --- a/x/distribution/keeper/keeper.go +++ b/x/distribution/keeper/keeper.go @@ -5,6 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/supply" "github.com/tendermint/tendermint/libs/log" ) @@ -17,6 +18,7 @@ type Keeper struct { bankKeeper types.BankKeeper stakingKeeper types.StakingKeeper feeCollectionKeeper types.FeeCollectionKeeper + supplyKeeper SupplyKeeper // codespace codespace sdk.CodespaceType @@ -24,7 +26,7 @@ type Keeper struct { // create a new keeper func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramSpace params.Subspace, ck types.BankKeeper, - sk types.StakingKeeper, fck types.FeeCollectionKeeper, codespace sdk.CodespaceType) Keeper { + sk types.StakingKeeper, fck types.FeeCollectionKeeper, supplyKeeper SupplyKeeper, codespace sdk.CodespaceType) Keeper { keeper := Keeper{ storeKey: key, cdc: cdc, @@ -32,6 +34,7 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramSpace params.Subspace, c bankKeeper: ck, stakingKeeper: sk, feeCollectionKeeper: fck, + supplyKeeper: supplyKeeper, codespace: codespace, } return keeper @@ -97,7 +100,20 @@ func (k Keeper) WithdrawValidatorCommission(ctx sdk.Context, valAddr sdk.ValAddr if _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, coins); err != nil { return nil, err } + + k.supplyKeeper.InflateSupply(ctx, supply.TypeCirculating, coins) } return coins, nil } + +// GetTotalRewards returns the total amount of fee distribution rewards held in the store +func (k Keeper) GetTotalRewards(ctx sdk.Context) (totalRewards sdk.DecCoins) { + k.IterateValidatorOutstandingRewards(ctx, + func(valAddr sdk.ValAddress, rewards types.ValidatorOutstandingRewards) (stop bool) { + totalRewards = totalRewards.Add(rewards) + return false + }, + ) + return totalRewards +} diff --git a/x/distribution/keeper/keeper_test.go b/x/distribution/keeper/keeper_test.go index 59471a13287a..d383e9dcf5be 100644 --- a/x/distribution/keeper/keeper_test.go +++ b/x/distribution/keeper/keeper_test.go @@ -62,3 +62,20 @@ func TestWithdrawValidatorCommission(t *testing.T) { require.True(t, true) } + +func TestGetTotalRewards(t *testing.T) { + ctx, _, keeper, _, _ := CreateTestInputDefault(t, false, 1000) + + valCommission := sdk.DecCoins{ + sdk.NewDecCoinFromDec("mytoken", sdk.NewDec(5).Quo(sdk.NewDec(4))), + sdk.NewDecCoinFromDec("stake", sdk.NewDec(3).Quo(sdk.NewDec(2))), + } + + keeper.SetValidatorOutstandingRewards(ctx, valOpAddr1, valCommission) + keeper.SetValidatorOutstandingRewards(ctx, valOpAddr2, valCommission) + + expectedRewards := valCommission.MulDec(sdk.NewDec(2)) + totalRewards := keeper.GetTotalRewards(ctx) + + require.Equal(t, expectedRewards, totalRewards) +} diff --git a/x/distribution/keeper/test_common.go b/x/distribution/keeper/test_common.go index c21d6a8d2c6a..5eb45b95ea9c 100644 --- a/x/distribution/keeper/test_common.go +++ b/x/distribution/keeper/test_common.go @@ -18,6 +18,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/supply" "github.com/cosmos/cosmos-sdk/x/distribution/types" ) @@ -77,14 +78,14 @@ func CreateTestInputDefault(t *testing.T, isCheckTx bool, initPower int64) ( communityTax := sdk.NewDecWithPrec(2, 2) - ctx, ak, _, dk, sk, fck, _ := CreateTestInputAdvanced(t, isCheckTx, initPower, communityTax) + ctx, ak, _, dk, sk, fck, _, _ := CreateTestInputAdvanced(t, isCheckTx, initPower, communityTax) return ctx, ak, dk, sk, fck } // hogpodge of all sorts of input required for testing func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initPower int64, communityTax sdk.Dec) (sdk.Context, auth.AccountKeeper, bank.Keeper, - Keeper, staking.Keeper, DummyFeeCollectionKeeper, params.Keeper) { + Keeper, staking.Keeper, DummyFeeCollectionKeeper, SupplyKeeper, params.Keeper) { initCoins := sdk.TokensFromTendermintPower(initPower) @@ -93,6 +94,7 @@ func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initPower int64, tkeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey) keyAcc := sdk.NewKVStoreKey(auth.StoreKey) keyFeeCollection := sdk.NewKVStoreKey(auth.FeeStoreKey) + keySupply := sdk.NewKVStoreKey(supply.StoreKey) keyParams := sdk.NewKVStoreKey(params.StoreKey) tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) @@ -104,6 +106,7 @@ func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initPower int64, ms.MountStoreWithDB(keyStaking, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyFeeCollection, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keySupply, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) @@ -120,6 +123,9 @@ func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initPower int64, sk.SetPool(ctx, staking.InitialPool()) sk.SetParams(ctx, staking.DefaultParams()) + supplyKeeper := supply.NewKeeper(cdc, keySupply) + supplyKeeper.SetSupplier(ctx, supply.DefaultSupplier()) + // fill all the addresses with some coins, set the loose pool tokens simultaneously for _, addr := range TestAddrs { pool := sk.GetPool(ctx) @@ -132,7 +138,7 @@ func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initPower int64, } fck := DummyFeeCollectionKeeper{} - keeper := NewKeeper(cdc, keyDistr, pk.Subspace(DefaultParamspace), bankKeeper, sk, fck, types.DefaultCodespace) + keeper := NewKeeper(cdc, keyDistr, pk.Subspace(DefaultParamspace), bankKeeper, sk, fck, supplyKeeper, types.DefaultCodespace) // set the distribution hooks on staking sk.SetHooks(keeper.Hooks()) @@ -143,7 +149,7 @@ func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initPower int64, keeper.SetBaseProposerReward(ctx, sdk.NewDecWithPrec(1, 2)) keeper.SetBonusProposerReward(ctx, sdk.NewDecWithPrec(4, 2)) - return ctx, accountKeeper, bankKeeper, keeper, sk, fck, pk + return ctx, accountKeeper, bankKeeper, keeper, sk, fck, supplyKeeper, pk } //__________________________________________________________________________________ diff --git a/x/gov/endblocker_test.go b/x/gov/endblocker_test.go index 86e9a4ef2f7e..c1c6d866adbb 100644 --- a/x/gov/endblocker_test.go +++ b/x/gov/endblocker_test.go @@ -18,7 +18,6 @@ func TestTickExpiredDepositPeriod(t *testing.T) { mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - keeper.ck.SetSendEnabled(ctx, true) govHandler := NewHandler(keeper) inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) @@ -64,7 +63,6 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) { mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - keeper.ck.SetSendEnabled(ctx, true) govHandler := NewHandler(keeper) inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) @@ -124,7 +122,6 @@ func TestTickPassedDepositPeriod(t *testing.T) { mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - keeper.ck.SetSendEnabled(ctx, true) govHandler := NewHandler(keeper) inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) @@ -170,7 +167,6 @@ func TestTickPassedVotingPeriod(t *testing.T) { mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - keeper.ck.SetSendEnabled(ctx, true) govHandler := NewHandler(keeper) inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) diff --git a/x/gov/expected_keepers.go b/x/gov/expected_keepers.go index bc80bcedcba1..9f639e19d799 100644 --- a/x/gov/expected_keepers.go +++ b/x/gov/expected_keepers.go @@ -1,12 +1,27 @@ package gov -import sdk "github.com/cosmos/cosmos-sdk/types" +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/supply" +) -// expected bank keeper +// AccountKeeper defines the expected account keeper +type AccountKeeper interface { + GetAccount(ctx sdk.Context, addr sdk.AccAddress) auth.Account + SetAccount(ctx sdk.Context, acc auth.Account) +} + +// BankKeeper defines the expected bank keeper type BankKeeper interface { GetCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - - // TODO remove once governance doesn't require use of accounts + SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Error) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) sdk.Error - SetSendEnabled(ctx sdk.Context, enabled bool) +} + +// SupplyKeeper defines the expected supply keeper +type SupplyKeeper interface { + InflateSupply(ctx sdk.Context, supplyType string, amount sdk.Coins) + DeflateSupply(ctx sdk.Context, supplyType string, amount sdk.Coins) + SetSupplier(ctx sdk.Context, supplier supply.Supplier) // used for testing } diff --git a/x/gov/genesis.go b/x/gov/genesis.go index 4aeb9d862388..5580d14dea0c 100644 --- a/x/gov/genesis.go +++ b/x/gov/genesis.go @@ -6,6 +6,7 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" ) const ( @@ -107,6 +108,14 @@ func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { // TODO: Handle this with #870 panic(err) } + + // check if the module account exists and create it if not + moduleAcc := k.ak.GetAccount(ctx, ModuleAddress) + if moduleAcc == nil { + moduleAcc = auth.NewModuleHolderAccount(ModuleName) + k.ak.SetAccount(ctx, moduleAcc) + } + k.setDepositParams(ctx, data.DepositParams) k.setVotingParams(ctx, data.VotingParams) k.setTallyParams(ctx, data.TallyParams) diff --git a/x/gov/keeper.go b/x/gov/keeper.go index f64b0fbc6cf0..f7185042c5cf 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -6,7 +6,7 @@ import ( codec "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params" - + "github.com/cosmos/cosmos-sdk/x/supply" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/libs/log" ) @@ -56,9 +56,15 @@ type Keeper struct { // The reference to the Paramstore to get and set gov specific params paramSpace params.Subspace - // The reference to the CoinKeeper to modify balances + // The reference to the BankKeeper to modify balances ck BankKeeper + // The reference to the AccountKeeper to create a module account + ak AccountKeeper + + // The reference to the SupplyKeeper to update module and circulating supply + supplyKeeper SupplyKeeper + // The ValidatorSet to get information about validators vs sdk.ValidatorSet @@ -81,13 +87,15 @@ type Keeper struct { // - users voting on proposals, with weight proportional to stake in the system // - and tallying the result of the vote. func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramsKeeper params.Keeper, - paramSpace params.Subspace, ck BankKeeper, ds sdk.DelegationSet, codespace sdk.CodespaceType) Keeper { - + paramSpace params.Subspace, bk BankKeeper, ak AccountKeeper, + supplyKeeper SupplyKeeper, ds sdk.DelegationSet, codespace sdk.CodespaceType) Keeper { return Keeper{ storeKey: key, paramsKeeper: paramsKeeper, paramSpace: paramSpace.WithKeyTable(ParamKeyTable()), - ck: ck, + ck: bk, + ak: ak, + supplyKeeper: supplyKeeper, ds: ds, vs: ds.GetValidatorSet(), cdc: cdc, @@ -96,7 +104,7 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramsKeeper params.Keeper, } // Logger returns a module-specific logger. -func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", "x/gov") } +func (keeper Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", "x/gov") } // Proposals func (keeper Keeper) SubmitProposal(ctx sdk.Context, content ProposalContent) (proposal Proposal, err sdk.Error) { @@ -384,13 +392,16 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositorAdd return ErrAlreadyFinishedProposal(keeper.codespace, proposalID), false } - // Send coins from depositor's account to DepositedCoinsAccAddr account - // TODO: Don't use an account for this purpose; it's clumsy and prone to misuse. - err := keeper.ck.SendCoins(ctx, depositorAddr, DepositedCoinsAccAddr, depositAmount) + // update the governance module account coin pool + err := keeper.ck.SendCoins(ctx, depositorAddr, ModuleAddress, depositAmount) if err != nil { return err, false } + // update the supply + keeper.supplyKeeper.InflateSupply(ctx, supply.TypeModules, depositAmount) + keeper.supplyKeeper.DeflateSupply(ctx, supply.TypeCirculating, depositAmount) + // Update proposal proposal.TotalDeposit = proposal.TotalDeposit.Add(depositAmount) keeper.SetProposal(ctx, proposal) @@ -430,11 +441,16 @@ func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID uint64) { deposit := &Deposit{} keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit) - err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, deposit.Depositor, deposit.Amount) + // update the governance module account coin pool + err := keeper.ck.SendCoins(ctx, ModuleAddress, deposit.Depositor, deposit.Amount) if err != nil { - panic("should not happen") + panic(err) } + // update the supply + keeper.supplyKeeper.InflateSupply(ctx, supply.TypeCirculating, deposit.Amount) + keeper.supplyKeeper.DeflateSupply(ctx, supply.TypeModules, deposit.Amount) + store.Delete(depositsIterator.Key()) } } @@ -448,12 +464,16 @@ func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID uint64) { deposit := &Deposit{} keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit) - // TODO: Find a way to do this without using accounts. - err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, BurnedDepositCoinsAccAddr, deposit.Amount) + // update the governance module account coin pool and burn deposit + _, err := keeper.ck.SubtractCoins(ctx, ModuleAddress, deposit.Amount) if err != nil { - panic("should not happen") + panic(err) } + // update the supply + keeper.supplyKeeper.DeflateSupply(ctx, supply.TypeModules, deposit.Amount) + keeper.supplyKeeper.DeflateSupply(ctx, supply.TypeTotal, deposit.Amount) + store.Delete(depositsIterator.Key()) } } diff --git a/x/gov/keeper_keys.go b/x/gov/keeper_keys.go index 690379566b1d..7c9e311c750f 100644 --- a/x/gov/keeper_keys.go +++ b/x/gov/keeper_keys.go @@ -6,6 +6,7 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tendermint/tendermint/crypto" ) // Key for getting a the next available proposalID from the store @@ -17,6 +18,9 @@ var ( PrefixInactiveProposalQueue = []byte("inactiveProposalQueue") ) +// Module account address +var ModuleAddress = sdk.AccAddress(crypto.AddressHash([]byte(ModuleName))) + // Key for getting a specific proposal from the store func KeyProposal(proposalID uint64) []byte { return []byte(fmt.Sprintf("proposals:%d", proposalID)) diff --git a/x/gov/keeper_test.go b/x/gov/keeper_test.go index 67905fd2da82..cb012bbea264 100644 --- a/x/gov/keeper_test.go +++ b/x/gov/keeper_test.go @@ -6,9 +6,8 @@ import ( "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" ) func TestGetSetProposal(t *testing.T) { @@ -103,6 +102,10 @@ func TestDeposits(t *testing.T) { require.Equal(t, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, expTokens)), addr0Initial) require.True(t, proposal.TotalDeposit.IsEqual(sdk.NewCoins())) + moduleAcc := keeper.ak.GetAccount(ctx, ModuleAddress) + deposits := proposal.TotalDeposit + require.True(t, moduleAcc.GetCoins().IsEqual(deposits)) + // Check no deposits at beginning deposit, found := keeper.GetDeposit(ctx, proposalID, addrs[1]) require.False(t, found) @@ -123,32 +126,45 @@ func TestDeposits(t *testing.T) { require.Equal(t, fourStake, proposal.TotalDeposit) require.Equal(t, addr0Initial.Sub(fourStake), keeper.ck.GetCoins(ctx, addrs[0])) + moduleAcc = keeper.ak.GetAccount(ctx, ModuleAddress) + deposits = deposits.Add(fourStake) + require.True(t, moduleAcc.GetCoins().IsEqual(deposits)) + // Check a second deposit from same address err, votingStarted = keeper.AddDeposit(ctx, proposalID, addrs[0], fiveStake) require.Nil(t, err) require.False(t, votingStarted) + deposits = fourStake.Add(fiveStake) deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[0]) require.True(t, found) - require.Equal(t, fourStake.Add(fiveStake), deposit.Amount) + require.Equal(t, deposits, deposit.Amount) require.Equal(t, addrs[0], deposit.Depositor) proposal, ok = keeper.GetProposal(ctx, proposalID) require.True(t, ok) - require.Equal(t, fourStake.Add(fiveStake), proposal.TotalDeposit) - require.Equal(t, addr0Initial.Sub(fourStake).Sub(fiveStake), keeper.ck.GetCoins(ctx, addrs[0])) + require.Equal(t, deposits, proposal.TotalDeposit) + require.Equal(t, addr0Initial.Sub(deposits), keeper.ck.GetCoins(ctx, addrs[0])) + + moduleAcc = keeper.ak.GetAccount(ctx, ModuleAddress) + + require.True(t, moduleAcc.GetCoins().IsEqual(deposits)) // Check third deposit from a new address err, votingStarted = keeper.AddDeposit(ctx, proposalID, addrs[1], fourStake) require.Nil(t, err) require.True(t, votingStarted) + deposits = deposits.Add(fourStake) deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[1]) require.True(t, found) require.Equal(t, addrs[1], deposit.Depositor) require.Equal(t, fourStake, deposit.Amount) proposal, ok = keeper.GetProposal(ctx, proposalID) require.True(t, ok) - require.Equal(t, fourStake.Add(fiveStake).Add(fourStake), proposal.TotalDeposit) + require.Equal(t, deposits, proposal.TotalDeposit) require.Equal(t, addr1Initial.Sub(fourStake), keeper.ck.GetCoins(ctx, addrs[1])) + moduleAcc = keeper.ak.GetAccount(ctx, ModuleAddress) + require.True(t, moduleAcc.GetCoins().IsEqual(deposits)) + // Check that proposal moved to voting period proposal, ok = keeper.GetProposal(ctx, proposalID) require.True(t, ok) diff --git a/x/gov/test_common.go b/x/gov/test_common.go index a14251841d56..53eb6264bc71 100644 --- a/x/gov/test_common.go +++ b/x/gov/test_common.go @@ -16,11 +16,12 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/mock" "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/supply" ) // initialize the mock application for this module func getMockApp(t *testing.T, numGenAccs int, genState GenesisState, genAccs []auth.Account) ( - mapp *mock.App, keeper Keeper, sk staking.Keeper, addrs []sdk.AccAddress, + mapp *mock.App, keeper Keeper, ds staking.Keeper, addrs []sdk.AccAddress, pubKeys []crypto.PubKey, privKeys []crypto.PrivKey) { mapp = mock.NewApp() @@ -30,30 +31,33 @@ func getMockApp(t *testing.T, numGenAccs int, genState GenesisState, genAccs []a keyStaking := sdk.NewKVStoreKey(staking.StoreKey) tkeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey) + keySupply := sdk.NewKVStoreKey(supply.StoreKey) keyGov := sdk.NewKVStoreKey(StoreKey) pk := mapp.ParamsKeeper - ck := bank.NewBaseKeeper(mapp.AccountKeeper, mapp.ParamsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace) - sk = staking.NewKeeper(mapp.Cdc, keyStaking, tkeyStaking, ck, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) - keeper = NewKeeper(mapp.Cdc, keyGov, pk, pk.Subspace("testgov"), ck, sk, DefaultCodespace) + bk := bank.NewBaseKeeper(mapp.AccountKeeper, mapp.ParamsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace) + sk := supply.NewKeeper(mapp.Cdc, keySupply) + ds = staking.NewKeeper(mapp.Cdc, keyStaking, tkeyStaking, bk, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) + keeper = NewKeeper(mapp.Cdc, keyGov, pk, pk.Subspace("testgov"), bk, mapp.AccountKeeper, sk, ds, DefaultCodespace) mapp.Router().AddRoute(RouterKey, NewHandler(keeper)) mapp.QueryRouter().AddRoute(QuerierRoute, NewQuerier(keeper)) - mapp.SetEndBlocker(getEndBlocker(keeper)) - mapp.SetInitChainer(getInitChainer(mapp, keeper, sk, genState)) - - require.NoError(t, mapp.CompleteSetup(keyStaking, tkeyStaking, keyGov)) - valTokens := sdk.TokensFromTendermintPower(42) + coins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, valTokens)) if genAccs == nil || len(genAccs) == 0 { - genAccs, addrs, pubKeys, privKeys = mock.CreateGenAccounts(numGenAccs, - sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, valTokens)}) + genAccs, addrs, pubKeys, privKeys = mock.CreateGenAccounts(numGenAccs, coins) } + accountsSupply := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, coins.AmountOf(sdk.DefaultBondDenom).MulRaw(42))) + + mapp.SetEndBlocker(getEndBlocker(keeper)) + mapp.SetInitChainer(getInitChainer(mapp, keeper, ds, genState, accountsSupply)) + + require.NoError(t, mapp.CompleteSetup(keyStaking, tkeyStaking, keyGov, keySupply)) mock.SetGenesis(mapp, genAccs) - return mapp, keeper, sk, addrs, pubKeys, privKeys + return mapp, keeper, ds, addrs, pubKeys, privKeys } // gov and staking endblocker @@ -67,7 +71,7 @@ func getEndBlocker(keeper Keeper) sdk.EndBlocker { } // gov and staking initchainer -func getInitChainer(mapp *mock.App, keeper Keeper, stakingKeeper staking.Keeper, genState GenesisState) sdk.InitChainer { +func getInitChainer(mapp *mock.App, keeper Keeper, stakingKeeper staking.Keeper, genState GenesisState, accountsSupply sdk.Coins) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { mapp.InitChainer(ctx, req) @@ -75,6 +79,13 @@ func getInitChainer(mapp *mock.App, keeper Keeper, stakingKeeper staking.Keeper, tokens := sdk.TokensFromTendermintPower(100000) stakingGenesis.Pool.NotBondedTokens = tokens + supplier := supply.DefaultSupplier() + notBondedSupply := sdk.NewCoins(sdk.NewCoin(stakingGenesis.Params.BondDenom, sdk.NewInt(100000))) + supplier.Inflate(supply.TypeTotal, notBondedSupply.Add(accountsSupply)) + supplier.Inflate(supply.TypeCirculating, notBondedSupply.Add(accountsSupply)) + + keeper.supplyKeeper.SetSupplier(ctx, supplier) + validators, err := staking.InitGenesis(ctx, stakingKeeper, stakingGenesis) if err != nil { panic(err) diff --git a/x/mint/abci_app.go b/x/mint/abci_app.go index 5016a464d18a..0ce36bcf176c 100644 --- a/x/mint/abci_app.go +++ b/x/mint/abci_app.go @@ -2,25 +2,12 @@ package mint import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/mint/keeper" ) -// Inflate every block, update inflation parameters once per hour -func BeginBlocker(ctx sdk.Context, k Keeper) { - - // fetch stored minter & params - minter := k.GetMinter(ctx) - params := k.GetParams(ctx) - - // recalculate inflation rate - totalSupply := k.sk.TotalTokens(ctx) - bondedRatio := k.sk.BondedRatio(ctx) - minter.Inflation = minter.NextInflationRate(params, bondedRatio) - minter.AnnualProvisions = minter.NextAnnualProvisions(params, totalSupply) - k.SetMinter(ctx, minter) - - // mint coins, add to collected fees, update supply - mintedCoin := minter.BlockProvision(params) - k.fck.AddCollectedFees(ctx, sdk.Coins{mintedCoin}) - k.sk.InflateSupply(ctx, mintedCoin.Amount) +// BeginBlocker inflates every block and updates inflation parameters once per hour +func BeginBlocker(ctx sdk.Context, k keeper.Keeper) { + k.CalculateInflationRate(ctx) + k.Mint(ctx) } diff --git a/x/mint/alias.go b/x/mint/alias.go new file mode 100644 index 000000000000..21b907f90acb --- /dev/null +++ b/x/mint/alias.go @@ -0,0 +1,31 @@ +//nolint +package mint + +import ( + "github.com/cosmos/cosmos-sdk/x/mint/keeper" + "github.com/cosmos/cosmos-sdk/x/mint/types" +) + +type ( + Keeper = keeper.Keeper + Minter = types.Minter + Params = types.Params + GenesisState = types.GenesisState +) + +var ( + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier + + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + InitialMinter = types.InitialMinter + DefaultInitialMinter = types.DefaultInitialMinter + NewParams = types.NewParams +) + +const ( + StoreKey = keeper.StoreKey + QuerierRoute = keeper.QuerierRoute + DefaultParamspace = keeper.DefaultParamspace +) diff --git a/x/mint/client/cli/query.go b/x/mint/client/cli/query.go index 65bde6adeb32..40aad8f628d0 100644 --- a/x/mint/client/cli/query.go +++ b/x/mint/client/cli/query.go @@ -8,7 +8,8 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/cosmos/cosmos-sdk/x/mint/keeper" + "github.com/cosmos/cosmos-sdk/x/mint/types" ) // GetCmdQueryParams implements a command to return the current minting @@ -21,13 +22,13 @@ func GetCmdQueryParams(cdc *codec.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - route := fmt.Sprintf("custom/%s/%s", mint.QuerierRoute, mint.QueryParameters) + route := fmt.Sprintf("custom/%s/%s", keeper.QuerierRoute, keeper.QueryParameters) res, err := cliCtx.QueryWithData(route, nil) if err != nil { return err } - var params mint.Params + var params types.Params if err := cdc.UnmarshalJSON(res, ¶ms); err != nil { return err } @@ -47,7 +48,7 @@ func GetCmdQueryInflation(cdc *codec.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - route := fmt.Sprintf("custom/%s/%s", mint.QuerierRoute, mint.QueryInflation) + route := fmt.Sprintf("custom/%s/%s", keeper.QuerierRoute, keeper.QueryInflation) res, err := cliCtx.QueryWithData(route, nil) if err != nil { return err @@ -73,7 +74,7 @@ func GetCmdQueryAnnualProvisions(cdc *codec.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - route := fmt.Sprintf("custom/%s/%s", mint.QuerierRoute, mint.QueryAnnualProvisions) + route := fmt.Sprintf("custom/%s/%s", keeper.QuerierRoute, keeper.QueryAnnualProvisions) res, err := cliCtx.QueryWithData(route, nil) if err != nil { return err diff --git a/x/mint/client/module_client.go b/x/mint/client/module_client.go index e77cd9205049..cb30e4a52637 100644 --- a/x/mint/client/module_client.go +++ b/x/mint/client/module_client.go @@ -2,8 +2,8 @@ package client import ( sdkclient "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/mint/client/cli" + "github.com/cosmos/cosmos-sdk/x/mint/keeper" "github.com/spf13/cobra" "github.com/tendermint/go-amino" ) @@ -20,7 +20,7 @@ func NewModuleClient(storeKey string, cdc *amino.Codec) ModuleClient { // GetQueryCmd returns the cli query commands for the minting module. func (mc ModuleClient) GetQueryCmd() *cobra.Command { mintingQueryCmd := &cobra.Command{ - Use: mint.ModuleName, + Use: keeper.ModuleName, Short: "Querying commands for the minting module", } @@ -38,7 +38,7 @@ func (mc ModuleClient) GetQueryCmd() *cobra.Command { // GetTxCmd returns the transaction commands for the minting module. func (mc ModuleClient) GetTxCmd() *cobra.Command { mintTxCmd := &cobra.Command{ - Use: mint.ModuleName, + Use: keeper.ModuleName, Short: "Minting transaction subcommands", } diff --git a/x/mint/client/rest/query.go b/x/mint/client/rest/query.go index 2d3cd4a0fea0..e516f2155aa9 100644 --- a/x/mint/client/rest/query.go +++ b/x/mint/client/rest/query.go @@ -9,7 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/types/rest" - "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/cosmos/cosmos-sdk/x/mint/keeper" ) func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) { @@ -31,7 +31,7 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Co func queryParamsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - route := fmt.Sprintf("custom/%s/%s", mint.QuerierRoute, mint.QueryParameters) + route := fmt.Sprintf("custom/%s/%s", keeper.QuerierRoute, keeper.QueryParameters) res, err := cliCtx.QueryWithData(route, nil) if err != nil { @@ -45,7 +45,7 @@ func queryParamsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Hand func queryInflationHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - route := fmt.Sprintf("custom/%s/%s", mint.QuerierRoute, mint.QueryInflation) + route := fmt.Sprintf("custom/%s/%s", keeper.QuerierRoute, keeper.QueryInflation) res, err := cliCtx.QueryWithData(route, nil) if err != nil { @@ -59,7 +59,7 @@ func queryInflationHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.H func queryAnnualProvisionsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - route := fmt.Sprintf("custom/%s/%s", mint.QuerierRoute, mint.QueryAnnualProvisions) + route := fmt.Sprintf("custom/%s/%s", keeper.QuerierRoute, keeper.QueryAnnualProvisions) res, err := cliCtx.QueryWithData(route, nil) if err != nil { diff --git a/x/mint/expected_keepers.go b/x/mint/expected_keepers.go deleted file mode 100644 index 5169b0957668..000000000000 --- a/x/mint/expected_keepers.go +++ /dev/null @@ -1,17 +0,0 @@ -package mint - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// expected staking keeper -type StakingKeeper interface { - TotalTokens(ctx sdk.Context) sdk.Int - BondedRatio(ctx sdk.Context) sdk.Dec - InflateSupply(ctx sdk.Context, newTokens sdk.Int) -} - -// 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 index 11f96c6f90bb..ae1104d20a9b 100644 --- a/x/mint/genesis.go +++ b/x/mint/genesis.go @@ -2,54 +2,36 @@ package mint import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/mint/keeper" + "github.com/cosmos/cosmos-sdk/x/mint/types" ) -// GenesisState - minter state -type GenesisState struct { - Minter Minter `json:"minter"` // minter object - Params Params `json:"params"` // inflation params -} - -// NewGenesisState creates a new GenesisState object -func NewGenesisState(minter Minter, params Params) GenesisState { - return GenesisState{ - Minter: minter, - Params: params, - } -} - -// DefaultGenesisState creates a default GenesisState object -func DefaultGenesisState() GenesisState { - return GenesisState{ - Minter: DefaultInitialMinter(), - Params: DefaultParams(), - } -} - -// new mint genesis -func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { - keeper.SetMinter(ctx, data.Minter) - keeper.SetParams(ctx, data.Params) +// InitGenesis new mint genesis +func InitGenesis(ctx sdk.Context, k keeper.Keeper, data types.GenesisState) { + k.SetMinter(ctx, data.Minter) + k.SetParams(ctx, data.Params) } // ExportGenesis returns a GenesisState for a given context and keeper. -func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { +func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState { - minter := keeper.GetMinter(ctx) - params := keeper.GetParams(ctx) - return NewGenesisState(minter, params) + minter := k.GetMinter(ctx) + params := k.GetParams(ctx) + return types.NewGenesisState(minter, params) } // ValidateGenesis validates the provided genesis state to ensure the // expected invariants holds. -func ValidateGenesis(data GenesisState) error { - err := validateParams(data.Params) +func ValidateGenesis(data types.GenesisState) error { + err := types.ValidateParams(data.Params) if err != nil { return err } - err = validateMinter(data.Minter) + + err = types.ValidateMinter(data.Minter) if err != nil { return err } + return nil } diff --git a/x/mint/keeper.go b/x/mint/keeper.go deleted file mode 100644 index 42a9f19fbaa5..000000000000 --- a/x/mint/keeper.go +++ /dev/null @@ -1,94 +0,0 @@ -package mint - -import ( - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/params" -) - -const ( - // ModuleName is the name of the module - ModuleName = "minting" - - // default paramspace for params keeper - DefaultParamspace = "mint" - - // StoreKey is the default store key for mint - StoreKey = "mint" - - // QuerierRoute is the querier route for the minting store. - QuerierRoute = StoreKey -) - -// keeper of the staking store -type Keeper struct { - storeKey sdk.StoreKey - cdc *codec.Codec - paramSpace params.Subspace - sk StakingKeeper - fck FeeCollectionKeeper -} - -func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, - paramSpace params.Subspace, sk StakingKeeper, fck FeeCollectionKeeper) Keeper { - - keeper := Keeper{ - storeKey: key, - cdc: cdc, - paramSpace: paramSpace.WithKeyTable(ParamKeyTable()), - 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 staking module -func ParamKeyTable() params.KeyTable { - return params.NewKeyTable( - ParamStoreKeyParams, Params{}, - ) -} - -//______________________________________________________________________ - -// 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.MustUnmarshalBinaryLengthPrefixed(b, &minter) - return -} - -// set the minter -func (k Keeper) SetMinter(ctx sdk.Context, minter Minter) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinaryLengthPrefixed(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/keeper/expected_keepers.go b/x/mint/keeper/expected_keepers.go new file mode 100644 index 000000000000..04252f5cf74f --- /dev/null +++ b/x/mint/keeper/expected_keepers.go @@ -0,0 +1,23 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// SupplyKeeper defines the expected supply keeper +type SupplyKeeper interface { + InflateSupply(ctx sdk.Context, supplyType string, amt sdk.Coins) +} + +// StakingKeeper defines the expected staking keeper +type StakingKeeper interface { + BondedRatio(ctx sdk.Context) sdk.Dec + BondDenom(ctx sdk.Context) string + InflateNotBondedTokenSupply(ctx sdk.Context, amt sdk.Int) + StakingTokenSupply(ctx sdk.Context) sdk.Int +} + +// FeeCollectionKeeper defines the expected fee collection keeper +type FeeCollectionKeeper interface { + AddCollectedFees(sdk.Context, sdk.Coins) sdk.Coins +} diff --git a/x/mint/keeper/keeper.go b/x/mint/keeper/keeper.go new file mode 100644 index 000000000000..1b1daf2b169d --- /dev/null +++ b/x/mint/keeper/keeper.go @@ -0,0 +1,78 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/cosmos-sdk/x/mint/types" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/supply" +) + +// Keeper defines the keeper of the minting store +type Keeper struct { + storeKey sdk.StoreKey + cdc *codec.Codec + paramSpace params.Subspace + supplyKeeper SupplyKeeper + sk StakingKeeper + fck FeeCollectionKeeper +} + +// NewKeeper creates a new mint Keeper instance +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, + paramSpace params.Subspace, supplyKeeper SupplyKeeper, sk StakingKeeper, fck FeeCollectionKeeper) Keeper { + + keeper := Keeper{ + storeKey: key, + cdc: cdc, + paramSpace: paramSpace.WithKeyTable(ParamKeyTable()), + supplyKeeper: supplyKeeper, + sk: sk, + fck: fck, + } + return keeper +} + +// get the minter +func (k Keeper) GetMinter(ctx sdk.Context) (minter types.Minter) { + store := ctx.KVStore(k.storeKey) + b := store.Get(minterKey) + if b == nil { + panic("Stored minter should not have been nil") + } + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &minter) + return +} + +// set the minter +func (k Keeper) SetMinter(ctx sdk.Context, minter types.Minter) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinaryLengthPrefixed(minter) + store.Set(minterKey, b) +} + +// CalculateInflationRate recalculates the inflation rate and annual provisions for a new block +func (k Keeper) CalculateInflationRate(ctx sdk.Context) { + minter := k.GetMinter(ctx) + params := k.GetParams(ctx) + + bondedRatio := k.sk.BondedRatio(ctx) + minter.Inflation = minter.NextInflationRate(params, bondedRatio) + minter.AnnualProvisions = minter.NextAnnualProvisions(params, k.sk.StakingTokenSupply(ctx)) + k.SetMinter(ctx, minter) +} + +// Mint creates new coins based on the current block provision, which are added +// to the collected fee pool and then updates the total supply +func (k Keeper) Mint(ctx sdk.Context) { + minter := k.GetMinter(ctx) + params := k.GetParams(ctx) + + mintedCoin := minter.BlockProvision(params) + k.fck.AddCollectedFees(ctx, sdk.NewCoins(mintedCoin)) + + // // passively keep track of the total and the not bonded supply + k.supplyKeeper.InflateSupply(ctx, supply.TypeTotal, sdk.NewCoins(mintedCoin)) + k.sk.InflateNotBondedTokenSupply(ctx, mintedCoin.Amount) +} diff --git a/x/mint/keeper/key.go b/x/mint/keeper/key.go new file mode 100644 index 000000000000..6bdf187e5d48 --- /dev/null +++ b/x/mint/keeper/key.go @@ -0,0 +1,23 @@ +// nolint +package keeper + +const ( + // ModuleName is the name of the module + ModuleName = "minting" + + // default paramspace for params keeper + DefaultParamspace = "mint" + + // StoreKey is the default store key for mint + StoreKey = "mint" + + // QuerierRoute is the querier route for the minting store. + QuerierRoute = StoreKey +) + +var ( + minterKey = []byte{0x00} // the one key to use for the keeper store + + // params store for inflation params + ParamStoreKeyParams = []byte("params") +) diff --git a/x/mint/keeper/params.go b/x/mint/keeper/params.go new file mode 100644 index 000000000000..1159f87951e1 --- /dev/null +++ b/x/mint/keeper/params.go @@ -0,0 +1,26 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/cosmos-sdk/x/mint/types" + "github.com/cosmos/cosmos-sdk/x/params" +) + +// ParamKeyTable for minting module +func ParamKeyTable() params.KeyTable { + return params.NewKeyTable( + ParamStoreKeyParams, types.Params{}, + ) +} + +// get inflation params from the global param store +func (k Keeper) GetParams(ctx sdk.Context) (params types.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 types.Params) { + k.paramSpace.Set(ctx, ParamStoreKeyParams, ¶ms) +} diff --git a/x/mint/querier.go b/x/mint/keeper/querier.go similarity index 99% rename from x/mint/querier.go rename to x/mint/keeper/querier.go index 4de3fdeeb526..b3265db839a6 100644 --- a/x/mint/querier.go +++ b/x/mint/keeper/querier.go @@ -1,4 +1,4 @@ -package mint +package keeper import ( "fmt" diff --git a/x/mint/querier_test.go b/x/mint/keeper/querier_test.go similarity index 95% rename from x/mint/querier_test.go rename to x/mint/keeper/querier_test.go index 7a73853d0a6d..469d773c15ee 100644 --- a/x/mint/querier_test.go +++ b/x/mint/keeper/querier_test.go @@ -1,4 +1,4 @@ -package mint +package keeper import ( "testing" @@ -7,6 +7,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/mint/types" abci "github.com/tendermint/tendermint/abci/types" ) @@ -35,7 +36,7 @@ func TestNewQuerier(t *testing.T) { func TestQueryParams(t *testing.T) { input := newTestInput(t) - var params Params + var params types.Params res, sdkErr := queryParams(input.ctx, input.mintKeeper) require.NoError(t, sdkErr) diff --git a/x/mint/test_common.go b/x/mint/keeper/test_common.go similarity index 84% rename from x/mint/test_common.go rename to x/mint/keeper/test_common.go index f2bec9515704..a5d3cabf87fc 100644 --- a/x/mint/test_common.go +++ b/x/mint/keeper/test_common.go @@ -1,4 +1,4 @@ -package mint +package keeper import ( "os" @@ -19,6 +19,9 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/supply" + + "github.com/cosmos/cosmos-sdk/x/mint/types" ) type testInput struct { @@ -43,6 +46,7 @@ func newTestInput(t *testing.T) testInput { keyParams := sdk.NewKVStoreKey(params.StoreKey) tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) keyFeeCollection := sdk.NewKVStoreKey(auth.FeeStoreKey) + keySupply := sdk.NewKVStoreKey(supply.StoreKey) keyMint := sdk.NewKVStoreKey(StoreKey) ms := store.NewCommitMultiStore(db) @@ -63,14 +67,16 @@ func newTestInput(t *testing.T) testInput { stakingKeeper := staking.NewKeeper( cdc, keyStaking, tkeyStaking, bankKeeper, paramsKeeper.Subspace(staking.DefaultParamspace), staking.DefaultCodespace, ) + supplyKeeper := supply.NewKeeper(cdc, keySupply) + mintKeeper := NewKeeper( - cdc, keyMint, paramsKeeper.Subspace(DefaultParamspace), &stakingKeeper, feeCollectionKeeper, + cdc, keyMint, paramsKeeper.Subspace(DefaultParamspace), supplyKeeper, &stakingKeeper, feeCollectionKeeper, ) ctx := sdk.NewContext(ms, abci.Header{Time: time.Unix(0, 0)}, false, log.NewTMLogger(os.Stdout)) - mintKeeper.SetParams(ctx, DefaultParams()) - mintKeeper.SetMinter(ctx, DefaultInitialMinter()) + mintKeeper.SetParams(ctx, types.DefaultParams()) + mintKeeper.SetMinter(ctx, types.DefaultInitialMinter()) return testInput{ctx, cdc, mintKeeper} } diff --git a/x/mint/types/genesis.go b/x/mint/types/genesis.go new file mode 100644 index 000000000000..642513abcae7 --- /dev/null +++ b/x/mint/types/genesis.go @@ -0,0 +1,23 @@ +package types + +// GenesisState - minter state +type GenesisState struct { + Minter Minter `json:"minter"` // minter object + Params Params `json:"params"` // inflation params +} + +// NewGenesisState creates a new GenesisState object +func NewGenesisState(minter Minter, params Params) GenesisState { + return GenesisState{ + Minter: minter, + Params: params, + } +} + +// DefaultGenesisState creates a default GenesisState object +func DefaultGenesisState() GenesisState { + return GenesisState{ + Minter: DefaultInitialMinter(), + Params: DefaultParams(), + } +} diff --git a/x/mint/minter.go b/x/mint/types/minter.go similarity index 96% rename from x/mint/minter.go rename to x/mint/types/minter.go index eff30785faf1..d7fa9cef4fab 100644 --- a/x/mint/minter.go +++ b/x/mint/types/minter.go @@ -1,4 +1,4 @@ -package mint +package types import ( "fmt" @@ -37,7 +37,8 @@ func DefaultInitialMinter() Minter { ) } -func validateMinter(minter Minter) error { +// ValidateMinter validates the minter inflation +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()) diff --git a/x/mint/minter_test.go b/x/mint/types/minter_test.go similarity index 99% rename from x/mint/minter_test.go rename to x/mint/types/minter_test.go index 0245c71972fb..8760a66f4466 100644 --- a/x/mint/minter_test.go +++ b/x/mint/types/minter_test.go @@ -1,4 +1,4 @@ -package mint +package types import ( "math/rand" diff --git a/x/mint/params.go b/x/mint/types/params.go similarity index 95% rename from x/mint/params.go rename to x/mint/types/params.go index f1bd7e37d944..d79fc6275e64 100644 --- a/x/mint/params.go +++ b/x/mint/types/params.go @@ -1,4 +1,4 @@ -package mint +package types import ( "fmt" @@ -41,7 +41,8 @@ func DefaultParams() Params { } } -func validateParams(params Params) error { +// ValidateParams validate the minting module params +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()) } diff --git a/x/staking/alias.go b/x/staking/alias.go index 3c8e19fd9f30..246a5f956ff3 100644 --- a/x/staking/alias.go +++ b/x/staking/alias.go @@ -70,7 +70,7 @@ var ( ValidatorQueueKey = keeper.ValidatorQueueKey RegisterInvariants = keeper.RegisterInvariants AllInvariants = keeper.AllInvariants - SupplyInvariants = keeper.SupplyInvariants + SupplyInvariants = keeper.BondedTokensInvariant NonNegativePowerInvariant = keeper.NonNegativePowerInvariant PositiveDelegationInvariant = keeper.PositiveDelegationInvariant DelegatorSharesInvariant = keeper.DelegatorSharesInvariant diff --git a/x/staking/keeper/alias_functions.go b/x/staking/keeper/alias_functions.go index aff57bda9fc5..dd1877259208 100644 --- a/x/staking/keeper/alias_functions.go +++ b/x/staking/keeper/alias_functions.go @@ -94,9 +94,9 @@ func (k Keeper) TotalBondedTokens(ctx sdk.Context) sdk.Int { } // total staking tokens supply bonded and unbonded -func (k Keeper) TotalTokens(ctx sdk.Context) sdk.Int { +func (k Keeper) StakingTokenSupply(ctx sdk.Context) sdk.Int { pool := k.GetPool(ctx) - return pool.TokenSupply() + return pool.StakingTokenSupply() } // the fraction of the staking tokens which are currently bonded @@ -106,7 +106,7 @@ func (k Keeper) BondedRatio(ctx sdk.Context) sdk.Dec { } // when minting new tokens -func (k Keeper) InflateSupply(ctx sdk.Context, newTokens sdk.Int) { +func (k Keeper) InflateNotBondedTokenSupply(ctx sdk.Context, newTokens sdk.Int) { pool := k.GetPool(ctx) pool.NotBondedTokens = pool.NotBondedTokens.Add(newTokens) k.SetPool(ctx, pool) diff --git a/x/staking/keeper/invariants.go b/x/staking/keeper/invariants.go index f9a495a4f6eb..f4e4a757dcb2 100644 --- a/x/staking/keeper/invariants.go +++ b/x/staking/keeper/invariants.go @@ -5,16 +5,15 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/cosmos/cosmos-sdk/x/supply" ) -// register all staking invariants -func RegisterInvariants(c types.CrisisKeeper, k Keeper, f types.FeeCollectionKeeper, - d types.DistributionKeeper, am auth.AccountKeeper) { +// RegisterInvariants registers all staking invariants +func RegisterInvariants(c types.CrisisKeeper, k Keeper, supplyKeeper supply.Keeper) { - c.RegisterRoute(types.ModuleName, "supply", - SupplyInvariants(k, f, d, am)) + c.RegisterRoute(types.ModuleName, "bonded-tokens", + BondedTokensInvariant(k, supplyKeeper)) c.RegisterRoute(types.ModuleName, "nonnegative-power", NonNegativePowerInvariant(k)) c.RegisterRoute(types.ModuleName, "positive-delegation", @@ -24,11 +23,11 @@ func RegisterInvariants(c types.CrisisKeeper, k Keeper, f types.FeeCollectionKee } // AllInvariants runs all invariants of the staking module. -func AllInvariants(k Keeper, f types.FeeCollectionKeeper, - d types.DistributionKeeper, am auth.AccountKeeper) sdk.Invariant { +func AllInvariants(k Keeper, supplyKeeper supply.Keeper) sdk.Invariant { return func(ctx sdk.Context) error { - err := SupplyInvariants(k, f, d, am)(ctx) + + err := BondedTokensInvariant(k, supplyKeeper)(ctx) if err != nil { return err } @@ -52,52 +51,24 @@ func AllInvariants(k Keeper, f types.FeeCollectionKeeper, } } -// SupplyInvariants checks that the total supply reflects all held not-bonded tokens, bonded tokens, and unbonding delegations -// nolint: unparam -func SupplyInvariants(k Keeper, f types.FeeCollectionKeeper, - d types.DistributionKeeper, am auth.AccountKeeper) sdk.Invariant { - +// BondedTokensInvariant checks that the total bonded tokens reflects all bonded tokens in delegations +func BondedTokensInvariant(k Keeper, supplyKeeper supply.Keeper) sdk.Invariant { return func(ctx sdk.Context) error { pool := k.GetPool(ctx) + supplier := supplyKeeper.GetSupplier(ctx) + + bondDenom := k.BondDenom(ctx) - loose := sdk.ZeroDec() bonded := sdk.ZeroDec() - am.IterateAccounts(ctx, func(acc auth.Account) bool { - loose = loose.Add(acc.GetCoins().AmountOf(k.BondDenom(ctx)).ToDec()) - return false - }) - k.IterateUnbondingDelegations(ctx, func(_ int64, ubd types.UnbondingDelegation) bool { - for _, entry := range ubd.Entries { - loose = loose.Add(entry.Balance.ToDec()) - } - return false - }) k.IterateValidators(ctx, func(_ int64, validator sdk.Validator) bool { switch validator.GetStatus() { case sdk.Bonded: bonded = bonded.Add(validator.GetBondedTokens().ToDec()) - case sdk.Unbonding, sdk.Unbonded: - loose = loose.Add(validator.GetTokens().ToDec()) } - // add yet-to-be-withdrawn - loose = loose.Add(d.GetValidatorOutstandingRewardsCoins(ctx, validator.GetOperator()).AmountOf(k.BondDenom(ctx))) + return false }) - // add outstanding fees - loose = loose.Add(f.GetCollectedFees(ctx).AmountOf(k.BondDenom(ctx)).ToDec()) - - // add community pool - loose = loose.Add(d.GetFeePoolCommunityCoins(ctx).AmountOf(k.BondDenom(ctx))) - - // Not-bonded tokens should equal coin supply plus unbonding delegations - // plus tokens on unbonded validators - if !pool.NotBondedTokens.ToDec().Equal(loose) { - return fmt.Errorf("loose token invariance:\n"+ - "\tpool.NotBondedTokens: %v\n"+ - "\tsum of account tokens: %v", pool.NotBondedTokens, loose) - } - // Bonded tokens should equal sum of tokens with bonded validators if !pool.BondedTokens.ToDec().Equal(bonded) { return fmt.Errorf("bonded token invariance:\n"+ @@ -105,6 +76,15 @@ func SupplyInvariants(k Keeper, f types.FeeCollectionKeeper, "\tsum of account tokens: %v", pool.BondedTokens, bonded) } + stakingTokensFromPool := pool.StakingTokenSupply() + stakingTokensFromSupplier := supplier.TotalSupply.AmountOf(bondDenom) + + if !stakingTokensFromPool.Equal(stakingTokensFromSupplier) { + return fmt.Errorf("total bonded token invariance:\n"+ + "\tpool.StakingTokenSupply: %v\n"+ + "\tsupplier.Total of bonded denom: %v", stakingTokensFromPool, stakingTokensFromSupplier) + } + return nil } } diff --git a/x/staking/types/expected_keepers.go b/x/staking/types/expected_keepers.go index 2a2aff06046b..5b2fdcbfc0ec 100644 --- a/x/staking/types/expected_keepers.go +++ b/x/staking/types/expected_keepers.go @@ -2,7 +2,7 @@ package types import sdk "github.com/cosmos/cosmos-sdk/types" -// expected coin keeper +// DistributionKeeper defines the expected distribution keeper type DistributionKeeper interface { GetFeePoolCommunityCoins(ctx sdk.Context) sdk.DecCoins GetValidatorOutstandingRewardsCoins(ctx sdk.Context, val sdk.ValAddress) sdk.DecCoins diff --git a/x/staking/types/pool.go b/x/staking/types/pool.go index d3905085a6c7..772442ada973 100644 --- a/x/staking/types/pool.go +++ b/x/staking/types/pool.go @@ -14,7 +14,7 @@ type Pool struct { BondedTokens sdk.Int `json:"bonded_tokens"` // tokens which are currently bonded to a validator } -// nolint +// Equal check if two pool intances are equal // TODO: This is slower than comparing struct fields directly func (p Pool) Equal(p2 Pool) bool { bz1 := MsgCdc.MustMarshalBinaryLengthPrefixed(&p) @@ -22,7 +22,7 @@ func (p Pool) Equal(p2 Pool) bool { return bytes.Equal(bz1, bz2) } -// initial pool for testing +// InitialPool default pool; used for testing func InitialPool() Pool { return Pool{ NotBondedTokens: sdk.ZeroInt(), @@ -30,14 +30,14 @@ func InitialPool() Pool { } } -// Sum total of all staking tokens in the pool -func (p Pool) TokenSupply() sdk.Int { +// StakingTokenSupply returns the total supply of all staking tokens in the pool +func (p Pool) StakingTokenSupply() sdk.Int { return p.NotBondedTokens.Add(p.BondedTokens) } -// Get the fraction of the staking token which is currently bonded +// BondedRatio gets the fraction of the staking token which is currently bonded func (p Pool) BondedRatio() sdk.Dec { - supply := p.TokenSupply() + supply := p.StakingTokenSupply() if supply.IsPositive() { return p.BondedTokens.ToDec().QuoInt(supply) } @@ -67,9 +67,9 @@ func (p Pool) String() string { return fmt.Sprintf(`Pool: Loose Tokens: %s Bonded Tokens: %s - Token Supply: %s + Staking Token Supply: %s Bonded Ratio: %v`, p.NotBondedTokens, - p.BondedTokens, p.TokenSupply(), + p.BondedTokens, p.StakingTokenSupply(), p.BondedRatio()) } diff --git a/x/supply/alias.go b/x/supply/alias.go new file mode 100644 index 000000000000..5da39f24d4fc --- /dev/null +++ b/x/supply/alias.go @@ -0,0 +1,38 @@ +// nolint +package supply + +import ( + "github.com/cosmos/cosmos-sdk/x/supply/keeper" + "github.com/cosmos/cosmos-sdk/x/supply/types" +) + +type ( + Keeper = keeper.Keeper + Supplier = types.Supplier + CoinsSupply = types.CoinsSupply + CoinSupply = types.CoinSupply + GenesisState = types.GenesisState +) + +var ( + NewKeeper = keeper.NewKeeper + RegisterInvariants = keeper.RegisterInvariants + + NewSupplier = types.NewSupplier + DefaultSupplier = types.DefaultSupplier + NewCoinsSupply = types.NewCoinsSupply + NewCoinsSupplyFromSupplier = types.NewCoinsSupplyFromSupplier + NewCoinSupply = types.NewCoinSupply + NewCoinSupplyFromSupplier = types.NewCoinSupplyFromSupplier + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState +) + +const ( + StoreKey = keeper.StoreKey + + TypeCirculating = types.TypeCirculating + TypeVesting = types.TypeVesting + TypeModules = types.TypeModules + TypeTotal = types.TypeTotal +) diff --git a/x/supply/genesis.go b/x/supply/genesis.go new file mode 100644 index 000000000000..0eecb6e25611 --- /dev/null +++ b/x/supply/genesis.go @@ -0,0 +1,27 @@ +package supply + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/supply/keeper" +) + +// InitGenesis sets supply information for genesis. +func InitGenesis(ctx sdk.Context, k keeper.Keeper, data GenesisState) { + k.SetSupplier(ctx, data.Supplier) +} + +// ExportGenesis returns a GenesisState for a given context and keeper. +func ExportGenesis(ctx sdk.Context, k keeper.Keeper) GenesisState { + return NewGenesisState(k.GetSupplier(ctx)) +} + +// ValidateGenesis performs basic validation of bank genesis data returning an +// error for any failed validation criteria. +func ValidateGenesis(data GenesisState) error { + if err := data.Supplier.ValidateBasic(); err != nil { + return fmt.Errorf(err.Error()) + } + return nil +} diff --git a/x/supply/keeper/expected_keepers.go b/x/supply/keeper/expected_keepers.go new file mode 100644 index 000000000000..7b5b854337da --- /dev/null +++ b/x/supply/keeper/expected_keepers.go @@ -0,0 +1,33 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" +) + +// AccountKeeper defines the expected account keeper +type AccountKeeper interface { + IterateAccounts(ctx sdk.Context, process func(auth.Account) (stop bool)) +} + +// CrisisKeeper defines the expected crisis keeper +type CrisisKeeper interface { + RegisterRoute(moduleName, route string, invar sdk.Invariant) +} + +// DistributionKeeper defines the expected distribution keeper +type DistributionKeeper interface { + GetFeePoolCommunityCoins(ctx sdk.Context) sdk.DecCoins + GetTotalRewards(ctx sdk.Context) sdk.DecCoins +} + +// FeeCollectionKeeper defines the expected fee collection keeper +type FeeCollectionKeeper interface { + GetCollectedFees(ctx sdk.Context) sdk.Coins +} + +// StakingKeeper defines the expected staking keeper +type StakingKeeper interface { + BondDenom(ctx sdk.Context) string + TotalBondedTokens(ctx sdk.Context) sdk.Int +} diff --git a/x/supply/keeper/invariants.go b/x/supply/keeper/invariants.go new file mode 100644 index 000000000000..3f6fd3322677 --- /dev/null +++ b/x/supply/keeper/invariants.go @@ -0,0 +1,123 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" +) + +// RegisterInvariants registers all supply invariants +func RegisterInvariants(ck CrisisKeeper, k Keeper, accountKeeper AccountKeeper, + distributionKeeper DistributionKeeper, feeCollectionKeeper FeeCollectionKeeper, + stakingKeeper StakingKeeper) { + ck.RegisterRoute( + ModuleName, "supplier", + SupplierInvariants(k, accountKeeper), + ) + ck.RegisterRoute( + ModuleName, "total-supply", + TotalSupplyInvariant(k, distributionKeeper, feeCollectionKeeper, stakingKeeper), + ) +} + +// AllInvariants runs all invariants of the staking module. +func AllInvariants(k Keeper, accountKeeper AccountKeeper, distributionKeeper DistributionKeeper, + feeCollectionKeeper FeeCollectionKeeper, stakingKeeper StakingKeeper) sdk.Invariant { + + return func(ctx sdk.Context) error { + err := SupplierInvariants(k, accountKeeper)(ctx) + if err != nil { + return err + } + + err = TotalSupplyInvariant(k, distributionKeeper, feeCollectionKeeper, stakingKeeper)(ctx) + if err != nil { + return err + } + + return nil + } +} + +// SupplierInvariants checks that the total supply reflects all held not-bonded tokens, bonded tokens, and unbonding delegations +func SupplierInvariants(k Keeper, accountKeeper AccountKeeper) sdk.Invariant { + + return func(ctx sdk.Context) error { + supplier := k.GetSupplier(ctx) + + var circulatingAmount sdk.Coins + var modulesAmount sdk.Coins + var initialVestingAmount sdk.Coins + + accountKeeper.IterateAccounts(ctx, func(acc auth.Account) bool { + + vacc, isVestingAccount := acc.(auth.VestingAccount) + if isVestingAccount && ctx.BlockHeader().Time.Unix() < vacc.GetEndTime() { + initialVestingAmount = initialVestingAmount.Add(vacc.GetOriginalVesting()) + } + + macc, isModuleAccount := acc.(auth.ModuleAccount) + if isModuleAccount { + modulesAmount = modulesAmount.Add(macc.GetCoins()) + } else { + // basic or vesting accounts + circulatingAmount = circulatingAmount.Add(acc.GetCoins()) + } + + return false + }) + + if !supplier.CirculatingSupply.IsEqual(circulatingAmount) { + return fmt.Errorf("circulating supply invariance:\n"+ + "\tsupplier.CirculatingSupply: %v\n"+ + "\tsum of circulating tokens: %v", supplier.CirculatingSupply, circulatingAmount) + } + + if !supplier.ModulesSupply.IsEqual(modulesAmount) { + return fmt.Errorf("modules holdings supply invariance:\n"+ + "\tsupplier.ModulesSupply: %v\n"+ + "\tsum of modules accounts tokens: %v", supplier.ModulesSupply, modulesAmount) + } + + if !supplier.InitialVestingSupply.IsEqual(initialVestingAmount) { + return fmt.Errorf("vesting supply invariance:\n"+ + "\tsupplier.InitialVestingSupply: %v\n"+ + "\tsum of vesting tokens: %v", supplier.InitialVestingSupply, initialVestingAmount) + } + + return nil + } +} + +// TotalSupplyInvariant checks that the total supply reflects all held not-bonded tokens, bonded tokens, and unbonding delegations +func TotalSupplyInvariant(k Keeper, distributionKeeper DistributionKeeper, + feeCollectionKeeper FeeCollectionKeeper, stakingKeeper StakingKeeper) sdk.Invariant { + + return func(ctx sdk.Context) error { + supplier := k.GetSupplier(ctx) + + bondedSupply := sdk.NewCoins(sdk.NewCoin(stakingKeeper.BondDenom(ctx), stakingKeeper.TotalBondedTokens(ctx))) + collectedFees := feeCollectionKeeper.GetCollectedFees(ctx) + communityPool, remainingCommunityPool := distributionKeeper.GetFeePoolCommunityCoins(ctx).TruncateDecimal() + totalRewards, remainingRewards := distributionKeeper.GetTotalRewards(ctx).TruncateDecimal() + + remaining, _ := remainingCommunityPool.Add(remainingRewards).TruncateDecimal() + + realTotalSupply := supplier.CirculatingSupply. + Add(supplier.ModulesSupply). + Add(bondedSupply). + Add(collectedFees). + Add(communityPool). + Add(totalRewards). + Add(remaining) + + if !supplier.TotalSupply.IsEqual(realTotalSupply) { + return fmt.Errorf("total supply invariance:\n"+ + "\tsupplier.TotalSupply: %v\n"+ + "\tcalculated total supply: %v", supplier.TotalSupply, realTotalSupply) + } + + return nil + } +} diff --git a/x/supply/keeper/keeper.go b/x/supply/keeper/keeper.go new file mode 100644 index 000000000000..df95e3a1dc57 --- /dev/null +++ b/x/supply/keeper/keeper.go @@ -0,0 +1,55 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/supply/types" +) + +// Keeper defines the keeper of the supply store +type Keeper struct { + cdc *codec.Codec + storeKey sdk.StoreKey +} + +// NewKeeper creates a new supply Keeper instance +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey) Keeper { + return Keeper{ + cdc: cdc, + storeKey: key, + } +} + +// GetSupplier retrieves the Supplier from store +func (k Keeper) GetSupplier(ctx sdk.Context) (supplier types.Supplier) { + store := ctx.KVStore(k.storeKey) + b := store.Get(supplierKey) + if b == nil { + panic("Stored supplier should not have been nil") + } + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &supplier) + return +} + +// SetSupplier sets the Supplier to store +func (k Keeper) SetSupplier(ctx sdk.Context, supplier types.Supplier) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinaryLengthPrefixed(supplier) + store.Set(supplierKey, b) +} + +// InflateSupply adds tokens to the supplier +func (k Keeper) InflateSupply(ctx sdk.Context, supplyType string, amount sdk.Coins) { + supplier := k.GetSupplier(ctx) + supplier.Inflate(supplyType, amount) + + k.SetSupplier(ctx, supplier) +} + +// DeflateSupply subtracts tokens to the suplier +func (k Keeper) DeflateSupply(ctx sdk.Context, supplyType string, amount sdk.Coins) { + supplier := k.GetSupplier(ctx) + supplier.Deflate(supplyType, amount) + + k.SetSupplier(ctx, supplier) +} diff --git a/x/supply/keeper/keeper_test.go b/x/supply/keeper/keeper_test.go new file mode 100644 index 000000000000..21ed0af0afbe --- /dev/null +++ b/x/supply/keeper/keeper_test.go @@ -0,0 +1,68 @@ +package keeper + +import ( + "testing" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/supply/types" +) + +var oneUatom = sdk.NewCoins(sdk.NewCoin("uatom", sdk.OneInt())) + +func TestSupplier(t *testing.T) { + ctx, keeper := CreateTestInput(t, false) + + require.Panics(t, func() { keeper.GetSupplier(ctx) }, "should panic when supplier is not set") + + expectedSupplier := types.DefaultSupplier() + keeper.SetSupplier(ctx, expectedSupplier) + + // test inflation + expectedSupplier.Inflate(types.TypeCirculating, oneUatom) + keeper.InflateSupply(ctx, types.TypeCirculating, oneUatom) + + supplier := keeper.GetSupplier(ctx) + require.Equal(t, expectedSupplier.CirculatingSupply, supplier.CirculatingSupply) + + // test deflation + expectedSupplier.Deflate(types.TypeCirculating, oneUatom) + keeper.DeflateSupply(ctx, types.TypeCirculating, oneUatom) + + supplier = keeper.GetSupplier(ctx) + require.Equal(t, expectedSupplier.CirculatingSupply, supplier.CirculatingSupply) +} + +func CreateTestInput(t *testing.T, isCheckTx bool) (sdk.Context, Keeper) { + + keySupply := sdk.NewKVStoreKey(StoreKey) + + db := dbm.NewMemDB() + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(keySupply, sdk.StoreTypeIAVL, db) + err := ms.LoadLatestVersion() + require.Nil(t, err) + + ctx := sdk.NewContext(ms, abci.Header{ChainID: "supply-chain"}, isCheckTx, log.NewNopLogger()) + ctx = ctx.WithConsensusParams( + &abci.ConsensusParams{ + Validator: &abci.ValidatorParams{ + PubKeyTypes: []string{tmtypes.ABCIPubKeyTypeEd25519}, + }, + }, + ) + + cdc := codec.New() + + keeper := NewKeeper(cdc, keySupply) + + return ctx, keeper +} diff --git a/x/supply/keeper/key.go b/x/supply/keeper/key.go new file mode 100644 index 000000000000..c88f2dd6ef66 --- /dev/null +++ b/x/supply/keeper/key.go @@ -0,0 +1,14 @@ +package keeper + +const ( + // ModuleName is the name of the module + ModuleName = "supply" + + // StoreKey is the default store key for supply + StoreKey = ModuleName + + // QuerierRoute is the querier route for the supply store. + QuerierRoute = StoreKey +) + +var supplierKey = []byte{0x0} diff --git a/x/supply/types/genesis.go b/x/supply/types/genesis.go new file mode 100644 index 000000000000..aa867edce12d --- /dev/null +++ b/x/supply/types/genesis.go @@ -0,0 +1,16 @@ +package types + +// GenesisState is the supply state that must be provided at genesis. +type GenesisState struct { + Supplier Supplier `json:"supplier"` +} + +// NewGenesisState creates a new genesis state. +func NewGenesisState(supplier Supplier) GenesisState { + return GenesisState{Supplier: supplier} +} + +// DefaultGenesisState returns a default genesis state +func DefaultGenesisState() GenesisState { + return NewGenesisState(DefaultSupplier()) +} diff --git a/x/supply/types/supplier.go b/x/supply/types/supplier.go new file mode 100644 index 000000000000..8965bc4e6dd6 --- /dev/null +++ b/x/supply/types/supplier.go @@ -0,0 +1,120 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// define constants for inflation +const ( + TypeCirculating = "circulating" + TypeVesting = "vesting" + TypeModules = "modules" + TypeTotal = "total" +) + +// Supplier represents a struct that passively keeps track of the total supply amounts in the network +type Supplier struct { + CirculatingSupply sdk.Coins `json:"circulating_supply"` // supply held by accounts that's not vesting; circulating = total - vesting + InitialVestingSupply sdk.Coins `json:"initial_vesting_supply"` // initial locked supply held by vesting accounts + ModulesSupply sdk.Coins `json:"modules_supply"` // supply held by modules acccounts + TotalSupply sdk.Coins `json:"total_supply"` // total supply of tokens on the chain +} + +// NewSupplier creates a new Supplier instance +func NewSupplier(circulating, vesting, modules, total sdk.Coins) Supplier { + + return Supplier{ + CirculatingSupply: circulating, + InitialVestingSupply: vesting, + ModulesSupply: modules, + TotalSupply: total, + } +} + +// DefaultSupplier creates an empty Supplier +func DefaultSupplier() Supplier { + coins := sdk.NewCoins() + return NewSupplier(coins, coins, coins, coins) +} + +// Inflate adds coins to a given supply type +func (supplier *Supplier) Inflate(supplyType string, amount sdk.Coins) { + switch supplyType { + + case TypeCirculating: + supplier.CirculatingSupply = supplier.CirculatingSupply.Add(amount) + + case TypeVesting: + supplier.InitialVestingSupply = supplier.InitialVestingSupply.Add(amount) + + case TypeModules: + supplier.ModulesSupply = supplier.ModulesSupply.Add(amount) + + case TypeTotal: + supplier.TotalSupply = supplier.TotalSupply.Add(amount) + + default: + panic(fmt.Errorf("invalid type %s", supplyType)) + } +} + +// Deflate subtracts coins for a given supply +func (supplier *Supplier) Deflate(supplyType string, amount sdk.Coins) { + + switch supplyType { + + case TypeCirculating: + supplier.CirculatingSupply = supplier.CirculatingSupply.Sub(amount) + + case TypeModules: + supplier.ModulesSupply = supplier.ModulesSupply.Sub(amount) + + case TypeTotal: + supplier.TotalSupply = supplier.TotalSupply.Sub(amount) + + default: + panic(fmt.Errorf("invalid type %s", supplyType)) + } +} + +// ValidateBasic validates the Supply coins and returns error if invalid +func (supplier Supplier) ValidateBasic() sdk.Error { + + if !supplier.CirculatingSupply.IsValid() { + return sdk.ErrInvalidCoins( + fmt.Sprintf("invalid circulating supply: %s", supplier.CirculatingSupply.String()), + ) + } + if !supplier.InitialVestingSupply.IsValid() { + return sdk.ErrInvalidCoins( + fmt.Sprintf("invalid initial vesting supply: %s", supplier.InitialVestingSupply.String()), + ) + } + if !supplier.ModulesSupply.IsValid() { + return sdk.ErrInvalidCoins( + fmt.Sprintf("invalid token holders supply: %s", supplier.ModulesSupply.String()), + ) + } + if !supplier.TotalSupply.IsValid() { + return sdk.ErrInvalidCoins( + fmt.Sprintf("invalid total supply: %s", supplier.ModulesSupply.String()), + ) + } + + return nil +} + +// String returns a human readable string representation of a supplier. +func (supplier Supplier) String() string { + return fmt.Sprintf(`Supplier: + Circulating Supply: %s + Initial Vesting Supply: %s + Modules Supply: %s + Total Supply: %s`, + supplier.CirculatingSupply.String(), + supplier.InitialVestingSupply.String(), + supplier.ModulesSupply.String(), + supplier.TotalSupply.String()) +} diff --git a/x/supply/types/supply.go b/x/supply/types/supply.go new file mode 100644 index 000000000000..6661e5f962e4 --- /dev/null +++ b/x/supply/types/supply.go @@ -0,0 +1,64 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// CoinsSupply defines relevant supply information from all the coins tracked by the current chain. +// It is the format exported to clients. +type CoinsSupply struct { + Circulating sdk.Coins `json:"circulating"` // supply held by accounts that's not vesting; circulating = total - vesting + InitialVesting sdk.Coins `json:"initial_vesting"` // initial locked supply held by vesting accounts + Modules sdk.Coins `json:"modules"` // supply held by modules acccounts + Liquid sdk.Coins `json:"liquid"` // sum of account spendable coins at a given time + Total sdk.Coins `json:"total"` // total supply of tokens on the chain +} + +// NewCoinsSupply creates a supply instance for all the coins +func NewCoinsSupply(circulating, vesting, modules, liquid, total sdk.Coins) CoinsSupply { + return CoinsSupply{ + Circulating: circulating, + InitialVesting: vesting, + Modules: modules, + Liquid: liquid, + Total: total, + } +} + +// NewCoinsSupplyFromSupplier creates CoinsSupply instance from a given Supplier +func NewCoinsSupplyFromSupplier(supplier Supplier) CoinsSupply { + return NewCoinsSupply(supplier.CirculatingSupply, + supplier.InitialVestingSupply, supplier.ModulesSupply, + supplier.TotalSupply.Sub(supplier.InitialVestingSupply), supplier.TotalSupply) +} + +// CoinSupply defines the supply information for a single coin on the current chain. +// It is the format exported to clients when an individual denom is queried. +type CoinSupply struct { + Circulating sdk.Int `json:"circulating"` // supply held by accounts that's not vesting; circulating = total - vesting + InitialVesting sdk.Int `json:"initial_vesting"` // initial locked supply held by vesting accounts + Modules sdk.Int `json:"modules"` // supply held by modules acccounts + Liquid sdk.Int `json:"liquid"` // sum of account spendable coins at a given time + Total sdk.Int `json:"total"` // total supply of tokens on the chain +} + +// NewCoinSupply creates a supply instance for a single coin denom +func NewCoinSupply(circulating, vesting, modules, liquid, total sdk.Int) CoinSupply { + return CoinSupply{ + Circulating: circulating, + InitialVesting: vesting, + Modules: modules, + Liquid: liquid, + Total: total, + } +} + +// NewCoinSupplyFromSupplier creates CoinSupply instance from a given Supplier +func NewCoinSupplyFromSupplier(denom string, supplier Supplier) CoinSupply { + return NewCoinSupply( + supplier.CirculatingSupply.AmountOf(denom), + supplier.InitialVestingSupply.AmountOf(denom), + supplier.ModulesSupply.AmountOf(denom), + supplier.TotalSupply.Sub(supplier.InitialVestingSupply).AmountOf(denom), + supplier.TotalSupply.AmountOf(denom)) +}