xanoutas/solidity-security-findings huggingface dataset:
Incorrect calculations across multiply functions due to wrong decimal assignment can be harnessed by an attacker to steal from the protocol.
Audit: 2024-07-basin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
On the call [`decodeWellData()`](https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L310C1-L325C6), the code line checks the wrong condition before it sets up the decimal for `token1`, allowing for a incorret decimal to be used for `token1`, this i'll affect the following critical functions in turn since the rely on the `decodeWellData()` 
- `calcReserveAtRatioLiquidity()`.
- `calcReserveAtRatioSwap()`.
- `calcRate()`
- `calcReserve()`.
- `calcLpTokenSupply()`.

These functions are view but the are used in the core protocol function when making calculation for either swaps, adding of liquidity and many more in `wells.sol` if the wrong decimal for a token is used in the process the protocol can be broken, or exploited.

> Please check code snippet for detailed info.

https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L310C1-L325C6
```solidity
310:      function decodeWellData(bytes memory data) public view virtual returns (uint256[] memory decimals) {
311:        (uint256 decimal0, uint256 decimal1) = abi.decode(data, (uint256, uint256));
312:
313:        // if well data returns 0, assume 18 decimals.
314:        if (decimal0 == 0) {
315:            decimal0 = 18;
316:        }
//@audit checks the `decimal0` instead of `decimal1` leading to incorrect assignment
317:        if (decimal0 == 0) {
318:            decimal1 = 18;
319:        }
320:        if (decimal0 > 18 || decimal1 > 18) revert InvalidTokenDecimals();
321:
322:        decimals = new uint256[](2);
323:        decimals[0] = decimal0;
324:        decimals[1] = decimal1;
325:    }
```
The issue stems from checking the decimal of `token0` in `Line-317` before setting the decimal to `18` in the next line, supposing the `token1` is of a different decimal due to checking `token0` if `token0` is of `0` is explicitly sets `18` as the decimal for the two tokens even when `token1` is of a different decimal.

Example of Issue.
- The two token data to be decoded is ETh and USDC.
- Eth is of zero decimal as per the `data` and USDC is of 6 decimal as per the data.
- On the call to `decodeWellData()` the decimal of USDC is made to be 18 instead of left to be 6, leading to incorrect scaling and calculations in underlying calculations.
- Supposing a swap, the user will get more USDC token than they're supposed to recieve in this scenario.

## 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;
Incorrect decimal interpretation in the function `decodeWellData` leads to incorrect results
Audit: 2024-07-basin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The `decodeWellData` function in the `Stable2` contract contains a critical bug where it mistakenly checks `decimal0` twice instead of checking both `decimal0` and `decimal1`. This oversight means that if `decimal1` is `0`, it won't be correctly set to `18`, potentially leading to incorrect scaling of token reserves.

This error affects the scaling of token reserves, which is crucial for accurate calculations throughout the contract. Functions like `calcLpTokenSupply`, `calcReserve`, and `calcRate` depend on correctly scaled reserves. An error in scaling could lead to incorrect LP token supply, miscalculated reserves, and unfair exchange rates, destabilizing the pool and putting user funds at risk.


## 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;
    }
```

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

Error
Anyone can upgrade the implementation of WellUpgradeable
Audit: 2024-07-basin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
An attacker can frontrun an upgrade of the WellUpgradeable contract, this is due to not enough access control implemented in the `_authorizeUpgrade` function.

The `_authorizeUpgrade` function checked for 
- delegate call verification
- The aquifer proxy should be same as the proxy called on upgrade
- and the implementation contract to be non-zero

There was no check for
- implementation owner matching previous owner of well implementation or owner of wellUpgradeable Proxy despite having OwnableUpgradeable extended on the contract

This results in anyone deploying a malicious implementation and upgrading it using the proxy to seize control of the contract, cause DOS, and/or steal funds.

It requires specialty, speed and accurate timing hijack the implementation so it is medium likely

## Proof of Concept
Add and run this code in the WellUpgradeable.t.sol test contract

```solidity
function testBypassUpgradeToNewImplementation() public {
        address attacker = makeAddr("attacker");
        IERC20[] memory tokens = new IERC20[](2);
        tokens[0] = new MockToken("BEAN", "BEAN", 6);
        tokens[1] = new MockToken("WETH", "WETH", 18);
        Call memory wellFunction = Call(wellFunctionAddress, abi.encode("2"));
        Call[] memory pumps = new Call[](1);
        pumps[0] = Call(mockPumpAddress, abi.encode("2"));
        // create new mock Well Implementation:

        vm.startPrank(attacker);
        // back mock upgradeable implementation
        address wellImpl = address(new MockWellUpgradeable());
        WellUpgradeable well2 =
            encodeAndBoreWellUpgradeable(aquifer, wellImpl, tokens, wellFunction, pumps, bytes32(abi.encode("2")));
        vm.label(address(well2), "upgradeableWell2");

        // well2 is a new implementation that hold malcious code and can be used to DOS or funds stealing
        WellUpgradeable proxy = WellUpgradeable(payable(proxyAddress));
        proxy.upgradeTo(address(well2));
        //confirm owner is still initial owner, while attacker was able to upgrade the implementation
        assert(attacker != MockWellUpgradeable(proxyAddress).owner());
        assertEq(initialOwner, MockWellUpgradeable(proxyAddress).owner());
        // verify proxy was upgraded.
        assertEq(address(well2), MockWellUpgradeable(proxyAddress).getImplementation());
        assertEq(1, MockWellUpgradeable(proxyAddress).getVersion());
        assertEq(100, MockWellUpgradeable(proxyAddress).getVersion(100));
        vm.stopPrank();
    }
```

```bash
forge test --mt testBypassUpgradeToNewImplementation
```

## Tools Used

## Recommended Mitigation Steps

Add an additional check with `_checkOwner()` or wellImplementation.owne
Incorrect `if` condition is used in the `decodeWellData()` function in `Stable2` contract
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

An incorrect `if` condition is used in the `decodeWellData()` function in `Stable2` contract, where `decimal0` is checked instead of `decimal1` before assigning a value to `decimal1`. This oversight can lead to incorrect scaling of reserves, which could have significant downstream effects on the functionality of the contract.

## Impact
The issue in the `decodeWellData()` function where `decimal0` is incorrectly checked instead of `decimal1` has significant consequences. This function is responsible for decoding and validating the token decimal values used throughout the contract. The error can lead to the following issues:

- **Incorrect Scaling of Reserves:** The `getScaledReserves` function scales reserves to 18 decimals using `decimal1`. If `decimal1` is zero, this scaling will fail, resulting in incorrect reserve values being used in calculations.

- **Faulty LP Token Supply Calculation:** The `calcLpTokenSupply` function uses these scaled reserves to calculate the supply of LP tokens. Incorrect reserve values could lead to an inaccurate supply calculation, impacting liquidity provision and withdrawal.

- **Inaccurate Rate Calculations:** The `calcRate` function also depends on correctly scaled reserves. With incorrect scaling, the rate between the tokens in the pool could be inaccurately calculated, leading to potential losses for users engaging in swaps.

- **Price Inaccuracy in `calcReserveAtRatioSwap` and `calcReserveAtRatioLiquidity`:** Both functions rely on accurate reserve scaling to ensure the correct target price is reached. A zero value in `decimal1` could result in improper adjustments, leading to price slippage or even reversion of transactions.


## Proof of Concept
In the `decodeWellData()` function, the logic to ensure that `decimal1` defaults to 18 decimals if it is zero is incorrect. The current implementation mistakenly checks `decimal0` instead of `decimal1`:

```solidity
if (decimal0 == 0) { // @audit should be decimal1 == 0
    decimal1 = 18;
}
```
https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L317

This means that `decimal1` will only be set to `18` if `decimal0` is zero, which is not the intended behavior. If `decimal1` is zero but `decimal0` is not, `decimal1` will remain zero, leading to incorrect scaling in downstream calculations.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Change the `if` condition to check `decimal1` instead of `decimal0`:

```diff
-   if (decimal0 == 0) {
+   if (decimal1 == 0) {
        decimal1 = 18;
    }
```


## Assessed type

Invalid Validation
Stable2::decodeWellData() - Incorrect check leads to return of wrong decimal precision of decimal1 inside decimals array
Audit: 2024-07-basin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
On decoding the encoded well data in [Stable2::decodeWellData()](https://github.com/code-423n4/2024-07-basin/blob/main/src/functions/Stable2.sol#L317-L319) decimals array will be returned containing values of decimal0 and decimal1 but within this function when abi.decode() returns any of these value as 0 then 18 decimals should be assumed and set before returning array but in this case due to incorrect check decimals array is being returned with `decimal1 = 0`, due to this when this array used in other functions for scaling reserves by precision then it will scale reserves[1] with wrong decimal precision.

## Proof of Concept
`// if well data returns 0, assume 18 decimals.`
        `if (decimal0 == 0) {`
            `decimal0 = 18;`
        `}`
        `if (decimal0 == 0) {  // @ audit - wrong check`
            `decimal1 = 18;`
        `}`
Here in [Stable2::decodeWellData()](https://github.com/code-423n4/2024-07-basin/blob/main/src/functions/Stable2.sol#L317-L319) if `decimal0 != 0` and `decimal1 == 0` then here decimal array will contain `decimal1 = 0` but in actual it should assume decimal1 as 18 decimals and set `decimal1 = 18` before returning decimals array.
## Tools Used
Manual review
## Recommended Mitigation Steps
`// if well data returns 0, assume 18 decimals.`
        `if (decimal0 == 0) {`
            `decimal0 = 18;`
        `}`
         `if (decimal1 == 0) {  // @ correct `
            `decimal1 = 18;`
        `}`





## Assessed type

Decimal
Incorrectly checking for `decimal1` with `decimal0`.
Audit: 2024-07-basin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

In the `Stable2.sol` contract the `decodeWellData` function responsible for decoding and validating token decimals from input data. And the function is intended to decode token decimals from the provided data and set default values if they are zero. However the function mistakenly checked `decimal0` twice, which could lead to incorrect handling of `decimal1` if it is set to 0.

## Impact
`decimal1` might incorrectly set to 0 instead of 18.

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

## Tools Used
Manual review.

## Recommended Mitigation Steps
Consider replace `decimal0` to `decimal1` in below 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;
        }
        if (decimal0 > 18 || decimal1 > 18) revert InvalidTokenDecimals();

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


## Assessed type

Decimal
Absence of `onlyOwner` modifier in `upgradeTo` and `upgradeToAndCall` 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
https://github.com/code-423n4/2024-07-basin/blob/main/src/WellUpgradeable.sol#L104
https://github.com/code-423n4/2024-07-basin/blob/main/src/WellUpgradeable.sol#L65


# Vulnerability details

The `WellUpgradeable.sol` contract is a upgradeable version of `Well` contract , and it uses two function `upgradeTo` & `upgradeToAndCall` .The `upgradeTo` function is used to upgrade the implementation of the proxy to a new version , where the `upgradeToAndCall` function is similar to `upgradeTo` but includes additional functionality to execute a function call on the new implementation immediately after upgrading.  However both functions lack `onlyOwner` modifier and along with that the `onlyOwner` modifier is not also adjusted in `_authorizeUpgrade` function, which means that these actions can be performed by anyone, not just the contract owner.

## Impact
Now the anybody has ownership they are able to perform an upgrade of the implementation contract's logic contract.

## Proof of Concept
https://github.com/code-423n4/2024-07-basin/blob/main/src/WellUpgradeable.sol;

The modifier is also missing in the `_authorizeUpgrade` function , which does not satisfy UUPSUpgradeable proxy pattern.

```javascript
 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"
        );
    }
```

You can check here - [_authorizeUpgrade](https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable-_authorizeUpgrade-address-)

## Tools Used
Manual review.

## Recommended Mitigation Steps
Consider adding `onlyOwner` modifier on both functions. Or adding modifier on `_authorizeUpgrade` function.




## Assessed type

Access Control
[H-1] Incorrect check while decoding decimals of provided pair of tokens
Audit: 2024-07-basin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Description
The function `decodeWellData()` is utilized throughout the codebase to decode decimals from the provided `data` bytes. This process is crucial for standardizing tokens with varying decimals to a uniform 1e18 scale, which is done by `getScaledReserves()`. While the function correctly adjusts a token's decimal to 18 if its value is zero, it fails to properly check the decimal value of the **second token** due to an incorrect comparison.
``` solidity
function decodeWellData(/*...*/) public view virtual returns (/*...*/) {
        //....
        //@audit Incorrect comparison, correct statement should be `decimal1 == 0`
        if (decimal0 == 0) {
            decimal1 = 18;
        }
        //....
    }
```

## Impact
This issue causes incorrect calculations whenever a token with a zero decimal is evaluated as the second token. This could lead to significant discrepancies in the output values, potentially affecting the overall accuracy of operations that rely on this function.

## Proof of Concept
Below are the test results from `Stable2.t.sol`, illustrating the discrepancy between expected and actual values when the second token’s decimal is set to zero.
``` solidity
function test_calcLpTokenSupply_diffDecimals()
//Keeping rest every variable same
//ActualValue = 398_000_083_749_971_996_546_125
//ExpectedValue = 3_174_550_119_730_348
```
Function like `testFuzz_calcLpTokenSupply()`, `testFuzz_stableSwap()` will also fail while testing with zero value decimal.

## Tools Used
Manual review and Fuzz testing

## Recommended Mitigation Steps
Replace the incorrect `if` statement with the correct condition: `decimal1 == 0`. This ensures that the second token's decimal value is correctly adjusted when it is zero.


## Assessed type

Invalid Validation
Incorrect Condition 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#L314-L318


# Vulnerability details

The `decodeWellData` function is supposed to decode the decimals of two tokens from the provided data. 

If the decoded values are 0, it should default them to 18. 

However, the function mistakenly checks `decimal0` twice, potentially leaving `decimal1` unchanged if it is 0.

## Impact
This logical error can lead to incorrect defaulting of `decimal1` if it is 0, causing potential inaccuracies in subsequent calculations that rely on the token decimals. 

This might affect the stability and reliability of the contract's functions.

## Proof of Concept
The current implementation of the `decodeWellData` function incorrectly checks `decimal0` twice:

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

This should be corrected to:

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


## Tools Used
Manual Review

## Recommended Mitigation Steps
Correct the condition in the `decodeWellData` function to check `decimal1` instead of `decimal0` in the second condition.


## Assessed type

Error
`notDelegatedOrIsMinimalProxy` reverts incorrectly
Audit: 2024-07-basin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The `notDelegatedOrIsMinimalProxy` modifier is used in the `proxiableUUID` function. The issue is that it has been implemented incorrectly, causing it to revert in many cases. As a result, it will not be possible to upgrade a Well.

## Proof of Concept
Here is how the modifier is implemented:
```solidity
    modifier notDelegatedOrIsMinimalProxy() {
        if (address(this) != ___self) {
            address aquifer = aquifer();
            address wellImplmentation = IAquifer(aquifer).wellImplementation(address(this));
            require(wellImplmentation == ___self, "Function must be called by a Well bored by an aquifer");
        } else {
            revert("UUPSUpgradeable: must not be called through delegatecall");
        }
        _;
    }
```

As we can see if it has not been called through a delegatecall it will revert (reverts if `address(this) == ___self`). This is likely a mistake, due to the revert statement stating: `"UUPSUpgradeable: must not be called through delegatecall"`, which is also incorrect as in reality it can only be called through a delegatecall.

Furthermore, when a Well gets upgraded and `_authorizeUpgrade` is called, a normal call(not a delegatecall) is made to `proxiableUUID`, thus invoking the `notDelegatedOrIsMinimalProxy`. As the call is not a delegatecall the modifier will instantly revert, causing the upgrade of the Well to fail.

## Tools Used
Manual review

## Recommended Mitigation Steps
Perhaps, removing the revert in the `else` statement will be a sufficient fix.


## Assessed type

Other