Full Report
Tidal Finance is a discretionary mutual cover protocol that offers the DeFi community the ability to hedge against the failure of any DeFi protocol or asset. In normal person terms, this is insurance. A user is able to stake or add assets to the pool. By providing assets to the pool, they are entitled to rewards from fees for using the service. The protocol needs to distribute these rewards evenly, depending on the amount of value contributed. The payout for rewards has four steps: startPayout, setPayout, doPayout, finishPayout. The administrator of the contract can initiate a payout for Asset at some index. Then, the four payout functions are called in order. The payout is marked as finished. A user has two main fields: rewardAmount and rewardDebt. The rewardAmount indicates the amount of money a user should be paid out. rewardDebt shows the amount taken out already. Since the pool had already finished, the rewardDebt for the asset is 0 (default value of Solidity). When doing the payout, the math user.rewardAmount - user.rewardDebt is performed. But, user.rewardDebt starts with 0, giving the user more money than they are really entitled to. In particular, the unset variable of the user for a specific field is what is causing the problem here. Many of the bugs in smart contracts are logic issues with the handling of money. It is really easy to have small/subtle mistakes with multi-step processes.
Analysis Summary
# Vulnerability: Tidal Finance Staking Reward Over-Accrual Logic Error
## CVE Details
- CVE ID: N/A (Reported vulnerability, not officially assigned a CVE at the time of summarization)
- CVSS Score: Not provided (Likely High or Critical based on impact of potentially draining rewards)
- CWE: CWE-840 (Improper Initialization / Logic Error)
## Affected Systems
- Products: Tidal Finance Staking Contract (on Polygon)
- Versions: Prior to commit `924e87f1aead70abb17760c839b53ba40d80bf2c`
- Configurations: Default configurations where `rewardDebt` for a user was not being correctly updated after a payout process completed for a specific asset index.
## Vulnerability Description
The vulnerability lies in the four-step reward payout process (`startPayout`, `setPayout`, `doPayout`, `finishPayout`). Specifically, after an asset index has successfully completed a payout, the user's `rewardDebt` (the amount already claimed) for that asset should be updated to equal their accrued `rewardAmount`.
The flaw was that the system failed to update `user.rewardDebt` to reflect the paid-out amount following the completion of the payout sequence for an asset index. In Solidity, unset variables default to 0. Therefore, if a user later called `doPayout` for that *already finalized* asset index, the calculation for the reward was performed as `user.rewardAmount - user.rewardDebt`. Since `user.rewardDebt` remained 0, the user received the *full* accrued `rewardAmount` again, effectively claiming unearned rewards, as the system could not recognize the previous payout.
## Exploitation
- Status: PoC available (The vulnerability was reported and fixed via a bug bounty submission, indicating a successful proof of concept likely existed by the whitehat.)
- Complexity: Medium (Requires understanding the multi-step payout process and timing the attack correctly after an admin-initiated payout).
- Attack Vector: Network (Via contract interaction)
## Impact
- Confidentiality: None (N/A for token/logic bug)
- Integrity: High (Allows unauthorized draining of staked reward tokens from the pool).
- Availability: Low (The flaw affects accounting/financial integrity, not service availability).
## Remediation
### Patches
- **Commit:** `924e87f1aead70abb17760c839b53ba40d80bf2c`
- **Fix Details:** The line `user.rewardDebt = user.amount.mul(poolInfo.accRewardPerShare).div(UNIT_PER_SHARE);` was duplicated and moved into the logic block checking if the payout for that asset index was `finished`. This ensures `rewardDebt` is correctly set *after* the reward has been finalized and paid out for that index.
### Workarounds
- No official long-term workaround was published, as a full patch was deployed. The temporary workaround during the time the fix was pending would have been to strictly monitor and halt any process that allowed reward claims on assets that had recently completed a payout cycle until the debt tracking was fixed.
## Detection
- **Indicators of Compromise:** Unexpected large drains of reward tokens from the staking contract attributed to existing stakers following a governance-initiated asset payout. Monitoring transactions involving the `doPayout` function post-payout completion.
- **Detection Methods and Tools:** Static analysis tools specializing in logic errors and variable initialization checks (especially in multi-step transactional flows). On-chain monitoring focused on the discrepancy between accrued rewards (`rewardAmount`) and recorded debt (`rewardDebt`) post-payout.
## References
- [Tidal Finance Logic Error Bugfix Review - Immunefi Post (Medium link)](https://medium.com/immunefi/tidal-finance-logic-error-bugfix-review-666b1b4578b0)
- [Vulnerable Commit (Staking Contract)](https://github.com/TidalFinance/tidal-contracts/commit/479439954b72041422dcd109a6562451398e1c45#diff-46a924754f71a2f8be88d0f20295f40653c881426d64b90e8bdd4f4bed303368)
- [Fix Commit](https://github.com/TidalFinance/tidal-contracts/commit/924e87f1aead70abb17760c839b53ba40d80bf2c#diff-46a924754f71a2f8be88d0f20295f40653c881426d64b90e8bdd4f4bed303368)