feat(P01): OY Chain skeleton + Watcher/Mirror/Still modules

---ci---
project: oy
phase: 1
milestone: v0.1
status: execute
---/ci---

Phase 1 (Component 1: OY Chain & Mirror) skeleton:
- x/watcher: Watcher bond, attestation quorum (6-of-9), slashing types
- x/mirror: NAV per Bread, reserve ratio publication types
- x/still: Still/Stir (pause/resume) with auto-still on quorum lost
- 12 unit tests passing (all locked constants verified: §7)
- Cosmos SDK app.go deferred to P1 continuation (dependency resolution)
- No regulated finance terminology used (lexicon compliant)
This commit is contained in:
CIAgent
2026-08-17 19:56:21 +00:00
parent 7a5dcb7b81
commit 71e429da5f
9 changed files with 392 additions and 3 deletions
+120
View File
@@ -0,0 +1,120 @@
package types
import "encoding/json"
const (
// ModuleName is the name of the watcher module
ModuleName = "watcher"
// StoreKey is the string store key
StoreKey = ModuleName
// RouterKey is the message route for the watcher module
RouterKey = ModuleName
// QuerierRoute is the querier route for the watcher module
QuerierRoute = ModuleName
// MinBond is the minimum bond for a Watcher: 100,000 Bread (§7)
// Bread atomic unit = Grain; 1 Bread = 10,000 Grain
MinBond = 100000
// Quorum is the minimum attestation count: 6-of-9 (§7)
Quorum = 6
// MaxWatchers is the maximum active Watcher count: 9 (§7)
MaxWatchers = 9
// AttestationEpochHours is the daily attestation window (§7)
AttestationEpochHours = 24
)
// Parameter store keys
var (
KeyActiveWatchers = []byte("ActiveWatchers")
KeyBondAmount = []byte("BondAmount")
)
// Watcher represents a bonded Watcher (§7)
type Watcher struct {
WatcherID string `json:"watcher_id" yaml:"watcher_id"`
BondAmount int64 `json:"bond_amount" yaml:"bond_amount"`
Jurisdiction string `json:"jurisdiction" yaml:"jurisdiction"`
Organization string `json:"organization" yaml:"organization"`
ActiveSince int64 `json:"active_since" yaml:"active_since"`
Slashes uint32 `json:"slashes" yaml:"slashes"`
LastAttestation int64 `json:"last_attestation" yaml:"last_attestation"`
}
// Attestation is a daily reserve snapshot signed by a Watcher (§7)
type Attestation struct {
WatcherID string `json:"watcher_id" yaml:"watcher_id"`
Snapshot ReserveSnapshot `json:"snapshot" yaml:"snapshot"`
Signature []byte `json:"signature" yaml:"signature"`
Timestamp int64 `json:"timestamp" yaml:"timestamp"`
}
// ReserveSnapshot is the off-chain reserve state attested by Watchers (§7)
type ReserveSnapshot struct {
OffChainTotal int64 `json:"off_chain_total" yaml:"off_chain_total"`
OnChainBread int64 `json:"on_chain_bread" yaml:"on_chain_bread"`
BasketBreakdown []BasketAsset `json:"basket_breakdown" yaml:"basket_breakdown"`
Timestamp int64 `json:"timestamp" yaml:"timestamp"`
AuditorRef string `json:"auditor_ref" yaml:"auditor_ref"`
}
// BasketAsset represents one asset in the Root Basket (§6)
type BasketAsset struct {
AssetType string `json:"asset_type" yaml:"asset_type"`
Amount int64 `json:"amount" yaml:"amount"`
}
// QuorumStatus represents the current quorum state
type QuorumStatus string
const (
QuorumMet QuorumStatus = "QuorumMet" // ≥6 valid sigs in last epoch
QuorumLost QuorumStatus = "QuorumLost" // <6 — Forge/Fold stilled
StaleEpoch QuorumStatus = "StaleEpoch" // no attestations in 24h
)
// SlashReason defines why a Watcher was slashed
type SlashReason string
const (
SlashInsufficientBond SlashReason = "InsufficientBond"
SlashStaleAttestation SlashReason = "StaleAttestation"
SlashGeographicCollusion SlashReason = "GeographicCollusion"
SlashForgery SlashReason = "Forgery"
)
// Params defines the parameters for the watcher module
type Params struct {
MinBondAmount int64 `json:"min_bond_amount" yaml:"min_bond_amount"`
QuorumCount uint32 `json:"quorum_count" yaml:"quorum_count"`
MaxWatchers uint32 `json:"max_watchers" yaml:"max_watchers"`
}
// DefaultParams returns default watcher module parameters
func DefaultParams() Params {
return Params{
MinBondAmount: int64(MinBond) * 10000, // 100,000 Bread in Grain
QuorumCount: Quorum,
MaxWatchers: MaxWatchers,
}
}
// GenesisState defines the watcher module genesis state
type GenesisState struct {
Params Params `json:"params" yaml:"params"`
Watchers []Watcher `json:"watchers" yaml:"watchers"`
}
func DefaultGenesisState() *GenesisState {
return &GenesisState{
Params: DefaultParams(),
Watchers: []Watcher{},
}
}
func ValidateGenesis(bz json.RawMessage) error { return nil }
+41
View File
@@ -0,0 +1,41 @@
package types_test
import (
"testing"
"github.com/oy/openyield/x/watcher/types"
)
func TestMinBond(t *testing.T) {
if types.MinBond != 100000 {
t.Errorf("MinBond = %d, expected 100000 (§7: 100,000 Bread bond)", types.MinBond)
}
}
func TestQuorum(t *testing.T) {
if types.Quorum != 6 {
t.Errorf("Quorum = %d, expected 6 (§7: 6-of-9 quorum)", types.Quorum)
}
}
func TestMaxWatchers(t *testing.T) {
if types.MaxWatchers != 9 {
t.Errorf("MaxWatchers = %d, expected 9 (§7: 9 active Watchers)", types.MaxWatchers)
}
}
func TestAttestationEpoch(t *testing.T) {
if types.AttestationEpochHours != 24 {
t.Errorf("AttestationEpochHours = %d, expected 24 (§7: daily attestations)", types.AttestationEpochHours)
}
}
func TestDefaultParams(t *testing.T) {
params := types.DefaultParams()
if params.QuorumCount != 6 {
t.Errorf("DefaultParams QuorumCount = %d, expected 6", params.QuorumCount)
}
if params.MaxWatchers != 9 {
t.Errorf("DefaultParams MaxWatchers = %d, expected 9", params.MaxWatchers)
}
}