Files
openyield/x/exit/keeper/keeper.go
T
cloudinit-bot c97e18fc1f
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 phase/01 into milestone/v0.5-bearers-runtime (P1 complete → v0.4.1)
---ci---
project: oy
phase: 1
milestone: v0.5
status: complete
requirements:
  covered: [REQ-033]
  partial: []
---/ci---
2026-08-18 00:20:46 +00:00

166 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/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
}