0xVolcano G

# Gas report

## Table of Contents

- [Gas report](#gas-report)
  - [Table of Contents](#table-of-contents)
  - [Avoid zero to none zero storage writes](#avoid-zero-to-none-zero-storage-writes)
  - [We can optimize the function `updateLRTConfig()`](#we-can-optimize-the-function-updatelrtconfig)
  - [Emit local variables instead of state variables(not found by bot)](#emit-local-variables-instead-of-state-variablesnot-found-by-bot)
  - [Don't cache calls/variables  if using them once](#dont-cache-callsvariables--if-using-them-once)
    - [No need to cache `lrtConfig.getContract(LRTConstants.EIGEN_STRATEGY_MANAGER)`](#no-need-to-cache-lrtconfiggetcontractlrtconstantseigen_strategy_manager)
    - [No need to cache `lrtConfig.getContract(LRTConstants.EIGEN_STRATEGY_MANAGER)`](#no-need-to-cache-lrtconfiggetcontractlrtconstantseigen_strategy_manager-1)
    - [No need to cache `lrtConfig.getContract(LRTConstants.LRT_DEPOSIT_POOL)`](#no-need-to-cache-lrtconfiggetcontractlrtconstantslrt_deposit_pool)
    - [No need to cache `lrtConfig.assetStrategy(asset)`](#no-need-to-cache-lrtconfigassetstrategyasset)
    - [No need to cache `nodeDelegatorQueue[ndcIndex]`](#no-need-to-cache-nodedelegatorqueuendcindex)
  - [Caching the length of calldata increases gas cost instead of reducing it](#caching-the-length-of-calldata-increases-gas-cost-instead-of-reducing-it)

## Avoid zero to none zero storage writes

When a storage variable goes from zero to non-zero, the user must pay 22,100 gas total (20,000 gas for a zero to non-zero write and 2,100 for a cold storage access).

This is why the Openzeppelin reentrancy guard registers functions as active or not with 1 and 2 rather than 0 and 1. It only costs 5,000 gas to alter a storage variable from non-zero to non-zero.

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L19-L38
```solidity
File: /src/LRTDepositPool.sol
19:contract LRTDepositPool is ILRTDepositPool, LRTConfigRoleCh

File: /src/LRTDepositPool.sol
19:contract LRTDepositPool is ILRTDepositPool, LRTConfigRoleChecker, PausableUpgradeable, ReentrancyGuardUpgradeable {
20:    uint256 public maxNodeDelegatorCount;

29:    /// @dev Initializes the contract
30:    /// @param lrtConfigAddr LRT config address
31:    function initialize(address lrtConfigAddr) external initializer {
32:        UtilLib.checkNonZeroAddress(lrtConfigAddr);
33:        __Pausable_init();
34:        __ReentrancyGuard_init();
35:        maxNodeDelegatorCount = 10;
36:        lrtConfig = ILRTConfig(lrtConfigAddr);
37:        emit UpdatedLRTConfig(lrtConfigAddr);
38:    }

## We can optimize the function `updateLRTConfig()`

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


SleepingBugs Q

## Single-step process for critical ownership transfer/renounce is risky
### Impact
Given that `BasePaymaster.sol` is derived from `Ownable`, the ownership management of this contract defaults to `Ownable` ’s `transferOwnership()` and `renounceOwnership()` methods which are not overridden here.

Such critical address transfer/renouncing in one-step is very risky because it is irrecoverable from any mistakes

Scenario: If an incorrect address, e.g. for which the private key is not known, is used accidentally then it prevents the use of all the `onlyOwner()` functions forever, which includes the changing of various critical addresses and parameters. This use of incorrect address may not even be immediately apparent given that these functions are probably not used immediately.

When noticed, due to a failing `onlyOwner()` function call, it will force the redeployment of these contracts and require appropriate changes and notifications for switching from the old to new address. This will diminish trust in the protocol and incur a significant reputational damage.

### Code Snippet
https://github.com/code-423n4/2023-01-biconomy/blob/721e2afb493d8bc0bc9488b84eaf2deb14c8b43f/scw-contracts/contracts/smart-contract-wallet/paymasters/BasePaymaster.sol#L16

### Recommended steps
Consider using OZ Ownable2Step library:
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol

Or do it manually. I recommend overriding the inherited methods to null functions and use separate functions for a two-step address change:
1. Approve a new address as a `pendingOwner`
2. A transaction from the `pendingOwner` address claims the pending ownership change.

This mitigates risk because if an incorrect address is used in step (1) then it can be fixed by re-approving the correct address. Only after a correct address is used in step (1) can step (2) happen and complete the address/ownership change.

Also, consider adding a time-delay for such sensitiv

contract SmartAccount is
    Singleton,
    BaseSmartAccount,
    IERC165,
    ModuleManager,
    SignatureDecoder,
    SecuredTokenTransfer,
    ISignatureValidatorConstants,
    FallbackManager,
    Initializable,
    ReentrancyGuardUpgradeable
{


0xm1ck G

## Gas Optimizations

| |Issue|Instances|
|-|:-|:-:|
| [GAS-1](#GAS-1) | Bytes constants are more efficient than string constants | 8 |
| [GAS-2](#GAS-2) | Incrementing with a smaller type than `uint256` incurs overhead | 3 |
| [GAS-3](#GAS-3) | Use `storage` instead of `memory` for structs/arrays | 16 |
| [GAS-4](#GAS-4) | Increments can be `unchecked` in for-loops | 14 |
| [GAS-5](#GAS-5) | Pre-increments and pre-decrements are cheaper than post-increments and post-decrements | 15 |
| [GAS-6](#GAS-6) | Use shift Right/Left instead of division/multiplication if possible | 1 |
| [GAS-7](#GAS-7) | Use assembly to check for `address(0)` | 8 |
| [GAS-8](#GAS-8) | Using bools for storage incurs overhead | 7 |
| [GAS-9](#GAS-9) | Cache array length outside of loop | 7 |
| [GAS-10](#GAS-10) | State variables should be cached in stack variables rather than re-reading them from storage | 10 |
| [GAS-11](#GAS-11) | Use calldata instead of memory for function arguments that do not get mutated | 1 |
| [GAS-12](#GAS-12) | Use Custom Errors | 58 |
| [GAS-13](#GAS-13) | Don't initialize variables with default value | 18 |
| [GAS-14](#GAS-14) | Long revert strings | 34 |
| [GAS-15](#GAS-15) | Using `private` rather than `public` for constants, saves gas | 21 |
| [GAS-16](#GAS-16) | Use != 0 instead of > 0 for unsigned integer comparison | 27 |
| [GAS-17](#GAS-17) | `internal` functions not called by the contract should be removed | 1 |
### [GAS-1] Bytes constants are more efficient than string constants

*Instances (8)*:
```solidity
File: ActivePool.sol

30:     string constant public NAME = "ActivePool";

```

```solidity
File: BorrowerOperations.sol

21:     string constant public NAME = "BorrowerOperations";

```

```solidity
File: LQTY/CommunityIssuance.sol

19:     string constant public NAME = "CommunityIssuance";

```

```solidity
File: LQTY/LQTYStaking.sol

23:     string constant public NAME = "LQTYStaking";

```

```solidity
File: LUSDToken.sol

32:

// Code block 1 (solidity):
File: ActivePool.sol

30:     string constant public NAME = "ActivePool";

// Code block 2 (solidity):
File: BorrowerOperations.sol

21:     string constant public NAME = "BorrowerOperations";

// Code block 3 (solidity):
File: LQTY/CommunityIssuance.sol

19:     string constant public NAME = "CommunityIssuance";

// Code block 4 (solidity):
File: LQTY/LQTYStaking.sol

23:     string constant public NAME = "LQTYStaking";

File: ActivePool.sol

30:     string constant public NAME = "ActivePool";

File: BorrowerOperations.sol

21:     string constant public NAME = "BorrowerOperations";

File: LQTY/CommunityIssuance.sol

19:     string constant public NAME = "CommunityIssuance";

File: LQTY/LQTYStaking.sol

23:     string constant public NAME = "LQTYStaking";


giovannidisiena G

### **Gas Optimisations**
#### **[G-01] Variable Assignment in Event**
5 gas can be saved by assigning `owner` within the event. This also follows checks-effects-interactions due to right-to-left evaluation.

*There is 1 instance of this issue:*

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

    function setOwner(address _newOwner) external mixedAuth {
        require(_newOwner != address(0), "Smart Account:: new Signatory address cannot be zero");
        address oldOwner = owner;
        owner = _newOwner;
        emit EOAChanged(address(this), oldOwner, _newOwner);
    }
```
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L109-L114

#### **[G-02] Use Global Address Code Length**
10 gas can be saved by using the built-in `<address>.code.length > 0`.

*There are 2 instances of this issue:*

```
File: contracts/smart-contract-wallet/libs/LibAddress.sol

    /**
     * @notice Will return true if provided address is a contract
     * @param account Address to verify if contract or not
     * @dev This contract will return false if called within the constructor of
     *      a contract's deployment, as the code is not yet stored on-chain.
     */
    function isContract(address account) internal view returns (bool) {
        uint256 csize;
        // solhint-disable-next-line no-inline-assembly
        assembly { csize := extcodesize(account) }
        return csize != 0;
    }
```
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/libs/LibAddress.sol#LL5C3-L16C4

```
File: contracts/smart-contract-wallet/paymasters/verifying/VerifyingSingletonPaymaster.sol

    import "@openzeppelin/contracts/utils/Address.sol";
    ...
    require(!Address.isContract(paymasterId), "Paymaster Id can not be smart contract address");
```
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/paymasters/

// Code block 1 (unknown):
File: contracts/smart-contract-wallet/SmartAccount.sol

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

// Code block 2 (unknown):
File: contracts/smart-contract-wallet/libs/LibAddress.sol

    /**
     * @notice Will return true if provided address is a contract
     * @param account Address to verify if contract or not
     * @dev This contract will return false if called within the constructor of
     *      a contract's deployment, as the code is not yet stored on-chain.
     */
    function isContract(address account) internal view returns (bool) {
        uint256 csize;
        // solhint-disable-next-line no-inline-assembly
        assembly { csize := extcodesize(account) }
        return csize != 0;
    }

// Code block 3 (unknown):
File: contracts/smart-contract-wallet/paymasters/verifying/VerifyingSingletonPaymaster.sol

    import "@openzeppelin/contracts/utils/Address.sol";
    ...
    require(!Address.isContract(paymasterId), "Paymaster Id can not be smart contract address");


Brenzee Q

## Use `require()` instead of `assert()`
BorrowerOperations.sol
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/BorrowerOperations.sol#L128
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/BorrowerOperations.sol#L197
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/BorrowerOperations.sol#L301
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/BorrowerOperations.sol#L331

LUSDToken.sol
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LUSDToken.sol#L312-L313
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LUSDToken.sol#L321
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LUSDToken.sol#L329
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LUSDToken.sol#L337-L338

RedemptionHelper.sol
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LUSDToken.sol#L337-L338

StabilityPool.sol
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/StabilityPool.sol#L526
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/StabilityPool.sol#L551
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/StabilityPool.sol#L591

TroveManager.sol
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/TroveManager.sol#L417
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/TroveManager.sol#L1224
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/TroveManager.sol#L1279
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/TroveManager.sol#L1342-L1348
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/TroveManager.sol#L1414
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/TroveManager.sol#L1489

### Explanation

The Solidity `assert()` function is meant to assert invaria

### Recommendation
Change `assert()` to `require()`

## Inconsistent `uint` and `uint256` usage
TroveManager.sol
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/TroveManager.sol#L129-L138
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/TroveManager.sol#L146-L158

BorrowerOperations.sol
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/BorrowerOperations.sol#L47-L78

### Explanation
`uint` and `uint256` are the same types. Mixing `uint256` and `uint` is not a good code style.

### Recommendation
Either use `uint256` or `uint` across all of the contracts.

## Call to `keccak256()` should use `immutable` rather than `constant`
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Vault/contracts/ReaperVaultV2.sol#L73-L76

### Recommendation
Change `constant` to `immutable`

## Avoid setting time variables with integer values
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/TroveManager.sol#L48

BorrowerOperations.sol#L128, BorrowerOperations.sol#L197, BorrowerOperations.sol#L301, BorrowerOperations.sol#L331, LUSDToken.sol#L312-L313, LUSDToken.sol#L321, LUSDToken.sol#L329, LUSDToken.sol#L337-L338, StabilityPool.sol#L526, StabilityPool.sol#L551, StabilityPool.sol#L591, TroveManager.sol#L417, TroveManager.sol#L1224, TroveManager.sol#L1279, TroveManager.sol#L1342-L1348, TroveManager.sol#L1414, TroveManager.sol#L1489, TroveManager.sol#L129-L138, TroveManager.sol#L146-L158, BorrowerOperations.sol#L47-L78, ReaperVaultV2.sol#L73-L76, TroveManager.sol#L48

StabilityPool.sol, TroveManager.sol, BorrowerOperations.sol, ReaperVaultV2.sol, LUSDToken.sol


UdarTeam Q

### Issues Template
| Letter | Name | Description |
|:--:|:-------:|:-------:|
| L  | Low risk | Potential risk |
| NC |  Non-critical | Non risky findings |
| R  | Refactor | Changing the code |
| O | Ordinary | Often found issues |

| Total Found Issues | 16 |
|:--:|:--:|

### Non-Critical Issues Template
| Count | Explanation | Instances |
|:--:|:-------|:--:|
| [N-01] | Explicitly mark state variables visibility | 5 |
| [N-02] | Explicitly mark uint type size | 557 |
| [N-03] | Create your own import names instead of using the regular ones | 97 |
| [N-04] | Insufficient coverage | - |

| Total Non-Critical Issues | 4 |
|:--:|:--:|

### Refactor Issues Template
| Count | Explanation | Instances |
|:--:|:-------|:--:|
| [R-01] | Use require instead of assert | 18 |
| [R-02] | Some number values can be refactored with _ | 2 |
| [R-03] | Validate input before storage reads | 1 |
| [R-04] | Remove unused variables | 2 |
| [R-05] | Function state mutability can be restricted to pure | 1 |
| [R-06] | For functions, follow solidity standard naming conventions | 1 |
| [R-07] | Enum values should start with capital letter by convention | 1 |

| Total Refactor Issues | 7 |
|:--:|:--:|

### Ordinary Issues Template
| Count | Explanation | Instances |
|:--:|:-------|:--:|
| [O-01] | Remove TODOs in code | 2 |
| [O-02] | Commented out code | 3 |
| [O-03] | Use a more recent pragma version | 15 |
| [O-04] | Locking pragma | 4 |
| [O-05] | Increase NatSpec comments | 103 |

| Total Ordinary Issues | 5 |
|:--:|:--:|

### [N-01] Explicitly mark state variables visibility

Explicitly marking state variables visibility improves code readability and consistency.

```solidity
Ethos-Core/contracts/BorrowerOperations.sol

28: address stabilityPoolAddress;
30: address gasPoolAddress;
32: ICollSurplusPool collSurplusPool;
```

```solidity
Ethos-Core/contracts/TroveManager.sol

31: address gasPoolAddress;
33: ICollSurplusPool collSurplusPool;
```

### [N-02] Explicitly mark uint type s

// Code block 1 (solidity):
Ethos-Core/contracts/BorrowerOperations.sol

28: address stabilityPoolAddress;
30: address gasPoolAddress;
32: ICollSurplusPool collSurplusPool;

// Code block 2 (solidity):
Ethos-Core/contracts/TroveManager.sol

31: address gasPoolAddress;
33: ICollSurplusPool collSurplusPool;

Ethos-Core/contracts/BorrowerOperations.sol

28: address stabilityPoolAddress;
30: address gasPoolAddress;
32: ICollSurplusPool collSurplusPool;

Ethos-Core/contracts/TroveManager.sol

31: address gasPoolAddress;
33: ICollSurplusPool collSurplusPool;


koxuan Q

### [L‑1] ERC20 tokens that do not implement optional decimals method cannot be used

Underlying token that does not implement optional decimals method cannot be used

 > [EIP-20](https://eips.ethereum.org/EIPS/eip-20#decimals) OPTIONAL - This method can be used to improve usability, but interfaces and other contracts MUST NOT expect these values to be present.

*Instances (7)*:
```solidity
File: /core/solidity/contracts/core/VaultController.sol

341:    uint8 _tokenDecimals = IERC20Metadata(_tokenAddress).decimals();

```
https://github.com/code-423n4/2023-07-amphora/blob/main/core/solidity/contracts/core/VaultController.sol#L341

```solidity
File: /core/solidity/contracts/periphery/oracles/CTokenOracle.sol

25:    uint256 _underlyingDecimals = cETH_ADDRESS == _cToken ? 18 : IERC20Metadata(cToken.underlying()).decimals();

28:    div = 10 ** (18 + _underlyingDecimals - cToken.decimals());

```
https://github.com/code-423n4/2023-07-amphora/blob/main/core/solidity/contracts/periphery/oracles/CTokenOracle.sol#L25

```solidity
File: /core/solidity/contracts/periphery/oracles/UniswapV3OracleRelay.sol

50:    BASE_TOKEN_DECIMALS = IERC20Metadata(BASE_TOKEN).decimals();

51:    QUOTE_TOKEN_DECIMALS = IERC20Metadata(QUOTE_TOKEN).decimals();

```
https://github.com/code-423n4/2023-07-amphora/blob/main/core/solidity/contracts/periphery/oracles/UniswapV3OracleRelay.sol#L50

```solidity
File: /core/solidity/scripts/fakes/MintableToken.sol

20:  function decimals() public view virtual override returns (uint8) {

```
https://github.com/code-423n4/2023-07-amphora/blob/main/core/solidity/scripts/fakes/MintableToken.sol#L20

```solidity
File: /core/solidity/interfaces/periphery/ICToken.sol

9:  function decimals() external view returns (uint8 _decimals);

```
https://github.com/code-423n4/2023-07-amphora/blob/main/core/solidity/interfaces/periphery/ICToken.sol#L9

### [L‑2] Divide before Multiplication

Unnecessary loss of precision caused by divide before multiplica

// Code block 1 (solidity):
File: /core/solidity/contracts/core/VaultController.sol

341:    uint8 _tokenDecimals = IERC20Metadata(_tokenAddress).decimals();

// Code block 2 (solidity):
File: /core/solidity/contracts/periphery/oracles/CTokenOracle.sol

25:    uint256 _underlyingDecimals = cETH_ADDRESS == _cToken ? 18 : IERC20Metadata(cToken.underlying()).decimals();

28:    div = 10 ** (18 + _underlyingDecimals - cToken.decimals());

// Code block 3 (solidity):
File: /core/solidity/contracts/periphery/oracles/UniswapV3OracleRelay.sol

50:    BASE_TOKEN_DECIMALS = IERC20Metadata(BASE_TOKEN).decimals();

51:    QUOTE_TOKEN_DECIMALS = IERC20Metadata(QUOTE_TOKEN).decimals();

// Code block 4 (solidity):
File: /core/solidity/scripts/fakes/MintableToken.sol

20:  function decimals() public view virtual override returns (uint8) {

// Code block 5 (solidity):
File: /core/solidity/interfaces/periphery/ICToken.sol

9:  function decimals() external view returns (uint8 _decimals);

File: /core/solidity/contracts/core/VaultController.sol

341:    uint8 _tokenDecimals = IERC20Metadata(_tokenAddress).decimals();

File: /core/solidity/contracts/periphery/oracles/CTokenOracle.sol

25:    uint256 _underlyingDecimals = cETH_ADDRESS == _cToken ? 18 : IERC20Metadata(cToken.underlying()).decimals();

28:    div = 10 ** (18 + _underlyingDecimals - cToken.decimals());

File: /core/solidity/contracts/periphery/oracles/UniswapV3OracleRelay.sol

50:    BASE_TOKEN_DECIMALS = IERC20Metadata(BASE_TOKEN).decimals();

51:    QUOTE_TOKEN_DECIMALS = IERC20Metadata(QUOTE_TOKEN).decimals();

File: /core/solidity/scripts/fakes/MintableToken.sol

20:  function decimals() public view virtual override returns (uint8) {

File: /core/solidity/interfaces/periphery/ICToken.sol

9:  function decimals() external view returns (uint8 _decimals);

VaultController.sol#L341, CTokenOracle.sol#L25, UniswapV3OracleRelay.sol#L50, MintableToken.sol#L20, ICToken.sol#L9

UniswapV3OracleRelay.sol, ICToken.sol, VaultController.sol, MintableToken.sol, CTokenOracle.sol


Go Langer Q

### No revert messages in TroveManager.sol

In TroveManager there are no revert messages within the require statements.

1530:   function _requireCallerIsBorrowerOperationsOrRedemptionHelper() internal view {
        require(msg.sender == borrowerOperationsAddress || msg.sender == address(redemptionHelper));
    }

Revert messages help us understand why a transaction has failed if called by parties other than,
for example borrowerOperationsAddress or redemptionHelper.

Refactored code:

```
function _requireCallerIsBorrowerOperationsOrRedemptionHelper() internal view {
    require(msg.sender == borrowerOperationsAddress || msg.sender == address(redemptionHelper), "Only borrowerOperationsAddress or redemptionHelper can call this function");
}
```

Also on lines:

1538 function _requireMoreThanOneTroveInSystem
1535 function _requireTroveIsActive
1530 function _requireCallerIsBorrowerOperationsOrRedemptionHelper
1526 function _requireCallerIsRedemptionHelper
1523 function _requireCallerIsBorrowerOperations
1450 function _calcRedemptionFee
754 function batchLiquidateTroves
716 function batchLiquidateTroves
551 function liquidateTroves
250 function setAddresses

### Remove comments from zombie code

In ReaperBaseStrategyv4, there is a comment that the DEFAULT_ADMIN_ROLE will be granted to a multisig requiring 4 signatures.
However, the code only grants 3 multisig roles.

     * The DEFAULT_ADMIN_ROLE (in-built access control role) will be granted to a multisig requiring 4
     * signatures. This role would have upgrading capability, as well as the ability to grant any other
     * roles.

    ```
    function __ReaperBaseStrategy_init(
        address _vault,
        address _want,
        address[] memory _strategists,
        address[] memory _multisigRoles
    ) internal onlyInitializing {
        __UUPSUpgradeable_init();
        __AccessControlEnumerable_init();

        vault = _vault;
        want = _want;
        IERC20Upgradeable(want).safeApprove(vault, t

    function __ReaperBaseStrategy_init(
        address _vault,
        address _want,
        address[] memory _strategists,
        address[] memory _multisigRoles
    ) internal onlyInitializing {
        __UUPSUpgradeable_init();
        __AccessControlEnumerable_init();

        vault = _vault;
        want = _want;
        IERC20Upgradeable(want).safeApprove(vault, type(uint256).max);

        uint256 numStrategists = _strategists.length;
        for (uint256 i = 0; i < numStrategists; i = i.uncheckedInc()) {
            _grantRole(STRATEGIST, _strategists[i]);
        }

        require(_multisigRoles.length == 3, "Invalid number of multisig roles");
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(DEFAULT_ADMIN_ROLE, _multisigRoles[0]);
        _grantRole(ADMIN, _multisigRoles[1]);
        _grantRole(GUARDIAN, _multisigRoles[2]);

        clearUpgradeCooldown();
    }

// Code block 1 (unknown):
function _requireCallerIsBorrowerOperationsOrRedemptionHelper() internal view {
    require(msg.sender == borrowerOperationsAddress || msg.sender == address(redemptionHelper), "Only borrowerOperationsAddress or redemptionHelper can call this function");
}


Sathish9098 Analysis

# Lybra Finance - Analysis

|Head |Details|
|:----------------|:------|
| Approach taken in evaluating the codebase | What is unique? How are the existing patterns used? |
|Codebase quality analysis| its structure, readability, maintainability, and adherence to best practices|
|Centralization risks| power, control, or decision-making authority is concentrated in a single entity|
|Bug Fix|process of identifying and resolving issues or errors|
|Gas Optimization|process of reducing the amount of gas consumed by a smart contract or transaction on a blockchain network|
|Other recommendations| Recommendations for improving the quality of your codebase|
|Time spent on analysis | Time detail of individual stages in my code review and analysis |

## Approach taken in evaluating the codebase

Steps:

- ``Use a static code analysis tool``: Static code analysis tools can scan the code for potential bugs and vulnerabilities. These tools can be used to identify a wide range of issues, including:

    - Insecure coding practices
    - Common vulnerabilities
    - Code that is not compliant with security standards

- ``Read the documentation``: The documentation for Lybra Finance should provide a detailed overview of the protocol and its codebase. This documentation can be used to understand the purpose of the code and to identify potential areas of concern.

- ``Scope the analysis``: Once you have a basic understanding of the protocol and its codebase, you can start to scope the analysis. This involves identifying the specific areas of code that you want to focus on. For example, you may want to focus on the code that handles user input, the code that interacts with external APIs, or the code that stores sensitive data.

- ``Manually review the code``: Once you have scoped the analysis, you can start to manually review the code. This involves reading the code line-by-line and looking for potential problems. Some of the things you should look for include:

   - Unvalidated user in

FILE: 2023-06-lybra/contracts/lybra/miner/EUSDMiningIncentives.sol

84: function setToken(address _lbr, address _eslbr) external onlyOwner {
89: function setLBROracle(address _lbrOracle) external onlyOwner {
93: function setPools(address[] memory _pools) external onlyOwner {
100:function setBiddingCost(uint256 _biddingRatio) external onlyOwner {
105:function setExtraRatio(uint256 ratio) external onlyOwner {
110: function setPeUSDExtraRatio(uint256 ratio) external onlyOwner {
115: function setBoost(address _boost) external onlyOwner {
119: function setRewardsDuration(uint256 _duration) external onlyOwner {
124: function setEthlbrStakeInfo(address _pool, address _lp) external onlyOwner {
128: function setEUSDBuyoutAllowed(bool _bool) external onlyOwner {

FILE: 2023-06-lybra/contracts/lybra/miner/ProtocolRewardsPool.sol

52:  function setTokenAddress(address _eslbr, address _lbr, address _boost) external onlyOwner {
58:  function setGrabCost(uint256 _ratio) external onlyOwner {

FILE: Breadcrumbs2023-06-lybra/contracts/lybra/miner/stakerewardV2pool.sol

121: function setRewardsDuration(uint256 _duration) external onlyOwner {
127: function setBoost(address _boost) external onlyOwner {
132: function notifyRewardAmount(uint256 _amount) external onlyOwner updateReward(address(0)) {


mgf15 G

## Gas Optimizations

| |Issue|Instances|
|-|:-|:-:|
| [GAS-1](#GAS-1) | Using bools for storage incurs overhead | 3 |
| [GAS-2](#GAS-2) | Cache array length outside of loop | 19 |
| [GAS-3](#GAS-3) | Don't initialize variables with default value | 21 |
| [GAS-4](#GAS-4) | Functions guaranteed to revert when called by normal users can be marked `payable` | 10 |
| [GAS-5](#GAS-5) | `++i` costs less gas than `i++`, especially when it's used in `for`-loops (`--i`/`i--` too) | 19 |
| [GAS-6](#GAS-6) | Use != 0 instead of > 0 for unsigned integer comparison | 17 |
| [GAS-7](#GAS-7) | <x> += <y> costs more gas than <x> = <x> + <y> for state variables (-= too) | 9 |
| [GAS-8](#GAS-8) | Setting the constructor to payable | 3 |
| [GAS-9](#GAS-9) | Structs can be packed into fewer storage slots | 5 |

### [GAS-1] Using bools for storage incurs overhead
Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past. See [source](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27).

*Instances (3)*:
```solidity
File: 2023-04-caviar/src/PrivatePool.sol

94:     bool public initialized;

97:     bool public payRoyalties;

100:     bool public useStolenNftOracle;

```

### [GAS-2] Cache array length outside of loop
If not cached, the solidity compiler will always read the length of the array during each iteration. That is, if it is a storage array, this is an extra sload operation (100 additional extra gas for each iteration except for the first) and if it is a memory array, this is an extra mload operation (3 additional gas for each iteration except for the first).

*Instances (19)*:
```solidity
File: 2023-04-caviar/src/EthRouter.sol

106:         for (uint256 i = 0; i < buys.length; i++) {

116:                     for (uint256 j = 0; j < buys[i].tokenIds.lengt

File: 2023-04-caviar/src/EthRouter.sol

106:         for (uint256 i = 0; i < buys.length; i++) {

116:                     for (uint256 j = 0; j < buys[i].tokenIds.length; j++) {

134:             for (uint256 j = 0; j < buys[i].tokenIds.length; j++) {

159:         for (uint256 i = 0; i < sells.length; i++) {

161:             for (uint256 j = 0; j < sells[i].tokenIds.length; j++) {

183:                     for (uint256 j = 0; j < sells[i].tokenIds.length; j++) {

239:         for (uint256 i = 0; i < tokenIds.length; i++) {

261:         for (uint256 i = 0; i < changes.length; i++) {

265:             for (uint256 j = 0; j < changes[i].inputTokenIds.length; j++) {

284:             for (uint256 j = 0; j < changes[i].outputTokenIds.length; j++) {

// Code block 1 (solidity):
File: 2023-04-caviar/src/PrivatePool.sol

94:     bool public initialized;

97:     bool public payRoyalties;

100:     bool public useStolenNftOracle;


0xWagmi Q

### Q/A Report

| Letter | Name | Description |
|:--:|:-------:|:-------:|
| L  | Low risk | Potential risk |
| NC |  Non-critical | Non risky findings |
| R  | Refactor | Changing the code |
| O | Ordinary | Often found issues |

| Total Found Issues | 3 |
|:--:|:--:|

---

## Low[0]  `calculateFunding()` function  could be bypassed  by passing a array with a length 0 ;

CODE : https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/perp-vault/PerpetualAtlanticVault.sol#L405

### Vulnerability details

In `PerpetualAtlanticVault::calculateFunding()` it takes an array of `strikes` then it checks `_isEligibleSender()`;
```js
 function _isEligibleSender() internal view {
        // the below condition checks whether the caller is a contract or not
        if (msg.sender != tx.origin) {
            require(whitelistedContracts[msg.sender], "Contract must be whitelisted");
        }
    }
```
Consider this also another low severity vulnerability . we should avoid using  `tx.origin` much as possible, https://twitter.com/pashovkrum/status/1692151232503144578

Ok , so  we have ` for (uint256 i = 0; i < strikes.length; i++) {` which takes a strikes array and calculate  the `fundingAccrued`
based on that.But it never checks that `array.length > 0` so the whole logic inside the for loop could be bypassed  ,

### POC
`copy this test into  /tests/perp-vault/Integration.sol`
`forge test --match-path  ./tests/perp-vault/Integration.t.sol   -vvvv`

```js
function test_strike_bypass_checks() public {
    uint256[] memory strikes = new uint256[](0);
    uint256 fundingAccrued = vault.calculateFunding(strikes);
}

```

### Tools Used
Manual Review / foundry

### Mitigation steps
Consider checking the length of the array is greater than 0 else revert the function

---

## Low[1] Re-assignable  `assetSymbol` result in pointing to a invalid reserveAsset

### Vulnerability details
In `rdpxV2Core::addAssetTotokenReserves` it isn't checked that the token symbol i

// Code block 1 (js):
function _isEligibleSender() internal view {
        // the below condition checks whether the caller is a contract or not
        if (msg.sender != tx.origin) {
            require(whitelistedContracts[msg.sender], "Contract must be whitelisted");
        }
    }

// Code block 2 (js):
function test_strike_bypass_checks() public {
    uint256[] memory strikes = new uint256[](0);
    uint256 fundingAccrued = vault.calculateFunding(strikes);
}


hunter_w3b G

# Gas Optimization

# Summary

| Number | Optimization Details                                                                         | Context |
| :----: | :------------------------------------------------------------------------------------------- | :-----: |
| [G-01] | FUNCTIONS GUARANTEED TO REVERT WHEN CALLED BY NORMAL USERS CAN BE MARKED PAYABLE             |   10    |
| [G-02] |  <X> += <Y> COSTS MORE GAS THAN <X> = <X> + <Y> FOR STATE VARIABLES                          |    4    |
| [G-03] | PUBLIC FUNCTIONS NOT CALLED BY THE CONTRACT SHOULD BE DECLARED EXTERNAL INSTEAD              |    9    |
| [G-04] | SETTING THE CONSTRUCTOR TO PAYABLE                                                           |    3    |
| [G-05] | STATE VARIABLES SHOULD BE CACHED IN STACK VARIABLES RATHER THAN RE-READING THEM FROM STORAGE |   14    |
| [G-06] | NOT USING THE NAMED RETURN VARIABLES WHEN A FUNCTION RETURNS, WASTES DEPLOYMENT GAS          |    9    |
| [G-07] | USING CALLDATA INSTEAD OF MEMORY FOR READ-ONLY ARGUMENTS IN EXTERNAL FUNCTIONS SAVES GAS     |    8    |
| [G-08] | USE ASSEMBLY TO CHECK FOR ADDRESS(0)                                                         |   19    |
| [G-09] | The result of function calls should be cached rather than re-calling the function            |    3    |
| [G-10] | REORDER STRUCTURE LAYOUT                                                                     |    1    |
| [G-11] | With assembly, .call (bool success)  transfer can be done gas-optimized                      |    1    |
| [G-12] | Duplicated require()/if() checks should be refactored to a modifier or function              |   29    |
| [G-13] | Use nested if and, avoid multiple check combinations                                         |   11    |
| [G-14] | Use assembly to write address storage values                                                 |    8    |
| [G-15] | abi.encodePacked() efficient then abi.encode()                                               |    1

File: /src/Factory.sol

129    function setPrivatePoolMetadata(address _privatePoolMetadata) public onlyOwner {

135    function setPrivatePoolImplementation(address _privatePoolImplementation) public onlyOwner {

141    function setProtocolFeeRate(uint16 _protocolFeeRate) public onlyOwner {

148    function withdraw(address token, uint256 amount) public onlyOwner {

File: /src/PrivatePool.sol

514    function withdraw(address _nft, uint256[] calldata tokenIds, address token, uint256 tokenAmount) public onlyOwner {

538    function setVirtualReserves(uint128 newVirtualBaseTokenReserves, uint128 newVirtualNftReserves) public onlyOwner {

550    function setMerkleRoot(bytes32 newMerkleRoot) public onlyOwner {

562    function setFeeRate(uint16 newFeeRate) public onlyOwner {

576    function setUseStolenNftOracle(bool newUseStolenNftOracle) public onlyOwner {

587    function setPayRoyalties(bool newPayRoyalties) public onlyOwner {


Haipls G

## Derivative never deleting
Even when the weight for the `derivative` was set to 0, it still affects the price of transactions
*Instances:*
```
contracts\SafEth\derivatives\SafEth.sol

71:         for (uint i = 0; i < derivativeCount; i++)
72:             underlyingValue +=
73:                 (derivatives[i].ethPerDerivative(derivatives[i].balance()) *
74:                     derivatives[i].balance()) /
75:                 10 ** 18;

84:         for (uint i = 0; i < derivativeCount; i++) {
85:             uint256 weight = weights[i];
86:             IDerivative derivative = derivatives[i];
87:             if (weight == 0) continue;

113:         for (uint256 i = 0; i < derivativeCount; i++) {
114:             // withdraw a percentage of each asset based on the amount of safETH
115:             uint256 derivativeAmount = (derivatives[i].balance() *
116:                 _safEthAmount) / safEthTotalSupply;
117:             if (derivativeAmount == 0) continue; // if derivative empty ignore
118:             derivatives[i].withdraw(derivativeAmount);

```

## Duplicate code should be extracted into a separate internal function.
In the contracts, there are duplicates of identical pieces of code that should be extracted into internal functions for optimizing contract size, gas usage, and readability.

*Instances:*
```
contracts\SafEth\derivatives\Reth.sol

121:         address rocketDepositPoolAddress = RocketStorageInterface(
122:             ROCKET_STORAGE_ADDRESS
123:         ).getAddress(
124:                 keccak256(
125:                     abi.encodePacked("contract.address", "rocketDepositPool")
126:                 )
127:             );
128:        RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(
129:                rocketDepositPoolAddress
130:            );

158:         address rocketDepositPoolAddress = RocketStorageInterface(
159:             ROCKET_STORAGE_ADDRESS
160:         ).getAddress(
161:                 keccak256(
162:

contracts\SafEth\derivatives\Reth.sol

121:         address rocketDepositPoolAddress = RocketStorageInterface(
122:             ROCKET_STORAGE_ADDRESS
123:         ).getAddress(
124:                 keccak256(
125:                     abi.encodePacked("contract.address", "rocketDepositPool")
126:                 )
127:             );
128:        RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(
129:                rocketDepositPoolAddress
130:            );

158:         address rocketDepositPoolAddress = RocketStorageInterface(
159:             ROCKET_STORAGE_ADDRESS
160:         ).getAddress(
161:                 keccak256(
162:                     abi.encodePacked("contract.address", "rocketDepositPool")
163:                 )
164:             );
165:
166:        RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(
167:                rocketDepositPoolAddress
168:            );

// Code block 1 (unknown):
contracts\SafEth\derivatives\SafEth.sol

71:         for (uint i = 0; i < derivativeCount; i++)
72:             underlyingValue +=
73:                 (derivatives[i].ethPerDerivative(derivatives[i].balance()) *
74:                     derivatives[i].balance()) /
75:                 10 ** 18;

84:         for (uint i = 0; i < derivativeCount; i++) {
85:             uint256 weight = weights[i];
86:             IDerivative derivative = derivatives[i];
87:             if (weight == 0) continue;

113:         for (uint256 i = 0; i < derivativeCount; i++) {
114:             // withdraw a percentage of each asset based on the amount of safETH
115:             uint256 derivativeAmount = (derivatives[i].balance() *
116:                 _safEthAmount) / safEthTotalSupply;
117:             if (derivativeAmount == 0) continue; // if derivative empty ignore
118:             derivatives[i].withdraw(derivativeAmount);


0xKowalski Q

[L-1] Potential DoS from High `roundId`/`winnerAddresses` in [`MergingPool::claimRewards`](https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/MergingPool.sol#L134-L167)

With increased round counts and/or `winnerAddresses`, users might face a DoS scenario when claiming large amounts of overdue rewards, due to the nested loops.

```
   uint32 lowerBound = numRoundsClaimed[msg.sender];
        for (uint32 currentRound = lowerBound; currentRound < roundId; currentRound++) {
            numRoundsClaimed[msg.sender] += 1;
            winnersLength = winnerAddresses[currentRound].length;
            for (uint32 j = 0; j < winnersLength; j++) {
                if (msg.sender == winnerAddresses[currentRound][j]) {
                    _fighterFarmInstance.mintFromMergingPool(
                        msg.sender,
                        modelURIs[claimIndex],
                        modelTypes[claimIndex],
                        customAttributes[claimIndex]
                    );
                    claimIndex += 1;
                }
            }
        }
```

Consider a user who has participated in 100 bi-weekly rounds over four years without claiming any rewards. Over this period, the platform's user base—and consequently, the number of `winnerAddresses`—has grown significantly. Initially, the user could have easily claimed rewards when the number of winners per round was low. However, as the platform expanded, each round's `winnerAddresses` array became substantially larger, due to admins increasing the size to account for new players.

Now imagine the user attempts to claim rewards from these 100 rounds. The transaction has to navigate through the much larger `winnerAddresses` for each round. This translates to higher gas costs, which, assuming the user mints in some of these rounds, might surpass the block's gas limit. As a result, the user could find themselves unable to claim their rewards.

Due to it being unlikely a use

// Code block 1 (unknown):
uint32 lowerBound = numRoundsClaimed[msg.sender];
        for (uint32 currentRound = lowerBound; currentRound < roundId; currentRound++) {
            numRoundsClaimed[msg.sender] += 1;
            winnersLength = winnerAddresses[currentRound].length;
            for (uint32 j = 0; j < winnersLength; j++) {
                if (msg.sender == winnerAddresses[currentRound][j]) {
                    _fighterFarmInstance.mintFromMergingPool(
                        msg.sender,
                        modelURIs[claimIndex],
                        modelTypes[claimIndex],
                        customAttributes[claimIndex]
                    );
                    claimIndex += 1;
                }
            }
        }


jasonxiale Q

# [L-01] `ActivePool.feeRecipientAddress` lacks of check that `feeRecipientAddress` should be greater than address(0)
File:
https://github.com/code-423n4/2023-10-badger/blob/f2f2e2cf9965a1020661d179af46cb49e993cb7e/packages/contracts/contracts/ActivePool.sol#L57
https://github.com/code-423n4/2023-10-badger/blob/f2f2e2cf9965a1020661d179af46cb49e993cb7e/packages/contracts/contracts/ActivePool.sol#L286C2-L286C2
https://github.com/code-423n4/2023-10-badger/blob/f2f2e2cf9965a1020661d179af46cb49e993cb7e/packages/contracts/contracts/ActivePool.sol#L362
https://github.com/code-423n4/2023-10-badger/blob/f2f2e2cf9965a1020661d179af46cb49e993cb7e/packages/contracts/contracts/ActivePool.sol#L381
Impact:
`ActivePool.feeRecipientAddress` lacks of check > address(0) in `constructor` function. If `ActivePool.feeRecipientAddress` is set to `address(0)`, any stETH transfer (like ActivePool.flashLoan, ActivePool.claimFeeRecipientCollShares, ActivePool.sweepToken) before calling `ActivePool.setFeeRecipientAddress` will be sent to address(0)

# [L-02] Sanity check in BorrowerOperations.sol#L463 can be removed
File:
https://github.com/code-423n4/2023-10-badger/blob/f2f2e2cf9965a1020661d179af46cb49e993cb7e/packages/contracts/contracts/BorrowerOperations.sol#L463
Impact:
The [sanity check](https://github.com/code-423n4/2023-10-badger/blob/f2f2e2cf9965a1020661d179af46cb49e993cb7e/packages/contracts/contracts/BorrowerOperations.sol#L463) can be removed because the check has been done in [BorrowerOperations.sol#L454](https://github.com/code-423n4/2023-10-badger/blob/f2f2e2cf9965a1020661d179af46cb49e993cb7e/packages/contracts/contracts/BorrowerOperations.sol#L454)

# [L-03] comments don't comform with code in BorrowerOperations.sol
File:
https://github.com/code-423n4/2023-10-badger/blob/f2f2e2cf9965a1020661d179af46cb49e993cb7e/packages/contracts/contracts/BorrowerOperations.sol#L865
https://github.com/code-423n4/2023-10-badger/blob/f2f2e2cf9965a1020661d179af46cb49e993cb7e/packages/contracts/con

 881         if (_isRecoveryMode) {
 882             _requireNoStEthBalanceDecrease(_stEthBalanceDecrease);
 883             if (_isDebtIncrease) {
 884                 _requireICRisNotBelowCCR(_vars.newICR);   <<<--------- here
 885                 _requireNoDecreaseOfICR(_vars.newICR, _vars.oldICR);
 886             }
 887
            ...

ActivePool.sol#L57, ActivePool.sol#L286-L2, ActivePool.sol#L362, ActivePool.sol#L381, BorrowerOperations.sol#L463, BorrowerOperations.sol#L454, BorrowerOperations.sol#L865


favelanky G

## [G-01] Setting the `constructor` to `payable` can save gas

Setting constructor to payable will save ~13 gas per instance.

There are 3 instance of this issue:

```diff
    constructor() {
        _disableInitializers();
    }
```

https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/WstEth.sol#L24-L26
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L38-L40
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L33-L35
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/SfrxEth.sol#L27-L29

## [G-02] Save gas with saving variables to memory

The instances below point to the second access of a storage variable within a function.

There are 2 instance of this issue:

```solidity
File: SafEth.sol
	// derivativeCount can be saved in memory
	{
        derivatives[derivativeCount] = IDerivative(_contractAddress);
        weights[derivativeCount] = _weight;
        derivativeCount++;
        uint256 localTotalWeight = 0;
        for (uint256 i = 0; i < derivativeCount; i++)
            localTotalWeight += weights[i];
        totalWeight = localTotalWeight;
        emit DerivativeAdded(_contractAddress, _weight, derivativeCount);
	}

for (uint i = 0; i < derivativeCount; i++) {
	// store external call derivatives[i].balance() in memory
	underlyingValue += (derivatives[i].ethPerDerivative(derivatives[i].balance()) * derivatives[i].balance()) / 10 ** 18;
}
```

https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L186-L194
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L72-L75

## [G-03] Using bools for storage incurs overhead

Booleans are more expensive than uint256 or any type that takes up a full word because each write operation emits an extra SLOAD to first read the slot’s contents, replace the bits taken up by the boolean, and then write back. Thi

https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L186-L194
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L72-L75

## [G-03] Using bools for storage incurs overhead

Booleans are more expensive than uint256 or any type that takes up a full word because each write operation emits an extra SLOAD to first read the slot’s contents, replace the bits taken up by the boolean, and then write back. This is the compiler’s defense against contract upgrades and pointer aliasing, and it cannot be disabled. Use uint256(1) and uint256(2) for true/false instead.

There are 2 instance of this issue:

// Code block 1 (diff):
constructor() {
        _disableInitializers();
    }

// Code block 2 (solidity):
File: SafEth.sol
	// derivativeCount can be saved in memory
	{
        derivatives[derivativeCount] = IDerivative(_contractAddress);
        weights[derivativeCount] = _weight;
        derivativeCount++;
        uint256 localTotalWeight = 0;
        for (uint256 i = 0; i < derivativeCount; i++)
            localTotalWeight += weights[i];
        totalWeight = localTotalWeight;
        emit DerivativeAdded(_contractAddress, _weight, derivativeCount);
	}

for (uint i = 0; i < derivativeCount; i++) {
	// store external call derivatives[i].balance() in memory
	underlyingValue += (derivatives[i].ethPerDerivative(derivatives[i].balance()) * derivatives[i].balance()) / 10 ** 18;
}

WstEth.sol#L24-L26, SafEth.sol#L38-L40, Reth.sol#L33-L35, SfrxEth.sol#L27-L29, SafEth.sol#L186-L194, SafEth.sol#L72-L75

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


SY_S G

## Summary

### Gas Optimization

no | Issue |Instances||
|-|:-|:-:|:-:|
| [G-01] | Use nested if and, avoid multiple check combinations | 6 |
| [G-02] | Use assembly to write address storage values | 4 |
| [G-03] |Use hardcode address instead of address(this) | 9 |
| [G-04] | Expressions for constant values such as a call to keccak256(), should use immutable rather than constant  | 4 |
| [G-05] |State variable can be packed into fewer storage slots | 1 |
| [G-06] | Variable names that consist of all capital letters should be reserved for constant/immutable variables| 1 |
| [G-07] |  Non efficient zero initialization| 3 |
| [G-08] |Duplicated require()/if() checks should be refactored to a modifier or function | 6 |
| [G-09] |Do not calculate  constant | 7 |
| [G-10] |Should use arguments instead of state variable | 7 |

## Gas Optimizations

## [G-1]  Use nested if and, avoid multiple check combinations

Using nested if is cheaper than using && multiple check combinations. There are more advantages, such as easier to read code and better coverage reports.

```solidity
file: /contracts/core/Vault.sol

158    if (_poolId != 0 && balances[_token] != 0 && !isTokenStaked[_token]) _canStake = true;

```
https://github.com/code-423n4/2023-07-amphora/blob/main/core/solidity/contracts/core/Vault.sol#L158

```solidity
file: /contracts/core/VaultController.sol

1018    if (_increase && (_collateral.totalDeposited + _amount) > _collateral.cap) revert VaultController_CapReached();

```
https://github.com/code-423n4/2023-07-amphora/blob/main/core/solidity/contracts/core/VaultController.sol#L1018

```solidity
file: /contracts/governance/GovernorCharlie.sol

170    if (amph.getPriorVotes(msg.sender, (block.number - 1)) < proposalThreshold && !isWhitelisted(msg.sender)) {

469    else if (
470      (_whitelisted && _proposal.againstVotes > _proposal.quorumVotes)
471        || (!_whitelisted && _proposal.forVotes <= _proposal.againstVotes)
472        || (!_whiteli

// Code block 1 (solidity):
file: /contracts/core/Vault.sol

158    if (_poolId != 0 && balances[_token] != 0 && !isTokenStaked[_token]) _canStake = true;

// Code block 2 (solidity):
file: /contracts/core/VaultController.sol

1018    if (_increase && (_collateral.totalDeposited + _amount) > _collateral.cap) revert VaultController_CapReached();

file: /contracts/core/Vault.sol

158    if (_poolId != 0 && balances[_token] != 0 && !isTokenStaked[_token]) _canStake = true;

file: /contracts/core/VaultController.sol

1018    if (_increase && (_collateral.totalDeposited + _amount) > _collateral.cap) revert VaultController_CapReached();


IgorZuk G

## Pack global storage variables of SafETH

The current storage takes 5 slots:
```solidity
bool public pauseStaking;
bool public pauseUnstaking;
uint256 public derivativeCount;
uint256 public totalWeight;
uint256 public minAmount;
uint256 public maxAmount;
```

This is excessive, the types can be much smaller:
- `derivativeCount` can be `uint16` there won't be more than 65 thousand derivatives
- `totalWeight` can be `uint32`, the weights scale of 4 billion should be accurate enough
- `minAmount` and `maxAmount` can be `uint96`, the total supply of ETH in the ecosystem is a number that can fit in 87 bits, using 96 is more than safe.

The optimized storage fits exactly in 256 bits and takes only 1 slot:
```solidity
bool public pauseStaking;
bool public pauseUnstaking;
uint16 public derivativeCount;
uint32 public totalWeight;
uint96 public minAmount;
uint96 public maxAmount;
```
If it's necessary, the storage can be reduced by 1 more byte if `pause` variables are merged into a bitmap.

## Pack per-derivative storage variables of SafETH

Each derivative takes at least 3 slots:
```solidity
mapping(uint256 => IDerivative) public derivatives; // derivatives in the system
mapping(uint256 => uint256) public weights; // weights for each derivative
```
The third slot is the `maxSlippage` variable stored by each derivative.

- `weights` can be reduced to `uint32` as mentioned in the global storage packing finding
- `maxSlippage` can be reduced to `uint64`, because it only deals with numbers on a scale from `0` (no slippage allowed) to `10^18` (any slippage allowed), which needs just 60 bits. Going further this value doesn't need to be stored in each derivative separately, it can be passed in `deposit` and `withdraw` functions. It's more flexible this way, keeps the configuration in one place, simplifies the derivatives implementation and makes events cleaner, currently derivatives don't emit anything even though their internal state is changed.

The optimized per-derivative sto

// Code block 1 (solidity):
bool public pauseStaking;
bool public pauseUnstaking;
uint256 public derivativeCount;
uint256 public totalWeight;
uint256 public minAmount;
uint256 public maxAmount;

// Code block 2 (solidity):
bool public pauseStaking;
bool public pauseUnstaking;
uint16 public derivativeCount;
uint32 public totalWeight;
uint96 public minAmount;
uint96 public maxAmount;

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

bool public pauseStaking;
bool public pauseUnstaking;
uint256 public derivativeCount;
uint256 public totalWeight;
uint256 public minAmount;
uint256 public maxAmount;

bool public pauseStaking;
bool public pauseUnstaking;
uint16 public derivativeCount;
uint32 public totalWeight;
uint96 public minAmount;
uint96 public maxAmount;

mapping(uint256 => IDerivative) public derivatives; // derivatives in the system
mapping(uint256 => uint256) public weights; // weights for each derivative


Rageur G

## GAS-1: 10 ** X can be changed to 1eX

### Affected file

* Maths.sol (Line: 30, 34, 38, 47)

## GAS-2: <X> += <Y> costs more gas than <X> = <X> + <Y> for state variables

### Description

Using the addition operator instead of plus-equals saves gas.

### Affected file

* ExtraordinaryFunding.sol (Line: 145)
* PositionManager.sol (Line: 320, 321)

## GAS-3: <X> <= <Y> costs more gas than <X> < <Y> + 1

### Description

In Solididy, the opcode 'less or equal' doesn't exist. So the EVM will translate this by two distinct operation, first the inferior, and then the equal which cost more gas then a strict less.

### Affected file

* ExtraordinaryFunding.sol (Line: 173, 176, 196)
* Maths.sol (Line: 9, 42)
* PositionManager.sol (Line: 285, 442)

## GAS-4: Cache the mapping values rather than fetch it every time

### Affected file

* ExtraordinaryFunding.sol (Line: 286, 287, 288, 289, 290, 291)
* PositionManager.sol (Line: 265, 317, 493, 494, 522, 523, 529)

## GAS-5: Do not calculate constants

### Description

Due to how constant variables are implemented (replacements at compile-time), an expression assigned to a constant variable is recomputed each time that the variable is used, which wastes some gas.

### Affected file

* ExtraordinaryFunding.sol (Line: 28)
* Maths.sol (Line: 6)

## GAS-6: Duplicated require()/revert() checks should be refactored to a modifier or function

### Description

This saves deployment gas.

### Affected file

* ExtraordinaryFunding.sol (Line: 70, 139, 195)
* PositionManager.sol (Line: 195, 372)

## GAS-7: Internal functions not called by the contract should be removed to save deployment gas

### Description

If the functions are required by an interface, the contract should inherit from that interface and use the override keyword.

### Affected file

* ExtraordinaryFunding.sol (Line: 190)
* Funding.sol (Line: 52, 76, 103, 152)
* Maths.sol (Line: 8, 18, 37, 41, 46)
* PositionManager.sol (Line: 404)

## GAS-8: Prefix increments cheaper than

### Affected file

* GrantFund.sol (Line: 67)
* PositionManager.sol (Line: 213, 390)

## GAS-14: Use nested if and avoid multiple check combinations

### Description

Using nested is cheaper than using && multiple check combinations. There are more advantages, such as easier to read code and better coverage reports.

### Affected file

* ExtraordinaryFunding.sol (Line: 196)

## GAS-15: Using Solidity version 0.8.19 will provide an overall gas optimization

### Description

Use a solidity version of at least 0.8.0 to get overflow protection without SafeMath.
Use a solidity version of at least 0.8.2 to get simple compiler automatic inlining.
Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads.
Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require() strings.
Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value.

### Affected file

* ExtraordinaryFunding.sol (Line: 3)
* Funding.sol (Line: 3)
* GrantFund.sol (Line: 3)
* IExtraordinaryFunding.sol (Line: 4)
* IFunding.sol (Line: 4)
* IGrantFund.sol (Line: 3)
* IStandardFunding.sol (Line: 4)
* Maths.sol (Line: 2)
* PositionManager.sol (Line: 3)

## GAS-16: Using bools for storage incurs overhead

### Description

Booleans are more expensive than uint256 or any type that takes up a full word because each write operation emits an extra SLOAD to first read the slot’s contents, replace the bits taken up by the boolean, and then write back. This is the compiler’s defense against contract upgrades and pointer aliasing, and it cannot be disabled. Use uint256(1) and uint256(2) for true/false instead.

### Affected file

* ExtraordinaryFunding.sol (Line: 49)

## GAS-17: Using immutable on variables that are only set in the constructor and never after (2.1k gas per var)

### Description

Use immutable if you want to assign a permanent val


Diana Q

## 01 require() should be used instead of assert()

Assert should not be used except for tests, `require` should be used

Prior to Solidity 0.8.0, pressing a confirm consumes the remainder of the process’s available gas instead of returning it, as request()/revert() did.

Assertion() should be avoided even after solidity version 0.8.0, because its documentation states “The Assert function generates an error of type Panic(uint256). Code that works properly should never Panic, even on invalid external input. If this happens, you need to fix it in your contract. there’s a mistake”.

_There are 2 instances of this issue_

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

```
File: contracts/smart-contract-wallet/Proxy.sol

16: assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("biconomy.scw.proxy.implementation")) - 1));
```

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

```
File: contracts/smart-contract-wallet/common/Singleton.sol

13: assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("biconomy.scw.proxy.implementation")) - 1));
```

------

## 02 Avoid using tx.origin

`tx.origin` is a global variable in Solidity that returns the address of the account that sent the transaction.

Using the variable could make a contract vulnerable if an authorized account calls a malicious contract. You can impersonate a user using a third party contract.

This can make it easier to create a vault on behalf of another user with an external administrator (by receiving it as an argument).

_There are 3 instances of this issue_

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

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

257: address payable receiver = refundReceiver == address(0) ? payable(tx.origin) : refundReceiver;
281: address payable receive

// Code block 1 (unknown):
File: contracts/smart-contract-wallet/Proxy.sol

16: assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("biconomy.scw.proxy.implementation")) - 1));

// Code block 2 (unknown):
File: contracts/smart-contract-wallet/common/Singleton.sol

13: assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("biconomy.scw.proxy.implementation")) - 1));


ast3ros Q

# [L-1] PausableUpgradeable contract is not initialized

Contract that inherit from PausableUpgradeable should call the `__Pausable_init()` function in their initialize function. This is not done in the rUSDY contract.

https://github.com/code-423n4/2023-09-ondo/blob/b88271d64112234b7a7273cd7f3cea73c350e6a7/contracts/usdy/rUSDY.sol#L128-L131

## Recommended Mitigation Steps:

```diff
    ) internal onlyInitializing {
        __BlocklistClientInitializable_init(blocklist);
        __AllowlistClientInitializable_init(allowlist);
        __SanctionsListClientInitializable_init(sanctionsList);
        __rUSDY_init_unchained(_usdy, guardian, _oracle);
+       __Pausable_init();
    }
```

# [L-2] Number of approve needed is always minus 1

In the DestinationBridge, the number of approvals needed is always one less than the threshold. This is because there is an automatic approval when `_execute` function is called.

        _approve(txnHash);

https://github.com/code-423n4/2023-09-ondo/blob/b88271d64112234b7a7273cd7f3cea73c350e6a7/contracts/bridge/DestinationBridge.sol#L111

This means that the number of approvals needed is always reduced by one. For example, if the threshold is 2, then the number of approvals needed is 1. If the threshold is 3, then the number of approvals needed is 2.

# [NC-1] Unnecessary overflow checking

In `_rmul` function, the x * y operation is executed in the _mul(x, y) function. The _mul function performs the overflow checking by `require(y == 0 || (z = x * y) / y == x);`. However since solidity 0.8, overflow checking is done by default. Therefore, the overflow checking in _mul function is unnecessary and can be removed.

https://github.com/code-423n4/2023-09-ondo/blob/b88271d64112234b7a7273cd7f3cea73c350e6a7/contracts/rwaOracles/RWADynamicOracle.sol#L405-L407

# [NC-2] State is not cleared for the executed transaction

In the destination bridge, after the transaction is executed, not all the state variables related to the transaction

// Code block 1 (diff):
) internal onlyInitializing {
        __BlocklistClientInitializable_init(blocklist);
        __AllowlistClientInitializable_init(allowlist);
        __SanctionsListClientInitializable_init(sanctionsList);
        __rUSDY_init_unchained(_usdy, guardian, _oracle);
+       __Pausable_init();
    }


ayden G

https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L94#L100
Using bools for storage incurs overhead
Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past

use assembly to check for address(0) save more gas

```solidity
error ZeroAddress();
function assembly_notZero(address toCheck) public pure returns(bool success) {
    assembly {
        if iszero(toCheck) {
            let ptr := mload(0x40)
            mstore(ptr, 0xd92e233d00000000000000000000000000000000000000000000000000000000) // selector for `ZeroAddress()`
            revert(ptr, 0x4)
        }
    }
    return true;
}
```

https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L484#L507
Optimize code logic by reducing the check for zero address when dealing with 'baseToken', storing the length of 'tokenIds' as a local variable, and moving the 'i++' incrementation inside the 'unchecked' statement.

```solidity
        // ~~~ Checks ~~~ //

        if((baseToken == address(0))){
            //eth
            if(msg.value != baseTokenAmount)  revert InvalidEthAmount();
        }else{
            //erc20
            if(msg.value > 0) revert InvalidEthAmount();
            ERC20(baseToken).safeTransferFrom(msg.sender, address(this), baseTokenAmount);
        }

        // ~~~ Interactions ~~~ //

        // transfer the nfts from the caller
        uint256 tokenIdsLength = tokenIds.length;
        for (uint256 i = 0; i < tokenIdsLength;) {
            ERC721(nft).safeTransferFrom(msg.sender, address(this), tokenIds[i]);
            unchecked{i++;}
        }

        // emit the deposit event
        emit Deposit(tokenIds, baseTokenAmount);
    }
```

https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L221
https://github.com/code-423n4/2023-04-caviar/blob/main/src/PrivatePool.sol#L306
https://github.com/code-423n4/2

// Code block 1 (solidity):
error ZeroAddress();
function assembly_notZero(address toCheck) public pure returns(bool success) {
    assembly {
        if iszero(toCheck) {
            let ptr := mload(0x40)
            mstore(ptr, 0xd92e233d00000000000000000000000000000000000000000000000000000000) // selector for `ZeroAddress()`
            revert(ptr, 0x4)
        }
    }
    return true;
}

// Code block 2 (solidity):
// ~~~ Checks ~~~ //

        if((baseToken == address(0))){
            //eth
            if(msg.value != baseTokenAmount)  revert InvalidEthAmount();
        }else{
            //erc20
            if(msg.value > 0) revert InvalidEthAmount();
            ERC20(baseToken).safeTransferFrom(msg.sender, address(this), baseTokenAmount);
        }

        // ~~~ Interactions ~~~ //

        // transfer the nfts from the caller
        uint256 tokenIdsLength = tokenIds.length;
        for (uint256 i = 0; i < tokenIdsLength;) {
            ERC721(nft).safeTransferFrom(msg.sender, address(this), tokenIds[i]);
            unchecked{i++;}
        }

        // emit the deposit event
        emit Deposit(tokenIds, baseTokenAmount);
    }

error ZeroAddress();
function assembly_notZero(address toCheck) public pure returns(bool success) {
    assembly {
        if iszero(toCheck) {
            let ptr := mload(0x40)
            mstore(ptr, 0xd92e233d00000000000000000000000000000000000000000000000000000000) // selector for `ZeroAddress()`
            revert(ptr, 0x4)
        }
    }
    return true;
}

// ~~~ Checks ~~~ //

        if((baseToken == address(0))){
            //eth
            if(msg.value != baseTokenAmount)  revert InvalidEthAmount();
        }else{
            //erc20
            if(msg.value > 0) revert InvalidEthAmount();
            ERC20(baseToken).safeTransferFrom(msg.sender, address(this), baseTokenAmount);
        }

        // ~~~ Interactions ~~~ //

        // transfer the nfts from the caller
        uint256 tokenIdsLength = tokenIds.length;
        for (uint256 i = 0; i < tokenIdsLength;) {
            ERC721(nft).safeTransferFrom(msg.sender, address(this), tokenIds[i]);
            unchecked{i++;}
        }

        // emit the deposit event
        emit Deposit(tokenIds, baseTokenAmount);
    }

PrivatePool.sol#L94, PrivatePool.sol#L484, PrivatePool.sol#L221, PrivatePool.sol#L306


uzay Q

# Some functions revert on failure even though they are expected to return false instead

In the `UFragments.sol` contract, the transfer and approval functions return `true` on success and `revert` on failure. However, according to the natspec comments written by the protocol developers, these functions are expected to return `false` on failure.

## PoC
```javascript
/**
* @notice Transfer tokens to a specified address.
* @param _to The address to transfer to.
* @param _value The amount to be transferred.
* @return _success True on success, false otherwise. @audit function never returns false
*/
function transfer(address _to, uint256 _value) external override validRecipient(_to) returns (bool _success) {
	uint256 _gonValue = _value * _gonsPerFragment;

	_gonBalances[msg.sender] = _gonBalances[msg.sender] - _gonValue;
	_gonBalances[_to] = _gonBalances[_to] + _gonValue;

	emit  Transfer(msg.sender, _to, _value);
	return  true;
}
```

## Affected functions
`transfer()`
`transferAll()`
`transferFrom()`
`transferAllFrom()`
`approve()`
`increaseAllowance()`
`decreaseAllowance()`

## Recommendation
If the comments in the code were a mistake, you can remove the related comments. However, if the comments accurately state the intended behavior, you should modify the function in a way that it returns false when the execution fails.

// Code block 1 (javascript):
/**
* @notice Transfer tokens to a specified address.
* @param _to The address to transfer to.
* @param _value The amount to be transferred.
* @return _success True on success, false otherwise. @audit function never returns false
*/
function transfer(address _to, uint256 _value) external override validRecipient(_to) returns (bool _success) {
	uint256 _gonValue = _value * _gonsPerFragment;

	_gonBalances[msg.sender] = _gonBalances[msg.sender] - _gonValue;
	_gonBalances[_to] = _gonBalances[_to] + _gonValue;

	emit  Transfer(msg.sender, _to, _value);
	return  true;
}


Jerry0x Q

## Impact
Calldata length must be greater than 68

## Proof of Concept
[mload(add(tokenDataWithSig, 68))](https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-grants/src/grants/base/Funding.sol#L133)
```
require(tokenDataWithSig.length >68, "invalid calldata");
assembly {
  tokensRequested := mload(add(tokenDataWithSig, 68))
}
```

// Code block 1 (unknown):
require(tokenDataWithSig.length >68, "invalid calldata");
assembly {
  tokensRequested := mload(add(tokenDataWithSig, 68))
}


Dug Q

# `Unstaked` event data can be manipulated

When the `unstake()` function is called, `withdraw()` is called on each derivative. At the end of each, the derivative sends its full ether balance to the `SafEth` contract.

```solidity
(bool sent, ) = address(msg.sender).call{value: address(this).balance}(
    ""
);
```

After receiving ether from each derivative, `unstake()` sends the total received ether to the user and then emits the `Unstaked` event.

```solidity
emit Unstaked(msg.sender, ethAmountToWithdraw, _safEthAmount);
```

Because of how this works, a user could send ether to a derivative contract ahead of calling unstake(). This would cause the derivative to send more ether to the `SafEth` contract than it normally would as it would include the amount just sent to the derivative.

The funds would ultimately be returned to the user and the `Unstaked` event would store this manipulated `ethAmountToWithdraw` amount instead of the amount that was actually related to the unstaking of safETH.

As a result, event data and any dependent reporting/analytics would be compromised.

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

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

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

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

# Funds are lost if `stake()` is called before the first derivative added

The `stake()` function will successfully execute even if no derivatives are added. This means that a user could stake safETH and lose all of their funds sent with the call.

https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e98726

// Code block 1 (solidity):
(bool sent, ) = address(msg.sender).call{value: address(this).balance}(
    ""
);

// Code block 2 (solidity):
emit Unstaked(msg.sender, ethAmountToWithdraw, _safEthAmount);

(bool sent, ) = address(msg.sender).call{value: address(this).balance}(
    ""
);

emit Unstaked(msg.sender, ethAmountToWithdraw, _safEthAmount);

SafEth.sol#L108-L129, Reth.sol#L107-L114, SfrxEth.sol#L60-L88, WstEth.sol#L56-L67


hunter_w3b Analysis

# Analysis - Kelp DAO | rsETH Contest

![Kelp DAO | rsETH](https://code4rena.com/_next/image?url=https%3A%2F%2Fstorage.googleapis.com%2Fcdn-c4-uploads-v0%2Fuploads%2F3LECGwvBspH.0&w=256&q=75)

## Description overview of Kelp DAO Contest

![Kelp DAO](https://i.im.ge/2023/11/15/AOVmfz.kepl-Dao.png)

Kelp DAO aims to unlock liquidity, DeFi opportunities, and higher rewards for restaked assets. As a DAO, Kelp operates in a decentralized manner driven by its community. The core focus of Kelp is the development of rsETH (Liquid Restaked ETH), its flagship project. RsETH provides liquid staking for ETH deposited in the EigenLayer proof-of-stake platform. It acts as a liquid token that represents the staked ETH. This innovative approach allows holders to retain ongoing staking yields while gaining liquidity and usability of their assets in decentralized finance applications.

**The key contracts of Kelp DAO | rsETH protocol for this Audit are**:

- **LRTDepositPool.sol**: It is the main contract that users interact with to deposit assets into the Kelp DAO protocol, allowing users to easily deposit assets and receive rsETH tokens, while properly distributing those assets across contracts, the LRTDepositPool contract forms the core user interface and asset management layer for the protocol.

- **LRTConfig.sol**: The LRTConfig contract centralizes all the important configuration details of the Kelp DAO protocol.

  It stores information like:

  - Supported assets and their deposit limits
  - Contract addresses for key components
  - Token and strategy mappings

- **NodeDelegator.sol**: This contract handles the actual deposit of assets into the strategies on EigenLayer.By acting as an intermediary that directly interacts with the EigenLayer strategies, the NodeDelegator contract enables assets to flow from the deposit pool into yield/liquidity generating strategies. This is a core part of realizing the liquid staking functionality that Kelp DAO aims to provide through integra

    modifier onlyLRTManager() {
        if (!IAccessControl(address(lrtConfig)).hasRole(LRTConstants.MANAGER, msg.sender)) {
            revert ILRTConfig.CallerNotLRTConfigManager();
        }
        _;
    }

      modifier onlyLRTAdmin() {
        bytes32 DEFAULT_ADMIN_ROLE = 0x00;
        if (!IAccessControl(address(lrtConfig)).hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) {
            revert ILRTConfig.CallerNotLRTConfigAdmin();
        }
        _;
    }


mxuse Q

QA (4 in total)

1. GetAssetCurrentLimit
``` javascript
function getAssetCurrentLimit(address asset) public view override returns (uint256) {

return lrtConfig.depositLimitByAsset(asset) - getTotalAssetDeposits(asset);

}
```
This function can be viewed by everyone and returns the Currentlimit from an asset.

``` javascript
if (depositAmount > getAssetCurrentLimit(asset)) {

revert MaximumDepositLimitReached();

}
```
This section of the `depositAsset` function checks whether the deposit amount exceeds the current limit obtained from `getAssetCurrentLimit(asset)`.  A malicious user could monitor the asset's limit using the public function and, as it approaches the limit, intentionally causes pending transactions from honest users to fail by depositing an amount that triggers the `revert ExceededMaximumDepositLimit()` condition. This could lead to a frustrating experience for users and eventually a loss of clients. Solution: You could change the `GetAssetCurrentLimit` function to `private` instead of public

2.  It is recommended to include the `pause` and `unpause` functions in the `LRTConfig.sol`. While the project incorporates the pause and unpause functions in most of the "scope.txt" contracts, the `LRTConfig.sol` contract currently lacks this feature. For safety and user reassurance, it would be prudent to enable pausing and unpausing for this contract as well.

Certainly! Here's a revised version with improved wording:

3. Consider adding `onlyLRTAdmin` to the `UpdatePriceOracleFor` function so that the admin has the capability to update the price oracle.

4. It's redundant to use both `onlyLRTAdmin` and `DEFAULT_ADMIN_ROLE` in the codebase while both terms have the same meaning. Choose one term for clarity and simplicity. For example, in the `updateAssetStrategy` function, you can replace `onlyRole(DEFAULT_ADMIN_ROLE)` with `onlyLRTAdmin` for consistency:

```javascript
function updateAssetStrategy(

address asset,

address strategy

)

external

onlyRole(DE

// Code block 1 (unknown):
This function can be viewed by everyone and returns the Currentlimit from an asset.

// Code block 2 (unknown):
This section of the `depositAsset` function checks whether the deposit amount exceeds the current limit obtained from `getAssetCurrentLimit(asset)`.  A malicious user could monitor the asset's limit using the public function and, as it approaches the limit, intentionally causes pending transactions from honest users to fail by depositing an amount that triggers the `revert ExceededMaximumDepositLimit()` condition. This could lead to a frustrating experience for users and eventually a loss of clients. Solution: You could change the `GetAssetCurrentLimit` function to `private` instead of public

2.  It is recommended to include the `pause` and `unpause` functions in the `LRTConfig.sol`. While the project incorporates the pause and unpause functions in most of the "scope.txt" contracts, the `LRTConfig.sol` contract currently lacks this feature. For safety and user reassurance, it would be prudent to enable pausing and unpausing for this contract as well.

Certainly! Here's a revised version with improved wording:

3. Consider adding `onlyLRTAdmin` to the `UpdatePriceOracleFor` function so that the admin has the capability to update the price oracle.

4. It's redundant to use both `onlyLRTAdmin` and `DEFAULT_ADMIN_ROLE` in the codebase while both terms have the same meaning. Choose one term for clarity and simplicity. For example, in the `updateAssetStrategy` function, you can replace `onlyRole(DEFAULT_ADMIN_ROLE)` with `onlyLRTAdmin` for consistency:


MatricksDeCoder Q

#### NC-1 Use named return values consistently

**Description** => There are contracts that have functions that make use of named return values e.g returns (uint256 premium) in the majority of cases for functions but some functions do not use this without any logical reasoning, policy, guide or documentation as to why some use named return variables and others do not.

It is important for code to be consistent. Other contracts do not use named return variables at all whereas others do and within same contract some functions use named returns others do not.

Examples below:

[Majority functions in UniV2LiquidityAmo.sol use named return variables ](https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/amo/UniV2LiquidityAmo.sol)
However, below functions do not:
[function getLpPrice() public view returns (uint256) { ..line 381 ](https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/amo/UniV2LiquidityAmo.sol#L381C3-L381C56)
[function getLpTokenBalanceInWeth() external view returns (uint256) { ..line 372 ](https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/amo/UniV2LiquidityAmo.sol#L372)

[UniV3LiquidityAmo.sol does not use named return variables at all](https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/amo/UniV3LiquidityAmo.sol)

[Majority functions in RdpxV2Core.sol use named return variables ](https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol)
However, below functions do not:
[....) external returns (uint256) { { ..RdpxV2Core.sol line 944 ](https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/core/RdpxV2Core.sol#L944)
[public view returns (address, uint256, string memory) { ..RdpxV2Core.sol line 1137 ](https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/core/RdpxV2Core.sol#L1137C5-L1137C60)
[..function getEthPrice() publi

  // ================================ PUBLIC VIEW FUNCTIONS ================================ //

  /// @notice Returns the amount of collateral and rdpx per share
  function redeemPreview(
    uint256 shares
  ) public view returns (uint256, uint256) {
    return _convertToAssets(shares);
  }

  /// @notice Returns the amount of shares recieved for a given amount of assets
  function previewDeposit(uint256 assets) public view returns (uint256) {
    return convertToShares(assets);
  }

  /// @notice Returns the amount of shares recieved for a given amount of assets
  function convertToShares(
    uint256 assets
  ) public view returns (uint256 shares) {
    uint256 supply = totalSupply;
    uint256 rdpxPriceInAlphaToken = perpetualAtlanticVault.getUnderlyingPrice();

UniV2LiquidityAmo.sol, UniV2LiquidityAmo.sol#L381-L3, UniV2LiquidityAmo.sol#L372, UniV3LiquidityAmo.sol, RdpxV2Core.sol, RdpxV2Core.sol#L944, RdpxV2Core.sol#L1137-L5


InAllHonesty Q

### [L01] Function addChainSupport from DestinationBridge.sol should check if the chain to support is already supported to avoid gas waste or other unintended consequences.

```solidity
  function addChainSupport(
    string calldata srcChain,
    string calldata srcContractAddress
  ) external onlyOwner {
+   if (chainToApprovedSender[srcChain] != bytes32(0)) {
+       revert ChainAlreadySupported();
+   }
    chainToApprovedSender[srcChain] = keccak256(abi.encode(srcContractAddress));
    emit ChainIdSupported(srcChain, srcContractAddress);
  }

// In the Structs, Events, Errors section
+error ChainAlreadySupported();
```

### [QA01] In DestinationBridge.sol there is a comment section that marks `Public Functions`
[Link to the bad comments](https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/bridge/DestinationBridge.sol#L327-L329)
None of the functions below the comments are public, which is a bit confusing.

// Code block 1 (solidity):
function addChainSupport(
    string calldata srcChain,
    string calldata srcContractAddress
  ) external onlyOwner {
+   if (chainToApprovedSender[srcChain] != bytes32(0)) {
+       revert ChainAlreadySupported();
+   }
    chainToApprovedSender[srcChain] = keccak256(abi.encode(srcContractAddress));
    emit ChainIdSupported(srcChain, srcContractAddress);
  }

// In the Structs, Events, Errors section
+error ChainAlreadySupported();


ch0bu G

## 1. Using unchecked blocks to save gas - increments in for loop can be unchecked

The majority of Solidity for loops increment a uint256 variable that starts at 0. These increment operations never need to be checked for overflow/underflow because the variable will never reach the max number of uint256 (will run out of gas long before that happens). The default overflow/underflow check wastes gas in every iteration of virtually every for loop.

This saves 30-40 gas per loop iteration.

e.g Let’s work with a sample loop below.
```
for(uint256 i; i < 10; i++){
//doSomething
}
```
can be written as shown below.
```
for(uint256 i; i < 10;) {
  // loop logic
  unchecked { i++; }
}
```

```
71       for (uint i = 0; i < derivativeCount; i++)
84       for (uint i = 0; i < derivativeCount; i++) {
113      for (uint256 i = 0; i < derivativeCount; i++) {
140      for (uint i = 0; i < derivativeCount; i++) {
147      for (uint i = 0; i < derivativeCount; i++) {
171      for (uint256 i = 0; i < derivativeCount; i++)
191      for (uint256 i = 0; i < derivativeCount; i++)
```
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol

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

The code should be refactored such that they no longer exist, or the block should do something useful, such as emitting an event or reverting. If the contract is meant to be extended, the contract should be abstract and the function signatures be added without any default implementation.
- If the block is an empty if-statement block to avoid doing subsequent checks in the else-if/else conditions, the else-if/else conditions should be nested under the negation of the if-statement, because they involve different classes of checks, which may lead to the introduction of errors when the code is later modified `(if(x){}else if(y){...}else{...} => if(!x){if(y){...}else{...}})`.
- Empty `receive()`/`fallback()` payable functions that are not used, can be removed to save

// Code block 1 (unknown):
for(uint256 i; i < 10; i++){
//doSomething
}

// Code block 2 (unknown):
for(uint256 i; i < 10;) {
  // loop logic
  unchecked { i++; }
}

// Code block 3 (unknown):
71       for (uint i = 0; i < derivativeCount; i++)
84       for (uint i = 0; i < derivativeCount; i++) {
113      for (uint256 i = 0; i < derivativeCount; i++) {
140      for (uint i = 0; i < derivativeCount; i++) {
147      for (uint i = 0; i < derivativeCount; i++) {
171      for (uint256 i = 0; i < derivativeCount; i++)
191      for (uint256 i = 0; i < derivativeCount; i++)


bengyles Q

# QA report

## Function that modifies the state has no access control

### lines

<https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/pools/PoolStats.sol#L77>

### Impact

Contracts inheriting from this abstract contract have a public function available which changes the state and can be called by anyone. There are no parameters for this function that can be exploited but there should be no reason for a user to call this function as the indices only have to be updated after adding a token to the whitelist or removing a token from the whitelist.

### Proof of Concept

Add the following test to `src/pools/tests/Pools.t.sol`

```javascript
  function testUpdateArbitrageIndicies(address user) public {
        // anyone can call this function
        vm.prank(user);
        pools.updateArbitrageIndicies();
    }
```

### Tools Used

vsCodium, Foundry

### Recommended Mitigation Steps

add the onlyOwner modifier to the function, which might need to be imported

```diff
+ function updateArbitrageIndicies() public onlyOwner {
        bytes32[] memory poolIDs = poolsConfig.whitelistedPools();

        for (uint256 i = 0; i < poolIDs.length; i++) {
            bytes32 poolID = poolIDs[i];
            (IERC20 arbToken2, IERC20 arbToken3) = poolsConfig
                .underlyingTokenPair(poolID);

            // The middle two tokens can never be WETH in a valid arbitrage path as the path is WETH->arbToken2->arbToken3->WETH.
            if ((arbToken2 != _weth) && (arbToken3 != _weth)) {
                uint64 poolIndex1 = _poolIndex(_weth, arbToken2, poolIDs);
                uint64 poolIndex2 = _poolIndex(arbToken2, arbToken3, poolIDs);
                uint64 poolIndex3 = _poolIndex(arbToken3, _weth, poolIDs);

                // Check if the indicies in storage have the correct values - and if not then update them
                ArbitrageIndicies memory indicies = _arbitrageIndicies[poolID];
                if (

Or add a custom check for specified contracts or addresses that are allowed to call this function, this is just an example though and depends on which roles contracts the developer thinks should have this permission.

// Code block 1 (javascript):
function testUpdateArbitrageIndicies(address user) public {
        // anyone can call this function
        vm.prank(user);
        pools.updateArbitrageIndicies();
    }


JCK Analysis

## System Overview

 This system is designed to manage lending and borrowing operations using Uniswap V3 LP positions as collateral, with a focus on automation, liquidity management,

1. V3Vault: Manages lending and borrowing operations, integrating with Uniswap V3 for liquidity management. It implements the ERC20 standard for representing shares in the lending pool and handles deposits, lending, borrowing, repayments, and liquidations.
2. V3Oracle: Provides accurate price feeds for Uniswap V3 positions using Chainlink Feeds and Uniswap V3 TWAPs, ensuring system reliability and resilience. It manages token price fetching, verification, and configurations.
3. InterestRateModel: Calculates interest rates for the Vault, adjusting rates based on utilization and incorporating a kink mechanism for competitive rates.
4. FlashloanLiquidator: Facilitates atomic liquidations using Uniswap V3 Flashloans, ensuring prompt and efficient liquidation of undercollateralized loans.
5. Swapper: Adds logic for executing swaps with different routing protocols, supporting complex swap operations within DeFi applications.
6. V3Utils: Manages atomic swaps and liquidity functions for Uniswap V3 positions, supporting Permit2 for enhanced security and flexibility.
7. LeverageTransformer: Enables leveraging and deleveraging of positions atomically, supporting both up and down leveraging.
8. AutoRange: Automatically adjusts the range of positions within a vault, managing risk and optimizing returns based on market conditions.
9. AutoCompound: Automatically compounds positions within a vault, maximizing returns based on market conditions.
10. Automator: Serves as a base class for automator contracts, handling operator roles, fees, and permissions.
11. AutoExit: Automatically exits positions in a v3 pool when certain conditions are met, managing risk and optimizing returns.

## Approach Taken to the Code Base

Modularity: The system imports and utilizes several external libraries and interfac

mapping(address => bool) public transformerAllowList;


JCK Q

## LOW Risk

| Number | Issue | Instances |
|--------|-------|-----------|
|[L-01]| Use of transfer instead of safeTransfer   | 4 |
|[L-02]| Use descriptive constant instead of 0 as a parameter  | 27 |
|[L-03]| Execution at deadlines should be allowed   | 3 |
|[L-04]| Constant array index within iteration  | 2 |
|[L-05]| payable function does not transfer ETH   | 2 |
|[L-06]| Functions calling contracts with transfer hooks are missing reentrancy guards   | 1 |
|[L-07]| Subtraction may underflow if multiplication is too large   | 3 |
|[L-08]| Use of abi.encodePacked with dynamic types inside keccak256   | 1 |
|[L-09]| Loss of precision on division   | 10 |
|[L-10]| No access control on receive/payable fallback   | 2 |
|[L-11]| Array is push()ed but not pop()ed   | 1 |
|[L-12]| Missing checks for address(0) when updating state variables   | 5 |
|[L-13]| Array lengths not checked   | 7 |
|[L-14]| Unused/empty receive/fallback   | 1 |
|[L-15]| Missing checks for address(0) in constructor   | 14 |
|[L-16]| Return values of approve not checked   | 6 |
|[L-17]| Use of ecrecover is susceptible to signature malleability   | 2 |
|[L-18]| Missing zero address check in initializer   | 2 |
|[L-19]| Named return variable used before assignment   | 1 |
|[L-20]| Revert on transfer to the zero address   | 2 |

## [L-01] Use of transfer instead of safeTransfer

It is good to add a require statement that checks the return value of token transfers, or to use something like OpenZeppelin's safeTransfer/safeTransferFrom, even if one is sure that the given token reverts in case of a failure.

This reduces the risk to zero even if these contracts are upgreadable, and it also helps with security reviews, as the auditor will not have to check this specific edge case.

```solidity
file:  main/packages/contracts/contracts/ActivePool.sol

381    IERC20(token).safeTransfer(cachedFeeRecipientAddress, amount);

```
https://github.com/code-423n4/2023-10-badger/blob/main/packages/contracts/contrac

file:  main/packages/contracts/contracts/CollSurplusPool.sol

148    IERC20(token).safeTransfer(feeRecipientAddress, amount);

// Code block 1 (solidity):
file:  main/packages/contracts/contracts/ActivePool.sol

381    IERC20(token).safeTransfer(cachedFeeRecipientAddress, amount);


0xSmartContractSamurai G

CHECKS TO HELP ME:

- [ ] Gas Optimization checks

    - [ ] Cache read variables in memory
    - [ ] Use `calldata` instead of `memory` if not modifying the function parameter passed
    - [ ] Don't call `view` function inside of another function
    - [ ] Use `++i` instead of `i++` (gas consumption order `i+=1` > `i=i+1` > `i++` > `++i`). Same for `--i`.
    - [ ] Use `unchecked` to not check for integer overflow and underflow if not required
    - [ ] Use `constant` and `immutable` for variables that don't change
    - [ ] Use `revert` instead of `require`
    - [ ] Create custom errors rather than `revert()`/`require()` strings to save gas
    - [ ] Put `require` statements on top in a function
    - [ ] Use `indexed` if less than three arguments are there in events for faster access
    - [ ] Use `!= 0` instead of `> 0` for unsigned integer comparison
    - [ ] `internal` functions not called by the contract should be removed
    - [ ] Cache array length outside of loop
    - [ ] Use `bytes` instead of `string`. Bytes constants are more efficient than string constants
    - [ ] Use external function modifier.
    - [ ] Use full 256 bit types unless packing with other variables.
    - [ ] Splitting `require` statements that use && saves gas.
    - [ ] State variables can be packed into fewer storage slots.
    - [ ] Use scratch space for keccak.
    - [ ] No Need to Allocate Unused Variable.
    - [ ] Use basis points for ratios.
    - [ ] Skip initializing default values.
    - [ ] Non-strict inequalities are cheaper than strict ones
    - [ ] Usage of `uint8` may increase gas cost
    - [ ] Use bytesN instead of bytes[]
    - [ ] Inline a modifier that’s only used once.
    - [ ] Inverting the condition of an [if-else-statement](https://gist.github.com/IllIllI000/44da6fbe9d12b9ab21af82f14add56b9) wastes gas.
    - [ ] [Consider having short revert strings.](https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc#consider-having-short-revert-strings)

1. should cache the length of the state variable struct, as well as some other gas optimizations.

https://github.com/code-423n4/2024-01-salty/blob/ce719503b43ebf086f5147c0f6efc3c0890bf0f9/src/dao/Proposals.sol#L423

2. can do `msb = msb + 128` instead as it uses less gas...same for all below...

https://github.com/code-423n4/2024-01-salty/blob/ce719503b43ebf086f5147c0f6efc3c0890bf0f9/src/pools/PoolMath.sol#L117-L127


0xnev G

### Summary

### Gas Optimizations
| |Issue|Instances|Total Gas Saved|
|-|:-|:-:|:-:|
| [G-01] | Multiple accesses of a storage variable should use a local variable cache | 1 |  - |
| [G-02] | Emit memory value instead of state variable | 4 |  388 |
| [G-03] | Sort solidity operations using short-circuit mode | 43 | at least 4171  |
| [G-04] | Functions guaranteed to revert when called by normal users can be marked payable | 17 |  357 |
| [G-05] | Refactor functions `adjustWeight` and `addDerivative` | 2 |  - |
| [G-06] | Shift checks before declaration for possible gas savings | 1 |  - |
| [G-07] | Consider declaring stack variables outside loop to save gas | 1 |  ~6 gas per loop |

| Total Possible Gas Savings | at least 4916 |
|:--:|:--:|

### [G-01] Multiple accesses of a storage variable should use a local variable cache

### Cache `STETH_TOKEN` in local variable
```solidity
2 results - 1 file

/WstEth.sol
56:    function withdraw(uint256 _amount) external onlyOwner
58:        uint256 stEthBal = IERC20(STETH_TOKEN).balanceOf(address(this));
59:        IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, stEthBal);
```

### Cache `LIDO_CRV_POOL` in local variable
```solidity
2 results - 1 file

/WstEth.sol
56:    function withdraw(uint256 _amount) external onlyOwner
59:        IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, stEthBal);
61:        IStEthEthPool(LIDO_CRV_POOL).exchange(1, 0, stEthBal, minOut);
```

### Cache `WST_ETH` in local variable
```solidity
3 results - 1 file

/WstEth.sol
73:    function deposit() external payable onlyOwner returns (uint256)
74:        uint256 wstEthBalancePre = IWStETH(WST_ETH).balanceOf(address(this));
76:        (bool sent, ) = WST_ETH.call{value: msg.value}("");
78:        uint256 wstEthBalancePost = IWStETH(WST_ETH).balanceOf(address(this));
```

### Cache `FRX_ETH_ADDRESS` in local variable
```solidity
/SfrxEth.sol
2 results - 1 file

60:    function withdraw(uint256 _amount) external onlyOwner
66:        uint256 frxEthBalance = IE

// Code block 1 (solidity):
2 results - 1 file

/WstEth.sol
56:    function withdraw(uint256 _amount) external onlyOwner
58:        uint256 stEthBal = IERC20(STETH_TOKEN).balanceOf(address(this));
59:        IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, stEthBal);

// Code block 2 (solidity):
2 results - 1 file

/WstEth.sol
56:    function withdraw(uint256 _amount) external onlyOwner
59:        IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, stEthBal);
61:        IStEthEthPool(LIDO_CRV_POOL).exchange(1, 0, stEthBal, minOut);

// Code block 3 (solidity):
3 results - 1 file

/WstEth.sol
73:    function deposit() external payable onlyOwner returns (uint256)
74:        uint256 wstEthBalancePre = IWStETH(WST_ETH).balanceOf(address(this));
76:        (bool sent, ) = WST_ETH.call{value: msg.value}("");
78:        uint256 wstEthBalancePost = IWStETH(WST_ETH).balanceOf(address(this));

2 results - 1 file

/WstEth.sol
56:    function withdraw(uint256 _amount) external onlyOwner
58:        uint256 stEthBal = IERC20(STETH_TOKEN).balanceOf(address(this));
59:        IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, stEthBal);

2 results - 1 file

/WstEth.sol
56:    function withdraw(uint256 _amount) external onlyOwner
59:        IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, stEthBal);
61:        IStEthEthPool(LIDO_CRV_POOL).exchange(1, 0, stEthBal, minOut);

3 results - 1 file

/WstEth.sol
73:    function deposit() external payable onlyOwner returns (uint256)
74:        uint256 wstEthBalancePre = IWStETH(WST_ETH).balanceOf(address(this));
76:        (bool sent, ) = WST_ETH.call{value: msg.value}("");
78:        uint256 wstEthBalancePost = IWStETH(WST_ETH).balanceOf(address(this));