The protectStrategy
state variable is not updated during emergencyRemoveStrategy
call
Description
The emergencyRemoveStrategy
function allows the owner of ConcreteMultiStrategyVault to remove a strategy in emergency situations.
function emergencyRemoveStrategy(uint256 index_, bool forceEject_) external onlyOwner {
StrategyHelper.emergencyRemoveStrategy(strategies, asset(), index_, forceEject_, protectStrategy);
}
The StrategyHelper.emergencyRemoveStrategy
function attempts to clear protectStrategy
if the strategy being removed is the designated protected strategy. However, the protectStrategy
function argument is passed to the library function as a memory copy, not a storage reference. As a result, any update to protectStrategy
inside the library function does not affect the protectStrategy
state variable in the ConcreteMultiStrategyVault contract.
function emergencyRemoveStrategy(
Strategy[] storage strategies,
address asset,
uint256 index_,
bool forceEject_,
address protectStrategy
) external {
// [...]
if (forceEject_) {
// [...]
if (address(stratToRemove.strategy) == protectStrategy) {
protectStrategy = address(0);
}
// [...]
} else {
// Normal removal process
removeStrategy(stratToRemove.strategy, protectStrategy, IERC20(asset));
}
}
function removeStrategy(IStrategy stratToBeRemoved_, address protectStrategy_, IERC20 asset)
public
returns (address protectStrategy)
{
// [...]
if (protectStrategy_ == address(stratToBeRemoved_)) {
protectStrategy = address(0);
} else {
// [...]
}
// [...]
}
Consequently, after the owner calls emergencyRemoveStrategy
to remove the protected strategy, the protectStrategy
state variable remains unchanged.
Impact
When the owner removes the current protectStrategy
using emergencyRemoveStrategy
, the strategy's address remains in the protectStrategy
state variable. The removed strategy retains its privileged permissions and can still call functions protected by the onlyProtect
modifier, such as the function requestFunds
, which could request funds from the vault.
Recommendations
Modify StrategyHelper.emergencyRemoveStrategy
to return the updated protected strategy address. Update ConcreteMultiStrategyVault.emergencyRemoveStrategy
to capture this return value, and update the protectStrategy
state variable accordingly.
Remediation
This issue has been acknowledged by Blueprint Finance, and a fix was implemented in commit 8b5c7fd0↗.