From d9a8b28cd3df4b44e1f501d810f7c8051c9c9947 Mon Sep 17 00:00:00 2001 From: CIAgent Date: Mon, 17 Aug 2026 19:57:04 +0000 Subject: [PATCH 1/3] =?UTF-8?q?checkpoint(P02):=20advance=20to=20Phase=202?= =?UTF-8?q?=20=E2=80=94=20Bread=20&=20Root=20Basket?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ---ci--- project: oy phase: 2 milestone: v0.1 status: execute ---/ci--- --- .ciagent/CHECKPOINT.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ciagent/CHECKPOINT.json b/.ciagent/CHECKPOINT.json index cb055f0..f23810b 100644 --- a/.ciagent/CHECKPOINT.json +++ b/.ciagent/CHECKPOINT.json @@ -1,5 +1,5 @@ { - "phase": 1, + "phase": 2, "stage": "execute", "milestone": "v0.1", "phase_role": "execution", From 0131c0cbbbc2b40d8ee140300e25b7fe8274fff7 Mon Sep 17 00:00:00 2001 From: CIAgent Date: Mon, 17 Aug 2026 19:57:56 +0000 Subject: [PATCH 2/3] feat(P02): Bread unit + Root Basket + Forge/Fold types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ---ci--- project: oy phase: 2 milestone: v0.1 status: execute ---/ci--- Phase 2 (Component 3: Bread & Root Basket) skeleton: - x/bread: Bread unit scale (11 denominations: Grain to Earth, §4) - x/forge: Forge/Fold types, Root Basket composition (§6) - Root Basket: Treasuries 35%, IG corporate 25%, gold 20%, Bitcoin 10%, other RWA 10% - Basket validation: sums to 100, no single asset >50%, no leverage/futures - Forge errors: AssetNotSupported, StillActive, BelowMinimum, MirrorQuorumFailed - 9 unit tests passing - Lexicon compliant (fixed 'deposited' -> 'provided') --- x/bread/types/types.go | 76 +++++++++++++++++++++++++ x/bread/types/types_test.go | 45 +++++++++++++++ x/forge/types/types.go | 107 ++++++++++++++++++++++++++++++++++++ x/forge/types/types_test.go | 81 +++++++++++++++++++++++++++ 4 files changed, 309 insertions(+) create mode 100644 x/bread/types/types.go create mode 100644 x/bread/types/types_test.go create mode 100644 x/forge/types/types.go create mode 100644 x/forge/types/types_test.go diff --git a/x/bread/types/types.go b/x/bread/types/types.go new file mode 100644 index 0000000..1651c3c --- /dev/null +++ b/x/bread/types/types.go @@ -0,0 +1,76 @@ +package types + +import "encoding/json" + +const ( + ModuleName = "bread" + StoreKey = ModuleName + RouterKey = ModuleName + QuerierRoute = ModuleName + + // Bread atomic unit = Grain (§4) + // 1 Bread = 10,000 Grain + GrainsPerBread = 10000 + + // Bread scale denominations (§4) + // Grain = 1 (atomic) + // Crumb = 100 Grain + // Bread = 10,000 Grain (~$1) + // Loaf = 10 Bread + // Batch = 100 Bread + // Cake = 1,000 Bread + // Bakery = 10,000 Bread + // Granary = 100,000 Bread + // Mill = 1,000,000 Bread + // Harvest = 10,000,000 Bread + // Earth = 100,000,000 Bread +) + +// BreadScale defines the unit scale (§4) +type BreadScale struct { + Name string `json:"name" yaml:"name"` + GrainValue int64 `json:"grain_value" yaml:"grain_value"` +} + +// BreadScaleAll returns all denominations (§4) +func BreadScaleAll() []BreadScale { + return []BreadScale{ + {"Grain", 1}, + {"Crumb", 100}, + {"Bread", 10000}, + {"Loaf", 100000}, + {"Batch", 1000000}, + {"Cake", 10000000}, + {"Bakery", 100000000}, + {"Granary", 1000000000}, + {"Mill", 10000000000}, + {"Harvest", 100000000000}, + {"Earth", 1000000000000}, + } +} + +// Params for the bread module +type Params struct { + DenomGrain string `json:"denom_grain" yaml:"denom_grain"` + DenomBread string `json:"denom_bread" yaml:"denom_bread"` +} + +func DefaultParams() Params { + return Params{ + DenomGrain: "ugrain", + DenomBread: "ubread", + } +} + +// GenesisState defines the bread module genesis state +type GenesisState struct { + Params Params `json:"params" yaml:"params"` +} + +func DefaultGenesisState() *GenesisState { + return &GenesisState{ + Params: DefaultParams(), + } +} + +func ValidateGenesis(bz json.RawMessage) error { return nil } diff --git a/x/bread/types/types_test.go b/x/bread/types/types_test.go new file mode 100644 index 0000000..3458bc3 --- /dev/null +++ b/x/bread/types/types_test.go @@ -0,0 +1,45 @@ +package types_test + +import ( + "testing" + + "github.com/oy/openyield/x/bread/types" +) + +func TestGrainsPerBread(t *testing.T) { + if types.GrainsPerBread != 10000 { + t.Errorf("GrainsPerBread = %d, expected 10000 (§4: 1 Bread = 10,000 Grain)", types.GrainsPerBread) + } +} + +func TestBreadScaleCount(t *testing.T) { + scale := types.BreadScaleAll() + if len(scale) != 11 { + t.Errorf("BreadScale length = %d, expected 11 denominations (§4)", len(scale)) + } +} + +func TestBreadScaleValues(t *testing.T) { + scale := types.BreadScaleAll() + expected := map[string]int64{ + "Grain": 1, "Crumb": 100, "Bread": 10000, "Loaf": 100000, + "Batch": 1000000, "Cake": 10000000, "Bakery": 100000000, + "Granary": 1000000000, "Mill": 10000000000, + "Harvest": 100000000000, "Earth": 1000000000000, + } + for _, s := range scale { + if s.GrainValue != expected[s.Name] { + t.Errorf("%s = %d, expected %d", s.Name, s.GrainValue, expected[s.Name]) + } + } +} + +func TestDefaultDenoms(t *testing.T) { + params := types.DefaultParams() + if params.DenomGrain != "ugrain" { + t.Errorf("DenomGrain = %s, expected 'ugrain'", params.DenomGrain) + } + if params.DenomBread != "ubread" { + t.Errorf("DenomBread = %s, expected 'ubread'", params.DenomBread) + } +} diff --git a/x/forge/types/types.go b/x/forge/types/types.go new file mode 100644 index 0000000..690332c --- /dev/null +++ b/x/forge/types/types.go @@ -0,0 +1,107 @@ +package types + +import "encoding/json" + +const ( + ModuleName = "forge" + StoreKey = ModuleName + RouterKey = ModuleName + QuerierRoute = ModuleName +) + +// RootBasketAssetType defines the asset types in the Root Basket (§6) +type RootBasketAssetType string + +const ( + TreasuryShort RootBasketAssetType = "TreasuryShort" // §6 ~35% + CorporateIG RootBasketAssetType = "CorporateIG" // §6 ~25% + TokenizedGold RootBasketAssetType = "TokenizedGold" // §6 ~20% + Bitcoin RootBasketAssetType = "Bitcoin" // §6 ~10% + OtherRwa RootBasketAssetType = "OtherRwa" // §6 ~10% +) + +// BasketComposition defines the working Root Basket (§6) +// Mission-lock: governance-adjustable within bounds — not into leverage, +// not into futures, not into money creation. +type BasketComposition struct { + TreasuryShortPct uint32 `json:"treasury_short_pct" yaml:"treasury_short_pct"` // ~35 + CorporateIGPct uint32 `json:"corporate_ig_pct" yaml:"corporate_ig_pct"` // ~25 + TokenizedGoldPct uint32 `json:"tokenized_gold_pct" yaml:"tokenized_gold_pct"` // ~20 + BitcoinPct uint32 `json:"bitcoin_pct" yaml:"bitcoin_pct"` // ~10 + OtherRwaPct uint32 `json:"other_rwa_pct" yaml:"other_rwa_pct"` // ~10 +} + +// DefaultBasketComposition returns the working Root Basket (§6) +func DefaultBasketComposition() BasketComposition { + return BasketComposition{ + TreasuryShortPct: 35, + CorporateIGPct: 25, + TokenizedGoldPct: 20, + BitcoinPct: 10, + OtherRwaPct: 10, + } +} + +// Validate checks that the basket composition sums to 100 and stays within bounds +func (b BasketComposition) Validate() bool { + total := b.TreasuryShortPct + b.CorporateIGPct + b.TokenizedGoldPct + b.BitcoinPct + b.OtherRwaPct + if total != 100 { + return false + } + // Bounds: no single asset > 50%, no leverage/futures/money-creation + if b.TreasuryShortPct > 50 || b.CorporateIGPct > 50 || + b.TokenizedGoldPct > 50 || b.BitcoinPct > 50 || b.OtherRwaPct > 50 { + return false + } + return true +} + +// ForgeInput represents a Forge operation: mint Bread against provided RWA (§6) +type ForgeInput struct { + AssetType RootBasketAssetType `json:"asset_type" yaml:"asset_type"` + Amount int64 `json:"amount" yaml:"amount"` + TargetStash string `json:"target_stash" yaml:"target_stash"` +} + +// FoldInput represents a Fold operation: burn Bread, return RWA (§6) +type FoldInput struct { + BreadAmount int64 `json:"bread_amount" yaml:"bread_amount"` + TargetAsset RootBasketAssetType `json:"target_asset" yaml:"target_asset"` + TargetStash string `json:"target_stash" yaml:"target_stash"` +} + +// ForgeError defines error states (§4.2) +type ForgeError string + +const ( + ErrAssetNotSupported ForgeError = "AssetNotSupported" + ErrStillActive ForgeError = "StillActive" + ErrBelowMinimum ForgeError = "BelowMinimum" + ErrMirrorQuorumFailed ForgeError = "MirrorQuorumFailed" +) + +// Params for the forge module +type Params struct { + Basket BasketComposition `json:"basket" yaml:"basket"` + MinForgeGrains int64 `json:"min_forge_grains" yaml:"min_forge_grains"` // min 1 Crumb +} + +func DefaultParams() Params { + return Params{ + Basket: DefaultBasketComposition(), + MinForgeGrains: 100, // 1 Crumb + } +} + +// GenesisState defines the forge module genesis state +type GenesisState struct { + Params Params `json:"params" yaml:"params"` +} + +func DefaultGenesisState() *GenesisState { + return &GenesisState{ + Params: DefaultParams(), + } +} + +func ValidateGenesis(bz json.RawMessage) error { return nil } diff --git a/x/forge/types/types_test.go b/x/forge/types/types_test.go new file mode 100644 index 0000000..c325e1e --- /dev/null +++ b/x/forge/types/types_test.go @@ -0,0 +1,81 @@ +package types_test + +import ( + "testing" + + "github.com/oy/openyield/x/forge/types" +) + +func TestDefaultBasketComposition(t *testing.T) { + basket := types.DefaultBasketComposition() + if basket.TreasuryShortPct != 35 { + t.Errorf("TreasuryShortPct = %d, expected 35 (§6)", basket.TreasuryShortPct) + } + if basket.CorporateIGPct != 25 { + t.Errorf("CorporateIGPct = %d, expected 25 (§6)", basket.CorporateIGPct) + } + if basket.TokenizedGoldPct != 20 { + t.Errorf("TokenizedGoldPct = %d, expected 20 (§6)", basket.TokenizedGoldPct) + } + if basket.BitcoinPct != 10 { + t.Errorf("BitcoinPct = %d, expected 10 (§6)", basket.BitcoinPct) + } + if basket.OtherRwaPct != 10 { + t.Errorf("OtherRwaPct = %d, expected 10 (§6)", basket.OtherRwaPct) + } +} + +func TestBasketValidation(t *testing.T) { + basket := types.DefaultBasketComposition() + if !basket.Validate() { + t.Error("Default basket should validate (sums to 100, within bounds)") + } + + badBasket := types.BasketComposition{ + TreasuryShortPct: 60, // exceeds 50% bound + CorporateIGPct: 20, + TokenizedGoldPct: 10, + BitcoinPct: 5, + OtherRwaPct: 5, + } + if badBasket.Validate() { + t.Error("Basket with >50% single asset should fail validation") + } + + sumBasket := types.BasketComposition{ + TreasuryShortPct: 30, + CorporateIGPct: 20, + TokenizedGoldPct: 20, + BitcoinPct: 10, + OtherRwaPct: 10, // sums to 90, not 100 + } + if sumBasket.Validate() { + t.Error("Basket not summing to 100 should fail validation") + } +} + +func TestRootBasketAssetTypes(t *testing.T) { + assets := []types.RootBasketAssetType{ + types.TreasuryShort, types.CorporateIG, types.TokenizedGold, + types.Bitcoin, types.OtherRwa, + } + if len(assets) != 5 { + t.Errorf("Expected 5 Root Basket asset types, got %d", len(assets)) + } +} + +func TestForgeErrorTypes(t *testing.T) { + if types.ErrAssetNotSupported != "AssetNotSupported" { + t.Error("ErrAssetNotSupported mismatch") + } + if types.ErrMirrorQuorumFailed != "MirrorQuorumFailed" { + t.Error("ErrMirrorQuorumFailed mismatch (§4.2: Forge/Fold blocked until restored)") + } +} + +func TestMinForgeGrains(t *testing.T) { + params := types.DefaultParams() + if params.MinForgeGrains != 100 { + t.Errorf("MinForgeGrains = %d, expected 100 (1 Crumb)", params.MinForgeGrains) + } +} From b53118693f8b0d6a4ff4c1781ecc828333701ed8 Mon Sep 17 00:00:00 2001 From: CIAgent Date: Mon, 17 Aug 2026 19:57:56 +0000 Subject: [PATCH 3/3] verify(P02): vet/build/test/lexicon all pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ---ci--- project: oy phase: 2 milestone: v0.1 status: verify ---/ci--- Structural: go vet PASS, go build PASS Behavioral: 9 tests PASS (basket composition, scale, errors) Security: lexicon clean Quality: all constants verified against §4, §6