Contract fails to deliver promised returns, but doesn't lose value
permissionedCalls` Protection can be bypassed
Description
Summary
The AlchemistAllocator contract blocks direct calls to allocate() and deallocate() functions through its proxy() function. However, an admin can bypass this protection by wrapping these calls inside the vault's multicall() function, completely defeating the intended privilege separation.
Description
In alchemistAllocator constructor set MorpheusVault::Allocate,Deallocate functions as permissioned calls to prevent calling them directly from the permissionedProxy
AlchemistAllocator.solconstructor(address_vault,address_admin,address_operator) {// Block direct allocate/deallocate calls via proxy permissionedCalls[0x5c9ce04d]=true;// allocate permissionedCalls[0x4b219d16]=true;// deallocate}
The issue here that the admin can bypass this through calling MorpheusVault::multiCall and route the calls to Morphues::Allocate,dellocate and since multiCall is not permissionedCall the call will succeed
Attack Flow
Impact
Defeats Security Model: The entire permissionedCalls protection is bypassed
AlchemistAllocator Logic:This path Ignore any custom logic, checks, or accounting in the AlchemistAllocator
Mitigation
Add multicall selector to permissionedCalls if we don't need to call it via proxy OR
Restrict calling allocateand deallocate from multicall
Proof of Concept
Proof of Concept
1.Paste the following test in AlchemistAllocator.t.sol
2.Run it via forge test --mc AlchemistAllocatorTest --mt test_byPass_permissionedCalls -vvv
VaultV2.sol
// Vault has a multicall function (NOT blocked)
function multicall(bytes[] calldata data) external {
for (uint256 i = 0; i < data.length; i++) {
(bool success,) = address(this).delegatecall(data[i]);
require(success);
}
}