DevABDee Q

# Qualitative Analysis:
| Metric | Rating |
| :-----: | :-----: |
| Test Coverage | Great |
| Best Practices | Followed |
| Documentation | Poor |
| NatSpec Comments | Great |
| Decentralization | Not Fully Decentralized |
| Code Quality & Complexity | Excellent |

In general, the codebase appears to be impressive. It is notably absent of code complexity and boasts excellent code quality. Best practices and NatSpec have also been utilized effectively, and test coverage is commendable at over 90%. However, given the relatively small size of the codebase, achieving 100% test coverage is expected and would be beneficial for further improvements. Regrettably, there is currently no developer documentation available. It is strongly recommended that the project team prioritize the addition of documentation as soon as possible. It is also worth noting that the protocol is not fully decentralized, with main functions such as stake and unstake being pausable. This presents a risk of a central point of failure, and measures should be taken to address this potential vulnerability.

# Findings Summary:
| No | Issue |
| :-----: | :-----: |
| L-01 | Users will lose funds if `totalWeight` will remained uninitialzed |
| L-01 | No Storage Gap for Upgradeable Contract Might Lead to Storage Slot Collision |
| L-01 | Unbounded loop |
| NC-1 | Dangerous Maths - Loss of precision due to rounding |
| NC-1 | Ignored return values |
| NC-1 | Boolean Equality - Compares to a boolean |
| NC-1 | Unlocked Pragma |
| NC-1 | Unspecific Imports |
| NC-1 | Unused function parameter/argument |
| NC-1 | Solidity Function Ordering Guidelines not followed |
| BONUS | Goerli & Rinkeby deprecated |

# Low Findings:
## L-01: Users will lose funds if `totalWeight` will remained uninitialized
`totalWeight` is initiated to the default value (zero) in [SafEthStorage.sol](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEthStorage.sol#L19)
```soli


hals G

# Gas Optimization

## [G-01]

fundingAmount\_ input is not checked if ==0

## Vulnerability Details

fundingAmount\_ input is not checked if ==0: if it equals to zero then gas will be wasted on “updating” the treasure state variable, emitting FundTreasury event & transfer.

## Impact

gas optimization

## Proof of Concept

Instances: 1

```solidity
File: 2023-05-ajna/ajna-grants/src/grants/GrantFund.sol
Line 58: fundTreasury function
```

## Tools Used

Manual Testing.

## Recommended Mitigation Steps

check if fundingAmount\_ input ==0 to save gas when it equals to zero.

#

## [G-02]

fundTreasury function:
gas will be wasted on “updating” the treasure state variable, emitting FundTreasury event if the token.balanceOf(msg.sender) >= fundingAmount\_

## Vulnerability Details

fundTreasury function doesn't check if the balance of the sender of ajnaTokens is >= fundingAmount\_ from the start which will result in wasted gas used for “updating” the treasure state variable, emitting FundTreasury event

## Impact

gas optimization

## Proof of Concept

Instances: 1

```solidity
File: 2023-05-ajna/ajna-grants/src/grants/GrantFund.sol
Line 58: fundTreasury function
```

## Tools Used

Manual Testing.

## Recommended Mitigation Steps

check if token.balanceOf(msg.sender) >= fundingAmount\_

#

// Code block 1 (solidity):
File: 2023-05-ajna/ajna-grants/src/grants/GrantFund.sol
Line 58: fundTreasury function

// Code block 2 (solidity):
File: 2023-05-ajna/ajna-grants/src/grants/GrantFund.sol
Line 58: fundTreasury function

File: 2023-05-ajna/ajna-grants/src/grants/GrantFund.sol
Line 58: fundTreasury function

File: 2023-05-ajna/ajna-grants/src/grants/GrantFund.sol
Line 58: fundTreasury function


Ephraim Q

- Inconsistency of Code Implementation and the Code Comments

There is a code comment in the PoolUtils.sol that explains how token reserves less than DUST (100) would be disregarded.

```
// Token reserves less than dust are treated as if they don't exist at all.
// With the 18 decimals that are used for most tokens, DUST has a value of 0.0000000000000001

uint256 constant public DUST = 100;
```

However, in Abritrage.sol, the _bisectionSearch function implements a check that requires that token reserves is less than or equal to DUST (100).

```
if ( reservesA0 <= PoolUtils.DUST || reservesA1 <= PoolUtils.DUST || reservesB0 <= PoolUtils.DUST || reservesB1 <= PoolUtils.DUST || reservesC0 <= PoolUtils.DUST || reservesC1 <= PoolUtils.DUST )
return 0;
```

https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/arbitrage/ArbitrageSearch.sol#L107

Remediation:
Correct checks of token reserves to be less than DUST only.

// Code block 1 (unknown):
// Token reserves less than dust are treated as if they don't exist at all.
// With the 18 decimals that are used for most tokens, DUST has a value of 0.0000000000000001

uint256 constant public DUST = 100;

// Code block 2 (unknown):
if ( reservesA0 <= PoolUtils.DUST || reservesA1 <= PoolUtils.DUST || reservesB0 <= PoolUtils.DUST || reservesB1 <= PoolUtils.DUST || reservesC0 <= PoolUtils.DUST || reservesC1 <= PoolUtils.DUST )
return 0;


kutugu Q

# Findings Summary

| ID     | Title                                                                                | Severity     |
| ------ | ------------------------------------------------------------------------------------- | ------------ |
| [L-01] | The collateralAmount and getClaimAbleLBR have an accuracy error                       | Low          |
| [L-02] | The extraRatio and rewardRatio updates should pass through the time lock              | Low          |
| [L-03] | StakedLBRLpValue value can be manipulated                                             | Low          |
| [L-04] | There are accuracy problems in EUSD internal accounting calculations                  | Low          |
| [N-01] | Decimals for collateral and USD may not be equal                                      | Non-Critical |
| [N-02] | ProtocolRewardsPool getReward event parameter miscalculation                          | Non-Critical |

# [L-01] The collateralAmount and getClaimAbleLBR have an accuracy error

## Description

```solidity
uint256 collateralAmount = (((eusdAmount * 1e18) / assetPrice) * (10000 - configurator.redemptionFee())) / 10000;
```

```solidity
    function getClaimAbleLBR(address user) public view returns (uint256 amount) {
        if (time2fullRedemption[user] > lastWithdrawTime[user]) {
            amount = block.timestamp > time2fullRedemption[user] ? unstakeRatio[user] * (time2fullRedemption[user] - lastWithdrawTime[user]) : unstakeRatio[user] * (block.timestamp - lastWithdrawTime[user]);
        }
    }
```

The collateralAmount and getClaimAbleLBR have precision error in dividing before multiplying.

## Recommendations

Multiply before divide

# [L-02] The extraRatio and rewardRatio updates should pass through the time lock

## Description

The calculation of incentives in EUSDMiningIncentives relies on extraRatio and rewardRatio, whose updates depend on the owner instead of the time lock.
If the owner updates the extraRatio and rewardRatio in an insta

// Code block 1 (solidity):
uint256 collateralAmount = (((eusdAmount * 1e18) / assetPrice) * (10000 - configurator.redemptionFee())) / 10000;

// Code block 2 (solidity):
function getClaimAbleLBR(address user) public view returns (uint256 amount) {
        if (time2fullRedemption[user] > lastWithdrawTime[user]) {
            amount = block.timestamp > time2fullRedemption[user] ? unstakeRatio[user] * (time2fullRedemption[user] - lastWithdrawTime[user]) : unstakeRatio[user] * (block.timestamp - lastWithdrawTime[user]);
        }
    }

uint256 collateralAmount = (((eusdAmount * 1e18) / assetPrice) * (10000 - configurator.redemptionFee())) / 10000;

function getClaimAbleLBR(address user) public view returns (uint256 amount) {
        if (time2fullRedemption[user] > lastWithdrawTime[user]) {
            amount = block.timestamp > time2fullRedemption[user] ? unstakeRatio[user] * (time2fullRedemption[user] - lastWithdrawTime[user]) : unstakeRatio[user] * (block.timestamp - lastWithdrawTime[user]);
        }
    }


Bauchibred Analysis

# Analysis Report for AI Arena

## Table of Contents

- [Approach](#approach)
- [Brief Overview](#brief-overview)
- [Scope and Architecture Overview](#scope-and-architecture-overview)
- [Centralization Risks](#centralization-risks)
- [Systemic Risks](#systemic-risks)
- [Recommendations](#recommendations)
- [Security Researcher Logistics](#security-researcher-logistics)
- [Conclusion](#conclusion)
- [Resources](#resources)

## Approach

Started with a comprehensive analysis of the provided docs to understand protocol functionality and key points, ambiguities were discussed with the sponsors(deva), and possible risk areas were outlined.

Followed by a manual review of each contract in scope, testing function behavior, protocol logic against expectations, and working out potential attack vectors. Vulnerabilities related to dependencies and inheritances, were also assessed. Comparisons with similar protocols was also performed to identify recurring issues and evaluate fix effectiveness.

Finally, issues identified during the security review were compiled into a comprehensive audit report.

## Brief Overview

AI Arena introduces a player-versus-player combat game set in a realm where human-trained AI fighters clash for supremacy. In the Web3 adaptation of AI Arena, these fighters are represented as NFTs through the` FighterFarm.sol` contract, where each NFT fighter is characterized by:

- Physical Attributes: Shape the visual aesthetics of the fighter.
- Generation: Also influences the fighter's visual traits.
- Weight: Impacts the combat capabilities.
- Element: Bestows unique special powers.
- Fighter Type: Distinguishes between a standard Champion or a Dendroid.
- Model Data: Includes the model type and its corresponding hash.

## Scope and Architecture Overview

### AIArenaHelper.sol

This contract helps generate and manage the AI Arena fighters physical attributes, having it's constructor initializing the contract with the attribute probabilities for gen 0.
**State

### FighterFarm.sol

**Purpose:**

- Manages AI Arena Fighter NFTs (ERC721 tokens).
- Also handles creation, ownership, transfers, redemption of mint passes, merging pool interactions, fighter staking, rerolling, model updates, and metadata retrieval.

**Key Roles and Permissions:**

- **Owner:** Privileged address with full control over contract functions (e.g., ownership transfer, generation increment).
- **Delegated Address:** Authorized to set token URIs and sign fighter claim messages.
- **Stakers:** Addresses allowed to stake fighters on behalf of users.

**NFT Creation and Management:**

- **Minting:**
  - Occurs through claiming (with signature verification), mint pass redemption, or merging pool minting.
- **Rerolling:**
  - Regenerates a fighter's traits with a Neuron token (NRN) cost, limited by a maximum reroll count.
- **Model Updates:**
  - Allows owners to update the ML model associated with their fighters.
- **Staking:**
  - Controls a fighter's ability to be transferred.

**Interactions with Other Contracts:**

- **AIArenaHelper:**
  - Assists with physical attribute generation.
- **AAMintPass:**
  - Interacts with mint pass redemption.
- **Neuron:**
  - Handles NRN token transfers for rerolling.
- **Merging Pool:**
  - Facilitates minting from the merging pool.

**Additional Features:**

- **URI Storage:**
  - Stores contract and token metadata on IPFS.
- **Fighter Information Retrieval:**
  - Provides a function to access comprehensive fighter data.

### GameItems.sol

**Purpose:**

This contract manages a collection of in-game items used in AI Arena, allowing users to:

- Buy items with the Neuron (NRN) token.
- Mint items with finite or infinite supply.
- Burn items (only authorized addresses).
- Transfer items (if allowed), with admin control over transferability.
- Manage item attributes (name, price, daily allowance, etc.) by admins.

**Key Features:**

- **ERC1155 compliant:** Manages multiple item types with a single contract.
- **Daily allowance:** Limits the number of items a user can buy per day.
- **Transferable/locked:** Admins can control if items can be traded.
- **Admin roles:** Defines addresses with special permissions.
- **Metadata storage:** Stores item data on IPFS.

**Contract Structure:**

- **State Variables:** Store contract data like item attributes, treasury address, owner address, etc.
- **Mappings:** Track user allowances, replenish times, burning/admin permissions, and token URIs.
- **Events:** Emit signals for item purchases, locking/unlocking, and more.
- **Functions:**
  - **External:** Public functions for buying, transferring, burning, and managing items (admins only).
  - **Public:** View information about items and allowances.
  - **Private:** Internal functions for managing allowances and metadata.

**Additional Notes:**

- The contract interacts with the `Neuron` contract for NRN token transfers.
- Only authorized addresses can burn items.
- Admins have full control over item creation, attributes, and transferability.
- The contract uses an ERC1155 base for efficient management of multiple item types.


report

---
sponsor: "Panoptic"
slug: "2024-04-panoptic"
date: "2024-06-24"
title: "Panoptic"
findings: "https://github.com/code-423n4/2024-04-panoptic-findings/issues"
contest: 354
---

# Overview

## About C4

Code4rena (C4) is an open organization consisting of security researchers, auditors, developers, and individuals with domain expertise in smart contracts.

A C4 audit is an event in which community participants, referred to as Wardens, review, audit, or analyze smart contract logic in exchange for a bounty provided by sponsoring projects.

During the audit outlined in this document, C4 conducted an analysis of the Panoptic smart contract system written in Solidity. The audit took place between April 1—April 22 2024.

## Wardens

60 Wardens contributed reports to Panoptic:

  1. [0xLogos](https://code4rena.com/@0xLogos)
  2. [bin2chen](https://code4rena.com/@bin2chen)
  3. [KupiaSec](https://code4rena.com/@KupiaSec)
  4. [rbserver](https://code4rena.com/@rbserver)
  5. [Kalogerone](https://code4rena.com/@Kalogerone)
  6. [0xdice91](https://code4rena.com/@0xdice91)
  7. [pkqs90](https://code4rena.com/@pkqs90)
  8. [petro\_1912](https://code4rena.com/@petro_1912)
  9. [Aymen0909](https://code4rena.com/@Aymen0909)
  10. [0xStalin](https://code4rena.com/@0xStalin)
  11. [DanielArmstrong](https://code4rena.com/@DanielArmstrong)
  12. [Joshuajee](https://code4rena.com/@Joshuajee)
  13. [JecikPo](https://code4rena.com/@JecikPo)
  14. [Udsen](https://code4rena.com/@Udsen)
  15. [FastChecker](https://code4rena.com/@FastChecker)
  16. [DadeKuma](https://code4rena.com/@DadeKuma)
  17. [Dup1337](https://code4rena.com/@Dup1337) ([ChaseTheLight](https://code4rena.com/@ChaseTheLight), [sorrynotsorry](https://code4rena.com/@sorrynotsorry), and [deliriusz](https://code4rena.com/@deliriusz))
  18. [Bauchibred](https://code4rena.com/@Bauchibred)
  19. [sammy](https://code4rena.com/@sammy)
  20. [jesjupyter](https://code4rena.com/@jesjupyter)
  21. [99Crits](https://code4rena.com/@99Cri

            accumulatedPremium = LeftRightUnsigned
                .wrap(0)
                .toRightSlot(premiumAccumulator0)
                .toLeftSlot(premiumAccumulator1);

            // update the premium accumulator for the long position to the latest value
            // (the entire premia delta will be settled)
            LeftRightUnsigned premiumAccumulatorsLast = s_options[owner][tokenId][legIndex];
            s_options[owner][tokenId][legIndex] = accumulatedPremium;

>           accumulatedPremium = accumulatedPremium.sub(premiumAccumulatorsLast);
        }

        uint256 liquidity = PanopticMath
            .getLiquidityChunk(tokenId, legIndex, s_positionBalance[owner][tokenId].rightSlot())
            .liquidity();

        unchecked {
            // update the realized premia
>           LeftRightSigned realizedPremia = LeftRightSigned
>               .wrap(0)
>               .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64)))
>               .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64)));

            // deduct the paid premium tokens from the owner's balance and add them to the cumulative settled token delta
            s_collateralToken0.exercise(owner, 0, 0, 0, realizedPremia.rightSlot());
            s_collateralToken1.exercise(owner, 0, 0, 0, realizedPremia.leftSlot());

   function exercise(
        address optionOwner,
        int128 longAmount,
        int128 shortAmount,
        int128 swappedAmount,
        int128 realizedPremium
    ) external onlyPanopticPool returns (int128) {
        unchecked {
            // current available assets belonging to PLPs (updated after settlement) excluding any premium paid
            int256 updatedAssets = int256(uint256(s_poolAssets)) - swappedAmount;

            // add premium to be paid/collected on position close
>           int256 tokenToPay = -realizedPremium;

            // if burning ITM and swap occurred, compute tokens to be paid through exercise and add swap fees
            int256 intrinsicValue = swappedAmount - (longAmount - shortAmount);

            if ((intrinsicValue != 0) && ((shortAmount != 0) || (longAmount != 0))) {
                // intrinsic value is the amount that need to be exchanged due to burning in-the-money

                // add the intrinsic value to the tokenToPay
                tokenToPay += intrinsicValue;
            }

>           if (tokenToPay > 0) {
                // if user must pay tokens, burn them from user balance (revert if balance too small)
                uint256 sharesToBurn = Math.mulDivRoundingUp(
                    uint256(tokenToPay),
                    totalSupply,
                    totalAssets()
                );
                _burn(optionOwner, sharesToBurn);
>           } else if (tokenToPay < 0) {
                // if user must receive tokens, mint them
                uint256 sharesToMint = convertToShares(uint256(-tokenToPay));
                _mint(optionOwner, sharesToMint);
            }


codeslide Q

### Low Risk Issues List

| Number | Issue | Instances |
| :----: | :---- | :-------: |
| [L-01] | Unspecific compiler version pragma | 5 |
| [L-02] | Unsafe casting of uints            | 4 |

#### [L-01] Unspecific compiler version pragma
The compiler version specified for a contract should be locked to the version it has been tested the most with. Locking the pragma helps ensure that contracts do not accidentally get deployed using, for example, the latest compiler which may have higher risks of undiscovered bugs. Contracts may also be deployed by others and the pragma indicates the compiler version intended by the original authors. [locking-pragmas](https://consensys.github.io/smart-contract-best-practices/development-recommendations/solidity-specific/locking-pragmas/)

For example:

```solidity
// bad
pragma solidity ^0.8.9;

// good
pragma solidity 0.8.9;
```

```solidity
File: src/Factory.sol

2:    pragma solidity ^0.8.19;
```

```solidity
File: src/PrivatePoolMetadata.sol

2:    pragma solidity ^0.8.19;
```

```solidity
File: src/EthRouter.sol

2:    pragma solidity ^0.8.19;
```

```solidity
File: src/PrivatePool.sol

2:    pragma solidity ^0.8.19;
```

```solidity
File: interfaces/IStolenNftOracle.sol

2:    pragma solidity ^0.8.19;
```

#### [L-02] Unsafe casting of uints

Downcasting from uint256 in Solidity does not revert on overflow. This can easily result in undesired exploitation or bugs, since developers usually assume that overflows raise errors. [OpenZeppelin's SafeCast](https://docs.openzeppelin.com/contracts/3.x/api/utils#SafeCast) restores this intuition by reverting the transaction when such an operation overflows. Using this library instead of the unchecked operations eliminates an entire class of bugs, so it’s recommended to use it always.

For example:

```solidity
// Before
virtualNftReserves -= uint128(weightSum);
```

```solidity
// After
virtualNftReserves -= toUint128(weightSum);
```

```solidity
File: src/PrivatePool.sol

230:    virt

// Code block 1 (solidity):
// bad
pragma solidity ^0.8.9;

// good
pragma solidity 0.8.9;

// Code block 2 (solidity):
File: src/Factory.sol

2:    pragma solidity ^0.8.19;

// Code block 3 (solidity):
File: src/PrivatePoolMetadata.sol

2:    pragma solidity ^0.8.19;

// Code block 4 (solidity):
File: src/EthRouter.sol

2:    pragma solidity ^0.8.19;

// Code block 5 (solidity):
File: src/PrivatePool.sol

2:    pragma solidity ^0.8.19;

// Code block 6 (solidity):
File: interfaces/IStolenNftOracle.sol

2:    pragma solidity ^0.8.19;

// Code block 7 (solidity):
// Before
virtualNftReserves -= uint128(weightSum);

// Code block 8 (solidity):
// After
virtualNftReserves -= toUint128(weightSum);

// bad
pragma solidity ^0.8.9;

// good
pragma solidity 0.8.9;

File: src/Factory.sol

2:    pragma solidity ^0.8.19;

File: src/PrivatePoolMetadata.sol

2:    pragma solidity ^0.8.19;

File: src/EthRouter.sol

2:    pragma solidity ^0.8.19;

File: src/PrivatePool.sol

2:    pragma solidity ^0.8.19;

File: interfaces/IStolenNftOracle.sol

2:    pragma solidity ^0.8.19;

// Before
virtualNftReserves -= uint128(weightSum);

// After
virtualNftReserves -= toUint128(weightSum);


Co0nan Q

Total of 33 issue Low and NC.
---------
1 - delete doesn’t delete mapping in struct

The _updateDepositAndSnapshots() function in SP contract deletes a depositSnapshots struct, but the mapping in the Snapshots struct is not deleted. Values from this “deleted” mapping can still be accessed and there are no comments, function names, or other indicators that this is known. This issue is documented in Solidity’s security considerations documentation:
https://docs.soliditylang.org/en/v0.8.10/security-considerations.html#clearing-mappings

The delete operation occurs on
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/StabilityPool.sol#L813

```
        if (_newValue == 0) {
            for (uint i = 0; i < collaterals.length; i++) {
                delete depositSnapshots[_depositor].S[collaterals[i]];
            }
            delete depositSnapshots[_depositor];
            emit DepositSnapshotUpdated(_depositor, 0, collaterals, amounts, 0);
            return;
        }
```

The mapping that still exists after the delete on:
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/StabilityPool.sol#L179

```
struct Snapshots {
        mapping (address => uint) S;
        uint P;
        uint G;
        uint128 scale;
        uint128 epoch;
    }

```
2 - Improper emit for events.

2.1 - The function "stake" inside LQTYStaking.sol emit the event "StakingGainsWithdrawn" before actually sending the accumulated LUSD and collateral gains to the caller. In case of transfer failure, this event will be emit already which is misleading.

https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LQTY/LQTYStaking.sol#L131

```
        lqtyToken.safeTransferFrom(msg.sender, address(this), _LQTYamount);

        emit StakeChanged(msg.sender, newStake);
        emit StakingGainsWithdrawn(msg.sender, LUSDGain, collGainAssets, collGainAmounts);

         // Send accumulated LUSD and collateral gains to the caller
        i

// Code block 1 (unknown):
if (_newValue == 0) {
            for (uint i = 0; i < collaterals.length; i++) {
                delete depositSnapshots[_depositor].S[collaterals[i]];
            }
            delete depositSnapshots[_depositor];
            emit DepositSnapshotUpdated(_depositor, 0, collaterals, amounts, 0);
            return;
        }

// Code block 2 (unknown):
struct Snapshots {
        mapping (address => uint) S;
        uint P;
        uint G;
        uint128 scale;
        uint128 epoch;
    }


orion Q

usage of transfer and transferFrom without check of returned bool, the usdy token if ever upgraded to another ERC20 that returns false for transfer failure of tokens some of Ondo's function will works in unintended way,

consider the wrapping function at : https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/usdy/rUSDY.sol#L434

it uses usdy.transferFrom without check, if usdy.transferFrom returns false on transfer failures, users will get the shares minted without transfering any tokens

Consider using safetransferFrom, or

```
(,bool s) = usdy.transferFrom(msg.sender, address(this), _USDYAmount);
require(s,"failed");
```

// Code block 1 (unknown):
(,bool s) = usdy.transferFrom(msg.sender, address(this), _USDYAmount);
require(s,"failed");


shark G

## 1. Functions that can only be called by users with correct permissions/roles can be marked `payable`

Functions with access control will cost less gas when marked as `payable` (assuming the caller has correct permissions). This is because the compiler doesn't need to check for `msg.value`, which saves approximately **20 gas** per call.

For example, the following functions may be marked `payable` to save gas:

- File: `CashManager.sol` [Line 336-350](https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/CashManager.sol#L336-L350)
- File: `CashManager.sol` [Line 526-528](https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/CashManager.sol#L526-L528)
- File: `CashManager.sol` [Line 533-535](https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/CashManager.sol#L533-L535)
- File: `KYCRegistry.sol` [Line 144-150](https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/kyc/KYCRegistry.sol#L144-L150)

## 2. Using `storage` instead of `memory` for structs/arrays saves gas

When retrieving data from a `storage` location, assigning the data to a `memory` variable will cause all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new `memory` variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declaring the variable with the memory keyword, declaring the variable with the `storage` keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incurring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a `memory` variable, is if the full struct/array is being returned by the function, is being passed to a function that requires `memory`, or if the array/struct is being read from another `memory` array/struct

Here are some instances of this issue:

File: `CTokenCash.sol` ([Line 221](https

221:    Exp memory exchangeRate = Exp({mantissa: exchangeRateCurrent()});
...
433:    Exp memory simpleInterestFactor = mul_(
...
506:    Exp memory exchangeRate = Exp({mantissa: exchangeRateStoredInternal()});
...
590:    Exp memory exchangeRate = Exp({mantissa: exchangeRateStoredInternal()});
...
1027:    Exp memory exchangeRate = Exp({mantissa: exchangeRateStoredInternal()});

    require(
      (answeredInRound >= roundId) &&
        (updatedAt >= block.timestamp - maxChainlinkOracleTimeDelay),
      "Chainlink oracle price is stale"
    );

CashManager.sol#L336-L350, CashManager.sol#L526-L528, CashManager.sol#L533-L535, KYCRegistry.sol#L144-L150


Rolezn G

## GAS Summary

### Gas Optimizations
| |Issue|Contexts|Estimated Gas Saved|
|-|:-|:-|:-:|
| [GAS‑1](#GAS‑1) | Consider activating `via-ir` for deploying | 1 | - |
| [GAS‑2](#GAS‑2) | Use assembly to emit events | 53 | 2014 |
| [GAS‑3](#GAS‑3) | Use `assembly` to write address storage values | 4 | - |
| [GAS‑4](#GAS‑4) | Structs can be packed into fewer storage slots by editing time variables | 1 | 2000 |
| [GAS‑5](#GAS‑5) | State variables can be packed into fewer storage slots | 3 | 6000 |
| [GAS‑6](#GAS‑6) | Use solidity version 0.8.20 to gain some gas boost | 21 | 1848 |

Total: 83 contexts over 6 issues

## Gas Optimizations

### [GAS‑1] Consider activating `via-ir` for deploying

The IR-based code generator was introduced with an aim to not only allow code generation to be more transparent and auditable but also to enable more powerful optimization passes that span across functions.

You can enable it on the command line using `--via-ir` or with the option `{"viaIR": true}`.

This will take longer to compile, but you can just simple test it before deploying and if you got a better benchmark then you can add --via-ir to your deploy command

More on: https://docs.soliditylang.org/en/v0.8.17/ir-breaking-changes.html

### [GAS‑2] Use assembly to emit events

We can use assembly to emit events efficiently by utilizing `scratch space` and the `free memory pointer`. This will allow us to potentially avoid memory expansion costs.
Note: In order to do this optimization safely, we will need to cache and restore the free memory pointer.

For example, for a generic `emit` event for `eventSentAmountExample`:
```solidity
// uint256 id, uint256 value, uint256 amount
emit eventSentAmountExample(id, value, amount);
```

We can use the following assembly emit

        assembly {
            let memptr := mload(0x40)
            mstore(0x00, calldataload(0x44))
            mstore(0x20, calldataload(0xa4))
            mstore(0x40, amount)
            log1(
                0x00,
                0x60,
                // keccak256("eventSentAmountExample(uint256,uint256,uint256)")
                0xa622cf392588fbf2cd020ff96b2f4ebd9c76d7a4bc7f3e6b2f18012312e76bc3
            )
            mstore(0x40, memptr)
        }

// Code block 1 (solidity):
// uint256 id, uint256 value, uint256 amount
emit eventSentAmountExample(id, value, amount);


Aymen0909 G

# Gas Optimizations

## Summary

|               | Issue         | Instances     |
| :-------------: |:-------------|:-------------:|
| 1  | Multiple address/IDs mappings can be combined into a single mapping of an address/id to a struct | 2 |
| 2  | `storage` variable should be cached into `memory` variables instead of re-reading them  |  5 |
| 3  | Use `unchecked` blocks to save gas  | 1 |
| 4  | `derivatives[i].balance()` should be cached into memory  | 2 |
| 5  | `derivativeCount` should not be read at each loop iteration | 7 |
| 6  | `memory` values should be emitted in events instead of `storage` ones  | 4 |
| 7  | `public` functions not called by the contract should be declared `external` instead | 8 |

## Findings

### 1- Multiple address/IDs mappings can be combined into a single mapping of an address/id to a struct :

Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.

There are 2 instances of this issue :

File: SafEth.sol [Line 22-23](https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEthStorage.sol#L22-L23)
```
mapping(uint256 => IDerivative) public derivatives; // derivatives in the system
mapping(uint256 => uint256) public weights; // weights for each derivative
```

These mappings could be refactored into the following struct and mapping for example :

```
struct Derivative {
    uint256 weight;
    IDerivative derivative;
}

mapping(uint256 => Derivative) public derivatives;
```

### 2- `storage` variable should be cached into `memory` variables instead of re-reading them :

The instance

// Code block 1 (unknown):
mapping(uint256 => IDerivative) public derivatives; // derivatives in the system
mapping(uint256 => uint256) public weights; // weights for each derivative

// Code block 2 (unknown):
struct Derivative {
    uint256 weight;
    IDerivative derivative;
}

mapping(uint256 => Derivative) public derivatives;


Raihan G

# Gas Optimization

# Summary

|   | Issue | Instance
| - | ----- | -------
|[G-01]|  Use double if statements instead of && | 11
|[G-02]|  Make 3 event parameters indexed when possible | 3
|[G-03]| public functions to external | 1
|[G-04]| Access mappings directly rather than using accessor functions | 2
|[G-05]| Amounts should be checked for 0 before calling a transfer | 2
|[G-06]| abi.encode() is less efficient than abi.encodePacked() | 2
|[G-07]| Functions guaranteed to revert when called by normal users can be marked payable | 9
|[G-08]| Change public state variable visibility to private | 2
|[G-09]| <X> += <Y> COSTS MORE GAS THAN <X> = <X> + <Y> FOR STATE VARIABLES | 4
|[G-10]| Use of Bit shift operators | 12
|[G-11]| Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead | 4
|[G-12]| Setting the constructor to payable | 3

## [G-01] Use double if statements instead of &&
 If the if statement has a logical AND and is not followed by an else statement, it can be replaced with 2 if statements.

```solidity
File: /tree/main/src/EthRouter.sol

101     if (block.timestamp > deadline && deadline != 0) {
154     if (block.timestamp > deadline && deadline != 0) {
228     if (block.timestamp > deadline && deadline != 0) {
256     if (block.timestamp > deadline && deadline != 0) {
```
https://github.com/code-423n4/2023-04-caviar/blob/main/src/EthRouter.sol

```solidity
File: /tree/main/src/Factory.sol

87       if ((_baseToken == address(0) && msg.value != baseTokenAmount) || (_baseToken != address(0) && msg.value > 0)) {

```
https://github.com/code-423n4/2023-04-caviar/blob/main/src/Factory.sol#L87

```solidity
File: /tree/main/src/PrivatePool.sol

225     if (baseToken != address(0) && msg.value > 0) revert InvalidEthAmount();

277     if (royaltyFee > 0 && recipient != address(0)) {

344     if (royaltyFee > 0 && recipient != address(0)) {

397     if (baseToken != address(0) && msg.value > 0) revert InvalidEthAmount();

489     if ((bas

// Code block 1 (solidity):
File: /tree/main/src/EthRouter.sol

101     if (block.timestamp > deadline && deadline != 0) {
154     if (block.timestamp > deadline && deadline != 0) {
228     if (block.timestamp > deadline && deadline != 0) {
256     if (block.timestamp > deadline && deadline != 0) {

// Code block 2 (solidity):
File: /tree/main/src/Factory.sol

87       if ((_baseToken == address(0) && msg.value != baseTokenAmount) || (_baseToken != address(0) && msg.value > 0)) {

File: /tree/main/src/EthRouter.sol

101     if (block.timestamp > deadline && deadline != 0) {
154     if (block.timestamp > deadline && deadline != 0) {
228     if (block.timestamp > deadline && deadline != 0) {
256     if (block.timestamp > deadline && deadline != 0) {

File: /tree/main/src/Factory.sol

87       if ((_baseToken == address(0) && msg.value != baseTokenAmount) || (_baseToken != address(0) && msg.value > 0)) {


lsaudit G

# [G-01] Using a SafeMath library, where substraction can be unchecked.

```
File: contracts/lybra/token/EUSD.sol

396:     require(_sharesAmount <= currentSenderShares, "TRANSFER_AMOUNT_EXCEEDS_BALANCE");
397:
398:        shares[_sender] = currentSenderShares.sub(_sharesAmount);
```

Line 396 requires that `_sharesAmount <= currentSenderShares`, thus `currentSenderShares - (_sharesAmount)` will never underflow. This operation can be `unchecked`, instead of being calculated with a help of SafeMath `sub` function.

// Code block 1 (unknown):
File: contracts/lybra/token/EUSD.sol

396:     require(_sharesAmount <= currentSenderShares, "TRANSFER_AMOUNT_EXCEEDS_BALANCE");
397:
398:        shares[_sender] = currentSenderShares.sub(_sharesAmount);


HE1M Q

#### Q1
The comment states that:
`amount Must be higher than 0. Individual mint amount shouldn't surpass 10% when the circulation reaches 10_000_000`
https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/base/LybraEUSDVaultBase.sol#L124C10-L124C126
But it is not implemented same as what the comment suggests.
This amount is limited to `mintVaultMaxSupply` set in the `LybraConfigurator`:
https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/base/LybraEUSDVaultBase.sol#L260
https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/configuration/LybraConfigurator.sol#L119
##### Recommendation
Whether remove this comment or implement such functionality in `mint(...)`:
```
     if (
            (borrowed[msg.sender] * 100) / poolTotalEUSDCirculation > 10 &&
            poolTotalEUSDCirculation  > 10_000_000 * 1e18
        ) revert("Mint Amount cannot be more than 10% of total circulation");
```

#### Q2
Wrong error message in require-statement. `EUSD` should be changed to `PeUSD`.
https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L131

// Code block 1 (unknown):
if (
            (borrowed[msg.sender] * 100) / poolTotalEUSDCirculation > 10 &&
            poolTotalEUSDCirculation  > 10_000_000 * 1e18
        ) revert("Mint Amount cannot be more than 10% of total circulation");

LybraEUSDVaultBase.sol#L124-L10, LybraEUSDVaultBase.sol#L260, LybraConfigurator.sol#L119, LybraPeUSDVaultBase.sol#L131


0xAgro Q

# QA Report
## Finding Summary

[**Low Severity**](#Low-Severity)
1. [**Compiler With Known Bug May Be Used**](#1-Compiler-With-Known-Bug-May-Be-Used)
2. [**minAmount maxAmount Input Sanitization**](#2-minAmount-maxAmount-Input-Sanitization)
3. [**Single Step Owner Transfer**](#3-Single-Step-Owner-Transfer)
4. [**Gas Grief If Relayed**](#4-Gas-Grief-If-Relayed)
5. [**Owner Can Bypass setPauseStaking**](#5-Owner-Can-Bypass-setPauseStaking)

[**Non-Critical**](#Non-Critical)
1. [**Explicit Variable Not Used**](#1-Explicit-Variable-Not-Used)
2. [**bytes.concat() Can Be Used Over abi.encodePacked()**](#2-bytesconcat-can-be-used-over-abiencodepacked)
3. [**Inconsistent Named Returns**](#3-Inconsistent-Named-Returns)
4. [**Spelling Mistake**](#4-Spelling-Mistake)
5. [**Unnecessary Collapsed Code**](#5-Unnecessary-Collapsed-Code)
6. [**Inconsistent Single Line If Style**](#6-Inconsistent-Single-Line-If-Style)
7. [**Inconsistent Trailing Period**](#7-inconsistent-trailing-period)
8. [**Inconsistent Comment Capitalization**](#8-Inconsistent-Comment-Capitalization)
9. [**Constant Not Used**](#9-Constant-Not-Used)
10. [**Inconsistent Exponentiation Style**](#10-Inconsistent-Exponentiation-Style)
11. [**Mimicked ERC20 Names**](#11-Mimicked-ERC20-Names)
12. [**ERC20 Token Recovery**](#12-ERC20-Token-Recovery)
13. [**Unbounded Compiler Version**](#13-Unbounded-Compiler-Version)

[**Style Guide Violations**](#Style-Guide-Violations)
1. [**Maximum Line Length**](#1-Maximum-Line-Length)
2. [**Order of Functions**](#2-Order-of-Functions)
3. [**Whitespace in Expressions**](#3-Whitespace-in-Expressions)
4. [**Function Declaration Style**](#4-Function-Declaration-Style)

# Low Severity

## 1. Compiler With Known Bug May Be Used

All contracts in scope ([ex](https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L2)) use a Solidity version of `^0.8.13`. There is [a known bug](https://medium.com/certora/overly-optimistic-optimizer-certora-bug-disclosure-2101

214:    function setMinAmount(uint256 _minAmount) external onlyOwner {
215:        minAmount = _minAmount;
216:        emit ChangeMinAmount(minAmount);
217:    }

223:    function setMaxAmount(uint256 _minAmount) external onlyOwner {
224:        maxAmount = _maxAmount;
225:        emit ChangeMaxAmount(maxAmount);
226:    }


8olidity Q

## There is no need to specify gas, call() is originally unlimited gas

There is no need to specify gas as type(uint256).max in call()
```solidity
File: scw-contracts/contracts/smart-contract-wallet/BaseSmartAccount.sol

    function _payPrefund(uint256 missingAccountFunds) internal virtual {
        if (missingAccountFunds != 0) {
            (bool success,) = payable(msg.sender).call{value : missingAccountFunds, gas : type(uint256).max}(""); //@audit
            (success);
            //ignore failure (its EntryPoint's job to verify, not account.)
        }
    }
```

## Transfer permissions require two-step authentication
Transferring the owner's permission requires the newowner address to accept it, rather than directly transferring the permission to the new address

```solidity
File: scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol

    function setOwner(address _newOwner) external mixedAuth { //@audit
        require(_newOwner != address(0), "Smart Account:: new Signatory address cannot be zero");
        address oldOwner = owner;
        owner = _newOwner;
        emit EOAChanged(address(this), oldOwner, _newOwner);
    }
```

## updateImplementation() should check if the `_implementation address` is 0

In the updateImplementation() function, directly assign the incoming _implementation address to _IMPLEMENTATION_SLOT without any judgment

```solidity
File: scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol

    function updateImplementation(address _implementation) external mixedAuth {
        require(_implementation.isContract(), "INVALID_IMPLEMENTATION"); //@audit
        _setImplementation(_implementation);
        // EOA + Version tracking
        emit ImplementationUpdated(address(this), VERSION, _implementation);
    }
```

## `_validateSignature()` return deadline is always 0

```solidity
File: scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol

    function _validateSignature(UserOperation calldata use

// Code block 1 (solidity):
File: scw-contracts/contracts/smart-contract-wallet/BaseSmartAccount.sol

    function _payPrefund(uint256 missingAccountFunds) internal virtual {
        if (missingAccountFunds != 0) {
            (bool success,) = payable(msg.sender).call{value : missingAccountFunds, gas : type(uint256).max}(""); //@audit
            (success);
            //ignore failure (its EntryPoint's job to verify, not account.)
        }
    }

// Code block 2 (solidity):
File: scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol

    function setOwner(address _newOwner) external mixedAuth { //@audit
        require(_newOwner != address(0), "Smart Account:: new Signatory address cannot be zero");
        address oldOwner = owner;
        owner = _newOwner;
        emit EOAChanged(address(this), oldOwner, _newOwner);
    }

// Code block 3 (solidity):
File: scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol

    function updateImplementation(address _implementation) external mixedAuth {
        require(_implementation.isContract(), "INVALID_IMPLEMENTATION"); //@audit
        _setImplementation(_implementation);
        // EOA + Version tracking
        emit ImplementationUpdated(address(this), VERSION, _implementation);
    }

File: scw-contracts/contracts/smart-contract-wallet/BaseSmartAccount.sol

    function _payPrefund(uint256 missingAccountFunds) internal virtual {
        if (missingAccountFunds != 0) {
            (bool success,) = payable(msg.sender).call{value : missingAccountFunds, gas : type(uint256).max}(""); //@audit
            (success);
            //ignore failure (its EntryPoint's job to verify, not account.)
        }
    }

File: scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol

    function setOwner(address _newOwner) external mixedAuth { //@audit
        require(_newOwner != address(0), "Smart Account:: new Signatory address cannot be zero");
        address oldOwner = owner;
        owner = _newOwner;
        emit EOAChanged(address(this), oldOwner, _newOwner);
    }

File: scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol

    function updateImplementation(address _implementation) external mixedAuth {
        require(_implementation.isContract(), "INVALID_IMPLEMENTATION"); //@audit
        _setImplementation(_implementation);
        // EOA + Version tracking
        emit ImplementationUpdated(address(this), VERSION, _implementation);
    }


mojito_auditor Q

# Summary

| Id | Title |
| --- | --- |
| L-1 | VaultDeployer constructor is payable but there is no way to get ETH out |
| L-2 | Code and comment do not match in function GovernorCharlie.cancel() |
| L-3 | Incorrect Collateral Type When Owner Uses `updateRegisteredErc20()` |

# L-1. VaultDeployer constructor is payable but there is no way to get ETH out

https://github.com/code-423n4/2023-07-amphora/blob/41cf810b02a150bb168698ff1eeb1f768d6bdcb0/core/solidity/contracts/core/VaultDeployer.sol#L18

## Detail

In the `VaultDeployer` contract, the constructor is `payable` and can receive ETH. However, ETH is not used anywhere in the contract and there is no function to withdraw it either.

```solidity
/// @param _cvx The address of the CVX token
/// @param _crv The address of the CRV token
constructor(IERC20 _cvx, IERC20 _crv) payable { // @audit payable but no way to get ETH out
  CVX = _cvx;
  CRV = _crv;
}

```

## Recommendation

Consider removing `payable` or adding a function to withdraw ETH.

# L-2. Code and comment do not match in function `GovernorCharlie.cancel()`

https://github.com/code-423n4/2023-07-amphora/blob/41cf810b02a150bb168698ff1eeb1f768d6bdcb0/core/solidity/contracts/governance/GovernorCharlie.sol#L361-L379

## Detail

In the function `cancel()`, the comment says:

> Whitelisted proposers can't be canceled for falling below proposal threshold.
>

However, the code still allows the whitelisted proposals to be cancelled:

```solidity
// Whitelisted proposers can't be canceled for falling below proposal threshold
if (isWhitelisted(_proposal.proposer)) {
    // @audit Contradict with the comment above.
    //        If it falls below proposal threshold, it won't revert and will be allowed to be cancelled
    if (
      (amph.getPriorVotes(_proposal.proposer, (block.number - 1)) >= proposalThreshold)
        || msg.sender != whitelistGuardian
    ) revert GovernorCharlie_WhitelistedProposer();
}

```

## Recommendation

Consider fixing the code or upda

// Code block 1 (solidity):
/// @param _cvx The address of the CVX token
/// @param _crv The address of the CRV token
constructor(IERC20 _cvx, IERC20 _crv) payable { // @audit payable but no way to get ETH out
  CVX = _cvx;
  CRV = _crv;
}

// Code block 2 (solidity):
// Whitelisted proposers can't be canceled for falling below proposal threshold
if (isWhitelisted(_proposal.proposer)) {
    // @audit Contradict with the comment above.
    //        If it falls below proposal threshold, it won't revert and will be allowed to be cancelled
    if (
      (amph.getPriorVotes(_proposal.proposer, (block.number - 1)) >= proposalThreshold)
        || msg.sender != whitelistGuardian
    ) revert GovernorCharlie_WhitelistedProposer();
}

/// @param _cvx The address of the CVX token
/// @param _crv The address of the CRV token
constructor(IERC20 _cvx, IERC20 _crv) payable { // @audit payable but no way to get ETH out
  CVX = _cvx;
  CRV = _crv;
}

// Whitelisted proposers can't be canceled for falling below proposal threshold
if (isWhitelisted(_proposal.proposer)) {
    // @audit Contradict with the comment above.
    //        If it falls below proposal threshold, it won't revert and will be allowed to be cancelled
    if (
      (amph.getPriorVotes(_proposal.proposer, (block.number - 1)) >= proposalThreshold)
        || msg.sender != whitelistGuardian
    ) revert GovernorCharlie_WhitelistedProposer();
}


ddimitrov22 Q

## Summary

| Number | Issues Details                                          |
| ------ | ------------------------------------------------------- |
| [L-01] | Outdated compiler version                               |
| [N-01] | Lock `pragma` to specific compiler version              |
| [N-02] | Assert statements should not be used                    |
| [N-03] | Using vulnerable dependency of OpenZeppelin             |
| [N-04] | For modern and more readable code; update import usages |

## [L-01] Outdated compiler version

Very old compiler version is used inside the contracts of Ethos-Core. There are a lot of major improvements and bug fixes in the new compiler versions which the protocol will be missing on.

**Context**
5 results - 5 files

    Ethos-Core/contracts/ActivePool.sol:
    3: pragma solidity 0.6.11;

    Ethos-Core/contracts/LUSDToken.sol:
    3: pragma solidity 0.6.11;

    Ethos-Core/contracts/StabilityPool.sol:
    3: pragma solidity 0.6.11;

    Ethos-Core/contracts/LQTY/CommunityIssuance.sol:
    3: pragma solidity 0.6.11;

    Ethos-Core/contracts/LQTY/LQTYStaking.sol:
    3: pragma solidity 0.6.11;

**Recommendation:**

Use a more recent version of the compiler. The latest compiler version available is 0.8.18.

## [N-01] Lock `pragma` to specific compiler version

Pragma statements can be allowed to float when a contract is intended for consumption by other developers, as in the case with contracts in a library or EthPM package. Otherwise, the developer would need to manually update the pragma in order to compile locally. All of the contracts inside Ethos-Vault which are in scope use floatable pragma.

**Recommendation:**
Ethereum Smart Contract Best Practices - Lock pragmas to specific compiler version.

[solidity-specific/locking-pragmas](https://consensys.github.io/smart-contract-best-practices/development-recommendations/solidity-specific/locking-pragmas/)

## [N-02] Assert statements should not be used

Properly functioning code should

import {Owned} from "solmate/auth/Owned.sol";
import {ERC721} from "solmate/tokens/ERC721.sol";
import {LibString} from "solmate/utils/LibString.sol";
import {MerkleProofLib} from "solmate/utils/MerkleProofLib.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {ERC1155, ERC1155TokenReceiver} from "solmate/tokens/ERC1155.sol";
import {toWadUnsafe, toDaysWadUnsafe} from "solmate/utils/SignedWadMath.sol";


fr33rh Analysis

## Systemic risks
`RSETH` represents the total price of different assets, and if one asset experiences significant fluctuations, other assets will also be affected.

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L91-L110
```
    /// @notice View amount of rsETH to mint for given asset amount
    /// @param asset Asset address
    /// @param amount Asset amount
    /// @return rsethAmountToMint Amount of rseth to mint
    function getRsETHAmountToMint(
        address asset,
        uint256 amount
    )
        public
        view
        override
        returns (uint256 rsethAmountToMint)
    {
        // setup oracle contract
        address lrtOracleAddress = lrtConfig.getContract(LRTConstants.LRT_ORACLE);
        ILRTOracle lrtOracle = ILRTOracle(lrtOracleAddress);

        // calculate rseth amount to mint based on asset amount and asset exchange rate
        rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice();
    }
```
## Other recommendations
Due to design, there is currently no withdraw function, which results in a significant reduction in attack surface area.
After improving the relevant functions, it is strongly recommended to hold another contest.

## learnings from the audit

`LST` has great potential,and `Eignelayer` is very interesting,I will continue to study after the contest.
```
https://github.com/Layr-Labs/eigenlayer-contracts
```

### Time spent:
28 hours

// Code block 1 (unknown):
/// @notice View amount of rsETH to mint for given asset amount
    /// @param asset Asset address
    /// @param amount Asset amount
    /// @return rsethAmountToMint Amount of rseth to mint
    function getRsETHAmountToMint(
        address asset,
        uint256 amount
    )
        public
        view
        override
        returns (uint256 rsethAmountToMint)
    {
        // setup oracle contract
        address lrtOracleAddress = lrtConfig.getContract(LRTConstants.LRT_ORACLE);
        ILRTOracle lrtOracle = ILRTOracle(lrtOracleAddress);

        // calculate rseth amount to mint based on asset amount and asset exchange rate
        rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice();
    }

// Code block 2 (unknown):
https://github.com/Layr-Labs/eigenlayer-contracts


CodeFoxInc Q

# Low Risk and Non-Critical Issues

## ****Codebase Impressions & Summary****

The core contracts extended the functionality of the collaterals categories, with not only Ether can be added as collateral. And the protocol added a Vault function besides it to make the collateral assets’ efficiency better so that the stable coin’s backing assets generate interest as well.

A single Liquity code base can already be a good example of project to get audited. The Ethos protocol was built upon it and added several customized functions. These functionalities combined with each other makes the protocol more complex.

This audit contest partially does not include the Liquity protocol code base but a good understanding of it is needed. And Ethos Reserve expanded the functionality over it. As a result, the code base is quite large.

The overall quality of the code is good, inline comments provided are helpful in describing the function of the code. But in terms of documentations, there are improvements can be made, e.g.,  natspec documentation style is recommended in my opinion. Tests ran without issues and can be modified to run POCs easily.

The findings are made mainly focused on the best practice and code quality, documentations, etc. At the same time, there are some low-risk issues about the upgradeability and function arguments’ validations.

## L-01 `_treasuryAddress` is not `checkContract`ed

In the ActivePool contract, the `_treasuryAddress` is not `checkContract`ed. I understand it can be an EOA, but as a big protocol which is going to generate millions of revenue and fees. I suggest put the `checkContract` function and make sure it is a multi-sig wallet.

[https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/ActivePool.sol#L93](https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/ActivePool.sol#L93)

```diff
+       checkContract(_treasuryAddress);

## L-02 Consider mitigating the centralization risk

The code base includes quite a lot of `renounceOwnership()` function to make sure the protocol is decentralized enough to be permission-less and trusted.

At the same time, `priceFeed` contract is quite an important feature and it should not be controlled by a single entity. Price feed can be updated by the owner address in this the current situation.

Team should consider set it as `guardianAddress` or `governanceAddress` instead like what is done in the `LUSDToken` contract. Or you can make the transition into a community governable feature.

[https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/PriceFeed.sol#L143-L146](https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/PriceFeed.sol#L143-L146)

[https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/PriceFeed.sol#L24](https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/PriceFeed.sol#L24)

And same thing can also happen in `CommunityIssuance` contract.

## L-03 Should check the `token` address and make sure it is a contract address

Should check the address to make sure it is a contract address or zero address.

[https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Vault/contracts/ReaperVaultV2.sol#L120](https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Vault/contracts/ReaperVaultV2.sol#L120)

### Recommendation

Should import the `checkContract` function and use it as a checker for `_token`.

## L-04 The `tvlCap` should be initialized as a reasonable value

The `tvlCap` value is set to zero when the input of it is zero. This makes the Vault not usable at the beginning. When the `tvlCap` is zero usually it means there is no limit, so it is reasonable to set the value as unlimited value(`type(uint256).max`). If the dev wants to set the vault as unusable in the beginning it also can be done by setting the value extremely low, e.g. 1 wei. Anyways, if dev deploys the contract it is supposed to work in the first place.

[https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Vault/contracts/ReaperVaultV2.sol#L123](https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Vault/contracts/ReaperVaultV2.sol#L123)

### Recommendation

Make a change as below:

ActivePool.sol#L93, PriceFeed.sol#L143-L146, PriceFeed.sol#L24, ReaperVaultV2.sol#L120, ReaperVaultV2.sol#L123


abiih Q

# Report

---

## Low Risks

---

|     | Issue                                                                                |
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1   | [Add non-zero address checks for address arguments in constructors](#usage-of-uintsints-smaller-than-32-bytes-256-bits-incurs-overhead)                                         |
| 2   | [Fix flash loan fee is unfair to the pool owner as well as to the user ](#fix-flash-loan-fee-is-unfair-to-the-pool-owner-as-well-as-to-the-user)                                |
| 3   | [Royalty receiver will not get correct royalty as saleprice is not calculated properly](#royalty-receiver-will-not-get-correct-royalty-as-saleprice-is-not-calculated-properly) |

---

1. ### Add non-zero address checks for address arguments in constructors

   check address value for zero

   - https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L143
   - https://github.com/code-423n4/2023-04-caviar/blob/main/src/EthRouter.sol#L90

2. ### Fix flash loan fee is unfair to the pool owner as well as to the user

- https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L632
- https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L750

```
Here the changeFee is used as the flash loan fee whenever the user initiates the flash loan. This is unfair to both the pool owner as well as to the user.
Different NFT has different rarity as well as value. When the flash loan fee is fixed then pool owner will not be able to collect the proper fee and also the user who want to use the NFT with less rarity and usefulness has to pay the same price as that of the NFT of higher price.
```

3. ### Royalty receiver will not get correct royalty as saleprice i

// Code block 1 (unknown):
Here the changeFee is used as the flash loan fee whenever the user initiates the flash loan. This is unfair to both the pool owner as well as to the user.
Different NFT has different rarity as well as value. When the flash loan fee is fixed then pool owner will not be able to collect the proper fee and also the user who want to use the NFT with less rarity and usefulness has to pay the same price as that of the NFT of higher price.

PrivatePool.sol#L143, EthRouter.sol#L90, PrivatePool.sol#L632, PrivatePool.sol#L750


ch0bu Q

# Non-critical

## 1. Use a more recent version of Solidity

- Use a solidity version of at least 0.8.0 to get overflow/underflow protection without `SafeMath`
- Use a solidity version of at least 0.8.2 to get compiler automatic inlining
- Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads
- Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than `revert()/require()` strings
- Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value
- Use a solidity version of at least 0.8.12 to get `string.concat()` to be used instead of `abi.encodePacked(<str>,<str>)`
- Use a solidity version of at least 0.8.13 to get the ability to use `using for` with a list of free functions
- Use a solidity version of 0.8.15 where conditions necessary for inlining are relaxed. It shows significant dicrease in the bytecode size (and thus reduced deployment cost)
- Use a solidity version of 0.8.17 to prevent the incorrect removal of storage writes before calls to Yul functions that conditionally terminate the external EVM call; Simplify the starting offset of zero-length operations to zero; More efficient overflow checks for multiplication

```
All contracts
```

## 2. Empty blocks should be removed or emit something

The code should be refactored such that they no longer exist, or the block should do something useful, such as emitting an event or reverting. If the contract is meant to be extended, the contract should be abstract and the function signatures be added without any default implementation.
- If the block is an empty if-statement block to avoid doing subsequent checks in the else-if/else conditions, the else-if/else conditions should be nested under the negation of the if-statement, because they involve different classes of checks, which may lead to the introduction of errors when the code is later mo

// Code block 1 (unknown):
All contracts


cartlex_ Q

| ID | Description | Severity |
| :-: | - | :-: |
| [L-01](#l-01-admin-can-use-renouncerole-for-self) | Admin can use `renounceRole` for self | Low |
| [L-02](#l-02-transferassettonodedelegator-function-can-be-used-when-contract-is-paused)| `transferAssetToNodeDelegator()` function can be used when contract is paused. | Low |
| [L-03](#l-03-burnfrom-function-burn-tokens-without-an-allowance)| `burnFrom` function burn tokens without an allowance. | Low |
| [NC-01](#nc-01-updateassetstrategy-doesnt-used-in-constructor)| `updateAssetStrategy()` doesn't used in constructor. | Non-critical |
| [NC-02](#nc-02-lack-of-check-in-getassetcurrentlimit)| Lack of check in `getAssetCurrentLimit()`. | Non-critical |

# [L-01] Admin can use `renounceRole` for self.

## Description
`LRTConfig.sol` and `RSETH.sol` contracts inherited from `AccessControlUpgradeable.sol`.

Admin can renounce role to self, for example, if there is only one account with `DEFAULT_ADMIN_ROLE` role, after admin renounce it, some important functionality can't be used.

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTConfig.sol

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/RSETH.sol

## Recommended Mitigation Steps
Consider to override `renounceRole` so the admin can't renounce `DEFAULT_ADMIN_ROLE` for self.

# [L-02] `transferAssetToNodeDelegator()` function can be used when contract is paused.

## Description
Manager still can use `transferAssetToNodeDelegator()` function when `LRTDepositPool.sol` contract is paused due to lack of `whenNotPaused` modifier.

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L183-L197

e.g. `transferBackToLRTDepositPool()` function does the opposite operation and it has `whenNotPaused` modifier.

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/NodeDelegator.sol#L79

## Reco

# [L-03] `burnFrom` function burn tokens without an allowance.

## Description
`burnFrom` function can burn user tokens without an allowance. Despite this function can be called by trusted `BURNER_ROLE` this function can be unexpectedly called by mistake.

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/RSETH.sol#L54-L56

## Recommended Mitigation Steps
Consider implement the next changes:

LRTConfig.sol, RSETH.sol, LRTDepositPool.sol#L183-L197, NodeDelegator.sol#L79, RSETH.sol#L54-L56

LRTConfig.sol, LRTDepositPool.sol, RSETH.sol, NodeDelegator.sol


dharma09 Q

### [L-01] Wrong condition check violates the rule of liquidity pool

**Details:**
The condition `require((reserves.reserve0 >= PoolUtils.DUST) && (reserves.reserve1 >= PoolUtils.DUST), "Insufficient reserves after liquidity removal");` checks whether both reserve0 and reserve1 are greater than or equal to the value specified by PoolUtils.DUST. This condition is intended to ensure that the removal of liquidity does not deplete the reserves below a certain threshold, defined by PoolUtils.DUST. The purpose is to prevent the reserves from becoming too low, which could potentially Details the stability and functionality of the liquidity pool.
**Proof of Code:**
- [Pools.sol#L170](https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/Pools.sol#L170C2-L194C1)
```solidity
function removeLiquidity( IERC20 tokenA, IERC20 tokenB, uint256 liquidityToRemove, uint256 minReclaimedA, uint256 minReclaimedB, uint256 totalLiquidity ) external nonReentrant returns (uint256 reclaimedA, uint256 reclaimedB)
		{
		require( msg.sender == address(collateralAndLiquidity), "Pools.removeLiquidity is only callable from the CollateralAndLiquidity contract" );
		require( liquidityToRemove > 0, "The amount of liquidityToRemove cannot be zero" );

		(bytes32 poolID, bool flipped) = PoolUtils._poolIDAndFlipped(tokenA, tokenB);

		// Determine how much liquidity is being withdrawn and round down in favor of the protocol
		PoolReserves storage reserves = _poolReserves[poolID];
		reclaimedA = ( reserves.reserve0 * liquidityToRemove ) / totalLiquidity;
		reclaimedB = ( reserves.reserve1 * liquidityToRemove ) / totalLiquidity;

		reserves.reserve0 -= uint128(reclaimedA);
		reserves.reserve1 -= uint128(reclaimedB);

		// Make sure that removing liquidity doesn't drive either of the reserves below DUST.
		// This is to ensure that ratios remain relatively constant even after a maximum withdrawal.
        require((reserves.reserve0 >= PoolUtils.DUST) && (reserves.reserve0 >= PoolUtils.DUST), "Ins

function removeLiquidity( IERC20 tokenA, IERC20 tokenB, uint256 liquidityToRemove, uint256 minReclaimedA, uint256 minReclaimedB, uint256 totalLiquidity ) external nonReentrant returns (uint256 reclaimedA, uint256 reclaimedB)
		{
		require( msg.sender == address(collateralAndLiquidity), "Pools.removeLiquidity is only callable from the CollateralAndLiquidity contract" );
		require( liquidityToRemove > 0, "The amount of liquidityToRemove cannot be zero" );

		(bytes32 poolID, bool flipped) = PoolUtils._poolIDAndFlipped(tokenA, tokenB);

		// Determine how much liquidity is being withdrawn and round down in favor of the protocol
		PoolReserves storage reserves = _poolReserves[poolID];
		reclaimedA = ( reserves.reserve0 * liquidityToRemove ) / totalLiquidity;
		reclaimedB = ( reserves.reserve1 * liquidityToRemove ) / totalLiquidity;

		reserves.reserve0 -= uint128(reclaimedA);
		reserves.reserve1 -= uint128(reclaimedB);

		// Make sure that removing liquidity doesn't drive either of the reserves below DUST.
		// This is to ensure that ratios remain relatively constant even after a maximum withdrawal.
        require((reserves.reserve0 >= PoolUtils.DUST) && (reserves.reserve0 >= PoolUtils.DUST), "Insufficient reserves after liquidity removal"); //@audit require check wrong check reserve0 -> reserve1

		// Switch reclaimed amounts back to the order that was specified in the call arguments so they make sense to the caller
		if (flipped)
			(reclaimedA,reclaimedB) = (reclaimedB,reclaimedA);

		require( (reclaimedA >= minReclaimedA) && (reclaimedB >= minReclaimedB), "Insufficient underlying tokens returned" );

		// Send the reclaimed tokens to the user
		tokenA.safeTransfer( msg.sender, reclaimedA );
		tokenB.safeTransfer( msg.sender, reclaimedB );

		emit LiquidityRemoved(tokenA, tokenB, reclaimedA, reclaimedB, liquidityToRemove);
		}

### [L-02] potential arithmetic underflow in `_arbitrage()`

**Details:**
Subtraction operation where arbitrageAmountIn is subtracted from amountOut. If amountOut is less than arbitrageAmountIn, it can lead to an underflow, and the result would wrap around to a very large positive number.

Assuming the amountOut is greater than arbitrageAmountIn, the function continues with depositing the arbitrage profit to the DAO and updating stats related to the pools involved in the arbitrage.
These actions, if successfully executed, could indirectly affect the state of the liquidity pool. For example, depositing arbitrage profits to the DAO might Details the available liquidity in the pool or influence the distribution of rewards.

**Proof of Code:**
- [Pools.sol#L283](https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/Pools.sol#L283C2-L298C4)


cryptostellar5 Q

### 1. REQUIRE() SHOULD BE USED INSTEAD OF ASSERT()

https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/factory/CashFactory.sol

```
97: assert(cashProxyAdmin.owner() == guardian);
```

https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/factory/CashKYCSenderFactory.sol

```
106: assert(cashKYCSenderProxyAdmin.owner() == guardian);
```

https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/factory/CashKYCSenderReceiverFactory.sol

```
106: assert(cashKYCSenderReceiverProxyAdmin.owner() == guardian);
```

### 2. UPGRADEABLE CONTRACT IS MISSING A `__GAP[50]` STORAGE VARIABLE TO ALLOW FOR NEW STORAGE VARIABLES IN LATER VERSIONS

*Number of Instances Identified: 4*

See [this](https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps) link for a description of this storage variable. While some contracts may not currently be sub-classed, adding the variable now protects against forgetting to add it in the future.

https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/Proxy.sol

```
20-26: contract TokenProxy is TransparentUpgradeableProxy {
  constructor(
    address _logic,
    address _admin,
    bytes memory _data
  ) TransparentUpgradeableProxy(_logic, _admin, _data) {}
}
```

https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/token/Cash.sol

```
21: contract Cash is ERC20PresetMinterPauserUpgradeable
```

https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/token/CashKYCSender.sol

```
22-24: contract CashKYCSender is
  ERC20PresetMinterPauserUpgradeable,
  KYCRegistryClientInitializable
```

https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/token/CashKYCSenderReceiver.sol

```
22-24: contract CashKYCSenderReceiver is
  ERC20PresetMinterPauserUpgradeable,
  KYCRegistryClientInitializable
```

### 3. MISSING EVENTS FOR FUNCTIONS THAT CHANGE CRITICAL PARAMETERS

The functions that change critical parameters should emit events. Events allow captu

// Code block 1 (unknown):
97: assert(cashProxyAdmin.owner() == guardian);

// Code block 2 (unknown):
106: assert(cashKYCSenderProxyAdmin.owner() == guardian);

// Code block 3 (unknown):
106: assert(cashKYCSenderReceiverProxyAdmin.owner() == guardian);

// Code block 4 (unknown):
20-26: contract TokenProxy is TransparentUpgradeableProxy {
  constructor(
    address _logic,
    address _admin,
    bytes memory _data
  ) TransparentUpgradeableProxy(_logic, _admin, _data) {}
}

// Code block 5 (unknown):
21: contract Cash is ERC20PresetMinterPauserUpgradeable

// Code block 6 (unknown):
22-24: contract CashKYCSender is
  ERC20PresetMinterPauserUpgradeable,
  KYCRegistryClientInitializable

// Code block 7 (unknown):
22-24: contract CashKYCSenderReceiver is
  ERC20PresetMinterPauserUpgradeable,
  KYCRegistryClientInitializable

CashFactory.sol, CashKYCSenderFactory.sol, CashKYCSenderReceiverFactory.sol, Proxy.sol, Cash.sol, CashKYCSender.sol, CashKYCSenderReceiver.sol

CashKYCSenderFactory.sol, CashKYCSenderReceiver.sol, CashFactory.sol, Proxy.sol, CashKYCSender.sol, Cash.sol, CashKYCSenderReceiverFactory.sol


0xMilenov Q

## Heading
Role mismatch in addNodeDelegatorContractToQueue function

## Context
[LRTDepositPool.sol](https://github.com/code-423n4/2023-11-kelp/blob/c5fdc2e62c5e1d78769f44d6e34a6fb9e40c00f0/src/LRTDepositPool.sol#L191-L211)

## Impact
The addNodeDelegatorContractToQueue function's control by onlyLRTAdmin, contrary to its NatSpec comment indicating onlyLRTManager access, may lead to administrative overreach and operational inefficiencies.

## Description
At addNodeDelegatorContractToQueue function has a role mismatch. It is currently restricted to LRT Admins but is documented as being accessible only by LRT Managers. This discrepancy can lead to administrative errors and potential security issues, as the admin role might not be intended for operational tasks like updating node delegator contracts.

```solidity
/// @notice add new node delegator contract addresses
    /// @dev only callable by LRT manager
    /// @param nodeDelegatorContracts Array of NodeDelegator contract addresses
    function addNodeDelegatorContractToQueue(
        address[] calldata nodeDelegatorContracts
    ) external onlyLRTAdmin {
        // @audit onlyLRTManager!
        uint256 length = nodeDelegatorContracts.length;
        if (nodeDelegatorQueue.length + length > maxNodeDelegatorCount) {
            revert MaximumNodeDelegatorCountReached();
        }

        for (uint256 i; i < length; ) {
            UtilLib.checkNonZeroAddress(nodeDelegatorContracts[i]);
            nodeDelegatorQueue.push(nodeDelegatorContracts[i]);
            emit NodeDelegatorAddedinQueue(nodeDelegatorContracts[i]);
            unchecked {
                ++i;
            }
        }
    }
```

## Recommendation
Change the modifier to onlyLRTManager.

// Code block 1 (solidity):
/// @notice add new node delegator contract addresses
    /// @dev only callable by LRT manager
    /// @param nodeDelegatorContracts Array of NodeDelegator contract addresses
    function addNodeDelegatorContractToQueue(
        address[] calldata nodeDelegatorContracts
    ) external onlyLRTAdmin {
        // @audit onlyLRTManager!
        uint256 length = nodeDelegatorContracts.length;
        if (nodeDelegatorQueue.length + length > maxNodeDelegatorCount) {
            revert MaximumNodeDelegatorCountReached();
        }

        for (uint256 i; i < length; ) {
            UtilLib.checkNonZeroAddress(nodeDelegatorContracts[i]);
            nodeDelegatorQueue.push(nodeDelegatorContracts[i]);
            emit NodeDelegatorAddedinQueue(nodeDelegatorContracts[i]);
            unchecked {
                ++i;
            }
        }
    }


arialblack14 G

# GAS OPTIMIZATION REPORT ⛽

## [G-1] Use shift right/left instead of division/multiplication if possible.

### Description
A division/multiplication by any number `x` being a power of 2 can be calculated by shifting `log2(x)` to the right/left.
While the `DIV` opcode uses 5 gas, the `SHR` opcode only uses 3 gas.
urthermore, Solidity's division operation also includes a division-by-0 prevention which is bypassed using shifting.

### ✅ Recommendation
You should change multiplication and division with powers of two to bit shift.

#### *Instances(1):*
File: [2023-01-ondo/contracts/cash/CashManager.sol](https://github.com/code-423n4/2023-01-ondo/tree/main/contracts/cash/CashManager.sol#L491 )
```solidity
491: uint256 amountE24 = _scaleUp(collateralAmountIn) * 1e6;
```

## [G-2] `++i/i++` should be `unchecked{++i}`/`unchecked{i++}` when  it is not possible for them to overflow, for example when used in `for` and `while` loops

### Description
In Solidity 0.8+, there’s a default overflow check on unsigned integers. It’s possible to uncheck this in for-loops and save some gas at each iteration, but at the cost of some code readability, as this uncheck [cannot be made inline](https://github.com/ethereum/solidity/issues/10695).
			Example for loop:

```Solidity
for (uint i = 0; i < length; i++) {
// do something that doesn't change the value of i
}
```

In this example, the for loop post condition, i.e., `i++` involves checked arithmetic, which is not required. This is because the value of i is always strictly less than `length <= 2**256 - 1`. Therefore, the theoretical maximum value of i to enter the for-loop body `is 2**256 - 2`. This means that the `i++` in the for loop can never overflow. Regardless, the overflow checks are performed by the compiler.

Unfortunately, the Solidity optimizer is not smart enough to detect this and remove the checks. You should manually do this by:

```Solidity
for (uint i = 0; i < length; i = unchecked_inc(i)) {
	// do something that doesn'

// Code block 1 (solidity):
491: uint256 amountE24 = _scaleUp(collateralAmountIn) * 1e6;

// Code block 2 (Solidity):
for (uint i = 0; i < length; i++) {
// do something that doesn't change the value of i
}

491: uint256 amountE24 = _scaleUp(collateralAmountIn) * 1e6;

for (uint i = 0; i < length; i++) {
// do something that doesn't change the value of i
}


rumen G

### GAS 01 - NodeDelegator.sol:110 -> cache **IStrategy(strategies[i])**: (Saves 118 gas)

```solidity
110: assets[i] = address(IStrategy(strategies[i]).underlyingToken());
111: assetBalances[i] = IStrategy(strategies[i]).userUnderlyingView(address(this));
```
**Suggested edit:**

```solidity
IStrategy strategy = IStrategy(strategies[i]);
assets[i] = address(strategy.underlyingToken());
assetBalances[i] = strategy.userUnderlyingView(address(this));
```

| Function Name                                | min             | avg   | median | max    | # calls |
|----------------------------------------------|-----------------|-------|--------|--------|---------|
| getAssetBalances        (Before)             | 24953           | 24953 | 24953  | 24953  | 1       |
| getAssetBalances        (After)              | 24835           | 24835 | 24835  | 24835  | 1       |

### GAS 02 - LRTDepositPool.sol:83 -> cache **nodeDelegatorQueue[i]** as a variable (Saves 837 gas)

```solidity
83: assetLyingInNDCs += IERC20(asset).balanceOf(nodeDelegatorQueue[i]);
84: assetStakedInEigenLayer += INodeDelegator(nodeDelegatorQueue[i]).getAssetBalance(asset);
```

**Suggested edit:**
```solidity
address currentNdc = nodeDelegatorQueue[i];
assetLyingInNDCs += IERC20(asset).balanceOf(nodeDelegatorQueue[i]);
assetStakedInEigenLayer += INodeDelegator(nodeDelegatorQueue[i]).getAssetBalance(asset);
```

| Function Name                                  | min             | avg   | median | max    | # calls |
|------------------------------------------------|-----------------|-------|--------|--------|---------|
| getAssetDistributionData     (Before)          | 10153           | 10153 | 10153  | 10153  | 1       |
| getAssetDistributionData     (After)           | 9316            | 9316  | 9316   | 9316   | 1       |

### GAS 03 - LRTOracle.sol:68 -> directly use **getAssetPrice(asset)** and **ILRTDepositPool(lrtDepositPoolAddr).getTotalAssetDeposits(asset)** instead of declaring variables **(assetER,

// Code block 1 (solidity):
110: assets[i] = address(IStrategy(strategies[i]).underlyingToken());
111: assetBalances[i] = IStrategy(strategies[i]).userUnderlyingView(address(this));

// Code block 2 (solidity):
IStrategy strategy = IStrategy(strategies[i]);
assets[i] = address(strategy.underlyingToken());
assetBalances[i] = strategy.userUnderlyingView(address(this));

// Code block 3 (solidity):
83: assetLyingInNDCs += IERC20(asset).balanceOf(nodeDelegatorQueue[i]);
84: assetStakedInEigenLayer += INodeDelegator(nodeDelegatorQueue[i]).getAssetBalance(asset);

// Code block 4 (solidity):
address currentNdc = nodeDelegatorQueue[i];
assetLyingInNDCs += IERC20(asset).balanceOf(nodeDelegatorQueue[i]);
assetStakedInEigenLayer += INodeDelegator(nodeDelegatorQueue[i]).getAssetBalance(asset);

110: assets[i] = address(IStrategy(strategies[i]).underlyingToken());
111: assetBalances[i] = IStrategy(strategies[i]).userUnderlyingView(address(this));

IStrategy strategy = IStrategy(strategies[i]);
assets[i] = address(strategy.underlyingToken());
assetBalances[i] = strategy.userUnderlyingView(address(this));

83: assetLyingInNDCs += IERC20(asset).balanceOf(nodeDelegatorQueue[i]);
84: assetStakedInEigenLayer += INodeDelegator(nodeDelegatorQueue[i]).getAssetBalance(asset);

address currentNdc = nodeDelegatorQueue[i];
assetLyingInNDCs += IERC20(asset).balanceOf(nodeDelegatorQueue[i]);
assetStakedInEigenLayer += INodeDelegator(nodeDelegatorQueue[i]).getAssetBalance(asset);


0xweb3boy Analysis

![aiA](https://github.com/0xWeb3boy/photo/assets/113019033/4d4d3c03-0c92-4e41-a09b-06db1da133a6)

#  AI Arena Analysis

## Overview
In the AI Arena - Gaming Competition, participants engage in a dynamic gaming environment where they have the opportunity to purchase, train, and engage in battles with AI-enabled Non-Fungible Tokens (NFTs) within a Player versus Player (PvP) fighting game.

This gaming platform offers a multifaceted experience, where users can acquire NFTs equipped with artificial intelligence capabilities. These NFTs serve as virtual entities that can be nurtured and customized through various training mechanisms, enabling gamers to enhance their abilities and strategize for competitive battles.

Once equipped with their trained NFTs, participants enter the PvP arena, where they engage in thrilling battles against opponents. These battles are characterized by strategic maneuvers, real-time decision-making, and the utilization of unique AI-driven abilities embedded within each NFT.

The AI Arena fosters a competitive gaming atmosphere, where players can showcase their skills, develop effective battle strategies, and strive for victory in exhilarating PvP encounters. Through the integration of AI technology and NFT mechanics, the AI Arena offers a dynamic and immersive gaming experience for enthusiasts seeking engaging and strategic gameplay.

``Smart Contracts``: Utilizes contracts like RankedBattle.sol  which enables users to stake NRN tokens on fighters, monitors battle records, computes and disburses rewards based on battle results and staked quantities, and permits the claiming of accumulated rewards.

``Neuron Token``:Within the platform, the Neuron token serves multiple purposes, including but not limited to staking, governance participation, and reward distribution..

``Risk Management``: With the help of StakeAtRisk this smart contract enables the RankedBattle functionality to oversee the staking process of NRN tokens during battle engagements

function updateAtRiskRecords(
        uint256 nrnToPlaceAtRisk,
        uint256 fighterId,
        address fighterOwner
    )
        external
    {
        require(msg.sender == _rankedBattleAddress, "Call must be from RankedBattle contract");
        stakeAtRisk[roundId][fighterId] += nrnToPlaceAtRisk;
        totalStakeAtRisk[roundId] += nrnToPlaceAtRisk;
        amountLost[fighterOwner] += nrnToPlaceAtRisk;
        emit IncreasedStakeAtRisk(fighterId, nrnToPlaceAtRisk);
    }

 function updateBattleRecord(
        uint256 tokenId,
        uint256 mergingPortion,
        uint8 battleResult,
        uint256 eloFactor,
        bool initiatorBool
    )
        external
    {
        require(msg.sender == _gameServerAddress);
        require(mergingPortion <= 100);
        address fighterOwner = _fighterFarmInstance.ownerOf(tokenId);
        require(
            !initiatorBool ||
            _voltageManagerInstance.ownerVoltageReplenishTime(fighterOwner) <= block.timestamp ||
            _voltageManagerInstance.ownerVoltage(fighterOwner) >= VOLTAGE_COST
        );

        _updateRecord(tokenId, battleResult);
        uint256 stakeAtRisk = _stakeAtRiskInstance.getStakeAtRisk(tokenId);
        if (amountStaked[tokenId] + stakeAtRisk > 0) {
            _addResultPoints(battleResult, tokenId, eloFactor, mergingPortion, fighterOwner);
        }
        if (initiatorBool) {
            _voltageManagerInstance.spendVoltage(fighterOwner, VOLTAGE_COST);
        }
        totalBattles += 1;
    }


Timenov Q

# AI Arena QA report by Timenov

## Summary

| |Issue|Instances|
 |-|:-|:-:|
| I-01 | State variable can be set in constructor. | 6 |
| I-02 | Process failed transfers. | 7 |
| I-03 | No way to change _neuronInstance. | 1 |
| L-01 | Wrong ERC1155 uri. | 1 |
| L-02 | Function will return wrong value if item has infinite supply. | 1 |
| L-03 | Mint will never get to the MAX_SUPPLY. | 1 |

### [I-01] State variables can be set in constructor.
In some contracts, state variable that are referencing other contracts are set in functions(usually their names start with `instantiate`). This can be removed so that the deployer will not need to call these functions on deployment, but only when these variables need to be updated. How each contract can be optimized:

- FighterFarm.sol -> Set `_aiArenaHelperInstance` and `_neuronInstance` in the constructor.
- GameItems.sol -> Set `_neuronInstance` in the constructor.
- MergingPool.sol -> Set `_fighterFarmInstance` in the constructor(this must be deployed after `FighterFarm` contract).
- RankedBattle.sol -> Set `_neuronInstance` and `_mergingPoolInstance` in the constructor.

The other contracts have done this already. Also consider renaming all the functions that will be affected from these changes. Currently they start with `instantiate`. A better naming will be `set` or `update`. All these changes will increase the code readability, remove deployment errors and cost less gas on deployment.

### [I-02] Process failed transfers.
The boolean `success` is taken after `NRN` transfer. However only the `true` value is processed. Consider reverting when the result is `false` so that the users are notified when the token transfer has failed.

https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L376

https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/GameItems.sol#L164

https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b4

    // Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
    string private _uri;

    function remainingSupply(uint256 tokenId) public view returns (uint256) {
        return allGameItemAttributes[tokenId].itemsRemaining;
    }


Beepidibop Q

## [INFO-1] `CollateralAndLiquidity`: Inconsistent Sources for Immutable Assignment in the Constructor

Some immutable states take assignment from `exchangeConfig` while others take assignment from `_exchangeConfig`.

e.g.

```
        usds = _exchangeConfig.usds();

        wbtc = exchangeConfig.wbtc();

        weth = exchangeConfig.weth();
```

It doesn't really matter In practise since `exchangeConfig` is immutable. But it's more professional to stay consistent.

### Recommendation

Change all immutable assignment sources to `_exchangeConfig`.

### Link To Affected Code

https://github.com/code-423n4/2024-01-salty/blob/f742b554e18ae1a07cb8d4617ec8aa50db037c1c/src/stable/CollateralAndLiquidity.sol#L59-L61

// Code block 1 (unknown):
usds = _exchangeConfig.usds();

        wbtc = exchangeConfig.wbtc();

        weth = exchangeConfig.weth();


unique Q

## \[L-01\] Stop Using Solidity's transfer() Now

**Proof Of Concept**
[https://consensys.io/diligence/blog/2019/09/stop-using-soliditys-transfer-now/\[\](https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/master/contracts/markets/singularity/SGLCommon.sol#L215)](https://consensys.io/diligence/blog/2019/09/stop-using-soliditys-transfer-now/%5B%5D%28https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/master/contracts/markets/singularity/SGLCommon.sol#L215%29 "https://consensys.io/diligence/blog/2019/09/stop-using-soliditys-transfer-now/%5B%5D(https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/master/contracts/markets/singularity/SGLCommon.sol#L215)")

```
collateral.transfer(address(receiver), amount);
```

https://github.com/code-423n4/2023-10-badger/blob/main/packages/contracts/contracts/ActivePool.sol#L274

```
collateral.transfer(feeRecipientAddress, fee);
```

https://github.com/code-423n4/2023-10-badger/blob/main/packages/contracts/contracts/ActivePool.sol#L286

## \[L-02\] Function Calls in Loop Could Lead to Denial of Service

Function calls made in unbounded loop are error-prone with potential resource exhaustion as it can trap the contract due to the gas limitations or failed transactions. Here are some of the instances entailed:

```
function getRolesForUser(address user) external view returns (uint8[] memory rolesForUser) {
```

 https://github.com/code-423n4/2023-10-badger/blob/main/packages/contracts/contracts/Governor.sol#L73

Consider bounding the loop where possible to avoid unnecessary gas wastage and denial of service.

## \[L-03\] Insufficient coverage

Description
The test coverage rate of the project is ~99%. Testing all functions is best practice in terms of security criteria.

Due to its capacity, test coverage is expected to be 100%.

# Non- Critical

## \[N-01\] For modern and more readable code; update import usages

Solidity code is also cleaner in another way that might not be noticeable: the struct Point. We were importi

// Code block 1 (unknown):
collateral.transfer(address(receiver), amount);

// Code block 2 (unknown):
collateral.transfer(feeRecipientAddress, fee);

// Code block 3 (unknown):
function getRolesForUser(address user) external view returns (uint8[] memory rolesForUser) {

SGLCommon.sol#L215, ActivePool.sol#L274, ActivePool.sol#L286, Governor.sol#L73


wahedtalash77 Q

# Non-Critical Findings

## [N-01] NatSpec comments should be increased in contracts

https://docs.soliditylang.org/en/v0.8.15/natspec-format.html

## [N-02] Function writing that does not comply with the Solidity Style Guide

https://docs.soliditylang.org/en/v0.8.17/style-guide.html

## [N‑03] Use scientific notation (e.g. 1e27) rather than exponentiation (e.g. 10**27)

While the compiler knows to optimize away the exponentiation, it’s still better coding practice to use idioms that do not require compiler optimization, if they exist.

```
uint256 private constant ONE = 10 ** 27;
```

https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/rwaOracles/RWADynamicOracle.sol#L343

## [N-04] Showing the actual values of numbers in NatSpec comments makes checking and reading code easier

## [N-05] Use constants for numbers

==In several locations in the code, numbers like 1e36, 100e18, 1e27 are used...==
https://github.com/code-423n4/2021-05-yield-findings/issues/3#issuecomment-852039791

## [N-06] Use SMTChecker

The highest tier of smart contract behavior assurance is formal mathematical verification. All assertions that are made are guaranteed to be true across all inputs → The quality of your asserts is the quality of your verification.

https://twitter.com/0xOwenThurm/status/1614359896350425088?t=dbG9gHFigBX85Rv29lOjIQ&s=19

## [N-07] For modern and more readable code; update import usages

Solidity code is also cleaner in another way that might not be noticeable: the struct Point. We were importing it previously with global import but not using it. The Point struct polluted the source code with an unnecessary object we were not using because we did not need it.
This was breaking the rule of modularity and modular programming: only import what you need Specific imports with curly braces allow us to apply this rule better.
*Recommendation*
`import {contract1 , contract2} from "filename.sol";`

## [N-07] Assembly Codes Specif

// Code block 1 (unknown):
uint256 private constant ONE = 10 ** 27;


codeislight G

# Summary
## Gas Optimizations List:
| Number |Issues Details|Instances|Gas Saved|
|:--:|:-------|:--:|:--:|
|[G-1]| Reconstructing mapping struct would save on extra storage slots | 4 | 198900 |
|[G-2]| One time set variables can be defined as immutable | 64 | 134400 |
|[G-3]| LUSDToken can be optimized using ERC20 solmate implementation | 1 | ~ |
|[G-4]| Extra unecessary check overflow, when having a require or if check | 1 | 200 |
|[G-5]| Caching reused storage variables | 33 | 3300 |
|[G-6]| Using return variable area to assign returned value | 45 | 135 |
|[G-7]| Reordering if checks to save on extra check | 1 | 3 |
|[G-8]| Define variables outside of the for loop | 11 | ~ |
|[G-9]| Operations safe to be unchecked | 18 | 981 |

 **~ refers to the fact that the Gas Saved would vary**

### [G-1] Reconstructing mapping struct would save on extra storage slots

In multiple cases, the mappings with the same key are not being efficiently packed, due to the fact that they are being defined seperately. by combining them together in a single mapping, we would be able to tightly pack them together, saving a couple of Gsset storage slots.

3 instances - 3 files

1. ReaperVaultV2.sol: (Saving 3 (Gsset+Gcoldsload) = 3 * (20000+2100) = 66300 gas)
```solidity
    // from:
    struct StrategyParams {
        uint256 activation; // Activation block.timestamp // uint64 is enough
        uint256 feeBPS; // Performance fee taken from profit, in BPS // max value is 2000 uint16
        uint256 allocBPS; // Allocation in BPS of vault's total assets // max value is 10000 uint16
        uint256 allocated; // Amount of capital allocated to this strategy
        uint256 gains; // Total returns that Strategy has realized for Vault
        uint256 losses; // Total losses that Strategy has realized for Vault
        uint256 lastReport; // block.timestamp of the last time a report occured // uint64 is enough
    }
    mapping(address => StrategyParams) public strategies;

    // to:
    stru

    // from:
    struct StrategyParams {
        uint256 activation; // Activation block.timestamp // uint64 is enough
        uint256 feeBPS; // Performance fee taken from profit, in BPS // max value is 2000 uint16
        uint256 allocBPS; // Allocation in BPS of vault's total assets // max value is 10000 uint16
        uint256 allocated; // Amount of capital allocated to this strategy
        uint256 gains; // Total returns that Strategy has realized for Vault
        uint256 losses; // Total losses that Strategy has realized for Vault
        uint256 lastReport; // block.timestamp of the last time a report occured // uint64 is enough
    }
    mapping(address => StrategyParams) public strategies;

    // to:
    struct StrategyParams {
        uint64 activation; // Activation block.timestamp // it's a timestamp <=> uint64
        uint64 lastReport; // block.timestamp of the last time a report occured // it's a timestamp <=> uint64
        uint64 feeBPS; // Performance fee taken from profit, in BPS // max value is 2000 therefore uint16
        uint64 allocBPS; // Allocation in BPS of vault's total assets // max value is 10000 therefore uint16
        uint256 allocated; // Amount of capital allocated to this strategy
        uint256 gains; // Total returns that Strategy has realized for Vault
        uint256 losses; // Total losses that Strategy has realized for Vault
    }
    mapping(address => StrategyParams) public strategies;

    // from
    mapping (address => bytes32) public tellorQueryId;  // collateral => Tellor query ID
    mapping (address => uint) public lastGoodPrice;
    mapping (address => AggregatorV3Interface) public priceAggregator;  // collateral => Mainnet Chainlink aggregator
    mapping (address => Status) public status;

    // to
    struct PriceFeedParams {
        bytes32 tellorQueryId; // 256 bits
        uint256 lastGoodPrice; // 256 bits
        address aggregator; // 160 bits
        Status status; // 8 bits
    }
    mapping (address => PriceFeedParams) public priceFeed;


adriro Q

## Upgradeable contract is missing a __gap[50] storage variable to allow for new storage variables in later versions

Add some storage gap to base contracts to allow adding storage variables in future upgrades.

Occurrences:

- BaseSmartAccount (used by SmartAccount)

Also the following mixins are used in the SmartAccount contract

- Singleton
- ModuleManager
- SignatureDecoder
- SecuredTokenTransfer
- FallbackManager

## Move setters to the "setters" section in SmartAccount contract

Setters defined between lines 94-103 should be placed after line 107.

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L93-L103

## Use `block.chainid` instead of assembly

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L140-L147

In Solidity 0.8 `block.chainid` can be used to retrieve the chain id value.

## Duplicated getter `getNonce` in SmartAccount contract

The getter `getNonce` is already defined by the `nonce(uint256 _batchId)` function.

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L155-L159

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L97-L99

## Wrong require description in SmartAccount contract

Line 171 reads "Invalid Entrypoint", but the check belongs to the "handler" variable.

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L171

## Dedupe code between `handlePayment` and `handlePaymentRevert` functions

Most of the code is duplicated in both functions. Consider extracting common functionality to an internal function.

https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L247-L269
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-

bytes memory deploymentData = abi.encodePacked(type(Proxy).creationCode, abi.encode(_defaultImpl));

function onERC721Received(
    address,
    address,
    uint256,
    bytes calldata
) external pure override returns (bytes4) {
    return ERC721TokenReceiver.onERC721Received.selector;
}

SmartAccount.sol#L93-L103, SmartAccount.sol#L140-L147, SmartAccount.sol#L155-L159, SmartAccount.sol#L97-L99, SmartAccount.sol#L171, SmartAccount.sol#L247-L269