Beginner FriendlyFoundryDeFi
100 EXP
View results
Submission Details
Severity: high
Valid

Incorrect handling of previous stakes.

Description:

The line self.usersToStakes[_onBehalfOf] = msg.value overwrites the user's previous stake with the new value instead of adding to the existing stake. If a user stakes multiple times, only the most recent staked amount will be recorded, effectively erasing their previous staked contributions.

def stake(_onBehalfOf: address):
assert not self._hasStakingPeriodEnded(), STEAK__STAKING_PERIOD_ENDED
assert msg.value >= MIN_STAKE_AMOUNT, STEAK__INSUFFICIENT_STAKE_AMOUNT
assert _onBehalfOf != ADDRESS_ZERO, STEAK__ADDRESS_ZERO
@> self.usersToStakes[_onBehalfOf] = msg.value
self.totalAmountStaked += msg.value
log Staked(msg.sender, msg.value, _onBehalfOf)

Impact:

This issue directly affects the accuracy of user staked balances and the overall integrity of the staking mechanism. It is a critical issue because it involves the potential loss of user staked funds, making it valid and severe.

Proof of Concept:

  1. User has 16 ether as his balance.

  2. User stakes 8 ether into the steaking contract.

  3. User stakes another 8 ether into the steaking contract.

  4. User expects to have staked 16 ether in the steaking contract.

  5. User expects to have (16 * 1000) points accumulated over his staked ETH in the steaking contract.

  6. User ends up getting (8 * 1000) points accumulated over his staked ETH due to wrong implementation of mapping.

  7. The steaking contract recorded that the user has just staked just only 8 ETH instead of 16 ETH due to wrong implementation of mapping.

Code

Place the following into Steaking.t.sol.

function testUserMultipleStakeAttemptsAreRecorded() public {
vm.deal(user1, 16 ether);
vm.startPrank(user1);
steaking.stake{value: 8 ether}(user1);
steaking.stake{value: 8 ether}(user1);
vm.stopPrank();
uint256 userStakedAmount = steaking.usersToStakes(user1);
uint256 totalAmountStaked = steaking.totalAmountStaked();
console2.log("This is how much the user Staked::", userStakedAmount);
console2.log("---------------------------------------------------------");
console2.log("This is the total Amount Staked::", totalAmountStaked);
console2.log("---------------------------------------------------------");
assertEq(userStakedAmount, 16 ether);
assertEq(totalAmountStaked, 16 ether);
}

Here are the logs that were shown:

[0] console::log("This is how much the user Staked::", 8000000000000000000 [8e18]) [staticcall]
│ └─ ← [Stop]
├─ [0] console::log("---------------------------------------------------------") [staticcall]
│ └─ ← [Stop]
├─ [0] console::log("This is the total Amount Staked::", 16000000000000000000 [1.6e19]) [staticcall]
│ └─ ← [Stop]
├─ [0] console::log("---------------------------------------------------------") [staticcall]
│ └─ ← [Stop]
├─ [0] VM::assertEq(8000000000000000000 [8e18], 16000000000000000000 [1.6e19]) [staticcall]
│ └─ ← [Revert] assertion failed: 8000000000000000000 != 16000000000000000000
└─ ← [Revert] assertion failed: 8000000000000000000 != 16000000000000000000

Here we can see an error where the mapping has recorded that the user had just staked just 8 ETH instead of 16 ETH. As the first assertion failed.

Recommended Mitigation:

Instead of overwriting, the function should add the new stake to the existing one:

def stake(_onBehalfOf: address):
assert not self._hasStakingPeriodEnded(), STEAK__STAKING_PERIOD_ENDED
assert msg.value >= MIN_STAKE_AMOUNT, STEAK__INSUFFICIENT_STAKE_AMOUNT
assert _onBehalfOf != ADDRESS_ZERO, STEAK__ADDRESS_ZERO
- self.usersToStakes[_onBehalfOf] = msg.value
+ self.usersToStakes[_onBehalfOf] += msg.value
self.totalAmountStaked += msg.value
log Staked(msg.sender, msg.value, _onBehalfOf)
Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Steaking::stake overwrites the msg.value into storage

Support

FAQs

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