c97e18fc1f
---ci--- project: oy phase: 1 milestone: v0.5 status: complete requirements: covered: [REQ-033] partial: [] ---/ci---
166 lines
5.1 KiB
Go
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
|
|
}
|