Using multicall functionality, one can bypass the deposit cap even without reducing debt #96
Labels
2 (Med Risk)
Assets not at direct risk, but function/availability of the protocol could be impacted or leak value
bug
Something isn't working
duplicate-238
🤖_48_group
AI based duplicate group recommendation
satisfactory
satisfies C4 submission criteria; eligible for awards
sufficient quality report
This report is of sufficient quality
Lines of code
https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/actions/Deposit.sol#L78-L82
https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/Multicall.sol#L29
https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/Multicall.sol#L37
Vulnerability details
The function
Size::multicall
allows users to batch multiple function calls in one external call. Among other things, multicall functionality allows going over the underlying borrow token (USDC currently) deposit limit only if it is to repay debt in the system therefore increasing the system’s health.As can be seen here, multicalls are not checked for breaching the underlying borrow token cap. The reason for this is that the protocol wants to allow users to deposit USDC only if they are being used to decrease the debt in the protocol (through regular borrower repayments or through liquidations). This reason justifies the breaching of the cap as can bee seen in this comment. If not for multicall, users will need to first call
Size::deposit
and then another external call forSize::repay
(or one of the liquidation functions) in order to repay debt. But if depositing breaches the cap then it will revert, therefore not allow users to repay debts once the cap has been reached. The multicall allows for bypassing the cap check so that users can deposit and repay.We can see in the function Multicall::multicall that the function will read the borrow aToken balance of the
Size
contract before and after the multicall, as well as the debt token total supply before and after. Then it will send this data toCapsLibrary::validateBorrowATokenIncreaseLteDebtTokenDecrease
to make sure the increase in borrow tokens is equal or smaller than the reduction in debt tokens, as can be seen in this check.Essentially what the protocol wants to achieve is to allow users to deposit an amount of USDC, even if the deposit cap has been reached, only to be used for repaying some debt. If depositing the USDC breaches the cap and the reduction in debt tokens is equal or greater than the deposited amount, then the protocol will allow breaching of the cap in exchange for a healthier protocol.
The issue here is that the way that
Multicall::multicall
andCapsLibrary::validateBorrowATokenIncreaseLteDebtTokenDecrease
are currently implemented, they allow users to deposit through the multicall functionality (instead of the regular deposit flow viaSize::deposit
) and breach the cap without repaying any debt. Meaning, users can use multicall to deposit tokens, with no other function call batched with the deposit, and the validation function will not revert.The root cause is in
Multicall:multicall
where the function reads the value of the balance of borrow aUSDC tokens held bySize
before and after the multicall. While it is true that the balance ofSize
increases when users repay debt (since the repayment is transferred from msg.sender toSize
), it doesn’t change when users just deposit, since the borrow tokens are assigned to the user that deposited and not toSize
. This will allow this check to be bypassed sinceborrowATokenSupplyIncrease
is 0 (balance ofSize
before and after does not change). Instead, the function should rely on total supply of borrow aUSDC instead ofSize
’s balance.Impact
The borrow aToken (
szaUSDC
) cap can be breached despite the fact that the protocol’s intended design is to allow it to be breached only to increase the health of the system (reducing debt). By breaching the cap freely, users can increase the USDC (andszaUSDC
) total supply regardless of the cap enforced, without increasing the health of the system, therefore increasing the risk taken by the protocol as unlimited funds are flowing into it.Proof of Concept
Add the following test function into
MulticallTest
:Note that this function is essentially the same as
MulticallTest::test_Multicall_multicall_bypasses_cap_if_it_is_to_reduce_debt
, but withoutdata[1]
which encodes a repayment function. This shows that callingSize::deposit
will revert when the deposit goes over the cap, but calling the same deposit via multicall will not revert and will breach the cap.Tools Used
Manual review
Recommended Mitigation Steps
Make the following changes in
Multicall::multicall
Assessed type
Invalid Validation
The text was updated successfully, but these errors were encountered: