For the complete documentation index, see llms.txt. This page is also available as Markdown.

51201 sc low contracts without payable entry points cannot withdraw nor claim rewards

Submitted on Jul 31st 2025 at 22:03:36 UTC by @jovi for Attackathon | Plume Network

  • Report ID: #51201

  • Report Type: Smart Contract

  • Report severity: Low

  • Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/plume/src/PlumeStakingRewardTreasury.sol

  • Impacts:

    • Permanent freezing of funds

    • Theft of unclaimed yield

Description

Brief / Intro

A missing “is-payable” check on stake admission means any non-payable contract can become a staker. Later, every native-coin transfer to that address reverts, freezing both yield and unstake withdraw claims.

Vulnerability Details

Both reward and principal payouts are executed with raw native transfers:

(bool success, ) = user.call{value: amountToWithdraw}("");
if (!success) revert NativeTransferFailed();   // StakingFacet.sol#L416-419

If the staker address is a smart contract without a payable receive() or fallback(), these calls always revert. The protocol nevertheless lets such contracts open stakes, so:

  • Rewards accumulate but can never be claimed.

  • Unstaked principal moves to the parked/withdrawable pool but withdraw() reverts.

The user’s rewards and entire stake end up locked permanently.

Impact

Asset / Function
Outcome

Staked PLUME principal

Permanent freezewithdraw() always reverts

Accrued native rewards

Unclaimable

Recommendation

1

Admission guard

Introduce an admission guard when someone opts to stake. Make sure to also have reentrancy protection so that the caller cannot exploit this mechanism. The 1 wei call ensures the staker is able to receive native amounts.

2

Rescue mechanism

Allow one-time designation of an alternate payable address if a native transfer fails.

3

Fail-safe escrow

Wrap calls in try/catch; credit failed amounts to an escrow users can sweep with an EOA.

4

Wrap native token

Consider wrapping the native token so that users receive back an ERC20 version of it.

Proof of Concept

1

Deploy a dummy contract without receive()/fallback().

2

Stake PLUME to any validator, using the dummy contract as staker.

3

Unstake after some time; wait for cooldown to mature.

4

Call withdraw() → tx reverts with NativeTransferFailed; principal remains trapped.

5

(Optional) Call claimRewards() before unstaking; observe identical revert for rewards.

Was this helpful?