Salty.IO - slvDev's results

An Ethereum-based DEX with zero swap fees, yield-generating Automatic Arbitrage, and a native WBTC/WETH backed stablecoin.

General Information

Platform: Code4rena

Start Date: 16/01/2024

Pot Size: $80,000 USDC

Total HM: 37

Participants: 178

Period: 14 days

Judge: Picodes

Total Solo HM: 4

Id: 320

League: ETH

Salty.IO

Findings Distribution

Researcher Performance

Rank: 123/178

Findings: 2

Award: $32.48

🌟 Selected for report: 0

🚀 Solo Findings: 0

Low Findings

IssueInstances
[L-01]Unsafe downcast from larger to smaller integer types14
[L-02]Use of ecrecover is susceptible to signature malleability1
[L-03]Unbounded loop may run out of gas18
[L-04]Prevent Division by Zero3
[L-05]Functions calling tokens with transfer hooks are missing reentrancy guards8
[L-06]Consider implementing two-step procedure for updating protocol addresses13
[L-07]internal/private Function calls within for loops7

NonCritical Findings

IssueInstances
[N-01]require()/revert() statements should have descriptive reason strings2
[N-02]uint/int variables should have the bit size defined explicitly2
[N-03]if-statement can be converted to a ternary27
[N-04]Avoid Empty Blocks in Code14
[N-05]Consider using OpenZeppelin's SafeCast for any casting32
[N-06]Leverage Recent Solidity Features with 0.8.2335
[N-07]Mixed usage of int/uint with int256/uint2562
[N-08]Inconsistent spacing in comments2
[N-09]Function/Constructor Argument Names Not in mixedCase29
[N-10]Insufficient Comments in Complex Functions3
[N-11]Variable Names Not in mixedCase3
[N-12]Consider making contracts Upgradeable32
[N-13]Named Imports of Parent Contracts Are Missing50
[N-14]Long functions should be refactored into multiple, smaller, functions3
[N-15]Prefer Casting to bytes or bytes32 Over abi.encodePacked() for Single Arguments2
[N-16]Use Unchecked for Divisions on Constant or Immutable Values2

Low Findings Details

[L-01] Unsafe downcast from larger to smaller integer types

Casting from larger types to smaller ones can potentially lead to overflows and thus unexpected behavior. For instance, when a uint256 value gets cast to uint8, it could end up as an entirely different, much smaller number due to the reduction in bits.

OpenZeppelin's SafeCast library provides functions for safe type conversions, throwing an error whenever an overflow would occur. It is generally recommended to use SafeCast or similar protective measures when performing type conversions to ensure the accuracy of your computations and the security of your contracts.

<details> <summary><i>14 issue instances in 3 files:</i></summary>
File: src/pools/Pools.sol

/// @audit cast from `uint256` to `uint128`
100: reserves.reserve0 += uint128(maxAmount0);
/// @audit cast from `uint256` to `uint128`
101: reserves.reserve1 += uint128(maxAmount1);
/// @audit cast from `uint256` to `uint128`
125: reserves.reserve0 += uint128(addedAmount0);
/// @audit cast from `uint256` to `uint128`
126: reserves.reserve1 += uint128(addedAmount1);
/// @audit cast from `uint256` to `uint128`
182: reserves.reserve0 -= uint128(reclaimedA);
/// @audit cast from `uint256` to `uint128`
183: reserves.reserve1 -= uint128(reclaimedB);

100 | 101 | 125 | 126 | 182 | 183

File: src/price_feed/CoreUniswapFeed.sol

/// @audit cast from `uint256` to `uint32`
53: secondsAgo[0] = uint32(twapInterval); // from (before)
/// @audit casted from `int56` to `int24`
59: int24 tick = int24((tickCumulatives[1] - tickCumulatives[0]) / int56(uint56(twapInterval)));

53 | 59

File: src/staking/StakingRewards.sol

/// @audit cast from `uint256` to `uint128`
83: user.virtualRewards += uint128(virtualRewardsToAdd);
/// @audit cast from `uint256` to `uint128`
84: totalRewards[poolID] += uint128(virtualRewardsToAdd);
/// @audit cast from `uint256` to `uint128`
88: user.userShare += uint128(increaseShareAmount);
/// @audit cast from `uint256` to `uint128`
125: user.userShare -= uint128(decreaseShareAmount);
/// @audit cast from `uint256` to `uint128`
126: user.virtualRewards -= uint128(virtualRewardsToRemove);
/// @audit cast from `uint256` to `uint128`
159: userInfo[poolID].virtualRewards += uint128(pendingRewards);

83 | 84 | 88 | 125 | 126 | 159

</details>

[L-02] Use of ecrecover is susceptible to signature malleability

The built-in EVM precompile ecrecover is susceptible to signature malleability, which could lead to replay attacks. References: https://swcregistry.io/docs/SWC-117, https://swcregistry.io/docs/SWC-121. While this is not immediately exploitable, this may become a vulnerability if used elsewhere. Consider using OpenZeppelin’s ECDSA library (which prevents this malleability) instead of the built-in function.

<details> <summary><i>1 issue instances in 1 files:</i></summary>
File: src/SigningTools.sol

26: ecrecover(messageHash, v, r, s)

26

</details>

[L-03] Unbounded loop may run out of gas

Unbounded loops in smart contracts pose a risk because they iterate over an unknown number of elements, potentially consuming all available gas for a transaction. This can result in unintended transaction failures. Gas consumption increases linearly with the number of iterations, and if it surpasses the gas limit, the transaction reverts, wasting the gas spent. To mitigate this, developers should either set a maximum limit on loop iterations.

<details> <summary><i>18 issue instances in 5 files:</i></summary>
File: src/dao/Proposals.sol

423: for( uint256 i = 0; i < _openBallotsForTokenWhitelisting.length(); i++ )

423

File: src/pools/PoolStats.sol

53: for( uint256 i = 0; i < poolIDs.length; i++ )
65: for( uint256 i = 0; i < poolIDs.length; i++ )
81: for( uint256 i = 0; i < poolIDs.length; i++ )
106: for( uint256 i = 0; i < poolIDs.length; i++ )

53 | 65 | 81 | 106

File: src/rewards/RewardsEmitter.sol

60: for( uint256 i = 0; i < addedRewards.length; i++ )
116: for( uint256 i = 0; i < poolIDs.length; i++ )
146: for( uint256 i = 0; i < rewards.length; i++ )

60 | 116 | 146

File: src/rewards/SaltRewards.sol

64: for( uint256 i = 0; i < addedRewards.length; i++ )
87: for( uint256 i = 0; i < addedRewards.length; i++ )
129: for( uint256 i = 0; i < poolIDs.length; i++ )

64 | 87 | 129

File: src/staking/StakingRewards.sol

152: for( uint256 i = 0; i < poolIDs.length; i++ )
185: for( uint256 i = 0; i < addedRewards.length; i++ )
216: for( uint256 i = 0; i < shares.length; i++ )
226: for( uint256 i = 0; i < rewards.length; i++ )
260: for( uint256 i = 0; i < rewards.length; i++ )
277: for( uint256 i = 0; i < shares.length; i++ )
296: for( uint256 i = 0; i < cooldowns.length; i++ )

152 | 185 | 216 | 226 | 260 | 277 | 296

</details>

[L-04] Prevent Division by Zero

On several locations in the code, precautions are not being taken for not dividing by 0. This will revert the code. These functions can be called with a 0 value in the input, this value is not checked for being bigger than 0, that means in some scenarios this can potentially trigger a division by zero.

<details> <summary><i>3 issue instances in 1 files:</i></summary>
File: src/staking/StakingRewards.sol

/// @audit totalShars - can be zero
114: uint256 rewardsForAmount = ( totalRewards[poolID] * decreaseShareAmount ) / totalShares[poolID];
/// @audit usr - can be zero
118: uint256 virtualRewardsToRemove = (user.virtualRewards * decreaseShareAmount) / user.userShare;
/// @audit totalShars - can be zero
243: uint256 rewardsShare = ( totalRewards[poolID] * user.userShare ) / totalShares[poolID];

114 | 118 | 243

</details>

[L-05] Functions calling tokens with transfer hooks are missing reentrancy guards

Functions that call contracts or addresses with transfer hooks should use reentrancy guards for protection. Even if these functions adhere to the check-effects-interaction best practice, absence of reentrancy guards can expose the protocol users to read-only reentrancies. Without the guards, the only protective measure is to block-list the entire protocol, which isn't an optimal solution.

<details> <summary><i>8 issue instances in 2 files:</i></summary>
File: src/dao/DAO.sol

375: tokenA.safeTransfer( address(liquidizer), reclaimedA );
376: tokenB.safeTransfer( address(liquidizer), reclaimedB );

375 | 376

File: src/staking/Liquidity.sol

/// @audit function `_depositLiquidityAndIncreaseShare()` 
88: tokenA.safeTransferFrom(msg.sender, address(this), maxAmountA );
/// @audit function `_depositLiquidityAndIncreaseShare()` 
89: tokenB.safeTransferFrom(msg.sender, address(this), maxAmountB );
/// @audit function `_depositLiquidityAndIncreaseShare()` 
111: tokenA.safeTransfer( msg.sender, maxAmountA - addedAmountA );
/// @audit function `_depositLiquidityAndIncreaseShare()` 
114: tokenB.safeTransfer( msg.sender, maxAmountB - addedAmountB );
/// @audit function `_withdrawLiquidityAndClaim()` 
131: tokenA.safeTransfer( msg.sender, reclaimedA );
/// @audit function `_withdrawLiquidityAndClaim()` 
132: tokenB.safeTransfer( msg.sender, reclaimedB );

88 | 89 | 111 | 114 | 131 | 132

</details>

[L-06] Consider implementing two-step procedure for updating protocol addresses

Direct state address changes in a function can be risky, as they don't allow for a verification step before the change is made. It's safer to implement a two-step process where the new address is first proposed, then later confirmed, allowing for more control and the chance to catch errors or malicious activity.

<details> <summary><i>13 issue instances in 4 files:</i></summary>
File: src/pools/Pools.sol

/// @audit `dao` is changed
62: dao = _dao;

62

File: src/stable/Liquidizer.sol

/// @audit `collateralAndLiquidity` is changed
65: collateralAndLiquidity = _collateralAndLiquidity;
/// @audit `pools` is changed
66: pools = _pools;
/// @audit `dao` is changed
67: dao = _dao;

65 | 66 | 67

File: src/ManagedWallet.sol

/// @audit `proposedMainWallet` is changed
50: proposedMainWallet = _proposedMainWallet;
/// @audit `proposedConfirmationWallet` is changed
52: proposedConfirmationWallet = _proposedConfirmationWallet;

50 | 52

File: src/ExchangeConfig.sol

/// @audit `dao` is changed
52: dao = _dao;
/// @audit `upkeep` is changed
54: upkeep = _upkeep;
/// @audit `initialDistribution` is changed
55: initialDistribution = _initialDistribution;
/// @audit `airdrop` is changed
56: airdrop = _airdrop;
/// @audit `teamVestingWallet` is changed
57: teamVestingWallet = _teamVestingWallet;
/// @audit `daoVestingWallet` is changed
58: daoVestingWallet = _daoVestingWallet;
/// @audit `accessManager` is changed
65: accessManager = _accessManager;

52 | 54 | 55 | 56 | 57 | 58 | 65

</details>

[L-07] internal/private Function calls within for loops

Making function calls or external calls within loops in Solidity can lead to inefficient gas usage, potential bottlenecks, and increased vulnerability to attacks. Each function call or external call consumes gas, and when executed within a loop, the gas cost multiplies, potentially causing the transaction to run out of gas or exceed block gas limits. This can result in transaction failure or unpredictable behavior.

<details> <summary><i>7 issue instances in 4 files:</i></summary>
File: src/arbitrage/ArbitrageSearch.sol

120: _rightMoreProfitable( midpoint, reservesA0, reservesA1, reservesB0, reservesB1, reservesC0, reservesC1 )

120

File: src/pools/PoolStats.sol

89: _poolIndex( _weth, arbToken2, poolIDs )
90: _poolIndex( arbToken2, arbToken3, poolIDs )
91: _poolIndex( arbToken3, _weth, poolIDs )

89 | 90 | 91

File: src/stable/CollateralAndLiquidity.sol

332: userShareForPool( wallet, collateralPoolID )

332

File: src/staking/StakingRewards.sol

156: userRewardForPool( msg.sender, poolID )
261: userRewardForPool( wallet, poolIDs[i] )

156 | 261

</details>

NonCritical Findings Details

[N-01] require()/revert() statements should have descriptive reason strings

In Solidity, require() and revert() functions can include an optional 'reason' string that describes what caused the function to fail. This string can be incredibly helpful during testing and debugging, as it provides more context for what went wrong.

Please ensure that the reason strings are comprehensive and descriptive. Strings with fewer than 12 characters may not provide enough context to understand the cause of the function failure.

<details> <summary><i>2 issue instances in 2 files:</i></summary>
File: src/pools/Pools.sol

/// @audit Reason string `TX EXPIRED` is not informative enough
83: require(block.timestamp <= deadline, "TX EXPIRED");

83

File: src/staking/Liquidity.sol

/// @audit Reason string `TX EXPIRED` is not informative enough
42: require(block.timestamp <= deadline, "TX EXPIRED");

42

</details>

[N-02] uint/int variables should have the bit size defined explicitly

In Solidity, int256 and uint256 are the preferred type names, especially since they are used for function signatures. Using int or uint instead can lead to confusion and inconsistency.

<details> <summary><i>2 issue instances in 2 files:</i></summary>
File: src/pools/Pools.sol

81: modifier ensureNotExpired(uint deadline)

81

File: src/staking/Liquidity.sol

40: modifier ensureNotExpired(uint deadline)

40

</details>

[N-03] if-statement can be converted to a ternary

The ternary operator provides a shorthand way to write if / else statements. It can improve readability and reduce the number of lines of code. Traditional if / else statements, particularly those spanning only a one lines, could potentially be refactored using the ternary operator.

<details> <summary><i>27 issue instances in 10 files:</i></summary>
File: src/dao/DAOConfig.sol

66: if (increase)
        	{
            if (bootstrappingRewards < 500000 ether)
                bootstrappingRewards += 50000 ether;
            }
       	 else
       	 	{
            if (bootstrappingRewards > 50000 ether)
                bootstrappingRewards -= 50000 ether;
	        }
83: if (increase)
			{
			if (percentPolRewardsBurned < 75)
				percentPolRewardsBurned += 5;
			}
		else
			{
			if (percentPolRewardsBurned > 25)
				percentPolRewardsBurned -= 5;
			}
100: if (increase)
			{
			if (baseBallotQuorumPercentTimes1000 < 20 * 1000)
				baseBallotQuorumPercentTimes1000 += 1000;
			}
		else
			{
			if (baseBallotQuorumPercentTimes1000 > 5 * 1000 )
				baseBallotQuorumPercentTimes1000 -= 1000;
			}
117: if (increase)
        	{
            if (ballotMinimumDuration < 14 days)
                ballotMinimumDuration += 1 days;
        	}
        else
        	{
            if (ballotMinimumDuration > 3 days)
                ballotMinimumDuration -= 1 days;
        	}
151: if (increase)
			{
			if (maxPendingTokensForWhitelisting < 12)
				maxPendingTokensForWhitelisting += 1;
			}
		else
			{
			if (maxPendingTokensForWhitelisting > 3)
				maxPendingTokensForWhitelisting -= 1;
			}
168: if (increase)
            {
            if (arbitrageProfitsPercentPOL < 45)
                arbitrageProfitsPercentPOL += 5;
            }
        else
            {
            if (arbitrageProfitsPercentPOL > 15)
                arbitrageProfitsPercentPOL -= 5;
            }
185: if (increase)
            {
            if (upkeepRewardPercent < 10)
                upkeepRewardPercent += 1;
            }
        else
            {
            if (upkeepRewardPercent > 1)
                upkeepRewardPercent -= 1;
            }

66 | 83 | 100 | 117 | 151 | 168 | 185

File: src/dao/Proposals.sol

267: if ( ballot.ballotType == BallotType.PARAMETER )
			require( (vote == Vote.INCREASE) || (vote == Vote.DECREASE) || (vote == Vote.NO_CHANGE), "Invalid VoteType for Parameter Ballot" );
		else // If a Ballot is not a Parameter Ballot, it is an Approval ballot
			require( (vote == Vote.YES) || (vote == Vote.NO), "Invalid VoteType for Approval Ballot" );

267

File: src/pools/PoolsConfig.sol

79: if (increase)
            {
            if (maximumWhitelistedPools < 100)
                maximumWhitelistedPools += 10;
            }
        else
            {
            if (maximumWhitelistedPools > 20)
                maximumWhitelistedPools -= 10;
            }
96: if (increase)
            {
            if (maximumInternalSwapPercentTimes1000 < 2000)
                maximumInternalSwapPercentTimes1000 += 250;
            }
        else
            {
            if (maximumInternalSwapPercentTimes1000 > 250)
                maximumInternalSwapPercentTimes1000 -= 250;
            }

79 | 96

File: src/pools/Pools.sol

131: if ( addedAmount0 > addedAmount1)
			addedLiquidity = (totalLiquidity * addedAmount0) / reserve0;
		else
			addedLiquidity = (totalLiquidity * addedAmount1) / reserve1;

131

File: src/price_feed/CoreChainlinkFeed.sol

44: if ( answerDelay <= MAX_ANSWER_DELAY )
				price = _price;
			else
				price = 0;

44

File: src/price_feed/PriceAggregator.sol

67: if (increase)
            {
            if (maximumPriceFeedPercentDifferenceTimes1000 < 7000)
                maximumPriceFeedPercentDifferenceTimes1000 += 500;
            }
        else
            {
            if (maximumPriceFeedPercentDifferenceTimes1000 > 1000)
                maximumPriceFeedPercentDifferenceTimes1000 -= 500;
            }
84: if (increase)
            {
            if (priceFeedModificationCooldown < 45 days)
                priceFeedModificationCooldown += 5 days;
            }
        else
            {
            if (priceFeedModificationCooldown > 30 days)
                priceFeedModificationCooldown -= 5 days;
            }

67 | 84

File: src/rewards/RewardsConfig.sol

38: if (increase)
            {
            if (rewardsEmitterDailyPercentTimes1000 < 2500)
                rewardsEmitterDailyPercentTimes1000 = rewardsEmitterDailyPercentTimes1000 + 250;
            }
        else
            {
            if (rewardsEmitterDailyPercentTimes1000 > 250)
                rewardsEmitterDailyPercentTimes1000 = rewardsEmitterDailyPercentTimes1000 - 250;
            }
54: if (increase)
            {
            if (emissionsWeeklyPercentTimes1000 < 1000)
                emissionsWeeklyPercentTimes1000 = emissionsWeeklyPercentTimes1000 + 250;
            }
        else
            {
            if (emissionsWeeklyPercentTimes1000 > 250)
                emissionsWeeklyPercentTimes1000 = emissionsWeeklyPercentTimes1000 - 250;
            }
71: if (increase)
            {
            if (stakingRewardsPercent < 75)
                stakingRewardsPercent = stakingRewardsPercent + 5;
            }
        else
            {
            if (stakingRewardsPercent > 25)
                stakingRewardsPercent = stakingRewardsPercent - 5;
            }
88: if (increase)
            {
            if (percentRewardsSaltUSDS < 25)
                percentRewardsSaltUSDS = percentRewardsSaltUSDS + 5;
            }
        else
            {
            if (percentRewardsSaltUSDS > 5)
                percentRewardsSaltUSDS = percentRewardsSaltUSDS - 5;
            }

38 | 54 | 71 | 88

File: src/stable/StableConfig.sol

69: if (increase)
            {
            if (maxRewardValueForCallingLiquidation < 1000 ether)
                maxRewardValueForCallingLiquidation += 100 ether;
            }
        else
            {
            if (maxRewardValueForCallingLiquidation > 100 ether)
                maxRewardValueForCallingLiquidation -= 100 ether;
            }
86: if (increase)
            {
            if (minimumCollateralValueForBorrowing < 5000 ether)
                minimumCollateralValueForBorrowing += 500 ether;
            }
        else
            {
            if (minimumCollateralValueForBorrowing > 1000 ether)
                minimumCollateralValueForBorrowing -= 500 ether;
            }
103: if (increase)
            {
            if (initialCollateralRatioPercent < 300)
                initialCollateralRatioPercent += 25;
            }
        else
            {
            if (initialCollateralRatioPercent > 150)
                initialCollateralRatioPercent -= 25;
            }
140: if (increase)
            {
            if (percentArbitrageProfitsForStablePOL < 10)
                percentArbitrageProfitsForStablePOL += 1;
            }
        else
            {
            if (percentArbitrageProfitsForStablePOL > 1)
                percentArbitrageProfitsForStablePOL -= 1;
            }

69 | 86 | 103 | 140

File: src/staking/StakingConfig.sol

36: if (increase)
            {
            if (minUnstakeWeeks < 12)
                minUnstakeWeeks += 1;
            }
        else
            {
            if (minUnstakeWeeks > 1)
                minUnstakeWeeks -= 1;
            }
53: if (increase)
            {
            if (maxUnstakeWeeks < 108)
                maxUnstakeWeeks += 8;
            }
        else
            {
            if (maxUnstakeWeeks > 20)
                maxUnstakeWeeks -= 8;
            }
70: if (increase)
            {
            if (minUnstakePercent < 50)
                minUnstakePercent += 5;
            }
        else
            {
            if (minUnstakePercent > 10)
                minUnstakePercent -= 5;
            }
87: if (increase)
            {
            if (modificationCooldown < 6 hours)
                modificationCooldown += 15 minutes;
            }
        else
            {
            if (modificationCooldown > 15 minutes)
                modificationCooldown -= 15 minutes;
            }

36 | 53 | 70 | 87

File: src/staking/StakingRewards.sol

299: if ( block.timestamp >= cooldownExpiration )
				cooldowns[i] = 0;
			else
				cooldowns[i] = cooldownExpiration - block.timestamp;

299

</details>

[N-04] Avoid Empty Blocks in Code

Empty blocks in code can lead to confusion and the introduction of errors when the code is later modified. They should be removed, or the block should do something useful, such as emitting an event or reverting.

For contracts meant to be extended, the contract should be abstract and the function signatures added without any default implementation.

<details> <summary><i>14 issue instances in 3 files:</i></summary>
File: src/price_feed/CoreChainlinkFeed.sol

50: catch (bytes memory) // Catching any failure
			{
			// In case of failure, price will remain 0
			}

50

File: src/price_feed/PriceAggregator.sol

155: catch (bytes memory)
			{
			// price remains 0
			}
168: catch (bytes memory)
			{
			// price remains 0
			}

155 | 168

File: src/Upkeep.sol

247: try this.step1() {}
250: try this.step2(msg.sender) {}
253: try this.step3() {}
256: try this.step4() {}
259: try this.step5() {}
262: try this.step6() {}
265: try this.step7() {}
268: try this.step8() {}
271: try this.step9() {}
274: try this.step10() {}
277: try this.step11() {}

247 | 250 | 253 | 256 | 259 | 262 | 265 | 268 | 271 | 274 | 277

</details>

[N-05] Consider using OpenZeppelin's SafeCast for any casting

OpenZeppelin's has SafeCast library that provides functions to safely cast. Recommended to use it instead of casting directly in any case.

<details> <summary><i>32 issue instances in 7 files:</i></summary>
File: src/arbitrage/ArbitrageSearch.sol

72: int256(amountOut)
72: int256(midpoint)
75: int256(PoolUtils.DUST)
86: int256(amountOut)
86: int256(midpoint)
133: int256(amountOut)
133: int256(bestArbAmountIn)
133: int256(PoolUtils.DUST)

72 | 72 | 75 | 86 | 86 | 133 | 133 | 133

File: src/pools/PoolUtils.sol

24: uint160(address(tokenB))
24: uint160(address(tokenA))
35: uint160(address(tokenB))
35: uint160(address(tokenA))

24 | 24 | 35 | 35

File: src/pools/PoolStats.sol

68: uint64(i)

68

File: src/pools/Pools.sol

100: uint128(maxAmount0)
101: uint128(maxAmount1)
125: uint128(addedAmount0)
126: uint128(addedAmount1)
182: uint128(reclaimedA)
183: uint128(reclaimedB)
274: uint128(reserve0)
275: uint128(reserve1)

100 | 101 | 125 | 126 | 182 | 183 | 274 | 275

File: src/price_feed/CoreChainlinkFeed.sol

59: uint256(price)

59

File: src/price_feed/CoreUniswapFeed.sol

53: uint32(twapInterval)
59: int24((tickCumulatives[1] - tickCumulatives[0]) / int56(uint56(twapInterval)))
59: int56(uint56(twapInterval))
59: uint56(twapInterval)

53 | 59 | 59 | 59

File: src/staking/StakingRewards.sol

83: uint128(virtualRewardsToAdd)
84: uint128(virtualRewardsToAdd)
88: uint128(increaseShareAmount)
125: uint128(decreaseShareAmount)
126: uint128(virtualRewardsToRemove)
159: uint128(pendingRewards)

83 | 84 | 88 | 125 | 126 | 159

</details>

[N-06] Leverage Recent Solidity Features with 0.8.23

The recent updates in Solidity provide several features and optimizations that, when leveraged appropriately, can significantly improve your contract's code clarity and maintainability. Key enhancements include the use of push0 for placing 0 on the stack for EVM versions starting from "Shanghai", making your code simpler and more straightforward. Moreover, Solidity has extended NatSpec documentation support to enum and struct definitions, facilitating more comprehensive and insightful code documentation.

Additionally, the re-implementation of the UnusedAssignEliminator and UnusedStoreEliminator in the Solidity optimizer provides the ability to remove unused assignments in deeply nested loops. This results in a cleaner, more efficient contract code, reducing clutter and potential points of confusion during code review or debugging. It's recommended to make full use of these features and optimizations to enhance the robustness and readability of your smart contracts.

<details> <summary><i>35 issue instances in 35 files:</i></summary>
File: src/arbitrage/ArbitrageSearch.sol

2: pragma solidity =0.8.22;

2

File: src/dao/DAO.sol

2: pragma solidity =0.8.22;

2

File: src/dao/DAOConfig.sol

2: pragma solidity =0.8.22;

2

File: src/dao/Proposals.sol

2: pragma solidity =0.8.22;

2

File: src/dao/Parameters.sol

2: pragma solidity =0.8.22;

2

File: src/launch/Airdrop.sol

2: pragma solidity =0.8.22;

2

File: src/launch/BootstrapBallot.sol

2: pragma solidity =0.8.22;

2

File: src/launch/InitialDistribution.sol

2: pragma solidity =0.8.22;

2

File: src/pools/PoolUtils.sol

1: pragma solidity =0.8.22;

1

File: src/pools/PoolsConfig.sol

2: pragma solidity =0.8.22;

2

File: src/pools/PoolStats.sol

2: pragma solidity =0.8.22;

2

File: src/pools/Pools.sol

2: pragma solidity =0.8.22;

2

File: src/pools/PoolMath.sol

1: pragma solidity =0.8.22;

1

File: src/price_feed/CoreChainlinkFeed.sol

2: pragma solidity =0.8.22;

2

File: src/price_feed/CoreUniswapFeed.sol

2: pragma solidity =0.8.22;

2

File: src/price_feed/PriceAggregator.sol

2: pragma solidity =0.8.22;

2

File: src/price_feed/CoreSaltyFeed.sol

2: pragma solidity =0.8.22;

2

File: src/rewards/RewardsConfig.sol

2: pragma solidity =0.8.22;

2

File: src/rewards/Emissions.sol

2: pragma solidity =0.8.22;

2

File: src/rewards/RewardsEmitter.sol

2: pragma solidity =0.8.22;

2

File: src/rewards/SaltRewards.sol

2: pragma solidity =0.8.22;

2

File: src/stable/USDS.sol

2: pragma solidity =0.8.22;

2

File: src/stable/StableConfig.sol

2: pragma solidity =0.8.22;

2

File: src/stable/CollateralAndLiquidity.sol

2: pragma solidity =0.8.22;

2

File: src/stable/Liquidizer.sol

2: pragma solidity =0.8.22;

2

File: src/staking/StakingConfig.sol

2: pragma solidity =0.8.22;

2

File: src/staking/Liquidity.sol

2: pragma solidity =0.8.22;

2

File: src/staking/StakingRewards.sol

2: pragma solidity =0.8.22;

2

File: src/staking/Staking.sol

2: pragma solidity =0.8.22;

2

File: src/ManagedWallet.sol

2: pragma solidity =0.8.22;

2

File: src/AccessManager.sol

2: pragma solidity =0.8.22;

2

File: src/Salt.sol

2: pragma solidity =0.8.22;

2

File: src/SigningTools.sol

1: pragma solidity =0.8.22;

1

File: src/Upkeep.sol

2: pragma solidity =0.8.22;

2

File: src/ExchangeConfig.sol

2: pragma solidity =0.8.22;

2

</details>

[N-07] Mixed usage of int/uint with int256/uint256

In Solidity, int256 and uint256 are the preferred type names, especially since they are used for function signatures. Using int or uint instead can lead to confusion and inconsistency. Suggest replacing them with int256 or uint256 respectively, for better clarity and uniformity in your codebase. Improving this aspect of your code can contribute to its readability, understandability, and thus maintainability in the long term.

<details> <summary><i>2 issue instances in 2 files:</i></summary>
File: src/pools/Pools.sol

81: modifier ensureNotExpired(uint deadline)

81

File: src/staking/Liquidity.sol

40: modifier ensureNotExpired(uint deadline)

40

</details>

[N-08] Inconsistent spacing in comments

Some lines use // x and some use //x. The instances below point out the usages that don't follow the majority, within each file:

<details> <summary><i>2 issue instances in 1 files:</i></summary>
File: src/pools/PoolMath.sol

187: //        uint256 C = r0 * ( r0 * z1 - r1 * z0 ) / ( r1 + z1 );
188: //        uint256 discriminant = B * B - 4 * A * C;

187 | 188

</details>

[N-09] Function/Constructor Argument Names Not in mixedCase

Underscore before of after function argument names is a common convention in Solidity NOT a documentation requirement.

Function arguments should use mixedCase for better readability and consistency with Solidity style guidelines. Examples of good practice include: initialSupply, account, recipientAddress, senderAddress, newOwner. More information in Documentation

Rule exceptions

  • Allow constant variable name/symbol/decimals to be lowercase (ERC20).
  • Allow _ at the beginning of the mixedCase match for private variables and unused parameters.
<details> <summary><i>29 issue instances in 24 files:</i></summary>
File: src/arbitrage/ArbitrageSearch.sol

17: constructor( IExchangeConfig _exchangeConfig )
    	{

17

File: src/dao/DAO.sol

68: constructor( IPools _pools, IProposals _proposals, IExchangeConfig _exchangeConfig, IPoolsConfig _poolsConfig, IStakingConfig _stakingConfig, IRewardsConfig _rewardsConfig, IStableConfig _stableConfig, IDAOConfig _daoConfig, IPriceAggregator _priceAggregator, IRewardsEmitter _liquidityRewardsEmitter, ICollateralAndLiquidity _collateralAndLiquidity )
		{

68

File: src/dao/Proposals.sol

68: constructor( IStaking _staking, IExchangeConfig _exchangeConfig, IPoolsConfig _poolsConfig, IDAOConfig _daoConfig )
		{

68

File: src/launch/Airdrop.sol

33: constructor( IExchangeConfig _exchangeConfig, IStaking _staking )
		{

33

File: src/launch/BootstrapBallot.sol

31: constructor( IExchangeConfig _exchangeConfig, IAirdrop _airdrop, uint256 ballotDuration )
		{

31

File: src/launch/InitialDistribution.sol

32: constructor( ISalt _salt, IPoolsConfig _poolsConfig, IEmissions _emissions, IBootstrapBallot _bootstrapBallot, IDAO _dao, VestingWallet _daoVestingWallet, VestingWallet _teamVestingWallet, IAirdrop _airdrop, ISaltRewards _saltRewards, ICollateralAndLiquidity _collateralAndLiquidity  )
		{

32

File: src/pools/PoolStats.sol

104: function _calculateArbitrageProfits( bytes32[] memory poolIDs, uint256[] memory _calculatedProfits ) internal view
		{
25: constructor( IExchangeConfig _exchangeConfig, IPoolsConfig _poolsConfig )
    	{

104 | 25

File: src/pools/Pools.sol

60: function setContracts( IDAO _dao, ICollateralAndLiquidity _collateralAndLiquidity ) external onlyOwner
		{

60

File: src/price_feed/CoreChainlinkFeed.sol

17: constructor( address _CHAINLINK_BTC_USD, address _CHAINLINK_ETH_USD )
		{

17

File: src/price_feed/CoreUniswapFeed.sol

30: constructor( IERC20 _wbtc, IERC20 _weth, IERC20 _usdc, address _UNISWAP_V3_WBTC_WETH, address _UNISWAP_V3_WETH_USDC )
		{

30

File: src/price_feed/PriceAggregator.sol

35: function setInitialFeeds( IPriceFeed _priceFeed1, IPriceFeed _priceFeed2, IPriceFeed _priceFeed3 ) public onlyOwner
		{

35

File: src/price_feed/CoreSaltyFeed.sol

20: constructor( IPools _pools, IExchangeConfig _exchangeConfig )
		{

20

File: src/rewards/Emissions.sol

25: constructor( ISaltRewards _saltRewards, IExchangeConfig _exchangeConfig, IRewardsConfig _rewardsConfig )
		{

25

File: src/rewards/RewardsEmitter.sol

36: constructor( IStakingRewards _stakingRewards, IExchangeConfig _exchangeConfig, IPoolsConfig _poolsConfig, IRewardsConfig _rewardsConfig, bool _isForCollateralAndLiquidity )
		{

36

File: src/rewards/SaltRewards.sol

26: constructor( IRewardsEmitter _stakingRewardsEmitter, IRewardsEmitter _liquidityRewardsEmitter, IExchangeConfig _exchangeConfig, IRewardsConfig _rewardsConfig )
		{

26

File: src/stable/USDS.sol

29: function setCollateralAndLiquidity( ICollateralAndLiquidity _collateralAndLiquidity ) external onlyOwner
		{

29

File: src/stable/CollateralAndLiquidity.sol

50: constructor( IPools _pools, IExchangeConfig _exchangeConfig, IPoolsConfig _poolsConfig, IStakingConfig _stakingConfig, IStableConfig _stableConfig, IPriceAggregator _priceAggregator, ILiquidizer _liquidizer )
		Liquidity( _pools, _exchangeConfig, _poolsConfig, _stakingConfig )
    	{

50

File: src/stable/Liquidizer.sol

63: function setContracts( ICollateralAndLiquidity _collateralAndLiquidity, IPools _pools, IDAO _dao) external onlyOwner
		{
47: constructor( IExchangeConfig _exchangeConfig, IPoolsConfig _poolsConfig  )
		{

63 | 47

File: src/staking/Liquidity.sol

29: constructor( IPools _pools, IExchangeConfig _exchangeConfig, IPoolsConfig _poolsConfig, IStakingConfig _stakingConfig )
		StakingRewards( _exchangeConfig, _poolsConfig, _stakingConfig )
		{

29

File: src/staking/StakingRewards.sol

46: constructor( IExchangeConfig _exchangeConfig, IPoolsConfig _poolsConfig, IStakingConfig _stakingConfig )
		{

46

File: src/ManagedWallet.sol

42: function proposeWallets( address _proposedMainWallet, address _proposedConfirmationWallet ) external
		{
29: constructor( address _mainWallet, address _confirmationWallet)
		{

42 | 29

File: src/AccessManager.sol

30: constructor( IDAO _dao )
		{

30

File: src/Upkeep.sol

65: constructor( IPools _pools, IExchangeConfig _exchangeConfig, IPoolsConfig _poolsConfig, IDAOConfig _daoConfig, IStableConfig _stableConfig, IPriceAggregator _priceAggregator, ISaltRewards _saltRewards, ICollateralAndLiquidity _collateralAndLiquidity, IEmissions _emissions, IDAO _dao )
		{

65

File: src/ExchangeConfig.sol

48: function setContracts( IDAO _dao, IUpkeep _upkeep, IInitialDistribution _initialDistribution, IAirdrop _airdrop, VestingWallet _teamVestingWallet, VestingWallet _daoVestingWallet ) external onlyOwner
		{
60: function setAccessManager( IAccessManager _accessManager ) external onlyOwner
		{
34: constructor( ISalt _salt, IERC20 _wbtc, IERC20 _weth, IERC20 _dai, IUSDS _usds, IManagedWallet _managedTeamWallet )
		{

48 | 60 | 34

</details>

[N-10] Insufficient Comments in Complex Functions

Complex functions spanning multiple lines should have more comments to better explain the purpose of each logic step. Proper commenting can greatly improve the readability and maintainability of the codebase. Consider adding explicit comments to provide insights into the function's operation.

<details> <summary><i>3 issue instances in 3 files:</i></summary>
File: src/dao/DAO.sol

/// @audit function with 38 lines has only 6 comment lines
153: function _executeApproval( Ballot memory ballot ) internal
		{

153

File: src/dao/Parameters.sol

/// @audit function with 52 lines has only 6 comment lines
57: function _executeParameterChange( ParameterTypes parameterType, bool increase, IPoolsConfig poolsConfig, IStakingConfig stakingConfig, IRewardsConfig rewardsConfig, IStableConfig stableConfig, IDAOConfig daoConfig, IPriceAggregator priceAggregator ) internal
		{

57

File: src/rewards/RewardsEmitter.sol

/// @audit function with 31 lines has only 11 comment lines
82: function performUpkeep( uint256 timeSinceLastUpkeep ) external
		{

82

</details>

[N-11] Variable Names Not in mixedCase

State or Local Variable names in your contract don't align with the Solidity naming convention. For clarity and code consistency, it's recommended to use mixedCase for local and state variables that are not constants, and add a trailing underscore for internal variables. Adhering to this convention helps in improving code readability and maintenance. More information in Documentation

<details> <summary><i>3 issue instances in 1 files:</i></summary>
File: src/price_feed/CoreUniswapFeed.sol

/// @audit - Variable name `uniswapWBTC_WETH` should be in mixedCase
99: uint256 uniswapWBTC_WETH = getUniswapTwapWei( UNISWAP_V3_WBTC_WETH, twapInterval );
/// @audit - Variable name `uniswapWETH_USDC` should be in mixedCase
100: uint256 uniswapWETH_USDC = getUniswapTwapWei( UNISWAP_V3_WETH_USDC, twapInterval );
/// @audit - Variable name `uniswapWETH_USDC` should be in mixedCase
119: uint256 uniswapWETH_USDC = getUniswapTwapWei( UNISWAP_V3_WETH_USDC, twapInterval );

99 | 100 | 119

</details>

[N-12] Consider making contracts Upgradeable

Contract uses a non-upgradeable design. Transitioning to an upgradeable contract structure is more aligned with contemporary smart contract practices. This approach not only enhances flexibility but also allows for continuous improvement and adaptation, ensuring the contract stays relevant and robust in an ever-evolving ecosystem.

<details> <summary><i>32 issue instances in 32 files:</i></summary>
File: src/arbitrage/ArbitrageSearch.sol

/// @audit - Contract `ArbitrageSearch` is not upgradeable
9: abstract contract ArbitrageSearch
    {

9

File: src/dao/DAO.sol

/// @audit - Contract `DAO` is not upgradeable
23: contract DAO is IDAO, Parameters, ReentrancyGuard
    {

23

File: src/dao/DAOConfig.sol

/// @audit - Contract `DAOConfig` is not upgradeable
9: contract DAOConfig is IDAOConfig, Ownable
    {

9

File: src/dao/Proposals.sol

/// @audit - Contract `Proposals` is not upgradeable
20: contract Proposals is IProposals, ReentrancyGuard
    {

20

File: src/dao/Parameters.sol

/// @audit - Contract `Parameters` is not upgradeable
10: abstract contract Parameters
    {

10

File: src/launch/Airdrop.sol

/// @audit - Contract `Airdrop` is not upgradeable
13: contract Airdrop is IAirdrop, ReentrancyGuard
    {

13

File: src/launch/BootstrapBallot.sol

/// @audit - Contract `BootstrapBallot` is not upgradeable
12: contract BootstrapBallot is IBootstrapBallot, ReentrancyGuard
    {

12

File: src/launch/InitialDistribution.sol

/// @audit - Contract `InitialDistribution` is not upgradeable
13: contract InitialDistribution is IInitialDistribution
    {

13

File: src/pools/PoolsConfig.sol

/// @audit - Contract `PoolsConfig` is not upgradeable
11: contract PoolsConfig is IPoolsConfig, Ownable
    {

11

File: src/pools/PoolStats.sol

/// @audit - Contract `PoolStats` is not upgradeable
12: abstract contract PoolStats is IPoolStats
	{

12

File: src/pools/Pools.sol

/// @audit - Contract `Pools` is not upgradeable
22: contract Pools is IPools, ReentrancyGuard, PoolStats, ArbitrageSearch, Ownable
	{

22

File: src/price_feed/CoreChainlinkFeed.sol

/// @audit - Contract `CoreChainlinkFeed` is not upgradeable
10: contract CoreChainlinkFeed is IPriceFeed
    {

10

File: src/price_feed/CoreUniswapFeed.sol

/// @audit - Contract `CoreUniswapFeed` is not upgradeable
14: contract CoreUniswapFeed is IPriceFeed
    {

14

File: src/price_feed/PriceAggregator.sol

/// @audit - Contract `PriceAggregator` is not upgradeable
13: contract PriceAggregator is IPriceAggregator, Ownable
    {

13

File: src/price_feed/CoreSaltyFeed.sol

/// @audit - Contract `CoreSaltyFeed` is not upgradeable
13: contract CoreSaltyFeed is IPriceFeed
    {

13

File: src/rewards/RewardsConfig.sol

/// @audit - Contract `RewardsConfig` is not upgradeable
9: contract RewardsConfig is IRewardsConfig, Ownable
    {

9

File: src/rewards/Emissions.sol

/// @audit - Contract `Emissions` is not upgradeable
14: contract Emissions is IEmissions
    {

14

File: src/rewards/RewardsEmitter.sol

/// @audit - Contract `RewardsEmitter` is not upgradeable
20: contract RewardsEmitter is IRewardsEmitter, ReentrancyGuard
    {

20

File: src/rewards/SaltRewards.sol

/// @audit - Contract `SaltRewards` is not upgradeable
15: contract SaltRewards is ISaltRewards
    {

15

File: src/stable/USDS.sol

/// @audit - Contract `USDS` is not upgradeable
13: contract USDS is ERC20, IUSDS, Ownable
    {

13

File: src/stable/StableConfig.sol

/// @audit - Contract `StableConfig` is not upgradeable
9: contract StableConfig is IStableConfig, Ownable
    {

9

File: src/stable/CollateralAndLiquidity.sol

/// @audit - Contract `CollateralAndLiquidity` is not upgradeable
21: contract CollateralAndLiquidity is Liquidity, ICollateralAndLiquidity
    {

21

File: src/stable/Liquidizer.sol

/// @audit - Contract `Liquidizer` is not upgradeable
21: contract Liquidizer is ILiquidizer, Ownable
    {

21

File: src/staking/StakingConfig.sol

/// @audit - Contract `StakingConfig` is not upgradeable
9: contract StakingConfig is IStakingConfig, Ownable
    {

9

File: src/staking/Liquidity.sol

/// @audit - Contract `Liquidity` is not upgradeable
17: abstract contract Liquidity is ILiquidity, StakingRewards
    {

17

File: src/staking/StakingRewards.sol

/// @audit - Contract `StakingRewards` is not upgradeable
20: abstract contract StakingRewards is IStakingRewards, ReentrancyGuard
    {

20

File: src/staking/Staking.sol

/// @audit - Contract `Staking` is not upgradeable
14: contract Staking is IStaking, StakingRewards
    {

14

File: src/ManagedWallet.sol

/// @audit - Contract `ManagedWallet` is not upgradeable
11: contract ManagedWallet is IManagedWallet
    {

11

File: src/AccessManager.sol

/// @audit - Contract `AccessManager` is not upgradeable
18: contract AccessManager is IAccessManager
	{

18

File: src/Salt.sol

/// @audit - Contract `Salt` is not upgradeable
6: contract Salt is ISalt, ERC20
    {

6

File: src/Upkeep.sol

/// @audit - Contract `Upkeep` is not upgradeable
38: contract Upkeep is IUpkeep, ReentrancyGuard
    {

38

File: src/ExchangeConfig.sol

/// @audit - Contract `ExchangeConfig` is not upgradeable
13: contract ExchangeConfig is IExchangeConfig, Ownable
    {

13

</details>

[N-13] Named Imports of Parent Contracts Are Missing

It's important to have named imports for parent contracts to ensure code readability and maintainability. Missing named imports can make it difficult to understand the code hierarchy and can lead to issues in the future.

<details> <summary><i>50 issue instances in 27 files:</i></summary>
File: src/dao/DAO.sol

/// @audit Missing named import for parent contract: `IDAO`
23: contract DAO is IDAO, Parameters, ReentrancyGuard
    {
/// @audit Missing named import for parent contract: `Parameters`
23: contract DAO is IDAO, Parameters, ReentrancyGuard
    {
/// @audit Missing named import for parent contract: `ReentrancyGuard`
23: contract DAO is IDAO, Parameters, ReentrancyGuard
    {

23 | 23 | 23

File: src/dao/DAOConfig.sol

/// @audit Missing named import for parent contract: `IDAOConfig`
9: contract DAOConfig is IDAOConfig, Ownable
    {
/// @audit Missing named import for parent contract: `Ownable`
9: contract DAOConfig is IDAOConfig, Ownable
    {

9 | 9

File: src/dao/Proposals.sol

/// @audit Missing named import for parent contract: `IProposals`
20: contract Proposals is IProposals, ReentrancyGuard
    {
/// @audit Missing named import for parent contract: `ReentrancyGuard`
20: contract Proposals is IProposals, ReentrancyGuard
    {

20 | 20

File: src/launch/Airdrop.sol

/// @audit Missing named import for parent contract: `IAirdrop`
13: contract Airdrop is IAirdrop, ReentrancyGuard
    {
/// @audit Missing named import for parent contract: `ReentrancyGuard`
13: contract Airdrop is IAirdrop, ReentrancyGuard
    {

13 | 13

File: src/launch/BootstrapBallot.sol

/// @audit Missing named import for parent contract: `IBootstrapBallot`
12: contract BootstrapBallot is IBootstrapBallot, ReentrancyGuard
    {
/// @audit Missing named import for parent contract: `ReentrancyGuard`
12: contract BootstrapBallot is IBootstrapBallot, ReentrancyGuard
    {

12 | 12

File: src/launch/InitialDistribution.sol

/// @audit Missing named import for parent contract: `IInitialDistribution`
13: contract InitialDistribution is IInitialDistribution
    {

13

File: src/pools/PoolsConfig.sol

/// @audit Missing named import for parent contract: `IPoolsConfig`
11: contract PoolsConfig is IPoolsConfig, Ownable
    {
/// @audit Missing named import for parent contract: `Ownable`
11: contract PoolsConfig is IPoolsConfig, Ownable
    {

11 | 11

File: src/pools/Pools.sol

/// @audit Missing named import for parent contract: `IPools`
22: contract Pools is IPools, ReentrancyGuard, PoolStats, ArbitrageSearch, Ownable
	{
/// @audit Missing named import for parent contract: `ReentrancyGuard`
22: contract Pools is IPools, ReentrancyGuard, PoolStats, ArbitrageSearch, Ownable
	{
/// @audit Missing named import for parent contract: `PoolStats`
22: contract Pools is IPools, ReentrancyGuard, PoolStats, ArbitrageSearch, Ownable
	{
/// @audit Missing named import for parent contract: `ArbitrageSearch`
22: contract Pools is IPools, ReentrancyGuard, PoolStats, ArbitrageSearch, Ownable
	{
/// @audit Missing named import for parent contract: `Ownable`
22: contract Pools is IPools, ReentrancyGuard, PoolStats, ArbitrageSearch, Ownable
	{

22 | 22 | 22 | 22 | 22

File: src/price_feed/CoreChainlinkFeed.sol

/// @audit Missing named import for parent contract: `IPriceFeed`
10: contract CoreChainlinkFeed is IPriceFeed
    {

10

File: src/price_feed/CoreUniswapFeed.sol

/// @audit Missing named import for parent contract: `IPriceFeed`
14: contract CoreUniswapFeed is IPriceFeed
    {

14

File: src/price_feed/PriceAggregator.sol

/// @audit Missing named import for parent contract: `IPriceAggregator`
13: contract PriceAggregator is IPriceAggregator, Ownable
    {
/// @audit Missing named import for parent contract: `Ownable`
13: contract PriceAggregator is IPriceAggregator, Ownable
    {

13 | 13

File: src/price_feed/CoreSaltyFeed.sol

/// @audit Missing named import for parent contract: `IPriceFeed`
13: contract CoreSaltyFeed is IPriceFeed
    {

13

File: src/rewards/RewardsConfig.sol

/// @audit Missing named import for parent contract: `IRewardsConfig`
9: contract RewardsConfig is IRewardsConfig, Ownable
    {
/// @audit Missing named import for parent contract: `Ownable`
9: contract RewardsConfig is IRewardsConfig, Ownable
    {

9 | 9

File: src/rewards/Emissions.sol

/// @audit Missing named import for parent contract: `IEmissions`
14: contract Emissions is IEmissions
    {

14

File: src/rewards/RewardsEmitter.sol

/// @audit Missing named import for parent contract: `IRewardsEmitter`
20: contract RewardsEmitter is IRewardsEmitter, ReentrancyGuard
    {
/// @audit Missing named import for parent contract: `ReentrancyGuard`
20: contract RewardsEmitter is IRewardsEmitter, ReentrancyGuard
    {

20 | 20

File: src/rewards/SaltRewards.sol

/// @audit Missing named import for parent contract: `ISaltRewards`
15: contract SaltRewards is ISaltRewards
    {

15

File: src/stable/USDS.sol

/// @audit Missing named import for parent contract: `ERC20`
13: contract USDS is ERC20, IUSDS, Ownable
    {
/// @audit Missing named import for parent contract: `IUSDS`
13: contract USDS is ERC20, IUSDS, Ownable
    {
/// @audit Missing named import for parent contract: `Ownable`
13: contract USDS is ERC20, IUSDS, Ownable
    {

13 | 13 | 13

File: src/stable/StableConfig.sol

/// @audit Missing named import for parent contract: `IStableConfig`
9: contract StableConfig is IStableConfig, Ownable
    {
/// @audit Missing named import for parent contract: `Ownable`
9: contract StableConfig is IStableConfig, Ownable
    {

9 | 9

File: src/stable/CollateralAndLiquidity.sol

/// @audit Missing named import for parent contract: `Liquidity`
21: contract CollateralAndLiquidity is Liquidity, ICollateralAndLiquidity
    {
/// @audit Missing named import for parent contract: `ICollateralAndLiquidity`
21: contract CollateralAndLiquidity is Liquidity, ICollateralAndLiquidity
    {

21 | 21

File: src/stable/Liquidizer.sol

/// @audit Missing named import for parent contract: `ILiquidizer`
21: contract Liquidizer is ILiquidizer, Ownable
    {
/// @audit Missing named import for parent contract: `Ownable`
21: contract Liquidizer is ILiquidizer, Ownable
    {

21 | 21

File: src/staking/StakingConfig.sol

/// @audit Missing named import for parent contract: `IStakingConfig`
9: contract StakingConfig is IStakingConfig, Ownable
    {
/// @audit Missing named import for parent contract: `Ownable`
9: contract StakingConfig is IStakingConfig, Ownable
    {

9 | 9

File: src/staking/Staking.sol

/// @audit Missing named import for parent contract: `IStaking`
14: contract Staking is IStaking, StakingRewards
    {
/// @audit Missing named import for parent contract: `StakingRewards`
14: contract Staking is IStaking, StakingRewards
    {

14 | 14

File: src/ManagedWallet.sol

/// @audit Missing named import for parent contract: `IManagedWallet`
11: contract ManagedWallet is IManagedWallet
    {

11

File: src/AccessManager.sol

/// @audit Missing named import for parent contract: `IAccessManager`
18: contract AccessManager is IAccessManager
	{

18

File: src/Salt.sol

/// @audit Missing named import for parent contract: `ISalt`
6: contract Salt is ISalt, ERC20
    {
/// @audit Missing named import for parent contract: `ERC20`
6: contract Salt is ISalt, ERC20
    {

6 | 6

File: src/Upkeep.sol

/// @audit Missing named import for parent contract: `ReentrancyGuard`
38: contract Upkeep is IUpkeep, ReentrancyGuard
    {

38

File: src/ExchangeConfig.sol

/// @audit Missing named import for parent contract: `IExchangeConfig`
13: contract ExchangeConfig is IExchangeConfig, Ownable
    {
/// @audit Missing named import for parent contract: `Ownable`
13: contract ExchangeConfig is IExchangeConfig, Ownable
    {

13 | 13

</details>

[N-14] Long functions should be refactored into multiple, smaller, functions

Functions that span many lines can be hard to understand and maintain. It is often beneficial to refactor long functions into multiple smaller functions. This improves readability, makes testing easier, and can even lead to gas optimization if it eliminates the need for variables that would otherwise have to be stored in memory.

<details> <summary><i>3 issue instances in 3 files:</i></summary>
File: src/dao/DAO.sol

 /// @audit 38 lines (excluding comments)
153: function _executeApproval( Ballot memory ballot ) internal
		{

153

File: src/dao/Parameters.sol

 /// @audit 52 lines (excluding comments)
57: function _executeParameterChange( ParameterTypes parameterType, bool increase, IPoolsConfig poolsConfig, IStakingConfig stakingConfig, IRewardsConfig rewardsConfig, IStableConfig stableConfig, IDAOConfig daoConfig, IPriceAggregator priceAggregator ) internal
		{

57

File: src/rewards/RewardsEmitter.sol

 /// @audit 31 lines (excluding comments)
82: function performUpkeep( uint256 timeSinceLastUpkeep ) external
		{

82

</details>

[N-15] Prefer Casting to bytes or bytes32 Over abi.encodePacked() for Single Arguments

When using abi.encodePacked() on a single argument, it is often clearer to use a cast to bytes or bytes32. This improves the semantic clarity of the code, making it easier for reviewers to understand the developer's intentions.

<details> <summary><i>2 issue instances in 1 files:</i></summary>
File: src/dao/Proposals.sol

251: require( keccak256(abi.encodePacked(newWebsiteURL)) != keccak256(abi.encodePacked("")), "newWebsiteURL cannot be empty" );
251: require( keccak256(abi.encodePacked(newWebsiteURL)) != keccak256(abi.encodePacked("")), "newWebsiteURL cannot be empty" );

251 | 251

</details>

[N-16] Use Unchecked for Divisions on Constant or Immutable Values

Unsigned divisions on constant or immutable values do not result in overflow. Therefore, these operations can be marked as unchecked, optimizing gas usage without compromising safety.

For instance, if a is an unsigned integer and b is a constant or immutable, a / b can be safely rewritten as: unchecked { a / b }

<details> <summary><i>2 issue instances in 1 files:</i></summary>
File: src/stable/CollateralAndLiquidity.sol

202: uint256 btcValue = ( amountBTC * btcPrice ) / wbtcTenToTheDecimals;
203: uint256 ethValue = ( amountETH * ethPrice ) / wethTenToTheDecimals;

202 | 203

</details>

#0 - c4-judge

2024-02-03T13:14:16Z

Picodes marked the issue as grade-b

Findings Information

Awards

20.7932 USDC - $20.79

Labels

bug
G (Gas Optimization)
grade-b
G-09

External Links

Gas Findings

Initial gas report was saved with forge snapshot.

Total gas saved per issue represent Overall gas change from forge snapshot --diff.

More details from forge snapshot --diff can be found in Gas Findings Details.

IssueInstancesTotal Gas Saved
[G-01]x.a = x.a + b always cheaper than x.a += b for Structs11778253
[G-02]Consider using delete rather than assigning false to clear values321620
[G-03]Stack variable is only used once83262798
[G-04]Optimize Storage with Byte Truncation for Time Related State Variables5100000
[G-05]Assigning state variables directly with struct constructors wastes gas479517
[G-06]Using delete on a uint/int variable is cheaper than assigning it to 079428
[G-07]Assembly: Check msg.sender using xor and the scratch space32672
[G-08]Use revert() to gain maximum gas savings1396950
[G-09]Using constants instead of enum can save gas10
[G-10]Optimize Increment and Decrement in loops with unchecked keyword211260
[G-11]Reduce deployment costs by tweaking contracts' metadata35371000
[G-12]Consider using solady's FixedPointMathLib200-
[G-13]Consider using OZ EnumerateSet in place of nested mappings55000
[G-14]Optimize require/revert Statements with Assembly13941700

Gas Findings Details

[G-01] x.a = x.a + b always cheaper than x.a += b for Structs

Examples:

    // direct write to storage   -> 5337 gas
    // struct storage pointer    -> 5341 gas
    // memory struct             -> 4628 gas
    // memory function param     -> 1109 gas
    x.a += b;

    // direct write to storage   -> 5330 gas
    // struct storage pointer    -> 5334 gas
    // memory struct             -> 4625 gas
    // memory function param     -> 1106 gas
    x.a = struct.a + b;

Result:

	testFinalizeDecreaseParameterBallots() (gas: -45 (-0.000%)) 
	testFinalizeNoChangeParameterBallots() (gas: -46 (-0.001%)) 
	testFinalizeIncreaseParameterBallots() (gas: -45 (-0.001%)) 
	testUnwhitelistTokenDenied() (gas: -57 (-0.003%)) 
	testUnwhitelistTokenApproved() (gas: -57 (-0.003%)) 
	testSuccessfulRewardDistributionAfterWhitelisting() (gas: -57 (-0.003%)) 
	testWhitelistTokenApproved() (gas: -57 (-0.003%)) 
	testProposeTokenUnwhitelistingChangesStateOnlyIfWhitelisted() (gas: -57 (-0.003%)) 
	testUserProposalAfterFinalization() (gas: -57 (-0.004%)) 
	testActiveProposalRestriction() (gas: -57 (-0.004%)) 
	testWhitelistTokenDenied() (gas: -57 (-0.004%)) 
	testPerformUpkeep() (gas: -36 (-0.005%)) 
	testIncludeCountryDenied() (gas: -45 (-0.005%)) 
	testIncludeCountryApproved() (gas: -46 (-0.005%)) 
	testSetWebsiteDenied2() (gas: -45 (-0.006%)) 
	testSetWebsiteApproved() (gas: -57 (-0.007%)) 
	testProposeTokenWhitelisting() (gas: -210 (-0.008%)) 
	testOpenBallotsForTokenWhitelisting() (gas: -210 (-0.008%)) 
	testCallContractApproved() (gas: -57 (-0.009%)) 
	testCallContractDenied() (gas: -57 (-0.010%)) 
	testFinalizedBallotsStillVisible() (gas: -45 (-0.011%)) 
	testSendSaltApproved() (gas: -57 (-0.011%)) 
	testProposeParameterBallotWithInvalidParameterType() (gas: -57 (-0.011%)) 
	testBallotIsApproved() (gas: -46 (-0.011%)) 
	testProposeCallContract() (gas: -57 (-0.011%)) 
	testSendSaltDenied() (gas: -57 (-0.011%)) 
	testProposeTokenWhitelistingMaxPending() (gas: -516 (-0.011%)) 
	testUserCannotVoteOnClosedBallot() (gas: -46 (-0.011%)) 
	testExcludeCountryApproved() (gas: -57 (-0.012%)) 
	testProposeWebsiteUpdate() (gas: -57 (-0.012%)) 
	testTokenWhitelistingBallots() (gas: -363 (-0.012%)) 
	testProposeTokenUnwhitelisting() (gas: -210 (-0.012%)) 
	testBallotForID() (gas: -45 (-0.012%)) 
	testUpdateLastUpkeepTime() (gas: -180 (-0.012%)) 
	testSetWebsiteDenied1() (gas: -57 (-0.013%)) 
	testExcludeCountryDenied() (gas: -57 (-0.013%)) 
	testMarkBallotAsFinalized() (gas: -46 (-0.013%)) 
	testUserHasActiveProposalFlagResetAfterBallotFinalized() (gas: -45 (-0.013%)) 
	testIncorrectApprovalVote() (gas: -57 (-0.013%)) 
	testMarkBallotAsFinalizedRemovesFromOpenBallots() (gas: -45 (-0.013%)) 
	testProposeParameterBallot() (gas: -57 (-0.013%)) 
	testIncorrectParameterVote() (gas: -57 (-0.013%)) 
	testSetContractApproved() (gas: -413 (-0.013%)) 
	testOpenBallots() (gas: -363 (-0.013%)) 
	testSetContractDenied2() (gas: -657 (-0.014%)) 
	testSwapDustRestriction() (gas: -325 (-0.016%)) 
	testSwapReserveBelowDust() (gas: -325 (-0.016%)) 
	testAddLiquidityWithReversedTokenOrder() (gas: -325 (-0.016%)) 
	testPlaceSmallInternalSwap() (gas: -382 (-0.017%)) 
	testPlaceInternalSwap() (gas: -382 (-0.017%)) 
	testRejectDualZapForNonWhitelistedPools() (gas: -325 (-0.019%)) 
	testAddLiquidityToNonWhitelistedPool() (gas: -325 (-0.019%)) 
	testAddLiquidityToBadPair() (gas: -325 (-0.019%)) 
	testCheckSwapK() (gas: -2600 (-0.020%)) 
	testCheckReserves() (gas: -2600 (-0.021%)) 
	testWhitelistingAlreadyWhitelisted() (gas: -57 (-0.021%)) 
	testAddLiquidityEstimate1() (gas: -854 (-0.023%)) 
	testAddLiquidityEstimate2() (gas: -854 (-0.023%)) 
	testUserLiquidationWithDivergentPrices() (gas: -382 (-0.023%)) 
	testInvalidInputs() (gas: -325 (-0.024%)) 
	testProposeDuplicateWebsiteUpdate() (gas: -210 (-0.025%)) 
	testUsersVotingPowerRemovedOnFullUnstake() (gas: -376 (-0.026%)) 
	testSetContractDenied1() (gas: -780 (-0.027%)) 
	testProposeCountryInclusionExclusion() (gas: -210 (-0.028%)) 
	testProposeSameBallotNameMultipleTimesAndForOpenBallot() (gas: -210 (-0.028%)) 
	testIncrementingBallotID() (gas: -210 (-0.029%)) 
	testAddLiquidityReturnValues() (gas: -644 (-0.029%)) 
	testDepositSwapWithdraw() (gas: -650 (-0.029%)) 
	testAddLiquidityRespectsTokenRatioWithNonZeroReserves() (gas: -644 (-0.031%)) 
	testQuorumReachedNotBeforeVotingPower() (gas: -168 (-0.031%)) 
	testFinalizeBallotWithNotFinalizableBallots() (gas: -168 (-0.034%)) 
	testPerformUpkeepMaxGas() (gas: -41238 (-0.037%)) 
	testVoteTallyResetUponChangingVote() (gas: -210 (-0.037%)) 
	testProposeSendSALT() (gas: -210 (-0.038%)) 
	testProposeCountryInclusionExclusionEmptyName() (gas: -57 (-0.041%)) 
	testProposeSetContractAddress() (gas: -210 (-0.042%)) 
	testUnstakeDurationTooLong() (gas: -57 (-0.043%)) 
	testUnstakeDurationTooShort() (gas: -57 (-0.043%)) 
	testProposeParameterBallotBeforeTimestamp() (gas: -57 (-0.043%)) 
	testDuplicateParameterBallot() (gas: -210 (-0.044%)) 
	testDuplicateApprovalBallot() (gas: -210 (-0.044%)) 
	testUserLiquidationWithTwoFailedPriceFeeds() (gas: -382 (-0.044%)) 
	testProposeSetContractAddressRejectsZeroAddress() (gas: -57 (-0.044%)) 
	testProposeCallContractZeroAddressRejection() (gas: -57 (-0.044%)) 
	testProposeWebsiteUpdateWithEmptyURL() (gas: -57 (-0.045%)) 
	testParameterBallotVoting() (gas: -363 (-0.046%)) 
	testUnstakeMoreThanStaked() (gas: -57 (-0.047%)) 
	testCollateralDepositersCanReceiveStakingRewards() (gas: -400 (-0.047%)) 
	testUserRewardsForPools() (gas: -114 (-0.048%)) 
	testAddLiquidityWithFlippedTokens() (gas: -1708 (-0.048%)) 
	testDepositDoubleSwapWithdraw() (gas: -1910 (-0.050%)) 
	testClaimRewardsWithNoShares() (gas: -111 (-0.050%)) 
	testMaximumLiquidityRemoval() (gas: -1055 (-0.050%)) 
	testRewardsCannotBeClaimedWithoutShares() (gas: -93 (-0.051%)) 
	testUserBorrowingWithBadPriceFeed() (gas: -382 (-0.055%)) 
	testClaimAllRewards() (gas: -150 (-0.055%)) 
	testDualZapInLiquidity() (gas: -1326 (-0.056%)) 
	testClaimAllRewardsMultipleValidPools() (gas: -150 (-0.058%)) 
	testAddLiquidity() (gas: -1300 (-0.059%)) 
	testCastVote() (gas: -363 (-0.060%)) 
	testClaimAirdrop() (gas: -433 (-0.061%)) 
	testStakingMoreThanBalance() (gas: -57 (-0.061%)) 
	testRepaymentAdjustsAccountingCorrectly() (gas: -305 (-0.061%)) 
	testStakeExcessSALT() (gas: -57 (-0.061%)) 
	testMaxWithdrawableCollateralAfterRepayment() (gas: -306 (-0.061%)) 
	testMultipleSwaps() (gas: -8990 (-0.062%)) 
	testStakeNegativeAmount() (gas: -57 (-0.062%)) 
	testRepayUSDS() (gas: -306 (-0.062%)) 
	testLiquidateUserWithoutBorrowedUSDS() (gas: -306 (-0.062%)) 
	testUserCannotVoteAfterUnstakingAllSALT() (gas: -376 (-0.062%)) 
	testBorrowBalanceNotNegativeAfterRepayment() (gas: -306 (-0.062%)) 
	testStakeWithNoAllowance() (gas: -57 (-0.063%)) 
	testClaimAirdrop2() (gas: -433 (-0.064%)) 
	testClaimedMappingAfterAirdropClaim() (gas: -433 (-0.064%)) 
	testStep9() (gas: -36 (-0.064%)) 
	testSeriesOfTrades() (gas: -1910 (-0.065%)) 
	testWinningParameterVote() (gas: -516 (-0.066%)) 
	testZapping() (gas: -25344 (-0.066%)) 
	testDepositAndWithdrawLiquidity2() (gas: -1584 (-0.067%)) 
	testProcessRewardsFromPOL() (gas: -800 (-0.067%)) 
	testDepositAndWithdrawMaximumLiquidity() (gas: -1584 (-0.068%)) 
	testZappingDust() (gas: -7920 (-0.068%)) 
	testArbitrageFailed() (gas: -1528 (-0.070%)) 
	testMultipleInteractions() (gas: -2056 (-0.070%)) 
	testComprehensivePerformUpkeep() (gas: -2168 (-0.071%)) 
	testPerformUpkeepZeroPrice() (gas: -2168 (-0.071%)) 
	testDoublePerformUpkeep() (gas: -2952 (-0.071%)) 
	testReserveAndBalanceManagement() (gas: -319 (-0.071%)) 
	testCanUserBeLiquidatedUnderThreshold() (gas: -382 (-0.073%)) 
	testProposeWithInsufficientStake() (gas: -363 (-0.073%)) 
	testTotalVotesCastForBallot() (gas: -516 (-0.074%)) 
	testMaxWithdrawableLP_and_maxBorrowableUSDS() (gas: -382 (-0.074%)) 
	testUserCollateralValueInUSD() (gas: -382 (-0.076%)) 
	testSuccessStep7() (gas: -917 (-0.079%)) 
	testCanUserBeLiquidated_TrueIfWalletOnBorrowedListAndUndercollateralized() (gas: -382 (-0.080%)) 
	testChangeVotesAfterUnstakingSALT() (gas: -695 (-0.080%)) 
	testWithdrawnPoolAddSALTRewards() (gas: -382 (-0.080%)) 
	testArbitrage4() (gas: -1910 (-0.080%)) 
	testArbitrage1() (gas: -1910 (-0.080%)) 
	testArbitrage2() (gas: -1910 (-0.080%)) 
	testArbitrage3() (gas: -1910 (-0.080%)) 
	testArbitrageReservesMin() (gas: -1910 (-0.080%)) 
	testRevertStep2() (gas: -36805 (-0.081%)) 
	testRevertStep3() (gas: -37187 (-0.081%)) 
	testRevertStep4() (gas: -37187 (-0.081%)) 
	testRevertStep9() (gas: -37533 (-0.082%)) 
	testRevertStep6() (gas: -37569 (-0.082%)) 
	testRevertStep1() (gas: -37569 (-0.082%)) 
	testRevertStep10() (gas: -37569 (-0.082%)) 
	testRevertStep5() (gas: -37569 (-0.082%)) 
	testRevertStep11() (gas: -37569 (-0.082%)) 
	testRevertStep7() (gas: -37569 (-0.082%)) 
	testUserCollateralValueInUSD2() (gas: -382 (-0.082%)) 
	testRevertStep8() (gas: -37569 (-0.082%)) 
	testApprovalBallotVoting() (gas: -835 (-0.083%)) 
	testClaimRewardsFromInvalidPools() (gas: -18 (-0.084%)) 
	testBorrowMaxPlusOneUSDS() (gas: -382 (-0.084%)) 
	testUserLiquidationWithTwoGoodFeeds() (gas: -1584 (-0.086%)) 
	testRepayUSDSExceedBorrowed() (gas: -382 (-0.087%)) 
	testSuccessStep9() (gas: -640 (-0.090%)) 
	testComprehensive() (gas: -2955 (-0.090%)) 
	testSuccessStep1() (gas: -917 (-0.090%)) 
	testNoDuplicateWalletsInBorrowUSDS() (gas: -683 (-0.091%)) 
	testMaxBorrowableUSDS() (gas: -1060 (-0.095%)) 
	testPerformUpkeepMaxGas() (gas: -5486 (-0.095%)) 
	testSuccessStep5() (gas: -1528 (-0.095%)) 
	testSuccessfulBalanceIncreaseAfterPerformUpkeep() (gas: -917 (-0.095%)) 
	testSuccessfulBalanceIncreaseAfterPerformUpkeepWithLimit() (gas: -917 (-0.095%)) 
	testPerformUpkeepSwapsCorrectTokenAmounts() (gas: -917 (-0.095%)) 
	testCannotBorrowMoreThanMaxBorrowableLimit() (gas: -306 (-0.096%)) 
	testSuccessStep3() (gas: -1223 (-0.096%)) 
	testCanFinalizeBallot() (gas: -892 (-0.097%)) 
	testUserPositionFunctions() (gas: -683 (-0.098%)) 
	testSuccessStep4() (gas: -1528 (-0.098%)) 
	testLiquidatePositionWithZeroBorrowedAmount() (gas: -306 (-0.101%)) 
	testFormPOL() (gas: -306 (-0.101%)) 
	testCannotClaimRewardsFromZeroPendingRewards() (gas: -150 (-0.101%)) 
	testArbitrage5() (gas: -4472 (-0.103%)) 
	testCorrectPOLFormationAndDistribution() (gas: -306 (-0.103%)) 
	testUserPendingReward() (gas: -210 (-0.105%)) 
	testPerformUpkeep() (gas: -1061 (-0.105%)) 
	testSaltyFeed() (gas: -611 (-0.106%)) 
	testRequiredQuorumForBallotType() (gas: -210 (-0.107%)) 
	testAddLiquidityAndIncreaseShare() (gas: -306 (-0.108%)) 
	testTotalCollateralValueInUSD() (gas: -1061 (-0.109%)) 
	testFindLiquidatablePositions_noLiquidatablePositions() (gas: -1061 (-0.109%)) 
	testWithdrawingMoreThanDeposited() (gas: -305 (-0.110%)) 
	testExpeditedUnstakeBurnsSalt() (gas: -301 (-0.111%)) 
	testDepositCollateralAndIncreaseTotalShares() (gas: -305 (-0.112%)) 
	testRecoverSALTAfterUnstaking() (gas: -300 (-0.112%)) 
	testLargeUSDSReservesETH() (gas: -305 (-0.112%)) 
	testCannotWithdrawPastDeadline() (gas: -306 (-0.112%)) 
	testLargeUSDSReservesBTC() (gas: -306 (-0.112%)) 
	testDepositCollateralAndIncreaseShare() (gas: -382 (-0.116%)) 
	testMinimumCollateralValueForBorrowing() (gas: -684 (-0.119%)) 
	testGetPriceConsistency() (gas: -611 (-0.125%)) 
	testRecoverSALTFromUnstakeNotBelongingToSender() (gas: -376 (-0.125%)) 
	testCorrectOperationOfGetPriceBTCAndETHWithSufficientReserves() (gas: -612 (-0.126%)) 
	testCancelUnstakeNonExistent() (gas: -376 (-0.129%)) 
	testRemoveLiquidity() (gas: -55820 (-0.130%)) 
	testUserCooldowns() (gas: -267 (-0.131%)) 
	testUnstakingDecreasesOnlyByUnstakedAmount() (gas: -376 (-0.131%)) 
	testFindLiquidatablePositions() (gas: -1645 (-0.131%)) 
	testUnstake() (gas: -376 (-0.132%)) 
	testNumberOfOpenPositions() (gas: -1645 (-0.136%)) 
	testExcessTokensAreReverted() (gas: -683 (-0.143%)) 
	testMaxRewardValueForCallingLiquidation() (gas: -1267 (-0.144%)) 
	testCancelUnstake2() (gas: -433 (-0.144%)) 
	testUserShareForPool_multipleCollateralPositions() (gas: -1438 (-0.145%)) 
	testUserShareForPools() (gas: -420 (-0.145%)) 
	testRewardValueForCallingLiquidation() (gas: -1267 (-0.150%)) 
	testRecoverSALTFromNonPendingUnstake() (gas: -433 (-0.150%)) 
	testUnstakingSALTWithVariousDurations() (gas: -1014 (-0.155%)) 
	testCancelCompletedUnstakeRevert() (gas: -376 (-0.156%)) 
	testLiquidateUserMarginallyAboveThreshold() (gas: -1267 (-0.158%)) 
	testLiquidatePositionFailure() (gas: -1267 (-0.158%)) 
	testTotalSharesForPool() (gas: -890 (-0.159%)) 
	testWithdrawCollateralAndSharesUpdate() (gas: -890 (-0.160%)) 
	testBorrowerPositionBeforeAndAfterRemovingLiquidity() (gas: -889 (-0.160%)) 
	testBorrowUSDSAndLiquidation() (gas: -1267 (-0.160%)) 
	testMultipleSharesAndRewards() (gas: -417 (-0.160%)) 
	testLiquidationRewardNotExceedingLimits() (gas: -1267 (-0.161%)) 
	testStakeSALTWithZeroBalance() (gas: -210 (-0.162%)) 
	testRecoverSalt() (gas: -1320 (-0.162%)) 
	testSimultaneousStaking() (gas: -363 (-0.165%)) 
	testMinLiquidityReceived() (gas: -325 (-0.166%)) 
	testLiquidateSelf() (gas: -1267 (-0.167%)) 
	testLiquidatePosition() (gas: -1645 (-0.170%)) 
	testMultipleUnstakeRecovery() (gas: -1014 (-0.173%)) 
	testMultiplePendingUnstakesWithDifferentCompletionTimes() (gas: -1014 (-0.174%)) 
	testUnstakesForUser() (gas: -1014 (-0.174%)) 
	testUnderwaterPosition() (gas: -2229 (-0.175%)) 
	testUserUnstakeIDs() (gas: -1167 (-0.176%)) 
	testPermissionChecksForCancelUnstakeAndRecoverSALT() (gas: -848 (-0.178%)) 
	testUnstakeStateUpdatesAfterCancelAndRecover() (gas: -848 (-0.180%)) 
	testCannotRecoverSALTFromCancelledUnstake() (gas: -529 (-0.181%)) 
	testPendingRewardsForPools() (gas: -2122 (-0.182%)) 
	testLiquidateUserUpdatesUsersWithBorrowedUSDS() (gas: -2229 (-0.183%)) 
	testCancelUnstake() (gas: -848 (-0.183%)) 
	testUserBalanceXSALT2() (gas: -1320 (-0.183%)) 
	testAliceBobCharlieMultipleSharesAndShareRewards() (gas: -471 (-0.184%)) 
	testUserLiquidationTwice() (gas: -2228 (-0.184%)) 
	testWithdrawLiquidityAfterPoolUnwhitelisted() (gas: -890 (-0.187%)) 
	testTotalSharesForPools() (gas: -726 (-0.187%)) 
	testUserDepositBorrowDepositAndLiquidate() (gas: -1900 (-0.188%)) 
	testCannotLiquidateWithinCertainTimeFrame() (gas: -1596 (-0.188%)) 
	testUnstakeBeforeCooldown() (gas: -634 (-0.192%)) 
	testUSDSBalanceDecreasedAfterPOLWithdrawal() (gas: -1474 (-0.195%)) 
	testDAOWithdrawPOL() (gas: -889 (-0.199%)) 
	testMaintainCorrectUnstakeIDsListAfterUnstakesAndCancels() (gas: -1486 (-0.199%)) 
	testWithdrawPOLProperlyWithdrawsLiquidity() (gas: -890 (-0.200%)) 
	testUserCollateralValueInUSD_multiplePositions() (gas: -1061 (-0.200%)) 
	testUserBalanceXSALT() (gas: -1473 (-0.203%)) 
	testStakingVariousAmounts() (gas: -516 (-0.213%)) 
	testWithdrawalOfPOLUpdatesDAOsPOLBalance() (gas: -1779 (-0.219%)) 
	testMultiAddRemoveLiquidity() (gas: -7336 (-0.222%)) 
	testAddLiquidityWithoutZapping() (gas: -1316 (-0.226%)) 
	testMultipleUserStakingClaiming() (gas: -3611 (-0.227%)) 
	testDepositAndWithdrawCollateral2() (gas: -1268 (-0.233%)) 
	testMultipleUserStakingClaiming() (gas: -3654 (-0.237%)) 
	testAddingDustLiquidity() (gas: -472 (-0.240%)) 
	testValidWithdrawLiquidityAndClaim() (gas: -890 (-0.253%)) 
	testtransferStakedSaltFromAirdropToUser() (gas: -529 (-0.260%)) 
	testDepositAndWithdrawCollateral() (gas: -1852 (-0.280%)) 
	testIncreaseDecreaseShare() (gas: -376 (-0.317%)) 
	testUserCanIncreaseOrDecreaseShare() (gas: -376 (-0.321%)) 
	testCorrectXSALTAssignment() (gas: -669 (-0.346%)) 
	testAddRemoveLiquidityBeforeExchangeLive() (gas: -11216 (-0.351%)) 
	testSetContracts() (gas: -11216 (-0.352%)) 
	testMultipleUsersActions() (gas: -1073 (-0.364%)) 
	testReturnValues() (gas: -319 (-0.372%)) 
	testAddLiquidityWithExceededMaxAmount0() (gas: -319 (-0.377%)) 
	testLiveBalanceCheck() (gas: -12194 (-0.378%)) 
	testAddLiquidityWithExceededMaxAmount1() (gas: -319 (-0.389%)) 
	testGasAddLiquidity() (gas: -319 (-0.419%)) 
	testSequentialLiquidityAdjustment() (gas: -3606 (-0.445%)) 
	testMinLiquidityAndReclaimedAmounts() (gas: -730 (-1.218%)) 
	testUnimodalHypothesis(uint256,uint256,uint256,uint256,uint256,uint256,uint256) (gas: -28 (-1.772%)) 
	Overall gas change: -778253 (-0.063%)
<details> <summary><i>11 issue instances in 2 files:</i></summary>
File: src/pools/Pools.sol

100: reserves.reserve0 += uint128(maxAmount0)
101: reserves.reserve1 += uint128(maxAmount1)
125: reserves.reserve0 += uint128(addedAmount0)
126: reserves.reserve1 += uint128(addedAmount1)
182: reserves.reserve0 -= uint128(reclaimedA)
183: reserves.reserve1 -= uint128(reclaimedB)

100 | 101 | 125 | 126 | 182 | 183

File: src/staking/StakingRewards.sol

83: user.virtualRewards += uint128(virtualRewardsToAdd)
88: user.userShare += uint128(increaseShareAmount)
125: user.userShare -= uint128(decreaseShareAmount)
126: user.virtualRewards -= uint128(virtualRewardsToRemove)
159: userInfo[poolID].virtualRewards += uint128(pendingRewards)

83 | 88 | 125 | 126 | 159

</details>

[G-02] Consider using delete rather than assigning false to clear values

Result:

	testIncludeCountryApproved() (gas: -20 (-0.002%)) 
	testRevertStep6() (gas: -1800 (-0.004%)) 
	testRevertStep1() (gas: -1800 (-0.004%)) 
	testRevertStep10() (gas: -1800 (-0.004%)) 
	testRevertStep5() (gas: -1800 (-0.004%)) 
	testRevertStep9() (gas: -1800 (-0.004%)) 
	testRevertStep11() (gas: -1800 (-0.004%)) 
	testRevertStep7() (gas: -1800 (-0.004%)) 
	testRevertStep3() (gas: -1800 (-0.004%)) 
	testRevertStep4() (gas: -1800 (-0.004%)) 
	testRevertStep8() (gas: -1800 (-0.004%)) 
	testRevertStep2() (gas: -1800 (-0.004%)) 
	testDAOConstructor() (gas: -1800 (-0.039%)) 
	Overall gas change: -21620 (-0.002%)
<details> <summary><i>3 issue instances in 2 files:</i></summary>
File: src/dao/DAO.sol

187: excludedCountries[ ballot.string1 ] = false

187

File: src/dao/Proposals.sol

143: ballot.ballotIsLive = false
147: _userHasActiveProposal[userThatPostedBallot] = false

143 | 147

</details>

[G-03] Stack variable is only used once

Test only one case due to the large number of instances.

Test case:

File: src/dao/DAO.sol
-			Ballot memory ballot = proposals.ballotForID(ballotID);
-			_executeApproval( ballot );
+			_executeApproval( proposals.ballotForID(ballotID) );

Result:

	testSuccessfulRewardDistributionAfterWhitelisting() (gas: -1 (-0.000%)) 
	testWhitelistTokenApproved() (gas: -1 (-0.000%)) 
	testWhitelistTokenDenied() (gas: -1 (-0.000%)) 
	testUnwhitelistTokenDenied() (gas: -2 (-0.000%)) 
	testCallContractDenied() (gas: -1 (-0.000%)) 
	testSendSaltDenied() (gas: -1 (-0.000%)) 
	testSetContractDenied1() (gas: -6 (-0.000%)) 
	testSetWebsiteDenied1() (gas: -1 (-0.000%)) 
	testExcludeCountryDenied() (gas: -1 (-0.000%)) 
	testUnwhitelistTokenApproved() (gas: -26 (-0.001%)) 
	testIncludeCountryDenied() (gas: -20 (-0.002%)) 
	testSetWebsiteDenied2() (gas: -21 (-0.003%)) 
	testSetContractDenied2() (gas: -125 (-0.003%)) 
	testCallContractApproved() (gas: -25 (-0.004%)) 
	testSendSaltApproved() (gas: -25 (-0.005%)) 
	testIncludeCountryApproved() (gas: -40 (-0.005%)) 
	testCoreUniswapFeedConstructor(address,address,address) (gas: -61 (-0.005%)) 
	testSetContractApproved() (gas: -160 (-0.005%)) 
	testExcludeCountryApproved() (gas: -25 (-0.005%)) 
	testSetWebsiteApproved() (gas: -50 (-0.006%)) 
	testRevertStep6() (gas: -21849 (-0.048%)) 
	testRevertStep1() (gas: -21849 (-0.048%)) 
	testRevertStep10() (gas: -21849 (-0.048%)) 
	testRevertStep5() (gas: -21849 (-0.048%)) 
	testRevertStep9() (gas: -21849 (-0.048%)) 
	testRevertStep11() (gas: -21849 (-0.048%)) 
	testRevertStep7() (gas: -21849 (-0.048%)) 
	testRevertStep3() (gas: -21849 (-0.048%)) 
	testRevertStep4() (gas: -21849 (-0.048%)) 
	testRevertStep8() (gas: -21849 (-0.048%)) 
	testRevertStep2() (gas: -21849 (-0.048%)) 
	testDAOConstructor() (gas: -21866 (-0.469%)) 
	Overall gas change: -262798 (-0.021%)
<details> <summary><i>83 issue instances in 22 files:</i></summary>
File: src/arbitrage/ArbitrageSearch.sol

/// @audit - `profitRightOfMidpoint` variable
86: int256 profitRightOfMidpoint = int256(amountOut) - int256(midpoint)

86

File: src/dao/DAO.sol

/// @audit - `ballot` variable
223: Ballot memory ballot = proposals.ballotForID(ballotID)
/// @audit - `saltBalance` variable
246: uint256 saltBalance = exchangeConfig.salt().balanceOf( address(this) )
/// @audit - `bestWhitelistingBallotID` variable
250: uint256 bestWhitelistingBallotID = proposals.tokenWhitelistingBallotWithTheMostVotes()
/// @audit - `pool1` variable
257: bytes32 pool1 = PoolUtils._poolID( IERC20(ballot.address1), exchangeConfig.wbtc() )
/// @audit - `pool2` variable
258: bytes32 pool2 = PoolUtils._poolID( IERC20(ballot.address1), exchangeConfig.weth() )
/// @audit - `remainingSALT` variable
345: uint256 remainingSALT = claimedSALT - amountToSendToTeam
/// @audit - `saltToBurn` variable
348: uint256 saltToBurn = ( remainingSALT * daoConfig.percentPolRewardsBurned() ) / 100
/// @audit - `poolID` variable
364: bytes32 poolID = PoolUtils._poolID(tokenA, tokenB)
/// @audit - `liquidityToWithdraw` variable
369: uint256 liquidityToWithdraw = (liquidityHeld * percentToLiquidate) / 100

223 | 246 | 250 | 257 | 258 | 345 | 348 | 364 | 369

File: src/dao/Proposals.sol

/// @audit - `totalStaked` variable
89: uint256 totalStaked = staking.totalShares(PoolUtils.STAKED_SALT)
/// @audit - `userXSalt` variable
94: uint256 userXSalt = staking.userShareForPool( msg.sender, PoolUtils.STAKED_SALT )
/// @audit - `ballotMinimumEndTime` variable
105: uint256 ballotMinimumEndTime = block.timestamp + daoConfig.ballotMinimumDuration()
/// @audit - `userThatPostedBallot` variable
146: address userThatPostedBallot = _usersThatProposedBallots[ballotID]
/// @audit - `ballotName` variable
157: string memory ballotName = string.concat("parameter:", Strings.toString(parameterType) )
/// @audit - `ballotName` variable
171: string memory ballotName = string.concat("whitelist:", Strings.toHexString(address(token)) )
/// @audit - `ballotName` variable
189: string memory ballotName = string.concat("unwhitelist:", Strings.toHexString(address(token)) )
/// @audit - `balance` variable
201: uint256 balance = exchangeConfig.salt().balanceOf( address(exchangeConfig.dao()) )
/// @audit - `maxSendable` variable
202: uint256 maxSendable = balance * 5 / 100
/// @audit - `ballotName` variable
207: string memory ballotName = "sendSALT"
/// @audit - `ballotName` variable
217: string memory ballotName = string.concat("callContract:", Strings.toHexString(address(contractAddress)) )
/// @audit - `ballotName` variable
226: string memory ballotName = string.concat("include:", country )
/// @audit - `ballotName` variable
235: string memory ballotName = string.concat("exclude:", country )
/// @audit - `ballotName` variable
244: string memory ballotName = string.concat("setContract:", contractName )
/// @audit - `ballotName` variable
253: string memory ballotName = string.concat("setURL:", newWebsiteURL )
/// @audit - `totalSupply` variable
334: uint256 totalSupply = ERC20(address(exchangeConfig.salt())).totalSupply()
/// @audit - `ballot` variable
346: Ballot memory ballot = ballots[ballotID]
/// @audit - `quorum` variable
419: uint256 quorum = requiredQuorumForBallotType( BallotType.WHITELIST_TOKEN)

89 | 94 | 105 | 146 | 157 | 171 | 189 | 201 | 202 | 207 | 217 | 226 | 235 | 244 | 253 | 334 | 346 | 419

File: src/launch/BootstrapBallot.sol

/// @audit - `messageHash` variable
53: bytes32 messageHash = keccak256(abi.encodePacked(block.chainid, msg.sender))

53

File: src/launch/InitialDistribution.sol

/// @audit - `poolIDs` variable
68: bytes32[] memory poolIDs = poolsConfig.whitelistedPools()

68

File: src/pools/PoolsConfig.sol

/// @audit - `poolID1` variable
146: bytes32 poolID1 = PoolUtils._poolID( token, wbtc )
/// @audit - `poolID2` variable
150: bytes32 poolID2 = PoolUtils._poolID( token, weth )

146 | 150

File: src/pools/PoolStats.sol

/// @audit - `poolID` variable
40: bytes32 poolID = PoolUtils._poolID( arbToken2, arbToken3 )
/// @audit - `poolID` variable
63: bytes32 poolID = PoolUtils._poolID( tokenA, tokenB )

40 | 63

File: src/pools/Pools.sol

/// @audit - `arbitrageProfit` variable
356: uint256 arbitrageProfit = _attemptArbitrage( swapTokenIn, swapTokenOut, swapAmountIn )
/// @audit - `middleAmountOut` variable
399: uint256 middleAmountOut = _adjustReservesForSwapAndAttemptArbitrage(swapTokenIn, swapTokenMiddle, swapAmountIn, 0 )

356 | 399

File: src/pools/PoolMath.sol

/// @audit - `C` variable
192: uint256 C = r0 * ( r1 * z0 - r0 * z1 ) / ( r1 + z1 )
/// @audit - `discriminant` variable
193: uint256 discriminant = B * B + 4 * A * C

192 | 193

File: src/price_feed/CoreChainlinkFeed.sol

/// @audit - `answerDelay` variable
43: uint256 answerDelay = block.timestamp - _answerTimestamp

43

File: src/price_feed/CoreUniswapFeed.sol

/// @audit - `tick` variable
59: int24 tick = int24((tickCumulatives[1] - tickCumulatives[0]) / int56(uint56(twapInterval)))

59

File: src/price_feed/PriceAggregator.sol

/// @audit - `price1` variable
178: uint256 price1 = _getPriceBTC(priceFeed1)
/// @audit - `price2` variable
179: uint256 price2 = _getPriceBTC(priceFeed2)
/// @audit - `price3` variable
180: uint256 price3 = _getPriceBTC(priceFeed3)
/// @audit - `price1` variable
190: uint256 price1 = _getPriceETH(priceFeed1)
/// @audit - `price2` variable
191: uint256 price2 = _getPriceETH(priceFeed2)
/// @audit - `price3` variable
192: uint256 price3 = _getPriceETH(priceFeed3)

178 | 179 | 180 | 190 | 191 | 192

File: src/rewards/Emissions.sol

/// @audit - `saltBalance` variable
52: uint256 saltBalance = salt.balanceOf( address( this ) )

52

File: src/rewards/RewardsEmitter.sol

/// @audit - `numeratorMult` variable
112: uint256 numeratorMult = timeSinceLastUpkeep * rewardsConfig.rewardsEmitterDailyPercentTimes1000()
/// @audit - `denominatorMult` variable
113: uint256 denominatorMult = 1 days * 100000
/// @audit - `sum` variable
115: uint256 sum = 0

112 | 113 | 115

File: src/rewards/SaltRewards.sol

/// @audit - `amountPerPool` variable
84: uint256 amountPerPool = liquidityBootstrapAmount / poolIDs.length
/// @audit - `liquidityRewardsAmount` variable
144: uint256 liquidityRewardsAmount = remainingRewards - stakingRewardsAmount

84 | 144

File: src/stable/StableConfig.sol

/// @audit - `remainingRatioAfterReward` variable
52: uint256 remainingRatioAfterReward = minimumCollateralRatioPercent - rewardPercentForCallingLiquidation - 1
/// @audit - `remainingRatioAfterReward` variable
128: uint256 remainingRatioAfterReward = minimumCollateralRatioPercent - 1 - rewardPercentForCallingLiquidation

52 | 128

File: src/stable/CollateralAndLiquidity.sol

/// @audit - `btcPrice` variable
198: uint256 btcPrice = priceAggregator.getPriceBTC()
/// @audit - `ethPrice` variable
199: uint256 ethPrice = priceAggregator.getPriceETH()
/// @audit - `btcValue` variable
202: uint256 btcValue = ( amountBTC * btcPrice ) / wbtcTenToTheDecimals
/// @audit - `ethValue` variable
203: uint256 ethValue = ( amountETH * ethPrice ) / wethTenToTheDecimals
/// @audit - `userWBTC` variable
232: uint256 userWBTC = (reservesWBTC * userCollateralAmount ) / totalCollateralShares
/// @audit - `userWETH` variable
233: uint256 userWETH = (reservesWETH * userCollateralAmount ) / totalCollateralShares
/// @audit - `maxWithdrawableValue` variable
258: uint256 maxWithdrawableValue = userCollateralValue - requiredCollateralValueAfterWithdrawal
/// @audit - `userCollateralValue` variable
304: uint256 userCollateralValue = userCollateralValueInUSD(wallet)
/// @audit - `totalCollateralShares` variable
317: uint256 totalCollateralShares = totalShares[collateralPoolID]
/// @audit - `minCollateralValue` variable
326: uint256 minCollateralValue = (usdsBorrowedByUsers[wallet] * stableConfig.minimumCollateralRatioPercent()) / 100
/// @audit - `minCollateral` variable
329: uint256 minCollateral = (minCollateralValue * totalCollateralShares) / totalCollateralValue

198 | 199 | 202 | 203 | 232 | 233 | 258 | 304 | 317 | 326 | 329

File: src/staking/StakingRewards.sol

/// @audit - `userInfo` variable
149: mapping(bytes32=>UserShareInfo) storage userInfo = _userShareInfo[msg.sender]
/// @audit - `userInfo` variable
294: mapping(bytes32=>UserShareInfo) storage userInfo = _userShareInfo[wallet]

149 | 294

File: src/staking/Staking.sol

/// @audit - `completionTime` variable
65: uint256 completionTime = block.timestamp + numWeeks * ( 1 weeks )
/// @audit - `u` variable
68: Unstake memory u = Unstake( UnstakeState.PENDING, msg.sender, amountUnstaked, claimableSALT, completionTime, unstakeID )
/// @audit - `index` variable
162: uint256 index
/// @audit - `percentAboveMinimum` variable
207: uint256 percentAboveMinimum = 100 - minUnstakePercent
/// @audit - `numerator` variable
210: uint256 numerator = unstakedXSALT * ( minUnstakePercent * unstakeRange + percentAboveMinimum * ( numWeeks - minUnstakeWeeks ) )

65 | 68 | 162 | 207 | 210

File: src/AccessManager.sol

/// @audit - `messageHash` variable
53: bytes32 messageHash = keccak256(abi.encodePacked(block.chainid, geoVersion, wallet))

53

File: src/SigningTools.sol

/// @audit - `recoveredAddress` variable
26: address recoveredAddress = ecrecover(messageHash, v, r, s)

26

File: src/Upkeep.sol

/// @audit - `rewardAmount` variable
119: uint256 rewardAmount = withdrawnAmount * daoConfig.upkeepRewardPercent() / 100
/// @audit - `amountOfWETH` variable
152: uint256 amountOfWETH = wethBalance * stableConfig.percentArbitrageProfitsForStablePOL() / 100
/// @audit - `amountOfWETH` variable
165: uint256 amountOfWETH = wethBalance * daoConfig.arbitrageProfitsPercentPOL() / 100
/// @audit - `amountSALT` variable
178: uint256 amountSALT = pools.depositSwapWithdraw( weth, salt, wethBalance, 0, block.timestamp )
/// @audit - `timeSinceLastUpkeep` variable
186: uint256 timeSinceLastUpkeep = block.timestamp - lastUpkeepTimeEmissions
/// @audit - `profitsForPools` variable
196: uint256[] memory profitsForPools = pools.profitsForWhitelistedPools()
/// @audit - `poolIDs` variable
198: bytes32[] memory poolIDs = poolsConfig.whitelistedPools()
/// @audit - `releaseableAmount` variable
233: uint256 releaseableAmount = VestingWallet(payable(exchangeConfig.teamVestingWallet())).releasable(address(salt))
/// @audit - `daoWETH` variable
287: uint256 daoWETH = pools.depositedUserBalance( address(dao), weth )

119 | 152 | 165 | 178 | 186 | 196 | 198 | 233 | 287

</details>

[G-04] Optimize Storage with Byte Truncation for Time Related State Variables

Certain state variables, particularly timestamps, can be safely stored using uint32. By optimizing these variables, contracts can utilize storage more efficiently. This not only results in a reduction in the initial gas costs (due to fewer Gsset operations) but also provides savings in subsequent read and write operations.

<details> <summary><i>5 issue instances in 5 files:</i></summary>
File: src/dao/DAOConfig.sol

/// @audit - State variables after packing use 8 storage slots, but can be packed to 5 storage slots.
//	uint256 public upkeepRewardPercent = 5;
//	uint256 public arbitrageProfitsPercentPOL = 20;
//	uint256 public percentPolRewardsBurned = 50;
//	uint256 public bootstrappingRewards = 200000 ether;
//	uint32 public maxPendingTokensForWhitelisting = 5;
//	uint32 public requiredProposalPercentStakeTimes1000 = 500;
//	uint32 public ballotMinimumDuration = 10 days;
//	uint32 public baseBallotQuorumPercentTimes1000 = 10 * 1000;

9: contract DAOConfig is IDAOConfig, Ownable

9

File: src/launch/BootstrapBallot.sol

/// @audit - State variables after packing use 4 storage slots, but can be packed to 2 storage slots.
//	mapping(address => bool) public hasVoted;
//	uint32 public startExchangeNo;
//	uint32 public startExchangeYes;
//	bool public startExchangeApproved;
//	bool public ballotFinalized;

13: contract BootstrapBallot is IBootstrapBallot, ReentrancyGuard

13

File: src/rewards/RewardsConfig.sol

/// @audit - State variables after packing use 4 storage slots, but can be packed to 3 storage slots.
//	uint256 public percentRewardsSaltUSDS = 10;
//	uint256 public stakingRewardsPercent = 50;
//	uint32 public emissionsWeeklyPercentTimes1000 = 500;
//	uint32 public rewardsEmitterDailyPercentTimes1000 = 1000;

9: contract RewardsConfig is IRewardsConfig, Ownable

9

File: src/ManagedWallet.sol

/// @audit - State variables after packing use 5 storage slots, but can be packed to 4 storage slots.
//	address public proposedConfirmationWallet;
//	uint32 public activeTimelock;
//	address public proposedMainWallet;
//	address public confirmationWallet;
//	address public mainWallet;

12: contract ManagedWallet is IManagedWallet

12

File: src/Upkeep.sol

/// @audit - State variables after packing use 2 storage slots, but can be packed to 1 storage slots.
//	uint32 public lastUpkeepTimeRewardsEmitters;
//	uint32 public lastUpkeepTimeEmissions;

39: contract Upkeep is IUpkeep, ReentrancyGuard

39

</details>

[G-05] Assigning state variables directly with struct constructors wastes gas

Examples:

    // stateStruct = TestStruct(20, 20); // 4633 gas
    // stateStruct = TestStruct({ var1: 20, var2: 20 }); // 4633 gas
    // stateStruct.var1 = 20;
    // stateStruct.var2 = 20; // 4562 gas

Test case:

File: src/dao/Proposals.sol
-		// _lastUserVoteForBallot[ballotID][msg.sender] = UserVote( vote, userVotingPower );
+		_lastUserVoteForBallot[ballotID][msg.sender].vote = Vote(vote);
+		_lastUserVoteForBallot[ballotID][msg.sender].votingPower = userVotingPower;

Result:

	testRevertStep6() (gas: -7815 (-0.017%)) 
	testRevertStep1() (gas: -7815 (-0.017%)) 
	testRevertStep10() (gas: -7815 (-0.017%)) 
	testRevertStep5() (gas: -7815 (-0.017%)) 
	testRevertStep9() (gas: -7815 (-0.017%)) 
	testRevertStep11() (gas: -7815 (-0.017%)) 
	testRevertStep7() (gas: -7815 (-0.017%)) 
	testRevertStep3() (gas: -7815 (-0.017%)) 
	testRevertStep4() (gas: -7815 (-0.017%)) 
	testRevertStep8() (gas: -7815 (-0.017%)) 
	testRevertStep2() (gas: -7815 (-0.017%)) 
	testParameterBallotVoting() (gas: 138 (0.018%)) 
	testTotalVotesCastForBallot() (gas: 138 (0.020%)) 
	testApprovalBallotVoting() (gas: 230 (0.023%)) 
	testWinningParameterVote() (gas: 184 (0.024%)) 
	Overall gas change: -79517 (-0.006%)
<details> <summary><i>4 issue instances in 3 files:</i></summary>
File: src/dao/Proposals.sol

109: ballots[ballotID] = Ballot( ballotID, true, ballotType, ballotName, address1, number1, string1, string2, ballotMinimumEndTime )
290: _lastUserVoteForBallot[ballotID][msg.sender] = UserVote( vote, userVotingPower )

109 | 290

File: src/pools/PoolsConfig.sol

54: underlyingPoolTokens[poolID] = TokenPair(tokenA, tokenB)

54

File: src/pools/PoolStats.sol

96: _arbitrageIndicies[poolID] = ArbitrageIndicies(poolIndex1, poolIndex2, poolIndex3)

96

</details>

[G-06] Using delete on a uint/int variable is cheaper than assigning it to 0

Result:

	testPerformUpkeepMaxGas() (gas: -396 (-0.000%)) 
	testPerformUpkeepZeroPrice() (gas: -14 (-0.000%)) 
	testComprehensivePerformUpkeep() (gas: -15 (-0.000%)) 
	testPerformUpkeepMaxGas() (gas: -29 (-0.001%)) 
	testDoublePerformUpkeep() (gas: -29 (-0.001%)) 
	testRevertStep7() (gas: -400 (-0.001%)) 
	testRevertStep6() (gas: -418 (-0.001%)) 
	testRevertStep1() (gas: -418 (-0.001%)) 
	testRevertStep10() (gas: -418 (-0.001%)) 
	testRevertStep5() (gas: -418 (-0.001%)) 
	testRevertStep9() (gas: -418 (-0.001%)) 
	testRevertStep11() (gas: -418 (-0.001%)) 
	testRevertStep3() (gas: -418 (-0.001%)) 
	testRevertStep4() (gas: -418 (-0.001%)) 
	testRevertStep8() (gas: -418 (-0.001%)) 
	testRevertStep2() (gas: -418 (-0.001%)) 
	testComprehensive() (gas: -36 (-0.001%)) 
	testSuccessStep7() (gas: -15 (-0.001%)) 
	testProcessRewardsFromPOL() (gas: -18 (-0.002%)) 
	testPerformUpkeep() (gas: -18 (-0.002%)) 
	testUserCooldowns() (gas: -5 (-0.002%)) 
	testProfitsForPoolsSimple() (gas: -24 (-0.003%)) 
	testUpdateLastUpkeepTime() (gas: -90 (-0.006%)) 
	testLiveBalanceCheck() (gas: -400 (-0.012%)) 
	testAddRemoveLiquidityBeforeExchangeLive() (gas: 400 (0.013%)) 
	testSetContracts() (gas: 400 (0.013%)) 
	testPriceFeedGas() (gas: -36 (-0.041%)) 
	testUserCooldownsWhenNotInPools() (gas: -10 (-0.052%)) 
	testUniswap() (gas: -54 (-0.066%)) 
	testCoreUniswapFeedConstructor(address,address,address) (gas: -2231 (-0.180%)) 
	testCoreUniswapFeedInitialization() (gas: -2200 (-0.183%)) 
	testUnimodalHypothesis(uint256,uint256,uint256,uint256,uint256,uint256,uint256) (gas: -28 (-1.772%)) 
	Overall gas change: -9428 (-0.001%)
<details> <summary><i>7 issue instances in 6 files:</i></summary>
File: src/pools/PoolStats.sol

54: _arbitrageProfits[ poolIDs[i] ] = 0

54

File: src/price_feed/CoreChainlinkFeed.sol

48: price = 0

48

File: src/price_feed/CoreUniswapFeed.sol

54: secondsAgo[1] = 0

54

File: src/stable/CollateralAndLiquidity.sol

184: usdsBorrowedByUsers[wallet] = 0

184

File: src/stable/Liquidizer.sol

113: usdsThatShouldBeBurned = 0

113

File: src/staking/StakingRewards.sol

151: claimableRewards = 0
301: cooldowns[i] = 0

151 | 301

</details>

[G-07] Assembly: Check msg.sender using xor and the scratch space

See this prior finding for details on the conversion

<details> <summary><i>32 issue instances in 17 files:</i></summary>
File: src/dao/DAO.sol

297: require( msg.sender == address(exchangeConfig.upkeep()), "DAO.withdrawArbitrageProfits is only callable from the Upkeep contract" );
318: require( msg.sender == address(exchangeConfig.upkeep()), "DAO.formPOL is only callable from the Upkeep contract" );
329: require( msg.sender == address(exchangeConfig.upkeep()), "DAO.processRewardsFromPOL is only callable from the Upkeep contract" );
362: require(msg.sender == address(liquidizer), "DAO.withdrawProtocolOwnedLiquidity is only callable from the Liquidizer contract" );

297 | 318 | 329 | 362

File: src/dao/Proposals.sol

86: if ( msg.sender != address(exchangeConfig.dao() ) )
124: require( msg.sender == address(exchangeConfig.dao()), "Only the DAO can create a confirmation proposal" );
132: require( msg.sender == address(exchangeConfig.dao()), "Only the DAO can mark a ballot as finalized" );

86 | 124 | 132

File: src/launch/Airdrop.sol

48: require( msg.sender == address(exchangeConfig.initialDistribution().bootstrapBallot()), "Only the BootstrapBallot can call Airdrop.authorizeWallet" );
58: require( msg.sender == address(exchangeConfig.initialDistribution()), "Airdrop.allowClaiming can only be called by the InitialDistribution contract" );

48 | 58

File: src/launch/InitialDistribution.sol

52: require( msg.sender == address(bootstrapBallot), "InitialDistribution.distributionApproved can only be called from the BootstrapBallot contract" );

52

File: src/pools/PoolStats.sol

49: require(msg.sender == address(exchangeConfig.upkeep()), "PoolStats.clearProfitsForPools is only callable from the Upkeep contract" );

49

File: src/pools/Pools.sol

72: require( msg.sender == address(exchangeConfig.initialDistribution().bootstrapBallot()), "Pools.startExchangeApproved can only be called from the BootstrapBallot contract" );
142: require( msg.sender == address(collateralAndLiquidity), "Pools.addLiquidity is only callable from the CollateralAndLiquidity contract" );
172: require( msg.sender == address(collateralAndLiquidity), "Pools.removeLiquidity is only callable from the CollateralAndLiquidity contract" );

72 | 142 | 172

File: src/rewards/Emissions.sol

42: require( msg.sender == address(exchangeConfig.upkeep()), "Emissions.performUpkeep is only callable from the Upkeep contract" );

42

File: src/rewards/RewardsEmitter.sol

84: require( msg.sender == address(exchangeConfig.upkeep()), "RewardsEmitter.performUpkeep is only callable from the Upkeep contract" );

84

File: src/rewards/SaltRewards.sol

108: require( msg.sender == address(exchangeConfig.initialDistribution()), "SaltRewards.sendInitialRewards is only callable from the InitialDistribution contract" );
119: require( msg.sender == address(exchangeConfig.upkeep()), "SaltRewards.performUpkeep is only callable from the Upkeep contract" );

108 | 119

File: src/stable/USDS.sol

42: require( msg.sender == address(collateralAndLiquidity), "USDS.mintTo is only callable from the Collateral contract" );

42

File: src/stable/CollateralAndLiquidity.sol

142: require( wallet != msg.sender, "Cannot liquidate self" );

142

File: src/stable/Liquidizer.sol

83: require( msg.sender == address(collateralAndLiquidity), "Liquidizer.incrementBurnableUSDS is only callable from the CollateralAndLiquidity contract" );
134: require( msg.sender == address(exchangeConfig.upkeep()), "Liquidizer.performUpkeep is only callable from the Upkeep contract" );

83 | 134

File: src/staking/StakingRewards.sol

65: if ( msg.sender != address(exchangeConfig.dao()) ) // DAO doesn't use the cooldown
105: if ( msg.sender != address(exchangeConfig.dao()) ) // DAO doesn't use the cooldown

65 | 105

File: src/staking/Staking.sol

90: require( msg.sender == u.wallet, "Sender is not the original staker" );
106: require( msg.sender == u.wallet, "Sender is not the original staker" );
132: require( msg.sender == address(exchangeConfig.airdrop()), "Staking.transferStakedSaltFromAirdropToUser is only callable from the Airdrop contract" );

90 | 106 | 132

File: src/ManagedWallet.sol

44: require( msg.sender == mainWallet, "Only the current mainWallet can propose changes" );
61: require( msg.sender == confirmationWallet, "Invalid sender" );
76: require( msg.sender == proposedMainWallet, "Invalid sender" );

44 | 61 | 76

File: src/AccessManager.sol

43: require( msg.sender == address(dao), "AccessManager.excludedCountriedUpdated only callable by the DAO" );

43

File: src/Upkeep.sol

97: require(msg.sender == address(this), "Only callable from within the same contract");

97

</details>

[G-08] Use revert() to gain maximum gas savings

If you dont need Error messages, or you want gain maximum gas savings - revert() is a cheapest way to revert transaction in terms of gas.

    revert(); // 117 gas 
    require(false); // 132 gas
    revert CustomError(); // 157 gas
    assert(false); // 164 gas
    revert("Custom Error"); // 406 gas
    require(false, "Custom Error"); // 421 gas
<details> <summary><i>139 issue instances in 23 files:</i></summary>
File: src/dao/DAO.sol

247: require( saltBalance >= bootstrappingRewards * 2, "Whitelisting is not currently possible due to insufficient bootstrapping rewards" )
251: require( bestWhitelistingBallotID == ballotID, "Only the token whitelisting ballot with the most votes can be finalized" )
281: require( proposals.canFinalizeBallot(ballotID), "The ballot is not yet able to be finalized" )
297: require( msg.sender == address(exchangeConfig.upkeep()), "DAO.withdrawArbitrageProfits is only callable from the Upkeep contract" )
318: require( msg.sender == address(exchangeConfig.upkeep()), "DAO.formPOL is only callable from the Upkeep contract" )
329: require( msg.sender == address(exchangeConfig.upkeep()), "DAO.processRewardsFromPOL is only callable from the Upkeep contract" )
362: require(msg.sender == address(liquidizer), "DAO.withdrawProtocolOwnedLiquidity is only callable from the Liquidizer contract" )

247 | 251 | 281 | 297 | 318 | 329 | 362

File: src/dao/Proposals.sol

83: require( block.timestamp >= firstPossibleProposalTimestamp, "Cannot propose ballots within the first 45 days of deployment" )
92: require( requiredXSalt > 0, "requiredXSalt cannot be zero" )
95: require( userXSalt >= requiredXSalt, "Sender does not have enough xSALT to make the proposal" )
98: require( ! _userHasActiveProposal[msg.sender], "Users can only have one active proposal at a time" )
102: require( openBallotsByName[ballotName] == 0, "Cannot create a proposal similar to a ballot that is still open" )
103: require( openBallotsByName[ string.concat(ballotName, "_confirm")] == 0, "Cannot create a proposal for a ballot with a secondary confirmation" )
124: require( msg.sender == address(exchangeConfig.dao()), "Only the DAO can create a confirmation proposal" )
132: require( msg.sender == address(exchangeConfig.dao()), "Only the DAO can mark a ballot as finalized" )
164: require( address(token) != address(0), "token cannot be address(0)" )
165: require( token.totalSupply() < type(uint112).max, "Token supply cannot exceed uint112.max" )
167: require( _openBallotsForTokenWhitelisting.length() < daoConfig.maxPendingTokensForWhitelisting(), "The maximum number of token whitelisting proposals are already pending" )
168: require( poolsConfig.numberOfWhitelistedPools() < poolsConfig.maximumWhitelistedPools(), "Maximum number of whitelisted pools already reached" )
169: require( ! poolsConfig.tokenHasBeenWhitelisted(token, exchangeConfig.wbtc(), exchangeConfig.weth()), "The token has already been whitelisted" )
182: require( poolsConfig.tokenHasBeenWhitelisted(token, exchangeConfig.wbtc(), exchangeConfig.weth()), "Can only unwhitelist a whitelisted token" )
183: require( address(token) != address(exchangeConfig.wbtc()), "Cannot unwhitelist WBTC" )
184: require( address(token) != address(exchangeConfig.weth()), "Cannot unwhitelist WETH" )
185: require( address(token) != address(exchangeConfig.dai()), "Cannot unwhitelist DAI" )
186: require( address(token) != address(exchangeConfig.usds()), "Cannot unwhitelist USDS" )
187: require( address(token) != address(exchangeConfig.salt()), "Cannot unwhitelist SALT" )
198: require( wallet != address(0), "Cannot send SALT to address(0)" )
203: require( amount <= maxSendable, "Cannot send more than 5% of the DAO SALT balance" )
215: require( contractAddress != address(0), "Contract address cannot be address(0)" )
224: require( bytes(country).length == 2, "Country must be an ISO 3166 Alpha-2 Code" )
233: require( bytes(country).length == 2, "Country must be an ISO 3166 Alpha-2 Code" )
242: require( newAddress != address(0), "Proposed address cannot be address(0)" )
251: require( keccak256(abi.encodePacked(newWebsiteURL)) != keccak256(abi.encodePacked("")), "newWebsiteURL cannot be empty" )
264: require( ballot.ballotIsLive, "The specified ballot is not open for voting" )
268: require( (vote == Vote.INCREASE) || (vote == Vote.DECREASE) || (vote == Vote.NO_CHANGE), "Invalid VoteType for Parameter Ballot" )
270: require( (vote == Vote.YES) || (vote == Vote.NO), "Invalid VoteType for Approval Ballot" )
277: require( userVotingPower > 0, "Staked SALT required to vote" )
321: require( totalStaked != 0, "SALT staked cannot be zero to determine quorum" )

83 | 92 | 95 | 98 | 102 | 103 | 124 | 132 | 164 | 165 | 167 | 168 | 169 | 182 | 183 | 184 | 185 | 186 | 187 | 198 | 203 | 215 | 224 | 233 | 242 | 251 | 264 | 268 | 270 | 277 | 321

File: src/launch/Airdrop.sol

48: require( msg.sender == address(exchangeConfig.initialDistribution().bootstrapBallot()), "Only the BootstrapBallot can call Airdrop.authorizeWallet" )
49: require( ! claimingAllowed, "Cannot authorize after claiming is allowed" )
58: require( msg.sender == address(exchangeConfig.initialDistribution()), "Airdrop.allowClaiming can only be called by the InitialDistribution contract" )
59: require( ! claimingAllowed, "Claiming is already allowed" )
60: require(numberAuthorized() > 0, "No addresses authorized to claim airdrop.")
76: require( claimingAllowed, "Claiming is not allowed yet" )
77: require( isAuthorized(msg.sender), "Wallet is not authorized for airdrop" )
78: require( ! claimed[msg.sender], "Wallet already claimed the airdrop" )

48 | 49 | 58 | 59 | 60 | 76 | 77 | 78

File: src/launch/BootstrapBallot.sol

36: require( ballotDuration > 0, "ballotDuration cannot be zero" )
50: require( ! hasVoted[msg.sender], "User already voted" )
54: require(SigningTools._verifySignature(messageHash, signature), "Incorrect BootstrapBallot.vote signatory" )
71: require( ! ballotFinalized, "Ballot has already been finalized" )
72: require( block.timestamp >= completionTimestamp, "Ballot is not yet complete" )

36 | 50 | 54 | 71 | 72

File: src/launch/InitialDistribution.sol

52: require( msg.sender == address(bootstrapBallot), "InitialDistribution.distributionApproved can only be called from the BootstrapBallot contract" )
53: require( salt.balanceOf(address(this)) == 100 * MILLION_ETHER, "SALT has already been sent from the contract" )

52 | 53

File: src/pools/PoolsConfig.sol

47: require( _whitelist.length() < maximumWhitelistedPools, "Maximum number of whitelisted pools already reached" )
48: require(tokenA != tokenB, "tokenA and tokenB cannot be the same token")
136: require(address(pair.tokenA) != address(0) && address(pair.tokenB) != address(0), "This poolID does not exist")

47 | 48 | 136

File: src/pools/PoolStats.sol

49: require(msg.sender == address(exchangeConfig.upkeep()), "PoolStats.clearProfitsForPools is only callable from the Upkeep contract" )

49

File: src/pools/Pools.sol

72: require( msg.sender == address(exchangeConfig.initialDistribution().bootstrapBallot()), "Pools.startExchangeApproved can only be called from the BootstrapBallot contract" )
83: require(block.timestamp <= deadline, "TX EXPIRED")
142: require( msg.sender == address(collateralAndLiquidity), "Pools.addLiquidity is only callable from the CollateralAndLiquidity contract" )
143: require( exchangeIsLive, "The exchange is not yet live" )
144: require( address(tokenA) != address(tokenB), "Cannot add liquidity for duplicate tokens" )
146: require( maxAmountA > PoolUtils.DUST, "The amount of tokenA to add is too small" )
147: require( maxAmountB > PoolUtils.DUST, "The amount of tokenB to add is too small" )
158: require( addedLiquidity >= minLiquidityReceived, "Too little liquidity received" )
172: require( msg.sender == address(collateralAndLiquidity), "Pools.removeLiquidity is only callable from the CollateralAndLiquidity contract" )
173: require( liquidityToRemove > 0, "The amount of liquidityToRemove cannot be zero" )
187: require((reserves.reserve0 >= PoolUtils.DUST) && (reserves.reserve0 >= PoolUtils.DUST), "Insufficient reserves after liquidity removal")
193: require( (reclaimedA >= minReclaimedA) && (reclaimedB >= minReclaimedB), "Insufficient underlying tokens returned" )
207: require( amount > PoolUtils.DUST, "Deposit amount too small")
221: require( _userDeposits[msg.sender][token] >= amount, "Insufficient balance to withdraw specified amount" )
222: require( amount > PoolUtils.DUST, "Withdraw amount too small")
243: require((reserve0 >= PoolUtils.DUST) && (reserve1 >= PoolUtils.DUST), "Insufficient reserves before swap")
271: require( (reserve0 >= PoolUtils.DUST) && (reserve1 >= PoolUtils.DUST), "Insufficient reserves after swap")
353: require( swapAmountOut >= minAmountOut, "Insufficient resulting token amount" )
371: require( userDeposits[swapTokenIn] >= swapAmountIn, "Insufficient deposited token balance of initial token" )

72 | 83 | 142 | 143 | 144 | 146 | 147 | 158 | 172 | 173 | 187 | 193 | 207 | 221 | 222 | 243 | 271 | 353 | 371

File: src/price_feed/PriceAggregator.sol

39: require( address(priceFeed1) == address(0), "setInitialFeeds() can only be called once" )
183: require (price != 0, "Invalid BTC price" )
195: require (price != 0, "Invalid ETH price" )

39 | 183 | 195

File: src/rewards/Emissions.sol

42: require( msg.sender == address(exchangeConfig.upkeep()), "Emissions.performUpkeep is only callable from the Upkeep contract" )

42

File: src/rewards/RewardsEmitter.sol

63: require( poolsConfig.isWhitelisted( addedReward.poolID ), "Invalid pool" )
84: require( msg.sender == address(exchangeConfig.upkeep()), "RewardsEmitter.performUpkeep is only callable from the Upkeep contract" )

63 | 84

File: src/rewards/SaltRewards.sol

60: require( poolIDs.length == profitsForPools.length, "Incompatible array lengths" )
108: require( msg.sender == address(exchangeConfig.initialDistribution()), "SaltRewards.sendInitialRewards is only callable from the InitialDistribution contract" )
119: require( msg.sender == address(exchangeConfig.upkeep()), "SaltRewards.performUpkeep is only callable from the Upkeep contract" )
120: require( poolIDs.length == profitsForPools.length, "Incompatible array lengths" )

60 | 108 | 119 | 120

File: src/stable/USDS.sol

42: require( msg.sender == address(collateralAndLiquidity), "USDS.mintTo is only callable from the Collateral contract" )
43: require( amount > 0, "Cannot mint zero USDS" )

42 | 43

File: src/stable/CollateralAndLiquidity.sol

83: require( userShareForPool( msg.sender, collateralPoolID ) > 0, "User does not have any collateral" )
84: require( collateralToWithdraw <= maxWithdrawableCollateral(msg.sender), "Excessive collateralToWithdraw" )
97: require( exchangeConfig.walletHasAccess(msg.sender), "Sender does not have exchange access" )
98: require( userShareForPool( msg.sender, collateralPoolID ) > 0, "User does not have any collateral" )
99: require( amountBorrowed <= maxBorrowableUSDS(msg.sender), "Excessive amountBorrowed" )
117: require( userShareForPool( msg.sender, collateralPoolID ) > 0, "User does not have any collateral" )
118: require( amountRepaid <= usdsBorrowedByUsers[msg.sender], "Cannot repay more than the borrowed amount" )
119: require( amountRepaid > 0, "Cannot repay zero amount" )
142: require( wallet != msg.sender, "Cannot liquidate self" )
145: require( canUserBeLiquidated(wallet), "User cannot be liquidated" )

83 | 84 | 97 | 98 | 99 | 117 | 118 | 119 | 142 | 145

File: src/stable/Liquidizer.sol

83: require( msg.sender == address(collateralAndLiquidity), "Liquidizer.incrementBurnableUSDS is only callable from the CollateralAndLiquidity contract" )
134: require( msg.sender == address(exchangeConfig.upkeep()), "Liquidizer.performUpkeep is only callable from the Upkeep contract" )

83 | 134

File: src/staking/Liquidity.sol

42: require(block.timestamp <= deadline, "TX EXPIRED")
85: require( exchangeConfig.walletHasAccess(msg.sender), "Sender does not have exchange access" )
124: require( userShareForPool(msg.sender, poolID) >= liquidityToWithdraw, "Cannot withdraw more than existing user share" )
148: require( PoolUtils._poolID( tokenA, tokenB ) != collateralPoolID, "Stablecoin collateral cannot be deposited via Liquidity.depositLiquidityAndIncreaseShare" )
159: require( PoolUtils._poolID( tokenA, tokenB ) != collateralPoolID, "Stablecoin collateral cannot be withdrawn via Liquidity.withdrawLiquidityAndClaim" )

42 | 85 | 124 | 148 | 159

File: src/staking/StakingRewards.sol

59: require( poolsConfig.isWhitelisted( poolID ), "Invalid pool" )
60: require( increaseShareAmount != 0, "Cannot increase zero share" )
67: require( block.timestamp >= user.cooldownExpiration, "Must wait for the cooldown to expire" )
99: require( decreaseShareAmount != 0, "Cannot decrease zero share" )
102: require( decreaseShareAmount <= user.userShare, "Cannot decrease more than existing user share" )
107: require( block.timestamp >= user.cooldownExpiration, "Must wait for the cooldown to expire" )
190: require( poolsConfig.isWhitelisted( poolID ), "Invalid pool" )

59 | 60 | 67 | 99 | 102 | 107 | 190

File: src/staking/Staking.sol

43: require( exchangeConfig.walletHasAccess(msg.sender), "Sender does not have exchange access" )
62: require( userShareForPool(msg.sender, PoolUtils.STAKED_SALT) >= amountUnstaked, "Cannot unstake more than the amount staked" )
88: require( u.status == UnstakeState.PENDING, "Only PENDING unstakes can be cancelled" )
89: require( block.timestamp < u.completionTime, "Unstakes that have already completed cannot be cancelled" )
90: require( msg.sender == u.wallet, "Sender is not the original staker" )
104: require( u.status == UnstakeState.PENDING, "Only PENDING unstakes can be claimed" )
105: require( block.timestamp >= u.completionTime, "Unstake has not completed yet" )
106: require( msg.sender == u.wallet, "Sender is not the original staker" )
132: require( msg.sender == address(exchangeConfig.airdrop()), "Staking.transferStakedSaltFromAirdropToUser is only callable from the Airdrop contract" )
153: require(end >= start, "Invalid range: end cannot be less than start")
157: require(userUnstakes.length > end, "Invalid range: end is out of bounds")
158: require(start < userUnstakes.length, "Invalid range: start is out of bounds")
204: require( numWeeks >= minUnstakeWeeks, "Unstaking duration too short" )
205: require( numWeeks <= maxUnstakeWeeks, "Unstaking duration too long" )

43 | 62 | 88 | 89 | 90 | 104 | 105 | 106 | 132 | 153 | 157 | 158 | 204 | 205

File: src/ManagedWallet.sol

44: require( msg.sender == mainWallet, "Only the current mainWallet can propose changes" )
45: require( _proposedMainWallet != address(0), "_proposedMainWallet cannot be the zero address" )
46: require( _proposedConfirmationWallet != address(0), "_proposedConfirmationWallet cannot be the zero address" )
49: require( proposedMainWallet == address(0), "Cannot overwrite non-zero proposed mainWallet." )
61: require( msg.sender == confirmationWallet, "Invalid sender" )
76: require( msg.sender == proposedMainWallet, "Invalid sender" )
77: require( block.timestamp >= activeTimelock, "Timelock not yet completed" )

44 | 45 | 46 | 49 | 61 | 76 | 77

File: src/AccessManager.sol

43: require( msg.sender == address(dao), "AccessManager.excludedCountriedUpdated only callable by the DAO" )
63: require( _verifyAccess(msg.sender, signature), "Incorrect AccessManager.grantAccess signatory" )

43 | 63

File: src/SigningTools.sol

13: require( signature.length == 65, "Invalid signature length" )

13

File: src/Upkeep.sol

97: require(msg.sender == address(this), "Only callable from within the same contract")

97

File: src/ExchangeConfig.sol

51: require( address(dao) == address(0), "setContracts can only be called once" )
64: require( address(_accessManager) != address(0), "_accessManager cannot be address(0)" )

51 | 64

</details>

[G-09] Using constants instead of enum can save gas

Enum is expensive and it is more efficient to use constants instead. An illustrative example of this approach can be found in the ReentrancyGuard.sol

<details> <summary><i>1 issue instances in 1 files:</i></summary>
File: src/dao/Parameters.sol

14: enum ParameterTypes {

		// PoolsConfig
		maximumWhitelistedPools,
		maximumInternalSwapPercentTimes1000,

		// StakingConfig
		minUnstakeWeeks,
		maxUnstakeWeeks,
		minUnstakePercent,
		modificationCooldown,

		// RewardsConfig
    	rewardsEmitterDailyPercentTimes1000,
		emissionsWeeklyPercentTimes1000,
		stakingRewardsPercent,
		percentRewardsSaltUSDS,

		// StableConfig
		rewardPercentForCallingLiquidation,
		maxRewardValueForCallingLiquidation,
		minimumCollateralValueForBorrowing,
		initialCollateralRatioPercent,
		minimumCollateralRatioPercent,
		percentArbitrageProfitsForStablePOL,

		// DAOConfig
		bootstrappingRewards,
		percentPolRewardsBurned,
		baseBallotQuorumPercentTimes1000,
		ballotDuration,
		requiredProposalPercentStakeTimes1000,
		maxPendingTokensForWhitelisting,
		arbitrageProfitsPercentPOL,
		upkeepRewardPercent,

		// PriceAggregator
		maximumPriceFeedPercentDifferenceTimes1000,
		setPriceFeedCooldown
		}

14

</details>

[G-10] Optimize Increment and Decrement in loops with unchecked keyword

Use unchecked{i++} or unchecked{++i} instead of i++ or ++i when it is not possible for them to overflow. This is applicable for Solidity version 0.8.0 or higher to 0.8.23 and saves 30-40 gas per loop.

<details> <summary><i>21 issue instances in 8 files:</i></summary>
File: src/arbitrage/ArbitrageSearch.sol

115: for( uint256 i = 0; i < 8; i++ )
				{

115

File: src/dao/Proposals.sol

423: for( uint256 i = 0; i < _openBallotsForTokenWhitelisting.length(); i++ )
			{

423

File: src/pools/PoolStats.sol

53: for( uint256 i = 0; i < poolIDs.length; i++ )
			_arbitrageProfits[ poolIDs[i] ] = 0;
		}


	// The index of pool tokenA/tokenB within the whitelistedPools array.
	// Should always find a value as only whitelisted pools are used in the arbitrage path.
	// Returns uint64.max in the event of failed lookup
	function _poolIndex( IERC20 tokenA, IERC20 tokenB, bytes32[] memory poolIDs ) internal pure returns (uint64 index)
		{
65: for( uint256 i = 0; i < poolIDs.length; i++ )
			{
81: for( uint256 i = 0; i < poolIDs.length; i++ )
			{
106: for( uint256 i = 0; i < poolIDs.length; i++ )
			{

53 | 65 | 81 | 106

File: src/rewards/RewardsEmitter.sol

60: for( uint256 i = 0; i < addedRewards.length; i++ )
			{
116: for( uint256 i = 0; i < poolIDs.length; i++ )
			{

60 | 116

File: src/rewards/SaltRewards.sol

64: for( uint256 i = 0; i < addedRewards.length; i++ )
			{
87: for( uint256 i = 0; i < addedRewards.length; i++ )
			addedRewards[i] = AddedReward( poolIDs[i], amountPerPool );

		// Send the liquidity bootstrap rewards to the liquidityRewardsEmitter
		liquidityRewardsEmitter.addSALTRewards( addedRewards );
		}


	function _sendInitialStakingRewards( uint256 stakingBootstrapAmount ) internal
		{

64 | 87

File: src/stable/CollateralAndLiquidity.sol

321: for ( uint256 i = startIndex; i <= endIndex; i++ )
				{
321: for ( uint256 i = startIndex; i <= endIndex; i++ )
				{
338: for ( uint256 i = 0; i < count; i++ )
			resizedLiquidatableUsers[i] = liquidatableUsers[i];

		return resizedLiquidatableUsers;
		}


	function findLiquidatableUsers() external view returns (address[] memory)
		{

321 | 321 | 338

File: src/staking/StakingRewards.sol

152: for( uint256 i = 0; i < poolIDs.length; i++ )
			{
185: for( uint256 i = 0; i < addedRewards.length; i++ )
			{
216: for( uint256 i = 0; i < shares.length; i++ )
			shares[i] = totalShares[ poolIDs[i] ];
		}


	// Returns the total rewards for specified pools.
	function totalRewardsForPools( bytes32[] calldata poolIDs ) external view returns (uint256[] memory rewards)
		{
226: for( uint256 i = 0; i < rewards.length; i++ )
			rewards[i] = totalRewards[ poolIDs[i] ];
		}


	// Returns the user's pending rewards for a specified pool.
	function userRewardForPool( address wallet, bytes32 poolID ) public view returns (uint256)
		{
260: for( uint256 i = 0; i < rewards.length; i++ )
			rewards[i] = userRewardForPool( wallet, poolIDs[i] );
		}


	// Get the user's shares for a specified pool.
	function userShareForPool( address wallet, bytes32 poolID ) public view returns (uint256)
		{
277: for( uint256 i = 0; i < shares.length; i++ )
			shares[i] = _userShareInfo[wallet][ poolIDs[i] ].userShare;
		}


	// Get the user's virtual rewards for a specified pool.
	function userVirtualRewardsForPool( address wallet, bytes32 poolID ) public view returns (uint256)
		{
296: for( uint256 i = 0; i < cooldowns.length; i++ )
			{

152 | 185 | 216 | 226 | 260 | 277 | 296

File: src/staking/Staking.sol

163: for(uint256 i = start; i <= end; i++)
            unstakes[index++] = _unstakesByID[ userUnstakes[i]];

        return unstakes;
    }


	// Retrieve all pending unstakes associated with a user.
	function unstakesForUser( address user ) external view returns (Unstake[] memory)
		{

163

</details>

[G-11] Reduce deployment costs by tweaking contracts' metadata

The Solidity compiler appends 53 bytes of metadata to the smart contract code which translates to an extra 10,600 gas (200 per bytecode) + the calldata cost (16 gas per non-zero bytes, 4 gas per zero-byte). This translates to up to 848 additional gas in calldata cost. One way to reduce this cost is by optimizing the IPFS hash that gets appended to the smart contract code.

Why is this important?

  • The metadata adds an extra 53 bytes, resulting in an additional 10,600 gas cost for deployment.
  • It also incurs up to 848 additional gas in calldata cost.

Options to Reduce Gas:

  1. Use the --no-cbor-metadata compiler option to exclude metadata, but this might affect contract verification.
  2. Mine for code comments that lead to an IPFS hash with more zeros, reducing calldata costs.
<details> <summary><i>35 issue instances in 35 files:</i></summary>
File: src/arbitrage/ArbitrageSearch.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/dao/DAO.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/dao/DAOConfig.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/dao/Proposals.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/dao/Parameters.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/launch/Airdrop.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/launch/BootstrapBallot.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/launch/InitialDistribution.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/pools/PoolUtils.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/pools/PoolsConfig.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/pools/PoolStats.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/pools/Pools.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/pools/PoolMath.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/price_feed/CoreChainlinkFeed.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/price_feed/CoreUniswapFeed.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/price_feed/PriceAggregator.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/price_feed/CoreSaltyFeed.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/rewards/RewardsConfig.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/rewards/Emissions.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/rewards/RewardsEmitter.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/rewards/SaltRewards.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/stable/USDS.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/stable/StableConfig.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/stable/CollateralAndLiquidity.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/stable/Liquidizer.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/staking/StakingConfig.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/staking/Liquidity.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/staking/StakingRewards.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/staking/Staking.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/ManagedWallet.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/AccessManager.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/Salt.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/SigningTools.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/Upkeep.sol

1: Consider optimizing the IPFS hash during deployment.

1

File: src/ExchangeConfig.sol

1: Consider optimizing the IPFS hash during deployment.

1

</details>

[G-12] Consider using solady's FixedPointMathLib

Utilizing gas-optimized math functions from libraries like Solady can lead to more efficient smart contracts. This is particularly beneficial in contracts where these operations are frequently used.

<details> <summary><i>200 issue instances in 21 files:</i></summary>
File: src/arbitrage/ArbitrageSearch.sol

68: uint256 amountOut = (reservesA1 * midpoint) / (reservesA0 + midpoint);
69: amountOut = (reservesB1 * amountOut) / (reservesB0 + amountOut);
70: amountOut = (reservesC1 * amountOut) / (reservesC0 + amountOut);
82: amountOut = (reservesA1 * midpoint) / (reservesA0 + midpoint);
83: amountOut = (reservesB1 * amountOut) / (reservesB0 + amountOut);
84: amountOut = (reservesC1 * amountOut) / (reservesC0 + amountOut);
129: uint256 amountOut = (reservesA1 * bestArbAmountIn) / (reservesA0 + bestArbAmountIn);
130: amountOut = (reservesB1 * amountOut) / (reservesB0 + amountOut);
131: amountOut = (reservesC1 * amountOut) / (reservesC0 + amountOut);
68: uint256 amountOut = (reservesA1 * midpoint) / (reservesA0 + midpoint);
69: amountOut = (reservesB1 * amountOut) / (reservesB0 + amountOut);
70: amountOut = (reservesC1 * amountOut) / (reservesC0 + amountOut);
82: amountOut = (reservesA1 * midpoint) / (reservesA0 + midpoint);
83: amountOut = (reservesB1 * amountOut) / (reservesB0 + amountOut);
84: amountOut = (reservesC1 * amountOut) / (reservesC0 + amountOut);
129: uint256 amountOut = (reservesA1 * bestArbAmountIn) / (reservesA0 + bestArbAmountIn);
130: amountOut = (reservesB1 * amountOut) / (reservesB0 + amountOut);
131: amountOut = (reservesC1 * amountOut) / (reservesC0 + amountOut);

68 | 69 | 70 | 82 | 83 | 84 | 129 | 130 | 131 | 68 | 69 | 70 | 82 | 83 | 84 | 129 | 130 | 131

File: src/dao/DAO.sol

247: require( saltBalance >= bootstrappingRewards * 2, "Whitelisting is not currently possible due to insufficient bootstrapping rewards" );
265: exchangeConfig.salt().approve( address(liquidityRewardsEmitter), bootstrappingRewards * 2 );
348: uint256 saltToBurn = ( remainingSALT * daoConfig.percentPolRewardsBurned() ) / 100;
369: uint256 liquidityToWithdraw = (liquidityHeld * percentToLiquidate) / 100;
341: uint256 amountToSendToTeam = claimedSALT / 10;
348: uint256 saltToBurn = ( remainingSALT * daoConfig.percentPolRewardsBurned() ) / 100;
369: uint256 liquidityToWithdraw = (liquidityHeld * percentToLiquidate) / 100;

247 | 265 | 348 | 369 | 341 | 348 | 369

File: src/dao/DAOConfig.sol

40: uint256 public baseBallotQuorumPercentTimes1000 = 10 * 1000; // Default 10% of the total amount of SALT staked with a 1000x multiplier
102: if (baseBallotQuorumPercentTimes1000 < 20 * 1000)
107: if (baseBallotQuorumPercentTimes1000 > 5 * 1000 )

40 | 102 | 107

File: src/dao/Proposals.sol

90: uint256 requiredXSalt = ( totalStaked * daoConfig.requiredProposalPercentStakeTimes1000() ) / ( 100 * 1000 );
90: uint256 requiredXSalt = ( totalStaked * daoConfig.requiredProposalPercentStakeTimes1000() ) / ( 100 * 1000 );
202: uint256 maxSendable = balance * 5 / 100;
324: requiredQuorum = ( 1 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 );
324: requiredQuorum = ( 1 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 );
324: requiredQuorum = ( 1 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 );
326: requiredQuorum = ( 2 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 );
326: requiredQuorum = ( 2 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 );
326: requiredQuorum = ( 2 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 );
329: requiredQuorum = ( 3 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 );
329: requiredQuorum = ( 3 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 );
329: requiredQuorum = ( 3 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 );
335: uint256 minimumQuorum = totalSupply * 5 / 1000;
90: uint256 requiredXSalt = ( totalStaked * daoConfig.requiredProposalPercentStakeTimes1000() ) / ( 100 * 1000 );
202: uint256 maxSendable = balance * 5 / 100;
324: requiredQuorum = ( 1 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 );
326: requiredQuorum = ( 2 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 );
329: requiredQuorum = ( 3 * totalStaked * daoConfig.baseBallotQuorumPercentTimes1000()) / ( 100 * 1000 );
335: uint256 minimumQuorum = totalSupply * 5 / 1000;

90 | 90 | 202 | 324 | 324 | 324 | 326 | 326 | 326 | 329 | 329 | 329 | 335 | 90 | 202 | 324 | 326 | 329 | 335

File: src/launch/Airdrop.sol

64: saltAmountForEachUser = saltBalance / numberAuthorized();

64

File: src/launch/InitialDistribution.sol

53: require( salt.balanceOf(address(this)) == 100 * MILLION_ETHER, "SALT has already been sent from the contract" );
56: salt.safeTransfer( address(emissions), 52 * MILLION_ETHER );
59: salt.safeTransfer( address(daoVestingWallet), 25 * MILLION_ETHER );
62: salt.safeTransfer( address(teamVestingWallet), 10 * MILLION_ETHER );
65: salt.safeTransfer( address(airdrop), 5 * MILLION_ETHER );
72: salt.safeTransfer( address(saltRewards), 8 * MILLION_ETHER );
73: saltRewards.sendInitialSaltRewards(5 * MILLION_ETHER, poolIDs );

53 | 56 | 59 | 62 | 65 | 72 | 73

File: src/pools/PoolUtils.sol

61: uint256 maxAmountIn = reservesIn * maximumInternalSwapPercentTimes1000 / (100 * 1000);
61: uint256 maxAmountIn = reservesIn * maximumInternalSwapPercentTimes1000 / (100 * 1000);
61: uint256 maxAmountIn = reservesIn * maximumInternalSwapPercentTimes1000 / (100 * 1000);

61 | 61 | 61

File: src/pools/Pools.sol

109: uint256 proportionalB = ( maxAmount0 * reserve1 ) / reserve0;
115: addedAmount0 = ( maxAmount1 * reserve0 ) / reserve1;
132: addedLiquidity = (totalLiquidity * addedAmount0) / reserve0;
134: addedLiquidity = (totalLiquidity * addedAmount1) / reserve1;
179: reclaimedA = ( reserves.reserve0 * liquidityToRemove ) / totalLiquidity;
180: reclaimedB = ( reserves.reserve1 * liquidityToRemove ) / totalLiquidity;
260: amountOut = reserve0 * amountIn / reserve1;
266: amountOut = reserve1 * amountIn / reserve0;
313: swapAmountInValueInETH = ( swapAmountIn * reservesWETH ) / reservesTokenIn;
109: uint256 proportionalB = ( maxAmount0 * reserve1 ) / reserve0;
115: addedAmount0 = ( maxAmount1 * reserve0 ) / reserve1;
132: addedLiquidity = (totalLiquidity * addedAmount0) / reserve0;
134: addedLiquidity = (totalLiquidity * addedAmount1) / reserve1;
179: reclaimedA = ( reserves.reserve0 * liquidityToRemove ) / totalLiquidity;
180: reclaimedB = ( reserves.reserve1 * liquidityToRemove ) / totalLiquidity;
260: amountOut = reserve0 * amountIn / reserve1;
266: amountOut = reserve1 * amountIn / reserve0;
313: swapAmountInValueInETH = ( swapAmountIn * reservesWETH ) / reservesTokenIn;

109 | 115 | 132 | 134 | 179 | 180 | 260 | 266 | 313 | 109 | 115 | 132 | 134 | 179 | 180 | 260 | 266 | 313

File: src/pools/PoolMath.sol

15: k = r0 * r1
21: s1 = r1 - r0 * r1 / (r0 + s0)
184: uint256 B = 2 * r0;
192: uint256 C = r0 * ( r1 * z0 - r0 * z1 ) / ( r1 + z1 );
192: uint256 C = r0 * ( r1 * z0 - r0 * z1 ) / ( r1 + z1 );
192: uint256 C = r0 * ( r1 * z0 - r0 * z1 ) / ( r1 + z1 );
193: uint256 discriminant = B * B + 4 * A * C;
193: uint256 discriminant = B * B + 4 * A * C;
203: swapAmount = ( sqrtDiscriminant - B ) / ( 2 * A );
215: if ( zapAmountA * reserveB > reserveA * zapAmountB )
215: if ( zapAmountA * reserveB > reserveA * zapAmountB )
219: if ( zapAmountA * reserveB < reserveA * zapAmountB )
219: if ( zapAmountA * reserveB < reserveA * zapAmountB )
18: s1 = r1 - k / (r0 + s0)
21: s1 = r1 - r0 * r1 / (r0 + s0)
24: (r0 + s0) / ( r1 - s1)
31: a0 / a1 = (r0 + s0) / ( r1 - s1)
31: a0 / a1 = (r0 + s0) / ( r1 - s1)
34: (z0 - s0) / ( z1 + s1) = (r0 + s0) / ( r1 - s1)
34: (z0 - s0) / ( z1 + s1) = (r0 + s0) / ( r1 - s1)
41: (c-x)/(d+y) = (a+x)/(b-y)
41: (c-x)/(d+y) = (a+x)/(b-y)
44: y = b - ab/(a+x)
47: (c-x)/(d+y) = (a+x)/(b-y)
47: (c-x)/(d+y) = (a+x)/(b-y)
68: x(b+d) + b(a+c) - ab(a+c)/(a+x) = bc - ad
95: xx + x(2a) + (aad-abc)/(b+d) = 0
101: C = a(ad - bc)/(b+d)
109: C = r0(r0z1 - r1z0)/(r1 + z1)
192: uint256 C = r0 * ( r1 * z0 - r0 * z1 ) / ( r1 + z1 );
203: swapAmount = ( sqrtDiscriminant - B ) / ( 2 * A );

15 | 21 | 184 | 192 | 192 | 192 | 193 | 193 | 203 | 215 | 215 | 219 | 219 | 18 | 21 | 24 | 31 | 31 | 34 | 34 | 41 | 41 | 44 | 47 | 47 | 68 | 95 | 101 | 109 | 192 | 203

File: src/price_feed/CoreChainlinkFeed.sol

59: return uint256(price) * 10**10;
59: return uint256(price) * 10**10;

59 | 59

File: src/price_feed/CoreUniswapFeed.sol

70: return ( FixedPoint96.Q96 * ( 10 ** 18 ) ) / ( p * ( 10 ** ( decimals0 - decimals1 ) ) );
70: return ( FixedPoint96.Q96 * ( 10 ** 18 ) ) / ( p * ( 10 ** ( decimals0 - decimals1 ) ) );
72: return ( FixedPoint96.Q96 * ( 10 ** 18 ) ) / p;
112: return ( uniswapWETH_USDC * 10**18) / uniswapWBTC_WETH;
59: int24 tick = int24((tickCumulatives[1] - tickCumulatives[0]) / int56(uint56(twapInterval)));
70: return ( FixedPoint96.Q96 * ( 10 ** 18 ) ) / ( p * ( 10 ** ( decimals0 - decimals1 ) ) );
72: return ( FixedPoint96.Q96 * ( 10 ** 18 ) ) / p;
107: uniswapWBTC_WETH = 10**36 / uniswapWBTC_WETH;
110: uniswapWETH_USDC = 10**36 / uniswapWETH_USDC;
112: return ( uniswapWETH_USDC * 10**18) / uniswapWBTC_WETH;
126: return 10**36 / uniswapWETH_USDC;
67: return FullMath.mulDiv( 10 ** ( 18 + decimals1 - decimals0 ), FixedPoint96.Q96, p );
70: return ( FixedPoint96.Q96 * ( 10 ** 18 ) ) / ( p * ( 10 ** ( decimals0 - decimals1 ) ) );
70: return ( FixedPoint96.Q96 * ( 10 ** 18 ) ) / ( p * ( 10 ** ( decimals0 - decimals1 ) ) );
72: return ( FixedPoint96.Q96 * ( 10 ** 18 ) ) / p;
107: uniswapWBTC_WETH = 10**36 / uniswapWBTC_WETH;
110: uniswapWETH_USDC = 10**36 / uniswapWETH_USDC;
112: return ( uniswapWETH_USDC * 10**18) / uniswapWBTC_WETH;
126: return 10**36 / uniswapWETH_USDC;

70 | 70 | 72 | 112 | 59 | 70 | 72 | 107 | 110 | 112 | 126 | 67 | 70 | 70 | 72 | 107 | 110 | 112 | 126

File: src/price_feed/PriceAggregator.sol

142: if (  (_absoluteDifference(priceA, priceB) * 100000) / averagePrice > maximumPriceFeedPercentDifferenceTimes1000 )
139: uint256 averagePrice = ( priceA + priceB ) / 2;
142: if (  (_absoluteDifference(priceA, priceB) * 100000) / averagePrice > maximumPriceFeedPercentDifferenceTimes1000 )

142 | 139 | 142

File: src/price_feed/CoreSaltyFeed.sol

40: return ( reservesUSDS * 10**8 ) / reservesWBTC;
52: return ( reservesUSDS * 10**18 ) / reservesWETH;
40: return ( reservesUSDS * 10**8 ) / reservesWBTC;
52: return ( reservesUSDS * 10**18 ) / reservesWETH;
52: return ( reservesUSDS * 10**18 ) / reservesWETH;

40 | 52 | 40 | 52 | 52

File: src/rewards/Emissions.sol

55: uint256 saltToSend = ( saltBalance * timeSinceLastUpkeep * rewardsConfig.emissionsWeeklyPercentTimes1000() ) / ( 100 * 1000 weeks );
55: uint256 saltToSend = ( saltBalance * timeSinceLastUpkeep * rewardsConfig.emissionsWeeklyPercentTimes1000() ) / ( 100 * 1000 weeks );
55: uint256 saltToSend = ( saltBalance * timeSinceLastUpkeep * rewardsConfig.emissionsWeeklyPercentTimes1000() ) / ( 100 * 1000 weeks );
55: uint256 saltToSend = ( saltBalance * timeSinceLastUpkeep * rewardsConfig.emissionsWeeklyPercentTimes1000() ) / ( 100 * 1000 weeks );

55 | 55 | 55 | 55

File: src/rewards/RewardsEmitter.sol

112: uint256 numeratorMult = timeSinceLastUpkeep * rewardsConfig.rewardsEmitterDailyPercentTimes1000();
113: uint256 denominatorMult = 1 days * 100000; // simplification of numberSecondsInOneDay * (100 percent) * 1000
113: uint256 denominatorMult = 1 days * 100000; // simplification of numberSecondsInOneDay * (100 percent) * 1000
113: uint256 denominatorMult = 1 days * 100000; // simplification of numberSecondsInOneDay * (100 percent) * 1000
121: uint256 amountToAddForPool = ( pendingRewards[poolID] * numeratorMult ) / denominatorMult;

112 | 113 | 113 | 113 | 121

File: src/rewards/SaltRewards.sol

67: uint256 rewardsForPool = ( liquidityRewardsAmount * profitsForPools[i] ) / totalProfits;
139: uint256 directRewardsForSaltUSDS = ( saltRewardsToDistribute * rewardsConfig.percentRewardsSaltUSDS() ) / 100;
143: uint256 stakingRewardsAmount = ( remainingRewards * rewardsConfig.stakingRewardsPercent() ) / 100;
67: uint256 rewardsForPool = ( liquidityRewardsAmount * profitsForPools[i] ) / totalProfits;
84: uint256 amountPerPool = liquidityBootstrapAmount / poolIDs.length; // poolIDs.length is guaranteed to not be zero
139: uint256 directRewardsForSaltUSDS = ( saltRewardsToDistribute * rewardsConfig.percentRewardsSaltUSDS() ) / 100;
143: uint256 stakingRewardsAmount = ( remainingRewards * rewardsConfig.stakingRewardsPercent() ) / 100;

67 | 139 | 143 | 67 | 84 | 139 | 143

File: src/stable/CollateralAndLiquidity.sol

159: uint256 rewardedWBTC = (reclaimedWBTC * rewardPercent) / 100;
160: uint256 rewardedWETH = (reclaimedWETH * rewardPercent) / 100;
167: rewardedWBTC = (rewardedWBTC * maxRewardValue) / rewardValue;
168: rewardedWETH = (rewardedWETH * maxRewardValue) / rewardValue;
202: uint256 btcValue = ( amountBTC * btcPrice ) / wbtcTenToTheDecimals;
203: uint256 ethValue = ( amountETH * ethPrice ) / wethTenToTheDecimals;
232: uint256 userWBTC = (reservesWBTC * userCollateralAmount ) / totalCollateralShares;
233: uint256 userWETH = (reservesWETH * userCollateralAmount ) / totalCollateralShares;
261: return userCollateralAmount * maxWithdrawableValue / userCollateralValue;
280: uint256 maxBorrowableAmount = ( userCollateralValue * 100 ) / stableConfig.initialCollateralRatioPercent();
307: return (( userCollateralValue * 100 ) / usdsBorrowedAmount) < stableConfig.minimumCollateralRatioPercent();
329: uint256 minCollateral = (minCollateralValue * totalCollateralShares) / totalCollateralValue;
159: uint256 rewardedWBTC = (reclaimedWBTC * rewardPercent) / 100;
160: uint256 rewardedWETH = (reclaimedWETH * rewardPercent) / 100;
167: rewardedWBTC = (rewardedWBTC * maxRewardValue) / rewardValue;
168: rewardedWETH = (rewardedWETH * maxRewardValue) / rewardValue;
202: uint256 btcValue = ( amountBTC * btcPrice ) / wbtcTenToTheDecimals;
203: uint256 ethValue = ( amountETH * ethPrice ) / wethTenToTheDecimals;
232: uint256 userWBTC = (reservesWBTC * userCollateralAmount ) / totalCollateralShares;
233: uint256 userWETH = (reservesWETH * userCollateralAmount ) / totalCollateralShares;
250: uint256 requiredCollateralValueAfterWithdrawal = ( usdsBorrowedByUsers[wallet] * stableConfig.initialCollateralRatioPercent() ) / 100;
261: return userCollateralAmount * maxWithdrawableValue / userCollateralValue;
280: uint256 maxBorrowableAmount = ( userCollateralValue * 100 ) / stableConfig.initialCollateralRatioPercent();
307: return (( userCollateralValue * 100 ) / usdsBorrowedAmount) < stableConfig.minimumCollateralRatioPercent();
326: uint256 minCollateralValue = (usdsBorrowedByUsers[wallet] * stableConfig.minimumCollateralRatioPercent()) / 100;
329: uint256 minCollateral = (minCollateralValue * totalCollateralShares) / totalCollateralValue;
63: wbtcTenToTheDecimals = 10 ** IERC20Metadata(address(wbtc)).decimals();
64: wethTenToTheDecimals = 10 ** IERC20Metadata(address(weth)).decimals();

159 | 160 | 167 | 168 | 202 | 203 | 232 | 233 | 261 | 280 | 307 | 329 | 159 | 160 | 167 | 168 | 202 | 203 | 232 | 233 | 250 | 261 | 280 | 307 | 326 | 329 | 63 | 64

File: src/staking/StakingRewards.sol

118: uint256 virtualRewardsToRemove = (user.virtualRewards * decreaseShareAmount) / user.userShare;
78: if ( existingTotalShares != 0 ) // prevent / 0
114: uint256 rewardsForAmount = ( totalRewards[poolID] * decreaseShareAmount ) / totalShares[poolID];
118: uint256 virtualRewardsToRemove = (user.virtualRewards * decreaseShareAmount) / user.userShare;
243: uint256 rewardsShare = ( totalRewards[poolID] * user.userShare ) / totalShares[poolID];

118 | 78 | 114 | 118 | 243

File: src/staking/Staking.sol

65: uint256 completionTime = block.timestamp + numWeeks * ( 1 weeks );
210: uint256 numerator = unstakedXSALT * ( minUnstakePercent * unstakeRange + percentAboveMinimum * ( numWeeks - minUnstakeWeeks ) );
210: uint256 numerator = unstakedXSALT * ( minUnstakePercent * unstakeRange + percentAboveMinimum * ( numWeeks - minUnstakeWeeks ) );
210: uint256 numerator = unstakedXSALT * ( minUnstakePercent * unstakeRange + percentAboveMinimum * ( numWeeks - minUnstakeWeeks ) );
211: return numerator / ( 100 * unstakeRange );
211: return numerator / ( 100 * unstakeRange );

65 | 210 | 210 | 210 | 211 | 211

File: src/Salt.sol

13: uint256 public constant INITIAL_SUPPLY = 100 * MILLION_ETHER ;

13

File: src/Upkeep.sol

119: uint256 rewardAmount = withdrawnAmount * daoConfig.upkeepRewardPercent() / 100;
152: uint256 amountOfWETH = wethBalance * stableConfig.percentArbitrageProfitsForStablePOL() / 100;
165: uint256 amountOfWETH = wethBalance * daoConfig.arbitrageProfitsPercentPOL() / 100;
289: return daoWETH * daoConfig.upkeepRewardPercent() / 100;
119: uint256 rewardAmount = withdrawnAmount * daoConfig.upkeepRewardPercent() / 100;
152: uint256 amountOfWETH = wethBalance * stableConfig.percentArbitrageProfitsForStablePOL() / 100;
165: uint256 amountOfWETH = wethBalance * daoConfig.arbitrageProfitsPercentPOL() / 100;
289: return daoWETH * daoConfig.upkeepRewardPercent() / 100;

119 | 152 | 165 | 289 | 119 | 152 | 165 | 289

</details>

[G-13] Consider using OZ EnumerateSet in place of nested mappings

Nested mappings and multi-dimensional arrays in Solidity operate through a process of double hashing, wherein the original storage slot and the first key are concatenated and hashed, and then this hash is again concatenated with the second key and hashed. This process can be quite gas expensive due to the double-hashing operation and subsequent storage operation (sstore).

A possible optimization involves manually concatenating the keys followed by a single hash operation and an sstore. However, this technique introduces the risk of storage collision, especially when there are other nested hash maps in the contract that use the same key types. Because Solidity is unaware of the number and structure of nested hash maps in a contract, it follows a conservative approach in computing the storage slot to avoid possible collisions.

OpenZeppelin's EnumerableSet provides a potential solution to this problem. It creates a data structure that combines the benefits of set operations with the ability to enumerate stored elements, which is not natively available in Solidity. EnumerableSet handles the element uniqueness internally and can therefore provide a more gas-efficient and collision-resistant alternative to nested mappings or multi-dimensional arrays in certain scenarios.

<details> <summary><i>5 issue instances in 4 files:</i></summary>
File: src/dao/Proposals.sol

51: mapping(uint256=>mapping(Vote=>uint256)) private _votesCastForBallot;
55: mapping(uint256=>mapping(address=>UserVote)) private _lastUserVoteForBallot;

51 | 55

File: src/pools/Pools.sol

49: mapping(address=>mapping(IERC20=>uint256)) private _userDeposits;

49

File: src/staking/StakingRewards.sol

36: mapping(address=>mapping(bytes32=>UserShareInfo)) private _userShareInfo;

36

File: src/AccessManager.sol

26: mapping(uint256 => mapping(address => bool)) private _walletsWithAccess;

26

</details>

[G-14] Optimize require/revert Statements with Assembly

Using inline assembly for revert statements in Solidity can offer gas optimizations. The typical require or revert statements in Solidity perform additional memory operations and type checks which can be avoided by using low-level assembly code.

In certain contracts, particularly those that might revert often or are otherwise sensitive to gas costs, using assembly to handle reversion can offer meaningful savings. These savings primarily come from avoiding memory expansion costs and extra type checks that the Solidity compiler performs.

Example:

/// calling restrictedAction(2) with a non-owner address: 24042
function restrictedAction(uint256 num)  external {
    require(owner == msg.sender, "caller is not owner");
    specialNumber = num;
}

// calling restrictedAction(2) with a non-owner address: 23734
function restrictedAction(uint256 num)  external {
    assembly {
        if sub(caller(), sload(owner.slot)) {
            mstore(0x00, 0x20) // store offset to where length of revert message is stored
            mstore(0x20, 0x13) // store length (19)
            mstore(0x40, 0x63616c6c6572206973206e6f74206f776e657200000000000000000000000000) // store hex representation of message
            revert(0x00, 0x60) // revert with data
        }
    }
    specialNumber = num;
}
<details> <summary><i>139 issue instances in 23 files:</i></summary>
File: src/dao/DAO.sol

247: require( saltBalance >= bootstrappingRewards * 2, "Whitelisting is not currently possible due to insufficient bootstrapping rewards" );
251: require( bestWhitelistingBallotID == ballotID, "Only the token whitelisting ballot with the most votes can be finalized" );
281: require( proposals.canFinalizeBallot(ballotID), "The ballot is not yet able to be finalized" );
297: require( msg.sender == address(exchangeConfig.upkeep()), "DAO.withdrawArbitrageProfits is only callable from the Upkeep contract" );
318: require( msg.sender == address(exchangeConfig.upkeep()), "DAO.formPOL is only callable from the Upkeep contract" );
329: require( msg.sender == address(exchangeConfig.upkeep()), "DAO.processRewardsFromPOL is only callable from the Upkeep contract" );
362: require(msg.sender == address(liquidizer), "DAO.withdrawProtocolOwnedLiquidity is only callable from the Liquidizer contract" );

247 | 251 | 281 | 297 | 318 | 329 | 362

File: src/dao/Proposals.sol

83: require( block.timestamp >= firstPossibleProposalTimestamp, "Cannot propose ballots within the first 45 days of deployment" );
92: require( requiredXSalt > 0, "requiredXSalt cannot be zero" );
95: require( userXSalt >= requiredXSalt, "Sender does not have enough xSALT to make the proposal" );
98: require( ! _userHasActiveProposal[msg.sender], "Users can only have one active proposal at a time" );
102: require( openBallotsByName[ballotName] == 0, "Cannot create a proposal similar to a ballot that is still open" );
103: require( openBallotsByName[ string.concat(ballotName, "_confirm")] == 0, "Cannot create a proposal for a ballot with a secondary confirmation" );
124: require( msg.sender == address(exchangeConfig.dao()), "Only the DAO can create a confirmation proposal" );
132: require( msg.sender == address(exchangeConfig.dao()), "Only the DAO can mark a ballot as finalized" );
164: require( address(token) != address(0), "token cannot be address(0)" );
165: require( token.totalSupply() < type(uint112).max, "Token supply cannot exceed uint112.max" ); // 5 quadrillion max supply with 18 decimals of precision
167: require( _openBallotsForTokenWhitelisting.length() < daoConfig.maxPendingTokensForWhitelisting(), "The maximum number of token whitelisting proposals are already pending" );
168: require( poolsConfig.numberOfWhitelistedPools() < poolsConfig.maximumWhitelistedPools(), "Maximum number of whitelisted pools already reached" );
169: require( ! poolsConfig.tokenHasBeenWhitelisted(token, exchangeConfig.wbtc(), exchangeConfig.weth()), "The token has already been whitelisted" );
182: require( poolsConfig.tokenHasBeenWhitelisted(token, exchangeConfig.wbtc(), exchangeConfig.weth()), "Can only unwhitelist a whitelisted token" );
183: require( address(token) != address(exchangeConfig.wbtc()), "Cannot unwhitelist WBTC" );
184: require( address(token) != address(exchangeConfig.weth()), "Cannot unwhitelist WETH" );
185: require( address(token) != address(exchangeConfig.dai()), "Cannot unwhitelist DAI" );
186: require( address(token) != address(exchangeConfig.usds()), "Cannot unwhitelist USDS" );
187: require( address(token) != address(exchangeConfig.salt()), "Cannot unwhitelist SALT" );
198: require( wallet != address(0), "Cannot send SALT to address(0)" );
203: require( amount <= maxSendable, "Cannot send more than 5% of the DAO SALT balance" );
215: require( contractAddress != address(0), "Contract address cannot be address(0)" );
224: require( bytes(country).length == 2, "Country must be an ISO 3166 Alpha-2 Code" );
233: require( bytes(country).length == 2, "Country must be an ISO 3166 Alpha-2 Code" );
242: require( newAddress != address(0), "Proposed address cannot be address(0)" );
251: require( keccak256(abi.encodePacked(newWebsiteURL)) != keccak256(abi.encodePacked("")), "newWebsiteURL cannot be empty" );
264: require( ballot.ballotIsLive, "The specified ballot is not open for voting" );
268: require( (vote == Vote.INCREASE) || (vote == Vote.DECREASE) || (vote == Vote.NO_CHANGE), "Invalid VoteType for Parameter Ballot" );
270: require( (vote == Vote.YES) || (vote == Vote.NO), "Invalid VoteType for Approval Ballot" );
277: require( userVotingPower > 0, "Staked SALT required to vote" );
321: require( totalStaked != 0, "SALT staked cannot be zero to determine quorum" );

83 | 92 | 95 | 98 | 102 | 103 | 124 | 132 | 164 | 165 | 167 | 168 | 169 | 182 | 183 | 184 | 185 | 186 | 187 | 198 | 203 | 215 | 224 | 233 | 242 | 251 | 264 | 268 | 270 | 277 | 321

File: src/launch/Airdrop.sol

48: require( msg.sender == address(exchangeConfig.initialDistribution().bootstrapBallot()), "Only the BootstrapBallot can call Airdrop.authorizeWallet" );
49: require( ! claimingAllowed, "Cannot authorize after claiming is allowed" );
58: require( msg.sender == address(exchangeConfig.initialDistribution()), "Airdrop.allowClaiming can only be called by the InitialDistribution contract" );
59: require( ! claimingAllowed, "Claiming is already allowed" );
60: require(numberAuthorized() > 0, "No addresses authorized to claim airdrop.");
76: require( claimingAllowed, "Claiming is not allowed yet" );
77: require( isAuthorized(msg.sender), "Wallet is not authorized for airdrop" );
78: require( ! claimed[msg.sender], "Wallet already claimed the airdrop" );

48 | 49 | 58 | 59 | 60 | 76 | 77 | 78

File: src/launch/BootstrapBallot.sol

36: require( ballotDuration > 0, "ballotDuration cannot be zero" );
50: require( ! hasVoted[msg.sender], "User already voted" );
54: require(SigningTools._verifySignature(messageHash, signature), "Incorrect BootstrapBallot.vote signatory" );
71: require( ! ballotFinalized, "Ballot has already been finalized" );
72: require( block.timestamp >= completionTimestamp, "Ballot is not yet complete" );

36 | 50 | 54 | 71 | 72

File: src/launch/InitialDistribution.sol

52: require( msg.sender == address(bootstrapBallot), "InitialDistribution.distributionApproved can only be called from the BootstrapBallot contract" );
53: require( salt.balanceOf(address(this)) == 100 * MILLION_ETHER, "SALT has already been sent from the contract" );

52 | 53

File: src/pools/PoolsConfig.sol

47: require( _whitelist.length() < maximumWhitelistedPools, "Maximum number of whitelisted pools already reached" );
48: require(tokenA != tokenB, "tokenA and tokenB cannot be the same token");
136: require(address(pair.tokenA) != address(0) && address(pair.tokenB) != address(0), "This poolID does not exist");

47 | 48 | 136

File: src/pools/PoolStats.sol

49: require(msg.sender == address(exchangeConfig.upkeep()), "PoolStats.clearProfitsForPools is only callable from the Upkeep contract" );

49

File: src/pools/Pools.sol

72: require( msg.sender == address(exchangeConfig.initialDistribution().bootstrapBallot()), "Pools.startExchangeApproved can only be called from the BootstrapBallot contract" );
83: require(block.timestamp <= deadline, "TX EXPIRED");
142: require( msg.sender == address(collateralAndLiquidity), "Pools.addLiquidity is only callable from the CollateralAndLiquidity contract" );
143: require( exchangeIsLive, "The exchange is not yet live" );
144: require( address(tokenA) != address(tokenB), "Cannot add liquidity for duplicate tokens" );
146: require( maxAmountA > PoolUtils.DUST, "The amount of tokenA to add is too small" );
147: require( maxAmountB > PoolUtils.DUST, "The amount of tokenB to add is too small" );
158: require( addedLiquidity >= minLiquidityReceived, "Too little liquidity received" );
172: require( msg.sender == address(collateralAndLiquidity), "Pools.removeLiquidity is only callable from the CollateralAndLiquidity contract" );
173: require( liquidityToRemove > 0, "The amount of liquidityToRemove cannot be zero" );
187: require((reserves.reserve0 >= PoolUtils.DUST) && (reserves.reserve0 >= PoolUtils.DUST), "Insufficient reserves after liquidity removal");
193: require( (reclaimedA >= minReclaimedA) && (reclaimedB >= minReclaimedB), "Insufficient underlying tokens returned" );
207: require( amount > PoolUtils.DUST, "Deposit amount too small");
221: require( _userDeposits[msg.sender][token] >= amount, "Insufficient balance to withdraw specified amount" );
222: require( amount > PoolUtils.DUST, "Withdraw amount too small");
243: require((reserve0 >= PoolUtils.DUST) && (reserve1 >= PoolUtils.DUST), "Insufficient reserves before swap");
271: require( (reserve0 >= PoolUtils.DUST) && (reserve1 >= PoolUtils.DUST), "Insufficient reserves after swap");
353: require( swapAmountOut >= minAmountOut, "Insufficient resulting token amount" );
371: require( userDeposits[swapTokenIn] >= swapAmountIn, "Insufficient deposited token balance of initial token" );

72 | 83 | 142 | 143 | 144 | 146 | 147 | 158 | 172 | 173 | 187 | 193 | 207 | 221 | 222 | 243 | 271 | 353 | 371

File: src/price_feed/PriceAggregator.sol

39: require( address(priceFeed1) == address(0), "setInitialFeeds() can only be called once" );
183: require (price != 0, "Invalid BTC price" );
195: require (price != 0, "Invalid ETH price" );

39 | 183 | 195

File: src/rewards/Emissions.sol

42: require( msg.sender == address(exchangeConfig.upkeep()), "Emissions.performUpkeep is only callable from the Upkeep contract" );

42

File: src/rewards/RewardsEmitter.sol

63: require( poolsConfig.isWhitelisted( addedReward.poolID ), "Invalid pool" );
84: require( msg.sender == address(exchangeConfig.upkeep()), "RewardsEmitter.performUpkeep is only callable from the Upkeep contract" );

63 | 84

File: src/rewards/SaltRewards.sol

60: require( poolIDs.length == profitsForPools.length, "Incompatible array lengths" );
108: require( msg.sender == address(exchangeConfig.initialDistribution()), "SaltRewards.sendInitialRewards is only callable from the InitialDistribution contract" );
119: require( msg.sender == address(exchangeConfig.upkeep()), "SaltRewards.performUpkeep is only callable from the Upkeep contract" );
120: require( poolIDs.length == profitsForPools.length, "Incompatible array lengths" );

60 | 108 | 119 | 120

File: src/stable/USDS.sol

42: require( msg.sender == address(collateralAndLiquidity), "USDS.mintTo is only callable from the Collateral contract" );
43: require( amount > 0, "Cannot mint zero USDS" );

42 | 43

File: src/stable/CollateralAndLiquidity.sol

83: require( userShareForPool( msg.sender, collateralPoolID ) > 0, "User does not have any collateral" );
84: require( collateralToWithdraw <= maxWithdrawableCollateral(msg.sender), "Excessive collateralToWithdraw" );
97: require( exchangeConfig.walletHasAccess(msg.sender), "Sender does not have exchange access" );
98: require( userShareForPool( msg.sender, collateralPoolID ) > 0, "User does not have any collateral" );
99: require( amountBorrowed <= maxBorrowableUSDS(msg.sender), "Excessive amountBorrowed" );
117: require( userShareForPool( msg.sender, collateralPoolID ) > 0, "User does not have any collateral" );
118: require( amountRepaid <= usdsBorrowedByUsers[msg.sender], "Cannot repay more than the borrowed amount" );
119: require( amountRepaid > 0, "Cannot repay zero amount" );
142: require( wallet != msg.sender, "Cannot liquidate self" );
145: require( canUserBeLiquidated(wallet), "User cannot be liquidated" );

83 | 84 | 97 | 98 | 99 | 117 | 118 | 119 | 142 | 145

File: src/stable/Liquidizer.sol

83: require( msg.sender == address(collateralAndLiquidity), "Liquidizer.incrementBurnableUSDS is only callable from the CollateralAndLiquidity contract" );
134: require( msg.sender == address(exchangeConfig.upkeep()), "Liquidizer.performUpkeep is only callable from the Upkeep contract" );

83 | 134

File: src/staking/Liquidity.sol

42: require(block.timestamp <= deadline, "TX EXPIRED");
85: require( exchangeConfig.walletHasAccess(msg.sender), "Sender does not have exchange access" );
124: require( userShareForPool(msg.sender, poolID) >= liquidityToWithdraw, "Cannot withdraw more than existing user share" );
148: require( PoolUtils._poolID( tokenA, tokenB ) != collateralPoolID, "Stablecoin collateral cannot be deposited via Liquidity.depositLiquidityAndIncreaseShare" );
159: require( PoolUtils._poolID( tokenA, tokenB ) != collateralPoolID, "Stablecoin collateral cannot be withdrawn via Liquidity.withdrawLiquidityAndClaim" );

42 | 85 | 124 | 148 | 159

File: src/staking/StakingRewards.sol

59: require( poolsConfig.isWhitelisted( poolID ), "Invalid pool" );
60: require( increaseShareAmount != 0, "Cannot increase zero share" );
67: require( block.timestamp >= user.cooldownExpiration, "Must wait for the cooldown to expire" );
99: require( decreaseShareAmount != 0, "Cannot decrease zero share" );
102: require( decreaseShareAmount <= user.userShare, "Cannot decrease more than existing user share" );
107: require( block.timestamp >= user.cooldownExpiration, "Must wait for the cooldown to expire" );
190: require( poolsConfig.isWhitelisted( poolID ), "Invalid pool" );

59 | 60 | 67 | 99 | 102 | 107 | 190

File: src/staking/Staking.sol

43: require( exchangeConfig.walletHasAccess(msg.sender), "Sender does not have exchange access" );
62: require( userShareForPool(msg.sender, PoolUtils.STAKED_SALT) >= amountUnstaked, "Cannot unstake more than the amount staked" );
88: require( u.status == UnstakeState.PENDING, "Only PENDING unstakes can be cancelled" );
89: require( block.timestamp < u.completionTime, "Unstakes that have already completed cannot be cancelled" );
90: require( msg.sender == u.wallet, "Sender is not the original staker" );
104: require( u.status == UnstakeState.PENDING, "Only PENDING unstakes can be claimed" );
105: require( block.timestamp >= u.completionTime, "Unstake has not completed yet" );
106: require( msg.sender == u.wallet, "Sender is not the original staker" );
132: require( msg.sender == address(exchangeConfig.airdrop()), "Staking.transferStakedSaltFromAirdropToUser is only callable from the Airdrop contract" );
153: require(end >= start, "Invalid range: end cannot be less than start");
157: require(userUnstakes.length > end, "Invalid range: end is out of bounds");
158: require(start < userUnstakes.length, "Invalid range: start is out of bounds");
204: require( numWeeks >= minUnstakeWeeks, "Unstaking duration too short" );
205: require( numWeeks <= maxUnstakeWeeks, "Unstaking duration too long" );

43 | 62 | 88 | 89 | 90 | 104 | 105 | 106 | 132 | 153 | 157 | 158 | 204 | 205

File: src/ManagedWallet.sol

44: require( msg.sender == mainWallet, "Only the current mainWallet can propose changes" );
45: require( _proposedMainWallet != address(0), "_proposedMainWallet cannot be the zero address" );
46: require( _proposedConfirmationWallet != address(0), "_proposedConfirmationWallet cannot be the zero address" );
49: require( proposedMainWallet == address(0), "Cannot overwrite non-zero proposed mainWallet." );
61: require( msg.sender == confirmationWallet, "Invalid sender" );
76: require( msg.sender == proposedMainWallet, "Invalid sender" );
77: require( block.timestamp >= activeTimelock, "Timelock not yet completed" );

44 | 45 | 46 | 49 | 61 | 76 | 77

File: src/AccessManager.sol

43: require( msg.sender == address(dao), "AccessManager.excludedCountriedUpdated only callable by the DAO" );
63: require( _verifyAccess(msg.sender, signature), "Incorrect AccessManager.grantAccess signatory" );

43 | 63

File: src/SigningTools.sol

13: require( signature.length == 65, "Invalid signature length" );

13

File: src/Upkeep.sol

97: require(msg.sender == address(this), "Only callable from within the same contract");

97

File: src/ExchangeConfig.sol

51: require( address(dao) == address(0), "setContracts can only be called once" );
64: require( address(_accessManager) != address(0), "_accessManager cannot be address(0)" );

51 | 64

</details>

#0 - c4-judge

2024-02-03T14:22:14Z

Picodes marked the issue as grade-b

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter