0xSmartContract Q

### QA Report Issues List

- [x] **Low 01** → Role-based `PAUSER_ROLE` is incorrectly defined
- [x] **Low 02** → `swap()` function becomes unusable after 13 years
- [x] **Low 03** → getLpPrice() value is received from Oracle with External call , but doesn't check to value for unexpected return values
- [x] **Low 04** → `maturity` may be less than expected due to arithmetic rounding
- [x] **Low 05** → There is Timelock function information in the comments, but no Timelock architecture can be seen in the project
- [x] **Low 06** → In the mint() function, the MINTER_ROLE mechanism check is used both times, which creates confusion
- [x] **Low 07** → Uses the deprecated  `_setupRole` function
- [x] **Low 08** → Although there is a role-based security approach with Access Control, the same address is defined for all roles.
- [x] **Low 09** → The `BURNER_ROLE ` constructor is also not defined
- [x] **Non-Critical  10** → MANAGER_ROLE is defined but never used
- [x] **Non-Critical  11** → Use `AccessControlDefaultAdminRule` instead of `AccessControl`
- [x] **Non-Critical  12** → Typo
- [x] **Non-Critical  13** → Project Upgrade and Stop Scenario should be
- [x] **Non-Critical  14** → Missing Event for  initialize

</br>
</br>

### [Low-01] Role-based `PAUSER_ROLE` is incorrectly defined

`RdpxReserve.pause() ` and `RdpxReserve.unpause() ` functions have `onlyRole` modifier like a other important functions in Project , but they functions has `DEFAULT_ADMIN_ROL`E check in `onlyRole` modifier instead `PAUSER_ROLE`

Since `DEFAULT_ADMIN_ROLE` and `PAUSER_ROLE` are `msg.sender`, the owner of both roles is the same in theory at first, but it should be noted that these roles can be changed in the future.

The fact that `PAUSER_ROLE` is defined in the constructor, that the function is `pause() ` and `unpase() ` and that `PAUSER_ROLE` is not used elsewhere in the `RdpxReserve` contract proves this.

```solidity

contracts\reserve\RdpxReserve.sol:
  22
  23:   constructor(address _

contracts\reserve\RdpxReserve.sol:
  22
  23:   constructor(address _rdpx) {
  24:     rdpx = _rdpx;
  25:     _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
  26:     _grantRole(PAUSER_ROLE, msg.sender);
  27:   }
  28:
  29:   /**
  30:    * @notice Pauses the vault for emergency cases
  31:    * @dev    Can only be called by the owner
  32:    **/
  33:   function pause() external onlyRole(DEFAULT_ADMIN_ROLE) { // @audit   onlyRole not PAUSER_ROLE
  34:     _pause();
  35:   }
  36:
  37:   /**
  38:    * @notice Unpauses the vault
  39:    * @dev    Can only be called by the owner
  40:    **/
  41:   function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) { // @audit   onlyRole not PAUSER_ROLE
  42:     _unpause();
  43:   }

### [Low-02] swap() function becomes unusable after 13 years

`UniV3LiquidityAmo.swap() ` function is very important role in project, in line 295 also we can see , deadline is 2105300114 unixtime value , this value means ; This function becomes unusable in 13 years later

13 years not as far and this contract not upgradable artitecture.

I recommend that the deadline value be in a user-definable architecture, not hardcoded.


Aymen0909 G

# Gas Optimizations

## Summary

|       | Issue        | Instances    |
| :---: |:-------------|:------------:|
| 1 | State variables should be packed to save storage slots | 3 |
| 2 | Emit events outside the for loops | 3 |
| 3 | Multiple address/IDs mappings can be combined into a single mapping of an address/id to a struct | 2 |

## Findings

### 1- State variables should be packed to save storage slots :

State variables inside contract should be packed if possible to save storage slots and thus will save gas cost.

There are 3 instances of this issue:

* Instance 1 :

```
File: core/AMPHClaimer.sol

45      uint256 public cvxRewardFee;
48      uint256 public crvRewardFee;
```

Both variables represents a fee value that cannot go above 1e18, which means that boths variables cannot overflow uint128, and they should be packed to save gas, **save 1 storage slot** :

```
/// @dev Percentage of rewards taken in CVX (1e18 == 100%)
uint256 public cvxRewardFee;

/// @dev Percentage of rewards taken in CRV (1e18 == 100%)
uint256 public crvRewardFee;
```

* Instance 2 :

```
File: core/VaultController.sol

67      uint192 public protocolFee;
69      uint192 public initialBorrowingFee;
71      uint192 public liquidationFee;
```

The 3 variables represent fee values that cannot go above 1e18, which means that all those variables cannot overflow uint96, and they should be packed to save gas, **save 2 storage slot** :

```
/// @dev The protocol's fee
uint96 public protocolFee;
/// @dev The initial borrowing fee (1e18 == 100%)
uint96 public initialBorrowingFee;
/// @dev The fee taken from the liquidator profit (1e18 == 100%)
uint96 public liquidationFee;
```

* Instance 3 :

```
File: core/AMPHClaimer.sol

35      uint256 public votingDelay;
38      uint256 public votingPeriod;
```

Both variables represent a time value that cannot realistically overflow uint64, so they should be packed to save gas **save 1 storage slot** :

```
/// @notice The delay before voting on a pro

// Code block 1 (unknown):
File: core/AMPHClaimer.sol

45      uint256 public cvxRewardFee;
48      uint256 public crvRewardFee;

// Code block 2 (unknown):
/// @dev Percentage of rewards taken in CVX (1e18 == 100%)
uint256 public cvxRewardFee;

/// @dev Percentage of rewards taken in CRV (1e18 == 100%)
uint256 public crvRewardFee;

// Code block 3 (unknown):
File: core/VaultController.sol

67      uint192 public protocolFee;
69      uint192 public initialBorrowingFee;
71      uint192 public liquidationFee;

// Code block 4 (unknown):
/// @dev The protocol's fee
uint96 public protocolFee;
/// @dev The initial borrowing fee (1e18 == 100%)
uint96 public initialBorrowingFee;
/// @dev The fee taken from the liquidator profit (1e18 == 100%)
uint96 public liquidationFee;

// Code block 5 (unknown):
File: core/AMPHClaimer.sol

35      uint256 public votingDelay;
38      uint256 public votingPeriod;


John_Femi Q

# 1
## Title
Unbounded loops used
## Impact
In the `setThresholds` function we see the function allows owner to input an unbounded length of arrays for amounts and numOfApprovers. This can lead to a long list of input which could cause the gasLimit to be hit and txn reverted with gas wasted.
## Proof of Concept
With amount array length of 15 and above, it is highly likely to hit the gasLimit
## Tools Used
Manual Review

## Recommended Mitigation Steps
Add a max length of array with mitigation steps to either revert or deal with the max limit of array length only

# 2
## Title
Use Maps rather than array
## Impact
We see the code `mapping(bytes32 => TxnThreshold) public txnToThresholdSet;` and
```solidity
struct TxnThreshold {
    uint256 numberOfApprovalsNeeded;
    address[] approvers;
  }
```
## Proof of Concept

## Tools Used
Manual Review

## Recommended Mitigation Steps
It could easily be update to `mapping(bytes32 => address => TxnThreshold)public txnToThresholdSet;` and
```solidity
struct TxnThreshold {
    uint256 numberOfApprovalsNeeded;
    address approver;
  }
```
and `mapping(bytes32 => number) TxnThreshold` for length of approvers, this is to avoid use of long arrays

# 3
## Title
Use an admin role instead of onlyOwner
## Impact
It is better to use admin or ownership roles to allow for inheritance and multiple access incase of wallet hacks and also to avoid a single point of failure.
## Proof of Concept
ownership can be stolen with wallet hacks, as seen by recent hacks
## Tools Used
Manual Review

## Recommended Mitigation Steps
It is better to use admin or ownership roles to allow for inheritance and multiple access incase of wallet hacks and also to avoid a single point of failure.

# 4
## Title
No check for number of approvers
## Impact
There is no check for the sorting of numOfApprovers in the `setThreshold` function, while the amount is checked to be sorted,there could be a scenario where `numOfApprovers` is not and this could lead to inconsist

// Code block 1 (solidity):
struct TxnThreshold {
    uint256 numberOfApprovalsNeeded;
    address[] approvers;
  }

// Code block 2 (solidity):
struct TxnThreshold {
    uint256 numberOfApprovalsNeeded;
    address approver;
  }

struct TxnThreshold {
    uint256 numberOfApprovalsNeeded;
    address[] approvers;
  }

struct TxnThreshold {
    uint256 numberOfApprovalsNeeded;
    address approver;
  }


Stormreckson Q

1- The following functions in `rUSDY.sol` lack checks on the returned values of the external calls made.
Not checking the return value of an external call can result in undesired state changes within the contract. This may lead to loss of funds, incorrect calculations or balances, or unintended consequences that can negatively impact the contract and its users.
Adding return value checks can be beneficial to ensure that the intended actions are executed successfully and to handle any potential errors or issues that may arise during the execution of the function.

`Burn()`

https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/usdy/rUSDY.sol#L680-L681

```
    usdy.transfer(msg.sender, sharesAmount / BPS_DENOMINATOR);
```
`Unwrap()`

https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/usdy/rUSDY.sol#L454-L455

```
usdy.transfer(msg.sender, usdyAmount / BPS_DENOMINATOR);
```

https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/usdy/rUSDY.sol#L436

```
    usdy.transferFrom(msg.sender, address(this), _USDYAmount);
```
`Wrap()`

example of how you can add return value checks to the transfer function calls in the unwrap and burn functions:

i. `Unwrap` Function:

```
function unwrap(uint256 _rUSDYAmount) external whenNotPaused {
  require(_rUSDYAmount > 0, "rUSDY: can't unwrap zero rUSDY tokens");
  uint256 usdyAmount = getSharesByRUSDY(_rUSDYAmount);
  if (usdyAmount < BPS_DENOMINATOR) revert UnwrapTooSmall();
  _burnShares(msg.sender, usdyAmount);
  require(usdy.transfer(msg.sender, usdyAmount / BPS_DENOMINATOR), "Transfer failed");
  emit TokensBurnt(msg.sender, _rUSDYAmount);
}
```

ii. `Burn` Function:

```
function burn(address _account, uint256 _amount) external onlyRole(BURNER_ROLE) {
  uint256 sharesAmount = getSharesByRUSDY(_amount);
  _burnShares(_account, sharesAmount);
  require(usdy.transfer(msg.sender, sharesAmount /

// Code block 1 (unknown):
usdy.transfer(msg.sender, sharesAmount / BPS_DENOMINATOR);

// Code block 2 (unknown):
usdy.transfer(msg.sender, usdyAmount / BPS_DENOMINATOR);

// Code block 3 (unknown):
usdy.transferFrom(msg.sender, address(this), _USDYAmount);

// Code block 4 (unknown):
function unwrap(uint256 _rUSDYAmount) external whenNotPaused {
  require(_rUSDYAmount > 0, "rUSDY: can't unwrap zero rUSDY tokens");
  uint256 usdyAmount = getSharesByRUSDY(_rUSDYAmount);
  if (usdyAmount < BPS_DENOMINATOR) revert UnwrapTooSmall();
  _burnShares(msg.sender, usdyAmount);
  require(usdy.transfer(msg.sender, usdyAmount / BPS_DENOMINATOR), "Transfer failed");
  emit TokensBurnt(msg.sender, _rUSDYAmount);
}


BanPaleo G

(1) We can pack structs to smaller size to save storage slot

Structs containing `uint256` should be checked again if it is really necessary to use `uint256` and instead can use a lower value such as `uint128` as it is unlikely there is ever need to pass the max value of `uint128`. By using `uint128` it can save significantly a large margin of gas to all affected structs.
Each storage slot would save about `~2.1k` in gas. Just by changing all `uint256` to `uint128` we can save about `~54` storage slots which total up to `~113,400` in gas savings. The gas savings can increase more if we can allow the use of even smaller `uint` such as `uint96` or `uint64`

```solidity
ActivePool.sol:
  218
  219      // Due to "stack too deep" error
  220:     struct LocalVariables_rebalance {
  221:         uint256 currentAllocated;
  222:         IERC4626 yieldGenerator;
  223:         uint256 ownedShares;
  224:         uint256 sharesToAssets;
  225:         uint256 profit;
  226:         uint256 finalBalance;
  227:         uint256 percentOfFinalBal;
  228:         uint256 yieldingPercentage;
  229:         uint256 toDeposit;
  230:         uint256 toWithdraw;
  231:         uint256 yieldingAmount;
  232:         uint256 finalYieldingAmount;
  233:         int256 netAssetMovement;
  234:         uint256 treasurySplit;
  235:         uint256 stakingSplit;
  236:         uint256 stabilityPoolSplit;

  //@audit Can save about 8 storage slots

BorrowerOperations.sol:
   47:      struct LocalVariables_adjustTrove {
   48:         uint256 collCCR;
   49:         uint256 collMCR;
   50:         uint256 collDecimals;
   51:         uint price;
   52:         uint collChange;
   53:         uint netDebtChange;
   54:         bool isCollIncrease;
   55:         uint debt;
   56:         uint coll;
   57:         uint oldICR;
   58:         uint newICR;
   59:         uint newTCR;
   60:         uint LUSDFee;
   61:         uint newDebt;
   62:         uint newColl;
   63:         uint st

ActivePool.sol:
  218
  219      // Due to "stack too deep" error
  220:     struct LocalVariables_rebalance {
  221:         uint256 currentAllocated;
  222:         IERC4626 yieldGenerator;
  223:         uint256 ownedShares;
  224:         uint256 sharesToAssets;
  225:         uint256 profit;
  226:         uint256 finalBalance;
  227:         uint256 percentOfFinalBal;
  228:         uint256 yieldingPercentage;
  229:         uint256 toDeposit;
  230:         uint256 toWithdraw;
  231:         uint256 yieldingAmount;
  232:         uint256 finalYieldingAmount;
  233:         int256 netAssetMovement;
  234:         uint256 treasurySplit;
  235:         uint256 stakingSplit;
  236:         uint256 stabilityPoolSplit;

  //@audit Can save about 8 storage slots

BorrowerOperations.sol:
   47:      struct LocalVariables_adjustTrove {
   48:         uint256 collCCR;
   49:         uint256 collMCR;
   50:         uint256 collDecimals;
   51:         uint price;
   52:         uint collChange;
   53:         uint netDebtChange;
   54:         bool isCollIncrease;
   55:         uint debt;
   56:         uint coll;
   57:         uint oldICR;
   58:         uint newICR;
   59:         uint newTCR;
   60:         uint LUSDFee;
   61:         uint newDebt;
   62:         uint newColl;
   63:         uint stake;
   64:     }

  //@audit Can save about 8 storage slots

   65
   66:     struct LocalVariables_openTrove {
   67:         uint256 collCCR;
   68:         uint256 collMCR;
   69:         uint256 collDecimals;
   70:         uint price;
   71:         uint LUSDFee;
   72:         uint netDebt;
   73:         uint compositeDebt;
   74:         uint ICR;
   75:         uint NICR;
   76:         uint stake;
   77:         uint arrayIndex;
   78:     }
  //@audit Can save about 5 storage slots

CollateralConfig.sol:
   27:     struct Config {
   28:         bool allowed;
   29:         uint256 decimals;
   30:         uint256 MCR;
   31:         uint256 CCR;
   32:

ActivePool.sol:
    3: pragma solidity 0.6.11;

BorrowerOperations.sol:
    3: pragma solidity 0.6.11;

CollateralConfig.sol:
    3: pragma solidity 0.6.11;

LUSDToken.sol:
    3: pragma solidity 0.6.11;

StabilityPool.sol:
    3: pragma solidity 0.6.11;

TroveManager.sol:
    3: pragma solidity 0.6.11;

CommunityIssuance.sol:
    3: pragma solidity 0.6.11;

LQTYStaking.sol:
    3: pragma solidity 0.6.11;


report

---
sponsor: "Lybra Finance"
slug: "2023-06-lybra"
date: "2023-08-21"
title: "Lybra Finance"
findings: "https://github.com/code-423n4/2023-06-lybra-findings/issues"
contest: 254
---

# 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 Lybra Finance smart contract system written in Solidity. The audit took place between June 23 - July 3 2023.

## Wardens

136 Wardens contributed reports to the Lybra Finance:

  1. 0x3b
  2. [0xAnah](https://twitter.com/0xAnah)
  3. 0xMAKEOUTHILL
  4. 0xNightRaven
  5. 0xRobocop
  6. 0xbrett8571
  7. 0xcm
  8. [0xgrbr](https://twitter.com/x0grbr)
  9. 0xhacksmithh
  10. 0xkazim
  11. [0xnacho](https://twitter.com/0xnacho17)
  12. [0xnev](https://twitter.com/0xnevi)
  13. [3agle](https://twitter.com/0x34gle)
  14. [8olidity](https://twitter.com/8olidity)
  15. [ABAIKUNANBAEV](https://twitter.com/_onlyowner)
  16. [Arz](https://twitter.com/arzdev)
  17. [Bauchibred](https://twitter.com/bauchibred?s=21&t=7sv-1qcnwtkdTA81Iog0yQ )
  18. Breeje
  19. Brenzee
  20. [BugBusters](https://twitter.com/0xnirlin) ([nirlin](https://twitter.com/0xnirlin) and [0xepley](https://twitter.com/0xepley))
  21. Bughunter101
  22. [Co0nan](https://twitter.com/Conan0x3)
  23. [CrypticShepherd](https://twitter.com/CrypticShepherd)
  24. [Cryptor](https://twitter.com/DeFiCast)
  25. D\_Auditor
  26. DavidGiladi
  27. [DedOhWale](https://twitter.com/dedohwale)
  28. [DelerRH](https://twitter.com/deler_rh)
  29. HE1M
  30. Hama
  31. IceBear
  32. Inspecktor
  33. Iurii3
  34. [JCN](https://twitter.com/0xJCN)
  35. [Jorgect](https://twitter.com/TamayoNft)
  36. [K42

contract GnosisSafeProxy {
    // singleton always needs to be first declared variable, to ensure that it is at the same location in the contracts to which calls are delegated.
    // To reduce deployment costs this variable is internal and needs to be retrieved via `getStorageAt`
    address internal singleton;

    /// @dev Constructor function sets address of singleton contract.
    /// @param _singleton Singleton address.
    constructor(address _singleton) {
        require(_singleton != address(0), "Invalid singleton address provided");
        singleton = _singleton;
    }

    /// @dev Fallback function forwards all transactions and returns all received return data.
    fallback() external payable {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            let _singleton := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff)
            // 0xa619486e == keccak("masterCopy()"). The value is right padded to 32-bytes with 0s
            if eq(calldataload(0), 0xa619486e00000000000000000000000000000000000000000000000000000000) {
                mstore(0, _singleton)
                return(0, 0x20)
            }
            calldatacopy(0, 0, calldatasize())
            let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            if eq(success, 0) {
                revert(0, returndatasize())
            }
            return(0, returndatasize())
        }
    }
}

uint256 onBehalfOfCollateralRatio = (depositedAsset[onBehalfOf] * assetPrice * 100) / getBorrowedOf(onBehalfOf);


codeslide G

### Gas Optimizations

| Number | Issue | Instances |
| :----: | :---- | :-------: |
| [G-01] | Use short circuiting to save gas | 5 |
| [G-02] | Use assembly to check for address(0) | 22 |
| [G-03] | Change function visibility from public to external | 19 |
| [G-04] | State variables should be cached | 28 |
| [G-05] | Check amount before transfer | 8 |
| [G-06] | Mark functions as payable | 11 |
| [G-07] | Subtraction syntax | 2 |

#### [G-01] Use short circuiting to save gas
Use short circuiting to save gas by putting the cheaper comparison first.

```solidity
File: src/EthRouter.sol

// before - always reading block.timestamp
102:    if (block.timestamp > deadline && deadline != 0) {

// after - don't have to read block.timestamp if local variable is zero
102:    if (deadline != 0 && block.timestamp > deadline) {

// same as line 102
154:    if (block.timestamp > deadline && deadline != 0) {

// same as line 102
228:    if (block.timestamp > deadline && deadline != 0) {

// same as line 102
256:    if (block.timestamp > deadline && deadline != 0) {
```

#### [G-02] Use assembly to check for address(0)

Use assembly to check for address(0). Code example is at Solidity Assembly: Checking if an Address is 0 (Efficiently).

```solidity
File: src/Factory.sol

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

110:   if (_baseToken == address(0)) {
```

```solidity
File: src/PrivatePool.sol

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

254:    if (baseToken != address(0)) {

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

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

357:    if (baseToken == address(0)) {

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

421:    if (baseToken != address(0)) {

489:    if ((baseToken == addres

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

// before - always reading block.timestamp
102:    if (block.timestamp > deadline && deadline != 0) {

// after - don't have to read block.timestamp if local variable is zero
102:    if (deadline != 0 && block.timestamp > deadline) {

// same as line 102
154:    if (block.timestamp > deadline && deadline != 0) {

// same as line 102
228:    if (block.timestamp > deadline && deadline != 0) {

// same as line 102
256:    if (block.timestamp > deadline && deadline != 0) {

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

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

110:   if (_baseToken == address(0)) {

File: src/EthRouter.sol

// before - always reading block.timestamp
102:    if (block.timestamp > deadline && deadline != 0) {

// after - don't have to read block.timestamp if local variable is zero
102:    if (deadline != 0 && block.timestamp > deadline) {

// same as line 102
154:    if (block.timestamp > deadline && deadline != 0) {

// same as line 102
228:    if (block.timestamp > deadline && deadline != 0) {

// same as line 102
256:    if (block.timestamp > deadline && deadline != 0) {

File: src/Factory.sol

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

110:   if (_baseToken == address(0)) {


Bauchibred Q

# Caviar QA report

# Low Issues

## L-01 Solmate’s SafeTransferLib doesn’t check whether the ERC20 contract exists

### Proof of Concept

[PrivatePool.sol L30](https://github.com/code-423n4/2023-04-caviar/blob/cd8a92667bcb6657f70657183769c244d04c015c/src/PrivatePool.sol#L30)

[Factory.sol L27](https://github.com/code-423n4/2023-04-caviar/blob/cd8a92667bcb6657f70657183769c244d04c015c/src/Factory.sol#L27)
[EthRouter.sol L32](https://github.com/code-423n4/2023-04-caviar/blob/cd8a92667bcb6657f70657183769c244d04c015c/src/EthRouter.sol#L32)

Solmate’s SafeTransferLib, which is often used to interact with non-compliant/unsafe ERC20 tokens, does not check whether the ERC20 contract exists. The following code will not revert in case the token doesn’t exist (yet).

This is stated in the Solmate library: https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol#L9

### Recommendation

Add a contract exist control in functions

## L-02 Low level calls don’t check for contract existence

### Proof of Concept

Low level calls return success if called on a destructed contract. See OpenZeppelin’s Address.so which checks address.code.length

There is 1 instance of this issue:

[PrivatePool.execute()](https://github.com/code-423n4/2023-04-caviar/blob/cd8a92667bcb6657f70657183769c244d04c015c/src/PrivatePool.sol#L459-L476)

```
    function execute(address target, bytes memory data) public payable onlyOwner returns (bytes memory) {
        // call the target with the value and data
        (bool success, bytes memory returnData) = target.call{value: msg.value}(data);

        // if the call succeeded return the return data
        if (success) return returnData;

        // if we got an error bubble up the error message
        if (returnData.length > 0) {
            // solhint-disable-next-line no-inline-assembly
            assembly {
                let returnData_size := mload(returnData)
                revert(add(32, returnData), returnData_size)

    function execute(address target, bytes memory data) public payable onlyOwner returns (bytes memory) {
        // call the target with the value and data
        (bool success, bytes memory returnData) = target.call{value: msg.value}(data);

        // if the call succeeded return the return data
        if (success) return returnData;

        // if we got an error bubble up the error message
        if (returnData.length > 0) {
            // solhint-disable-next-line no-inline-assembly
            assembly {
                let returnData_size := mload(returnData)
                revert(add(32, returnData), returnData_size)
            }
        } else {
            revert();
        }
    }

PrivatePool.sol#L30, Factory.sol#L27, EthRouter.sol#L32, SafeTransferLib.sol#L9, PrivatePool.sol#L459-L476

EthRouter.sol, SafeTransferLib.sol, PrivatePool.sol, Factory.sol


niki G

### Don't Initialize Variables with Default Value

#### Findings:
```
Funding.sol::62 => for (uint256 i = 0; i < targets_.length; ++i) {
Funding.sol::112 => for (uint256 i = 0; i < targets_.length;) {
PositionManager.sol::181 => for (uint256 i = 0; i < indexesLength; ) {
PositionManager.sol::364 => for (uint256 i = 0; i < indexesLength; ) {
PositionManager.sol::474 => uint256 filteredIndexesLength = 0;
PositionManager.sol::476 => for (uint256 i = 0; i < indexesLength; ) {
RewardsManager.sol::163 => for (uint256 i = 0; i < fromBucketLength; ) {
RewardsManager.sol::229 => for (uint256 i = 0; i < positionIndexes.length; ) {
RewardsManager.sol::290 => for (uint256 i = 0; i < positionIndexes.length; ) {
RewardsManager.sol::440 => for (uint256 i = 0; i < positionIndexes_.length; ) {
RewardsManager.sol::680 => for (uint256 i = 0; i < indexes_.length; ) {
RewardsManager.sol::704 => for (uint256 i = 0; i < indexes_.length; ) {
StandardFunding.sol::208 => for (uint i = 0; i < numFundedProposals; ) {
StandardFunding.sol::324 => for (uint i = 0; i < numProposalsInSlate; ) {
StandardFunding.sol::431 => uint256 totalTokensRequested = 0;
StandardFunding.sol::434 => for (uint i = 0; i < numProposalsInSlate_; ) {
StandardFunding.sol::468 => for (uint i = 0; i < numProposals; ) {
StandardFunding.sol::491 => for (uint i = 0; i < proposalIdSubset_.length;) {
StandardFunding.sol::549 => for (uint256 i = 0; i < numVotesCast; ) {
StandardFunding.sol::582 => for (uint256 i = 0; i < numVotesCast; ) {
StandardFunding.sol::848 => for (uint256 i = 0; i < numVotesCast; ) {
```
### Cache Array Length Outside of Loop

#### Findings:
```
Funding.sol::62 => for (uint256 i = 0; i < targets_.length; ++i) {
Funding.sol::112 => for (uint256 i = 0; i < targets_.length;) {
RewardsManager.sol::229 => for (uint256 i = 0; i < positionIndexes.length; ) {
RewardsManager.sol::290 => for (uint256 i = 0; i < positionIndexes.length; ) {
RewardsManager.sol::440 => for (uint256 i = 0; i < positionIndexes_.length; )

Funding.sol::62 => for (uint256 i = 0; i < targets_.length; ++i) {
Funding.sol::112 => for (uint256 i = 0; i < targets_.length;) {
RewardsManager.sol::229 => for (uint256 i = 0; i < positionIndexes.length; ) {
RewardsManager.sol::290 => for (uint256 i = 0; i < positionIndexes.length; ) {
RewardsManager.sol::440 => for (uint256 i = 0; i < positionIndexes_.length; ) {
RewardsManager.sol::680 => for (uint256 i = 0; i < indexes_.length; ) {
RewardsManager.sol::704 => for (uint256 i = 0; i < indexes_.length; ) {
StandardFunding.sol::491 => for (uint i = 0; i < proposalIdSubset_.length;) {

// Code block 1 (unknown):
Funding.sol::62 => for (uint256 i = 0; i < targets_.length; ++i) {
Funding.sol::112 => for (uint256 i = 0; i < targets_.length;) {
PositionManager.sol::181 => for (uint256 i = 0; i < indexesLength; ) {
PositionManager.sol::364 => for (uint256 i = 0; i < indexesLength; ) {
PositionManager.sol::474 => uint256 filteredIndexesLength = 0;
PositionManager.sol::476 => for (uint256 i = 0; i < indexesLength; ) {
RewardsManager.sol::163 => for (uint256 i = 0; i < fromBucketLength; ) {
RewardsManager.sol::229 => for (uint256 i = 0; i < positionIndexes.length; ) {
RewardsManager.sol::290 => for (uint256 i = 0; i < positionIndexes.length; ) {
RewardsManager.sol::440 => for (uint256 i = 0; i < positionIndexes_.length; ) {
RewardsManager.sol::680 => for (uint256 i = 0; i < indexes_.length; ) {
RewardsManager.sol::704 => for (uint256 i = 0; i < indexes_.length; ) {
StandardFunding.sol::208 => for (uint i = 0; i < numFundedProposals; ) {
StandardFunding.sol::324 => for (uint i = 0; i < numProposalsInSlate; ) {
StandardFunding.sol::431 => uint256 totalTokensRequested = 0;
StandardFunding.sol::434 => for (uint i = 0; i < numProposalsInSlate_; ) {
StandardFunding.sol::468 => for (uint i = 0; i < numProposals; ) {
StandardFunding.sol::491 => for (uint i = 0; i < proposalIdSubset_.length;) {
StandardFunding.sol::549 => for (uint256 i = 0; i < numVotesCast; ) {
StandardFunding.sol::582 => for (uint256 i = 0; i < numVotesCast; ) {
StandardFunding.sol::848 => for (uint256 i = 0; i < numVotesCast; ) {


LeoS G

# Summary
|        | Issue | Instances | Gas Saved |
|--------|-------|-----------|-----------|
|[G-01]|Use `calldata` instead of `memory`|7|-15 946|
|[G-02]| Addition to  automated findings [G-07]|1|-210|
|[G-03]|Parts of the solmate library are available more efficiently|1|-104|
|[G-04]|Variables do not need to be cached|1|-89|
|[G-05]|Increase the number of optimiser runs|-|-133 044|

The gas saved column simply adds up the evolution in the snapshot, using the method described in the next section.

# Benchmark
A benchmark is performed on each optimization, using the tests snapshot provided by foundry. This snapshot is based on tests and therefore does not take into account all functions, this loss is accepted. But it is also subject to intrinsic variance. This means that some tests are not relevant to the comparison because they vary too much and are thus not taken into account. These are listed below:
>testSettle
>testIntegration
>testRedeem

# [G-01] Use `calldata` instead of `memory`
Using `calldata` instead of `memory` for function parameters can save gas if the argument is only read in the function. Only instances compilable with a simple type swap are considered valid.

*7 instances*

- [RdpxV2Core.sol#L242](https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L242)
- [RdpxV2Core.sol#L271](https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L271)
- [RdpxV2Core.sol#L821](https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L821)
- [RdpxV2Core.sol#L822](https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L822)
- [RdpxV2Core.sol#L1136](https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L1136)
- [PerpetualAtlanticVault.sol#L316](https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/perp-vault/PerpetualAtlanticVault.sol#L316)
- [PerpetualAtlanticVault.sol#L406](https://github.com/code-423n4/2

testAdminFunctions() (gas: -12 (-0.004%))
testEmergencyWithdrawNonNative() (gas: 32 (0.030%))
testFundingAccruedForOneOption() (gas: -215 (-0.030%))
testRedeemOnBehalfOf() (gas: -396 (-0.044%))
testBondWithDelegateMintDecayRiptide() (gas: -580 (-0.045%))
testWithdraw() (gas: -685 (-0.048%))
testBondWithoutOptions() (gas: -1940 (-0.050%))
testMockups() (gas: -645 (-0.052%))
testPayFunding() (gas: -1264 (-0.053%))
testBond() (gas: -1940 (-0.053%))
testEmergencyWithdraw() (gas: 63 (0.053%))
testBondWithDelegate() (gas: -5066 (-0.154%))
testUpperDepeg() (gas: -970 (-0.298%))
testlowerDepeg() (gas: -2328 (-0.374%))
Overall gas change: -15946 (-0.040%)

testAddToDelegate() (gas: -210 (-0.061%))
Overall gas change: -210 (-0.001%)

RdpxV2Core.sol#L242, RdpxV2Core.sol#L271, RdpxV2Core.sol#L821, RdpxV2Core.sol#L822, RdpxV2Core.sol#L1136, PerpetualAtlanticVault.sol#L316


Daniel526 Q

A. The `mint` function in the RdpxDecayingBonds contract contains potentially duplicative access control checks, which might lead to code redundancy and confusion during maintenance. Ensuring consistency and reducing complexity in access control mechanisms is crucial to prevent vulnerabilities from creeping into the contract.
```solidity
function mint(address to, uint256 expiry, uint256 rdpxAmount) external onlyRole(MINTER_ROLE) {
    _whenNotPaused();
    require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter");
    uint256 bondId = _mintToken(to);
    bonds[bondId] = Bond(to, expiry, rdpxAmount);
    emit BondMinted(to, bondId, expiry, rdpxAmount);
}
```
- In the provided snippet, the `mint` function includes both the `onlyRole(MINTER_ROLE)` modifier and a subsequent `require` statement to check if the caller has the `MINTER_ROLE` role. This redundancy might lead to inconsistencies and potentially introduce confusion in understanding the code's intended behavior.
- Redundant checks can lead to maintenance challenges, overlooked vulnerabilities, and code that is harder to understand. Additionally, it might increase the risk of mistakenly modifying one instance of the check without updating the other, introducing vulnerabilities due to inconsistencies.
- Create a single point of access control verification and apply it consistently across all relevant functions.
<br/>
B. Possibility of Exceeding Available Collateral in `lockCollateral` Function
The `lockCollateral` function within the ERC4626 Vault contract does not include a mechanism to prevent the locking of collateral exceeding the total available collateral. This can potentially lead to situations where more collateral is locked than what is present in the contract.
The `lockCollateral` function, as provided in the code snippet, allows for the locking of a specified `amount` of collateral. However, the function does not contain any explicit checks to ensure that the cumulative `_activeCollateral` doe

function lockCollateral(uint256 amount) public onlyPerpVault {
    require(_activeCollateral + amount <= totalCollateral(), "Insufficient available collateral");
    _activeCollateral += amount;
}

// Code block 1 (solidity):
function mint(address to, uint256 expiry, uint256 rdpxAmount) external onlyRole(MINTER_ROLE) {
    _whenNotPaused();
    require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter");
    uint256 bondId = _mintToken(to);
    bonds[bondId] = Bond(to, expiry, rdpxAmount);
    emit BondMinted(to, bondId, expiry, rdpxAmount);
}


Madalad G

# Gas Optimizations Summary
| |Issue|Instances|
|:-:|:-|:-:|
|[G-01]|Functions guaranteed to revert when called by normal users can be marked `payable`|14|
|[G-02]|Use assembly to calculate hashes|6|
|[G-03]|Do not compare boolean expressions to boolean literals|2|
|[G-04]|Division/Multiplication by 2 should use bit shifting|1|
|[G-05]|Use `indexed` to save gas|6|
|[G-06]|Use `unchecked` for operations that cannot overflow/underflow|7|
|[G-07]|Use `private` rather than `public` for constants|11|
|[G-08]|Change `public` functions to `external`|8|
|[G-09]|Use named return values|15|

Total issues: 9

Total instances: 70

# Gas Optimizations
## [G-01] Functions guaranteed to revert when called by normal users can be marked `payable`

If a function modifier such as `onlyOwner` is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided.

The extra opcodes avoided are CALLVALUE(2), DUP1(3), ISZERO(3), PUSH2(3), JUMPI(10), PUSH1(3), DUP1(3), REVERT(0), JUMPDEST(1), POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost (2400 per instance).

Instances: 14
```solidity
File: contracts/SafEth/derivatives/Reth.sol

58:     function setMaxSlippage(uint256 _slippage) external onlyOwner {

107:     function withdraw(uint256 amount) external onlyOwner {

```
- [contracts/SafEth/derivatives/Reth.sol#L58](https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L58)
- [contracts/SafEth/derivatives/Reth.sol#L107](https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L107)

```solidity
File: contracts/SafEth/derivatives/WstEth.sol

48:     function setMaxSlippage(uint256 _slippage) external onlyOwner {

56:     function withdraw(uint256 _amount) external onlyOwner {

// Code block 1 (solidity):
File: contracts/SafEth/derivatives/Reth.sol

58:     function setMaxSlippage(uint256 _slippage) external onlyOwner {

107:     function withdraw(uint256 amount) external onlyOwner {


MiniGlome G

## Gas Optimizations
| |Issue|Instances|
|-|:-|:-:|
| [GAS-01] | Use custom errors rather than `revert()`/`require()` strings | 96 |
| [GAS-02] | Don't Initialize Variables with Default Value | 27 |
| [GAS-03] | Long Revert String | 36 |
| [GAS-04] | Use Shift Right/Left instead of Division/Multiplication if possible | 2 |
| [GAS-05] | `++i`/`i++` Should Be `unchecked{++i}`/`unchecked{i++}` When It Is Not Possible For Them To Overflow | 17 |
| [GAS-06] | Splitting `require()` statements that use `&&` saves gas | 2 |
| [GAS-07] | Superfluous event fields | 1 |
| [GAS-08] | Setting the `constructor` to `payable` | 6 |
| [GAS-09] | Usage of uint/int smaller than 32 bytes | 5 |
| [GAS-10] | Use `<`/`>` instead of `>=`/`>=` | 3 |
| [GAS-11] | Using fixed bytes is cheaper than using `string` | 18 |
| [GAS-12] | `<x> += <y>` Costs More Gas Than `<x> = <x> + <y>` For State Variables | 9 |
### [GAS-01] Use custom errors rather than `revert()`/`require()` strings
Custom errors are available from solidity version 0.8.4. Custom errors save [~50 gas](https://gist.github.com/IllIllI000/ad1bd0d29a0101b25e57c293b4b0c746) each time they're hit by [avoiding having to allocate and store the revert string](https://blog.soliditylang.org/2021/04/21/custom-errors/#errors-in-depth). Not defining the strings also save deployment gas.

*Instances (96)*:
```solidity
File: Ethos\ActivePool.sol
85:        require(!addressesSet, "Can call setAddresses only once");

93:        require(_treasuryAddress != address(0), "Treasury cannot be 0 address");

107:        require(numCollaterals == _erc4626vaults.length, "Vaults array length must match number of collaterals");

111:            require(IERC4626(vault).asset() == collateral, "Vault asset must be collateral");

127:        require(_bps <= 10_000, "Invalid BPS value");

133:        require(_driftBps <= 500, "Exceeds max allowed value of 500 BPS");

145:        require(_treasurySplit + _SPSplit + _stakingSplit == 10_000, "Splits must

File: Ethos\ActivePool.sol
85:        require(!addressesSet, "Can call setAddresses only once");

93:        require(_treasuryAddress != address(0), "Treasury cannot be 0 address");

107:        require(numCollaterals == _erc4626vaults.length, "Vaults array length must match number of collaterals");

111:            require(IERC4626(vault).asset() == collateral, "Vault asset must be collateral");

127:        require(_bps <= 10_000, "Invalid BPS value");

133:        require(_driftBps <= 500, "Exceeds max allowed value of 500 BPS");

145:        require(_treasurySplit + _SPSplit + _stakingSplit == 10_000, "Splits must add up to 10000 BPS");

314:        require(
            ICollateralConfig(collateralConfigAddress).isCollateralAllowed(_collateral),
            "Invalid collateral address"
        );

321:        require(
            msg.sender == borrowerOperationsAddress ||
            msg.sender == defaultPoolAddress,
            "ActivePool: Caller is neither BO nor Default Pool");

329:        require(
            msg.sender == borrowerOperationsAddress ||
            msg.sender == troveManagerAddress ||
            msg.sender == redemptionHelper ||
            msg.sender == stabilityPoolAddress,
            "ActivePool: Caller is neither BorrowerOperations nor TroveManager nor StabilityPool");

338:        require(
            msg.sender == borrowerOperationsAddress ||
            msg.sender == troveManagerAddress,
            "ActivePool: Caller is neither BorrowerOperations nor TroveManager");

File: Ethos\BorrowerOperations.sol
525:        require(collateralConfig.isCollateralAllowed(_collateral), "BorrowerOps: Invalid collateral address");

529:        require(IERC20(_collateral).balanceOf(_user) >= _collAmount, "BorrowerOperations: Insufficient user collateral balance");

530:        require(IERC20(_collateral).allowance(_user, address(this)) >= _collAmount, "BorrowerOperations: Insufficient collateral allowance");

534:        require(_collTopUp == 0 || _collWithdrawal == 0, "BorrowerOperations: Cannot withdraw and add coll");

538:        require(_collTopUp != 0 || _collWithdrawal != 0 || _LUSDChange != 0, "BorrowerOps: There must be either a collateral change or a debt change");

543:        require(status == 1, "BorrowerOps: Trove does not exist or is closed");

548:        require(status != 1, "BorrowerOps: Trove is active");

552:        require(_LUSDChange > 0, "BorrowerOps: Debt increase requires non-zero debtChange");

561:        require(
            !_checkRecoveryMode(_collateral, _price, _CCR, _collateralDecimals),
            "BorrowerOps: Operation not permitted during Recovery Mode"
        );

568:        require(_collWithdrawal == 0, "BorrowerOps: Collateral withdrawal not permitted Recovery Mode");

617:        require(_newICR >= _MCR, "BorrowerOps: An operation that would result in ICR < MCR is not permitted");

621:        require(_newICR >= _CCR, "BorrowerOps: Operation must leave trove with ICR >= CCR");

625:        require(_newICR >= _oldICR, "BorrowerOps: Cannot decrease your Trove's ICR in Recovery Mode");

629:        require(_newTCR >= _CCR, "BorrowerOps: An operation that would result in TCR < CCR is not permitted");

633:        require (_netDebt >= MIN_NET_DEBT, "BorrowerOps: Trove's net debt must be greater than minimum");

637:        require(_debtRepayment <= _currentDebt.sub(LUSD_GAS_COMPENSATION), "BorrowerOps: Amount repaid must not be larger than the Trove's debt");

641:        require(msg.sender == stabilityPoolA


0xweb3boy Q

NC-01 : incorrect naming of local variable in unwrap() in rUSDY.sol

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/usdy/rUSDY.sol#L451

```solidity

function unwrap(uint256 _rUSDYAmount) external whenNotPaused {
    require(_rUSDYAmount > 0, "rUSDY: can't unwrap zero rUSDY tokens");
    uint256 usdyAmount = getSharesByRUSDY(_rUSDYAmount);
    if (usdyAmount < BPS_DENOMINATOR) revert UnwrapTooSmall();
    _burnShares(msg.sender, usdyAmount);
    usdy.transfer(msg.sender, usdyAmount / BPS_DENOMINATOR);
    emit TokensBurnt(msg.sender, _rUSDYAmount);
  }
```

This function is basically unwrapping rUSDYAmount and burning the amount of equivalent rUSDY but in
```
uint256 usdyAmount = getSharesByRUSDY(_rUSDYAmount);
```

here while calculating `getSharesByRUSDY(_rUSDYAmount)` it is being stored in `usdyAmount` instead the correct name should be `rUSDYAmount` since the amount to be burned is in `rUSDY` and not `usdy`

// Code block 1 (solidity):
function unwrap(uint256 _rUSDYAmount) external whenNotPaused {
    require(_rUSDYAmount > 0, "rUSDY: can't unwrap zero rUSDY tokens");
    uint256 usdyAmount = getSharesByRUSDY(_rUSDYAmount);
    if (usdyAmount < BPS_DENOMINATOR) revert UnwrapTooSmall();
    _burnShares(msg.sender, usdyAmount);
    usdy.transfer(msg.sender, usdyAmount / BPS_DENOMINATOR);
    emit TokensBurnt(msg.sender, _rUSDYAmount);
  }

// Code block 2 (unknown):
uint256 usdyAmount = getSharesByRUSDY(_rUSDYAmount);


Rickard G

# [G-01] `bytes constant` are more efficient than `string constant`

## Lines of code
[https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/ActivePool.sol#L30](https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/ActivePool.sol#L30)
[https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/BorrowerOperations.sol#L21](https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/BorrowerOperations.sol#L21)
[https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LUSDToken.sol#L32-L34](https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LUSDToken.sol#L32-L34)
[https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/StabilityPool.sol#L150](https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/StabilityPool.sol#L150)
[https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LQTY/CommunityIssuance.sol#L19](https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LQTY/CommunityIssuance.sol#L19)
[https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LQTY/LQTYStaking.sol#L23](https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LQTY/LQTYStaking.sol#L23)
## Vulnerability details
If data can fit into 32 bytes, then you should use bytes32 datatype rather than bytes or strings as it is cheaper in solidity.
[https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/ActivePool.sol#L30](https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/ActivePool.sol#L30)
````solidity
	string constant public NAME = "ActivePool";
````
[https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/BorrowerOperations.sol#L21](https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/BorrowerOperations.sol#L21)
````solidity
	string constant public NAME = "BorrowerOperati

string constant public NAME = "BorrowerOperations";

// Code block 1 (solidity):
string constant public NAME = "ActivePool";

ActivePool.sol#L30, BorrowerOperations.sol#L21, LUSDToken.sol#L32-L34, StabilityPool.sol#L150, CommunityIssuance.sol#L19, LQTYStaking.sol#L23

StabilityPool.sol, BorrowerOperations.sol, LQTYStaking.sol, LUSDToken.sol, ActivePool.sol, CommunityIssuance.sol


dingo2077 Q

## [L-01] Users can't stake and unstake if owner added derivative contract with inappropriate interface.
SC: SafEth.sol

The core of lack is lying in function `addDerivative()` where any address can be added as vault.
If in new vault has not implemented IDerivative interface or even uncorrect realization of functions - user's can't stake or stake their funds. Also there is no function to delete derivative contract.
![](https://i.imgur.com/YUZkIb1.png)

## Proof of Concept
Foundry test:
`forge test --fork-url mainnet -vvv`
```solidity
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../../src/SafEth/SafEth.sol";
import "../../src/SafEth/derivatives/Reth.sol";
import "../../src/SafEth/derivatives/SfrxEth.sol";
import "../../src/SafEth/derivatives/WstEth.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract SafEthAudit is Test {

    SafEth public safEthSC;
    SafEth public SafEthProxy;
    ERC1967Proxy public safERC1967ProxySC;

    Reth public rethEthSC;
    Reth public RethProxy;
    ERC1967Proxy public rethERC1967ProxySC;

    SfrxEth public SfrxEthSC;
    SfrxEth public SfrxEthProxy;
    ERC1967Proxy public sfrxEthERC1967ProxySC;

    WstEth public WstEthSC;
    WstEth public WstEthProxy;
    ERC1967Proxy public wstEthERC1967ProxySC;

    address eoa = vm.addr(123);
    address user1 = vm.addr(12347);
    address attacker = vm.addr(1234);

    function setUp() public {

        vm.deal(eoa, 100 ether);
        vm.deal(user1, 100 ether);
        vm.deal(attacker, 100000 ether);

        vm.startPrank(eoa);
        //SafEthProxy and SafETH deploy;
        safEthSC = new SafEth();
        safERC1967ProxySC = new ERC1967Proxy(address(safEthSC),abi.encodeWithSelector(
                    safEthSC.initialize.selector,
                    "TokenName",
                    "TSBL"
                ));

        SafEthProxy = SafEth(payable(address(safERC1967ProxySC)));

        //RethProxy and Reth deploy;
        rethEthSC = n

pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../../src/SafEth/SafEth.sol";
import "../../src/SafEth/derivatives/Reth.sol";
import "../../src/SafEth/derivatives/SfrxEth.sol";
import "../../src/SafEth/derivatives/WstEth.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract SafEthAudit is Test {

    SafEth public safEthSC;
    SafEth public SafEthProxy;
    ERC1967Proxy public safERC1967ProxySC;

    Reth public rethEthSC;
    Reth public RethProxy;
    ERC1967Proxy public rethERC1967ProxySC;

    SfrxEth public SfrxEthSC;
    SfrxEth public SfrxEthProxy;
    ERC1967Proxy public sfrxEthERC1967ProxySC;

    WstEth public WstEthSC;
    WstEth public WstEthProxy;
    ERC1967Proxy public wstEthERC1967ProxySC;

    address eoa = vm.addr(123);
    address user1 = vm.addr(12347);
    address attacker = vm.addr(1234);

    function setUp() public {

        vm.deal(eoa, 100 ether);
        vm.deal(user1, 100 ether);
        vm.deal(attacker, 100000 ether);

        vm.startPrank(eoa);
        //SafEthProxy and SafETH deploy;
        safEthSC = new SafEth();
        safERC1967ProxySC = new ERC1967Proxy(address(safEthSC),abi.encodeWithSelector(
                    safEthSC.initialize.selector,
                    "TokenName",
                    "TSBL"
                ));

        SafEthProxy = SafEth(payable(address(safERC1967ProxySC)));

        //RethProxy and Reth deploy;
        rethEthSC = new Reth();
        rethERC1967ProxySC = new ERC1967Proxy(address(rethEthSC),abi.encodeWithSelector(
                    rethEthSC.initialize.selector,address(SafEthProxy)));

        RethProxy = Reth(payable(address(rethERC1967ProxySC)));

        //SfrxEthProxy and SfrxEth deploy;
        SfrxEthSC = new SfrxEth();
        sfrxEthERC1967ProxySC = new ERC1967Proxy(address(SfrxEthSC),abi.encodeWithSelector(
                    SfrxEthSC.initialize.selector,address(SafEthProxy)));

        SfrxEthProxy = SfrxEth(payable(address(sfrxEthER

uint256 maxSlip = 1e16;

    function testFuzz_Slippage(uint256 ethDep) public {
        vm.assume(ethDep > 1 ether && ethDep < 200 ether);
        console.log("ethDep:             ",ethDep);
        console.log("maxSlip:            ",maxSlip);
        uint256 minOutSC = (ethDep * (10 ** 18 - maxSlip)) / 10 ** 18;
        uint256 minOutReal = ethDep - 1e16 * ethDep / 1e18;
        uint256 actualSlippage = ethDep - minOutSC;

        console.log("Slippace calculated in current SC:              ",1e16);
        console.log("Slippace calculated appropriatly w/o roundring: ",actualSlippage);

        assertEq(1e16,actualSlippage);

    }


0x11singh99 G

## Gas Optimizations
| Number |Issue|Instances|
|-|:-|:-:|
| [[G-01](#g-01-structs-can-be-packed-into-fewer-storage-slots)] | `Structs` can be packed into fewer storage slots. | 1 |
| [[G-02](#g-02-abiencode-is-less-efficient-than-abiencodepacked-to-save-gas)] | `abi.encode()` is less efficient than `abi.encodePacked()` to save gas. | 3 |
| [[G-03](#g-03-use-calldata-instead-of-memory-for-function-arguments-that-do-not-get-mutated)] | Use `calldata` instead of `memory` for function arguments that do not get mutated. | 3 |
| [[G-04](#g-04-no-need-to-explicitly-initialize-variables-with-default-values)] | No need to explicitly initialize variables with `default` values. | 8 |
| [[G-05](#g-05-using-ternary-operator-instead-of-if-else-saves-gas)] | Using `ternary` operator instead of `if-else` saves gas | 1 |
| [[G-06](#g-06-use-hardcode-address-instead-addressthis)] | Use `hardcode address` instead `address(this)` | 2 |
| [[G-07](#g-07-change-constant-to-immutable-for-keccak-variables-20-gas-per-keccak)] | Change `Constant` to `Immutable` for keccak Variables (20 gas per keccak) | 7 |

Total 7 issues.

## [G-01] Structs can be packed into fewer storage slots.

The EVM works with 32 byte words. Variables less than 32 bytes can be declared next to each other in storage and this will pack the values together into a single 32 byte storage slot (if values combined are <= 32 bytes). If the variables packed together are retrieved together in functions (more likely with structs), we will effectively save ~2000 gas with every subsequent SLOAD for that storage slot. This is due to us incurring a Gwarmaccess (100 gas) versus a Gcoldsload (2100 gas).

**_1 Instance - 1 File_**

### Reduce uint type for `start`,`end` and `dailyInterestRate` and pack them into 1 storage slot to save 2 SLOT (~4000 Gas)

These  `start`,`end`  can be reduced to `uint64` because they are holding timestamps and `uint64` is more than enough to hold timestamps.

`dailyInterestRate`  are holding *%* valu

File : contracts/rwaOracles/RWADynamicOracle.sol

295:   struct Range {
296:    uint256 start;
297:    uint256 end;
298:    uint256 dailyInterestRate;
299:    uint256 prevRangeClosePrice;
300:  }

## [G-02] abi.encode() is less efficient than abi.encodePacked() to save gas.

In Solidity, abi.encode() and abi.encodePacked() are used to encode function arguments and data into a byte array for storage or transmission. However, abi.encodePacked() is generally more gas-efficient than abi.encode() because it does not add padding to the encoded data.
When using abi.encode(), the function arguments are encoded with padding to ensure that the encoded data is a multiple of 32 bytes. This padding can add unnecessary zeros to the encoded data, which can increase the size and gas cost of the transaction.
On the other hand, abi.encodePacked() does not add padding to the encoded data and returns a tightly packed representation of the input data. This can reduce the size and gas cost of the transaction, as it avoids unnecessary padding.

**_3 Instances - 2 Files_**


0xAnah G

# **GAS OPTIMIZATIONS**

## [G-01] Unused Imports
The following files were imported but were not used. These files would costs gas during deployment and is a bad coding practice .

- https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/LybraWbETHVault.sol#L5
- https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/LybraWbETHVault.sol#L7
- https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/LybraWstETHVault.sol#L5
- https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/LybraWstETHVault.sol#L7
- https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/LybraRETHVault.sol#L5
- https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/LybraRETHVault.solL7
- https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/LybraStETHVault.sol#5

## [G-02] Use bit shifting for multiplication by 2

- https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L130

## [G-03] Sort Solidity operations using short-circuit mode
Short-circuiting is a solidity contract development model that uses OR/AND logic to sequence different cost operations. It puts low gas cost operations in the front and high gas cost operations in the back, so that if the front is low If the cost operation is feasible, you can skip (short-circuit) the subsequent high-cost Ethereum virtual machine operation.

//f(x) is a low gas cost operation
//g(y) is a high gas cost operation

//Sort operations with different gas costs as follows
f(x) || g(y)
f(x) && g(y)

The following instances could be restructured to use short-circuit mode
- https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/token/PeUSD.sol#L46
- https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/token/LBR.sol#L64
- https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/token/PeUSDMainnetStableVision.sol#L199
-

function mint(address onBehalfOf, uint256 amount) external virtual {
        require(onBehalfOf != address(0), "TZA");
        require(amount > 0, "ZA");
        _mintPeUSD(msg.sender, onBehalfOf, amount, getAssetPrice());
}

function mint(address onBehalfOf, uint256 amount) external virtual {
        require(amount > 0, "ZA");
        require(onBehalfOf != address(0), "TZA");
        _mintPeUSD(msg.sender, onBehalfOf, amount, getAssetPrice());
}

LybraWbETHVault.sol#L5, LybraWbETHVault.sol#L7, LybraWstETHVault.sol#L5, LybraWstETHVault.sol#L7, LybraRETHVault.sol#L5, LybraRETHVault.sol, LybraStETHVault.sol, LybraPeUSDVaultBase.sol#L130, PeUSD.sol#L46, LBR.sol#L64, PeUSDMainnetStableVision.sol#L199

LybraWstETHVault.sol, LybraRETHVault.sol, PeUSDMainnetStableVision.sol, LybraStETHVault.sol, LybraWbETHVault.sol, PeUSD.sol, LBR.sol, LybraPeUSDVaultBase.sol


Kaysoft Q

## [NC-1] Import declarations should import specific identifiers, rather than the whole file
Using import declarations of the form `import {<identifier_name>} from "My/Contract.sol"` avoids polluting the symbol namespace making flattened files smaller, and speeds up compilation. Avoid import declarations of the form `import "My/Contract.sol;`.
Files: All files.

Consider using named imports

Files: All files.

## [NC-2] INCOMPLETE NATSPEC COMMENTS
The NatSpec comments on the does not include the input field. Consider adding the amount argument to Natspec definition.
Files:
- [contracts/SafEth/derivatives/Reth.sol#L105](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/Reth.sol#L105)
- [contracts/SafEth/derivatives/WstEth.sol#L86](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/WstEth.sol#L86)
- [contracts/SafEth/derivatives/WstEth.sol#L56](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/WstEth.sol#L56)

```
/**
        @notice - Convert derivative into ETH
     */
    function withdraw(uint256 amount) external onlyOwner {
        RocketTokenRETHInterface(rethAddress()).burn(amount);
        // solhint-disable-next-line
        (bool sent, ) = address(msg.sender).call{value: address(this).balance}(
            ""
        );
        require(sent, "Failed to send Ether");
    }

    /**
        @notice - Get price of derivative in terms of ETH
     */
    function ethPerDerivative(uint256 _amount) public view returns (uint256) {
        uint256 frxAmount = IsFrxEth(SFRX_ETH_ADDRESS).convertToAssets(
            10 ** 18
        );
        return ((10 ** 18 * frxAmount) /
            IFrxEthEthPool(FRX_ETH_CRV_POOL_ADDRESS).price_oracle());
    }

```

## [NC-3] USE LATEST SOLIDITY PRAGMA VERSION
Consider using Solidity latest stable pragma version 0.

// Code block 1 (unknown):
/**
        @notice - Convert derivative into ETH
     */
    function withdraw(uint256 amount) external onlyOwner {
        RocketTokenRETHInterface(rethAddress()).burn(amount);
        // solhint-disable-next-line
        (bool sent, ) = address(msg.sender).call{value: address(this).balance}(
            ""
        );
        require(sent, "Failed to send Ether");
    }

    /**
        @notice - Get price of derivative in terms of ETH
     */
    function ethPerDerivative(uint256 _amount) public view returns (uint256) {
        uint256 frxAmount = IsFrxEth(SFRX_ETH_ADDRESS).convertToAssets(
            10 ** 18
        );
        return ((10 ** 18 * frxAmount) /
            IFrxEthEthPool(FRX_ETH_CRV_POOL_ADDRESS).price_oracle());
    }


TheSavageTeddy G

# Using bitwise-not `~` instead of `-(i+1)` saves gas
Calculating `-(i+1)` instead of `~` costs **9 more gas**, because of the extra `ADD` , `SUB` and `PUSH`s that can be replaced by `NOT` to achieve the same result.

`startIdx = uint(-(_startIdx + 1));` => `startIdx = uint(~_startIdx);`

*There is 1 instance of this issue:*
```solidity
File: MultiTroveGetter.sol
44:             startIdx = uint(-(_startIdx + 1));
```
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/MultiTroveGetter.sol#L44

# Check `Require()` / `Revert()` statements that use less gas first
Checks that cost less gas, such as checking function parameters, should be placed before more expensive checks such as ones that invoke `msg.sender`, because the function will be able to revert before checking more expensive statements. In this case, `require(msg.sender == vault, "Only vault can withdraw");` should be placed after the other `require()` statements which use less gas, to prevent wasting around **24680 gas**.

*There is 1 instance of this issue:*
```solidity
File: ReaperBaseStrategyv4.sol
95:     function withdraw(uint256 _amount) external override returns (uint256 loss) {
96:         require(msg.sender == vault, "Only vault can withdraw");
97:         require(_amount != 0, "Amount cannot be zero");
98:         require(_amount <= balanceOf(), "Ammount must be less than balance");
```
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol#L95-L98

# Expressions for constant values such as calling `KECCAK256()` should use `immutable` rather than `constant`
Constant values for function calls result in increased gas costs as `immutable`s are evaluated at deployment time, in contrast to `constants` which are executed at runtime for each invocation.

*There are 8 instances of this issue:*
```solidity
File: ReaperVaultV2.sol
73:     bytes32 public constant DEPOSITOR = keccak256("DEPOSITOR");
74:     bytes32 public constant STR

// Code block 1 (solidity):
File: MultiTroveGetter.sol
44:             startIdx = uint(-(_startIdx + 1));

// Code block 2 (solidity):
File: ReaperBaseStrategyv4.sol
95:     function withdraw(uint256 _amount) external override returns (uint256 loss) {
96:         require(msg.sender == vault, "Only vault can withdraw");
97:         require(_amount != 0, "Amount cannot be zero");
98:         require(_amount <= balanceOf(), "Ammount must be less than balance");

File: MultiTroveGetter.sol
44:             startIdx = uint(-(_startIdx + 1));

File: ReaperBaseStrategyv4.sol
95:     function withdraw(uint256 _amount) external override returns (uint256 loss) {
96:         require(msg.sender == vault, "Only vault can withdraw");
97:         require(_amount != 0, "Amount cannot be zero");
98:         require(_amount <= balanceOf(), "Ammount must be less than balance");


ABAIKUNANBAEV Q

## Finding Summary

| ID | Description | Severity |
| - | - | :-: |
| [L-01](#l-01-unnecessary-abundance-of-roles) | Unnecessary abundance of roles in `rUSDY.sol` | Low |
| [L-02](#l-02-unchecked-return-data-when-implementing-multiexcall) | Unchecked return data when implementing multiexcall in `SourceBridge.sol` | Low |

## [L-01] Unnecessary abundance of roles in `rUSDY.sol`

In `rUSDY.sol` contract, there are plenty of roles used to impement access control system but they are not actually needed as it's the one address that is granted all these roles.

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/usdy/rUSDY.sol#L141-146
```
_grantRole(DEFAULT_ADMIN_ROLE, guardian);
    _grantRole(USDY_MANAGER_ROLE, guardian);
    _grantRole(PAUSER_ROLE, guardian);
    _grantRole(MINTER_ROLE, guardian);
    _grantRole(BURNER_ROLE, guardian);
    _grantRole(LIST_CONFIGURER_ROLE, guardian);
```

This can create additional difficulties when looking at different functions (especially for auditors) as most of them will have different roles.

### Recommendation

Delegate all this roles to `DEFAULT_ADMIN_ROLE`.

## [L-02] Unchecked return data when implementing multiexcall in `SourceBridge.sol`

When implementing the call using `call()` opcode it's important to check the size of the return data as it's copied to memory. And if it's too big payload returned from `target`, the usage of memory can become expensive and even lead to DoS:

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/SourceBridge.sol#L165-169
```
   (bool success, bytes memory ret) = address(exCallData[i].target).call{
        value: exCallData[i].value
      }(exCallData[i].data);
      require(success, "Call Failed");
      results[i] = ret;

```

### Recommendation

Check the return data using assembly.

// Code block 1 (unknown):
_grantRole(DEFAULT_ADMIN_ROLE, guardian);
    _grantRole(USDY_MANAGER_ROLE, guardian);
    _grantRole(PAUSER_ROLE, guardian);
    _grantRole(MINTER_ROLE, guardian);
    _grantRole(BURNER_ROLE, guardian);
    _grantRole(LIST_CONFIGURER_ROLE, guardian);

// Code block 2 (unknown):
(bool success, bytes memory ret) = address(exCallData[i].target).call{
        value: exCallData[i].value
      }(exCallData[i].data);
      require(success, "Call Failed");
      results[i] = ret;


eyexploit Q

# Finding 1 :  Risk of updating CR via `updateCollateralRatios()` can be minimized

https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/CollateralConfig.sol#L78-L100

There might be a scenario where you want to lower the `MCR` but keeping the `CCR` unchanged for a collateral or vice-versa. But when doing it through `updateCollateralRatios` where we only want to update the `MCR`, we risk the updating of the `CCR` accidentally also.

And as it says
```
     // !!!PLEASE USE EXTREME CARE AND CAUTION. THIS IS IRREVERSIBLE!!!

     // You probably don't want to do this unless a specific asset has proved itself through tough times.
     // Doing this irresponsibly can permanently harm the protocol.
```

**Recommendation**
This risk can be minimize by splitting the `udpateCollateralRatios` into two different function,

```solidity
    function updateMinimumCollateralRatio(address _collateral, uint256 _MCR) external onlyOwner checkCollateral(_collateral) {
        Config storage config = collateralConfig[_collateral];
        require(_MCR <= config.MCR, "Can only walk down the MCR");

        require(_MCR >= MIN_ALLOWED_MCR, "MCR below allowed minimum");
        config.MCR = _MCR;

        emit CollateralRatiosUpdated(_collateral, _MCR, config.CCR);
    }

    function updateCriticalCollateralRatio(address _collateral, uint256 _CCR) external onlyOwner checkCollateral(_collateral) {
        Config storage config = collateralConfig[_collateral];
        require(_CCR <= config.CCR, "Can only walk down the CCR");

        require(_CCR >= MIN_ALLOWED_CCR, "CCR below allowed minimum");
        config.CCR = _CCR;
        emit CollateralRatiosUpdated(_collateral, config.MCR, _CCR);
    }
```

# Finding 2 : Wrong error message return when validating `_feeBPS`
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Vault/contracts/ReaperVaultV2.sol#L155
https://github.com/code-423n4/

// Code block 1 (unknown):
// !!!PLEASE USE EXTREME CARE AND CAUTION. THIS IS IRREVERSIBLE!!!

     // You probably don't want to do this unless a specific asset has proved itself through tough times.
     // Doing this irresponsibly can permanently harm the protocol.

// Code block 2 (solidity):
function updateMinimumCollateralRatio(address _collateral, uint256 _MCR) external onlyOwner checkCollateral(_collateral) {
        Config storage config = collateralConfig[_collateral];
        require(_MCR <= config.MCR, "Can only walk down the MCR");

        require(_MCR >= MIN_ALLOWED_MCR, "MCR below allowed minimum");
        config.MCR = _MCR;

        emit CollateralRatiosUpdated(_collateral, _MCR, config.CCR);
    }

    function updateCriticalCollateralRatio(address _collateral, uint256 _CCR) external onlyOwner checkCollateral(_collateral) {
        Config storage config = collateralConfig[_collateral];
        require(_CCR <= config.CCR, "Can only walk down the CCR");

        require(_CCR >= MIN_ALLOWED_CCR, "CCR below allowed minimum");
        config.CCR = _CCR;
        emit CollateralRatiosUpdated(_collateral, config.MCR, _CCR);
    }


Iurii3 Q

1. During initiation of tokens in LybraConfiguration.sol wrong interface is used

[LybraConfigurator.solL100](https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/configuration/LybraConfigurator.sol#L100C1-L100C65)

Should use IPeUSD instead of IEUSD. However, this mistake won't escalate into bigger error as IEUSD interface has all necessary functions

Correct line of code: ``` if (address(peUSD) == address(0)) peUSD = IPeUSD(_peusd); ```

2. setTokenMiner function may revert due to array length discrepancies

[LybraConfigurator.solL235-240](https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/configuration/LybraConfigurator.sol#L235C1-L240C6)

setTokenMiner function has two arguments - array of _contracts and array of _bools. In case their length is not equal function will revert while using extensive amount of gas in a for loop.
Adding ```require(_contracts.length == _bools.length, "Length of contracts not equal to bools"); ``` will save gas in this situation

3. Check in setLockStatus might be done wrongly

[esLBRBoost.solL41](https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/miner/esLBRBoost.sol#L42)

Considering Lybra Fincance allow users to extend the time of lock-up, this require statement is not correctly implemented.

For example
* user's current LockStatus is (unlockTime = +7 days from now; duration = 90 days, miningBoost = 30 * 1e18)
* user want to change in to (unlockTime = +30 days from now; duration = 30 days, miningBoost = 20 * 1e18)
* his lock-up time will increase from 7 to 30 days, but it is currently not allowed as overall duration will be lowered.

Team should consider rewriting requirement to
``` require(userStatus.unlockTime <= block.timestamp + _setting.duration, "Your lock-in period has not ended, and the term can only be extended, not reduced."); ```

4. Vaults would be almost im

4. Vaults would be almost impossible to use with assets with decimals < 18.

[LybraEUSDVaultBase.solL73](https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/base/LybraEUSDVaultBase.sol#L73-L73)
[LybraPeUSDVaultBase.solL59](https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L59-L59)

Currently Lybra Vaults may be used with 18-decimal LSD assets - stETH, wstETH, rETH, wbETH.

As it stated in Lybra docs "_Collateral is any asset that a borrower must provide to take out a loan, acting as security for the debt._"
Should there appear LSD asset with lower decimals, or if team decides to add 6-decimal USDC/USDT as a possible collateral this requirement ``` require(assetAmount >= 1 ether, "") ``` would be passed only with much bigger amount then anticipated. For 6 decimal assets user would need to deposit 1_000_000_000_000 token to pass this requirement

Team might consider adding mapping with assets decimals (address => uint) or move this requirement from base abstract contract to higher level contract for particular asset

5. Check before transfer would save gas cost in case of revert
[LybraEUSDVaultBase.solL109](https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/base/LybraEUSDVaultBase.sol#L109-L109)

6. Wrong notation. Should use collateral asset instead of stETH
[LybraPeUSDVaultBase.solL80](https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L80-L80)
[LybraPeUSDVaultBase.solL123](https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/base/LybraPeUSDVaultBase.sol#L123-L123)
[LybraPeUSDVaultBase.solL149](https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/pools/base/

// Code block 1 (unknown):
2. setTokenMiner function may revert due to array length discrepancies

[LybraConfigurator.solL235-240](https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/configuration/LybraConfigurator.sol#L235C1-L240C6)

setTokenMiner function has two arguments - array of _contracts and array of _bools. In case their length is not equal function will revert while using extensive amount of gas in a for loop.
Adding

LybraConfigurator.sol#L100-L1, LybraConfigurator.sol#L235-L1, esLBRBoost.sol#L42, LybraEUSDVaultBase.sol#L73, LybraPeUSDVaultBase.sol#L59, LybraEUSDVaultBase.sol#L109, LybraPeUSDVaultBase.sol#L80, LybraPeUSDVaultBase.sol#L123

esLBRBoost.sol, LybraConfigurator.sol, LybraPeUSDVaultBase.sol, LybraEUSDVaultBase.sol


0xNorman Q

1. Lack of input validation in setPauseStaking() and setPauseUnstaking()
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L232-L244

When using the ```setPauseStaking``` and ```setPauseUnstaking``` function, it's better to check if the input parameters are different than the global ones. So unnecessary changes will be discarded and confusing events won't be out:

```
function setPauseStaking(bool _pause) external onlyOwner {
        require(_pause != pauseStaking, " pauseStaking unchanged!");
        pauseStaking = _pause;
        emit StakingPaused(pauseStaking);
    }

    /**
        @notice - Owner only function that enables/disables the unstake function
        @param _pause - true disables unstaking / false enables unstaking
    */
    function setPauseUnstaking(bool _pause) external onlyOwner {
        require(_pause != pauseUnstaking, "pauseUnstaking unchanged!");
        pauseUnstaking = _pause;
        emit UnstakingPaused(pauseUnstaking);
    }

```

2. lack of validation for the existence of derivative in adjustWeight()
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L165-L175
The existence of derivatives should be checked before adjusting the weight, otherwise if weight is set for an unexisting derivative, will result in the wrong totalWeight and cause panic in stake(), unstake() function.
```
function adjustWeight(
        uint256 _derivativeIndex,
        uint256 _weight
    ) external onlyOwner {
        require(__derivativeIndex < derivativeCount, "derivative does not exist!")
        weights[_derivativeIndex] = _weight;
        uint256 localTotalWeight = 0;
        for (uint256 i = 0; i < derivativeCount; i++)
            localTotalWeight += weights[i];
        totalWeight = localTotalWeight;
        emit WeightChange(_derivativeIndex, _weight);
    }
```
Lack of input checks on setMinAmount() and setMax

// Code block 1 (unknown):
function setPauseStaking(bool _pause) external onlyOwner {
        require(_pause != pauseStaking, " pauseStaking unchanged!");
        pauseStaking = _pause;
        emit StakingPaused(pauseStaking);
    }

    /**
        @notice - Owner only function that enables/disables the unstake function
        @param _pause - true disables unstaking / false enables unstaking
    */
    function setPauseUnstaking(bool _pause) external onlyOwner {
        require(_pause != pauseUnstaking, "pauseUnstaking unchanged!");
        pauseUnstaking = _pause;
        emit UnstakingPaused(pauseUnstaking);
    }

// Code block 2 (unknown):
function adjustWeight(
        uint256 _derivativeIndex,
        uint256 _weight
    ) external onlyOwner {
        require(__derivativeIndex < derivativeCount, "derivative does not exist!")
        weights[_derivativeIndex] = _weight;
        uint256 localTotalWeight = 0;
        for (uint256 i = 0; i < derivativeCount; i++)
            localTotalWeight += weights[i];
        totalWeight = localTotalWeight;
        emit WeightChange(_derivativeIndex, _weight);
    }


rouhsamad Q

## rounding down error when depositing stETH tokens
The `transfer` function in stETH token first converts the amount that is being transferred to  correct amount of shares using getSharesByPooledEth function:
```
    function getSharesByPooledEth(uint256 _ethAmount) public view returns (uint256) {
        return _ethAmount
            .mul(_getTotalShares())
            .div(_getTotalPooledEther());
    }
```
this conversion causes a rounding down error. for example, if a user deposited 1 ether of stETH into the pool, (1 ether - 1) will be received by the contract, this means both the emitted event (AssetDeposit) and calculation of rsETH to mint, are done using a wrong number.

POC:
Tests were done on ETH mainnet (forked):
```
    function test_DepositSTETHFlooring() external {
        uint256 amountToDeposit = 1 ether;
        vm.startPrank(manager);
        lrtConfig.updateAssetDepositLimit(address(stETH), amountToDeposit);
        vm.stopPrank();

        vm.startPrank(alice);
        //approve the pool
        stETH.approve(address(lrtDepositPool), ~uint256(0));

        //wrong even emitted, contract received 1 ether - 1, not 1 ether
        vm.expectEmit(true, true, true, false);
        emit AssetDeposit(address(stETH), 1 ether, rseth.balanceOf(alice));
        lrtDepositPool.depositAsset(address(stETH), amountToDeposit);

        //stETH balance of contract is  1 ether - 1
        assertEq(stETH.balanceOf(address(lrtDepositPool)), 1 ether - 1);

        vm.stopPrank();
    }
```
Mitigation:
Consider calculating how much asset deposit pool actually received instead of relaying on `depositAmount`

// Code block 1 (unknown):
function getSharesByPooledEth(uint256 _ethAmount) public view returns (uint256) {
        return _ethAmount
            .mul(_getTotalShares())
            .div(_getTotalPooledEther());
    }

// Code block 2 (unknown):
function test_DepositSTETHFlooring() external {
        uint256 amountToDeposit = 1 ether;
        vm.startPrank(manager);
        lrtConfig.updateAssetDepositLimit(address(stETH), amountToDeposit);
        vm.stopPrank();

        vm.startPrank(alice);
        //approve the pool
        stETH.approve(address(lrtDepositPool), ~uint256(0));

        //wrong even emitted, contract received 1 ether - 1, not 1 ether
        vm.expectEmit(true, true, true, false);
        emit AssetDeposit(address(stETH), 1 ether, rseth.balanceOf(alice));
        lrtDepositPool.depositAsset(address(stETH), amountToDeposit);

        //stETH balance of contract is  1 ether - 1
        assertEq(stETH.balanceOf(address(lrtDepositPool)), 1 ether - 1);

        vm.stopPrank();
    }


qpzm Q

## Unnecessary _eta check in `GovernorCharlie._queueTransaction`
`if (_eta < (_getBlockTimestamp() + _delay)) revert GovernorCharlie_DelayNotReached();` is not necessary.
https://github.com/code-423n4/2023-07-amphora/blob/5d1cea9410db5448760c834f001af04a72edf3e0/core/solidity/contracts/governance/GovernorCharlie.sol#L297
It is because `_eta = block.timestamp + delay` in [`GovernorCharlie.queue`](https://github.com/code-423n4/2023-07-amphora/blob/5d1cea9410db5448760c834f001af04a72edf3e0/core/solidity/contracts/governance/GovernorCharlie.sol#L258).

## Revert with the original error message in `GovernorCharlie.executeTransaction`.
Openzeppelin TimelockController preserves the original error message. It is more informational than `GovernorCharlie_ProposalNotSucceeded`.
```solidity
function _execute(address target, uint256 value, bytes calldata data) internal virtual {
    (bool success, bytes memory returndata) = target.call{value: value}(data);
    Address.verifyCallResult(success, returndata);
}
```
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/governance/TimelockController.sol#L412-L415
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol#L204

// Code block 1 (solidity):
function _execute(address target, uint256 value, bytes calldata data) internal virtual {
    (bool success, bytes memory returndata) = target.call{value: value}(data);
    Address.verifyCallResult(success, returndata);
}

GovernorCharlie.sol#L297, GovernorCharlie.sol#L258, TimelockController.sol#L412-L415, Address.sol#L204


report

---
sponsor: "Ondo Finance"
slug: "2023-01-ondo"
date: "2023-02-28"
title: "Ondo Finance contest"
findings: "https://github.com/code-423n4/2023-01-ondo-findings/issues"
contest: 204
---

# 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 contest 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 contest outlined in this document, C4 conducted an analysis of the Ondo Finance smart contract system written in Solidity. The audit contest took place between January 11—January 17 2023.

## Wardens

74 Wardens contributed reports to the Ondo Finance contest:

  1. 0x1f8b
  2. 0x52
  3. 0x5rings
  4. [0xAgro](https://twitter.com/0xAgro)
  5. [0xSmartContract](https://twitter.com/0xSmartContract)
  6. 0xcm
  7. 0xjuicer
  8. 0xkato
  9. 2997ms
  10. [AkshaySrivastav](https://twitter.com/akshaysrivastv)
  11. [Aymen0909](https://github.com/Aymen1001)
  12. BClabs (nalus and Reptilia)
  13. BPZ (pa6221, Bitcoinfever244 and PrasadLak)
  14. BRONZEDISC
  15. Bauer
  16. Bnke0x0
  17. CodingNameKiki
  18. Deekshith99
  19. Diana
  20. IllIllI
  21. Josiah
  22. [Kaysoft](https://www.linkedin.com/in/kayode-okunlade-862001142/)
  23. RaymondFam
  24. Rolezn
  25. SaeedAlipoor01988
  26. [Sathish9098](https://www.linkedin.com/in/sathishkumar-p-26069915a)
  27. SleepingBugs ([Deivitto](https://twitter.com/Deivitto) and 0xLovesleep)
  28. Tajobin
  29. [Udsen](https://github.com/udsene)
  30. Viktor\_Cortess
  31. [adriro](https://github.com/romeroadrian)
  32. arialblack14
  33. [betweenETHlines](https://twitter.com/eth_lines)
  34. [bin2chen](https://twitter.com/bin2chen)
  35. btk
  36. [c3phas](https://twitter.com/c3ph_)
  37. cccz
  38. chaduke
  39. chrisdior4
  40. cryptostellar5
  41. cryptphi
  42.

function completeRedemptions(
  address[] calldata redeemers,
  address[] calldata refundees,
  uint256 collateralAmountToDist,
  uint256 epochToService,
  uint256 fees
) external override updateEpoch onlyRole(MANAGER_ADMIN) {
  _checkAddressesKYC(redeemers);
  _checkAddressesKYC(refundees);
  if (epochToService >= currentEpoch) {
    revert MustServicePastEpoch();
  }
  // Calculate the total quantity of shares tokens burned w/n an epoch
  uint256 refundedAmt = _processRefund(refundees, epochToService);
  uint256 quantityBurned = redemptionInfoPerEpoch[epochToService]
    .totalBurned - refundedAmt;
  uint256 amountToDist = collateralAmountToDist - fees;
  _processRedemption(redeemers, amountToDist, quantityBurned, epochToService);
  collateral.safeTransferFrom(assetSender, feeRecipient, fees);
  emit RedemptionFeesCollected(feeRecipient, fees, epochToService);
}

uint256 collateralAmountDue = (amountToDist * cashAmountReturned) /
        quantityBurned;


lfzkoala Q

#Issue: Missing Access Control (Minor)
Description of the security issue: The `fundTreasury` function does not have any access control in place. This means that any address can call this function and increase the treasury amount, which might not be the intended behavior.

Location of the security issue: ajna-grants/src/grants/GrantFund.sol#58-68

How to resolve the security issue: Implement access control for the `fundTreasury` function, such as restricting it to only the contract owner or specific authorized addresses.

#Issue: No Constructor or Initialization Function (MInor)
Description of the security issue: The contract does not have a constructor or initialization function to set the initial state or configuration parameters, such as the `ajnaTokenAddress`. Without proper initialization, the contract may not function as expected.
Location of the security issue: ajna-grants/src/grants/GrantFund.sol
How to resolve the security issue: Add a constructor or an initialization function to set the initial state or configuration parameters, such as the `ajnaTokenAddress`. Ensure that proper access control is in place if using an initialization function.

#Issue: IERC20 is not used
Description: The IERC20 is imported but not used in the contract
Location: ajna-core/src/PositionManager.sol
Suggestion: Clarify whether it should be used, if not, delete it.

#Issue: Lack Owner Validation
Description: The `memorializePositions()` function allows any address to call it, potentially causing unauthorized actions.
Location: ajna-core/src/PositionManager.sol#170-216
Suggestion: One of the simplest ways to add access control is to check whether `msg.sender` is the owner of the token. The `ownerOf` function, which is standard in ERC721 contracts, can be used for this purpose. If `msg.sender` isn't the owner, the function can revert.

Here's an example of how this might look like:

```solidity
function memorializePositions(
    MemorializePositionsParams calldata params_
) extern

function memorializePositions(
    MemorializePositionsParams calldata params_
) external override {
    address owner = ownerOf(params_.tokenId);

    // Check if the sender is the owner of the token
    require(msg.sender == owner, "Not token owner");

    // ... rest of your code ...
}

import "@openzeppelin/contracts/utils/math/SafeMath.sol";

contract PositionManager is ERC721, PermitERC721, IPositionManager, Multicall, ReentrancyGuard {
    using SafeMath for uint256;
    ...
    function memorializePositions(MemorializePositionsParams calldata params_) external override mayInteract(poolKey[params_.tokenId], params_.tokenId) {
        ...
        for (uint256 i = 0; i < indexesLength; ) {
            ...
            // Update token position LP using SafeMath to prevent overflow
            position.lps = position.lps.add(lpBalance);
            ...
        }
        ...
    }
}


browep Q

Multiple misspellings of "receive", "receives", "received".

```
$ find . -name "*.sol" | xargs grep -n recieve
./src/transformers/V3Utils.sol:633:        // check if recieved correct amount of tokens
./src/V3Vault.sol:230:    /// @return liquidationValue If position is liquidatable - the value of the (partial) position which the liquidator recieves - otherwise 0
./src/V3Vault.sol:399:    /// @param recipient Address to recieve the position in the vault
./src/V3Vault.sol:407:    /// @param recipient Address to recieve the position in the vault
./src/V3Vault.sol:427:    /// @notice Whenever a token is recieved it either creates a new loan, or modifies an existing one when in transform mode.
```

// Code block 1 (unknown):
$ find . -name "*.sol" | xargs grep -n recieve
./src/transformers/V3Utils.sol:633:        // check if recieved correct amount of tokens
./src/V3Vault.sol:230:    /// @return liquidationValue If position is liquidatable - the value of the (partial) position which the liquidator recieves - otherwise 0
./src/V3Vault.sol:399:    /// @param recipient Address to recieve the position in the vault
./src/V3Vault.sol:407:    /// @param recipient Address to recieve the position in the vault
./src/V3Vault.sol:427:    /// @notice Whenever a token is recieved it either creates a new loan, or modifies an existing one when in transform mode.


carlitox477 Q

# wstETH: If the protocol is meant to be multichain, then WST_ETH, LIDO_CRV_POOL and STETH_TOKEN should be immutable and the constructor modified
In order to not change the current code every time a deployment is done, the mentioned variables should be delcared as immutable and their values set in the constructor:

```diff
    contract WstEth is IDerivative, Initializable, OwnableUpgradeable {
-      address public constant WST_ETH =
-        0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
+      address public immutable WST_ETH;
-      address public constant LIDO_CRV_POOL =
-          0xDC24316b9AE028F1497c275EB9192a3Ea0f67022;
+      address public immutable LIDO_CRV_POOL;
-       address public constant STETH_TOKEN =
-          0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
+       address public immutable STETH_TOKEN;

        uint256 public maxSlippage;

        // As recommended by https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable
        /// @custom:oz-upgrades-unsafe-allow constructor
-       constructor() {
+       constructor(address _WST_ETH, address _LIDO_CRV_POOL, address _STETH_TOKEN) {
            _disableInitializers();
+           WST_ETH = _WST_ETH;
+           LIDO_CRV_POOL = _LIDO_CRV_POOL;
+           STETH_TOKEN = _STETH_TOKEN;
        }
```

# `WstEth.setMaxSlippage(uint256)`, `Reth.setMaxSlippage(uint256)` and `Sfrx.setMaxSlippage(uint256)`  should limit their inputs values to `1e18`
Given that `1e16` is 1%, then 100% would be `1e18`. Given that the slippage should never be greater than 100%.

This would DOS function withdraw/deposit in the different contracts:
* [wstEth](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/WstEth.sol#L60)
* [Reth](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/Reth.sol#L173-L174)
* [SfrxEth](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd

# `WstEth.setMaxSlippage(uint256)`, `Reth.setMaxSlippage(uint256)` and `Sfrx.setMaxSlippage(uint256)`  should limit their inputs values to `1e18`
Given that `1e16` is 1%, then 100% would be `1e18`. Given that the slippage should never be greater than 100%.

This would DOS function withdraw/deposit in the different contracts:
* [wstEth](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/WstEth.sol#L60)
* [Reth](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/Reth.sol#L173-L174)
* [SfrxEth](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/SfrxEth.sol#L74-L75)

This constraint must be checked.

# `SafETH.stake()`: avoid totalSupply shadowing
Current code creates a variable which shadows `totalSupply`. It's name should be replaced by `_totalSupply`. This means:

# `SafETH` Stuck ETH would be freeze forever
Current `SafETH` allows receiving ETH from anyone. In case that a user/contract mistakenly sent ETH to this `SafEth` or in case that `msg.value * weight < totalWeight` in [stake](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L88) function, these funds will be freeze forever. Given that `rebalance` function takes into account the funds at the start of it calls, and the funds after converting derivatives to ETH.

There are three ways to approach this issue:

## Option A: Stuck ETH belongs to the owner
This would mean that a `withdrawStuckETH` function should be added. Given that ETH corresponding to stake/unstake actions are immediately sent to the corresponding contract/user, this function is easy to implement

// Code block 1 (diff):
contract WstEth is IDerivative, Initializable, OwnableUpgradeable {
-      address public constant WST_ETH =
-        0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
+      address public immutable WST_ETH;
-      address public constant LIDO_CRV_POOL =
-          0xDC24316b9AE028F1497c275EB9192a3Ea0f67022;
+      address public immutable LIDO_CRV_POOL;
-       address public constant STETH_TOKEN =
-          0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
+       address public immutable STETH_TOKEN;

        uint256 public maxSlippage;

        // As recommended by https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable
        /// @custom:oz-upgrades-unsafe-allow constructor
-       constructor() {
+       constructor(address _WST_ETH, address _LIDO_CRV_POOL, address _STETH_TOKEN) {
            _disableInitializers();
+           WST_ETH = _WST_ETH;
+           LIDO_CRV_POOL = _LIDO_CRV_POOL;
+           STETH_TOKEN = _STETH_TOKEN;
        }

WstEth.sol#L60, Reth.sol#L173-L174, SfrxEth.sol#L74-L75, SafEth.sol#L88

SfrxEth.sol, Reth.sol, WstEth.sol, SafEth.sol


descharre G

# Summary
|ID     | Finding|  Gas saved | Instances|
|:----: | :---           |  :----:         |:----:         |
|G-01      |Do budget check at the end of the for loop| - |  1 |
|G-02      |Use constants instead of type(uintx).max| 20 |  1 |
|G-03      |Remove block.number from events| 20 |  2 |
|G-04      |Use double if statement instead of &&| 30 |  1 |
|G-05      |Make up to 3 fields in an event indexed| 100 |  - |

# Details
## [G-01] Do budget check at the end of the for loop
The if statement to check if the slate has exceeded the budget in [`_validateSlate()`](https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/StandardFunding.sol#L421-L454) is now in every iteration. Gas can be saved when you put it at the end of the for loop.
[StandardFunding.sol#L421-L454](https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/StandardFunding.sol#L421-L454)
```diff
        for (uint i = 0; i < numProposalsInSlate_; ) {
            Proposal memory proposal = _standardFundingProposals[proposalIds_[i]];

            // check if Proposal is in the topTenProposals list
            if (_findProposalIndex(proposalIds_[i], _topTenProposals[distributionId_]) == -1) revert InvalidProposalSlate();

            // account for fundingVotesReceived possibly being negative
            if (proposal.fundingVotesReceived < 0) revert InvalidProposalSlate();

            // update counters
            sum_ += uint128(proposal.fundingVotesReceived); // since we are converting from int128 to uint128, we can safely assume that the value will not overflow
            totalTokensRequested += proposal.tokensRequested;

            // check if slate of proposals exceeded budget constraint ( 90% of GBC )
-           if (totalTokensRequested > (gbc * 9 / 10)) {
-               revert InvalidProposalSlate();
-           }

            unchecked { ++i; }
        }
+       if (totalTokensRequested > (gbc * 9 / 10)) {
+           revert InvalidProposa

## [G-02] Use constants instead of type(uintx).max
It's more gas efficiënt to use a constant instead of a max from a type. The constant won't be pretty if it's a high value but it will save around 20 gas everytime the function gets called.

[StandardFunding.sol#L659](https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/StandardFunding.sol#L659)

## [G-03] Remove block.number from events
block.number is added by default to event information. Around 300 gas can be saved if you remove block.number when emitting events.


chaduke Q

QA1. It might overwrite an existing key:

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

Mitigation:

```diff
 function _setToken(bytes32 key, address val) private {
        UtilLib.checkNonZeroAddress(val);
-        if (tokenMap[key] == val) {
+        if (tokenMap[key] != address(0)) {

            revert ValueAlreadyInUse();
        }
        tokenMap[key] = val;
        emit SetToken(key, val);
    }
```

QA2. LRTConfigRoleChecker.lrtConfigAddr() has the danger to lose the admin for the newly set lrtConfigAddr.

[https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/utils/LRTConfigRoleChecker.sol#L47-L50]

The function is only callable by the current LRTAdmin, which might not be the LRTAdmin for the newly set lrtConfigAddr. If an invalid lrtConfigAddr is set, then there is no way to correct it.

Mitigation: ensure that the current LRTadmin will also be the LRTAdmin for the new lrtConfigAddr. After that, one can also transfer the LRTadmin to another use using a two-step procedure.

QA3. There is no mechanism to check whether duplicate nodeDelegatorContract is added to the array or not.

[https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L162-L176](https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L162-L176)

Impact: getTotalAssetDeposits() might return the wrong total value since the balance for the duplicate nodeDelegatorContract will be doubly accounted. A user might not be able to deposit asset due to falsely inflated total deposit amount.

Mitigation:
Introduce a mapping so that one can check whether we are adding a new nodeDelegatorContract or not to avoid adding duplicate. Otherwise, the limit ``maxNodeDelegatorCount`` might be wasted due to duplicates.

QA4. LRTDepositPool.updateMaxNodeDelegatorCount() might decrease

// Code block 1 (diff):
function _setToken(bytes32 key, address val) private {
        UtilLib.checkNonZeroAddress(val);
-        if (tokenMap[key] == val) {
+        if (tokenMap[key] != address(0)) {

            revert ValueAlreadyInUse();
        }
        tokenMap[key] = val;
        emit SetToken(key, val);
    }


chrisdior4 Q

# QA report

## Low Risk
| L-N    |Issue|
|:------:|:----|
| [L‑01] | Single-step ownership transfer pattern is dangerous | 9 |
| [L‑02] | Decimals() not part of ERC20 standard | 1 |
| [L‑03] | Open TODO in the code | 2 |
| [L‑04] | Using vulnerable dependency of OpenZeppelin | 2 |
| [L‑05] | Usage of `ecrecover` should be replaced with usage of OpenZeppelin's ECDSA library

Total: 5 issues

## Non-critical

| N-N    |Issue|
|:------:|:----|
| [N‑01] | Solidity safe pragma best practices are not used | 2 |
| [N‑02] | NatSpecs are missing | 6 |
| [N‑03] | Events, structs and custom errors declaration | 11 |
| [N‑04] | Event is missing `indexed` fields | 19 |
| [N‑05] | Typos in the comments | 31 |
| [N‑06] | Redundant code | 2 |
| [N‑07] | Use of magic number | 1 |
| [N‑08] | Interchangeable usage of uint and uint256 in in `BorrowerOperations.sol` | 1 |
| [N‑09] | Lines are too long | 1 |

Total: 9 issues

## Low Risk

### [L-01] Single-step ownership transfer pattern is dangerous

File: `CollateralConfig.sol`

Inheriting from OpenZeppelin's `Ownable` contract means you are using a single-step ownership transfer pattern. If an admin provides an incorrect address for the new owner this will result in none of the `onlyOwner` marked methods being callable again. The better way to do this is to use a two-step ownership transfer approach, where the new owner should first claim its new rights before they are transferred.

```solidity
import "./Dependencies/Ownable.sol";
```

Lines of code:

- https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/CollateralConfig.sol#L6

#### Recommendation

Use OpenZeppelin's `Ownable2Step` instead of `Ownable`

### [L-02] Decimals() not part of ERC20 standard

File: `CollateralConfig.sol`

The `initialize` method is looping through `_collaterals` array and fetching tokens decimals. H

uint256 decimals = IERC20(collateral).decimals();

// Code block 1 (solidity):
import "./Dependencies/Ownable.sol";