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 }