cosmos-sdk/x/gov/keeper.go

492 lines
16 KiB
Go

package gov
import (
"time"
codec "github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/tendermint/tendermint/crypto"
)
// Parameter store default namestore
const (
DefaultParamspace = "gov"
)
// Parameter store key
var (
ParamStoreKeyDepositParams = []byte("depositparams")
ParamStoreKeyVotingParams = []byte("votingparams")
ParamStoreKeyTallyParams = []byte("tallyparams")
DepositedCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("govDepositedCoins")))
BurnedDepositCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("govBurnedDepositCoins")))
)
// Type declaration for parameters
func ParamTypeTable() params.TypeTable {
return params.NewTypeTable(
ParamStoreKeyDepositParams, DepositParams{},
ParamStoreKeyVotingParams, VotingParams{},
ParamStoreKeyTallyParams, TallyParams{},
)
}
// Governance Keeper
type Keeper struct {
// The reference to the Param Keeper to get and set Global Params
paramsKeeper params.Keeper
// The reference to the Paramstore to get and set gov specific params
paramSpace params.Subspace
// 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
// Reserved codespace
codespace sdk.CodespaceType
}
// 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 {
return Keeper{
storeKey: key,
paramsKeeper: paramsKeeper,
paramSpace: paramSpace.WithTypeTable(ParamTypeTable()),
ck: ck,
ds: ds,
vs: ds.GetValidatorSet(),
cdc: cdc,
codespace: codespace,
}
}
// =====================================================
// Proposals
// Creates a NewProposal
func (keeper Keeper) NewTextProposal(ctx sdk.Context, title string, description string, proposalType ProposalKind) Proposal {
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,
}
depositPeriod := keeper.GetDepositParams(ctx).MaxDepositPeriod
proposal.SetDepositEndTime(proposal.GetSubmitTime().Add(depositPeriod))
keeper.SetProposal(ctx, proposal)
keeper.InsertInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposalID)
return proposal
}
// Get Proposal from store by ProposalID
func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID uint64) Proposal {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyProposal(proposalID))
if bz == nil {
return nil
}
var proposal Proposal
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposal)
return proposal
}
// Implements sdk.AccountKeeper.
func (keeper Keeper) SetProposal(ctx sdk.Context, proposal Proposal) {
store := ctx.KVStore(keeper.storeKey)
bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposal)
store.Set(KeyProposal(proposal.GetProposalID()), bz)
}
// Implements sdk.AccountKeeper.
func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposalID uint64) {
store := ctx.KVStore(keeper.storeKey)
proposal := keeper.GetProposal(ctx, proposalID)
keeper.RemoveFromInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposalID)
keeper.RemoveFromActiveProposalQueue(ctx, proposal.GetVotingEndTime(), proposalID)
store.Delete(KeyProposal(proposalID))
}
// Get Proposal from store by ProposalID
func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddress, depositorAddr sdk.AccAddress, status ProposalStatus, numLatest uint64) []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 depositorAddr != nil && len(depositorAddr) != 0 {
_, found := keeper.GetDeposit(ctx, proposalID, depositorAddr)
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
}
func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID uint64) sdk.Error {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
if bz != nil {
return ErrInvalidGenesis(keeper.codespace, "Initial ProposalID already set")
}
bz = keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID)
store.Set(KeyNextProposalID, bz)
return nil
}
// Get the last used proposal ID
func (keeper Keeper) GetLastProposalID(ctx sdk.Context) (proposalID uint64) {
proposalID, err := keeper.peekCurrentProposalID(ctx)
if err != nil {
return 0
}
proposalID--
return
}
// Gets the next available ProposalID and increments it
func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID uint64, err sdk.Error) {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
if bz == nil {
return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set")
}
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID)
bz = keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID + 1)
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) {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
if bz == nil {
return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set")
}
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID)
return proposalID, nil
}
func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal Proposal) {
proposal.SetVotingStartTime(ctx.BlockHeader().Time)
votingPeriod := keeper.GetVotingParams(ctx).VotingPeriod
proposal.SetVotingEndTime(proposal.GetVotingStartTime().Add(votingPeriod))
proposal.SetStatus(StatusVotingPeriod)
keeper.SetProposal(ctx, proposal)
keeper.RemoveFromInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposal.GetProposalID())
keeper.InsertActiveProposalQueue(ctx, proposal.GetVotingEndTime(), proposal.GetProposalID())
}
// =====================================================
// Params
// Returns the current DepositParams from the global param store
// nolint: errcheck
func (keeper Keeper) GetDepositParams(ctx sdk.Context) DepositParams {
var depositParams DepositParams
keeper.paramSpace.Get(ctx, ParamStoreKeyDepositParams, &depositParams)
return depositParams
}
// Returns the current VotingParams from the global param store
// nolint: errcheck
func (keeper Keeper) GetVotingParams(ctx sdk.Context) VotingParams {
var votingParams VotingParams
keeper.paramSpace.Get(ctx, ParamStoreKeyVotingParams, &votingParams)
return votingParams
}
// Returns the current TallyParam from the global param store
// nolint: errcheck
func (keeper Keeper) GetTallyParams(ctx sdk.Context) TallyParams {
var tallyParams TallyParams
keeper.paramSpace.Get(ctx, ParamStoreKeyTallyParams, &tallyParams)
return tallyParams
}
// nolint: errcheck
func (keeper Keeper) setDepositParams(ctx sdk.Context, depositParams DepositParams) {
keeper.paramSpace.Set(ctx, ParamStoreKeyDepositParams, &depositParams)
}
// nolint: errcheck
func (keeper Keeper) setVotingParams(ctx sdk.Context, votingParams VotingParams) {
keeper.paramSpace.Set(ctx, ParamStoreKeyVotingParams, &votingParams)
}
// nolint: errcheck
func (keeper Keeper) setTallyParams(ctx sdk.Context, tallyParams TallyParams) {
keeper.paramSpace.Set(ctx, ParamStoreKeyTallyParams, &tallyParams)
}
// =====================================================
// 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 := keeper.GetProposal(ctx, proposalID)
if proposal == nil {
return ErrUnknownProposal(keeper.codespace, proposalID)
}
if proposal.GetStatus() != StatusVotingPeriod {
return ErrInactiveProposal(keeper.codespace, proposalID)
}
if !validVoteOption(option) {
return ErrInvalidVote(keeper.codespace, option)
}
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) {
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)
return vote, true
}
func (keeper Keeper) setVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, vote Vote) {
store := ctx.KVStore(keeper.storeKey)
bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(vote)
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 {
store := ctx.KVStore(keeper.storeKey)
return sdk.KVStorePrefixIterator(store, KeyVotesSubspace(proposalID))
}
func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) {
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) {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyDeposit(proposalID, depositorAddr))
if bz == nil {
return Deposit{}, false
}
var deposit Deposit
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &deposit)
return deposit, true
}
func (keeper Keeper) setDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, deposit Deposit) {
store := ctx.KVStore(keeper.storeKey)
bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(deposit)
store.Set(KeyDeposit(proposalID, depositorAddr), bz)
}
// Adds or updates a deposit of a specific depositor on a specific proposal
// Activates voting period when appropriate
func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, depositAmount sdk.Coins) (sdk.Error, bool) {
// Checks to see if proposal exists
proposal := keeper.GetProposal(ctx, proposalID)
if proposal == nil {
return ErrUnknownProposal(keeper.codespace, proposalID), false
}
// Check if proposal is still depositable
if (proposal.GetStatus() != StatusDepositPeriod) && (proposal.GetStatus() != StatusVotingPeriod) {
return ErrAlreadyFinishedProposal(keeper.codespace, proposalID), false
}
// Send coins from depositor's account to DepositedCoinsAccAddr account
_, err := keeper.ck.SendCoins(ctx, depositorAddr, DepositedCoinsAccAddr, 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().IsAllGTE(keeper.GetDepositParams(ctx).MinDeposit) {
keeper.activateVotingPeriod(ctx, proposal)
activatedVotingPeriod = true
}
// Add or update deposit object
currDeposit, found := keeper.GetDeposit(ctx, proposalID, depositorAddr)
if !found {
newDeposit := Deposit{depositorAddr, proposalID, depositAmount}
keeper.setDeposit(ctx, proposalID, depositorAddr, newDeposit)
} else {
currDeposit.Amount = currDeposit.Amount.Plus(depositAmount)
keeper.setDeposit(ctx, proposalID, depositorAddr, currDeposit)
}
return nil, activatedVotingPeriod
}
// Gets all the deposits on a specific proposal
func (keeper Keeper) GetDeposits(ctx sdk.Context, proposalID uint64) 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 uint64) {
store := ctx.KVStore(keeper.storeKey)
depositsIterator := keeper.GetDeposits(ctx, proposalID)
for ; depositsIterator.Valid(); depositsIterator.Next() {
deposit := &Deposit{}
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit)
_, err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, deposit.Depositor, 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 uint64) {
store := ctx.KVStore(keeper.storeKey)
depositsIterator := keeper.GetDeposits(ctx, proposalID)
for ; depositsIterator.Valid(); depositsIterator.Next() {
deposit := &Deposit{}
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit)
_, err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, BurnedDepositCoinsAccAddr, deposit.Amount)
if err != nil {
panic("should not happen")
}
store.Delete(depositsIterator.Key())
}
depositsIterator.Close()
}
// =====================================================
// 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 {
store := ctx.KVStore(keeper.storeKey)
return store.Iterator(PrefixActiveProposalQueue, sdk.PrefixEndBytes(PrefixActiveProposalQueueTime(endTime)))
}
// Inserts a ProposalID into the active proposal queue at endTime
func (keeper Keeper) InsertActiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) {
store := ctx.KVStore(keeper.storeKey)
bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID)
store.Set(KeyActiveProposalQueueProposal(endTime, proposalID), bz)
}
// removes a proposalID from the Active Proposal Queue
func (keeper Keeper) RemoveFromActiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) {
store := ctx.KVStore(keeper.storeKey)
store.Delete(KeyActiveProposalQueueProposal(endTime, proposalID))
}
// 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 {
store := ctx.KVStore(keeper.storeKey)
return store.Iterator(PrefixInactiveProposalQueue, sdk.PrefixEndBytes(PrefixInactiveProposalQueueTime(endTime)))
}
// 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)
}
// 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))
}