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/exit/types" ) // keeper.go holds the store-backed Keeper for the exit module (P1-05-01). // // The Keeper wraps an sdk.KVStore via a storeKey. It holds the ExitRoute // records (by route-id) and the DEXSwap records (by swap-id). The Keeper // also holds the expected-keeper shim (BridgeKeeper for cross-chain exits). // The shim is an interface (G-003 — no struct import of x/bridge/types); // the concrete x/bridge keeper satisfies it structurally. // // The Fee Covenant clamp (x/feecovenant/types.Clamp) is invoked on // exit-fee-bps at runtime per the v0.5 interface extension. The clamp // ensures the exit fee is within [FeeFloorBps=1, FeeCeilingBps=10] (§18 // Mission-Lock Fee Covenant — auto-decline-only, never auto-increase). // // State-machine ordering (vision §7, enforced in every handler): // ValidateBasic → keeper authz → state mutation → ctx.EventManager().EmitEvent // Keeper is the store-backed exit keeper. type Keeper struct { cdc codec.Codec storeKey storetypes.StoreKey bridgeKeeper types.BridgeKeeper } // NewKeeper constructs a new store-backed exit Keeper. The BridgeKeeper // expected-keeper shim is injected (nil-able for partial tests; the // ExecuteDEXSwap handler guards a nil shim for same-chain exits). func NewKeeper(cdc codec.Codec, storeKey storetypes.StoreKey, bk types.BridgeKeeper) Keeper { return Keeper{ cdc: cdc, storeKey: storeKey, bridgeKeeper: bk, } } // SetBridgeKeeper sets the BridgeKeeper expected-keeper shim (for // post-construction wiring, e.g., app wiring or test setup). func (k *Keeper) SetBridgeKeeper(bk types.BridgeKeeper) { k.bridgeKeeper = bk } // --- ExitRoute store ---------------------------------------------------------- var routeKeyPrefix = []byte("route/") func routeKey(routeID string) []byte { return append(routeKeyPrefix, []byte(routeID)...) } // GetExitRoute loads an ExitRoute by route-id. Returns the route and true // if found, or zero value + false if not. func (k Keeper) GetExitRoute(ctx sdk.Context, routeID string) (types.ExitRoute, bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(routeKey(routeID)) if bz == nil { return types.ExitRoute{}, false } var r types.ExitRoute if err := json.Unmarshal(bz, &r); err != nil { return types.ExitRoute{}, false } return r, true } // SetExitRoute persists an ExitRoute by route-id. func (k Keeper) SetExitRoute(ctx sdk.Context, r types.ExitRoute) { store := ctx.KVStore(k.storeKey) bz, err := json.Marshal(r) if err != nil { panic(fmt.Sprintf("exit: marshal route %q: %v", r.RouteID, err)) } store.Set(routeKey(r.RouteID), bz) } // AllExitRoutes returns all persisted ExitRoute records (iteration helper). func (k Keeper) AllExitRoutes(ctx sdk.Context) []types.ExitRoute { store := ctx.KVStore(k.storeKey) iterator := store.Iterator(routeKeyPrefix, prefixEnd(routeKeyPrefix)) defer iterator.Close() out := []types.ExitRoute{} for ; iterator.Valid(); iterator.Next() { var r types.ExitRoute if err := json.Unmarshal(iterator.Value(), &r); err == nil { out = append(out, r) } } return out } // --- DEXSwap store ------------------------------------------------------------ var swapKeyPrefix = []byte("swap/") func swapKey(swapID string) []byte { return append(swapKeyPrefix, []byte(swapID)...) } // GetDEXSwap loads a DEXSwap by swap-id. Returns the swap and true if found. func (k Keeper) GetDEXSwap(ctx sdk.Context, swapID string) (types.DEXSwap, bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(swapKey(swapID)) if bz == nil { return types.DEXSwap{}, false } var s types.DEXSwap if err := json.Unmarshal(bz, &s); err != nil { return types.DEXSwap{}, false } return s, true } // SetDEXSwap persists a DEXSwap by swap-id. func (k Keeper) SetDEXSwap(ctx sdk.Context, s types.DEXSwap) { store := ctx.KVStore(k.storeKey) bz, err := json.Marshal(s) if err != nil { panic(fmt.Sprintf("exit: marshal swap %q: %v", s.SwapID, err)) } store.Set(swapKey(s.SwapID), bz) } // AllDEXSwaps returns all persisted DEXSwap records (iteration helper). func (k Keeper) AllDEXSwaps(ctx sdk.Context) []types.DEXSwap { store := ctx.KVStore(k.storeKey) iterator := store.Iterator(swapKeyPrefix, prefixEnd(swapKeyPrefix)) defer iterator.Close() out := []types.DEXSwap{} for ; iterator.Valid(); iterator.Next() { var s types.DEXSwap if err := json.Unmarshal(iterator.Value(), &s); err == nil { out = append(out, s) } } 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 }