xanoutas/solidity-security-findings huggingface dataset:
`requestL2Transaction` can call system contracts on L2
Audit: 2022-10-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/zksync/facets/Mailbox.sol#L106


# Vulnerability details

## Impact
In `Mailbox.sol` the `requestL2Transaction` function allows anyone to call important system contract addresses on L2 while passing in arbitrary calldata. This can be seen as the function accepts the arguments `_contractL2` and `_calldata`.  The docs highlight the following in regards to important L2 system contracts.

"To keep the zero-knowledge circuits as simple as possible and enable simple extensions, a large chunk of the logic of zkSync was moved to the so-called "system contracts" – a set of contracts that have special privileges and serve special purposes, e.g. deployment of contracts, making sure that the user pays only once for publishing contracts' calldata, etc"

Since the `Mailbox.sol` facet deals with L1 <-> L2 communication, anyone should not be able to call a system contract address unless it is  needed for the system to properly function. You don't want validators processing malicious transactions on L2 directed at these system contracts so access should be limited. 

Some of the system contracts include:
* `BOOTLOADER_FORMAL_ADDRESS` `address(0x8001)`
* `L2_KNOWN_CODE_STORAGE_ADDRESS` address(0x8004)`
* `L2_TO_L1_MESSENGER` `address(0x8008)`
*  `L2_SYSTEM_CONTEXT_ADDRESS` `address(0x800b)`

These are core contract on the L2 side that should not be allowed to be called unless absolutely necessary. You can see in the V2 testnet repo below that there may even be a `ETH_TOKEN_SYSTEM_CONTRACT`

## Proof of Concept
https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/zksync/facets/Mailbox.sol#L106

https://v2-docs.zksync.io/dev/developer-guides/contracts/system-contracts.html#interfaces

https://github.com/matter-labs/v2-testnet-contracts/blob/8de367778f3b7ed7e47ee8233c46c7fe046a75a3/l2/system-contracts/Constants.sol

## Tools Used
Manual code review 

## Recommended Mitigation Steps
A mapping of prohibited system contract addresses should be created so when `requestL2Transaction` is called it will fail if the `_contractL2` argument is a protected system address.
`commitBlocks` loops over an unbounded array
Audit: 2022-10-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/zksync/facets/Executor.sol#L162
https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/zksync/facets/Executor.sol#L111


# Vulnerability details

## Impact
In `Executor.sol` the `commitBlocks` function loops over an unbounded array and in doing so performs many operations which include checking timestamps, processing L2 logs and also storing block commitments for each iteration.  This can easily cause an out of gas failure to occur as there is no check on the size of the `_newBlocksData` array passed into the `commitBlocks` function.  Also for every single new block committed in the array there is yet another for loop that iterates over  the `emittedL2Logs` which is eventually called inside the `_processL2Logs` function. Due to the heavy computation, multiple for loops, and the iterating over and unbounded array it can be quite easy to run out of gas in this situation which is why checking the length of the `_newBlocksData` array is so important. 

## Proof of Concept
https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/zksync/facets/Executor.sol#L162

https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/zksync/facets/Executor.sol#L111

## Tools Used
Manual code review 

## Recommended Mitigation Steps
The length of the `_newBlocksData` array passed into the `commitBlocks` function should be checked and not allowed to go above a certain limit to prevent out of gas failures.
Incorrect require checks in `executeDiamondCutProposal`
Audit: 2022-10-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-10-zksync/blob/4db6c596931a291b17a4e0e2929adf810a4a0eed/ethereum/contracts/zksync/facets/DiamondCut.sol#L55-L57


# Vulnerability details

## Impact
In `DiamondCut.sol` in the `executeDiamondCutProposal` function there are 2 incorrect require statement checks. They both use the `||` operator when they should use the `&&` operator instead as everything inside the statement should be true for the system to work correctly.
A proposal should be `approvedBySecurityCouncil`, have the `upgradeNoticePeriodPassed` and also occur when `!idiamondStorage.isFrozen`.  The current check allows for only some of these values to be true while others are not.  For example a proposal can currently pass if it is approved by the security council but while the system is frozen or if not pass the upgrade notice period which should not be allowed.  This is all due to the wrong operators being used in the require checks. 

## Proof of Concept
https://github.com/code-423n4/2022-10-zksync/blob/4db6c596931a291b17a4e0e2929adf810a4a0eed/ethereum/contracts/zksync/facets/DiamondCut.sol#L55-L57


## Tools Used
Manual code review 

## Recommended Mitigation Steps
```
-        require(approvedBySecurityCouncil || upgradeNoticePeriodPassed, "a6"); // notice period should expire
-        require(approvedBySecurityCouncil || !diamondStorage.isFrozen, "f3");
     
+        require(approvedBySecurityCouncil && upgradeNoticePeriodPassed && !diamondStorage.isFrozen, "a6");        

```
Replay attack protection is missing
Audit: 2022-10-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-10-zksync/blob/358f38736351a8a27e325dfcb665eeba5ec02bd5/zksync/contracts/L2ContractHelper.sol#L30


# Vulnerability details

## Impact

Having the same L2Bridge address in different chains may result in replay attack.

## Proof of Concept
There is a chain id check in the diamondProxy:

```
 constructor(uint256 _chainId, Diamond.DiamondCutData memory _diamondCut) {
        // Check that the contract is deployed on the expected chain.
        // Thus, the contract deployed by the same Create2 factory on the different chain will have different addresses!
        require(_chainId == block.chainid, "pr");
        Diamond.diamondCut(_diamondCut);
    }
```
https://github.com/code-423n4/2022-10-zksync/blob/358f38736351a8a27e325dfcb665eeba5ec02bd5/ethereum/contracts/zksync/DiamondProxy.sol#L13

But, it is missing in other contract deployment. It is a good action if `l2Bridge` has different address if zkSync is deployed in other chains.

```
l2Bridge = L2ContractHelper.computeCreate2Address(
            address(this),
            create2Salt,
            l2BridgeBytecodeHash,
            keccak256(create2Input)
        );
```
https://github.com/code-423n4/2022-10-zksync/blob/358f38736351a8a27e325dfcb665eeba5ec02bd5/ethereum/contracts/bridge/L1ERC20Bridge.sol#L89
https://github.com/code-423n4/2022-10-zksync/blob/358f38736351a8a27e325dfcb665eeba5ec02bd5/ethereum/contracts/bridge/L1EthBridge.sol#L65

## Tools Used

## Recommended Mitigation Steps
The function below is a better code:
```
function computeCreate2Address(
        address _sender,
        bytes32 _salt,
        bytes32 _bytecodeHash,
        bytes32 _constructorInputHash
    ) internal pure returns (address) {
        bytes32 senderBytes = bytes32(uint256(uint160(_sender)));
        bytes32 data = keccak256(
            bytes.concat(CREATE2_PREFIX, senderBytes, abi.encodePacked(_salt, block.chainid), _bytecodeHash, _constructorInputHash)
        );

        return address(uint160(uint256(data)));
    }
```
https://github.com/code-423n4/2022-10-zksync/blob/358f38736351a8a27e325dfcb665eeba5ec02bd5/zksync/contracts/L2ContractHelper.sol#L30
Uninitialized state variables facets AppStorage
Audit: 2022-10-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-10-zksync/blob/4db6c596931a291b17a4e0e2929adf810a4a0eed/ethereum/contracts/zksync/facets/Base.sol#L12


# Vulnerability details

## Impact
https://github.com/code-423n4/2022-10-zksync/blob/4db6c596931a291b17a4e0e2929adf810a4a0eed/ethereum/contracts/zksync/facets/Base.sol#L12
Uninitialized variables are powerful bugs since they can be exploited to leak arbitrary memory or to achieve arbitrary memory overwrite or to gain code execution, depending on the case. When exploiting a software which utilizes address space layout randomization (ASLR), it is often required to know the base address of the software in memory. Exploiting an uninitialized variable in a way to force the software to leak a pointer from its address space can be used to bypass ASLR.


## Proof of Concept
when a variable is implicitly initialized to a default value like 0, this is typically not the correct value. Initialized does not mean correct if the value is a default one. (However, default initialization to 0 is a right practice for pointers and arrays of pointers, since it makes them invalid before they are actually initialized to their correct value.)

## Tools Used
Slither

## Recommended Mitigation Steps
Initialize all the variables. If a variable is meant to be initialized to zero, explicitly set it to zero to improve code readability.
Liquidators will never profit from liquidations
Audit: 2024-06-size-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/actions/Liquidate.sol#L75-L126


# Vulnerability details

## Impact
Liquidators will never profit from liquidations

## Proof of Concept
This is the function responsible for executing the liquidation:
```solidity
function executeLiquidate(State storage state, LiquidateParams calldata params)
        external
        returns (uint256 liquidatorProfitCollateralToken)
    {
        DebtPosition storage debtPosition = state.getDebtPosition(params.debtPositionId);
        LoanStatus loanStatus = state.getLoanStatus(params.debtPositionId);
        uint256 collateralRatio = state.collateralRatio(debtPosition.borrower);

        emit Events.Liquidate(params.debtPositionId, params.minimumCollateralProfit, collateralRatio, loanStatus);

        // if the loan is both underwater and overdue, the protocol fee related to underwater liquidations takes precedence
        uint256 collateralProtocolPercent = state.isUserUnderwater(debtPosition.borrower)
            ? state.feeConfig.collateralProtocolPercent
            : state.feeConfig.overdueCollateralProtocolPercent;

        uint256 assignedCollateral = state.getDebtPositionAssignedCollateral(debtPosition);
        uint256 debtInCollateralToken = state.debtTokenAmountToCollateralTokenAmount(debtPosition.futureValue);
        uint256 protocolProfitCollateralToken = 0;

        // profitable liquidation
        if (assignedCollateral > debtInCollateralToken) {
            uint256 liquidatorReward = Math.min(
                assignedCollateral - debtInCollateralToken,
                Math.mulDivUp(debtPosition.futureValue, state.feeConfig.liquidationRewardPercent, PERCENT)
            );
            liquidatorProfitCollateralToken = debtInCollateralToken + liquidatorReward;

            // split the remaining collateral between the protocol and the borrower, capped by the crLiquidation
            uint256 collateralRemainder = assignedCollateral - liquidatorProfitCollateralToken;

            // cap the collateral remainder to the liquidation collateral ratio
            //   otherwise, the split for non-underwater overdue loans could be too much
            uint256 collateralRemainderCap =
                Math.mulDivDown(debtInCollateralToken, state.riskConfig.crLiquidation, PERCENT);

            collateralRemainder = Math.min(collateralRemainder, collateralRemainderCap);

            protocolProfitCollateralToken = Math.mulDivDown(collateralRemainder, collateralProtocolPercent, PERCENT);
        } else {
            // unprofitable liquidation
            liquidatorProfitCollateralToken = assignedCollateral;
        }

        state.data.borrowAToken.transferFrom(msg.sender, address(this), debtPosition.futureValue);
        state.data.collateralToken.transferFrom(debtPosition.borrower, msg.sender, liquidatorProfitCollateralToken);
        state.data.collateralToken.transferFrom(
Wrong calculation of `fees` in `SellCreditMarket` when `params.exactAmountIn == false` and `params.creditPositionId == RESERVED_ID`
Audit: 2024-06-size-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/AccountingLibrary.sol#L249
https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/AccountingLibrary.sol#L256


# Vulnerability details

## Impact
In `SellCreditMarket`, when `params.exactAmountIn == false` and `params.creditPositionId == RESERVED_ID`, `params.amount` is used as `cashAmountOut` in `getCreditAmountIn` 

https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/actions/SellCreditMarket.sol#L172

then `fees` is calculated based on `cashAmountOut` in `getCreditAmountIn`

https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/AccountingLibrary.sol#L249

The calculation of `fees` is wrong because `fees` should be calculated based on the amount of cash swapped (`cashAmountOut + fees`), in other words, the right formula for `fees` is `fees = (cashAmountOut * swapFeePercent) / (1 - swapFeePercent)`

As a result, the protocol always loses `fees` when users sell credit using market order with `params.exactAmountIn == false`, `params.creditPositionId == RESERVED_ID`.

## Proof of Concept
```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import {AccountingLibrary} from "@src/libraries/AccountingLibrary.sol";
import {State} from "@src/SizeStorage.sol";
import {Math, PERCENT, YEAR} from "@src/libraries/Math.sol";

import {Test, console} from "forge-std/Test.sol";

contract POC is Test {
    using AccountingLibrary for State;

    State state;
    function test() public {
        state.feeConfig.swapFeeAPR = 5e15;
        uint256 ratePerTenor = 1e17;
        uint256 tenor = YEAR;
        uint256 nextCashAmountOut;

        {
            uint256 creditAmountIn = 11e5;
            (uint256 cashAmountOut, uint256 fees) = state.getCashAmountOut({
                creditAmountIn: creditAmountIn,
                maxCredit: creditAmountIn,
                ratePerTenor: ratePerTenor,
                tenor: tenor
            });
            console.log("getCashAmountOut");
            console.log("creditAmountIn :", creditAmountIn);
            console.log("cashAmountOut: ", cashAmountOut);
            console.log("fees: ", fees);

            nextCashAmountOut = cashAmountOut;
        }

        {
            (uint256 creditAmountIn, uint256 fees) = state.getCreditAmountIn({
                cashAmountOut: nextCashAmountOut,
                maxCashAmountOut: nextCashAmountOut, 
                maxCredit: Math.mulDivUp(nextCashAmountOut, PERCENT + ratePerTenor, PERCENT - state.getSwapFeePercent(tenor)),
                ratePerTenor: ratePerTenor,
                tenor: tenor
            });
            console.log("getCreditAmountIn");
            console.log("creditAmountIn: ", creditAmountIn);
            console.log("cashAmountOut: ", nextCashAmountOut);
            console.log("
Incorrect calculation of liquidator reward in Liquidate.sol leads to failed liquidations
Audit: 2024-06-size-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/actions/Liquidate.sol#L95-L100


# Vulnerability details

## Impact
On liquidation, the total amount of collateralTokens to be sent to the liquidator is calculated by adding the liquidatorReward (5% of the debt position's `futureValue`) to the debt value in collateral tokens. However, `liquidatorReward` is calculated in `borrowAtokens`, not `collateralTokens`, and is added to the total sum without prior conversion. As a result, the liquidator receives significantly less than the expected reward.

If the liquidator does not set `minimumCollateralProfit`, this will lead to a loss for the liquidator. If `minimumCollateralProfit` is set, the liquidation will fail. If we assume that the frontend automatically sets `minimumCollateralProfit`, this would lead to failed liquidations & accrual of bad protocol debt.

This issue also affects `liquidateWithReplacement`.

## Proof of Concept
Assume Alice opens a debt position of 100,000 USD, collateralized with 150 WETH at a WETH price of 1000. Let's also assume a 0% rate for simplicity. The `futureValue` of the position is 100,000 USDC, stored as `100000e6` by the protocol.

The price of WETH drops to 800 USDC, dropping the collateral ratio to 1.2x. Bob steps in to liquidate the position. 

Using the current reward calculation code, with a 5% liquidator reward, Bob's reward is calculated as:

```
debtInCollateralTokens + futureValue * 0.05
= 125e18 + 100000e6 * 0.05
= 125000000005000000000 Wei
```


The total amount sent to Bob by the protocol is 125.000000005 WETH. We can see that the amount is far below what Bob `should` have been paid (the correct amount is be 131.25 WETH).

Here is a test to verify that a liquidation with an appropriately set `minimumCollateralProfit` fails:

```
   function test_Liquidate_minuscule_reward() public {

        uint256 amount = 100000e6;
        _mint(address(usdc), alice, amount);
        _mint(address(weth), bob, 125e18);

        _setPrice(1000e18);

        _deposit(alice, usdc, 100000e6);
        _deposit(bob, weth, 150e18);
        _deposit(liquidator, usdc, 100000e6);

        _buyCreditLimit(alice, block.timestamp + 365 days, YieldCurveHelper.pointCurve(365 days, 0));

        uint256 debtPositionId = _sellCreditMarket(bob, alice, RESERVED_ID, amount, 365 days, true);

        // console.log(futureValue);
        // futureValue = 100000e6

        uint256 futureValue = size.getDebtPosition(debtPositionId).futureValue;

        _setPrice(800e18);

        // collateral = 120000e6 = 1.2x

        assertTrue(size.isDebtPositionLiquidatable(debtPositionId));

        // liquidatorProfitCollateralToken = debtInCollateralTokens + 5% of futureValue
        // collateralProtocolPercent = 0.1e18 by default
        // debtInCollateralTokens = 125 WETH
        // 5% of futureValue = 10,000 USDC = 6.25 WETH
        // Total expected rewards = 131.25 WETH
Invalid comparison when computing the liquidator's reward in `executeLiquidate`
Audit: 2024-06-size-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-size/blob/main/src/libraries/actions/Liquidate.sol#L96-L99


# Vulnerability details

## Impact
When a loan is liquidatable, the protocol checks if the position is profitable for the liquidator, this acts as an incentive for users to liquidate. While doing so, the protocol first computes the liquidator reward using the following:
```
uint256 liquidatorReward = Math.min(
    assignedCollateral - debtInCollateralToken,
    Math.mulDivUp(
        debtPosition.futureValue,
        state.feeConfig.liquidationRewardPercent,
        PERCENT
    )
);
```
However, there's an issue here as it's comparing collateral token balance, with a borrow token balance. As an example, in the protocol's default pool (WETH/USDC), the above will always return the wrong value as USDC has 6 decimals while WETH has 18. So the `mulDivUp` value will always be less.

This causes liquidators to lose money when liquidating positions.

## Proof of Concept

```solidity
function test_liquidate_invalid_liquidator_reward() public {
    _deposit(alice, weth, 100e18);
    _deposit(alice, usdc, 500e6);
    _deposit(bob, weth, 100e18);
    _deposit(bob, usdc, 500e6);
    _deposit(candy, weth, 100e18);
    _deposit(candy, usdc, 500e6);

    int256[] memory aprs = new int256[](1);
    uint256[] memory tenors = new uint256[](1);
    uint256[] memory marketRateMultipliers = new uint256[](1);

    aprs[0] = 0.2e18;
    tenors[0] = 365 days;
    marketRateMultipliers[0] = 0;

    // Bob creates a limit order
    vm.prank(bob);
    size.buyCreditLimit(
        BuyCreditLimitParams({
            curveRelativeTime: YieldCurve({
                tenors: tenors,
                marketRateMultipliers: marketRateMultipliers,
                aprs: aprs
            }),
            maxDueDate: block.timestamp + 365 days
        })
    );

    // Alice sells credit market (borrows)
    uint256 debtPositionId = 0;
    vm.prank(alice);
    size.sellCreditMarket(
        SellCreditMarketParams({
            lender: bob,
            creditPositionId: type(uint256).max,
            tenor: 365 days,
            amount: 100e6,
            exactAmountIn: true,
            deadline: block.timestamp,
            maxAPR: type(uint256).max
        })
    );

    // Collateral price drops
    _setPrice(1.2e18);

    // Alice is liquidatable
    assertTrue(size.isUserUnderwater(alice));

    // Alice liquidatable position is profitable
    uint256 assignedCollateral = size.getDebtPositionAssignedCollateral(
        debtPositionId
    );
    uint256 debtInCollateralToken = size
        .debtTokenAmountToCollateralTokenAmount(
            size.getDebtPosition(debtPositionId).futureValue
        );
    assertGt(assignedCollateral, debtInCollateralToken);

    uint256 borrowTokenBalanceBefore = size.data().borrowAToken.balanceOf(
        candy
    );
    uint256 collateralTokenBalanceBefore = size
        .data()
        .collateralToken
        .balanceOf(candy);