57090 sc low ownership transfer failure in alchemistcurator https github com alchemix finance v3 poc blob immunefi audit src alchemistcurator sol prevents future dao governance or recovery

Submitted on Oct 23rd 2025 at 11:45:54 UTC by @Ratt13snak3 for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #57090

  • Report Type: Smart Contract

  • Report severity: Low

  • Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistCurator.sol

  • Impacts:

    • Contract fails to deliver promised returns, but doesn't lose value

Description

Brief / Intro

The AlchemistCurator contract defines a two-step ownership transfer mechanism (transferAdminOwnerShip and acceptAdminOwnershiparrow-up-right), but the latter function incorrectly restricts access to the current admin instead of the pendingAdmin. Although the underlying PermissionedProxy base contract allows direct admin reassignment via setAdmin()arrow-up-right, this inconsistency introduces a broken transfer path. If the DAO or multisig relies on the two-step process for ownership transfer, ownership transfer will silently fail, potentially leading to governance confusion or lockout.


Vulnerability Details

In AlchemistCurator, ownership transfer is handled through transferAdminOwnerShip() and acceptAdminOwnership(). However, the acceptAdminOwnership() function incorrectly restricts execution to the current admin instead of allowing the pendingAdmin to call it.

function acceptAdminOwnership() external onlyAdmin {
    admin = pendingAdmin;
    pendingAdmin = address(0);
    emit AdminChanged(admin);
}

This means only the existing admin can call acceptAdminOwnership(), not the pendingAdmin who is meant to assume ownership. As a result, a new admin cannot be set unless acceptAdminOwnership() is called by the current admin, which breaks the intended flow. As a result:

  • Ownership can be set correctly at deployment.

  • But any later ownership transfer attempt will fail, since the pendingAdmin cannot accept the role.

  • Following the intended flow, the contract remains permanently locked under the current admin, blocking any DAO or governance role rotation. unless setAdmin() or acceptAdminOwnership() is called by the admin.

Because AlchemistCurator is a permissioned proxy controlling privileged actions (like managing operators or Permissioned Calls), losing the ability to change the admin can freeze critical system functions.


Impact Details

Severity: Medium

  • Governance control confusion or lockout Although ownership can still technically be changed via setAdmin(), the inconsistency between mechanisms can lead to misconfigured or irrecoverable governance state in practice.


References

Proof of Concept

Proof of Concept

insert the test function below into AlchemistCurator.t.solarrow-up-right

then run:

Was this helpful?