6c34650a0d
v0.5 Bearers Runtime — 7 runtime REQs (REQ-033..039) shipped as feature. 8 modules promoted to runtime (MsgServer + simtest). cosmos-sdk v0.50.8 + ibc-go v8.2.1 added (G-006 controlled exception). G-003 + locked-const firewalls intact. 8 keeper packages ≥80% coverage. 5 GRILL decisions ratified; 8 binding fixes landed; 5 P1+ flagged for v0.6+. ---ci--- project: oy phase: 8 milestone: v0.5 status: complete requirements: covered: [REQ-033, REQ-034, REQ-035, REQ-036, REQ-037, REQ-038, REQ-039] partial: [] ---/ci---
245 lines
8.0 KiB
Go
245 lines
8.0 KiB
Go
package keeper
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
storetypes "cosmossdk.io/store/types"
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
"github.com/oy/openyield/x/council/types"
|
|
)
|
|
|
|
// keeper.go holds the store-backed Keeper for the council module's
|
|
// Proposal-lifecycle runtime (P7-02-01, REQ-039, D-060).
|
|
//
|
|
// The Keeper wraps an sdk.KVStore via a storeKey. It holds the Proposal
|
|
// records (by proposal-id) and the Vote records (by vote-id). The v0.2
|
|
// skeleton had NO keeper (only types/); v0.5 (P7) promotes the council
|
|
// module to runtime by adding the store-backed Keeper + MsgServer.
|
|
//
|
|
// The Keeper also holds the three expected-keeper shims (WatcherKeeper
|
|
// for Veto authz; StandKeeper + GuildKeeper for proposal-target
|
|
// validation). The shims are interfaces (G-003 — no struct import of
|
|
// x/watcher/types, x/stand/types, or x/guild/types); the concrete
|
|
// keepers satisfy them structurally.
|
|
//
|
|
// State-machine ordering (vision §7, enforced in every handler):
|
|
// ValidateBasic → keeper authz → state mutation → ctx.EventManager().EmitEvent
|
|
|
|
// Keeper is the store-backed council Proposal-lifecycle keeper.
|
|
type Keeper struct {
|
|
cdc codec.Codec
|
|
storeKey storetypes.StoreKey
|
|
watcherKeeper types.WatcherKeeper
|
|
standKeeper types.StandKeeper
|
|
guildKeeper types.GuildKeeper
|
|
params types.Params
|
|
}
|
|
|
|
// NewKeeper constructs a new store-backed council Proposal-lifecycle
|
|
// Keeper. The WatcherKeeper, StandKeeper, and GuildKeeper expected-keeper
|
|
// shims are injected (nil-able for partial tests; the handlers guard nil
|
|
// shims and skip the corresponding authz/validity check, still mutating
|
|
// state — the simtest wiring documents this). The Params default is set
|
|
// here; the simtest can override via SetParams.
|
|
func NewKeeper(cdc codec.Codec, storeKey storetypes.StoreKey, wk types.WatcherKeeper, sk types.StandKeeper, gk types.GuildKeeper) Keeper {
|
|
return Keeper{
|
|
cdc: cdc,
|
|
storeKey: storeKey,
|
|
watcherKeeper: wk,
|
|
standKeeper: sk,
|
|
guildKeeper: gk,
|
|
params: types.DefaultParams(),
|
|
}
|
|
}
|
|
|
|
// SetWatcherKeeper sets the WatcherKeeper expected-keeper shim (for
|
|
// post-construction wiring, e.g., app wiring or test setup).
|
|
func (k *Keeper) SetWatcherKeeper(wk types.WatcherKeeper) { k.watcherKeeper = wk }
|
|
|
|
// SetStandKeeper sets the StandKeeper expected-keeper shim (for
|
|
// post-construction wiring).
|
|
func (k *Keeper) SetStandKeeper(sk types.StandKeeper) { k.standKeeper = sk }
|
|
|
|
// SetGuildKeeper sets the GuildKeeper expected-keeper shim (for
|
|
// post-construction wiring).
|
|
func (k *Keeper) SetGuildKeeper(gk types.GuildKeeper) { k.guildKeeper = gk }
|
|
|
|
// SetParams sets the council Params (the simtest overrides
|
|
// WatcherVetoQuorum for the quorum-Veto-fails test).
|
|
func (k *Keeper) SetParams(p types.Params) { k.params = p }
|
|
|
|
// GetParams returns the council Params.
|
|
func (k Keeper) GetParams() types.Params { return k.params }
|
|
|
|
// --- Proposal store --------------------------------------------------------
|
|
|
|
var proposalKeyPrefix = []byte("proposal/")
|
|
|
|
func proposalKey(proposalID string) []byte {
|
|
return append(proposalKeyPrefix, []byte(proposalID)...)
|
|
}
|
|
|
|
// GetProposal loads a Proposal by proposal-id. Returns the Proposal and
|
|
// true if found, or zero value + false if not.
|
|
func (k Keeper) GetProposal(ctx sdk.Context, proposalID string) (types.Proposal, bool) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
bz := store.Get(proposalKey(proposalID))
|
|
if bz == nil {
|
|
return types.Proposal{}, false
|
|
}
|
|
var p types.Proposal
|
|
if err := json.Unmarshal(bz, &p); err != nil {
|
|
return types.Proposal{}, false
|
|
}
|
|
return p, true
|
|
}
|
|
|
|
// SetProposal persists a Proposal by proposal-id.
|
|
func (k Keeper) SetProposal(ctx sdk.Context, p types.Proposal) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
bz, err := json.Marshal(p)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("council: marshal proposal %q: %v", p.ProposalID, err))
|
|
}
|
|
store.Set(proposalKey(p.ProposalID), bz)
|
|
}
|
|
|
|
// AllProposals returns all persisted Proposal records (iteration helper).
|
|
func (k Keeper) AllProposals(ctx sdk.Context) []types.Proposal {
|
|
store := ctx.KVStore(k.storeKey)
|
|
iterator := store.Iterator(proposalKeyPrefix, prefixEnd(proposalKeyPrefix))
|
|
defer iterator.Close()
|
|
out := []types.Proposal{}
|
|
for ; iterator.Valid(); iterator.Next() {
|
|
var p types.Proposal
|
|
if err := json.Unmarshal(iterator.Value(), &p); err == nil {
|
|
out = append(out, p)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// --- Vote store ------------------------------------------------------------
|
|
|
|
var voteKeyPrefix = []byte("vote/")
|
|
|
|
func voteKey(voteID string) []byte {
|
|
return append(voteKeyPrefix, []byte(voteID)...)
|
|
}
|
|
|
|
// GetVote loads a Vote by vote-id. Returns the Vote and true if found,
|
|
// or zero value + false if not.
|
|
func (k Keeper) GetVote(ctx sdk.Context, voteID string) (types.Vote, bool) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
bz := store.Get(voteKey(voteID))
|
|
if bz == nil {
|
|
return types.Vote{}, false
|
|
}
|
|
var v types.Vote
|
|
if err := json.Unmarshal(bz, &v); err != nil {
|
|
return types.Vote{}, false
|
|
}
|
|
return v, true
|
|
}
|
|
|
|
// SetVote persists a Vote by vote-id.
|
|
func (k Keeper) SetVote(ctx sdk.Context, v types.Vote) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
bz, err := json.Marshal(v)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("council: marshal vote %q: %v", v.VoteID, err))
|
|
}
|
|
store.Set(voteKey(v.VoteID), bz)
|
|
}
|
|
|
|
// AllVotes returns all persisted Vote records (iteration helper).
|
|
func (k Keeper) AllVotes(ctx sdk.Context) []types.Vote {
|
|
store := ctx.KVStore(k.storeKey)
|
|
iterator := store.Iterator(voteKeyPrefix, prefixEnd(voteKeyPrefix))
|
|
defer iterator.Close()
|
|
out := []types.Vote{}
|
|
for ; iterator.Valid(); iterator.Next() {
|
|
var v types.Vote
|
|
if err := json.Unmarshal(iterator.Value(), &v); err == nil {
|
|
out = append(out, v)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// VotesForProposal returns all persisted Vote records for a given
|
|
// proposal-id (iteration + filter helper; used by the TallyProposal
|
|
// handler to compute the tally).
|
|
func (k Keeper) VotesForProposal(ctx sdk.Context, proposalID string) []types.Vote {
|
|
all := k.AllVotes(ctx)
|
|
out := []types.Vote{}
|
|
for _, v := range all {
|
|
if v.ProposalID == proposalID {
|
|
out = append(out, v)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// --- Council store (for SubmitProposal target validation) ------------------
|
|
|
|
var councilKeyPrefix = []byte("council/")
|
|
|
|
func councilKey(councilID string) []byte {
|
|
return append(councilKeyPrefix, []byte(councilID)...)
|
|
}
|
|
|
|
// GetCouncil loads a Council by council-id from the runtime store.
|
|
// Returns the Council and true if found, or zero value + false if not.
|
|
// The Council store is the runtime home for the v0.2 skeleton Council
|
|
// struct (the v0.2 skeleton had Council only in genesis; v0.5 promotes
|
|
// it to the runtime store so the SubmitProposal handler can validate the
|
|
// proposal-target against the Council's stand-id-ref / guild-id-ref).
|
|
func (k Keeper) GetCouncil(ctx sdk.Context, councilID string) (types.Council, bool) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
bz := store.Get(councilKey(councilID))
|
|
if bz == nil {
|
|
return types.Council{}, false
|
|
}
|
|
var c types.Council
|
|
if err := json.Unmarshal(bz, &c); err != nil {
|
|
return types.Council{}, false
|
|
}
|
|
return c, true
|
|
}
|
|
|
|
// SetCouncil persists a Council by council-id (runtime store home for the
|
|
// v0.2 skeleton Council struct; the simtest seeds a Council for the
|
|
// SubmitProposal target validation).
|
|
func (k Keeper) SetCouncil(ctx sdk.Context, c types.Council) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
bz, err := json.Marshal(c)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("council: marshal council %q: %v", c.CouncilID, err))
|
|
}
|
|
store.Set(councilKey(c.CouncilID), bz)
|
|
}
|
|
|
|
// prefixEnd returns the key that sorts immediately after all keys sharing
|
|
// the given prefix (the standard prefix-iteration end key: increment the
|
|
// last byte, drop overflow). Used for store.Iterator(start, prefixEnd(start))
|
|
// prefix scans.
|
|
func prefixEnd(prefix []byte) []byte {
|
|
if len(prefix) == 0 {
|
|
return nil
|
|
}
|
|
end := make([]byte, len(prefix))
|
|
copy(end, prefix)
|
|
for i := len(end) - 1; i >= 0; i-- {
|
|
end[i]++
|
|
if end[i] != 0 {
|
|
return end
|
|
}
|
|
}
|
|
// All bytes were 0xFF; return nil (iterate to end of store).
|
|
return nil
|
|
}
|