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

Does not update rewards state correctly on ```Staking.deposit()``` resulting in significant loss of rewards of the existing stakers.

Summary

Staking.deposit() There's a significant issue in how the contract updates its record of cumulative rewards, which results in users not receiving the correct amount of rewards and the remaining portion of funds stuck in the contract forever.

Vulnerability Details

Staking.deposit() There's a significant issue in how the contract updates its record of cumulative rewards, which results in users not receiving the correct amount of rewards and the remaining portion of funds stuck in the contract forever.

Here's an example to illustrate the issue: Suppose the staking contract has 100 TKN deposited, and the protocol transfers 100 WETH as rewards for those staking their tokens. Until the next action that changes the state of the staking contract, the cumulative rewards and other necessary details aren't updated.

Now, let's say another user deposits 100 TKN into the contract. The deposit function takes 100 TKN from the user and updates the contract's state. However, instead of calculating the additional cumulative rewards as 100 WETH divided by the original 100 TKN (which would be correct), it calculates the additional cumulative rewards as 100 WETH divided by the new total of 200 TKN. This is because the contract fetches the current deposit amount before it updates the rewards information. As a result, the rewards are distributed over a larger number of tokens than they should be, leading to inaccurately low reward payouts.

Impact

users will not receive the correct amount of rewards and funds will get stuck in the contract forever.

POC

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test, console2} from "forge-std/Test.sol";
import {ERC20, IERC20} from "openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Staking} from "../src/Staking.sol";
contract Token is ERC20 {
constructor(address minter) ERC20("TKN", "TOKEN") {
_mint(minter, 1e23);
}
}
contract StakingTest is Test {
Staking public staking;
IERC20 public weth;
ERC20 public tkn;
address wethWhale = 0x8EB8a3b98659Cce290402893d0123abb75E3ab28;
address user = address(123456);
function setUp() public {
vm.createSelectFork("https://mainnet.infura.io/v3/API_KEY");
tkn = new Token(wethWhale);
weth = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
staking = new Staking(address(tkn), address(weth));
vm.deal(user, 100e18);
vm.startPrank(wethWhale);
tkn.transfer(user, 1000e18);
vm.stopPrank();
}
function testStake() public {
uint256 amount = 1e20;
vm.startPrank(wethWhale);
tkn.approve(address(staking), amount);
staking.deposit(amount);
console2.log("Initial TKN Deposit by user 1: ", amount / 1e18);
// deposit 100 weth as rewards
weth.transfer(address(staking), amount);
console2.log("Total reward WETH deposited: ", weth.balanceOf(address(staking)) / 1e18);
vm.stopPrank();
vm.startPrank(user);
tkn.approve(address(staking), amount);
staking.deposit(amount);
console2.log("Later TKN deposit by user 2: ", amount / 1e18);
// deposit 100 weth as rewards
vm.stopPrank();
uint256 wethBalBefore = weth.balanceOf(wethWhale);
vm.startPrank(wethWhale);
console2.log("Updated Index: ", staking.index());
console2.log("Total Reward in the pool before claim: ", weth.balanceOf(address(staking)) / 1e18);
staking.claim();
uint256 wethBalAfter = weth.balanceOf(wethWhale);
console2.log("Total TKN supply", tkn.balanceOf(address(staking)) / 1e18);
console2.log("Reward claimed by user expected 100 but getting: ", (wethBalAfter - wethBalBefore) / 1e18);
console2.log("Reward in the pool after claim: ", weth.balanceOf(address(staking)) / 1e18);
assertEq(wethBalAfter - wethBalBefore, amount / 2);
vm.stopPrank();
wethBalBefore = weth.balanceOf(user);
vm.startPrank(user);
staking.claim();
wethBalAfter = weth.balanceOf(user);
console2.log("Reward Claimed by user 2: ", (wethBalAfter - wethBalBefore) / 1e18);
vm.stopPrank();
}
}

Output

Screenshot-from-2023-07-30-11-30-28
temporary image hosting

Tools Used

manual review

Recommendations

Update the user’s rewards before transferring tokens from the staker.

function deposit(uint _amount) external {
updateFor(msg.sender);
TKN.transferFrom(msg.sender, address(this), _amount);
balances[msg.sender] += _amount;
}

Support

FAQs

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

Give us feedback!