xanoutas/solidity-security-findings huggingface dataset:
Centralization risk of Owner in controlling allowed assets 
Audit: 2022-12-tigris-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/GovNFT.sol#L307-L309
https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/BondNFT.sol#L349-L355


# Vulnerability details

## Impact

Malicious owner can deny service of whole protocol by disallowing all assets or simply not including them

## Proof of Concept

Owner has to add asset in order to use the asset in BondNFT.sol

    function addAsset(address _asset) external onlyOwner {
        require(assets.length == 0 || assets[assetsIndex[_asset]] != _asset, "Already added");
        assetsIndex[_asset] = assets.length;
        assets.push(_asset);
        allowedAsset[_asset] = true;
        epoch[_asset] = block.timestamp/DAY;
    }

Owner can also editAsset in Lock.sol

    function editAsset(
        address _tigAsset,
        bool _isAllowed
    ) external onlyOwner() {
        allowedAssets[_tigAsset] = _isAllowed;
    }


Likewise, in GovNFT.sol, owner can setAllowedAsset

    function setAllowedAsset(address _asset, bool _bool) external onlyOwner {
        _allowedAsset[_asset] = _bool;
    }

## Tools Used

Manual Review

## Recommended Mitigation Steps

Usage of a DAO, or some assets can already be in play, or a series of multisig address to counter the centralization issue.
Unsafe ERC20 methods
Audit: 2022-12-tigris-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/StableVault.sol#L46-L50
https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/StableVault.sol#L68-L71


# Vulnerability details

## Impact
The ERC20.transfer() and ERC20.transferFrom() functions return a boolean value indicating success. This parameter needs to be checked for success. USDT, one of the allowed tokens, does not revert if the transfer failed but returns false instead.

## Proof of Concept
https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/StableVault.sol#L46-L50

https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/StableVault.sol#L68-L71

## Tools Used
Manual review.

## Recommended Mitigation Steps

Recommend using OpenZeppelin's SafeERC20 versions with the safeTransfer and safeTransferFrom functions that handle the return value check as well as non-standard-compliant tokens.
User assets in StableVaults may be drain
Audit: 2022-12-tigris-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/StableVault.sol#L78-L83
https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/StableVault.sol#L65-L72


# Vulnerability details

## Impact

All users may lose all their assets in StableVault

## Proof of Concept

Owner can drain all the valuable assets in the StableVault easily and quickly like this:

1. Create a mock ERC20 token
2. Mint a lot of the mock token, e.g. `10^15 * 10^18`
3. Call [StableVault.sol#listToken](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/StableVault.sol#L78) to list the mock token
4. Deposit a lot of the mock token to the vault
5. Call [StableVault.sol#withdraw](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/StableVault.sol#L65) drain all the valuable assets in the vault. Each asset needs to be called only once.

## Tools Used

Manual

## Recommended Mitigation Steps

All assets supported by the vault should be fixed at [construction time](https://github.com/code-423n4/2022-12-tigris/blob/588c84b7bb354d20cbca6034544c4faa46e6a80e/contracts/StableVault.sol#L35) and can not be modified.
The owner will not be able to list any new token after the StableVault is deployed.
If there is a real need, consider deploying a new StableVault contract.
Calculation error
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/RubiconDeFi/rubicon-protocol-v1/blob/master/contracts/RubiconRouter.sol#L282


# Vulnerability details

## Impact


```
    function _swap(
        uint256 pay_amt,
        uint256 buy_amt_min,
```
`maxAmount` is a pay_amt denominated in token `route[0]` (=pay_gem)

`buy_amt_min` is a buy_amt denominated in token `route[last]` (=buy_gem)

```
buy_amt_min * expectedMarketFeeBPS / 1000 
is the fee denominated in token `route[last]`(=buy_gem)
-- takers pay fees with the token they receive from the swap
```

Line 282 subtracts a buy_gem amount (the fee) from a pay_gem amount, which makes no sense. Moreover, the fee is taken in pay_gems (from the tokens sent by the taker) so it has no place being mentioned in a buy_amt_min argument.


## Recommended Mitigation Steps
Set `buy_amt_min` argument of _swap to `buy_amt_min` that was given as argument to this function (`swapEntireBalance`).
Multi-hop routes will leave a dust trail
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/RubiconDeFi/rubicon-protocol-v1/blob/6a0e1d89ff710f81428228d132e7d0c42de3a3cd/contracts/RubiconRouter.sol#L233


# Vulnerability details

## Impact
By calling `RubiconMarket.buy(id, quantity)` (as a consequence of L239 and L241) with 
```
quantity = currentAmount - currentAmount * expectedMarketFeeBPS / 10000
```

...the fee calculated by the buy function amounts to:
```
(currentAmount - currentAmount * expectedMarketFeeBPS / 10000)
 * expectedMarketFeeBPS / 10000

=
currentAmount * expectedMarketFeeBPS / 10000
- currentAmount * (expectedMarketFeeBPS / 10000)^2
```

Adding that fee to the quantity (that the buyer sells, possibly as part of a multihop route) results in:
```
currentAmount
- currentAmount * expectedMarketFeeBPS / 10000
+ currentAmount * expectedMarketFeeBPS / 10000
- currentAmount * (expectedMarketFeeBPS / 10000)^2

= currentAmount - currentAmount * (expectedMarketFeeBPS / 10000)^2
```
...which is the amount that will be spent by the contract from the buyer's wallet, spending over 99.99% of the (fillAmount) output from each loop iteration as the input of the next, and leaving a trail of dust in the buyer's wallet for each intermediary token.

The first token of a route is not affected by this issue because of LL229-230, meaning also that a swap requires the taker to have `pay_amt + fee` pay_gems available in his wallet and allowed to the router.

## Recommended Mitigation Steps

Do not subtract the fee at this stage.
rewardsVestingWallet is never initialized
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
rewardsVestingWallet in BathToken is never initialized thus release will never happen:
```solidity
    /// @notice Address of the OZ Vesting Wallet which acts as means to vest bonusToken incentives to pool HODLers
    IBathBuddy public rewardsVestingWallet;
```
When calling distributeBonusTokenRewards this will be skipped:
```solidity
  if (rewardsVestingWallet != IBathBuddy(0)) {
      rewardsVestingWallet.release(
          (token),
          receiver,
          sharesWithdrawn,
          initialTotalSupply,
          feeBPS
      );
  }
```

## Recommended Mitigation Steps
Consider either initializing rewardsVestingWallet or restructuring this functionality.
There is no way to set the `rewardsVestingWallet` (IBathBuddy) in BathToken contract
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


# Vulnerability details

rewardsVestingWallet (IBathBuddy) helps in releasing or distributing vested bonus tokens during withdrawals. There is currently no function to set it in BathToken contract. rewardsVestingWallet (IBathBuddy) remains as address(0) and is unusable.

### Impact
Admin can not add BathBuddy. User can not get bonus token incentives.

### Proof of Concept
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

## Tools Used
Manual review

### Recommended Mitigation Steps
Add the following function to BathToken contract
```
/// @notice Admin-only function to set a Bath Token's rewardsVestingWallet
function setRewardsVestingWallet(address rewardsVestingWallet_) external onlyBathHouse {
    require(rewardsVestingWallet_ != address(0), "invalid_addr");
    rewardsVestingWallet = rewardsVestingWallet_;
}
```
Accordingly, add a new function to BathHouse also.

END
Reentrancy attack
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconMarket.sol#L1049-L1108
https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconMarket.sol#L951-L981
https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconMarket.sol#L1113-L1124
https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconMarket.sol#L959-L981
https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconRouter.sol#L494-L517
https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconRouter.sol#L267-L287
https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconRouter.sol#L519-L549
https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconMarket.sol#L272-L347
https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconMarket.sol#L351-L376
https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconMarket.sol#L392-L429
https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/RubiconRouter.sol#L194-L215
https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/peripheral_contracts/WETH9.sol#L48-L53


# Vulnerability details

## Impact
High impact. Reentrancy Attack may lead in lost of funds.

## Proof of Concept
Reentrancy in RubiconMarket._matcho(uint256,ERC20,uint256,ERC20,uint256,bool) (c
ontracts/RubiconMarket.sol#1049-1108):
        External calls:
        - buy(best_maker_id,min(m_pay_amt,t_buy_amt)) (contracts/RubiconMarket.s
ol#1087)
                - IAqueduct(AqueductAddress).distributeToMakerAndTaker(getOwner(
id),msg.sender) (contracts/RubiconMarket.sol#665-668)
        - id = super.offer(t_pay_amt,t_pay_gem,t_buy_amt,t_buy_gem) (contracts/R
ubiconMarket.sol#1104)
                - require(bool)(pay_gem.transferFrom(msg.sender,address(this),pa
y_amt)) (contracts/RubiconMarket.sol#416)
        State variables written after the call(s):
        - _sort(id,pos) (contracts/RubiconMarket.sol#1106)
                - _best[address(pay_gem)][address(buy_gem)] = id (contracts/Rubi
conMarket.sol#1154)
        - id = super.offer(t_pay_amt,t_pay_gem,t_buy_amt,t_buy_gem) (contracts/R
ubiconMarket.sol#1104)
                - locked = true (contracts/RubiconMarket.sol#227)
                - locked = false (contracts/RubiconMarket.sol#229)
        - id = super.offer(t_pay_amt,t_pay_gem,t_buy_amt,t_buy_gem) (contracts/R
ubiconMarket.sol#1104)
                - offers[id] = info (contracts/RubiconMarket.sol#414)
Reentrancy in SimpleMarket.buy(uint256,uint256) (contracts/RubiconMarket.sol#272
-347):
        External calls:
        - require(bool,string)(_offer.buy_gem.transferFrom(msg.sender,feeTo,fee)
,Insufficient funds to cover fee) (contracts/RubiconMarket.sol#297-300)
        State variables written after the call(s):
        - offers[id].pay_amt = sub(_offer.pay_amt,quantity) (contracts/RubiconMa
rket.sol#302)
        - offers[id].buy_amt = sub(_offer.buy_amt,spend) (contracts/RubiconMarke
t.sol#303)
Ree
First pool 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#L557-L585


# Vulnerability details



## Impact
First depositor of the pool can break minting of the Bath Token shares

## Proof of Concept
* An attacker -who is the first one to deposit- sends 1 wei and bypasses `(totalSupply == 0)` condition.
* Later a very large amount of donation to the pool and inflates the shares proportionality ratio.
* 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.


```solidity
    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);
    }
```
https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L557-L585

## Tools Used
Manual Review
## Recommended Mitigation Steps
Ensure the number of shares to be minted is non-zero
[Uniswap V2 solved this problem by sending the first 1000 LP tokens to the zero address](https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L119-L124). 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.
First depositor can break minting of shares
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/RubiconDeFi/rubicon-protocol-v1/blob/master/contracts/rubiconPools/BathToken.sol#L569-L571


# Vulnerability details

## Impact
The attack vector and impact is the same as [TOB-YEARN-003](https://github.com/yearn/yearn-security/blob/master/audits/20210719_ToB_yearn_vaultsv2/ToB_-_Yearn_Vault_v_2_Smart_Contracts_Audit_Report.pdf), where users may not receive shares in exchange for their deposits if the total asset amount has been manipulated through a large “donation”.

## Proof of Concept
In `BathToken.sol:569-571`, the allocation of shares is calculated as follows:
```js
(totalSupply == 0) ? shares = assets : shares = (
    assets.mul(totalSupply)
).div(_pool);
```

An early attacker can exploit this by:
* Attacker calls `openBathTokenSpawnAndSignal()` with `initialLiquidityNew = 1`, creating a new bath token with `totalSupply = 1`
* Attacker transfers a large amount of underlying tokens to the bath token contract, such as `1000000`
* Using `deposit()`, a victim deposits an amount less than `1000000`, such as `1000`:
    * `assets = 1000`
    * `(assets * totalSupply) / _pool = (1000 * 1) / 1000000 = 0.001`, which would round down to `0`
    * Thus, the victim receives no shares in return for his deposit

To avoid minting 0 shares, subsequent depositors have to deposit equal to or more than the amount transferred by the attacker. Otherwise, their deposits accrue to the attacker who holds the only share.

```js
it("Victim receives 0 shares", async () => {
    // 1. Attacker deposits 1 testCoin first when creating the liquidity pool
    const initialLiquidityNew = 1;
    const initialLiquidityExistingBathToken = ethers.utils.parseUnits("100", decimals);
    
    // Approve DAI and testCoin for bathHouseInstance
    await testCoin.approve(bathHouseInstance.address, initialLiquidityNew, {
        from: attacker,
    });
    await DAIInstance.approve(
        bathHouseInstance.address,
        initialLiquidityExistingBathToken,
        { from: attacker }
    );

    // Call open creation function, attacker deposits only 1 testCoin
    const desiredPairedAsset = await DAIInstance.address;
    await bathHouseInstance.openBathTokenSpawnAndSignal(
        await testCoin.address,
        initialLiquidityNew,
        desiredPairedAsset,
        initialLiquidityExistingBathToken,
        { from: attacker }
    );
    
    // Retrieve resulting bathToken address
    const newbathTokenAddress = await bathHouseInstance.getBathTokenfromAsset(testCoin.address);
    const _newBathToken = await BathToken.at(newbathTokenAddress);

    // 2. Attacker deposits large amount of testCoin into liquidity pool
    let attackerAmt = ethers.utils.parseUnits("1000000", decimals);
    await testCoin.approve(newbathTokenAddress, attackerAmt, {from: attacker});
    await testCoin.transfer(newbathTokenAddress, attackerAmt, {from: attacker});

    // 3. Victim deposits a smaller amount of testCoin, receives 0 shares
    // In this case, we use (
BathToken uninitialized rewardsVestingWallet leading to loss of funds
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-L650


# Vulnerability details

## Impact
Contract `BathToken.sol` implements `distributeBonusTokenRewards` function that allows distributing non-underlying bath token incentives to pool withdrawers. In case of `rewardsVestingWallet` being set implementation triggers `release` function of `rewardsVestingWallet`. The issue is that `rewardsVestingWallet` is uninitalized (set to zero address) and cannot be set in any way which means that there is no way of executing `rewardsVestingWallet.release` logic. The result is that function `distributeBonusTokenRewards` does not work which leads to loss of bonus token rewards thus effectively loss of funds.

## Proof of Concept
* 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-L650

## Tools Used
Manual Review / VSCode

## Recommended Mitigation Steps
It is recommended to initialize value of `rewardsVestingWallet` in `initialize` or add additional function for setting its value.
Wrong fee calculation between Router & Market
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The wrong fee calculation can cause a loss to users' fund and this loss will be stuck in RubiconRouter

## Proof of Concept

We have the default $feeBPS = 20, BPS = 10000$

Let's assume that alice call `RubiconRouter.swap(pay_amt=1000000)`

Through router, alice will have to transfer in:

$amountTransferIn = 1000000 \times (BPS + feeBPS) / BPS = 1002000$

However in the internal `_swap` function, the actual amount being pass into `RubiconMarket.sellAllAmount()` is 

$amountTransferIn - amountTransferIn \times feeBPS / BPS = 999996$

So in this case, $4$ over $1002000$ unit of user's fund were stuck in the router and not being used to swap.

## Recommended Mitigation Steps

The formula in L232-L234 in Rubicon Router should be changed to:

```solidity
    currentAmount.sub(
        currentAmount.mul(expectedMarketFeeBPS).div(expectedMarketFeeBPS + 10000)
    )

```
Steal user's first deposit
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-05-rubicon/blob/main/contracts/rubiconPools/BathToken.sol#L435


# Vulnerability details

## Impact
Steal user's first deposit

## Proof of Concept
The attack's flow is as following:
1. The first depositor wants to deposit X in `bathToken.sol`.
2. We detect it and frontrun 2 operations:
    1.  We deposit 1 of the underlying to the system. In exchange we receive 1 share. (Based on that formula:
 ```
(totalSupply == 0) ? shares = assets : shares = (
            assets.mul(totalSupply)
        ).div(_pool);
```
And because `totalSupply == 0` we'll return 0.

    2. We transfer X from the underlying to the BathToken.sol contract address. From that the `_pool` would increase to x+1 but `totalSupply` stays on 1. 
3. Now the victim perform his transaction and deposits X to the system and receive 0 shares, because:

 ```
(totalSupply == 0) ? shares = assets : shares = (
            assets.mul(totalSupply)
        ).div(_pool);
```
so it will be `1.mul(x).div(x+1) = 0`.

4. Now we call `redeemToken(shares = 1)` and in there we call: `assets = _withdraw(shares, receiver);`.
in _withdraw:
```
        uint256 r = (underlyingBalance().mul(_shares)).div(_initialTotalSupply);

```
Which will be :
```
 r = (2x+1).mul|(1).div(1)
 r = 2x+1
```
`r` is the amount we should be given after we burn our 1 share (minus fee)
Which means we indeed steal the user funds.
 
## Tools Used

## Recommended Mitigation Steps