xanoutas/solidity-security-findings huggingface dataset:
`WellUpgradeable#_authorizeUpgrade` should check tokens in the new implementation
Audit: 2024-07-basin-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/WellUpgradeable.sol#L65-L85


# Vulnerability details

## Impact

The current composition of contracts and proxies is as follows:

(entry point)      (delegateCalls)      (minimalProxy)   (delegateCalls and append immutableCallArgs) (original implementation)
`ERC1967Proxy`            ->                `WellUpgradeable`  ->  `WellUpgradeable`

If we want to upgrade the implementation of the `ERC1967Proxy`, we should deploy the new `WellUpgradeable` and bore it from the `Aquifier`. The problem is that we may bore it by providing different `tokens` data when the new implementation is being cloned into minimalProxy.(**NOTE** There is a problem is we have same tokens, but their order is swapped) The next step is to pass the minimal proxy address to the `ERC1967Proxy#upgradeTo` function, which will only check if the implementation is "authorized" by the `aquifer` address saved in the current minimal proxy and if the new implementation is valid `ERC1967Proxy` implementation. If so, `WellUpgradeable` minimal proxy is updated for the `ERC1967Proxy`. If the `tokens` inside the new minimal proxy are different from the last, this may lead to a lot of problems, such as the inability for users to claim their original tokens liquidity.

**Another realistic scenario** is to have the same tokens set, but in reversed order. This will also cause problems, because of the way `Well` reads from storage and lods `I and J`:

## Proof of Concept
Example:
- Imagine we have a WellUpgradable with tokens set to [BEAN;USDC].
When we add liquidity, the contract will store corresponding reserves for the tokens in two consequent storage slots (RESERVES_STORAGE_SLOT -> BEAN Reserves; RESERVES_STORAGE_SLOT  + 32 = USDC Reserves).

- If the implementation is updated and the new minimal proxy received [USDC;BEAN] for tokens, this will result in the following miscalculations:
- If we try to swap from BEAN -> USDC
```
function _swapFrom(
        IERC20 fromToken,
        IERC20 toToken,
        uint256 amountIn,
        uint256 minAmountOut,
        address recipient
    ) internal returns (uint256 amountOut) {
        IERC20[] memory _tokens = tokens();
        (uint256 i, uint256 j) = _getIJ(_tokens, fromToken, toToken);

        uint256[] memory reserves = _updatePumps(_tokens.length);

        reserves[i] += amountIn;
        uint256 reserveJBefore = reserves[j];
        reserves[j] = _calcReserve(wellFunction(), reserves, j, totalSupply());
```

- `i` will be 1 and `j` will be 0
- `_updatePumps` calls `_getReserves`, which will read the reserves from `ERC1967` storage (reserves[0] = bean reserves; reserves[1] = usdc reserves)
- `reserves[I] + amountIn` corresponds to increasing the value of the slot responsible for usdc reserves, instead of the one, which holds bean 
- The following will result in unexpected price and liquidity calculations and may be unnoticed for some
Incorrect decimal handling in Newton estimation mechanism of `calcReserveAtRatioSwap` and `calcReserveAtRatioLiquidity` functions of `Stable2` contract
Audit: 2024-07-basin-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-basin/blob/main/src/functions/Stable2.sol#L173-L239
https://github.com/code-423n4/2024-07-basin/blob/main/src/functions/Stable2.sol#L246-L304
https://github.com/code-423n4/2024-07-basin/blob/main/src/functions/Stable2.sol#L338-L351


# Vulnerability details

## Impact
In the Newton estimation mechanism of the `calcReserveAtRatioSwap` and `calcReserveAtRatioLiquidity` functions of the `Stable2` contract, incorrect decimal handling of `pd.currentPrice` can cause various issues, such as not converging exactly to the limit value.

## Proof of Concept
The `calcReserveAtRatioSwap` and `calcReserveAtRatioLiquidity` functions of the `Stable2` contract get the approximate closing ratio from the target price and perform Newton's method to converge to the reserve.
```solidity
    for (uint256 k; k < 255; k++) {
        scaledReserves[j] = updateReserve(pd, scaledReserves[j]);

        // calculate scaledReserve[i]:
        scaledReserves[i] = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18));
        // calc currentPrice:
-->     pd.currentPrice = _calcRate(scaledReserves, i, j, lpTokenSupply);

        // check if new price is within 1 of target price:
-->     if (pd.currentPrice > pd.targetPrice) {
            if (pd.currentPrice - pd.targetPrice <= PRICE_THRESHOLD) {
                return scaledReserves[j] / (10 ** (18 - decimals[j]));
            }
        } else {
            if (pd.targetPrice - pd.currentPrice <= PRICE_THRESHOLD) {
                return scaledReserves[j] / (10 ** (18 - decimals[j]));
            }
        }
    }
```
The root of the issue is that here `pd.currentPrice` is calculated as a decimal of 18, and `pd.targetPrice` is calculated as 6.

`pd.targetPrice` is calculated as follows:
```solidity
    // calc target price with 6 decimal precision:
    pd.targetPrice = scaledRatios[i] * PRICE_PRECISION / scaledRatios[j];  // 6 decimal
```
Meanwhile, `pd.currentPrice` is calculated as follows by the `_calcRate` function.
```solidity
    function _calcRate(
        uint256[] memory reserves,
        uint256 i,
        uint256 j,
        uint256 lpTokenSupply
    ) internal view returns (uint256 rate) {
        // add 1e6 to reserves:
        uint256[] memory _reserves = new uint256[](2);
        _reserves[i] = reserves[i];
        _reserves[j] = reserves[j] + PRICE_PRECISION;

        // calculate rate:
-->     rate = _reserves[i] - calcReserve(_reserves, i, lpTokenSupply, abi.encode(18, 18));  // 18 decmal
    }
```
As you can see, `pd.currentPrice` is calculated as a decimal of 18, and `pd.targetPrice` is calculated as 6.

As a result, the `calcReserveAtRatioSwap` and `calcReserveAtRatioLiquidity` functions compare `pd.currentPrice`, which is calculated as 18 decimals, with `pd.targetPrice`, which is calculated as 6 decimals, which does not work as intended.

## Tools Used
Manual Review

## Recommended Mitigation Steps
It is recommended to modify the Newton Estim
Anyone can upgrade Well's implementation
Audit: 2024-07-basin-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/WellUpgradeable.sol#L64-L85
https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/WellUpgradeable.sol#L92-L95


# Vulnerability details

### Impact

Anyone can upgrade the contract to a malicious implementation that they control, allowing them to exploit the contract, and its users.

### Proof of Concept

1. Context

WellUpgradeable.sol is designed to be an upgradeable version of Well.sol, for which a new implementation can be easily and seamlessly upgraded to. As thus, the contract inherits UUPSUprageable and other upgradeable counterparts and also exposes the `_authorizeUpgrade`, `upgradeTo` and `upgradeToAndCall` functions. This ensures that the contracts can be updated.

2. Bug Location

As would be expected from a sensitive contract's implementation, these functions should be limited to the admins alone. This however isn't the case as the functions lacks an `onlyOwner` modifier [(despite inheriting openzeppelin's OwnableUpgradeable.sol)](https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/WellUpgradeable.sol#L38). 

Looking at the functions, It can be observed that there's no form of access control.

```solidity
    function upgradeTo(address newImplementation) public override {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallUUPS(newImplementation, new bytes(0), false);
    }
```

```solidity
    function upgradeToAndCall(address newImplementation, bytes memory data) public payable override {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallUUPS(newImplementation, data, true);
    }
```


```solidity
    function _authorizeUpgrade(address newImplmentation) internal view override {
//...
    }
```

Particularly the `_authorizeUpgrade` function, which according to [OpenZeppelin's implementation](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/3d4c0d5741b131c231e558d7a6213392ab3672a5/contracts/proxy/utils/UUPSUpgradeable.sol#L94C1-L104C76) will normally be protected by an access control.

```solidity
    /**
     * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
     * {upgradeTo} and {upgradeToAndCall}.
     *
     * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
     *
     * ```solidity
     * function _authorizeUpgrade(address) internal override onlyOwner {}
     * ```
     */
    function _authorizeUpgrade(address newImplementation) internal virtual;
```

3. Final Effect

The possibilities are endless as anyone can easily upgrade the contract to an implementation of their choice, tailoring the new upgrade to their desires.

4. Runnable POC

The gist link below holds a test case that shows a user can upgrade the contract to any implementation of his choice.

https://gi
Incorrectly assigned `decimal1` parameter upon decoding
Audit: 2024-07-basin-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L317-L319


# Vulnerability details

### Impact

Impact is high as lots of functions depend on the `decodeWellData`, and as such will be fed incorrect data about `token1`'s decimals. This can lead to potential overvaluation or undervaluation of the token's amount and prices, depending on the context with which its used. It also ignores the fact that `decimals1` can return a 0 value and as such will work with that for scaling, rather than scaling it to 18. This also depends on the `decimal0` being 0;

### Proof of Concept

1. Context

In various functions in Stable2.sol, the `decodeWellData` function is called to decode the passed in token decimals data, which is then used to scale the token reserves to a function of 18. This is because the tokens may have different decimals, ranging from as low as 0 to 18. 

2. Bug Location

In the `decodeWellData` function, we can see that if the well data returns 0, a returned decimal of 18 is assumed as standard for the token. And as such, each decimals are scaled to that level. However, as can be seen from the @audit tag, to set a value "18" `decimal1`, the function incorrectly checks if `decimal0` is 0, rather than checking if `decimal1` is 0;

```solidity
    function decodeWellData(bytes memory data) public view virtual returns (uint256[] memory decimals) {
        (uint256 decimal0, uint256 decimal1) = abi.decode(data, (uint256, uint256));

        // if well data returns 0, assume 18 decimals.
        if (decimal0 == 0) {
            decimal0 = 18;
        }
        if (decimal0 == 0) { //@audit
            decimal1 = 18;
        }
        if (decimal0 > 18 || decimal1 > 18) revert InvalidTokenDecimals();

        decimals = new uint256[](2);
        decimals[0] = decimal0;
        decimals[1] = decimal1;
    }
```
3. Final Effect

This means that regardless of the value returned for `decimal1` from the decoding, it's replaced with 18, even if `token1` is a token with 6 decimals, or less than 18. As a result, the reserves will be potentially scaled with a decimal different from actual. Also, when `decimal1` is 0, rather than scaling to a value of 18, it ignores this value and attempts to work with a value of 0 instead. As the function is used extensively, in the codebase, this can lead to serious price miscalculatons.

The functions are listed below:

i. [`calcLpTokenSupply`](https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L79-L81)
ii. [`calcReserve`](https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L120-L121)
iii. [`calcRate` ](https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L159-L160)
iv. [`calcReserveAtRatioSwap` ](https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb
pd.currentPrice in calcReserveAtRatioSwap() is not updated correctly.
Audit: 2024-07-basin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L226


# Vulnerability details

## Impact
`currentPrice` is converged towards `targetPrice` as reserve of `token0` or `token1` changes.
```solidity
for (uint256 k; k < 255; k++) {
            scaledReserves[j] = updateReserve(pd, scaledReserves[j]);

            // calculate scaledReserve[i]:
            scaledReserves[i] = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18));
            // calc currentPrice:
            pd.currentPrice = _calcRate(scaledReserves, i, j, lpTokenSupply);
            ....
}
```
`lpTokenSupply` is calculated from `scaledReserves` which is updated inside `for` loop. But `lpTokenSupply` in [here](https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L226) is calculated only once before `for` loop and keeps unchanged.


## Tools Used
Manual audit
## Recommended Mitigation Steps
```diff
            // calc currentPrice:
-           pd.currentPrice = _calcRate(scaledReserves, i, j, lpTokenSupply);
+           pd.currentPrice = calcRate(scaledReserves, i, j, abi.encode(18, 18));
```


## Assessed type

Other
Flawed Access Control in `notDelegatedOrIsMinimalProxy` Modifier Allows Unauthorized Function Calls
Audit: 2024-07-basin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-basin/blob/main/src/WellUpgradeable.sol#L22-L31


# Vulnerability details

The `notDelegatedOrIsMinimalProxy` modifier is used to ensure that certain functions are not called through delegate calls and are only callable by certain contracts. The logic assumes that `address(this) != ___self` indicates a delegate call, but this may not be accurate for all minimal proxy patterns. This can lead to incorrect access control and function usage.

## Impact
This could lead to unauthorized access or execution of critical functions if the modifier does not correctly identify delegate calls or minimal proxies.

## Proof of Concept
1.Deploy the `WellUpgradeable` contract as a minimal proxy.
2.Call functions protected by the `notDelegatedOrIsMinimalProxy` modifier from an unexpected context.
3.The modifier may not correctly enforce the intended restrictions.

## Tools Used
Manual Review 

## Recommended Mitigation Steps
1.Adjust the modifier to more accurately check for delegate calls and minimal proxies.
2.Use alternative mechanisms or patterns for access control to avoid reliance on potentially incorrect assumptions.


## Assessed type

Access Control
Misuse of UUPS Upgrade Pattern and Minimal Proxies in WellUpgradeable.sol
Audit: 2024-07-basin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-basin/blob/main/src/WellUpgradeable.sol#L65-L120


# Vulnerability details

## Impact

The contract `WellUpgradeable` has heavily modified the upgrade mechanism of the UUPS upgrade pattern for compatibility with the ERC-1167 minimal proxy pattern in hopes of achieving more gas-efficient contract creation. This has introduced significant complexity and potential security risks. 

In particular, the use of the implementation slot (`_IMPLEMENTATION_SLOT`) may be prone to attacks, where a malicious user can manipulate this value, leading to an incorrect contract being used as the target implementation.

Additionally, the `_authorizeUpgrade` method requires extensive and complex checks to ensure that updates are legitimately performed. This complexity could lead to accidental vulnerabilities. 

## Proof of Concept

The complicated upgrade mechanism is evident in these method implementations:

``` solidity
function _authorizeUpgrade(address newImplmentation) internal view override {...}

function upgradeTo(address newImplementation) public override {...}

function upgradeToAndCall(address newImplementation, bytes memory data) public payable override  {...}

function proxiableUUID() external view override notDelegatedOrIsMinimalProxy returns (bytes32) {...}
```


## Tools Used

Manual review

## Recommended Mitigation Steps

It is generally recommended to follow well-established and audited patterns and libraries for implementing upgradable contracts, such as the OpenZeppelin library. This reduces the complexity and risk of introducing new security vulnerabilities, and increases code maintainability and understandability.

If the approach in the contract is intended, it is crucial to ensure each part of the added complexity is thoroughly reviewed and tested, with the code's assumptions and constraints being clearly documented. 

Furthermore, custom upgrade logic should be designed to fail-close, i.e., refuse upgrades in any case that is not explicitly allowed, rather than attempting to enumerate and prevent all unsafe cases. 



## Assessed type

Upgradable
Potential Inaccurate Reserve Calculations Due To Hardcoded decimals
Audit: 2024-07-basin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-basin/blob/main/src/functions/Stable2.sol#L224


# Vulnerability details

## Vulnerability  Details

The calcReserveAtRatioSwap function calls `calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18))` to calculate the `scaledReserves[i]` value. However, the `calcReserve` function expects the `decimals` parameter to be the actual decimal values of the tokens in the well, not hardcoded 18.



## Code

```solidity
scaledReserves[i] = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18));
```

## Impact

If the reserve calculation is inaccurate, it can result in incorrect pricing and resource allocation within the well. This could lead to users experiencing losses when performing swaps or providing liquidity, as the contract may not be able to accurately track the true value of the assets.

## Poc

```solidity
function test_calcReserveAtRatioSwap() public {
    uint256[] memory reserves = new uint256[](2);
    uint256[] memory ratios = new uint256[](2);

    reserves[0] = 1000 * 1e18; 
    reserves[1] = 2000 * 1e6; 
    ratios[0] = 1 * 1e6; 
    ratios[1] = 2 * 1e6;

    // Encode well data with different decimal values
    _data = abi.encode(18, 6);

    uint256 reserve = _function.calcReserveAtRatioSwap(reserves, 1, ratios, _data);
    uint256 expectedReserve = 1000 * 1e6; // 1000 tokens with 6 decimals
    assertEq(reserve, expectedReserve, "Incorrect reserve calculated");
}
```

## Mitigation

The correct way to calculate `scaledReserves[i]` would be to use the `decodeWellData` function to get the actual decimal values of the tokens, and then use those values when calling `calcReserve`.

The corrected code would look like this:

```solidity
uint256[] memory decimals = decodeWellData(data);
uint256 lpTokenSupply = calcLpTokenSupply(scaledReserves, abi.encode(decimals[0], decimals[1]));
scaledReserves[i] = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(decimals[0], decimals[1]));
```


## Assessed type

Decimal
`calcReserve` calculates `b` incorrectly
Audit: 2024-07-basin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L124


# Vulnerability details

## Impact
In `calcReserve` the `b` variable is calculated incorrectly, as a result, its value will be lower than intended, causing `calcReserve`, `calcReserveAtRatioSwap` and `_calcRate` to return inaccurate data.

## Proof of Concept
Here is how `b` is currently calculated in the `getBandC` function:
```solidity
b = reserves + ((lpTokenSupply * A_PRECISION) / Ann);
```
The issue is that the value passed as the `reserves` parameter, when `getBandC` is called, is either one of the two `scaledReserves`:
```solidity
(uint256 c, uint256 b) = getBandC(a * N * N, lpTokenSupply, j == 0 ? scaledReserves[1] : scaledReserves[0]);
```

This is an issue, due to the fact that in the Curve implementation of the function, which should operate the same, the value used in place of `reserves` is the sum of the reserves:
https://github.com/curvefi/curve-stablecoin/blob/176e637a4b82874c49c90883ee1afc7e317ca051/contracts/Stableswap.vy#L708C1-L719C51
```solidity
    for _i in range(N_COINS_128):
        if _i == i:
            _x = x
        elif _i != j:
            _x = xp[_i]
        else:
            continue
        S_ += _x
        c = c * D / (_x * N_COINS)

    c = c * D * A_PRECISION / (Ann * N_COINS)
    b: uint256 = S_ + D * A_PRECISION / Ann  # - D
```

Here we see that `S_` (plays the same role as `reserves` in `getBandC`), is a sum of `_x`(single reserve).

Therefore, the value of `b` will always be smaller than intended, causing `_calcReserve` to return a larger value(the smaller the number you divide by the bigger the result). As a result of that, `calcReserve` will always return larger values than what is correct.

## Tools Used
Manual review

## Recommended Mitigation Steps
Supply the sum of both `scaledReserves` in `getBandC`.


## Assessed type

Other