As stated in the docs of Size, protocol wants to have a "guarded launch" and prevent growing at a faster pace than its security/safety/trust. For this reason, they have implemented an initial cap on the amount of szaUSDC that can be minted which is 1,000,000. This check is happening in every deposit call which tries to deposit USDC but it is not happening if the isMulticall flag is true.
function executeDeposit(State storage state, DepositParams calldata params) public {
// ...
if (params.token == address(state.data.underlyingBorrowToken)) {
state.depositUnderlyingBorrowTokenToVariablePool(from, params.to, amount);
// borrow aToken cap is not validated in multicall,
// since users must be able to deposit more tokens to repay debt
if (!state.data.isMulticall) {
state.validateBorrowATokenCap();
}
} else {
state.depositUnderlyingCollateralToken(from, params.to, amount);
}
// ...
}
So to call deposit() from multicall and bypass the check, user can call multicall() function giving only one call to deposit() function with the amount over the limit in data bytes[] (see PoC below). As we can see there will be no check of the total supply of szaUSDC at all in this case :
As a result, anyone can easily bypass the validateBorrowATokenCap check and deposit over whatever limit is set by the protocol. This can break any assumptions and intentions of the team for "guarded launch" or any other reason to limit the supply.
Proof of Concept
To understand the issue, add the following test in Multicall.t.sol file and run forge test --mt test_Multicall_multicall_can_break_cap -vvvv :
function test_Multicall_multicall_can_break_cap() public {
vm.startPrank(alice);
uint256 amount = 10_000_000e6;
address token = address(usdc);
deal(token, alice, amount);
IERC20Metadata(token).approve(address(size), amount);
assertEq(size.getUserView(alice).borrowATokenBalance, 0);
bytes[] memory data = new bytes[](1);
data[0] = abi.encodeCall(size.deposit, (DepositParams({token: token, amount: amount, to: alice})));
bytes[] memory results = size.multicall(data);
assertEq(results[0], bytes(""));
assertEq(size.getUserView(alice).borrowATokenBalance, amount);
}
Tools Used
Manual Review, Foundry
Recommended Mitigation Steps
Consider adding the check of the total supply of szaUSDC in the multicall() function as well.
Lines of code
https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/actions/Deposit.sol#L80
Vulnerability details
Description
As stated in the docs of Size, protocol wants to have a "guarded launch" and prevent growing at a faster pace than its security/safety/trust. For this reason, they have implemented an initial cap on the amount of
szaUSDC
that can be minted which is 1,000,000. This check is happening in every deposit call which tries to depositUSDC
but it is not happening if theisMulticall
flag is true.Link to code
So to call
deposit()
from multicall and bypass the check, user can callmulticall()
function giving only one call todeposit()
function with the amount over the limit in data bytes[] (see PoC below). As we can see there will be no check of the total supply ofszaUSDC
at all in this case :Link to code
Impact
As a result, anyone can easily bypass the
validateBorrowATokenCap
check and deposit over whatever limit is set by the protocol. This can break any assumptions and intentions of the team for "guarded launch" or any other reason to limit the supply.Proof of Concept
To understand the issue, add the following test in
Multicall.t.sol
file and runforge test --mt test_Multicall_multicall_can_break_cap -vvvv
:Tools Used
Manual Review, Foundry
Recommended Mitigation Steps
Consider adding the check of the total supply of
szaUSDC
in themulticall()
function as well.Assessed type
Context