Skip to content

feat: optional governed protocol fee module - #421

Open
ghg001 wants to merge 18 commits into
tributary-protocol:mainfrom
ghg001:fix/issue-354-optional-protocol-fee-module-with-governed-rate
Open

feat: optional governed protocol fee module#421
ghg001 wants to merge 18 commits into
tributary-protocol:mainfrom
ghg001:fix/issue-354-optional-protocol-fee-module-with-governed-rate

Conversation

@ghg001

@ghg001 ghg001 commented Aug 30, 2026

Copy link
Copy Markdown

Overview

This PR adds an optional protocol-level fee module to Tributary, off by default and backward compatible. A governed fee rate and recipient are applied once at the top level of pay/distribute/pay_many, with the remaining amount split exactly among recipients. The accounting invariant is updated to amount_in == fee + sum(recipient_amounts), with zero-rate behavior identical to today and an on-chain cap preventing abusive governance rates.

Related Issue

Closes the optional protocol fee module bounty.

Changes

⚖️ Governed Protocol Fee Module

  • [DECISION] Product direction recorded: protocol-level optional fee only; per-split fee configs are deferred. Nested splits do not re-apply the fee; escrow deposit/distribute apply the fee at distribution time; each pay_many leg applies the configured fee once.

  • [ADD] contracts/splitter/src/lib.rs

    • Added FeeConfig { rate_bps, recipient } state.
    • Added governance-only set_fee_config with MAX_FEE_BPS = 1000 (10%) enforced on-chain.
    • Added zero-rate default and short-circuit so disabled fees cost nothing.
  • [MODIFY] contracts/splitter/src/lib.rs

    • Updated amounts()/pay()/distribute() to skim fee = amount_in * rate_bps / 10000 before recipient splitting.
    • Emitted FeeTaken event leg for transparency.
    • Preserved existing dust/rounding rules on the post-fee remainder.
  • [MODIFY] contracts/splitter/src/test.rs

    • Added conservation-with-fee tests.
    • Added zero-rate equivalence tests.
    • Added nested-split no-double-fee tests.
    • Added pay_many and escrow composition tests.
  • [MODIFY] contracts/splitter-proofs/src/model.rs and contracts/splitter-proofs/src/proofs.rs

    • Updated invariants to prove amount_in == fee + sum(recipient_amounts).
    • Kept differential-fuzzing models aligned with the new fee-aware state.
  • [MODIFY] sdk/src/index.ts

    • Exposed fee config getters/setter through the SDK.
    • Added fee and fee recipient to preview_payout results.
  • [MODIFY] app/src/lib/tributary.ts and app/src/lib/fees.ts

    • Surfaced fee rate, recipient, and fee-aware previews in the dashboard.

Verification Results

cargo test -p splitter
✅ 54/54 passed (conservation-with-fee, zero-rate equivalence, nested-split no-double-fee)

cargo test -p splitter-proofs
✅ Invariant checks passed: amount_in == fee + sum(recipient_amounts)

npm test -- --run src/lib/__tests__/feeModule.test.ts
✅ 18/18 passed (SDK fee config + preview_payout + dashboard fee surfacing)
Acceptance Criteria Status
Maintainer decision recorded before implementation ✅ Protocol-level optional fee approved; per-split fees deferred; zero-rate default preserves current behavior
amount_in == fee + sum(recipient_amounts) holds exactly ✅ Updated invariant tests and proofs enforce exact reconstruction with floor fee and existing dust rules
Zero fee rate is behaviorally identical and costs no extra resources ✅ Zero-rate short-circuit skips fee math/storage/events
Rate is governed with enforced on-chain maximum ✅ Governance-only setter; MAX_FEE_BPS = 1000 (10%) enforced in set_fee_config
preview_payout and events expose the fee transparently preview_payout returns fee/fee_recipient; FeeTaken emitted for pay/distribute

Closes #354

@ghg001
ghg001 requested a review from Spagero763 as a code owner August 30, 2026 14:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optional protocol fee module with governed rate and conservation-preserving accounting

1 participant