Summary
StakingPool::strategyDeposit doesnt update totalStaked variable when depositing to strategies, leading to wrong staked tracking balance and wrong staked return values in StakingPool::canDeposit()
Vulnerability Details
StakingPool uses totalStaked variable to keep track of how much token have been deposited to its strategies.
For example in deposit function:
function deposit(
address _account,
uint256 _amount,
bytes[] calldata _data
) external onlyPriorityPool {
require(strategies.length > 0, "Must be > 0 strategies to stake");
uint256 startingBalance = token.balanceOf(address(this));
if (_amount > 0) {
token.safeTransferFrom(msg.sender, address(this), _amount);
@=> totalStaked += _amount;
}
}
However in strategyDeposit this doesnt occours:
function strategyDeposit(
uint256 _index,
uint256 _amount,
bytes calldata _data
) external onlyOwner {
require(_index < strategies.length, "Strategy does not exist");
IStrategy(strategies[_index]).deposit(_amount, _data);
}
So, when this method its called, will deposit to strategy, decreasing strategy capacity and leading to inacurate link staked/deposited balance tracking amount in StakingPool.
Also, this leads to canDeposit() function to malfunction because it mainly depends on totalStaked variable value
function canDeposit() external view returns (uint256) {
uint256 max = getMaxDeposits();
if (max <= totalStaked) {
return 0;
} else {
return max - totalStaked;
}
}
The following PoC defines an scenario with:
A StakingPool with:
unusedDepositLimit = 20 Eth and
One Strategy with maxDeposits = 100 Eth
StakingPool's owner calls strategyDeposit with 90 eth of link this decreases strategy capacity.
But StakingPool::strategyDeposit() doesnt increment totalStaked variable, so, its like nothing was deposited.
After this, StakingPool::canDeposit() will return a wrong amount of how much can be staked in this pool.
Any user who calls StakingPool::canDeposit() to query how much can be deposited receives a wrong value, and if it uses this value, his calls to StakingPool::deposit() will fail.
Create test in test/core/priorityPool/priority-pool.test.ts
it.only('strategyDeposit doesnt update totalStaked causing canDeposit() to give wrong return value', async () => {
const { signers, accounts, adrs, token, stakingPool, strategy, sdlPool, pp, withdrawalPool } = await loadFixture(
deployFixture
)
await strategy.setMaxDeposits(toEther(100));
await stakingPool.setUnusedDepositLimit(toEther(20));
let stakingLimit = await stakingPool.getMaxDeposits()
console.log("StakingLimit\t\t\t",fromEther(stakingLimit))
let unusedDeposits = await stakingPool.unusedDepositLimit()
console.log("unusedDeposits\t\t\t",fromEther(unusedDeposits));
console.log(
"stakingLimit + unusedDeposits\t",
fromEther(stakingLimit + unusedDeposits)
);
let etherAmount = toEther(90);
let index = 1;
let signer = signers[index]
let account = accounts[index]
console.log("\namountToStake\t\t\t",fromEther(etherAmount));
console.log(`Will use strategyDeposit instead of deposit to deposit ${fromEther(etherAmount)}`)
console.log(
`\ncanDeposit() before strategyDeposit\t`,
fromEther(await stakingPool.canDeposit())
)
console.log("\nCalling stakingPool.strategyDeposit");
await token.transfer(stakingPool.getAddress(), etherAmount)
await stakingPool.strategyDeposit(0, etherAmount, '0x')
console.log(
`\ncanDeposit() after strategyDeposit\t`,
fromEther(await stakingPool.canDeposit())
)
console.log("await stakingPool.getMaxDeposits()\t",fromEther(await stakingPool.getMaxDeposits()));
let depositAmount = await stakingPool.canDeposit();
console.log(`\nUser use canDeposit() value to stake calling SPool::deposit() but his transaction fails`)
console.log(`(tx is catched with expect .to.be.reverted)`)
await token.connect(signers[2]).transfer(accounts[0], depositAmount)
await expect(pp.connect(signers[2]).deposit(
await stakingPool.canDeposit(),
true,
['0x', '0x', '0x']
)).to.be.reverted
})
Impact
Severity: Medium due to this flaw leads to wrong balance tracking and wrong return value of how much can be staked in canDeposit() function
Tools Used
Manual Review
Recommendations
SPool::strategyDeposit should update totalStaked amount (the variable that tracks deposits to strategies) and check if max deposit limit has been reached, calling SPool::canDeposit()