9abda8d01e
P5 (final execution phase) of v0.7 delivers REQ-056, REQ-059, REQ-064, REQ-066: x/cover — Bill of Rights ceremony (REQ-056) + Pier Selection (REQ-066): MsgCounselReviewBillOfRights (bonded Counsel, Staked=true gate); MsgSelectPier (Guild Council + GuildKeeper shim + PierSelectionIndex); MsgRevokePierSelection (supermajority + Counsel witness); PierSelectionIndex + PierSelectionRecord structs; bill_review/pier_selection/pier_index stores. x/guild — Secession cooling (REQ-064) + Stand->Pier (REQ-059, D-074): MsgInitiateSecession (SecessionStartedAt + lien audit); MsgCompleteSecession (21d Cover-active / 14d non-Cover cooling + lien audit + covenant clearance + pro-rata settlement event); MsgEscalateStandToPier (10M Grain-cents D-074 threshold, soft upgrade); MsgAcceptPierInvitation; Guild.SecessionStartedAt + SecededAt + Lien.Cleared additive; LOCAL StandPierEscalationAnnualPass VolumeCents const (G-003 local-const mirror of x/stand canonical). x/stand — StandPierEscalationAnnualPassVolumeCents=10000000 canonical const. Coverage: cover/keeper 94.6%, guild/keeper 94.1%, stand/types 100%. G-006/G-028 intact. go.mod/go.sum diff EMPTY. go vet clean. Lexicon green. ---ci--- project: oy phase: 5 milestone: v0.7 status: execute ---/ci---
742 lines
27 KiB
Go
742 lines
27 KiB
Go
package keeper
|
||
|
||
// keeper.go holds the store-backed Keeper for the cover module's Cover Pool
|
||
// runtime (REQ-046, REQ-047, REQ-049, REQ-050, REQ-055, D-077, D-086,
|
||
// D-088, D-089).
|
||
//
|
||
// The Keeper wraps an sdk.KVStore via a storeKey. It holds:
|
||
// - the CoverPool records (pool-id -> CoverPool);
|
||
// - the CoverCall records (call-id -> CoverCall; the FileCoverCall
|
||
// handler persists here; P4 adds the Voucher adjudication).
|
||
//
|
||
// The Cover-Fee routing (RouteCoverFee) does NOT persist a separate record
|
||
// in P1 — the routing is the event (the reserve balance update is a
|
||
// simtest-grade stub). P2 may add a CoverFeeRouting record; P1 ships the
|
||
// event-only path.
|
||
//
|
||
// The Keeper also holds the FOUR expected-keeper shims (StandingKeeper for
|
||
// the D-077 gate; WatcherKeeper for the launch attestation; BondKeeper for
|
||
// the P4 MAB check; StillKeeper for the below-floor auto-pause). The shims
|
||
// are interfaces (G-003 — no struct import of x/standing/types,
|
||
// x/watcher/types, x/bond/types, x/still/types); the concrete keepers (or
|
||
// simtest stubs) satisfy them structurally.
|
||
//
|
||
// State-machine ordering (vision §7, enforced in every handler):
|
||
// ValidateBasic -> handler authz/gate -> state mutation -> ctx.EventManager().EmitEvent
|
||
|
||
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/cover/types"
|
||
)
|
||
|
||
// Keeper is the store-backed cover Cover-Pool keeper.
|
||
type Keeper struct {
|
||
cdc codec.Codec
|
||
storeKey storetypes.StoreKey
|
||
standingKeeper types.StandingKeeper
|
||
watcherKeeper types.WatcherKeeper
|
||
bondKeeper types.BondKeeper
|
||
stillKeeper types.StillKeeper
|
||
guildKeeper types.GuildKeeper
|
||
// paramsOverride is a simtest-grade Params override (nil = use
|
||
// DefaultParams). A future P2+ will load the Params from the params
|
||
// store; for now the handler uses DefaultParams unless an override is
|
||
// set via SetParamsOverride (the D-086 simtest case (f) uses this to
|
||
// restrict FactoryAllowedPhases to [Phase2, Phase3] only and reject a
|
||
// Phase4 launch).
|
||
paramsOverride *types.Params
|
||
}
|
||
|
||
// NewKeeper constructs a new store-backed cover Keeper. The four expected-
|
||
// keeper shims are injected (all nil-able for partial tests; the handlers
|
||
// guard nil shims and skip the corresponding check, still mutating state —
|
||
// the simtest wiring documents this). The StandingKeeper gates the launch
|
||
// (D-077); the WatcherKeeper attests the launch (REQ-046); the BondKeeper
|
||
// is held for P4 (the P1 handlers do not call it); the StillKeeper records
|
||
// the below-floor auto-pause (D-089(1)). The P5 GuildKeeper verifies a
|
||
// Guild exists on Pier selection (REQ-066; a nil shim skips the check).
|
||
func NewKeeper(cdc codec.Codec, storeKey storetypes.StoreKey, sk types.StandingKeeper, wk types.WatcherKeeper, bk types.BondKeeper, stK types.StillKeeper) Keeper {
|
||
return Keeper{
|
||
cdc: cdc,
|
||
storeKey: storeKey,
|
||
standingKeeper: sk,
|
||
watcherKeeper: wk,
|
||
bondKeeper: bk,
|
||
stillKeeper: stK,
|
||
}
|
||
}
|
||
|
||
// SetStandingKeeper sets the StandingKeeper expected-keeper shim (for
|
||
// post-construction wiring, e.g., app wiring or test setup).
|
||
func (k *Keeper) SetStandingKeeper(sk types.StandingKeeper) { k.standingKeeper = sk }
|
||
|
||
// SetWatcherKeeper sets the WatcherKeeper expected-keeper shim.
|
||
func (k *Keeper) SetWatcherKeeper(wk types.WatcherKeeper) { k.watcherKeeper = wk }
|
||
|
||
// SetBondKeeper sets the BondKeeper expected-keeper shim.
|
||
func (k *Keeper) SetBondKeeper(bk types.BondKeeper) { k.bondKeeper = bk }
|
||
|
||
// SetStillKeeper sets the StillKeeper expected-keeper shim.
|
||
func (k *Keeper) SetStillKeeper(stK types.StillKeeper) { k.stillKeeper = stK }
|
||
|
||
// SetGuildKeeper sets the GuildKeeper expected-keeper shim (P5 — REQ-066
|
||
// Pier Selection uses this to verify the selecting Guild exists; a nil
|
||
// shim skips the existence check, simtest wiring).
|
||
func (k *Keeper) SetGuildKeeper(gk types.GuildKeeper) { k.guildKeeper = gk }
|
||
|
||
// SetParamsOverride sets a simtest-grade Params override (nil = use
|
||
// DefaultParams). The D-086 simtest case (f) uses this to restrict
|
||
// FactoryAllowedPhases to [Phase2, Phase3] only and reject a Phase4
|
||
// launch. A future P2+ will replace this with a params-store load.
|
||
func (k *Keeper) SetParamsOverride(p types.Params) { k.paramsOverride = &p }
|
||
|
||
// Params returns the effective Params (the override if set, else
|
||
// DefaultParams). The handler calls this to get FactoryAllowedPhases +
|
||
// PoolStandingGate.
|
||
func (k Keeper) Params() types.Params {
|
||
if k.paramsOverride != nil {
|
||
return *k.paramsOverride
|
||
}
|
||
return types.DefaultParams()
|
||
}
|
||
|
||
// StoreKey returns the keeper's store key (exported for simtest access to
|
||
// the underlying KVStore, e.g. to inject corrupt bytes for marshal-error
|
||
// coverage). Mirrors the x/hub simtest pattern (the simtest reaches the
|
||
// store via ctx.KVStore(k.StoreKey())).
|
||
func (k Keeper) StoreKey() storetypes.StoreKey { return k.storeKey }
|
||
|
||
// --- CoverPool store ----------------------------------------------------------
|
||
|
||
var poolKeyPrefix = []byte("pool/")
|
||
|
||
func poolKey(poolID string) []byte {
|
||
return append(poolKeyPrefix, []byte(poolID)...)
|
||
}
|
||
|
||
// GetCoverPool loads a CoverPool by pool-id. Returns the pool and true if
|
||
// found, or zero value + false if not.
|
||
func (k Keeper) GetCoverPool(ctx sdk.Context, poolID string) (types.CoverPool, bool) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz := store.Get(poolKey(poolID))
|
||
if bz == nil {
|
||
return types.CoverPool{}, false
|
||
}
|
||
var p types.CoverPool
|
||
if err := json.Unmarshal(bz, &p); err != nil {
|
||
return types.CoverPool{}, false
|
||
}
|
||
return p, true
|
||
}
|
||
|
||
// SetCoverPool persists a CoverPool by pool-id.
|
||
func (k Keeper) SetCoverPool(ctx sdk.Context, p types.CoverPool) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz, err := json.Marshal(p)
|
||
if err != nil {
|
||
panic(fmt.Sprintf("cover: marshal pool %q: %v", p.PoolID, err))
|
||
}
|
||
store.Set(poolKey(p.PoolID), bz)
|
||
}
|
||
|
||
// AllCoverPools returns all persisted CoverPool records (iteration helper,
|
||
// unordered).
|
||
func (k Keeper) AllCoverPools(ctx sdk.Context) []types.CoverPool {
|
||
store := ctx.KVStore(k.storeKey)
|
||
iterator := store.Iterator(poolKeyPrefix, prefixEnd(poolKeyPrefix))
|
||
defer iterator.Close()
|
||
out := []types.CoverPool{}
|
||
for ; iterator.Valid(); iterator.Next() {
|
||
var p types.CoverPool
|
||
if err := json.Unmarshal(iterator.Value(), &p); err == nil {
|
||
out = append(out, p)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// --- CoverCall store ----------------------------------------------------------
|
||
|
||
var callKeyPrefix = []byte("call/")
|
||
|
||
func callKey(callID string) []byte {
|
||
return append(callKeyPrefix, []byte(callID)...)
|
||
}
|
||
|
||
// GetCoverCall loads a CoverCall by call-id. Returns the call and true if
|
||
// found, or zero value + false if not.
|
||
func (k Keeper) GetCoverCall(ctx sdk.Context, callID string) (types.CoverCall, bool) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz := store.Get(callKey(callID))
|
||
if bz == nil {
|
||
return types.CoverCall{}, false
|
||
}
|
||
var c types.CoverCall
|
||
if err := json.Unmarshal(bz, &c); err != nil {
|
||
return types.CoverCall{}, false
|
||
}
|
||
return c, true
|
||
}
|
||
|
||
// SetCoverCall persists a CoverCall by call-id.
|
||
func (k Keeper) SetCoverCall(ctx sdk.Context, c types.CoverCall) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz, err := json.Marshal(c)
|
||
if err != nil {
|
||
panic(fmt.Sprintf("cover: marshal call %q: %v", c.CallID, err))
|
||
}
|
||
store.Set(callKey(c.CallID), bz)
|
||
}
|
||
|
||
// AllCoverCalls returns all persisted CoverCall records (iteration helper,
|
||
// unordered).
|
||
func (k Keeper) AllCoverCalls(ctx sdk.Context) []types.CoverCall {
|
||
store := ctx.KVStore(k.storeKey)
|
||
iterator := store.Iterator(callKeyPrefix, prefixEnd(callKeyPrefix))
|
||
defer iterator.Close()
|
||
out := []types.CoverCall{}
|
||
for ; iterator.Valid(); iterator.Next() {
|
||
var c types.CoverCall
|
||
if err := json.Unmarshal(iterator.Value(), &c); err == nil {
|
||
out = append(out, c)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// --- P4: CoverClaimsVoucher store (REQ-055, D-090(2)) ------------------------
|
||
//
|
||
// The Voucher store is keyed by voucher-reach-id + pool-id (composite key)
|
||
// -> CoverClaimsVoucher. A Voucher is registered per-Pool; the composite key
|
||
// enforces idempotency (no duplicate Voucher for the same Pool). The
|
||
// GetAvgCallSize helper computes the average Cover Call amount for a Pool
|
||
// from the call/ store (returns 0 if no Calls — the D-090(2) cold-start
|
||
// case).
|
||
|
||
var voucherKeyPrefix = []byte("voucher/")
|
||
|
||
func voucherKey(voucherReachID, poolID string) []byte {
|
||
return append(append(voucherKeyPrefix, []byte(voucherReachID)...), []byte("/"+poolID)...)
|
||
}
|
||
|
||
// GetCoverClaimsVoucher loads a CoverClaimsVoucher by voucher-reach-id +
|
||
// pool-id. Returns the Voucher and true if found, or zero value + false if
|
||
// not.
|
||
func (k Keeper) GetCoverClaimsVoucher(ctx sdk.Context, voucherReachID, poolID string) (types.CoverClaimsVoucher, bool) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz := store.Get(voucherKey(voucherReachID, poolID))
|
||
if bz == nil {
|
||
return types.CoverClaimsVoucher{}, false
|
||
}
|
||
var v types.CoverClaimsVoucher
|
||
if err := json.Unmarshal(bz, &v); err != nil {
|
||
return types.CoverClaimsVoucher{}, false
|
||
}
|
||
return v, true
|
||
}
|
||
|
||
// SetCoverClaimsVoucher persists a CoverClaimsVoucher by voucher-reach-id +
|
||
// pool-id.
|
||
func (k Keeper) SetCoverClaimsVoucher(ctx sdk.Context, v types.CoverClaimsVoucher) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz, err := json.Marshal(v)
|
||
if err != nil {
|
||
panic(fmt.Sprintf("cover: marshal voucher %q/%q: %v", v.VoucherReachID, v.PoolID, err))
|
||
}
|
||
store.Set(voucherKey(v.VoucherReachID, v.PoolID), bz)
|
||
}
|
||
|
||
// AllCoverClaimsVouchers returns all persisted CoverClaimsVoucher records
|
||
// (iteration helper, unordered).
|
||
func (k Keeper) AllCoverClaimsVouchers(ctx sdk.Context) []types.CoverClaimsVoucher {
|
||
store := ctx.KVStore(k.storeKey)
|
||
iterator := store.Iterator(voucherKeyPrefix, prefixEnd(voucherKeyPrefix))
|
||
defer iterator.Close()
|
||
out := []types.CoverClaimsVoucher{}
|
||
for ; iterator.Valid(); iterator.Next() {
|
||
var v types.CoverClaimsVoucher
|
||
if err := json.Unmarshal(iterator.Value(), &v); err == nil {
|
||
out = append(out, v)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// GetAvgCallSize computes the average Cover Call amount (Grain) for a Pool
|
||
// from the call/ store (REQ-055, D-090(2)). Returns 0 if no Calls have been
|
||
// filed for the Pool — the D-090(2) cold-start case (the Voucher bond falls
|
||
// back to MinimumVoucherBond, NOT zero).
|
||
func (k Keeper) GetAvgCallSize(ctx sdk.Context, poolID string) int64 {
|
||
calls := k.AllCoverCalls(ctx)
|
||
sum := int64(0)
|
||
n := 0
|
||
for _, c := range calls {
|
||
if c.PoolID == poolID {
|
||
sum += c.AmountGrain
|
||
n++
|
||
}
|
||
}
|
||
if n == 0 {
|
||
return 0
|
||
}
|
||
return sum / int64(n)
|
||
}
|
||
|
||
// --- prefixEnd helper ---------------------------------------------------------
|
||
|
||
// 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. Mirrors x/hub/keeper/keeper.go.
|
||
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
|
||
}
|
||
|
||
// --- P2: CoverCharter / PoolCouncil / CoverCallVote / CharterAmendment stores --
|
||
//
|
||
// (REQ-052, REQ-062). Four new stores keyed by ID-string. The
|
||
// CoverCharter store is keyed by CharterID; the PoolCouncil store is keyed
|
||
// by PoolID (one council per pool); the CoverCallVote store is keyed by
|
||
// VoteID; the CharterAmendment store is keyed by AmendmentID. All four
|
||
// use the same JSON-marshal pattern as the P1 CoverPool / CoverCall
|
||
// stores. The Get/Set/All helpers mirror the P1 helpers.
|
||
|
||
var charterKeyPrefix = []byte("charter/")
|
||
|
||
func charterKey(charterID string) []byte {
|
||
return append(charterKeyPrefix, []byte(charterID)...)
|
||
}
|
||
|
||
// GetCoverCharter loads a CoverCharter by charter-id. Returns the charter
|
||
// and true if found, or zero value + false if not.
|
||
func (k Keeper) GetCoverCharter(ctx sdk.Context, charterID string) (types.CoverCharter, bool) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz := store.Get(charterKey(charterID))
|
||
if bz == nil {
|
||
return types.CoverCharter{}, false
|
||
}
|
||
var c types.CoverCharter
|
||
if err := json.Unmarshal(bz, &c); err != nil {
|
||
return types.CoverCharter{}, false
|
||
}
|
||
return c, true
|
||
}
|
||
|
||
// SetCoverCharter persists a CoverCharter by charter-id.
|
||
func (k Keeper) SetCoverCharter(ctx sdk.Context, c types.CoverCharter) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz, err := json.Marshal(c)
|
||
if err != nil {
|
||
panic(fmt.Sprintf("cover: marshal charter %q: %v", c.CharterID, err))
|
||
}
|
||
store.Set(charterKey(c.CharterID), bz)
|
||
}
|
||
|
||
// AllCoverCharters returns all persisted CoverCharter records (iteration
|
||
// helper, unordered).
|
||
func (k Keeper) AllCoverCharters(ctx sdk.Context) []types.CoverCharter {
|
||
store := ctx.KVStore(k.storeKey)
|
||
iterator := store.Iterator(charterKeyPrefix, prefixEnd(charterKeyPrefix))
|
||
defer iterator.Close()
|
||
out := []types.CoverCharter{}
|
||
for ; iterator.Valid(); iterator.Next() {
|
||
var c types.CoverCharter
|
||
if err := json.Unmarshal(iterator.Value(), &c); err == nil {
|
||
out = append(out, c)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
var councilKeyPrefix = []byte("council/")
|
||
|
||
func councilKey(poolID string) []byte {
|
||
return append(councilKeyPrefix, []byte(poolID)...)
|
||
}
|
||
|
||
// GetPoolCouncil loads a PoolCouncil by pool-id. Returns the council and
|
||
// true if found, or zero value + false if not.
|
||
func (k Keeper) GetPoolCouncil(ctx sdk.Context, poolID string) (types.PoolCouncil, bool) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz := store.Get(councilKey(poolID))
|
||
if bz == nil {
|
||
return types.PoolCouncil{}, false
|
||
}
|
||
var c types.PoolCouncil
|
||
if err := json.Unmarshal(bz, &c); err != nil {
|
||
return types.PoolCouncil{}, false
|
||
}
|
||
return c, true
|
||
}
|
||
|
||
// SetPoolCouncil persists a PoolCouncil by pool-id.
|
||
func (k Keeper) SetPoolCouncil(ctx sdk.Context, c types.PoolCouncil) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz, err := json.Marshal(c)
|
||
if err != nil {
|
||
panic(fmt.Sprintf("cover: marshal council for pool %q: %v", c.PoolID, err))
|
||
}
|
||
store.Set(councilKey(c.PoolID), bz)
|
||
}
|
||
|
||
// AllPoolCouncils returns all persisted PoolCouncil records (iteration
|
||
// helper, unordered).
|
||
func (k Keeper) AllPoolCouncils(ctx sdk.Context) []types.PoolCouncil {
|
||
store := ctx.KVStore(k.storeKey)
|
||
iterator := store.Iterator(councilKeyPrefix, prefixEnd(councilKeyPrefix))
|
||
defer iterator.Close()
|
||
out := []types.PoolCouncil{}
|
||
for ; iterator.Valid(); iterator.Next() {
|
||
var c types.PoolCouncil
|
||
if err := json.Unmarshal(iterator.Value(), &c); err == nil {
|
||
out = append(out, c)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
var voteKeyPrefix = []byte("vote/")
|
||
|
||
func voteKey(voteID string) []byte {
|
||
return append(voteKeyPrefix, []byte(voteID)...)
|
||
}
|
||
|
||
// GetCoverCallVote loads a CoverCallVote by vote-id. Returns the vote and
|
||
// true if found, or zero value + false if not.
|
||
func (k Keeper) GetCoverCallVote(ctx sdk.Context, voteID string) (types.CoverCallVote, bool) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz := store.Get(voteKey(voteID))
|
||
if bz == nil {
|
||
return types.CoverCallVote{}, false
|
||
}
|
||
var v types.CoverCallVote
|
||
if err := json.Unmarshal(bz, &v); err != nil {
|
||
return types.CoverCallVote{}, false
|
||
}
|
||
return v, true
|
||
}
|
||
|
||
// SetCoverCallVote persists a CoverCallVote by vote-id.
|
||
func (k Keeper) SetCoverCallVote(ctx sdk.Context, v types.CoverCallVote) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz, err := json.Marshal(v)
|
||
if err != nil {
|
||
panic(fmt.Sprintf("cover: marshal vote %q: %v", v.VoteID, err))
|
||
}
|
||
store.Set(voteKey(v.VoteID), bz)
|
||
}
|
||
|
||
// AllCoverCallVotes returns all persisted CoverCallVote records (iteration
|
||
// helper, unordered).
|
||
func (k Keeper) AllCoverCallVotes(ctx sdk.Context) []types.CoverCallVote {
|
||
store := ctx.KVStore(k.storeKey)
|
||
iterator := store.Iterator(voteKeyPrefix, prefixEnd(voteKeyPrefix))
|
||
defer iterator.Close()
|
||
out := []types.CoverCallVote{}
|
||
for ; iterator.Valid(); iterator.Next() {
|
||
var v types.CoverCallVote
|
||
if err := json.Unmarshal(iterator.Value(), &v); err == nil {
|
||
out = append(out, v)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
var amendmentKeyPrefix = []byte("amendment/")
|
||
|
||
func amendmentKey(amendmentID string) []byte {
|
||
return append(amendmentKeyPrefix, []byte(amendmentID)...)
|
||
}
|
||
|
||
// GetCharterAmendment loads a CharterAmendment by amendment-id. Returns
|
||
// the amendment and true if found, or zero value + false if not.
|
||
func (k Keeper) GetCharterAmendment(ctx sdk.Context, amendmentID string) (types.CharterAmendment, bool) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz := store.Get(amendmentKey(amendmentID))
|
||
if bz == nil {
|
||
return types.CharterAmendment{}, false
|
||
}
|
||
var a types.CharterAmendment
|
||
if err := json.Unmarshal(bz, &a); err != nil {
|
||
return types.CharterAmendment{}, false
|
||
}
|
||
return a, true
|
||
}
|
||
|
||
// SetCharterAmendment persists a CharterAmendment by amendment-id.
|
||
func (k Keeper) SetCharterAmendment(ctx sdk.Context, a types.CharterAmendment) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz, err := json.Marshal(a)
|
||
if err != nil {
|
||
panic(fmt.Sprintf("cover: marshal amendment %q: %v", a.AmendmentID, err))
|
||
}
|
||
store.Set(amendmentKey(a.AmendmentID), bz)
|
||
}
|
||
|
||
// AllCharterAmendments returns all persisted CharterAmendment records
|
||
// (iteration helper, unordered).
|
||
func (k Keeper) AllCharterAmendments(ctx sdk.Context) []types.CharterAmendment {
|
||
store := ctx.KVStore(k.storeKey)
|
||
iterator := store.Iterator(amendmentKeyPrefix, prefixEnd(amendmentKeyPrefix))
|
||
defer iterator.Close()
|
||
out := []types.CharterAmendment{}
|
||
for ; iterator.Valid(); iterator.Next() {
|
||
var a types.CharterAmendment
|
||
if err := json.Unmarshal(iterator.Value(), &a); err == nil {
|
||
out = append(out, a)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// CoolCharterAmendment transitions a Proposed CharterAmendment to Cooled
|
||
// if the 7-day cooling has elapsed (REQ-052). Returns an error if the
|
||
// amendment is not found, not in the Proposed status, or the cooling has
|
||
// not elapsed. The handler (or simtest) calls this after the cooling
|
||
// period; a separate RatifyCharterAmendment transitions to Ratified.
|
||
func (k Keeper) CoolCharterAmendment(ctx sdk.Context, amendmentID string, now int64) (types.CharterAmendment, error) {
|
||
a, ok := k.GetCharterAmendment(ctx, amendmentID)
|
||
if !ok {
|
||
return types.CharterAmendment{}, fmt.Errorf("cover: amendment %q not found", amendmentID)
|
||
}
|
||
if a.Status != types.AmendmentProposed {
|
||
return types.CharterAmendment{}, fmt.Errorf("cover: amendment %q status %q (only Proposed can be Cooled)", amendmentID, a.Status)
|
||
}
|
||
if now-a.ProposedAt < types.CharterAmendmentCoolingSeconds {
|
||
return types.CharterAmendment{}, fmt.Errorf("cover: amendment %q cooling not elapsed (now=%d ProposedAt=%d, need %d seconds)", amendmentID, now, a.ProposedAt, types.CharterAmendmentCoolingSeconds)
|
||
}
|
||
a.Status = types.AmendmentCooled
|
||
a.CooledAt = now
|
||
k.SetCharterAmendment(ctx, a)
|
||
return a, nil
|
||
}
|
||
|
||
// RatifyCharterAmendment transitions a Cooled CharterAmendment to
|
||
// Ratified (REQ-052). Returns an error if the amendment is not found or
|
||
// not in the Cooled status. The Pool supermajority + Watcher + Counsel
|
||
// are checked upstream (the handler); this helper does the state
|
||
// transition + appends the amendment to the parent charter's Amendments
|
||
// slice.
|
||
func (k Keeper) RatifyCharterAmendment(ctx sdk.Context, amendmentID string, now int64) (types.CharterAmendment, error) {
|
||
a, ok := k.GetCharterAmendment(ctx, amendmentID)
|
||
if !ok {
|
||
return types.CharterAmendment{}, fmt.Errorf("cover: amendment %q not found", amendmentID)
|
||
}
|
||
if a.Status != types.AmendmentCooled {
|
||
return types.CharterAmendment{}, fmt.Errorf("cover: amendment %q status %q (only Cooled can be Ratified)", amendmentID, a.Status)
|
||
}
|
||
a.Status = types.AmendmentRatified
|
||
a.RatifiedAt = now
|
||
k.SetCharterAmendment(ctx, a)
|
||
return a, nil
|
||
}
|
||
|
||
// --- P5: Bill of Rights review + Pier Selection stores (REQ-056, REQ-066) ------
|
||
//
|
||
// (REQ-056, REQ-066). Two new stores. The bill_review/ store is keyed by
|
||
// ReviewID -> the MsgCounselReviewBillOfRights record (the handler persists
|
||
// the review on a bonded-Counsel ceremony). The pier_selection/ store is
|
||
// keyed by GuildID -> PierSelectionRecord (the MsgSelectPier handler
|
||
// persists + MsgRevokePierSelection removes). The pier_index/ store is
|
||
// keyed by PierID -> PierSelectionIndex (the mesh-maintained index; the
|
||
// MsgSelectPier handler creates or updates the entry, accumulating scores
|
||
// from successive selections). All three use the same JSON-marshal pattern
|
||
// as the P1/P2 stores.
|
||
|
||
var billReviewKeyPrefix = []byte("bill_review/")
|
||
|
||
func billReviewKey(reviewID string) []byte {
|
||
return append(billReviewKeyPrefix, []byte(reviewID)...)
|
||
}
|
||
|
||
// BillOfRightsReview is the persisted record of a Counsel review of the
|
||
// Anti-Capture Bill of Rights (REQ-056 §7 acceptance ceremony). The
|
||
// MsgCounselReviewBillOfRights handler persists this in the bill_review/
|
||
// store keyed by ReviewID.
|
||
type BillOfRightsReview struct {
|
||
ReviewID string `json:"review_id" yaml:"review_id"`
|
||
CounselReachID string `json:"counsel_reach_id" yaml:"counsel_reach_id"`
|
||
Staked bool `json:"staked" yaml:"staked"`
|
||
ReviewResult string `json:"review_result" yaml:"review_result"`
|
||
ReviewedAt int64 `json:"reviewed_at" yaml:"reviewed_at"`
|
||
}
|
||
|
||
// GetBillOfRightsReview loads a BillOfRightsReview by review-id. Returns
|
||
// the review and true if found, or zero value + false if not.
|
||
func (k Keeper) GetBillOfRightsReview(ctx sdk.Context, reviewID string) (BillOfRightsReview, bool) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz := store.Get(billReviewKey(reviewID))
|
||
if bz == nil {
|
||
return BillOfRightsReview{}, false
|
||
}
|
||
var r BillOfRightsReview
|
||
if err := json.Unmarshal(bz, &r); err != nil {
|
||
return BillOfRightsReview{}, false
|
||
}
|
||
return r, true
|
||
}
|
||
|
||
// SetBillOfRightsReview persists a BillOfRightsReview by review-id.
|
||
func (k Keeper) SetBillOfRightsReview(ctx sdk.Context, r BillOfRightsReview) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz, err := json.Marshal(r)
|
||
if err != nil {
|
||
panic(fmt.Sprintf("cover: marshal bill-of-rights review %q: %v", r.ReviewID, err))
|
||
}
|
||
store.Set(billReviewKey(r.ReviewID), bz)
|
||
}
|
||
|
||
// AllBillOfRightsReviews returns all persisted BillOfRightsReview records
|
||
// (iteration helper, unordered).
|
||
func (k Keeper) AllBillOfRightsReviews(ctx sdk.Context) []BillOfRightsReview {
|
||
store := ctx.KVStore(k.storeKey)
|
||
iterator := store.Iterator(billReviewKeyPrefix, prefixEnd(billReviewKeyPrefix))
|
||
defer iterator.Close()
|
||
out := []BillOfRightsReview{}
|
||
for ; iterator.Valid(); iterator.Next() {
|
||
var r BillOfRightsReview
|
||
if err := json.Unmarshal(iterator.Value(), &r); err == nil {
|
||
out = append(out, r)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
var pierSelectionKeyPrefix = []byte("pier_selection/")
|
||
|
||
func pierSelectionKey(guildID string) []byte {
|
||
return append(pierSelectionKeyPrefix, []byte(guildID)...)
|
||
}
|
||
|
||
// GetPierSelectionRecord loads a PierSelectionRecord by guild-id. Returns
|
||
// the record and true if found, or zero value + false if not.
|
||
func (k Keeper) GetPierSelectionRecord(ctx sdk.Context, guildID string) (types.PierSelectionRecord, bool) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz := store.Get(pierSelectionKey(guildID))
|
||
if bz == nil {
|
||
return types.PierSelectionRecord{}, false
|
||
}
|
||
var r types.PierSelectionRecord
|
||
if err := json.Unmarshal(bz, &r); err != nil {
|
||
return types.PierSelectionRecord{}, false
|
||
}
|
||
return r, true
|
||
}
|
||
|
||
// SetPierSelectionRecord persists a PierSelectionRecord by guild-id.
|
||
func (k Keeper) SetPierSelectionRecord(ctx sdk.Context, r types.PierSelectionRecord) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz, err := json.Marshal(r)
|
||
if err != nil {
|
||
panic(fmt.Sprintf("cover: marshal pier selection for guild %q: %v", r.GuildID, err))
|
||
}
|
||
store.Set(pierSelectionKey(r.GuildID), bz)
|
||
}
|
||
|
||
// RemovePierSelectionRecord removes a PierSelectionRecord by guild-id (the
|
||
// MsgRevokePierSelection handler calls this). Returns true if a record was
|
||
// removed, false if no record existed.
|
||
func (k Keeper) RemovePierSelectionRecord(ctx sdk.Context, guildID string) bool {
|
||
store := ctx.KVStore(k.storeKey)
|
||
key := pierSelectionKey(guildID)
|
||
if store.Get(key) == nil {
|
||
return false
|
||
}
|
||
store.Delete(key)
|
||
return true
|
||
}
|
||
|
||
var pierIndexKeyPrefix = []byte("pier_index/")
|
||
|
||
func pierIndexKey(pierID string) []byte {
|
||
return append(pierIndexKeyPrefix, []byte(pierID)...)
|
||
}
|
||
|
||
// GetPierSelectionIndex loads a PierSelectionIndex by pier-id (REQ-066 —
|
||
// the mesh-maintained index query). Returns the index and true if found,
|
||
// or zero value + false if not.
|
||
func (k Keeper) GetPierSelectionIndex(ctx sdk.Context, pierID string) (types.PierSelectionIndex, bool) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz := store.Get(pierIndexKey(pierID))
|
||
if bz == nil {
|
||
return types.PierSelectionIndex{}, false
|
||
}
|
||
var idx types.PierSelectionIndex
|
||
if err := json.Unmarshal(bz, &idx); err != nil {
|
||
return types.PierSelectionIndex{}, false
|
||
}
|
||
return idx, true
|
||
}
|
||
|
||
// SetPierSelectionIndex persists a PierSelectionIndex by pier-id.
|
||
func (k Keeper) SetPierSelectionIndex(ctx sdk.Context, idx types.PierSelectionIndex) {
|
||
store := ctx.KVStore(k.storeKey)
|
||
bz, err := json.Marshal(idx)
|
||
if err != nil {
|
||
panic(fmt.Sprintf("cover: marshal pier selection index for pier %q: %v", idx.PierID, err))
|
||
}
|
||
store.Set(pierIndexKey(idx.PierID), bz)
|
||
}
|
||
|
||
// AllPierSelectionIndexes returns all persisted PierSelectionIndex records
|
||
// (iteration helper, unordered).
|
||
func (k Keeper) AllPierSelectionIndexes(ctx sdk.Context) []types.PierSelectionIndex {
|
||
store := ctx.KVStore(k.storeKey)
|
||
iterator := store.Iterator(pierIndexKeyPrefix, prefixEnd(pierIndexKeyPrefix))
|
||
defer iterator.Close()
|
||
out := []types.PierSelectionIndex{}
|
||
for ; iterator.Valid(); iterator.Next() {
|
||
var idx types.PierSelectionIndex
|
||
if err := json.Unmarshal(iterator.Value(), &idx); err == nil {
|
||
out = append(out, idx)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// DefaultPierScores are the simtest-grade default scores for a fresh
|
||
// PierSelectionIndex entry (the mesh assigns these on a Pier's first
|
||
// selection — the live mesh oracle is a v0.8+ concern; the simtest uses
|
||
// these deterministic defaults so the index is non-empty on first
|
||
// selection). All three scores are in [0,1]; the OverallScore is the
|
||
// weighted aggregate (a deterministic blend: 0.4 × JurisdictionalReliability
|
||
// + 0.3 × IntegrationQuality + 0.3 × (FiduciaryRecordHash non-empty ? 1.0
|
||
// : 0.0)).
|
||
const (
|
||
DefaultPierJurisdictionalReliabilityScore = 0.8
|
||
DefaultPierIntegrationQualityScore = 0.7
|
||
)
|
||
|
||
// DefaultPierOverallScore computes the deterministic OverallScore blend
|
||
// for a PierSelectionIndex (the keeper uses this when creating or updating
|
||
// an index entry). The blend is 0.4 × JurisdictionalReliability + 0.3 ×
|
||
// IntegrationQuality + 0.3 × FiduciaryConfidence (FiduciaryConfidence is
|
||
// 1.0 if the FiduciaryRecordHash is non-empty, else 0.0). The simtest
|
||
// asserts the OverallScore is non-decreasing on successive selections (a
|
||
// second selection with the same scores yields the same OverallScore).
|
||
func DefaultPierOverallScore(jurisdictionalReliability, integrationQuality float64, fiduciaryRecordHash []byte) float64 {
|
||
fiduciaryConfidence := 0.0
|
||
if len(fiduciaryRecordHash) > 0 {
|
||
fiduciaryConfidence = 1.0
|
||
}
|
||
return 0.4*jurisdictionalReliability + 0.3*integrationQuality + 0.3*fiduciaryConfidence
|
||
}
|