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

Loss of a fraction of staking reward

Summary

Staking participants could irrecoverably lose part of their reward.

Vulnerability Details

When a user decides to stake (make a deposit), the total reward that the existing staking participants are supposed to earn is supposed to be calculated based on the total staked token (TKN) balance before the new deposit is added but that is not the case. It is currently calculated based on the new TKN balance (after deposit) which dilutes the reward that the existing participants would have otherwise received.

Impact

Staking rewards would be lost

POC

contract StakeTest is Test {
TERC20 public BDL;
TERC20 public WETH;
address public user = address(0x1);
Staking staking;
function setUp() public {
BDL = new TERC20();
WETH = new TERC20();
staking = new Staking(
address(BDL),
address(WETH)
);
BDL.mint(address(user), 100_000 * 1e18);
}
function _addWethToPool(uint x) public {
WETH.mint(address(staking), x * 1e18);
}
function _stake(address _user, uint _amount) public {
vm.prank(_user);
staking.deposit( _amount * 1e18);
}
function test_stake() public {
// unlimited approval to get that out of the way
vm.prank(user);
BDL.approve(address(staking), type(uint).max);
// The user stakes 2 BDL
_stake(user, 2);
// add 4 WETH to staking contract
_addWethToPool(4);
// staking.update();
// uncommenting the line above or adding a call to the
// update() function before any other action in the
// deposit(amount) function will make the test pass
// user stakes another 3 BDL
_stake(user, 3);
// add 8 WETH to staking contract to
// make the total balance 12 WETH
_addWethToPool(8);
// 12 WETH have been added since the user staked
// for the first time so the reward should be
// (2/2 * 4) + (5/5 * 8) = 12 claimable WETH
// the test fails as `staking.claimable(user)` returns
// 9.6 * 1e18
staking.updateFor(user);
assertEq(staking.claimable(user), 12 * 1e18);
}
}

Tools Used

Foundry and the fear of losing my money

Recommendations

Make a call to the update function as the first step in the deposit function before any other action so that existing rewards are calculated first.

Support

FAQs

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