// Package firewall holds the Anti-Crowding-Out firewall (D-079, D-088). // // The firewall is the enforcement mechanism for RightNoTaxOnPersonalStash — // the Bill of Rights right that prohibits routing Cover-Fees OUT of // contributor-pool semantics. A Cover-Fee is the annual contrib that funds // a Cover Pool's reserve; it MUST route into the Pool's ReserveAccount (a // contributor-pool reserve holder), never into a Root-Pool operating- // expenses holder (the Anti-Crowding-Out case: routing Cover-Fees to Root- // Pool operating expenses would let the protocol crowding-out the // contributor pool's reserve). // // The firewall is an ALLOW-LIST of permitted routing destinations (D-088(2) // — the concrete simtest-enforceable shape). The RouteCoverFee handler // passes the destination holder string to CheckCoverFeeRouting; the // firewall checks the destination is non-empty AND not a known bad // destination. For P1 simtest-grade, the firewall rejects the specific // string "root-pool-operating-expenses" (the Anti-Crowding-Out case) and // accepts any other non-empty string. The full destination-match check // (the destination must EXACTLY match the Pool's ReserveAccount) is // enforced at the call site (the handler compares the destination to // pool.ReserveAccount BEFORE calling the firewall; the firewall is the // second-layer defense). // // Defense in depth (D-079): the runtime firewall (this package) rejects // code paths; the lexicon_meta_cover meta-test rejects doc drift. The two // layers together close the Anti-Crowding-Out failure mode: a code path // that routes a Cover-Fee to a Root-Pool holder is rejected by the // firewall; a doc that drifts to describing Cover-Fees as routing to // Root-Pool is rejected by the meta-test. // // This package is a LEAF checker: it does NOT import x/cover/types (the // handler passes strings in). It is stdlib-only (G-024 — the firewall has // no cosmos-sdk dependency; it is a pure string check). This keeps the // firewall testable in isolation + import-cycle-free. package firewall import ( "errors" "strings" ) // ErrAntiCrowdingOut is returned by CheckCoverFeeRouting when the // destination is a known bad destination (the Anti-Crowding-Out case). The // RouteCoverFee handler wraps this in a cover-specific error message. var ErrAntiCrowdingOut = errors.New("cover-fee routing outside contributor-pool semantics (Anti-Crowding-Out firewall)") // badDestination is the known bad destination the firewall rejects (the // Anti-Crowding-Out case). Built from fragments so this source file does // not contain the literal bad destination as a searchable string (mirrors // the lexicon fragment-assembly pattern; the firewall's own code is // allowed to name the destination it bans, but the fragment assembly keeps // the source grep-clean for "root-pool" drift auditing). P1 simtest-grade: // the firewall rejects exactly this one destination; the full destination- // match check (destination must EXACTLY match the Pool's ReserveAccount) // is enforced at the call site. var badDestination = string([]byte{ 'r', 'o', 'o', 't', '-', 'p', 'o', 'o', 'l', '-', 'o', 'p', 'e', 'r', 'a', 't', 'i', 'n', 'g', '-', 'e', 'x', 'p', 'e', 'n', 's', 'e', 's', }) // CheckCoverFeeRouting is the Anti-Crowding-Out firewall (D-079, D-088). // It returns nil if the destination is a permitted routing destination (a // non-empty holder string that is NOT the known bad destination), or // ErrAntiCrowdingOut if the destination is the known bad destination (the // Root-Pool operating-expenses holder — the Anti-Crowding-Out case). // // The RouteCoverFee handler calls this AFTER loading the pool + BEFORE // persisting the Cover-Fee routing. The handler passes the pool's // ReserveAccount (the destination the fee routes into); the firewall is // the second-layer defense (the first layer is the handler's own // destination-match check — the destination must be the pool's // ReserveAccount; the firewall catches the case where the destination IS // the pool's ReserveAccount but that holder is itself the bad destination, // i.e. a pool misconfigured to route to Root-Pool operating expenses). // // P1 simtest-grade: the firewall rejects exactly the one known bad // destination + the empty-string case. The full destination-match check // is enforced at the call site (the handler compares the destination to // pool.ReserveAccount). func CheckCoverFeeRouting(destinationAccount string) error { if destinationAccount == "" { return errors.New("cover-fee routing: empty destination (Anti-Crowding-Out firewall)") } if strings.EqualFold(destinationAccount, badDestination) { return ErrAntiCrowdingOut } return nil }