cosmos-sdk/x/gov/keeper.go

519 lines
16 KiB
Go
Raw Normal View History

2018-06-21 17:19:14 -07:00
package gov
import (
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/bank"
"github.com/cosmos/cosmos-sdk/x/params"
)
2018-09-18 04:16:20 -07:00
// Parameter store default namestore
const (
2018-09-18 04:16:20 -07:00
DefaultParamspace = "gov"
)
2018-09-17 08:28:13 -07:00
// Parameter store key
2018-09-27 11:52:29 -07:00
var (
ParamStoreKeyDepositProcedure = []byte("depositprocedure")
ParamStoreKeyVotingProcedure = []byte("votingprocedure")
ParamStoreKeyTallyingProcedure = []byte("tallyingprocedure")
2018-06-21 17:19:14 -07:00
)
2018-10-06 09:08:49 -07:00
// Type declaration for parameters
func ParamTypeTable() params.TypeTable {
return params.NewTypeTable(
2018-10-06 08:32:41 -07:00
ParamStoreKeyDepositProcedure, DepositProcedure{},
ParamStoreKeyVotingProcedure, VotingProcedure{},
ParamStoreKeyTallyingProcedure, TallyingProcedure{},
)
}
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 bank.Keeper
// 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 bank.Keeper, 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,
paramSpace: paramSpace.WithTypeTable(ParamTypeTable()),
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
}
}
// =====================================================
// Proposals
// Creates a NewProposal
2018-07-10 17:59:07 -07:00
func (keeper Keeper) NewTextProposal(ctx sdk.Context, title string, description string, proposalType ProposalKind) Proposal {
2018-06-21 17:19:14 -07:00
proposalID, err := keeper.getNewProposalID(ctx)
if err != nil {
return nil
}
var proposal Proposal = &TextProposal{
ProposalID: proposalID,
Title: title,
Description: description,
ProposalType: proposalType,
Status: StatusDepositPeriod,
TallyResult: EmptyTallyResult(),
TotalDeposit: sdk.Coins{},
SubmitTime: ctx.BlockHeader().Time,
2018-06-21 17:19:14 -07:00
}
keeper.SetProposal(ctx, proposal)
keeper.InactiveProposalQueuePush(ctx, proposal)
return proposal
}
// Get Proposal from store by ProposalID
func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID int64) Proposal {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyProposal(proposalID))
if bz == nil {
return nil
}
var proposal Proposal
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposal)
2018-06-21 17:19:14 -07:00
return proposal
}
// 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)
2018-06-21 17:19:14 -07:00
store.Set(KeyProposal(proposal.GetProposalID()), bz)
}
// Implements sdk.AccountKeeper.
2018-06-21 17:19:14 -07:00
func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposal Proposal) {
store := ctx.KVStore(keeper.storeKey)
store.Delete(KeyProposal(proposal.GetProposalID()))
}
2018-08-03 12:55:00 -07:00
// Get Proposal from store by ProposalID
func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddress, depositerAddr sdk.AccAddress, status ProposalStatus, numLatest int64) []Proposal {
maxProposalID, err := keeper.peekCurrentProposalID(ctx)
if err != nil {
return nil
}
matchingProposals := []Proposal{}
if numLatest <= 0 {
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 depositerAddr != nil && len(depositerAddr) != 0 {
_, found := keeper.GetDeposit(ctx, proposalID, depositerAddr)
if !found {
continue
}
}
proposal := keeper.GetProposal(ctx, proposalID)
if proposal == nil {
continue
}
if validProposalStatus(status) {
if proposal.GetStatus() != status {
continue
}
}
matchingProposals = append(matchingProposals, proposal)
}
return matchingProposals
}
2018-06-21 17:19:14 -07:00
func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID int64) sdk.Error {
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 int64) {
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
2018-06-21 17:19:14 -07:00
func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID int64, err sdk.Error) {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
if bz == nil {
2018-09-18 10:16:51 -07:00
return -1, 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 int64, err sdk.Error) {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
if bz == nil {
2018-09-18 10:16:51 -07:00
return -1, 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.SetVotingStartTime(ctx.BlockHeader().Time)
2018-06-21 17:19:14 -07:00
proposal.SetStatus(StatusVotingPeriod)
keeper.SetProposal(ctx, proposal)
keeper.ActiveProposalQueuePush(ctx, proposal)
}
// =====================================================
// Procedures
// Returns the current Deposit Procedure from the global param store
2018-08-31 15:22:37 -07:00
// nolint: errcheck
func (keeper Keeper) GetDepositProcedure(ctx sdk.Context) DepositProcedure {
var depositProcedure DepositProcedure
keeper.paramSpace.Get(ctx, ParamStoreKeyDepositProcedure, &depositProcedure)
return depositProcedure
}
2018-07-16 10:08:22 -07:00
// Returns the current Voting Procedure from the global param store
2018-08-31 15:22:37 -07:00
// nolint: errcheck
func (keeper Keeper) GetVotingProcedure(ctx sdk.Context) VotingProcedure {
var votingProcedure VotingProcedure
keeper.paramSpace.Get(ctx, ParamStoreKeyVotingProcedure, &votingProcedure)
return votingProcedure
2018-06-21 17:19:14 -07:00
}
// Returns the current Tallying Procedure from the global param store
2018-08-31 15:22:37 -07:00
// nolint: errcheck
func (keeper Keeper) GetTallyingProcedure(ctx sdk.Context) TallyingProcedure {
var tallyingProcedure TallyingProcedure
keeper.paramSpace.Get(ctx, ParamStoreKeyTallyingProcedure, &tallyingProcedure)
return tallyingProcedure
2018-06-21 17:19:14 -07:00
}
2018-08-31 15:22:37 -07:00
// nolint: errcheck
func (keeper Keeper) setDepositProcedure(ctx sdk.Context, depositProcedure DepositProcedure) {
keeper.paramSpace.Set(ctx, ParamStoreKeyDepositProcedure, &depositProcedure)
}
2018-08-31 15:22:37 -07:00
// nolint: errcheck
func (keeper Keeper) setVotingProcedure(ctx sdk.Context, votingProcedure VotingProcedure) {
keeper.paramSpace.Set(ctx, ParamStoreKeyVotingProcedure, &votingProcedure)
}
2018-08-31 15:22:37 -07:00
// nolint: errcheck
func (keeper Keeper) setTallyingProcedure(ctx sdk.Context, tallyingProcedure TallyingProcedure) {
keeper.paramSpace.Set(ctx, ParamStoreKeyTallyingProcedure, &tallyingProcedure)
2018-06-21 17:19:14 -07:00
}
// =====================================================
// Votes
// Adds a vote on a specific proposal
2018-07-06 00:06:53 -07:00
func (keeper Keeper) AddVote(ctx sdk.Context, proposalID int64, voterAddr sdk.AccAddress, option VoteOption) sdk.Error {
2018-06-21 17:19:14 -07:00
proposal := keeper.GetProposal(ctx, proposalID)
if proposal == nil {
2018-09-18 10:16:51 -07:00
return ErrUnknownProposal(keeper.codespace, proposalID)
2018-06-21 17:19:14 -07:00
}
if proposal.GetStatus() != 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
2018-07-06 00:06:53 -07:00
func (keeper Keeper) GetVote(ctx sdk.Context, proposalID int64, 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
}
2018-07-06 00:06:53 -07:00
func (keeper Keeper) setVote(ctx sdk.Context, proposalID int64, 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 int64) sdk.Iterator {
store := ctx.KVStore(keeper.storeKey)
return sdk.KVStorePrefixIterator(store, KeyVotesSubspace(proposalID))
}
2018-07-06 00:06:53 -07:00
func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID int64, 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 depositer on a specific proposal
2018-07-06 00:06:53 -07:00
func (keeper Keeper) GetDeposit(ctx sdk.Context, proposalID int64, depositerAddr sdk.AccAddress) (Deposit, bool) {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyDeposit(proposalID, depositerAddr))
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
}
2018-07-06 00:06:53 -07:00
func (keeper Keeper) setDeposit(ctx sdk.Context, proposalID int64, depositerAddr sdk.AccAddress, deposit Deposit) {
2018-06-21 17:19:14 -07:00
store := ctx.KVStore(keeper.storeKey)
bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(deposit)
2018-06-21 17:19:14 -07:00
store.Set(KeyDeposit(proposalID, depositerAddr), bz)
}
// Adds or updates a deposit of a specific depositer on a specific proposal
// Activates voting period when appropriate
2018-07-06 00:06:53 -07:00
func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID int64, depositerAddr sdk.AccAddress, depositAmount sdk.Coins) (sdk.Error, bool) {
2018-06-21 17:19:14 -07:00
// Checks to see if proposal exists
proposal := keeper.GetProposal(ctx, proposalID)
if proposal == nil {
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.GetStatus() != StatusDepositPeriod) && (proposal.GetStatus() != StatusVotingPeriod) {
2018-09-18 10:16:51 -07:00
return ErrAlreadyFinishedProposal(keeper.codespace, proposalID), false
2018-06-21 17:19:14 -07:00
}
// Subtract coins from depositer's account
2018-06-21 17:19:14 -07:00
_, _, err := keeper.ck.SubtractCoins(ctx, depositerAddr, depositAmount)
if err != nil {
return err, false
}
// Update Proposal
proposal.SetTotalDeposit(proposal.GetTotalDeposit().Plus(depositAmount))
keeper.SetProposal(ctx, proposal)
// Check if deposit tipped proposal into voting period
// Active voting period if so
activatedVotingPeriod := false
if proposal.GetStatus() == StatusDepositPeriod && proposal.GetTotalDeposit().IsGTE(keeper.GetDepositProcedure(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, depositerAddr)
if !found {
newDeposit := Deposit{depositerAddr, proposalID, depositAmount}
2018-06-21 17:19:14 -07:00
keeper.setDeposit(ctx, proposalID, depositerAddr, newDeposit)
} else {
currDeposit.Amount = currDeposit.Amount.Plus(depositAmount)
keeper.setDeposit(ctx, proposalID, depositerAddr, currDeposit)
}
return nil, activatedVotingPeriod
}
// Gets all the deposits on a specific proposal
func (keeper Keeper) GetDeposits(ctx sdk.Context, proposalID int64) sdk.Iterator {
store := ctx.KVStore(keeper.storeKey)
return sdk.KVStorePrefixIterator(store, KeyDepositsSubspace(proposalID))
}
// Returns and deletes all the deposits on a specific proposal
func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID int64) {
store := ctx.KVStore(keeper.storeKey)
depositsIterator := keeper.GetDeposits(ctx, proposalID)
for ; depositsIterator.Valid(); depositsIterator.Next() {
deposit := &Deposit{}
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit)
2018-06-21 17:19:14 -07:00
_, _, err := keeper.ck.AddCoins(ctx, deposit.Depositer, deposit.Amount)
if err != nil {
panic("should not happen")
}
store.Delete(depositsIterator.Key())
}
depositsIterator.Close()
}
// Deletes all the deposits on a specific proposal without refunding them
func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID int64) {
store := ctx.KVStore(keeper.storeKey)
depositsIterator := keeper.GetDeposits(ctx, proposalID)
for ; depositsIterator.Valid(); depositsIterator.Next() {
store.Delete(depositsIterator.Key())
}
depositsIterator.Close()
}
// =====================================================
// ProposalQueues
func (keeper Keeper) getActiveProposalQueue(ctx sdk.Context) ProposalQueue {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyActiveProposalQueue)
if bz == nil {
return nil
}
var proposalQueue ProposalQueue
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalQueue)
2018-06-21 17:19:14 -07:00
return proposalQueue
}
func (keeper Keeper) setActiveProposalQueue(ctx sdk.Context, proposalQueue ProposalQueue) {
store := ctx.KVStore(keeper.storeKey)
bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalQueue)
2018-06-21 17:19:14 -07:00
store.Set(KeyActiveProposalQueue, bz)
}
// Return the Proposal at the front of the ProposalQueue
func (keeper Keeper) ActiveProposalQueuePeek(ctx sdk.Context) Proposal {
proposalQueue := keeper.getActiveProposalQueue(ctx)
if len(proposalQueue) == 0 {
return nil
}
return keeper.GetProposal(ctx, proposalQueue[0])
}
// Remove and return a Proposal from the front of the ProposalQueue
func (keeper Keeper) ActiveProposalQueuePop(ctx sdk.Context) Proposal {
proposalQueue := keeper.getActiveProposalQueue(ctx)
if len(proposalQueue) == 0 {
return nil
}
frontElement, proposalQueue := proposalQueue[0], proposalQueue[1:]
keeper.setActiveProposalQueue(ctx, proposalQueue)
return keeper.GetProposal(ctx, frontElement)
}
// Add a proposalID to the back of the ProposalQueue
func (keeper Keeper) ActiveProposalQueuePush(ctx sdk.Context, proposal Proposal) {
proposalQueue := append(keeper.getActiveProposalQueue(ctx), proposal.GetProposalID())
keeper.setActiveProposalQueue(ctx, proposalQueue)
}
func (keeper Keeper) getInactiveProposalQueue(ctx sdk.Context) ProposalQueue {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyInactiveProposalQueue)
if bz == nil {
return nil
}
var proposalQueue ProposalQueue
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalQueue)
2018-06-21 17:19:14 -07:00
return proposalQueue
}
func (keeper Keeper) setInactiveProposalQueue(ctx sdk.Context, proposalQueue ProposalQueue) {
store := ctx.KVStore(keeper.storeKey)
bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalQueue)
2018-06-21 17:19:14 -07:00
store.Set(KeyInactiveProposalQueue, bz)
}
// Return the Proposal at the front of the ProposalQueue
func (keeper Keeper) InactiveProposalQueuePeek(ctx sdk.Context) Proposal {
proposalQueue := keeper.getInactiveProposalQueue(ctx)
if len(proposalQueue) == 0 {
return nil
}
return keeper.GetProposal(ctx, proposalQueue[0])
}
// Remove and return a Proposal from the front of the ProposalQueue
func (keeper Keeper) InactiveProposalQueuePop(ctx sdk.Context) Proposal {
proposalQueue := keeper.getInactiveProposalQueue(ctx)
if len(proposalQueue) == 0 {
return nil
}
frontElement, proposalQueue := proposalQueue[0], proposalQueue[1:]
keeper.setInactiveProposalQueue(ctx, proposalQueue)
return keeper.GetProposal(ctx, frontElement)
}
// Add a proposalID to the back of the ProposalQueue
func (keeper Keeper) InactiveProposalQueuePush(ctx sdk.Context, proposal Proposal) {
proposalQueue := append(keeper.getInactiveProposalQueue(ctx), proposal.GetProposalID())
keeper.setInactiveProposalQueue(ctx, proposalQueue)
}