github.com/Finschia/finschia-sdk@v0.48.1/x/foundation/keeper/internal/proposal.go (about)

     1  package internal
     2  
     3  import (
     4  	"time"
     5  
     6  	sdk "github.com/Finschia/finschia-sdk/types"
     7  	sdkerrors "github.com/Finschia/finschia-sdk/types/errors"
     8  	"github.com/Finschia/finschia-sdk/x/foundation"
     9  )
    10  
    11  func (k Keeper) newProposalID(ctx sdk.Context) uint64 {
    12  	id := k.getPreviousProposalID(ctx) + 1
    13  	k.setPreviousProposalID(ctx, id)
    14  
    15  	return id
    16  }
    17  
    18  func (k Keeper) getPreviousProposalID(ctx sdk.Context) uint64 {
    19  	store := ctx.KVStore(k.storeKey)
    20  	bz := store.Get(previousProposalIDKey)
    21  	if len(bz) == 0 {
    22  		panic("previous proposal ID hasn't been set")
    23  	}
    24  	return Uint64FromBytes(bz)
    25  }
    26  
    27  func (k Keeper) setPreviousProposalID(ctx sdk.Context, id uint64) {
    28  	store := ctx.KVStore(k.storeKey)
    29  	store.Set(previousProposalIDKey, Uint64ToBytes(id))
    30  }
    31  
    32  func (k Keeper) SubmitProposal(ctx sdk.Context, proposers []string, metadata string, msgs []sdk.Msg) (*uint64, error) {
    33  	if err := validateMetadata(metadata, k.config); err != nil {
    34  		return nil, err
    35  	}
    36  
    37  	foundationInfo := k.GetFoundationInfo(ctx)
    38  	authority := sdk.MustAccAddressFromBech32(k.GetAuthority())
    39  	if err := ensureMsgAuthz(msgs, authority); err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	// Prevent proposal that can not succeed.
    44  	policy := foundationInfo.GetDecisionPolicy()
    45  	if err := policy.Validate(foundationInfo, k.config); err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	id := k.newProposalID(ctx)
    50  	proposal := foundation.Proposal{
    51  		Id:                id,
    52  		Metadata:          metadata,
    53  		Proposers:         proposers,
    54  		SubmitTime:        ctx.BlockTime(),
    55  		FoundationVersion: foundationInfo.Version,
    56  		Status:            foundation.PROPOSAL_STATUS_SUBMITTED,
    57  		ExecutorResult:    foundation.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
    58  		VotingPeriodEnd:   ctx.BlockTime().Add(policy.GetVotingPeriod()),
    59  		FinalTallyResult:  foundation.DefaultTallyResult(),
    60  	}
    61  	if err := proposal.SetMsgs(msgs); err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	k.setProposal(ctx, proposal)
    66  	k.addProposalToVPEndQueue(ctx, proposal)
    67  
    68  	return &id, nil
    69  }
    70  
    71  func (k Keeper) WithdrawProposal(ctx sdk.Context, proposalID uint64) error {
    72  	proposal, err := k.GetProposal(ctx, proposalID)
    73  	if err != nil {
    74  		return err
    75  	}
    76  
    77  	// Ensure the proposal can be withdrawn.
    78  	if proposal.Status != foundation.PROPOSAL_STATUS_SUBMITTED {
    79  		return sdkerrors.ErrInvalidRequest.Wrapf("cannot withdraw a proposal with the status of %s", proposal.Status)
    80  	}
    81  
    82  	proposal.Status = foundation.PROPOSAL_STATUS_WITHDRAWN
    83  	k.setProposal(ctx, *proposal)
    84  
    85  	return nil
    86  }
    87  
    88  // pruneProposal deletes a proposal from state.
    89  func (k Keeper) pruneProposal(ctx sdk.Context, proposal foundation.Proposal) {
    90  	k.pruneVotes(ctx, proposal.Id)
    91  	k.removeProposalFromVPEndQueue(ctx, proposal)
    92  	k.deleteProposal(ctx, proposal.Id)
    93  }
    94  
    95  // PruneExpiredProposals prunes all proposals which are expired,
    96  // i.e. whose `submit_time + voting_period + max_execution_period` is smaller than (or equal to) now.
    97  func (k Keeper) PruneExpiredProposals(ctx sdk.Context) {
    98  	votingPeriodEnd := ctx.BlockTime().Add(-k.config.MaxExecutionPeriod).Add(time.Nanosecond)
    99  
   100  	var proposals []foundation.Proposal
   101  	k.iterateProposalsByVPEnd(ctx, votingPeriodEnd, func(proposal foundation.Proposal) (stop bool) {
   102  		proposals = append(proposals, proposal)
   103  		return false
   104  	})
   105  
   106  	for _, proposal := range proposals {
   107  		k.pruneProposal(ctx, proposal)
   108  	}
   109  }
   110  
   111  // abortOldProposals aborts all proposals which have lower version than the current foundation's
   112  func (k Keeper) abortOldProposals(ctx sdk.Context) {
   113  	latestVersion := k.GetFoundationInfo(ctx).Version
   114  
   115  	k.iterateProposals(ctx, func(proposal foundation.Proposal) (stop bool) {
   116  		if proposal.FoundationVersion == latestVersion {
   117  			return true
   118  		}
   119  
   120  		if proposal.Status == foundation.PROPOSAL_STATUS_SUBMITTED {
   121  			k.pruneVotes(ctx, proposal.Id)
   122  
   123  			proposal.Status = foundation.PROPOSAL_STATUS_ABORTED
   124  			k.setProposal(ctx, proposal)
   125  		}
   126  
   127  		return false
   128  	})
   129  }
   130  
   131  func (k Keeper) GetProposals(ctx sdk.Context) []foundation.Proposal {
   132  	var proposals []foundation.Proposal
   133  	k.iterateProposals(ctx, func(proposal foundation.Proposal) (stop bool) {
   134  		proposals = append(proposals, proposal)
   135  		return false
   136  	})
   137  
   138  	return proposals
   139  }
   140  
   141  func (k Keeper) iterateProposals(ctx sdk.Context, fn func(proposal foundation.Proposal) (stop bool)) {
   142  	store := ctx.KVStore(k.storeKey)
   143  	prefix := proposalKeyPrefix
   144  	iterator := sdk.KVStorePrefixIterator(store, prefix)
   145  	defer iterator.Close()
   146  
   147  	for ; iterator.Valid(); iterator.Next() {
   148  		var proposal foundation.Proposal
   149  		k.cdc.MustUnmarshal(iterator.Value(), &proposal)
   150  		if stop := fn(proposal); stop {
   151  			break
   152  		}
   153  	}
   154  }
   155  
   156  func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, endTime time.Time, fn func(proposal foundation.Proposal) (stop bool)) {
   157  	store := ctx.KVStore(k.storeKey)
   158  	iter := store.Iterator(proposalByVPEndKeyPrefix, sdk.PrefixEndBytes(append(proposalByVPEndKeyPrefix, sdk.FormatTimeBytes(endTime)...)))
   159  	defer iter.Close()
   160  
   161  	for ; iter.Valid(); iter.Next() {
   162  		_, id := splitProposalByVPEndKey(iter.Key())
   163  
   164  		proposal, err := k.GetProposal(ctx, id)
   165  		if err != nil {
   166  			panic(err)
   167  		}
   168  
   169  		if fn(*proposal) {
   170  			break
   171  		}
   172  	}
   173  }
   174  
   175  func (k Keeper) UpdateTallyOfVPEndProposals(ctx sdk.Context) {
   176  	var proposals []foundation.Proposal
   177  	k.iterateProposalsByVPEnd(ctx, ctx.BlockTime(), func(proposal foundation.Proposal) (stop bool) {
   178  		proposals = append(proposals, proposal)
   179  		return false
   180  	})
   181  
   182  	for _, proposal := range proposals {
   183  		proposal := proposal
   184  
   185  		if proposal.Status == foundation.PROPOSAL_STATUS_ABORTED || proposal.Status == foundation.PROPOSAL_STATUS_WITHDRAWN {
   186  			k.pruneProposal(ctx, proposal)
   187  			continue
   188  		}
   189  
   190  		if err := k.doTallyAndUpdate(ctx, &proposal); err != nil {
   191  			panic(err)
   192  		}
   193  		k.setProposal(ctx, proposal)
   194  	}
   195  }
   196  
   197  func (k Keeper) GetProposal(ctx sdk.Context, id uint64) (*foundation.Proposal, error) {
   198  	store := ctx.KVStore(k.storeKey)
   199  	key := proposalKey(id)
   200  	bz := store.Get(key)
   201  	if len(bz) == 0 {
   202  		return nil, sdkerrors.ErrNotFound.Wrapf("No proposal for id: %d", id)
   203  	}
   204  
   205  	var proposal foundation.Proposal
   206  	k.cdc.MustUnmarshal(bz, &proposal)
   207  
   208  	return &proposal, nil
   209  }
   210  
   211  func (k Keeper) setProposal(ctx sdk.Context, proposal foundation.Proposal) {
   212  	store := ctx.KVStore(k.storeKey)
   213  	key := proposalKey(proposal.Id)
   214  
   215  	bz := k.cdc.MustMarshal(&proposal)
   216  	store.Set(key, bz)
   217  }
   218  
   219  func (k Keeper) deleteProposal(ctx sdk.Context, proposalID uint64) {
   220  	store := ctx.KVStore(k.storeKey)
   221  	key := proposalKey(proposalID)
   222  	store.Delete(key)
   223  }
   224  
   225  func (k Keeper) addProposalToVPEndQueue(ctx sdk.Context, proposal foundation.Proposal) {
   226  	store := ctx.KVStore(k.storeKey)
   227  	key := proposalByVPEndKey(proposal.VotingPeriodEnd, proposal.Id)
   228  	store.Set(key, []byte{})
   229  }
   230  
   231  func (k Keeper) removeProposalFromVPEndQueue(ctx sdk.Context, proposal foundation.Proposal) {
   232  	store := ctx.KVStore(k.storeKey)
   233  	key := proposalByVPEndKey(proposal.VotingPeriodEnd, proposal.Id)
   234  	store.Delete(key)
   235  }
   236  
   237  func validateActorForProposal(address string, proposal foundation.Proposal) error {
   238  	for _, proposer := range proposal.Proposers {
   239  		if address == proposer {
   240  			return nil
   241  		}
   242  	}
   243  
   244  	return sdkerrors.ErrUnauthorized.Wrapf("not a proposer: %s", address)
   245  }