Problem Statement
Every contract replaces its admin in a single linear call frame. There is no two-step transfer (propose → accept) for:
reward-pool::Admin (currently no setter at all — the initialize is the only place this is set).
course-registry::Admin, course-registry::RewardPoolAddress, course-registry::BadgeNftAddress.
badge-nft::Admin (the authorized registry).
quest-engine::Admin, QuestEngine::StakeVault, QuestEngine::RewardPool.
stake-vault::Admin.
governance::Admin, Governance::BadgeContractAddress.
A typo or loss of admin key in any of these single-step transfers is catastrophic.
Why It Matters
- A buggy key sent to
set_reward_pool_address could route all USDC payouts to an enemy contract.
- Standard practice (OpenZeppelin AccessControl, Compound Timelock) is two-step with explicit acceptance.
- Audit deficiency.
Expected Outcome
For each contract:
- A
propose_new_X(proposer, proposed_value) callable by current admin.
- A
accept_X(acceptor) callable by the proposed value, setting the live value.
- 7-day timelock or similar (configurable per contract).
- Events
XTransferProposed, XTransferAccepted, XTransferCancelled.
Acceptance Criteria
- All proposed transfers are cancellable by the proposer or admin.
- Acceptance by non-proposed addresses is rejected.
- Tests for propose-then-typo-then-cancel recovery.
- README updated with the new auth flow.
Implementation Notes
- Consider a shared
TwoStepAdmin helper in contracts/common.
- Reference EIP-1967 / Slip-004 standards.
Files / Modules Affected
All six contracts; possible contracts/common.
Dependencies
Issue #7 (common crate) helpful.
Difficulty
Hard.
Estimated Effort
3–5 days.
Suggested Labels
security, feature, P1, access-control
Problem Statement
Every contract replaces its admin in a single linear call frame. There is no two-step transfer (propose → accept) for:
reward-pool::Admin(currently no setter at all — theinitializeis the only place this is set).course-registry::Admin,course-registry::RewardPoolAddress,course-registry::BadgeNftAddress.badge-nft::Admin(the authorized registry).quest-engine::Admin,QuestEngine::StakeVault,QuestEngine::RewardPool.stake-vault::Admin.governance::Admin,Governance::BadgeContractAddress.A typo or loss of admin key in any of these single-step transfers is catastrophic.
Why It Matters
set_reward_pool_addresscould route all USDC payouts to an enemy contract.Expected Outcome
For each contract:
propose_new_X(proposer, proposed_value)callable by current admin.accept_X(acceptor)callable by the proposed value, setting the live value.XTransferProposed,XTransferAccepted,XTransferCancelled.Acceptance Criteria
Implementation Notes
TwoStepAdminhelper incontracts/common.Files / Modules Affected
All six contracts; possible
contracts/common.Dependencies
Issue #7 (common crate) helpful.
Difficulty
Hard.
Estimated Effort
3–5 days.
Suggested Labels
security,feature,P1,access-control