20,000 USDC
View results
Submission Details
Severity: high
Valid

WETH token balance is incorrectly updated when claiming staking rewards resulting in stuck WETH rewards

Summary

The Staking contract incorrectly updates the WETH token balance when claiming staking rewards, preventing newly topped-up WETH rewards from being reflected in the index storage variable.

Vulnerability Details

Claiming staking rewards with the claim function in the Staking contract updates the contract's storage variable balance to the current WETH balance in line 47. This update intends to reflect the WETH transfer out of the contract to the staker, i.e., the sent rewards.

However, if the total supply (totalSupply, i.e., staked TKN tokens) is zero, the reward index is not updated due to the if in line 63, while the WETH balance is updated in line 57. Please note that totalSupply can be zero right after the staking contract is deployed and before stakers deposit tokens. It can also be zero at a later time in case all stakers unstaked their deposits. Moreover, the claim function can be called by anyone, even if the caller is not a staker and has no claimable rewards.

Storing the current WETH token balance in balance while there have been new WETH tokens sent to the contract as rewards (by the Fees contract), those rewards are not reflected in the index as the balance check in line 65 would not detect the WETH balance change.

As the Lending contract continuously accrues fees and sends fees via the Fees contract to the Staking contract, WETH rewards can potentially accumulate in the Staking contract before stakers deposit tokens (i.e., totalSupply == 0). Thus the chosen High severity as the likelihood of stuck WETH rewards is high.

Staking.sol#L57

53: function claim() external {
54: updateFor(msg.sender);
55: WETH.transfer(msg.sender, claimable[msg.sender]);
56: claimable[msg.sender] = 0;
57: @> balance = WETH.balanceOf(address(this)); // @audit-info `balance` is cached here
58: }

Staking.sol#L65

61: function update() public {
62: uint256 totalSupply = TKN.balanceOf(address(this));
63: if (totalSupply > 0) {
64: uint256 _balance = WETH.balanceOf(address(this));
65: @> if (_balance > balance) { // @audit-info `balance` got cached in a previous `claim` call and thus `_balance` and `balance` are equal
66: uint256 _diff = _balance - balance;
67: if (_diff > 0) {
68: uint256 _ratio = _diff * 1e18 / totalSupply;
69: if (_ratio > 0) {
70: index = index + _ratio;
71: balance = _balance;
72: }
73: }
74: }
75: }
76: }

Impact

Topped-up WETH rewards can not be claimed as rewards and remain unutilized and stuck in the Staking contract.

Tools Used

Manual Review

Recommendations

Consider subtracting the claimed rewards amount from balance in line 57 instead of storing the current WETH balance.

Additionally, consider reverting if no rewards are claimable, i.e., claimable[msg.sender] == 0 by the caller (msg.sender) in the claim function.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.