52915 sc low yield are transferred before eligibility check potentially leading to freezing of funds
Submitted on Aug 14th 2025 at 09:13:02 UTC by @spongebob for Attackathon | Plume Network
- Report ID: #52915
- Report Type: Smart Contract
- Report severity: Low
- Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/arc/src/ArcToken.sol
- Impacts: Permanent freezing of funds
Description
The distributeYield and distributeYieldWithLimit functions in the ArcToken contract contain a flaw where yield tokens are transferred from the caller to the contract before checking if any token holders are eligible to receive yield.
Behavior:
- When all token holders are restricted from receiving yield (resulting in
effectiveTotalSupply == 0), the functions still execute thesafeTransferFromcall but thenemit YieldDistributed(0, ...)and return without distributing any tokens. - The transferred yield tokens become permanently trapped in the contract since there are no sweep or recovery functions implemented.
Root cause summary:
safeTransferFromexecutes first, pulling tokens from the caller.effectiveTotalSupplyis calculated afterward by checking yield restrictions.- If no holders are eligible (
effectiveTotalSupply == 0), the function returns early without distribution. - No recovery mechanism exists to retrieve the trapped tokens.
The same pattern exists in distributeYieldWithLimit where tokens are transferred on the first batch (startIndex == 0) before eligibility checks.
Impact
Yield tokens transferred to the contract cannot be recovered through any on-chain mechanism, leading to permanent loss of yield tokens when all token holders become ineligible for yield distribution.
Proof of Concept
Deploy ArcToken with yield restrictions
- Deploy an ArcToken contract with yield blacklist restrictions enabled.
- Set up a yield token (e.g., USDC) for distribution.
Create token holders and restrict them
- Mint ArcTokens to multiple addresses (e.g., Alice, Bob, Charlie).
- Add all token holders to the yield blacklist using
YieldBlacklistRestrictions.addToBlacklist().
Verify all holders are restricted
- Confirm
effectiveTotalSupply == 0by checking that_isYieldAllowed()returns false for all holders. - All holders should be ineligible for yield distribution.
Attempt yield distribution
- As a
YIELD_DISTRIBUTOR_ROLEaccount, approve yield tokens to the ArcToken contract. - Call
distributeYield(amount)with a significant amount of yield tokens.
Observe fund loss
- The
safeTransferFromexecutes first, transferring yield tokens from caller to contract. - Function calculates
effectiveTotalSupply == 0and emitsYieldDistributed(0, yieldTokenAddr). - Yield tokens are now permanently trapped in the
ArcTokencontract.
Confirm permanent loss
- Check that yield tokens are stuck in the contract balance.
- Verify no sweep/recovery functions exist in the contract to retrieve the tokens.
- Funds remain inaccessible to all parties, including contract administrators.
Note: The same scenario applies to distributeYieldWithLimit() when called with startIndex == 0.
References
- ArcToken.sol at lines referenced above:
- https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/arc/src/ArcToken.sol#L408
- https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/arc/src/ArcToken.sol#L417-L423
- https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/arc/src/ArcToken.sol#L425-L428
- https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/arc/src/ArcToken.sol#L526-L528