Contract fails to deliver promised returns, but doesn't lose value
Description
Brief/Intro
AlchemistCurator::acceptAdminOwnership() is incorrectly restricted by onlyAdmin modifier. After the current Admin calls transferAdminOwnership(newAdmin), the pendingAdmin cannot call acceptAdminOwnership() to complete the transfer because accept function require caller to already to be as current Admin. As implemented, only the current admin can accept the pending admin, which defeats the two step handover flow.
Vulnerability Details
The Root cause is that AlchemistCurator::acceptAdminOwnership() function is declared as:
// FILE: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistCurator.sol?utm_source=immunefi#L31-L35functionacceptAdminOwnership()externalonlyAdmin{// @audit-issue : current admin, should be pendingAdmin admin = pendingAdmin; pendingAdmin =address(0);emitAdminChanged(admin);}
Because of onlyAdmin, only the existing admin can call acceptAdminOwnersup(). The intended two-step transfer pattern requires the pendingAdmin to be able to opt in and claim the role. The correct guard is that caller must be equal pendingAdmin, not admin.
Impact Details
The acceptAdminOwnership() function is incorrectly restricted to the current admin, preventing the pending admin from claiming ownership. This breaks the two-step ownership transfer logic.
References
To achieve two step ownership look at the openzeppelin Ownable2Step process: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol#L60-L67
Broken Logic Code Link - https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistCurator.sol?utm_source=immunefi#L31-L35
Proof of Concept
Proof of Concept
Copy Paste this function into v3-poc/src/test/AlchemistCurator.t.sol: