xanoutas/solidity-security-findings huggingface dataset:
The borrower can use `refinanceFromLoanExecutionData` delay on loan
Audit: 2024-04-gondi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-gondi/blob/b9863d73c08fcdd2337dc80a8b5e0917e18b036c/src/lib/loans/MultiSourceLoan.sol#L306


# Vulnerability details

## Impact
The borrower uses `refinance` to extend the loan and avoid liquidation.

## Proof of Concept

In MultiSourceLoan, the borrower can use `refinanceFromLoanExecutionData` function, refinance the loan.

This function needs to use lender's `offer`.

The key issue is that `offer` can be reused if offer.capacity > 0, the `offer` can be used again.

```solidity
    function _processOffersFromExecutionData(
        .....
        if (offer.capacity > 0) {
            _used[lender][offer.offerId] += amount;
        } else {
            isOfferCancelled[lender][offer.offerId] = true;
        }
        .....
    }

    function _validateOfferExecution(){
        .....
        if (isOfferCancelled[_lender][offerId] || (offerId <= minOfferId[_lender])) {
            revert CancelledOrExecutedOfferError(_lender, offerId);
        }
        .....
        if ((offer.capacity > 0) && (_used[_offerer][offer.offerId] + _offerExecution.amount > offer.capacity)) {
            revert MaxCapacityExceededError();
        }
        .....
    }
```

The borrower can use ths offer(offer.capacity > 0) call `refinanceFromLoanExecutionData` repeatedly.

`refinanceFromLoanExecutionData` will generate a new loan, the new loan due time will be reset.
Therefore, the borrower can always call this function so that his loan will never be liquidated.

borrower can then act as lender and provide an offer with a capacity greater than 0 to call.

Here is the test code:

```solidity
    function testRefinanceFromLoanExecutionData2() public {
        (uint256 loanId, IMultiSourceLoan.Loan memory loan) = _getInitialLoan();

        vm.warp(1 days);

        IMultiSourceLoan.LoanOffer memory loanOffer = _getSampleOffer(
            address(collateralCollection),
            collateralTokenId,
            _INITIAL_PRINCIPAL
        );
        loanOffer.duration = 90 days;
        loanOffer.principalAmount *= 2;

        loanOffer.capacity = loanOffer.principalAmount * 10;

        IMultiSourceLoan.LoanExecutionData memory led = IMultiSourceLoan
            .LoanExecutionData(
                _sampleExecutionData(loanOffer, loan.borrower),
                loan.borrower,
                ""
            );
        led.executionData.offerExecution[0].amount = loanOffer.principalAmount;

        testToken.mint(loanOffer.lender, loanOffer.principalAmount * 20);
        vm.prank(loanOffer.lender);
        testToken.approve(address(_msLoan), loanOffer.principalAmount * 20);

        vm.startPrank(_borrower);
        testToken.mint(_borrower, loanOffer.principalAmount * 10);
        testToken.approve(address(_msLoan), loanOffer.principalAmount * 10);

        (uint256 newLoanId, IMultiSourceLoan.Loan memory newLoan) = _msLoan
            .refinanceFromLoanExecutionData(loanId, loan, led);

        _msLoan.refinanceFromLoanE
MultiSourceLoan#addNewTranche can be DoS
Audit: 2024-04-gondi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-gondi/blob/b9863d73c08fcdd2337dc80a8b5e0917e18b036c/src/lib/loans/MultiSourceLoan.sol#L359


# Vulnerability details

## Impact
The `addNewTranche` function is unavailable due to a DoS attack.

## Proof of Concept

The `addNewTranche` function checks whether the number of `_loan.tranches` has exceeded `MaxTranches`.

```solidity
    function addNewTranche(....) external nonReentrant returns (uint256, Loan memory) {
        ....
        if (_loan.tranche.length == getMaxTranches) {
            revert TooManyTranchesError();
        }
    }
```

getMaxTranches sets 20 in the test code

```solidity
abstract contract MultiSourceCommons is TestLoanSetup {
    uint256 internal _maxTranches = 20;
}
```

An attacker can pass in a small amount of `principalAmount`, allowing `_loan.tranche` to increase.

When the length reaches the maximum, the `addNewTranche` function cannot be called.

An attacker can `front-running` the `addNewTranche` function and call it several times before the user, maximizing the array length and causing the user's call to fail.

If `getMaxTranches` is set to a large value, an attacker can also attack, because executing this function consumes excessive gas when the array length is too large,
The caller will fail the call due to insufficient gas.

## Tools Used
vscode, manual

## Recommended Mitigation Steps
Set the minimum `principalAmount`.
Or use maps to hold `Tranche` data instead of arrays.



## Assessed type

DoS
Loans that are being liquidated can still perform mergeTranches/refinance operations
Audit: 2024-04-gondi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-gondi/blob/b9863d73c08fcdd2337dc80a8b5e0917e18b036c/src/lib/loans/MultiSourceLoan.sol#L649


# Vulnerability details

## Impact
For the check of loan time, the case _loan.startTime + _loan.duration = block.timestamp is not taken into account.
If liquidateLoan is executed at block.timestamp = _loan.startTime + _loan.duration,
The mergeTranches/refinance function can also be executed within the same block, generating a new loanId that will result in the collateral not being auctioned.

## Proof of Concept

First let's look at the `loan` maturity check:

1. In _liquidateLoan, if block.timestamp = _loan.startTime + _loan.duration, revert will not be generated.
The liquidateLoan function can be executed.

```solidity
    function _liquidateLoan(uint256 _loanId, IMultiSourceLoan.Loan calldata _loan, bool _canClaim)
        internal
        returns (bool liquidated, bytes memory liquidation)
    {
        uint256 expirationTime = _loan.startTime + _loan.duration;
        if (expirationTime > block.timestamp) {
            revert LoanNotDueError(expirationTime);
        }
    }
```

2. The _baseLoanChecks function will not revert if block.timestamp = _loan.startTime + _loan.duration,
can pass the inspection.

```solidity
    function _baseLoanChecks(uint256 _loanId, Loan memory _loan) private view {
        if (_loan.hash() != _loans[_loanId]) {
            revert InvalidLoanError(_loanId);
        }
        if (_loan.startTime + _loan.duration < block.timestamp) {
            revert LoanExpiredError();
        }
    }
```

Then let's look at the `liquidateLoan` function:

```solidity
    function liquidateLoan(uint256 _loanId, Loan calldata _loan)
        external
        override
        nonReentrant
        returns (bytes memory)
    {
        if (_loan.hash() != _loans[_loanId]) {
            revert InvalidLoanError(_loanId);
        }
        (bool liquidated, bytes memory liquidation) = _liquidateLoan(
            _loanId, _loan, _loan.tranche.length == 1 && !getLoanManagerRegistry.isLoanManager(_loan.tranche[0].lender)
        );
        
@>      if (liquidated) {
            delete _loans[_loanId];
        }
        return liquidation;
    }
```

liquidateLoan function, in some cases (_canClaim! = true), will not immediately delete _loans[_loanId],

```solidity
  function _liquidateLoan(uint256 _loanId, IMultiSourceLoan.Loan calldata _loan, bool _canClaim)
        internal
        returns (bool liquidated, bytes memory liquidation)
    {
        uint256 expirationTime = _loan.startTime + _loan.duration;
        // if(_loan.startTime + _loan.duration > block.timestamp) 没到期
        if (expirationTime > block.timestamp) {
            revert LoanNotDueError(expirationTime);
        }
@>      if (_canClaim) {
            ERC721(_loan.nftCollateralAddress).transferFrom(
                address(this), _loan.tranche[0].lender, _loan.nftCollateralTokenId
            );
            emit LoanForeclosed(
The attackers front-running `repayloans` so that the debt cannot be repaid
Audit: 2024-04-gondi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-gondi/blob/b9863d73c08fcdd2337dc80a8b5e0917e18b036c/src/lib/loans/MultiSourceLoan.sol#L405


# Vulnerability details

## Impact
The attackers make it impossible for borrowers to repay their debts, and the collateral is liquidated when the debts mature.

## Proof of Concept

`repayLoan`, need to check the `loanId`, if the id is inconsistent will revert.

```solidity
    function repayLoan(LoanRepaymentData calldata _repaymentData) external override nonReentrant {
        uint256 loanId = _repaymentData.data.loanId;
        Loan calldata loan = _repaymentData.loan;
        .....
@>      _baseLoanChecks(loanId, loan);
        .....
    }
    
    function _baseLoanChecks(uint256 _loanId, Loan memory _loan) private view {
        if (_loan.hash() != _loans[_loanId]) {
            revert InvalidLoanError(_loanId);
        }
        if (_loan.startTime + _loan.duration < block.timestamp) {
            revert LoanExpiredError();
        }
    }
```

The problem is that `_loans[_loanId]` can change, for example, when `mergeTranches` delete the old `loanId` and write the new one.

```solidity
    _loans[loanId] = loanMergedTranches.hash();
    delete _loans[_loanId];
```

An attacker can use the `front-running` attack method,
when `repayLoan` is called, execute the `mergeTranches` function in advance, and make the id in `_loans` updated.
In this case, the `repayLoan` execution will fail due to inconsistent `_loanId`.

If the attacker keeps using this attack, the borrower's debt will not be repaid, eventually causing the collateral to be liquidated.

In addition to the `mergeTranches` function, the attacker can also call `addNewTranche`, and the borrower can also call the refinance-related function, again causing `_loanId` to be updated.

An attacker can also use the same method to attack `refinance` related functions, making refinance unable to execute.

An attacker can also use the same method to attack the `liquidateLoan` function, making it impossible for debts to be cleared.

## Tools Used
vscode, manual

## Recommended Mitigation Steps
Do not delete `_loanId`



## Assessed type

DoS
Incorrect protocol fee implementation results in outstandingValues to be mis-accounted in Pool.sol
Audit: 2024-04-gondi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-gondi/blob/b9863d73c08fcdd2337dc80a8b5e0917e18b036c/src/lib/LiquidationDistributor.sol#L117


# Vulnerability details

## Impact
Incorrect protocol fee implementation results in outstandingValues to be mis-accounted in Pool.sol

## Proof of Concept
The vulnerability is that LiquidationDistributer::_handleLoanMangerCall [hardcode 0 as protocol fee](https://github.com/code-423n4/2024-04-gondi/blob/b9863d73c08fcdd2337dc80a8b5e0917e18b036c/src/lib/LiquidationDistributor.sol#L117) when calling `LoanManager(_tranche.lender).loanLiquidation()`.
```solidity
//src/lib/LiquidationDistributor.sol
    function _handleLoanManagerCall(IMultiSourceLoan.Tranche calldata _tranche, uint256 _sent) private {
        if (getLoanManagerRegistry.isLoanManager(_tranche.lender)) {
            LoanManager(_tranche.lender).loanLiquidation(
                _tranche.loanId,
                _tranche.principalAmount,
                _tranche.aprBps,
                _tranche.accruedInterest,
   |>           0,  //@audit this should be the actual protocol fee fraction
                _sent,
                _tranche.startTime
            );
        }
    }

```

`_handleLoanManagerCall()` will be called as part of the flow to [distribute proceeds](https://github.com/code-423n4/2024-04-gondi/blob/b9863d73c08fcdd2337dc80a8b5e0917e18b036c/src/lib/AuctionLoanLiquidator.sol#L288) from a liquidation.

When protocol fee is hardcoded 0, in the [Pool::loanliquidation](https://github.com/code-423n4/2024-04-gondi/blob/b9863d73c08fcdd2337dc80a8b5e0917e18b036c/src/lib/LiquidationDistributor.sol#L112) call, [netApr will not account for protocol fee fraction](https://github.com/code-423n4/2024-04-gondi/blob/b9863d73c08fcdd2337dc80a8b5e0917e18b036c/src/lib/pools/Pool.sol#L458) which will [inflate the `_apr` used to offset _outstandingValues.sumApr](https://github.com/code-423n4/2024-04-gondi/blob/b9863d73c08fcdd2337dc80a8b5e0917e18b036c/src/lib/pools/Pool.sol#L751), a state variable that accounts for the total annual apr of outstanding loans.  
```solidity
//src/lib/pools/Pool.sol
        OutstandingValues memory __outstandingValues,
        uint256 _principalAmount,
        uint256 _apr,
        uint256 _interestEarned
    ) private view returns (OutstandingValues memory) {
...
         //@audit inflated _apr will offset __outstandingValues.sumApr to an incorrect lower value, causing accounting error
|>        __outstandingValues.sumApr -= uint128(_apr * _principalAmount);
...
```
(https://github.com/code-423n4/2024-04-gondi/blob/b9863d73c08fcdd2337dc80a8b5e0917e18b036c/src/lib/pools/Pool.sol#L751)

For comparison, when a loan is created ([pool::validateOffer](https://github.com/code-423n4/2024-04-gondi/blob/b9863d73c08fcdd2337dc80a8b5e0917e18b036c/src/lib/loans/MultiSourceLoan.sol#L760)), the actual protocol fee ([protocolFee.fraction](https://github.com/code-423n4/2024-04-gondi/blob/b9863d73c08fcdd2337dc80a8b5e0917e18b036c/src/lib/loans/M
settleWithBuyout() cannot pay triggerFee
Audit: 2024-04-gondi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-gondi/blob/b9863d73c08fcdd2337dc80a8b5e0917e18b036c/src/lib/AuctionWithBuyoutLoanLiquidator.sol#L97


# Vulnerability details

## Vulnerability details

in `settleWithBuyout` use `safeTransfer()` to pay `triggerFee`

```solidity
    function settleWithBuyout(
        address _nftAddress,
        uint256 _tokenId,
        Auction calldata _auction,
        IMultiSourceLoan.Loan calldata _loan
    ) external nonReentrant {
        /// TODO: Originator fee
        _checkAuction(_nftAddress, _tokenId, _auction);
        uint256 timeLimit = _auction.startTime + _timeForMainLenderToBuy;
        if (timeLimit < block.timestamp) {
            revert OptionToBuyExpiredError(timeLimit);
        }
        uint256 largestTrancheIdx;
        uint256 largestPrincipal;
        for (uint256 i = 0; i < _loan.tranche.length;) {
            if (_loan.tranche[i].principalAmount > largestPrincipal) {
                largestPrincipal = _loan.tranche[i].principalAmount;
                largestTrancheIdx = i;
            }
            unchecked {
                ++i;
            }
        }
        if (msg.sender != _loan.tranche[largestTrancheIdx].lender) {
            revert NotMainLenderError();
        }
        ERC20 asset = ERC20(_auction.asset); 
        uint256 totalOwed;
        for (uint256 i; i < _loan.tranche.length;) {
            if (i != largestTrancheIdx) {
                IMultiSourceLoan.Tranche calldata thisTranche = _loan.tranche[i];
                uint256 owed = thisTranche.principalAmount + thisTranche.accruedInterest
                    + thisTranche.principalAmount.getInterest(thisTranche.aprBps, block.timestamp - thisTranche.startTime);
                totalOwed += owed; 
                asset.safeTransferFrom(msg.sender, thisTranche.lender, owed);
            }
            unchecked {
                ++i;
            }
        }
        IMultiSourceLoan(_auction.loanAddress).loanLiquidated(_auction.loanId, _loan);

@>      asset.safeTransfer(_auction.originator, totalOwed.mulDivDown(_auction.triggerFee, _BPS));

        ERC721(_loan.nftCollateralAddress).transferFrom(address(this), msg.sender, _tokenId);

        delete _auctions[_nftAddress][_tokenId];

        emit AuctionSettledWithBuyout(_auction.loanAddress, _auction.loanId, _nftAddress, _tokenId, largestTrancheIdx);
    }
```

Unlike `settleAuction()` the funds are not in the contract and `transfer()` won't work.

Correct should be: `asset.safeTransferFrom(msg.sender, _auction.originator, totalOwed.mulDivDown(_auction.triggerFee, _BPS));`

## Impact

cannot pay triggerFee


## Recommended Mitigation

```diff
    function settleWithBuyout(
        address _nftAddress,
        uint256 _tokenId,
        Auction calldata _auction,
        IMultiSourceLoan.Loan calldata _loan
    ) external nonReentrant {
...
-       asset.safeTransfer(_auction.originator, totalOwed.mulDivDown(_auction.triggerFee, _BPS));
+       asset.safeTransferFrom(ms
addNewTranche() no authorization from borrower
Audit: 2024-04-gondi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-gondi/blob/b9863d73c08fcdd2337dc80a8b5e0917e18b036c/src/lib/loans/MultiSourceLoan.sol#L358


# Vulnerability details

## Vulnerability details
`addNewTranche() ` The code implementation is as follows：
```solidity
    function addNewTranche(
        RenegotiationOffer calldata _renegotiationOffer,
        Loan memory _loan,
        bytes calldata _renegotiationOfferSignature
    ) external nonReentrant returns (uint256, Loan memory) {
        uint256 loanId = _renegotiationOffer.loanId;

        _baseLoanChecks(loanId, _loan);
        _baseRenegotiationChecks(_renegotiationOffer, _loan);
@>      _checkSignature(_renegotiationOffer.lender, _renegotiationOffer.hash(), _renegotiationOfferSignature);
        if (_loan.tranche.length == getMaxTranches) {
            revert TooManyTranchesError();
        }

        uint256 newLoanId = _getAndSetNewLoanId();
        Loan memory loanWithTranche = _addNewTranche(newLoanId, _loan, _renegotiationOffer);
        _loans[newLoanId] = loanWithTranche.hash();
        delete _loans[loanId];

        ERC20(_loan.principalAddress).safeTransferFrom(
            _renegotiationOffer.lender, _loan.borrower, _renegotiationOffer.principalAmount - _renegotiationOffer.fee
        );
        if (_renegotiationOffer.fee > 0) {
            /// @dev Cached
            ProtocolFee memory protocolFee = _protocolFee;
            ERC20(_loan.principalAddress).safeTransferFrom(
                _renegotiationOffer.lender,
                protocolFee.recipient,
                _renegotiationOffer.fee.mulDivUp(protocolFee.fraction, _PRECISION)
            );
        }

        emit LoanRefinanced(
            _renegotiationOffer.renegotiationId, loanId, newLoanId, loanWithTranche, _renegotiationOffer.fee
        );

        return (newLoanId, loanWithTranche);
    }
```
Currently only the signature of the `lender` is checked, not the authorization of the `borrower`.
Then any `lender` can add `tranche` to any `loan` by
1. specify a very high apr
2. specify any `_renegotiationOffer.fee`,example : set `_renegotiationOffer.fee==_renegotiationOffer.principalAmount`.

This doesn't make sense for `borrower`.

It is recommended that only the `borrower` performs this method.

## Impact

`lender` can be specified to generate a malicious `tranche` to compromise `borrower`.

## Recommended Mitigation

```diff
    function addNewTranche(
        RenegotiationOffer calldata _renegotiationOffer,
        Loan memory _loan,
        bytes calldata _renegotiationOfferSignature
    ) external nonReentrant returns (uint256, Loan memory) {
        uint256 loanId = _renegotiationOffer.loanId;
+       if (msg.sender != _loan.borrower) {
+           revert InvalidCallerError();
+       } 
        _baseLoanChecks(loanId, _loan);
        _baseRenegotiationChecks(_renegotiationOffer, _loan);
        _checkSignature(_renegotiationOffer.lender, _renegotiationOffer.hash(), _renegotiationOfferSignature);
_processOffersFromExecutionData() lack of check  executionData.duration<=offer.duration
Audit: 2024-04-gondi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-gondi/blob/b9863d73c08fcdd2337dc80a8b5e0917e18b036c/src/lib/loans/MultiSourceLoan.sol#L124


# Vulnerability details

## Vulnerability details

when `emitLoan()`, only limit`offer.duration != 0`,There's no limit `executionData.duration<=offer.duration`

`emitLoan()` -> `_processOffersFromExecutionData()` -> `_validateOfferExecution()`
```solidity
    function _validateOfferExecution(
        OfferExecution calldata _offerExecution,
        uint256 _tokenId,
        address _lender,
        address _offerer,
        bytes calldata _lenderOfferSignature,
        uint256 _feeFraction,
        uint256 _totalAmount
    ) private {
...

@>      if (offer.duration == 0) {
            revert ZeroDurationError();
        }
        if (offer.aprBps == 0) {
            revert ZeroInterestError();
        }
        if ((offer.capacity > 0) && (_used[_offerer][offer.offerId] + _offerExecution.amount > offer.capacity)) { 
            revert MaxCapacityExceededError();
        }

        _checkValidators(_offerExecution.offer, _tokenId);
    }
```

If the `executionData.duration` time is not limited, it can lead to far exceeding the borrowing time `offer.duration`.
If the `lender` is a `LoanManager`, when `repayLoan()` it can also exceed the maximum `pendingQueues`, leading to accounting issues

## Impact

far exceeding the borrowing time than `offer.duration`.
If `lender` is `LoanManager` also exceeds max `pendingQueues`, causing bookkeeping issues

## Recommended Mitigation

check `executionData.duration<=offer[n].duration`


## Assessed type

Context
mergeTranches()/refinancePartial() lack of nonReentrant
Audit: 2024-04-gondi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-gondi/blob/b9863d73c08fcdd2337dc80a8b5e0917e18b036c/src/lib/loans/MultiSourceLoan.sol#L389


# Vulnerability details

## Vulnerability details
`mergeTranches() ` the method's code implementation is as follows::
```solidity
 function mergeTranches(uint256 _loanId, Loan memory _loan, uint256 _minTranche, uint256 _maxTranche)
        external
        returns (uint256, Loan memory)
    {
        _baseLoanChecks(_loanId, _loan);
        uint256 loanId = _getAndSetNewLoanId();
        Loan memory loanMergedTranches = _mergeTranches(loanId, _loan, _minTranche, _maxTranche);
        _loans[loanId] = loanMergedTranches.hash();
        delete _loans[_loanId];

        emit TranchesMerged(loanMergedTranches, _minTranche, _maxTranche);

        return (loanId, loanMergedTranches);
    }
```

As shown above, this method lacks reentrancy protection, which could allow reentrancy attacks to manipulate the `_loans[]`.

Example:
Suppose `_loans[1] = {NFT = 1}`
1. Alice calls refinanceFromLoanExecutionData(_loans[1],LoanExecutionData)
    - LoanExecutionData.ExecutionData.OfferExecution.LoanOffer.OfferValidator[0].validator =  CustomContract   => for callback
2. `refinanceFromLoanExecutionData() `-> `_processOffersFromExecutionData()`-> `_validateOfferExecution()`->`_checkValidators()`->`IOfferValidator(CustomContract).validateOffer()`
3. in `IOfferValidator(CustomContract).validateOffer()` call `MultiSourceLoan.mergeTranches(_loans[1])` -->pass without `nonReentrant`
   - _loans[3] = newLoan.hash()
4. return to refinanceFromLoanExecutionData(), will execute:
   - _loans[2] = newOtherLoan.hash()

There will be `_loans[2] ` and `_loans[3] `, both containing `NFT=1`.
Note: Both Loans 's lender are all himself

1. the user can `repayLoan(_loans[2])` and get the NFT back.
2. use the NFT to borrow other people's funds, e.g. to generate `_loans[100]`. 
3. `repayLoan(_loans[3])`, get NFT back

## Impact

Stealing funds

## Proof of Concept

## Recommended Mitigation

add nonReentrant

```diff
 function mergeTranches(uint256 _loanId, Loan memory _loan, uint256 _minTranche, uint256 _maxTranche)
        external
+       nonReentrant
        returns (uint256, Loan memory)
    {
        _baseLoanChecks(_loanId, _loan);
        uint256 loanId = _getAndSetNewLoanId();

    function refinancePartial(RenegotiationOffer calldata _renegotiationOffer, Loan memory _loan)
        external
+       nonReentrant
        returns (uint256, Loan memory)
    {
```


## Assessed type

Context