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"
2020-04-27 18:19:05 +00:00
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
2020-03-11 19:52:25 +00:00
"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-04-27 18:19:05 +00:00
func ( k Keeper ) SubmitProposal ( ctx sdk . Context , proposer sdk . AccAddress , committeeID uint64 , pubProposal types . PubProposal ) ( uint64 , error ) {
2020-03-11 19:52:25 +00:00
// Limit proposals to only be submitted by committee members
com , found := k . GetCommittee ( ctx , committeeID )
if ! found {
2020-04-27 18:19:05 +00:00
return 0 , sdkerrors . Wrapf ( types . ErrUnknownCommittee , "%d" , committeeID )
2020-03-11 19:52:25 +00:00
}
if ! com . HasMember ( proposer ) {
2020-04-27 18:19:05 +00:00
return 0 , sdkerrors . Wrap ( sdkerrors . ErrUnauthorized , "proposer not member of committee" )
2020-03-11 19:52:25 +00:00
}
// Check committee has permissions to enact proposal.
2020-05-15 19:25:49 +00:00
if ! com . HasPermissionsFor ( ctx , k . cdc , k . ParamKeeper , pubProposal ) {
2020-04-27 18:19:05 +00:00
return 0 , sdkerrors . Wrap ( sdkerrors . 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-04-24 23:22:56 +00:00
deadline := ctx . BlockTime ( ) . Add ( com . ProposalDuration )
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-04-27 18:19:05 +00:00
func ( k Keeper ) AddVote ( ctx sdk . Context , proposalID uint64 , voter sdk . AccAddress ) error {
2020-03-11 19:52:25 +00:00
// Validate
pr , found := k . GetProposal ( ctx , proposalID )
if ! found {
2020-04-27 18:19:05 +00:00
return sdkerrors . Wrapf ( types . ErrUnknownProposal , "%d" , proposalID )
2020-03-11 19:52:25 +00:00
}
2020-03-12 17:05:40 +00:00
if pr . HasExpiredBy ( ctx . BlockTime ( ) ) {
2020-04-27 18:19:05 +00:00
return sdkerrors . Wrapf ( types . ErrProposalExpired , "%s ≥ %s" , 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-04-27 18:19:05 +00:00
return sdkerrors . Wrapf ( types . ErrUnknownCommittee , "%d" , pr . CommitteeID )
2020-03-11 19:52:25 +00:00
}
if ! com . HasMember ( voter ) {
2020-04-27 18:19:05 +00:00
return sdkerrors . Wrap ( sdkerrors . ErrUnauthorized , "voter must be a member of committee" )
2020-03-11 19:52:25 +00:00
}
// Store vote, overwriting any prior vote
2020-05-15 19:25:49 +00:00
k . SetVote ( ctx , types . NewVote ( proposalID , voter ) )
2020-03-11 19:52:25 +00:00
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-05-07 20:58:39 +00:00
sdk . NewAttribute ( types . AttributeKeyVoter , voter . String ( ) ) ,
2020-03-27 20:28:51 +00:00
) ,
)
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.
2020-04-27 18:19:05 +00:00
func ( k Keeper ) GetProposalResult ( ctx sdk . Context , proposalID uint64 ) ( bool , error ) {
2020-03-11 19:52:25 +00:00
pr , found := k . GetProposal ( ctx , proposalID )
if ! found {
2020-04-27 18:19:05 +00:00
return false , sdkerrors . Wrapf ( types . ErrUnknownProposal , "%d" , proposalID )
2020-03-11 19:52:25 +00:00
}
com , found := k . GetCommittee ( ctx , pr . CommitteeID )
if ! found {
2020-04-27 18:19:05 +00:00
return false , sdkerrors . Wrapf ( types . ErrUnknownCommittee , "%d" , 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-04-27 14:04:47 +00:00
votes := k . GetVotesByProposal ( ctx , proposalID )
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.
2020-05-15 19:25:49 +00:00
func ( k Keeper ) EnactProposal ( ctx sdk . Context , proposal types . Proposal ) error {
// Check committee still has permissions for the proposal
// Since the proposal was submitted params could have changed, invalidating the permission of the committee.
com , found := k . GetCommittee ( ctx , proposal . CommitteeID )
2020-03-26 20:17:49 +00:00
if ! found {
2020-05-15 19:25:49 +00:00
return sdkerrors . Wrapf ( types . ErrUnknownCommittee , "%d" , proposal . CommitteeID )
}
if ! com . HasPermissionsFor ( ctx , k . cdc , k . ParamKeeper , proposal . PubProposal ) {
return sdkerrors . Wrap ( sdkerrors . ErrUnauthorized , "committee does not have permissions to enact proposal" )
2020-03-26 20:17:49 +00:00
}
2020-05-15 19:25:49 +00:00
if err := k . ValidatePubProposal ( ctx , proposal . PubProposal ) ; err != nil {
2020-03-26 20:17:49 +00:00
return err
}
2020-05-15 19:25:49 +00:00
// enact the proposal
handler := k . router . GetRoute ( proposal . ProposalRoute ( ) )
if err := handler ( ctx , proposal . PubProposal ) ; err != nil {
2020-04-25 16:39:59 +00:00
// the handler should not error as it was checked in ValidatePubProposal
panic ( fmt . Sprintf ( "unexpected handler error: %s" , err ) )
}
2020-03-26 20:17:49 +00:00
return nil
}
2020-05-15 19:25:49 +00:00
// EnactPassedProposals puts in place the changes proposed in any proposal that has enough votes
func ( k Keeper ) EnactPassedProposals ( ctx sdk . Context ) {
k . IterateProposals ( ctx , func ( proposal types . Proposal ) bool {
passes , err := k . GetProposalResult ( ctx , proposal . ID )
if err != nil {
panic ( err )
}
2020-06-17 09:09:44 +00:00
2020-05-15 19:25:49 +00:00
if ! passes {
2020-06-17 09:09:44 +00:00
// continue to next proposal
2020-05-15 19:25:49 +00:00
return false
}
err = k . EnactProposal ( ctx , proposal )
outcome := types . AttributeValueProposalPassed
if err != nil {
outcome = types . AttributeValueProposalFailed
}
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 , outcome ) ,
) ,
)
return false
} )
}
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 {
2020-04-27 14:37:25 +00:00
if ! proposal . HasExpiredBy ( ctx . BlockTime ( ) ) {
return false
2020-04-24 22:15:51 +00:00
}
2020-04-27 14:37:25 +00:00
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 ) ,
) ,
)
2020-04-24 22:15:51 +00:00
return false
} )
}
2020-03-26 20:17:49 +00:00
// ValidatePubProposal checks if a pubproposal is valid.
2020-04-27 18:19:05 +00:00
func ( k Keeper ) ValidatePubProposal ( ctx sdk . Context , pubProposal types . PubProposal ) ( returnErr error ) {
2020-03-11 19:52:25 +00:00
if pubProposal == nil {
2020-04-27 18:19:05 +00:00
return sdkerrors . Wrap ( types . ErrInvalidPubProposal , "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-04-27 18:19:05 +00:00
return sdkerrors . Wrapf ( types . ErrNoProposalHandlerExists , "%T" , 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 ( ) )
2020-04-25 16:39:59 +00:00
// Handle an edge case where a param change proposal causes the proposal handler to panic.
// A param change proposal with a registered subspace value but unregistered key value will cause a panic in the param change proposal handler.
// This defer will catch panics and return a normal error: `recover()` gets the panic value, then the enclosing function's return value is swapped for an error.
// reference: https://stackoverflow.com/questions/33167282/how-to-return-a-value-in-a-go-function-that-panics?noredirect=1&lq=1
defer func ( ) {
if r := recover ( ) ; r != nil {
2020-04-27 18:19:05 +00:00
returnErr = sdkerrors . Wrapf ( types . ErrInvalidPubProposal , "proposal handler panicked: %s" , r )
2020-04-25 16:39:59 +00:00
}
} ( )
2020-03-11 19:52:25 +00:00
if err := handler ( cacheCtx , pubProposal ) ; err != nil {
return err
}
return nil
}