xanoutas/solidity-security-findings huggingface dataset:
An offer created where the owner & recipient are the zero address will lead to funds being lost.
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
If a user mistakenly creates an offer where the owner & recipient are the zero address the funds can be locked in the contract or burned if the token is a non standard ERC20 token that allows transfers to the zero address. The buy function will either revert because it will try to transfer tokens to address zero or will burn them. The cancel function will also revert or burn the tokens because it's using _offer.owner (which is address zero) to transfer the tokens back to.

## Tools Used

## Recommended Mitigation Steps
Have a require check when creating an offer that ensures that recipient is not the zero address.
arbitrary-send-eth : Dangerous calls to send eth to arbitrary user
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/utilities/FeeWrapper.sol#L118


# Vulnerability details

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

Arbitrary Eth Send : Functions that send Ether to arbitrary destinations.
2 instances - 1 file (./contracts/utilities/FeeWraper.sol)


Both links refers to low-level opcode CALL. 

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

https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/utilities/FeeWrapper.sol#L118

https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/utilities/FeeWrapper.sol#L82


## Tools Used
Slither, SmartCheck, Solidity Static analysis.

## Recommended Mitigation Steps
 - Validate inputs and implement access control to set FeeTo.
Token reward can be stolen
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/periphery/BathBuddy.sol#L150
https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/BathHouseV2.sol#L131-L133
https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/BathHouseV2.sol#L115-L128


# Vulnerability details


## Impact
The BathBuddy contract assumes that all ERC20 tokens (referred to as "myBathTokenBuddy" in the contract) are always staked. The staking reward should be distributed based on the time-weighted share of each user of the total myBathTokenBuddy supply (see: [math explainer](https://twitter.com/ProgrammerSmart/status/1545637246543736833?t=dzVIFWPvIZJnOUqxSUSoVw&s=33), [video](https://www.youtube.com/watch?v=LWWsjw3cgDk). 

However, this is not the case:
The BathBuddy contract does not update the reward for the user before the transfer is executed. Instead, the BathBuddy contract updates the reward for the user only when the user calls the `updateReward` function. This means that the user can transfer the tokens to another address, and the reward will be lost. Additionally, an attacker can call the `updateReward` function once, move their tokens to another address, and call the `updateReward` function again. This will result in the attacker receiving the reward multiple times. A skilled attacker could potentially take the vast majority of the reward pool by using a flashloan.

##  Proof of Concept
1)The user calls the updateReward function in the contract.
2)The user transfers the funds to another address.
3)The simplified formula for calculating the share of each user is: [balanceOfUser * (allTimeRewardPerToken - rewardPerTokenPaidOut)](https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/periphery/BathBuddy.sol#L146-L152)
In the Solidity code, rewardPerTokenPaidOut is accessed via this mapping:

```solidity
userRewardsPerTokenPaid[token][account]
```
As the user just moved their funds to their new address, this account will have no rewards paid out, so 
`rewardPerTokenPaidOut` = 0 
`allTimeRewardPerToken` && `balanceOfUser` >> 0
The user can claim a share of the balance even when they just got transferred some tokens.

## Tools Used
Manual Review

## Recommended Mitigation Steps
This issue is equivalent to [previous issues](https://github.com/code-423n4/2022-05-rubicon-findings/issues/109).

One solution is similar:

The rewards must be updated each time the myBathTokenBuddy tokens are transferred between addresses.
When the current contract does not allow this kind of behavior, the developers can create a wrapper contract, like a Synthetix rewards contract, in which the tokens can be staked and that can distribute the token rewards.
Lack of input validation
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The [constructor](https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/V2Migrator.sol#L30-L35) function on the bathTokensV2 contract assumes that the input arrays bathTokensV1 and bathTokensV2 have the same length, and no validation is done to ensure that this is the case. This could lead to unexpected behavior or even vulnerabilities. Consider adding input validation to ensure that the input arrays are of equal length.

## Proof of Concept
the constructor function assumes that both input arrays have two elements each, but an attacker provides input arrays with one element each. This would result in an out-of-bounds error when accessing the second element of the shorter array


## Recommended Mitigation Steps

A check should be implemented on length of bathTokensV1 and bathTokensV2
Consider adding a require(bathTokensV1.length == bathTokensV2.length, "V2Migrator: arrays must have equal length");
Staking rewards can be stolen from BathBuddy and Comptroller
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 transferred (using `transfer` or `transferFrom`) "behind the back" of the `BathBuddy` and `Comptroller` (out of scope) contracts, i.e. transferring 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, a malicious user can use multiple accounts/addresses and pass around the same BathTokens among them while getting the full staking rewards for each account. This way, an attacker can drain the reward tokens from both contracts.

## Proof of Concept

The following PoC demonstrates the multiple account 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..bc5141b 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, userBob, userCharlie] = await ethers.getSigners();

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

@@ -157,6 +161,9 @@ describe("RubiconV2 rewards system", function () {
         buddy,
         bathHouse,
         comptroller,
+        userAlice,
+        userBob,
+        userCharlie,
       } = await loadFixture(buddySetupFixture);

       expect(await comptroller.getCompAddress()).to.be.equal(
@@ -165,22 +172,67 @@ describe("RubiconV2 rewards system", function () {

       a
DDOS - Market could be blocked if the best offered is blacklisted by the bought token issuer
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/6ba583bf7a7c873eb4236b4b708e75724f200c99/contracts/RubiconMarket.sol#L1069


# Vulnerability details

## Impact
It's a common feature for some of the most popular tokens to have a blocklisting functionality. Tokens such as USDC and USDT can block transfers from and to specific addresses. This functionality is not rare and we have plenty of examples of hacker's address blocked by the owners of these tokens.
An offer can be created by anyone at rubicon, if the owner's offer (or recipient if this is a different address than the owner) is later blocklisted by the bought token (the token that the offer's owner will receive in exchange for the offered token) then every time a buyer is matched with that specific offered, the transaction will be reverted.
There is no functionality that protocol could use for remove this kind of offers, blocking part of the procotol functionality.

## Proof of Concept
```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "src/RubiconMarket.sol";

import "forge-std/Test.sol";

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {BlockableToken} from "weird-erc20/BlockList.sol";

contract DDOSBlocklistedTest is Test {
    address private owner;
    address private feeTo;

    RubiconMarket market;

    ERC20 private usdc;
    ERC20 private usdt;

    function setUp() public {
        owner = makeAddr("owner");
        feeTo = makeAddr("feeTo");

        // tokens have blocklist functionality
        usdc = ERC20(address(new BlockableToken(0)));
        usdt = ERC20(address(new BlockableToken(0)));

        vm.startPrank(owner);
        market = new RubiconMarket();
        market.initialize(feeTo);
        market.setFeeBPS(10);
        vm.stopPrank();
    }

    function test_blocklistDDOS() public {
        address seller1 = makeAddr("seller1");
        deal(address(usdc), seller1, 10 * 10e6);

        address seller2 = makeAddr("seller2");
        deal(address(usdc), seller2, 10 * 10e6);

        address buyer = makeAddr("buyer");
        deal(address(usdt), buyer, 10 * 10e6);

        // seller1 creates an offer: 5 usdc for 5 usdt
        vm.startPrank(seller1);
        usdc.approve(address(market), 5 * 10e6);
        market.offer(5 * 10e6, usdc, 5 * 10e6, usdt);
        vm.stopPrank();

        // seller2 creates an offer: 5 usdc for 5 usdt
        vm.startPrank(seller2);
        usdc.approve(address(market), 5 * 10e6);
        // I want to buy 5 usdt for 5 usdc
        market.offer(5 * 10e6, usdc, 5 * 10e6, usdt);
        vm.stopPrank();

        // seller1 is blocklisted by usdt
        BlockableToken(address(usdt)).block(seller1);

        // now the market for usdc-usdt is locked and no one can trade

        // buyer buys 5 usdc for 5 usdt
        vm.startPrank(buyer);
        // buyer wants to buy 5 usdc for 5 usdt
        usdt.approve(address(market), 5 * 10e6);
        // will always revert si
Risk of reentrancy attack
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/main/contracts/utilities/FeeWrapper.sol#L108-L122


# Vulnerability details

## Impact
the code can result in a reentrancy attack, which can drain funds from the contract.

## Proof of Concept
https://github.com/code-423n4/2023-04-rubicon/blob/main/contracts/utilities/FeeWrapper.sol#L108-L122
the `_chargeFeePayable` function, which transfers ETH to the third-party protocol before updating the `_msgValue` variable. This creates a reentrancy vulnerability, as the third-party protocol can call the `_chargeFeePayable` function again before it finishes executing, resulting in an unexpected state of the contract.

## Tools Used
VS code

## Recommended Mitigation Steps
Updating the code to transfer funds at the end of the function will prevent such an attack from happening.
no slippage protection for Positions
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/main/contracts/utilities/poolsUtility/Position.sol#L460-L472
https://github.com/code-423n4/2023-04-rubicon/blob/main/contracts/utilities/poolsUtility/Position.sol#L482-L510


# Vulnerability details

## Impact
In Position you open a leveraged position, either short or long. Both of these work in a way that you take a loan against compound and then use that to trade in RubiconMarket.

The issue is that there is no slippage protection in the trading, which can cause some issues:
1. The gas provided for the tx might be low and it might take a long time for the tx to be included during which the price might have moved disadvantageous to the trader.
2. MEV bots can spot this and sandwich trade the Position possibly cause a significant loss for the trader.

This is especially impactful when closing your position since you are closing at a specific market price which could be front run and drastically manipulated.

## Proof of Concept
Take `_marketSell` as an example:
https://github.com/code-423n4/2023-04-rubicon/blob/main/contracts/utilities/poolsUtility/Position.sol#L482-L510
```solidity
File: utilities/poolsUtility/Position.sol

            // here the current price for the RubiconMarket is asked
482:        uint256 _payAmount = rubiconMarket.getPayAmount(
483:            ERC20(_asset),
484:            ERC20(_quote),
485:            _minFill.add(_fee)
486:        );
487:        uint256 _assetBalance = IERC20(_asset).balanceOf(address(this));

...
            // and here that price is traded against
505:        rubiconMarket.sellAllAmount(
506:            ERC20(_asset),
507:            _payAmount,
508:            ERC20(_quote),
509:            _minFill
510:        );
```
The flow is very similar in `_marketBuy`.

Someone might have manipulated this price beforehand and make the Position trade on a disadvantageous price in the Market.

## Recommended Mitigation Steps
I recommend you introduce a slippage parameter when opening and closing a position. This is preferable to using the price oracle as there is no guarantee that the price oracle will give a accurate current price against RubiconMarket and the trader might want to trade specifically against spot prices on RubiconMarket.
`rewardsPerTokensStored` can be manipulated
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/main/contracts/periphery/BathBuddy.sol#L133


# Vulnerability details

## Description
When calculating the amount of tokens to hand out per held `bathToken` this formula is used:
$$rewardsPerTokensStored=\frac{(lastTimeRewardApplicable - lastUpdate) * 1e18}{totalSupply}$$
https://github.com/code-423n4/2023-04-rubicon/blob/main/contracts/periphery/BathBuddy.sol#L128-L134
```solidity
File: periphery/BathBuddy.sol

128:            rewardsPerTokensStored[token].add(
129:                lastTimeRewardApplicable(token)
130:                    .sub(lastUpdateTime[token])
131:                    .mul(rewardRates[token])
132:                    .mul(1e18)
133:                    .div(IERC20(myBathTokenBuddy).totalSupply())
134:            );
```

The issue is that it is divided by `myBathTokenBuddy.totalSupply()`. This incentivises users to hold `bathToken` (`cToken`) as late as possible. Specifically not when another user calls `BathHouseV2::getReward`/`claimRewards`. Since the lesser the totalSupply is, the greater the `rewardsPerTokensStored`.

## Impact
A user or users collectively can inflate the `rewardsPerTokensStored` by _not_ holding `bathTokens` until they want to claim rewards. Categorizing this as medium since it requires a collective effort to exploit. It will be mitigated by normal users just holding `bathTokens`.

## Proof of Concept
This PoC is very similar to the flash loan test because it is overly simplified. Since there are no other stakers. What it shows is that stakers are incentivised to keep the `rewardsPerTokensStored` by _not_ holding `bathTokens` which goes directly against what the protocol aims.

Test `BathBuddy.t.sol`:

```solidity
pragma solidity ^0.8.0;

import "../../contracts/compound-v2-fork/WhitePaperInterestRateModel.sol";
import "../../contracts/compound-v2-fork/CErc20Delegate.sol";
import "../../contracts/compound-v2-fork/CErc20.sol";
import "../../contracts/compound-v2-fork/Comptroller.sol";
import "../../contracts/periphery/TokenWithFaucet.sol";
import "../../contracts/periphery/BathBuddy.sol";
import "../../contracts/BathHouseV2.sol";
import "forge-std/Test.sol";

contract BathBuddyTest is Test {
  Comptroller comptroller;
  BathHouseV2 bathHouse;

  // test tokens
  TokenWithFaucet TEST;
  TokenWithFaucet BUDDY;

  CErc20 cTEST;

  BathBuddy bathBuddy;

  address alice = 0x0000000000000000000000000000000000000123;
  address alice2 = 0x0000000000000000000000000000000000000125;

  function setUp() public {
    comptroller = new Comptroller();

    // deploy test tokens
    TEST = new TokenWithFaucet(address(this), "Test", "TEST", 18);
    vm.label(address(TEST),"TEST");

    WhitePaperInterestRateModel irModel = new WhitePaperInterestRateModel(3e17, 2e16);
    CErc20Delegate bathTokenImplementation = new CErc20Delegate();

    bathHouse = new BathHouseV2();
    bathHouse.initialize(address(comptroller),address(this));
    bathHouse.createBathToken(
Missing Access Control Check in createBathToken Function
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/BathHouseV2.sol#L60


# Vulnerability details

## Impact
The missing access control check in the createBathToken function allows any user to create a new CErc20Delegator instance with arbitrary parameters, which could lead to potential exploits of the system. This could result in loss of funds or unauthorized access to sensitive data.

## Proof of Concept
Lets say amrah she is the attacker deploys a new contract that calls the createBathToken function with arbitrary parameters, including an implementation address that the amrah controls.

			contract amrah {
			    BathHouseV2 public bathHouse;
			    address public amrahImplementation;

			    constructor(BathHouseV2 _bathHouse, address _amrahImplementation) {
				bathHouse = _bathHouse;
				amrahImplementation = _amrahImplementation;
			    }

			    function attack(address _underlying) external {
				// call createBathToken with amrah-controlled implementation
				bathHouse.createBathToken(
				    _underlying,
				    InterestRateModel(address(0)),
				    0,
				    amrahImplementation,
				    ""
				);
			    }
			}


  amrah deploys a malicious implementation contract that overrides the borrow function of CErc20Delegator to steal funds from the contract.

		 contract MaliciousImplementation {
		    function borrow(uint256 borrowAmount) external returns (uint256) {
			// steal funds
			require(false, "Oops, funds stolen!");
		    }
		}
amrah sets the amrahImplementation variable in the amrah contract to the address of the malicious implementation contract.

			 MaliciousImplementation public maliciousImplementation;

			function setMaliciousImplementation(address _maliciousImplementation) external {
			    maliciousImplementation = MaliciousImplementation(_maliciousImplementation);
			    amrahImplementation = _maliciousImplementation;
			}

amrah calls the attack function in the amrah contract with the address of an ERC20 token as the _underlying parameter. This creates a new CErc20Delegator contract with the amrah malicious implementation.

			 function attack(address _underlying) external {
			    bathHouse.createBathToken(
				_underlying,
				InterestRateModel(address(0)),
				0,
				amrahImplementation,
				""
			    );
			}

amrah can now call the borrow function on the new CErc20Delegator contract to steal funds from it.
			  
function exploit() external {
			    CErc20Delegator cToken =     CErc20Delegator(bathHouse.getBathTokenFromAsset(address(token)));
			    cToken.borrow(1000); // steal 1000 tokens
			}

## Tools Used
VSCODE
## Recommended Mitigation Steps
To prevent this attack, the createBathToken function should include an access control check to ensure that only authorized users can create new CErc20Delegator instances with specified parameters. One possible way to implement this check is by using an access control framework like OpenZeppelin AccessContro
Canceling V1 offer in closed V2 market will refund to address(0)
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
When market is closed, anyone can call `cancel(id)` to cancel an offer. For offers made on market V1 and closed in market V2, this will result in the transfer of the `pay_gem` to `address(0)` resulting in a loss of fund for the owner of the offer.

## Proof of Concept
After migration of V1 to V2, offers made in a V1 market will have `offer.recipient = old_owner` and `offer.owner = address(0)`.

The code to decide whom to send the funds to is the following:

```solidity
    _offer.owner == address(0) && msg.sender == _offer.recipient
        ? require(_offer.pay_gem.transfer(_offer.recipient, _offer.pay_amt))
        : require(_offer.pay_gem.transfer(_offer.owner, _offer.pay_amt));
```

If V1 offer is canceled by an address that is not the V1 offer recipient then `msg.sender != _offer.recipient` and the `pay_gem` will be transferred to `_offer.owner` which is `address(0)`.

The modifier `can_cancel(id)` is the following:

```solidity
modifier can_cancel(uint256 id) virtual override {
    require(isActive(id));
    require(
        (msg.sender == getOwner(id)) ||
            isClosed() ||
            (msg.sender == getRecipient(id) && getOwner(id) == address(0))
    );
    _;
}
```

Meaning anyone can cancel anyone else's offer when market is closed. The docs of `ExpiringMarket` state:

```solidity
/// @notice Expiring market is a Simple Market with a market lifetime.
/// @dev When the close_time has been reached, offers can only be cancelled (offer and buy will throw).
```

And `RubiconMarket` inherits from `ExpiringMarket`. The `isClosed()` function currently constantly returns false, I expect this to be bug that will be corrected, enabling the abuse of the above mentioned issue.

## Tools Used

Manual review

## Recommended Mitigation Steps

Do not use msg.sender to check if we deal with a V1 offer in `cancel()` but check that `_offer.owner == address(0)` and refund to `_offer.recipient` in that case.