The bug occurs due to a reentrancy vulnerability in the StakingPool contract's deposit() function.
It's happening because.
The deposit() function calls _depositLiquidity() which loops through a list of strategy contracts and calls their deposit() function with user-supplied calldata.
If one of these strategy contracts is malicious, it can reenter the StakingPool contract before the original deposit() call finishes executing.
The malicious strategy can call donateTokens() or other state-changing functions in StakingPool, modifying accounting variables like totalStaked.
When the original deposit() resumes, the _mint() call reverts because totalStaked is now out of sync with the actual token balances.
If exploited, users' balances could be manipulated, funds could be drained from the pool, or the contract could be put in an inconsistent state.
The deposit() function calls the _depositLiquidity() internal function which loops through a list of strategy contracts and invokes their deposit() function with user-supplied calldata.
If one of these strategy contracts is malicious, it can reenter the StakingPool contract before the original deposit() call completes execution. The malicious strategy can then call donateTokens() or other state-changing functions in StakingPool, modifying accounting variables like totalStaked.
When the original deposit() call resumes, the _mint() call reverts because totalStaked is now inconsistent with the actual token balances due to the reentrancy.
Here's how a malicious user could exploit:
Deploy a malicious strategy contract that reenters StakingPool when its deposit() function is called.
Call StakingPool.donateTokens(0) to initialize totalStaked with a non-zero value.
Call StakingPool.deposit(attacker, 0, maliciousData) where maliciousData contains calldata for invoking the malicious strategy.
StakingPool._depositLiquidity() is invoked which calls deposit() on the malicious strategy.
The malicious strategy reenters StakingPool, calling donateTokens(1) to increase totalStaked.
The original deposit() call resumes but the _mint() call reverts because totalStaked is now out of sync.
StakingPool.sol:111-132: https://github.com/Cyfrin/2024-09-stakelink/blob/f5824f9ad67058b24a2c08494e51ddd7efdbb90b/contracts/core/StakingPool.sol#L111-L132
StakingPool.sol:477-492: https://github.com/Cyfrin/2024-09-stakelink/blob/f5824f9ad67058b24a2c08494e51ddd7efdbb90b/contracts/core/StakingPool.sol#L477-L492
deposit() calls into untrusted strategy contracts before updating accounting variables like totalStaked
Vs
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.