xanoutas/solidity-security-findings huggingface dataset:
Anyone can cancel and get a refund of orders placed through the `RubiconRouter`
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconRouter.sol#L440-L452


# Vulnerability details

## Impact
Users will lose funds associated with orders they've placed, to other users that cancel the order

## Proof of Concept
The `RubiconRouter` does no validation itself of whether a user should be able to cancel an order, and instead relies on `RubiconMarket` to do the checks:
```solidity
File: contracts/RubiconRouter.sol   #1

440       function cancelForETH(uint256 id) external returns (bool outcome) {
441           (uint256 pay_amt, ERC20 pay_gem, , ) = RubiconMarket(
442               RubiconMarketAddress
443           ).getOffer(id);
444           require(
445               address(pay_gem) == wethAddress,
446               "trying to cancel a non WETH order"
447           );
448           // Cancel order and receive WETH here in amount of pay_amt
449           outcome = RubiconMarket(RubiconMarketAddress).cancel(id);
450           WETH9(wethAddress).withdraw(pay_amt);
451           msg.sender.transfer(pay_amt);
452       }
```
https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconRouter.sol#L440-L452

The `RubiconMarket` only verifies that the message sender is the owner:
```solidity
File: contracts/RubiconMarket.sol   #2

215       modifier can_cancel(uint256 id) virtual {
216           require(isActive(id));
217           require(getOwner(id) == msg.sender);
218           _;
219       }
```
https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconMarket.sol#L215-L219

which will always be the `RubiconRouter` for orders placed through the `RubiconRouter`:
```solidity
File: contracts/RubiconMarket.sol   #3

391       /// @notice Key function to make a new offer. Takes funds from the caller into market escrow.
392       function offer(
393           uint256 pay_amt,
394           ERC20 pay_gem,
395           uint256 buy_amt,
396           ERC20 buy_gem
397       ) public virtual can_offer synchronized returns (uint256 id) {
398           require(uint128(pay_amt) == pay_amt);
399           require(uint128(buy_amt) == buy_amt);
400           require(pay_amt > 0);
401           require(pay_gem != ERC20(0x0));
402           require(buy_amt > 0);
403           require(buy_gem != ERC20(0x0));
404           require(pay_gem != buy_gem);
405   
406           OfferInfo memory info;
407           info.pay_amt = pay_amt;
408           info.pay_gem = pay_gem;
409           info.buy_amt = buy_amt;
410           info.buy_gem = buy_gem;
411           info.owner = msg.sender;
```
https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconMarket.sol#L391-L411


## Tools Used
Code inspection

## Recommended Mitigation Steps
Maintain a mapping from submitters to order IDs, inside `RubiconRouter`, and validate during cancel
`swapEntireBalance()` charges the wrong fee if the start and end tokens don't have the same decimals
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconRouter.sol#L267-L282


# Vulnerability details

## Impact
Users can either be under-charged or over-charged fees if the decimals of the two tokens don't match. This can also lead to the full input token amount being taken as a fee.

## Proof of Concept
`swapEntireBalance()` charges a fee based on `buy_amt_min`, which is the number of tokens the user expects to receive. All other functions charge the fee on the input token amount, not the output token amount. 
```solidity
File: contracts/RubiconRouter.sol   #1

267       function swapEntireBalance(
268           uint256 buy_amt_min,
269           address[] calldata route, // First address is what is being payed, Last address is what is being bought
270           uint256 expectedMarketFeeBPS
271       ) external returns (uint256) {
272           //swaps msg.sender entire balance in the trade
273           uint256 maxAmount = ERC20(route[0]).balanceOf(msg.sender);
274           ERC20(route[0]).transferFrom(
275               msg.sender,
276               address(this),
277               maxAmount // Account for expected fee
278           );
279           return
280               _swap(
281                   maxAmount,
282                   maxAmount.sub(buy_amt_min.mul(expectedMarketFeeBPS).div(10000)), //account for fee
```
https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconRouter.sol#L267-L282

Consider the case where a user wants to use USDC (6 decimals) to buy 1 WETH (18 decimals) with a .02% fee
```
~6,000 USDC = 1 WETH
6000000000(balanceOf(msg.sender)) = 1000000000000000000(buy_amt_min)
1000000000000000000 * 0.0002 = 200000000000000
Fee: 200,000,000 USDC
```

It's possible to fiddle with the numbers to make the fee eat up exactly the user's balance whenever the output token has more decimals than the input token

## Tools Used
Code inspection

## Recommended Mitigation Steps
Charge the fee based on `maxAmount` instead of `buy_amt_min`
Strategist can use up all reserve in `placeMarketMakingTrades`
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathPair.sol#L344


# Vulnerability details

## Impact
Strategist can use up all liquidity in `BathToken` well below the reserve ratio requirement, denying the ability for depositors to withdraw.

When placing trades, `enforceReserveRatio` is called to make sure that the current token balance still satisfy the `reserveRatio` requirement. However, this check is called *before* the trades are placed and tokens are transferred, which means a strategist can make trades using all the balance within the contract and it will pass.

## Recommended Mitigation Steps
Replace [L341-L344](https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathPair.sol#L341-L344) to a simple bathToken fetch, and move the reserveRatio check `enforceReserveRatio` to be executed after offers are placed.
```
function placeMarketMakingTrades() {
    ...
    bathAssetAddress = IBathHouse(bathHouse).tokenToBathToken(
        underlyingAsset
    );
    bathQuoteAddress = IBathHouse(bathHouse).tokenToBathToken(
        underlyingQuote
    );
    ...
    uint256 newAskID = IBathToken(bathAssetAddress).placeOffer(
        ask.pay_amt,
        ask.pay_gem,
        ask.buy_amt,
        ask.buy_gem
    );

    uint256 newBidID = IBathToken(bathQuoteAddress).placeOffer(
        bid.pay_amt,
        bid.pay_gem,
        bid.buy_amt,
        bid.buy_gem
    );
    
    enforceReserveRatio(_underlyingAsset, _underlyingQuote); // after tokens are transferred out
    ...
}

```
`BathBuddy` bonus can be drained
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L595
https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/peripheral_contracts/BathBuddy.sol#L87-L128


# Vulnerability details

## Impact
The current implementation of `BathBuddy.sol` allows anyone to drain most of the released bonus, reducing the amount of bonus for other depositors.

When withdrawing funds from `BathToken`, `distributeBonusTokenRewards` will be called and bonus token will be distributed to the user, proportional to the size of their share in the pool.  The issue here is that anyone can simply deposit and instantly withdraw to trigger the `distributeBonusTokenRewards`.

Any time the accumulated unlocked bonus is greater than the withdrawal fee, this issue is exploitable.

## Proof of Concept
- Assume the following configuration:
    - Withdrawal fee set to `0`.
    - Underlying token is `USDC`.
    - Vested bonus token is `10,000 USDT`.
    - Vesting schedule is halfway, `5,000 USDT` is releasable.
- Alice and Bob both deposited 5,000 USDC each.
- Alice withdraws and receive 50% share of releasable bonus: 2,500 USDT.
- Alice then re-deposit 5,000 USDC and instantly withdraw, taking another 50% share of the bonus left in BathBuddy: 50% * 2500 = 1,250 USDT.
- Repeat until bonus is no longer worth the gas (plus fee if fee is > 0).

## Recommended Mitigation Steps
Record and include deposit duration as part of bonus reward calculation. Consider using battle-tested staking reward contracts such as [Unipool](https://github.com/k06a/Unipool/blob/master/contracts/Unipool.sol) or [StakingRewards](https://github.com/Synthetixio/synthetix/blob/develop/contracts/StakingRewards.sol) instead, which features solve this exact issue.
users will lose fund when depositing in BathToken because of rounding in _deposit() especially in lower precision tokens
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L556-L585


# Vulnerability details

## Impact
The `_deposit()` function in `BathToken` calculates `shares` with rounding error and this error can be high when `underlying token` has (lower precisions and high price) or the ratio (`underlyingBalance() / totalSupply()` become high, by compounding or by attacker). so contract should return extra tokens (`depositAmount - mintedShare * underlyingBalance() / totalSupply()`) to depositor. 

## Proof of Concept
This `_deposit()` code in `BathToken`:
```
    function _deposit(uint256 assets, address receiver)
        internal
        returns (uint256 shares)
    {
        uint256 _pool = underlyingBalance();
        uint256 _before = underlyingToken.balanceOf(address(this));

        // **Assume caller is depositor**
        underlyingToken.transferFrom(msg.sender, address(this), assets);
        uint256 _after = underlyingToken.balanceOf(address(this));
        assets = _after.sub(_before); // Additional check for deflationary tokens

        (totalSupply == 0) ? shares = assets : shares = (
            assets.mul(totalSupply)
        ).div(_pool);

        // Send shares to designated target
        _mint(receiver, shares);
        emit LogDeposit(
            assets,
            underlyingToken,
            shares,
            msg.sender,
            underlyingBalance(),
            outstandingAmount,
            totalSupply
        );
        emit Deposit(msg.sender, msg.sender, assets, shares);
    }
```
As you can see in the line `underlyingToken.transferFrom(msg.sender, address(this), assets);` contract transfer deposit tokens and with this: `shares = (assets.mul(totalSupply)).div(_pool)` it calculates minted share amount. because of rounding in this line, user lose up to `int( underlyingBalance() / totalSupply() )` amount of deposit amount (the minted `share` has lower value than deposited amount equal to rounding error). in some cases this lost can be very high, those are:
1. `underlyingBalance() / totalSupply()` become very high by compounding or by manipulation (transferring underlying token directly to contract address when contract balance is low) 
2. when `underlying token` has low precision, for example `0` to `2`.
3. when `underlying token` has high price, like more than $10K for each token.
because these are common cases and attacker have some control over #1 so users will lose funds when depositing in some `BathToken` contracts.

## Tools Used
VIM

## Recommended Mitigation Steps
add more precision to `shares` or consider sending back extra amount of token deposited token by user.
There is no way to set the `rewardsVestingWallet` in contract `BathToken`
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L80
https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L642-L648


# Vulnerability details

## Impact

The bonus tokens feature cannot be utilised since there is no way set the value of `rewardsVestingWallet` from within the `BathToken` and `BathHouse` contracts.
Users may well expect this feature and miss out on rewards that they were expecting because there is no way to initialise this variable.

## Proof of Concept

The `rewardsVestingWallet` variable is declared [here](https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L80)  and used [here](https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L642-L643) but nowhere is it ever assigned.

## Tools used
Manual inspection

## Recommended Mitigation Steps

Add the following function in `BathHouse`

```solidity
function setBathTokenBathBuddy(address bathToken, address _bathBuddy) external onlyAdmin {
    IBathToken(bathToken).setBathBuddy(_bathBuddy);
}
```

And the following function in `BathToken`

```solidity
function setBathBuddy(address _bathBuddy) external onlyBathHouse {
    rewardsVestingWallet = IBathBuddy(_bathBuddy);
}
```
Sorted offer list can become unsorted by anyone.
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconMarket.sol#L280


# Vulnerability details

## Impact
- In `RubiconMarket.buy()`, fee is calculated using `spend` amount and is rounded down.
- In sorted offer list, offers are sorted based on their price (buy_amt / pay_amt)
- After taker buy, the price of the offer can be changed a little due to rounded down in calculation. It can make the sorted offer list become unsorted.
- And maker can do it multiple times to make the price go more far away from original price.


## Proof of concept

Consider the scenario

1. Maker want to sell 2000 USDT for 1 ETH
```
offers[0] = {
pay_amt: 1e18, 
pay_gem: ETH, 
buy_amt: 2e9, 
buy_gem: USDT
}
```
Currently, the price is `2e9 / 1e18 = 2e-9`

2. Taker call buy with input params
```
id = 0;
quantity = 1e9 - 1;
```
Spend amount is calculated as
```
spend	= quantity * buy_amt / pay_amt 
= (1e9 - 1) * 2e9 / 1e18
= (2e18 - 2e9) / 1e18
= 1 (rounded down) 
```
After this, offer of maker is updated 
```
offers[0] = {
pay_amt: 1e18 - 1e9 + 1, 
pay_gem: ETH, 
buy_amt: 2e9 - 1, 
buy_gem: USDT
}
```
The new price is `(2e9 - 1) / (1e18 - 1e9 + 1) = 2.000000001e-9 > 2e-9`
So the price of the offer is changed. 

3. Taker still can use the same trick with `quantity = 1e9 - 1` and `spend` is 
```
spend = (1e9 - 1) * (2e9 - 1) / (1e18 - 1e9 + 1) = 1
```
Offer is updated
```
offers[0] = {
pay_amt: 1e18 - 2e9 + 2, 
pay_gem: ETH, 
buy_amt: 2e9 - 2, 
buy_gem: USDT
}
```
The new price is `(2e9 - 2) / (1e18 - 2e9 + 2) = 2.000000002e-9`

## Tools Used

Manual Review

## Recommended Mitigation Steps
Check if offer is still sorted after `buy()` then move it to unsorted list if it’s not.
Unsorted offers can be prevented from `cancel()` by anyone.
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconMarket.sol#L1217-L1221


# Vulnerability details

## Impact
In `RubiconMarket._hide()` function, there is a while loop to find offer in unsorted order list from beginning.

But this unsorted order list can be spammed by anyone, make this list become too large and cause the while loop out of gas.

Attacker can add a lot of offers like sell 1 USDT for 1000 ETH, of courses no one will buy them, so them will stay there.

## Proof of concept

Scenario
1. Alice has an offer with `id = 1` in unsorted order book say “sell 2000 USDT for 1 ETH”.
2. Attacker spams to add 100000000 offer say “sell 1 USDT for 1000 ETH” with id from 2 to 100000001. 
3. Now Alice want to call `cancel()` to cancel offer with `id = 1`. It will call `_hide()` function and while loop to find offer of Alice in the list. Note that it will start finding from `id = _head = 100000001`. Eventually, this while loop will be reverted because of out of gas. 
4. If Alice or someone want to remove offers of attacker, they will have to pay `1000 ETH` for `1 USDT` each offer, which is unrealistic. 

## Tools Used

Manual Review

## Recommended Mitigation Steps
Use double linked-list in unsorted order book, so remove 1 element from the list will be O(1)
First depositor can break minting of shares
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L562-L571


# Vulnerability details

## Impact
In `BathToken._deposit()` function, it uses underlyingToken balance of address(this) and `totalSupply` to calculate shares.

Attacker can transfer a large amount of `underlyingToken` into without calling `deposit()`, make the share’s price inflated.

## Proof of concept

1. Attacker deposit 1 wei to mint 1 share

2. Attacker transfers huge amount to `BathToken` to greatly inflate the share’s price. 

3. Subsequent depositors instead have to deposit an equivalent sum to avoid minting 0 shares. Otherwise, their deposits accrue to the attacker who holds the only share.

## Tools Used
	Manual Review

## Recommened Mitigation Steps

Uniswap V2 solved this problem by sending the first 1000 LP tokens to the zero address. The same can be done in this case i.e. when totalSupply() == 0, send the first min liquidity LP tokens to the zero address to enable share dilution.

Ensure the number of shares to be minted is non-zero: require(_shares != 0, "zero shares minted");

Call deposit() once in initialize() to achieve the same effect as the suggestion above.
Taker can benefit 2x more than expected with same `buy_gem` amount.
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconMarket.sol#L280


# Vulnerability details

## Impact
In `RubiconMarket.buy()`, the amount taker have to spend is rounded down in calculation. 

Malicious takers can benefit from this by call `buy()` multiple times with small amount instead of call `buy()` 1 time.

In best case, malicious takers can benefit approximately 2x with same `buy_gem` amount

## Proof of concept

Consider the scenario

1. Maker want to sell 1 ETH for 2000 USDT 
```
offers[0] = {
pay_amt: 1e18, 
pay_gem: ETH, 
buy_amt: 2e9, 
buy_gem: USDT
}
```
2. Taker call buy with input params
```
id = 0;
quantity = 1e9 - 1;
```
Spend amount is calculated as
```
spend	= quantity * buy_amt / pay_amt 
= (1e9 - 1) * 2e9 / 1e18
= (2e18 - 2e9) / 1e18
= 1 (rounded down) 
```
So taker will have to pay `1 USDT` to receive `1e9 - 1 ETH`. But in fact, `1 USDT` is only worth `5e8 ETH` (2x profit)

After this, offer of maker is updated 
```
offers[0] = {
pay_amt: 1e18 - 1e9 + 1, 
pay_gem: ETH, 
buy_amt: 2e9 - 1, 
buy_gem: USDT
}
```
3. Taker still can use the same trick with `quantity = 1e9 - 1` and `spend` is 
```
spend = (1e9 - 1) * (2e9 - 1) / (1e18 - 1e9 + 1) = 1
```
Still 2x profit.
4. Taker can repeat this process until offer become dust

## Tools Used

Manual Review

## Recommended Mitigation Steps
Round up in `spend` calculation
```
uint256 spend = (mul(quantity, _offer.buy_amt) + _offer.pay_amt - 1) / _offer.pay_amt;
```
User can claim more rewards than expected 
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/peripheral_contracts/BathBuddy.sol#L87-L128


# Vulnerability details

## Impact
* User can claim more reward than expected by call deposit -> withdraw many times
* Make other users lose their reward 

## Proof of Concept
`BathBuddy` just divides the portion of rewards based on `BathToken`'s shares. But it doesn't care about how long that user has deposited to the BathToken. It will lead to the scenario that the user can call `deposit` and `withdraw` many times to claim more rewards. 

For more detail pls read and add this [file](https://gist.github.com/WelToHackerLand/a8d2ca5618f283e255c7401d47ae5046) to your test folder and run it (also note that add one more function `setRewardsVestingWallet()` that I note in the file for easy to test). 
I described all the detail of the issue through a simple example. 

## Tools Used
javascript, truffle 

## Recommended Mitigation Steps
The logic of `BathBuddy` should follow the logic of [this one](https://solidity-by-example.org/defi/staking-rewards/)
No checks to prevent Integer Underflow in RubiconRouter.sol
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconRouter.sol#L351
https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconRouter.sol#L406
https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconRouter.sol#L432


# Vulnerability details

## Impact
There is no check carried out on the local variable that is the difference of the balance of after a deposit and before a deposit. With this, it is possible for an integer underflow to occur which may lead to a user being transferred more tokens than they deposited.

Indications of integer underflow or possible integer underflow are in :
RubiconRouter.buyAllAmountWithETH() - https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconRouter.sol#L351
RubiconRouter.offerWithETH() - https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconRouter.sol#L406
RubiconRouter.offerForETH() - https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconRouter.sol#L432


## Proof of Concept
In `buyAllAmountWithETH()` - https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconRouter.sol#L325.
1. Assume the `wethAddress` contract has 1 eth balance.
2. A user calls `buyAllAmountWithETH`, inputs the value 0 for `uint256 max_fill_amount` , which means `uint256 max_fill_withFee = 0`
3. Deposit is called with 0 eth  and `_after` = 0
4. Hence an underflow occurs with `delta` = `2**256 -1` 
5. User now has a large amount of token to withdraw.

## Tools Used
Manual review

## Recommended Mitigation Steps
Some require checks and unchecked arithmetic are necessary in the functions.
No Function to set `rewardsVestingWallet` in BathToken
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L80


# Vulnerability details

## Impact
* Make `BathBuddy` contract useless 
* Protocol is not working as expected 
* Fewer incentives for users to join `BathToken` 

## Proof of Concept
variable `rewardsVestingWallet` in `BathToken` is used to store `BathBuddy` contract. `BathBuddy` make it easy for `Bath Tokens` to payout their withdrawers any `bonusToken` they may have accrued while staking in Bath Token (incentives). 
But in contract `BathToken.sol`, there is no function to initialize/set variable `rewardVestingWallet`.

## Tools Used
manual review 

## Recommended Mitigation Steps
Add one more function to let BathHouse set variable `rewardsVestingWallet`. 
```solidity=
function setRewardsVestingWallet(IBathBuddy newRewardsVestingWallet) onlyBathHouse {
    rewardsVestingWallet = newRewardsVestingWallet;
}
```