xanoutas/solidity-security-findings huggingface dataset:
refinanceFromLoanExecutionData() Reusing borrower's signature to steal funds
Audit: 2024-05-gondi-mitigation-findings
Severity: HIGH

# Lines of code

https://github.com/pixeldaogg/florida-contracts/blob/b83b37bbe69325b12e3b8119dfd86eb86f16fc73/src/lib/loans/MultiSourceLoan.sol#L320


# Vulnerability details

## Vulnerability details
in `refinanceFromLoanExecutionData()`

The `LoanExecutionData` signature of `borrower` is reusable (since there are no nonces, as long as it doesn't expire)

Suppose `loanOffer.fee =100`.
Then each time `refinanceFromLoanExecutionData()` is executed the funds flow as follows.
1. lender pay = loanOffer.principalAmount - loanOffer.fee
2. borrower repay = loanOffer.principalAmount

So for each execution, `lender` receives a `loanOffer.fee` difference.

( loanOffer.principalAmount - (loanOffer.principalAmount - loanOffer.fee))

This way a malicious `lender` can monitor `emitLoan()` to reuse the signature of the `borrower` to steal funds
Example.
1. bob signs a borrower's `LoanExecutionData` and executes `emitLoan()`. loan : {fee = 1% , lender = alice}
2. Malicious user alice executes `refinanceFromLoanExecutionData(loan)` after `bob`'s transaction, using the signature that `bob` just signed.
3. every time `refinanceFromLoanExecutionData(loan)` is executed, alice gets an extra `fee = 1%`.

## POC
The following code demonstrates the reuse of the `emitLoan()` signature to steal funds.

add to MultiSourceLoan.t.sol
```solidity
    function testReuseBorrowSign() public {
        uint256 privateKey = 100;
        address otherBorrower = vm.addr(privateKey);
        uint256 otherToken = collateralTokenId + 1;

        IMultiSourceLoan.LoanOffer memory loanOffer =
            _getSampleOffer(address(collateralCollection), otherToken, _INITIAL_PRINCIPAL);
        //@info capacity can't 0
        loanOffer.capacity = 1000000e18;
        //@info need fees 1 %
        loanOffer.fee = _INITIAL_PRINCIPAL / 100;         
        testToken.mint(loanOffer.lender, _INITIAL_PRINCIPAL * 10000);
        testToken.mint(otherBorrower, _INITIAL_PRINCIPAL * 10000);
        vm.prank(otherBorrower);
        testToken.approve(address(_msLoan), type(uint256).max);

        collateralCollection.mint(otherBorrower, otherToken);
        vm.prank(otherBorrower);
        collateralCollection.approve(address(_msLoan), otherToken);
        loanOffer.nftCollateralTokenId = otherToken;

        loanOffer.duration = 30 days;
        IMultiSourceLoan.LoanExecutionData memory lde = _sampleLoanExecutionData(loanOffer);
        lde.executionData.tokenId = otherToken;
        lde.borrower = otherBorrower;
        bytes32 executionDataHash = _msLoan.DOMAIN_SEPARATOR().toTypedDataHash(lde.executionData.hash());
        (uint8 vOffer, bytes32 rOffer, bytes32 sOffer) = vm.sign(privateKey, executionDataHash);
        lde.borrowerOfferSignature = abi.encodePacked(rOffer, sOffer, vOffer);
        //@info first loan
        (uint256 loanId, IMultiSourceLoan.Loan memory loan) = _msLoan.emitLoan(lde);

        //@info ********* reuse sign *********
        uint256 initBalance = testToken.balanceOf(loanOff
Auction proceeds might be distributed to an incorrect queue, due to incorrect loan contract passed to _loanTermination()
Audit: 2024-05-gondi-mitigation-findings
Severity: MEDIUM

See the markdown file with the details of this report [here](https://github.com/code-423n4/2024-05-gondi-mitigation-findings/blob/main/data/oakcobalt-Q.md).
Loan offer's required collateral tokenId is not validated in some conditions, borrower can use any NFT to initiate loans
Audit: 2024-05-gondi-mitigation-findings
Severity: MEDIUM

# Lines of code

https://github.com/pixeldaogg/florida-contracts/blob/7bacbe3f2b4c1bb6c87961e3553118a6e6c2dcee/src/lib/loans/MultiSourceLoan.sol#L850


# Vulnerability details

### Impacts
Loan offer's required collateral tokenId is not validated in some conditions, borrower can use any NFT to initiate loans.

### Proof of concept
When a lender generate LoanOffer, they can either specify a specific NFT tokenId, or allow a collection offer (any tokenId within the NFT collection). LoanOffer and executionData's collateral Id match is checked in `_checkValidators()`.

Based on code doc, a lender's LoanOffer struct -> the collateral tokenId required :
(1) Empty validator array input -> lender want the exact tokenId match;
(2) Single validator element input && _loanOffer.validators[0].validator == address(0) -> lender accepts any token in the collection;
(3) Non-empty validator array &&_loanOffer.validators[0].validator != address(0) -> lender wants offer validators to check the offer.
```solidity
|>  /// @notice Check generic offer validators for a given offer or
    ///         an exact match if no validators are given. The validators
    ///         check is performed only if tokenId is set to 0.
    ///         Having one empty validator is used for collection offers (all IDs match).
...
    function _checkValidators(LoanOffer calldata _loanOffer, uint256 _tokenId) private view {
...
```
Edge case: A lender wants an exact tokenId match and the tokenId is 0. 

Based on the code doc, an exact match check should be performed when no validators are given. So lender's LoanOffer will be: (1) _loanOffer.nftCollateralTokenId =0 ; (2) _loanOffer.validators.length == 0;

However, _checkValidators() will not check this loanOffer at all. Due to empty validator array is provided, exact tokenId match is not checked due to vulnerable check `if (_loanOffer.nftCollateralTokenId != 0){...`. In the else branch, for-loop will be directly skipped, and exit the function with no checks.
```solidity
    function _checkValidators(LoanOffer calldata _loanOffer, uint256 _tokenId) private view {
       uint256 offerTokenId = _loanOffer.nftCollateralTokenId;
        //@audit This is vulnerable check condition, will cause tokenId=0 and empty validators to be skipped entirely
 |>     if (_loanOffer.nftCollateralTokenId != 0) {
            if (offerTokenId != _tokenId) {
                revert InvalidCollateralIdError();
            }
        } else {
            uint256 totalValidators = _loanOffer.validators.length;
            if (totalValidators == 0 && _tokenId != 0) {
                revert InvalidCollateralIdError();
            } else if ((totalValidators == 1) && _isZeroAddress(_loanOffer.validators[0].validator)) {
                return;
            }
            for (uint256 i = 0; i < totalValidators;) {
                IBaseLoan.OfferValidator memory thisValidator = _loanOffer.validators[i];
                IOfferValidator(thisValidator.validator).validateOffer(_loa
Pool::validateOffer fails to account for protocolFee, loan creation might drain pool collected fees or revert 
Audit: 2024-05-gondi-mitigation-findings
Severity: MEDIUM

# Lines of code

https://github.com/pixeldaogg/florida-contracts/blob/7bacbe3f2b4c1bb6c87961e3553118a6e6c2dcee/src/lib/pools/Pool.sol#L365


# Vulnerability details

### Impacts
Pool::validateOffer fails to account for protocolFee, loan creation might drain pool collected fees or revert 


### Proof of concept
During a loan initiation (emitLoan()/refinanceFromExecutionData()), if the lender is the pool, pool::validateOffer will be called and reallocate assets to make sure there is enough balance to transfer funds for the loan.

However, the problem is pool::validateOffer only assumes principalAmount is required to be transferred but misses that [protocolFee is also required to be transferred to MultiSourceLoan.sol](https://github.com/pixeldaogg/florida-contracts/blob/7bacbe3f2b4c1bb6c87961e3553118a6e6c2dcee/src/lib/loans/MultiSourceLoan.sol#L985-L986).(MultiSourceLoan::_handleProtocolFeeForFee transfer protocolFees directly from the lender)
```solidity
//src/lib/pools/Pool.sol
    function validateOffer(bytes calldata _offer, uint256 _protocolFee) external override {
...
        if (principalAmount > undeployedAssets) {
            revert InsufficientAssetsError();
 |>     } else if (principalAmount > currentBalance) {
  //@audit this misses protocolFee amount
            |> IBaseInterestAllocator(getBaseInterestAllocator).reallocate(currentBalance, principalAmount, true);
        }
        /// @dev If the txn doesn't revert, we can assume the loan was executed.
        _outstandingValues = _getNewLoanAccounting(principalAmount, _netApr(apr, _protocolFee));
```
(https://github.com/pixeldaogg/florida-contracts/blob/7bacbe3f2b4c1bb6c87961e3553118a6e6c2dcee/src/lib/pools/Pool.sol#L365)
emitLoan()/refinanceFromExecutionData() → _processOffersFromExecutionData() → _validateOfferExecution() → validateOffer() -> reallocate()

In [reallocate()](https://github.com/pixeldaogg/florida-contracts/blob/7bacbe3f2b4c1bb6c87961e3553118a6e6c2dcee/src/lib/pools/AaveUsdcBaseInterestAllocator.sol#L100), only delta (principalAmount -currentBalance ) will be transferred to the pool contract. This means that the pool will not have enough funds to pay (principalAmount + protocolFee).

(1) If the pool contract doesn't have enough `getCollectedFees` to cover protocolFee, loan creation will revert [during later token transfer](https://github.com/pixeldaogg/florida-contracts/blob/7bacbe3f2b4c1bb6c87961e3553118a6e6c2dcee/src/lib/loans/MultiSourceLoan.sol#L985-L986); Loan creation fails; (2) If the pool contract has enough `getCollectedFees`/`getAvailableToWithdraw` to cover protocolFee, `getCollectedFees`/`getAvailableToWithdraw` will be drained during later token transfer. And the pool doesn't have enough token to cover `getCollectedFees` or cover [queue claims](https://github.com/pixeldaogg/florida-contracts/blob/7bacbe3f2b4c1bb6c87961e3553118a6e6c2dcee/src/lib/pools/Pool.sol#L733) (`getAvailableToWithdraw`)

### Tools
Manual
### Recommendations
In Pool::validateOffer, calc
H-17 Unmitigated
Audit: 2024-05-gondi-mitigation-findings
Severity: MEDIUM

# Lines of code

https://github.com/pixeldaogg/florida-contracts/blob/7212bfbe9f78ca6eabb5eec86e24d754feb47f15/src/lib/loans/MultiSourceLoan.sol#L1195


# Vulnerability details

### Impacts:
In an edge case, the same struct renegotiationOffer and renegotiaionOffer signature can still be used interchangeably between refinanceFull() and addNewTranche().

### Original Issue/ Current fix:
C4 Issue:
H-17: [refinanceFull/addNewTranche reusing a lender's signature leads to unintended behavior](https://github.com/code-423n4/2024-04-gondi-findings/issues/13)

Original vulnerabilities:
refinanceFull and addNewTranche both require RenegotiationOffer’s signature check, but allows the same renegotiationOffer struct and same signature to pass. 

Original impacts:
This encourages the attack vector of front-running to use the signature for malicious operations. In this case, an attacker can use the renegotiationOffer input and signature that was meant for refinanceFull() in addNewTranche(). 

Mitigation:
Fix: https://github.com/pixeldaogg/florida-contracts/pull/390/files
```solidity
//src/lib/loans/MultiSourceLoan.sol
    function refinanceFull(
        RenegotiationOffer calldata _renegotiationOffer,
        Loan memory _loan,
        bytes calldata _renegotiationOfferSignature
    ) external nonReentrant returns (uint256, Loan memory) {
        _checkRefinanceFullRenegotiationOffer(_renegotiationOffer, _loan.tranche.length);
...

    function addNewTranche(
        RenegotiationOffer calldata _renegotiationOffer,
        Loan memory _loan,
        bytes calldata _renegotiationOfferSignature
    ) external nonReentrant returns (uint256, Loan memory) {
...
        _checkAddNewTrancheOffer(_renegotiationOffer, _loan.tranche.length);


    function _checkAddNewTrancheOffer(RenegotiationOffer calldata _renegotiationOffer, uint256 _totalTranches)
        private
        pure
    {
|>       if (_renegotiationOffer.trancheIndex.length != 1 || _renegotiationOffer.trancheIndex[0] != _totalTranches) {
            revert InvalidRenegotiationOfferError();
        }
    }

    function _checkRefinanceFullRenegotiationOffer(
        RenegotiationOffer calldata _renegotiationOffer,
        uint256 _totalTranches
    ) private pure {
 |>     if (_renegotiationOffer.trancheIndex.length != _totalTranches) {
            revert InvalidRenegotiationOfferError();
        }
    }
```
The current fix is to add additional checks on struct renegotiationOffer. In r`efinanceFull()`, checks are added to ensure that `_renegotiationOffer.trancheIndex.length != _totalTranches` . In `addNewTranche()`, a different check is in place to make sure `tranchIndex.length ==1 && trancheIndex[0]== _totalTranches` (the only trancheIndex would be the next tranche index).

### Proof of concept

The problem is an edge case will still allow struct renegotiationOffer and renegotiaionOffer signature to be used interchangeably between refinanceFull() and addNewTranche().

Edge Case: 
A loan only has 1 tranche,
`LoanManager.updateOfferHandler()` is called before `confirmOfferHandler()`
Audit: 2024-05-gondi-mitigation-findings
Severity: MEDIUM

# Lines of code

https://github.com/pixeldaogg/florida-contracts/blob/10d48b51313496c41c886cd46e610b627ef159aa/src/lib/loans/LoanManagerParameterSetter.sol#L70


# Vulnerability details

## Impact
The offer handler is a crucial part of the LoanManager. It handles what loan terms are allowed to taken from the Pool, affect the risk and interest of Pool's users directly. To facilitate changes made by the owner, the protocol uses a two-step process involving two functions: `setOfferHandler()` and `confirmOfferHandler()`. These are located in the `LoanManagerParameterSetter`.

However, the function `LoanManager.updateOfferHandler()` is immediately executed within `setOfferHandler()`, not `confirmOfferHandler()`. Consequently, the updated offer handler takes effect instantly without needing confirmation, which undermines the two-step process's intent.

## Proof of Concept
```solidity
function setOfferHandler(address __offerHandler) external onlyOwner {
    // ...
    getProposedOfferHandler = __offerHandler;
    getProposedOfferHandlerSetTime = block.timestamp;
    // @audit should wait for confirmOfferHandler before updating in LoanManager
    ILoanManager(getLoanManager).updateOfferHandler(__offerHandler); 
      // ...
}

/// @notice Confirm the OfferHandler contract.
/// @param __offerHandler The new OfferHandler address.
function confirmOfferHandler(address __offerHandler) external onlyOwner {
    // ...

    getOfferHandler = __offerHandler;
    getProposedOfferHandler = address(0);
    getProposedOfferHandlerSetTime = type(uint256).max;
  
    // ...
}
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Instead of calling `LoanManager.updateOfferHandler()` within `setOfferHandler()`, it should be executed within `confirmOfferHandler()`.



## Assessed type

Other
M-12 Unmitigated
Audit: 2024-05-gondi-mitigation-findings
Severity: MEDIUM

# Lines of code




# Vulnerability details

## Issue

The issue is `getMinTimeBetweenWithdrawalQueues` could be too small, causing `pendingQueues` will be overwritten too early.

Currently, `_LOAN_BUFFER_TIME = 7 days` and `LiquidationHandler.MAX_AUCTION_DURATION = 7 days`.

So in case a loan has a max auction duration and be extended, the `getMinTimeBetweenWithdrawalQueues` will not be enough.

## Mitigation
The sponsor fixed the issue by introducing `maxExtension` to limit auction extensions. 

However, since other configs do not change, I believed the issue is not fixed. The scenario here is `duration = MAX_AUCTION_DURATION = 7 days`, any value `maxExtension > 0` will still cause the issue

```solidity
uint256 currentTime = block.timestamp;
uint96 expiration = _auction.startTime + _auction.duration;
uint96 withMargin = _auction.lastBidTime + _MIN_NO_ACTION_MARGIN;
uint96 maxExtension = expiration + getMaxExtension;
uint96 minWithMarginMaxExtension = withMargin > maxExtension ? maxExtension : withMargin;
/// @dev max (min(withMargin, maxExtension), expiration)
uint96 max = minWithMarginMaxExtension > expiration ? minWithMarginMaxExtension : expiration;
if (max < currentTime && currentHighestBid > 0) {
    revert AuctionOverError(max);
}
```



## Assessed type

Other
M-08 Unmitigated
Audit: 2024-05-gondi-mitigation-findings
Severity: MEDIUM

# Lines of code

https://github.com/pixeldaogg/florida-contracts/blob/10d48b51313496c41c886cd46e610b627ef159aa/src/interfaces/loans/IMultiSourceLoan.sol#L52


# Vulnerability details

## Issue

The borrower signature could be reused in the Loan contract. 

## Mitigation
The sponsor acknowledged and added a comment in the code. So I believed the issue is not fixed. It should be marked as Acknowledged and Out of Scope instead.
```diff
+ /// @dev It's advised that borrowers only set an expirationTime close to the actual time they will execute the loan
+ ///      to avoid replays. 
/// @param offerExecution List of offers to be filled and amount for each.
/// @param tokenId NFT collateral token ID.
```



## Assessed type

Other
H-01 Unmitigated
Audit: 2024-05-gondi-mitigation-findings
Severity: MEDIUM

# Lines of code

https://github.com/pixeldaogg/florida-contracts/blob/7212bfbe9f78ca6eabb5eec86e24d754feb47f15/src/lib/loans/MultiSourceLoan.sol#L404


# Vulnerability details

### Impact
Other lenders merging tranches can still cause incorrect accounting in _loanTermnination() of the Pool contract.

### Proof of Concept
C4 Issue: [H-01 Merging tranches could make _loanTermination() accounting incorrect](https://github.com/code-423n4/2024-04-gondi-findings/issues/69)
The original issue raises the vulnerability that mergeTranches() is permissionless and will create a new loandId. The original impact was that a new loanId will result in incorrect accounting in `_loanTermination()`(e.g. called in repayLoan flow) - the received funds will be assigned to the pool instead of the corresponding withdrawal queue of the original loan.

Fix: https://github.com/pixeldaogg/florida-contracts/pull/360/files
The mitigation is adding access control to `mergeTranches()` to limit the caller to be the lender of the merged tranches.

The problem is this impact and attack vector persists after the mitigation. A loan can be consisted of multiple tranches / lenders. When some tranches are merged in the loan, the loanId is updated which affects all other tranches as well.
```solidity
//https://github.com/pixeldaogg/florida-contracts/blob/feature/developWithOpt/src/lib/loans/MultiSourceLoan.sol#L404
    function mergeTranches(uint256 _loanId, Loan memory _loan, uint256 _minTranche, uint256 _maxTranche)
        external
        nonReentrant
        returns (uint256, Loan memory)
    {
...
//@audit Vulnerable case: msg.sender is not a pool contract (loanManager), msg.sender can cause other loanManager's loanAccounting to be incorrect 
        if (msg.sender != _loan.tranche[_minTranche].lender) {
            revert InvalidCallerError();
        }

...
```
(https://github.com/pixeldaogg/florida-contracts/blob/7212bfbe9f78ca6eabb5eec86e24d754feb47f15/src/lib/loans/MultiSourceLoan.sol#L404)

Vulnerable case: Because the original impact is the accounting error in the pool contract(loanManager), suppose the lenderA of the tranches to be merged is NOT a loanManager. And lenderB is a pool contract(loanManager).

LoanID 100: tranche0 (lenderA), tranche1(lenderA), tranche2(lenderB)

The attack scenario is lenderA calling `mergeTranches()`. The tx will pass because lenderA is the lender of tranche0 and tranche1. After the tx, suppose the new LoanID is 150.

When the loan is repaid, lenderB's _loanTermination() will be called with the new LoanId 150, causing the payment to be assigned to the pool instead of the original queue.

### Tools Used
Manual

### Recommended Mitigation Steps
In `mergeTranches()`, add a check condition to ensure the lender who's calling is also a loan manager(`getLoanManagerRegistry.isLoanManager()`). Consider only allowing a lender who is also a loanManager to merge tranches.







## Assessed type

Other
AuctionWithBuyoutLoanLiquidator lender get less interest
Audit: 2024-05-gondi-mitigation-findings
Severity: MEDIUM

# Lines of code

https://github.com/pixeldaogg/florida-contracts/blob/9f496f5b7cfcfe70fb60d1c3fa4cb7f4c3f66699/src/lib/AuctionWithBuyoutLoanLiquidator.sol#L100
https://github.com/pixeldaogg/florida-contracts/blob/9f496f5b7cfcfe70fb60d1c3fa4cb7f4c3f66699/src/lib/LiquidationDistributor.sol#L60


# Vulnerability details

## Vulnerability details
https://github.com/pixeldaogg/florida-contracts/pull/371

this PR Changed to loan end time instead of current timestamp

In order to resolve the issue of liquidation ,`LiquidationDistributor.distribute()` may break maxSeniorRepayment's expectations

There are two issues
### first
this PR Also modified another contract `AuctionWithBuyoutLoanLiquidator.sol`

```diff
contract AuctionWithBuyoutLoanLiquidator is AuctionLoanLiquidator {
...

+       uint256 loanEndTime = _loan.startTime + _loan.duration;
        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);
+                   + thisTranche.principalAmount.getInterest(thisTranche.aprBps, loanEndTime - thisTranche.startTime);
                totalOwed += owed;
                asset.safeTransferFrom(msg.sender, thisTranche.lender, owed);

                if (getLoanManagerRegistry.isLoanManager(thisTranche.lender)) {
                    LoanManager(thisTranche.lender).loanLiquidation(
                        thisTranche.loanId,
                        thisTranche.principalAmount,
                        thisTranche.aprBps,
                        thisTranche.accruedInterest,
                        _loan.protocolFee,
                        owed,
                        thisTranche.startTime
                    );
                }
            }

```

The change to `AuctionWithBuyoutLoanLiquidator` does not make sense, the interest should still be calculated using the current time, because:

1. `AuctionWithBuyoutLoanLiquidator` is a user-initiated repayment and requires full payment of the amount owed, unlike a bidding auction where there is a shortfall in the amount owed.
So there is no "maxSeniorRepayment" problem. 

2. the user has 4 days to consider `MAX_TIME_FOR_MAIN_LENDER_TO_BUY = 4 days`, during this time, `lender` has not received the payment, and should calculate the interest.
(Users can maliciously delay the repayment, anyway, when to repay, the repayment amount is the same)

### second
`LiquidationDistributor` should only use `loan end time` if the reimbursement amount is insufficient.
It makes more sense to use `block.timestamp` when the amount is enough.
Since each `Tranche` has different `aprBps`, it would make more sense to calculate the interest at the `current time` and allocate the excess am