xanoutas/solidity-security-findings huggingface dataset:
Lack of `swap` consideration, while calculating number of iteration (lend=>borrow=>swap) needs to be performed leads to incorrect iteration number
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/utilities/poolsUtility/Position.sol#L526-L583


# Vulnerability details

## Title 
Lack of `swap` consideration, while calculating the number of iteration (lend=>borrow=>swap) that needs to be performed leads to incorrect iteration number
## Link
https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/utilities/poolsUtility/Position.sol#L526-L583
## Impact
The [`openPosition()`](https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/utilities/poolsUtility/Position.sol#L125) calls [`_borrowLimit()`](https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/utilities/poolsUtility/Position.sol#L526) to get the `limit` variable which implies the number of iteration we need to do `lens=>borrow=>swap` loop.

The ideal way of doing it is
-1. Calculate the `desiredAmount = assetAmount * leverage`
-2. Then calculate how much we can borrow from `assetAmount` => borrowAmount
-3. Calculate approximately how much amount of asset we get after swapping it with quote
-4. Compare it with the desiredAmount
     - If reached, then break otherwise reiterate the loop


Here's the first step in the function
https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/utilities/poolsUtility/Position.sol#L534
```
        uint256 _desiredAmount = wmul(_assetAmount, _leverage);
```
Here's 2nd step:
https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/utilities/poolsUtility/Position.sol#L548-L555
```
                    _loopBorrowed = wmul(_assetAmount, _collateralFactor).add(
                        _max
                    );
                } else {
                    _loopBorrowed = wmul(_assetAmount, _collateralFactor);
                }
            } else {
                _loopBorrowed = wmul(_loopBorrowed, _collateralFactor);
```
But the 3rd step is missing. [`_borrowLimit()`](https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/utilities/poolsUtility/Position.sol#L526) never accounts for the loss that can possibly happen during the swap. It just assumes the `_loopBorrowed` as the `assetAmount` and compares it with `desiredAmount` here:
4th step: 
https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/utilities/poolsUtility/Position.sol#L562
```
            if (_assetAmount > _desiredAmount) {
```

This leads to an incorrect number of iterations of `lend=>borrow=>swap` loop, which leads to an incorrect amount of leverage the user gets.

This may result in unexpected outcomes (One of them can be early liquidation)
## Proof of Concept
## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider swap loss while calculating
offer() lack of sanity check can result in a loss of fund for traders
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/RubiconMarket.sol#L802-L822
https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/RubiconMarket.sol#L824-L852
https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/RubiconMarket.sol#L511-L562


# Vulnerability details

### Impact

There is 3 dangerous public function that can result in a lost of fund for the user : 

[RubiconMarket.sol#L511-L562](https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/RubiconMarket.sol#L511-L562)

[RubiconMarket.sol#L824-L852](https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/RubiconMarket.sol#L824-L852)

[RubiconMarket.sol#L802-L822](https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/RubiconMarket.sol#L802-L822)

### Proof of Concept

These function does not check if at least a recipient or owner has been passed correctly. 

When cancelling or buying an offer, if `owner == recipient == address(0)` fund will be loss by sending to address(0) when calling `cancel()` or `buy()` : 

[RubiconMarket.sol#L461](https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/RubiconMarket.sol#L461)

[RubiconMarket.sol#L351-L355](https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/RubiconMarket.sol#L351-L355)

### Recommended Mitigation Steps

A solution can be to address(0) check that at least an owner or a recipient address has been passed on these functions :

[RubiconMarket.sol#L511-L562](https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/RubiconMarket.sol#L511-L562)

[RubiconMarket.sol#L824-L852](https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/RubiconMarket.sol#L824-L852)

[RubiconMarket.sol#L802-L822](https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/RubiconMarket.sol#L802-L822)

By adding : 

```jsx
if (owner == address(0) && recipient == address(0)) revert;
```
Buy function transfers the assets to zero address for the offer where owner and recipient are both zero
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/RubiconMarket.sol#L349-L364


# Vulnerability details

## Impact
So we know that the offer can be created using the function where user can pass in owner and recipeint so if both the owner and recipient are set to zero address buy function transfers the assets to 0 address, eventually burning them and loss of funds for the user because there are no zero checks on offer function at:
https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/RubiconMarket.sol#L511

## Proof of Concept
```
    /// @notice Key function to make a new offer. Takes funds from the caller into market escrow.
    function offer(
        uint256 pay_amt,
        ERC20 pay_gem,
        uint256 buy_amt,
        ERC20 buy_gem,
        address owner,
        address recipient
    ) public virtual can_offer synchronized returns (uint256 id) {
        require(uint128(pay_amt) == pay_amt);
        require(uint128(buy_amt) == buy_amt);
        require(pay_amt > 0);
        require(pay_gem != ERC20(address(0))); /// @dev Note, modified from: require(pay_gem != ERC20(0x0)) which compiles in 0.7.6
        require(buy_amt > 0);
        require(buy_gem != ERC20(address(0))); /// @dev Note, modified from: require(buy_gem != ERC20(0x0)) which compiles in 0.7.6
        require(pay_gem != buy_gem);

        OfferInfo memory info;
        info.pay_amt = pay_amt;
        info.pay_gem = pay_gem;
        info.buy_amt = buy_amt;
        info.buy_gem = buy_gem;
        info.recipient = recipient;
        info.owner = owner;
        info.timestamp = uint64(block.timestamp);
        id = _next_id();
        offers[id] = info;

        require(pay_gem.transferFrom(msg.sender, address(this), pay_amt));

        emit LogItemUpdate(id);

        /// emit LogMake(
        ///     bytes32(id),
        ///     keccak256(abi.encodePacked(pay_gem, buy_gem)),
        ///     msg.sender,
        ///     pay_gem,
        ///     buy_gem,
        ///     uint128(pay_amt),
        ///     uint128(buy_amt),
        ///     uint64(block.timestamp)
        /// );

        emit emitOffer(
            bytes32(id),
            keccak256(abi.encodePacked(pay_gem, buy_gem)),
            msg.sender,
            pay_gem,
            buy_gem,
            uint128(pay_amt),
            uint128(buy_amt)
        );
    }
```

Using this function offer can be created with zero addresses of owner and recipient and than in buy we have following statement:
```
            if (_offer.owner == address(0) && getRecipient(id) != address(0)) {
                require(
                    _offer.buy_gem.transferFrom(
                        msg.sender,
                        _offer.recipient,
                        mFee
                    ),
                    "Insufficient funds to cover fee"
                );
            } else {
                require(
Potential loss of tokens in The V2Migrator contract
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/V2Migrator.sol#L38-L74


# Vulnerability details

## Impact
The Vfunction migrate of the 2Migrator contract does not have any mechanisms in place to handle failed or partial migrations. If the mint function call fails, 
```
require(
            CErc20Interface(bathTokenV2).mint(amountWithdrawn) == 0,
            "migrate: MINT FAILED"
        );
```
the function will revert, but the deposited tokens will be stuck in the bathTokenV2 contract, and the user will have lost the tokens they tried to migrate. 



the developpers implemented a require that test test this case 
```
 require(
            IERC20(bathTokenV2).balanceOf(address(this)) == 0,
            "migrate: BATH TOKENS V2 STUCK IN THE CONTRACT"
        );
```
but that require will not be responsible to returning transfered funds to the caller

## Recommended Mitigation Steps
Consider implementing a mechanism to handle failed or partial migrations, you can modify the code to use a try/catch block and handle the exception by transferring the deposited tokens back to the user.

```
 try CErc20Interface(bathTokenV2).mint(amountWithdrawn) returns (uint256 error) {
        require(error == 0, "migrate: MINT FAILED");
        // ...
    } catch {
        // If mint fails, transfer deposited tokens back to the user
        SafeERC20.safeTransfer(
            bathTokenV1,
            msg.sender,
            bathTokenV1.balanceOf(address(this))
        );
        emit MigrateFailed(msg.sender, address(bathTokenV1), amountWithdrawn);
        return;
    }
```
BathBuddy Staking Contract Fails to Update Staking Parameters Correctly, Allowing Exploitation of Rewards System
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/periphery/BathBuddy.sol#L247-L257


# Vulnerability details

## Impact

The BathBuddy staking contract is a modified version of the Synthetix staking contract. Here, the user's Bath Token balance is used to calculate rewards. However, the contract doesn't update its staking parameters correctly. The Synthetix contract uses the updateReward modifier to update staking parameters whenever a stake, unstake, or claim event happens. In the BathBuddy contract, since there are no direct staking or unstaking events, the updateReward function doesn't update staking attributes correctly. It only updates when the getReward function is called and not when someone mints BathToken (cToken) or redeems BathToken (cToken).

A hacker can exploit the system by minting, claiming, and redeeming Bath tokens without waiting for any period of time.

Here is the updateReward function/modifier which needs to called when user's bath Token balance changes but this contract fails to do so. 

```solidity
// Rewards set here
    modifier updateReward(address account, address token) {
        rewardsPerTokensStored[token] = rewardPerToken(token);
        lastUpdateTime[token] = lastTimeRewardApplicable(token);
        if (account != address(0)) {
            tokenRewards[token][account] = earned(account, token);
            userRewardsPerTokenPaid[token][account] = rewardsPerTokensStored[
                token
            ];
        }
        _;
    }
```


## Proof of Concept

Here is a simple proof of concept. In this test, the reward duration is set to 365 days to match existing tests.

1. Alice mints 1000 Bath tokens at time T0.
2. Move the evm forward by 365 days.
3. The hacker mints 1000 Bath tokens at T0 + 365 days, claims rewards, and redeems Bath tokens in the same transaction.
4. Alice claims rewards at time T0 + 365 days.
5. Alice and the hacker get the same amount of reward.

```js
 describe("POC", async function () {
    it.only("should give hacker same reward as alice ", async function () {
      const {
        bathHouse,
        buddy,
        testCoin,
        testStableCoin,
        bathTEST,
        otherAccount,
        alice,
        hacker,
      } = await loadFixture(buddySetupFixture);

      // alice approves and mints cToken and wait for one year to pass.
      await testCoin
        .connect(alice)
        .approve(bathTEST.address, parseUnits("1000"));

      await bathTEST.connect(alice).mint(parseUnits("1000"));

      // skip a year
      await time.increaseTo((await time.latest()) + 365 * 24 * 60 * 60);

      // hacker approves and mints cToken after one year.
      await testCoin
        .connect(hacker)
        .approve(bathTEST.address, parseUnits("1000"));
      await bathTEST.connect(hacker).mint(parseUnits("1000"));

      // both alice and hacker claim rewards
      await bathHouse
        .connect(hacker)
        .claimRe
name-reused
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/V2Migrator.sol#L8-L14
https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/interfaces/IBathToken.sol#L7


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

IBathToken has been reused. First under Interface/IBathToken.sol and another one under V2Migrator.sol

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

## Tools Used

Slither

## Recommended Mitigation Steps
Rename the contract or remove it. And replace it with the correct one.
Full staking rewards can be claimed for freshly minted BathTokens
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/periphery/BathBuddy.sol#L139-L155
https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/periphery/BathBuddy.sol#L247-L257


# Vulnerability details

## Impact

BathTokens (CToken/CErc20 fork, staking tokens) can be minted (using `mint`) "behind the back" of the `BathBuddy` and `Comptroller` (out of scope) contracts, i.e. minting BathTokens does not update the reward accounting in the aforementioned contracts. In case of the BathBuddy contract it's the [updateReward](https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/periphery/BathBuddy.sol#L247-L257) modifier that updates the critical storage mapping `userRewardsPerTokenPaid[token][account]` that could avoid this bug.  

As a result, when a user mints BathToken for the first time (previous balance 0), he can claim the full staking rewards as if he already had the tokens since the creation of the corresponding `BathBuddy` contract.  
A potential attacker could flash-loan the required tokens to mint BathTokens, then claim the rewards and drain the contracts, afterwards redeem the BathTokens again and repay the flash-loan.

## Proof of Concept

The following PoC demonstrates the mint vulnerability outlined above, please apply the diff to your `bath-buddy.ts` test and run it.
```diff
diff --git a/test/hardhat-tests/bath-buddy.ts b/test/hardhat-tests/bath-buddy.ts
index 8616871..885bd24 100644
--- a/test/hardhat-tests/bath-buddy.ts
+++ b/test/hardhat-tests/bath-buddy.ts
@@ -13,6 +13,7 @@ describe("RubiconV2 rewards system", function () {
     "function balanceOf(address) external view returns(uint256)",
     "function admin() external view returns(address)",
     "function mint(uint256) external returns(uint256)",
+    "function transfer(address, uint256) external returns (bool)",
   ];
   const BUDDY_ABI = [
     "function setRewardsDuration(uint256,address) external",
@@ -22,7 +23,7 @@ describe("RubiconV2 rewards system", function () {

   async function buddySetupFixture() {
     // Contracts are deployed using the first signer/account by default
-    const [owner, otherAccount] = await ethers.getSigners();
+    const [owner, otherAccount, userAlice] = await ethers.getSigners();

     // Use Compound Fork to spawn some cTokens...
     // Note: In prod COMP uses Unitroller for Comptroller storage and upgradeability*
@@ -143,6 +144,7 @@ describe("RubiconV2 rewards system", function () {
       testRewardCoin,
       buddy,
       bathHouse,
+      userAlice,
     };
   }

@@ -157,30 +159,38 @@ describe("RubiconV2 rewards system", function () {
         buddy,
         bathHouse,
         comptroller,
+        userAlice,
       } = await loadFixture(buddySetupFixture);

       expect(await comptroller.getCompAddress()).to.be.equal(
         testRewardCoin.address
       );
-
-      await
Market can be inflated with unkillable offers
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/main/contracts/RubiconMarket.sol#L511-L517


# Vulnerability details

## Impact
The market can be inflated with offers which can not be killed/canceled or bought. Currently, when a new offer is created owner and recipient address are coming from arguments previously setted from msg.sender [516-517](https://github.com/code-423n4/2023-04-rubicon/blob/main/contracts/RubiconMarket.sol#L516-L517) . Malicious user can create many offers where owner and recipient address are `address(0)` like make this offers to can not be killed/canceled or bought.

## Proof of Concept
Paste this test in ProtocolDeployment.ts

```
    it.only(Offers", async function () {
        const { testStableCoin, testCoin, rubiconMarket, owner, otherAccount } =
          await loadFixture(deployRubiconProtocolFixture);

          console.log('start:');

        await rubiconMarket.functions[
          "offer(uint256,address,uint256,address,address,address)"
        ](
          parseUnits("0.9", 6),
          testStableCoin.address,
          parseUnits("1"),
          testCoin.address,
          ethers.constants.AddressZero, // owner
          ethers.constants.AddressZero, // recipient
          { from: owner.address }
        );

        // my id is 5 here
        expect(await rubiconMarket.getOwner(5)).to.equal(ethers.constants.AddressZero );
        expect(await rubiconMarket.getRecipient(5)).to.equal(
          ethers.constants.AddressZero 
        );

        await rubiconMarket.functions[
          "kill(bytes32)"
        ](
          ethers.utils.hexZeroPad(ethers.utils.hexlify(5), 32),
          { from: owner.address }
        );
        
      });
```

## Tools Used
Manual review, Hardhat test

## Recommended Mitigation Steps
Add one more condition when the user create a new offer.

```
require(owner != address(0) && recipient != address, "Zero address for owner and recipient!");
```
_borrowLimit will return wrong iteration count
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/utilities/poolsUtility/Position.sol#L526-L583


# Vulnerability details

## Impact
[`_borrowLimit()`](https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/utilities/poolsUtility/Position.sol#L526-L583) is assumed to return `limit` variable, which is the number of iteration (lend=>borrow=>swap) needs to be performed to reach leverage.

It assumes that multiplying the `_assetAmount` with `_collateralFactor` will give us the amount we can borrow against `_assetAmount`
```
        while (_assetAmount <= _desiredAmount) {
            if (_limit == 0) {
		// if collateral already provided
                if (_minted != 0) {
                    uint256 _max = _maxBorrow(_bathToken);

		    // take into account previous collateral
                    _loopBorrowed = wmul(_assetAmount, _collateralFactor).add(
                        _max
                    );
                } else {
                    _loopBorrowed = wmul(_assetAmount, _collateralFactor);
                }
            } else {
                _loopBorrowed = wmul(_loopBorrowed, _collateralFactor);
            }
```
This is correct, but the thing is compound's comptroller(bathHouseV2 here) deals everything in USD price
https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/compound-v2-fork/Comptroller.sol#L896-L987
Here, on line 927, it fetches the USD price of the underlying and then multiplies it with collateralFactor. Which is different than how it is calculated in position.sol

In position.sol => borrowed = amout * collateralFactor
In comptroller => borrowed = amount * USDPrice * collateralFactor

So, the number of iterations would be wrong, which leads to an incorrect `lend=>borrow=>swap` loop, which may result in unexpected outcomes (i.e user may get liquidated)


## Tools Used
Manual Review

## Recommended Mitigation Steps
Convert the amount into USD first and then calculate the borrowAmount
Anybody can drain entire token reward pot
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/periphery/BathBuddy.sol#L139-L155
https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/periphery/BathBuddy.sol#L251
https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/periphery/BathBuddy.sol#L176


# Vulnerability details

## Impact
Any user with a small amount of bathToken can steal all additional token rewards.

## Proof of Concept

BathBuddy rewards distribution rely on BathToken balances to reward users. It doesn't have an internal mechanism to keep track of each user balances. 

```solidity 
    function earned(
        address account,
        address token
    ) public view override returns (uint256) {
        require(friendshipStarted, "I have not started a bathToken friendship");


        return
            IERC20(myBathTokenBuddy) // Care with this?
                .balanceOf(account)
                .mul(
                    rewardPerToken(token).sub(
                        userRewardsPerTokenPaid[token][account]
                    )
                )
                .div(1e18)
                .add(tokenRewards[token][account]);
    }
```

Rewards are calculated using `IERC20(myBathTokenBuddy).balanceOf(account)`, any bathToken holder can transfer it's bathToken balance to a different wallet he controls and claim rewards again. He can do this again and again until there are no rewards left. 

1.Let's suppose Alice deposit 1000 DAI and in return she will get 1000 bathDai.
2.Other users deposit too and totalSupply of bathDai is 10000 (so Alice has 10% of total supply).
3.onlyOwner set `rewardsDuration` to 10 days, send 100 rewardTokenX and calls `notifyRewardAmount`
4.After first day Alice wants to claim her rewards and calls `BathHouseV2.getReward()` 
She receive 10% * (1/10 days) * 100 = 1 rewardTokenX.
5.Alice transfer her bathDai to a different wallet she control.
6.Repeat step 4-5 until there are no rewards left. Or she can [flash mint](https://docs.makerdao.com/smart-contract-modules/flash-mint-module) a huge amount of DAI (respectively bathDai) and get entire reward pool in a single transaction.

## Tools Used
Manual review

## Recommended Mitigation Steps
Consider only [staked](https://solidity-by-example.org/defi/staking-rewards/) bathTokens to accumulate additional rewards.