feat(bond,cover,standing): P4 MAB + Cover Claims Voucher + Shadow vouch

v0.7 P4 (REQ-054, REQ-055, REQ-060, REQ-063, D-080, D-089, D-090):

x/bond (Mutual Aid Bond): MAB struct (Bond anonymous embed) mirroring
GrowthBond; CouponDenom enum (CoverCall/MutualAidCredit/Bread-rejected);
MABIssuanceCeilingAnnualSurplusMultiple=3 locked const; ValidateMAB
rejects CouponDenomBread (FR-MAB-3 dual firewall); D-080 tagged streaming
(reserve_build_out); 4 handlers (IssueMAB with 3x ceiling check,
DebitMABProceeds with auto-Still on misuse, WitnessMABProceedsRelease
with Watcher quorum, WatcherAttestMAB); CoverKeeper reverse edge (D-089).

x/cover (Cover Claims Voucher + dissolution): CoverClaimsVoucher struct;
D-090(2) cold-start bond = max(10x avgCallSize, MinimumVoucherBond); 4
handlers (RegisterCoverClaimsVoucher, AdjudicateCoverCall with FR-CPCV-2
no self-adjudication, SlashCoverClaimsVoucher with cross-Pool bucket
drop, DissolveCoverPool with FR-MAB-4 waterfall Cover-Fee > MAB > Bread);
MAB holders have NO Voice (REQ-063).

x/standing (Shadow vouch): Vouch.IsShadow field; ShadowVouchWeightMultiplier
=0.5 locked const (REQ-060); GetVoucherWeight extended with isShadow param
(post-step 0.5x multiplier; all call sites updated); SlashReasonFraudulent
CoverCall const (REQ-055).

Coverage: bond/keeper 92.1%, cover/keeper 95.0%, standing/types 90.3%.
G-006/G-028 intact. go.mod/go.sum diff EMPTY. go vet clean. Lexicon green.

---ci---
project: oy
phase: 4
milestone: v0.7
status: execute
---/ci---
This commit is contained in:
2026-08-19 02:31:52 +00:00
parent bcae60666b
commit 9e7fc403f5
20 changed files with 3294 additions and 36 deletions
+78
View File
@@ -203,6 +203,84 @@ func (k Keeper) AllCoverCalls(ctx sdk.Context) []types.CoverCall {
return out
}
// --- P4: CoverClaimsVoucher store (REQ-055, D-090(2)) ------------------------
//
// The Voucher store is keyed by voucher-reach-id + pool-id (composite key)
// -> CoverClaimsVoucher. A Voucher is registered per-Pool; the composite key
// enforces idempotency (no duplicate Voucher for the same Pool). The
// GetAvgCallSize helper computes the average Cover Call amount for a Pool
// from the call/ store (returns 0 if no Calls — the D-090(2) cold-start
// case).
var voucherKeyPrefix = []byte("voucher/")
func voucherKey(voucherReachID, poolID string) []byte {
return append(append(voucherKeyPrefix, []byte(voucherReachID)...), []byte("/"+poolID)...)
}
// GetCoverClaimsVoucher loads a CoverClaimsVoucher by voucher-reach-id +
// pool-id. Returns the Voucher and true if found, or zero value + false if
// not.
func (k Keeper) GetCoverClaimsVoucher(ctx sdk.Context, voucherReachID, poolID string) (types.CoverClaimsVoucher, bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(voucherKey(voucherReachID, poolID))
if bz == nil {
return types.CoverClaimsVoucher{}, false
}
var v types.CoverClaimsVoucher
if err := json.Unmarshal(bz, &v); err != nil {
return types.CoverClaimsVoucher{}, false
}
return v, true
}
// SetCoverClaimsVoucher persists a CoverClaimsVoucher by voucher-reach-id +
// pool-id.
func (k Keeper) SetCoverClaimsVoucher(ctx sdk.Context, v types.CoverClaimsVoucher) {
store := ctx.KVStore(k.storeKey)
bz, err := json.Marshal(v)
if err != nil {
panic(fmt.Sprintf("cover: marshal voucher %q/%q: %v", v.VoucherReachID, v.PoolID, err))
}
store.Set(voucherKey(v.VoucherReachID, v.PoolID), bz)
}
// AllCoverClaimsVouchers returns all persisted CoverClaimsVoucher records
// (iteration helper, unordered).
func (k Keeper) AllCoverClaimsVouchers(ctx sdk.Context) []types.CoverClaimsVoucher {
store := ctx.KVStore(k.storeKey)
iterator := store.Iterator(voucherKeyPrefix, prefixEnd(voucherKeyPrefix))
defer iterator.Close()
out := []types.CoverClaimsVoucher{}
for ; iterator.Valid(); iterator.Next() {
var v types.CoverClaimsVoucher
if err := json.Unmarshal(iterator.Value(), &v); err == nil {
out = append(out, v)
}
}
return out
}
// GetAvgCallSize computes the average Cover Call amount (Grain) for a Pool
// from the call/ store (REQ-055, D-090(2)). Returns 0 if no Calls have been
// filed for the Pool — the D-090(2) cold-start case (the Voucher bond falls
// back to MinimumVoucherBond, NOT zero).
func (k Keeper) GetAvgCallSize(ctx sdk.Context, poolID string) int64 {
calls := k.AllCoverCalls(ctx)
sum := int64(0)
n := 0
for _, c := range calls {
if c.PoolID == poolID {
sum += c.AmountGrain
n++
}
}
if n == 0 {
return 0
}
return sum / int64(n)
}
// --- prefixEnd helper ---------------------------------------------------------
// prefixEnd returns the key that sorts immediately after all keys sharing