xanoutas/solidity-security-findings huggingface dataset:
Incorrect decimals check in `decodeWellData` function
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


# Vulnerability details

## Impact
The `decodeWellData` function is intended to correctly set the decimals for tokens. The function has logic to set default decimal values for tokens (18), if the provided data shows 0 for any of the tokens. However, the implementation contains a mistake in its conditional checks for default values. The function incorrectly rechecks `decimal0` instead of `decimal1`, which prevents `decimal1` from being set to 18 if it is initially 0. This results in `decimal1` potentially remaining 0, leading to incorrect decimal values being used in further operations or calculations.
## Proof of concept
Paste the following test into `Stable2.t.sol`:
```solidity
function test_incorrectAssignDefaultDecimals() public {

        address lut = address(new Stable2LUT1());
        Stable2 stable2 = new Stable2(address(lut));
  
        uint256[] memory decimals = stable2.decodeWellData(abi.encode(0, 0));

        assertEq(decimals[0], 18);
        assertEq(decimals[1], 0);
    }
```
## Recommended mitigation steps
Update the code as follows, to ensure that both `decimal0` and `decimal1` are correctly set to 18 if they are initially 0:
```diff
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) {
+       if (decimal1 == 0) {
            decimal1 = 18;
        }
        if (decimal0 > 18 || decimal1 > 18) revert InvalidTokenDecimals();

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








## Assessed type

Invalid Validation
Missing Access Control in `upgradeTo` Function
Audit: 2024-07-basin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

**Contract Code: WellUpgradeable.sol#L93-L96**

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


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

```

**Issue Summary:**
The `upgradeTo` and `upgradeToAndCall` function lacks proper access control modifiers, which may allow unauthorized users to perform an upgrade operation. This poses a significant security risk as it enables anyone to upgrade the contract implementation, potentially leading to malicious activities or unintended behaviors.

**Detailed Description:**

1. **Function Purpose:**
   - The `upgradeTo` function is intended to upgrade the contract's implementation to a new address (`newImplementation`). It calls the internal function `_authorizeUpgrade` to verify authorization and then performs the upgrade using `_upgradeToAndCallUUPS`.

2. **Missing Access Control:**
   - The function is marked as `public`, meaning it can be called by any address. Without proper access control, any user can trigger the upgrade process, which can lead to:
     - Unauthorized upgrades to the contract implementation.
     - Potential introduction of malicious code or vulnerabilities in the contract.

**Additional Notes:**
- Ensure that the `_authorizeUpgrade` function performs necessary checks to confirm that the caller is authorized to perform the upgrade.
- Review and adhere to best practices for upgradeable contracts, such as those outlined by OpenZeppelin's upgradeable contracts library.

## Tools Used

 Manual review 

## Recommended Mitigation Steps

```solidity
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyContract is Ownable {
    function upgradeTo(address newImplementation) public override onlyOwner {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallUUPS(newImplementation, new bytes(0), false);
    }
}
```





## Assessed type

Access Control
Unauthorized Contract Upgrade Vulnerability
Audit: 2024-07-basin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The identified vulnerability in the `_authorizeUpgrade` function of the `UUPSUpgradeable` contract allows unauthorized users to upgrade the contract. Without proper access control, malicious actors can upgrade the contract to a malicious implementation, potentially leading to severe consequences including:

* Loss of Funds: Unauthorized users can divert funds to their own addresses.
* Data Manipulation: Unauthorized changes to the contract logic can manipulate stored data, impacting the integrity and reliability of the contract.
* Denial of Service: Malicious upgrades could introduce bugs or vulnerabilities, making the contract unusable.

## Proof of Concept
```solidity
function _authorizeUpgrade(address newImplmentation) internal view override {
        // verify the function is called through a delegatecall.
        require(address(this) != ___self, "Function must be called through delegatecall");

        // verify the function is called through an active proxy bored by an aquifer.
        address aquifer = aquifer();
        address activeProxy = IAquifer(aquifer).wellImplementation(_getImplementation());
        require(activeProxy == ___self, "Function must be called through active proxy bored by an aquifer");

        // verify the new implmentation is a well bored by an aquifier.
        require(
            IAquifer(aquifer).wellImplementation(newImplmentation) != address(0),
            "New implementation must be a well implmentation"
        );

        // verify the new implmentation is a valid ERC-1967 implmentation.
        require(
            UUPSUpgradeable(newImplmentation).proxiableUUID() == _IMPLEMENTATION_SLOT,
            "New implementation must be a valid ERC-1967 implmentation"
        );
    }
```

## Tools Used

## Recommended Mitigation Steps

To mitigate this vulnerability, it is essential to implement access control in the `_authorizeUpgrade` function to restrict upgrades to only authorized users. 
Use `onlyOwner` access modifier from OpenZeppelin


## Assessed type

Access Control
Incorrect Decimal Validation in decodeWellData Function Leading to Inaccurate Calculations and Overflows
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


# Vulnerability details

## Impact
The decodeWellData function contains an erroneous check for the second decimal value, which incorrectly checks decimal0 again instead of decimal1. This logic flaw can lead to unintended behavior in functions relying on accurate decimal values, such as calcLpTokenSupply and others. Specifically, this mistake can cause the function to incorrectly assume the decimal places for tokens, potentially leading to inaccurate calculations and overflows in the LP token supply calculations.

## Proof of Concept
None

## Tools Used
Manual review

## Recommended Mitigation Steps

    // if well data returns 0, assume 18 decimals.
    if (decimal0 == 0) {
        decimal0 = 18;
    }
    if (decimal1 == 0) {  // Corrected check
        decimal1 = 18;
    }


## Assessed type

Invalid Validation
Wrong code
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


# Vulnerability details

## Impact
Wrong variable Code

## Proof of Concept
in decodeWellData function of Stable2.sol file
    if (decimal0 == 0) {
        decimal0 = 18;
    }
    if (decimal0 == 0) { //wrong variable
        decimal1 = 18;
    }

## Recommended Mitigation Steps
    if (decimal0 == 0) { 
        decimal1 = 18;
    }

    It needs to be decimal1 == 0 in compare code like following.
    if (decimal1 == 0) { 
        decimal1 = 18;
    }


## Assessed type

Invalid Validation
Missing `onlyOwner` modifier on `_authorizeUpgrade` allows anyone to change underlying well 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

Well can be upgraded by anyone to any implementation bored(deployed) by the Aquifer, because `_authorizeUpgrade` has no access control

## Proof of Concept

From the openzeppelin docs 
```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;
```

However , from `WellUpgradeable` implementation, neither `_authorizeUpgrade` nor `upgradeToAndCall` nor `upgradeTo` have any access control/modifiers that prevents the contract from being upgraded by anyone. `_authorizeUpgrade` does require that the `newImplementation` be bored by the `Aquifer` ,but this is also a permissionless process. As a result anyone can bore a well and upgrade an existing to it.

## Tools Used

Manual Review

## Recommended Mitigation Steps

```diff
-function _authorizeUpgrade(address newImplmentation) internal view override {
+function _authorizeUpgrade(address newImplmentation) internal view override onlyOwner {
        // verify the function is called through a delegatecall.
        require(address(this) != ___self, "Function must be called through delegatecall");

        // verify the function is called through an active proxy bored by an aquifer.
        address aquifer = aquifer();
        address activeProxy = IAquifer(aquifer).wellImplementation(_getImplementation());
        require(activeProxy == ___self, "Function must be called through active proxy bored by an aquifer");

        // verify the new implmentation is a well bored by an aquifier.
        require(
            IAquifer(aquifer).wellImplementation(newImplmentation) != address(0),
            "New implementation must be a well implmentation"
        );

        // verify the new implmentation is a valid ERC-1967 implmentation.
        require(
            UUPSUpgradeable(newImplmentation).proxiableUUID() == _IMPLEMENTATION_SLOT,
            "New implementation must be a valid ERC-1967 implmentation"
        );
    }
```



## Assessed type

Access Control
incorrect check in decoding well data via `decodeWellData` can lead to wrong scaling of reserves and wrong calculations
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


# Vulnerability details

## Impact

`Stable2::decodeWellData` incorrectly assumes the decimal precision of `decimal1` based on provided value for `decimal0`.If this not the case (i.e. decimal1 != decimal0) then the reserves will be incorrectly scaled and well calculations will be incorrect.

## Proof of Concept

```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) {
            decimal1 = 18;
        }
        if (decimal0 > 18 || decimal1 > 18) revert InvalidTokenDecimals();

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

From the above code snippet, we can see that the value of `decimal0` is used to set `decimal1`. This can be an issue when `decimal0` is 0 but `decimal1` is not ,or `decimal1` is 0 but `decimal0` is not, as token reserves will be incorrectly scaled leading to wrong calculations.

## Tools Used

Manual Review

## Recommended Mitigation Steps

```diff
 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) {
+       if (decimal1 == 0) {
            decimal1 = 18;
        }
        if (decimal0 > 18 || decimal1 > 18) revert InvalidTokenDecimals();

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



## Assessed type

Decimal
Delegate Call Vulnerability in WellUpgradeable.sol
Audit: 2024-07-basin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

There is a potential Delegate Call vulnerability in the `WellUpgradeable` contract. 

This vulnerability occurs due to the use of delegatecall in the implementation of the proxy contract which has the potential to change the state of the calling contract. The dangerous part about delegatecall is that if the target function isn't careful about what state variables it manipulates or how it manipulates them, it could compromise the calling contract's state.

In this contract, the `_authorizeUpgrade()` function is using delegatecall to change the state of the current implementation. This could potentially allow an attacker to change the state of the contract in a way that wasn't intended, compromising the contract's security.

Furthermore,  the developer needs to understand that delegatecall is potentially unsafe since it gives the target contract complete control over the calling contract's state. The function is obscured into believing that the call was made to itself, and it operates on msg.values as if they were its own. This vulnerability only gets worse if it is combined with another bug or oversights in permissions such as unprotected internal calls or external calls to untrusted contracts. 

## Proof of Concept

Affected code snippet:

```solidity
 function _authorizeUpgrade(address newImplmentation) internal view override {
    require(address(this) != ___self, "Function must be called through delegatecall");
    ...
 }
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

One way to mitigate the vulnerability is to only allow certain trusted contracts to make delegate calls. This can be done by maintaining a list of trusted contract addresses, and checking the msg.sender before performing a delegate call. 

Here is how:

```solidity
mapping (address => bool) trustedContracts;

function addTrustedContract(address contract) external onlyOwner {
    trustedContracts[contract] = true;
}

function removeTrustedContract(address contract) external onlyOwner {
    trustedContracts[contract] = false;
}

function _authorizeUpgrade(address newImplmentation) internal view override {
    require(trustedContracts[msg.sender], "Caller is not trusted");
    require(address(this) != ___self, "Function must be called through delegatecall");
    ...
}
```
Furthermore, the best workaround for ERC-1167 minimal proxies is to make them point to another proxy and not to an actual implementation so that the proxy can control the implementation address. 

Lastly, the developer should always ensure that functions using delegatecall are properly protected (for example, using an `onlyOwner` or `onlyAdmin` modifier), to prevent external malicious contracts from exploiting its mechanism to modify the state of the contract. 


## Assessed type

call/delegatecall
StableSwap pricing for some wells would be wrong
Audit: 2024-07-basin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Proof of Concept

Take a look at https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L309-L325

```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) {
            decimal1 = 18;
        }
        if (decimal0 > 18 || decimal1 > 18) revert InvalidTokenDecimals();

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

This function is used to decode the data encoded in the well and it then returns an array of the decimals of the tokens in the well.

Issue however is that the `if (decimal0 == 0)` check is repeated twice, which then means that no checks are applied on `decimal1` and as such would cause for the wrong array to be returned in the case where `deciimal0 != decimal1`.

Now would be key to note that the array being returned is crucial to the calculation logics present in `Stable2.sol`, which then means that all the instances linked below could then have their calculations with the wrong array in the case where `deciimal0 != decimal1`.

- [Stable2#calcLpTokenSupply()](https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L79)
- [Stable2#calcReserveAtRatioSwap()](https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L181)
- [Stable2#calcReserveAtRatioSwap()](https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L181)
- [Stable2#calcReserveAtRatioLiquidity()](https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L254)

## Impact

As hinted under _Proof of Concept_, in the case where `deciimal0 != decimal1` crucial calculations would be done on wrong data, which goes ahead to heavier impact accross the whole protocol.

To take one of the instances, i.e `calcLpTokenSupply()`, this then means that whe reserves are provided, the amount of lp tokens attahced to this reserve would be wrong and the `calcLpTokenSupply()` is used all around scope with multiple instances even in the core well itself:

<details>
<summary>src/Well.sol</summary>

```solidity
  473
  474:         lpAmountOut = _calcLpTokenSupply(wellFunction(), reserves) - totalSupply();
  475          if (lpAmountOut < minLpAmountOut) {

  498          }
  499:         lpAmountOut = _calcLpTokenSupply(wellFunction(), reserves) - totalSupply();
  500      }

  612
Incorrect Decimal Checking in the `Stable2::decodeWellData()` function
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


# Vulnerability details

## Impact

The current logic incorrectly checks `decimal0` instead of `decimal1`. This mistake could cause unintended consequences in scenarios where the value of `decimal1` needs to be checked and updated accordingly.

## Proof of Concept

In the `Stable2::decodeWellData()` function, there are conditional checks to set default values (decimal 18) when either of the decimal values is zero.

```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) {
            decimal1 = 18;
        }
        ... ...
    }
```

However, the function checks `decimal0` twice instead of checking `decimal1`, which may result in `decimal1` being incorrectly set to 18.

Therefore, in case that reserve token 1 has non-18 decimals, the funtionality of the pool will be ruined due to the whole incorrect calculations.


## Tools Used

Manual Review

## Recommended Mitigation Steps

`decimal1` should be checked in the function:
```diff
    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) {
+       if (decimal1 == 0) {
            decimal1 = 18;
        }
        ... ...
    }
```


## Assessed type

ERC20
Decimal Handling Flaw in decodeWellData Function Leads to Potential Miscalculations
Audit: 2024-07-basin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
A critical bug has been identified in the `decodeWellData` function that compromises the handling of token decimals, particularly for the second token in a pair. This function is intended to standardize decimal place representation, but it fails to correct the second token's decimals when set to zero. The consequences of this error cascade through several key functions:

1. `calcRate`: It would give the wrong exchange rates between tokens.

2. `calcLpTokenSupply`: It could give wrong information about how much liquidity is in the pool.

3. `getScaledReserves`: It might make token amounts look way bigger than they really are.

```scaledReserves[1] = reserves[1] * 10 ** (18 - 0) = reserves[1] * 1e18```

4. `calcReserve`: It might calculate token reserves incorrectly and could even cause math errors.


These mistakes could lead to big problems with how tokens are valued and how the whole system works.

## Proof of Concept
The problem is in this part of the code:

```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) {
            decimal1 = 18;
        }
        if (decimal0 > 18 || decimal1 > 18) revert InvalidTokenDecimals();

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

The second `if` statement checks `decimal0` again instead of checking `decimal1`. This means it never fixes `decimal1` when it's zero.

## Tools Used
Manual review

## Recommended Mitigation Steps
To fix this, change the code to:

```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) {
+   if (decimal1 == 0) {
        decimal1 = 18;
    }
    if (decimal0 > 18 || decimal1 > 18) revert InvalidTokenDecimals();

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

This change makes sure both tokens are handled correctly, which will keep all the calculations in the system accurate.


## Assessed type

Decimal