2020-03-11 19:52:25 +00:00
package keeper
import (
2020-03-13 23:13:42 +00:00
"fmt"
2020-03-11 19:52:25 +00:00
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/committee/types"
)
2020-03-26 20:17:49 +00:00
// SubmitProposal adds a proposal to a committee so that it can be voted on.
2020-03-11 19:52:25 +00:00
func ( k Keeper ) SubmitProposal ( ctx sdk . Context , proposer sdk . AccAddress , committeeID uint64 , pubProposal types . PubProposal ) ( uint64 , sdk . Error ) {
// Limit proposals to only be submitted by committee members
com , found := k . GetCommittee ( ctx , committeeID )
if ! found {
2020-03-29 19:43:25 +00:00
return 0 , types . ErrUnknownCommittee ( k . codespace , committeeID )
2020-03-11 19:52:25 +00:00
}
if ! com . HasMember ( proposer ) {
2020-03-29 19:43:25 +00:00
return 0 , sdk . ErrUnauthorized ( "proposer not member of committee" )
2020-03-11 19:52:25 +00:00
}
// Check committee has permissions to enact proposal.
if ! com . HasPermissionsFor ( pubProposal ) {
2020-03-29 19:43:25 +00:00
return 0 , sdk . ErrUnauthorized ( "committee does not have permissions to enact proposal" )
2020-03-11 19:52:25 +00:00
}
// Check proposal is valid
if err := k . ValidatePubProposal ( ctx , pubProposal ) ; err != nil {
return 0 , err
}
// Get a new ID and store the proposal
2020-03-27 18:34:03 +00:00
deadline := ctx . BlockTime ( ) . Add ( com . MaxProposalDuration )
2020-03-13 23:13:42 +00:00
proposalID , err := k . StoreNewProposal ( ctx , pubProposal , committeeID , deadline )
if err != nil {
return 0 , err
}
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
2020-03-27 20:28:51 +00:00
types . EventTypeProposalSubmit ,
sdk . NewAttribute ( types . AttributeKeyCommitteeID , fmt . Sprintf ( "%d" , com . ID ) ) ,
2020-03-13 23:13:42 +00:00
sdk . NewAttribute ( types . AttributeKeyProposalID , fmt . Sprintf ( "%d" , proposalID ) ) ,
) ,
)
return proposalID , nil
2020-03-11 19:52:25 +00:00
}
2020-03-26 20:17:49 +00:00
// AddVote submits a vote on a proposal.
2020-03-11 19:52:25 +00:00
func ( k Keeper ) AddVote ( ctx sdk . Context , proposalID uint64 , voter sdk . AccAddress ) sdk . Error {
// Validate
pr , found := k . GetProposal ( ctx , proposalID )
if ! found {
2020-03-29 19:43:25 +00:00
return types . ErrUnknownProposal ( k . codespace , proposalID )
2020-03-11 19:52:25 +00:00
}
2020-03-12 17:05:40 +00:00
if pr . HasExpiredBy ( ctx . BlockTime ( ) ) {
2020-03-29 19:43:25 +00:00
return types . ErrProposalExpired ( k . codespace , ctx . BlockTime ( ) , pr . Deadline )
2020-03-12 17:05:40 +00:00
}
2020-03-11 19:52:25 +00:00
com , found := k . GetCommittee ( ctx , pr . CommitteeID )
if ! found {
2020-03-29 19:43:25 +00:00
return types . ErrUnknownCommittee ( k . codespace , pr . CommitteeID )
2020-03-11 19:52:25 +00:00
}
if ! com . HasMember ( voter ) {
2020-03-29 19:43:25 +00:00
return sdk . ErrUnauthorized ( "voter must be a member of committee" )
2020-03-11 19:52:25 +00:00
}
// Store vote, overwriting any prior vote
k . SetVote ( ctx , types . Vote { ProposalID : proposalID , Voter : voter } )
2020-03-27 20:28:51 +00:00
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
types . EventTypeProposalVote ,
sdk . NewAttribute ( types . AttributeKeyCommitteeID , fmt . Sprintf ( "%d" , com . ID ) ) ,
sdk . NewAttribute ( types . AttributeKeyProposalID , fmt . Sprintf ( "%d" , pr . ID ) ) ,
) ,
)
2020-03-11 19:52:25 +00:00
return nil
}
2020-03-26 20:17:49 +00:00
// GetProposalResult calculates if a proposal currently has enough votes to pass.
func ( k Keeper ) GetProposalResult ( ctx sdk . Context , proposalID uint64 ) ( bool , sdk . Error ) {
2020-03-11 19:52:25 +00:00
pr , found := k . GetProposal ( ctx , proposalID )
if ! found {
2020-03-29 19:43:25 +00:00
return false , types . ErrUnknownProposal ( k . codespace , proposalID )
2020-03-11 19:52:25 +00:00
}
com , found := k . GetCommittee ( ctx , pr . CommitteeID )
if ! found {
2020-03-29 19:43:25 +00:00
return false , types . ErrUnknownCommittee ( k . codespace , pr . CommitteeID )
2020-03-11 19:52:25 +00:00
}
2020-03-26 20:17:49 +00:00
numVotes := k . TallyVotes ( ctx , proposalID )
2020-03-27 18:34:03 +00:00
proposalResult := sdk . NewDec ( numVotes ) . GTE ( com . VoteThreshold . MulInt64 ( int64 ( len ( com . Members ) ) ) )
2020-03-26 20:17:49 +00:00
return proposalResult , nil
}
// TallyVotes counts all the votes on a proposal
func ( k Keeper ) TallyVotes ( ctx sdk . Context , proposalID uint64 ) int64 {
2020-03-11 19:52:25 +00:00
var votes [ ] types . Vote
k . IterateVotes ( ctx , proposalID , func ( vote types . Vote ) bool {
votes = append ( votes , vote )
return false
} )
2020-03-26 20:17:49 +00:00
return int64 ( len ( votes ) )
2020-03-11 19:52:25 +00:00
}
2020-03-26 20:17:49 +00:00
// EnactProposal makes the changes proposed in a proposal.
func ( k Keeper ) EnactProposal ( ctx sdk . Context , proposalID uint64 ) sdk . Error {
pr , found := k . GetProposal ( ctx , proposalID )
if ! found {
2020-03-29 19:43:25 +00:00
return types . ErrUnknownProposal ( k . codespace , proposalID )
2020-03-26 20:17:49 +00:00
}
// Run the proposal's changes through the associated handler, but using a cached version of state to ensure changes are not permanent if an error occurs.
handler := k . router . GetRoute ( pr . ProposalRoute ( ) )
cacheCtx , writeCache := ctx . CacheContext ( )
if err := handler ( cacheCtx , pr . PubProposal ) ; err != nil {
return err
}
// write state to the underlying multi-store
writeCache ( )
return nil
}
2020-04-24 22:15:51 +00:00
// CloseExpiredProposals removes proposals (and associated votes) that have past their deadline.
func ( k Keeper ) CloseExpiredProposals ( ctx sdk . Context ) {
k . IterateProposals ( ctx , func ( proposal types . Proposal ) bool {
if proposal . HasExpiredBy ( ctx . BlockTime ( ) ) {
k . DeleteProposalAndVotes ( ctx , proposal . ID )
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
types . EventTypeProposalClose ,
sdk . NewAttribute ( types . AttributeKeyCommitteeID , fmt . Sprintf ( "%d" , proposal . CommitteeID ) ) ,
sdk . NewAttribute ( types . AttributeKeyProposalID , fmt . Sprintf ( "%d" , proposal . ID ) ) ,
sdk . NewAttribute ( types . AttributeKeyProposalCloseStatus , types . AttributeValueProposalTimeout ) ,
) ,
)
}
return false
} )
}
2020-03-26 20:17:49 +00:00
// ValidatePubProposal checks if a pubproposal is valid.
2020-03-11 19:52:25 +00:00
func ( k Keeper ) ValidatePubProposal ( ctx sdk . Context , pubProposal types . PubProposal ) sdk . Error {
if pubProposal == nil {
2020-03-29 19:43:25 +00:00
return types . ErrInvalidPubProposal ( k . codespace , "pub proposal cannot be nil" )
2020-03-11 19:52:25 +00:00
}
if err := pubProposal . ValidateBasic ( ) ; err != nil {
return err
}
if ! k . router . HasRoute ( pubProposal . ProposalRoute ( ) ) {
2020-03-29 19:43:25 +00:00
return types . ErrNoProposalHandlerExists ( k . codespace , pubProposal )
2020-03-11 19:52:25 +00:00
}
2020-03-26 20:17:49 +00:00
// Run the proposal's changes through the associated handler using a cached version of state to ensure changes are not permanent.
2020-03-11 19:52:25 +00:00
cacheCtx , _ := ctx . CacheContext ( )
handler := k . router . GetRoute ( pubProposal . ProposalRoute ( ) )
if err := handler ( cacheCtx , pubProposal ) ; err != nil {
return err
}
return nil
}
2020-03-21 18:06:58 +00:00
2020-03-26 20:17:49 +00:00
// DeleteProposalAndVotes removes a proposal and its associated votes.
2020-03-21 18:06:58 +00:00
func ( k Keeper ) DeleteProposalAndVotes ( ctx sdk . Context , proposalID uint64 ) {
var votes [ ] types . Vote
k . IterateVotes ( ctx , proposalID , func ( vote types . Vote ) bool {
votes = append ( votes , vote )
return false
} )
k . DeleteProposal ( ctx , proposalID )
for _ , v := range votes {
k . DeleteVote ( ctx , v . ProposalID , v . Voter )
}
}