package keeper_test // msg_server_simtest_test.go is the x/bond keeper simtest (P6-03-01, // REQ-038). // // D-054: simtest-grade — in-memory sdk.Context + dbm in-memory store, no // real Stand keeper (the StandKeeper shim is wired to a stub; G-003 test // exemption). The simtest exercises: // // Bond issuance (coupon clamp at issuance): // - IssueBond with coupon in-band (e.g., 500) -> recorded unchanged; no // clamp event. // - IssueBond with coupon above 800 (e.g., 1200) -> ValidateBasic REJECTS // (stateless guard; the handler re-clamps at runtime — defense in // depth, but ValidateBasic is the first gate). // - IssueBond on a non-existent Stand (StandKeeper stub reports false) -> // REJECTED (the bond is not created). // - IssueBond on an existing bond-id -> idempotent reject. // - Nil StandKeeper shim -> skips the StandExists check (simtest wiring). // // GrowthBond issuance + tick (growth clamp): // - IssueGrowthBond with coupon + growth in-band -> recorded unchanged. // - IssueGrowthBond with growth that would push post-growth above cap -> // growth clamped to room (G-012). // - TickGrowthBond -> coupon grows by growth-rate, clamped so post-growth // <= cap. // - TickGrowthBond on a non-GrowthBond -> REJECTED. // // CLOB matching (D-057 — price-time priority FCFS per REQ-007; NO AMM): // - PlaceSecondaryOrder rests an order on the book. // - MatchSecondaryOrder full fill: taker fills the resting order // completely; resting order -> Filled (deleted from book). // - MatchSecondaryOrder partial fill + rest: taker partially fills the // resting order; resting order's remaining quantity is updated; taker // is not rested (simplification — the taker is a one-shot match). // - MatchSecondaryOrder no-match: taker price does not cross any resting // order -> filled quantity 0; the resting book is unchanged. // - CancelSecondaryOrder: resting order removed from book (Cancelled). // - Price-time priority FCFS: at the same price, the earlier resting // order fills first (by sequence). // // Per-match coupon clamp (D-063 REJECT above 800 — G-019 ImpliedCoupon): // - A match within [0, 800] bps clears (clamp event emitted; the matched // coupon is within band). // - A match whose implied coupon EXCEEDS 800 bps (resting price-bps < // 9200) is REJECTED (fails closed — D-063; the resting order stays on // the book; the incoming taker is rejected; no refund path). // // G-019 ImpliedCoupon boundary unit test (800/801/799 bps): // - price-bps 9200 -> ImpliedCoupon 800 (== cap, in-band, clears). // - price-bps 9199 -> ImpliedCoupon 801 (> cap, REJECTED). // - price-bps 9201 -> ImpliedCoupon 799 (< cap, in-band, clears). // // D-028 regression: CouponCapBps=800, CouponFloorBps=0 unchanged. // REQ-030 cross-const test green (run in x/hub/types/cross_const_test.go; // this simtest asserts the bond consts are the mission-locked values). // G-003 import-invariant green (the production firewall test in // x/window/types scans all x/ production files; this simtest is a test // file, G-003-exempt). // // Coverage target: >=80% on x/bond/keeper. import ( "strings" "testing" "time" "cosmossdk.io/log" "cosmossdk.io/store" storetypes "cosmossdk.io/store/types" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" dbm "github.com/cosmos/cosmos-db" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/oy/openyield/x/bond/keeper" btypes "github.com/oy/openyield/x/bond/types" ) // --- Stub expected-keepers (G-003 test exemption) --------------------------- // stubStandKeeper satisfies btypes.StandKeeper for the simtest. It returns // the configured StandExists result per stand-id (default: exists=true). type stubStandKeeper struct { exists map[string]bool existsAll bool } func (s *stubStandKeeper) StandExists(standID string) bool { if s.exists != nil { return s.exists[standID] } return s.existsAll } // --- Simtest context helper -------------------------------------------------- // newSimtestContext constructs an in-memory sdk.Context with a KVStore // mounted at the bond store key. D-054: in-memory, no real Stand keeper. // Returns the ctx, the stub StandKeeper, and the Keeper. func newSimtestContext(t *testing.T) (sdk.Context, *stubStandKeeper, keeper.Keeper) { t.Helper() db := dbm.NewMemDB() cdc := newTestCodec() storeKey := storetypes.NewKVStoreKey(btypes.StoreKey) cms := store.NewCommitMultiStore(db, log.NewNopLogger(), nil) cms.MountStoreWithDB(storeKey, storetypes.StoreTypeDB, nil) if err := cms.LoadLatestVersion(); err != nil { t.Fatalf("load latest version: %v", err) } ctx := sdk.NewContext(cms, cmtproto.Header{Time: time.Unix(1000, 0)}, false, log.NewNopLogger()) sk := &stubStandKeeper{existsAll: true} k := keeper.NewKeeper(cdc, storeKey, sk) return ctx, sk, k } // newTestCodec constructs a minimal codec for the simtest. func newTestCodec() codec.Codec { registry := codectypes.NewInterfaceRegistry() return codec.NewProtoCodec(registry) } // hasEvent reports whether ctx emitted an event of the given type. func hasEvent(ctx sdk.Context, eventType string) bool { for _, ev := range ctx.EventManager().Events() { if ev.Type == eventType { return true } } return false } // eventAttr returns the value of an attribute on the last event of the // given type, or "" if not found. func eventAttr(ctx sdk.Context, eventType, attrKey string) string { for _, ev := range ctx.EventManager().Events() { if ev.Type == eventType { for _, a := range ev.Attributes { if string(a.Key) == attrKey { return string(a.Value) } } } } return "" } // eventCount returns the number of events of the given type emitted on ctx. func eventCount(ctx sdk.Context, eventType string) int { n := 0 for _, ev := range ctx.EventManager().Events() { if ev.Type == eventType { n++ } } return n } // freshCtx returns a fresh ctx (no prior events) on the same multi-store, // so event assertions per-test are isolated. The keeper is shared (state // persists across calls within a test; tests that need a fresh store call // newSimtestContext instead). func freshCtx(t *testing.T) (sdk.Context, *stubStandKeeper, keeper.Keeper) { return newSimtestContext(t) } // --- Bond issuance (coupon clamp at issuance) -------------------------------- // TestIssueBondInBand asserts an in-band coupon (500) is recorded unchanged // and the bond.issued event is emitted. func TestIssueBondInBand(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) resp, err := srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b1", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) if err != nil { t.Fatalf("IssueBond: %v", err) } if resp.ClampedCouponBps != 500 { t.Errorf("ClampedCouponBps = %d, want 500 (in-band, unchanged)", resp.ClampedCouponBps) } if !hasEvent(ctx, "bond.issued") { t.Error("bond.issued event not emitted") } // Read it back. b, ok := k.GetBond(ctx, "b1") if !ok { t.Fatal("bond not persisted") } if b.CouponBps != 500 { t.Errorf("persisted CouponBps = %d, want 500", b.CouponBps) } if b.Status != btypes.BondIssued { t.Errorf("Status = %q, want BondIssued", b.Status) } } // TestIssueBondAboveCapRejectedAtValidateBasic asserts an above-cap coupon // (1200) is REJECTED at ValidateBasic (the stateless guard; D-028). func TestIssueBondAboveCapRejectedAtValidateBasic(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, err := srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b2", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 1200, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) if err == nil { t.Error("IssueBond with above-cap coupon should be REJECTED at ValidateBasic") } if !strings.Contains(err.Error(), "out of band") { t.Errorf("err = %q, want 'out of band'", err.Error()) } } // TestIssueBondNonExistentStandRejected asserts a non-existent Stand // REJECTS the issuance (the StandKeeper shim reports false). func TestIssueBondNonExistentStandRejected(t *testing.T) { ctx, sk, k := newSimtestContext(t) sk.exists = map[string]bool{"stand-1": false} sk.existsAll = false srv := keeper.NewMsgServerImpl(k) _, err := srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b3", IssuerStandID: "no-such-stand", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) if err == nil { t.Error("IssueBond on non-existent Stand should be REJECTED") } if !strings.Contains(err.Error(), "does not exist") { t.Errorf("err = %q, want 'does not exist'", err.Error()) } } // TestIssueBondIdempotentReject asserts issuing the same bond-id twice // REJECTS the second issuance. func TestIssueBondIdempotentReject(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, err := srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b4", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) if err != nil { t.Fatalf("first IssueBond: %v", err) } _, err = srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b4", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 600, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) if err == nil { t.Error("second IssueBond on same bond-id should be REJECTED") } if !strings.Contains(err.Error(), "already exists") { t.Errorf("err = %q, want 'already exists'", err.Error()) } } // TestIssueBondNilStandKeeperSkipsCheck asserts a nil StandKeeper shim skips // the StandExists check (simtest wiring — the handler still mutates state). func TestIssueBondNilStandKeeperSkipsCheck(t *testing.T) { ctx, _, k := newSimtestContext(t) k.SetStandKeeper(nil) // nil shim — skip StandExists check srv := keeper.NewMsgServerImpl(k) _, err := srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b5", IssuerStandID: "any-stand", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) if err != nil { t.Fatalf("IssueBond with nil StandKeeper should skip the check, got: %v", err) } } // --- GrowthBond issuance + tick ---------------------------------------------- // TestIssueGrowthBondInBand asserts an in-band coupon + growth are recorded // unchanged. func TestIssueGrowthBondInBand(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) resp, err := srv.IssueGrowthBond(ctx, &btypes.MsgIssueGrowthBond{ BondID: "gb1", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, GrowthRateBps: 200, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) if err != nil { t.Fatalf("IssueGrowthBond: %v", err) } if resp.ClampedCouponBps != 500 { t.Errorf("ClampedCouponBps = %d, want 500", resp.ClampedCouponBps) } if resp.ClampedGrowthRateBps != 200 { t.Errorf("ClampedGrowthRateBps = %d, want 200", resp.ClampedGrowthRateBps) } if !hasEvent(ctx, "bond.growth_issued") { t.Error("bond.growth_issued event not emitted") } } // TestIssueGrowthBondGrowthClampedToRoom asserts a growth-rate that would // push post-growth above cap is clamped to the room-to-cap (G-012). func TestIssueGrowthBondGrowthClampedToRoom(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) // coupon=500, cap=800, room=300. growth=400 -> clamped to 300. resp, err := srv.IssueGrowthBond(ctx, &btypes.MsgIssueGrowthBond{ BondID: "gb2", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, GrowthRateBps: 400, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) if err != nil { t.Fatalf("IssueGrowthBond: %v", err) } if resp.ClampedCouponBps != 500 { t.Errorf("ClampedCouponBps = %d, want 500", resp.ClampedCouponBps) } if resp.ClampedGrowthRateBps != 300 { t.Errorf("ClampedGrowthRateBps = %d, want 300 (room=300, G-012)", resp.ClampedGrowthRateBps) } if !hasEvent(ctx, "bond.growth_coupon_clamped") { t.Error("bond.growth_coupon_clamped event not emitted (growth was clamped)") } } // TestTickGrowthBond asserts a growth tick grows the coupon by the growth- // rate, clamped so post-growth <= cap. func TestTickGrowthBond(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) // Issue a GrowthBond: coupon=500, growth=200 (room=300; growth ClampGrowth(800, 200) = 0 (at cap, no room); // post-growth = 800 + 0 = 800. resp3, err := srv.TickGrowthBond(ctx, &btypes.MsgTickGrowthBond{BondID: "gb3", Signer: "stand-1"}) if err != nil { t.Fatalf("third TickGrowthBond: %v", err) } if resp3.PostGrowthCouponBps != 800 { t.Errorf("PostGrowthCouponBps after third tick = %d, want 800 (at cap, no room)", resp3.PostGrowthCouponBps) } } // TestTickGrowthBondNotFound asserts TickGrowthBond on a non-GrowthBond is // REJECTED. func TestTickGrowthBondNotFound(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, err := srv.TickGrowthBond(ctx, &btypes.MsgTickGrowthBond{BondID: "no-such-bond", Signer: "stand-1"}) if err == nil { t.Error("TickGrowthBond on non-existent bond should be REJECTED") } } // --- CLOB matching: Place + Match full fill -------------------------------- // TestPlaceSecondaryOrder rests an order on the book. func TestPlaceSecondaryOrder(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) // Issue a bond first (the order rests on an issued bond). _, err := srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b10", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) if err != nil { t.Fatalf("IssueBond: %v", err) } _, err = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "o1", BondID: "b10", Side: btypes.OrderSell, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "holder-1", Signer: "holder-1", }) if err != nil { t.Fatalf("PlaceSecondaryOrder: %v", err) } if !hasEvent(ctx, "bond.order_placed") { t.Error("bond.order_placed event not emitted") } // The order is on the book. ro, ok := k.GetRestingOrder(ctx, "o1") if !ok { t.Fatal("resting order not persisted") } if ro.Order.Status != btypes.OrderOpen { t.Errorf("Status = %q, want Open", ro.Order.Status) } if ro.PriceBps != 9500 { t.Errorf("PriceBps = %d, want 9500", ro.PriceBps) } if ro.RemainingQuantityGrain != 100 { t.Errorf("RemainingQuantityGrain = %d, want 100", ro.RemainingQuantityGrain) } } // TestPlaceSecondaryOrderNonExistentBondRejected asserts placing an order on // a non-existent bond is REJECTED. func TestPlaceSecondaryOrderNonExistentBondRejected(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, err := srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "o2", BondID: "no-such-bond", Side: btypes.OrderSell, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "holder-1", Signer: "holder-1", }) if err == nil { t.Error("PlaceSecondaryOrder on non-existent bond should be REJECTED") } } // TestPlaceSecondaryOrderIdempotentReject asserts placing the same order-id // twice REJECTS the second. func TestPlaceSecondaryOrderIdempotentReject(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, _ = srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b11", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) _, err := srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "o3", BondID: "b11", Side: btypes.OrderSell, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "holder-1", Signer: "holder-1", }) if err != nil { t.Fatalf("first PlaceSecondaryOrder: %v", err) } _, err = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "o3", BondID: "b11", Side: btypes.OrderSell, PriceBps: 9600, QuantityGrain: 100, HolderReachID: "holder-1", Signer: "holder-1", }) if err == nil { t.Error("second PlaceSecondaryOrder on same order-id should be REJECTED") } } // TestMatchSecondaryOrderFullFill asserts a taker fully fills a resting // order; the resting order is removed from the book (Filled). func TestMatchSecondaryOrderFullFill(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, _ = srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b20", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) // Rest a Sell order at price 9500 (implied coupon 500 bps, in-band). _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "sell-1", BondID: "b20", Side: btypes.OrderSell, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "holder-sell", Signer: "holder-sell", }) // Buy taker at price 9500 (willing to pay up to 9500; matches the Sell). resp, err := srv.MatchSecondaryOrder(ctx, &btypes.MsgMatchSecondaryOrder{ IncomingOrderID: "buy-1", BondID: "b20", Side: btypes.OrderBuy, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "holder-buy", Signer: "holder-buy", }) if err != nil { t.Fatalf("MatchSecondaryOrder: %v", err) } if resp.Rejected { t.Error("Rejected = true, want false (in-band match)") } if resp.FilledQuantityGrain != 100 { t.Errorf("FilledQuantityGrain = %d, want 100 (full fill)", resp.FilledQuantityGrain) } // The resting order is removed (Filled). if _, ok := k.GetRestingOrder(ctx, "sell-1"); ok { t.Error("resting order should be removed after full fill") } // A match event was emitted. if !hasEvent(ctx, "bond.match") { t.Error("bond.match event not emitted") } if !hasEvent(ctx, "bond.match_completed") { t.Error("bond.match_completed event not emitted") } } // TestMatchSecondaryOrderPartialFillRest asserts a taker partially fills a // resting order; the resting order's remaining quantity is updated. func TestMatchSecondaryOrderPartialFillRest(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, _ = srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b21", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) // Rest a Sell order at 9500 for 100. _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "sell-2", BondID: "b21", Side: btypes.OrderSell, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "holder-sell", Signer: "holder-sell", }) // Buy taker at 9500 for 40 (partial fill). resp, err := srv.MatchSecondaryOrder(ctx, &btypes.MsgMatchSecondaryOrder{ IncomingOrderID: "buy-2", BondID: "b21", Side: btypes.OrderBuy, PriceBps: 9500, QuantityGrain: 40, HolderReachID: "holder-buy", Signer: "holder-buy", }) if err != nil { t.Fatalf("MatchSecondaryOrder: %v", err) } if resp.FilledQuantityGrain != 40 { t.Errorf("FilledQuantityGrain = %d, want 40 (partial fill)", resp.FilledQuantityGrain) } // The resting order is still on the book with 60 remaining. ro, ok := k.GetRestingOrder(ctx, "sell-2") if !ok { t.Fatal("resting order should still be on the book after partial fill") } if ro.RemainingQuantityGrain != 60 { t.Errorf("RemainingQuantityGrain = %d, want 60 (100 - 40)", ro.RemainingQuantityGrain) } } // TestMatchSecondaryOrderNoMatch asserts a taker whose price does not cross // any resting order results in filled quantity 0 (the resting book is // unchanged). func TestMatchSecondaryOrderNoMatch(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, _ = srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b22", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) // Rest a Sell order at 9500. _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "sell-3", BondID: "b22", Side: btypes.OrderSell, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "holder-sell", Signer: "holder-sell", }) // Buy taker at 9400 (below the Sell price — no cross). resp, err := srv.MatchSecondaryOrder(ctx, &btypes.MsgMatchSecondaryOrder{ IncomingOrderID: "buy-3", BondID: "b22", Side: btypes.OrderBuy, PriceBps: 9400, QuantityGrain: 100, HolderReachID: "holder-buy", Signer: "holder-buy", }) if err != nil { t.Fatalf("MatchSecondaryOrder: %v", err) } if resp.FilledQuantityGrain != 0 { t.Errorf("FilledQuantityGrain = %d, want 0 (no cross)", resp.FilledQuantityGrain) } // The resting order is unchanged. ro, ok := k.GetRestingOrder(ctx, "sell-3") if !ok { t.Fatal("resting order should still be on the book (no match)") } if ro.RemainingQuantityGrain != 100 { t.Errorf("RemainingQuantityGrain = %d, want 100 (unchanged)", ro.RemainingQuantityGrain) } } // --- CancelSecondaryOrder --------------------------------------------------- // TestCancelSecondaryOrder asserts cancelling a resting order removes it // from the book. func TestCancelSecondaryOrder(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, _ = srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b30", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "o-cancel", BondID: "b30", Side: btypes.OrderSell, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "holder-sell", Signer: "holder-sell", }) _, err := srv.CancelSecondaryOrder(ctx, &btypes.MsgCancelSecondaryOrder{OrderID: "o-cancel", Signer: "holder-sell"}) if err != nil { t.Fatalf("CancelSecondaryOrder: %v", err) } if !hasEvent(ctx, "bond.order_cancelled") { t.Error("bond.order_cancelled event not emitted") } // The order is removed from the book. if _, ok := k.GetRestingOrder(ctx, "o-cancel"); ok { t.Error("resting order should be removed after cancel") } } // TestCancelSecondaryOrderNotFound asserts cancelling a non-existent order // is REJECTED. func TestCancelSecondaryOrderNotFound(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, err := srv.CancelSecondaryOrder(ctx, &btypes.MsgCancelSecondaryOrder{OrderID: "no-such-order", Signer: "holder-sell"}) if err == nil { t.Error("CancelSecondaryOrder on non-existent order should be REJECTED") } } // --- Price-time priority FCFS (REQ-007) -------------------------------------- // TestPriceTimePriorityFCFS asserts at the same price, the earlier resting // order fills first (by sequence). Two Sell orders at the same price 9500; // a Buy taker at 9500 for 50 fills the FIRST resting order (lower sequence) // completely, leaving the second untouched. func TestPriceTimePriorityFCFS(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, _ = srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b40", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) // Rest two Sell orders at the SAME price 9500 (implied coupon 500, in-band). _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "sell-first", BondID: "b40", Side: btypes.OrderSell, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "h1", Signer: "h1", }) _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "sell-second", BondID: "b40", Side: btypes.OrderSell, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "h2", Signer: "h2", }) // Buy taker at 9500 for 50 — should fill the FIRST resting order (lower // sequence). resp, err := srv.MatchSecondaryOrder(ctx, &btypes.MsgMatchSecondaryOrder{ IncomingOrderID: "buy-fcfs", BondID: "b40", Side: btypes.OrderBuy, PriceBps: 9500, QuantityGrain: 50, HolderReachID: "hb", Signer: "hb", }) if err != nil { t.Fatalf("MatchSecondaryOrder: %v", err) } if resp.FilledQuantityGrain != 50 { t.Errorf("FilledQuantityGrain = %d, want 50", resp.FilledQuantityGrain) } // The FIRST resting order has 50 remaining (100 - 50); the SECOND is // untouched at 100. ro1, ok := k.GetRestingOrder(ctx, "sell-first") if !ok { t.Fatal("sell-first should still be on the book (partial fill)") } if ro1.RemainingQuantityGrain != 50 { t.Errorf("sell-first RemainingQuantityGrain = %d, want 50 (FCFS — first fills first)", ro1.RemainingQuantityGrain) } ro2, ok := k.GetRestingOrder(ctx, "sell-second") if !ok { t.Fatal("sell-second should still be on the book (untouched)") } if ro2.RemainingQuantityGrain != 100 { t.Errorf("sell-second RemainingQuantityGrain = %d, want 100 (untouched — FCFS)", ro2.RemainingQuantityGrain) } } // TestPriceTimePriorityBestPriceFirst asserts the best price fills first // (lowest Sell price for a Buy taker). A Sell at 9400 fills before a Sell at // 9500 for a Buy taker. func TestPriceTimePriorityBestPriceFirst(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, _ = srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b41", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) // Rest a Sell at 9500 (implied coupon 500) FIRST (lower sequence). _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "sell-9500", BondID: "b41", Side: btypes.OrderSell, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "h1", Signer: "h1", }) // Rest a Sell at 9400 (implied coupon 600 — better price for the buyer) // SECOND (higher sequence). _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "sell-9400", BondID: "b41", Side: btypes.OrderSell, PriceBps: 9400, QuantityGrain: 100, HolderReachID: "h2", Signer: "h2", }) // Buy taker at 9500 for 50 — should fill the 9400 Sell FIRST (best price, // even though it has a higher sequence — price beats sequence). resp, err := srv.MatchSecondaryOrder(ctx, &btypes.MsgMatchSecondaryOrder{ IncomingOrderID: "buy-best", BondID: "b41", Side: btypes.OrderBuy, PriceBps: 9500, QuantityGrain: 50, HolderReachID: "hb", Signer: "hb", }) if err != nil { t.Fatalf("MatchSecondaryOrder: %v", err) } if resp.FilledQuantityGrain != 50 { t.Errorf("FilledQuantityGrain = %d, want 50", resp.FilledQuantityGrain) } // The 9400 Sell has 50 remaining (filled first — best price); the 9500 // Sell is untouched at 100. ro9400, ok := k.GetRestingOrder(ctx, "sell-9400") if !ok { t.Fatal("sell-9400 should still be on the book (partial fill)") } if ro9400.RemainingQuantityGrain != 50 { t.Errorf("sell-9400 RemainingQuantityGrain = %d, want 50 (best price fills first)", ro9400.RemainingQuantityGrain) } ro9500, ok := k.GetRestingOrder(ctx, "sell-9500") if !ok { t.Fatal("sell-9500 should still be on the book (untouched — worse price)") } if ro9500.RemainingQuantityGrain != 100 { t.Errorf("sell-9500 RemainingQuantityGrain = %d, want 100 (untouched — worse price)", ro9500.RemainingQuantityGrain) } } // --- Per-match coupon clamp (D-063 REJECT above 800 — G-019 ImpliedCoupon) --- // TestMatchInBandClears asserts a match within [0, 800] bps clears (the // matched coupon is within band; clamp event emitted). func TestMatchInBandClears(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, _ = srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b50", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) // Rest a Sell at 9250 (implied coupon 750 bps, in-band). _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "sell-750", BondID: "b50", Side: btypes.OrderSell, PriceBps: 9250, QuantityGrain: 100, HolderReachID: "h1", Signer: "h1", }) // Buy taker at 9250 (matches). resp, err := srv.MatchSecondaryOrder(ctx, &btypes.MsgMatchSecondaryOrder{ IncomingOrderID: "buy-750", BondID: "b50", Side: btypes.OrderBuy, PriceBps: 9250, QuantityGrain: 100, HolderReachID: "hb", Signer: "hb", }) if err != nil { t.Fatalf("MatchSecondaryOrder: %v", err) } if resp.Rejected { t.Error("Rejected = true, want false (in-band 750 bps clears)") } if resp.FilledQuantityGrain != 100 { t.Errorf("FilledQuantityGrain = %d, want 100", resp.FilledQuantityGrain) } // The match event carries the clamped coupon (750, in-band). attr := eventAttr(ctx, "bond.match", "matched_coupon_bps") if attr != "750" { t.Errorf("matched_coupon_bps = %q, want 750 (in-band)", attr) } } // TestMatchAboveCapRejected asserts a match whose implied coupon EXCEEDS 800 // bps (resting price-bps < 9200) is REJECTED (fails closed — D-063). The // resting order stays on the book; the incoming taker is rejected. func TestMatchAboveCapRejected(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, _ = srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b51", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) // Rest a Sell at 9000 (implied coupon 1000 bps, ABOVE cap 800). // PlaceSecondaryOrder does NOT reject (a resting order may rest at any // price; the REJECT is at MATCH time per D-063). _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "sell-1000", BondID: "b51", Side: btypes.OrderSell, PriceBps: 9000, QuantityGrain: 100, HolderReachID: "h1", Signer: "h1", }) // Buy taker at 9000 (matches the price, but the implied coupon is above // cap -> REJECTED per D-063). resp, err := srv.MatchSecondaryOrder(ctx, &btypes.MsgMatchSecondaryOrder{ IncomingOrderID: "buy-1000", BondID: "b51", Side: btypes.OrderBuy, PriceBps: 9000, QuantityGrain: 100, HolderReachID: "hb", Signer: "hb", }) if err == nil { t.Error("MatchSecondaryOrder above cap should be REJECTED (D-063)") } if !resp.Rejected { t.Error("Rejected = false, want true (above-cap match — D-063 fails closed)") } if resp.FilledQuantityGrain != 0 { t.Errorf("FilledQuantityGrain = %d, want 0 (rejected — no fill)", resp.FilledQuantityGrain) } // The resting order STAYS on the book (D-063 — the resting order is not // consumed by a rejected match). ro, ok := k.GetRestingOrder(ctx, "sell-1000") if !ok { t.Fatal("resting order should STAY on the book after D-063 reject") } if ro.RemainingQuantityGrain != 100 { t.Errorf("RemainingQuantityGrain = %d, want 100 (resting order unchanged)", ro.RemainingQuantityGrain) } // The reject event was emitted. if !hasEvent(ctx, "bond.match_rejected_above_cap") { t.Error("bond.match_rejected_above_cap event not emitted") } } // --- G-019 ImpliedCoupon boundary unit test (800/801/799 bps) --------------- // TestImpliedCouponBoundary asserts the G-019 ImpliedCoupon helper at the // 800-bps cap boundary: // - price-bps 9200 -> ImpliedCoupon 800 (== cap, in-band, clears via Clamp). // - price-bps 9199 -> ImpliedCoupon 801 (> cap, REJECTED — D-063). // - price-bps 9201 -> ImpliedCoupon 799 (< cap, in-band, clears). // // This is the G-019 BINDING boundary unit test — a single helper + boundary // test closing the formula ambiguity in the D-063 REJECT threshold. func TestImpliedCouponBoundary(t *testing.T) { cases := []struct { priceBps uint32 wantCoupon uint32 description string }{ {9200, 800, "at cap (800) — in-band, clears"}, {9199, 801, "above cap (801) — REJECTED per D-063"}, {9201, 799, "below cap (799) — in-band, clears"}, {10000, 0, "par — 0 implied coupon"}, {10500, 0, "premium — 0 implied coupon (floored at 0)"}, {9000, 1000, "deep discount — 1000 bps implied coupon"}, {0, 10000, "zero price — 10000 bps implied coupon"}, } for _, c := range cases { got := keeper.ImpliedCoupon(c.priceBps, 0) if got != c.wantCoupon { t.Errorf("ImpliedCoupon(%d, 0) = %d, want %d (%s)", c.priceBps, got, c.wantCoupon, c.description) } } } // TestImpliedCouponBoundaryAtCapClears asserts a match at exactly the cap // (800 bps, price-bps 9200) clears (in-band — the cap is inclusive; the // REJECT is strictly above 800 per D-063). func TestImpliedCouponBoundaryAtCapClears(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, _ = srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b60", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) // Rest a Sell at 9200 (implied coupon 800, == cap). _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "sell-800", BondID: "b60", Side: btypes.OrderSell, PriceBps: 9200, QuantityGrain: 100, HolderReachID: "h1", Signer: "h1", }) resp, err := srv.MatchSecondaryOrder(ctx, &btypes.MsgMatchSecondaryOrder{ IncomingOrderID: "buy-800", BondID: "b60", Side: btypes.OrderBuy, PriceBps: 9200, QuantityGrain: 100, HolderReachID: "hb", Signer: "hb", }) if err != nil { t.Fatalf("MatchSecondaryOrder at cap: %v", err) } if resp.Rejected { t.Error("Rejected = true, want false (at-cap 800 bps clears — D-063 rejects strictly above 800)") } } // TestImpliedCouponBoundaryAboveCapRejected asserts a match at 801 bps // (price-bps 9199) is REJECTED (D-063). func TestImpliedCouponBoundaryAboveCapRejected(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, _ = srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b61", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) // Rest a Sell at 9199 (implied coupon 801, ABOVE cap). _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "sell-801", BondID: "b61", Side: btypes.OrderSell, PriceBps: 9199, QuantityGrain: 100, HolderReachID: "h1", Signer: "h1", }) resp, err := srv.MatchSecondaryOrder(ctx, &btypes.MsgMatchSecondaryOrder{ IncomingOrderID: "buy-801", BondID: "b61", Side: btypes.OrderBuy, PriceBps: 9199, QuantityGrain: 100, HolderReachID: "hb", Signer: "hb", }) if err == nil { t.Error("MatchSecondaryOrder at 801 bps should be REJECTED (D-063)") } if !resp.Rejected { t.Error("Rejected = false, want true (801 bps > cap 800 — D-063)") } } // --- D-028 regression: 8%/0% consts unchanged -------------------------------- // TestCouponCapBpsUnchanged asserts CouponCapBps is 800 (D-028 — the 8% // mission-locked cap is unchanged by the P6 runtime promotion). func TestCouponCapBpsUnchanged(t *testing.T) { if btypes.CouponCapBps != 800 { t.Errorf("CouponCapBps = %d, want 800 (D-028 mission-locked 8pct — unchanged by P6)", btypes.CouponCapBps) } } // TestCouponFloorBpsUnchanged asserts CouponFloorBps is 0 (D-028 — the 0% // mission-locked floor is unchanged by the P6 runtime promotion). func TestCouponFloorBpsUnchanged(t *testing.T) { if btypes.CouponFloorBps != 0 { t.Errorf("CouponFloorBps = %d, want 0 (D-028 mission-locked 0pct — unchanged by P6)", btypes.CouponFloorBps) } } // TestOrderSideCountUnchanged asserts OrderSideCount is 2 (locked-const // regression — the P6 runtime does not change the v0.3 OrderSide enum). func TestOrderSideCountUnchanged(t *testing.T) { if btypes.OrderSideCount != 2 { t.Errorf("OrderSideCount = %d, want 2 (A-313 locked-const — unchanged by P6)", btypes.OrderSideCount) } } // TestOrderStatusCountUnchanged asserts OrderStatusCount is 3 (locked-const // regression — the P6 runtime does not change the v0.3 OrderStatus enum). func TestOrderStatusCountUnchanged(t *testing.T) { if btypes.OrderStatusCount != 3 { t.Errorf("OrderStatusCount = %d, want 3 (A-313 locked-const — unchanged by P6)", btypes.OrderStatusCount) } } // --- MatchSecondaryOrder on a GrowthBond + non-existent bond ---------------- // TestMatchSecondaryOrderOnGrowthBond asserts a match works on a GrowthBond // (the order rests on an issued GrowthBond too). func TestMatchSecondaryOrderOnGrowthBond(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, _ = srv.IssueGrowthBond(ctx, &btypes.MsgIssueGrowthBond{ BondID: "gb50", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, GrowthRateBps: 100, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "sell-gb", BondID: "gb50", Side: btypes.OrderSell, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "h1", Signer: "h1", }) resp, err := srv.MatchSecondaryOrder(ctx, &btypes.MsgMatchSecondaryOrder{ IncomingOrderID: "buy-gb", BondID: "gb50", Side: btypes.OrderBuy, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "hb", Signer: "hb", }) if err != nil { t.Fatalf("MatchSecondaryOrder on GrowthBond: %v", err) } if resp.FilledQuantityGrain != 100 { t.Errorf("FilledQuantityGrain = %d, want 100", resp.FilledQuantityGrain) } } // TestMatchSecondaryOrderNonExistentBondRejected asserts a match on a non- // existent bond is REJECTED. func TestMatchSecondaryOrderNonExistentBondRejected(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, err := srv.MatchSecondaryOrder(ctx, &btypes.MsgMatchSecondaryOrder{ IncomingOrderID: "buy-x", BondID: "no-such-bond", Side: btypes.OrderBuy, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "hb", Signer: "hb", }) if err == nil { t.Error("MatchSecondaryOrder on non-existent bond should be REJECTED") } } // --- ValidateBasic error paths (coverage) ----------------------------------- // TestValidateBasicErrorPaths exercises each Msg* ValidateBasic error path // to push coverage >=80%. func TestValidateBasicErrorPaths(t *testing.T) { // MsgIssueBond if err := (&btypes.MsgIssueBond{}).ValidateBasic(); err == nil { t.Error("empty MsgIssueBond should fail ValidateBasic") } if err := (&btypes.MsgIssueBond{BondID: "x", IssuerStandID: "s", PrincipalGrain: 1, CouponBps: 900}).ValidateBasic(); err == nil { t.Error("above-cap MsgIssueBond should fail ValidateBasic") } // MsgIssueGrowthBond if err := (&btypes.MsgIssueGrowthBond{}).ValidateBasic(); err == nil { t.Error("empty MsgIssueGrowthBond should fail ValidateBasic") } // MsgTickGrowthBond if err := (&btypes.MsgTickGrowthBond{}).ValidateBasic(); err == nil { t.Error("empty MsgTickGrowthBond should fail ValidateBasic") } // MsgPlaceSecondaryOrder if err := (&btypes.MsgPlaceSecondaryOrder{}).ValidateBasic(); err == nil { t.Error("empty MsgPlaceSecondaryOrder should fail ValidateBasic") } if err := (&btypes.MsgPlaceSecondaryOrder{OrderID: "x", BondID: "b", Side: "Bogus", QuantityGrain: 1, Signer: "s"}).ValidateBasic(); err == nil { t.Error("bad-side MsgPlaceSecondaryOrder should fail ValidateBasic") } if err := (&btypes.MsgPlaceSecondaryOrder{OrderID: "x", BondID: "b", Side: btypes.OrderBuy, QuantityGrain: 0, Signer: "s"}).ValidateBasic(); err == nil { t.Error("zero-quantity MsgPlaceSecondaryOrder should fail ValidateBasic") } // MsgCancelSecondaryOrder if err := (&btypes.MsgCancelSecondaryOrder{}).ValidateBasic(); err == nil { t.Error("empty MsgCancelSecondaryOrder should fail ValidateBasic") } // MsgMatchSecondaryOrder if err := (&btypes.MsgMatchSecondaryOrder{}).ValidateBasic(); err == nil { t.Error("empty MsgMatchSecondaryOrder should fail ValidateBasic") } if err := (&btypes.MsgMatchSecondaryOrder{IncomingOrderID: "x", BondID: "b", Side: "Bogus", QuantityGrain: 1, Signer: "s"}).ValidateBasic(); err == nil { t.Error("bad-side MsgMatchSecondaryOrder should fail ValidateBasic") } } // --- Keeper accessors (coverage) -------------------------------------------- // TestKeeperAccessors exercises the exported Keeper accessors that the // simtest above does not directly hit (AllBonds, AllGrowthBonds, // AllRestingOrders empty paths; SetStandKeeper) to push coverage >=80%. func TestKeeperAccessors(t *testing.T) { ctx, sk, k := newSimtestContext(t) _ = sk // Empty-store accessors return empty (not nil) slices. if got := k.AllBonds(ctx); len(got) != 0 { t.Errorf("AllBonds empty = %d, want 0", len(got)) } if got := k.AllGrowthBonds(ctx); len(got) != 0 { t.Errorf("AllGrowthBonds empty = %d, want 0", len(got)) } if got := k.AllRestingOrders(ctx); len(got) != 0 { t.Errorf("AllRestingOrders empty = %d, want 0", len(got)) } // Populate + read back. srv := keeper.NewMsgServerImpl(k) _, _ = srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "acc-b", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) _, _ = srv.IssueGrowthBond(ctx, &btypes.MsgIssueGrowthBond{ BondID: "acc-gb", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, GrowthRateBps: 100, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) if got := k.AllBonds(ctx); len(got) != 1 { t.Errorf("AllBonds = %d, want 1", len(got)) } if got := k.AllGrowthBonds(ctx); len(got) != 1 { t.Errorf("AllGrowthBonds = %d, want 1", len(got)) } // Marshal-error path on GetBond (corrupt bytes in store). // Use the ctx's existing KVStore (the mounted store key) — creating a // new store key here would panic (not mounted on the multi-store). rawStore := ctx.KVStore(k.StoreKey()) rawStore.Set([]byte("bond/corrupt"), []byte("not-json")) if _, ok := k.GetBond(ctx, "corrupt"); ok { t.Error("GetBond on corrupt bytes should return false") } // Marshal-error path on GetGrowthBond (corrupt bytes). rawStore.Set([]byte("growth/corrupt-gb"), []byte("not-json")) if _, ok := k.GetGrowthBond(ctx, "corrupt-gb"); ok { t.Error("GetGrowthBond on corrupt bytes should return false") } // Marshal-error path on GetRestingOrder (corrupt bytes). rawStore.Set([]byte("order/corrupt-order"), []byte("not-json")) if _, ok := k.GetRestingOrder(ctx, "corrupt-order"); ok { t.Error("GetRestingOrder on corrupt bytes should return false") } // SetStandKeeper post-construction wiring coverage. k.SetStandKeeper(nil) } // --- UnwrapCtx panic (coverage) --------------------------------------------- // TestUnwrapCtxPanic asserts unwrapCtx panics on a non-sdk.Context value. func TestUnwrapCtxPanic(t *testing.T) { defer func() { if r := recover(); r == nil { t.Error("unwrapCtx on non-sdk.Context should panic") } }() _, _ = keeper.NewMsgServerImpl(keeper.Keeper{}).IssueBond("not-a-ctx", &btypes.MsgIssueBond{BondID: "x", IssuerStandID: "s", PrincipalGrain: 1, CouponBps: 500, Signer: "s"}) } // --- IssueGrowthBond idempotency + non-existent Stand ------------------------ // TestIssueGrowthBondIdempotentReject asserts issuing the same growth-bond-id // twice REJECTS the second. func TestIssueGrowthBondIdempotentReject(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, err := srv.IssueGrowthBond(ctx, &btypes.MsgIssueGrowthBond{ BondID: "gb-dup", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, GrowthRateBps: 100, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) if err != nil { t.Fatalf("first IssueGrowthBond: %v", err) } _, err = srv.IssueGrowthBond(ctx, &btypes.MsgIssueGrowthBond{ BondID: "gb-dup", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 600, GrowthRateBps: 100, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) if err == nil { t.Error("second IssueGrowthBond on same id should be REJECTED") } } // TestIssueGrowthBondNonExistentStandRejected asserts a non-existent Stand // REJECTS the GrowthBond issuance. func TestIssueGrowthBondNonExistentStandRejected(t *testing.T) { ctx, sk, k := newSimtestContext(t) sk.exists = map[string]bool{"stand-1": false} sk.existsAll = false srv := keeper.NewMsgServerImpl(k) _, err := srv.IssueGrowthBond(ctx, &btypes.MsgIssueGrowthBond{ BondID: "gb-stand", IssuerStandID: "no-such-stand", PrincipalGrain: 1_000_000, CouponBps: 500, GrowthRateBps: 100, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) if err == nil { t.Error("IssueGrowthBond on non-existent Stand should be REJECTED") } } // --- Sell taker against Buy resting orders (coverage of the Sell side) ------ // TestSellTakerMatchesBuyResting asserts a Sell taker matches against Buy // resting orders (the opposite side of the Buy-taker tests above). func TestSellTakerMatchesBuyResting(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, _ = srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b70", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) // Rest a Buy order at 9500 (implied coupon 500, in-band). A Buy bid is // willing to pay UP TO 9500; a Sell taker at 9500 matches. _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "buy-rest", BondID: "b70", Side: btypes.OrderBuy, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "h1", Signer: "h1", }) // Sell taker at 9500 (matches the Buy bid). resp, err := srv.MatchSecondaryOrder(ctx, &btypes.MsgMatchSecondaryOrder{ IncomingOrderID: "sell-taker", BondID: "b70", Side: btypes.OrderSell, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "hb", Signer: "hb", }) if err != nil { t.Fatalf("MatchSecondaryOrder Sell taker: %v", err) } if resp.FilledQuantityGrain != 100 { t.Errorf("FilledQuantityGrain = %d, want 100", resp.FilledQuantityGrain) } } // TestSellTakerNoCross asserts a Sell taker whose price does not cross the // Buy resting order results in filled 0. func TestSellTakerNoCross(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, _ = srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b71", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) // Rest a Buy at 9400 (bid — willing to pay up to 9400). _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "buy-9400", BondID: "b71", Side: btypes.OrderBuy, PriceBps: 9400, QuantityGrain: 100, HolderReachID: "h1", Signer: "h1", }) // Sell taker at 9500 (above the Buy bid — no cross; the seller wants more // than the buyer bids). resp, err := srv.MatchSecondaryOrder(ctx, &btypes.MsgMatchSecondaryOrder{ IncomingOrderID: "sell-taker", BondID: "b71", Side: btypes.OrderSell, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "hb", Signer: "hb", }) if err != nil { t.Fatalf("MatchSecondaryOrder Sell taker no-cross: %v", err) } if resp.FilledQuantityGrain != 0 { t.Errorf("FilledQuantityGrain = %d, want 0 (no cross)", resp.FilledQuantityGrain) } } // --- Multiple matches in one taker (coverage) -------------------------------- // TestMatchTakerMultipleResting asserts a taker matches against multiple // resting orders (filling against the best price first, then the next). func TestMatchTakerMultipleResting(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, _ = srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b80", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) // Rest two Sell orders: one at 9400 (implied coupon 600, in-band) for 50, // and one at 9500 (implied coupon 500, in-band) for 50. _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "sell-9400", BondID: "b80", Side: btypes.OrderSell, PriceBps: 9400, QuantityGrain: 50, HolderReachID: "h1", Signer: "h1", }) _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "sell-9500", BondID: "b80", Side: btypes.OrderSell, PriceBps: 9500, QuantityGrain: 50, HolderReachID: "h2", Signer: "h2", }) // Buy taker at 9500 for 100 — fills 50 at 9400 (best price, first) + 50 // at 9500 (next). Total filled = 100. resp, err := srv.MatchSecondaryOrder(ctx, &btypes.MsgMatchSecondaryOrder{ IncomingOrderID: "buy-multi", BondID: "b80", Side: btypes.OrderBuy, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "hb", Signer: "hb", }) if err != nil { t.Fatalf("MatchSecondaryOrder multi: %v", err) } if resp.FilledQuantityGrain != 100 { t.Errorf("FilledQuantityGrain = %d, want 100", resp.FilledQuantityGrain) } // Two match events emitted (one per resting fill). if got := eventCount(ctx, "bond.match"); got != 2 { t.Errorf("bond.match events = %d, want 2 (one per resting fill)", got) } // Both resting orders are removed (Filled). if _, ok := k.GetRestingOrder(ctx, "sell-9400"); ok { t.Error("sell-9400 should be removed (filled)") } if _, ok := k.GetRestingOrder(ctx, "sell-9500"); ok { t.Error("sell-9500 should be removed (filled)") } } // --- D-063 reject advances to no further resting (fails closed) -------------- // TestMatchAboveCapRejectStopsMatching asserts a D-063 REJECT on the best // resting order STOPS matching (fails closed — the taker does not advance to // the next resting order even if it is in-band). This is the mission-lock- // true choice: the 8% cap is a hard invariant. func TestMatchAboveCapRejectStopsMatching(t *testing.T) { ctx, _, k := newSimtestContext(t) srv := keeper.NewMsgServerImpl(k) _, _ = srv.IssueBond(ctx, &btypes.MsgIssueBond{ BondID: "b90", IssuerStandID: "stand-1", PrincipalGrain: 1_000_000, CouponBps: 500, TermDays: 365, IssuedAt: 1000, Maturity: 1365, Signer: "stand-1", }) // Rest a Sell at 9000 (implied coupon 1000, ABOVE cap) — the BEST price // for a Buy taker (lowest Sell price). _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "sell-above", BondID: "b90", Side: btypes.OrderSell, PriceBps: 9000, QuantityGrain: 50, HolderReachID: "h1", Signer: "h1", }) // Rest a Sell at 9500 (implied coupon 500, in-band) — the WORSE price. _, _ = srv.PlaceSecondaryOrder(ctx, &btypes.MsgPlaceSecondaryOrder{ OrderID: "sell-inband", BondID: "b90", Side: btypes.OrderSell, PriceBps: 9500, QuantityGrain: 50, HolderReachID: "h2", Signer: "h2", }) // Buy taker at 9500 for 100 — the best resting (9000) is ABOVE cap -> // REJECTED (fails closed). The taker does NOT advance to the in-band // 9500 order. resp, err := srv.MatchSecondaryOrder(ctx, &btypes.MsgMatchSecondaryOrder{ IncomingOrderID: "buy-reject", BondID: "b90", Side: btypes.OrderBuy, PriceBps: 9500, QuantityGrain: 100, HolderReachID: "hb", Signer: "hb", }) if err == nil { t.Error("MatchSecondaryOrder with above-cap best resting should be REJECTED (D-063)") } if !resp.Rejected { t.Error("Rejected = false, want true (D-063 fails closed on the best resting)") } // The in-band 9500 order is UNTOUCHED (fails closed — no advance). ro, ok := k.GetRestingOrder(ctx, "sell-inband") if !ok { t.Fatal("sell-inband should STAY on the book (D-063 fails closed — no advance)") } if ro.RemainingQuantityGrain != 50 { t.Errorf("sell-inband RemainingQuantityGrain = %d, want 50 (untouched)", ro.RemainingQuantityGrain) } }