Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 42895a74dd | |||
| b53118693f | |||
| 0131c0cbbb | |||
| d9a8b28cd3 |
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"phase": 1,
|
"phase": 2,
|
||||||
"stage": "execute",
|
"stage": "execute",
|
||||||
"milestone": "v0.1",
|
"milestone": "v0.1",
|
||||||
"phase_role": "execution",
|
"phase_role": "execution",
|
||||||
|
|||||||
@@ -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 }
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 }
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user