# Beedle - Oracle free perpetual lending - Findings Report
# Table of contents
- ## [Contest Summary](#contest-summary)
- ## [Results Summary](#results-summary)
- ## High Risk Findings
- ### [H-01. `sellProfits()` is not slippage protected](#H-01)
- ### [H-02. auctionLength not set in the loan ](#H-02)
- ### [H-03. Incorrect setting of the lender while buying loan ](#H-03)
- ### [H-04. Protocol doesn't support tokens not compatible with ERC20 standard](#H-04)
- ## Medium Risk Findings
- ### [M-01. Frontrun issues in the `giveLoan` and `buyLoan` function](#M-01)
- ### [M-02. Borrows can pay higher interest than expected](#M-02)
# <a id='contest-summary'></a>Contest Summary
### Sponsor: BeedleFi
### Dates: Jul 24th, 2023 - Aug 7th, 2023
[See more contest details here](https://www.codehawks.com/contests/clkbo1fa20009jr08nyyf9wbx)
# <a id='results-summary'></a>Results Summary
### Number of findings:
- High: 4
- Medium: 2
- Low: 0
# High Risk Findings
## <a id='H-01'></a>H-01. `sellProfits()` is not slippage protected
### Relevant GitHub Links
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Fees.sol#L38
## Summary
Uniswap swap done in `sellProfits()` can be sandwiched heavily.
## Vulnerability Details
[`sellProfits()`](https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Fees.sol#L38) makes a Uniswap trade and sets `amountOutMinimum` to 0:
```solidity
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
.ExactInputSingleParams({
tokenIn: _profits,
tokenOut: WETH,
fee: 3000,
recipient: address(this),
deadline: block.timestamp,
amountIn: amount,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
});
```
Thus this trade succeeds even if the amount received from the swap is 0. This allows anyone to sandwich this trade for their own profit resulting in 0 WETH in `sellProfits()` Uniswap trade.
## Impact
Any token sold via `sellProfits()` for WETH will result in a heavy loss.
## Tools Used
Manual
## Recommendations
Add a `_amountOutMinimum` parameter to `sellProfits` which is passed to `amountOutMinimum`. This ensures that the caller has control over the slippage.
## <a id='H-02'></a>H-02. auctionLength not set in the loan
### Relevant GitHub Links
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L522
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/utils/Structs.sol#L54
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L412
## Summary
`loan.auctionLength` is not updated in `giveLoan` and `buyLoan` functions.
## Vulnerability Details
In the `giveLoan` function, when the loan is given to another pool and the loan is updated with new details, `auctionlength` is not updated which has to be updated based on the new pool parameters.
Similarly in the function `buyLoan` when the loan goes to the auction
After the loan is given to new pool, the `loan.auctionlength` is not set according to the new pool.
Even the event to be emitted should include the auction length value.
## Impact
The auction length will be different from the new pool setting.
## Tools Used
## Recommendations
In the function `giveLoan`,`buyLoan` add the following
```diff
// update the loan with the new info
+loans[loanId].auctionlength = pools[poolId].auctionLength
loans[loanId].lender = msg.sender;
loans[loanId].interestRate = pools[poolId].interestRate;
loans[loanId].startTimestamp = block.timestamp;
loans[loanId].auctionStartTimestamp = type(uint256).max;
loans[loanId].debt = totalDebt;
```
## <a id='H-03'></a>H-03. Incorrect setting of the lender while buying loan
### Relevant GitHub Links
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L518
## Summary
Anyone can call `function buyLoan(uint256 loanId, bytes32 poolId)` and become the lender of the loan.
## Vulnerability Details
`buyLoan()` can be called by anyone for any loan and pool. The function then transfers the loan to that pool after all the checks pass. Finally, it sets the lender of the transferred loan to `msg.sender` [here](https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L518):
```solidity
// update the loan with the new info
loans[loanId].lender = msg.sender;
```
Since anyone call this function, lender can be set to an address different from the pool's actual lender. After this, if the loan is seized by calling [`seizeLoan()`](https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L566), loan's lender is transferred the collateral:
```solidity
IERC20(loan.collateralToken).transfer(
loan.lender,
loan.collateral - govFee
);
```
Since `loan.lender` is set incorrectly, the collateral tokens are effectively stolen.
## Impact
Collateral token can be incorrectly transferred
## Tools Used
Manual Review
## Recommendations
Lender assignment should be based on the actual lender of the pool through `poolId.lender`
```diff
-loans[loanId].lender = msg.sender;
+loans[loanId].lender = pools[poolId].lender;
```
## <a id='H-04'></a>H-04. Protocol doesn't support tokens not compatible with ERC20 standard
### Relevant GitHub Links
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L152
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L159
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L187
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L203
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L317-L329
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L403
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L505
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L563-L565
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L642-L656
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L663-L670
## Summary
If non-compliant ERC20 tokens are added to pools (like USDT), then protocol may not be able to handle the transfers.
## Vulnerability Details
The protocol uses `transfer()` and `transferFrom()` to transfer tokens. However, there are popular tokens like USDT which doesn't return data on transfers. Other tokens may return false on failure.
## Impact
Protocol will revert or do incorrect accounting for these tokens.
## Tools Used
Manual
## Recommendations
Use OpenZeppelin's `SafeERC20` library to handle ERC20 transfer. It handles cases where a token may return false on failure or not return anything at all.
Add the following at the top of the contract:
```solidity
using SafeERC20 for IERC20;
```
Replace all instances of calling `transfer` and `transferFrom` on ERC20 token with `safeTransfer` and `safeTransferFrom`.
For example for
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L159
```diff
-IERC20(p.loanToken).transfer(
+IERC20(p.loanToken).safeTransfer(
```
# Medium Risk Findings
## <a id='M-01'></a>M-01. Frontrun issues in the `giveLoan` and `buyLoan` function
### Relevant GitHub Links
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L417
https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L519
## Summary
The new pool lender can frontrun a previous pool lender transaction to charge a higher interest from the borrower than expected.
## Vulnerability Details
The borrower can pay higher interest rate than expected. `giveLoan` and `buyLoan` function transfers a loan to a new pool. If the new pool owner sees these transactions in the mempool, it can set its pools' interest rate to the maximum possible (which is loan's current interest rate, or auction's current interest rate).
The loan’s interest rate is coming from `pool.interestRate` which the new pool lender can increase by calling `setPool` or `updateInterestRate` through frontrunning.
## Impact
Borrowers will pay the interest rate higher than expected for their loans. However, `giveLoan` ensures that the interest rate cannot exceed the current loan's interest rate, and `buyLoan` function ensures that the interest rate cannot exceed the auction's interest rate, the risk is limited.
## Tools Used
Manual review.
## Recommendations
Update `giveLoan` and `buyLoan` function to to include `interestRate` argument for each loan being given or bought. This denotes the expected interest rate of the new pool. Revert if it doesn't match with the current value of the new pools's interest rate.
## <a id='M-02'></a>M-02. Borrows can pay higher interest than expected
### Relevant GitHub Links
https://github.com/Cyfrin/2023-07-beedle/blob/main/src/Lender.sol#L256
## Summary
The lender can frontrun a borrow transaction to charge a higher interest than the borrower expected.
## Vulnerability Details
The borrower will have to go through a high interest rate even though they saw a lower interest rate at the time of signing the transaction. The loan parameters are created as:
```solidity
Loan memory loan = Loan({
lender: pool.lender,
borrower: msg.sender,
loanToken: pool.loanToken,
collateralToken: pool.collateralToken,
debt: debt,
collateral: collateral,
interestRate: pool.interestRate,
startTimestamp: block.timestamp,
auctionStartTimestamp: type(uint256).max,
auctionLength: pool.auctionLength
});
```
Here, the loan’s interest rate is coming from `pool.interestRate` which the lender can increase by calling `setPool` or `updateInterestRate` through frontrunning.
## Impact
Borrowers will pay the interest rate higher than expected for their loans.
## Tools Used
Manual review.
## Recommendations
Update `Borrow` struct to include `maxInterestRate` field. This denotes the maximum interest rate the borrower is willing to pay. Now revert in `borrow` function if `borrows[I].maxInterestRate < pool.interestRate`.