Skip to content

Base/pre 1221 - #1382

Merged
mikewheeleer merged 2 commits into
Talenttrust:mainfrom
gracepeterfejokwu:base/pre-1221
Aug 30, 2026
Merged

Base/pre 1221#1382
mikewheeleer merged 2 commits into
Talenttrust:mainfrom
gracepeterfejokwu:base/pre-1221

Conversation

@gracepeterfejokwu

@gracepeterfejokwu gracepeterfejokwu commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Closes #1346


#closes1221

Implements a request → approve/reject → apply state machine that prevents
a single unreviewed request from executing a high-impact parameter override.

## What changed

### Core state machine (contracts/escrow/src/governance_proposal.rs)
Four new entrypoints on the Escrow contract:
- request_governance_proposal(kind) → proposal_id
  Admin-gated. Validates the payload immediately (same bounds as the live
  setter). Returns a monotonic ID and stores a Pending proposal.
- approve_governance_proposal(proposal_id, approver)
  Approver must differ from the requester (GovernanceSelfApproval guard).
  Moves proposal from Pending → Approved.
- reject_governance_proposal(proposal_id, approver)
  Same self-approval guard. Moves Pending → Rejected (terminal).
- apply_governance_proposal(proposal_id)
  Admin-gated. Requires Approved state. Materialises the parameter change,
  then sets Applied (idempotency: a second call fails with
  GovernanceProposalInvalidState).

Two read-only helpers:
- get_governance_proposal(id) → Option<GovernanceProposal>
- get_next_governance_proposal_id() → u64

### Types (contracts/escrow/src/types.rs)
- GovernanceProposalKind enum (SetProtocolFeeBps, SetGovernedParams,
  SetFeeWithdrawalCap, SetFeeWithdrawalCooldown, SetMaxMilestones)
- GovernanceProposalState enum (Pending / Approved / Rejected / Applied)
- GovernanceProposal struct with requester, approver, state, kind, expiry
- DataKey variants: GovernanceProposal(u64) and NextGovernanceProposalId
- Error codes: GovernanceProposalNotFound(77), GovernanceProposalInvalidState(78),
  GovernanceProposalExpired(79), GovernanceSelfApproval(80)

### TTL (contracts/escrow/src/ttl.rs)
- GOVERNANCE_PROPOSAL_TTL_LEDGERS = LEDGERS_PER_DAY * 3 (~3 days)
- GOVERNANCE_PROPOSAL_BUMP_THRESHOLD = LEDGERS_PER_DAY
- set_governance_proposal_ttl() helper

### Tests (contracts/escrow/src/test/governance_proposal.rs) — 26 tests
All 5 required edge cases from issue Talenttrust#1221:
1. request by operator — admin submits proposal; ID positive; Pending state
2. self-approval — requester cannot approve own proposal → GovernanceSelfApproval
3. expired request — any action past TTL → GovernanceProposalExpired
4. rejected request — Rejected terminal; approve/apply fail → GovernanceProposalInvalidState
5. apply twice — second apply → GovernanceProposalInvalidState

Plus: happy-path round-trips for all 3 proposal kinds, not-found guards,
expiry boundary (succeeds at boundary, fails one past), events for each
transition, multiple independent proposals, approver identity recorded.

## Security notes
- Self-approval is blocked at both approve and reject paths.
- Expiry is checked on every mutable action; the 3-day window limits the
  blast radius of a forgotten or compromised pending proposal.
- Rejection is terminal — a rejected proposal cannot be re-approved or
  applied; the admin must open a fresh proposal.
- Applied proposals cannot be re-applied (idempotency guard on state).
- Payload is validated at request time so invalid values never reach storage.

Closes Talenttrust#1221
…-approval-1221

feat(governance): two-step approval workflow for high-impact overrides
@mikewheeleer
mikewheeleer merged commit 05cccc2 into Talenttrust:main Aug 30, 2026
1 check passed
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.

Normalize token amounts using configured decimals

2 participants