codeislight Q

# Summary
## Quality Assurance Findings List:
| Number |Issues Details|Instances|
|:--:|:-------|:--:|
|[QA-1]| internal functions used only once | 4 |
|[QA-2]| Unnecessary return statement | 64 |
|[QA-3]| Using a consistent data type, instead of rounding up to uint256 | 1 |
|[QA-4]| The recovery mode is exclusive for CCR value | 1 |
|[QA-5]| Lack of sanity check for parameters being contracts | 45 |
|[QA-6]| Explicitly define the array size limit in the parameter | 1 |

### [QA-1] internal functions used only once

One of the main benefits of using internal function is the reusability aspect of it, but in the case being used in 1 function only, it would be better to move the logic to the function that is using it, Unless the purpose is to breakdown a long/complex logic into multiple parts.

3 instances - 3 files

instances:
- LUSDToken._requireCallerIsStabilityPool()
- LUSDToken._requireCallerIsTroveMorSP()
- LUSDToken._requireMintingNotPaused()
- ActivePool._requireCallerIsBorrowerOperationsOrDefaultPool()
- BorrowerOperations._getCollChange()
- BorrowerOperations._updateTroveFromAdjustment()
- BorrowerOperations._moveTokensAndCollateralfromAdjustment()
- BorrowerOperations._requireValidCollateralAddress()
- StabilityPool._updateG()
...

### [QA-2] Unnecessary return statement

in the ITroveManager interface, we have updateTroveRewardSnapshots() function prototype doesn't return any value, but in the implementaion in TroveManager.updateTroveRewardSnapshots(), it does have a return statmenet, despite that _updateTroveRewardSnapshots() doesn't return any value.

```solidity
    function updateTroveRewardSnapshots(address _borrower, address _collateral)
        external
        override
    {
        _requireCallerIsBorrowerOperations();

        return _updateTroveRewardSnapshots(_borrower, _collateral);
    }

    function _updateTroveRewardSnapshots(address _borrower, address _collateral)
        internal
    {
        rewardSnapshots[_borrower][_collater

    function updateTroveRewardSnapshots(address _borrower, address _collateral)
        external
        override
    {
        _requireCallerIsBorrowerOperations();

        return _updateTroveRewardSnapshots(_borrower, _collateral);
    }

    function _updateTroveRewardSnapshots(address _borrower, address _collateral)
        internal
    {
        rewardSnapshots[_borrower][_collateral].collAmount = L_Collateral[
            _collateral
        ];
        rewardSnapshots[_borrower][_collateral].LUSDDebt = L_LUSDDebt[
            _collateral
        ];
        emit TroveSnapshotsUpdated(
            _collateral,
            L_Collateral[_collateral],
            L_LUSDDebt[_collateral]
        );
    }

    function getTroveStatus(address _borrower, address _collateral)
        external
        view
        override
        returns (uint256)
    {
        // tbd QA return uint8 instead of uint256, to maintain a consistency since enum are uint8 based
        return uint256(Troves[_borrower][_collateral].status);
    }


halden G

## [G-01] Use assembly to check for address(0). Missed in the C4udit output
File CashManager.sol: [141](https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/CashManager.sol#L141), [144](https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/CashManager.sol#L144), [147](https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/CashManager.sol#L147), [150](https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/CashManager.sol#L150), [150](https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/CashManager.sol#L150)

File KYCRegistryClient.sol: [40](https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/kyc/KYCRegistryClient.sol#L40)

File CashKYCSender.sol: [68](https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/token/CashKYCSender.sol#L68)

File CashKYCSenderReceiver.sol: [68](https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/token/CashKYCSenderReceiver.sol#L68), [76](https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/token/CashKYCSenderReceiver.sol#L76)

File KYCRegistryClient.sol: [40](https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/kyc/KYCRegistryClient.sol#L40)

## [G-02] Cache storage values in memory to minimize SLOADs
The code can be optimized by minimising the number of SLOADs. Storage value should get cached in memory if they occur more than once.

1) Use `_epochDuration` instead of `epochDuration` [176](https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/CashManager.sol#L176)
Recommended:
```
(block.timestamp % _epochDuration);
```
2) Cache `epochToExchangeRate[epochToClaim] ` in memory. If the check for equal to zero is false we can save 2 SLOAD. One in line 266 and another in 492. [249](https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/cash/CashManager.sol#L249)
Recommended:
```
uint256 rate = epochToExchangeRate[epochToClaim];
if (rate == 0) { // line 249
     revert ExchangeRa

uint256 rate = epochToExchangeRate[epochToClaim];
if (rate == 0) { // line 249
     revert ExchangeRateNotSet();
}

emit MintCompleted(
    user,
    cashOwed,
    collateralDeposited,
    rate // line 266
    epochToClaim
);

function _getMintAmountForEpoch(
    uint256 collateralAmountIn,
    uint256 rate // line 489
  ) private view returns (uint256 cashAmountOut) {
    uint256 amountE24 = _scaleUp(collateralAmountIn) * 1e6;
    cashAmountOut = amountE24 / rate; // line 492
}

// Code block 1 (unknown):
(block.timestamp % _epochDuration);

CashManager.sol#L141, CashManager.sol#L144, CashManager.sol#L147, CashManager.sol#L150, KYCRegistryClient.sol#L40, CashKYCSender.sol#L68, CashKYCSenderReceiver.sol#L68, CashKYCSenderReceiver.sol#L76, CashManager.sol#L176, CashManager.sol#L249

CashManager.sol, CashKYCSenderReceiver.sol, CashKYCSender.sol, KYCRegistryClient.sol


ansoncrypto007 G

### Emitting a storage value instead of memory one.
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Vault/contracts/ReaperVaultV2.sol#L581

```
578    function updateTvlCap(uint256 _newTvlCap) public {
579        _atLeastRole(ADMIN);
580        tvlCap = _newTvlCap;
581        emit TvlCapUpdated(tvlCap);
582    }
```

### Reading a storage variable twice. Instead, a local variable could be used.
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Vault/contracts/ReaperVaultV2.sol#L395
##### ReaperVaultV2.sol._withdraw(): strategyBal should be used when updating strategies[stratAddr].allocated.
```
379                uint256 strategyBal = strategies[stratAddr].allocated;
380                if (strategyBal == 0) {
381                 continue;
382                }
383
384                uint256 remaining = value - vaultBalance;
385                uint256 loss = IStrategy(stratAddr).withdraw(Math.min(remaining, strategyBal));
386                uint256 actualWithdrawn = token.balanceOf(address(this)) - vaultBalance;
387
388                // Withdrawer incurs any losses from withdrawing as reported by strat
389                if (loss != 0) {
390                    value -= loss;
391                    totalLoss += loss;
392                    _reportLoss(stratAddr, loss);
393                }
394
395                strategies[stratAddr].allocated -= actualWithdrawn;
```
'-=' reads `strategies[stratAddr].allocated` one more time. Instead, `strategyBal` could be used.
`strategies[stratAddr].allocated = strategyBal - actualWithdrawn;`

https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Vault/contracts/ReaperVaultV2.sol#L156
##### ReaperVaultV2.sol.addStrategy(): totalAllocBPS should be cached in memory.
```
156        require(_allocBPS + totalAllocBPS <= PERCENT_DIVISOR, "Invalid allocBPS value");
156
158        strategies[_strategy] = StrategyParams({
159            activation: block.timestamp,
160            feeBPS: _feeBPS,
161

// Code block 1 (unknown):
578    function updateTvlCap(uint256 _newTvlCap) public {
579        _atLeastRole(ADMIN);
580        tvlCap = _newTvlCap;
581        emit TvlCapUpdated(tvlCap);
582    }

// Code block 2 (unknown):
379                uint256 strategyBal = strategies[stratAddr].allocated;
380                if (strategyBal == 0) {
381                 continue;
382                }
383
384                uint256 remaining = value - vaultBalance;
385                uint256 loss = IStrategy(stratAddr).withdraw(Math.min(remaining, strategyBal));
386                uint256 actualWithdrawn = token.balanceOf(address(this)) - vaultBalance;
387
388                // Withdrawer incurs any losses from withdrawing as reported by strat
389                if (loss != 0) {
390                    value -= loss;
391                    totalLoss += loss;
392                    _reportLoss(stratAddr, loss);
393                }
394
395                strategies[stratAddr].allocated -= actualWithdrawn;


hl_ G

# Table of contents

- [G-01] Use a more recent version of solidity
- [G-02] x += y costs more gas than x = x + y for state variables
- [G-03] Duplicated require() / revert() checks should be refactored to a modifier or function
- [G-04] Structs can be packed into fewer storage slots
- [G-05] Use Shift Right/Left instead of Division/Multiplication
- [G-06] Use constants instead of type(uintx).max
- [G-07] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead
- [G-08] Use double require instead of using &&
- [G-09] Use nested if and avoid multiple check combinations
- [G-10] Sort Solidity operations using short-circuit mode
- [G-11] Optimize names to save gas

## [G-01] Use a more recent version of solidity

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 and get bytes.concat() instead of abi.encodePacked(<bytes>,<bytes>.
Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value.
Use a solidity version of at least 0.8.12 to get string.concat() instead of abi.encodePacked (<str>,<str>)

```
LUSDToken.sol
3:    pragma solidity 0.6.11;
```

```
ReaperVaultERC4626.sol
3: pragma solidity ^0.8.0;
```

## [G-02] x += y costs more gas than x = x + y for state variables

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

For example:
```
File:   2023-02-ethos/Ethos-Vault/contracts/ReaperVaultV2.sol
168:    totalAllocBPS += _allocBPS;
196:    totalAllocBPS += _allocBPS;
```

## [G-03] Duplicated require() / revert() checks should be refactored to a modifier or function

Saves deployment costs

```
File:    2023-02-ethos/Ethos-Vault/contracts/ReaperVaultV2.sol
180:    require(strategies[_strategy].ac

// Code block 1 (unknown):
LUSDToken.sol
3:    pragma solidity 0.6.11;

// Code block 2 (unknown):
ReaperVaultERC4626.sol
3: pragma solidity ^0.8.0;

// Code block 3 (unknown):
File:   2023-02-ethos/Ethos-Vault/contracts/ReaperVaultV2.sol
168:    totalAllocBPS += _allocBPS;
196:    totalAllocBPS += _allocBPS;


bigtone G

## Gas Optimizations

| |Issue|Instances|
|-|:-|:-:|
| [GAS-1](#GAS-1) | Use the returns variable directly instead of declaring the variables | 3 |

### [GAS-1] Use the returns variable directly instead of declaring the variables

*Instances (3)*:
```diff
File: https://github.com/code-423n4/2023-07-amphora/blob/main/core/solidity/contracts/core/AMPHClaimer.sol

-171:         uint256 _cvxRewardsFeeToExchange = _totalToFraction(_cvxTotalRewards, cvxRewardFee);
+171:         _cvxAmountToSend = _totalToFraction(_cvxTotalRewards, cvxRewardFee);

-172:         uint256 _crvRewardsFeeToExchange = _totalToFraction(_crvTotalRewards, crvRewardFee);
+172:         _crvAmountToSend = _totalToFraction(_crvTotalRewards, crvRewardFee);

-174:         uint256 _amphByCrv = _calculate(_crvRewardsFeeToExchange);
+174:         _claimableAmph = _calculate(_crvAmountToSend);

176:            // Check if all cliffs consumed
177:            if (_getCliff((_claimableAmph / 1e12) + distributedAmph) >= TOTAL_CLIFFS) return (0, 0, 0);
178:
179:            // check for rounding errors
180:            if (_claimableAmph == 0) return (0, 0, 0);
181:
182:            if (_amphBalance >= _claimableAmph) {
-184:      _cvxAmountToSend = _cvxRewardsFeeToExchange;
-185:      _crvAmountToSend = _crvRewardsFeeToExchange;
-186:      _claimableAmph = _amphByCrv;
```

// Code block 1 (diff):
File: https://github.com/code-423n4/2023-07-amphora/blob/main/core/solidity/contracts/core/AMPHClaimer.sol

-171:         uint256 _cvxRewardsFeeToExchange = _totalToFraction(_cvxTotalRewards, cvxRewardFee);
+171:         _cvxAmountToSend = _totalToFraction(_cvxTotalRewards, cvxRewardFee);

-172:         uint256 _crvRewardsFeeToExchange = _totalToFraction(_crvTotalRewards, crvRewardFee);
+172:         _crvAmountToSend = _totalToFraction(_crvTotalRewards, crvRewardFee);

-174:         uint256 _amphByCrv = _calculate(_crvRewardsFeeToExchange);
+174:         _claimableAmph = _calculate(_crvAmountToSend);

176:            // Check if all cliffs consumed
177:            if (_getCliff((_claimableAmph / 1e12) + distributedAmph) >= TOTAL_CLIFFS) return (0, 0, 0);
178:
179:            // check for rounding errors
180:            if (_claimableAmph == 0) return (0, 0, 0);
181:
182:            if (_amphBalance >= _claimableAmph) {
-184:      _cvxAmountToSend = _cvxRewardsFeeToExchange;
-185:      _crvAmountToSend = _crvRewardsFeeToExchange;
-186:      _claimableAmph = _amphByCrv;


Myrault G

## There is duplicate code in the constructor of AiArenaHelper.sol, resulting in unnecessary gas consumption

## POC

The following code exists in the constructor of the AiArenaHelper contract:
```
//AiArenaHelper.sol
constructor(uint8[][] memory probabilities) {
        _ownerAddress = msg.sender;

        // Initialize the probabilities for each attribute
        addAttributeProbabilities(0, probabilities);// @audit：init

        uint256 attributesLength = attributes.length;
        for (uint8 i = 0; i < attributesLength; i++) {
            attributeProbabilities[0][attributes[i]] = probabilities[i]; // Unnecessary gas consumption
            attributeToDnaDivisor[attributes[i]] = defaultAttributeDivisor[i];
        }
    }
```

The addAttributeProbabilities function is called for initialization, but this sentence duplicates the function of the first sentence in the for loop, resulting in unnecessary gas consumption. The implementation of the addAttributeProbabilities function is as follows:

```
/// @notice Add attribute probabilities for a given generation.
     /// @dev Only the owner can call this function.
     /// @param generation The generation number.
     /// @param probabilities An array of attribute probabilities for the generation.
    function addAttributeProbabilities(uint256 generation, uint8[][] memory probabilities) public {
        require(msg.sender == _ownerAddress);
        require(probabilities.length == 6, "Invalid number of attribute arrays");

        uint256 attributesLength = attributes.length;
        for (uint8 i = 0; i < attributesLength; i++) {
            attributeProbabilities[generation][attributes[i]] = probabilities[i];
        }
    }
```

## Recommended Mitigation Steps

```
diff --git a/src/AiArenaHelper.sol b/src/AiArenaHelper.sol
index b93dde8..14527fd 100644
--- a/src/AiArenaHelper.sol
+++ b/src/AiArenaHelper.sol
@@ -42,7 +42,8 @@ contract AiArenaHelper {
         _ownerAddress = msg.sender;

         // Initialize the p

// Code block 1 (unknown):
//AiArenaHelper.sol
constructor(uint8[][] memory probabilities) {
        _ownerAddress = msg.sender;

        // Initialize the probabilities for each attribute
        addAttributeProbabilities(0, probabilities);// @audit：init

        uint256 attributesLength = attributes.length;
        for (uint8 i = 0; i < attributesLength; i++) {
            attributeProbabilities[0][attributes[i]] = probabilities[i]; // Unnecessary gas consumption
            attributeToDnaDivisor[attributes[i]] = defaultAttributeDivisor[i];
        }
    }

// Code block 2 (unknown):
/// @notice Add attribute probabilities for a given generation.
     /// @dev Only the owner can call this function.
     /// @param generation The generation number.
     /// @param probabilities An array of attribute probabilities for the generation.
    function addAttributeProbabilities(uint256 generation, uint8[][] memory probabilities) public {
        require(msg.sender == _ownerAddress);
        require(probabilities.length == 6, "Invalid number of attribute arrays");

        uint256 attributesLength = attributes.length;
        for (uint8 i = 0; i < attributesLength; i++) {
            attributeProbabilities[generation][attributes[i]] = probabilities[i];
        }
    }


0x11singh99 G

# Gas Optimizations

**Note : _G-08_ and _G-09_ contains only those instances which were missed by bot. Since they are major gas savings so I included those missed instances**
| Number | Issue |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |
| [[G-01](#g-01-pack-structs-by-reducing-variable-sizes-to-save-storage-slots-saves-12000-gas)] | Pack structs by reducing variable sizes to save storage slots (saves ~12000 Gas) |
| [[G-02](#g-02-state-variables-can-be-packed-into-fewer-storage-slot-saves-8000-gas)] | State variables can be packed into fewer storage slot (saves ~8000 Gas) |
| [[G-03](#g-03-restructure-nftsclaimed-mapping-elements-and-make-struct-elementsaves-2000-gas)] | Restructure `nftsClaimed` mapping elements and make struct element(saves ~2000 Gas) |
| [[G-04](#g-04-remove-unnecessary-isselectioncomplete-mapping-from-mergingpool-contractsaves-22100-gas)] |Remove unnecessary `isSelectionComplete` mapping from `MergingPool` contract(saves ~22100 Gas) |
| [[G-05](#g-05-remove-unnecessary-addattributeprobabilities-function-call-in-constructor)] | Remove unnecessary `addAttributeProbabilities` function call in `constructor` |
| [[G-06](#g-06-refactor-code-to-save-gassaves-100-gas)] | `Refactor` code to save gas(saves ~100 Gas) |
| [[G-07](#g-07-external-calls-should-be-cached-instead-of-re-calling-the-same-function-in-the-same-transaction)] | `External` calls should be `cached` instead of re-calling the same `function` in the same transaction |
| [[G-08](#g-08-dont-read-state-variable-in-loopinstances-missed-by-bot-report)] | Don't read `state` variable in `loop`(_Instances missed by bot report_) |
| [[G-09](#g-09-update-state-variable-outside-of-the-loop-after-accumulating-results-in-stack-variableinstance

File : src/FighterOps.sol

26:  struct FighterPhysicalAttributes {
27:        uint256 head;
28:        uint256 eyes;
29:        uint256 mouth;
30:        uint256 body;
31:        uint256 hands;
32:        uint256 feet;
33:    }

### `dailyAllowance` and `transferable` can be packed in single storage slot `SAVES: 2000 GAS, 1 SLOT`

`dailyAllowance` can easily reduced to `uint16` since in [GameItems::createGameItem](https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol#L208C5-L229C11) function the input parameter type for `dailyAllowance` is uint16. And after assigning it this variable never updated. So `uint16` is more than sufficient to hold it.

[GameItems::createGameItem](https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol#L208C5-L229C11) function


chaduke Q

QA1. Zero address check is recommended for the input:

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

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

QA2.A range check is recommended for the input:
[https://github.com/code-423n4/2023-04-caviar/blob/cd8a92667bcb6657f70657183769c244d04c015c/src/Factory.sol#L141-L143](https://github.com/code-423n4/2023-04-caviar/blob/cd8a92667bcb6657f70657183769c244d04c015c/src/Factory.sol#L141-L143)

QA3. One should check whether tokenIds.length == tokenWeights.length.

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

QA4. Maybe ``bytes.concat`` is not needed here and the calculation can be simplified:

```diff
- leafs[i] = keccak256(bytes.concat(keccak256(abi.encode(tokenIds[i], tokenWeights[i]))));
+ leafs[i] = keccak256(abi.encode(tokenIds[i], tokenWeights[i]));

```

QA5. There is an error in the NatSpec of ``change()``:

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

Mitigation: the correction should be
```diff
- The sum of the caller's NFT weights must be less than or equal to the sum of the
    /// output pool NFTs weights.

+ The sum of the caller's NFT weights must be greater than or equal to the sum of the
    /// output pool NFTs weights.
```

QA6. The ``buy()

QA6. The ``buy()`` function fails to verify that a private pool only supports ETH as base tokens.
Similarly, the ``deposit()`` function does not check this either.

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

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

Mitigation: check whether the private pool only supports ETH as base tokens:

// Code block 1 (diff):
- leafs[i] = keccak256(bytes.concat(keccak256(abi.encode(tokenIds[i], tokenWeights[i]))));
+ leafs[i] = keccak256(abi.encode(tokenIds[i], tokenWeights[i]));

// Code block 2 (diff):
- The sum of the caller's NFT weights must be less than or equal to the sum of the
    /// output pool NFTs weights.

+ The sum of the caller's NFT weights must be greater than or equal to the sum of the
    /// output pool NFTs weights.

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

- The sum of the caller's NFT weights must be less than or equal to the sum of the
    /// output pool NFTs weights.

+ The sum of the caller's NFT weights must be greater than or equal to the sum of the
    /// output pool NFTs weights.

Factory.sol#L129-L131, Factory.sol#L135-L137, Factory.sol#L141-L143, PrivatePool.sol#L661-L687, PrivatePool.sol#L375-L377, EthRouter.sol#L129, EthRouter.sol#L247


0xta G

# Gas Optimization

# Summary

| Number | Gas Optimization                                                                                | Context |
| :----: | :----------------------------------------------------------------------------------------------------------------- | :-----: |
| [G-01] | Use hardcode address instead address(this)                                                                         |   23    |
| [G-02] | Do not calculate constants                                                                                |    1    |
| [G-03] | Shorten the array rather than copying to a new one                                                                 |    6    |
| [G-04] | Internal functions only called once can be inlined to save gas                                                     |   76    |
| [G-05] | Splitting require() statements that use && saves gas                                                               |    9    |
| [G-06] | MULTIPLE ADDRESS/ID MAPPINGS CAN BE COMBINED INTO A SINGLE MAPPING OF AN ADDRESS/ID TO A STRUCT, WHERE APPROPRIATE |    4    |
| [G-07] | BEFORE SOME FUNCTIONS, WE SHOULD CHECK SOME VARIABLES FOR POSSIBLE GAS SAVE                                        |    7    |
| [G-08] | Use Assembly to write Address Storage Value                                                                        |   25    |
| [G-09] | A modifier used only once and not being inherited should be inlined to save gas                                    |    1    |
| [G-10] | Using storage instead of memory for Structs/Arrays Save Gas                                                        |   10    |
| [G-11] | Use constants instead of type(uintx).max                                                                           |   12    |
| [G-12] | Using delete statement can save gas                                                                                |    9    |
| [G-13] | Using fixed bytes is cheap

File: contracts/contracts/LeverageMacroBase.sol

131                address(this),

143            initialCdpIndex = sortedCdps.cdpCountOf(address(this));

149                IERC3156FlashBorrower(address(this)),

156                IERC3156FlashBorrower(address(this)),

173            bytes32 cdpId = sortedCdps.cdpOfOwnerByIndex(address(this), initialCdpIndex);

220        uint256 ebtcBal = ebtcToken.balanceOf(address(this));

221        uint256 collateralBal = stETH.sharesOf(address(this));

352        require(initiator == address(this), "LeverageMacroReference: wrong initiator for flashloan");

441                    IERC20(swapChecks[i].tokenToCheck).balanceOf(address(this)) >

456        require(addy != address(this)); // If it could call this it could fake the forwarded caller

File: contracts/contracts/ActivePool.sol

283        collateral.transferFrom(address(receiver), address(this), amountWithFee);

295            collateral.balanceOf(address(this)) >= collateral.getPooledEthByShares(systemCollShares),

299            collateral.sharesOf(address(this)) >= systemCollShares,

337        return collateral.balanceOf(address(this));

376        uint256 balance = IERC20(token).balanceOf(address(this));


0xSmartContract Q

## Summary
### Issues List
| Number |Issues Details|Context|
|:--:|:-------|:--:|
|[L-01]|Calculation of flash loan fee should be rounded up | 1 |
|[L-02]|Even if there is a fee of `msg.value`, the transaction will be reverted | 1 |
|[L-03]|Missing ReEntrancy Guard to ` safeTransferFrom `in functions | 6 |
|[L-04]|Prevent division by `0`|2|
|[L-05]|`PrivatePoolMetadata.tokenURI` is not compliant with EIP721 |1|
|[L-06]|Use ```safeTransferOwnership``` instead of ```transferOwnership``` function | 1 |
|[L-07]|Use Fuzzing Test for math code bases | All Contracts |
|[L-08]|Project Upgrade and Stop Scenario should be | All Contracts |
|[L-09]|Insufficient coverage| All Contracts |
|[L-10]|Add a timelock to critical functions| 1 |
|[L-11] |Loss of precision due to rounding|1|
|[L-12] |Should an airdrop token arrive on the `private.sol` contract, it will be stuck| 1|
|[L-13] |Add to _blacklist function_| 1|
|[L-14]|Function Calls in Loop Could Lead to Denial of Service due to Array length not being checked| 1 |
|[NC-15] |Constants on the left are better| 8 |
|[NC-16] |`Function writing` that does not comply with the `Solidity Style Guide`| All Contracts |
|[NC-17] |Include return parameters in NatSpec comments| All Contracts |
|[NC-18] |Tokens accidentally sent to the contract cannot be recovered | 1 |
|[NC-19] |` sell ` event is missing parameters| 1 |
|[NC-20] |Assembly Codes Specific – Should Have Comments | 1|
|[NC-21] |For functions, follow Solidity standard naming conventions (internal function style rule)| 1 |
|[NC-22] |Floating pragma| 5 |
|[NC-23] |Use SMTChecker| All Contract |
|[NC-24] |Lines are too long| 3 |
|[NC-25] |Omissions in Events| 8 |
|[NC-26] |Missing Event for  initialize|3|

Total 26 issues

### [L-01] Calculation of flash loan fee should be rounded up

Math rounding should never favor the user, the calculation of flash load fee should be rounded up..

```solidity
    uint56 public changeFee;

src/PrivatePool.sol:
  632:         uint256 fee = fla

    uint56 public changeFee;

src/PrivatePool.sol:
  632:         uint256 fee = flashFee(token, tokenId);

 function flashFee(address, uint256) public view returns (uint256) {
        return changeFee;
    }

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

uint256 public changeFee;

function flashFee(address token, uint256 tokenId) public view returns (uint256) {
    uint256 fee = changeFee;
    uint256 amount = getFlashLoanAmount(token, tokenId);
    uint256 feeAmount = amount * fee / 1e18;
    return Math.ceil(feeAmount); // Round up the fee calculation
}


ktg Q

### L-01: AutoRange incorrect maxAmountAdd calculation
Function `AutoRange.execute` currently calculates maxAmountAdd as follows:
```solidity
// max amount to add - removing max potential fees (if config.onlyFees - the have been removed already)
            state.maxAddAmount0 = config.onlyFees ? state.amount0 : state.amount0 * Q64 / (params.rewardX64 + Q64);
            state.maxAddAmount1 = config.onlyFees ? state.amount1 : state.amount1 * Q64 / (params.rewardX64 + Q64);
```
In case `onlyFees = False`, the maxAddAmount is calculated as `state.amount * (Q64)/ (rewardX64 + Q64)`, this is for `removing max potentials fees` (to keep enough token as fee for operator). However, this calculation is wrong for large `rewardX64`, since `rewardX64` reflect the percentage at which the reward is calculated, then the formula must be `1 - (rewardX64)/Q64`

So if `rewardX64` = 20%, then `maxAddAmount` should be 80%, but in fact, it's calculated as  `1/(1+0.2)` = 83.3%.

### L-02: AutoExit does not work with Revert Lend position
`AutoExit` does not work with Revert Lend position, function `configToken` will revert if `msg.sender` is not the owner:
```solidity
address owner = nonfungiblePositionManager.ownerOf(tokenId);
        if (owner != msg.sender) {
            revert Unauthorized();
        }
```
So after transferring NFT to Revert Lend Vault, `AutoExit` cannot be used.
Function `execute` also allow only `operator` to call, not `vault`:
```solidity
function execute(ExecuteParams calldata params) external {
        if (!operators[msg.sender]) {
            revert Unauthorized();
        }
...
```

### L-03: `Automator.setVault` should check if the vault has the same NonfungiblePositionManager with the automator
```solidity
function setVault(address _vault, bool _active) public onlyOwner {
        emit VaultChanged(_vault, _active);
        vaults[_vault] = _active;
    }
```
Function `setVault` does not check if `_vault` having the same `nonfungiblePositionManager` with the

// Code block 1 (solidity):
// max amount to add - removing max potential fees (if config.onlyFees - the have been removed already)
            state.maxAddAmount0 = config.onlyFees ? state.amount0 : state.amount0 * Q64 / (params.rewardX64 + Q64);
            state.maxAddAmount1 = config.onlyFees ? state.amount1 : state.amount1 * Q64 / (params.rewardX64 + Q64);

// Code block 2 (solidity):
address owner = nonfungiblePositionManager.ownerOf(tokenId);
        if (owner != msg.sender) {
            revert Unauthorized();
        }

// Code block 3 (solidity):
function execute(ExecuteParams calldata params) external {
        if (!operators[msg.sender]) {
            revert Unauthorized();
        }
...

// Code block 4 (solidity):
function setVault(address _vault, bool _active) public onlyOwner {
        emit VaultChanged(_vault, _active);
        vaults[_vault] = _active;
    }

// max amount to add - removing max potential fees (if config.onlyFees - the have been removed already)
            state.maxAddAmount0 = config.onlyFees ? state.amount0 : state.amount0 * Q64 / (params.rewardX64 + Q64);
            state.maxAddAmount1 = config.onlyFees ? state.amount1 : state.amount1 * Q64 / (params.rewardX64 + Q64);

address owner = nonfungiblePositionManager.ownerOf(tokenId);
        if (owner != msg.sender) {
            revert Unauthorized();
        }

function execute(ExecuteParams calldata params) external {
        if (!operators[msg.sender]) {
            revert Unauthorized();
        }
...

function setVault(address _vault, bool _active) public onlyOwner {
        emit VaultChanged(_vault, _active);
        vaults[_vault] = _active;
    }


K42 G

## Gas Optimization Report for [Ondo](https://github.com/code-423n4/2023-09-ondo) by K42

### Possible Optimization in [SourceBridge.sol](https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/SourceBridge.sol)

Possible Optimization 1 =
- Use ``immutable`` for [destChainToContractAddr mapping](https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/SourceBridge.sol#L15) if the ``mapping`` is not expected to change after contract deployment.

Here is the optimized code snippet:

```solidity
mapping(string => string) public immutable destChainToContractAddr;
```

- Estimated gas saved = Around ~100 gas for ``SLOAD`` opcode savings per access.

Possible Optimization 2 =
- In [burnAndCallAxelar()](https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/SourceBridge.sol#L61C1-L82C4) you could batch multiple state variable updates into a single external function call, to reduce the gas cost of multiple ``SSTORE`` operations.

Here is the optimized code:

```solidity
function burnAndCallAxelar(
    uint256 amount,
    string calldata destinationChain,
    uint256 newNonce
) external payable whenNotPaused {
    // ... existing logic
    nonce = newNonce;
}
```

- Estimated gas saved = Around ~5000 gas for reduced ``SSTORE`` operations.

Possible Optimization 3 =
- You could batch ``msg.sender`` and ``msg.value`` into a single ``struct`` to reduce stack depth.

After Optimization:

```solidity
struct MsgData {
    address sender;
    uint256 value;
}

function burnAndCallAxelar(
    uint256 amount,
    string calldata destinationChain,
    MsgData calldata msgData
) external payable whenNotPaused {
    // Then use msgData.sender and msgData.value instead of msg.sender and msg.value here
}
```

- Estimated gas saved = ~1000-2000 gas (stack operations usually cost around 3 gas for PUSH and 3 gas for POP).

Possible Optimization 4 =
- Use ``bytes32`` for [destinationChain](https://github.com/code-423n4/2023-09

// Code block 1 (solidity):
mapping(string => string) public immutable destChainToContractAddr;

// Code block 2 (solidity):
function burnAndCallAxelar(
    uint256 amount,
    string calldata destinationChain,
    uint256 newNonce
) external payable whenNotPaused {
    // ... existing logic
    nonce = newNonce;
}

// Code block 3 (solidity):
struct MsgData {
    address sender;
    uint256 value;
}

function burnAndCallAxelar(
    uint256 amount,
    string calldata destinationChain,
    MsgData calldata msgData
) external payable whenNotPaused {
    // Then use msgData.sender and msgData.value instead of msg.sender and msg.value here
}

mapping(string => string) public immutable destChainToContractAddr;

function burnAndCallAxelar(
    uint256 amount,
    string calldata destinationChain,
    uint256 newNonce
) external payable whenNotPaused {
    // ... existing logic
    nonce = newNonce;
}

struct MsgData {
    address sender;
    uint256 value;
}

function burnAndCallAxelar(
    uint256 amount,
    string calldata destinationChain,
    MsgData calldata msgData
) external payable whenNotPaused {
    // Then use msgData.sender and msgData.value instead of msg.sender and msg.value here
}


report

---
sponsor: "Revert Lend"
slug: "2024-03-revert-lend"
date: "2024-05-22"
title: "Revert Lend"
findings: "https://github.com/code-423n4/2024-03-revert-lend-findings/issues"
contest: 342
---

# Overview

## About C4

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

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

During the audit outlined in this document, C4 conducted an analysis of the Revert Lend smart contract system written in Solidity. The audit took place between March 4 — March 15, 2024.

Following the C4 audit, 3 wardens ([b0g0](https://code4rena.com/@b0g0), [ktg](https://code4rena.com/@ktg) and [thank\_you](https://code4rena.com/@thank_you)) reviewed the mitigations for all identified issues; the [mitigation review report](#mitigation-review) is appended below the audit report.

## Wardens

105 Wardens contributed reports to the Revert Lend:

  1. [b0g0](https://code4rena.com/@b0g0)
  2. [0xjuan](https://code4rena.com/@0xjuan)
  3. [ktg](https://code4rena.com/@ktg)
  4. [thank\_you](https://code4rena.com/@thank_you)
  5. [Aymen0909](https://code4rena.com/@Aymen0909)
  6. [grearlake](https://code4rena.com/@grearlake)
  7. [cryptphi](https://code4rena.com/@cryptphi)
  8. [Giorgio](https://code4rena.com/@Giorgio)
  9. [lanrebayode77](https://code4rena.com/@lanrebayode77)
  10. [Bauchibred](https://code4rena.com/@Bauchibred)
  11. [falconhoof](https://code4rena.com/@falconhoof)
  12. [0xAlix2](https://code4rena.com/@0xAlix2) ([a\_kalout](https://code4rena.com/@a_kalout) and [ali\_shehab](https://code4rena.com/@ali_shehab))
  13. [santiellena](https://code4rena.com/@santiellena)
  14. [FastChecker](https://code4rena.com/@FastChecker)
  15. [Arz](https://code4rena.com/@Arz)
  16. [14si2o\_Flint](https://code4rena.com/@14si2

    if (params.permitData.length > 0) {
        (ISignatureTransfer.PermitTransferFrom memory permit, bytes memory signature) =
            abi.decode(params.permitData, (ISignatureTransfer.PermitTransferFrom, bytes));
        permit2.permitTransferFrom(
            permit,
            ISignatureTransfer.SignatureTransferDetails(address(this), state.liquidatorCost),
            msg.sender,
            signature
        );
    } else {
        // take value from liquidator
        SafeERC20.safeTransferFrom(IERC20(asset), msg.sender, address(this), state.liquidatorCost);
    }

interface ISignatureTransfer is IEIP712 {
    /// @notice The token and amount details for a transfer signed in the permit transfer signature
    struct TokenPermissions {
        // ERC20 token address
        address token;
        // the maximum amount that can be spent
        uint256 amount;
    }

    /// @notice The signed permit message for a single token transfer
    struct PermitTransferFrom {
        TokenPermissions permitted;
        // a unique value for every token owner's signature to prevent signature replays
        uint256 nonce;
        // deadline on the permit signature
        uint256 deadline;
    }
}


0xkeesmark G

# [G-01]++i costs less gas than i++
## Lines of code
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L71
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L84
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L113
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L140
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L147
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L171
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L191

## Proof of Concept
Saves 6 gas per loop.

## Recommended Mitigation Steps
```
- i++
+ ++i
```

# [G-02]Using unchecked
## Lines of code
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L71
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L84
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L113
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L140
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L147
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L171
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L191

## Proof of Concept
Use unchecked for arithmetic where you are sure it won't over or underflow, saving gas costs for checks added from solidity v0.8.0.

the variable i cannot overflow because of the condition i < length.

## Recommended Mitigation Steps
Consider to use below.
```
- for (uint i = 0; i < derivativeCount; i++)
+ for (uint i = 0; i < derivativeCount;)
  // something...
+  unchecked {
+	   i++;
+	 }
  }
```

# [G-03] It costs more gas to initialize variables to zero than to let the default of zero be applied

## L

// Code block 1 (unknown):
- i++
+ ++i

// Code block 2 (unknown):
- for (uint i = 0; i < derivativeCount; i++)
+ for (uint i = 0; i < derivativeCount;)
  // something...
+  unchecked {
+	   i++;
+	 }
  }

SafEth.sol#L71, SafEth.sol#L84, SafEth.sol#L113, SafEth.sol#L140, SafEth.sol#L147, SafEth.sol#L171, SafEth.sol#L191


tabriz Q

## [L-01] Stop Using Solidity's transfer() Now
**Proof Of Concept**
https://consensys.io/diligence/blog/2019/09/stop-using-soliditys-transfer-now/
```
  collateral.transfer(address(receiver), amount);
```
https://github.com/code-423n4/2023-10-badger/blob/main/packages/contracts/contracts/ActivePool.sol#L274C7-L274C56

```
  collateral.transfer(feeRecipientAddress, fee);
```
https://github.com/code-423n4/2023-10-badger/blob/main/packages/contracts/contracts/ActivePool.sol#L286C7-L286C55

```
 function transfer(address recipient, uint256 amount) external override returns (bool) {
```
https://github.com/code-423n4/2023-10-badger/blob/main/packages/contracts/contracts/EBTCToken.sol#L119C4-L119C92

```
  emit Transfer(account, address(0), amount);
```
https://github.com/code-423n4/2023-10-badger/blob/main/packages/contracts/contracts/EBTCToken.sol#L282C7-L282C52

```
 ebtcToken.transfer(msg.sender, ebtcBal);
        }
```

https://github.com/code-423n4/2023-10-badger/blob/main/packages/contracts/contracts/LeverageMacroBase.sol#L224C12-L225C10

## [L-02] Keccak Constant values should used to immutable rather than constant
There is a difference between constant variables and immutable variables, and they should each be used in their appropriate contexts.

While it doesn’t save any gas because the compiler knows that developers often make this mistake, it’s still best to use the right tool for the task at hand.

```
  bytes32 constant FLASH_LOAN_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");
```

https://github.com/code-423n4/2023-10-badger/blob/main/packages/contracts/contracts/LeverageMacroBase.sol#L37C3-L37C89

```
 bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");
```
https://github.com/code-423n4/2023-10-badger/blob/main/packages/contracts/contracts/SimplifiedDiamondLike.sol#L39C4-L39C95

```
 bytes32 public constant FLASH_SUCCESS_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan");
```

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

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

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

// Code block 3 (unknown):
function transfer(address recipient, uint256 amount) external override returns (bool) {

// Code block 4 (unknown):
emit Transfer(account, address(0), amount);

// Code block 5 (unknown):
ebtcToken.transfer(msg.sender, ebtcBal);
        }

// Code block 6 (unknown):
bytes32 constant FLASH_LOAN_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");

// Code block 7 (unknown):
bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");

// Code block 8 (unknown):
bytes32 public constant FLASH_SUCCESS_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan");

ActivePool.sol#L274-L7, ActivePool.sol#L286-L7, EBTCToken.sol#L119-L4, EBTCToken.sol#L282-L7, LeverageMacroBase.sol#L224-L12, LeverageMacroBase.sol#L37-L3, SimplifiedDiamondLike.sol#L39-L4

EBTCToken.sol, ActivePool.sol, LeverageMacroBase.sol, SimplifiedDiamondLike.sol


JCK G

# Gas Optimizations

| Number | Issue | Instances | Gas |
|--------|-------|-----------|-----|
|[G-01]| Don’t cache value if it is only used once | 5  |  450 |
|[G-02]| Use assembly to check for address(0) | 12  | 72 |
|[G-03]| Use hardcode address instead address(this) | 3  |   |
|[G-04]| Make 3 event parameters indexed when possible | 7  | 3500 |
|[G-05]| Use assembly to write address storage values | 3 | 600 |
|[G-06]| Assigning keccak operations to constant variables results in extra gas costs | 7 |   |
|[G-07]| A mapping is more efficient than an array | 1 | 248 |
|[G-08]| Public Functions not called by they contract should be declear External | 10  |   |
|[G-09]| Save loop calls | 3 |   |
|[G-10]| Create immutable variable to avoid redundant external calls | 2 |   |
|[G-11]| Use assembly to validate msg.sender | 6  | 18  |
|[G-12]| Cache calldata/memory pointers for complex types to avoid offset calculations | 2  |   |
|[G-13]| Combine events to save 2 Glogtopic (375 gas) | 3  | 1125 |
|[G-14]| Internal/Private functions only called once can be inlined to save gas Gas saved: 40 | 1 | 40 |
|[G-15]| bytes constants are more eficient than string constans | 19 |   |
|[G-16]| Amounts should be checked for 0 before calling a transfer | 6 |   |
|[G-17]| abi.encode() is less efficient than abi.encodePacked() | 3  |   |
|[G-18]| Do not calculate constants | 1 |   |
|[G-19]| Use assembly for loops | 6 |   |
|[G-20]| Avoid emitting storage values | 1 |   |
|[G-21]| Use assembly to make more efficient back-to-back calls | 1 |   |
|[G-22]| Use a do while loop instead of a for loop | 4 |   |
|[G-23]| State variables can be packed into fewer storage slots | 1  | 2100 |
|[G-24]| Cache state variables outside of loop to avoid reading storage on every iteration | 1 |   |
|[G-25]| Use calldata instead of memory for function parameters that don’t change | 3  | 900 |
|[G-26]| Don’t initialize variables with default value | 8  | 46  |
|[G-27]| Use uint256(1)/uint256(2) instead for

file:  contracts/bridge/DestinationBridge.sol

322     function rescueTokens(address _token) external onlyOwner {
    uint256 balance = IRWALike(_token).balanceOf(address(this));
    IRWALike(_token).transfer(owner(), balance);
  }

file:  contracts/bridge/DestinationBridge.sol

337    function _mintIfThresholdMet(bytes32 txnHash) internal {
    bool thresholdMet = _checkThresholdMet(txnHash);
    Transaction memory txn = txnHashToTransaction[txnHash];
    if (thresholdMet) {
      _checkAndUpdateInstantMintLimit(txn.amount);
      if (!ALLOWLIST.isAllowed(txn.sender)) {
        ALLOWLIST.setAccountStatus(
          txn.sender,
          ALLOWLIST.getValidTermIndexes()[0],
          true
        );
      }
      TOKEN.mint(txn.sender, txn.amount);
      delete txnHashToTransaction[txnHash];
      emit BridgeCompleted(txn.sender, txn.amount);
    }
  }


T1MOH Q

## Mark SafEthStorage as abstract
This contract is dependent on SafEth.sol and provides read-only functionality, there is know sense in deploying it

## Upgradeable contract is missing a __gap[50] storage variable to allow for new storage variables in later versions
See this link for a description of this storage variable. While some contracts may not currently be sub-classed, adding the variable now protects against forgetting to add it in the future.
1 instance: SafEthStorage

## Use Ownable2StepUpgradeable instead of OwnableUpgradeable contract for SafEth
There is another Openzeppelin Ownable contract (Ownable2StepUpgradeable.sol) has transferOwnership function, use is more secure due to 2-stage ownership transfer.

## Lack of functions for sending randomly received ETH and ERC-20 (apart from the derivatives themselves)
In [WstEth.sol](https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/WstEth.sol#L56-L63) send balanceAfter - balanceBefore, but not all balance
```solidity
        uint256 minOut = (stEthBal * (10 ** 18 - maxSlippage)) / 10 ** 18;
        uint256 balanceBefore = address(this).balance;
        IStEthEthPool(LIDO_CRV_POOL).exchange(1, 0, stEthBal, minOut);
        uint256 balanceAfter = address(this).balance;
        // solhint-disable-next-line
        (bool sent, ) = address(msg.sender).call{value: balanceAfter - balanceBefore}(
            ""
        );
```
For resque ERC-20 add
```solidity
        uint256 balancePre = IERC20(STETH_TOKEN).balanceOf(address(this));
        IWStETH(WST_ETH).unwrap(_amount);
        uint256 balancePost = IERC20(STETH_TOKEN).balanceOf(address(this));
        uint256 stEthBal = balancePost - balancePre
        IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, stEthBal);
```

And by analogy provide resque for all derivatives

## Unnecessary operation
[Reth](https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L215)
```solidity
        else return (po

// Code block 1 (solidity):
uint256 minOut = (stEthBal * (10 ** 18 - maxSlippage)) / 10 ** 18;
        uint256 balanceBefore = address(this).balance;
        IStEthEthPool(LIDO_CRV_POOL).exchange(1, 0, stEthBal, minOut);
        uint256 balanceAfter = address(this).balance;
        // solhint-disable-next-line
        (bool sent, ) = address(msg.sender).call{value: balanceAfter - balanceBefore}(
            ""
        );

// Code block 2 (solidity):
uint256 balancePre = IERC20(STETH_TOKEN).balanceOf(address(this));
        IWStETH(WST_ETH).unwrap(_amount);
        uint256 balancePost = IERC20(STETH_TOKEN).balanceOf(address(this));
        uint256 stEthBal = balancePost - balancePre
        IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, stEthBal);

uint256 minOut = (stEthBal * (10 ** 18 - maxSlippage)) / 10 ** 18;
        uint256 balanceBefore = address(this).balance;
        IStEthEthPool(LIDO_CRV_POOL).exchange(1, 0, stEthBal, minOut);
        uint256 balanceAfter = address(this).balance;
        // solhint-disable-next-line
        (bool sent, ) = address(msg.sender).call{value: balanceAfter - balanceBefore}(
            ""
        );

uint256 balancePre = IERC20(STETH_TOKEN).balanceOf(address(this));
        IWStETH(WST_ETH).unwrap(_amount);
        uint256 balancePost = IERC20(STETH_TOKEN).balanceOf(address(this));
        uint256 stEthBal = balancePost - balancePre
        IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, stEthBal);


0xSmartContract Q

### QA Report Issues List

- [x] **Low 01** → The `execute()` function is payable and has no control of the `msg.value` value, this may cause the user to lose Money
- [x] **Low 02** → Lack of price oracle output validation can result in wrong or stale price being used in UniswapV3OracleRelay.sol
- [x] **Low 03** → There is a risk that a user with a high governance power will not be able to bid with `propose()`
- [x] **Low 04** → Project Upgrade and Stop Scenario should be
- [x] **Low 05** → Project has  security risk from DAO attack using proposal
- [x] **Low 06** → Migrating with "_migrateCollateralsFrom()"may result in loss of user funds
- [x] **Low 07** → Missing Event for  initialize
- [x] **Low 08**→ If onlyOwner runs `renounceOwnership()` in the `AMPHClaimer` contract, the contract may become unavailable
- [x] **Non-Critical 09**→ Unused Import

### [Low-01] The `execute()` function is payable and has no control of the `msg.value` value, this may cause the user to lose Money

The `msg.value` value is sent to the contact with the `execute()` function.

but the following code is used to find the msg.value ;
`this.executeTransaction{value: _proposal.values[_i]}(`

Also in the for loop the msg.value is sent too many times, it has no control over it

```solidity
core/solidity/contracts/governance/GovernorCharlie.sol:
  308     */
  309:   function execute(uint256 _proposalId) external payable override {
  310:     if (state(_proposalId) != ProposalState.Queued) revert GovernorCharlie_ProposalNotQueued();
  311:     Proposal storage _proposal = proposals[_proposalId];
  312:     _proposal.executed = true;
  313:     for (uint256 _i = 0; _i < _proposal.targets.length; _i++) {
  314:       this.executeTransaction{value: _proposal.values[_i]}(
  315:         _proposal.targets[_i], _proposal.values[_i], _proposal.signatures[_i], _proposal.calldatas[_i], _proposal.eta
  316:       );
  317:     }
  318:     emit ProposalExecutedIndexed(_proposalId);
  319:     emit Prop

core/solidity/contracts/governance/GovernorCharlie.sol:
  308     */
  309:   function execute(uint256 _proposalId) external payable override {
  310:     if (state(_proposalId) != ProposalState.Queued) revert GovernorCharlie_ProposalNotQueued();
  311:     Proposal storage _proposal = proposals[_proposalId];
  312:     _proposal.executed = true;
  313:     for (uint256 _i = 0; _i < _proposal.targets.length; _i++) {
  314:       this.executeTransaction{value: _proposal.values[_i]}(
  315:         _proposal.targets[_i], _proposal.values[_i], _proposal.signatures[_i], _proposal.calldatas[_i], _proposal.eta
  316:       );
  317:     }
  318:     emit ProposalExecutedIndexed(_proposalId);
  319:     emit ProposalExecuted(_proposalId);
  320:   }

core/solidity/contracts/periphery/oracles/UniswapV3OracleRelay.sol:
  78
  79:   function _getPriceFromUniswap(uint32 _seconds, uint128 _amount) internal view virtual returns (uint256 _price) {
  80:     (int24 _arithmeticMeanTick,) = OracleLibrary.consult(address(POOL), _seconds);
  81:     _price = OracleLibrary.getQuoteAtTick(_arithmeticMeanTick, _amount, BASE_TOKEN, QUOTE_TOKEN);
  82:   }


0xComfyCat Q

# [L-01] USDA `withdrawAll` doesn’t take pending interest into account leaving user's interest unclaimed

USDA `withdrawAll` and `withdrawAllTo` doesn't really withdraw all of user balance. Since it calculate withdrawal balance before doing interest accrual, accrued interest won't be reflected in the calculated balance, leaving interest unclaimed.
```
  function withdrawAll() external override returns (uint256 _susdWithdrawn) {
    uint256 _balance = this.balanceOf(_msgSender());
    _susdWithdrawn = _balance > reserveAmount ? reserveAmount : _balance; // @audit calculate withdrawal balance
    _withdraw(_susdWithdrawn, _msgSender()); // @audit accrue interest after balance is calculated
  }

  // @audit interest is accrued here after `_susdAmount` calculation
  function _withdraw(uint256 _susdAmount, address _target) internal paysInterest whenNotPaused {
    // ...
  }
```
https://github.com/code-423n4/2023-07-amphora/blob/main/core/solidity/contracts/core/USDA.sol#L126-L144
https://github.com/code-423n4/2023-07-amphora/blob/main/core/solidity/contracts/core/USDA.sol#L147

## Proof of Concept
Based on project's e2e test. The setup is that someone was borrowing USDA and there is enough sUSD reserve to withdraw. `testWithdrawAllDustLeft` test demonstrated that `withdrawAll` isn't able to withdraw accrued interest, leaving it unclaimed.
```
import {CommonE2EBase, IVault, console} from '@test/e2e/Common.sol';

contract WithdrawInterestPoC is CommonE2EBase {
  function setUp() public override {
    super.setUp();

    // Someone borrows
    bobVaultId = _mintVault(bob);
    vm.startPrank(bob);
    bobVault = IVault(vaultController.vaultIdVaultAddress(bobVaultId));
    weth.approve(address(bobVault), bobWETH);
    IVault(address(bobVault)).depositERC20(address(weth), bobWETH);
    uint256 _toBorrow = vaultController.vaultBorrowingPower(bobVaultId);
    vaultController.borrowUSDA(bobVaultId, uint192(_toBorrow / 2));
    vm.stopPrank();

    // Increase sUSD reserve
    vm

import {CommonE2EBase, IVault, console} from '@test/e2e/Common.sol';

contract WithdrawInterestPoC is CommonE2EBase {
  function setUp() public override {
    super.setUp();

    // Someone borrows
    bobVaultId = _mintVault(bob);
    vm.startPrank(bob);
    bobVault = IVault(vaultController.vaultIdVaultAddress(bobVaultId));
    weth.approve(address(bobVault), bobWETH);
    IVault(address(bobVault)).depositERC20(address(weth), bobWETH);
    uint256 _toBorrow = vaultController.vaultBorrowingPower(bobVaultId);
    vaultController.borrowUSDA(bobVaultId, uint192(_toBorrow / 2));
    vm.stopPrank();

    // Increase sUSD reserve
    vm.startPrank(andy);
    susd.approve(address(usdaToken), 100 ether);
    usdaToken.donate(100 ether);
    vm.stopPrank();
  }

  function testWithdrawAllDustLeft() public {
    uint256 _daveSUSDBefore = susd.balanceOf(dave);

    // User deposit sUSD, receive USDA
    _depositSUSD(dave, 100 ether);

    assertEq(susd.balanceOf(dave), _daveSUSDBefore - 100 ether);
    assertEq(usdaToken.balanceOf(dave), 100 ether);

    // Time travel
    vm.warp(block.timestamp + 1 days);

    // Withdraw
    vm.prank(dave);
    usdaToken.withdrawAll();
    // `withdrawAll` failed to account for pending interest thus user only received what they deposited
    assertEq(susd.balanceOf(dave), _daveSUSDBefore);
    // There is dust amount left unclaimed for user
    assertGt(usdaToken.balanceOf(dave), 0);
  }

  function testWithdrawAllNoDust() public {
    uint256 _daveSUSDBefore = susd.balanceOf(dave);

    // User deposit sUSD, receive USDA
    _depositSUSD(dave, 100 ether);

    assertEq(susd.balanceOf(dave), _daveSUSDBefore - 100 ether);
    assertEq(usdaToken.balanceOf(dave), 100 ether);

    // Time travel
    vm.warp(block.timestamp + 1 days);

    // This time we accrue interest before withdrawing
    vaultController.calculateInterest();

    // Withdraw
    vm.prank(dave);
    usdaToken.withdrawAll();
    // `withdrawAll` take pending interest into ac

// Code block 1 (unknown):
function withdrawAll() external override returns (uint256 _susdWithdrawn) {
    uint256 _balance = this.balanceOf(_msgSender());
    _susdWithdrawn = _balance > reserveAmount ? reserveAmount : _balance; // @audit calculate withdrawal balance
    _withdraw(_susdWithdrawn, _msgSender()); // @audit accrue interest after balance is calculated
  }

  // @audit interest is accrued here after `_susdAmount` calculation
  function _withdraw(uint256 _susdAmount, address _target) internal paysInterest whenNotPaused {
    // ...
  }


dd0x7e8 Q

The issue in the `wrap()` function of `rUSDY` contract lies in the event logs for the `Transfer` and `TransferShares` events. Currently, the USDY amount (`_USDYAmount`) is being used as the value for the shares in the event logs, which is incorrect. The accurate number of shares should be `_USDYAmount * BPS_DENOMINATOR`.

```solidity=434
  function wrap(uint256 _USDYAmount) external whenNotPaused {
    require(_USDYAmount > 0, "rUSDY: can't wrap zero USDY tokens");
    _mintShares(msg.sender, _USDYAmount * BPS_DENOMINATOR);
    usdy.transferFrom(msg.sender, address(this), _USDYAmount);
    emit Transfer(address(0), msg.sender, getRUSDYByShares(_USDYAmount));
    emit TransferShares(address(0), msg.sender, _USDYAmount);
  }
```
https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/usdy/rUSDY.sol#L438
https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/usdy/rUSDY.sol#L439

To address this issue, it's recommended to update the event logs to reflect the correct number of shares minted. Here's the corrected code:
```solidity
function wrap(uint256 _USDYAmount) external whenNotPaused {
  require(_USDYAmount > 0, "rUSDY: can't wrap zero USDY tokens");
  uint256 sharesToMint = _USDYAmount * BPS_DENOMINATOR; // Calculate the correct number of shares
  _mintShares(msg.sender, sharesToMint);
  usdy.transferFrom(msg.sender, address(this), _USDYAmount);
  emit Transfer(address(0), msg.sender, getRUSDYByShares(sharesToMint)); // Use sharesToMint instead of _USDYAmount
  emit TransferShares(address(0), msg.sender, sharesToMint); // Use sharesToMint instead of _USDYAmount
}
```

// Code block 1 (unknown):
https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/usdy/rUSDY.sol#L438
https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/usdy/rUSDY.sol#L439

To address this issue, it's recommended to update the event logs to reflect the correct number of shares minted. Here's the corrected code:


climber2002 Q

# Add require(_derivativeIndex < derivativeCount) validation in SafETH.adjustWeight

In [SafETH.adjustWeight](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L177) it doesn't make sense if _derivativeIndex >= derivativeCount. It's better to add such check in case of contract owner call this function with wrong params accidentally.

```solidity
function adjustWeight(
        uint256 _derivativeIndex,
        uint256 _weight
    ) external onlyOwner {
  require(_derivativeIndex < derivativeCount, "Invalid _derivativeIndex");
  ...
}
```

# When unstake user can lose funds if _safEthAmount is extremely small
In [unstake](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L115-L116) it calculates `derivativeAmount`

```solidity
uint256 derivativeAmount = (derivatives[i].balance() *
                _safEthAmount) / safEthTotalSupply;
```

if `_safEthAmount` is extremely small, then derivativeAmount could be 0 for all derivatives. For example, for 3 derivatives with equal weights, if _safETHAmount is 2wei, then derivativeAmount is `1 * 2 / 3 = 0` for all derivatives.

## Recommendation
Revert if `ethAmountToWithdraw` is 0

# Add a function previewUnstake
Currently `unstake` doesn't return the ETH amount that it's actually unstaked. It's better to have a `function previewUnstake(uint256 _safEthAmount) external view returns (uint256)` which can let the user know how much ETH he will receive for a given `_safEthAmount` before doing `unstake`

// Code block 1 (solidity):
function adjustWeight(
        uint256 _derivativeIndex,
        uint256 _weight
    ) external onlyOwner {
  require(_derivativeIndex < derivativeCount, "Invalid _derivativeIndex");
  ...
}

// Code block 2 (solidity):
uint256 derivativeAmount = (derivatives[i].balance() *
                _safEthAmount) / safEthTotalSupply;

function adjustWeight(
        uint256 _derivativeIndex,
        uint256 _weight
    ) external onlyOwner {
  require(_derivativeIndex < derivativeCount, "Invalid _derivativeIndex");
  ...
}

uint256 derivativeAmount = (derivatives[i].balance() *
                _safEthAmount) / safEthTotalSupply;


Saintcode_ G

# Gas Optimizations

## Consider marking constants as private

Consider marking constant variables in storage as private to save gas (unless a constant variable should be easily accessible by another protocol or offchain logic).

```js
contract GasTest is DSTest {
    Contract0 c0;
    Contract1 c1;

    function setUp() public {
        c0 = new Contract0();
        c1 = new Contract1();

    }
    function testGas() public view {
        uint256 a = 100;
        c0.addPublicConstant(a);
        c1.addPrivateConstant(a);

    }
}
contract Contract0 {

    uint256 constant public x = 100;

    function addPublicConstant(uint256 a) external pure returns (uint256) {
        return a + x;
    }
}

contract Contract1 {

        uint256 constant private x = 100;

    function addPrivateConstant(uint256 a) external pure returns (uint256) {
        return a +x;
    }
}
```

### Gas Report

```js
╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮
│ src/test/GasTest.t.sol:Contract0 contract ┆                 ┆     ┆        ┆     ┆         │
╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡
│ Deployment Cost                           ┆ Deployment Size ┆     ┆        ┆     ┆         │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
│ 92741                                     ┆ 495             ┆     ┆        ┆     ┆         │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
│ Function Name                             ┆ min             ┆ avg ┆ median ┆ max ┆ # calls │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
│ addPublicConstant                         ┆ 790             ┆ 790 ┆ 790    ┆ 790 ┆ 1       │
╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯
╭────────────────────────────────

╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮
│ src/test/GasTest.t.sol:Contract0 contract ┆                 ┆     ┆        ┆     ┆         │
╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡
│ Deployment Cost                           ┆ Deployment Size ┆     ┆        ┆     ┆         │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
│ 92741                                     ┆ 495             ┆     ┆        ┆     ┆         │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
│ Function Name                             ┆ min             ┆ avg ┆ median ┆ max ┆ # calls │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
│ addPublicConstant                         ┆ 790             ┆ 790 ┆ 790    ┆ 790 ┆ 1       │
╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯
╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮
│ src/test/GasTest.t.sol:Contract1 contract ┆                 ┆     ┆        ┆     ┆         │
╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡
│ Deployment Cost                           ┆ Deployment Size ┆     ┆        ┆     ┆         │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
│ 83535                                     ┆ 449             ┆     ┆        ┆     ┆         │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
│ Function Name                             ┆ min             ┆ avg ┆ median ┆ max ┆ # calls │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
│ addPrivateConstant                        ┆ 768             ┆ 768 ┆ 768    ┆ 768 ┆ 1       │
╰────

// Code block 1 (js):
contract GasTest is DSTest {
    Contract0 c0;
    Contract1 c1;

    function setUp() public {
        c0 = new Contract0();
        c1 = new Contract1();

    }
    function testGas() public view {
        uint256 a = 100;
        c0.addPublicConstant(a);
        c1.addPrivateConstant(a);

    }
}
contract Contract0 {

    uint256 constant public x = 100;

    function addPublicConstant(uint256 a) external pure returns (uint256) {
        return a + x;
    }
}

contract Contract1 {

        uint256 constant private x = 100;

    function addPrivateConstant(uint256 a) external pure returns (uint256) {
        return a +x;
    }
}


Bason G

## Gas Findings
|  Number  |Issues Details |
|:--------:|:-------|
| [GAS-01] | Hash keccak256 variables off-chain to save gas

***

## |[GAS-01]| Hash keccak256 variables off-chain to save gas

ExtraordinaryFunding.sol
```solidity
bytes32 internal constant DESCRIPTION_PREFIX_HASH_EXTRAORDINARY = keccak256(bytes("Extraordinary Funding: "));
```
StandardFunding.sol
```solidity
bytes32 internal constant DESCRIPTION_PREFIX_HASH_STANDARD = keccak256(bytes("Standard Funding: "));
```
Both of these values can be calculated off-chain and the values can be passed directly into the constructor to save gas
from executing the keccak256 hashing function.

***

// Code block 1 (solidity):
bytes32 internal constant DESCRIPTION_PREFIX_HASH_EXTRAORDINARY = keccak256(bytes("Extraordinary Funding: "));

// Code block 2 (solidity):
bytes32 internal constant DESCRIPTION_PREFIX_HASH_STANDARD = keccak256(bytes("Standard Funding: "));

bytes32 internal constant DESCRIPTION_PREFIX_HASH_EXTRAORDINARY = keccak256(bytes("Extraordinary Funding: "));

bytes32 internal constant DESCRIPTION_PREFIX_HASH_STANDARD = keccak256(bytes("Standard Funding: "));


turvy_fuzz Q

## Low - Conflicting specification to code implementation
```solidity
if (newStart < ranges[indexToModify - 1].end) revert InvalidRange();
```
This above checks that newStart goes below the end time of the previous range therefore it ensures newStart is greater than it and not less than it. However, it conflicts with the statement [here](https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/rwaOracles/RWADynamicOracle.sol#L211).

Recommendation:
Ensure the code implementation is compliant with the statement or vice versa

## Low - Doesn't revert if numOfApprovers[i] == 0

It is assumed that all numOfApprovers of the set thresholds are > 0 and even uses this invariant to determine threshold was found in some function
```
if (txnToThresholdSet[txnHash].numberOfApprovalsNeeded == 0) {
      revert NoThresholdMatch();
    }
```
But doesn't check this when setting thresholds
https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/DestinationBridge.sol#L273

Recommendation:
Add a revert check if numOfApprovers[i] == 0 when setting thresholds

// Code block 1 (solidity):
if (newStart < ranges[indexToModify - 1].end) revert InvalidRange();

// Code block 2 (unknown):
if (txnToThresholdSet[txnHash].numberOfApprovalsNeeded == 0) {
      revert NoThresholdMatch();
    }


yosuke G

## [YO GAS-1] USE CUSTOM ERRORS RATHER THAN REVERT()/REQUIRE() STRINGS TO SAVE GAS

### Handle
yosuke

## Vulnerability details
### Impact
Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they’re hit by avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas.

### Proof of Concept
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/BaseSmartAccount.sol#L74
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L77
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L83
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L89
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L110
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L121
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L128
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L167-L171
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L224
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L232
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L262
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L265
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.s

## [YO GAS-3] USING BOOLS FOR STORAGE INCURS OVERHEAD

### Handle
yosuke

## Vulnerability details
### Impact
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.

### Proof of Concept
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/BaseSmartAccount.sol#L120
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L197
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L322
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L489
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L545
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/interfaces/IERC165.sol#L14
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/base/ModuleManager.sol#L66
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/base/ModuleManager.sol#L85
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/base/ModuleManager.sol#L105
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/common/SecuredTokenTransfer.sol#L14
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/SmartAccountFactory.sol#L15
https://github.com/code-423n4/2023-01-biconomy/blob/main/scw-contracts/contracts/smart-contract-wallet/base/Execut

BaseSmartAccount.sol#L74, SmartAccount.sol#L77, SmartAccount.sol#L83, SmartAccount.sol#L89, SmartAccount.sol#L110, SmartAccount.sol#L121, SmartAccount.sol#L128, SmartAccount.sol#L167-L171, SmartAccount.sol#L224, SmartAccount.sol#L232, SmartAccount.sol#L262, SmartAccount.sol#L265, BaseSmartAccount.sol#L120, SmartAccount.sol#L197, SmartAccount.sol#L322, SmartAccount.sol#L489, SmartAccount.sol#L545, IERC165.sol#L14, ModuleManager.sol#L66, ModuleManager.sol#L85, ModuleManager.sol#L105, SecuredTokenTransfer.sol#L14, SmartAccountFactory.sol#L15

IERC165.sol, BaseSmartAccount.sol, SmartAccountFactory.sol, ModuleManager.sol, SmartAccount.sol, SecuredTokenTransfer.sol


report

---
sponsor: "AI Arena"
slug: "2024-02-ai-arena"
date: "2024-05-14"
title: "AI Arena"
findings: "https://github.com/code-423n4/2024-02-ai-arena-findings/issues"
contest: 328
---

# Overview

## About C4

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

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

During the audit outlined in this document, C4 conducted an analysis of the AI Arena smart contract system written in Solidity. The audit took place between February 9—February 21 2024.

Following the C4 audit, 3 wardens ([d3e4](https://code4rena.com/@d3e4), [fnanni](https://code4rena.com/@fnanni), and [niser93](https://code4rena.com/@niser93)) reviewed the mitigations for all identified issues; the [mitigation review report](#mitigation-review) is appended below the audit report.

## Wardens

299 Wardens contributed reports to AI Arena:

  1. [fnanni](https://code4rena.com/@fnanni)
  2. [d3e4](https://code4rena.com/@d3e4)
  3. [niser93](https://code4rena.com/@niser93)
  4. [haxatron](https://code4rena.com/@haxatron)
  5. [BARW](https://code4rena.com/@BARW) ([BenRai](https://code4rena.com/@BenRai) and [albertwh1te](https://code4rena.com/@albertwh1te))
  6. [juancito](https://code4rena.com/@juancito)
  7. [DanielArmstrong](https://code4rena.com/@DanielArmstrong)
  8. [0xDetermination](https://code4rena.com/@0xDetermination)
  9. [merlinboii](https://code4rena.com/@merlinboii)
  10. [0x11singh99](https://code4rena.com/@0x11singh99)
  11. [MrPotatoMagic](https://code4rena.com/@MrPotatoMagic)
  12. [klau5](https://code4rena.com/@klau5)
  13. [MidgarAudits](https://code4rena.com/@MidgarAudits) ([vangrim](https://code4rena.com/@vangrim) and [EVDoc](https://code4rena.com/@EVDoc))
  14. [Timenov](https://code4rena.com/@Timenov)
  15. [lil

Place the PoC into `test/RankedBattle.t.sol`, and execute with

    forge test --match-test testExploitTransferStakedFighterAndPlay

</details>

### Impact 2: Another fighter can't win, game server unable to commit

<details>

Place the PoC into `test/RankedBattle.t.sol`, and execute with

    forge test --match-test testExploitTransferStakeAtRiskFighterAndSpoilOtherPlayer

</details>

### Recommended Mitigation Steps

Remove the incomplete checks in the inherited functions `transferFrom()` and `safeTransferFrom()` of `FighterFarm` contract, and instead to enforce the transferability restriction via the `_beforeTokenTransfer()` hook, which applies equally to all token transfers, as illustrated below.

<Details>


jnrlouis Q

## Summary

## Non Critical Issues

|  | Issue | Instances |
| --- | --- | ---|
| 1 | Functions not called internally should be marked `external` | 6 |
| 2 | Return value from `transfer` is ignored | 3 |
| 3 | Input Validation not done | 2 |

## Non Critical Issues

# [NC-01] Functions not called internally should be marked `external`

*There are 6 instances of this issue:*

```javascript
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

```

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

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

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

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

```javascript
File: src/PrivatePool.sol

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

742    function price() public view returns (uint256)

```

https://github.com/code-423n4/2023-04-caviar/blob/cd8a92667bcb6657f70657183769c244d04c015c/src/PrivatePool.sol#L514

https://github.com/code-423n4/2023-04-caviar/blob/cd8a92667bcb6657f70657183769c244d04c015c/src/PrivatePool.sol#L742

# [NC-02] Return value from `transfer` is ignored

Not all IERC20 implementations revert() when there’s a failure in the `transfer()`. The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentiall

// Code block 1 (javascript):
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

// Code block 2 (javascript):
File: src/PrivatePool.sol

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

742    function price() public view returns (uint256)

Factory.sol#L129, Factory.sol#L135, Factory.sol#L141, Factory.sol#L148, PrivatePool.sol#L514, PrivatePool.sol#L742


ADM Q

#### [L01] addAttributeProbabilities should be called when generation is incremented. (otherwise reroll may have no stats)
###### Summary:
When FighterFarm#incrementGeneration() is called both the generation and the maxRerollsAllowed are incremented. However if a user decides to utilise this reroll before an admin has the chance to call addAttributeProbabilities for the new generation then the call to AiArenaHelper#getAttributeProbabilities will return the default values of 0.
###### LoC:
[FighterFarm.sol#L129-L134](https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L129-L134)
[FighterFarm.sol#L383-L388](https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L383-L388)
[AiArenaHelper.sol#L131-L139](https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/AiArenaHelper.sol#L131-L139)
###### Recommendation:
I recommend adding a call to AiArenaHelper#addAttributeProbabilities in FighterFarm#incrementGeneration to update the attribute the probabilities for the new generation at the same time it is created.
```solidity
function incrementGeneration(uint8 fighterType, uint8[][] memory probabilities)) external returns (uint8) {
	require(msg.sender == _ownerAddress);
	generation[fighterType] += 1;
	_aiArenaHelperInstance.addAttributeProbabilities(generation[fighterType], probabilities)
	maxRerollsAllowed[fighterType] += 1;
	return generation[fighterType];
}
```

#### [L02] claimFighters is vulnerable to cross chain replay attacks due to no chain.id in the msgHash.
###### Summary:
If AI Arena is only ever deployed on Arbitrum this is a non issue, however if the protocol team ever decides to deploy on other networks as well users will be able to user the Arbitrum signatures to claim a bunch of fighters instantly.
###### LoC:
[FighterFarm.sol#L199-L205](https://github.com/code-423n4/2024-02-ai-arena/blob/f2952187a8afc

// Code block 1 (solidity):
function incrementGeneration(uint8 fighterType, uint8[][] memory probabilities)) external returns (uint8) {
	require(msg.sender == _ownerAddress);
	generation[fighterType] += 1;
	_aiArenaHelperInstance.addAttributeProbabilities(generation[fighterType], probabilities)
	maxRerollsAllowed[fighterType] += 1;
	return generation[fighterType];
}


Shubham G

## Gas Optimization

| |Issue|Instances|
|-|:-|:-:|
| [G-01](#G-01) | Successive sorting of token addresses wastes gas | 1 |
| [G-02](#G-02) | Checking the value of `supply` early on can prevent execution of external calls & calculations | 1 |

## [G-01] Successive sorting of token addresses wastes gas

In 'reLP()', first the token addresses are sorted by calling 'UniswapV2Library.sortTokens()' & then the reserves are calculated by calling 'UniswapV2Library.getReserves()'.

```solidity
File: contracts/reLP/ReLPContract

    function reLP(uint256 _amount) external onlyRole(RDPXV2CORE_ROLE) {
    // get the pool reserves
    (address tokenASorted, address tokenBSorted) = UniswapV2Library.sortTokens(
      addresses.tokenA,
      addresses.tokenB
    );
    (uint256 reserveA, uint256 reserveB) = UniswapV2Library.getReserves(
      addresses.ammFactory,
      tokenASorted,
      tokenBSorted
    );
    .................
```
https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/reLP/ReLPContract.sol#L202-L212

But in the 'UniswapV2Library' we can see that the 'getReserves()' already calls the 'sortTokens' function.

```solidity
File: UniswapV2Library.sol

    function getReserves(
    address factory,
    address tokenA,
    address tokenB
    ) internal view returns (uint256 reserveA, uint256 reserveB) {
    (address token0, ) = sortTokens(tokenA, tokenB);                   --------> Sorting already happens here
    (uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(
      pairFor(factory, tokenA, tokenB)
    ).getReserves();
    (reserveA, reserveB) = tokenA == token0
      ? (reserve0, reserve1)
      : (reserve1, reserve0);
    }
```

Thus sorting of the same token addresses occur twice inside the same function wasting gas.

## [G-02] Checking the value of `supply` early on can prevent execution of external calls & calculations

The value of 'supply' is checked at the end of the function after performing the necessary calculations. But instead if we c

// Code block 1 (solidity):
File: contracts/reLP/ReLPContract

    function reLP(uint256 _amount) external onlyRole(RDPXV2CORE_ROLE) {
    // get the pool reserves
    (address tokenASorted, address tokenBSorted) = UniswapV2Library.sortTokens(
      addresses.tokenA,
      addresses.tokenB
    );
    (uint256 reserveA, uint256 reserveB) = UniswapV2Library.getReserves(
      addresses.ammFactory,
      tokenASorted,
      tokenBSorted
    );
    .................

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

    function getReserves(
    address factory,
    address tokenA,
    address tokenB
    ) internal view returns (uint256 reserveA, uint256 reserveB) {
    (address token0, ) = sortTokens(tokenA, tokenB);                   --------> Sorting already happens here
    (uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(
      pairFor(factory, tokenA, tokenB)
    ).getReserves();
    (reserveA, reserveB) = tokenA == token0
      ? (reserve0, reserve1)
      : (reserve1, reserve0);
    }

File: contracts/reLP/ReLPContract

    function reLP(uint256 _amount) external onlyRole(RDPXV2CORE_ROLE) {
    // get the pool reserves
    (address tokenASorted, address tokenBSorted) = UniswapV2Library.sortTokens(
      addresses.tokenA,
      addresses.tokenB
    );
    (uint256 reserveA, uint256 reserveB) = UniswapV2Library.getReserves(
      addresses.ammFactory,
      tokenASorted,
      tokenBSorted
    );
    .................

File: UniswapV2Library.sol

    function getReserves(
    address factory,
    address tokenA,
    address tokenB
    ) internal view returns (uint256 reserveA, uint256 reserveB) {
    (address token0, ) = sortTokens(tokenA, tokenB);                   --------> Sorting already happens here
    (uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(
      pairFor(factory, tokenA, tokenB)
    ).getReserves();
    (reserveA, reserveB) = tokenA == token0
      ? (reserve0, reserve1)
      : (reserve1, reserve0);
    }


alejandrocovrr Q

# Withdraw function on WstEth can be improved

On [/contracts/SafEth/derivatives/WstEth.sol#L56](https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/WstEth.sol#L56) there are some refactors that can be applied to improve both code quality and security.

The current code is:

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

Has some points that could be improved, the following refactored code shows it:

```
function withdraw(uint256 _amount) external onlyOwner {
    require(_amount > 0 && _amount <= address(this).balance, "Invalid amount");
    IWStETH(WST_ETH).unwrap(_amount);
    uint256 stEthBal = IERC20(STETH_TOKEN).balanceOf(address(this));
    IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, stEthBal);
    uint256 minOut = (stEthBal * (10 ** 18 - maxSlippage)) / 10 ** 18;
    IStEthEthPool(LIDO_CRV_POOL).exchange(1, 0, stEthBal, minOut);
    // solhint-disable-next-line
    payable(msg.sender).transfer(address(this).balance);
}
```

**1. Use require() for input validation:** Currently, there is no validation on the _amount parameter. It would be better to use a require() statement to validate that the _amount parameter is greater than 0 and less than the balance of the contract.
**2. Use transfer() instead of call():** The current code uses call() to send Ether to the owner of the contract. However, transfer() is a more secure and gas-efficient way to transfer Ether.

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

// Code block 2 (unknown):
function withdraw(uint256 _amount) external onlyOwner {
    require(_amount > 0 && _amount <= address(this).balance, "Invalid amount");
    IWStETH(WST_ETH).unwrap(_amount);
    uint256 stEthBal = IERC20(STETH_TOKEN).balanceOf(address(this));
    IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, stEthBal);
    uint256 minOut = (stEthBal * (10 ** 18 - maxSlippage)) / 10 ** 18;
    IStEthEthPool(LIDO_CRV_POOL).exchange(1, 0, stEthBal, minOut);
    // solhint-disable-next-line
    payable(msg.sender).transfer(address(this).balance);
}


LinKenji Analysis

## Project Summary

Salty.IO is building a decentralized exchange that aims to offer zero fee swaps, yield generation from automatic arbitrage, and a native stablecoin called USDS collateralized by WBTC/WETH.

The system is governed by a DAO where SALT token holders vote on parameters and upgrades. No special admin access exists. Pools.sol implements the AMM DEX logic with arbitrage done atomically at swap time to yield profits. USDS.sol manages stablecoin collateral ratios, liquidations, and redemptions.

Incentives focus on distributing arbitrage yields to liquidity providers and SALT stakers to drive adoption. The goal is ultimately a self-sustaining platform lacking centralized points of failure.

**Key Risks**

**Liquidity Bootstrapping:** As an AMM, Salty.IO requires external liquidity to function efficiently. Without incentives attracting liquidity in a circular way, high swap costs could prevent profitability.

**Technical:** The smart contract system is complex. Subtle logical errors or bugs could disrupt core functionality around swaps, yields, or the USDS peg. Rigorous testing is critical.

**Price Manipulation:** Asset valuations originate from external Chainlink and Uniswap price oracles. Market volatility could be amplified by manipulated oracles.

**Governance:** The DAO introduces risks around proposal attacks, improper parameter changes, and funds mismanagement. Timelocks and process controls are important.

**Summary**

Salty.IO brings innovative architecture but has complexity risks. Prioritizing liquidity incentives and strong technical audit practices will help mitigate issues in core areas like AMM swaps, price oracles, and governance.

The core price feed contracts are `PriceAggregator.sol` and its associated `IPriceFeed` implementations (`CoreUniswapFeed`, `CoreChainlinkFeed` etc.) The main vulnerabilities stem from:

**Centralized Price Feeds**

If an attacker compromises the private keys of the admin or DAO governing the price feed contrac

function setPriceFeed(uint id, IPriceFeed feed) onlyOwner external {
  // Set manipulated feed
}

function swap(
  IERC20 tokenIn,
  IERC20 tokenOut,
  uint amountIn
) external {

  // Place user swap

  uint arbitrageProfit = _attemptArbitrage(
    tokenIn,
    tokenOut,
    amountIn
  );

}

function _attemptArbitrage(
  IERC20 tokenIn,
  IERC20 tokenOut,
  uint amountIn
) internal returns (uint profit) {

  // Calculate and perform arbitrage
}


niroh Q

# _fallbackIsFrozen makes a call to fallbackCaller.fallbackTimeout() without checking that fallbackCaller is non-zero

## Github Links
https://github.com/code-423n4/2023-10-badger/blob/f2f2e2cf9965a1020661d179af46cb49e993cb7e/packages/contracts/contracts/PriceFeed.sol#L490

## Description
if the PriceFeed contract function _fallbackIsFrozen, a call is made to [fallbackCaller.fallbackTimeout()](https://github.com/code-423n4/2023-10-badger/blob/f2f2e2cf9965a1020661d179af46cb49e993cb7e/packages/contracts/contracts/PriceFeed.sol#L490) without checking that fallbackCaller is non-zero. This is unsafe since a zero address fallback is an acceptable state in the system (and is checked for in other places in the code, such as [here](https://github.com/code-423n4/2023-10-badger/blob/f2f2e2cf9965a1020661d179af46cb49e993cb7e/packages/contracts/contracts/PriceFeed.sol#L224)). Currently all calls to _fallbackIsFrozen are preceded by a check that fallback is not broken (which eliminates the zero value fallback case), however relying on an external check is unsafe and may be overlooked in future development.

## Impact
If _fallbackIsFrozen is called when fallbackCaller is zero, if will revert, potentially reverting many of the main protocol operations that indirectly use _fallbackIsFrozen.

## Tools Used
Foundry

## Recommended Mitigation Steps
At a minimum the documentation for _fallbackIsFrozen should mention that it should not be called without a prior check that _fallbackIsBroken is false. For an even safer approach,
add a check at the start of the function to return true is fallbackCaller is zero (The logic being that if you reach _fallbackIsFrozen when fallbackCaller is zero it's better to consider the fallback frozen than to fail the entire transaction).
```
if (address(fallbackCaller) == address(0)) {
    return true;
}
return
            _fallbackResponse.timestamp > 0 &&
            _responseTimeout(_fallbackResponse.timestamp, fallbackCaller.fallbackTimeout());
`

function closeCdp(bytes32 _cdpId) external override nonReentrantSelfAndCdpM {


report

---
sponsor: "Biconomy"
slug: "2023-01-biconomy"
date: "2023-03-03"
title: "Biconomy - Smart Contract Wallet contest"
findings: "https://github.com/code-423n4/2023-01-biconomy-findings/issues"
contest: 200
---

# Overview

## About C4

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

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

During the audit contest outlined in this document, C4 conducted an analysis of the Biconomy Smart Contract Wallet system written in Solidity. The audit contest took place between January 4—January 9 2023.

## Wardens

115 Wardens contributed reports to the Biconomy Smart Contract Wallet contest:

  1. 0x1f8b
  2. 0x52
  3. [0x73696d616f](https://twitter.com/3xJanx2009)
  4. [0xAgro](https://twitter.com/0xAgro)
  5. [0xDave](https://twitter.com/nl__park)
  6. [0xSmartContract](https://twitter.com/0xSmartContract)
  7. 0xbepresent
  8. 0xdeadbeef0x
  9. 0xhacksmithh
  10. 2997ms
  11. Atarpara
  12. [Aymen0909](https://github.com/Aymen1001)
  13. BClabs (nalus and Reptilia)
  14. Bnke0x0
  15. [Deivitto](https://twitter.com/Deivitto)
  16. DevTimSch
  17. Diana
  18. Fon
  19. [Franfran](https://franfran.dev/)
  20. HE1M
  21. Haipls
  22. IllIllI
  23. Jayus
  24. Josiah
  25. [Kalzak](https://twitter.com/kalzak)
  26. Koolex
  27. [Kutu](https://twitter.com/0xKutu)
  28. Lirios
  29. MalfurionWhitehat
  30. [Manboy](https://twitter.com/manboy_eth)
  31. Matin
  32. MyFDsYours
  33. [PwnedNoMore](https://twitter.com/pwnednomore) ([izhuer](https://www.cs.purdue.edu/homes/zhan3299/index.html), ItsNio, [wen](https://twitter.com/0xtarafans), h5n8514, and hashminer0725)
  34. [Qeew](https://twitter.com/adigunq_adigun)
  35. Rageur
  36. [Raiders](https://raiders0786.github.io/Portfolio/)
  37. Ra

contract Destructor {
    fallback() external {
        selfdestruct(payable(0));
    }
}

So, in the deploy script there is no enforce that the `SmartAccount` contract implementation was initialized.

The same situation in `scw-contracts/scripts/wallet-factory.deploy.ts` script.

Please note, that in case only the possibility of initialization of the `SmartAccount` implementation will be banned it will be possible to use this attack. This is so because in such a case `owner` variable will be equal to zero and it will be easy to pass a check inside of `checkSignatures` function using the fact that for incorrect input parameters `ecrecover` returns a zero address.

### Impact

Complete freezing of all functionality of all wallets (including complete funds freezing).

### Recommended Mitigation Steps

Add to the deploy script initialization of the `SmartAccount` implementation, or add to the `SmartAccount` contract the following constructor that will prevent implementation contract from the initialization: