Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8cc9cf26a0 | |||
| 899b76b572 | |||
| 40d878fd17 | |||
| 8c59bd48e0 | |||
| 79a72c48cf | |||
| edea9422ac | |||
| 42895a74dd | |||
| b53118693f | |||
| 0131c0cbbb | |||
| d9a8b28cd3 |
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"phase": 1,
|
||||
"phase": 2,
|
||||
"stage": "execute",
|
||||
"milestone": "v0.1",
|
||||
"phase_role": "execution",
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package types
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
const (
|
||||
ModuleName = "bloom"
|
||||
StoreKey = ModuleName
|
||||
RouterKey = ModuleName
|
||||
QuerierRoute = ModuleName
|
||||
|
||||
// Target Bloom rate: ~4-5% per year (§6)
|
||||
// Honest expectation, not a promise
|
||||
TargetBloomRateBasisPoints = 450 // 4.5%
|
||||
MinBloomRateBasisPoints = 400 // 4.0%
|
||||
MaxBloomRateBasisPoints = 500 // 5.0%
|
||||
|
||||
// Bloom accrues automatically to every Grain in every Stash (§6)
|
||||
// Accrual period: daily (in blocks, assuming ~10min blocks)
|
||||
AccrualPeriodBlocks = 144
|
||||
)
|
||||
|
||||
// BloomRecord tracks Bloom accrued to a Stash
|
||||
type BloomRecord struct {
|
||||
StashID string `json:"stash_id" yaml:"stash_id"`
|
||||
AccruedGrain int64 `json:"accrued_grain" yaml:"accrued_grain"`
|
||||
LastAccrualBlock int64 `json:"last_accrual_block" yaml:"last_accrual_block"`
|
||||
RateBasisPoints uint32 `json:"rate_basis_points" yaml:"rate_basis_points"`
|
||||
}
|
||||
|
||||
// BloomBoosterBucket represents a higher-strategy bucket (§3 lexicon)
|
||||
type BloomBoosterBucket struct {
|
||||
BucketID string `json:"bucket_id" yaml:"bucket_id"`
|
||||
StashID string `json:"stash_id" yaml:"stash_id"`
|
||||
PledgeAmount int64 `json:"pledge_amount" yaml:"pledge_amount"`
|
||||
BoostRateBps uint32 `json:"boost_rate_bps" yaml:"boost_rate_bps"`
|
||||
UnlockHeight int64 `json:"unlock_height" yaml:"unlock_height"`
|
||||
}
|
||||
|
||||
// BloomSource defines where Bloom originates (§6: only from real yield)
|
||||
type BloomSource string
|
||||
|
||||
const (
|
||||
BloomFromTreasury BloomSource = "TreasuryCoupons"
|
||||
BloomFromCorporate BloomSource = "CorporateCoupons"
|
||||
BloomFromGoldCarry BloomSource = "GoldCarry"
|
||||
BloomFromRwaCashflow BloomSource = "RwaCashflow"
|
||||
)
|
||||
|
||||
// MissionLockBloom: Bloom originates ONLY from real yield (§6)
|
||||
// No synthetic Bloom. No protocol-printed Bloom.
|
||||
// This is a Mission Lock — no Council vote can change it.
|
||||
const MissionLockBloom = "Bloom originates only from real yield. No synthetic Bloom. No protocol-printed Bloom."
|
||||
|
||||
type Params struct {
|
||||
TargetRateBps uint32 `json:"target_rate_bps" yaml:"target_rate_bps"`
|
||||
AccrualBlocks uint32 `json:"accrual_blocks" yaml:"accrual_blocks"`
|
||||
}
|
||||
|
||||
func DefaultParams() Params {
|
||||
return Params{
|
||||
TargetRateBps: TargetBloomRateBasisPoints,
|
||||
AccrualBlocks: AccrualPeriodBlocks,
|
||||
}
|
||||
}
|
||||
|
||||
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,41 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/oy/openyield/x/bloom/types"
|
||||
)
|
||||
|
||||
func TestTargetBloomRate(t *testing.T) {
|
||||
if types.TargetBloomRateBasisPoints != 450 {
|
||||
t.Errorf("TargetBloomRate = %d, expected 450 (§6: 4.5 pct)", types.TargetBloomRateBasisPoints)
|
||||
}
|
||||
if types.MinBloomRateBasisPoints != 400 {
|
||||
t.Errorf("MinBloomRate = %d, expected 400 (§6: 4-5 pct)", types.MinBloomRateBasisPoints)
|
||||
}
|
||||
if types.MaxBloomRateBasisPoints != 500 {
|
||||
t.Errorf("MaxBloomRate = %d, expected 500 (§6: 4-5 pct)", types.MaxBloomRateBasisPoints)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBloomSources(t *testing.T) {
|
||||
sources := []types.BloomSource{
|
||||
types.BloomFromTreasury, types.BloomFromCorporate,
|
||||
types.BloomFromGoldCarry, types.BloomFromRwaCashflow,
|
||||
}
|
||||
if len(sources) != 4 {
|
||||
t.Errorf("Expected 4 Bloom sources, got %d", len(sources))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissionLockBloom(t *testing.T) {
|
||||
if types.MissionLockBloom == "" {
|
||||
t.Error("MissionLockBloom should not be empty (§6: mission-locked)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccrualPeriod(t *testing.T) {
|
||||
if types.AccrualPeriodBlocks != 144 {
|
||||
t.Errorf("AccrualPeriodBlocks = %d, expected 144 (~daily)", types.AccrualPeriodBlocks)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package types
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
const (
|
||||
ModuleName = "rootpool"
|
||||
StoreKey = ModuleName
|
||||
RouterKey = ModuleName
|
||||
QuerierRoute = ModuleName
|
||||
)
|
||||
|
||||
// RootPool is the mesh's treasury (§5)
|
||||
// Attested by Watchers, visible to every Holder.
|
||||
type RootPool struct {
|
||||
BalanceGrain int64 `json:"balance_grain" yaml:"balance_grain"`
|
||||
LastAttestation int64 `json:"last_attestation" yaml:"last_attestation"`
|
||||
QuorumMet bool `json:"quorum_met" yaml:"quorum_met"`
|
||||
ReserveRatioPct uint32 `json:"reserve_ratio_pct" yaml:"reserve_ratio_pct"`
|
||||
}
|
||||
|
||||
// RootPoolEntry tracks incoming/outgoing Root-Pool movements
|
||||
type RootPoolEntry struct {
|
||||
EntryID string `json:"entry_id" yaml:"entry_id"`
|
||||
Source string `json:"source" yaml:"source"`
|
||||
Amount int64 `json:"amount" yaml:"amount"`
|
||||
Reason string `json:"reason" yaml:"reason"`
|
||||
Timestamp int64 `json:"timestamp" yaml:"timestamp"`
|
||||
}
|
||||
|
||||
type Params struct{}
|
||||
|
||||
func DefaultParams() Params { return Params{} }
|
||||
|
||||
type GenesisState struct {
|
||||
Params Params `json:"params" yaml:"params"`
|
||||
RootPool RootPool `json:"root_pool" yaml:"root_pool"`
|
||||
}
|
||||
|
||||
func DefaultGenesisState() *GenesisState {
|
||||
return &GenesisState{Params: DefaultParams()}
|
||||
}
|
||||
|
||||
func ValidateGenesis(bz json.RawMessage) error { return nil }
|
||||
@@ -0,0 +1,55 @@
|
||||
package types
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
const (
|
||||
ModuleName = "stash"
|
||||
StoreKey = ModuleName
|
||||
RouterKey = ModuleName
|
||||
QuerierRoute = ModuleName
|
||||
)
|
||||
|
||||
// Stash is a per-Holder sovereign storage (§5)
|
||||
// The Holder owns it. No one else can access it without a Window (§10).
|
||||
type Stash struct {
|
||||
HolderID string `json:"holder_id" yaml:"holder_id"`
|
||||
StashID string `json:"stash_id" yaml:"stash_id"`
|
||||
CreatedAt int64 `json:"created_at" yaml:"created_at"`
|
||||
LastActive int64 `json:"last_active" yaml:"last_active"`
|
||||
BalanceGrain int64 `json:"balance_grain" yaml:"balance_grain"`
|
||||
IsStill bool `json:"is_still" yaml:"is_still"` // Still = Holder paused partner access (§10)
|
||||
}
|
||||
|
||||
// StashActivity tracks Stash maturity for Freeholder signals (§9.1: 90 days, no gap >30 days)
|
||||
type StashActivity struct {
|
||||
StashID string `json:"stash_id" yaml:"stash_id"`
|
||||
ActiveDays uint32 `json:"active_days" yaml:"active_days"`
|
||||
MaxGapDays uint32 `json:"max_gap_days" yaml:"max_gap_days"` // must be ≤30 for Freeholder
|
||||
LastActivityDay int64 `json:"last_activity_day" yaml:"last_activity_day"`
|
||||
}
|
||||
|
||||
// MaturityThreshold for Freeholder signal (§9.1: 90 days)
|
||||
const MaturityThresholdDays = 90
|
||||
|
||||
// MaxGapForMaturity for Freeholder signal (§9.1: no gap > 30 days)
|
||||
const MaxGapForMaturity = 30
|
||||
|
||||
// IsMature checks if Stash activity meets the Freeholder maturity threshold (§9.1)
|
||||
func (a StashActivity) IsMature() bool {
|
||||
return a.ActiveDays >= MaturityThresholdDays && a.MaxGapDays <= MaxGapForMaturity
|
||||
}
|
||||
|
||||
type Params struct{}
|
||||
|
||||
func DefaultParams() Params { return Params{} }
|
||||
|
||||
type GenesisState struct {
|
||||
Params Params `json:"params" yaml:"params"`
|
||||
Stashes []Stash `json:"stashes" yaml:"stashes"`
|
||||
}
|
||||
|
||||
func DefaultGenesisState() *GenesisState {
|
||||
return &GenesisState{Params: DefaultParams(), Stashes: []Stash{}}
|
||||
}
|
||||
|
||||
func ValidateGenesis(bz json.RawMessage) error { return nil }
|
||||
@@ -0,0 +1,43 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/oy/openyield/x/stash/types"
|
||||
)
|
||||
|
||||
func TestMaturityThreshold(t *testing.T) {
|
||||
if types.MaturityThresholdDays != 90 {
|
||||
t.Errorf("MaturityThresholdDays = %d, expected 90 (§9.1)", types.MaturityThresholdDays)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxGapForMaturity(t *testing.T) {
|
||||
if types.MaxGapForMaturity != 30 {
|
||||
t.Errorf("MaxGapForMaturity = %d, expected 30 (§9.1: no gap > 30 days)", types.MaxGapForMaturity)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStashIsMature(t *testing.T) {
|
||||
mature := types.StashActivity{ActiveDays: 90, MaxGapDays: 15}
|
||||
if !mature.IsMature() {
|
||||
t.Error("90 days active, 15 max gap should be mature (§9.1)")
|
||||
}
|
||||
|
||||
notEnoughDays := types.StashActivity{ActiveDays: 89, MaxGapDays: 15}
|
||||
if notEnoughDays.IsMature() {
|
||||
t.Error("89 days should NOT be mature (threshold = 90)")
|
||||
}
|
||||
|
||||
gapTooBig := types.StashActivity{ActiveDays: 100, MaxGapDays: 31}
|
||||
if gapTooBig.IsMature() {
|
||||
t.Error("31-day gap should NOT be mature (max gap = 30)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStashStillFlag(t *testing.T) {
|
||||
stash := types.Stash{IsStill: true}
|
||||
if !stash.IsStill {
|
||||
t.Error("Stash with IsStill=true should reflect Still state (§10: Holder paused partner access)")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package types
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
const (
|
||||
ModuleName = "vault"
|
||||
StoreKey = ModuleName
|
||||
RouterKey = ModuleName
|
||||
QuerierRoute = ModuleName
|
||||
)
|
||||
|
||||
// Vault is a per-Stand governed storage (§5)
|
||||
// Governed by the Stand Council (§19). Holds shared funds for a Stand.
|
||||
type Vault struct {
|
||||
VaultID string `json:"vault_id" yaml:"vault_id"`
|
||||
StandID string `json:"stand_id" yaml:"stand_id"`
|
||||
CreatedAt int64 `json:"created_at" yaml:"created_at"`
|
||||
BalanceGrain int64 `json:"balance_grain" yaml:"balance_grain"`
|
||||
}
|
||||
|
||||
// VaultAllocation tracks sub-Vault allocations within a Stand
|
||||
type VaultAllocation struct {
|
||||
VaultID string `json:"vault_id" yaml:"vault_id"`
|
||||
SubVaultID string `json:"sub_vault_id" yaml:"sub_vault_id"`
|
||||
Percentage uint32 `json:"percentage" yaml:"percentage"`
|
||||
Purpose string `json:"purpose" yaml:"purpose"`
|
||||
}
|
||||
|
||||
type Params struct{}
|
||||
|
||||
func DefaultParams() Params { return Params{} }
|
||||
|
||||
type GenesisState struct {
|
||||
Params Params `json:"params" yaml:"params"`
|
||||
Vaults []Vault `json:"vaults" yaml:"vaults"`
|
||||
}
|
||||
|
||||
func DefaultGenesisState() *GenesisState {
|
||||
return &GenesisState{Params: DefaultParams(), Vaults: []Vault{}}
|
||||
}
|
||||
|
||||
func ValidateGenesis(bz json.RawMessage) error { return nil }
|
||||
Reference in New Issue
Block a user