cosmos-sdk/x/gov/keeper.go

414 lines
13 KiB
Go

package gov
import (
sdk "github.com/cosmos/cosmos-sdk/types"
wire "github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/bank"
)
// Governance Keeper
type Keeper struct {
// 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 wire codec for binary encoding/decoding.
cdc *wire.Codec
// Reserved codespace
codespace sdk.CodespaceType
}
// NewGovernanceMapper returns a mapper that uses go-wire to (binary) encode and decode gov types.
func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, ds sdk.DelegationSet, codespace sdk.CodespaceType) Keeper {
return Keeper{
storeKey: key,
ck: ck,
ds: ds,
vs: ds.GetValidatorSet(),
cdc: cdc,
codespace: codespace,
}
}
// Returns the go-wire codec.
func (keeper Keeper) WireCodec() *wire.Codec {
return keeper.cdc
}
// =====================================================
// 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,
TotalDeposit: sdk.Coins{},
SubmitBlock: ctx.BlockHeight(),
VotingStartBlock: -1, // TODO: Make Time
}
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.MustUnmarshalBinary(bz, &proposal)
return proposal
}
// Implements sdk.AccountMapper.
func (keeper Keeper) SetProposal(ctx sdk.Context, proposal Proposal) {
store := ctx.KVStore(keeper.storeKey)
bz := keeper.cdc.MustMarshalBinary(proposal)
store.Set(KeyProposal(proposal.GetProposalID()), bz)
}
// Implements sdk.AccountMapper.
func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposal Proposal) {
store := ctx.KVStore(keeper.storeKey)
store.Delete(KeyProposal(proposal.GetProposalID()))
}
func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID int64) 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.MustMarshalBinary(proposalID)
store.Set(KeyNextProposalID, bz)
return nil
}
func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID int64, err sdk.Error) {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
if bz == nil {
return -1, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set")
}
keeper.cdc.MustUnmarshalBinary(bz, &proposalID)
bz = keeper.cdc.MustMarshalBinary(proposalID + 1)
store.Set(KeyNextProposalID, bz)
return proposalID, nil
}
func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal Proposal) {
proposal.SetVotingStartBlock(ctx.BlockHeight())
proposal.SetStatus(StatusVotingPeriod)
keeper.SetProposal(ctx, proposal)
keeper.ActiveProposalQueuePush(ctx, proposal)
}
// =====================================================
// Procedures
var (
defaultMinDeposit int64 = 10
defaultMaxDepositPeriod int64 = 10000
defaultVotingPeriod int64 = 10000
)
// Gets procedure from store. TODO: move to global param store and allow for updating of this
func (keeper Keeper) GetDepositProcedure() DepositProcedure {
return DepositProcedure{
MinDeposit: sdk.Coins{sdk.NewCoin("steak", defaultMinDeposit)},
MaxDepositPeriod: defaultMaxDepositPeriod,
}
}
// Gets procedure from store. TODO: move to global param store and allow for updating of this
func (keeper Keeper) GetVotingProcedure() VotingProcedure {
return VotingProcedure{
VotingPeriod: defaultVotingPeriod,
}
}
// Gets procedure from store. TODO: move to global param store and allow for updating of this
func (keeper Keeper) GetTallyingProcedure() TallyingProcedure {
return TallyingProcedure{
Threshold: sdk.NewRat(1, 2),
Veto: sdk.NewRat(1, 3),
GovernancePenalty: sdk.NewRat(1, 100),
}
}
// =====================================================
// Votes
// Adds a vote on a specific proposal
func (keeper Keeper) AddVote(ctx sdk.Context, proposalID int64, 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 int64, 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.MustUnmarshalBinary(bz, &vote)
return vote, true
}
func (keeper Keeper) setVote(ctx sdk.Context, proposalID int64, voterAddr sdk.AccAddress, vote Vote) {
store := ctx.KVStore(keeper.storeKey)
bz := keeper.cdc.MustMarshalBinary(vote)
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))
}
func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID int64, voterAddr sdk.AccAddress) {
store := ctx.KVStore(keeper.storeKey)
store.Delete(KeyVote(proposalID, voterAddr))
}
// =====================================================
// Deposits
// Gets the deposit of a specific depositer on a specific proposal
func (keeper Keeper) GetDeposit(ctx sdk.Context, proposalID int64, depositerAddr sdk.AccAddress) (Deposit, bool) {
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyDeposit(proposalID, depositerAddr))
if bz == nil {
return Deposit{}, false
}
var deposit Deposit
keeper.cdc.MustUnmarshalBinary(bz, &deposit)
return deposit, true
}
func (keeper Keeper) setDeposit(ctx sdk.Context, proposalID int64, depositerAddr sdk.AccAddress, deposit Deposit) {
store := ctx.KVStore(keeper.storeKey)
bz := keeper.cdc.MustMarshalBinary(deposit)
store.Set(KeyDeposit(proposalID, depositerAddr), bz)
}
// Adds or updates a deposit of a specific depositer on a specific proposal
// Activates voting period when appropriate
func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID int64, depositerAddr 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
}
// Subtract coins from depositer's account
_, _, 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().MinDeposit) {
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}
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.MustUnmarshalBinary(depositsIterator.Value(), deposit)
_, _, 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.MustUnmarshalBinary(bz, &proposalQueue)
return proposalQueue
}
func (keeper Keeper) setActiveProposalQueue(ctx sdk.Context, proposalQueue ProposalQueue) {
store := ctx.KVStore(keeper.storeKey)
bz := keeper.cdc.MustMarshalBinary(proposalQueue)
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.MustUnmarshalBinary(bz, &proposalQueue)
return proposalQueue
}
func (keeper Keeper) setInactiveProposalQueue(ctx sdk.Context, proposalQueue ProposalQueue) {
store := ctx.KVStore(keeper.storeKey)
bz := keeper.cdc.MustMarshalBinary(proposalQueue)
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)
}