The logic of first transfer the tokens and them update the global state lead to loss of fees in case a new user enters in staking before receive rewards.
If a new user enters the staking contract after it has already received rewards and the contract's balance is updated before the state, the general state will be calculated with incorrect values. This leads to a decrease in rewards per token and causes some tokens to become stuck in the contract.
It almost the same issue of first deposit but it can happens in any deposit time.
In this POC half o rewards got stuck in the contract.
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);
address public staker2 = address(0x3);
function setUp() public {
beedle = new TERC20();
weth = new TERC20();
staking = new Staking(address(beedle), address(weth));
beedle.mint(staker, 10e18);
beedle.mint(staker2, 10e18);
}
function testStuckRewardOnDeposit() public {
vm.startPrank(staker);
beedle.approve(address(staking), 10e18);
staking.deposit(10e18);
vm.stopPrank();
weth.mint(address(staking), 1e18);
vm.startPrank(staker2);
beedle.approve(address(staking), 10e18);
staking.deposit(10e18);
vm.stopPrank();
vm.prank(staker);
staking.claim();
console2.log("Staker claim: ", weth.balanceOf(staker));
console2.log("Contract balance: ", weth.balanceOf(address(staking)));
assertGt(weth.balanceOf(address(staking)), 0);
weth.mint(address(staking), 1e18);
vm.prank(staker);
staking.claim();
vm.prank(staker2);
staking.claim();
console2.log("Staker claim: ", weth.balanceOf(staker));
console2.log("Staker 2 claim: ", weth.balanceOf(staker2));
console2.log("Contract balance: ", weth.balanceOf(address(staking)));
assertGt(weth.balanceOf(address(staking)), 0);
}
}