Impacts: Temporary freezing of funds for at least 24 hours
Description
Brief / Intro
ArcToken.distributeYield pulls the yield token from the distributor (treasury) before it computes the “eligible supply.” If every holder is ineligible (e.g., all blacklisted/sanctioned so effTotal == 0), no recipients are credited, yet the transferred amount remains stranded on the ArcToken contract address with no withdrawal mechanism. In production, anyone with YIELD_DISTRIBUTOR_ROLE (or a compromised treasury) could permanently lock arbitrary amounts of the yield token on ArcToken by calling distributeYield during an all-ineligible state, resulting in irreversible loss of unclaimed yield and potential depletion of the protocol’s yield funds.
Root cause: In ArcToken.distributeYield, the contract pulls the yield token from the distributor (treasury) before validating that there is any eligible supply to receive it.
When all holders are ineligible (e.g., every account fails either YieldBlacklistRestrictions or the global isYieldAllowed check), the function proceeds to compute an effective total of 0, distributes to no one, and then returns — leaving the transferred amount stranded on the ArcToken contract address. There is no refund path so the funds become permanently locked.
What the execution shows:
The caller approves and distributeYield(amount) is invoked.
This results in a retained balance on the ArcToken contract with no mechanism to retrieve it.
Impact Details
What can be lost? The entire yield-asset amount passed to distributeYield(amount) when the effective eligible supply is 0. Because the function pulls tokens from the distributor (e.g., treasury) before checking eligibility and credits no one when effTotal == 0, the full amount becomes permanently stranded at ArcToken.
If the treasury holds 5,000,000 units of the yield token and has unlimited approval to ArcToken, then a single call with amount = 5,000,000 while everyone is ineligible locks the full 5,000,000 on ArcToken.
The following Forge test demonstrates the issue. The second test makes every account ineligible, calls distributeYield from the treasury and asserts that no one received funds while the ArcToken contract retained the tokens (unexpected behavior).
Run the test: forge test --match-test test_DistributeYield_NoEligibleSupplyCreditsNoOne -vvvv