Files
openyield/x/bearers/keeper/keeper.go
T
cloudinit-bot 6c34650a0d
docs-build / go test ./... (lexicon firewall + all x/* tests) (push) Has been cancelled
docs-build / mkdocs build (docs site artifact) (push) Has been cancelled
Merge milestone/v0.5-bearers-runtime into main (v0.5 Bearers Runtime feature milestone release)
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---
2026-08-18 03:42:01 +00:00

167 lines
5.1 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/bearers/types"
)
// keeper.go holds the store-backed Keeper for the bearers module (P2-02-01,
// REQ-034).
//
// The Keeper wraps an sdk.KVStore via a storeKey. It holds the Session
// records (by session-id) and the OYQRCode records (by qr-id). The Keeper
// also holds the expected-keeper shim (BreadKeeper for the OY-QR consume
// transfer effect). The shim is an interface (G-003 — no struct import of
// x/bread/types); the concrete x/bread keeper satisfies it structurally.
//
// State-machine ordering (vision §7, enforced in every handler):
// ValidateBasic → keeper authz → state mutation → ctx.EventManager().EmitEvent
//
// Surveillance-resistant invariant (A-522): the Keeper carries NO
// geolocation fields; the handlers emit NO geolocation in events.
// Keeper is the store-backed bearers keeper.
type Keeper struct {
cdc codec.Codec
storeKey storetypes.StoreKey
breadKeeper types.BreadKeeper
}
// NewKeeper constructs a new store-backed bearers Keeper. The BreadKeeper
// expected-keeper shim is injected (nil-able for partial tests; the
// ConsumeOYQR handler guards a nil shim and skips the transfer effect,
// still flipping the consumed flag — the A-521 state-write-first invariant
// holds regardless).
func NewKeeper(cdc codec.Codec, storeKey storetypes.StoreKey, bk types.BreadKeeper) Keeper {
return Keeper{
cdc: cdc,
storeKey: storeKey,
breadKeeper: bk,
}
}
// SetBreadKeeper sets the BreadKeeper expected-keeper shim (for
// post-construction wiring, e.g., app wiring or test setup).
func (k *Keeper) SetBreadKeeper(bk types.BreadKeeper) { k.breadKeeper = bk }
// --- Session store -----------------------------------------------------------
var sessionKeyPrefix = []byte("session/")
func sessionKey(sessionID string) []byte {
return append(sessionKeyPrefix, []byte(sessionID)...)
}
// GetSession loads a Session by session-id. Returns the session and true
// if found, or zero value + false if not.
func (k Keeper) GetSession(ctx sdk.Context, sessionID string) (types.Session, bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(sessionKey(sessionID))
if bz == nil {
return types.Session{}, false
}
var s types.Session
if err := json.Unmarshal(bz, &s); err != nil {
return types.Session{}, false
}
return s, true
}
// SetSession persists a Session by session-id.
func (k Keeper) SetSession(ctx sdk.Context, s types.Session) {
store := ctx.KVStore(k.storeKey)
bz, err := json.Marshal(s)
if err != nil {
panic(fmt.Sprintf("bearers: marshal session %q: %v", s.SessionID, err))
}
store.Set(sessionKey(s.SessionID), bz)
}
// AllSessions returns all persisted Session records (iteration helper).
func (k Keeper) AllSessions(ctx sdk.Context) []types.Session {
store := ctx.KVStore(k.storeKey)
iterator := store.Iterator(sessionKeyPrefix, prefixEnd(sessionKeyPrefix))
defer iterator.Close()
out := []types.Session{}
for ; iterator.Valid(); iterator.Next() {
var s types.Session
if err := json.Unmarshal(iterator.Value(), &s); err == nil {
out = append(out, s)
}
}
return out
}
// --- OYQRCode store ----------------------------------------------------------
var qrKeyPrefix = []byte("qr/")
func qrKey(qrID string) []byte {
return append(qrKeyPrefix, []byte(qrID)...)
}
// GetOYQRCode loads an OYQRCode by qr-id. Returns the QR and true if found.
func (k Keeper) GetOYQRCode(ctx sdk.Context, qrID string) (types.OYQRCode, bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(qrKey(qrID))
if bz == nil {
return types.OYQRCode{}, false
}
var q types.OYQRCode
if err := json.Unmarshal(bz, &q); err != nil {
return types.OYQRCode{}, false
}
return q, true
}
// SetOYQRCode persists an OYQRCode by qr-id.
func (k Keeper) SetOYQRCode(ctx sdk.Context, q types.OYQRCode) {
store := ctx.KVStore(k.storeKey)
bz, err := json.Marshal(q)
if err != nil {
panic(fmt.Sprintf("bearers: marshal qr %q: %v", q.QRID, err))
}
store.Set(qrKey(q.QRID), bz)
}
// AllOYQRCodes returns all persisted OYQRCode records (iteration helper).
func (k Keeper) AllOYQRCodes(ctx sdk.Context) []types.OYQRCode {
store := ctx.KVStore(k.storeKey)
iterator := store.Iterator(qrKeyPrefix, prefixEnd(qrKeyPrefix))
defer iterator.Close()
out := []types.OYQRCode{}
for ; iterator.Valid(); iterator.Next() {
var q types.OYQRCode
if err := json.Unmarshal(iterator.Value(), &q); err == nil {
out = append(out, q)
}
}
return out
}
// 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
}