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
Rank: 123/178
Findings: 2
Award: $32.48
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: juancito
Also found by: 0x3b, 0xBinChook, 0xCiphky, 0xHelium, 0xMango, 0xOmer, 0xRobocop, 0xSmartContractSamurai, 0xWaitress, 0xbepresent, 0xpiken, 7ashraf, Arz, Audinarey, Banditx0x, Bauchibred, Draiakoo, IceBear, J4X, Jorgect, Kaysoft, KingNFT, Rhaydden, Rolezn, The-Seraphs, Tigerfrake, Topmark, Tripathi, Udsen, ZanyBonzy, a3yip6, b0g0, chaduke, codeslide, csanuragjain, dharma09, djxploit, eta, ether_sky, grearlake, inzinko, jasonxiale, jesjupyter, josephdara, lanrebayode77, linmiaomiao, lsaudit, niroh, oakcobalt, peanuts, pina, sivanesh_808, slvDev, t0x1c, vnavascues, wangxx2026
11.6897 USDC - $11.69
Issue | Instances | |
---|---|---|
[L-01] | Unsafe downcast from larger to smaller integer types | 14 |
[L-02] | Use of ecrecover is susceptible to signature malleability | 1 |
[L-03] | Unbounded loop may run out of gas | 18 |
[L-04] | Prevent Division by Zero | 3 |
[L-05] | Functions calling tokens with transfer hooks are missing reentrancy guards | 8 |
[L-06] | Consider implementing two-step procedure for updating protocol addresses | 13 |
[L-07] | internal/private Function calls within for loops | 7 |
Issue | Instances | |
---|---|---|
[N-01] | require() /revert() statements should have descriptive reason strings | 2 |
[N-02] | uint/int variables should have the bit size defined explicitly | 2 |
[N-03] | if -statement can be converted to a ternary | 27 |
[N-04] | Avoid Empty Blocks in Code | 14 |
[N-05] | Consider using OpenZeppelin's SafeCast for any casting | 32 |
[N-06] | Leverage Recent Solidity Features with 0.8.23 | 35 |
[N-07] | Mixed usage of int/uint with int256/uint256 | 2 |
[N-08] | Inconsistent spacing in comments | 2 |
[N-09] | Function/Constructor Argument Names Not in mixedCase | 29 |
[N-10] | Insufficient Comments in Complex Functions | 3 |
[N-11] | Variable Names Not in mixedCase | 3 |
[N-12] | Consider making contracts Upgradeable | 32 |
[N-13] | Named Imports of Parent Contracts Are Missing | 50 |
[N-14] | Long functions should be refactored into multiple, smaller, functions | 3 |
[N-15] | Prefer Casting to bytes or bytes32 Over abi.encodePacked() for Single Arguments | 2 |
[N-16] | Use Unchecked for Divisions on Constant or Immutable Values | 2 |
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.
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)));
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>ecrecover
is susceptible to signature malleabilityThe 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></details>File: src/SigningTools.sol 26: ecrecover(messageHash, v, r, s)
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++ )
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++ )
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++ )
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++ )
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>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></details>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];
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 );
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>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;
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;
File: src/ManagedWallet.sol /// @audit `proposedMainWallet` is changed 50: proposedMainWallet = _proposedMainWallet; /// @audit `proposedConfirmationWallet` is changed 52: proposedConfirmationWallet = _proposedConfirmationWallet;
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>internal/private
Function calls within for loopsMaking 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 )
File: src/pools/PoolStats.sol 89: _poolIndex( _weth, arbToken2, poolIDs ) 90: _poolIndex( arbToken2, arbToken3, poolIDs ) 91: _poolIndex( arbToken3, _weth, poolIDs )
File: src/stable/CollateralAndLiquidity.sol 332: userShareForPool( wallet, collateralPoolID )
</details>File: src/staking/StakingRewards.sol 156: userRewardForPool( msg.sender, poolID ) 261: userRewardForPool( wallet, poolIDs[i] )
require()
/revert()
statements should have descriptive reason stringsIn 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");
</details>File: src/staking/Liquidity.sol /// @audit Reason string `TX EXPIRED` is not informative enough 42: require(block.timestamp <= deadline, "TX EXPIRED");
uint/int
variables should have the bit size defined explicitlyIn 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)
</details>File: src/staking/Liquidity.sol 40: modifier ensureNotExpired(uint deadline)
if
-statement can be converted to a ternaryThe 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.
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" );
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; }
File: src/pools/Pools.sol 131: if ( addedAmount0 > addedAmount1) addedLiquidity = (totalLiquidity * addedAmount0) / reserve0; else addedLiquidity = (totalLiquidity * addedAmount1) / reserve1;
File: src/price_feed/CoreChainlinkFeed.sol 44: if ( answerDelay <= MAX_ANSWER_DELAY ) price = _price; else price = 0;
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; }
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; }
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; }
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; }
</details>File: src/staking/StakingRewards.sol 299: if ( block.timestamp >= cooldownExpiration ) cooldowns[i] = 0; else cooldowns[i] = cooldownExpiration - block.timestamp;
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 }
File: src/price_feed/PriceAggregator.sol 155: catch (bytes memory) { // price remains 0 } 168: catch (bytes memory) { // price remains 0 }
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>OpenZeppelin's has SafeCast
library that provides functions to safely cast. Recommended to use it instead of casting directly in any case.
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))
File: src/pools/PoolStats.sol 68: uint64(i)
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)
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)
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>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;
File: src/dao/DAO.sol 2: pragma solidity =0.8.22;
File: src/dao/DAOConfig.sol 2: pragma solidity =0.8.22;
File: src/dao/Proposals.sol 2: pragma solidity =0.8.22;
File: src/dao/Parameters.sol 2: pragma solidity =0.8.22;
File: src/launch/Airdrop.sol 2: pragma solidity =0.8.22;
File: src/launch/BootstrapBallot.sol 2: pragma solidity =0.8.22;
File: src/launch/InitialDistribution.sol 2: pragma solidity =0.8.22;
File: src/pools/PoolUtils.sol 1: pragma solidity =0.8.22;
File: src/pools/PoolsConfig.sol 2: pragma solidity =0.8.22;
File: src/pools/PoolStats.sol 2: pragma solidity =0.8.22;
File: src/pools/Pools.sol 2: pragma solidity =0.8.22;
File: src/pools/PoolMath.sol 1: pragma solidity =0.8.22;
File: src/price_feed/CoreChainlinkFeed.sol 2: pragma solidity =0.8.22;
File: src/price_feed/CoreUniswapFeed.sol 2: pragma solidity =0.8.22;
File: src/price_feed/PriceAggregator.sol 2: pragma solidity =0.8.22;
File: src/price_feed/CoreSaltyFeed.sol 2: pragma solidity =0.8.22;
File: src/rewards/RewardsConfig.sol 2: pragma solidity =0.8.22;
File: src/rewards/Emissions.sol 2: pragma solidity =0.8.22;
File: src/rewards/RewardsEmitter.sol 2: pragma solidity =0.8.22;
File: src/rewards/SaltRewards.sol 2: pragma solidity =0.8.22;
File: src/stable/USDS.sol 2: pragma solidity =0.8.22;
File: src/stable/StableConfig.sol 2: pragma solidity =0.8.22;
File: src/stable/CollateralAndLiquidity.sol 2: pragma solidity =0.8.22;
File: src/stable/Liquidizer.sol 2: pragma solidity =0.8.22;
File: src/staking/StakingConfig.sol 2: pragma solidity =0.8.22;
File: src/staking/Liquidity.sol 2: pragma solidity =0.8.22;
File: src/staking/StakingRewards.sol 2: pragma solidity =0.8.22;
File: src/staking/Staking.sol 2: pragma solidity =0.8.22;
File: src/ManagedWallet.sol 2: pragma solidity =0.8.22;
File: src/AccessManager.sol 2: pragma solidity =0.8.22;
File: src/Salt.sol 2: pragma solidity =0.8.22;
File: src/SigningTools.sol 1: pragma solidity =0.8.22;
File: src/Upkeep.sol 2: pragma solidity =0.8.22;
</details>File: src/ExchangeConfig.sol 2: pragma solidity =0.8.22;
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)
</details>File: src/staking/Liquidity.sol 40: modifier ensureNotExpired(uint deadline)
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></details>File: src/pools/PoolMath.sol 187: // uint256 C = r0 * ( r0 * z1 - r1 * z0 ) / ( r1 + z1 ); 188: // uint256 discriminant = B * B - 4 * A * C;
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
_
at the beginning of the mixedCase match for private variables
and unused parameters
.File: src/arbitrage/ArbitrageSearch.sol 17: constructor( IExchangeConfig _exchangeConfig ) {
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 ) {
File: src/dao/Proposals.sol 68: constructor( IStaking _staking, IExchangeConfig _exchangeConfig, IPoolsConfig _poolsConfig, IDAOConfig _daoConfig ) {
File: src/launch/Airdrop.sol 33: constructor( IExchangeConfig _exchangeConfig, IStaking _staking ) {
File: src/launch/BootstrapBallot.sol 31: constructor( IExchangeConfig _exchangeConfig, IAirdrop _airdrop, uint256 ballotDuration ) {
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 ) {
File: src/pools/PoolStats.sol 104: function _calculateArbitrageProfits( bytes32[] memory poolIDs, uint256[] memory _calculatedProfits ) internal view { 25: constructor( IExchangeConfig _exchangeConfig, IPoolsConfig _poolsConfig ) {
File: src/pools/Pools.sol 60: function setContracts( IDAO _dao, ICollateralAndLiquidity _collateralAndLiquidity ) external onlyOwner {
File: src/price_feed/CoreChainlinkFeed.sol 17: constructor( address _CHAINLINK_BTC_USD, address _CHAINLINK_ETH_USD ) {
File: src/price_feed/CoreUniswapFeed.sol 30: constructor( IERC20 _wbtc, IERC20 _weth, IERC20 _usdc, address _UNISWAP_V3_WBTC_WETH, address _UNISWAP_V3_WETH_USDC ) {
File: src/price_feed/PriceAggregator.sol 35: function setInitialFeeds( IPriceFeed _priceFeed1, IPriceFeed _priceFeed2, IPriceFeed _priceFeed3 ) public onlyOwner {
File: src/price_feed/CoreSaltyFeed.sol 20: constructor( IPools _pools, IExchangeConfig _exchangeConfig ) {
File: src/rewards/Emissions.sol 25: constructor( ISaltRewards _saltRewards, IExchangeConfig _exchangeConfig, IRewardsConfig _rewardsConfig ) {
File: src/rewards/RewardsEmitter.sol 36: constructor( IStakingRewards _stakingRewards, IExchangeConfig _exchangeConfig, IPoolsConfig _poolsConfig, IRewardsConfig _rewardsConfig, bool _isForCollateralAndLiquidity ) {
File: src/rewards/SaltRewards.sol 26: constructor( IRewardsEmitter _stakingRewardsEmitter, IRewardsEmitter _liquidityRewardsEmitter, IExchangeConfig _exchangeConfig, IRewardsConfig _rewardsConfig ) {
File: src/stable/USDS.sol 29: function setCollateralAndLiquidity( ICollateralAndLiquidity _collateralAndLiquidity ) external onlyOwner {
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 ) {
File: src/stable/Liquidizer.sol 63: function setContracts( ICollateralAndLiquidity _collateralAndLiquidity, IPools _pools, IDAO _dao) external onlyOwner { 47: constructor( IExchangeConfig _exchangeConfig, IPoolsConfig _poolsConfig ) {
File: src/staking/Liquidity.sol 29: constructor( IPools _pools, IExchangeConfig _exchangeConfig, IPoolsConfig _poolsConfig, IStakingConfig _stakingConfig ) StakingRewards( _exchangeConfig, _poolsConfig, _stakingConfig ) {
File: src/staking/StakingRewards.sol 46: constructor( IExchangeConfig _exchangeConfig, IPoolsConfig _poolsConfig, IStakingConfig _stakingConfig ) {
File: src/ManagedWallet.sol 42: function proposeWallets( address _proposedMainWallet, address _proposedConfirmationWallet ) external { 29: constructor( address _mainWallet, address _confirmationWallet) {
File: src/AccessManager.sol 30: constructor( IDAO _dao ) {
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 ) {
</details>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 ) {
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 {
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 {
</details>File: src/rewards/RewardsEmitter.sol /// @audit function with 31 lines has only 11 comment lines 82: function performUpkeep( uint256 timeSinceLastUpkeep ) external {
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></details>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 );
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 {
File: src/dao/DAO.sol /// @audit - Contract `DAO` is not upgradeable 23: contract DAO is IDAO, Parameters, ReentrancyGuard {
File: src/dao/DAOConfig.sol /// @audit - Contract `DAOConfig` is not upgradeable 9: contract DAOConfig is IDAOConfig, Ownable {
File: src/dao/Proposals.sol /// @audit - Contract `Proposals` is not upgradeable 20: contract Proposals is IProposals, ReentrancyGuard {
File: src/dao/Parameters.sol /// @audit - Contract `Parameters` is not upgradeable 10: abstract contract Parameters {
File: src/launch/Airdrop.sol /// @audit - Contract `Airdrop` is not upgradeable 13: contract Airdrop is IAirdrop, ReentrancyGuard {
File: src/launch/BootstrapBallot.sol /// @audit - Contract `BootstrapBallot` is not upgradeable 12: contract BootstrapBallot is IBootstrapBallot, ReentrancyGuard {
File: src/launch/InitialDistribution.sol /// @audit - Contract `InitialDistribution` is not upgradeable 13: contract InitialDistribution is IInitialDistribution {
File: src/pools/PoolsConfig.sol /// @audit - Contract `PoolsConfig` is not upgradeable 11: contract PoolsConfig is IPoolsConfig, Ownable {
File: src/pools/PoolStats.sol /// @audit - Contract `PoolStats` is not upgradeable 12: abstract contract PoolStats is IPoolStats {
File: src/pools/Pools.sol /// @audit - Contract `Pools` is not upgradeable 22: contract Pools is IPools, ReentrancyGuard, PoolStats, ArbitrageSearch, Ownable {
File: src/price_feed/CoreChainlinkFeed.sol /// @audit - Contract `CoreChainlinkFeed` is not upgradeable 10: contract CoreChainlinkFeed is IPriceFeed {
File: src/price_feed/CoreUniswapFeed.sol /// @audit - Contract `CoreUniswapFeed` is not upgradeable 14: contract CoreUniswapFeed is IPriceFeed {
File: src/price_feed/PriceAggregator.sol /// @audit - Contract `PriceAggregator` is not upgradeable 13: contract PriceAggregator is IPriceAggregator, Ownable {
File: src/price_feed/CoreSaltyFeed.sol /// @audit - Contract `CoreSaltyFeed` is not upgradeable 13: contract CoreSaltyFeed is IPriceFeed {
File: src/rewards/RewardsConfig.sol /// @audit - Contract `RewardsConfig` is not upgradeable 9: contract RewardsConfig is IRewardsConfig, Ownable {
File: src/rewards/Emissions.sol /// @audit - Contract `Emissions` is not upgradeable 14: contract Emissions is IEmissions {
File: src/rewards/RewardsEmitter.sol /// @audit - Contract `RewardsEmitter` is not upgradeable 20: contract RewardsEmitter is IRewardsEmitter, ReentrancyGuard {
File: src/rewards/SaltRewards.sol /// @audit - Contract `SaltRewards` is not upgradeable 15: contract SaltRewards is ISaltRewards {
File: src/stable/USDS.sol /// @audit - Contract `USDS` is not upgradeable 13: contract USDS is ERC20, IUSDS, Ownable {
File: src/stable/StableConfig.sol /// @audit - Contract `StableConfig` is not upgradeable 9: contract StableConfig is IStableConfig, Ownable {
File: src/stable/CollateralAndLiquidity.sol /// @audit - Contract `CollateralAndLiquidity` is not upgradeable 21: contract CollateralAndLiquidity is Liquidity, ICollateralAndLiquidity {
File: src/stable/Liquidizer.sol /// @audit - Contract `Liquidizer` is not upgradeable 21: contract Liquidizer is ILiquidizer, Ownable {
File: src/staking/StakingConfig.sol /// @audit - Contract `StakingConfig` is not upgradeable 9: contract StakingConfig is IStakingConfig, Ownable {
File: src/staking/Liquidity.sol /// @audit - Contract `Liquidity` is not upgradeable 17: abstract contract Liquidity is ILiquidity, StakingRewards {
File: src/staking/StakingRewards.sol /// @audit - Contract `StakingRewards` is not upgradeable 20: abstract contract StakingRewards is IStakingRewards, ReentrancyGuard {
File: src/staking/Staking.sol /// @audit - Contract `Staking` is not upgradeable 14: contract Staking is IStaking, StakingRewards {
File: src/ManagedWallet.sol /// @audit - Contract `ManagedWallet` is not upgradeable 11: contract ManagedWallet is IManagedWallet {
File: src/AccessManager.sol /// @audit - Contract `AccessManager` is not upgradeable 18: contract AccessManager is IAccessManager {
File: src/Salt.sol /// @audit - Contract `Salt` is not upgradeable 6: contract Salt is ISalt, ERC20 {
File: src/Upkeep.sol /// @audit - Contract `Upkeep` is not upgradeable 38: contract Upkeep is IUpkeep, ReentrancyGuard {
</details>File: src/ExchangeConfig.sol /// @audit - Contract `ExchangeConfig` is not upgradeable 13: contract ExchangeConfig is IExchangeConfig, Ownable {
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 {
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 {
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 {
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 {
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 {
File: src/launch/InitialDistribution.sol /// @audit Missing named import for parent contract: `IInitialDistribution` 13: contract InitialDistribution is IInitialDistribution {
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 {
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 {
File: src/price_feed/CoreChainlinkFeed.sol /// @audit Missing named import for parent contract: `IPriceFeed` 10: contract CoreChainlinkFeed is IPriceFeed {
File: src/price_feed/CoreUniswapFeed.sol /// @audit Missing named import for parent contract: `IPriceFeed` 14: contract CoreUniswapFeed is IPriceFeed {
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 {
File: src/price_feed/CoreSaltyFeed.sol /// @audit Missing named import for parent contract: `IPriceFeed` 13: contract CoreSaltyFeed is IPriceFeed {
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 {
File: src/rewards/Emissions.sol /// @audit Missing named import for parent contract: `IEmissions` 14: contract Emissions is IEmissions {
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 {
File: src/rewards/SaltRewards.sol /// @audit Missing named import for parent contract: `ISaltRewards` 15: contract SaltRewards is ISaltRewards {
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 {
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 {
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 {
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 {
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 {
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 {
File: src/ManagedWallet.sol /// @audit Missing named import for parent contract: `IManagedWallet` 11: contract ManagedWallet is IManagedWallet {
File: src/AccessManager.sol /// @audit Missing named import for parent contract: `IAccessManager` 18: contract AccessManager is IAccessManager {
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 {
File: src/Upkeep.sol /// @audit Missing named import for parent contract: `ReentrancyGuard` 38: contract Upkeep is IUpkeep, ReentrancyGuard {
</details>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 {
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 {
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 {
</details>File: src/rewards/RewardsEmitter.sol /// @audit 31 lines (excluding comments) 82: function performUpkeep( uint256 timeSinceLastUpkeep ) external {
bytes
or bytes32
Over abi.encodePacked()
for Single ArgumentsWhen 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>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" );
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>File: src/stable/CollateralAndLiquidity.sol 202: uint256 btcValue = ( amountBTC * btcPrice ) / wbtcTenToTheDecimals; 203: uint256 ethValue = ( amountETH * ethPrice ) / wethTenToTheDecimals;
#0 - c4-judge
2024-02-03T13:14:16Z
Picodes marked the issue as grade-b
🌟 Selected for report: 0xVolcano
Also found by: 0x11singh99, 0xAnah, Beepidibop, JCK, JcFichtner, K42, Kaysoft, Pechenite, Raihan, Rolezn, dharma09, hunter_w3b, lsaudit, n0kto, naman1778, niroh, sivanesh_808, slvDev, unique
20.7932 USDC - $20.79
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.
Issue | Instances | Total Gas Saved | |
---|---|---|---|
[G-01] | x.a = x.a + b always cheaper than x.a += b for Structs | 11 | 778253 |
[G-02] | Consider using delete rather than assigning false to clear values | 3 | 21620 |
[G-03] | Stack variable is only used once | 83 | 262798 |
[G-04] | Optimize Storage with Byte Truncation for Time Related State Variables | 5 | 100000 |
[G-05] | Assigning state variables directly with struct constructors wastes gas | 4 | 79517 |
[G-06] | Using delete on a uint/int variable is cheaper than assigning it to 0 | 7 | 9428 |
[G-07] | Assembly: Check msg.sender using xor and the scratch space | 32 | 672 |
[G-08] | Use revert() to gain maximum gas savings | 139 | 6950 |
[G-09] | Using constants instead of enum can save gas | 1 | 0 |
[G-10] | Optimize Increment and Decrement in loops with unchecked keyword | 21 | 1260 |
[G-11] | Reduce deployment costs by tweaking contracts' metadata | 35 | 371000 |
[G-12] | Consider using solady's FixedPointMathLib | 200 | - |
[G-13] | Consider using OZ EnumerateSet in place of nested mappings | 5 | 5000 |
[G-14] | Optimize require/revert Statements with Assembly | 139 | 41700 |
x.a = x.a + b
always cheaper than x.a += b
for StructsExamples:
// 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:
<details> <summary><i>11 issue instances in 2 files:</i></summary>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%)
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
</details>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)
delete
rather than assigning false
to clear valuesResult:
<details> <summary><i>3 issue instances in 2 files:</i></summary>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%)
File: src/dao/DAO.sol 187: excludedCountries[ ballot.string1 ] = false
</details>File: src/dao/Proposals.sol 143: ballot.ballotIsLive = false 147: _userHasActiveProposal[userThatPostedBallot] = false
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:
<details> <summary><i>83 issue instances in 22 files:</i></summary>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%)
File: src/arbitrage/ArbitrageSearch.sol /// @audit - `profitRightOfMidpoint` variable 86: int256 profitRightOfMidpoint = int256(amountOut) - int256(midpoint)
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))
File: src/launch/InitialDistribution.sol /// @audit - `poolIDs` variable 68: bytes32[] memory poolIDs = poolsConfig.whitelistedPools()
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 )
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 )
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 )
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
File: src/price_feed/CoreChainlinkFeed.sol /// @audit - `answerDelay` variable 43: uint256 answerDelay = block.timestamp - _answerTimestamp
File: src/price_feed/CoreUniswapFeed.sol /// @audit - `tick` variable 59: int24 tick = int24((tickCumulatives[1] - tickCumulatives[0]) / int56(uint56(twapInterval)))
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 ) )
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
File: src/rewards/SaltRewards.sol /// @audit - `amountPerPool` variable 84: uint256 amountPerPool = liquidityBootstrapAmount / poolIDs.length /// @audit - `liquidityRewardsAmount` variable 144: uint256 liquidityRewardsAmount = remainingRewards - stakingRewardsAmount
File: src/stable/StableConfig.sol /// @audit - `remainingRatioAfterReward` variable 52: uint256 remainingRatioAfterReward = minimumCollateralRatioPercent - rewardPercentForCallingLiquidation - 1 /// @audit - `remainingRatioAfterReward` variable 128: uint256 remainingRatioAfterReward = minimumCollateralRatioPercent - 1 - rewardPercentForCallingLiquidation
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]
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 ) )
File: src/AccessManager.sol /// @audit - `messageHash` variable 53: bytes32 messageHash = keccak256(abi.encodePacked(block.chainid, geoVersion, wallet))
File: src/SigningTools.sol /// @audit - `recoveredAddress` variable 26: address recoveredAddress = ecrecover(messageHash, v, r, s)
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>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.
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
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
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
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
</details>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
state
variables directly with struct constructors wastes gasExamples:
// 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:
<details> <summary><i>4 issue instances in 3 files:</i></summary>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%)
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 )
File: src/pools/PoolsConfig.sol 54: underlyingPoolTokens[poolID] = TokenPair(tokenA, tokenB)
</details>File: src/pools/PoolStats.sol 96: _arbitrageIndicies[poolID] = ArbitrageIndicies(poolIndex1, poolIndex2, poolIndex3)
delete
on a uint/int
variable is cheaper than assigning it to 0
Result:
<details> <summary><i>7 issue instances in 6 files:</i></summary>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%)
File: src/pools/PoolStats.sol 54: _arbitrageProfits[ poolIDs[i] ] = 0
File: src/price_feed/CoreChainlinkFeed.sol 48: price = 0
File: src/price_feed/CoreUniswapFeed.sol 54: secondsAgo[1] = 0
File: src/stable/CollateralAndLiquidity.sol 184: usdsBorrowedByUsers[wallet] = 0
File: src/stable/Liquidizer.sol 113: usdsThatShouldBeBurned = 0
</details>File: src/staking/StakingRewards.sol 151: claimableRewards = 0 301: cooldowns[i] = 0
msg.sender
using xor and the scratch spaceSee 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" );
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" );
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" );
File: src/launch/InitialDistribution.sol 52: require( msg.sender == address(bootstrapBallot), "InitialDistribution.distributionApproved can only be called from the BootstrapBallot contract" );
File: src/pools/PoolStats.sol 49: require(msg.sender == address(exchangeConfig.upkeep()), "PoolStats.clearProfitsForPools is only callable from the Upkeep contract" );
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" );
File: src/rewards/Emissions.sol 42: require( msg.sender == address(exchangeConfig.upkeep()), "Emissions.performUpkeep is only callable from the Upkeep contract" );
File: src/rewards/RewardsEmitter.sol 84: require( msg.sender == address(exchangeConfig.upkeep()), "RewardsEmitter.performUpkeep is only callable from the Upkeep contract" );
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" );
File: src/stable/USDS.sol 42: require( msg.sender == address(collateralAndLiquidity), "USDS.mintTo is only callable from the Collateral contract" );
File: src/stable/CollateralAndLiquidity.sol 142: require( wallet != msg.sender, "Cannot liquidate self" );
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" );
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
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" );
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" );
File: src/AccessManager.sol 43: require( msg.sender == address(dao), "AccessManager.excludedCountriedUpdated only callable by the DAO" );
</details>File: src/Upkeep.sol 97: require(msg.sender == address(this), "Only callable from within the same contract");
revert()
to gain maximum gas savingsIf you dont need Error messages, or you want gain maximum gas savings - revert()
is a cheapest way to revert transaction in terms of gas.
<details> <summary><i>139 issue instances in 23 files:</i></summary>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
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" )
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" )
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")
File: src/pools/PoolStats.sol 49: require(msg.sender == address(exchangeConfig.upkeep()), "PoolStats.clearProfitsForPools is only callable from the Upkeep contract" )
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" )
File: src/rewards/Emissions.sol 42: require( msg.sender == address(exchangeConfig.upkeep()), "Emissions.performUpkeep is only callable from the Upkeep contract" )
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" )
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" )
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" )
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" )
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" )
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" )
File: src/SigningTools.sol 13: require( signature.length == 65, "Invalid signature length" )
File: src/Upkeep.sol 97: require(msg.sender == address(this), "Only callable from within the same contract")
</details>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)" )
constants
instead of enum
can save gasEnum
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>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 }
unchecked
keywordUse 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.
File: src/arbitrage/ArbitrageSearch.sol 115: for( uint256 i = 0; i < 8; i++ ) {
File: src/dao/Proposals.sol 423: for( uint256 i = 0; i < _openBallotsForTokenWhitelisting.length(); i++ ) {
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++ ) {
File: src/rewards/RewardsEmitter.sol 60: for( uint256 i = 0; i < addedRewards.length; i++ ) { 116: for( uint256 i = 0; i < poolIDs.length; i++ ) {
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 {
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) {
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
</details>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) {
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?
Options to Reduce Gas:
--no-cbor-metadata
compiler option to exclude metadata, but this might affect contract verification.File: src/arbitrage/ArbitrageSearch.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/dao/DAO.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/dao/DAOConfig.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/dao/Proposals.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/dao/Parameters.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/launch/Airdrop.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/launch/BootstrapBallot.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/launch/InitialDistribution.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/pools/PoolUtils.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/pools/PoolsConfig.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/pools/PoolStats.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/pools/Pools.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/pools/PoolMath.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/price_feed/CoreChainlinkFeed.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/price_feed/CoreUniswapFeed.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/price_feed/PriceAggregator.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/price_feed/CoreSaltyFeed.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/rewards/RewardsConfig.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/rewards/Emissions.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/rewards/RewardsEmitter.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/rewards/SaltRewards.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/stable/USDS.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/stable/StableConfig.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/stable/CollateralAndLiquidity.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/stable/Liquidizer.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/staking/StakingConfig.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/staking/Liquidity.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/staking/StakingRewards.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/staking/Staking.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/ManagedWallet.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/AccessManager.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/Salt.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/SigningTools.sol 1: Consider optimizing the IPFS hash during deployment.
File: src/Upkeep.sol 1: Consider optimizing the IPFS hash during deployment.
</details>File: src/ExchangeConfig.sol 1: Consider optimizing the IPFS hash during deployment.
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 )
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();
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);
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;
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 )
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;
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 );
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;
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];
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 ;
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>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;
File: src/pools/Pools.sol 49: mapping(address=>mapping(IERC20=>uint256)) private _userDeposits;
File: src/staking/StakingRewards.sol 36: mapping(address=>mapping(bytes32=>UserShareInfo)) private _userShareInfo;
</details>File: src/AccessManager.sol 26: mapping(uint256 => mapping(address => bool)) private _walletsWithAccess;
require/revert
Statements with AssemblyUsing 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:
<details> <summary><i>139 issue instances in 23 files:</i></summary>/// 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; }
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" );
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" );
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");
File: src/pools/PoolStats.sol 49: require(msg.sender == address(exchangeConfig.upkeep()), "PoolStats.clearProfitsForPools is only callable from the Upkeep contract" );
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" );
File: src/rewards/Emissions.sol 42: require( msg.sender == address(exchangeConfig.upkeep()), "Emissions.performUpkeep is only callable from the Upkeep contract" );
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" );
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" );
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" );
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" );
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" );
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" );
File: src/SigningTools.sol 13: require( signature.length == 65, "Invalid signature length" );
File: src/Upkeep.sol 97: require(msg.sender == address(this), "Only callable from within the same contract");
</details>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)" );
#0 - c4-judge
2024-02-03T14:22:14Z
Picodes marked the issue as grade-b