cosmos-sdk/x/gov/keeper.go

500 lines
17 KiB
Go
Raw Normal View History

2018-06-21 17:19:14 -07:00
package gov
import (
"time"
codec "github.com/cosmos/cosmos-sdk/codec"
2018-06-21 17:19:14 -07:00
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/libs/log"
)
const (
2019-02-08 18:33:06 -08:00
// ModuleKey is the name of the module
ModuleName = "gov"
// StoreKey is the store key string for gov
2019-02-08 18:33:06 -08:00
StoreKey = ModuleName
// RouterKey is the message route for gov
2019-02-08 18:33:06 -08:00
RouterKey = ModuleName
// QuerierRoute is the querier route for gov
2019-02-08 18:33:06 -08:00
QuerierRoute = ModuleName
// Parameter store default namestore
2019-02-08 18:33:06 -08:00
DefaultParamspace = ModuleName
)
2018-09-17 08:28:13 -07:00
// Parameter store key
2018-09-27 11:52:29 -07:00
var (
ParamStoreKeyDepositParams = []byte("depositparams")
ParamStoreKeyVotingParams = []byte("votingparams")
ParamStoreKeyTallyParams = []byte("tallyparams")
2019-02-08 18:33:06 -08:00
// TODO: Find another way to implement this without using accounts, or find a cleaner way to implement it using accounts.
DepositedCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("govDepositedCoins")))
BurnedDepositCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("govBurnedDepositCoins")))
2018-06-21 17:19:14 -07:00
)
2019-02-04 18:13:04 -08:00
// Key declaration for parameters
func ParamKeyTable() params.KeyTable {
return params.NewKeyTable(
ParamStoreKeyDepositParams, DepositParams{},
ParamStoreKeyVotingParams, VotingParams{},
ParamStoreKeyTallyParams, TallyParams{},
2018-10-06 08:32:41 -07:00
)
}
2018-06-21 17:19:14 -07:00
// Governance Keeper
type Keeper struct {
// The reference to the Param Keeper to get and set Global Params
2018-10-06 06:50:58 -07:00
paramsKeeper params.Keeper
// The reference to the Paramstore to get and set gov specific params
paramSpace params.Subspace
2018-06-21 17:19:14 -07:00
// The reference to the CoinKeeper to modify balances
ck BankKeeper
2018-06-21 17:19:14 -07:00
// The ValidatorSet to get information about validators
vs sdk.ValidatorSet
// The reference to the DelegationSet to get information about delegators
ds sdk.DelegationSet
// The (unexposed) keys used to access the stores from the Context.
storeKey sdk.StoreKey
// The codec codec for binary encoding/decoding.
cdc *codec.Codec
2018-06-21 17:19:14 -07:00
2018-09-18 10:16:51 -07:00
// Reserved codespace
codespace sdk.CodespaceType
2018-06-21 17:19:14 -07:00
}
// NewKeeper returns a governance keeper. It handles:
// - submitting governance proposals
// - depositing funds into proposals, and activating upon sufficient funds being deposited
// - 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 {
2018-06-21 17:19:14 -07:00
return Keeper{
2018-10-06 06:50:58 -07:00
storeKey: key,
paramsKeeper: paramsKeeper,
2019-02-04 18:13:04 -08:00
paramSpace: paramSpace.WithKeyTable(ParamKeyTable()),
2018-10-06 06:50:58 -07:00
ck: ck,
ds: ds,
vs: ds.GetValidatorSet(),
cdc: cdc,
codespace: codespace,
2018-06-21 17:19:14 -07:00
}
}
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", "x/gov") }
2018-06-21 17:19:14 -07:00
// Proposals
func (keeper Keeper) SubmitProposal(ctx sdk.Context, content ProposalContent) (proposal Proposal, err sdk.Error) {
2018-06-21 17:19:14 -07:00
proposalID, err := keeper.getNewProposalID(ctx)
if err != nil {
return
2018-06-21 17:19:14 -07:00
}
submitTime := ctx.BlockHeader().Time
depositPeriod := keeper.GetDepositParams(ctx).MaxDepositPeriod
proposal = Proposal{
ProposalContent: content,
ProposalID: proposalID,
Status: StatusDepositPeriod,
FinalTallyResult: EmptyTallyResult(),
2019-03-07 16:55:08 -08:00
TotalDeposit: sdk.NewCoins(),
SubmitTime: submitTime,
DepositEndTime: submitTime.Add(depositPeriod),
2018-06-21 17:19:14 -07:00
}
2018-06-21 17:19:14 -07:00
keeper.SetProposal(ctx, proposal)
keeper.InsertInactiveProposalQueue(ctx, proposal.DepositEndTime, proposalID)
return
2018-06-21 17:19:14 -07:00
}
// Get Proposal from store by ProposalID
func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID uint64) (proposal Proposal, ok bool) {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyProposal(proposalID))
if bz == nil {
return
2018-06-21 17:19:14 -07:00
}
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposal)
return proposal, true
2018-06-21 17:19:14 -07:00
}
// Implements sdk.AccountKeeper.
2018-06-21 17:19:14 -07:00
func (keeper Keeper) SetProposal(ctx sdk.Context, proposal Proposal) {
store := ctx.KVStore(keeper.storeKey)
bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposal)
store.Set(KeyProposal(proposal.ProposalID), bz)
2018-06-21 17:19:14 -07:00
}
// Implements sdk.AccountKeeper.
func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposalID uint64) {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
proposal, ok := keeper.GetProposal(ctx, proposalID)
if !ok {
panic("DeleteProposal cannot fail to GetProposal")
}
keeper.RemoveFromInactiveProposalQueue(ctx, proposal.DepositEndTime, proposalID)
keeper.RemoveFromActiveProposalQueue(ctx, proposal.VotingEndTime, proposalID)
store.Delete(KeyProposal(proposalID))
2018-06-21 17:19:14 -07:00
}
2018-08-03 12:55:00 -07:00
// Get Proposal from store by ProposalID
2019-02-08 18:33:06 -08:00
// voterAddr will filter proposals by whether or not that address has voted on them
// depositorAddr will filter proposals by whether or not that address has deposited to them
// status will filter proposals by status
// numLatest will fetch a specified number of the most recent proposals, or 0 for all proposals
func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddress, depositorAddr sdk.AccAddress, status ProposalStatus, numLatest uint64) []Proposal {
2018-08-03 12:55:00 -07:00
maxProposalID, err := keeper.peekCurrentProposalID(ctx)
if err != nil {
return nil
}
matchingProposals := []Proposal{}
if numLatest == 0 {
2018-08-03 12:55:00 -07:00
numLatest = maxProposalID
}
for proposalID := maxProposalID - numLatest; proposalID < maxProposalID; proposalID++ {
if voterAddr != nil && len(voterAddr) != 0 {
_, found := keeper.GetVote(ctx, proposalID, voterAddr)
if !found {
continue
}
}
if depositorAddr != nil && len(depositorAddr) != 0 {
_, found := keeper.GetDeposit(ctx, proposalID, depositorAddr)
2018-08-03 12:55:00 -07:00
if !found {
continue
}
}
proposal, ok := keeper.GetProposal(ctx, proposalID)
if !ok {
2018-08-03 12:55:00 -07:00
continue
}
if validProposalStatus(status) {
if proposal.Status != status {
2018-08-03 12:55:00 -07:00
continue
}
}
matchingProposals = append(matchingProposals, proposal)
}
return matchingProposals
}
2019-02-08 18:33:06 -08:00
// Set the initial proposal ID
func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID uint64) sdk.Error {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
if bz != nil {
2018-09-18 10:16:51 -07:00
return ErrInvalidGenesis(keeper.codespace, "Initial ProposalID already set")
2018-06-21 17:19:14 -07:00
}
bz = keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID)
2018-06-21 17:19:14 -07:00
store.Set(KeyNextProposalID, bz)
return nil
}
// Get the last used proposal ID
func (keeper Keeper) GetLastProposalID(ctx sdk.Context) (proposalID uint64) {
2018-08-22 00:17:37 -07:00
proposalID, err := keeper.peekCurrentProposalID(ctx)
if err != nil {
return 0
}
proposalID--
return
}
2018-08-03 12:55:00 -07:00
// Gets the next available ProposalID and increments it
func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID uint64, err sdk.Error) {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
if bz == nil {
return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set")
2018-06-21 17:19:14 -07:00
}
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID)
bz = keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID + 1)
2018-08-03 12:55:00 -07:00
store.Set(KeyNextProposalID, bz)
return proposalID, nil
}
// Peeks the next available ProposalID without incrementing it
func (keeper Keeper) peekCurrentProposalID(ctx sdk.Context) (proposalID uint64, err sdk.Error) {
2018-08-03 12:55:00 -07:00
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
if bz == nil {
return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set")
2018-08-03 12:55:00 -07:00
}
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID)
2018-06-21 17:19:14 -07:00
return proposalID, nil
}
func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal Proposal) {
proposal.VotingStartTime = ctx.BlockHeader().Time
votingPeriod := keeper.GetVotingParams(ctx).VotingPeriod
proposal.VotingEndTime = proposal.VotingStartTime.Add(votingPeriod)
proposal.Status = StatusVotingPeriod
2018-06-21 17:19:14 -07:00
keeper.SetProposal(ctx, proposal)
keeper.RemoveFromInactiveProposalQueue(ctx, proposal.DepositEndTime, proposal.ProposalID)
keeper.InsertActiveProposalQueue(ctx, proposal.VotingEndTime, proposal.ProposalID)
2018-06-21 17:19:14 -07:00
}
// Params
2018-06-21 17:19:14 -07:00
// Returns the current DepositParams from the global param store
func (keeper Keeper) GetDepositParams(ctx sdk.Context) DepositParams {
var depositParams DepositParams
keeper.paramSpace.Get(ctx, ParamStoreKeyDepositParams, &depositParams)
return depositParams
}
2018-07-16 10:08:22 -07:00
// Returns the current VotingParams from the global param store
func (keeper Keeper) GetVotingParams(ctx sdk.Context) VotingParams {
var votingParams VotingParams
keeper.paramSpace.Get(ctx, ParamStoreKeyVotingParams, &votingParams)
return votingParams
2018-06-21 17:19:14 -07:00
}
// Returns the current TallyParam from the global param store
func (keeper Keeper) GetTallyParams(ctx sdk.Context) TallyParams {
var tallyParams TallyParams
keeper.paramSpace.Get(ctx, ParamStoreKeyTallyParams, &tallyParams)
return tallyParams
2018-06-21 17:19:14 -07:00
}
func (keeper Keeper) setDepositParams(ctx sdk.Context, depositParams DepositParams) {
keeper.paramSpace.Set(ctx, ParamStoreKeyDepositParams, &depositParams)
}
func (keeper Keeper) setVotingParams(ctx sdk.Context, votingParams VotingParams) {
keeper.paramSpace.Set(ctx, ParamStoreKeyVotingParams, &votingParams)
}
func (keeper Keeper) setTallyParams(ctx sdk.Context, tallyParams TallyParams) {
keeper.paramSpace.Set(ctx, ParamStoreKeyTallyParams, &tallyParams)
2018-06-21 17:19:14 -07:00
}
// Votes
// Adds a vote on a specific proposal
func (keeper Keeper) AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, option VoteOption) sdk.Error {
proposal, ok := keeper.GetProposal(ctx, proposalID)
if !ok {
2018-09-18 10:16:51 -07:00
return ErrUnknownProposal(keeper.codespace, proposalID)
2018-06-21 17:19:14 -07:00
}
if proposal.Status != StatusVotingPeriod {
2018-09-18 10:16:51 -07:00
return ErrInactiveProposal(keeper.codespace, proposalID)
2018-06-21 17:19:14 -07:00
}
2018-07-10 17:59:07 -07:00
if !validVoteOption(option) {
2018-09-18 10:16:51 -07:00
return ErrInvalidVote(keeper.codespace, option)
2018-06-21 17:19:14 -07:00
}
vote := Vote{
ProposalID: proposalID,
Voter: voterAddr,
Option: option,
}
keeper.setVote(ctx, proposalID, voterAddr, vote)
return nil
}
// Gets the vote of a specific voter on a specific proposal
func (keeper Keeper) GetVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) (Vote, bool) {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyVote(proposalID, voterAddr))
if bz == nil {
return Vote{}, false
}
var vote Vote
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &vote)
2018-06-21 17:19:14 -07:00
return vote, true
}
func (keeper Keeper) setVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, vote Vote) {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(vote)
2018-06-21 17:19:14 -07:00
store.Set(KeyVote(proposalID, voterAddr), bz)
}
// Gets all the votes on a specific proposal
func (keeper Keeper) GetVotes(ctx sdk.Context, proposalID uint64) sdk.Iterator {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
return sdk.KVStorePrefixIterator(store, KeyVotesSubspace(proposalID))
}
func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
store.Delete(KeyVote(proposalID, voterAddr))
}
// Deposits
// Gets the deposit of a specific depositor on a specific proposal
func (keeper Keeper) GetDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) (Deposit, bool) {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyDeposit(proposalID, depositorAddr))
2018-06-21 17:19:14 -07:00
if bz == nil {
return Deposit{}, false
}
var deposit Deposit
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &deposit)
2018-06-21 17:19:14 -07:00
return deposit, true
}
func (keeper Keeper) setDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, deposit Deposit) {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(deposit)
store.Set(KeyDeposit(proposalID, depositorAddr), bz)
2018-06-21 17:19:14 -07:00
}
// Adds or updates a deposit of a specific depositor on a specific proposal
2018-06-21 17:19:14 -07:00
// Activates voting period when appropriate
func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, depositAmount sdk.Coins) (sdk.Error, bool) {
2018-06-21 17:19:14 -07:00
// Checks to see if proposal exists
proposal, ok := keeper.GetProposal(ctx, proposalID)
if !ok {
2018-09-18 10:16:51 -07:00
return ErrUnknownProposal(keeper.codespace, proposalID), false
2018-06-21 17:19:14 -07:00
}
// Check if proposal is still depositable
if (proposal.Status != StatusDepositPeriod) && (proposal.Status != StatusVotingPeriod) {
2018-09-18 10:16:51 -07:00
return ErrAlreadyFinishedProposal(keeper.codespace, proposalID), false
2018-06-21 17:19:14 -07:00
}
// Send coins from depositor's account to DepositedCoinsAccAddr account
2019-02-08 18:33:06 -08:00
// TODO: Don't use an account for this purpose; it's clumsy and prone to misuse.
err := keeper.ck.SendCoins(ctx, depositorAddr, DepositedCoinsAccAddr, depositAmount)
2018-06-21 17:19:14 -07:00
if err != nil {
return err, false
}
2019-02-08 18:33:06 -08:00
// Update proposal
proposal.TotalDeposit = proposal.TotalDeposit.Add(depositAmount)
2018-06-21 17:19:14 -07:00
keeper.SetProposal(ctx, proposal)
2019-02-08 18:33:06 -08:00
// Check if deposit has provided sufficient total funds to transition the proposal into the voting period
2018-06-21 17:19:14 -07:00
activatedVotingPeriod := false
if proposal.Status == StatusDepositPeriod && proposal.TotalDeposit.IsAllGTE(keeper.GetDepositParams(ctx).MinDeposit) {
2018-06-21 17:19:14 -07:00
keeper.activateVotingPeriod(ctx, proposal)
activatedVotingPeriod = true
}
// Add or update deposit object
currDeposit, found := keeper.GetDeposit(ctx, proposalID, depositorAddr)
2018-06-21 17:19:14 -07:00
if !found {
newDeposit := Deposit{depositorAddr, proposalID, depositAmount}
keeper.setDeposit(ctx, proposalID, depositorAddr, newDeposit)
2018-06-21 17:19:14 -07:00
} else {
currDeposit.Amount = currDeposit.Amount.Add(depositAmount)
keeper.setDeposit(ctx, proposalID, depositorAddr, currDeposit)
2018-06-21 17:19:14 -07:00
}
return nil, activatedVotingPeriod
}
2019-02-08 18:33:06 -08:00
// Gets all the deposits on a specific proposal as an sdk.Iterator
func (keeper Keeper) GetDeposits(ctx sdk.Context, proposalID uint64) sdk.Iterator {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
return sdk.KVStorePrefixIterator(store, KeyDepositsSubspace(proposalID))
}
2019-02-08 18:33:06 -08:00
// Refunds and deletes all the deposits on a specific proposal
func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID uint64) {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
depositsIterator := keeper.GetDeposits(ctx, proposalID)
defer depositsIterator.Close()
2018-06-21 17:19:14 -07:00
for ; depositsIterator.Valid(); depositsIterator.Next() {
deposit := &Deposit{}
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit)
2018-06-21 17:19:14 -07:00
err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, deposit.Depositor, deposit.Amount)
2018-06-21 17:19:14 -07:00
if err != nil {
panic("should not happen")
}
store.Delete(depositsIterator.Key())
}
}
// Deletes all the deposits on a specific proposal without refunding them
func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID uint64) {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
depositsIterator := keeper.GetDeposits(ctx, proposalID)
defer depositsIterator.Close()
2018-06-21 17:19:14 -07:00
for ; depositsIterator.Valid(); depositsIterator.Next() {
deposit := &Deposit{}
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit)
2019-02-08 18:33:06 -08:00
// TODO: Find a way to do this without using accounts.
err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, BurnedDepositCoinsAccAddr, deposit.Amount)
if err != nil {
panic("should not happen")
}
2018-06-21 17:19:14 -07:00
store.Delete(depositsIterator.Key())
}
}
// ProposalQueues
// Returns an iterator for all the proposals in the Active Queue that expire by endTime
func (keeper Keeper) ActiveProposalQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
return store.Iterator(PrefixActiveProposalQueue, sdk.PrefixEndBytes(PrefixActiveProposalQueueTime(endTime)))
2018-06-21 17:19:14 -07:00
}
// Inserts a ProposalID into the active proposal queue at endTime
func (keeper Keeper) InsertActiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID)
store.Set(KeyActiveProposalQueueProposal(endTime, proposalID), bz)
2018-06-21 17:19:14 -07:00
}
// removes a proposalID from the Active Proposal Queue
func (keeper Keeper) RemoveFromActiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
store.Delete(KeyActiveProposalQueueProposal(endTime, proposalID))
2018-06-21 17:19:14 -07:00
}
// Returns an iterator for all the proposals in the Inactive Queue that expire by endTime
func (keeper Keeper) InactiveProposalQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
return store.Iterator(PrefixInactiveProposalQueue, sdk.PrefixEndBytes(PrefixInactiveProposalQueueTime(endTime)))
2018-06-21 17:19:14 -07:00
}
// Inserts a ProposalID into the inactive proposal queue at endTime
func (keeper Keeper) InsertInactiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) {
store := ctx.KVStore(keeper.storeKey)
bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID)
store.Set(KeyInactiveProposalQueueProposal(endTime, proposalID), bz)
2018-06-21 17:19:14 -07:00
}
// removes a proposalID from the Inactive Proposal Queue
func (keeper Keeper) RemoveFromInactiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) {
store := ctx.KVStore(keeper.storeKey)
store.Delete(KeyInactiveProposalQueueProposal(endTime, proposalID))
2018-06-21 17:19:14 -07:00
}