xanoutas/solidity-security-findings huggingface dataset:
`BathToken.sol#_deposit()` attacker can mint more shares with re-entrancy from hookable 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#L557-L568


# Vulnerability details

`BathToken.sol#_deposit()` calculates the actual transferred amount by comparing the before and after balance, however, since there is no reentrancy guard on this function, there is a risk of re-entrancy attack to mint more shares.

Some token standards, such as ERC777, allow a callback to the source of the funds (the `from` address) before the balances are updated in `transferFrom()`. This callback could be used to re-enter the function and inflate the amount.

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

```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
    ...
```

### PoC

With a ERC777 token by using the ERC777TokensSender `tokensToSend` hook to re-enter the `deposit()` function.

Given: 

-   `underlyingBalance()`: `100_000e18 XYZ`.
-   `totalSupply`: `1e18`

The attacker can create a contracts with `tokensToSend()` function, then:

1.   `deposit(1)`
    -   preBalance  = `100_000e18`;
    -   `underlyingToken.transferFrom(msg.sender, address(this), 1)`
2. reenter using `tokensToSend` hook for the 2nd call: `deposit(1_000e18)`
    -   preBalance  = `100_000e18`;
    -   `underlyingToken.transferFrom(msg.sender, address(this), 1_000e18)`
    -   postBalance = `101_000e18`;
    -   assets (actualDepositAmount) = `101_000e18 - 100_000e18 = 1_000e18`;
    -   mint `1000` shares;
3. continue with the first `deposit()` call:
    -   `underlyingToken.transferFrom(msg.sender, address(this), 1)`
    -   postBalance = `101_000e18 + 1`;
    -   assets (actualDepositAmount) = `(101_000e18 + 1) - 100_000e18 = 1_000e18 + 1`;
    -   mint `1000` shares;

As a result, with only `1 + 1_000e18` transferred to the contract, the attacker minted `2_000e18 XYZ` worth of shares.

### Recommendation

Consider adding `nonReentrant` modifier from OZ's `ReentrancyGuard`.
`BathPair.sol#rebalancePair()` can be front run to steal the pending rebalancing amount
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

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

```solidity
function underlyingBalance() public view returns (uint256) {
    uint256 _pool = IERC20(underlyingToken).balanceOf(address(this));
    return _pool.add(outstandingAmount);
}
```

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

```solidity
function removeFilledTradeAmount(uint256 amt) external onlyPair {
    outstandingAmount = outstandingAmount.sub(amt);
    emit LogRemoveFilledTradeAmount(
        IERC20(underlyingToken),
        amt,
        underlyingBalance(),
        outstandingAmount,
        totalSupply
    );
}
```

For `BathToken`, there will be non-underlyingToken assets sitting on the contract that have filled to the contract and are awaiting rebalancing by strategists.

We assume the rebalance will happen periodically, between one rebalance to the next rebalance, `underlyingBalance()` will decrease over time as the orders get filled, so that the price per share will get lower while the actual equity remain relatively stable. This kind of price deviation will later be corrected by rebalancing.

Every time a `BathPair.sol#rebalancePair()` get called, there will be a surge of price per share for the `BathToken`, as a certain amount of `underlyingToken` will be transferred into the contract. 

This enables a well known attack vector, which allows the pending yields to be stolen by front run the strategist's `BathPair.sol#rebalancePair()` transaction, deposit and take a large share of the vault, and `withdraw()` right after the `rebalancePair()` transaction for instant profit.

### PoC

Given:

- Current `underlyingBalance()` is `100,000 USDC`;
- Pending rebalancing amount is `1000 USDC`;

1. `strategist` calls `rebalancePair()`;
2. The attacker sends a deposit tx with a higher gas price to deposit `100,000 USDC`, take 50% share of the pool;
3. After the transaction in step 1 is mined, the attacker calls `withdraw()` and retireve `100,500 USDC`.

As a result, the attacker has stolen half of the pending yields in about 1 block of time.

### Recommendation

Consider adding a new variable to track rebalancingAmount on `BathToken`.

`BathToken` should be notified for any pending rebalancing amount changes via `BathPair` in order to avoid sudden surge of pricePerShare over `rebalancePair()`.

`rebalancingAmount` should be considered as part of `underlyingBalance()`.
QA Report
Audit: 2022-05-rubicon-findings
Severity: HIGH

## Impact
[RubiconRouter.swapEntireBalance](https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconRouter.sol#L267) is supposed to buy at least `buy_amt_min` of Y (output) tokens by using the caller's entire X (input) token  balance.

The problem is that instead of passing the `buy_amt_min` (minus the fee) to `_swap`, the code uses the caller's X token balance, [maxAmount](https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconRouter.sol#L282).

This means the call will [revert if the purchase amount is not above the user's balance](https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconRouter.sol#L247) (minus fee).

## Recommended Mitigation Steps

Pass the correct parameter, `buy_amt_min`:

```
/// @dev this function takes a user's entire balance for the trade in case they want to do a max trade so there's no leftover dust
    function swapEntireBalance(
        uint256 buy_amt_min,
        address[] calldata route, // First address is what is being payed, Last address is what is being bought
        uint256 expectedMarketFeeBPS
    ) external returns (uint256) {
        //swaps msg.sender entire balance in the trade
        uint256 maxAmount = ERC20(route[0]).balanceOf(msg.sender);
        ERC20(route[0]).transferFrom(
            msg.sender,
            address(this),
            maxAmount // Account for expected fee
        );
        return
            _swap(
                maxAmount,
                maxAmount.sub(buy_amt_min.mul(expectedMarketFeeBPS).div(10000)), //account for fee
                route,
                expectedMarketFeeBPS,
                msg.sender
            );
    }
```

After

```
/// @dev this function takes a user's entire balance for the trade in case they want to do a max trade so there's no leftover dust
    function swapEntireBalance(
        uint256 buy_amt_min,
        address[] calldata route, // First address is what is being payed, Last address is what is being bought
        uint256 expectedMarketFeeBPS
    ) external returns (uint256) {
        //swaps msg.sender entire balance in the trade
        uint256 maxAmount = ERC20(route[0]).balanceOf(msg.sender);
        ERC20(route[0]).transferFrom(
            msg.sender,
            address(this),
            maxAmount // Account for expected fee
        );
        return
            _swap(
                maxAmount,
                buy_amt_min.sub(buy_amt_min.mul(expectedMarketFeeBPS).div(10000)), //account for fee
                route,
                expectedMarketFeeBPS,
                msg.sender
            );
    }
```
Wrong formula to calculate new `t_pay_amt` in `_matcho` function (RubiconMarket.sol)
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
* function`_matcho` won't work as expected 
* `t_pay_amount` updated incorrectly can lead to reverted transaction because not enough fund to transfer 

## Proof of Concept
In `_matcho` function, after matching with offer, `t_buy_amt` and `t_pay_amt` will updated as follow: 
```solidity=
t_buy_amt_old = t_buy_amt;
t_buy_amt = sub(t_buy_amt, min(m_pay_amt, t_buy_amt));
t_pay_amt = mul(t_buy_amt, t_pay_amt) / t_buy_amt_old;
```
But the `t_pay_amt` should be substract by [`spend`](https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconMarket.sol#L280), because buyer just need to pay `spend` amount to buy a part / entire of offer `id`.   
For example: 
* `t_buy_amt = 100` 
* `t_pay_amt = 10`
* `m_pay_amt = 10`
* `m_buy_amt = 2` 

So user want to match with offer (10, 2). He will get 10 `m_pay_gem` and need to pay 2 `m_buy_gem`. After matching, `t_buy_amt` should be `100 - 10 = 90` and `t_pay_amt` should be `10 - 2 = 8`. 
But when we updated with contract's formula, we will have
```
t_buy_amt_old = t_buy_amt = 100
t_buy_amt = t_buy_amt - min(m_pay_amt, t_buy_amt) = 100 - 10 = 90 
t_pay_amt = t_buy_amt * t_pay_amt / t_buy_amt_old = 90 * 10 / 100 = 9 != 8
```
There still remain 9 `t_pay_gem` after paying 2 `t_pay_gem` for offer --> Make no sense 

## Tools Used
Manual review 

## Recommended Mitigation Steps
Instead of calculating `t_pay_amt` like in [formula](https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconMarket.sol#L1091), we can recalculate the `spend` amount need to pay, and subtract `t_pay_amt` by that value.
`BathToken.sol` A malicious early user/attacker can manipulate the vault's pricePerShare to take an unfair share of future users' deposits
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-L574


# Vulnerability details

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

```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);
    ...
```

A malicious early user can deposit()` with `1 wei` of `asset` token as the first depositor of the `BathToken`, and get `1 wei` of shares token.

This can be done by calling `BathHouse.sol#openBathTokenSpawnAndSignal()` with `initialLiquidityNew: 1 wei`:

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

Then the attacker can send `10000e18 - 1` of `asset` tokens and inflate the price per share from 1.0000 to an extreme value of 1.0000e22 ( from `(1 + 10000e18 - 1) / 1`) .

As a result, the future user who deposits `19999e18` will only receive `1 wei` (from `19999e18 * 1 / 10000e18`) of shares token.

They will immediately lose `9999e18` or half of their deposits if they `redeem()` right after the deposit.

### Recommendation

Consider requiring a minimal amount of share tokens to be minted for the first minter, and send a port of the initial mints as a reserve to the DAO so that the pricePerShare can be more resistant to manipulation.

```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);

    // for the first mint, we require the mint amount > (10 ** decimals) / 100
    // and send (10 ** decimals) / 1_000_000 of the initial supply as a reserve to DAO
    if (totalSupply == 0 && decimals >= 6) {
        require(shares > 10 ** (decimals - 2));
        uint256 reserveShares = 10 ** (decimals - 6);
        _mint(DAO, reserveShares);
        shares -= reserveShares;
    }

    // Send shares to designate
Wrong `buy_amt_min` calculation in `RubiconRouter.swapEntireBalance` for multi-hop swaps
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The `buy_amount_min` value provided for the `_swap` call is calculating too few fees for multi-hop swaps. Every swap within a multi-hop swap is taking fees.

Currently, only a one-time fee is calculated on the basis of the `swapEntireBalance` function parameter `buy_amt_min` and subtracted from `maxAmount`.

To correctly consider fees, a user would have to provide a `buy_amt_min` value larger than `maxAmount`. This is counterintuitive.

## Proof of Concept

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

```solidity
maxAmount.sub(buy_amt_min.mul(expectedMarketFeeBPS).div(10000))
```

## Tools Used

Manual review

## Recommended mitigation steps

Simply pass the provided `buy_amt_min` value to the `_swap` function.
`BathToken` with initial liquidity of `1 wei` causes very expensive share price leading to precision errors and 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#L569-L571


# Vulnerability details

## Impact

The creator of a new `BathToken` is able to maliciously manipulate the share price by providing lowest possible amount (`1 wei`) of liquidity (`initialLiquidityNew`) and then artificially blowing up the `BathToken` token balance. Following depositors will loose their deposited funds due to precisions issues.

This is a well known issue, Uniswap and other protocols had similar issues when `supply == 0`.

## Proof of Concept

1. Alice (attacker) creates a new `BathToken` `bathDAI` (`DAI` as the underlying token) with a initial liquidity of `1 wei` via [BathToken.deposit()](https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L441)
2. Alice receives `1e-18` (1 wei) shares
3. Alice transfers `1 ether` of `DAI` via `transfer()` to `bathDAI` to artificially blow up balance without minting new shares. `bathDAI` balance is now `1 ether + 1 wei` DAI -> share price is now very high (= `1000000000000000000001` wei ~ `1000 * 1e18`)
4. Bob (victim) deposits `100 ether` via [BathToken.deposit()](https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L441)
5. Bob receives `0` shares

Bob receives `0` shares due to precision issue. His deposited funds are lost.

The shares are calculated as following ([BathToken.sol#L569-L571](https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathToken.sol#L569-L571)):

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

In case of a very high share price, due to `_pool` `>` `(assets.mul(totalSupply))`, shares will be 0.

## Tools Used

Manual review

## Recommended mitigation steps

For the first deposit, mint fixed amount of `shares`, e.g. `10**decimals()`

```solidity
if (totalSupply == 0) {
    shares = 10**decimals; // @audit-info recommended mitigation
} else {
    shares = (assets.mul(totalSupply)).div(_pool);
}
```

Fix inspired by [Uniswap V2](https://github.com/Uniswap/v2-core/blob/4dd59067c76dea4a0e8e4bfdda41877a6b16dedc/contracts/UniswapV2Pair.sol#L120-L121) and [Harvest Finance](https://github.com/harvest-finance/harvest/blob/master/contracts/Vault.sol#L150)
Anyone can cancel orders from the router and get the tokens
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

##Impact

Anyone can cancel orders from the router and get the tokens

##Proof of concept
-A user makes a WETH order from the router

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

-Any attacker can call the cancel function with the order ID and get all the unfilled funds from the order 

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

Basically orders in the router are up for grabs to the first who cancels then since the orders are made in name of the router, not the user msg.sender in cancelation will be the router.

## Recommended Mitigation Steps

Only allow users to cancel orders they themselves sent
Orders sent with RubiconRouter Will get fulfilled in the router and tokens will be Lost
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details


## impact

Users of the router will lose their funds

## proof of concept

-Users can send orders from the router using offerForEth() or offerWithEth()
https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconRouter.sol#L396

-In order creation msg.sender will be the router since the router calls the market

- If the order doesn't get instantaneously filled but it gets filled later then new tokens are in the balance of the router and can't be taken out since there is no function to do so



## Recommended Mitigation Steps
Add a mechanism of ownership of orders and recovering of tokens from the router after execution.
Reserve ratio isn't enforced after a trade execution
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 
Users could get exposed to higher risk than desired and funds to withdraw from the vault could not be available 

## Proof of Concept

The reserve ratio is the parameter that ensures a percentage of the tokens is always available to be withdrawn from a pool by the users. It also ensures a certain risk limit to how many funds can be risked at the same time.
From the documentatión : 
enforceReserveRatio - this modifier ensures that the reserveRatio for each of the underlying liquidity pools (asset and quote bathTokens) is observed before and after function execution.

In bathPair.sol the reserve ratio is verified before but not after an offer is made so strategists can make trades that break the reserve ratio. 
https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/rubiconPools/BathPair.sol#L344
## Recommended Mitigation Steps
Enforce reserve ratio only after orders are placed. Doing it before doesn’t serve any purpose since if the reserve ratio isn’t met before but is met after a trade then that trade would actually be enforcing the ratio.

So the ratio should ideally be checked after execution and not checked before.
Malicious user can spam create an offer to make function `_hide` run out of gas (RubiconMarket.sol)
Audit: 2022-05-rubicon-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Impossible to call `insert()`, `buy()`, `cancel()` due to run out of gas 

## Proof of Concept
In `_hide(uint256 id)` [function](https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconMarket.sol#L1203-L1229), we need to iterate through all the elements of linked list `_near` to get the previous id `pre` of given `id`, and use it to update the linked list when deleting the offer `id`. 
Malicous user can spam create many "crazy" offers and make the linked list `_near` too long to run `hide` function.
For example: user create 1M offers with 
* `buy_gem = BTC` 
* `buy_amt = 1000000`
* `pay_gem = USDC`
* `pay_amt = 1`
(buy 1000000BTC with 1USDC, I don't think anyone wants this offer)



## Tools Used
Manual review 

## Recommended Mitigation Steps
Instead of using a single linked list `_near`, we can use a double linked list `_near_prev`, `_near_next` instead. Double linked list let us to find the previous and next offer of `id` in O(1)
`swapEntireBalance()` takes more fees than requires, and locks them in 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#L282
https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconRouter.sol#L171-L177


# Vulnerability details

## Impact
Extra funds are stolen from the user, and become locked in the `RubiconRouter` for others to steal

## Proof of Concept
Most of the other functions of the `RubiconRouter` handle fees by taking the amount being provided, using a percentage as the fee, then adding that to the total. `swapEntireBalance()` (and also `getExpectedSwapFill()`) use the user's total balance and *subtract* the fee:
```solidity
File: contracts/RubiconRouter.sol   #1

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#L282

```solidity
File: contracts/RubiconRouter.sol   #2

171               uint256 _pay = i == 0
172                   ? pay_amt
173                   : (
174                       currentAmount.sub(
175                           currentAmount.mul(expectedMarketFeeBPS).div(10000)
176                       )
177                   );
```
https://github.com/code-423n4/2022-05-rubicon/blob/8c312a63a91193c6a192a9aab44ff980fbfd7741/contracts/RubiconRouter.sol#L171-L177


If a user has a balance of $1000 and the fee is 1%, to consume the total funds $990.0990099 should be used as the payment, and $9.90099009 as the fee since those two add up to ~$1,000. The code however uses $10 as the fee and $990 as the fee. When amounts are large, this difference can be significant

## Tools Used
Code inspection

## Recommended Mitigation Steps
Divide `buy_amt_min` by the sum of one plus the fee decimal percentage, in order to get the correct fee