Madalad Q

# QA Report

## Chainlink price feed `decimals` not checked

The price value returned by a Chainlink price feed will have a different
`decimals` value depending on the price feed used. While currently **most** ETH pairs use
18 decimals and USD pairs use 8 decimals (see the price feeds for
[LINK/ETH](https://etherscan.io/address/0xDC530D9457755926550b59e8ECcdaE7624181557#readContract)
and
[LINK/USD](https://etherscan.io/address/0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c#readContract)
for example), there is no guarantee that this will be the case for price feeds deployed
in the future. If the decimals are not checked when querying a price feed,
incorrect decimals may be assumed which can lead to significant accounting errors. Specifically,
in `LRTDepositPool#getRsETHAmountToMint`, the decimals of `getAssetPrice()` is assumed to be
exactly 18, otherwise the returned value could be far smaller than expected, leading to users
being minted far fewer rsETH tokens than intended.

To access a price feeds decimals, simply call `priceFeed.decimals()`.

https://github.com/code-423n4/2023-11-kelp/blob/main/src/oracles/ChainlinkPriceOracle.sol#L38

## Chainlink's `latestAnswer` can return stale or incorrect results

Chainlink's `latestAnswer` is used here to retrieve price feed data,
however there is insufficient protection against price staleness. Only the
price is returned, and it is possible for an outdated price
to be received. See
[here](https://ethereum.stackexchange.com/questions/133242/how-future-resilient-is-a-chainlink-price-feed/133843#133843)
for reasons why a price feed might stop updating.

Inaccurate price data can lead to functions not working as expected and/or
lost funds. For example, if the price of a supported asset drops suddenly but the
oracle is returning an outdated higher price, users can take advantage of this by
calling `LRTDepositPool#depositAsset` to mint themselves more rsETH than their
transferred assets are worth, stealing value from the protocol.

https://github.com/code-423n4/2023-11-kelp/blob/main/src/oracles/ChainlinkPriceOracle.sol#L38

## Chainlink aggregators return the incorrect price if it drops below `minAnswer`

Chainlink aggregators have a built in circuit breaker if the price of an asset
goes outside of a predetermined price band. The result is that if an asset
experiences a huge drop in value (i.e. LUNA crash) the price of the oracle will
continue to return the `minAnswer` instead of the actual price of the asset. See
[Chainlink's docs](https://docs.chain.link/data-feeds#check-the-latest-answer-against-reasonable-limits)
for more info.

This discrepency could allow users to `deposit` supported assets that the protocol
perceives as worth more than they actually are, minting them more rsETH than intended
and causing bad debt in the protocol. A similar attack occurred to
[Venus on BSC when LUNA imploded](https://rekt.news/venus-blizz-rekt/).

Consider adding a check to revert if the price received from the oracle is
out of bounds.

https://github.com/code-423n4/2023-11-kelp/blob/main/src/oracles/ChainlinkPriceOracle.sol#L38

## `DEFAULT_ADMIN_ROLE` can renounce role

`LRTConfig` inherits OpenZeppelin's `AccessControlUpgradeable` which allows callers to renounce roles via the
`renounceRole` function. This includes the `DEFAULT_ADMIN_ROLE`, which is
the admin for every other role, as well as the role responsible for many critical functions
in the protocol, including `updateAssetStrategy`, `unpause`, etc. If this role is renounced, then calling
any of those functions, as well as management of
all other roles is impossible.

Renouncing this role should be explicitly prevented by overriding `renounceRole` and throwing
if `DEFAULT_ADMIN_ROLE` is passed.

## `LRTConfig#setToken` should require the asset to be supported

If `setToken()` is called on an `assetAddress` that is not supported, it may mislead those who would call `getLSTToken()` and receive a non-zero address.

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

## LRTManager can transfer asset tokens to `NodeDelegator` while contract is paused

`LRTDepositPool` uses `Pausable` to allow the manager/admin to pause the contract and prevent any sensitive actions, however `transferAssetToNodeDelegator()` is missing the `whenNotPaused` modifier, meaning it is callable even if the contract is in a paused state. The transfer of funds should not be permitted while the contract is paused (in `NodeDelegator`, functions that transfer funds use the `whenNotPaused` modifier even if they are admin protected).

Add the `whenNotPaused` modifier to `transferAssetToNodeDelegator()`.

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

## `LRTConfig#updateAssetDepositLimit()` can cause DoS

`LRTConfig#updateAssetDepositLimit()` is used by the LTR manager to change the `depositLimit` for a particular asset after initialization.


Diana Q

-------

| S No. | Issue | Instances |
|-----|-----|-----|
| [01] | Unused receive() function will lock ether in contract | 3
| [02] | Upgradeable contract is missing a_\_gap[50] storage variable to allow for new storage variables in later versions | 4
| [03] | Use scientific notation rather than exponentiation | 21
| [04] | Missing events for functions that change critical parameters | 3
| [05] | Non-library or interface files should use fixed compiler versions, not floating ones | 4
| [06] | Use named imports instead of plain 'import file.sol' | 31
| [07] | Use a more recent version of solidity | 4

-------------

## 01 Unused receive() function will lock ether in contract

Receive() function exists in this contract, so it is possible to send ETH. If someone sends ETH by mistake for instance `msg.value = 3`, then this will be stuck in the contract as there is no functionality to refund the ETH back.

If the intention is for the Ether to be used, the function should call another function, otherwise it should revert

_There are 3 instances of this issue:_

https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol

```
File: SafEth/derivatives/Reth.sol

244: receive() external payable {}
```

https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/SfrxEth.sol

```
File: SafEth/derivatives/SfrxEth.sol

126: receive() external payable {}
```

https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/WstEth.sol

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

97: receive() external payable {}
```

### Recommended Mitigation Steps

Remove these functions, or include a call to rescueETH in receive(), so that a user that mistakenly sends ETH, is able to retrieve it immediately.

--------

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

See [this](https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_ga

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

244: receive() external payable {}

// Code block 2 (unknown):
File: SafEth/derivatives/SfrxEth.sol

126: receive() external payable {}

// Code block 3 (unknown):
File: SafEth/derivatives/WstEth.sol

97: receive() external payable {}


c3phas G

### Table Of Contents
- [FINDINGS](#findings)
- [IF's/require() statements that check input arguments should be at the top of the function](#ifsrequire-statements-that-check-input-arguments-should-be-at-the-top-of-the-function)
  - [Don't read from storage if we might revert on a check that involves a function arguments.](#dont-read-from-storage-if-we-might-revert-on-a-check-that-involves-a-function-arguments)
  - [Reorder the checks here to have the cheaper one first](#reorder-the-checks-here-to-have-the-cheaper-one-first)
  - [Checks involving constants or local variables should be done before those reading from storage](#checks-involving-constants-or-local-variables-should-be-done-before-those-reading-from-storage)
  - [Incase of a revert here, we can save 2 SSTOREs here](#incase-of-a-revert-here-we-can-save-2-sstores-here)
  - [Some checks being performed at the bottom should be checked earlier](#some-checks-being-performed-at-the-bottom-should-be-checked-earlier)
- [x += y costs more gas than x = x + y for state variables](#x--y-costs-more-gas-than-x--x--y-for-state-variables)
- [Using unchecked blocks to save gas](#using-unchecked-blocks-to-save-gas)
- [Cache storage values in memory to minimize SLOADs](#cache-storage-values-in-memory-to-minimize-sloads)
  - [GrantFund.sol.fundTreasury(): treasury can be cached in memory](#grantfundsolfundtreasury-treasury-can-be-cached-in-memory)
- [Nested if is cheaper than single statement using \&\&](#nested-if-is-cheaper-than-single-statement-using-)
- [StandardFunding.sol.\_validateSlate(): No need to cache a function parameter](#standardfundingsol_validateslate-no-need-to-cache-a-function-parameter)
- [Declare a constant variable instead of repeatedly doing the same calculation](#declare-a-constant-variable-instead-of-repeatedly-doing-the-same-calculation)
- [Don't cache variables that are used only once](#dont-cache-variables-that-are-used-only-once)
- [Unnecessary assignement](#unnecessary-assignement)

## FINDINGS
NB

File: /ajna-core/src/RewardsManager.sol
135:    function moveStakedLiquidity(
136:        uint256 tokenId_,
137:        uint256[] memory fromBuckets_,
138:        uint256[] memory toBuckets_,
139:        uint256 expiry_
140:    ) external nonReentrant override {
141:        StakeInfo storage stakeInfo = stakes[tokenId_];

143:        if (msg.sender != stakeInfo.owner) revert NotOwnerOfDeposit();

145:        // check move array sizes match to be able to match on index
146:        uint256 fromBucketLength = fromBuckets_.length;
147:        if (fromBucketLength != toBuckets_.length) revert MoveStakedLiquidityInvalid();

https://github.com/code-423n4/2023-05-ajna/blob/276942bc2f97488d07b887c8edceaaab7a5c3964/ajna-core/src/RewardsManager.sol#L207-L213
### Reorder the checks here to have the cheaper one first


djanerch Q

**Summary**

The `setOracle` function in the `rUSDY.sol` contract doesn't check if the `_oracle` address is valid. This oversight can lead to problems if the contract owner mistakenly sets the address to `address(0)`.

**Details:**

The `setOracle` function in the `rUSDY.sol` contract allows the contract owner to update the Oracle address. However, it doesn't verify the validity of the `_oracle` address provided as input. This could create issues within the contract, particularly if the owner accidentally sets `_oracle` to the Ethereum zero address, `address(0)`.

**Suggested Fix:**

To improve the contract's security and reliability, it's advisable to include a simple check. Before updating the Oracle address, ensure that `_oracle` is not equal to `address(0)`. You can implement this fix as shown below:

```solidity
function setOracle(address _oracle) external onlyRole(USDY_MANAGER_ROLE) {
    require(_oracle != address(0), "Invalid oracle address");
    oracle = IRWADynamicOracle(_oracle);
}
```

**Benefits of the Fix:**

- Increases the security of the `rUSDY.sol` contract by verifying input data.
- Prevents unintended issues and potential disruptions caused by mistakenly setting the Oracle address to `address(0)`.

// Code block 1 (solidity):
function setOracle(address _oracle) external onlyRole(USDY_MANAGER_ROLE) {
    require(_oracle != address(0), "Invalid oracle address");
    oracle = IRWADynamicOracle(_oracle);
}


ernestognw Q

## ETH sent to the contract will be unlocked

The `SafEth.sol` contract contains a `receive() external payable` function that allows depositing into the contract directly. However, there's no sweeping mechanism for ETH in the contract and the only way to get withdraw ETH from the contract is by calling `unstake()` which only sends the difference in ETH after exchanging the derivatives.

With the current configuration, any excedent ETH on the contract will be locked.

### PoC

Consider the following test using Foundry:

```solidity
// @notice Test
function test_alterBalance() public {
    _deploy();
    (bool success, ) = address(safEth).call{value: 1 ether}("");
    require(success);
    assertEq(safEth.totalSupply(), 0);
    assertEq(address(safEth).balance, 1 ether);
}

function _deploy() internal {
   // Implementations of these omitted for clarity
    _deploySafETH();
    _deployDerivatives();
    _addDerivatives();
}
```

### Recommendation

Whitelist the allowed callers of the `receive()` function, so compatibility with derivatives is not broken. Also, consider calling `stake()` on every `receive()` execution for every non-whitelisted caller.

OpenZeppelin's [EnumerableSet](https://docs.openzeppelin.com/contracts/4.x/api/utils#EnumerableSet) is recommended for the whitelisting mechanism.

## Calling `stake()` can lock ETH up to `derivativeCount` wei

While computing `ethAmount` inside the `stake()` function in line 87 of the `SafEth.sol` contract, there's a rounding error of 1 wei before depositing on each derivative, leaving small amounts of ETH in the contract.

```solidity
uint256 ethAmount = (msg.value * weight) / totalWeight;
```

Although this can be considered negligible since the minimum deposit is `0.5 ether`, it's recommended to have a mechanism of sweeping the loss funds if they become relevant enough. Otherwise, these can be carried in the same line by adding the current contract balance (although it may increase gas costs and be potentially danger

// Code block 1 (solidity):
// @notice Test
function test_alterBalance() public {
    _deploy();
    (bool success, ) = address(safEth).call{value: 1 ether}("");
    require(success);
    assertEq(safEth.totalSupply(), 0);
    assertEq(address(safEth).balance, 1 ether);
}

function _deploy() internal {
   // Implementations of these omitted for clarity
    _deploySafETH();
    _deployDerivatives();
    _addDerivatives();
}

// Code block 2 (solidity):
uint256 ethAmount = (msg.value * weight) / totalWeight;

// @notice Test
function test_alterBalance() public {
    _deploy();
    (bool success, ) = address(safEth).call{value: 1 ether}("");
    require(success);
    assertEq(safEth.totalSupply(), 0);
    assertEq(address(safEth).balance, 1 ether);
}

function _deploy() internal {
   // Implementations of these omitted for clarity
    _deploySafETH();
    _deployDerivatives();
    _addDerivatives();
}

uint256 ethAmount = (msg.value * weight) / totalWeight;


Sabit G

Here are some gas optimization issues:

Redundant ERC20 balance check in withdraw() function.

https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/WstEth.sol#L56

```
function withdraw(uint256 _amount) external onlyOwner {
    IWStETH(WST_ETH).unwrap(_amount);
    uint256 stEthBal = IERC20(STETH_TOKEN).balanceOf(address(this)); // Redundant
    IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, stEthBal);
    uint256 minOut = (stEthBal * (10 ** 18 - maxSlippage)) / 10 ** 18;
    IStEthEthPool(LIDO_CRV_POOL).exchange(1, 0, stEthBal, minOut);
    // solhint-disable-next-line
    (bool sent, ) = address(msg.sender).call{value: address(this).balance}(
        ""
    );
    require(sent, "Failed to send Ether");
}
```

The stEthBal variable is not used.

https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/WstEth.sol#L56

```
function withdraw(uint256 _amount) external onlyOwner {
    IWStETH(WST_ETH).unwrap(_amount);
    uint256 minOut = (_amount * (10 ** 18 - maxSlippage)) / 10 ** 18;
    IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, _amount);
    IStEthEthPool(LIDO_CRV_POOL).exchange(1, 0, _amount, minOut);
    // solhint-disable-next-line
    (bool sent, ) = address(msg.sender).call{value: address(this).balance}(
        ""
    );
    require(sent, "Failed to send Ether");
}

```

balanceOf() not used.
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/WstEth.sol#L93

function balance() public view returns (uint256) {
    return IERC20(WST_ETH).balanceOf(address(this)); // Redundant
}
```
The code assigns wstEthBalancePre and wstEthBalancePost variables the same value as the WST_ETH balance of the contract. It's not needed because the wstETH amount is equal to the value of msg.value. The variables and the subtraction are redundant.

https://github.com/code-423n4/2023-

// Code block 1 (unknown):
function withdraw(uint256 _amount) external onlyOwner {
    IWStETH(WST_ETH).unwrap(_amount);
    uint256 stEthBal = IERC20(STETH_TOKEN).balanceOf(address(this)); // Redundant
    IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, stEthBal);
    uint256 minOut = (stEthBal * (10 ** 18 - maxSlippage)) / 10 ** 18;
    IStEthEthPool(LIDO_CRV_POOL).exchange(1, 0, stEthBal, minOut);
    // solhint-disable-next-line
    (bool sent, ) = address(msg.sender).call{value: address(this).balance}(
        ""
    );
    require(sent, "Failed to send Ether");
}

// Code block 2 (unknown):
function withdraw(uint256 _amount) external onlyOwner {
    IWStETH(WST_ETH).unwrap(_amount);
    uint256 minOut = (_amount * (10 ** 18 - maxSlippage)) / 10 ** 18;
    IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, _amount);
    IStEthEthPool(LIDO_CRV_POOL).exchange(1, 0, _amount, minOut);
    // solhint-disable-next-line
    (bool sent, ) = address(msg.sender).call{value: address(this).balance}(
        ""
    );
    require(sent, "Failed to send Ether");
}


Rolezn G

## GAS Summary<a name="GAS Summary">

### Gas Optimizations
| |Issue|Contexts|Estimated Gas Saved|
|-|:-|:-|:-:|
| [GAS‑1](#GAS‑1) | Activate the optimizer | 1 | - |
| [GAS‑2](#GAS‑2) | `address(this)` can be stored in `state variable` that will ultimately cost less, than every time calculating this, specifically when it used multiple times in a `contract` | 1 | - |
| [GAS‑3](#GAS‑3) | Use assembly to emit events | 11 | 418 |
| [GAS‑4](#GAS‑4) | Avoid emitting event on every iteration | 1 | 375 |
| [GAS‑5](#GAS‑5) | Avoid repeating computations | 20 | - |
| [GAS‑6](#GAS‑6) | Gas savings can be achieved by changing the model for assigning value to the structure | 7 | 910 |
| [GAS‑7](#GAS‑7) | Combine events to save `Glogtopic (375 gas)` for each instance | 2 | 750 |
| [GAS‑8](#GAS‑8) | Counting down in `for` statements is more gas efficient | 12 | 3084 |
| [GAS‑9](#GAS‑9) | Hash using `assembly` instead of solidity | 2 | 160 |
| [GAS‑10](#GAS‑10) | Using `unchecked` blocks to save gas | 3 | 120 |
| [GAS‑11](#GAS‑11) | Add `unchecked {}` for subtractions where the operands cannot underflow | 14 | 560 |
| [GAS‑12](#GAS‑12) | Use do while loops instead of for loops | 12 | 48 |
| [GAS‑13](#GAS‑13) | Using XOR (^) and AND (&) bitwise equivalents | 14 | 182 |

Total: 172 contexts over 13 issues

### Experimental Gas Optimizations

The following gas optimization include snapshots and the code snippet that was used. The issues may also include invalid instances that were detected to either increase gas usage or not provide any improvements.

| |Issue|
|-|:-|
| [EXPGAS‑1](#EXPGAS‑1) | Use do while loops instead of for loops |
| [EXPGAS‑2](#EXPGAS‑2) | `unchecked {}` can be used on the division of two `uints` in order to save gas |
| [EXPGAS‑3](#EXPGAS‑3) |

module.exports = {
	solidity: {
		version: "0.8.19",
		settings: {
			optimizer: {
				enabled: true,
				runs: 1000,
			},
		},
	},
};

for !=0 before optimization
PUSH1 0x00
DUP2
EQ
ISZERO
PUSH1 [cont offset]
JUMPI

after optimization
DUP1
PUSH1 [revert offset]
JUMPI


SAQ G

## Summary

### Gas Optimization

no | Issue |Instances||
|-|:-|:-:|:-:|
| [G-01] | Using fixed bytes is cheaper than using string | 1 | - |
| [G-02] | Can make the variable outside the loop to save gas | 1 | - |
| [G-03] | Using calldata instead of memory for read-only arguments in external functions saves gas | 2 | - |
| [G-04] | Use assembly to check for address(0) | 5 | - |
| [G-05] | Before transfer of  some functions, we should check some variables for possible gas save | 5 | - |
| [G-06] | State variables can be packed to use fewer storage slots | 1 | - |
| [G-07] | Use constants instead of type(uintx).max | 1 | - |
| [G-08] | Use nested if and, avoid multiple check combinations | 3 | - |
| [G-09] | Use assembly to write address storage values | 9 | - |
| [G-10] | Sort Solidity operations using short-circuit mode | 3 | - |
| [G-11] | Use hardcode address instead address(this) | 8 | - |
| [G-12] | Use assembly for math (add, sub, mul, div) | 2 | - |
| [G-13] | Don't apply the same value to state variables | 4 | - |
| [G-14] |  Minimize external calls if possible | more files | - |

## Gas Optimizations

## [G-1]  Using fixed bytes is cheaper than using string

### Details

As a rule of thumb, use bytes for arbitrary-length raw byte data and string for arbitrary-length string (UTF-8) data.
If you can limit the length to a certain number of bytes, always use one of bytes1 to bytes32 because they are much cheaper

```solidity
file: /contracts/lybra/token/EUSD.sol

107        function symbol() public pure returns (string memory) {
108          return "eUSD";

```
https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/token/EUSD.sol#L107

## [G-2]  Can make the variable outside the loop to save gas

Consider making the stack variables before the loop which gonna save gas

```solidity
file: /contracts/lybra/miner/EUSDMiningIncentives.sol

140    uint borrowed = pool.getBorrowedOf(user);

```
https://github.com/code-423n4/2023-06-lybra/blob/main

// Code block 1 (solidity):
file: /contracts/lybra/token/EUSD.sol

107        function symbol() public pure returns (string memory) {
108          return "eUSD";

// Code block 2 (solidity):
file: /contracts/lybra/miner/EUSDMiningIncentives.sol

140    uint borrowed = pool.getBorrowedOf(user);

file: /contracts/lybra/token/EUSD.sol

107        function symbol() public pure returns (string memory) {
108          return "eUSD";

file: /contracts/lybra/miner/EUSDMiningIncentives.sol

140    uint borrowed = pool.getBorrowedOf(user);


0xnev G

| Count | Title | Instances | Gas Savings |
|:--:|:-------|:--:|:--:|
| [G-01] | Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead | 1 | 16511 |
| [G-03] | Do not need to use `+=` operator, `=` can be used instead | 1 | 62631 |
| [G-03] | `else if` block in `Maths.wsqrt()` can be removed | 1 | 11 |
| [G-04] | Implement `StandardFunding._setNewDistributionId()` in `StandardFunding.startNewDistributionPeriod()` directly | 1 | 3222 |
| [G-05] | Use constant instead of immutable for `ajnaTokenAddress` in `Funding.sol` | 1 | 16919 |
| [G-06] | `<x> += <y>` costs more gas than `<x> = <x> + <y>` for state variables | 3 | 339 |
| [G-07] | RMultiple accesses of a mapping/array should use a local variable cache | 5 | 210 |

| Total Gas-Optimization Issues | 7 |
|:--:|:--:|

| Total Estimated Gas Savings | 99843 |
|:--:|:--:|

All gas savings benchmark are retrieved from comparisons from unit tests

### [G-01] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead
[PositionManager.sol#L62](https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/PositionManager.sol#L62)
```solidity
62:    uint176 private _nextId = 1;
```
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Each operation involving a uint8 costs extra as compared to ones involving uint256, due to the compiler having to clear the higher bits of the memory word before operating on the uint8, as well as the associated stack operations of doing so. Use a larger size then downcast where needed

Saves ~16255 gas on deployment and on average 256 gas for `PositionManager.mint()` function that uses the `_nextId` variable.

`PositionManager.sol` Deployment Cost:
|        | Deployment Cost   |
| ------ | --- |
| Before                                | 3922538 |
| After                                | 3906283 |

`PositionManager.mint()`  gas savings:
|        | Min    | Average | Median   | Max   |
| ------ | --- | ------- | ----- | ----- |
| Before

rewards_ += Maths.wmul(UPDATE_CLAIM_REWARD, Maths.wmul(burnFactor, interestFactor));

// Code block 1 (solidity):
62:    uint176 private _nextId = 1;


Fahrul G

# Replace `getChainId()` with `block.chainid`
We can get the chainId using `block.chainid` instead of using `getChainId()`.

*Instances (2)*:
```solidity
File: contracts/smart-contract-wallet/SmartAccount.sol
Line 135-147

    function domainSeparator() public view returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, getChainId(), this));
    }

    /// @dev Returns the chain id used by this contract.
    function getChainId() public view returns (uint256) {
        uint256 id;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            id := chainid()
        }
        return id;
    }
```
[Link to code](https://github.com/code-423n4/2023-01-biconomy/tree/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol)

```solidity
File: contracts/smart-contract-wallet/SmartAccountNoAuth.sol
Line 135-147

    function domainSeparator() public view returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, getChainId(), this));
    }

    /// @dev Returns the chain id used by this contract.
    function getChainId() public view returns (uint256) {
        uint256 id;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            id := chainid()
        }
        return id;
    }

```
[Link to code](https://github.com/code-423n4/2023-01-biconomy/tree/main/scw-contracts/contracts/smart-contract-wallet/SmartAccountNoAuth.sol)

We also could delete the `getChainId` function to reduce the deployment cost, since it will never get used.

// Code block 1 (solidity):
File: contracts/smart-contract-wallet/SmartAccount.sol
Line 135-147

    function domainSeparator() public view returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, getChainId(), this));
    }

    /// @dev Returns the chain id used by this contract.
    function getChainId() public view returns (uint256) {
        uint256 id;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            id := chainid()
        }
        return id;
    }

// Code block 2 (solidity):
File: contracts/smart-contract-wallet/SmartAccountNoAuth.sol
Line 135-147

    function domainSeparator() public view returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, getChainId(), this));
    }

    /// @dev Returns the chain id used by this contract.
    function getChainId() public view returns (uint256) {
        uint256 id;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            id := chainid()
        }
        return id;
    }

File: contracts/smart-contract-wallet/SmartAccount.sol
Line 135-147

    function domainSeparator() public view returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, getChainId(), this));
    }

    /// @dev Returns the chain id used by this contract.
    function getChainId() public view returns (uint256) {
        uint256 id;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            id := chainid()
        }
        return id;
    }

File: contracts/smart-contract-wallet/SmartAccountNoAuth.sol
Line 135-147

    function domainSeparator() public view returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, getChainId(), this));
    }

    /// @dev Returns the chain id used by this contract.
    function getChainId() public view returns (uint256) {
        uint256 id;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            id := chainid()
        }
        return id;
    }


Blckhv G

# Gas report

## Globally available variables such as block.number can be cached in variable when used multiple times in one function

https://github.com/code-423n4/2023-05-ajna/blob/276942bc2f97488d07b887c8edceaaab7a5c3964/ajna-grants/src/grants/base/StandardFunding.sol#L119-L164

When it's necessary to use globally available vars such as `block.number` in current scenario, consider caching it.

e.g apply these modifications:

`uint256 blockNumber = block.number;`

and change it in all the places where used

## Nested if statements can be combined into one to reduce code nesting

https://github.com/code-423n4/2023-05-ajna/blob/276942bc2f97488d07b887c8edceaaab7a5c3964/ajna-core/src/PositionManager.sol#L193-L199

When there is no logic between 2 or more if statements it is recommended to merge them into one with the help of conditional statements

e.g
```
if (position.depositTime != 0) {
                // check that bucket didn't go bankrupt after prior memorialization
                if (_bucketBankruptAfterDeposit(pool, index, position.depositTime)) {
                    // if bucket did go bankrupt, zero out the LP tracked by position manager
                    position.lps = 0;
                }
            }
```
to
```
if (position.depositTime != 0 && _bucketBankruptAfterDeposit(pool, index, position.depositTime) {
                 // if bucket did go bankrupt, zero out the LP tracked by position manager
                    position.lps = 0;
            }
```

## Declare all the variables as `constants` when their value is known at the time of development and is not intended to change in the future

https://github.com/code-423n4/2023-05-ajna/blob/276942bc2f97488d07b887c8edceaaab7a5c3964/ajna-grants/src/grants/base/Funding.sol#L21
https://github.com/code-423n4/2023-05-ajna/blob/276942bc2f97488d07b887c8edceaaab7a5c3964/ajna-grants/src/grants/base/Funding.sol#L61

Declaring constant for variables which value is hardcoded can result in a lot of saved gas if

// Code block 1 (unknown):
if (position.depositTime != 0) {
                // check that bucket didn't go bankrupt after prior memorialization
                if (_bucketBankruptAfterDeposit(pool, index, position.depositTime)) {
                    // if bucket did go bankrupt, zero out the LP tracked by position manager
                    position.lps = 0;
                }
            }

// Code block 2 (unknown):
if (position.depositTime != 0 && _bucketBankruptAfterDeposit(pool, index, position.depositTime) {
                 // if bucket did go bankrupt, zero out the LP tracked by position manager
                    position.lps = 0;
            }

StandardFunding.sol#L119-L164, PositionManager.sol#L193-L199, Funding.sol#L21, Funding.sol#L61


martin Q

## Introduction

Caviar Private Pools QA report was done by martin, with a main focus on the low severity and non-critical security aspects of the implementaion and logic of the project.

## Findings Summary

The following issues were found, categorized by their severity:

## Findings Summary

| ID      | Title                                                 | Severity     |
| ------- | ----------------------------------------------------- | ------------ |
| [L-01]  | Missing `nonReentrant` modifier                       | Low          |
| [L-02]  | User is unable to withdraw mis-sent funds             | Low          |
| [L-03]  | No upper limit validation on user-supplied parameters | Low          |
| [NC-01] | Use a safe pragma statement                           | Non-Critical |
| [NC-02] | Incomplete NatSpec                                    | Non-Critical |
| [NC-03] | Follow best practices when concatenating strings      | Non-Critical |
| [NC-04] | Typos                                                 | Non-Critical |

### [L-01] Missing `nonReentrant` modifier

`_safeMint` performs external function call that creates a security loophole. Specifically, the attacker can perform a reentrant call inside the onERC721Received callback. More detailed information why a reeentrancy can occur - https://blocksecteam.medium.com/when-safemint-becomes-unsafe-lessons-from-the-hypebears-security-incident-2965209bda2a.

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

### [L-02] User is unable to withdraw mis-sent funds

If the intention is for the Ether to be used, the function should call another function, otherwise it should revert. Having no access control on the function means that someone may send Ether to the contract, and have no way to get anything back out, which is a loss of funds. There is `withdraw` function which allows only the admin to withdraw the earned protocol fees (Ether or Tokens)

https://github.com/code-423n4/2023-04-caviar

File: /src/Factory.sol

File: /src/PrivatePoolMetadata.sol

File: /src/EthRouter.sol

File: /src/PrivatePool.sol

File: /src/interfaces/IStolenNftOracle.sol

251: // add the royalty fee amount to the net input aount


lilizhu Q

## [L-01] **Missing address(0) checks in** ManagedWallet contract

### Impact

In the constructor of the ManagedWallet contract in the src/ManagedWallet.sol file, there is no verification of non-zero addresses for the input parameters. If the product team specifies _mainWallet or _confirmationWallet as address(0) during initialization, it will cause the entire system's mainWallet mechanism to completely fail.

- If _mainWallet is mistakenly specified as address(0) during initialization, no one can propose a new main wallet and confirmation wallet, causing the mainWallet mechanism to fail.
- If _confirmationWallet is mistakenly specified as address(0) during initialization, no one can confirm or reject the confirmation wallet, causing the mainWallet mechanism to fail.

https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/ManagedWallet.sol#L31

ps: I don't agree with the reasons described   “**The absence of checks for the zero address ('0x0') in the code is an intentional gas-saving measure.”**,because the construct function is more import than proposeWallets() function, the proposeWallets() function also has the address(0) check, so the construct function more necessary set the address(0) checker.

## Tools Used

vs code, forge

## Recommended Mitigation Steps

```solidity

constructor( address _mainWallet, address _confirmationWallet)
		{
		require( _mainWallet != address(0), "_mainWallet cannot be the zero address" );
		require( _confirmationWallet != address(0), "_confirmationWallet cannot be the zero address" );

		mainWallet = _mainWallet;
		confirmationWallet = _confirmationWallet;

                // Write a value so subsequent writes take less gas
		activeTimelock = type(uint256).max;
        }
```

// Code block 1 (solidity):
constructor( address _mainWallet, address _confirmationWallet)
		{
		require( _mainWallet != address(0), "_mainWallet cannot be the zero address" );
		require( _confirmationWallet != address(0), "_confirmationWallet cannot be the zero address" );

		mainWallet = _mainWallet;
		confirmationWallet = _confirmationWallet;

                // Write a value so subsequent writes take less gas
		activeTimelock = type(uint256).max;
        }


0x11singh99 Q

# Non Critical
1. Function params name in all over the protocol doesn't follow one convention, sometimes it is prepended with _ sometimes not. Which is confusing.
2. named return is confusing
3. In many places natspec comments not written, which is really confusing to understand the intention of function or contract.
4. Prepend internal/private functions and variables with _ to make clear. Sometimes it is done sometimes not.
5. Use constant variables instead of direct numeric value it not good practice. It is used all over the protocol.
## 6. Use proper solidity style guide layout it is not followed all over the protocol
### Order of Solidity Layout :
### Layout contract elements in the following order:
Import statements
Interfaces
Libraries
Contracts

## Inside each contract, library or interface, use the following order:

Type declarations
State variables
Events
Errors
Modifiers
Functions

### Functions should be grouped according to their visibility and ordered:

constructor
receive function (if exists)
fallback function (if exists)
external
public
internal
private

#### Within a grouping, place the view and pure functions last.

# Low level
## L-01  Function input not verified, which can be vulnerable and gas consuming verify to fail early
###  user can withdraw more than he staked . or unexpected underflow will occur which is not desired response
To get it check the balance before tranfser

```solidity
File : contracts/lybra/miner/stakerewardV2pool.sol
    //@audit low check balance before transfer to prevent unexpected under flow
        balanceOf[msg.sender] -= _amount;
```
https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/miner/stakerewardV2pool.sol#L95
### it is on many places in protocol check it and improve it.
## L-02 Transfer return value not checked, if transfer fails it will not report which is a issue
### transfer retrun not checked
```solidity
```

// Code block 1 (solidity):
File : contracts/lybra/miner/stakerewardV2pool.sol
    //@audit low check balance before transfer to prevent unexpected under flow
        balanceOf[msg.sender] -= _amount;


report

---
sponsor: "Asymmetry Finance"
slug: "2023-03-asymmetry"
date: "2023-07-28"
title: "Asymmetry contest"
findings: "https://github.com/code-423n4/2023-03-asymmetry-findings/issues"
contest: 226
---

# 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 Asymmetry audit smart contract system written in Solidity. The audit took place between March 24—March 30 2023.

Following the C4 audit, 3 wardens ([0x52](https://twitter.com/IAm0x52), [adriro](https://twitter.com/adrianromero), and d3e4) reviewed the mitigations for all identified issues; the [Mitigation Review](#mitigation-review) report is appended below the audit report.

## Wardens

271 Wardens contributed reports to the Asymmetry audit:

  1. 019EC6E2
  2. [0Kage](https://twitter.com/0kage_eth)
  3. 0x3b
  4. [0x52](https://twitter.com/IAm0x52)
  5. [0xAgro](https://twitter.com/0xAgro)
  6. 0xGordita
  7. 0xGusMcCrae
  8. 0xMirce
  9. 0xNorman
  10. [0xRajkumar](https://twitter.com/0xRajkumar)
  11. 0xRobocop
  12. [0xSmartContract](https://twitter.com/0xSmartContract)
  13. 0xTraub
  14. 0xWagmi
  15. 0xWaitress
  16. [0xadrii](https://twitter.com/0xadrii)
  17. 0xbepresent
  18. 0xc0ffEE
  19. [0xd1r4cde17a](https://twitter.com/0xd1r4cde17a)
  20. 0xepley
  21. 0xffchain
  22. [0xfusion](https://twitter.com/sstrenev)
  23. 0xhacksmithh
  24. 0xkazim
  25. 0xl51
  26. 0xmuxyz
  27. [0xnev](https://twitter.com/0xnevi)
  28. [0xpanicError](https://twitter.com/0xpanicError)
  29. 3dgeville
  30. 4lulz
  31. 7siech
  32. [AkshaySrivastav](https://twitter.com/akshaysrivastv)
  33. [Angry\_Mustache\_Man](https://twitter.com/AdityaK0010

if (totalSupply == 0)
            preDepositPrice = 10 ** 18; // initializes with a price of 1
        else preDepositPrice = (10 ** 18 * underlyingValue) / totalSupply;

uint256 mintAmount = (totalStakeValueEth * 10 ** 18) / preDepositPrice;


Rolezn G

## Summary

### Gas Optimizations
| |Issue|Contexts|Estimated Gas Saved|
|-|:-|:-|:-:|
| [GAS‑1](#GAS‑1) | `abi.encode()` is less efficient than `abi.encodepacked()` | 2 | 200 |
| [GAS‑2](#GAS‑2) | Use calldata instead of memory for function parameters | 4 | 1200 |
| [GAS‑3](#GAS‑3) | Setting the `constructor` to `payable` | 3 | 39 |
| [GAS‑4](#GAS‑4) | Using `delete` statement can save gas | 2 | - |
| [GAS‑5](#GAS‑5) | Functions guaranteed to revert when called by normal users can be marked `payable` | 11 | 231 |
| [GAS‑6](#GAS‑6) | Use hardcoded address instead `address(this)` | 25 | - |
| [GAS‑7](#GAS‑7) | Optimize names to save gas | 4 | 88 |
| [GAS‑8](#GAS‑8) | `<x> += <y>` Costs More Gas Than `<x> = <x> + <y>` For State Variables | 9 | - |
| [GAS‑9](#GAS‑9) | Public Functions To External | 32 | - |
| [GAS‑10](#GAS‑10) | Shorten the array rather than copying to a new one | 1 | - |
| [GAS‑11](#GAS‑11) | String literals passed to `abi.encode()`/`abi.encodePacked()` should not be split by commas | 58 | 1218 |
| [GAS‑12](#GAS‑12) | Structs can be packed into fewer storage slots | 2 | 4000 |
| [GAS‑13](#GAS‑13) | Usage of `uints`/`ints` smaller than 32 bytes (256 bits) incurs overhead | 5 | - |

Total: 158 contexts over 13 issues

## Gas Optimizations

### [GAS‑1] `abi.encode()` is less efficient than `abi.encodepacked()`

See for more information: https://github.com/ConnorBlockchain/Solidity-Encode-Gas-Comparison

#### Proof Of Concept

```solidity
177: abi.decode(abi.encode(sells[i].stolenNftProofs), (ReservoirOracle.Message[]))
```

https://github.com/code-423n4/2023-04-caviar/tree/main/src/EthRouter.sol#L177

```solidity
675: leafs[i] = keccak256(bytes.concat(keccak256(abi.encode(tokenIds[i], tokenWei

675: leafs[i] = keccak256(bytes.concat(keccak256(abi.encode(tokenIds[i], tokenWeights[i]))));

// Code block 1 (solidity):
177: abi.decode(abi.encode(sells[i].stolenNftProofs), (ReservoirOracle.Message[]))


0xDetermination Q

# QA report by 0xDetermination

|Issue number|Description|
|:-|:-|
|Low issues|
|[L-1](#l-1)|Discrepancy between docs and code- false `initiatorBool` argument for `RankedBattle.updateBattleRecord()` should not change a fighter's points according to docs |
|[L-2](#l-2)|Small stake amounts can't be lost to `StakeAtRisk` due to precision loss|
|[L-3](#l-3)|`RankedBattle.claimNRN()` provides extra attack surface in case of an attacker inflating `claimableNRN` value|
|[L-4](#l-4)|New users to the protocol joining a significant amount of time after project launch will incur huge gas fees when calling `RankedBattle.claimNRN()`|
|[L-5](#l-5)|`FighterFarm.IncrementGeneration()` should enforce a `fighterType` of 0 or 1|
|[L-6](#l-6)|`MergingPool.claimRewards()` can mint an NFT with out-of-range fighter weight|
|[L-7](#l-7)|Ensure off-chain validation is performed to check player voltage and prevent the game server from being gas griefed|
|Info issues|
|[I-1](#i-1)|Comments for win/lose case can be put on the same line as the conditional statement for clarity|
|[I-2](#i-2)|Consider calling `GameItems.instantiateNeuronContract()` in the constructor|
|[I-3](#i-3)|`success` bool checks on NRN transfers are not necessary because the OZ implementation will not silently fail|
|[I-4](#i-4)|Explicitly declare state variable visibility for code clarity|
|[I-5](#i-5)|Ambiguous NatSpec for `MergingPool.claimRewards()`|
|[I-6](#i-6)|Consider adding a `minId` param to `MergingPool.getFighterPoints()`|
|[I-7](#i-7)|`Neuron.burnFrom()` is not used in the protocol and can be removed to reduce attack surface area|
|[I-8](#i-8)|`FighterFarm.reRoll()` NatSpec should specify pseudo-randomness|
|[I-9](#i-9)|Fighter generations can't increase past 255; `FighterFarm.incrementGeneration()` will fail with overflow|
|[I-10](#i-10)|Consider using interfaces instead of importing contracts directly|
|[I-11](#i-11)|`FighterFarm.sol` doesn't need to inherit `ERC721` since it already inherits `ERC721Enumerab

    function _addResultPoints(
        ...
        if (_calculatedStakingFactor[tokenId][roundId] == false) {
            stakingFactor[tokenId] = _getStakingFactor(tokenId, stakeAtRisk);
            _calculatedStakingFactor[tokenId][roundId] = true;
        }

        curStakeAtRisk = (bpsLostPerLoss * (amountStaked[tokenId] + stakeAtRisk)) / 10**4; //Precision loss in stakeAtRisk calculation
        ...
            points = stakingFactor[tokenId] * eloFactor; //Points are calculated based on stakingFactor

    ...
    function _getStakingFactor(
        uint256 tokenId,
        uint256 stakeAtRisk
    )
        private
        view
        returns (uint256)
    {
      uint256 stakingFactor_ = FixedPointMathLib.sqrt(
          (amountStaked[tokenId] + stakeAtRisk) / 10**18
      );
      if (stakingFactor_ == 0) { //stakingFactor is set to 1 if it was calculated as zero
        stakingFactor_ = 1;
      }
      return stakingFactor_;
    }

        if (claimableNRN > 0) {
            amountClaimed[msg.sender] += claimableNRN;
            _neuronInstance.mint(msg.sender, claimableNRN);


chriszhou Q

## Event is missing indexed fields

Index event fields make the field more quickly accessible to off-chain tools that parse events. However, note that each index field costs extra gas during emission, so it’s not necessarily best to index the maximum allowed per event (three fields). Each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three fields, all of the fields should be indexed.

There are 4 instances of this issue.
```
File: contracts/SafEth/SafEth.sol
26:   	event Staked(address indexed recipient, uint ethIn, uint safEthOut);
27:     event Unstaked(address indexed recipient, uint ethOut, uint safEthIn);
28:     event WeightChange(uint indexed index, uint weight);
29-31:      event DerivativeAdded(
        address indexed contractAddress,
        uint weight,
        uint index
    );
```

// Code block 1 (unknown):
File: contracts/SafEth/SafEth.sol
26:   	event Staked(address indexed recipient, uint ethIn, uint safEthOut);
27:     event Unstaked(address indexed recipient, uint ethOut, uint safEthIn);
28:     event WeightChange(uint indexed index, uint weight);
29-31:      event DerivativeAdded(
        address indexed contractAddress,
        uint weight,
        uint index
    );


HollaDieWaldfee Q

# Asymmetry Low Risk and Non-Critical Issues
## Summary
| Risk      | Title | File | Instances
| ----------- | ----------- | ----------- | ----------- |
| L-01      | Use fixed compiler version | - | 4 |
| L-02      | Wrong index emitted in `DerivativeAdded` event | SafEth.sol | 1 |
| L-03      | OwnableUpgradeable: Does not implement 2-Step-Process for transferring ownership | SafEth.sol | 1 |
| L-04      | `SafEth.stake`: check `ethAmount` for zero instead of `weight` | SafEth.sol | 1 |
| L-05      | Fixed slippage setting is inefficient because traded amounts can vary | - | 3 |
| L-06      | `Reth.ethPerDerivative` function should always use ETH value from Rocketpool instead of pool price | Reth.sol | 1 |
| N-01      | Remove unnecessary imports | - | 5 |
| N-02      | `SafEth` contract `receive` function can be dangerous for users | SafEth.sol | 1 |
| N-03      | `SafEth`: Use `address(this).balance` instead of calculating difference | SafEth.sol | 2 |
| N-04      | `Reth.ethPerDerivative` calculation can be simplified | Reth.sol | 1 |

## [L-01] Use fixed compiler version
All in scope contracts use `^0.8.13` as compiler version.
They should use a fixed version, i.e. `0.8.13`, to make sure the contracts are always compiled with
the intended version.

## [L-02] Wrong index emitted in `DerivativeAdded` event
The `DerivativeAdded` event is defined as this:
[Link](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L29-L33)
```solidity
event DerivativeAdded(
    address indexed contractAddress,
    uint weight,
    uint index
);
```

So the third parameter is the index of the new derivative in the `derivatives` mapping.

The `addDerivative` function that emits this event does it like this:
[Link](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L182-L195)
```solidity
function addDerivative(
    address _c

function addDerivative(
    address _contractAddress,
    uint256 _weight
) external onlyOwner {
    derivatives[derivativeCount] = IDerivative(_contractAddress);
    weights[derivativeCount] = _weight;
    // @audit increment happens
    derivativeCount++;

    uint256 localTotalWeight = 0;
    for (uint256 i = 0; i < derivativeCount; i++)
        localTotalWeight += weights[i];
    totalWeight = localTotalWeight;
    // @audit should be derivativeCount - 1
    emit DerivativeAdded(_contractAddress, _weight, derivativeCount);
}

// Code block 1 (solidity):
event DerivativeAdded(
    address indexed contractAddress,
    uint weight,
    uint index
);


NentoR G

File: contracts/bridge/DestinationBridge.sol, line 159.

Unnecessary "if" check for the length of the "approvers" array.
```
 function _approve(bytes32 txnHash) internal {
    // Check that the approver has not already approved
    TxnThreshold storage t = txnToThresholdSet[txnHash];
    if (t.approvers.length > 0) {
      for (uint256 i = 0; i < t.approvers.length; ++i) {
        if (t.approvers[i] == msg.sender) {
          revert AlreadyApproved();
        }
      }
    }
    t.approvers.push(msg.sender);
  }
```

Some gas could be saved by removing the "if" check, like so:
```
 function _approve(bytes32 txnHash) internal {
    // Check that the approver has not already approved
    TxnThreshold storage t = txnToThresholdSet[txnHash];
    for (uint256 i = 0; i < t.approvers.length; ++i) {
      if (t.approvers[i] == msg.sender) {
        revert AlreadyApproved();
      }
    }
    t.approvers.push(msg.sender);
  }
```

// Code block 1 (unknown):
function _approve(bytes32 txnHash) internal {
    // Check that the approver has not already approved
    TxnThreshold storage t = txnToThresholdSet[txnHash];
    if (t.approvers.length > 0) {
      for (uint256 i = 0; i < t.approvers.length; ++i) {
        if (t.approvers[i] == msg.sender) {
          revert AlreadyApproved();
        }
      }
    }
    t.approvers.push(msg.sender);
  }

// Code block 2 (unknown):
function _approve(bytes32 txnHash) internal {
    // Check that the approver has not already approved
    TxnThreshold storage t = txnToThresholdSet[txnHash];
    for (uint256 i = 0; i < t.approvers.length; ++i) {
      if (t.approvers[i] == msg.sender) {
        revert AlreadyApproved();
      }
    }
    t.approvers.push(msg.sender);
  }


Bnke0x0 Q

### [L01] Missing checks for `address(0x0)` when assigning values to `address` state variables

#### Findings:
```
2023-04-caviar/src/EthRouter.sol::91 => royaltyRegistry = _royaltyRegistry;
2023-04-caviar/src/Factory.sol::130 => privatePoolMetadata = _privatePoolMetadata;
2023-04-caviar/src/Factory.sol::136 => privatePoolImplementation = _privatePoolImplementation;
2023-04-caviar/src/Factory.sol::142 => protocolFeeRate = _protocolFeeRate;
2023-04-caviar/src/PrivatePool.sol::145 => royaltyRegistry = _royaltyRegistry;
2023-04-caviar/src/PrivatePool.sol::146 => stolenNftOracle = _stolenNftOracle;
2023-04-caviar/src/PrivatePool.sol::175 => baseToken = _baseToken;
2023-04-caviar/src/PrivatePool.sol::176 => nft = _nft;
2023-04-caviar/src/PrivatePool.sol::177 => virtualBaseTokenReserves = _virtualBaseTokenReserves;
2023-04-caviar/src/PrivatePool.sol::178 => virtualNftReserves = _virtualNftReserves;
2023-04-caviar/src/PrivatePool.sol::179 => changeFee = _changeFee;
2023-04-caviar/src/PrivatePool.sol::180 => feeRate = _feeRate;
2023-04-caviar/src/PrivatePool.sol::181 => merkleRoot = _merkleRoot;
2023-04-caviar/src/PrivatePool.sol::182 => useStolenNftOracle = _useStolenNftOracle;
2023-04-caviar/src/PrivatePool.sol::183 => payRoyalties = _payRoyalties;
```

### [L02] Unused `receive()` function will lock Ether in contract

#### Impact
If the intention is for the Ether to be used, the function should call another function, otherwise, it should revert
#### Findings:
```
2023-04-caviar/src/EthRouter.sol::88 => receive() external payable {}
2023-04-caviar/src/Factory.sol::55 => receive() external payable {}
2023-04-caviar/src/PrivatePool.sol::134 => receive() external payable {}
```

### [L03] Unspecific Compiler Version Pragma

#### Impact
For most source-units the compiler version pragma is very unspecific `^0.8.19`. While this often makes sense for libraries to allow them to be included with multiple different versions of an application, it may be a security risk for t

// Code block 1 (unknown):
2023-04-caviar/src/EthRouter.sol::91 => royaltyRegistry = _royaltyRegistry;
2023-04-caviar/src/Factory.sol::130 => privatePoolMetadata = _privatePoolMetadata;
2023-04-caviar/src/Factory.sol::136 => privatePoolImplementation = _privatePoolImplementation;
2023-04-caviar/src/Factory.sol::142 => protocolFeeRate = _protocolFeeRate;
2023-04-caviar/src/PrivatePool.sol::145 => royaltyRegistry = _royaltyRegistry;
2023-04-caviar/src/PrivatePool.sol::146 => stolenNftOracle = _stolenNftOracle;
2023-04-caviar/src/PrivatePool.sol::175 => baseToken = _baseToken;
2023-04-caviar/src/PrivatePool.sol::176 => nft = _nft;
2023-04-caviar/src/PrivatePool.sol::177 => virtualBaseTokenReserves = _virtualBaseTokenReserves;
2023-04-caviar/src/PrivatePool.sol::178 => virtualNftReserves = _virtualNftReserves;
2023-04-caviar/src/PrivatePool.sol::179 => changeFee = _changeFee;
2023-04-caviar/src/PrivatePool.sol::180 => feeRate = _feeRate;
2023-04-caviar/src/PrivatePool.sol::181 => merkleRoot = _merkleRoot;
2023-04-caviar/src/PrivatePool.sol::182 => useStolenNftOracle = _useStolenNftOracle;
2023-04-caviar/src/PrivatePool.sol::183 => payRoyalties = _payRoyalties;

// Code block 2 (unknown):
2023-04-caviar/src/EthRouter.sol::88 => receive() external payable {}
2023-04-caviar/src/Factory.sol::55 => receive() external payable {}
2023-04-caviar/src/PrivatePool.sol::134 => receive() external payable {}


0xSmartContract Analysis

# 🛠 Analysis - Salty.IO Audit
### Summary
| List |Head |Details|
|:--|:----------------|:------|
|a) |The approach I followed when reviewing the code | Stages in my code review and analysis |
|b) |Analysis of the code base | What is unique? How are the existing patterns used? "Solidity-metrics" was used  |
|c) |Test analysis | Test scope of the project and quality of tests |
|d) |Security Approach of the Project | Audit approach of the Project |
|e) |Other Audit Reports and Automated Findings | What are the previous Audit reports and their analysis |
|f) |Packages and Dependencies Analysis | Details about the project Packages |
|g) |Other recommendations | What is unique? How are the existing patterns used? |
|h) |Gas Optimizations | Gas usage approach of the project and alternative solutions |

## a) The approach I followed when reviewing the code

First, by examining the scope of the code, I determined my code review and analysis strategy.
https://github.com/code-423n4/2024-01-salty

Accordingly, I analyzed and audited the subject in the following steps;

| Number |Stage |Details|Information|
|:--|:----------------|:------|:------|
|1|Compile and Run Test|[Installation](https://github.com/code-423n4/2024-01-salty?tab=readme-ov-file#build--test-instructions)|Test and installation structure is simple, cleanly designed|
|2|Architecture Review| [SaltyIO](https://tech.salty.io/) |Provides a basic architectural teaching for General Architecture|
|3|Graphical Analysis  |Graphical Analysis with [Solidity-metrics](https://github.com/ConsenSys/solidity-metrics)|A visual view has been made to dominate the general structure of the codes of the project.|
|4|Slither Analysis  | [Slither Report](https://github.com/crytic/slither)| The project does not currently have a slither result, a slither control was created from initial|
|5|Test Suits|[Tests](https://github.com/code-423n4/2024-01-salty?tab=readme-ov-file#build--test-instructions)|In this section, the scope and content of

## h) Gas Optimization
The project is generally efficient in terms of gas optimizations, many generally accepted gas optimizations have been implemented, gas optimizations with minor effects are already mentioned in automatic finding, but gas optimizations will not be a priority considering code readability and code base size

When the project is analyzed in terms of Gas Optimization, there is a very important gas optimization; "Using Mapping instead of Openzeppelin's EnumerableSet library provides high gas optimization"


jnrlouis Q

## Low Risk Issues

### [L-01] Wrong Parameter emitted in the `wrap` function

In the `wrap` function from `rUSDY.sol`, the `_USDYAmount` is a parameter for the function, and the `_USDYAmount` parameter is converted to shares by multiplying the amount by the `BPS_DENOMINATOR`. The issue is from the parameters of the event emitted:

```javascript
File: usdy/rUSDY.sol

438    emit Transfer(address(0), msg.sender, getRUSDYByShares(_USDYAmount));
439    emit TransferShares(address(0), msg.sender, _USDYAmount);
```

The `Transfer` event uses `getRUSDYByShares(_USDYAmount)`. The function `getRUSDYByShares` takes in the amount in shares and converts it to the equivalent balance of `rUSDY` tokens.

The issue is that `_USDYAmount` is NOT the shares amount and would produce an inaccurate equivalent balance, instead, it should be `getRUSDYByShares(_USDYAmount * BPS_DENOMINATOR)`. Similarly, the `TransferShares` event emits `_USDYAmount` as the shares, which also should be `emit TransferShares(address(0), msg.sender, _USDYAmount * BPS_DENOMINATOR)`.

```javascript
File: usdy/rUSDY.sol

436    _mintShares(msg.sender, _USDYAmount * BPS_DENOMINATOR);
437    usdy.transferFrom(msg.sender, address(this), _USDYAmount);
```
The `_mintShares` and `transferFrom` uses the correct parameters.

If the results of the events emitted are used, this can lead to unintended behaviors.

// Code block 1 (javascript):
File: usdy/rUSDY.sol

438    emit Transfer(address(0), msg.sender, getRUSDYByShares(_USDYAmount));
439    emit TransferShares(address(0), msg.sender, _USDYAmount);

// Code block 2 (javascript):
File: usdy/rUSDY.sol

436    _mintShares(msg.sender, _USDYAmount * BPS_DENOMINATOR);
437    usdy.transferFrom(msg.sender, address(this), _USDYAmount);


fr33rh Q

## The function `burn` in `src/interfaces/IRSETH.sol` not implement
In `src/interfaces/IRSETH.sol`
```
function burn(address account, uint256 amount) external;
```
while in `src/RSETH.sol`,there is only a similar function named `burnFrom`
```
    function burnFrom(address account, uint256 amount) external onlyRole(BURNER_ROLE) whenNotPaused {
        _burn(account, amount);
    }
```

// Code block 1 (unknown):
function burn(address account, uint256 amount) external;

// Code block 2 (unknown):
function burnFrom(address account, uint256 amount) external onlyRole(BURNER_ROLE) whenNotPaused {
        _burn(account, amount);
    }


dharma09 G

## Summary

The findings from the bot should be reviewed as they contain some solid issues The following findings were missed by the bot(in some cases,bot has identified some instances but missed others, I try to explain whenever this happens)

Gas estimates are done using the opcodes involved since most of this functions were not covered by tests

## Gas Optimizations

| |Issue|Instances||
|-|:-|:-:|:-:|
|[GAS-01](#GAS-01)|Use uint256(1)/uint256(2) instead for true and false boolean states|4|
|[GAS-02](#GAS-02)|Structs can be packed into fewer storage slots|1|
|[GAS-03](#GAS-03)|State variables only set in the constructor should be declared immutable|3|
|[GAS-04](#GAS-04)|Cache state variables with stack variables|2|
|[GAS-05](#GAS-05)|Use assembly for back to back external call|3|
|[GAS-06](#GAS-06)|mark constant for enum variable|1|
|[GAS-07](#GAS-07)|Do not emit block.timestamp in event|1|
|[GAS-08](#GAS-08)|if function check Use only once then  its better to use inline |2|
|[GAS-09](#GAS-09)|Move the declaration of user outside the loop to reduce SLOAD (storage load) operations, as storage reads can be expensive in terms of gas.|1|
|[GAS-10](#GAS-10)|Consider activating via-ir for deploying||
| [GAS-11](#GAS-11) | Reduce gas usage by moving to Solidity 0.8.19 or later | 39 |
| [GAS-12](#GAS-12) | `abi.encode` is more efficient than `abi.encodePacked` | 3 |
| [GAS-13](#GAS-3) | Use assembly to check for `address(0)` | 29 |
| [GAS-14](#GAS-4) | With assembly, .call (bool success) transfer can be done gas-optimized | 5 |
| [GAS-15](#GAS-5) | Use assembly to emit events | 88 |
| [GAS-16](#GAS-6) | `array[index] += amount` is cheaper than `array[index] = array[index] + amount` (or related variants) | 2 |
| [GAS-17](#GAS-7) | Redundant event fields can be removed | 3 |
| [GAS-18](#GAS-9) | <array>.length should not be looked up in every loop of a for-loop | 5 |
| [GAS-19](#GAS-10) | State variables should be cached in stack variables rather than re-reading them fro

File: packages/contracts/contracts/CdpManagerStorage.sol

143:     bool public redemptionsPaused;

File: packages/contracts/contracts/Dependencies/AuthNoOwner.sol

14:     bool private _authorityInitialized;


Praise Q

## in function deployWallet() in SmartAccountFactory.sol Lc 53, there are no zero-address checks for this parameters. address _owner, address _entryPoint, address _handler, invalid wallet addresses can be inputed and deployed.
```

 function deployWallet(address _owner, address _entryPoint, address _handler) public returns(address proxy){
        bytes memory deploymentData = abi.encodePacked(type(Proxy).creationCode, uint(uint160(_defaultImpl)));
        // solhint-disable-next-line no-inline-assembly
        assembly {
            proxy := create(0x0, add(0x20, deploymentData), mload(deploymentData))
        }
        BaseSmartAccount(proxy).init(_owner, _entryPoint, _handler);
        isAccountExist[proxy] = true;
    }
```
## Also,
iIn BasePayMaster.sol, Lc 67 the public function withdrawTo() has no Zero-address check. funds can be withdrawn to an invalid address.

```
    function withdrawTo(address payable withdrawAddress, uint256 amount) public virtual onlyOwner {
        entryPoint.withdrawTo(withdrawAddress, amount);
    }
```

## In Lc 9, The external function withdrawStake() has no zero- Address check also.

```
    function withdrawStake(address payable withdrawAddress) external onlyOwner {
        entryPoint.withdrawStake(withdrawAddress);
}
```
## In Executor.sol, Lc 14,  the internal function execute() has no zero-address check for "address to" parameter

```
    function execute(
        address to,
        uint256 value,
        bytes memory data,
        Enum.Operation operation,
        uint256 txGas
    ) internal returns (bool success) {
        if (operation == Enum.Operation.DelegateCall) {
            // solhint-disable-next-line no-inline-assembly
            assembly {
                success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0)
            }
        } else {
            // solhint-disable-next-line no-inline-assembly
            assembly {
                success := call(txGas, to, value, add(data, 0x20), mloa

// Code block 1 (unknown):
function deployWallet(address _owner, address _entryPoint, address _handler) public returns(address proxy){
        bytes memory deploymentData = abi.encodePacked(type(Proxy).creationCode, uint(uint160(_defaultImpl)));
        // solhint-disable-next-line no-inline-assembly
        assembly {
            proxy := create(0x0, add(0x20, deploymentData), mload(deploymentData))
        }
        BaseSmartAccount(proxy).init(_owner, _entryPoint, _handler);
        isAccountExist[proxy] = true;
    }

// Code block 2 (unknown):
function withdrawTo(address payable withdrawAddress, uint256 amount) public virtual onlyOwner {
        entryPoint.withdrawTo(withdrawAddress, amount);
    }

// Code block 3 (unknown):
function withdrawStake(address payable withdrawAddress) external onlyOwner {
        entryPoint.withdrawStake(withdrawAddress);
}


0x1f8b G

- [Gas](#gas)
    - [**1. Optimize sanction List**](#1-optimize-sanction-list)
    - [**2. Remove return**](#2-remove-return)
    - [**3. Use a recent solidity version**](#3-use-a-recent-solidity-version)
    - [**4. Use require instead of assert**](#4-use-require-instead-of-assert)
    - [**5. Avoid compound assignment operator in state variables**](#5-avoid-compound-assignment-operator-in-state-variables)
        - [Total gas saved: **13 * 8 = 104**](#total-gas-saved-13--8--104)
    - [**6. delete optimization**](#6-delete-optimization)
        - [Total gas saved: **5 * 4 = 20**](#total-gas-saved-5--4--20)
    - [**7. Remove natspec complaints**](#7-remove-natspec-complaints)
    - [**8. Optimize CErc20.initialize and CCash.initialize**](#8-optimize-cerc20initialize-and-ccashinitialize)
    - [**9. Remove SafeMath from JumpRateModelV2**](#9-remove-safemath-from-jumpratemodelv2)

# Gas

## **1. Optimize sanction List**

If the `ISanctionsList` interface had an `isSanctionedAny` method, you could check multiple addresses at once without jumping into the contract multiple times.

```js
    require(!sanctionsList.isSanctioned(spender), "Spender is sanctioned");
    require(!sanctionsList.isSanctioned(src), "Source is sanctioned");
    require(!sanctionsList.isSanctioned(dst), "Destination is sanctioned");
```

**Affected source code:**

- [CTokenModified.sol:97-99](https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/tokens/cToken/CTokenModified.sol#L97-L99)

## **2. Remove return**

It always returns `NO_ERROR`, it is better not to return or check the value.

**Affected source code:**

- [CTokenModified.sol:143](https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/tokens/cToken/CTokenModified.sol#L143)
- [CTokenModified.sol:401](https://github.com/code-423n4/2023-01-ondo/blob/f3426e5b6b4561e09460b2e6471eb694efdd6c70/contracts/lending/tokens/cToken/CTokenModifie

pragma solidity 0.8.15;

contract TesterA {
uint256 private _a;
function testShort() public {
_a += 1;
}
}

contract TesterB {
Uint256 private _a;
function testLong() public {
_a = _a + 1;
}
}

// Code block 1 (js):
require(!sanctionsList.isSanctioned(spender), "Spender is sanctioned");
    require(!sanctionsList.isSanctioned(src), "Source is sanctioned");
    require(!sanctionsList.isSanctioned(dst), "Destination is sanctioned");


Phantasmagoria Q

# Low Risk Issues
|       | Issue | Instances     |
| :---              |    :----             |         :----:  |
| 1     | Deprecated safeApprove() function     | 1      |
| 2     |   _safeMint() should be used rather than _mint() wherever possible   | 2    |

Total: 3 instances

# Non-Critical Issues
|       | Issue | Instances     |
| :---              |    :----             |         :----:  |
| 1     | Imports can be grouped together     | 3      |
| 2     |  Constant redefined elsewhere  |  2   |
| 3     | Named imports can be used    | 3      |
| 4     |   Remove console.log import   | 5  |
| 5     | Interchangeable usage of uint and uint256   | 3      |
| 6     |   Add a limit for the maximum number of characters per line   | 1    |
| 7     | Can be rewritten in many lines    | 2      |
| 8     |  NatSpec comments should be increased in contracts  | 2    |
| 9     | Commented code    | 1      |
| 10     |  Unused events  | 2    |
| 11    | Proper use of _ as a function name prefix   | 1      |
| 12     |   Use named returns for local variables where it is possible   | 22    |
| 13     | No need to use return    | 4      |
| 14     |   Code is not properly formatted  | 5    |
| 15     | Use _ with big numbers     | 1      |

Total: 57 instances

### [L-01] Deprecated safeApprove() function
Deprecated in favor of safeIncreaseAllowance() and safeDecreaseAllowance(). If only setting the initial allowance to the value that means infinite, safeIncreaseAllowance() can be used instead
```
Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol

74: IERC20Upgradeable(want).safeApprove(vault, type(uint256).max);
```
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol#L74

### [L-02] _safeMint() should be used rather than _mint() wherever possible
_mint() is [discouraged](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/d4d8d2ed9798cc3383912a23b5e8d5cb602f7d4b/contracts/token/ERC721/ERC721.sol#L271)

Ethos-Vault/contracts/ReaperVaultV2.sol

336: _mint(_receiver, shares);

// Code block 1 (unknown):
Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol

74: IERC20Upgradeable(want).safeApprove(vault, type(uint256).max);


petrichor G

|  No    | Issue   | Instance |
|------|---------|----------|
|[G-01]|Can Make The Variable Outside The Loop To Save Gas |12|
|[G-02]|Expressions for constant values such as a call to keccak256(), should use immutable rather than constant|5|
|[G-03]|internal functions not called by the contract should be removed to save deployment gas|3|
|[G-04]|Use nested if statements instead of &&|7|
|[G-05]|Make 3 event parameters indexed when possible|31|
|[G-06]|Do not calculate constants|8|
|[G-07]|Use != 0 instead of > 0 for unsigned integer comparison|8|
|[G-08]|Use hardcode address instead address(this)|8|
|[G-09]|Using XOR (^) and OR (pipe line) bitwise equivalents|7|
|[G-10]|Shuold use arguments instead of state varible |16|
|[G-11]|keccak256() should only need to be called on a specific string literal once|4|
|[G-12]|Not using the named return variables when a function returns, wastes deployment gas|6|
|[G-13]|public functions to external|9|
|[G-14]|Amounts should be checked for 0 before calling a transfer|7|
|[G-15]|Duplicated require()/if() checks should be refactored to a modifier or function|5|
|[G-16]|Use assembly to write address storage values|10|

## [G-01] Can Make The Variable Outside The Loop To Save Gas

creating a variable outside a loop can help save gas in Ethereum smart contracts if the variable is used multiple times inside the loop. This is because creating a new variable inside the loop for each iteration would incur additional gas costs due to the cost of initializing a new variable every time.

```solidity
177   uint256 _crvReward = _rewardsContract.earned(address(this));

187   uint256 _extraRewards = _rewardsContract.extraRewardsLength();

209   (uint256 _takenCVX, uint256 _takenCRV, uint256 _claimableAmph) =
          _amphClaimer.claimable(address(this), this.id(), _totalCvxReward, _totalCrvReward);

257   uint256 _earnedReward = _virtualReward.earned(address(this));
```
https://github.com/code-423n4/2023-07-amphora/blob/41cf810b02

874   address _tokenAddress = enabledTokens[_i];

876   uint256 _balance = _vault.balances(_tokenAddress);

879   uint192 _rawPrice = _safeu192(_collateral.oracle.currentValue());

882   uint192 _tokenValue = _safeu192(

902    address _tokenAddress = enabledTokens[_i];

904    uint256 _balance = _vault.balances(_tokenAddress);

907    uint192 _rawPrice = _safeu192(_collateral.oracle.peekValue());

910    uint192 _tokenValue = _safeu192(

996    uint256[] memory _tokenBalances = new uint256[](enabledTokens.length);

// Code block 1 (solidity):
177   uint256 _crvReward = _rewardsContract.earned(address(this));

187   uint256 _extraRewards = _rewardsContract.extraRewardsLength();

209   (uint256 _takenCVX, uint256 _takenCRV, uint256 _claimableAmph) =
          _amphClaimer.claimable(address(this), this.id(), _totalCvxReward, _totalCrvReward);

257   uint256 _earnedReward = _virtualReward.earned(address(this));


0xhex G

# Gas Optimization

# Summary

| Number | Gas Optimization Details                                                                 | Context |
| :----: | :--------------------------------------------------------------------------------------- | :-----: |
| [G-01] | Use solidity version 0.8.20 to gain some gas boost                                       |   10    |
| [G-02] | use solmate for erc721 instead of openzeppelin                                           |    1    |
| [G-03] | Zero value check posiible Gas Save                                                       |    3    |
| [G-04] | Using > 0 costs more gas than != 0 when used on a uint in a require() statement          |    7    |
| [G-05] | Do not calculate constants                                                               |    2    |
| [G-06] | Pre-increment and pre-decrement are cheaper than +1,-1                                   |    4    |
| [G-07] | Use hardcode address instead `address(this)`                                             |   36    |
| [G-08] | Using storage instead of memory for structs/arrays saves gas                             |    3    |
| [G-09] | USING CALLDATA INSTEAD OF MEMORY FOR READ-ONLY ARGUMENTS IN EXTERNAL FUNCTIONS SAVES GAS |    5    |
| [G-10] | Shorten the array rather than copying to a new one                                       |    8    |
| [G-11] | Duplicated if() checks should be refactored to a modifier or function                    |    3    |
| [G-12] | Empty blocks should be removed or emit something to save some amount of Gas              |    1    |
| [G-13] | CAN MAKE THE VARIABLE OUTSIDE THE LOOP                                                   |    3    |
| [G-14] | Use assembly for math (add, sub, mul, div)                                               |   10    |
| [G-15] | Ternary operation is cheaper than if-else statement                                      |    3    |
| [G-16] | Avoid emitting storage values


DijkstraDev G

# Gas Findings

## GAS-01

Caching `lastSetMintExchangeRate`  (6 readings are removed)

```markdown
contracts/cash/CashManager.sol | setMintExchangeRate(…)
```

Before:

```solidity
uint256 rateDifference;
if (exchangeRate > lastSetMintExchangeRate) {
  rateDifference = exchangeRate - lastSetMintExchangeRate;
} else if (exchangeRate < lastSetMintExchangeRate) {
  rateDifference = lastSetMintExchangeRate - exchangeRate;
}

uint256 maxDifferenceThisEpoch = (lastSetMintExchangeRate *
  exchangeRateDeltaLimit) / BPS_DENOMINATOR;

if (rateDifference > maxDifferenceThisEpoch) {
  epochToExchangeRate[epochToSet] = exchangeRate;
  _pause();
  emit MintExchangeRateCheckFailed(
    epochToSet,
    lastSetMintExchangeRate,
    exchangeRate
  );
} else {
  uint256 oldExchangeRate = lastSetMintExchangeRate;
  epochToExchangeRate[epochToSet] = exchangeRate;
  lastSetMintExchangeRate = exchangeRate;
  emit MintExchangeRateSet(epochToSet, oldExchangeRate, exchangeRate);
}
```

After:

```solidity
uint256 rateDifference;
uint256 lastSetMintExchangeRate_ = lastSetMintExchangeRate;

if (exchangeRate > lastSetMintExchangeRate_) {
    rateDifference = exchangeRate - lastSetMintExchangeRate_;
} else if (exchangeRate < lastSetMintExchangeRate_) {
    rateDifference = lastSetMintExchangeRate_ - exchangeRate;
}

uint256 maxDifferenceThisEpoch = (lastSetMintExchangeRate_ *
    exchangeRateDeltaLimit) / BPS_DENOMINATOR;

if (rateDifference > maxDifferenceThisEpoch) {
    epochToExchangeRate[epochToSet] = exchangeRate;
    _pause();
    emit MintExchangeRateCheckFailed(
        epochToSet,
        lastSetMintExchangeRate_,
        exchangeRate
    );
} else {
    epochToExchangeRate[epochToSet] = exchangeRate;
    lastSetMintExchangeRate = exchangeRate;
    emit MintExchangeRateSet(epochToSet, lastSetMintExchangeRate_, exchangeRate);
}
```

## GAS-02

Do not repeat code. Move out of the IF statement the common code between both branches. (Savings of 5 gas units per call)

```markdown
contracts

// Code block 1 (markdown):
contracts/cash/CashManager.sol | setMintExchangeRate(…)

// Code block 2 (solidity):
uint256 rateDifference;
if (exchangeRate > lastSetMintExchangeRate) {
  rateDifference = exchangeRate - lastSetMintExchangeRate;
} else if (exchangeRate < lastSetMintExchangeRate) {
  rateDifference = lastSetMintExchangeRate - exchangeRate;
}

uint256 maxDifferenceThisEpoch = (lastSetMintExchangeRate *
  exchangeRateDeltaLimit) / BPS_DENOMINATOR;

if (rateDifference > maxDifferenceThisEpoch) {
  epochToExchangeRate[epochToSet] = exchangeRate;
  _pause();
  emit MintExchangeRateCheckFailed(
    epochToSet,
    lastSetMintExchangeRate,
    exchangeRate
  );
} else {
  uint256 oldExchangeRate = lastSetMintExchangeRate;
  epochToExchangeRate[epochToSet] = exchangeRate;
  lastSetMintExchangeRate = exchangeRate;
  emit MintExchangeRateSet(epochToSet, oldExchangeRate, exchangeRate);
}

// Code block 3 (solidity):
uint256 rateDifference;
uint256 lastSetMintExchangeRate_ = lastSetMintExchangeRate;

if (exchangeRate > lastSetMintExchangeRate_) {
    rateDifference = exchangeRate - lastSetMintExchangeRate_;
} else if (exchangeRate < lastSetMintExchangeRate_) {
    rateDifference = lastSetMintExchangeRate_ - exchangeRate;
}

uint256 maxDifferenceThisEpoch = (lastSetMintExchangeRate_ *
    exchangeRateDeltaLimit) / BPS_DENOMINATOR;

if (rateDifference > maxDifferenceThisEpoch) {
    epochToExchangeRate[epochToSet] = exchangeRate;
    _pause();
    emit MintExchangeRateCheckFailed(
        epochToSet,
        lastSetMintExchangeRate_,
        exchangeRate
    );
} else {
    epochToExchangeRate[epochToSet] = exchangeRate;
    lastSetMintExchangeRate = exchangeRate;
    emit MintExchangeRateSet(epochToSet, lastSetMintExchangeRate_, exchangeRate);
}

uint256 rateDifference;
if (exchangeRate > lastSetMintExchangeRate) {
  rateDifference = exchangeRate - lastSetMintExchangeRate;
} else if (exchangeRate < lastSetMintExchangeRate) {
  rateDifference = lastSetMintExchangeRate - exchangeRate;
}

uint256 maxDifferenceThisEpoch = (lastSetMintExchangeRate *
  exchangeRateDeltaLimit) / BPS_DENOMINATOR;

if (rateDifference > maxDifferenceThisEpoch) {
  epochToExchangeRate[epochToSet] = exchangeRate;
  _pause();
  emit MintExchangeRateCheckFailed(
    epochToSet,
    lastSetMintExchangeRate,
    exchangeRate
  );
} else {
  uint256 oldExchangeRate = lastSetMintExchangeRate;
  epochToExchangeRate[epochToSet] = exchangeRate;
  lastSetMintExchangeRate = exchangeRate;
  emit MintExchangeRateSet(epochToSet, oldExchangeRate, exchangeRate);
}

uint256 rateDifference;
uint256 lastSetMintExchangeRate_ = lastSetMintExchangeRate;

if (exchangeRate > lastSetMintExchangeRate_) {
    rateDifference = exchangeRate - lastSetMintExchangeRate_;
} else if (exchangeRate < lastSetMintExchangeRate_) {
    rateDifference = lastSetMintExchangeRate_ - exchangeRate;
}

uint256 maxDifferenceThisEpoch = (lastSetMintExchangeRate_ *
    exchangeRateDeltaLimit) / BPS_DENOMINATOR;

if (rateDifference > maxDifferenceThisEpoch) {
    epochToExchangeRate[epochToSet] = exchangeRate;
    _pause();
    emit MintExchangeRateCheckFailed(
        epochToSet,
        lastSetMintExchangeRate_,
        exchangeRate
    );
} else {
    epochToExchangeRate[epochToSet] = exchangeRate;
    lastSetMintExchangeRate = exchangeRate;
    emit MintExchangeRateSet(epochToSet, lastSetMintExchangeRate_, exchangeRate);
}


debo Q

## [L-01] Lengthy numerals
Utilising big numerals that have a lot of precisions is within the file and this can be bettered by utilising scientific numerals.

**Locations**
```sol
// The number 525600000 is seen on line 60.
// contracts/Dependencies/EbtcMath.sol#L60-L60
if (_minutes > 525600000) {

// The number 525600000 is seen on line 61.
// contracts/Dependencies/EbtcMath.sol#L61-L61
_minutes = 525600000;

// The number 1030000000000000000 is seen on line 19.
// contracts/Dependencies/EbtcBase.sol#L19-L19
    uint256 public constant LICR = 1030000000000000000; // 103%

// The number 1100000000000000000 is seen on line 22.
// contracts/Dependencies/EbtcBase.sol#L22-L22
    uint256 public constant MCR = 1100000000000000000; // 110%

// The number 1250000000000000000 is seen on line 25.
// contracts/Dependencies/EbtcBase.sol#L25-L25
    uint256 public constant CCR = 1250000000000000000; // 125%
```
**Remediation**
Change the number 525600000 to scientific notation of `5256e5`.
Change the number 1030000000000000000 to scientific notation of `103e16`.
Change the number 1100000000000000000 to scientific notation of `11e17`.
Change the number 1250000000000000000 to scientific notation of `125e16`.
## [L-02] Unchecked Transfer
## Impact
If the transferFrom does not go through or responds with 0 then sometimes the transfer does not roll back.
So when calling the transferFrom method one should check it in the code.

## Proof of Concept
**Vulnerable flash loan function**
```sol
// line 261-310
    function flashLoan(
        IERC3156FlashBorrower receiver,
        address token,
        uint256 amount,
        bytes calldata data
    ) external override returns (bool) {
        require(amount > 0, "ActivePool: 0 Amount");
        uint256 fee = flashFee(token, amount); // NOTE: Check for `token` is implicit in the requires above // also checks for paused
        require(amount <= maxFlashLoan(token), "ActivePool: Too much");

        uint256 amountWithFee = amount + fee;

// line 261-310
    function flashLoan(
        IERC3156FlashBorrower receiver,
        address token,
        uint256 amount,
        bytes calldata data
    ) external override returns (bool) {
        require(amount > 0, "ActivePool: 0 Amount");
        uint256 fee = flashFee(token, amount); // NOTE: Check for `token` is implicit in the requires above // also checks for paused
        require(amount <= maxFlashLoan(token), "ActivePool: Too much");

        uint256 amountWithFee = amount + fee;
        uint256 oldRate = collateral.getPooledEthByShares(DECIMAL_PRECISION);

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

        // Callback
        require(
            receiver.onFlashLoan(msg.sender, token, amount, fee, data) == FLASH_SUCCESS_VALUE,
            "ActivePool: IERC3156: Callback failed"
        );

        // Transfer of (principal + Fee) from flashloan receiver
        collateral.transferFrom(address(receiver), address(this), amountWithFee);

        // Send earned fee to designated recipient
        collateral.transfer(feeRecipientAddress, fee);

        // Check new balance
        // NOTE: Invariant Check, technically breaks CEI but I think we must use it
        // NOTE: This means any balance > systemCollShares is stuck, this is also present in LUSD as is

        // NOTE: This check effectively prevents running 2 FL at the same time
        //  You technically could, but you'd be having to repay any amount below systemCollShares to get Fl2 to not revert
        require(
            collateral.balanceOf(address(this)) >= collateral.getPooledEthByShares(systemCollShares),
            "ActivePool: Must repay Balance"
        );
        require(
            collateral.sharesOf(address(this)) >= systemCollShares,
            "ActivePool: Must repay Share"
        );
        require(
            collateral.getPooledEthByShares(DECIMAL_PRECISION) == oldRate,
            "ActivePool: Should keep same collateral share rate"
        );

// Code block 1 (sol):
// The number 525600000 is seen on line 60.
// contracts/Dependencies/EbtcMath.sol#L60-L60
if (_minutes > 525600000) {

// The number 525600000 is seen on line 61.
// contracts/Dependencies/EbtcMath.sol#L61-L61
_minutes = 525600000;

// The number 1030000000000000000 is seen on line 19.
// contracts/Dependencies/EbtcBase.sol#L19-L19
    uint256 public constant LICR = 1030000000000000000; // 103%

// The number 1100000000000000000 is seen on line 22.
// contracts/Dependencies/EbtcBase.sol#L22-L22
    uint256 public constant MCR = 1100000000000000000; // 110%

// The number 1250000000000000000 is seen on line 25.
// contracts/Dependencies/EbtcBase.sol#L25-L25
    uint256 public constant CCR = 1250000000000000000; // 125%


0xAnah G

# SALTY GAS OPTIMIZATIONS

## INTRODUCTION
Highlighted below are optimizations exclusively targeting state-mutating functions and view/pure functions invoked by state-mutating functions. In the discussion that follows, only runtime gas is emphasized, given its inevitable dominance over deployment gas costs throughout the protocol's lifetime.

Please be aware that some code snippets may be shortened to conserve space, and certain code snippets may include @audit tags in comments to facilitate issue explanations."

## [G-01] Refactor `Staking.unstakesForUser()` to avoid coping storage array to memory
- https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/Staking.sol#L174

In the `Staking.unstakesForUser()` function as shown below a uint256 array is copied from storage to memory but this array is not used in the function only the length of the array is required in the function so rather than coping the uint256 array from storage to memory just to get the length we can access and cache the length of the uint256 array directly from storage. The diff below shows how the code should be refactored:

```solidity
file: src/staking/Staking.sols

171:	function unstakesForUser( address user ) external view returns (Unstake[] memory)
172:		{
173:		// Check to see how many unstakes the user has
174:		uint256[] memory unstakeIDs = _userUnstakeIDs[user];
175:		if ( unstakeIDs.length == 0 )
176:			return new Unstake[](0);
177:
178:		// Return them all
179:		return unstakesForUser( user, 0, unstakeIDs.length - 1 );
180:		}
```

```diff
diff --git a/src/staking/Staking.sol b/src/staking/Staking.sol
index 22e5970..aa6632d 100644
--- a/src/staking/Staking.sol
+++ b/src/staking/Staking.sol
@@ -171,12 +171,12 @@ contract Staking is IStaking, StakingRewards
        function unstakesForUser( address user ) external view returns (Unstake[] memory)
                {
                // Check to see how many unstakes the user has
-               uint256[] memory unstakeIDs = _u

## [G-02] Pre-calculate equations or computations which contain only constant values in constructor
For equations or computations that only involve constants or immutable values they could be computed in the contract's constructor and saved to an immutable variable. Using the immutable variable would be cheaper than re-computing the value every time the function is called.

### 1 Instance
1. #### Compute `keccak256(abi.encodePacked(""))` in the constructor and save value to an immutable variable
- https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/Proposals.sol#

Since `abi.encodePacked("")` is a constant value, the value of `keccak256(abi.encodePacked(""))` can be calculated in the constructor. Calling `keccak256(abi.encodePacked(""))` on a known, constant value is a waste of gas rather the calcualtion should be done in the constructor then saved to an immutable variable so that everytime the `proposeWebsiteUpdate()` function is we would not have to re-compute the value of `keccak256(abi.encodePacked(""))` rather it would be replaced with cheaper stack read. The code could be refactored as shown in the diff below:

// Code block 1 (solidity):
file: src/staking/Staking.sols

171:	function unstakesForUser( address user ) external view returns (Unstake[] memory)
172:		{
173:		// Check to see how many unstakes the user has
174:		uint256[] memory unstakeIDs = _userUnstakeIDs[user];
175:		if ( unstakeIDs.length == 0 )
176:			return new Unstake[](0);
177:
178:		// Return them all
179:		return unstakesForUser( user, 0, unstakeIDs.length - 1 );
180:		}


TheSavageTeddy G

## Gas Optimizations List

| Number | Optimization Details | Instances |
|:------:|:--------------------|:---------:|
| [G-01] | Expressions for constant values such as calling `keccak256()` should use `immutable` rather than `constant` | 7 |
| [G-02] | `keccak256()` should only need to be called on a specific string literal once | 3 |
| [G-03] | Check `Require()` / `Revert()` statements that use less gas first | 1 |
| [G-04] | Hardcode address instead of using `address(this)` | 40 |
| [G-05] | Structs can be packed into fewer storage slots | 2 |
| [G-06] | Change `public` function visibility to `external` when appropriate | 32 |
| [G-07] | Avoid multiple check combinations by using nested `if` statements | 4 |
| [G-08] | Using `uint`/`int`s smaller than 32 bytes incurs overhead | 7 |
| [G-09] | Use assembly to check for `address(0)` | 5 |
| [G-10] | Use constants instead of `type(uintx).max` | 1 |

### [G-01] 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 7 instances of this issue:*
```solidity
File: LybraConfigurator.sol
76:     bytes32 public constant DAO = keccak256("DAO");
77:     bytes32 public constant TIMELOCK = keccak256("TIMELOCK");
78:     bytes32 public constant ADMIN = keccak256("ADMIN");
```
https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/configuration/LybraConfigurator.sol#L76-L78

```solidity
File: GovernanceTimelock.sol
10:     bytes32 public constant DAO = keccak256("DAO");
11:     bytes32 public constant TIMELOCK = keccak256("TIMELOCK");
12:     bytes32 public constant ADMIN = keccak256("ADMIN");
13:     bytes32 public constant GOV = keccak256("GOV");
```
https://github.com/code-423n4/2023-06-lybra/blob/main/contracts/lybra/governance/GovernanceTimelock.sol#L10-L1

// Code block 1 (solidity):
File: LybraConfigurator.sol
76:     bytes32 public constant DAO = keccak256("DAO");
77:     bytes32 public constant TIMELOCK = keccak256("TIMELOCK");
78:     bytes32 public constant ADMIN = keccak256("ADMIN");

// Code block 2 (solidity):
File: GovernanceTimelock.sol
10:     bytes32 public constant DAO = keccak256("DAO");
11:     bytes32 public constant TIMELOCK = keccak256("TIMELOCK");
12:     bytes32 public constant ADMIN = keccak256("ADMIN");
13:     bytes32 public constant GOV = keccak256("GOV");

File: LybraConfigurator.sol
76:     bytes32 public constant DAO = keccak256("DAO");
77:     bytes32 public constant TIMELOCK = keccak256("TIMELOCK");
78:     bytes32 public constant ADMIN = keccak256("ADMIN");

File: GovernanceTimelock.sol
10:     bytes32 public constant DAO = keccak256("DAO");
11:     bytes32 public constant TIMELOCK = keccak256("TIMELOCK");
12:     bytes32 public constant ADMIN = keccak256("ADMIN");
13:     bytes32 public constant GOV = keccak256("GOV");


report

---
sponsor: "Ajna Protocol"
slug: "2023-05-ajna"
date: "2023-06-29"
title: "Ajna Protocol"
findings: "https://github.com/code-423n4/2023-05-ajna-findings/issues"
contest: 234
---

# 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 Ajna Protocol smart contract system written in Solidity. The audit took place between May 3—May 11 2023.

## Wardens

123 Wardens contributed reports to the Ajna Protocol:

  1. [0x73696d616f](https://twitter.com/3xJanx2009)
  2. 0xRobocop
  3. [0xSmartContract](https://twitter.com/0xSmartContract)
  4. [0xStalin](https://twitter.com/Stalin_eth)
  5. [0xTheC0der](https://twitter.com/MarioPoneder)
  6. 0xWaitress
  7. 0xcm
  8. [0xnev](https://twitter.com/0xnevi)
  9. 7siech
  10. [ABAIKUNANBAEV](https://twitter.com/_onlyowner)
  11. [Audinarey](https://twitter.com/Audinarey)
  12. [Audit\_Avengers](https://twitter.com/JP_Courses) ([JP\_Courses](https://twitter.com/JP_Courses), [pxng0lin](https://www.twitter.com/pxng0lin), [zzebra83](https://www.linkedin.com/in/hamid-abubakr-732901ab/), Aon\_8, and ravikiranweb3)
  13. [Aymen0909](https://github.com/Aymen1001)
  14. BGSecurity ([anonresercher](https://twitter.com/anonresercher) and [martin](https://github.com/martin-petrov03))
  15. BPZ (Bitcoinfever244, PrasadLak, and zinc42)
  16. BRONZEDISC
  17. Bason
  18. [Bauchibred](https://twitter.com/bauchibred?s=21&t=7sv-1qcnwtkdTA81Iog0yQ )
  19. [Blckhv](https://twitter.com/Aydoanbalakchie)
  20. Brenzee
  21. [DadeKuma](https://twitter.com/DadeKuma)
  22. Dug
  23. Eurovickk
  24. Evo
  25. GG\_Security ([georgits](https://twitter.com/georgits_

    function moveLiquidity(
        MoveLiquidityParams calldata params_
    ) external override mayInteract(params_.pool, params_.tokenId) nonReentrant {
        Position storage fromPosition = positions[params_.tokenId][params_.fromIndex];

        MoveLiquidityLocalVars memory vars;
        vars.depositTime = fromPosition.depositTime;

        // handle the case where owner attempts to move liquidity after they've already done so
        if (vars.depositTime == 0) revert RemovePositionFailed();

        // ensure bucketDeposit accounts for accrued interest
        IPool(params_.pool).updateInterest();

        // retrieve info of bucket from which liquidity is moved
        (
            vars.bucketLP,
            vars.bucketCollateral,
            vars.bankruptcyTime,
            vars.bucketDeposit,
        ) = IPool(params_.pool).bucketInfo(params_.fromIndex);

        // check that bucket hasn't gone bankrupt since memorialization
        if (vars.depositTime <= vars.bankruptcyTime) revert BucketBankrupt();

        // calculate the max amount of quote tokens that can be moved, given the tracked LP
        vars.maxQuote = _lpToQuoteToken(
            vars.bucketLP,
            vars.bucketCollateral,
            vars.bucketDeposit,
            fromPosition.lps,
            vars.bucketDeposit,
            _priceAt(params_.fromIndex)
        );

        EnumerableSet.UintSet storage positionIndex = positionIndexes[params_.tokenId];

        // remove bucket index from which liquidity is moved from tracked positions
>>      if (!positionIndex.remove(params_.fromIndex)) revert RemovePositionFailed();

        // update bucket set at which a position has liquidity
        // slither-disable-next-line unused-return
        positionIndex.add(params_.toIndex);

        // move quote tokens in pool
        (
            vars.lpbAmountFrom,
            vars.lpbAmountTo,
        ) = IPool(params_.pool).moveQuoteToken(
            vars.maxQuote,
            params_.fromI

    function _lpToQuoteToken(
        uint256 bucketLP_,
        uint256 bucketCollateral_,
        uint256 deposit_,
        uint256 lenderLPBalance_,
        uint256 maxQuoteToken_,
        uint256 bucketPrice_
    ) pure returns (uint256 quoteTokenAmount_) {
        uint256 rate = Buckets.getExchangeRate(bucketCollateral_, bucketLP_, deposit_, bucketPrice_);

        quoteTokenAmount_ = Maths.wmul(lenderLPBalance_, rate);

        if (quoteTokenAmount_ > deposit_)       quoteTokenAmount_ = deposit_;
        if (quoteTokenAmount_ > maxQuoteToken_) quoteTokenAmount_ = maxQuoteToken_;
    }


Lavishq Q

## 1. Use a more recent version of solidity if possible
 Version 0.6.11 is too old consider upgrading it that is possible to a newer solidity version as it will give `custom Error` benefits from `v0.8.4` and reduce gas usage for the users by a significant margin and has `safe math` check for under and overflow so there will be no need to use SafeMath library and reducing code size along with saving in the gas from `v0.8.0`

All the contracts in `Ethos-Vault` use floating pragma which in not recommended so using a version that is above v0.8.4 and using custom errors is recommended, as well as it is recommended using more recent version of solidity will be a better practice in general.

## 2. No need to inherits self interface and override it:
The following contracts inherit themselves and override themselves that results in increase of bytecode
removing the interface from below contracts and removing override will optimize them to a significant extent
1. `Ethos-Core/contracts/CollateralConfig.sol`
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/CollateralConfig.sol#L8
2. `Ethos-Core/contracts/BorrowerOperations.sol`
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/BorrowerOperations.sol#L5
`removing "override" of the below functions so that they are not taking a significant chunk in bytecode`
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/CollateralConfig.sol#L46
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/CollateralConfig.sol#L102
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/CollateralConfig.sol#L106
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/CollateralConfig.sol#L110
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/CollateralConfig.sol#L116
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/CollateralConfig.sol#L122

https://github.com/code-42

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

eg: 2`https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/CollateralConfig.sol#L91` and 92
require(_MCR <= config.MCR && _CCR <= config.CCR, "Can only walk down the MCR");
and followed by the same pattern in line 94 and 97 can use
require(_MCRs[i] >= MIN_ALLOWED_MCR && _CCRs[i] >= MIN_ALLOWED_CCR, "MCR and CCR below allowed min"); to save gas
### e.g. 2, implementation

CollateralConfig.sol#L8, BorrowerOperations.sol#L5, CollateralConfig.sol#L46, CollateralConfig.sol#L102, CollateralConfig.sol#L106, CollateralConfig.sol#L110, CollateralConfig.sol#L116, CollateralConfig.sol#L122, CollateralConfig.sol#L91