69505 sc low user cannot revoke migration permit after migrator role is revoked

Submitted on Mar 15th 2026 at 09:35:04 UTC by @hcrlen for Audit Comp | Folks Finance: Staking Contracts

  • Report ID: #69505

  • Report Type: Smart Contract

  • Report severity: Low

  • Target: https://github.com/Folks-Finance/folks-staking-contracts/blob/main/src/Staking.sol

  • Impacts:

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

Description

Brief/Intro

The setMigrationPermit function enforces that the target address must currently hold MIGRATOR_ROLE for both granting and revoking permits. This means if an admin revokes a migrator's role, any user who previously granted that migrator a permit is permanently unable to revoke it, breaking the protocol's explicit documentation guarantee.

README.md:

  1. Grant permission to a specific migrator contract:

staking.setMigrationPermit(migratorAddress, true);

The migrator must hold the MIGRATOR_ROLE in the staking contract. The permission can be revoked at any time by calling setMigrationPermit(migratorAddress, false).

Vulnerability Details

setMigrationPermit checks the migrator role unconditionally:

The role check makes sense when granting a permit. You don't want users granting permits to random addresses. But when revoking, the check is unnecessary and harmful. If the admin revokes MIGRATOR_ROLE from a migrator, any user attempting to call setMigrationPermit(migrator, false) will revert with MigratorNotFound.

The protocol documentation explicitly states:

"The permission can be revoked at any time by calling setMigrationPermit(migratorAddress, false)."

Impact Details

The stale true permit persists in migrationPermits[migrator][user] mapping. If the admin ever re-grants MIGRATOR_ROLE to the same migrator address, that migrator can immediately call migratePositionsFrom(user) and migrate the user's positions, without the user's current consent, since they were unable to revoke during the window when the role was inactive.

References

Add any relevant links to documentation or code

Proof of Concept

Paste the following code snippet to staking.t.sol:

Was this helpful?