Omniscia Myso Finance Audit

LenderVaultImpl Manual Review Findings

LenderVaultImpl Manual Review Findings

LVI-01M: Potentially Dangerous Overwrite of Storage

TypeSeverityLocation
Logical FaultLenderVaultImpl.sol:L110-L124

Description:

The LenderVaultImpl::updateLoanInfo function will overwrite any entry that was previously present in the _loans[loanId] entry with the input memory loan. This behaviour can lead to multiple types of attacks, including re-entrancy ones, that would affect the protocol significantly.

Impact:

The current function implementation would have been exploitable if the BorrowerGateway functions had not been marked as nonReentrant, showcasing that a potential update of the protocol could cause this vulnerability to manifest.

The adjustment proposed that removes attack vectors relying on re-entrancies and outdated memory data would also lead to a gas optimization of LenderVaultImpl::updateLoanInfo as it would require less input arguments. As such, we strongly advise the Myso Finance team to introduce this change even if there is no presently exploitable attack vector.

Example:

contracts/peer-to-peer/LenderVaultImpl.sol
110function updateLoanInfo(
111 DataTypesPeerToPeer.Loan memory _loan,
112 uint128 repayAmount,
113 uint256 loanId,
114 uint256 collAmount
115) external {
116 senderCheckGateway();
117 _loan.amountRepaidSoFar += repayAmount;
118
119 // only update lockedAmounts when no compartment
120 if (_loan.collTokenCompartmentAddr == address(0)) {
121 lockedAmounts[_loan.collToken] -= collAmount;
122 }
123 _loans[loanId] = _loan;
124}

Recommendation:

Given that the _loan member passed into LenderVaultImpl::updateLoanInfo by BorrowerGateway::repay is not adjusted and is the same as the loan in the storage of LenderVaultImpl, we advise the amountRepaidSoFar member of _loans[loanId] to be adjusted directly. This would also render the entire _loan argument redundant, instead only requiring the collToken and collTokenCompartmentAddr members of the struct as new input arguments to LenderVaultImpl::updateLoanInfo.

Alleviation (c740f7c6b5ebd365618fd2d7ea77370599e1ca11):

The code now properly updates the amountRepaidSoFar member of the Loan struct in storage, ensuring that an outdated memory struct does not overwrite its storage and thus alleviating this exhibit in full.

LVI-02M: Unsafe Type Casts

TypeSeverityLocation
Mathematical OperationsLenderVaultImpl.sol:L179, L180-L182

Description:

The referenced type casts to a uint40 are performed unsafely.

Impact:

While the type casts are performed unsafely, they would manifest as unwanted behaviour solely in the case the protocol applies a zero percent fee per annum.

In such a case, a quote could be created with an abnormally high tenor that would cause the block.timestamp + tenor calculation to be performed safely but truncate when cast to an uint40 variable.

Example:

contracts/peer-to-peer/LenderVaultImpl.sol
179_loan.expiry = uint40(block.timestamp + quoteTuple.tenor);
180_loan.earliestRepay = uint40(
181 block.timestamp + generalQuoteInfo.earliestRepayTenor
182);

Recommendation:

We advise them to be performed safely, ensuring that the values being cast are at most equal to type(uint40).max, the maximum value an unsigned integer of 40 bits can hold.

Alleviation (c740f7c6b5ebd365618fd2d7ea77370599e1ca11):

All referenced type casts are now performed safely via the import of the SafeCast library from OpenZeppelin, alleviating this exhibit.

LVI-03M: Insufficient Restriction of Loan Tenors

TypeSeverityLocation
Input SanitizationLenderVaultImpl.sol:L183

Description:

The LenderVaultImpl::processQuote function will validate that the expiry of a loan is greater than the earliestRepay, however, this delta can be as little as one second.

Impact:

Should the expiry be only one second away from the earliestRepay, the repayment of the loan would be impossible in practice due to the varying block times a blockchain has as the time window the transaction would need to execute in would be 1 second.

Example:

contracts/peer-to-peer/LenderVaultImpl.sol
183if (_loan.expiry <= _loan.earliestRepay) {
184 revert Errors.ExpiresBeforeRepayAllowed();
185}

Recommendation:

We advise a minimum time delta to be enforced between a loan's expiry and a loan's earliest repayment, ensuring that the purveyor of the loan has sufficient time to repay the loan under normal conditions (i.e. 24 hours).

Alleviation (c740f7c6b5ebd365618fd2d7ea77370599e1ca11):

A minimum delta of 24 hours has been introduced to the codebase in the form of Constants::MIN_TIME_BETWEEN_EARLIEST_REPAY_AND_EXPIRY, guaranteeing adequate time for a borrower to repay their loan and thus alleviating this exhibit.

LVI-04M: Incorrect Interest Rate Factor Validation

TypeSeverityLocation
Mathematical OperationsLenderVaultImpl.sol:L436

Description:

The interest rate factor percent that is permitted by LenderVaultImpl::getLoanAndRepayAmount is contradictory to the sanitization performed in QuoteHandler::isValidOnChainQuote as it permits the edge case of _interestRateFactor being 0.

In such a case, the repayment amount would become 0 thus disallowing the user from ever repaying their "-100% interest" loan due to a division by zero. As such, a malicious lender could create off-chain quotes of "-100%" interest loans that victims would be allured by and the lender would then have to wait until those loans expire to claim them as the users would never be able to repay them.

Impact:

It is currently possible for a malicious lender to create seemingly lucrative loans that will ultimately cause the users to lose their collateral and never be able to repay the loans. In practice, this would be exploited by creating an off-chain quote (which does not go through the QuoteHandler::isValidOnChainQuote process) that also utilizes a basic compartment (ensuring that LenderVaultImpl::unlockCollateral will not suffer from the same division-by-zero error). Users would be enticed to take out these loans as they would assume the lender made a mistake when in reality the lender crafted these loans maliciously.

Example:

contracts/peer-to-peer/LenderVaultImpl.sol
434int256 _interestRateFactor = int256(Constants.BASE) +
435 quoteTuple.interestRatePctInBase;
436if (_interestRateFactor < 0) {
437 revert Errors.NegativeRepaymentAmount();
438}
439uint256 interestRateFactor = uint256(_interestRateFactor);
440repayAmount = (loanAmount * interestRateFactor) / Constants.BASE;

Recommendation:

We strongly advise the LenderVaultImpl::getLoanAndRepayAmount function to be updated, yielding an error in case the _interestRateFactor amounts to 0 and thus prohibiting this edge case from manifesting.

Alleviation (c740f7c6b5ebd365618fd2d7ea77370599e1ca11):

The LenderVaultImpl::getLoanAndRepayAmount function has been updated as advised, preventing an _interestRateFactor of 0 from being specified and thus preventing this vulnerability from manifesting.

LVI-05M: Insecure Collateral Compartment Creation

TypeSeverityLocation
Logical FaultLenderVaultImpl.sol:L191-L194

Description:

The collateral compartments of the Myso Finance project are meant to support specific asset classes or even unique assets. Quotes validated by the QuoteHandler::checkSenderAndGeneralQuoteInfo as well as by the LenderVaultImpl::processQuote functions do not sanitize the type of compartment paired with a particular collateral asset.

This is a significant flaw as for example a quote that has specified the GLPStakingCompartment with a WETH collateral would successfully execute. In such a case, any withdrawal would be essentially "doubled" as the BaseCompartment::_transferCollFromCompartment function would unlock the proportionate withdrawal of WETH and the top-level GLPStakingCompartment::transferCollFromCompartment function would re-calculate and re-unlock another proportion of WETH, affecting the loan's repayment behaviour in favour of the borrower.

Impact:

As an example exploitation flow for unaware lenders, a borrower may ask a lender to utilize the CurveLPStakingCompartment for their CRV collateral. Such a quote would cause the double-withdraw exploit to manifest and seemingly appears "correct" as a Curve-related asset is utilized with a Curve-related compartment.

Example:

contracts/peer-to-peer/LenderVaultImpl.sol
362function createCollCompartment(
363 address borrowerCompartmentImplementation,
364 uint256 loanId
365) internal returns (address collCompartment) {
366 if (
367 IAddressRegistry(addressRegistry).whitelistState(
368 borrowerCompartmentImplementation
369 ) != DataTypesPeerToPeer.WhitelistState.COMPARTMENT
370 ) {
371 revert Errors.NonWhitelistedCompartment();
372 }
373 bytes32 salt = keccak256(
374 abi.encodePacked(
375 borrowerCompartmentImplementation,
376 address(this),
377 loanId
378 )
379 );
380 collCompartment = Clones.cloneDeterministic(
381 borrowerCompartmentImplementation,
382 salt
383 );
384 IBaseCompartment(collCompartment).initialize(address(this), loanId);
385}

Recommendation:

We advise a new function to be introduced to the IBaseCompartment that is meant to be overridden by derivative implementations, is view, and validates its input address as being a supported collateral or not.

The code of LenderVaultImpl::createCollCompartment would then need to be updated to invoke this function after it has been created, ensuring that the created compartment explicitly supports the collateral that will be sent to it.

Alleviation (c740f7c6b5ebd365618fd2d7ea77370599e1ca11):

The Myso Finance team has opted to remediate this vulnerability using an alternative approach, introducing a new whitelist in the AddressRegistry that associates tokens with their compartments and making use of this whitelist for all types of quotes (off-chain and on-chain) at the QuoteHandler contract when they are created as well as consumed.

After reviewing the changes that were made to introduce this system, we consider this exhibit fully alleviated as the quote-based validation mechanism works as expected and will validate the compartment and token associations both at each quote's creation as well as consumption.