xanoutas/solidity-security-findings huggingface dataset:
`SimpleMarket.offer` doesn't validate owner and recipient
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
When owner and the recipient of an offer are address(0), the fund will be locked in the market contract, and `buy` method will fail, and it can be abused to malicious DOS attacks.

## Proof of Concept

```solidity
        info.recipient = recipient;
        info.owner = owner;
```

`SimpleMarket.offer` doesn't validate owner and recipient. So an offer with address(0) for both owner and recipient can be accpeted.

In this case, The `pay_amt` of this offer will be locked in the Market contract and there is no way to return.

`SimpleMarket.buy` and related methods such as `RubiconMarket.buy` will revert for this offer.

This offer can be the best offer, and there is no way to fulfill this offer, so this will block all trading operations via `buyAllAmount` and `sellAllAmount`.


## Tools Used
Manual Review

## Recommended Mitigation Steps
We should validate `owner` != 0 in `SimpleMarket.offer`
BathBuddy.getReward should update rewards from all reward tokens
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details


## Impact
`BathBuddy.getReward` only updates reward states for a token, and the reward amount will be wrong.

## Proof of Concept
The reward amount from BathBuddy is calculated by total bath token supply, so when the total supply of a bath token is changed, we need to update reward states for all reward tokens in order to get correct rewards. 

The reward states of all tokens are not updated simultaneously. `BathHouseV2.claimRewards` are updates states for all tokens(actually, it only updates multiple tokens, but the comment says it will update all tokens, so we can safely assume this will update all reward tokens), but `BathHouseV2` also supports `getReward` method, which updates only one reward token's state.

Updating reward states of all tokens doesn't guarantee that the reward amount is correct, but it is always better to update all tokens than only one token.

For detailed POC, let us assume these:

 - There are two reward tokens - tokenA and tokenB, and 10 tokens are given for blockB and blockC both, 5 tokens for each block.
 - We will take 3 consecutive blocks - blockA, blockB and blockC. 
 - The time between these blocks are the same for simplicity
 - The total supply of a bath token is 100, 500 and 1000 in blockA, blockB, and blockC respectively.
 - Alice and has 100 bath tokens respectively during this period.
 - Only Bob gets rewards during this period.

If Bob gets rewards in blockA, blockB, blockC for tokenA, and in blockA and blockC for tokenB, we can calculate exact rewards amount of Alice.

For tokenA, Alice will get 5 * 100 / 500 in blockB and 5 * 100 / 1000 in blockC. So she will get 1.5 tokens and this is correct.
But for tokenB, she will get 10 * 100 / 1000 = 10 tokens in blockC and this is not correct.

As we can see from this POC, we should update reward states for all tokens simultaneously.


## Tools Used
Manual Review

## Recommended Mitigation Steps
We should update reward states for all tokens simultaneously in `getReward` and `notifyRewardAmount` of `BathBuddy` contract.
FeeWrapper fails to handle ETH payment refunds
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The FeeWrapper contract can be used to wrap calls that include ETH payments. This is handled by the `_rubicallPayable` function:

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

```solidity
76:     function _rubicallPayable(
77:         CallParams memory _params
78:     ) internal returns (bytes memory) {
79:         // charge fee from feeParams
80:         uint256 _msgValue = _chargeFeePayable(_params.feeParams);
81: 
82:         (bool _OK, bytes memory _data) = _params.target.call{value: _msgValue}(
83:             bytes.concat(_params.selector, _params.args)
84:         );
85: 
86:         require(_OK, "low-level call to the router failed");
87: 
88:         return _data;
89:     }
```

As we can see in the previous snippet, the implementation will forward the ETH payment (minus fees) to the target contract. If the target contract ends up using less ETH than the sent amount, then the usual approach would be to refund the remaining ETH back to the caller, which is a normal and common operation.

If this is the case, then the wrapped call will fail as the FeeWrapper doesn't implement the `receive` or `fallback` function to allow ETH payments. Even though there is no loss of funds as the transaction is reverted, the issue will prevent users from wrapping calls to target contracts that may refund ETH as part of their normal behavior.

As a potential real example, we can the explore the `buyAllAmountWithETH` function present in the `RubiconRouter` contract:

https://github.com/RubiconDeFi/rubi-protocol-v2/blob/master/contracts/utilities/RubiconRouter.sol#L379-L418

```solidity
379:     function buyAllAmountWithETH(
380:         ERC20 buy_gem,
381:         uint256 buy_amt,
382:         uint256 max_fill_amount
383:     ) external payable beGoneReentrantScum returns (uint256 fill) {
384:         address _weth = address(wethAddress);
385:         uint256 _before = ERC20(_weth).balanceOf(address(this));
386:         require(
387:             msg.value == max_fill_amount,
388:             "must send as much ETH as max_fill_amount"
389:         );
390:         IWETH(wethAddress).deposit{value: max_fill_amount}(); // Pay with native ETH -> WETH
391: 
392:         if (
393:             IWETH(wethAddress).allowance(address(this), RubiconMarketAddress) <
394:             max_fill_amount
395:         ) {
396:             approveAssetOnMarket(wethAddress);
397:         }
398: 
399:         // An amount in WETH
400:         fill = RubiconMarket(RubiconMarketAddress).buyAllAmount(
401:             buy_gem,
402:             buy_amt,
403:             ERC20(wethAddress),
404:             max_fill_amount
405:         );
406:         IERC20(buy_gem).safeTransfer(msg.sender, fill);
407: 
408:         uint256 _after = ERC20(_weth).balanceOf(address(this));
409:         uin
FeeWrapper allows calls to functions in the RubiconRouter that cannot be cancelled or will revert
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
User's will be impacted as they will be unable to cancel offers. This will cause third party integrators reputational risk as they will not be able to use the functionality as intended.

## Proof of Concept
In the example contract "Test3rdPartyProtocol.sol" we can see that the main intention is for the FeeWrapper to call to RubiconRouter contract and handle fees for Thirdparty integrators.
The RubiconRouter contract keeps track of orders using msg.sender as listed in the code in the functions offerForEth and OfferWithEth.
https://github.com/code-423n4/2023-04-rubicon/blob/main/contracts/utilities/RubiconRouter.sol#L548-L588
```
        // Track the user's order so they can cancel it
        userNativeAssetOrders[msg.sender].push(id);

```
Once an offer has been successfully created, only the msg.sender can cancel an offer as shown in the code below:

```
    // Cancel an offer made in WETH
    function cancelForETH(
        uint256 id
    ) external beGoneReentrantScum returns (bool outcome) {
        uint256 indexOrFail = getIndexFromElement(
            id,
            userNativeAssetOrders[msg.sender]
        );
        /// @dev Verify that the offer the user is trying to cancel is their own
        require(
            userNativeAssetOrders[msg.sender][indexOrFail] == id,
            "You did not provide an Id for an offer you own"
        );
```
In the above case a user would never be able to cancel their offer because the call to cancel would need to originate from the FeeWrapper as the tracked owner, and then would try to repay the msg.sender using the "call" method, however there is no fallback function in FeeWrapper so it will revert every time.
In cancelForETH:
https://github.com/code-423n4/2023-04-rubicon/blob/main/contracts/utilities/RubiconRouter.sol#L591-L617
```
        IWETH(wethAddress).withdraw(pay_amt);
        // msg.sender.transfer(pay_amt);
        (bool success, ) = msg.sender.call{value: pay_amt}("");
        require(success, "Transfer failed.");
```
For the same reason any integrating parties would need to be made aware of certain functions that would possibly revert, even though successful when called in the RubiconRouter as they all implement the "call" method to the msg.sender.
These functions are:
buyAllAmountWithETH
buyAllAmountForETH
swapForETH
withdrawForETH

Example code snippet:
https://github.com/code-423n4/2023-04-rubicon/blob/main/contracts/utilities/RubiconRouter.sol#L379-L418
as it also implements the "call" method to return any unspent ETH.
```
        // Return unspent coins to sender
        if (delta > 0) {
            IWETH(wethAddress).withdraw(delta);
            // msg.sender.transfer(delta);
            (bool success, ) = msg.sender.call{value: delta}("");
            require(success, "Transfer failed.");
        }
```

## Tools Used
Manual Inspecti
IERC20 approve should be approved to zero first 
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/utilities/FeeWrapper.sol#L93-L106
https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/utilities/FeeWrapper.sol#L60-L73
https://github.com/code-423n4/2023-04-rubicon/blob/511636d889742296a54392875a35e4c0c4727bb7/contracts/utilities/FeeWrapper.sol#L48-L56


# Vulnerability details

## Impact
attacker can front run that Ierc20 approve in charge fee() and losing fund from protocol

## Proof of Concept
if feeamount and totalamount are changed to lower amount than previous ones , attacker pay high gas fee and front run that 

## Tools Used
manual review 
## Recommended Mitigation Steps
 IERC20(_feeToken).approve(_target, 0);
 IERC20(_feeToken).approve(_target, (_totalAmount - _feeAmount));
Unused Return 
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#L475


# Vulnerability details

## Impact
Unused Return

## 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/poolsUtility/Position.sol#L475

## Tools Used

Slither

## Recommended Mitigation Steps
Ensure that all the return values of the function calls are used.
FeeWrapper will be incompatible with most integrations 
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The FeeWrapper contract can be used to wrap calls to arbitrary contracts to include a fee for a third party. The contract at a high level works as follows:

1. User calculates fee for amount
2. User calls FeeWrapper with the proper params and amounts (total and fee)
3. FeeWrapper takes funds from user
4. FeeWrapper sends fee amount to fee recipient
5. FeeWrapper calls the target contract, forwarding the amount. In the case of an ERC20 it approves the spend, in case of ETH it sends the value as call value.

The general approach sounds ok, however there is an important detail that will conflict with most contract implementations. In point 5 the caller to the target is the FeeWrapper itself, not the original user. This means that in the context of the target contract, `msg.sender` is the FeeWrapper contract. Most smart contract implementations will use the caller of the function as the beneficiary of the actions. For example, a swap action in a decentralized exchange will return the swapped assets back to the caller. A deposit on a lending protocol will associate the collateral with the caller of the function, and only the caller will be able to eventually redeem that collateral.

The potential impact of the issue will depend on the case, but it certainly can be high for many cases. We will explore a few situations of loss of funds for the `RubiconRouter` contract in the next section.

## Proof of Concept

There are many functions present in the `RubiconRouter` that utilize the `msg.sender`. In this section we will explore just two to demonstrate the issue. The first one is the `maxBuyAllAmount` function:

https://github.com/RubiconDeFi/rubi-protocol-v2/blob/master/contracts/utilities/RubiconRouter.sol#L338-L356

```solidity
338:     function maxBuyAllAmount(
339:         ERC20 buy_gem,
340:         ERC20 pay_gem,
341:         uint256 max_fill_amount
342:     ) external returns (uint256 fill) {
343:         //swaps msg.sender's entire balance in the trade
344:         uint256 maxAmount = _calcAmountAfterFee(
345:             ERC20(buy_gem).balanceOf(msg.sender)
346:         );
347: 
348:         IERC20(buy_gem).safeTransferFrom(msg.sender, address(this), maxAmount);
349:         fill = RubiconMarket(RubiconMarketAddress).buyAllAmount(
350:             buy_gem,
351:             maxAmount,
352:             pay_gem,
353:             max_fill_amount
354:         );
355:         ERC20(buy_gem).transfer(msg.sender, fill);
356:     }
```

As we can see in the code snippet, the function will execute the action using the RubiconMarket and transfer the output amount back to the caller (line 355). In the context of the FeeWrapper call, the resulting amount will be sent to the FeeWrapper contract itself and not the original caller, which will cause a loss of funds.

The other case is the `offerWithETH` function:

h
Unused amount of ERC20 token by target contract will be lost in `FeeWrapper`
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The main purpose of the FeeWrapper contract is to wrap a call to an arbitrary target contract that potentially includes an ETH or ERC20 payment. A fee portion of that payment is routed to a fee recipient while the rest of it is forward to the wrapped call itself.

In the case of an ERC20 payment, the total amount of the payment is first moved from the caller to the FeeWrapper contract. Then the fee amount is transferred to the fee recipient, and the rest is approved to the target contract so it can call pull the payment. This can be seen in the `_chargeFee` function:

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

```solidity
093:     function _chargeFee(FeeParams memory _feeParams, address _target) internal {
094:         address _feeToken = _feeParams.feeToken;
095:         uint256 _totalAmount = _feeParams.totalAmount;
096:         uint256 _feeAmount = _feeParams.feeAmount;
097:         address _feeTo = _feeParams.feeTo;
098: 
099:         // transfer total amount to the FeeWrapper
100:         IERC20(_feeToken).transferFrom(msg.sender, address(this), _totalAmount);
101:         // transfer fee to the 3rd party protocol
102:         IERC20(_feeToken).transfer(_feeTo, _feeAmount);
103: 
104:         // approve tokens to the `_target`
105:         IERC20(_feeToken).approve(_target, (_totalAmount - _feeAmount));
106:     }
```

The issue is raised when the target contract doesn't use the total amount of the funds, which is a common scenario. Different types of contracts run some calculations that depend on the current state when the transaction is finally executed and pull a payment amount based on that, such as AMMs where swap operations may include a slippage factor. 

Under these circumstances, the unused amount will be left in the FeeWrapper contract, since the total amount of the funds were initially moved to this contract. These funds will not be refunded to the user and the user will lose them.

## Proof of Concept

In the following example we create a demonstration contract `FeeWrapperTarget` which includes a function named `demoERC20` that only pulls half of the initial amount. Alice executes the call to the FeeWrapper `rubicall` function, including a payment of 1e18 and a fee of 0.1e18. The total amount of the funds are pulled out from Alice balance, the target contract will end up having half of the amount and the other half will be lost in the FeeWrapper contract.

Note: the snippet shows only the relevant code for the test. Full test file can be found [here](https://gist.github.com/romeroadrian/f3b7d6f9ab043340de7deb67a9c515e5).

```solidity
function test_FeeWrapper_UnusedERC20IsLost() public {
    MockToken token = new MockToken("Mock", "MOCK");
    FeeWrapperTarget target = new FeeWrapperTarget(token);

    uint256 amount = 1
Orders created in RubiconMarket V1 will send dust to address(0)
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The migration of the RubiconMarket contract from v1 to v2 involves some critical changes in the `OfferInfo` struct that holds order information. The protocol handles this change with a special treatment in the new v2 code. However, there's a missing edge case that will potentially lead to loss of funds.

After a trade is executed, orders that are left with an amount (`pay_amt`) below a certain configured dust limit (`_dust` mapping) are considered dust orders and will be canceled. This behavior is present in the `_buys` internal function with is part of the `buy` process:

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

```solidity
1183:     function _buys(uint256 id, uint256 amount) internal returns (bool) {
1184:         require(buyEnabled);
1185:         if (amount == offers[id].pay_amt) {
1186:             if (isOfferSorted(id)) {
1187:                 //offers[id] must be removed from sorted list because all of it is bought
1188:                 _unsort(id);
1189:             } else {
1190:                 _hide(id);
1191:             }
1192:         }
1193: 
1194:         require(super.buy(id, amount));
1195: 
1196:         // If offer has become dust during buy, we cancel it
1197:         if (
1198:             isActive(id) &&
1199:             offers[id].pay_amt < _dust[address(offers[id].pay_gem)]
1200:         ) {
1201:             dustId = id; //enable current msg.sender to call cancel(id)
1202:             cancel(id);
1203:         }
1204:         return true;
1205:     }
```

When an order is canceled, the remaining amount left in the offer is returned to the creator. The `cancel` function has separate cases for offers created in v2 and offers coming from v1. If owner is empty and caller is then recipient then it is an offer from v1 and the funds are returned to the `recipient` field, otherwise it is an order from v2 and the funds are returned to the `owner` field. This can be seen in the next snippet in lines 459-461:

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

```solidity
452:     function cancel(
453:         uint256 id
454:     ) public virtual can_cancel(id) synchronized returns (bool success) {
455:         OfferInfo memory _offer = offers[id];
456:         delete offers[id];
457: 
458:         /// @dev V1 orders after V2 upgrade will point to address(0) in owner
459:         _offer.owner == address(0) && msg.sender == _offer.recipient
460:             ? require(_offer.pay_gem.transfer(_offer.recipient, _offer.pay_amt))
461:             : require(_offer.pay_gem.transfer(_offer.owner, _offer.pay_amt));
462: 
463:         emit LogItemUpdate(id);
464:         /// emit LogKill(
465:         ///     bytes32(id),
466:         ///     keccak256(abi.encodePacked(_offer.pay_gem, _offer.buy_g
Reward accounting is incorrect in BathBuddy contract
Audit: 2023-04-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The `BathBuddy` contracts implements rewards for liquidity providers (holders of `BathToken`). The contract is modeled after the famous Synthetix staking contract, with some tweaks to support rewards for multiple tokens at the same time.

The implementation overall is correct, however there is a critical difference with the Synthetix contract that is ignored in the `BathBuddy` contract. In the Synthetix implementation the main actions related to rewards accounting are the `stake` and `withdraw` actions, these trigger the `updateReward` modifier to ensure correct reward accounting. Staked tokens cannot be transferred, as these are held in the staking contract. In the BathBuddy implementation things are very different as there is no staking, rewards are intended to be distributed directly to holders of the BathToken without any need of staking the tokens in the contract. This means that, as there is no "staking" action in the BathBuddy implementation (i.e. depositing funds in the contract), rewards fail to be correctly accounted whenever BathToken are minted, burned or transferred between different accounts.

These are two critical places in the code where the BathBuddy contract uses the state from the BathToken, but fails to be triggered whenever the state in the BathToken is modified. The first is the `rewardPerToken` that calculates the amount of rewards that should correspond to one unit of the BathToken token, this is logically dependent on the total supply of the token (lines 124 and 133):

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

```solidity
121:     function rewardPerToken(address token) public view returns (uint256) {
122:         require(friendshipStarted, "I have not started a bathToken friendship");
123: 
124:         if (IERC20(myBathTokenBuddy).totalSupply() == 0) {
125:             return rewardsPerTokensStored[token];
126:         }
127:         return
128:             rewardsPerTokensStored[token].add(
129:                 lastTimeRewardApplicable(token)
130:                     .sub(lastUpdateTime[token])
131:                     .mul(rewardRates[token])
132:                     .mul(1e18)
133:                     .div(IERC20(myBathTokenBuddy).totalSupply())
134:             );
135:     }
```

The other place is in the `earned` function which uses the BathToken `balanceOf` function of an account (lines 146-147):

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

```solidity
139:     function earned(
140:         address account,
141:         address token
142:     ) public view override returns (uint256) {
143:         require(friendshipStarted, "I have not started a bathToken friendship");
144: 
145:         return
146:             IERC20(myBathTokenBuddy) // Care with this?