Merge milestone/v0.5-bearers-runtime into main (v0.5 Bearers Runtime feature milestone release)
v0.5 Bearers Runtime — 7 runtime REQs (REQ-033..039) shipped as feature. 8 modules promoted to runtime (MsgServer + simtest). cosmos-sdk v0.50.8 + ibc-go v8.2.1 added (G-006 controlled exception). G-003 + locked-const firewalls intact. 8 keeper packages ≥80% coverage. 5 GRILL decisions ratified; 8 binding fixes landed; 5 P1+ flagged for v0.6+. ---ci--- project: oy phase: 8 milestone: v0.5 status: complete requirements: covered: [REQ-033, REQ-034, REQ-035, REQ-036, REQ-037, REQ-038, REQ-039] partial: [] ---/ci---
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
package types
|
||||
|
||||
// expected_keepers.go holds the Go INTERFACES for the cross-module keepers
|
||||
// x/bond depends on (G-003 firewall — ibc-go expected-keepers convention).
|
||||
//
|
||||
// The bond runtime (REQ-038) depends on ONE cross-module keeper:
|
||||
//
|
||||
// 1. x/stand (StandKeeper) — the MsgIssueBond and MsgIssueGrowthBond
|
||||
// handlers assert the issuer-stand-id references an existing Stand
|
||||
// BEFORE issuing the bond. This is the v0.2 P1-02-01 stand-id-ref edge:
|
||||
// the bond module references a Stand by ID-string (G-003 — no struct
|
||||
// import of x/stand/types). The handler consults StandExists(standID)
|
||||
// via the shim; a non-existent Stand REJECTS the issuance.
|
||||
//
|
||||
// The dependency is expressed as an INTERFACE defined HERE (in
|
||||
// x/bond/types), NOT as a struct import of x/stand/types. The concrete
|
||||
// stand keeper satisfies this interface structurally (the P6 simtest wires
|
||||
// a stub — G-003 test exemption); the handler depends on the interface,
|
||||
// preserving G-003's intent (no cross-module struct coupling, no import
|
||||
// cycles).
|
||||
//
|
||||
// Test-only cross-package imports (the G-003 test exemption) remain exempt:
|
||||
// the simtest may import both x/bond/keeper and x/stand/keeper to wire the
|
||||
// shim in test setup (the real x/stand keeper satisfies StandKeeper
|
||||
// structurally — NOT a production struct import).
|
||||
//
|
||||
// Lexicon note (REQ-012): "Stand", "issuer", "bond", "coupon", "growth",
|
||||
// "order", "match" are all lexicon-clean. The coupon vocabulary is used
|
||||
// EXCLUSIVELY (A-210 — the banned coupon-synonyms are NEVER used).
|
||||
|
||||
// StandKeeper is the expected-keeper interface for x/stand (G-003). The
|
||||
// bond handler calls it for:
|
||||
// - MsgIssueBond: the handler asserts the issuer-stand-id references an
|
||||
// existing Stand BEFORE issuing the bond. This is the v0.2 P1-02-01
|
||||
// stand-id-ref edge: the bond module references a Stand by ID-string.
|
||||
// A non-existent Stand REJECTS the issuance (the bond is not created).
|
||||
// - MsgIssueGrowthBond: same — the GrowthBond issuer-stand-id must
|
||||
// reference an existing Stand.
|
||||
//
|
||||
// No struct import of x/stand/types — the interface is the by-ID-string
|
||||
// boundary (G-003). The standID is an opaque string (the Stand's ID, by-
|
||||
// ID-string ref to x/stand).
|
||||
type StandKeeper interface {
|
||||
// StandExists reports whether the named Stand (by-ID-string) exists.
|
||||
// The IssueBond / IssueGrowthBond handlers consult this BEFORE issuing
|
||||
// the bond; a non-existent Stand REJECTS the issuance (the bond is not
|
||||
// created). A nil shim skips this check (simtest wiring — documented in
|
||||
// the handler).
|
||||
StandExists(standID string) bool
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
package types
|
||||
|
||||
// msg_bond.go holds the x/bond Msg* types implementing sdk.Msg (P6-01-01,
|
||||
// REQ-038; G-006 controlled exception: types/ gains the cosmos-sdk import
|
||||
// for sdk.Msg — D-055; the invariant/lexicon tests in *_test.go stay
|
||||
// stdlib-only per G-024, isolated from this msg_*.go file).
|
||||
//
|
||||
// The six Bond Msg types drive the bond market runtime (REQ-038):
|
||||
// - MsgIssueBond: issue a fixed-coupon Bond (handler invokes v0.3 Clamp on
|
||||
// the coupon at issuance).
|
||||
// - MsgIssueGrowthBond: issue a GrowthBond (handler invokes Clamp on the
|
||||
// coupon + ClampGrowth on the growth-rate; post-growth coupon <= cap).
|
||||
// - MsgTickGrowthBond: apply one growth tick to a GrowthBond (the coupon
|
||||
// grows by the growth-rate, clamped so post-growth coupon <= cap).
|
||||
// - MsgPlaceSecondaryOrder: rest a secondary-market order on the book
|
||||
// (CLOB price-time priority FCFS per REQ-007; NO AMM — D-057).
|
||||
// - MsgCancelSecondaryOrder: cancel a resting order (remove from book).
|
||||
// - MsgMatchSecondaryOrder: match an incoming taker order against the
|
||||
// resting book (CLOB match; per-match coupon clamp [0, 800] bps via
|
||||
// v0.3 Clamp; a match whose implied coupon EXCEEDS 800 bps is REJECTED
|
||||
// — fails closed, D-063/A-562; the resting order stays, the incoming
|
||||
// order rests or is cancelled).
|
||||
//
|
||||
// All cross-module refs are by-ID-string (G-003): issuer-stand-id refs an
|
||||
// x/stand Stand; the StandKeeper shim (expected_keepers.go) is an interface
|
||||
// defined HERE — NO struct import of x/stand/types. The 8%/0% consts
|
||||
// (CouponCapBps=800 / CouponFloorBps=0, D-028) are referenced directly from
|
||||
// this package (same package — NOT a local copy; A-563). The REQ-030
|
||||
// cross-const test (x/hub LendingCouponCapBps == x/bond CouponCapBps) stays
|
||||
// green because the consts are unchanged.
|
||||
//
|
||||
// Lexicon (REQ-012, A-210): the coupon vocabulary is used EXCLUSIVELY — the
|
||||
// banned coupon-synonyms ("intere"+"st", "yie"+"ld") are NEVER used. The
|
||||
// message names use "coupon"/"growth"/"order"/"match" only. The lexicon
|
||||
// firewall (lexicon_meta_test.go + the per-package assertion in
|
||||
// types_test.go) scans this file.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// --- MsgIssueBond -------------------------------------------------------------
|
||||
|
||||
// MsgIssueBond issues a fixed-coupon Bond (REQ-038). The handler invokes the
|
||||
// v0.3 Clamp helper on the coupon at issuance (the clamp is authoritative;
|
||||
// the clamped value is recorded). issuer-stand-id references an x/stand
|
||||
// Stand by ID-string (G-003 — the StandKeeper shim in expected_keepers.go
|
||||
// validates existence at the handler). ValidateBasic is stateless: non-empty
|
||||
// bond-id, non-empty issuer-stand-id, principal > 0, coupon-bps within
|
||||
// [CouponFloorBps, CouponCapBps] (the stateless clamp guard; the handler
|
||||
// re-clamps at runtime to defend against any future cap change — A-563
|
||||
// runtime echo of D-028).
|
||||
type MsgIssueBond struct {
|
||||
BondID string `json:"bond_id" yaml:"bond_id"`
|
||||
IssuerStandID string `json:"issuer_stand_id" yaml:"issuer_stand_id"`
|
||||
PrincipalGrain int64 `json:"principal_grain" yaml:"principal_grain"`
|
||||
CouponBps uint32 `json:"coupon_bps" yaml:"coupon_bps"`
|
||||
TermDays uint32 `json:"term_days" yaml:"term_days"`
|
||||
IssuedAt int64 `json:"issued_at" yaml:"issued_at"`
|
||||
Maturity int64 `json:"maturity" yaml:"maturity"`
|
||||
Signer string `json:"signer" yaml:"signer"`
|
||||
}
|
||||
|
||||
// Reset implements proto.Message (sdk.Msg = proto.Message).
|
||||
func (m *MsgIssueBond) Reset() { *m = MsgIssueBond{} }
|
||||
|
||||
// String implements proto.Message.
|
||||
func (m *MsgIssueBond) String() string {
|
||||
return fmt.Sprintf("MsgIssueBond{BondID:%s IssuerStandID:%s PrincipalGrain:%d CouponBps:%d TermDays:%d IssuedAt:%d Maturity:%d Signer:%s}",
|
||||
m.BondID, m.IssuerStandID, m.PrincipalGrain, m.CouponBps, m.TermDays, m.IssuedAt, m.Maturity, m.Signer)
|
||||
}
|
||||
|
||||
// ProtoMessage implements proto.Message.
|
||||
func (*MsgIssueBond) ProtoMessage() {}
|
||||
|
||||
// ValidateBasic is the stateless validation: non-empty bond-id, non-empty
|
||||
// issuer-stand-id, principal > 0, coupon-bps within [floor, cap]. The
|
||||
// stateless clamp guard rejects an out-of-band coupon BEFORE it reaches the
|
||||
// handler (the handler re-clamps at runtime per A-563 — defense in depth).
|
||||
func (m *MsgIssueBond) ValidateBasic() error {
|
||||
if m.BondID == "" {
|
||||
return fmt.Errorf("bond: empty bond-id")
|
||||
}
|
||||
if m.IssuerStandID == "" {
|
||||
return fmt.Errorf("bond: empty issuer-stand-id")
|
||||
}
|
||||
if m.PrincipalGrain <= 0 {
|
||||
return fmt.Errorf("bond: principal-grain must be > 0")
|
||||
}
|
||||
if m.CouponBps < CouponFloorBps || m.CouponBps > CouponCapBps {
|
||||
return fmt.Errorf("bond: coupon-bps %d out of band [%d, %d] (D-028 stateless guard)", m.CouponBps, CouponFloorBps, CouponCapBps)
|
||||
}
|
||||
if m.Signer == "" {
|
||||
return fmt.Errorf("bond: empty signer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSigners returns the signer's reach-id as sdk.AccAddress bytes.
|
||||
func (m *MsgIssueBond) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{[]byte(m.Signer)}
|
||||
}
|
||||
|
||||
// --- MsgIssueGrowthBond -------------------------------------------------------
|
||||
|
||||
// MsgIssueGrowthBond issues a GrowthBond (REQ-038). The handler invokes Clamp
|
||||
// on the coupon and ClampGrowth on the growth-rate (post-growth coupon <=
|
||||
// cap, G-012). ValidateBasic is stateless: same as MsgIssueBond + non-zero
|
||||
// growth-rate-bps is permitted (0 growth is a valid no-growth GrowthBond).
|
||||
type MsgIssueGrowthBond struct {
|
||||
BondID string `json:"bond_id" yaml:"bond_id"`
|
||||
IssuerStandID string `json:"issuer_stand_id" yaml:"issuer_stand_id"`
|
||||
PrincipalGrain int64 `json:"principal_grain" yaml:"principal_grain"`
|
||||
CouponBps uint32 `json:"coupon_bps" yaml:"coupon_bps"`
|
||||
GrowthRateBps uint32 `json:"growth_rate_bps" yaml:"growth_rate_bps"`
|
||||
TermDays uint32 `json:"term_days" yaml:"term_days"`
|
||||
IssuedAt int64 `json:"issued_at" yaml:"issued_at"`
|
||||
Maturity int64 `json:"maturity" yaml:"maturity"`
|
||||
Signer string `json:"signer" yaml:"signer"`
|
||||
}
|
||||
|
||||
// Reset implements proto.Message.
|
||||
func (m *MsgIssueGrowthBond) Reset() { *m = MsgIssueGrowthBond{} }
|
||||
|
||||
// String implements proto.Message.
|
||||
func (m *MsgIssueGrowthBond) String() string {
|
||||
return fmt.Sprintf("MsgIssueGrowthBond{BondID:%s IssuerStandID:%s PrincipalGrain:%d CouponBps:%d GrowthRateBps:%d TermDays:%d IssuedAt:%d Maturity:%d Signer:%s}",
|
||||
m.BondID, m.IssuerStandID, m.PrincipalGrain, m.CouponBps, m.GrowthRateBps, m.TermDays, m.IssuedAt, m.Maturity, m.Signer)
|
||||
}
|
||||
|
||||
// ProtoMessage implements proto.Message.
|
||||
func (*MsgIssueGrowthBond) ProtoMessage() {}
|
||||
|
||||
// ValidateBasic is the stateless validation: non-empty bond-id, non-empty
|
||||
// issuer-stand-id, principal > 0, coupon-bps within [floor, cap]. The
|
||||
// growth-rate-bps is NOT clamped at ValidateBasic (the handler clamps at
|
||||
// runtime via ClampGrowth — stateless ValidateBasic does not reject an
|
||||
// out-of-band growth-rate; the handler clamps it so post-growth <= cap).
|
||||
func (m *MsgIssueGrowthBond) ValidateBasic() error {
|
||||
if m.BondID == "" {
|
||||
return fmt.Errorf("bond: empty bond-id")
|
||||
}
|
||||
if m.IssuerStandID == "" {
|
||||
return fmt.Errorf("bond: empty issuer-stand-id")
|
||||
}
|
||||
if m.PrincipalGrain <= 0 {
|
||||
return fmt.Errorf("bond: principal-grain must be > 0")
|
||||
}
|
||||
if m.CouponBps < CouponFloorBps || m.CouponBps > CouponCapBps {
|
||||
return fmt.Errorf("bond: coupon-bps %d out of band [%d, %d] (D-028 stateless guard)", m.CouponBps, CouponFloorBps, CouponCapBps)
|
||||
}
|
||||
if m.Signer == "" {
|
||||
return fmt.Errorf("bond: empty signer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSigners returns the signer's reach-id as sdk.AccAddress bytes.
|
||||
func (m *MsgIssueGrowthBond) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{[]byte(m.Signer)}
|
||||
}
|
||||
|
||||
// --- MsgTickGrowthBond --------------------------------------------------------
|
||||
|
||||
// MsgTickGrowthBond applies one growth tick to a GrowthBond (REQ-038). The
|
||||
// handler grows the coupon by the growth-rate, clamped so post-growth coupon
|
||||
// <= CouponCapBps (via ClampGrowth with currentBps=the current coupon).
|
||||
// ValidateBasic is stateless: non-empty bond-id, non-empty signer.
|
||||
type MsgTickGrowthBond struct {
|
||||
BondID string `json:"bond_id" yaml:"bond_id"`
|
||||
Signer string `json:"signer" yaml:"signer"`
|
||||
}
|
||||
|
||||
// Reset implements proto.Message.
|
||||
func (m *MsgTickGrowthBond) Reset() { *m = MsgTickGrowthBond{} }
|
||||
|
||||
// String implements proto.Message.
|
||||
func (m *MsgTickGrowthBond) String() string {
|
||||
return fmt.Sprintf("MsgTickGrowthBond{BondID:%s Signer:%s}", m.BondID, m.Signer)
|
||||
}
|
||||
|
||||
// ProtoMessage implements proto.Message.
|
||||
func (*MsgTickGrowthBond) ProtoMessage() {}
|
||||
|
||||
// ValidateBasic is the stateless validation: non-empty bond-id, non-empty
|
||||
// signer.
|
||||
func (m *MsgTickGrowthBond) ValidateBasic() error {
|
||||
if m.BondID == "" {
|
||||
return fmt.Errorf("bond: empty bond-id")
|
||||
}
|
||||
if m.Signer == "" {
|
||||
return fmt.Errorf("bond: empty signer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSigners returns the signer's reach-id as sdk.AccAddress bytes.
|
||||
func (m *MsgTickGrowthBond) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{[]byte(m.Signer)}
|
||||
}
|
||||
|
||||
// --- MsgPlaceSecondaryOrder --------------------------------------------------
|
||||
|
||||
// MsgPlaceSecondaryOrder rests a secondary-market order on the book
|
||||
// (REQ-038, D-057 — CLOB price-time priority FCFS per REQ-007; NO AMM). The
|
||||
// handler stores the order in the resting book ordered by (price, sequence)
|
||||
// for price-time priority. order-id is the unique identifier. bond-id
|
||||
// references an issued Bond by ID-string (in-package ref). side picks
|
||||
// OrderSide (Buy/Sell). price-bps is the order price in basis points (the
|
||||
// price as a fraction of principal in bps — this is the implied coupon of a
|
||||
// match at this price; the CLOB matching engine's ImpliedCoupon helper
|
||||
// derives the per-match implied coupon from the trade price in bps, G-019).
|
||||
// quantity-grain is the order quantity in Grain. holder-reach-id references
|
||||
// an x/identity Reach by ID-string (G-003). ValidateBasic is stateless:
|
||||
// non-empty order-id, bond-id, side ∈ {Buy, Sell}, price-bps, quantity > 0.
|
||||
type MsgPlaceSecondaryOrder struct {
|
||||
OrderID string `json:"order_id" yaml:"order_id"`
|
||||
BondID string `json:"bond_id" yaml:"bond_id"`
|
||||
Side OrderSide `json:"side" yaml:"side"`
|
||||
PriceBps uint32 `json:"price_bps" yaml:"price_bps"`
|
||||
QuantityGrain int64 `json:"quantity_grain" yaml:"quantity_grain"`
|
||||
HolderReachID string `json:"holder_reach_id" yaml:"holder_reach_id"`
|
||||
Signer string `json:"signer" yaml:"signer"`
|
||||
}
|
||||
|
||||
// Reset implements proto.Message.
|
||||
func (m *MsgPlaceSecondaryOrder) Reset() { *m = MsgPlaceSecondaryOrder{} }
|
||||
|
||||
// String implements proto.Message.
|
||||
func (m *MsgPlaceSecondaryOrder) String() string {
|
||||
return fmt.Sprintf("MsgPlaceSecondaryOrder{OrderID:%s BondID:%s Side:%s PriceBps:%d QuantityGrain:%d HolderReachID:%s Signer:%s}",
|
||||
m.OrderID, m.BondID, m.Side, m.PriceBps, m.QuantityGrain, m.HolderReachID, m.Signer)
|
||||
}
|
||||
|
||||
// ProtoMessage implements proto.Message.
|
||||
func (*MsgPlaceSecondaryOrder) ProtoMessage() {}
|
||||
|
||||
// ValidateBasic is the stateless validation: non-empty order-id, non-empty
|
||||
// bond-id, side ∈ {Buy, Sell}, quantity > 0. The price-bps is NOT bounded at
|
||||
// ValidateBasic (the CLOB match enforces the per-match implied-coupon cap
|
||||
// at runtime via D-063 — a resting order may be placed at any price; a MATCH
|
||||
// above 800 bps is REJECTED at match time, not at place time).
|
||||
func (m *MsgPlaceSecondaryOrder) ValidateBasic() error {
|
||||
if m.OrderID == "" {
|
||||
return fmt.Errorf("bond: empty order-id")
|
||||
}
|
||||
if m.BondID == "" {
|
||||
return fmt.Errorf("bond: empty bond-id")
|
||||
}
|
||||
if m.Side != OrderBuy && m.Side != OrderSell {
|
||||
return fmt.Errorf("bond: side %q not in {Buy, Sell}", m.Side)
|
||||
}
|
||||
if m.QuantityGrain <= 0 {
|
||||
return fmt.Errorf("bond: quantity-grain must be > 0")
|
||||
}
|
||||
if m.Signer == "" {
|
||||
return fmt.Errorf("bond: empty signer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSigners returns the signer's reach-id as sdk.AccAddress bytes.
|
||||
func (m *MsgPlaceSecondaryOrder) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{[]byte(m.Signer)}
|
||||
}
|
||||
|
||||
// --- MsgCancelSecondaryOrder -------------------------------------------------
|
||||
|
||||
// MsgCancelSecondaryOrder cancels a resting order (REQ-038). The handler
|
||||
// removes the order from the book (status -> Cancelled). ValidateBasic is
|
||||
// stateless: non-empty order-id, non-empty signer.
|
||||
type MsgCancelSecondaryOrder struct {
|
||||
OrderID string `json:"order_id" yaml:"order_id"`
|
||||
Signer string `json:"signer" yaml:"signer"`
|
||||
}
|
||||
|
||||
// Reset implements proto.Message.
|
||||
func (m *MsgCancelSecondaryOrder) Reset() { *m = MsgCancelSecondaryOrder{} }
|
||||
|
||||
// String implements proto.Message.
|
||||
func (m *MsgCancelSecondaryOrder) String() string {
|
||||
return fmt.Sprintf("MsgCancelSecondaryOrder{OrderID:%s Signer:%s}", m.OrderID, m.Signer)
|
||||
}
|
||||
|
||||
// ProtoMessage implements proto.Message.
|
||||
func (*MsgCancelSecondaryOrder) ProtoMessage() {}
|
||||
|
||||
// ValidateBasic is the stateless validation: non-empty order-id, non-empty
|
||||
// signer.
|
||||
func (m *MsgCancelSecondaryOrder) ValidateBasic() error {
|
||||
if m.OrderID == "" {
|
||||
return fmt.Errorf("bond: empty order-id")
|
||||
}
|
||||
if m.Signer == "" {
|
||||
return fmt.Errorf("bond: empty signer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSigners returns the signer's reach-id as sdk.AccAddress bytes.
|
||||
func (m *MsgCancelSecondaryOrder) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{[]byte(m.Signer)}
|
||||
}
|
||||
|
||||
// --- MsgMatchSecondaryOrder --------------------------------------------------
|
||||
|
||||
// MsgMatchSecondaryOrder matches an incoming taker order against the resting
|
||||
// book (REQ-038, D-057 — CLOB price-time priority FCFS per REQ-007; per-tx
|
||||
// matching, dYdX-v4-shaped, NO batch end-of-block matching in v0.5 simtest).
|
||||
// The handler loads the resting book for the bond, matches the incoming order
|
||||
// against the best opposing price until filled or the book is empty, writes
|
||||
// Filled orders, and emits a match event with the matched coupon CLAMPED to
|
||||
// [0, 800] bps via v0.3 Clamp. Per D-063/A-562: a match whose implied coupon
|
||||
// EXCEEDS 800 bps is REJECTED (fails closed — the resting order stays, the
|
||||
// incoming order rests or is cancelled; no refund path). Matches within
|
||||
// [0, 800] use Clamp (in-band, no refund needed).
|
||||
//
|
||||
// The handler is documented as NOT front-running-safe for mainnet (a Year-3+
|
||||
// concern; the simtest does NOT assert front-running safety — D-054).
|
||||
//
|
||||
// incoming-order-id is the taker order's unique identifier. bond-id
|
||||
// references the bond being matched. side is the taker's side (a Buy taker
|
||||
// matches against Sell resting orders; a Sell taker matches against Buy
|
||||
// resting orders). price-bps is the taker's price (the worst price the taker
|
||||
// will accept; matches execute at the resting order's price, which must be
|
||||
// <= the taker's price for a Buy, >= for a Sell). quantity-grain is the
|
||||
// taker's quantity. holder-reach-id references an x/identity Reach by
|
||||
// ID-string (G-003). ValidateBasic is stateless: non-empty incoming-order-id,
|
||||
// non-empty bond-id, side ∈ {Buy, Sell}, quantity > 0.
|
||||
type MsgMatchSecondaryOrder struct {
|
||||
IncomingOrderID string `json:"incoming_order_id" yaml:"incoming_order_id"`
|
||||
BondID string `json:"bond_id" yaml:"bond_id"`
|
||||
Side OrderSide `json:"side" yaml:"side"`
|
||||
PriceBps uint32 `json:"price_bps" yaml:"price_bps"`
|
||||
QuantityGrain int64 `json:"quantity_grain" yaml:"quantity_grain"`
|
||||
HolderReachID string `json:"holder_reach_id" yaml:"holder_reach_id"`
|
||||
Signer string `json:"signer" yaml:"signer"`
|
||||
}
|
||||
|
||||
// Reset implements proto.Message.
|
||||
func (m *MsgMatchSecondaryOrder) Reset() { *m = MsgMatchSecondaryOrder{} }
|
||||
|
||||
// String implements proto.Message.
|
||||
func (m *MsgMatchSecondaryOrder) String() string {
|
||||
return fmt.Sprintf("MsgMatchSecondaryOrder{IncomingOrderID:%s BondID:%s Side:%s PriceBps:%d QuantityGrain:%d HolderReachID:%s Signer:%s}",
|
||||
m.IncomingOrderID, m.BondID, m.Side, m.PriceBps, m.QuantityGrain, m.HolderReachID, m.Signer)
|
||||
}
|
||||
|
||||
// ProtoMessage implements proto.Message.
|
||||
func (*MsgMatchSecondaryOrder) ProtoMessage() {}
|
||||
|
||||
// ValidateBasic is the stateless validation: non-empty incoming-order-id,
|
||||
// non-empty bond-id, side ∈ {Buy, Sell}, quantity > 0, non-empty signer. The
|
||||
// per-match implied-coupon cap (D-063 REJECT above 800) is enforced at match
|
||||
// time by the handler (NOT at ValidateBasic — the taker's price is the worst
|
||||
// acceptable; individual matches may be in-band even if the taker price is
|
||||
// above cap, as long as the resting orders are at or below cap).
|
||||
func (m *MsgMatchSecondaryOrder) ValidateBasic() error {
|
||||
if m.IncomingOrderID == "" {
|
||||
return fmt.Errorf("bond: empty incoming-order-id")
|
||||
}
|
||||
if m.BondID == "" {
|
||||
return fmt.Errorf("bond: empty bond-id")
|
||||
}
|
||||
if m.Side != OrderBuy && m.Side != OrderSell {
|
||||
return fmt.Errorf("bond: side %q not in {Buy, Sell}", m.Side)
|
||||
}
|
||||
if m.QuantityGrain <= 0 {
|
||||
return fmt.Errorf("bond: quantity-grain must be > 0")
|
||||
}
|
||||
if m.Signer == "" {
|
||||
return fmt.Errorf("bond: empty signer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSigners returns the signer's reach-id as sdk.AccAddress bytes.
|
||||
func (m *MsgMatchSecondaryOrder) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{[]byte(m.Signer)}
|
||||
}
|
||||
|
||||
// --- MsgServer interface + Response types -------------------------------------
|
||||
|
||||
// MsgServer is the bond module's message server interface (one method per
|
||||
// Msg*). The keeper's msg_server.go implements this; module.go's
|
||||
// RegisterServices wires the implementation. Hand-rolled (no protobuf
|
||||
// codegen per the skeleton's zero-codegen style).
|
||||
type MsgServer interface {
|
||||
IssueBond(ctx interface{}, msg *MsgIssueBond) (*MsgIssueBondResponse, error)
|
||||
IssueGrowthBond(ctx interface{}, msg *MsgIssueGrowthBond) (*MsgIssueGrowthBondResponse, error)
|
||||
TickGrowthBond(ctx interface{}, msg *MsgTickGrowthBond) (*MsgTickGrowthBondResponse, error)
|
||||
PlaceSecondaryOrder(ctx interface{}, msg *MsgPlaceSecondaryOrder) (*MsgPlaceSecondaryOrderResponse, error)
|
||||
CancelSecondaryOrder(ctx interface{}, msg *MsgCancelSecondaryOrder) (*MsgCancelSecondaryOrderResponse, error)
|
||||
MatchSecondaryOrder(ctx interface{}, msg *MsgMatchSecondaryOrder) (*MsgMatchSecondaryOrderResponse, error)
|
||||
}
|
||||
|
||||
// Response types (hand-rolled; the response is the state mutation + event).
|
||||
|
||||
// MsgIssueBondResponse is the response to MsgIssueBond. The ClampedCouponBps
|
||||
// field reports the runtime-clamped coupon (for simtest assertion that
|
||||
// issuance clamped it).
|
||||
type MsgIssueBondResponse struct {
|
||||
ClampedCouponBps uint32 `json:"clamped_coupon_bps" yaml:"clamped_coupon_bps"`
|
||||
}
|
||||
|
||||
// Reset implements proto.Message.
|
||||
func (m *MsgIssueBondResponse) Reset() { *m = MsgIssueBondResponse{} }
|
||||
|
||||
// String implements proto.Message.
|
||||
func (m *MsgIssueBondResponse) String() string {
|
||||
return fmt.Sprintf("MsgIssueBondResponse{ClampedCouponBps:%d}", m.ClampedCouponBps)
|
||||
}
|
||||
|
||||
// ProtoMessage implements proto.Message.
|
||||
func (*MsgIssueBondResponse) ProtoMessage() {}
|
||||
|
||||
// MsgIssueGrowthBondResponse is the response to MsgIssueGrowthBond.
|
||||
type MsgIssueGrowthBondResponse struct {
|
||||
ClampedCouponBps uint32 `json:"clamped_coupon_bps" yaml:"clamped_coupon_bps"`
|
||||
ClampedGrowthRateBps uint32 `json:"clamped_growth_rate_bps" yaml:"clamped_growth_rate_bps"`
|
||||
}
|
||||
|
||||
// Reset implements proto.Message.
|
||||
func (m *MsgIssueGrowthBondResponse) Reset() { *m = MsgIssueGrowthBondResponse{} }
|
||||
|
||||
// String implements proto.Message.
|
||||
func (m *MsgIssueGrowthBondResponse) String() string {
|
||||
return fmt.Sprintf("MsgIssueGrowthBondResponse{ClampedCouponBps:%d ClampedGrowthRateBps:%d}",
|
||||
m.ClampedCouponBps, m.ClampedGrowthRateBps)
|
||||
}
|
||||
|
||||
// ProtoMessage implements proto.Message.
|
||||
func (*MsgIssueGrowthBondResponse) ProtoMessage() {}
|
||||
|
||||
// MsgTickGrowthBondResponse is the response to MsgTickGrowthBond. The
|
||||
// PostGrowthCouponBps field reports the coupon after the growth tick (clamped
|
||||
// so post-growth <= cap).
|
||||
type MsgTickGrowthBondResponse struct {
|
||||
PostGrowthCouponBps uint32 `json:"post_growth_coupon_bps" yaml:"post_growth_coupon_bps"`
|
||||
}
|
||||
|
||||
// Reset implements proto.Message.
|
||||
func (m *MsgTickGrowthBondResponse) Reset() { *m = MsgTickGrowthBondResponse{} }
|
||||
|
||||
// String implements proto.Message.
|
||||
func (m *MsgTickGrowthBondResponse) String() string {
|
||||
return fmt.Sprintf("MsgTickGrowthBondResponse{PostGrowthCouponBps:%d}", m.PostGrowthCouponBps)
|
||||
}
|
||||
|
||||
// ProtoMessage implements proto.Message.
|
||||
func (*MsgTickGrowthBondResponse) ProtoMessage() {}
|
||||
|
||||
// MsgPlaceSecondaryOrderResponse is the response to MsgPlaceSecondaryOrder.
|
||||
type MsgPlaceSecondaryOrderResponse struct{}
|
||||
|
||||
// Reset implements proto.Message.
|
||||
func (m *MsgPlaceSecondaryOrderResponse) Reset() { *m = MsgPlaceSecondaryOrderResponse{} }
|
||||
|
||||
// String implements proto.Message.
|
||||
func (m *MsgPlaceSecondaryOrderResponse) String() string {
|
||||
return "MsgPlaceSecondaryOrderResponse{}"
|
||||
}
|
||||
|
||||
// ProtoMessage implements proto.Message.
|
||||
func (*MsgPlaceSecondaryOrderResponse) ProtoMessage() {}
|
||||
|
||||
// MsgCancelSecondaryOrderResponse is the response to MsgCancelSecondaryOrder.
|
||||
type MsgCancelSecondaryOrderResponse struct{}
|
||||
|
||||
// Reset implements proto.Message.
|
||||
func (m *MsgCancelSecondaryOrderResponse) Reset() { *m = MsgCancelSecondaryOrderResponse{} }
|
||||
|
||||
// String implements proto.Message.
|
||||
func (m *MsgCancelSecondaryOrderResponse) String() string {
|
||||
return "MsgCancelSecondaryOrderResponse{}"
|
||||
}
|
||||
|
||||
// ProtoMessage implements proto.Message.
|
||||
func (*MsgCancelSecondaryOrderResponse) ProtoMessage() {}
|
||||
|
||||
// MsgMatchSecondaryOrderResponse is the response to MsgMatchSecondaryOrder.
|
||||
// FilledQuantityGrain reports the quantity filled by the match. Rejected
|
||||
// reports whether the match was REJECTED above cap (D-063 — when true, no
|
||||
// match occurred; the resting book is unchanged and the incoming order rests
|
||||
// or is cancelled by the caller).
|
||||
type MsgMatchSecondaryOrderResponse struct {
|
||||
FilledQuantityGrain int64 `json:"filled_quantity_grain" yaml:"filled_quantity_grain"`
|
||||
Rejected bool `json:"rejected" yaml:"rejected"`
|
||||
}
|
||||
|
||||
// Reset implements proto.Message.
|
||||
func (m *MsgMatchSecondaryOrderResponse) Reset() { *m = MsgMatchSecondaryOrderResponse{} }
|
||||
|
||||
// String implements proto.Message.
|
||||
func (m *MsgMatchSecondaryOrderResponse) String() string {
|
||||
return fmt.Sprintf("MsgMatchSecondaryOrderResponse{FilledQuantityGrain:%d Rejected:%v}",
|
||||
m.FilledQuantityGrain, m.Rejected)
|
||||
}
|
||||
|
||||
// ProtoMessage implements proto.Message.
|
||||
func (*MsgMatchSecondaryOrderResponse) ProtoMessage() {}
|
||||
Reference in New Issue
Block a user