In simple terms the intended flow of the protocol is that user initiate deposit in PriorityPool
, then from PriorityPool
after certain checks and conditions tokens should be deposited in StakingPool
and after that funds should go to OperatorVCS
or CommunityVCS
which then deposit tokens into desired vaults and strategies. So flow should look something like this:
User -> PriorityPool
-> StakingPool
-> OperatorVCS
or CommunityVCS
-> Operator Vault
or Community Vault
-> Operator
or Community
Pool on Chainlink
Throughout this intended flow deposit amount is tracked succesfully and protocol make sure that max deposit limit is not breached. This is enforced also throughout functions:
deposit function on PriorityPool
calls deposit function on StakingPool
that have onlyPriorityPool
modifier which make sure only PriorityPool
should be able to deposit in StakingPool
as we can see here:
Which eventually deposits funds to desired strategies by calling deposit function in VaultControllerStrategy
which is abstract contract that have onlyStakingPool
modifier which make sure the function is called only by StakingPool
.The deposit
function via delegatecall
calls deposit
function on VaultDepositController
as we can see here:
Which intended design is to deposit tokens from StakingPool
into Vaults as mentioned in Nat spec above (and also in the first paragraph of this finding).
The deposit function in VaultDepositController
looks like this:
The intended design is that function should be only called via delegatecall
, this also confirm the flow that is mentioned in the beginning of this finding.
The issue here is that the deposit
function does not check if it is called only by delegatecall
and if the caller is correct contract.
Anyone could call the deposit
function in VaultDepositController
and effectively bypass the intended flow of the protocol.
The protocol that way would not be able to track deposited amount correctly.
Malicious user (or multiple users) could bypass the intended flow and deposit directly to strategies, the deposited amount could eventually reach deposit cap, which would make protocol unable to deposit the regular way because maximum amount of tokens that the strategy can hold would be reached. The protocol would try to deposit to different strategies if first one fail to do so but this can be done effectively to to other strategies which eventually could make protocol unable to deposit. This can impact also users they would not be able to deposit to the protocol and they could possibly incur loses.
After passing canDeposit
, the malicious user could frontrun and execute their malicious act in mem pool before deposit
function is called, so when it is called it would revert but the PriorityPool
does not catch that and would think deposit was succesfull and it will deduct toDepositIntoPool
amount from toDeposit
amount, the remaining amount would be queued or transfered back to user, but the deducted difference would be stuck in PriorityPool
and users would incure loss that way. The best case scenario is that user maybe would not incur direct loses because their amount would be returned to them or queued if they want to do that, but they would get denial of service (DoS) when they should not incur that and they should be able to deposit.
Protocol intended flow can be bypassed and users possibly could incur loss of funds.
Manual Review.
This can be prevented by adding whitelisting addresses and onlyDelegateCall
modifier.
Make the following changes to the VaultDepositController
:
This will ensure that only the intended VaultControllerStrategy
contract can call deposit
and withdraw
via delegatecall
, preventing unauthorized contracts from bypassing the protocol’s intended flow and access control.
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.