xanoutas/solidity-security-findings huggingface dataset:
WETH not recognized as Native when using X2Y2 adapter
Audit: 2022-11-paraspace-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/misc/marketplaces/X2Y2Adapter.sol#L65


# Vulnerability details

## Impact
When a customer sends ETH using X2Y2 as an adapter the ETH is wrapped and sent back to caller because item type is ERC20 not native. 
The transaction fails due to lack of ETH and the `vars.ethLeft` being set to zero. 

## Proof of Concept
A customer calls the buyWithCredit using X2Y2 as his adapter setting WETH as his currency, token == address(0) and sending ETH with the transaction
When `getAskOrderInfo` is called `ItemType` is set to `ItemType.ERC20` instead of Native
When `_depositETH` sees that there is ETH but ItemType is ERC20 it will refund the ETH to the customer
When `_delegateToPool` is called it will fail because  `vars.ethLeft` is set to zero.

## Recommended Mitigation Steps
Use a similar if statement thats in the LooksRare adapter that checks if token == address(0) and if so set `ItemType` to Native
ChainLink price calls a deprecated API
Audit: 2022-11-paraspace-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/misc/ParaSpaceOracle.sol#L128


# Vulnerability details

According to Chainlink’s documentation, the latestAnswer function is deprecated. This function might suddenly stop working if Chainlink stops supporting deprecated APIs. And the old API can return stale data.


## Proof of Concept
[ParaSpaceOracle.sol#L128](https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/misc/ParaSpaceOracle.sol#L128).
[UiIncentiveDataProvider.sol#L118, L179, L275, L342](https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/ui/UiIncentiveDataProvider.sol#L118).
[UiPoolDataProvider.sol#L221, L232, L245](https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/ui/UiPoolDataProvider.sol#L221).

## Recommended Mitigation Steps
Use the latestRoundData function to get the price instead. Add checks on the return data with proper revert messages if the price is stale or the round is incomplete.
https://docs.chain.link/docs/price-feeds-api-reference/
Pending Apecoin staking reward does not count towards the total collateral value when calculating account health factor.
Audit: 2022-11-paraspace-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/protocol/pool/PoolApeStaking.sol#L428
https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/protocol/pool/PoolApeStaking.sol#L439
https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/protocol/libraries/logic/GenericLogic.sol#L74


# Vulnerability details

## Impact

Pending Apecoin staking reward does not count towards the account health

## Proof of Concept

The account health factor is important because it is used to determine how much user can borrow and when the price drops, it is used to determine if the liquidation is allowed.

Let us look into the health factor calculation in GenericLogic

```solidity

        CalculateUserAccountDataVars memory vars;

        while (vars.i < params.reservesCount) {
            if (!params.userConfig.isUsingAsCollateralOrBorrowing(vars.i)) {
                unchecked {
                    ++vars.i;
                }
                continue;
            }

            vars.currentReserveAddress = reservesList[vars.i];

            if (vars.currentReserveAddress == address(0)) {
                unchecked {
                    ++vars.i;
                }
                continue;
            }

            DataTypes.ReserveData storage currentReserve = reservesData[
                vars.currentReserveAddress
            ];

            vars.reserveConfiguration = currentReserve.configuration;

            (
                vars.ltv,
                vars.liquidationThreshold,
                vars.liquidationBonus,
                vars.decimals,

            ) = currentReserve.configuration.getParams();

            unchecked {
                vars.assetUnit = 10**vars.decimals;
            }

            vars.xTokenAddress = currentReserve.xTokenAddress;

            if (
                vars.reserveConfiguration.getAssetType() ==
                DataTypes.AssetType.ERC20
            ) {
                vars.assetPrice = _getAssetPrice(
                    params.oracle,
                    vars.currentReserveAddress
                );

                if (
                    (vars.liquidationThreshold != 0) &&
                    params.userConfig.isUsingAsCollateral(vars.i)
                ) {
                    vars.userBalanceInBaseCurrency = _getUserBalanceForERC20(
                        params.user,
                        currentReserve,
                        vars.xTokenAddress,
                        vars.assetUnit,
                        vars.assetPrice
                    );

                    vars.payableDebtByERC20Assets += vars
                        .userBalanceInBaseCurrency
                        .percentDiv(vars.liquidationBonus);

                    vars.liquidationThreshold =
                        vars.userBalance
During oracle outages or feeder outages/disagreement, the `ParaSpaceFallbackOracle` is not used
Audit: 2022-11-paraspace-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/misc/ParaSpaceOracle.sol#L114-L136


# Vulnerability details

## Impact
If the feeders haven't updated their prices within the default six-hour time window, the floor oracle will revert when requests are made to get the current price, and these reverts are not caught by the wrapper oracle which handles the fallback oracle, so rather than using the fallback oracle in these cases, the calls will revert, leading to liquidations to fail (see my other submission for the full chain from the floor oracle to the liquidation function).

Additionally, the code uses the deprecated chainlink `latestAnswer()` API for its token requests, which may revert once it is no longer supported

## Proof of Concept
`getPrice()` will fail if the values are stale:
```solidity
File: /paraspace-core/contracts/misc/NFTFloorOracle.sol   #1

236      function getPrice(address _asset)
237          external
238          view
239          override
240          returns (uint256 price)
241      {
242 @>       uint256 updatedAt = assetPriceMap[_asset].updatedAt;
243          require(
244              (block.number - updatedAt) <= config.expirationPeriod,
245 @>           "NFTOracle: asset price expired"
246          );
247          return assetPriceMap[_asset].twap;
248:     }
```
https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/misc/NFTFloorOracle.sol#L236-L248

They can be stale due to too much price skew, or the feeders being down, e.g. due to another bug:
```solidity
File: /paraspace-core/contracts/misc/NFTFloorOracle.sol   #2

369          // config maxPriceDeviation as multiple directly(not percent) for simplicity
370          if (priceDeviation >= config.maxPriceDeviation) {
371              return false;
372:         }
```
https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/misc/NFTFloorOracle.sol#L369-L372

```solidity
File: /paraspace-core/contracts/misc/NFTFloorOracle.sol   #3

376      function _finalizePrice(address _asset, uint256 _twap) internal {
377          PriceInformation storage assetPriceMapEntry = assetPriceMap[_asset];
378          assetPriceMapEntry.twap = _twap;
379 @>       assetPriceMapEntry.updatedAt = block.number;
380          assetPriceMapEntry.updatedTimestamp = block.timestamp;
381          emit AssetDataSet(
382              _asset,
383              assetPriceMapEntry.twap,
384              assetPriceMapEntry.updatedAt
385          );
386:     }
```
https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/misc/NFTFloorOracle.sol#L376-L386

The wrapper oracle does not use a try-catch, so it can't swallow these reverts and allow the fallback oracle to be used:
```solidity
File: /paraspace-core/contracts/misc/ParaSpaceOrac
Retrieving Token Balances with balanceOf is dangerous
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L567


# Vulnerability details

## Impact
`MagicLP.sol` contract relies on the `balanceOf` function of the ERC20 tokens to keep track of the reserves within the contract. It uses the actual token balances for internal accounting rather than maintaining a separate ledger for `sellBase` and `sellQuote` function. Retrieving Token Balances with `balanceOf`  is dangerous as `balanceOf` may be gamed.
Future liquidity providers and traders will be affected because the pool's price and reserve ratios have been manipulated, potentially leading to arbitrage opportunities or loss of funds. 

## Proof of Concept
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L567-L579
```solidity
function _sync() internal {
        uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this));
        uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this));

        if (baseBalance != _BASE_RESERVE_) {
            _BASE_RESERVE_ = baseBalance.toUint112();
        }
        if (quoteBalance != _QUOTE_RESERVE_) {
            _QUOTE_RESERVE_ = quoteBalance.toUint112();
        }

        _twapUpdate();
    }
```
The above `_sync` function retrieve Token Balances by calling `balanceOf` function on the ERC20 token contract for both the `base and qoute tokens`. Then checks if the retrieved balances differ from the contract's internal reserve balances (`_BASE_RESERVE_ and _QUOTE_RESERVE_`) to update reserves. Finally, after updating the reserve balances, the function calls `_twapUpdate` to update the Time-Weighted Average Price (TWAP) of the base token.

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L545
The `_twapUpdate` function in the `MagicLP.sol` contract is responsible for updating the Time-Weighted Average Price (TWAP) of the base token.
```solidity
 function _twapUpdate() internal {
        uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32);
        uint32 timeElapsed = blockTimestamp - _BLOCK_TIMESTAMP_LAST_;

        if (timeElapsed > 0 && _BASE_RESERVE_ != 0 && _QUOTE_RESERVE_ != 0) {
            /// @dev It is desired and expected for this value to
            /// overflow once it has hit the max of `type.uint256`.
            unchecked {
                _BASE_PRICE_CUMULATIVE_LAST_ += getMidPrice() * timeElapsed;
            }
        }
```
Hence, `_sync()` in `MagicLP` contract relies on the `balanceOf` function of the ERC20 tokens to keep track of the reserves within the contract. It uses the actual token balances for internal accounting rather than maintaining a separate ledger for `sellBase` and `sellQuote` function. Retrieving Token Balances with `balanceOf`  is dangerous as `balanceOf` may be gamed.  This is evident in the `_sync` function and in `sellBase
Retrieving Token Balances with `balanceOf` is dangerous
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L567


# Vulnerability details

## Impact
`MagicLP.sol` contract relies on the `balanceOf` function of the ERC20 tokens to keep track of the reserves within the contract. It uses the actual token balances for internal accounting rather than maintaining a separate ledger for `sellBase` and `sellQuote` function. Retrieving Token Balances with `balanceOf`  is dangerous as `balanceOf` may be gamed.
Future liquidity providers and traders will be affected because the pool's price and reserve ratios have been manipulated, potentially leading to arbitrage opportunities or loss of funds. 

## Proof of Concept
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L567-L579
```solidity
function _sync() internal {
        uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this));
        uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this));

        if (baseBalance != _BASE_RESERVE_) {
            _BASE_RESERVE_ = baseBalance.toUint112();
        }
        if (quoteBalance != _QUOTE_RESERVE_) {
            _QUOTE_RESERVE_ = quoteBalance.toUint112();
        }

        _twapUpdate();
    }
```
The above `_sync` function retrieve Token Balances by calling `balanceOf` function on the ERC20 token contract for both the `base and qoute tokens`. Then checks if the retrieved balances differ from the contract's internal reserve balances (`_BASE_RESERVE_ and _QUOTE_RESERVE_`) to update reserves. Finally, after updating the reserve balances, the function calls `_twapUpdate` to update the Time-Weighted Average Price (TWAP) of the base token.

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L545
The `_twapUpdate` function in the `MagicLP.sol` contract is responsible for updating the Time-Weighted Average Price (TWAP) of the base token.
```solidity
 function _twapUpdate() internal {
        uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32);
        uint32 timeElapsed = blockTimestamp - _BLOCK_TIMESTAMP_LAST_;

        if (timeElapsed > 0 && _BASE_RESERVE_ != 0 && _QUOTE_RESERVE_ != 0) {
            /// @dev It is desired and expected for this value to
            /// overflow once it has hit the max of `type.uint256`.
            unchecked {
                _BASE_PRICE_CUMULATIVE_LAST_ += getMidPrice() * timeElapsed;
            }
        }
```
Hence, `_sync()` in `MagicLP` contract relies on the `balanceOf` function of the ERC20 tokens to keep track of the reserves within the contract. It uses the actual token balances for internal accounting rather than maintaining a separate ledger for `sellBase` and `sellQuote` function. Retrieving Token Balances with `balanceOf`  is dangerous as `balanceOf` may be gamed.  This is evident in the `_sync` function and in `sellBase
Loss of gas yield in BlastMagicLP and BlastCauldronV4
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastWrappers.sol#L42


# Vulnerability details

## Description

MIM on Blast accrues gas yield, therefore there are various wrappers written in the `blast` directory to accomodate for it. The design for MagicLP and CauldronV4 is to have an implementation contract, and clones which delegatecall into the parent contract.

An issue occurs because gas yield in Blast is given to the currently executing contract. In case the Clone calls the Implementation, gas accrues on the implementation (see [docs](https://docs.blast.io/building/guides/gas-fees#how-gas-fees-are-allocated)). Therefore, it's important to have the implementation contracts contain claiming logic, otherwise this represents a loss of yield.

In fact the BlastMagicLP contract only allows  claiming on the implementation:
```
function claimGasYields() external onlyClones onlyImplementationOperators returns (uint256) {
    address feeTo_ = BlastMagicLP(address(implementation)).feeTo();
    return BlastYields.claimMaxGasYields(feeTo_);
}
function claimTokenYields() external onlyClones onlyImplementationOperators returns (uint256 token0Amount, uint256 token1Amount) {
    address feeTo_ = BlastMagicLP(address(implementation)).feeTo();
    if (registry.nativeYieldTokens(_BASE_TOKEN_)) {
        token0Amount = BlastYields.claimAllTokenYields(_BASE_TOKEN_, feeTo_);
    }
    if (registry.nativeYieldTokens(_QUOTE_TOKEN_)) {
        token1Amount = BlastYields.claimAllTokenYields(_QUOTE_TOKEN_, feeTo_);
    }
}
```

Note the `onlyClones` modifier above.
In the BlastCauldronV4 contract, the claimables are only configured for the Clone:
```
constructor(address box_, address mim_, address governor_) CauldronV4(IBentoBoxV1(box_), IERC20(mim_)) {
    if (governor_ == address(0)) {
        revert ErrZeroAddress();
    }
    if (governor_ == address(this)) {
        revert ErrInvalidGovernorAddress();
    }
    _governor = governor_;
    _setupBlacklist();
}
function init(bytes calldata data) public payable override {
    if (_governor == address(this)) {
        revert ErrInvalidGovernorAddress();
    }
    _setupBlacklist();
    super.init(data);
    BlastYields.configureDefaultClaimables(_governor);
}
```

## Impact

Loss of yield which the rest of MIMswap is coded to capture.

## Proof of Concept
1. Users call MagicLP's swap(), generating fees
2. The MagicLP delegatecalls into IMPL, which generates most fees
3. The IMPL contract can never access their fees, as they are not configured.

## Tools Used

Manual audit

## Recommended Mitigation Steps

Add yield configuration and claiming logic to the implementation contracts as well.


## Assessed type

Context
Anyone making use of the MagicLP's TWAP to determine token prices will be exploitable.
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L564


# Vulnerability details

## Description

MagicLP provides a TWAP value which can be accessed via `_BASE_PRICE_CUMULATIVE_LAST_` 

It is updated in the function below:
```
function _twapUpdate() internal {
    uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32);
    uint32 timeElapsed = blockTimestamp - _BLOCK_TIMESTAMP_LAST_;
    if (timeElapsed > 0 && _BASE_RESERVE_ != 0 && _QUOTE_RESERVE_ != 0) {
        /// @dev It is desired and expected for this value to
        /// overflow once it has hit the max of `type.uint256`.
        unchecked {
            _BASE_PRICE_CUMULATIVE_LAST_ += getMidPrice() * timeElapsed;
        }
    }
    _BLOCK_TIMESTAMP_LAST_ = blockTimestamp;
}
```

It is updated by any function that changes the reserves, for example:
```
function _resetTargetAndReserve() internal returns (uint256 baseBalance, uint256 quoteBalance) {
    baseBalance = _BASE_TOKEN_.balanceOf(address(this));
    quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this));
    if (baseBalance > type(uint112).max || quoteBalance > type(uint112).max) {
        revert ErrOverflow();
    }
    _BASE_RESERVE_ = uint112(baseBalance);
    _QUOTE_RESERVE_ = uint112(quoteBalance);
    _BASE_TARGET_ = uint112(baseBalance);
    _QUOTE_TARGET_ = uint112(quoteBalance);
    _RState_ = uint32(PMMPricing.RState.ONE);
    _twapUpdate();
}

function _setReserve(uint256 baseReserve, uint256 quoteReserve) internal {
    _BASE_RESERVE_ = baseReserve.toUint112();
    _QUOTE_RESERVE_ = quoteReserve.toUint112();
    _twapUpdate();
}
```

The root cause of the issue is that the TWAP is updated *after* reserve changes. Since the TWAP multiplies the duration of time since the last update with the new reserves, an attacker has control over the registered price for the entire passed duration. 

For reference, the Uniswap and Beanstalk TWAPs are provided below:
- [UniswapV2](https://github.com/Uniswap/v2-core/blob/ee547b17853e71ed4e0101ccfd52e70d5acded58/contracts/UniswapV2Pair.sol#L79) - priceXCumulativeLast is written and then reserves are changed
- [Beanstalk](https://github.com/BeanstalkFarms/Basin/blob/e5441fc78f0fd4b77a898812d0fd22cb43a0af55/src/Well.sol#L209) - pumps are updated before the swap operation.

RareSkills details how TWAP operation works [here](https://www.rareskills.io/post/twap-uniswap-v2).

## Impact

Any application making use of the MagicLP's TWAP to determine token prices will be exploitable.

## Proof of Concept

1. At time T0, `_BASE_PRICE_CUMULATIVE_LAST_ = X.
2. Integrating contract records (T0,X)
3. T seconds pass, without swaps, meaning price remained X.
4. A contract queries `_BASE_PRICE_CUMULATIVE_LAST_` to execute a large swap(A->B). The swap could be done through MIMswap or any other AMM.
5. Attacker frontruns the query by inflating the A reserves with a large swap. New price is Y >> X.
6. Integratin
Attacker can amplify a rounding error in MagicLP to break the I invariant and cause malicious pricing
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L381


# Vulnerability details

## Description

One of the two key parameters in MagicLP pools is `I`, which is defined to be the ideal ratio between the two reserves. It is set during MagicLP initialization:
`_I_ = i;`

It is used when performing the initial LP deposit, in `buyShares()`:
```
if (totalSupply() == 0) {
    // case 1. initial supply
    if (quoteBalance == 0) {
        revert ErrZeroQuoteAmount();
    }
    shares = quoteBalance < DecimalMath.mulFloor(baseBalance, _I_) ? DecimalMath.divFloor(quoteBalance, _I_) : baseBalance;
    _BASE_TARGET_ = shares.toUint112();
    _QUOTE_TARGET_ = DecimalMath.mulFloor(shares, _I_).toUint112();
    if (_QUOTE_TARGET_ == 0) {
        revert ErrZeroQuoteTarget();
    }
    if (shares <= 2001) {
        revert ErrMintAmountNotEnough();
    }
    _mint(address(0), 1001);
    shares -= 1001;
```
The `QUOTE_TARGET` is determined by multiplying the `BASE_TARGET` with `I`. 

The flaw is in the check below:
`shares = quoteBalance < DecimalMath.mulFloor(baseBalance, _I_) ? DecimalMath.divFloor(quoteBalance, _I_) : baseBalance;`
Essentially there needs to be enough `quoteBalance` at the `I` ratio to mint `baseBalance` shares, if there's not enough then shares are instead determined by dividing the `quoteBalance` with `I`.
An attacker can abuse the `mulFloor()` to create a major inconsistency.
Suppose `quoteBalance = 1`, `baseBalance = 19999`, `I = 1e14`. Then we have:
`1 < 19999 * 1e14 / 1e18 => 1 < 1 => False`
Therefore `shares = 19999` .
It sets the targets:
```
_BASE_TARGET_ = 19999
_QUOTE_TARGET_ = 19999 * 1e14 / 1e18 = 1
```
The result is the ratio 1:19999, when the intended ratio from `I` is 1:1000. 

Essentially a small rounding error is magnified. The rounding direction should instead be: 
`quoteBalance < DecimalMath.mulCeil(baseBalance, _I_)`
This would ensure that `DecimalMath.divFloor(quoteBalance, _I_)` is executed. At this point, when calculating `QUOTE_TARGET` there will not be a precision error as above (it performs the opposing actions to the divFloor).

An attacker can abuse it by making users perform trades under the assumption `I` is the effective ratio, however the ratio is actually `2I`. The pool's pricing mechanics will be wrong. Note that users will legitimately trust any MagicLP pool created by the Factory as it is supposed to enforce that ratio.

The attack can be performed on another entity's pool right after the `init()` call, or on a self-created pool. The initial cost for the attack is very small due to the small numbers involved.

## Impact

Attacker can initialize a Pool with malicious pricing mechanics that break the assumed invariants of the pool, leading to incorrect pool interactions.

## POC

Step by step of the initial `buyShares()` was provided above.  `_QUOTE_TARGET_` is used by the pricer:
```
function getPMMState() public vie