58051 sc low incorrect access control in acceptadminownership

Submitted on Oct 30th 2025 at 09:55:07 UTC by @dldLambda for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58051

  • Report Type: Smart Contract

  • Report severity: Low

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

  • Impacts:

    • Permanent freezing of funds

Description

Brief/Intro

The acceptAdminOwnership() function is incorrectly protected by the onlyAdmin modifier. This prevents the pending admin from claiming ownership, rendering the two-step ownership transfer mechanism broken. As a result:

The current admin is the only one who can call acceptAdminOwnership(). If the current admin loses access (key loss, multisig failure, etc.), no one can ever become the new admin. The contract becomes permanently unmanageable.

Vulnerability Details

Let's take a closer look at what the problem is.

onlyAdmin modifier:

That is, to call a function with this modifier, the caller must have admin status.

Two-step transfer of access rights:

The main problem is that function acceptAdminOwnership is implemented incorrectly. When called, it requires that the caller already be an admin, but he cannot be one because he is still a pendingAdmin.

It's a vicious circle: to call a function and gain admin rights, you need to be an admin, but you can't do that because you're not an admin.

Here's what a proper implementation looks like, for example (openzeppelin-contracts):

Impact Details

Ownership transfer is permanently broken — the two-step admin transfer mechanism fails at the final step. If the current admin becomes unavailable (lost key, team departure, multisig failure), no one can ever assume control.

References

https://github.com/alchemix-finance/v3-poc/blob/a192ab313c81ba3ab621d9ca1ee000110fbdd1e9/src/AlchemistCurator.sol#L27

https://github.com/alchemix-finance/v3-poc/blob/a192ab313c81ba3ab621d9ca1ee000110fbdd1e9/src/AlchemistCurator.sol#L31

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol

Proof of Concept

Proof of Concept

  1. add getters to MockAlchemistCurator:

  1. add this test and run:

And it will obviously fall under the current implementation, since it is impossible to accept the rights:

If you replace the implementation with the correct one, the test passes.

Was this helpful?