This guide outlines the implementation steps for routing protocol parameter changes through governance instead of per-contract admin timelocks.
- File:
governance_parameter_inventory.md - Content: Complete inventory of all admin-settable parameters across pool, invoice, oracle_registry, and compliance contracts
- Added
GovernanceActionenum with typed variants for all governance-gated parameters - Updated
Proposalstruct to useGovernanceActioninstead of genericfunction_name/calldata - Added cross-contract client traits for pool, invoice, oracle_registry, and compliance
- Implemented
execute_governance_action()to dispatch all governance actions - Added governance address storage, bootstrap, and getter functions
- Added
GovernanceNotConfigurederror andrequire_governance()helper
- Pool Contract: Added 28
*_via_governanceentrypoints - Invoice Contract: Added 19
*_via_governanceentrypoints - Oracle Registry Contract: Added 4
*_via_governanceentrypoints - Compliance Contract: Added 2
*_via_governanceentrypoints
- Updated
packages/sdk/src/generated/governance.tswith new types:- Added
GovernanceNotConfigurederror - Added
ProposalStatus,ProposalCategoryenums - Added
LoyaltyTier,FeeTier,CollateralConfig,QuorumTierinterfaces - Added
GovernanceActiondiscriminated union with all action variants - Added
ProposalandGovernanceConfiginterfaces
- Added
- Created
packages/sdk/src/clients/governance.tsclient with:initialize()- Bootstrap governance contractcreateProposal()- Create governance proposals with typed actionsvote()- Vote on proposalsexecuteProposal()- Execute passed proposalscancelProposal()- Cancel proposalsgetProposal()/getAllProposals()- Query proposalsgetConfig()- Get governance configurationgetGovernanceAddress()- Get governance address from target contractsgovernanceActionToScVal()- Convert TypeScript actions to Soroban ScVal
- Updated
packages/sdk/src/index.tsto export governance client and types
- Added "Governance" link to admin navigation in
frontend/components/AdminNav.tsx - Created
frontend/app/admin/governance/page.tsxwith:- Proposal listing with status badges and category tags
- Voting interface (for/against) for active proposals
- Execution button for passed proposals
- Vote progress visualization with for/against bars
- Quorum and pass threshold indicators
- Stats cards showing total, active, passed, and executed proposals
- Create proposal modal (placeholder - requires governance contract deployment)
- Action description formatting for all governance action types
- Added governance translations to
frontend/locales/en/common.json:- Navigation label
- All UI text for proposal listing, voting, and execution
- Status and category labels
- Action descriptions for common parameter changes
- Created
contracts/governance/tests/governance_flow_tests.rswith comprehensive integration tests:- Basic governance flow tests (create proposal, vote, execute after timelock)
- Pool parameter change tests (yield, treasury, fee tier, collateral config)
- Invoice parameter change tests (grace period, max amount)
- Oracle registry parameter change tests (invoice contract, quorum tiers)
- Compliance parameter change tests (rescreening interval, screener timelock)
- Governance gating tests (reject non-governance callers, require governance address)
- Quorum and pass threshold tests (reject when not met)
- Timelock tests (cannot execute before timelock expires)
All governance parameter change refactoring tasks have been completed:
✅ Phase 1: Parameter inventory and classification
✅ Phase 2: Governance contract updates with typed actions
✅ Phase 3: Target contract governance-gated entrypoints (53 total across 4 contracts)
✅ Phase 4: SDK updates with governance client and types
✅ Phase 5: Frontend admin UI for governance proposals
✅ Phase 6: Integration tests for full governance flow
The protocol now has a centralized governance system that routes all governance-gated parameter changes through the governance contract with proposal, vote, timelock, and execute safeguards.
Pattern for *_via_governance entrypoints:
pub fn set_yield_via_governance(
env: Env,
governance: Address,
new_yield_bps: u32,
) -> Result<(), PoolError> {
governance.require_auth();
Self::require_governance(&env, &governance)?;
// ... existing setter logic ...
Ok(())
}File: contracts/invoice/src/lib.rs
Required Changes:
- Add
GOVERNANCESymbol key - Add
require_governance()helper function - Add
*_via_governanceentrypoints for all governance-gated parameters (19 parameters) - Add
set_governance_address()bootstrap entrypoint - Add
get_governance_address()getter - Add
GovernanceNotConfigurederror variant toInvoiceError
File: contracts/oracle_registry/src/lib.rs
Required Changes:
- Add
GOVERNANCESymbol key - Add
require_governance()helper function - Add
*_via_governanceentrypoints for all governance-gated parameters (4 parameters) - Add
set_governance_address()bootstrap entrypoint - Add
get_governance_address()getter - Add
GovernanceNotConfigurederror variant toOracleRegistryError
File: contracts/compliance/src/lib.rs
Required Changes:
- Add
GOVERNANCESymbol key - Add
require_governance()helper function - Add
*_via_governanceentrypoints for all governance-gated parameters (2 parameters) - Add
set_governance_address()bootstrap entrypoint - Add
get_governance_address()getter - Add
GovernanceNotConfigurederror variant toComplianceError
File: contracts/governance/src/lib.rs
Remaining Work:
- Complete all client trait methods (currently only a few are implemented)
- Implement all remaining
GovernanceActionvariants inexecute_governance_action() - Add proper error handling for cross-contract call failures
Directory: packages/sdk/sdk/
Required Changes:
- Update governance client to use new
create_proposalsignature withGovernanceAction - Add helper functions for creating parameter change proposals:
create_yield_change_proposal()create_grace_period_proposal()- etc. (one helper per common parameter change)
- Update type generation to include new
GovernanceActionenum
Directory: frontend/app/admin/*
Required Changes:
- Update parameter change forms to create governance proposals instead of direct admin calls
- Add governance proposal status tracking UI
- Keep emergency pause as direct action (fast path)
- Update parameter change confirmation dialogs to show proposal flow
- Add proposal history view for parameter changes
Directory: contracts/*/tests/
Required Changes:
- Update existing tests that call admin setters directly to use governance flow
- Add test-only fast paths clearly marked as
#[cfg(test)] - Add integration tests for full proposal → vote → timelock → execute cycle
- Test at least one parameter per affected contract through full governance cycle
- Test emergency pause path independently
- Test governance address bootstrap and rotation
- Test each
*_via_governanceentrypoint with proper governance verification - Test rejection when governance address not configured
- Test rejection when caller is not governance contract
- Test parameter validation still works via governance path
- Test full governance cycle:
- Bootstrap governance address on target contract
- Create proposal with
GovernanceAction - Vote on proposal
- Wait for voting period + timelock
- Execute proposal
- Verify parameter changed on target contract
- Test emergency pause independently
- Test governance address rotation
Add test helpers that bypass governance for unit tests:
#[cfg(test)]
fn test_set_yield_directly(env: &Env, new_yield: u32) {
// Direct setter for testing only
}- Deploy updated governance contract with new
GovernanceActionenum and client traits - Update target contracts with
*_via_governanceentrypoints - Bootstrap governance relationship by calling
set_governance_address()on each target contract - Gradual migration: Keep existing admin setters for backward compatibility, mark as deprecated
- Update SDK and frontend to use new governance flow
- Monitor and deprecate: After successful migration, consider removing or restricting direct admin setters
If issues arise:
- Governance-gated entrypoints are additive - existing admin setters still work
- Can revert to old flow by not calling
set_governance_address() - Access control multisig path remains independent and unaffected
- Governance address bootstrap: Only callable by admin, one-time setup per contract
- Self-rotation: Governance contract can rotate its own address via
set_governance_address()if needed - Emergency pause: Remains fast via admin or access_control multisig
- Timelock: Governance proposals still respect existing timelock (48 hours default)
- Quorum tiers: Parameter changes use lower quorum (10%) than critical actions (50%)
- Every parameter classified as "governance-gated" can only be changed via executed governance proposal
- Emergency-pause path remains fast and independently testable
- Full proposal → vote → timelock → execute → parameter-changed cycle covered by integration test
- Admin frontend reflects new flow with no dead UI pointing at removed direct-setter entrypoints
- All governance-gated parameters have corresponding
*_via_governanceentrypoints - Governance contract can execute all defined
GovernanceActionvariants - SDK updated for new governance flow
- Frontend admin pages updated for governance proposal UI
- Contract test suites updated to use new governance flow
- Integration tests added for full governance cycle
- Implement Phase 1: Add
*_via_governanceentrypoints to all four contracts - Complete Phase 2: Finish implementing all
GovernanceActionvariants in governance contract - Test Phase 1-2: Write unit tests for new entrypoints
- Implement Phase 3: Update SDK
- Implement Phase 4: Update frontend
- Implement Phase 5: Update all contract tests
- Integration testing: Add full governance cycle tests
- Documentation: Update deployment guides and user documentation